#include "ble_prov.h" #include "wifi_cfg.h" #include "protocol.h" #include #include #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" #include "host/ble_hs.h" #include "host/ble_uuid.h" #include "host/util/util.h" #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" #include "esp_mac.h" /* wifi_transport provides these */ extern esp_err_t wifi_transport_reconnect_sta(const char *ssid, const char *pass); extern void wifi_transport_get_sta_state(uint8_t *state, uint32_t *ip); #define DEVICE_NAME "EIS4" #define FW_VERSION "4.0" #define RSP_BUF_MAX 128 /* EIS4 provisioning service UUID: 4e455334-a001-4801-b947-001122334455 */ static const ble_uuid128_t svc_uuid = BLE_UUID128_INIT(0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x47, 0xb9, 0x01, 0x48, 0x01, 0xa0, 0x34, 0x53, 0x45, 0x4e); /* EIS4 provisioning characteristic UUID: 4e455334-a002-4801-b947-001122334455 */ static const ble_uuid128_t chr_uuid = BLE_UUID128_INIT(0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x47, 0xb9, 0x01, 0x48, 0x02, 0xa0, 0x34, 0x53, 0x45, 0x4e); static bool s_ble_active; static uint16_t s_chr_handle; static uint8_t s_rsp_buf[RSP_BUF_MAX]; static uint16_t s_rsp_len; static uint8_t s_own_addr_type; static void start_advertise(void); /* -- response builders -- */ static void build_wifi_cfg_rsp(void) { char ssid[WIFI_CFG_SSID_MAX], pass[WIFI_CFG_PASS_MAX]; uint8_t *p = s_rsp_buf; *p++ = 0xF0; *p++ = 0x7D; *p++ = RSP_WIFI_CFG; /* STA SSID */ if (wifi_cfg_get_sta(ssid, sizeof(ssid), pass, sizeof(pass)) == ESP_OK) { uint8_t slen = (uint8_t)strlen(ssid); *p++ = slen; memcpy(p, ssid, slen); p += slen; /* STA status */ uint8_t state; uint32_t ip; wifi_transport_get_sta_state(&state, &ip); *p++ = state; } else { *p++ = 0; *p++ = WIFI_STATE_DISCONNECTED; } /* AP SSID */ wifi_cfg_get_ap(ssid, sizeof(ssid), pass, sizeof(pass)); { uint8_t slen = (uint8_t)strlen(ssid); *p++ = slen; memcpy(p, ssid, slen); p += slen; } *p++ = 0xF7; s_rsp_len = (uint16_t)(p - s_rsp_buf); } static void build_set_result(uint8_t ok) { uint8_t *p = s_rsp_buf; *p++ = 0xF0; *p++ = 0x7D; *p++ = RSP_SET_WIFI_RESULT; *p++ = ok ? 0 : 1; *p++ = 0xF7; s_rsp_len = (uint16_t)(p - s_rsp_buf); } static void build_device_info_rsp(void) { uint8_t mac[6]; esp_read_mac(mac, ESP_MAC_WIFI_STA); uint8_t *p = s_rsp_buf; *p++ = 0xF0; *p++ = 0x7D; *p++ = RSP_DEVICE_INFO; /* version */ uint8_t vlen = (uint8_t)strlen(FW_VERSION); *p++ = vlen; memcpy(p, FW_VERSION, vlen); p += vlen; /* name */ uint8_t nlen = (uint8_t)strlen(DEVICE_NAME); *p++ = nlen; memcpy(p, DEVICE_NAME, nlen); p += nlen; /* MAC (6 bytes, each masked to 7-bit halves) */ for (int i = 0; i < 6; i++) { *p++ = mac[i] & 0x7F; *p++ = (mac[i] >> 7) & 0x01; } *p++ = 0xF7; s_rsp_len = (uint16_t)(p - s_rsp_buf); } static void build_wifi_status_rsp(void) { uint8_t state; uint32_t ip; wifi_transport_get_sta_state(&state, &ip); uint8_t *p = s_rsp_buf; *p++ = 0xF0; *p++ = 0x7D; *p++ = RSP_WIFI_STATUS; *p++ = state; if (state == WIFI_STATE_CONNECTED) { *p++ = (ip >> 0) & 0x7F; *p++ = (ip >> 7) & 0x7F; *p++ = (ip >> 14) & 0x7F; *p++ = (ip >> 21) & 0x7F; *p++ = (ip >> 28) & 0x0F; } *p++ = 0xF7; s_rsp_len = (uint16_t)(p - s_rsp_buf); } /* -- command processing -- */ static void process_command(const uint8_t *data, uint16_t len) { if (len < 4 || data[0] != 0xF0 || data[1] != 0x7D) return; uint16_t end = 0; for (uint16_t i = 2; i < len; i++) { if (data[i] == 0xF7) { end = i; break; } } if (!end) return; uint8_t cmd = data[2]; switch (cmd) { case CMD_GET_WIFI_CFG: build_wifi_cfg_rsp(); break; case CMD_SET_WIFI_STA: { if (end < 6) { build_set_result(0); break; } uint8_t ssid_len = data[3]; if (3 + 1 + ssid_len + 1 >= end) { build_set_result(0); break; } char ssid[WIFI_CFG_SSID_MAX] = {0}; memcpy(ssid, &data[4], ssid_len); uint8_t pass_len = data[4 + ssid_len]; char pass[WIFI_CFG_PASS_MAX] = {0}; if (pass_len > 0 && (4 + ssid_len + 1 + pass_len) <= end) memcpy(pass, &data[5 + ssid_len], pass_len); esp_err_t err = wifi_cfg_set_sta(ssid, pass); if (err == ESP_OK) err = wifi_transport_reconnect_sta(ssid, pass); build_set_result(err == ESP_OK); break; } case CMD_SET_WIFI_AP: { if (end < 6) { build_set_result(0); break; } uint8_t ssid_len = data[3]; if (3 + 1 + ssid_len + 1 >= end) { build_set_result(0); break; } char ssid[WIFI_CFG_SSID_MAX] = {0}; memcpy(ssid, &data[4], ssid_len); uint8_t pass_len = data[4 + ssid_len]; char pass[WIFI_CFG_PASS_MAX] = {0}; if (pass_len > 0 && (4 + ssid_len + 1 + pass_len) <= end) memcpy(pass, &data[5 + ssid_len], pass_len); esp_err_t err = wifi_cfg_set_ap(ssid, pass); build_set_result(err == ESP_OK); break; } case CMD_GET_DEVICE_INFO: build_device_info_rsp(); break; case CMD_WIFI_STATUS: build_wifi_status_rsp(); break; default: return; } } /* -- GATT -- */ static int chr_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { (void)conn_handle; (void)attr_handle; (void)arg; switch (ctxt->op) { case BLE_GATT_ACCESS_OP_READ_CHR: if (s_rsp_len > 0) os_mbuf_append(ctxt->om, s_rsp_buf, s_rsp_len); return 0; case BLE_GATT_ACCESS_OP_WRITE_CHR: { uint8_t buf[RSP_BUF_MAX]; uint16_t out_len = 0; uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om); if (om_len > sizeof(buf)) om_len = sizeof(buf); ble_hs_mbuf_to_flat(ctxt->om, buf, sizeof(buf), &out_len); process_command(buf, out_len); return 0; } default: return BLE_ATT_ERR_UNLIKELY; } } static const struct ble_gatt_svc_def gatt_svr_svcs[] = { { .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &svc_uuid.u, .characteristics = (struct ble_gatt_chr_def[]) { { .uuid = &chr_uuid.u, .access_cb = chr_access_cb, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE, .val_handle = &s_chr_handle, }, { 0 }, }, }, { 0 }, }; static int gatt_svr_init(void) { ble_svc_gap_init(); ble_svc_gatt_init(); int rc = ble_gatts_count_cfg(gatt_svr_svcs); if (rc != 0) return rc; return ble_gatts_add_svcs(gatt_svr_svcs); } /* -- GAP -- */ static int gap_event_cb(struct ble_gap_event *event, void *arg) { (void)arg; switch (event->type) { case BLE_GAP_EVENT_CONNECT: if (event->connect.status != 0) start_advertise(); return 0; case BLE_GAP_EVENT_DISCONNECT: if (s_ble_active) start_advertise(); return 0; case BLE_GAP_EVENT_ADV_COMPLETE: if (s_ble_active) start_advertise(); return 0; default: return 0; } } static void start_advertise(void) { struct ble_hs_adv_fields fields = {0}; fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; fields.tx_pwr_lvl_is_present = 1; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; const char *name = ble_svc_gap_device_name(); fields.name = (uint8_t *)name; fields.name_len = strlen(name); fields.name_is_complete = 1; ble_gap_adv_set_fields(&fields); struct ble_gap_adv_params params = {0}; params.conn_mode = BLE_GAP_CONN_MODE_UND; params.disc_mode = BLE_GAP_DISC_MODE_GEN; ble_gap_adv_start(s_own_addr_type, NULL, BLE_HS_FOREVER, ¶ms, gap_event_cb, NULL); } static void on_sync(void) { ble_hs_util_ensure_addr(0); ble_hs_id_infer_auto(0, &s_own_addr_type); start_advertise(); printf("BLE: advertising\n"); } static void on_reset(int reason) { printf("BLE: host reset, reason=%d\n", reason); } static void ble_host_task(void *param) { (void)param; nimble_port_run(); nimble_port_freertos_deinit(); } /* -- public API -- */ esp_err_t ble_prov_start(void) { if (s_ble_active) return ESP_OK; esp_err_t err = nimble_port_init(); if (err != ESP_OK) { printf("BLE: nimble_port_init failed: %d\n", (int)err); return err; } ble_hs_cfg.reset_cb = on_reset; ble_hs_cfg.sync_cb = on_sync; ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO; int rc = gatt_svr_init(); if (rc != 0) { printf("BLE: GATT init failed: %d\n", rc); nimble_port_deinit(); return ESP_FAIL; } ble_svc_gap_device_name_set(DEVICE_NAME); nimble_port_freertos_init(ble_host_task); s_ble_active = true; s_rsp_len = 0; printf("BLE: provisioning started\n"); return ESP_OK; } esp_err_t ble_prov_stop(void) { if (!s_ble_active) return ESP_OK; s_ble_active = false; if (ble_gap_adv_active()) ble_gap_adv_stop(); nimble_port_stop(); nimble_port_deinit(); printf("BLE: provisioning stopped\n"); return ESP_OK; } bool ble_prov_is_active(void) { return s_ble_active; }