#include "st67w6.h" #include "net.h" #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_ST67W6) && \ MG_ENABLE_DRIVER_ST67W6 static struct mg_tcpip_if *s_ifp; static uint32_t s_ip, s_mask; static bool s_link = false, s_connecting = false; static void wifi_cb(struct mg_tcpip_if *ifp, int ev, void *ev_data) { struct mg_wifi_data *wifi = &((struct mg_tcpip_driver_st67w6_data *) ifp->driver_data)->wifi; if (wifi->apmode && ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_UP) { MG_DEBUG(("Access Point started")); s_ip = ifp->ip, ifp->ip = wifi->apip; s_mask = ifp->mask, ifp->mask = wifi->apmask; ifp->enable_dhcp_client = false; ifp->enable_dhcp_server = true; } } static bool st67w6_init(uint8_t *mac); static void st67w6_poll(bool is_at); static bool mg_tcpip_driver_st67w6_init(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; struct mg_wifi_data *wifi = &d->wifi; if (MG_BIG_ENDIAN) { MG_ERROR(("Big-endian host")); return false; } if (d->is_ready == NULL) return false; s_ifp = ifp; s_ip = ifp->ip; s_mask = ifp->mask; s_link = false; ifp->pfn = wifi_cb; if (!st67w6_init(ifp->mac)) return false; if (d->send_queue.size == 0) d->send_queue.size = 8192; d->send_queue.buf = (char *) mg_calloc(1, d->send_queue.size); if (d->send_queue.buf == NULL) { MG_ERROR(("OOM")); return false; } if (wifi->apmode) { return mg_wifi_ap_start(wifi); } else if (wifi->ssid != NULL && wifi->pass != NULL) { return mg_wifi_connect(wifi); } return true; } // Decouple; module access depends on it being RDY. See st67w6_poll() size_t mg_tcpip_driver_st67w6_output(const void *buf, size_t len, struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; char *p; if (mg_queue_book(&d->send_queue, &p, len) < len) return 0; memcpy(p, buf, len); mg_queue_add(&d->send_queue, len); return len; } static bool mg_tcpip_driver_st67w6_poll(struct mg_tcpip_if *ifp, bool s1) { struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) ifp->driver_data; if (d->is_ready == NULL) return false; st67w6_poll(false); if (!s1) return false; return s_link; } struct mg_tcpip_driver mg_tcpip_driver_st67w6 = { mg_tcpip_driver_st67w6_init, mg_tcpip_driver_st67w6_output, NULL, mg_tcpip_driver_st67w6_poll}; // AT | STA | AP | HCI | OT // -------------------------- // framing <-- includes rx stall indication // -------------------------- // SPI <-- padded to 32-bit // // Transactions take place when the module signals RDY, 'tx' and 'write' // functions actually just write to memory // - AT: handles configuration and events (unsolicited +foo:). Responses may // come as: // - 1 frame: just OK or ERROR // - 2 frames: 1 text line each, a response and OK // - 3 frames: 2 text lines: 1 frame with text and no CR/LF, 1 frame with // binary and CR/LF, 1 text line with OK // - STA and AP contain plain Ethernet frames to/from the STA and AP networks, // respectively // - HCI: BLE stuff // - OT: ? #pragma pack(push, 1) // little endian struct spi_hdr { uint16_t magic; uint16_t len; uint8_t vflags; // version :2, rx_stall :1, flags :5 uint8_t type; uint16_t reserved; }; #define ST67W6_SPI_TYPE_AT 0 #define ST67W6_SPI_TYPE_STA 1 #define ST67W6_SPI_TYPE_AP 2 #define ST67W6_SPI_TYPE_HCI 3 #define ST67W6_SPI_TYPE_OT 4 #pragma pack(pop) static uint32_t txdata[2048 / 4], rxdata[2048 / 4]; static uint8_t at_resp[300]; static bool s_at_ok, s_at_err; static size_t s_at_resp_len; static void st67w6_handle_wifi_evnt(char *, size_t len); static void st67w6_handle_scan_result(char *, size_t len); static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read); static void st67w6_write(unsigned int f, void *data, uint16_t len); static void st67w6_update_hash_table(void); // High-level comm stuff static void st67w6_poll(bool is_at) { struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; struct spi_hdr *h = (struct spi_hdr *) rxdata; unsigned int type; uint8_t *txsource = (uint8_t *) txdata; if (s_ifp->update_mac_hash_table) { // first call to _poll() is after _init(), so this is safe st67w6_update_hash_table(); s_ifp->update_mac_hash_table = false; } if (!is_at) { // send outstanding WLAN frames in the queue char *buf; size_t len; // NOTE(): have traffic-dependent queues or queue traffic type with data if ((len = mg_queue_next(&d->send_queue, &buf)) > 0) { st67w6_write(d->wifi.apmode ? ST67W6_SPI_TYPE_AP : ST67W6_SPI_TYPE_STA, buf, (uint16_t) len); mg_queue_del(&d->send_queue, len); } else { // nothing to send txsource = NULL; } } if (st67w6_spi_poll(txsource, (uint8_t *) rxdata) == 0) return; if (h->len == 0) return; type = h->type; if (type == ST67W6_SPI_TYPE_AT) { char *p = (char *) (h + 1); size_t len = h->len; if (len > 7 && strncmp(p, "+CWLAP:", 7) == 0) { // scan result st67w6_handle_scan_result(p + 7, len - 7); } else if (len > 4 && strncmp(p, "+CW:", 4) == 0) { st67w6_handle_wifi_evnt(p + 4, len - 4); } else { MG_VERBOSE(("AT partial: %.*s", (int) len, p)); if (s_at_resp_len + len >= sizeof(at_resp)) s_at_resp_len = 0; // truncate response, error will be caught later memcpy(at_resp + s_at_resp_len, p, len); s_at_resp_len += len; if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*ERROR*", 7), NULL)) { s_at_err = true; } else if (mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*OK*", 4), NULL)) { s_at_ok = true; } } } else if (type == ST67W6_SPI_TYPE_STA || type == ST67W6_SPI_TYPE_AP) { // WLAN frame reception mg_tcpip_qwrite(h + 1, h->len, s_ifp); } // else silently discard } // WLAN event handling // - Do not call any AT functions here, otherwise revise st67w6_at_wait() // - The module likes to send ERROR along with other events static void st67w6_handle_wifi_evnt(char *p, size_t len) { struct mg_str data[2]; MG_VERBOSE(("event: %.*s", (int) len, p)); if (len > 9 && strncmp(p, "CONNECTED", 9) == 0) { s_link = true; s_connecting = false; } else if (len > 12 && strncmp(p, "DISCONNECTED", 12) == 0) { s_link = false; s_connecting = false; // should not be needed } else if (s_connecting && len > 6 && mg_match(mg_str_n(p, len), mg_str_n("ERROR,*\r\n*", 10), data)) { size_t reason = 0; bool ok = mg_to_size_t(data[0], &reason); s_connecting = false; MG_ERROR(("CONNECT FAILED")); mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_CONNECT_ERR, ok ? &reason : NULL); } else if (len > 9 && strncmp(p, "SCAN_DONE", 9) == 0) { MG_VERBOSE(("scan complete")); mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_END, NULL); } // else silently discard: CONNECTING; STA_CONNECTED,"MAC"; // STA_DISCONNECTED,"MAC"; DIST_STA_IP,"MAC","IP" } static bool st67w6_at_cmd(char *cmd, size_t len); // Wi-Fi network stuff static bool st67w6_wifi_connect(char *ssid, char *pass) { char cmd[90]; // ssid + pass + AT size_t cmd_len; if (!st67w6_at_cmd("AT+CWMODE=1,0\r\n", 15)) return false; cmd_len = mg_snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\",,0\r\n", ssid, pass); // takes >700ms if (!st67w6_at_cmd(cmd, cmd_len)) return false; st67w6_at_cmd("AT+CWRECONNCFG=0,0\r\n", 20); // disregard error, connecting s_connecting = true; return true; } static bool st67w6_wifi_disconnect(void) { s_connecting = false; if (!st67w6_at_cmd("AT+CWQAP=0\r\n", 12)) return false; // takes >800ms return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms } static bool st67w6_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { char cmd[90]; // ssid + pass + AT size_t cmd_len; if (!st67w6_at_cmd("AT+CWMODE=2,0\r\n", 15)) return false; // takes >800ms cmd_len = mg_snprintf( cmd, sizeof(cmd), "AT+CWSAP=\"%s\",\"%s\",%u,3,2,0\r\n", ssid, pass, channel); // 3: WPA2_PSK; 2: max stations // takes >350ms s_link = true; return st67w6_at_cmd(cmd, cmd_len); } static bool st67w6_wifi_ap_stop(void) { s_link = false; return st67w6_at_cmd("AT+CWMODE=0,0\r\n", 15); // takes >550ms } // WLAN scan handling // +CWLAP:(security,"SSID",RSSI,"BSSID",channel,cipher,proto,wps)\r\n // security: OPEN, WEP, WPA, WPA2, WPA-WPA2, WPA-EAP, WPA3-SAE, WPA2-WPA3-SAE // cipher: NONE, WEP, AES/CCMP, TKIP, TKIP and AES/CCMP // proto: 4-bit bitmap AX,N,G,B; all set from right to left static bool st67w6_wifi_scan(void) { return st67w6_at_cmd("AT+CWLAPOPT=1,1695,-100,255,50\r\n", 32) && st67w6_at_cmd("AT+CWLAP=0,,,0\r\n", 16); } static void st67w6_handle_scan_result(char *data, size_t len) { struct mg_wifi_scan_bss_data bss; struct mg_str fields[2]; char mac[6]; uint8_t val; unsigned int i; MG_VERBOSE(("scan result event: %.*s", (int) len, data)); ++data, --len; // skip '(' if (!mg_span(mg_str_n(data, len), &fields[0], &fields[1], ',') || !mg_str_to_num(fields[0], 10, &val, 1)) return; bss.security = (val == 0) ? MG_WIFI_SECURITY_OPEN : (uint8_t) MG_WIFI_SECURITY_WEP; if (val == 2 || val == 4) bss.security |= MG_WIFI_SECURITY_WPA; if (val == 3 || val == 4 || val == 7) bss.security |= MG_WIFI_SECURITY_WPA2; if (val == 6 || val == 7) bss.security |= MG_WIFI_SECURITY_WPA3; if (val == 5) bss.security |= MG_WIFI_SECURITY_WPA_ENTERPRISE; if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; bss.SSID.buf = fields[0].buf + 1, bss.SSID.len = fields[0].len - 2; if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; while (fields[0].buf[0] == ' ') ++fields[0].buf, --fields[0].len; if (fields[0].buf[0] != '-') return; // positive RSSI would be great ++fields[0].buf, --fields[0].len; if (!mg_str_to_num(fields[0], 10, &val, 1)) return; bss.RSSI = (int8_t) - (int8_t) val; if (!mg_span(fields[1], &fields[0], &fields[1], ',')) return; if (fields[0].len < 19) return; ++fields[0].buf, --fields[0].len; // skip '"' for (i = 0; i < 6; i++) { struct mg_str str; str.buf = fields[0].buf + 3 * i; str.len = 2; if (!mg_str_to_num(str, 16, &mac[i], 1)) return; } bss.BSSID = mac; if (!mg_span(fields[1], &fields[0], &fields[1], ',') || !mg_str_to_num(fields[0], 10, &bss.channel, 1)) return; if (!mg_span(fields[1], &fields[0], &fields[1], ',') || !mg_str_to_num(fields[0], 10, &val, 1)) return; // ignore cypher if (!mg_span(fields[1], &fields[0], &fields[1], ',') || !mg_str_to_num(fields[0], 10, &val, 1)) return; bss.has_n = (val & 4) != 0; bss.has_ax = (val & 8) != 0; bss.band = MG_WIFI_BAND_2G; // NOT INFORMED with default options, no docs MG_VERBOSE(("BSS: %.*s (%u) (%M) %d dBm %u", bss.SSID.len, bss.SSID.buf, bss.channel, mg_print_mac, bss.BSSID, (int) bss.RSSI, bss.security)); mg_tcpip_call(s_ifp, MG_TCPIP_EV_WIFI_SCAN_RESULT, &bss); } // AT stuff static inline bool delayms(unsigned int ms) { mg_delayms(ms); return true; } // send AT command, wait for a response or timeout, meanwhile delivering // received frames and events static bool st67w6_at_cmd(char *cmd, size_t len) { bool is_at = true; unsigned int times = 1000; s_at_resp_len = 0; st67w6_write(ST67W6_SPI_TYPE_AT, cmd, (uint16_t) len); s_at_ok = false, s_at_err = false; do { // AT response processing does not call any other AT function st67w6_poll(is_at); // otherwise we can't allow them to pile up here is_at = false; // avoid repeating, allow queued WLAN frames to be sent // network frames will be pushed to the queue so that is safe } while (!s_at_ok && !s_at_err && times-- > 0 && delayms(1)); MG_VERBOSE(("AT response:\n%.*s", s_at_resp_len, at_resp)); MG_VERBOSE(("ok: %c, err: %c, times: %d", s_at_ok ? '1' : '0', s_at_err ? '1' : '0', (int) times)); return s_at_ok; } static bool st67w6_spi_init(void); bool mg_to_size_t(struct mg_str str, size_t *val); static bool st67w6_init(uint8_t *mac) { // struct mg_tcpip_driver_st67w6_data *d = (struct // mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; struct mg_str data[3]; size_t val; bool is_b = false; if (!st67w6_spi_init()) return false; if (!st67w6_at_cmd("AT\r\n", 4)) return false; if (!st67w6_at_cmd("AT+CWNETMODE?\r\n", 15) || !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*:*\r\n*", 6), data) || !mg_to_size_t(data[1], &val)) return false; if (val != 0) { MG_ERROR(("Wrong firmware, T02 is needed")); return false; } // set clock, who cares ??? if (!st67w6_at_cmd("AT+GET_CLOCK\r\n", 14)) return false; MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // TODO(scaprile): --> VERBOSE // BT-ENABLED DEPENDENCY // MODULE DEPENDENCY if (!st67w6_at_cmd("AT+EFUSE-R=24,\"0x100\"\r\n", 23) || !mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), data)) return false; if (data[1].buf[0] == 'C' && data[1].buf[1] == '6') is_b = true; MG_DEBUG(("WLAN module is %sB type", is_b ? "" : "not")); // --> VERBOSE if (is_b) { // Disable the antenna diversity pin if (!st67w6_at_cmd("AT+IORST=0\r\n", 12)) return false; // Apparently they intend to disable some antenna ... if (!st67w6_at_cmd("AT+CWANTENABLE?\r\n", 17)) return false; MG_DEBUG(("%.*s", s_at_resp_len, at_resp)); // --> VERBOSE } // Do not set wake-up pin (AT+SLWKIO) // Disable power save mode // NOTE(scaprile): (no response if in hibernate mode, though I guess we // wouldn't have reached this point in that case either) if (!st67w6_at_cmd("AT+PWR=0\r\n", 12)) return false; // set Wi-Fi // set country code if (!st67w6_at_cmd("AT+CWCOUNTRY=0,\"00\"\r\n", 21)) return false; #if 0 // set DTIM if (!st67w6_at_cmd("AT+SLWKDTIM=1\r\n", 15)) return false; #endif // Read only default MAC, ignore set bit count (data[6] & 0x3F). Custom MACs // reside at @0x64 and 0x70 if (st67w6_at_cmd("AT+EFUSE-R=7,\"0x014\"\r\n", 22) && mg_match(mg_str_n((char *) at_resp, s_at_resp_len), mg_str_n("*,*", 3), data)) { int i; for (i = 0; i < 6; i++) mac[i] = data[1].buf[5 - i]; MG_DEBUG(("MAC: %M", mg_print_mac, mac)); } else { MG_ERROR(("read MAC failed")); } return true; } static void st67w6_update_hash_table(void) { // TODO(): read database, rebuild hash table // uint32_t val = 0; // val = 1; // st67w6_at_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), // (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); } // SPI specifics #define ST67W6_SPI_MAGIC 0x55AA // #define ST67W6_SPI_VERSION(x), IS_STALL, FLAGS(x), ... static const uint8_t idlehdr[sizeof(struct spi_hdr)] = {0xaa, 0x55, 0, 0, 0, 0, 0, 0}; static void st67w6_write(unsigned int f, void *data, uint16_t len) { struct spi_hdr *h = (struct spi_hdr *) txdata; h->magic = ST67W6_SPI_MAGIC; h->type = (uint8_t) f; h->vflags = 0; h->len = len; h->reserved = 0; memmove(h + 1, data, len); } static size_t st67w6_spi_poll(uint8_t *write, uint8_t *read) { struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; struct spi_hdr *th, *rh = (struct spi_hdr *) read; struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; size_t padded; unsigned int times; th = (write != NULL) ? (struct spi_hdr *) write : (struct spi_hdr *) idlehdr; s->begin(s->spi); times = 50; while (times--) { if (d->is_ready()) break; if (times == 0) { MG_ERROR(("RDY TIMEOUT")); s->end(s->spi); return 0; } mg_delayms(1); } padded = (th->len + 3) & ~3; s->txn(s->spi, (uint8_t *) th, (uint8_t *) rh, sizeof(*th) + padded); if (rh->magic == ST67W6_SPI_MAGIC && rh->len > padded) { size_t remaining_padded = (rh->len - padded + 3) & ~3; if (remaining_padded > (2048 - sizeof(*rh) - padded)) remaining_padded = 2048 - sizeof(*rh) - padded; s->txn(s->spi, NULL, read + sizeof(*rh) + padded, remaining_padded); } times = 50; while (times--) { if (!d->is_ready()) break; if (times == 0) { MG_ERROR(("!RDY TIMEOUT")); break; } mg_delayms(1); } s->end(s->spi); return (size_t) rh->len; } static bool st67w6_spi_init(void) { struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) s_ifp->driver_data; size_t len; unsigned int times = 1000; while (times--) { if (d->is_ready()) break; if (times == 0) return false; mg_delayms(1); } if (((len = st67w6_spi_poll(NULL, (uint8_t *) rxdata)) == 0) || !mg_match(mg_str_n(((char *) rxdata) + sizeof(struct spi_hdr), len), mg_str_n("*ready*", 7), NULL)) return false; return true; } // Mongoose Wi-Fi API functions bool mg_wifi_scan(void) { return st67w6_wifi_scan(); } bool mg_wifi_connect(struct mg_wifi_data *wifi) { s_ifp->ip = s_ip; s_ifp->mask = s_mask; if (s_ifp->ip == 0) s_ifp->enable_dhcp_client = true; s_ifp->enable_dhcp_server = false; MG_DEBUG(("Connecting to '%s'", wifi->ssid)); return st67w6_wifi_connect(wifi->ssid, wifi->pass); } bool mg_wifi_disconnect(void) { return st67w6_wifi_disconnect(); } bool mg_wifi_ap_start(struct mg_wifi_data *wifi) { MG_DEBUG(("Starting AP '%s' (%u)", wifi->apssid, wifi->apchannel)); return st67w6_wifi_ap_start(wifi->apssid, wifi->appass, wifi->apchannel); } bool mg_wifi_ap_stop(void) { return st67w6_wifi_ap_stop(); } #endif