/* * A narrow C surface over ESP-Hosted's Wi-Fi RPC, so Zig never transcribes an IDF struct. * * `rpc_wifi_init` takes a `wifi_init_config_t`, `rpc_wifi_set_config` takes a `wifi_config_t`, and * scanning hands back `wifi_ap_record_t`. Those are large, versioned structs full of bitfields, and * IDF builds them with macros - WIFI_INIT_CONFIG_DEFAULT() alone sets over twenty fields * (esp_wifi.h:316). Writing Zig `extern struct`s to match would be a transcription that compiles * happily and goes wrong on the next IDF release, exactly the mistake that * `esp_hosted_sdio_get_config` taught this project once already. * * So the structs stay on the C side, built by IDF's own macros, and Zig gets plain scalars and byte * buffers. Everything here is a thin forwarder; the interesting code is all in ESP-Hosted's RPC * layer, which this does not duplicate. */ #include #include "esp_wifi_types.h" #include "esp_wifi.h" #include "rpc_wrap.h" /* ESP-Hosted's RPC entry points (host/drivers/rpc/wrap/rpc_wrap.c). Declared here rather than * relying on the header, so a signature change is a compile error in this file. */ int rpc_wifi_init(const wifi_init_config_t *arg); int rpc_wifi_set_mode(wifi_mode_t mode); int rpc_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf); int rpc_wifi_connect(void); int rpc_wifi_scan_start(const wifi_scan_config_t *config, bool block); int rpc_wifi_scan_get_ap_num(uint16_t *number); int rpc_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records); int rpc_wifi_start(void); int rpc_wifi_get_mac(wifi_interface_t mode, uint8_t mac[6]); int rpc_wifi_set_ps(wifi_ps_type_t type); /* Initialise the coprocessor's Wi-Fi and put it in station mode, started. * * The order is IDF's own and is not negotiable: init, set_mode, start. `esp_wifi_start` is what * actually brings the radio up on the C6; a config set before it is accepted and a connect before * it is not. */ int hosted_wifi_sta_start(void) { /* IDF's WIFI_INIT_CONFIG_DEFAULT() is deliberately NOT used, and this is not a shortcut. * * That macro's first two fields are `.osi_funcs = &g_wifi_osi_funcs` and * `.wpa_crypto_funcs = g_wifi_default_wpa_crypto_funcs` (esp_wifi.h:317-318) - the local Wi-Fi * driver's OS adapter and crypto tables. This chip has no Wi-Fi driver: the C6 does, and it uses * its own. Referencing them here pulls in symbols that cannot exist in this image, which is * exactly the link error that led to this comment. * * They are also provably unnecessary. rpc_req.c:182-215 packs the request field by field, and * every field it packs is a scalar; neither function pointer is ever serialised. So the struct * only has to carry the scalars, and those come from the same Kconfig-derived macros the real * default uses - via the checked-in sdkconfig, so they are this project's configuration and not * a second set of numbers. * * `magic` is load-bearing: the coprocessor validates it (esp_wifi.h's own note says it must * always be WIFI_INIT_CONFIG_MAGIC), so a zeroed struct is rejected. */ wifi_init_config_t cfg = { 0 }; cfg.static_rx_buf_num = CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM; cfg.dynamic_rx_buf_num = CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM; cfg.tx_buf_type = CONFIG_ESP_WIFI_TX_BUFFER_TYPE; cfg.static_tx_buf_num = WIFI_STATIC_TX_BUFFER_NUM; cfg.dynamic_tx_buf_num = WIFI_DYNAMIC_TX_BUFFER_NUM; cfg.rx_mgmt_buf_type = CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF; cfg.rx_mgmt_buf_num = WIFI_RX_MGMT_BUF_NUM_DEF; cfg.cache_tx_buf_num = WIFI_CACHE_TX_BUFFER_NUM; cfg.csi_enable = WIFI_CSI_ENABLED; cfg.ampdu_rx_enable = WIFI_AMPDU_RX_ENABLED; cfg.ampdu_tx_enable = WIFI_AMPDU_TX_ENABLED; cfg.amsdu_tx_enable = WIFI_AMSDU_TX_ENABLED; cfg.nvs_enable = WIFI_NVS_ENABLED; cfg.nano_enable = WIFI_NANO_FORMAT_ENABLED; cfg.rx_ba_win = WIFI_DEFAULT_RX_BA_WIN; cfg.wifi_task_core_id = WIFI_TASK_CORE_ID; cfg.beacon_max_len = WIFI_SOFTAP_BEACON_MAX_LEN; cfg.mgmt_sbuf_num = WIFI_MGMT_SBUF_NUM; cfg.feature_caps = WIFI_FEATURE_CAPS; cfg.sta_disconnected_pm = WIFI_STA_DISCONNECTED_PM_ENABLED; cfg.espnow_max_encrypt_num = CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM; cfg.tx_hetb_queue_num = WIFI_TX_HETB_QUEUE_NUM; cfg.dump_hesigb_enable = WIFI_DUMP_HESIGB_ENABLED; cfg.magic = WIFI_INIT_CONFIG_MAGIC; int err = rpc_wifi_init(&cfg); if (err) { return err; } err = rpc_wifi_set_mode(WIFI_MODE_STA); if (err) { return err; } err = rpc_wifi_start(); if (err) { return err; } /* Power save OFF, and this is not a performance tweak - it decides whether the board is * reachable at all. * * ESP-IDF's default is WIFI_PS_MIN_MODEM (esp_wifi_types_generic.h:376): the station sleeps and * only wakes for a beacon every DTIM period. A sleeping station misses frames the AP does not * buffer for it, and broadcast ARP is exactly that. The observed symptom on this board was * precise and misleading: DHCP completed - because the host speaks first and the reply arrives * inside the wake window - the board took a real lease, and then it answered no ARP and no ping, * with the frame counter advancing about once per ten seconds. It looked like a broken receive * path rather than a radio that was asleep. * * A device that exists to answer requests cannot sleep between them. WIFI_PS_NONE. */ return rpc_wifi_set_ps(WIFI_PS_NONE); } /* The station's MAC. Needed by the IP stack: ARP and Ethernet framing are built around it, and it * belongs to the C6's radio, not to this chip. */ int hosted_wifi_get_mac(uint8_t out[6]) { return rpc_wifi_get_mac(WIFI_IF_STA, out); } /* Scan every channel and report how many networks were seen. * * Blocking: the RPC layer waits for the coprocessor to finish, which takes a couple of seconds * across all channels. A scan needs no credentials, which makes it the cheapest end-to-end proof * that the RPC path and the radio both work. */ int hosted_wifi_scan(uint16_t *found) { wifi_scan_config_t scan = { 0 }; scan.show_hidden = true; int err = rpc_wifi_scan_start(&scan, true); if (err) { return err; } return rpc_wifi_scan_get_ap_num(found); } /* One scan result, flattened to scalars. * * `ssid_out` must have room for 33 bytes; the SSID is copied NUL-terminated. Returns the number of * records actually written into the caller's view, which is `min(*count, what the slave has)`. */ int hosted_wifi_scan_record(uint16_t index, char *ssid_out, int8_t *rssi_out, uint8_t *channel_out, uint8_t *authmode_out) { /* One record at a time, into a local, so the caller never sees a wifi_ap_record_t. Asking the * slave for a single record by index is not part of the RPC, so this fetches the run up to * `index` and keeps the last - fine for the small numbers a diagnostic prints, and stated here * rather than hidden because it is O(n^2) if someone loops it over hundreds of networks. */ static wifi_ap_record_t records[16]; uint16_t want = index + 1; if (want > 16) { return -1; } int err = rpc_wifi_scan_get_ap_records(&want, records); if (err) { return err; } if (index >= want) { return -1; } const wifi_ap_record_t *r = &records[index]; size_t n = strnlen((const char *)r->ssid, 32); memcpy(ssid_out, r->ssid, n); ssid_out[n] = 0; *rssi_out = r->rssi; *channel_out = r->primary; *authmode_out = (uint8_t)r->authmode; return 0; } /* Join a network. * * `ssid` and `psk` are NUL-terminated. The PSK is copied into the request and never stored here; * it arrives from a build option so it is not in the source, and this function keeps no copy after * the RPC returns. * * `threshold.authmode` is deliberately left at 0 (WIFI_AUTH_OPEN) rather than forced to WPA2: it is * a *minimum* acceptable security level, and pinning it too high refuses networks that would * otherwise work while pinning it low refuses nothing. The AP's actual authmode is what gets used. */ int hosted_wifi_connect(const char *ssid, const char *psk) { wifi_config_t conf = { 0 }; size_t ssid_len = strnlen(ssid, sizeof(conf.sta.ssid) - 1); memcpy(conf.sta.ssid, ssid, ssid_len); size_t psk_len = strnlen(psk, sizeof(conf.sta.password) - 1); memcpy(conf.sta.password, psk, psk_len); /* Scan all channels and pick the strongest match rather than the first: this network has both a * 2.4 GHz and a 5 GHz radio on the same SSID family, and the C6 is 2.4 GHz only. */ conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; conf.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; int err = rpc_wifi_set_config(WIFI_IF_STA, &conf); if (err) { return err; } return rpc_wifi_connect(); }