#include "provision.h" #include "spaxel.h" #include "esp_log.h" #include "nvs_flash.h" #include "nvs.h" #include "cJSON.h" #include "driver/uart.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include static const char *TAG = "provision"; #define PROVISION_UART UART_NUM_0 #define PROVISION_BAUD_RATE 115200 #define PROVISION_WINDOW_MS 10000 #define UART_RX_BUF_SIZE 1024 #define MAX_LINE_LEN 768 void provision_listen_window(void) { uart_config_t uart_cfg = { .baud_rate = PROVISION_BAUD_RATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; if (uart_param_config(PROVISION_UART, &uart_cfg) != ESP_OK) { ESP_LOGW(TAG, "UART param config failed, skipping provision window"); return; } if (uart_driver_install(PROVISION_UART, UART_RX_BUF_SIZE, 0, 0, NULL, 0) != ESP_OK) { ESP_LOGW(TAG, "UART driver install failed, skipping provision window"); return; } char mac_str[18]; mac_to_str(g_state.mac, mac_str, sizeof(mac_str)); // Signal that firmware is ready for provisioning const char *ready = "SPAXEL READY\n"; uart_write_bytes(PROVISION_UART, ready, strlen(ready)); ESP_LOGI(TAG, "Provisioning window open for %d ms (MAC: %s)", PROVISION_WINDOW_MS, mac_str); TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(PROVISION_WINDOW_MS); char line[MAX_LINE_LEN]; int line_pos = 0; while (xTaskGetTickCount() < deadline) { uint8_t ch; int n = uart_read_bytes(PROVISION_UART, &ch, 1, pdMS_TO_TICKS(50)); if (n <= 0) { continue; } if (ch == '\r') { continue; // ignore CR } if (ch == '\n') { line[line_pos] = '\0'; line_pos = 0; if (strlen(line) == 0) { continue; } cJSON *root = cJSON_Parse(line); if (!root) { const char *err_resp = "{\"ok\":false,\"error\":\"invalid_json\"}\n"; uart_write_bytes(PROVISION_UART, err_resp, strlen(err_resp)); continue; } cJSON *prov = cJSON_GetObjectItem(root, "provision"); if (!prov) { cJSON_Delete(root); const char *err_resp = "{\"ok\":false,\"error\":\"missing_provision_key\"}\n"; uart_write_bytes(PROVISION_UART, err_resp, strlen(err_resp)); continue; } esp_err_t err = provision_write_nvs(prov); cJSON_Delete(root); if (err == ESP_OK) { char resp[80]; snprintf(resp, sizeof(resp), "{\"ok\":true,\"mac\":\"%s\"}\n", mac_str); uart_write_bytes(PROVISION_UART, resp, strlen(resp)); ESP_LOGI(TAG, "Provisioning complete via serial"); uart_driver_delete(PROVISION_UART); return; } else { const char *err_resp = "{\"ok\":false,\"error\":\"nvs_write_failed\"}\n"; uart_write_bytes(PROVISION_UART, err_resp, strlen(err_resp)); } } else if (line_pos < MAX_LINE_LEN - 1) { line[line_pos++] = (char)ch; } else { // Line too long — flush buffer line_pos = 0; } } ESP_LOGI(TAG, "Provisioning window closed (no provisioning received)"); uart_driver_delete(PROVISION_UART); } esp_err_t provision_write_nvs(cJSON *prov) { nvs_handle_t nvs; esp_err_t err = nvs_open(SPAXEL_NAMESPACE, NVS_READWRITE, &nvs); if (err != ESP_OK) { ESP_LOGE(TAG, "NVS open failed: %s", esp_err_to_name(err)); return err; } cJSON *ssid = cJSON_GetObjectItem(prov, "wifi_ssid"); if (ssid && cJSON_IsString(ssid) && strlen(ssid->valuestring) > 0) { nvs_set_str(nvs, NVS_KEY_WIFI_SSID, ssid->valuestring); } else { nvs_close(nvs); return ESP_ERR_INVALID_ARG; } cJSON *pass = cJSON_GetObjectItem(prov, "wifi_pass"); if (pass && cJSON_IsString(pass)) { nvs_set_str(nvs, NVS_KEY_WIFI_PASS, pass->valuestring); } cJSON *node_id = cJSON_GetObjectItem(prov, "node_id"); if (node_id && cJSON_IsString(node_id)) { nvs_set_str(nvs, NVS_KEY_NODE_ID, node_id->valuestring); } cJSON *token = cJSON_GetObjectItem(prov, "node_token"); if (token && cJSON_IsString(token)) { nvs_set_str(nvs, NVS_KEY_NODE_TOKEN, token->valuestring); } cJSON *mdns_name = cJSON_GetObjectItem(prov, "ms_mdns"); if (mdns_name && cJSON_IsString(mdns_name)) { nvs_set_str(nvs, NVS_KEY_MS_MDNS, mdns_name->valuestring); } cJSON *port = cJSON_GetObjectItem(prov, "ms_port"); if (port && cJSON_IsNumber(port) && port->valueint > 0) { nvs_set_u16(nvs, NVS_KEY_MS_PORT, (uint16_t)port->valueint); } cJSON *debug_flag = cJSON_GetObjectItem(prov, "debug"); if (debug_flag) { nvs_set_u8(nvs, NVS_KEY_DEBUG, cJSON_IsTrue(debug_flag) ? 1 : 0); } cJSON *ntp_server = cJSON_GetObjectItem(prov, "ntp_server"); if (ntp_server && cJSON_IsString(ntp_server)) { nvs_set_str(nvs, NVS_KEY_NTP_SERVER, ntp_server->valuestring); } nvs_set_u8(nvs, NVS_KEY_PROVISIONED, 1); nvs_set_u8(nvs, NVS_KEY_SCHEMA_VER, NVS_SCHEMA_VERSION); err = nvs_commit(nvs); nvs_close(nvs); if (err == ESP_OK) { g_state.provisioned = true; } return err; }