Merge "/bin/wifi:  /bin/serial call may fail."
diff --git a/Makefile b/Makefile
index f433d89..0d5a5da 100644
--- a/Makefile
+++ b/Makefile
@@ -84,6 +84,10 @@
 DIRS+=presterastats
 endif
 
+ifeq ($(BUILD_LEDPATTERN),y)
+DIRS+=ledpattern
+endif
+
 PREFIX=/usr
 BINDIR=$(DESTDIR)$(PREFIX)/bin
 LIBDIR=$(DESTDIR)$(PREFIX)/lib
diff --git a/cmds/Makefile b/cmds/Makefile
index 069e6ff..38557fa 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -227,7 +227,7 @@
 host-wifi_files_test: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
 dhcpvendortax: dhcpvendortax.o dhcpvendorlookup.o
 dhcpvendorlookup.c: dhcpvendorlookup.gperf
-	$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class \
+	$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class --delimiters="|" \
 		--includes --output-file=dhcpvendorlookup.c dhcpvendorlookup.gperf
 dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
 host-dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
diff --git a/cmds/dhcpvendorlookup.gperf b/cmds/dhcpvendorlookup.gperf
index b4b0ee1..884625a 100644
--- a/cmds/dhcpvendorlookup.gperf
+++ b/cmds/dhcpvendorlookup.gperf
@@ -18,50 +18,51 @@
 %}
 struct string_match {
   char *vendor_class;
+  char *genus;
   char *species;
 };
 %%
-6328-2Re, "InnoMedia VoIP adapter"
-AEROHIVE, "Aerohive Wifi AP"
-AirStation Series BUFFALO INC., "Buffalo Wifi AP"
-ArubaAP, "Aruba Wifi AP"
-ArubaInstantAP, "Aruba Wifi AP"
-ccp.avaya.com, "Avaya IP Phone"
-Cisco 802.11n AP Bridge, "Cisco Wifi AP"
-Dell Network Printer, "Dell Printer"
-DUNEHD, "Dune media player"
-ecobee1, "ecobee thermostat"
-HD409N, "ZaapTV"
-Hewlett-Packard JetDirect, "HP Printer"
-Hewlett-Packard LaserJet, "HP LaserJet"
-Hewlett-Packard OfficeJet, "HP OfficeJet"
-iDRAC, "Dell Remote Access Controller"
-ipphone.mitel.com, "Mitel IP Phone"
-IP2061, "Icon IP Phone"
-IWATSUIP, "Icon IP Phone"
-MC361, "Oki Printer"
-MC362, "Oki Printer"
-MERAKI, "Meraki Wifi AP"
-MicroChip Network Stack, "Microchip board"
-Motorola_AP, "Motorola Wifi AP"
-OptiIpPhone, "Siemens IP Phone"
-PS3, "Sony Playstation 3"
-PS4, "Sony Playstation 4"
-PS Vita, "Sony Playstation Vita"
-PS Vita TV, "Sony Playstation Vita"
-Ruckus CPE, "Ruckus Wifi AP"
-SAMSUNG Network Printer, "Samsung Printer"
-SEC_ITP, "Samsung IP Phone"
-ShoreTel IP Phone, "ShoreTel IP Phone"
-SIP-T38G, "Yealink IP Phone"
-SSG5-Serial-WLAN, "Juniper Gateway"
-TOSHIBA IPedge, "Toshiba VoIP adapter"
-ubnt, "Ubiquiti AP"
-VIZIO VIA, "Vizio TV"
-Withings00, "Withings Scale"
-XBOX 1.0, "Xbox"
-Xbox 360, "Xbox 360"
-Xerox Phaser, "Xerox Printer"
-XEROX Network Printer, "Xerox Printer"
-yealink, "Yealink IP Phone"
+6328-2Re| "InnoMedia VoIP adapter", "MTA6328-2Re"
+AEROHIVE| "Aerohive Wifi AP", "Aerohive Wifi AP"
+AirStation Series BUFFALO INC.| "Buffalo Wifi AP", "Buffalo AirStation AP"
+ArubaAP| "Aruba Wifi AP", "Aruba Wifi AP"
+ArubaInstantAP| "Aruba Wifi AP", "Aruba Wifi AP"
+ccp.avaya.com| "Avaya IP Phone", "Avaya IP Phone"
+Cisco 802.11n AP Bridge| "Cisco Wifi AP", "Cisco Wifi AP"
+Dell Network Printer| "Dell Printer", "Dell Printer"
+DUNEHD| "Dune media player", "DuneHD media player"
+ecobee1| "ecobee thermostat", "ecobee thermostat"
+HD409N| "ZaapTV", "Zaap HD409N"
+Hewlett-Packard JetDirect| "HP Printer", "HP Printer"
+Hewlett-Packard LaserJet| "HP Printer", "HP LaserJet"
+Hewlett-Packard OfficeJet| "HP Printer", "HP OfficeJet"
+iDRAC| "Dell Remote Access Controller", "DRAC"
+ipphone.mitel.com| "Mitel IP Phone", "Mitel IP Phone"
+IP2061| "Icon IP Phone", "IP2061"
+IWATSUIP| "Icon IP Phone", "Icon IP Phone"
+MC361| "Oki Printer", "Oki MC361"
+MC362| "Oki Printer", "Oki MC362"
+MERAKI| "Meraki Wifi AP", "Meraki Wifi AP"
+MicroChip Network Stack| "Microchip board", "Microchip board"
+Motorola_AP| "Motorola Wifi AP", "Motorola Wifi AP"
+OptiIpPhone| "Siemens IP Phone", "Siemens IP Phone"
+PS3| "Sony Playstation", "PS3"
+PS4| "Sony Playstation", "PS4"
+PS Vita| "Sony Playstation", "PS-Vita"
+PS Vita TV| "Sony Playstation", "PS-Vita"
+Ruckus CPE| "Ruckus Wifi AP", "Ruckus Wifi AP"
+SAMSUNG Network Printer| "Samsung Printer", "Samsung Printer"
+SEC_ITP| "Samsung IP Phone", "Samsung IP Phone"
+ShoreTel IP Phone| "ShoreTel IP Phone", "ShoreTel IP Phone"
+SIP-T38G| "Yealink IP Phone", "SIP-T38G"
+SSG5-Serial-WLAN| "Juniper Gateway", "SSG5"
+TOSHIBA IPedge| "Toshiba VoIP adapter", "Toshiba VoIP adapter"
+ubnt| "Ubiquiti Wifi AP", "Ubiquiti Wifi AP"
+VIZIO VIA| "Vizio Smart TV", "Vizio Smart TV"
+Withings00| "Withings Scale", "Withings Scale"
+XBOX 1.0| "Xbox", "Xbox"
+Xbox 360| "Xbox", "Xbox 360"
+Xerox Phaser| "Xerox Printer", "Xerox Printer"
+XEROX Network Printer| "Xerox Printer", "Xerox Printer"
+yealink| "Yealink IP Phone", "Yealink IP Phone"
 %%
diff --git a/cmds/dhcpvendortax.c b/cmds/dhcpvendortax.c
index ee0ca24..7c9adac 100644
--- a/cmds/dhcpvendortax.c
+++ b/cmds/dhcpvendortax.c
@@ -25,6 +25,7 @@
 
 struct string_match {
   char *vendor_class;
+  char *genus;
   char *species;
 };
 
@@ -33,39 +34,45 @@
     unsigned int len);
 
 
-struct string_match substring_matches[] = {
+struct regex_match {
+  char *regex;
+  char *genus;
+};
+
+
+struct regex_match regex_matches[] = {
   /*
    * Examples:
    *   AastraIPPhone55i
    *   AastraIPPhone57iCT
    *   AastraIPPhone6737i
    */
-  {"AastraIPPhone", "Aastra IP Phone"},
+  {"AastraIPPhone([[:alnum:]]+)", "Aastra IP Phone"},
 
   /* Examples:
    * AXIS,Network Camera,M3006,5.40.13
    * AXIS,Network Camera,P3346,5.20.1
    * AXIS,Thermal Network Camera,Q1931-E,5.55.4.1
    */
-  {"AXIS,Network Camera", "AXIS Network Camera"},
-  {"AXIS,Thermal Network Camera", "AXIS Network Camera"},
+  {"AXIS,Network Camera,([^,]+)", "AXIS Network Camera"},
+  {"AXIS,Thermal Network Camera,([^,]+)", "AXIS Network Camera"},
 
   /* Examples:
    * Canon MF620C Series
    */
-  {"Canon MF", "Canon Printer"},
+  {"Canon (MF[[:alnum:]]+)", "Canon Printer"},
 
   /* Examples:
    * Cisco AP c1200
    * Cisco AP c1240
    */
-  {"Cisco AP", "Cisco Wifi AP"},
+  {"Cisco AP ([[:alnum:]]+)", "Cisco Wifi AP"},
 
   /* Examples:
    *   Cisco Systems, Inc. IP Phone CP-7961G
    *   Cisco Systems, Inc. IP Phone CP-8861
    */
-  {"Cisco Systems, Inc. IP Phone", "Cisco IP Phone"},
+  {"Cisco Systems, Inc. IP Phone ([[:alnum:]-]+)", "Cisco IP Phone"},
 
   /* Examples:
    *   Cisco SPA504G
@@ -73,24 +80,31 @@
    *   CISCO SPA112
    *   ATA186-H6.0|V3.2.0|B041111A
    */
-  {"Cisco SPA", "Cisco IP Phone"},
-  {"CISCO SPA", "Cisco IP Phone"},
-  {"ATA186", "Cisco IP Phone"},
+  {"Cisco (SPA[[:alnum:]]+)", "Cisco IP Phone"},
+  {"CISCO (SPA[[:alnum:]]+)", "Cisco IP Phone"},
+  {"(ATA186)", "Cisco IP Phone"},
 
   /* Examples:
    * CPQRIB3
    */
-  {"CPQRIB", "Compaq Remote Insight"},
+  {"(CPQRIB[[:digit:]]+)", "Compaq Remote Insight"},
 
   /* Examples:
    * Dell Color MFP E525w
    */
-  {"Dell Color MFP", "Dell Printer"},
+  {"Dell Color MFP ([[:alnum:]]+)", "Dell Printer"},
+
+  /* Examples:
+   * Dell C1760nw Color Printer
+   * Dell C2660dn Color Laser
+   * Dell 2155cn Color MFP
+   */
+  {"^Dell ([[:alnum:]]+) Color (Printer|Laser|MFP)", "Dell Printer"},
 
   /* Examples:
    * digium_D40_1_4_2_0_63880
    */
-  {"digium", "Digium IP Phone"},
+  {"digium_([^_]+)", "Digium IP Phone"},
 
   /* Examples:
    * FortiAP-FP321C-AC-Discovery
@@ -98,8 +112,8 @@
    * FortiAP-FP321C
    * FortiWiFi-60D-POE
    */
-  {"FortiAP", "Fortinet Wifi AP"},
-  {"FortiWiFi", "Fortinet Wifi AP"},
+  {"FortiAP-([^-]+)", "Fortinet Wifi AP"},
+  {"FortiWiFi-([[:alnum:]-]+)", "Fortinet Wifi AP"},
 
   /* Examples:
    * Grandstream GXP1405 dslforum.org
@@ -107,41 +121,48 @@
    * Grandstream GXV3275 dslforum.org
    * Grandstream HT702 dslforum.org
    */
-  {"Grandstream GXP", "Grandstream IP Phone"},
-  {"Grandstream GXV", "Grandstream IP Phone"},
-  {"Grandstream HT", "Grandstream VoIP adapter"},
+  {"Grandstream (GX[[:alnum:]]+)", "Grandstream IP Phone"},
+  {"Grandstream (HT[[:alnum:]]+)", "Grandstream VoIP Adapter"},
+
+  /* Grandstream Voice over IP adapters. Examples:
+   * HT500 dslforum.org
+   * HT7XX dslforum.org
+   * DP7XX dslforum.org
+   */
+  {"^(HT[[:alnum:]]+) dslforum.org", "Grandstream VoIP Adapter"},
+  {"^(DP[[:alnum:]]+) dslforum.org", "Grandstream IP Phone"},
 
   /* Examples:
    * iPECS IP Edge 5000i-24G
    */
-  {"iPECS IP Edge", "iPECS IP PHONE"},
+  {"iPECS IP Edge ([[:alnum:]-]+)", "iPECS IP Phone"},
 
   /* Examples:
    * Juniper-ex2200-c-12p-2g
    */
-  {"Juniper-ex", "Juniper router"},
+  {"Juniper-(ex[^-]+)", "Juniper Router"},
 
   /* Examples:
    *   LINKSYS SPA-922
    *   LINKSYS SPA-942
    */
-  {"LINKSYS SPA", "Linksys IP Phone"},
+  {"LINKSYS (SPA-[[:alnum:]]+)", "Linksys IP Phone"},
 
   /* Examples:
    * MotorolaAP.AP7131
    */
-  {"MotorolaAP", "Motorola Wifi AP"},
+  {"MotorolaAP.([[:alnum:]]+)", "Motorola Wifi AP"},
 
   /* Examples:
    * NECDT700
    */
-  {"NECDT", "NEC IP Phone"},
+  {"(NECDT[[:alnum:]]+)", "NEC IP Phone"},
 
   /* Examples:
    *   6=qPolycomSoundPointIP-SPIP_1234567-12345-001
    *   6=tPolycomSoundStationIP-SSIP_12345678-12345-001
    */
-  {"PolycomSoundPointIP", "Polycom IP Phone"},
+  {".*PolycomSoundPointIP-([^-]+)", "Polycom IP Phone"},
 
   /* Examples:
    *   Polycom-SPIP335
@@ -151,26 +172,26 @@
    *   Polycom-VVX500
    *   Polycom-VVX600
    */
-  {"Polycom-SPIP", "Polycom IP Phone"},
-  {"Polycom-SSIP", "Polycom IP Phone"},
-  {"Polycom-VVX", "Polycom IP Phone"},
+  {"Polycom-(SPIP[[:alnum:]]+)", "Polycom IP Phone"},
+  {"Polycom-(SSIP[[:alnum:]]+)", "Polycom IP Phone"},
+  {"Polycom-(VVX[[:alnum:]]+)", "Polycom IP Phone"},
 
   /* Examples:
-   * Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3
    * Rabbit-TCPIP:Z-World:DHCP-Test:1.2.0
+   * Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3
    */
-  {"Rabbit-TCPIP", "Rabbit Microcontroller"},
-  {"Rabbit2000-TCPIP", "Rabbit Microcontroller"},
+  {"(Rabbit)-TCPIP", "Rabbit Microcontroller"},
+  {"(Rabbit2000)-TCPIP", "Rabbit Microcontroller"},
 
   /* Examples:
    * ReadyNet_WRT500
    */
-  {"ReadyNet_WRT", "ReadyNet Wifi AP"},
+  {"ReadyNet_(WRT[[:alnum:]]+)", "ReadyNet Wifi AP"},
 
   /* Examples:
    * SAMSUNG SCX-6x45
    */
-  {"SAMSUNG SCX", "Samsung Network MFP"},
+  {"SAMSUNG (SCX-[[:alnum:]]+)", "Samsung Network MFP"},
 
   /* Examples:
    * SF200-24P
@@ -182,29 +203,30 @@
    * SG200-50P
    * SG300-10
    */
-  {"SF200", "Cisco Managed Switch"},
-  {"SG 200", "Cisco Managed Switch"},
-  {"SG200", "Cisco Managed Switch"},
-  {"SG 300", "Cisco Managed Switch"},
-  {"SG300", "Cisco Managed Switch"},
+  {"(SF200-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG 200-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG200-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG 300-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG300-[[:alnum:]]+)", "Cisco Managed Switch"},
 
   /* Examples:
    * snom-m3-SIP/02.11//18-Aug-10 15:36
    * snom320
    * snom710
    */
-  {"snom", "Snom IP Phone"},
+  {"(snom[^/]+)", "Snom IP Phone"},
 
   /* Examples:
    * telsey-stb-f8
    */
-  {"telsey-stb", "Telsey Media Player"},
+  {"telsey-stb-([[:alnum:]]+)", "Telsey Media Player"},
 
   {NULL, NULL}
 };
 
 
-/* Copy a string with no funny schtuff allowed; only alphanumerics + space. */
+/* Copy a string with no funny schtuff allowed; only alphanumerics + space
+ * plus a few characters considered safe. */
 static void no_mischief_strncpy(char *dst, const char *src, size_t n)
 {
   size_t i;
@@ -213,10 +235,11 @@
     int is_lower = (s >= 'a' && s <= 'z');
     int is_upper = (s >= 'A' && s <= 'Z');
     int is_digit = (s >= '0' && s <= '9');
+    int is_safe = (s == '_' || s == '-' || s == '.');
     if (s == '\0') {
       dst[i] = '\0';
       break;
-    } else if (is_lower || is_upper || is_digit) {
+    } else if (is_lower || is_upper || is_digit || is_safe) {
       dst[i] = s;
     } else if (s == ' ' || s == '\t') {
       dst[i] = ' ';
@@ -237,8 +260,9 @@
  *   Mfg=Hewlett Packard;Typ=Printer;Mod=HP LaserJet 400 M401n;Ser=ABCDE01234;
  *   mfg=Xerox;typ=MFP;mod=WorkCentre 3220;ser=ABC012345;loc=
  */
-int check_for_printer(const char *vendor_class, char *species,
-    size_t species_len)
+int check_for_printer(const char *vendor_class,
+    char *genus, size_t genus_len,
+    char *species, size_t species_len)
 {
   regex_t r_vendor, r_type, r_model;
   regmatch_t match[2];
@@ -270,9 +294,10 @@
   if (vendor && type) {
     char buf[128];
     snprintf(buf, sizeof(buf), "%s %s", vendor, type);
-    no_mischief_strncpy(species, buf, species_len);
+    no_mischief_strncpy(genus, buf, genus_len);
     rc = 0;
-  } else if (model) {
+  }
+  if (model) {
     no_mischief_strncpy(species, model, species_len);
     rc = 0;
   }
@@ -280,92 +305,49 @@
   if (vendor) free(vendor);
   if (type) free(type);
   if (model) free(model);
+  regfree(&r_vendor);
+  regfree(&r_type);
+  regfree(&r_model);
 
   return(rc);
 }
 
-/*
- * Check a few patterns from common vendors with lots of model
- * numbers.
- */
-int check_specials(const char *vendor_class, char *species,
-    size_t species_len)
-{
-  regex_t r_dellprinter, r_grandstream;
 
-  /*
-   * Dell printers. Examples:
-   * Dell C1760nw Color Printer
-   * Dell C2660dn Color Laser
-   * Dell 2155cn Color MFP
-   */
-  if (regcomp(&r_dellprinter, "^Dell \\S+ Color (Printer|Laser|MFP)",
-        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
-    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
-    exit(1);
-  }
-  if (regexec(&r_dellprinter, vendor_class, 0, NULL, 0) == 0) {
-    snprintf(species, species_len, "Dell Printer");
-    return(0);
-  }
-
-  /*
-   * Grandstream Voice over IP adapters. Examples:
-   * HT500 dslforum.org
-   * HT7XX dslforum.org
-   */
-  if (regcomp(&r_grandstream, "^HT.* dslforum.org",
-        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
-    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
-    exit(1);
-  }
-  if (regexec(&r_grandstream, vendor_class, 0, NULL, 0) == 0) {
-    snprintf(species, species_len, "Grandstream VoIP adapter");
-    return(0);
-  }
-
-  /*
-   * Grandstream IP phones. Examples:
-   * DP7XX dslforum.org
-   */
-  if (regcomp(&r_grandstream, "^DP.* dslforum.org",
-        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
-    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
-    exit(1);
-  }
-  if (regexec(&r_grandstream, vendor_class, 0, NULL, 0) == 0) {
-    snprintf(species, species_len, "Grandstream IP phone");
-    return(0);
-  }
-
-  return(1);
-}
-
-
-int lookup_vc(const char *vendor_class, char *species, size_t species_len)
+int lookup_vc(const char *vendor_class,
+    char *genus, size_t genus_len,
+    char *species, size_t species_len)
 {
   const struct string_match *p;
+  const struct regex_match *r;
   int slen = strlen(vendor_class);
 
   if ((p = exact_match(vendor_class, slen)) != NULL) {
+    no_mischief_strncpy(genus, p->genus, genus_len);
     no_mischief_strncpy(species, p->species, species_len);
     return(0);
   }
 
-  p = &substring_matches[0];
-  while (p->vendor_class != NULL) {
-    if (strstr(vendor_class, p->vendor_class) != NULL) {
-      no_mischief_strncpy(species, p->species, species_len);
+  for (r = &regex_matches[0]; r->regex != NULL; ++r) {
+    regex_t rx;
+    regmatch_t match[2];
+    if (regcomp(&rx, r->regex, REG_EXTENDED | REG_ICASE)) {
+      fprintf(stderr, "%s: regcomp failed!\n", r->regex);
+      exit(1);
+    }
+    if (regexec(&rx, vendor_class, 2, match, 0) == 0) {
+      int len = match[1].rm_eo - match[1].rm_so;
+      char *model = strndup(vendor_class + match[1].rm_so, len);
+      no_mischief_strncpy(species, model, species_len);
+      free(model);
+
+      snprintf(genus, genus_len, "%s", r->genus);
       return(0);
     }
-    p++;
+    regfree(&rx);
   }
 
-  if (check_for_printer(vendor_class, species, species_len) == 0) {
-    return(0);
-  }
-
-  if (check_specials(vendor_class, species, species_len) == 0) {
+  if (check_for_printer(vendor_class,
+        genus, genus_len, species, species_len) == 0) {
     return(0);
   }
 
@@ -390,6 +372,7 @@
   int c;
   const char *label = NULL;
   const char *vendor = NULL;
+  char genus[80];
   char species[80];
 
   setlinebuf(stdout);
@@ -410,9 +393,10 @@
   if (optind < argc || vendor == NULL || label == NULL)
     usage(argv[0]);
 
+  memset(genus, 0, sizeof(genus));
   memset(species, 0, sizeof(species));
-  if (lookup_vc(vendor, species, sizeof(species)) == 0) {
-    printf("dhcpv %s %s\n", label, species);
+  if (lookup_vc(vendor, genus, sizeof(genus), species, sizeof(species)) == 0) {
+    printf("dhcpv %s %s;%s\n", label, genus, species);
   }
   exit(0);
 }
diff --git a/cmds/host-test-ssdptax.sh b/cmds/host-test-ssdptax.sh
index 3b12fdf..ebb3ae7 100755
--- a/cmds/host-test-ssdptax.sh
+++ b/cmds/host-test-ssdptax.sh
@@ -5,12 +5,21 @@
 . ./wvtest/wvtest.sh
 
 SSDP=./host-ssdptax
-
 FIFO="/tmp/ssdptax.test.$$"
-python ./ssdptax-test-server.py "$FIFO" &
-sleep 0.5
 
 WVSTART "ssdptax test"
-WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax"
 
+python ./ssdptax-test-server.py "$FIFO" 1 &
+sleep 0.5
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax"
+rm "$FIFO"
+
+python ./ssdptax-test-server.py "$FIFO" 2 &
+sleep 0.5
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 REDACTED;server type"
+rm "$FIFO"
+
+python ./ssdptax-test-server.py "$FIFO" 3 &
+sleep 0.5
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Unknown;server type"
 rm "$FIFO"
diff --git a/cmds/hostnamelookup.gperf b/cmds/hostnamelookup.gperf
index bfae6fe..5c3526f 100644
--- a/cmds/hostnamelookup.gperf
+++ b/cmds/hostnamelookup.gperf
@@ -75,10 +75,16 @@
 NP-1P| "Roku", "Roku LT 2700"
 NP-1X| "Roku", "Roku 1 2710"
 NP-2L| "Roku", "Roku Streaming Stick 3500"
+NP-2N| "Roku TV", "Roku TV"
 NP-41| "Roku", "Roku 3 4200"
+NP-4A| "Roku", "Roku 2 4210X"
 NP-4E| "Roku", "Roku 3 4230RW"
+NP-5F| "Roku", "Roku 2 4210X"
+NP-5G| "Roku", "Roku 3 4230X"
+NP-5S| "Roku", "Roku Streaming Stick 3600"
 NP-5Y| "Roku", "Roku 2 4210"
 NP-63| "Roku", "Roku 3 4230"
+NP-YW| "Roku TV", "Roku TV"
 NP-YY| "Roku", "Roku 4 4400"
 OBi200%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi200"
 OBi202%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi202"
@@ -97,6 +103,7 @@
 Slingbox-500%1,3,6,12,15,28,42| "Slingbox", "Slingbox 500"
 Slingbox500%1,3,6,12,15,28,42| "Slingbox", "Slingbox 500"
 Slingbox-M1%1,3,6,12,15,28,40,41,42,119| "Slingbox", "Slingbox M1"
+SmartThings%1,3,6,12| "Samsung SmartThings", "SmartThings Hub"
 SPA112%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA112"
 SPA-112%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA112"
 SPA122%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA122"
diff --git a/cmds/ssdptax-test-server.py b/cmds/ssdptax-test-server.py
index c0346cb..54831d4 100644
--- a/cmds/ssdptax-test-server.py
+++ b/cmds/ssdptax-test-server.py
@@ -8,18 +8,39 @@
 import sys
 
 
+text_device_xml = """<root>
+  <specVersion><major>1</major><minor>0</minor></specVersion>
+  <device><friendlyName>Test Device</friendlyName>
+  <manufacturer>Google Fiber</manufacturer>
+  <modelDescription>Unit Test</modelDescription>
+  <modelName>ssdptax</modelName>
+</device></root>"""
+
+
+email_address_xml = """<root>
+  <specVersion><major>1</major><minor>0</minor></specVersion>
+  <device><friendlyName>FOOBAR: foo@example.com:</friendlyName>
+  <manufacturer>Google Fiber</manufacturer>
+  <modelDescription>Unit Test</modelDescription>
+  <modelName>ssdptax</modelName>
+</device></root>"""
+
+
+no_friendlyname_xml = """<root>
+  <specVersion><major>1</major><minor>0</minor></specVersion>
+  <device></device></root>"""
+
+
+xml = ['']
+
+
 class XmlHandler(BaseHTTPServer.BaseHTTPRequestHandler):
   def do_GET(self):
     self.send_response(200)
     self.send_header('Content-type','text/xml')
     self.end_headers()
-    self.wfile.write("""<root>
-        <specVersion><major>1</major><minor>0</minor></specVersion>
-        <device><friendlyName>Test Device</friendlyName>
-        <manufacturer>Google Fiber</manufacturer>
-        <modelDescription>Unit Test</modelDescription>
-        <modelName>ssdptax</modelName>
-    </device></root>""")
+    self.wfile.write(xml[0])
+
 
 def main():
   un = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -27,6 +48,14 @@
   un.listen(1)
   conn, _ = un.accept()
 
+  testnum = int(sys.argv[2])
+  if testnum == 1:
+    xml[0] = text_device_xml
+  if testnum == 2:
+    xml[0] = email_address_xml
+  if testnum == 3:
+    xml[0] = no_friendlyname_xml
+
   s = BaseHTTPServer.HTTPServer(("", 0), XmlHandler)
   sn = s.socket.getsockname()
   port = sn[1]
diff --git a/cmds/ssdptax.cc b/cmds/ssdptax.cc
index 2a991cb..2a06c7a 100644
--- a/cmds/ssdptax.cc
+++ b/cmds/ssdptax.cc
@@ -31,6 +31,7 @@
 #include <curl/curl.h>
 #include <getopt.h>
 #include <netinet/in.h>
+#include <regex.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -172,6 +173,27 @@
 
 
 /*
+ * Returns true if the friendlyName appears to include an email address.
+ */
+bool contains_email_address(const std::string &friendlyName)
+{
+  regex_t r_email;
+  int rc;
+
+  if (regcomp(&r_email, ".+@[a-z0-9.-]+\\.[a-z0-9.-]+",
+        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
+    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
+    exit(1);
+  }
+
+  rc = regexec(&r_email, friendlyName.c_str(), 0, NULL, 0);
+  regfree(&r_email);
+
+  return (rc == 0);
+}
+
+
+/*
  * Combine the manufacturer and model. If the manufacturer name
  * is already present in the model string, don't duplicate it.
  */
@@ -206,7 +228,9 @@
   }
 
   mac = get_l2addr_for_ip(info->ipaddr);
-  if (info->friendlyName.length() > 0) {
+  if (contains_email_address(info->friendlyName)) {
+    result = "ssdp " + mac + " REDACTED;" + info->srv_type;
+  } else if (info->friendlyName.length() > 0) {
     result = "ssdp " + mac + " " + info->friendlyName + ";" +
       unfriendly_name(info->manufacturer, info->model);
   } else {
diff --git a/cmds/test-dhcpvendortax.sh b/cmds/test-dhcpvendortax.sh
index 8f8d474..5704ed2 100755
--- a/cmds/test-dhcpvendortax.sh
+++ b/cmds/test-dhcpvendortax.sh
@@ -8,48 +8,101 @@
 
 # Check regex matches
 WVPASS $TAX -l label -v "AastraIPPhone55i" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Aastra IP Phone"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Aastra IP Phone;55i"
+WVPASS $TAX -l label -v "AXIS,Network Camera,M3006,5.40.13" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label AXIS Network Camera;M3006"
+WVPASS $TAX -l label -v "AXIS,Thermal Network Camera,Q1931-E,5.55.4.1" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label AXIS Network Camera;Q1931-E"
+WVPASS $TAX -l label -v "Canon MF620C Series" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Canon Printer;MF620C"
+WVPASS $TAX -l label -v "Cisco AP c1240" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Wifi AP;c1240"
+WVPASS $TAX -l label -v "Cisco Systems, Inc. IP Phone CP-7961G" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco IP Phone;CP-7961G"
+WVPASS $TAX -l label -v "Cisco SPA525G2" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco IP Phone;SPA525G2"
+WVPASS $TAX -l label -v "ATA186-H6.0|V3.2.0|B041111A" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco IP Phone;ATA186"
+WVPASS $TAX -l label -v "CPQRIB3" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Compaq Remote Insight;CPQRIB3"
+WVPASS $TAX -l label -v "Dell Color MFP E525w" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;E525w"
+WVPASS $TAX -l label -v "Dell C1760nw Color Printer" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;C1760nw"
+WVPASS $TAX -l label -v "Dell C2660dn Color Laser" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;C2660dn"
+WVPASS $TAX -l label -v "Dell 2155cn Color MFP" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;2155cn"
+WVPASS $TAX -l label -v "FortiAP-FP321C" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Fortinet Wifi AP;FP321C"
+WVPASS $TAX -l label -v "FortiWiFi-60D-POE" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Fortinet Wifi AP;60D-POE"
+WVPASS $TAX -l label -v "Grandstream GXP1405 dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream IP Phone;GXP1405"
+WVPASS $TAX -l label -v "Grandstream HT702 dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP Adapter;HT702"
+WVPASS $TAX -l label -v "HT500 dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP Adapter;HT500"
+WVPASS $TAX -l label -v "DP7XX dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream IP Phone;DP7XX"
+WVPASS $TAX -l label -v "iPECS IP Edge 5000i-24G" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label iPECS IP Phone;5000i-24G"
+WVPASS $TAX -l label -v "Juniper-ex2200-c-12p-2g" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Juniper Router;ex2200"
+WVPASS $TAX -l label -v "LINKSYS SPA-942" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Linksys IP Phone;SPA-942"
+WVPASS $TAX -l label -v "MotorolaAP.AP7131" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Motorola Wifi AP;AP7131"
+WVPASS $TAX -l label -v "NECDT700" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label NEC IP Phone;NECDT700"
 WVPASS $TAX -l label -v "6=qPolycomSoundPointIP-SPIP_1234567-12345-001" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone;SPIP_1234567"
 WVPASS $TAX -l label -v "Polycom-VVX310" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone;VVX310"
+WVPASS $TAX -l label -v "Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Rabbit Microcontroller;Rabbit2000"
+WVPASS $TAX -l label -v "Rabbit-TCPIP:Z-World:DHCP-Test:1.2.0" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Rabbit Microcontroller;Rabbit"
+WVPASS $TAX -l label -v "ReadyNet_WRT500" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label ReadyNet Wifi AP;WRT500"
+WVPASS $TAX -l label -v "SAMSUNG SCX-6x45" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Samsung Network MFP;SCX-6x45"
+WVPASS $TAX -l label -v "SF200-24P" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SF200-24P"
+WVPASS $TAX -l label -v "SG 200-08" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SG 200-08"
+WVPASS $TAX -l label -v "SG200-26" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SG200-26"
+WVPASS $TAX -l label -v "SG300-10" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SG300-10"
+WVPASS $TAX -l label -v "snom-m3-SIP/02.11//18-Aug-10 15:36" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Snom IP Phone;snom-m3-SIP"
+WVPASS $TAX -l label -v "snom320" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Snom IP Phone;snom320"
+WVPASS $TAX -l label -v "telsey-stb-f8" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Telsey Media Player;f8"
 
 # Check exact matches
 WVPASS $TAX -l label -v "Dell Network Printer" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;Dell Printer"
 WVPASS $TAX -l label -v "Xbox 360" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xbox 360"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xbox;Xbox 360"
 
 # Check model/type/manufacturer handling
 WVPASS $TAX -l label -v "Mfg=DELL;Typ=Printer;Mod=Dell 2330dn Laser Printer;Ser=0123AB5;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer"
-WVPASS $TAX -l label -v "Mfg=DELL;Mod=Dell 2330dn Laser Printer;Ser=0123AB5;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell 2330dn Laser Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer;Dell 2330dn Laser Printer"
 
 # Check case sensitivity
 WVPASS $TAX -l label -v "mFG=DELL;tYP=Printer;mOD=Dell 2330dn Laser Printer;Ser=0123AB5;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer;Dell 2330dn Laser Printer"
 
 # Check some other printer vendor formats
 WVPASS $TAX -l label -v "Mfg=FujiXerox;Typ=AIO;Mod=WorkCentre 6027;Ser=P1A234567" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label FujiXerox AIO"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label FujiXerox AIO;WorkCentre 6027"
 WVPASS $TAX -l label -v "Mfg=Hewlett Packard;Typ=Printer;Mod=HP LaserJet 400 M401n;Ser=ABCDE01234;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Hewlett Packard Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Hewlett Packard Printer;HP LaserJet 400 M401n"
 WVPASS $TAX -l label -v "mfg=Xerox;typ=MFP;mod=WorkCentre 3220;ser=ABC012345;loc=" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xerox MFP"
-
-# Check specials
-WVPASS $TAX -l label -v "Dell 2155cn Color MFP" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
-WVPASS $TAX -l label -v "Dell C1760nw Color Printer" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
-WVPASS $TAX -l label -v "Dell C2660dn Color Laser" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
-
-WVPASS $TAX -l label -v "HT500 dslforum.org" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP adapter"
-WVPASS $TAX -l label -v "HT7XX dslforum.org" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP adapter"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xerox MFP;WorkCentre 3220"
 
 # check invalid or missing arguments. -l and -v are required.
 WVFAIL $TAX
diff --git a/conman/iw.py b/conman/iw.py
index f4932f1..300c3e2 100755
--- a/conman/iw.py
+++ b/conman/iw.py
@@ -7,6 +7,7 @@
 
 
 FIBER_OUI = 'f4:f5:e8'
+DEFAULT_GFIBERSETUP_SSID = 'GFiberSetupAutomation'
 
 
 def _scan(band, **kwargs):
@@ -115,6 +116,11 @@
           bss_info.ssid = ''.join(octets[1:]).decode('hex')
           continue
 
+    # Some of our devices (e.g. Frenzy) can't see vendor IEs.  If we find a
+    # hidden network no vendor IEs or SSID, guess 'GFiberSetupAutomation'.
+    if not bss_info.ssid and not bss_info.vendor_ies:
+      bss_info.ssid = DEFAULT_GFIBERSETUP_SSID
+
     for oui, data in bss_info.vendor_ies:
       if vendor_ie_function(oui, data):
         result_with_ie.add(bss_info)
diff --git a/conman/iw_test.py b/conman/iw_test.py
index 9c259e8..3f80d6c 100755
--- a/conman/iw_test.py
+++ b/conman/iw_test.py
@@ -486,7 +486,7 @@
      * BK: CW 15-1023, AIFSN 7
      * VI: CW 7-15, AIFSN 2, TXOP 3008 usec
      * VO: CW 3-7, AIFSN 2, TXOP 1504 usec
-BSS 94:b4:0f:f1:36:41(on wcli0)
+BSS 94:b4:0f:f1:36:42(on wcli0)
   TSF: 12499150000 usec (0d, 03:28:19)
   freq: 2437
   beacon interval: 100 TUs
@@ -552,6 +552,70 @@
      * VO: CW 3-7, AIFSN 2, TXOP 1504 usec
   Vendor specific: OUI 00:11:22, data: 01 23 45 67
   Vendor specific: OUI f4:f5:e8, data: 03 47 46 69 62 65 72 53 65 74 75 70 41 75 74 6f 6d 61 74 69 6f 6e
+BSS f4:f5:e8:f1:36:43(on wcli0)
+  TSF: 12499150000 usec (0d, 03:28:19)
+  freq: 2437
+  beacon interval: 100 TUs
+  capability: ESS Privacy ShortPreamble SpectrumMgmt ShortSlotTime RadioMeasure (0x1531)
+  signal: -66.00 dBm
+  last seen: 2350 ms ago
+  Information elements from Probe Response frame:
+  SSID:
+  Supported rates: 36.0* 48.0 54.0
+  DS Parameter set: channel 6
+  TIM: DTIM Count 0 DTIM Period 1 Bitmap Control 0x0 Bitmap[0] 0x0
+  Country: US Environment: Indoor/Outdoor
+    Channels [1 - 11] @ 36 dBm
+  Power constraint: 0 dB
+  TPC report: TX power: 3 dBm
+  ERP: <no flags>
+  BSS Load:
+     * station count: 0
+     * channel utilisation: 28/255
+     * available admission capacity: 27500 [*32us]
+  HT capabilities:
+    Capabilities: 0x19ad
+      RX LDPC
+      HT20
+      SM Power Save disabled
+      RX HT20 SGI
+      TX STBC
+      RX STBC 1-stream
+      Max AMSDU length: 7935 bytes
+      DSSS/CCK HT40
+    Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
+    Minimum RX AMPDU time spacing: 4 usec (0x05)
+    HT RX MCS rate indexes supported: 0-23
+    HT TX MCS rate indexes are undefined
+  HT operation:
+     * primary channel: 6
+     * secondary channel offset: no secondary
+     * STA channel width: 20 MHz
+     * RIFS: 1
+     * HT protection: nonmember
+     * non-GF present: 1
+     * OBSS non-GF present: 1
+     * dual beacon: 0
+     * dual CTS protection: 0
+     * STBC beacon: 0
+     * L-SIG TXOP Prot: 0
+     * PCO active: 0
+     * PCO phase: 0
+  Overlapping BSS scan params:
+     * passive dwell: 20 TUs
+     * active dwell: 10 TUs
+     * channel width trigger scan interval: 300 s
+     * scan passive total per channel: 200 TUs
+     * scan active total per channel: 20 TUs
+     * BSS width channel transition delay factor: 5
+     * OBSS Scan Activity Threshold: 0.25 %
+  Extended capabilities: HT Information Exchange Supported, Extended Channel Switching, BSS Transition, 6
+  WMM:   * Parameter version 1
+     * u-APSD
+     * BE: CW 15-1023, AIFSN 3
+     * BK: CW 15-1023, AIFSN 7
+     * VI: CW 7-15, AIFSN 2, TXOP 3008 usec
+     * VO: CW 3-7, AIFSN 2, TXOP 1504 usec
 """
 
 
@@ -573,9 +637,11 @@
                                  bssid='00:23:97:57:f4:d8',
                                  security=['WEP'],
                                  vendor_ies=[test_ie])
-  provisioning_bss_info = iw.BssInfo(ssid='GFiberSetupAutomation',
-                                     bssid='94:b4:0f:f1:36:41',
+  provisioning_bss_info = iw.BssInfo(ssid=iw.DEFAULT_GFIBERSETUP_SSID,
+                                     bssid='94:b4:0f:f1:36:42',
                                      vendor_ies=[test_ie, ssid_ie])
+  provisioning_bss_info_frenzy = iw.BssInfo(ssid=iw.DEFAULT_GFIBERSETUP_SSID,
+                                            bssid='f4:f5:e8:f1:36:43')
 
   with_ie, without_ie = iw.find_bssids('wcli0', lambda o, d: o == '00:11:22',
                                        True)
@@ -584,7 +650,8 @@
 
   wvtest.WVPASSEQ(
       without_ie,
-      set([iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),
+      set([provisioning_bss_info_frenzy,
+           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),
            iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:3a:e1'),
            iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:35:61'),
            iw.BssInfo(ssid='Google', bssid='94:b4:0f:f1:36:40',
@@ -601,7 +668,8 @@
   wvtest.WVPASSEQ(with_ie, set([provisioning_bss_info]))
   wvtest.WVPASSEQ(
       without_ie,
-      set([iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),
+      set([provisioning_bss_info_frenzy,
+           iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:36:41'),
            iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:3a:e1'),
            iw.BssInfo(ssid='GoogleGuest', bssid='94:b4:0f:f1:35:61')]))
 
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 59441b3..5088c20 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -293,8 +293,6 @@
       'rx_gainindex': Glaukus(VGainIndex, '/api/radio/rx/agcDigitalGainIndex',
                               '%s'),
       'palna_on': Glaukus(VTrueFalse, '/api/radio/paLnaPowerEnabled', '%s'),
-      'transceivers_on': Glaukus(VTrueFalse,
-                                 '/api/radio/transceiversPowerEnabled', '%s'),
 
       'reboot': Reboot(VTrueFalse)
   }
@@ -450,6 +448,7 @@
     data['refreshCount'] += 1
     data['uptime'] = self.ReadFile(sim + '/proc/uptime')
     data['ledstate'] = self.ReadFile(sim + '/tmp/gpio/ledstate')
+    data['peer_up'] = os.path.exists(sim + '/tmp/peer-up')
     cs = '/config/settings/'
     data['craft_ipaddr'] = self.ReadFile(sim + cs + 'craft_ipaddr')
     data['link_ipaddr'] = self.ReadFile(sim + cs + 'local_ipaddr')
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index aa81c3e..829bc96 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -172,7 +172,7 @@
 	      <span id=rx_gainindex_result>...</span>
 
 	  <tr>
-	    <td><b>Receiver PA/LNA Power Enabled
+	    <td><b>PA & LNA Enabled
 	    <td align=right><span id="radio/paLnaPowerEnabled">...</span>
 	    <td>
 	      <input id=palna_on type=text value="">
@@ -180,15 +180,6 @@
 	    <td>
 	      <span id=palna_on_result>...</span>
 
-	  <tr>
-	    <td><b>Transceivers Power Enabled
-	    <td align=right><span id="radio/transceiversPowerEnabled">...</span>
-	    <td>
-	      <input id=transceivers_on type=text value="">
-	      <input type=submit value=Configure onclick="CraftUI.config('transceivers_on')">
-	    <td>
-	      <span id=transceivers_on_result>...</span>
-
 	</table>
       </div>
     </div>
diff --git a/craftui/www/index.thtml b/craftui/www/index.thtml
index 54a5ccb..3ca4237 100644
--- a/craftui/www/index.thtml
+++ b/craftui/www/index.thtml
@@ -39,6 +39,7 @@
       <input type="radio" id="tab-2" name="tab-group-1">
       <label for="tab-2">Network</label>
       <div class="content">
+        <b>Peer is up:</b><span class="values" id="platform/peer_up">...</span><br>
         <b>IP Addresses:</b>
 	<table>
 	  <tr>
diff --git a/jsonpoll/jsonpoll.py b/jsonpoll/jsonpoll.py
index f69fac3..bee52a0 100755
--- a/jsonpoll/jsonpoll.py
+++ b/jsonpoll/jsonpoll.py
@@ -23,7 +23,6 @@
 import socket
 import sys
 import tempfile
-import textwrap
 import time
 import urllib2
 import options
@@ -61,17 +60,39 @@
                                'api/radio': self.api_radio_output_file}
     self.last_response = None
 
+  def _FlatObject(self, base, obj, out):
+    """Open json object to a list of strings.
+
+    Args:
+      base: is a string that has a base name for a key.
+      obj: is a JSON object that will be flatten.
+      out: is an output where strings will be appended.
+
+    Example:
+         data = {"a": 1, "b": { "c": 2, "d": 3}}
+         out = []
+         _FlatObject('', data, out)
+
+         out will be equal to
+           [u'/a=1', u'/b/c=2', u'/b/d=3']
+    """
+    for k, v in obj.items():
+      name = base + '/' + k
+      if isinstance(v, dict):
+        self._FlatObject(name, v, out)
+      else:
+        val = '%s=' % name
+        out.append(val + str(v))
+
   def WriteToStderr(self, msg, is_json=False):
     """Write a message to stderr."""
     if is_json:
-      # Make the json easier to parse from the logs.
       json_data = json.loads(msg)
-      json_str = json.dumps(json_data, sort_keys=True, indent=2,
-                            separators=(',', ': '))
-      # Logging pretty-printed json is like logging one huge line. Logos is
-      # configured to limit lines to 768 characters. Split the logged output at
-      # half of that to make sure logos doesn't clip our output.
-      sys.stderr.write('\n'.join(textwrap.wrap(json_str, width=384)))
+      flat_data = []
+      self._FlatObject('', json_data, flat_data)
+      # Make the json easier to parse from the logs.
+      for s in flat_data:
+        sys.stderr.write('%s\n' % s)
       sys.stderr.flush()
     else:
       sys.stderr.write(msg)
diff --git a/jsonpoll/jsonpoll_test.py b/jsonpoll/jsonpoll_test.py
index d8431f0..f4f0240 100644
--- a/jsonpoll/jsonpoll_test.py
+++ b/jsonpoll/jsonpoll_test.py
@@ -120,6 +120,12 @@
       output = ''.join(line.rstrip() for line in f)
       self.assertEqual('', output)
 
+  def testFlatObject(self):
+    obj = {'key1': 1, 'key2': {'key3': 3, 'key4': 4}}
+    got = []
+    self.poller._FlatObject('base', obj, got)
+    want = ['base/key1=1', 'base/key2/key3=3', 'base/key2/key4=4']
+    self.assertEqual(got.sort(), want.sort())
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/ledpattern/Makefile b/ledpattern/Makefile
new file mode 100644
index 0000000..705a398
--- /dev/null
+++ b/ledpattern/Makefile
@@ -0,0 +1,28 @@
+default:
+
+PREFIX=/
+BINDIR=$(DESTDIR)$(PREFIX)/bin
+PYTHON?=python
+
+all:
+
+install:
+	mkdir -p $(BINDIR)
+	cp ledpattern.py $(BINDIR)/ledpattern
+
+install-libs:
+	@echo "No libs to install."
+
+test: lint
+	set -e; \
+	for pytest in $(wildcard *_test.py); do \
+		echo; \
+		echo "Testing $$pytest"; \
+		$(PYTHON) $$pytest; \
+	done
+
+clean:
+	rm -rf *.pyc
+
+lint:
+	gpylint ledpattern.py ledpattern_test.py
diff --git a/ledpattern/ledpattern.py b/ledpattern/ledpattern.py
new file mode 100755
index 0000000..bcd3b03
--- /dev/null
+++ b/ledpattern/ledpattern.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python
+
+"""Blinks a specific LED pattern read from a simple pattern file.
+
+The first value is the state that the LED pattern represents. Followed by
+any combination of the following values:
+
+  R = Red blink
+  B = Blue blink
+  P = Purple blink
+
+An example pattern file might look like:
+
+ echo "blink,R,R,R" > /tmp/test.pat
+
+Invoking "ledpattern.py /tmp/test.pat blink" would result in the red LED
+blinking three times.
+"""
+
+__author__ = 'Chris Gibson <cgibson@google.com>'
+
+import csv
+import os
+import sys
+import time
+
+# Unit tests can override these values.
+DISABLE_GPIOMAILBOX = '/tmp/gpio/disable'
+SYSFS_RED_BRIGHTNESS = '/sys/class/leds/sys-red/brightness'
+SYSFS_BLUE_BRIGHTNESS = '/sys/class/leds/sys-blue/brightness'
+SLEEP_TIMEOUT = 0.5
+
+
+class LedPattern(object):
+  """Read, parse, and blink LEDs based on a pattern from a file."""
+
+  def ReadCsvPatternFile(self, pattern_file, state):
+    """Read a CSV pattern file."""
+    if not os.path.exists(pattern_file):
+      print 'Error: Pattern file: "%s" not found.' % pattern_file
+      return None
+    if not state:
+      print 'Error: led state cannot be empty.'
+      return None
+    try:
+      with open(pattern_file, 'r') as f:
+        patterns = csv.reader(f, delimiter=',')
+        for row in patterns:
+          if row[0] == state:
+            return [c for c in row[1:] if c in ['R', 'B', 'P']]
+      print ('Error: Could not find led state: "%s" in pattern file: %s'
+             % (state, pattern_file))
+      return None
+    except (IOError, OSError) as ex:
+      print ('Failed to open the pattern file: %s, error: %s.'
+             % (pattern_file, ex))
+      return None
+
+  def SetRedBrightness(self, level):
+    with open(SYSFS_RED_BRIGHTNESS, 'w') as f:
+      f.write(level)
+
+  def SetBlueBrightness(self, level):
+    with open(SYSFS_BLUE_BRIGHTNESS, 'w') as f:
+      f.write(level)
+
+  def SetLedsOff(self):
+    self.SetRedBrightness('0')
+    self.SetBlueBrightness('0')
+
+  def RedBlink(self):
+    self.SetLedsOff()
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetRedBrightness('100')
+    self.SetBlueBrightness('0')
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetLedsOff()
+
+  def BlueBlink(self):
+    self.SetLedsOff()
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetRedBrightness('0')
+    self.SetBlueBrightness('100')
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetLedsOff()
+
+  def PurpleBlink(self):
+    self.SetLedsOff()
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetRedBrightness('100')
+    self.SetBlueBrightness('100')
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetLedsOff()
+
+  def PlayPattern(self, pattern):
+    for color in pattern:
+      if color == 'R':
+        self.RedBlink()
+      elif color == 'B':
+        self.BlueBlink()
+      elif color == 'P':
+        self.PurpleBlink()
+
+  def Run(self, pattern_file, state):
+    """Sets up an LED pattern to play.
+
+    Arguments:
+      pattern_file: Pattern file containing a list of LED states/patterns.
+      state: Name of the LED state to play.
+
+    Returns:
+      An integer exit code: 0 means everything succeeded. Non-zero exit codes
+      mean something went wrong.
+    """
+    try:
+      open(DISABLE_GPIOMAILBOX, 'w').close()
+    except (IOError, OSError) as ex:
+      # If we can't disable gpio-mailbox then we can't guarantee control
+      # over the LEDs, so we just have to admit defeat.
+      print 'Error: Failed to disable gpio-mailbox! %s' % ex
+      return 1
+
+    try:
+      pattern = self.ReadCsvPatternFile(pattern_file, state)
+      if not pattern:
+        print 'Reading pattern failed! Exiting!'
+        return 1
+    except (IOError, OSError) as ex:
+      print 'Error: Failed to read pattern file, %s' % ex
+      return 1
+
+    # Depending on what state the LEDs are in when we touched the gpio-mailbox
+    # disable file, the LEDs will remain in that last state. Firstly, turn both
+    # LEDs off then sleep for a second to indicate that the pattern is about
+    # to begin.
+    self.SetLedsOff()
+    time.sleep(1)
+
+    self.PlayPattern(pattern)
+
+    # Turn off the LEDs and sleep for a second to clearly delineate the end of
+    # the current pattern.
+    self.SetLedsOff()
+    time.sleep(1)
+
+    os.unlink(DISABLE_GPIOMAILBOX)
+    return 0
+
+
+def Usage():
+  print 'Usage:'
+  print '  %s {pattern file} {state}' % sys.argv[0]
+  print
+  print '    pattern file: path to a pattern file'
+  print '    state: the LED state to select'
+
+
+if __name__ == '__main__':
+  if len(sys.argv) != 3:
+    Usage()
+    sys.exit(1)
+  ledpattern = LedPattern()
+  sys.exit(ledpattern.Run(sys.argv[1], sys.argv[2]))
diff --git a/ledpattern/ledpattern_test.py b/ledpattern/ledpattern_test.py
new file mode 100755
index 0000000..0a42dad
--- /dev/null
+++ b/ledpattern/ledpattern_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+"""Tests for ledpattern."""
+
+import os
+import tempfile
+import unittest
+
+import ledpattern
+
+
+class TestLedpattern(unittest.TestCase):
+
+  def setUp(self):
+    self.ledpattern = ledpattern.LedPattern()
+    self.fd_red, ledpattern.SYSFS_RED_BRIGHTNESS = tempfile.mkstemp()
+    print ledpattern.SYSFS_RED_BRIGHTNESS
+    self.fd_blue, ledpattern.SYSFS_BLUE_BRIGHTNESS = tempfile.mkstemp()
+    print ledpattern.SYSFS_BLUE_BRIGHTNESS
+
+  def tearDown(self):
+    os.close(self.fd_red)
+    os.close(self.fd_blue)
+    os.unlink(ledpattern.SYSFS_RED_BRIGHTNESS)
+    os.unlink(ledpattern.SYSFS_BLUE_BRIGHTNESS)
+
+  def testReadCsvPatternFileTest(self):
+    expected = ['R', 'B', 'P']
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', 'test')
+    self.assertEqual(expected, actual)
+
+  def testReadCsvPatternFileNotFound(self):
+    actual = self.ledpattern.ReadCsvPatternFile('/does/not/exist.pat', 'test')
+    self.assertEqual(None, actual)
+
+  def testReadCsvPatternFileNoState(self):
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', '')
+    self.assertEqual(None, actual)
+
+  def testReadCsvPatternFileIsMissingState(self):
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', 'foo')
+    self.assertEqual(None, actual)
+
+  def testReadCsvPatternFileInvalidCharsGetFiltered(self):
+    expected = ['B', 'B', 'B']
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat',
+                                                'test_with_invalid_chars')
+    self.assertEqual(expected, actual)
+
+  def testReadCsvPatternFileEmptyPattern(self):
+    expected = []
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat',
+                                                'test_empty_pattern')
+    self.assertEqual(expected, actual)
+
+  def testSetRedBrightness(self):
+    self.ledpattern.SetRedBrightness('foo')
+    with open(ledpattern.SYSFS_RED_BRIGHTNESS, 'r') as f:
+      actual = f.read()
+    self.assertEqual('foo', actual)
+
+  def testSetBlueBrightness(self):
+    self.ledpattern.SetBlueBrightness('bar')
+    with open(ledpattern.SYSFS_BLUE_BRIGHTNESS, 'r') as f:
+      actual = f.read()
+    self.assertEqual('bar', actual)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/ledpattern/testdata/test.pat b/ledpattern/testdata/test.pat
new file mode 100644
index 0000000..6b49acf
--- /dev/null
+++ b/ledpattern/testdata/test.pat
@@ -0,0 +1,3 @@
+test_with_invalid_chars,B,B,B,X,X,X
+test,R,B,P
+test_empty_pattern,,
diff --git a/logupload/client/log_uploader_main.c b/logupload/client/log_uploader_main.c
index 7a4e0fb..e160dd1 100644
--- a/logupload/client/log_uploader_main.c
+++ b/logupload/client/log_uploader_main.c
@@ -52,6 +52,13 @@
   interrupted = 1;
 }
 
+static void got_usr1(int sig) {
+  // Do nothing.
+  // We create the signal handler without SA_RESTART, so this will interrupt
+  // an in-progress sleep, which is enough to wake us up and cause another
+  // upload cycle, if we're not already uploading.
+}
+
 // To allow overriding for testing.
 int getnameinfo_resolver(const struct sockaddr* sa, socklen_t salen, char* host,
     size_t hostlen, char* serv, size_t servlen, int flags) {
@@ -92,7 +99,7 @@
   fprintf(stderr, " --server URL    Server URL [" DEFAULT_SERVER "]\n");
   fprintf(stderr, " --all           Upload entire logs, not just new data\n");
   fprintf(stderr, " --logtype TYPE  Tell server which log category this is\n");
-  fprintf(stderr, " --freq SECS     Upload logs every SECS seconds [60]\n");
+  fprintf(stderr, " --freq SECS     Repeat every SECS secs (default=once)\n");
   fprintf(stderr, " --stdout        Print to stdout instead of uploading\n");
   fprintf(stderr,
           " --stdin NAME    Get data from stdin, not /dev/kmsg, and\n"
@@ -183,6 +190,9 @@
   sa.sa_handler = got_alarm;
   sigaction(SIGALRM, &sa, NULL);
 
+  sa.sa_handler = got_usr1;
+  sigaction(SIGUSR1, &sa, NULL);
+
   // Initialize the random number generator
   srandom(getpid() ^ time(NULL));
 
@@ -220,7 +230,7 @@
   parse_params.dev_kmsg_path = DEV_KMSG_PATH;
   parse_params.version_path = VERSION_PATH;
   parse_params.ntp_synced_path = NTP_SYNCED_PATH;
-  // This'll set it to zero if it can't read the file which is fine.
+  // This'll set it to zero if it can't read the file, which is fine.
   parse_params.last_log_counter = read_file_as_uint64(COUNTER_MARKER_FILE);
   parse_params.log_buffer = log_buffer;
   parse_params.line_buffer = line_buffer;
diff --git a/presterastats/presterastats.py b/presterastats/presterastats.py
index bb703c5..2757e0c 100755
--- a/presterastats/presterastats.py
+++ b/presterastats/presterastats.py
@@ -75,7 +75,7 @@
       return
     kill_proc = lambda p: os.killpg(os.getpgid(p.pid), signal.SIGTERM)
     timer = threading.Timer(self.timeout, kill_proc, [proc])
-    cpss_cmd_prefix = 'show interfaces mac json-counters ethernet '
+    cpss_cmd_prefix = 'do show interfaces mac json-counters ethernet '
     try:
       timer.start()
       cpssout, _ = proc.communicate(input=cpss_cmd_prefix + self.ports + '\n')
diff --git a/taxonomy/ethernet.py b/taxonomy/ethernet.py
index 7489a76..223d228 100644
--- a/taxonomy/ethernet.py
+++ b/taxonomy/ethernet.py
@@ -38,6 +38,7 @@
 
     '08:60:6e': ['asus'],
     '08:62:66': ['asus'],
+    '10:bf:48': ['asus'],
     '1c:87:2c': ['asus'],
     '2c:56:dc': ['asus'],
     '30:85:a9': ['asus'],
@@ -94,6 +95,7 @@
     '3c:bd:d8': ['lg'],
     '40:b0:fa': ['lg'],
     '58:3f:54': ['lg'],
+    '5c:70:a3': ['lg'],
     '64:89:9a': ['lg'],
     '64:bc:0c': ['lg'],
     '78:f8:82': ['lg'],
@@ -111,6 +113,8 @@
     'f8:95:c7': ['lg'],
     'f8:a9:d0': ['lg'],
 
+    'd0:73:d5': ['lifx'],
+
     '24:fd:52': ['liteon', 'sling'],
 
     '00:0d:3a': ['microsoft'],
@@ -128,6 +132,7 @@
     '60:45:bd': ['microsoft'],
     '7c:1e:52': ['microsoft'],
     '7c:ed:8d': ['microsoft'],
+    '98:5f:d3': ['microsoft'],
     'b4:ae:2b': ['microsoft'],
     'c0:33:5e': ['microsoft'],
 
@@ -146,8 +151,9 @@
     '98:4b:4a': ['motorola'],
     '9c:d9:17': ['motorola'],
     'cc:c3:ea': ['motorola'],
-    'ec:88:92': ['motorola'],
+    'e4:90:7e': ['motorola'],
     'e8:91:20': ['motorola'],
+    'ec:88:92': ['motorola'],
     'f8:7b:7a': ['motorola'],
     'f8:cf:c5': ['motorola'],
     'f8:e0:79': ['motorola'],
@@ -171,7 +177,7 @@
     'fc:c2:de': ['murata', 'samsung'],
     'fc:db:b3': ['murata', 'samsung'],
 
-    '18:b4:30': ['nest'],
+    '18:b4:30': ['nest', 'dropcam'],
 
     'c0:ee:fb': ['oneplus'],
 
@@ -183,8 +189,10 @@
     '14:32:d1': ['samsung'],
     '14:49:e0': ['samsung'],
     '18:22:7e': ['samsung'],
+    '20:55:31': ['samsung'],
     '20:6e:9c': ['samsung'],
     '24:4b:81': ['samsung'],
+    '28:27:bf': ['samsung'],
     '28:ba:b5': ['samsung'],
     '2c:ae:2b': ['samsung'],
     '30:19:66': ['samsung'],
@@ -194,6 +202,7 @@
     '38:d4:0b': ['samsung'],
     '3c:8b:fe': ['samsung'],
     '3c:a1:0d': ['samsung'],
+    '3c:bb:fd': ['samsung'],
     '40:0e:85': ['samsung'],
     '48:5a:3f': ['samsung', 'wisol'],
     '4c:3c:16': ['samsung'],
@@ -207,6 +216,7 @@
     '78:40:e4': ['samsung'],
     '78:d6:f0': ['samsung'],
     '78:bd:bc': ['samsung'],
+    '7c:0b:c6': ['samsung'],
     '80:65:6d': ['samsung'],
     '84:11:9e': ['samsung'],
     '84:25:db': ['samsung'],
@@ -223,10 +233,12 @@
     'a8:06:00': ['samsung'],
     'ac:36:13': ['samsung'],
     'ac:5f:3e': ['samsung'],
+    'b0:47:bf': ['samsung'],
     'b0:df:3a': ['samsung'],
     'b0:ec:71': ['samsung'],
     'b4:07:f9': ['samsung'],
     'b4:79:a7': ['samsung'],
+    'b8:57:d8': ['samsung'],
     'b8:5a:73': ['samsung'],
     'bc:20:a4': ['samsung'],
     'bc:72:b1': ['samsung'],
@@ -238,9 +250,11 @@
     'cc:07:ab': ['samsung'],
     'cc:3a:61': ['samsung'],
     'd0:22:be': ['samsung'],
+    'd0:59:e4': ['samsung'],
     'e0:99:71': ['samsung'],
     'e0:db:10': ['samsung'],
     'e4:12:1d': ['samsung'],
+    'e4:58:b8': ['samsung'],
     'e4:92:fb': ['samsung'],
     'e8:3a:12': ['samsung'],
     'e8:50:8b': ['samsung'],
@@ -248,6 +262,7 @@
     'ec:9b:f3': ['samsung'],
     'f0:25:b7': ['samsung'],
     'f4:09:d8': ['samsung'],
+    'f8:04:2e': ['samsung'],
     'fc:f1:36': ['samsung'],
 
     '00:d9:d1': ['sony'],
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index 87c7d86..9d2c906 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -23,6 +23,9 @@
 
 
 database = {
+    'wifi4|probe:0,1,50,127,107,221(0050f2,4),221(506f9a,9),221(506f9a,16),extcap:00000080,wps:5042T|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+        ('', 'Alcatel Pop Astro', '2.4GHz'),
+
     'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|os:dashbutton':
         ('BCM43362', 'Amazon Dash Button', '2.4GHz'),
 
@@ -157,6 +160,8 @@
         ('', 'Epson Printer', '2.4GHz'),
     'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:epsonprinter':
         ('', 'Epson Printer', '2.4GHz'),
+    'wifi4|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:0020,htagg:1a,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff|os:epsonprinter':
+        ('', 'Epson Printer', '2.4GHz'),
 
     'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|os:hpprinter':
         ('', 'HP Printer', '2.4GHz'),
@@ -204,6 +209,11 @@
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a8201400000|oui:htc':
         ('WCN3680', 'HTC One M8', '2.4GHz'),
 
+    'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(0050f2,4),221(506f9a,10),221(506f9a,9),221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a820040,wps:831C|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a8201400000':
+        ('', 'HTC One M8, Sprint version', '2.4GHz'),
+    'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(0050f2,4),221(506f9a,9),221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a820040,wps:HTC6525LVW|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:160d,extcap:00000a8201400000':
+        ('', 'HTC One M8, Verizon version', '2.4GHz'),
+
     'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:0063,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088001400040|assoc:0,1,33,36,48,45,127,107,191,221(001018,2),221(0050f2,2),htcap:0063,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e009,extcap:0000088001400040|oui:htc':
         ('BCM4356', 'HTC One M9', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:0000088001400040|assoc:0,1,50,33,36,45,127,107,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140|oui:htc':
@@ -211,6 +221,9 @@
     'wifi4|probe:0,1,50,3,45,127,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:0000088001400040|assoc:0,1,50,33,36,45,127,107,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140|oui:htc':
         ('BCM4356', 'HTC One M9', '2.4GHz'),
 
+    'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:000008800140,wps:0PJA2|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140':
+        ('', 'HTC One M9, Sprint version', '2.4GHz'),
+
     'wifi4|probe:0,1,50,45,3,221(0050f2,4),221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff,wps:_|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|oui:htc':
         ('', 'HTC One V', '2.4GHz'),
 
@@ -332,6 +345,8 @@
         ('BCM4334', 'iPhone 5s', '5GHz'),
     'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,33,36,48,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1603|os:ios':
         ('BCM4334', 'iPhone 5s', '5GHz'),
+    'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1603|os:ios':
+        ('BCM4334', 'iPhone 5s', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1805|os:ios':
         ('BCM4334', 'iPhone 5s', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1805|os:ios':
@@ -447,6 +462,9 @@
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LGLS660|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff':
         ('', 'LG Tribute', '2.4GHz'),
 
+    'wifi4|probe:0,1,50|assoc:0,1,50,48,221(0050f2,2)|oui:lifx':
+        ('', 'LIFX LED light bulb', '2.4GHz'),
+
     'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:087e,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:087e,htagg:1b,htmcs:0000ffff,txpow:0f07|os:macos':
         ('BCM43224', 'MacBook Air late 2010 (A1369)', '5GHz'),
     'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:187c,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:187c,htagg:1b,htmcs:0000ffff,txpow:1207|os:macos':
@@ -553,6 +571,8 @@
         ('BCM4339', 'Nexus 5', '5GHz'),
     'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
         ('BCM4339', 'Nexus 5', '5GHz'),
+    'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
+        ('BCM4339', 'Nexus 5', '5GHz'),
     'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088001400040|assoc:0,1,33,36,48,45,127,70,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000008001400040|oui:lg':
         ('BCM4339', 'Nexus 5', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1303|oui:lg':
@@ -661,14 +681,14 @@
 
     'wifi4|probe:0,1,45,127,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:000008800140,wps:Nexus_9|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e009,extcap:000008800140':
         ('BCM4354', 'Nexus 9', '5GHz'),
+    'wifi4|probe:0,1,45,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,wps:Nexus_9|assoc:0,1,33,36,48,45,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e009':
+        ('BCM4354', 'Nexus 9', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000088001400040,wps:Nexus_9|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1309,extcap:000008800140':
         ('BCM4354', 'Nexus 9', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140,wps:Nexus_9|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:150b,extcap:000008800140':
         ('BCM4354', 'Nexus 9', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140,wps:Nexus_9|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1309,extcap:000008800140':
         ('BCM4354', 'Nexus 9', '2.4GHz'),
-    'wifi4|probe:0,1,50,3,45,127,221(00904c,4),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1309,extcap:000008800140|oui:samsung':
-        ('BCM4354', 'Nexus 9', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,wps:Nexus_9|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1309':
         ('BCM4354', 'Nexus 9', '2.4GHz'),
 
@@ -694,6 +714,11 @@
     'wifi4|probe:0,1,50,45,htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff|os:windows-phone':
         ('', 'Nokia Lumia 920', '2.4GHz'),
 
+    'wifi4|probe:0,1,45,127,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000008001000040,wps:SHIELD_Android_TV|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e007,extcap:0000008001000040':
+        ('', 'NVidia SHIELD Android TV', '5GHz'),
+    'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000008001000040,wps:SHIELD_Android_TV|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1207,extcap:0000008001000040':
+        ('', 'NVidia SHIELD Android TV', '2.4GHz'),
+
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a0200000000|oui:oneplus':
         ('', 'Oneplus X', '2.4GHz'),
 
@@ -717,6 +742,12 @@
     'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6303W87DK|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
         ('', 'RCA 10 Viking Pro', '2.4GHz'),
 
+    'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6773W42B|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+        ('', 'RCA Voyager', '2.4GHz'),
+
+    'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6773W22B|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+        ('', 'RCA Voyager-II', '2.4GHz'),
+
     # Roku model 1100, 2500 and LT model 2450
     'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|os:roku':
         ('BCM43362', 'Roku HD/LT', '2.4GHz'),
@@ -950,6 +981,8 @@
         ('', 'Sony Bravia TV', '5GHz'),
     'wifi4|probe:0,1,50,45,127,221(0050f2,4),221(506f9a,10),221(506f9a,9),htcap:01ed,htagg:13,htmcs:0000ffff,extcap:00,wps:BRAVIA_2015|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,127,htcap:008c,htagg:13,htmcs:0000ffff,extcap:00000a02':
         ('', 'Sony Bravia TV', '2.4GHz'),
+    'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,10),221(506f9a,9),wps:BRAVIA_4K_2015|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,127,htcap:008c,htagg:13,htmcs:0000ffff,extcap:00000a02':
+        ('', 'Sony Bravia TV', '2.4GHz'),
 
     'wifi4|probe:0,1,3,45,221(0050f2,8),191,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffc,vhttxmcs:0000fffc|assoc:0,1,33,36,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff|oui:sony':
         ('WCN3680', 'Sony Xperia Z Ultra', '5GHz'),
@@ -967,12 +1000,6 @@
     'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000088001400040|assoc:0,1,50,33,36,48,70,45,127,107,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1307,extcap:0000088001400040|oui:sony':
         ('', 'Sony Xperia Z4/Z5', '2.4GHz'),
 
-    'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(0050f2,4),221(506f9a,10),221(506f9a,9),221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a820040,wps:831C|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a8201400000':
-        ('', 'Sprint One M8', '2.4GHz'),
-
-    'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:000008800140,wps:0PJA2|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140':
-        ('', 'Sprint One M9', '2.4GHz'),
-
     # TIVO-849
     'wifi4|probe:0,1,45,127,191,221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000008001|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e009,extcap:0000008001|os:tivo':
         ('', 'TiVo BOLT', '5GHz'),
@@ -1038,6 +1065,9 @@
 
     'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,33,48,70,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff,txpow:170d|oui:xiaomi':
         ('', 'Xiaomi Redmi 3', '2.4GHz'),
+
+    'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:Z820|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+        ('', 'ZTE Obsidian', '2.4GHz'),
 }
 
 
diff --git a/wifi/quantenna.py b/wifi/quantenna.py
index 6c68029..33378ba 100755
--- a/wifi/quantenna.py
+++ b/wifi/quantenna.py
@@ -78,6 +78,9 @@
   if not interface:
     return False
 
+  if opt.encryption == 'WEP':
+    raise utils.BinWifiException('WEP not supported')
+
   if mode == 'scan':
     mode = 'sta'
     scan = True
@@ -88,7 +91,7 @@
   _qcsapi('restore_default_config', 'noreboot')
 
   config = {
-      'bw': opt.width,
+      'bw': opt.width if mode == 'ap' else 80,
       'channel': 149 if opt.channel == 'auto' else opt.channel,
       'mode': mode,
       'pmf': 0,
@@ -102,7 +105,7 @@
   if int(_qcsapi('is_startprod_done')):
     _qcsapi('reload_in_mode', 'wifi0', mode)
   else:
-    _qcsapi('startprod', 'wifi0')
+    _qcsapi('startprod')
     for _ in xrange(30):
       if int(_qcsapi('is_startprod_done')):
         break
@@ -113,20 +116,35 @@
   if mode == 'ap':
     _set_interface_in_bridge(opt.bridge, interface, True)
     _qcsapi('set_ssid', 'wifi0', opt.ssid)
-    _qcsapi('set_passphrase', 'wifi0', 0, os.environ['WIFI_PSK'])
+    if opt.encryption == 'NONE':
+      _qcsapi('set_beacon_type', 'wifi0', 'Basic')
+    else:
+      protocol, authentication, encryption = opt.encryption.split('_')
+      protocol = {'WPA': 'WPA', 'WPA2': '11i', 'WPA12': 'WPAand11i'}[protocol]
+      authentication += 'Authentication'
+      encryption += 'Encryption'
+      _qcsapi('set_beacon_type', 'wifi0', protocol)
+      _qcsapi('set_wpa_authentication_mode', 'wifi0', authentication)
+      _qcsapi('set_wpa_encryption_modes', 'wifi0', encryption)
+      _qcsapi('set_passphrase', 'wifi0', 0, os.environ['WIFI_PSK'])
     _qcsapi('set_option', 'wifi0', 'ssid_broadcast', int(not opt.hidden_mode))
     _qcsapi('rfenable', 1)
   elif mode == 'sta' and not scan:
     _set_interface_in_bridge(opt.bridge, interface, False)
     _qcsapi('create_ssid', 'wifi0', opt.ssid)
-    _qcsapi('ssid_set_passphrase', 'wifi0', opt.ssid, 0,
-            os.environ['WIFI_CLIENT_PSK'])
+    if opt.bssid:
+      _qcsapi('set_ssid_bssid', 'wifi0', opt.ssid, opt.bssid)
+    if opt.encryption == 'NONE' or not os.environ.get('WIFI_CLIENT_PSK'):
+      _qcsapi('ssid_set_authentication_mode', 'wifi0', opt.ssid, 'NONE')
+    else:
+      _qcsapi('ssid_set_passphrase', 'wifi0', opt.ssid, 0,
+              os.environ['WIFI_CLIENT_PSK'])
     # In STA mode, 'rfenable 1' is already done by 'startprod'/'reload_in_mode'.
     # 'apply_security_config' must be called instead.
     _qcsapi('apply_security_config', 'wifi0')
 
     for _ in xrange(10):
-      if _qcsapi('get_bssid', 'wifi0') != '00:00:00:00:00:00':
+      if _qcsapi('get_ssid', 'wifi0'):
         break
       time.sleep(1)
     else:
@@ -177,7 +195,8 @@
   if not _get_interface():
     return False
 
-  if _qcsapi('get_mode', 'wifi0') == 'Access point':
+  if (int(_qcsapi('is_startprod_done')) and
+      _qcsapi('get_mode', 'wifi0') == 'Access point'):
     _qcsapi('rfenable', 0)
 
   return True
@@ -188,7 +207,8 @@
   if not _get_interface():
     return False
 
-  if _qcsapi('get_mode', 'wifi0') == 'Station':
+  if (int(_qcsapi('is_startprod_done')) and
+      _qcsapi('get_mode', 'wifi0') == 'Station'):
     _qcsapi('rfenable', 0)
 
   return True
diff --git a/wifi/quantenna_test.py b/wifi/quantenna_test.py
index e19c76e..ebef2a0 100755
--- a/wifi/quantenna_test.py
+++ b/wifi/quantenna_test.py
@@ -20,9 +20,9 @@
 def fake_qcsapi(*args):
   calls.append([str(x) for x in args])
   if args[0] == 'is_startprod_done':
-    return '1' if ['startprod', 'wifi0'] in calls else '0'
-  if args[0] == 'get_bssid':
-    return '00:11:22:33:44:55'
+    return '1' if ['startprod'] in calls else '0'
+  if args[0] == 'get_ssid':
+    return calls[matching_calls_indices(['set_ssid', 'create_ssid'])[-1]][1]
   if args[0] == 'get_mode':
     i = [c for c in matching_calls_indices(['update_config_param'])
          if calls[c][2] == 'mode']
@@ -126,7 +126,7 @@
 
   # 'update_config_param' and 'set_mac_addr' must be run before 'startprod',
   # since 'startprod' runs scripts that read these configs.
-  sp = calls.index(['startprod', 'wifi0'])
+  sp = calls.index(['startprod'])
   i = matching_calls_indices(['update_config_param', 'set_mac_addr'])
   wvtest.WVPASSLT(i[-1], sp)
 
@@ -161,7 +161,7 @@
   wvtest.WVPASSEQ(calls[1], ['restore_default_config', 'noreboot'])
 
   # Check that 'startprod' is not run.
-  wvtest.WVPASS(['startprod', 'wifi0'] not in calls)
+  wvtest.WVPASS(['startprod'] not in calls)
 
   # Check that configs are written.
   wvtest.WVPASS(['update_config_param', 'wifi0', 'bw', '80'] in calls)