/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2023 Arduino SA * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * ESP-Hosted WiFi driver. */ #include "py/mphal.h" #include "py/mperrno.h" #if MICROPY_PY_NETWORK_ESP_HOSTED #include #include #include #include "lwip/err.h" #include "lwip/dns.h" #include "lwip/dhcp.h" #include "netif/etharp.h" #include "shared/netutils/netutils.h" #include "shared/netutils/dhcpserver.h" #include "esp_hosted.pb-c.h" #include "esp_hosted_hal.h" #include "esp_hosted_stack.h" #include "esp_hosted_netif.h" #include "esp_hosted_wifi.h" #include "esp_hosted_internal.h" static esp_hosted_state_t esp_state; static ProtobufCAllocator protobuf_alloc = { .alloc = &esp_hosted_hal_alloc, .free = &esp_hosted_hal_free, .allocator_data = NULL, }; static void esp_hosted_macstr_to_bytes(const uint8_t *mac_str, size_t mac_len, uint8_t *mac_out) { uint8_t byte = 0; for (int i = 0; i < mac_len; i++) { char c = mac_str[i]; if (c >= '0' && c <= '9') { byte = (byte << 4) | (c - '0'); } else if (c >= 'a' && c <= 'f') { byte = (byte << 4) | (c - 'a' + 10); } else if (c >= 'A' && c <= 'F') { byte = (byte << 4) | (c - 'A' + 10); } if (c == ':' || (i + 1) == mac_len) { *mac_out++ = byte; byte = 0; } } } // to avoid bleeding the protocol buffer API into the public interface, convert esp_hosted_security_t // to/from CtrlWifiSecProt here. static esp_hosted_security_t sec_prot_to_hosted_security(CtrlWifiSecProt sec_prot) { switch (sec_prot) { case CTRL__WIFI_SEC_PROT__Open: return ESP_HOSTED_SEC_OPEN; case CTRL__WIFI_SEC_PROT__WEP: return ESP_HOSTED_SEC_WEP; case CTRL__WIFI_SEC_PROT__WPA_PSK: return ESP_HOSTED_SEC_WPA_PSK; case CTRL__WIFI_SEC_PROT__WPA2_PSK: return ESP_HOSTED_SEC_WPA2_PSK; case CTRL__WIFI_SEC_PROT__WPA_WPA2_PSK: return ESP_HOSTED_SEC_WPA_WPA2_PSK; case CTRL__WIFI_SEC_PROT__WPA2_ENTERPRISE: return ESP_HOSTED_SEC_WPA2_ENTERPRISE; case CTRL__WIFI_SEC_PROT__WPA3_PSK: return ESP_HOSTED_SEC_WPA3_PSK; case CTRL__WIFI_SEC_PROT__WPA2_WPA3_PSK: return ESP_HOSTED_SEC_WPA2_WPA3_PSK; default: return ESP_HOSTED_SEC_INVALID; } } static CtrlWifiSecProt hosted_security_to_sec_prot(esp_hosted_security_t hosted_security) { switch (hosted_security) { case ESP_HOSTED_SEC_OPEN: return CTRL__WIFI_SEC_PROT__Open; case ESP_HOSTED_SEC_WEP: return CTRL__WIFI_SEC_PROT__WEP; case ESP_HOSTED_SEC_WPA_PSK: return CTRL__WIFI_SEC_PROT__WPA_PSK; case ESP_HOSTED_SEC_WPA2_PSK: return CTRL__WIFI_SEC_PROT__WPA2_PSK; case ESP_HOSTED_SEC_WPA_WPA2_PSK: return CTRL__WIFI_SEC_PROT__WPA_WPA2_PSK; case ESP_HOSTED_SEC_WPA2_ENTERPRISE: return CTRL__WIFI_SEC_PROT__WPA2_ENTERPRISE; case ESP_HOSTED_SEC_WPA3_PSK: return CTRL__WIFI_SEC_PROT__WPA3_PSK; case ESP_HOSTED_SEC_WPA2_WPA3_PSK: return CTRL__WIFI_SEC_PROT__WPA2_WPA3_PSK; default: abort(); // Range should be checked by the caller, making this unreachable } } uint16_t esp_hosted_checksum(esp_header_t *esp_header) { uint16_t checksum = 0; esp_header->checksum = 0; uint8_t *buf = (uint8_t *)esp_header; for (size_t i = 0; i < (esp_header->len + sizeof(esp_header_t)); i++) { checksum += buf[i]; } return checksum; } #if ESP_HOSTED_DEBUG static void esp_hosted_dump_header(esp_header_t *esp_header) { static const char *if_strs[] = { "STA", "AP", "SERIAL", "HCI", "PRIV", "TEST" }; if (esp_header->if_type > ESP_HOSTED_MAX_IF) { return; } debug_printf("esp header: if %s_IF length %d offset %d checksum %d seq %d flags %x\n", if_strs[esp_header->if_type], esp_header->len, esp_header->offset, esp_header->checksum, esp_header->seq_num, esp_header->flags); if (esp_header->if_type == ESP_HOSTED_SERIAL_IF) { tlv_header_t *tlv_header = (tlv_header_t *)(esp_header->payload); debug_printf("tlv header: ep_type %d ep_length %d ep_value %.8s data_type %d data_length %d\n", tlv_header->ep_type, tlv_header->ep_length, tlv_header->ep_value, tlv_header->data_type, tlv_header->data_length); } } #endif static int32_t esp_hosted_resp_value(CtrlMsg *ctrl_msg) { // Each response struct return value is located at a different offset, // the following array maps response CtrlMsgs to return values (resp) // offsets within each response struct. const static size_t ctrl_msg_resp_offset[] = { offsetof(CtrlMsgRespGetMacAddress, resp), offsetof(CtrlMsgRespSetMacAddress, resp), offsetof(CtrlMsgRespGetMode, resp), offsetof(CtrlMsgRespSetMode, resp), offsetof(CtrlMsgRespScanResult, resp), offsetof(CtrlMsgRespGetAPConfig, resp), offsetof(CtrlMsgRespConnectAP, resp), offsetof(CtrlMsgRespGetStatus, resp), offsetof(CtrlMsgRespGetSoftAPConfig, resp), offsetof(CtrlMsgRespSetSoftAPVendorSpecificIE, resp), offsetof(CtrlMsgRespStartSoftAP, resp), offsetof(CtrlMsgRespSoftAPConnectedSTA, resp), offsetof(CtrlMsgRespGetStatus, resp), offsetof(CtrlMsgRespSetMode, resp), offsetof(CtrlMsgRespGetMode, resp), offsetof(CtrlMsgRespOTABegin, resp), offsetof(CtrlMsgRespOTAWrite, resp), offsetof(CtrlMsgRespOTAEnd, resp), offsetof(CtrlMsgRespSetWifiMaxTxPower, resp), offsetof(CtrlMsgRespGetWifiCurrTxPower, resp), offsetof(CtrlMsgRespConfigHeartbeat, resp), }; int32_t resp = -1; size_t index = ctrl_msg->msg_id - CTRL_MSG_ID__Resp_Base; // All types of messages share the same payload base address. if (ctrl_msg->resp_get_mac_address != NULL && ctrl_msg->msg_type == CTRL_MSG_TYPE__Resp && index > 0 && index <= MP_ARRAY_SIZE(ctrl_msg_resp_offset)) { // Return the response struct's return value. size_t offset = ctrl_msg_resp_offset[index - 1]; resp = *((int32_t *)((char *)ctrl_msg->resp_get_mac_address + offset)); } return resp; } static int esp_hosted_request(CtrlMsgId msg_id, void *ctrl_payload) { CtrlMsg ctrl_msg = {0}; ctrl_msg__init(&ctrl_msg); ctrl_msg.msg_id = msg_id; ctrl_msg.payload_case = msg_id; // All types of messages share the same payload base address. ctrl_msg.req_get_mac_address = ctrl_payload; // Pack protobuf size_t payload_size = ctrl_msg__get_packed_size(&ctrl_msg); if ((payload_size + sizeof(tlv_header_t)) > ESP_FRAME_MAX_PAYLOAD) { error_printf("esp_hosted_request() payload size > max payload %d\n", msg_id); return -1; } esp_header_t *esp_header = (esp_header_t *)(esp_state.buf); tlv_header_t *tlv_header = (tlv_header_t *)(esp_header->payload); esp_header->if_type = ESP_HOSTED_SERIAL_IF; esp_header->if_num = 0; esp_header->flags = 0; esp_header->len = payload_size + sizeof(tlv_header_t); esp_header->offset = sizeof(esp_header_t); esp_header->seq_num = esp_state.seq_num++; tlv_header->ep_type = TLV_HEADER_TYPE_EP; tlv_header->ep_length = 8; memcpy(tlv_header->ep_value, TLV_HEADER_EP_RESP, 8); tlv_header->data_type = TLV_HEADER_TYPE_DATA; tlv_header->data_length = payload_size; ctrl_msg__pack(&ctrl_msg, tlv_header->data); esp_header->checksum = esp_hosted_checksum(esp_header); size_t frame_size = (sizeof(esp_header_t) + esp_header->len + 3) & ~3U; if (esp_hosted_hal_spi_transfer(esp_state.buf, NULL, frame_size) != 0) { error_printf("esp_hosted_request() request %d failed\n", msg_id); return -1; } return 0; } static CtrlMsg *esp_hosted_response(CtrlMsgId msg_id, uint32_t timeout) { CtrlMsg *ctrl_msg = NULL; for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(10)) { if (!esp_hosted_stack_empty(&esp_state.stack)) { ctrl_msg = esp_hosted_stack_pop(&esp_state.stack, true); if (ctrl_msg->msg_id == msg_id) { ctrl_msg = esp_hosted_stack_pop(&esp_state.stack, false); break; } debug_printf("esp_hosted_response() waiting for id %lu last id %lu\n", msg_id, ctrl_msg->msg_id); ctrl_msg = NULL; } if (timeout == 0) { // Request expected a sync response. return NULL; } // Check timeout. if ((mp_hal_ticks_ms() - start) >= timeout) { return NULL; } MICROPY_EVENT_POLL_HOOK } // If message type is a response, check the response struct's return value. if (ctrl_msg->msg_type == CTRL_MSG_TYPE__Resp && esp_hosted_resp_value(ctrl_msg) != 0) { error_printf("esp_hosted_response() response %d failed %d\n", msg_id, esp_hosted_resp_value(ctrl_msg)); ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); return NULL; } return ctrl_msg; } static int esp_hosted_ctrl(CtrlMsgId req_id, void *req_payload, CtrlMsg **resp_msg) { if (esp_hosted_request(req_id, req_payload) != 0) { return -1; } uint32_t resp_id = (req_id - CTRL_MSG_ID__Req_Base) + CTRL_MSG_ID__Resp_Base; if ((*resp_msg = esp_hosted_response(resp_id, ESP_SYNC_REQ_TIMEOUT)) == NULL) { return -1; } return 0; } int esp_hosted_wifi_poll(void) { size_t offset = 0; esp_header_t *esp_header = (esp_header_t *)(esp_state.buf); tlv_header_t *tlv_header = (tlv_header_t *)(esp_header->payload); if (!(esp_state.flags & ESP_HOSTED_FLAGS_INIT) || !esp_hosted_hal_data_ready()) { return 0; } do { esp_header_t *frag_header = (esp_header_t *)(esp_state.buf + offset); if ((ESP_STATE_BUF_SIZE - offset) < ESP_FRAME_MAX_SIZE) { // This shouldn't happen, but if it did stop polling. error_printf("esp_hosted_poll() spi buffer overflow offs %d\n", offset); return -1; } if (esp_hosted_hal_spi_transfer(NULL, esp_state.buf + offset, ESP_FRAME_MAX_SIZE) != 0) { error_printf("esp_hosted_poll() spi transfer failed\n"); return 0; } if (frag_header->len == 0 || frag_header->len > ESP_FRAME_MAX_PAYLOAD || frag_header->offset != sizeof(esp_header_t)) { // Invalid or empty packet, just ignore it silently. warn_printf("esp_hosted_poll() invalid frame size %d offset %d\n", esp_header->len, esp_header->offset); return 0; } uint16_t checksum = frag_header->checksum; frag_header->checksum = esp_hosted_checksum(frag_header); if (frag_header->checksum != checksum) { warn_printf("esp_hosted_poll() invalid checksum, expected %d\n", checksum); return 0; } if (offset) { // Combine fragmented packet if ((esp_header->seq_num + 1) != frag_header->seq_num) { error_printf("esp_hosted_poll() fragmented frame sequence mismatch\n"); return 0; } esp_header->len += frag_header->len; esp_header->seq_num = frag_header->seq_num; esp_header->flags = frag_header->flags; info_printf("esp_hosted_poll() received fragmented packet %d\n", frag_header->len); // Append the current fragment's payload to the previous one. memcpy(esp_state.buf + offset, frag_header->payload, frag_header->len); } offset = sizeof(esp_header_t) + esp_header->len; } while ((esp_header->flags & ESP_FRAME_FLAGS_FRAGMENT)); #if ESP_HOSTED_DEBUG esp_hosted_dump_header(esp_header); #endif switch (esp_header->if_type) { case ESP_HOSTED_STA_IF: case ESP_HOSTED_AP_IF: { // Networking traffic uint32_t itf = esp_header->if_type; if (netif_is_link_up(&esp_state.netif[itf])) { if (esp_hosted_netif_input(&esp_state, itf, esp_header->payload, esp_header->len) != 0) { error_printf("esp_hosted_poll() netif input failed\n"); return -1; } debug_printf("esp_hosted_poll() eth frame input %d\n", esp_header->len); } return 0; } case ESP_HOSTED_PRIV_IF: { esp_event_t *priv_event = (esp_event_t *)(esp_header->payload); if (esp_header->priv_pkt_type == ESP_PACKET_TYPE_EVENT && priv_event->event_type == ESP_PRIV_EVENT_INIT) { esp_state.chip_id = priv_event->event_data[2]; esp_state.spi_clk = priv_event->event_data[5]; esp_state.chip_flags = priv_event->event_data[8]; info_printf("esp_hosted_poll() chip id %d spi_mhz %d caps 0x%x\n", esp_state.chip_id, esp_state.spi_clk, esp_state.chip_flags); } return 0; } case ESP_HOSTED_HCI_IF: case ESP_HOSTED_TEST_IF: case ESP_HOSTED_MAX_IF: error_printf("esp_hosted_poll() unexpected interface type %d\n", esp_header->if_type); return 0; case ESP_HOSTED_SERIAL_IF: // Requires further processing break; } CtrlMsg *ctrl_msg = ctrl_msg__unpack(&protobuf_alloc, tlv_header->data_length, tlv_header->data); if (ctrl_msg == NULL) { error_printf("esp_hosted_poll() failed to unpack protobuf\n"); return 0; } if (ctrl_msg->msg_type == CTRL_MSG_TYPE__Event) { switch (ctrl_msg->msg_id) { case CTRL_MSG_ID__Event_ESPInit: esp_state.flags |= ESP_HOSTED_FLAGS_ACTIVE; break; case CTRL_MSG_ID__Event_Heartbeat: esp_state.last_hb_ms = mp_hal_ticks_ms(); info_printf("esp_hosted_poll() heartbeat %lu\n", esp_state.last_hb_ms); return 0; case CTRL_MSG_ID__Event_StationDisconnectFromAP: esp_state.flags &= ~ESP_HOSTED_FLAGS_STA_CONNECTED; return 0; case CTRL_MSG_ID__Event_StationDisconnectFromESPSoftAP: return 0; default: error_printf("esp_hosted_poll() unexpected event %d\n", ctrl_msg->msg_id); return 0; } } // Responses that should be handled here. if (ctrl_msg->msg_type == CTRL_MSG_TYPE__Resp) { switch (ctrl_msg->msg_id) { case CTRL_MSG_ID__Resp_ConnectAP: { if (esp_hosted_resp_value(ctrl_msg) == 0) { esp_state.flags |= ESP_HOSTED_FLAGS_STA_CONNECTED; } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); debug_printf("esp_hosted_poll() state %d\n", esp_state.flags); return 0; } default: break; } } // A control message resp/event will be pushed on the stack for further processing. if (!esp_hosted_stack_push(&esp_state.stack, ctrl_msg)) { error_printf("esp_hosted_poll() message stack full\n"); return -1; } debug_printf("esp_hosted_poll() pushed msg_type %lu msg_id %lu\n", ctrl_msg->msg_type, ctrl_msg->msg_id); return 0; } int esp_hosted_wifi_init(uint32_t itf) { if (esp_state.flags == ESP_HOSTED_FLAGS_RESET) { // Init state memset(&esp_state, 0, sizeof(esp_hosted_state_t)); esp_hosted_stack_init(&esp_state.stack); // Low-level pins and SPI init, memory pool allocation etc... if (esp_hosted_hal_init(ESP_HOSTED_MODE_WIFI) != 0) { return -1; } // Allow polling the bus. esp_state.flags |= ESP_HOSTED_FLAGS_INIT; CtrlMsg *ctrl_msg = NULL; // Wait for an ESPInit control event. ctrl_msg = esp_hosted_response(CTRL_MSG_ID__Event_ESPInit, ESP_SYNC_REQ_TIMEOUT); if (ctrl_msg == NULL) { return -1; } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); // Set WiFi mode to STA/AP. CtrlMsgReqSetMode ctrl_payload; ctrl_msg__req__set_mode__init(&ctrl_payload); ctrl_payload.mode = CTRL__WIFI_MODE__APSTA; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_SetWifiMode, &ctrl_payload, &ctrl_msg) != 0) { return -1; } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); info_printf("esp_hosted_init() device initialized\n"); } if (!netif_is_link_up(&esp_state.netif[itf])) { // Init lwip netif, and start DHCP client/server. esp_hosted_netif_init(&esp_state, itf); info_printf("esp_hosted_init() initialized itf %lu\n", itf); } return 0; } int esp_hosted_wifi_disable(uint32_t itf) { // Remove netif esp_hosted_netif_deinit(&esp_state, itf); if (itf == ESP_HOSTED_STA_IF) { esp_state.flags &= ~ESP_HOSTED_FLAGS_STA_CONNECTED; } else { esp_state.flags &= ~ESP_HOSTED_FLAGS_AP_STARTED; } info_printf("esp_hosted_deinit() deinitialized itf %lu\n", itf); return 0; } int esp_hosted_wifi_deinit(void) { if (esp_state.flags & ESP_HOSTED_FLAGS_INIT) { // Remove network interfaces esp_hosted_wifi_disable(ESP_HOSTED_STA_IF); esp_hosted_wifi_disable(ESP_HOSTED_AP_IF); // Reset state memset(&esp_state, 0, sizeof(esp_hosted_state_t)); esp_hosted_stack_init(&esp_state.stack); info_printf("esp_hosted_deinit() deinitialized\n"); } return 0; } void *esp_hosted_wifi_get_netif(uint32_t itf) { return &esp_state.netif[itf]; } int esp_hosted_wifi_get_mac(int itf, uint8_t *mac) { CtrlMsgReqGetMacAddress ctrl_payload; ctrl_msg__req__get_mac_address__init(&ctrl_payload); ctrl_payload.mode = (itf == ESP_HOSTED_STA_IF) ? CTRL__WIFI_MODE__STA : CTRL__WIFI_MODE__AP; CtrlMsg *ctrl_msg = NULL; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetMACAddress, &ctrl_payload, &ctrl_msg) != 0) { error_printf("esp_hosted_get_mac() request failed\n"); return -1; } ProtobufCBinaryData macstr = ctrl_msg->resp_get_mac_address->mac; if (macstr.data) { esp_hosted_macstr_to_bytes(macstr.data, macstr.len, mac); } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); return 0; } int esp_hosted_wifi_connect(const char *ssid, const char *bssid, esp_hosted_security_t security, const char *key, uint16_t channel) { CtrlMsgReqConnectAP ctrl_payload; ctrl_msg__req__connect_ap__init(&ctrl_payload); if (security >= ESP_HOSTED_SEC_MAX) { // Note: this argument is otherwise unused(!) return -1; } ctrl_payload.ssid = (char *)ssid; ctrl_payload.bssid = (char *)bssid; ctrl_payload.pwd = (char *)key; ctrl_payload.is_wpa3_supported = false; ctrl_payload.listen_interval = 0; if (esp_hosted_request(CTRL_MSG_ID__Req_ConnectAP, &ctrl_payload) != 0) { return -1; } return 0; } int esp_hosted_wifi_start_ap(const char *ssid, esp_hosted_security_t security, const char *key, uint16_t channel) { CtrlMsgReqStartSoftAP ctrl_payload; ctrl_msg__req__start_soft_ap__init(&ctrl_payload); if (security >= ESP_HOSTED_SEC_MAX) { return -1; } ctrl_payload.ssid = (char *)ssid; ctrl_payload.pwd = (char *)key; ctrl_payload.chnl = channel; ctrl_payload.sec_prot = hosted_security_to_sec_prot(security); ctrl_payload.max_conn = ESP_HOSTED_MAX_AP_CLIENTS; ctrl_payload.ssid_hidden = false; ctrl_payload.bw = CTRL__WIFI_BW__HT40; CtrlMsg *ctrl_msg = NULL; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_StartSoftAP, &ctrl_payload, &ctrl_msg) != 0) { return -1; } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); esp_state.flags |= ESP_HOSTED_FLAGS_AP_STARTED; return 0; } int esp_hosted_wifi_disconnect(uint32_t itf) { CtrlMsg *ctrl_msg = NULL; CtrlMsgReqGetStatus ctrl_payload; ctrl_msg__req__get_status__init(&ctrl_payload); if (itf == ESP_HOSTED_STA_IF) { esp_state.flags &= ~ESP_HOSTED_FLAGS_STA_CONNECTED; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_DisconnectAP, &ctrl_payload, &ctrl_msg) != 0) { return -1; } } else { esp_state.flags &= ~ESP_HOSTED_FLAGS_AP_STARTED; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_StopSoftAP, &ctrl_payload, &ctrl_msg) != 0) { return -1; } } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); return 0; } int esp_hosted_wifi_link_status(uint32_t itf) { return netif_is_link_up(&esp_state.netif[itf]); } int esp_hosted_wifi_is_connected(uint32_t itf) { if (!esp_hosted_wifi_link_status(itf)) { return false; } if (itf == ESP_HOSTED_AP_IF) { return esp_state.flags & ESP_HOSTED_FLAGS_AP_STARTED; } if ((esp_state.flags & ESP_HOSTED_FLAGS_STA_CONNECTED) && ((esp_state.flags & ESP_HOSTED_FLAGS_STATIC_IP) || dhcp_supplied_address(&esp_state.netif[itf]))) { return true; } return false; } int esp_hosted_wifi_get_stations(uint8_t *sta_list, size_t *sta_count) { CtrlMsgReqSoftAPConnectedSTA ctrl_payload; ctrl_msg__req__soft_apconnected_sta__init(&ctrl_payload); CtrlMsg *ctrl_msg = NULL; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetSoftAPConnectedSTAList, &ctrl_payload, &ctrl_msg) != 0) { return -1; } CtrlMsgRespSoftAPConnectedSTA *resp = ctrl_msg->resp_softap_connected_stas_list; *sta_count = resp->n_stations; for (size_t i = 0; i < resp->n_stations; i++) { ProtobufCBinaryData mac = resp->stations[i]->mac; esp_hosted_macstr_to_bytes(mac.data, mac.len, &sta_list[i * 6]); } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); return 0; } int esp_hosted_wifi_netinfo(esp_hosted_netinfo_t *netinfo) { CtrlMsgReqGetAPConfig ctrl_payload; ctrl_msg__req__get_apconfig__init(&ctrl_payload); CtrlMsg *ctrl_msg = NULL; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetAPConfig, &ctrl_payload, &ctrl_msg) != 0) { return -1; } netinfo->rssi = ctrl_msg->resp_get_ap_config->rssi; netinfo->security = sec_prot_to_hosted_security(ctrl_msg->resp_get_ap_config->sec_prot); netinfo->channel = ctrl_msg->resp_get_ap_config->chnl; ProtobufCBinaryData ssid = ctrl_msg->resp_get_ap_config->ssid; if (ssid.data) { size_t ssid_len = MIN(ssid.len, (ESP_HOSTED_MAX_SSID_LEN - 1)); memcpy(netinfo->ssid, ssid.data, ssid_len); netinfo->ssid[ssid_len] = 0; } ProtobufCBinaryData bssid = ctrl_msg->resp_get_ap_config->bssid; if (bssid.data) { esp_hosted_macstr_to_bytes(bssid.data, bssid.len, netinfo->bssid); } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); return 0; } int esp_hosted_wifi_scan(esp_hosted_scan_callback_t scan_callback, void *arg, uint32_t timeout) { CtrlMsgReqScanResult ctrl_payload; ctrl_msg__req__scan_result__init(&ctrl_payload); CtrlMsg *ctrl_msg = NULL; if (esp_hosted_ctrl(CTRL_MSG_ID__Req_GetAPScanList, &ctrl_payload, &ctrl_msg) != 0) { return -MP_ETIMEDOUT; } CtrlMsgRespScanResult *rp = ctrl_msg->resp_scan_ap_list; for (int i = 0; i < rp->count; i++) { esp_hosted_scan_result_t result = {0}; result.rssi = rp->entries[i]->rssi; result.security = sec_prot_to_hosted_security(rp->entries[i]->sec_prot); result.channel = rp->entries[i]->chnl; if (rp->entries[i]->bssid.data) { esp_hosted_macstr_to_bytes(rp->entries[i]->bssid.data, rp->entries[i]->bssid.len, result.bssid); } if (rp->entries[i]->ssid.len) { size_t ssid_len = MIN(rp->entries[i]->ssid.len, (ESP_HOSTED_MAX_SSID_LEN - 1)); memcpy(result.ssid, rp->entries[i]->ssid.data, ssid_len); result.ssid[ssid_len] = 0; } scan_callback(&result, arg); } ctrl_msg__free_unpacked(ctrl_msg, &protobuf_alloc); return 0; } #endif // MICROPY_PY_NETWORK_ESP_HOSTED