diff --git a/.gitignore b/.gitignore index caa347ce..02b736f9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,13 @@ components/wifi-manager/UML-State-Machine-in-C envfile.txt artifacts web-installer + +squeezelite-esp32.code-workspace + +esp-idf-vscode-generated.gdb + +debug.log + +components/wifi-manager/esp32_improv.cpp.txt + +components/wifi-manager/esp32_improv.h.txt diff --git a/components/platform_console/CMakeLists.txt b/components/platform_console/CMakeLists.txt index 5a315a3e..1d653ecc 100644 --- a/components/platform_console/CMakeLists.txt +++ b/components/platform_console/CMakeLists.txt @@ -5,6 +5,7 @@ idf_component_register( SRCS cmd_system.c cmd_wifi.c platform_console.c + improv_console.c cmd_config.c INCLUDE_DIRS . REQUIRES nvs_flash diff --git a/components/platform_console/cmd_wifi.c b/components/platform_console/cmd_wifi.c index 048a1cf5..1655292d 100644 --- a/components/platform_console/cmd_wifi.c +++ b/components/platform_console/cmd_wifi.c @@ -33,6 +33,7 @@ #include "esp_netif.h" #include "esp_event.h" #include "led.h" +#include "improv.h" extern bool bypass_network_manager; #define JOIN_TIMEOUT_MS (10000) #include "platform_console.h" @@ -48,6 +49,15 @@ static struct { struct arg_str *password; struct arg_end *end; } join_args; +static struct { + struct arg_lit * conect; + struct arg_lit * state; + struct arg_lit * info; + struct arg_lit * list; + struct arg_str *ssid; + struct arg_str *password; + struct arg_end *end; +} improv_args; @@ -185,6 +195,44 @@ static int connect(int argc, char **argv) return 0; } +extern bool on_improv_command(ImprovCommandStruct_t *command); // command callback +static int do_improv(int argc, char **argv) +{ + ImprovCommandStruct_t command; + int nerrors = arg_parse_msg(argc, argv,(struct arg_hdr **)&improv_args); + if (nerrors != 0) { + return 1; + } + if(improv_args.info->count>0){ + memset(&command,0x00,sizeof(command)); + command.command = IMPROV_CMD_GET_DEVICE_INFO; + on_improv_command(&command); + } + if(improv_args.conect->count>0){ + if(improv_args.ssid->count == 0){ + ESP_LOGE(__func__,"Parameter ssid is required to connect"); + return 1; + } + command.ssid = improv_args.ssid->sval[0]; + if(improv_args.password->count == 0){ + command.password= improv_args.password->sval[0]; + } + command.command = IMPROV_CMD_WIFI_SETTINGS; + on_improv_command(&command); + } + if(improv_args.state->count>0){ + memset(&command,0x00,sizeof(command)); + command.command = IMPROV_CMD_GET_CURRENT_STATE; + on_improv_command(&command); + } + if(improv_args.list->count>0){ + memset(&command,0x00,sizeof(command)); + command.command = IMPROV_CMD_GET_WIFI_NETWORKS; + on_improv_command(&command); + } + + return 0; +} void register_wifi_join() { join_args.timeout = arg_int0(NULL, "timeout", "", "Connection timeout, ms"); @@ -201,10 +249,28 @@ void register_wifi_join() }; ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) ); } +static void register_improv_debug(){ + improv_args.conect = arg_lit0(NULL,"connect","Connects to the specified wifi ssid and password"); + improv_args.ssid = arg_str0(NULL, NULL, "", "SSID of AP"); + improv_args.password = arg_str0(NULL, NULL, "", "Password of AP"); + improv_args.info = arg_lit0(NULL,"info","Request the info packet"); + improv_args.list = arg_lit0(NULL,"list","Request the wifi list packet"); + improv_args.state = arg_lit0(NULL,"state","Requests the state packet"); + improv_args.end = arg_end(2); + const esp_console_cmd_t improv_cmd = { + .command = "improv", + .help = "Send an improv-wifi serial command to the system", + .hint = NULL, + .func = &do_improv, + .argtable = &improv_args + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&improv_cmd) ); +} void register_wifi() { register_wifi_join(); + register_improv_debug(); if(bypass_network_manager){ initialise_wifi(); } diff --git a/components/platform_console/improv_console.c b/components/platform_console/improv_console.c new file mode 100644 index 00000000..bd96fa49 --- /dev/null +++ b/components/platform_console/improv_console.c @@ -0,0 +1,162 @@ +#include "platform_console.h" + +#include +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "pthread.h" +#include "platform_esp32.h" +#include "cmd_decl.h" +#include "trace.h" +#include "platform_config.h" +#include "telnet.h" +#include "tools.h" +#include "improv.h" +#include "messaging.h" +#include "config.h" +#include "improv_console.h" +#include "network_status.h" + +static const char * TAG ="improv_console"; +const time_t improv_timeout_ms = 50; +TickType_t improv_timeout_tick = pdMS_TO_TICKS(improv_timeout_ms); +ImprovState_t improv_state = IMPROV_STATE_READY_AUTHORIZED; +const size_t improv_buffer_size = 121; +size_t improv_buffer_len = 0; +uint8_t * improv_buffer_data = NULL; +TickType_t improv_delay = portMAX_DELAY; + + +void cb_improv_got_ip(nm_state_t new_state, int sub_state){ + if(improv_state == IMPROV_STATE_PROVISIONING){ + char * url = network_status_alloc_get_system_url(); + ESP_LOGI(TAG,"Signaling improv state connected state with url: %s",STR_OR_BLANK(url)); + improv_send_device_url(IMPROV_CMD_WIFI_SETTINGS,url); + FREE_AND_NULL(url); + } +} +void cb_improv_disconnected(nm_state_t new_state, int sub_state){ + if(improv_state == IMPROV_STATE_PROVISIONING){ + ESP_LOGI(TAG,"Signalling improv connect failure "); + improv_state = IMPROV_STATE_READY_AUTHORIZED; + improv_send_error(IMPROV_ERROR_UNABLE_TO_CONNECT); + } + +} +bool on_improv_command(ImprovCommandStruct_t *command){ + esp_err_t err = ESP_OK; + wifi_connect_state_t wifi_state = network_wifi_get_connect_state(); + const esp_app_desc_t* desc = esp_ota_get_app_description(); + improv_buffer_len = 0; + char * url=NULL; + char * host_name = NULL; + ESP_LOGI(TAG, "Processing improv command %s",improv_get_command_desc(command->command)); + if(!command){ + return false; + } + switch (command->command) + { + case IMPROV_CMD_WIFI_SETTINGS: + // attempt to connect to the provided SSID+password + improv_state = IMPROV_STATE_PROVISIONING; + ESP_LOGI(TAG,"Improv connect to %s",command->ssid ); + network_async_connect(command->ssid, command->password); + FREE_AND_NULL(command->ssid); + FREE_AND_NULL(command->password); + + break; + case IMPROV_CMD_GET_CURRENT_STATE: + if(wifi_state !=NETWORK_WIFI_STATE_CONNECTING){ + network_async_scan(); + } + switch (wifi_state) + { + case NETWORK_WIFI_STATE_CONNECTING: + ESP_LOGI(TAG,"Signaling improv state " ); + return improv_send_current_state(improv_state); + break; + case NETWORK_WIFI_STATE_INVALID_CONFIG: + improv_state = IMPROV_STATE_READY_AUTHORIZED; + ESP_LOGW(TAG,"Signaling improv state IMPROV_ERROR_UNABLE_TO_CONNECT" ); + return improv_send_error(IMPROV_ERROR_UNABLE_TO_CONNECT); + break; + case NETWORK_WIFI_STATE_FAILED: + ESP_LOGW(TAG,"Signaling improv state IMPROV_ERROR_NOT_AUTHORIZED" ); + network_async_scan(); + improv_state = IMPROV_STATE_READY_AUTHORIZED; + return improv_send_error(IMPROV_ERROR_NOT_AUTHORIZED); + break; + case NETWORK_WIFI_STATE_CONNECTED: + network_async_scan(); + url = network_status_alloc_get_system_url(); + ESP_LOGI(TAG,"Signaling improv state connected state with url: %s",STR_OR_BLANK(url)); + improv_state = IMPROV_STATE_PROVISIONED; + improv_send_current_state(improv_state); + // also send url + improv_send_device_url(IMPROV_CMD_GET_CURRENT_STATE,url); + FREE_AND_NULL(url); + break; + default: + ESP_LOGI(TAG,"Signaling improv state " ); + return improv_send_current_state(improv_state); + break; + } + break; + case IMPROV_CMD_GET_DEVICE_INFO: + ESP_LOGI(TAG,"Signaling improv with device info. Firmware Name: %s, Version: %s ",desc->project_name,desc->version ); + host_name = config_alloc_get_str("host_name",NULL,"Squeezelite"); + improv_send_device_info(desc->project_name,desc->version,"ESP32",host_name); + FREE_AND_NULL(host_name); + break; + case IMPROV_CMD_GET_WIFI_NETWORKS: + ESP_LOGI(TAG,"Signaling improv with list of wifi networks " ); + improv_wifi_list_send(); + break; + default: + ESP_LOGE(TAG,"Signaling improv with invalid RPC call received" ); + improv_send_error(IMPROV_ERROR_INVALID_RPC); + break; + } + return false; +} +void on_improv_error(ImprovError_t error){ + improv_send_error(error); + ESP_LOGE(TAG,"Error processing improv-wifi packet : %s", improv_get_error_desc(error)); + +} + +#if BUFFER_DEBUG +void dump_buffer(const char * prefix, const char * buff, size_t len){ + + printf("\n%s (%d): ",prefix, len); + for(int i=0;i +#include +#define BUFFER_DEBUG 0 + + +extern TickType_t improv_timeout_tick; +extern const size_t improv_buffer_size; +extern size_t improv_buffer_len; +extern uint8_t * improv_buffer_data; +extern const time_t improv_timeout_ms; +extern TickType_t improv_delay; + + +void cb_improv_got_ip(nm_state_t new_state, int sub_state); +bool on_improv_command(ImprovCommandStruct_t *command); +void on_improv_error(ImprovError_t error); +void dump_buffer(const char * prefix, const char * buff, size_t len); +bool improv_send_callback(uint8_t * buffer, size_t length); +void improv_console_init(); \ No newline at end of file diff --git a/components/platform_console/platform_console.c b/components/platform_console/platform_console.c index ee9ace26..e2224084 100644 --- a/components/platform_console/platform_console.c +++ b/components/platform_console/platform_console.c @@ -27,17 +27,18 @@ #include "platform_config.h" #include "telnet.h" #include "tools.h" - +#include "improv.h" #include "messaging.h" - +#include "network_manager.h" #include "config.h" +#include "improv_console.h" static pthread_t thread_console; static void * console_thread(); void console_start(); static const char * TAG = "console"; extern bool bypass_network_manager; extern void register_squeezelite(); - +bool improv=false; static EXT_RAM_ATTR QueueHandle_t uart_queue; static EXT_RAM_ATTR struct { uint8_t _buf[512]; @@ -249,31 +250,77 @@ void process_autoexec(){ } } + +#define BUFFERDEBUG 0 static ssize_t stdin_read(int fd, void* data, size_t size) { size_t bytes = -1; + uint32_t improv_next_timeout = 0; + if(!improv_buffer_data){ + improv_buffer_data = (uint8_t * )malloc_init_external(improv_buffer_size); + memset(improv_buffer_data,0x00,improv_buffer_size); + improv_set_send_callback(improv_send_callback); + } + size_t read_size = 0; while (1) { - QueueSetMemberHandle_t activated = xQueueSelectFromSet(stdin_redir.queue_set, portMAX_DELAY); - - if (activated == uart_queue) { - uart_event_t event; - - xQueueReceive(uart_queue, &event, 0); - - if (event.type == UART_DATA) { - bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, data, size < event.size ? size : event.size, 0); - // we have to do our own line ending translation here - for (int i = 0; i < bytes; i++) if (((char*)data)[i] == '\r') ((char*)data)[i] = '\n'; - break; - } - } else if (xRingbufferCanRead(stdin_redir.handle, activated)) { + QueueSetMemberHandle_t activated = xQueueSelectFromSet(stdin_redir.queue_set, improv_delay); + uint32_t now = esp_timer_get_time() / 1000; uart_event_t event; + xQueueReceive(uart_queue, &event, 0); + //esp_rom_printf("."); + //esp_rom_printf("\n********Activated: 0x%6X, type: %d\n",(unsigned int)activated, event.type); + + if (event.type == UART_DATA) { + //esp_rom_printf("uart."); + #if BUFFERDEBUG + printf("\n********event: %d, read: %d\n", event.size,bytes); + #endif + bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, improv_buffer_data+improv_buffer_len,1, 0); + while(bytes>0){ + //esp_rom_printf("rb[%c]\n",*(bufferdata+buffer_len)); + improv_buffer_len++; + if(!improv_parse_serial_byte(improv_buffer_len-1,improv_buffer_data[improv_buffer_len-1],improv_buffer_data,on_improv_command,on_improv_error)){ + #if BUFFERDEBUG + //dump_buffer("improv invalid",(const char *)bufferdata,buffer_len); + #endif + if(improv_buffer_len>0){ + //esp_rom_printf("not improv\n"); + xRingbufferSend(stdin_redir.handle, improv_buffer_data,improv_buffer_len, pdMS_TO_TICKS(100)); + } + improv_buffer_len=0; + } + bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, improv_buffer_data+improv_buffer_len,1, 0); + } + improv_next_timeout = esp_timer_get_time() / 1000+improv_timeout_ms; + #if BUFFERDEBUG + //dump_buffer("after event",(const char *)bufferdata,buffer_len); + #endif + } + if ( xRingbufferCanRead(stdin_redir.handle, activated)) { + //esp_rom_printf("\n********rbr!\n"); char *p = xRingbufferReceiveUpTo(stdin_redir.handle, &bytes, 0, size); // we might receive strings, replace null by \n + #if BUFFERDEBUG + //dump_buffer("Ringbuf read",p,bytes); + #endif for (int i = 0; i < bytes; i++) if (p[i] == '\0' || p[i] == '\r') p[i] = '\n'; memcpy(data, p, bytes); vRingbufferReturnItem(stdin_redir.handle, p); break; } + if(improv_buffer_len>0){ + improv_delay = improv_timeout_tick; + } + else { + improv_delay = portMAX_DELAY; + } + if (now > improv_next_timeout && improv_buffer_len > 0) { + #if BUFFERDEBUG + //dump_buffer("improv timeout",(const char *)bufferdata,buffer_len); + #endif + //esp_rom_printf("\n********QueueSent\n"); + xRingbufferSendFromISR(stdin_redir.handle, improv_buffer_data, improv_buffer_len, pdMS_TO_TICKS(100)); + improv_buffer_len = 0; + } } return bytes; @@ -304,6 +351,9 @@ void initialize_console() { /* re-direct stdin to our own driver so we can gather data from various sources */ stdin_redir.queue_set = xQueueCreateSet(2); + if(!stdin_redir.queue_set){ + ESP_LOGE(TAG,"Serial event queue set could not be created"); + } stdin_redir.handle = xRingbufferCreateStatic(sizeof(stdin_redir._buf), RINGBUF_TYPE_BYTEBUF, stdin_redir._buf, &stdin_redir._ringbuf); xRingbufferAddToQueueSetRead(stdin_redir.handle, stdin_redir.queue_set); xQueueAddToSet(uart_queue, stdin_redir.queue_set); @@ -312,7 +362,6 @@ void initialize_console() { vfs.flags = ESP_VFS_FLAG_DEFAULT; vfs.open = stdin_dummy; vfs.read = stdin_read; - ESP_ERROR_CHECK(esp_vfs_register("/dev/console", &vfs, NULL)); freopen("/dev/console", "r", stdin); @@ -352,6 +401,7 @@ bool console_push(const char *data, size_t size) { void console_start() { /* we always run console b/c telnet sends commands to stdin */ initialize_console(); + improv_console_init(); /* Register commands */ MEMTRACE_PRINT_DELTA_MESSAGE("Registering help command"); @@ -449,6 +499,7 @@ static esp_err_t run_command(char * line){ } static void * console_thread() { + if(!is_recovery_running){ MEMTRACE_PRINT_DELTA_MESSAGE("Running autoexec"); process_autoexec(); diff --git a/components/wifi-manager/CMakeLists.txt b/components/wifi-manager/CMakeLists.txt index c65e47fb..7c6bb4c3 100644 --- a/components/wifi-manager/CMakeLists.txt +++ b/components/wifi-manager/CMakeLists.txt @@ -3,8 +3,8 @@ set( WEBPACK_DIR webapp/webpack/dist ) idf_component_register( SRC_DIRS . webapp UML-State-Machine-in-C/src INCLUDE_DIRS . webapp UML-State-Machine-in-C/src - REQUIRES squeezelite-ota json mdns - PRIV_REQUIRES tools services platform_config esp_common json newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console esp_http_server console driver_bt + REQUIRES squeezelite-ota json mdns bt + PRIV_REQUIRES tools services platform_config esp_common json newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console esp_http_server console driver_bt ) include(webapp/webapp.cmake) \ No newline at end of file diff --git a/components/wifi-manager/improv.c b/components/wifi-manager/improv.c new file mode 100644 index 00000000..fa0c717e --- /dev/null +++ b/components/wifi-manager/improv.c @@ -0,0 +1,431 @@ +#include "esp_system.h" +#include "esp_log.h" +#include "improv.h" +#include "tools.h" +#include "string.h" +static ImprovCommandStruct_t last_command; + +static callback_table_t callbacks[] = { + {IMPROV_CMD_UNKNOWN, NULL}, + {IMPROV_CMD_WIFI_SETTINGS, NULL}, + {IMPROV_CMD_GET_CURRENT_STATE, NULL}, + {IMPROV_CMD_GET_DEVICE_INFO, NULL}, + {IMPROV_CMD_GET_WIFI_NETWORKS, NULL}, + {IMPROV_CMD_BAD_CHECKSUM, NULL}, + {-1, NULL}}; +const char *improv_get_error_desc(ImprovError_t error) +{ + switch (error) + { + ENUM_TO_STRING(IMPROV_ERROR_NONE) + ENUM_TO_STRING(IMPROV_ERROR_INVALID_RPC) + ENUM_TO_STRING(IMPROV_ERROR_UNKNOWN_RPC) + ENUM_TO_STRING(IMPROV_ERROR_UNABLE_TO_CONNECT) + ENUM_TO_STRING(IMPROV_ERROR_NOT_AUTHORIZED) + ENUM_TO_STRING(IMPROV_ERROR_UNKNOWN) + } + return ""; +} +const char *improv_get_command_desc(ImprovCommand_t command) +{ + switch (command) + { + ENUM_TO_STRING(IMPROV_CMD_UNKNOWN) + ENUM_TO_STRING(IMPROV_CMD_WIFI_SETTINGS) + ENUM_TO_STRING(IMPROV_CMD_GET_CURRENT_STATE) + ENUM_TO_STRING(IMPROV_CMD_GET_DEVICE_INFO) + ENUM_TO_STRING(IMPROV_CMD_GET_WIFI_NETWORKS) + ENUM_TO_STRING(IMPROV_CMD_BAD_CHECKSUM) + } + return ""; +}; +static improv_send_callback_t send_callback = NULL; +const uint8_t improv_prefix[] = {'I', 'M', 'P', 'R', 'O', 'V', IMPROV_SERIAL_VERSION}; +typedef struct __attribute__((__packed__)) +{ + uint8_t prefix[6]; + uint8_t version; + uint8_t packet_type; + uint8_t data_len; +} improv_packet_t; +#define PACKET_CHECKSUM_SIZE sizeof(uint8_t) +#define PACKET_PAYLOAD(packet) ((uint8_t *)packet) + sizeof(improv_packet_t) + +static ImprovAPListStruct_t *ap_list = NULL; +static size_t ap_list_size = 0; +static size_t ap_list_actual = 0; +void improv_wifi_list_free() +{ + ap_list_actual = 0; + ImprovAPListStruct_t *current = ap_list; + for (int i = 0; i < ap_list_actual; i++) + { + if (!current) + { + break; + } + FREE_AND_NULL(current->rssi); + FREE_AND_NULL(current->ssid); + FREE_AND_NULL(current->auth_req); + current++; + } + FREE_AND_NULL(ap_list); +} +bool improv_wifi_list_allocate(size_t num_entries) +{ + improv_wifi_list_free(); + ap_list = malloc_init_external(num_entries * sizeof(ImprovAPListStruct_t) + 1); // last byte will always be null + ap_list_size = num_entries; + ap_list_actual = 0; + return ap_list != NULL; +} + +bool improv_wifi_list_add(const char *ssid, int8_t rssi, bool auth_req) +{ + const size_t yes_no_length = 4; + ImprovAPListStruct_t *current = ap_list + ap_list_actual; + if (ap_list_actual > ap_list_size || !current) + { + return false; + } + current->ssid = strdup_psram(ssid); + size_t length = snprintf(NULL, 0, "%02d", rssi) + 1; + current->auth_req = malloc_init_external(yes_no_length); // enough for YES/NO to fit + current->rssi = (char *)malloc_init_external(length); + if (!current->rssi || !current->auth_req) + { + return false; + } + snprintf(current->rssi, length, "%02d", rssi); + snprintf(current->auth_req, yes_no_length, "%s", auth_req ? "YES" : "NO"); + ap_list_actual++; + return true; +} + +void improv_parse_data(ImprovCommandStruct_t *improv_command, const uint8_t *data, size_t length, bool check_checksum) +{ + + ImprovCommand_t command = (ImprovCommand_t)data[0]; + uint8_t data_length = data[1]; + + if (data_length != length - 2 - check_checksum) + { + improv_command->command = IMPROV_CMD_UNKNOWN; + return; + } + + if (check_checksum) + { + uint8_t checksum = data[length - 1]; + + uint32_t calculated_checksum = 0; + for (uint8_t i = 0; i < length - 1; i++) + { + calculated_checksum += data[i]; + } + + if ((uint8_t)calculated_checksum != checksum) + { + improv_command->command = IMPROV_CMD_BAD_CHECKSUM; + return; + } + } + + if (command == IMPROV_CMD_WIFI_SETTINGS) + { + uint8_t ssid_length = data[2]; + uint8_t ssid_start = 3; + size_t ssid_end = ssid_start + ssid_length; + + uint8_t pass_length = data[ssid_end]; + size_t pass_start = ssid_end + 1; + size_t pass_end = pass_start + pass_length; + + improv_command->ssid = malloc(ssid_length + 1); + memset(improv_command->ssid, 0x00, ssid_length + 1); + memcpy(improv_command->ssid, &data[ssid_start], ssid_length); + improv_command->password = NULL; + if (pass_length > 0) + { + improv_command->password = malloc(pass_length + 1); + memset(improv_command->password, 0x00, pass_length + 1); + memcpy(improv_command->password, &data[pass_start], pass_length); + } + } + + improv_command->command = command; +} + +bool improv_parse_serial_byte(size_t position, uint8_t byte, const uint8_t *buffer, + improv_command_callback_t callback, on_error_callback_t on_error) +{ + ImprovCommandStruct_t command = {0}; + if (position < 7) + return byte == improv_prefix[position]; + if (position <= 8) + return true; + + uint8_t command_type = buffer[7]; + uint8_t data_len = buffer[8]; + + if (position <= 8 + data_len) + return true; + + if (position == 8 + data_len + 1) + { + uint8_t checksum = 0x00; + for (size_t i = 0; i < position; i++) + checksum += buffer[i]; + + if (checksum != byte) + { + on_error(IMPROV_ERROR_INVALID_RPC); + return false; + } + + if (command_type == IMPROV_PACKET_TYPE_RPC) + { + + improv_parse_data(&command, &buffer[9], data_len, false); + callback(&command); + } + } + + return false; +} + +void improv_set_send_callback(improv_send_callback_t callback) +{ + send_callback = callback; +} + +bool improv_set_callback(ImprovCommand_t command, improv_command_callback_t callback) +{ + callback_table_t *pCt = &callbacks; + while (pCt->index > -1) + { + if (pCt->index == command) + { + pCt->callback = callback; + return true; + } + } + return false; +} +bool improv_handle_callback(ImprovCommandStruct_t *command) +{ + const callback_table_t *pCt = &callbacks; + while (pCt->index > -1) + { + if (pCt->index == command->command) + { + return pCt->callback && pCt->callback(command); + } + } + return false; +} +bool improv_send_packet(uint8_t *packet, size_t msg_len) +{ + bool result = false; + if (send_callback && packet && msg_len > 0) + { + result = send_callback(packet, msg_len); + } + return result; +} +bool improv_send_byte(ImprovSerialType_t packet_type, uint8_t data) +{ + size_t msg_len; + uint8_t *packet = improv_build_response(packet_type, (const char *)&data, 1, &msg_len); + bool result = improv_send_packet(packet, msg_len); + FREE_AND_NULL(packet); + return result; +} + +bool improv_send_current_state(ImprovState_t state) +{ + return improv_send_byte(IMPROV_PACKET_TYPE_CURRENT_STATE, (uint8_t)state); +} +bool improv_send_error(ImprovError_t error) +{ + return improv_send_byte(IMPROV_PACKET_TYPE_ERROR_STATE, (uint8_t)error); +} +size_t improv_wifi_get_wifi_list_count(){ + return ap_list_actual; +} +bool improv_wifi_list_send() +{ + size_t msglen = 0; + bool result = true; + if (ap_list_actual == 0) + { + return false; + } + for (int i = 0; i < ap_list_actual && result; i++) + { + uint8_t *packet = improv_build_rpc_response(IMPROV_CMD_GET_WIFI_NETWORKS,(const char **) &ap_list[i], IMPROV_AP_STRUCT_NUM_STR, &msglen); + result = improv_send_packet(packet, msglen); + FREE_AND_NULL(packet); + } + uint8_t *packet = improv_build_rpc_response(IMPROV_CMD_GET_WIFI_NETWORKS, NULL, 0, &msglen); + result = improv_send_packet(packet, msglen); + FREE_AND_NULL(packet); + return result; +} +bool improv_send_device_url(ImprovCommand_t from_command, const char *url) +{ + size_t msglen = 0; + uint8_t *packet = NULL; + bool result = false; + if (url && strlen(url)) + { + packet = improv_build_rpc_response(from_command, &url, 1, &msglen); + if (!packet) + return false; + result = improv_send_packet(packet, msglen); + FREE_AND_NULL(packet); + } + packet = improv_build_rpc_response(from_command, "", 0, &msglen); + if (!packet) + return false; + result = improv_send_packet(packet, msglen); + + + + return result; +} +bool improv_send_device_info(const char *firmware_name, const char *firmware_version, const char *hardware_chip_variant, const char *device_name) +{ + ImprovDeviceInfoStruct_t device_info; + size_t msglen = 0; + device_info.device_name = device_name; + device_info.firmware_name = firmware_name; + device_info.firmware_version = firmware_version; + device_info.hardware_chip_variant = hardware_chip_variant; + device_info.nullptr = NULL; + uint8_t *packet = improv_build_rpc_response(IMPROV_CMD_GET_DEVICE_INFO, &device_info, IMPROV_DEVICE_INFO_NUM_STRINGS, &msglen); + if (!packet) + return false; + bool result = improv_send_packet(packet, msglen); + FREE_AND_NULL(packet); + return true; +} +bool parse_improv_serial_line(const uint8_t *buffer) +{ + const uint8_t *b = buffer; + const uint8_t *p = improv_prefix; + const uint8_t *data = NULL; + uint8_t checksum = 0x00; + uint8_t rec_checksum = 0x00; + + while (*p != '\0' && *b != '\0') + { + // check if line prefix matches the standard + if (*p++ != *b++) + { + return false; + } + } + + uint8_t command_type = *p++; + if (command_type == 0) + return false; + uint8_t data_len = *p++; + data = p; + rec_checksum = buffer[sizeof(improv_prefix) + data_len]; + for (size_t i = 0; i < sizeof(improv_prefix) + data_len; i++) + { + checksum += buffer[i]; + } + + if (checksum != rec_checksum) + { + improv_send_error(IMPROV_ERROR_INVALID_RPC); + return false; + } + + if (command_type == IMPROV_PACKET_TYPE_RPC) + { + improv_parse_data(&last_command, &data, data_len, false); + return improv_handle_callback(&last_command); + } + + return false; +} +// Improv packet format +// 1-6 Header will equal IMPROV +// 7 Version CURRENT VERSION = 1 +// 8 Type (see below) +// 9 Length +// 10...X Data +// X + 10 Checksum +improv_packet_t *improv_alloc_prefix(size_t data_len, ImprovSerialType_t packet_type, size_t *out_len) +{ + size_t buffer_len = sizeof(improv_packet_t) + data_len + 1; // one byte for checksum + if (out_len) + { + *out_len = buffer_len; + } + improv_packet_t *out = (improv_packet_t *)malloc_init_external(buffer_len + 1); + memcpy(out, improv_prefix, sizeof(improv_prefix)); + out->packet_type = (uint8_t)packet_type; + out->data_len = (uint8_t)data_len; + return out; +} +uint8_t improv_set_checksum(improv_packet_t *data, size_t buffer_len) +{ + uint32_t calculated_checksum = 0; + for (int b = 0; b < buffer_len - 1; b++) + { + calculated_checksum += ((uint8_t *)data)[b]; + } + calculated_checksum = calculated_checksum & 0xFF; + ((uint8_t *)data)[buffer_len - 1] = (uint8_t)calculated_checksum; + return calculated_checksum; +} +uint8_t *improv_build_response(ImprovSerialType_t packet_type, const char *datum, size_t len, size_t *out_len) +{ + size_t buffer_len = 0; + improv_packet_t *improv_packet = improv_alloc_prefix(len, packet_type, &buffer_len); + if (out_len) + { + *out_len = buffer_len; + } + uint8_t *p = PACKET_PAYLOAD(improv_packet); + for (int i = 0; i < len; i++) + { + *p++ = datum[i]; // string 1 + } + improv_set_checksum(improv_packet, buffer_len); + return (uint8_t *)improv_packet; +} +uint8_t *improv_build_rpc_response(ImprovCommand_t command, const char **results, size_t num_strings, size_t *out_len) +{ + size_t buffer_len = 0; + size_t total_string_len = 0; + size_t string_buffer_len = 0; + for (int i = 0; i < num_strings && (results[i] && (results[i])[0] != '\0'); i++) + { + size_t l = strlen(results[i]); + total_string_len += l; + string_buffer_len += l + 1; // length of the string plus byte for length + } + + improv_packet_t *improv_packet = improv_alloc_prefix(string_buffer_len + 2, IMPROV_PACKET_TYPE_RPC_RESPONSE, &buffer_len); // 2 bytes for command and length of all strings + if (out_len) + { + *out_len = buffer_len; + } + uint8_t *p = PACKET_PAYLOAD(improv_packet); + *p++ = (uint8_t)command; // command being responded to + *p++ = (uint8_t)string_buffer_len; // + for (int i = 0; i < num_strings && results[i]; i++) + { + uint8_t curlel = (uint8_t)strlen(results[i]); + *p++ = curlel; + memcpy(p, results[i], curlel); + p += curlel; + } + improv_set_checksum(improv_packet, buffer_len); + return (uint8_t *)improv_packet; +} diff --git a/components/wifi-manager/improv.h b/components/wifi-manager/improv.h new file mode 100644 index 00000000..fc327da1 --- /dev/null +++ b/components/wifi-manager/improv.h @@ -0,0 +1,279 @@ +#pragma once +// This is the description of the Improv Wi-Fi protocol using a serial port. +// The device needs to be connected to the computer via a USB/UART serial port. +// The protocol has two actors: the Improv service running on the gadget and the Improv client. +// The Improv service will receive Wi-Fi credentials from the client via the serial connection. +// The Improv client asks for the current state and sends the Wi-Fi credentials. + +// ========================================================================================= +// Packet format +// ====================================== +// Byte Purpose +// ---- ------------------------------- +// 1-6 Header will equal IMPROV +// 7 Version CURRENT VERSION = 1 +// 8 Type (see below) +// 9 Length +// 10...X Data +// X + 10 Checksum + +// ========================================================================================= +// Packet types +// ====================================== +// Type Description Direction +// ---- ------------ ----------------- +// 0x01 Current state Device to Client +// 0x02 Error state Device to Client +// 0x03 RPC Command Device to Client +// 0x04 RPC Result Client to Device +typedef enum { + IMPROV_PACKET_TYPE_CURRENT_STATE = 0x01, + IMPROV_PACKET_TYPE_ERROR_STATE = 0x02, + IMPROV_PACKET_TYPE_RPC = 0x03, + IMPROV_PACKET_TYPE_RPC_RESPONSE = 0x04 +} ImprovSerialType_t; + +// ========================================================================================= +// Packet: Current State +// ====================================== +// Type: 0x01 +// Direction: Device to Client +// -------------------------------------- +// The data of this packet is a single byte and contains the current status of the provisioning +// service. It is to be written to any listening clients for instant feedback. + +// Byte Description +// 1 current state +// The current state can be the following values: + +// Value State Purpose +// ----- ------------------ ----------------------------------------- +// 0x02 Ready (Authorized) Ready to accept credentials. +// 0x03 Provisioning Credentials received, attempt to connect. +// 0x04 Provisioned Connection successful. +typedef enum { + IMPROV_STATE_READY_AUTHORIZED = 0x02, + IMPROV_STATE_PROVISIONING = 0x03, + IMPROV_STATE_PROVISIONED = 0x04, +} ImprovState_t; + +// ========================================================================================= +// Packet: Error state +// ====================================== +// Type: 0x02 +// Direction: Device to client +// -------------------------------------- +// The data of this packet is a single byte and contains the current status of the +// provisioning service. Whenever it changes the device needs to sent it to any listening +// clients for instant feedback. + +// Byte Description +// 1 error state +// Error state can be the following values: + +// Value State Purpose +// ----- ------------------ ----------------------------------------- +// 0x00 No error This shows there is no current error state. +// 0x01 Invalid RPC packet RPC packet was malformed/invalid. +// 0x02 Unknown RPC command The command sent is unknown. +// 0x03 Unable to connect The credentials have been received and an attempt to connect +// to the network has failed. +// 0xFF Unknown Error +typedef enum { + IMPROV_ERROR_NONE = 0x00, + IMPROV_ERROR_INVALID_RPC = 0x01, + IMPROV_ERROR_UNKNOWN_RPC = 0x02, + IMPROV_ERROR_UNABLE_TO_CONNECT = 0x03, + IMPROV_ERROR_NOT_AUTHORIZED = 0x04, + IMPROV_ERROR_UNKNOWN = 0xFF, +} ImprovError_t; + + +// ========================================================================================= +// Packet: RPC Command +// Type: 0x03 +// Direction: Client to device +// -------------------------------------- +// This packet type is used for the client to send commands to the device. When an RPC +// command is sent, the device should sent an update to the client to set the error state to +// 0 (no error). The response will either be an RPC result packet or an error state update. + +// Byte Description +// ----- --------------------- +// 1 Command (see below) +// 2 Data length +// 3...X Data +typedef enum { + IMPROV_CMD_UNKNOWN = 0x00, + IMPROV_CMD_WIFI_SETTINGS = 0x01, + IMPROV_CMD_GET_CURRENT_STATE = 0x02, + IMPROV_CMD_GET_DEVICE_INFO = 0x03, + IMPROV_CMD_GET_WIFI_NETWORKS = 0x04, + IMPROV_CMD_BAD_CHECKSUM = 0xFF, +} ImprovCommand_t; + +// ====================================== +// RPC Command: Send Wi-Fi settings +// Submit Wi-Fi credentials to the Improv Service to attempt to connect to. +// Type: 0x03 +// Command ID: 0x01 + +// Byte Description +// ----- ---------------- +// 1 command (0x01) +// 2 data length +// 3 ssid length +// 4...X ssid bytes +// X password length +// X...Y password bytes + +// Example: SSID = MyWirelessAP, Password = mysecurepassword +// 01 1E 0C {MyWirelessAP} 10 {mysecurepassword} +// This command will generate an RPC result. The first entry in the list is an URL to +// redirect the user to. +// If there is no URL, omit the entry or add an empty string. + +// ====================================== +// RPC Command: Request current state +// Sends a request for the device to send the current state of improv to the client. + +// Type: 0x03 +// Command ID: 0x02 + +// Byte Description +// ----- ---------------- +// 1 command (0x02) +// 2 data length (0) +// This command will trigger at least one packet, the Current State (see above) and if +// already provisioned, +// the same response you would get if device provisioning was successful (see below). + +// ====================================== +// RPC Command: Request device information +// Sends a request for the device to send information about itself. + +// Type: 0x03 +// Command ID: 0x03 + +// Byte Description +// ----- ---------------- +// 1 command (0x02) +// 2 data length (0) + +// This command will trigger one packet, the Device Information formatted as a RPC result. +// This result will contain at least 4 strings. + +// Order of strings: Firmware name, firmware version, hardware chip/variant, device name. + +// Example: ESPHome, 2021.11.0, ESP32-C3, Temperature Monitor. + +// ====================================== +// RPC Command: Request scanned Wi-Fi networks +// Sends a request for the device to send the Wi-Fi networks it sees. + +// Type: 0x03 +// Command ID: 0x04 + +// Byte Description +// ----- ---------------- +// 1 command (0x02) +// 2 data length (0) +// This command will trigger at least one RPC Response. Each response will contain at +// least 3 strings. + +// Order of strings: Wi-Fi SSID, RSSI, Auth required. + +// Example: MyWirelessNetwork, -60, YES. + +// The final response (or the first if no networks are found) will have 0 strings in the body. + +// ========================================================================================= +// Packet: RPC Result +// ====================================== +// Type: 0x04 +// Direction: Device to client +// -------------------------------------- +// This packet type contains the response to an RPC command. Results are returned as a list +// of strings. An empty list is allowed. + +// Byte Description +// ----- ---------------- +// 1 Command being responded to (see above) +// 2 Data length +// 3 Length of string 1 +// 4...X String 1 +// X Length of string 2 +// X...Y String 2 +// ... etc + + + + + + + + + +static const uint8_t CAPABILITY_IDENTIFY = 0x01; +static const uint8_t IMPROV_SERIAL_VERSION = 1; + +#ifndef FREE_AND_NULL +#define FREE_AND_NULL(x) if(x) { free(x); x=NULL; } +#endif +#ifndef ENUM_TO_STRING +#define ENUM_TO_STRING(g) \ + case g: \ + return STR(g); \ + break; +#endif + + +typedef struct { + ImprovCommand_t command; + char * ssid; + char * password; +} ImprovCommandStruct_t; +typedef struct { + char * ssid; + char * rssi; + char * auth_req; // YES/NO +} ImprovAPListStruct_t; +#define IMPROV_AP_STRUCT_NUM_STR 3 +typedef struct { + char * firmware_name; + char * firmware_version; + char * hardware_chip_variant; + char * device_name; + char * nullptr; +} ImprovDeviceInfoStruct_t; +#define IMPROV_DEVICE_INFO_NUM_STRINGS 4 + +typedef bool (*improv_command_callback_t)(ImprovCommandStruct_t *cmd); +typedef void (*on_error_callback_t)(ImprovError_t error); +typedef bool (*improv_send_callback_t)(uint8_t * buffer, size_t length); +typedef struct { + int index ; + improv_command_callback_t callback ; +} callback_table_t; + +void improv_parse_data(ImprovCommandStruct_t * improv_command, const uint8_t *data, size_t length, bool check_checksum) ; +bool improv_parse_serial_byte(size_t position, uint8_t byte, const uint8_t *buffer,improv_command_callback_t callback, on_error_callback_t on_error); +bool parse_improv_serial_line( const uint8_t *buffer); +void improv_set_send_callback(improv_send_callback_t callback ); + +bool improv_set_callback(ImprovCommand_t command, improv_command_callback_t callback ); +bool improv_wifi_list_allocate(size_t num_entries); +bool improv_wifi_list_add(const char * ssid, int8_t rssi, bool auth_req ); +bool improv_wifi_list_send( ); +size_t improv_wifi_get_wifi_list_count(); +bool improv_send_device_info( const char * firmware_name, const char * firmware_version, const char * hardware_chip_variant, const char * device_name); +uint8_t * improv_build_response(ImprovSerialType_t command, const char * datum, size_t len, size_t * out_len); +uint8_t * improv_build_rpc_response(ImprovCommand_t command, const char ** results, size_t num_strings, size_t * out_len); +bool improv_send_current_state(ImprovState_t state); +bool improv_send_error(ImprovError_t error); +const char * improv_get_error_desc(ImprovError_t error); +const char * improv_get_command_desc(ImprovCommand_t command); +bool improv_send_device_url( ImprovCommand_t from_command, const char * url); +// Improv Wi-Fi – Contact – GitHub +// Improv is an initiative by ESPHome & Home Assistant. +// Development funded by Nabu Casa. \ No newline at end of file diff --git a/components/wifi-manager/network_manager.c b/components/wifi-manager/network_manager.c index 2127ca70..f9548811 100644 --- a/components/wifi-manager/network_manager.c +++ b/components/wifi-manager/network_manager.c @@ -62,20 +62,23 @@ typedef struct network_callback { SLIST_ENTRY(network_callback) next; //!< next callback } network_callback_t; - +static wifi_connect_state_t wifi_connect_state = NETWORK_WIFI_STATE_INIT; /** linked list of command structures */ static SLIST_HEAD(cb_list, network_callback) s_cb_list; - network_t NM; - //! Create and initialize the array of state machines. state_machine_t* const SM[] = {(state_machine_t*)&NM}; static void network_timer_cb(void* timer_id); int get_root_id(const state_t * state); const state_t* get_root( const state_t* const state); static void network_task(void* pvParameters); - +void network_wifi_set_connect_state(wifi_connect_state_t state){ + wifi_connect_state = state; +} +wifi_connect_state_t network_wifi_get_connect_state(){ + return wifi_connect_state; +} void network_start_stop_dhcp_client(esp_netif_t* netif, bool start) { tcpip_adapter_dhcp_status_t status; esp_err_t err = ESP_OK; @@ -195,17 +198,21 @@ void network_start_stop_dhcps(esp_netif_t* netif, bool start) { #define ADD_LEAF(name,...) CASE_TO_STR(name); #define ADD_EVENT(name) CASE_TO_STR(name); #define ADD_FIRST_EVENT(name) CASE_TO_STR(name); -static const char* state_to_string(const state_t * state) { - if(!state) { - return ""; - } - switch (state->Parent?state->Parent->Id:state->Id) { +static const char* nm_state_to_string(nm_state_t state) { + switch (state) { ALL_NM_STATE default: break; } return "Unknown"; } +static const char* state_to_string(const state_t * state) { + if(!state) { + return ""; + } + return nm_state_to_string(state->Parent?state->Parent->Id:state->Id); + +} static const char* wifi_state_to_string(mn_wifi_active_state_t state) { switch (state) { ALL_WIFI_STATE(,) @@ -230,25 +237,27 @@ static const char* wifi_configuring_state_to_string(mn_wifi_configuring_state_t } return "Unknown"; } -static const char* sub_state_to_string(const state_t * state) { - if(!state) { - return "N/A"; - } - int root_id = get_root_id(state); - switch (root_id) +static const char* sub_state_id_to_string(nm_state_t state, int substate) { + switch (state) { case NETWORK_ETH_ACTIVE_STATE: - return eth_state_to_string(state->Id); + return eth_state_to_string(substate); break; case NETWORK_WIFI_ACTIVE_STATE: - return wifi_state_to_string(state->Id); + return wifi_state_to_string(substate); case NETWORK_WIFI_CONFIGURING_ACTIVE_STATE: - return wifi_configuring_state_to_string(state->Id); + return wifi_configuring_state_to_string(substate); default: break; } return "*"; } +static const char* sub_state_to_string(const state_t * state) { + if(!state) { + return "N/A"; + } + return sub_state_id_to_string(get_root_id(state), state->Id); +} static const char* event_to_string(network_event_t state) { switch (state) { @@ -274,8 +283,7 @@ static const max_sub_states_t state_max[] = { { .parent_state = NETWORK_INSTANTIATED_STATE, .sub_state_last = 0 }, {.parent_state = NETWORK_ETH_ACTIVE_STATE, .sub_state_last = TOTAL_ETH_ACTIVE_STATE-1 }, {.parent_state = NETWORK_WIFI_ACTIVE_STATE, .sub_state_last = TOTAL_WIFI_ACTIVE_STATE-1 }, -{.parent_state = WIFI_CONFIGURING_STATE, .sub_state_last = TOTAL_WIFI_CONFIGURING_STATE-1 }, -{.parent_state = WIFI_CONFIGURING_STATE, .sub_state_last = TOTAL_WIFI_CONFIGURING_STATE-1 }, +{.parent_state = NETWORK_WIFI_CONFIGURING_ACTIVE_STATE, .sub_state_last = TOTAL_WIFI_CONFIGURING_STATE-1 }, {.parent_state =-1} }; @@ -345,23 +353,27 @@ static void network_task(void* pvParameters) { return -1; } esp_err_t network_register_state_callback(nm_state_t state,int sub_state, const char* from, network_status_reached_cb cb) { + const char * error_prefix = "Error registering callback for State" ; network_callback_t* item = NULL; if (!cb) { return ESP_ERR_INVALID_ARG; } item = calloc(1, sizeof(*item)); if (item == NULL) { + ESP_LOGE(TAG,"%s %s. Memory allocation failed",error_prefix, nm_state_to_string(state)); return ESP_ERR_NO_MEM; } if(sub_state != -1 && sub_state>get_max_substate(state)){ // sub state has to be valid + ESP_LOGE(TAG,"%s %s. Substate %d/%d %s",error_prefix, nm_state_to_string(state), sub_state,get_max_substate(state), sub_state>get_max_substate(state)?"out of boundaries":"invalid"); return ESP_ERR_INVALID_ARG; } - + ESP_LOGI(TAG,"Registering callback for State %s, substate %s: %s", nm_state_to_string(state), sub_state_id_to_string(state,sub_state), from); item->state = state; item->cb = cb; item->from = from; item->sub_state=sub_state; + network_callback_t* last = SLIST_FIRST(&s_cb_list); if (last == NULL) { SLIST_INSERT_HEAD(&s_cb_list, item, next); @@ -389,19 +401,26 @@ static bool is_root_state(const state_t * state){ static bool is_current_state(const state_t* state, nm_state_t state_id, int sub_state_id){ return get_root(state)->Id == state_id && (sub_state_id==-1 || (!is_root_state(state) && state->Id == sub_state_id) ); } + void network_execute_cb(state_machine_t* const state_machine, const char * caller) { network_callback_t* it; + bool found=false; + ESP_LOGI(TAG,"Checking if we need to invoke callbacks. "); SLIST_FOREACH(it, &s_cb_list, next) { if (is_current_state(state_machine->State,it->state, it->sub_state)) { char * cb_prefix= messaging_alloc_format_string("BEGIN Executing Callback %s", it->from) ; NETWORK_DEBUG_STATE_MACHINE(true,STR_OR_BLANK(cb_prefix),state_machine,false, STR_OR_BLANK(caller)); FREE_AND_NULL(cb_prefix); it->cb((nm_state_t)get_root(state_machine->State)->Id, is_root_state(state_machine->State)?-1:state_machine->State->Id); + found = true; cb_prefix= messaging_alloc_format_string("END Executing Callback %s", it->from) ; NETWORK_DEBUG_STATE_MACHINE(false,STR_OR_BLANK(cb_prefix),state_machine,false, STR_OR_BLANK(caller)); FREE_AND_NULL(cb_prefix); } } + if(!found){ + NETWORK_DEBUG_STATE_MACHINE(true,"No Callback found ",state_machine,false, STR_OR_BLANK(caller)); + } } bool network_is_wifi_prioritized() { @@ -465,7 +484,7 @@ void network_manager_format_from_to_states(esp_log_level_t level, const char* pr source_sub_state = sub_state_to_string(from_state); } if (show_source) { - ESP_LOG_LEVEL(level, TAG, "%s %s %s(%s)->%s(%s) [%s]", + ESP_LOG_LEVEL(level, TAG, "%s %s %s.%s->%s.%s [evt:%s]", STR_OR_BLANK(caller), prefix, source_state, @@ -724,19 +743,23 @@ void network_ip_event_handler(void* arg, esp_event_base_t event_base, int32_t ev break; } } -void network_set_hostname(esp_netif_t* interface) { - esp_err_t err; +char * alloc_get_host_name(){ ESP_LOGD(TAG, "Retrieving host name from nvs"); char* host_name = (char*)config_alloc_get(NVS_TYPE_STR, "host_name"); if (host_name == NULL) { ESP_LOGE(TAG, "Could not retrieve host name from nvs"); - } else { - ESP_LOGD(TAG, "Setting host name to : %s", host_name); - if ((err = esp_netif_set_hostname(interface, host_name)) != ESP_OK) { - ESP_LOGE(TAG, "Unable to set host name. Error: %s", esp_err_to_name(err)); - } - free(host_name); + } + return host_name; +} +void network_set_hostname(esp_netif_t* interface) { + esp_err_t err; + char * host_name = alloc_get_host_name(); + if(!host_name) return; + ESP_LOGD(TAG, "Setting host name to : %s", host_name); + if ((err = esp_netif_set_hostname(interface, host_name)) != ESP_OK) { + ESP_LOGE(TAG, "Unable to set host name. Error: %s", esp_err_to_name(err)); } + free(host_name); } #define LOCAL_MAC_SIZE 20 char* network_manager_alloc_get_mac_string(uint8_t mac[6]) { diff --git a/components/wifi-manager/network_manager.h b/components/wifi-manager/network_manager.h index d3205c77..4507f839 100644 --- a/components/wifi-manager/network_manager.h +++ b/components/wifi-manager/network_manager.h @@ -10,6 +10,7 @@ #include "hsm.h" #include "esp_log.h" #include "network_services.h" +#include "improv.h" #ifdef __cplusplus extern "C" { @@ -257,10 +258,20 @@ typedef enum update_reason_code_t { UPDATE_LOST_CONNECTION = 3, UPDATE_FAILED_ATTEMPT_AND_RESTORE = 4, UPDATE_ETHERNET_CONNECTED = 5 - }update_reason_code_t; +typedef enum { + NETWORK_WIFI_STATE_INIT, + NETWORK_WIFI_STATE_CONNECTING, + NETWORK_WIFI_STATE_DOWN, + NETWORK_WIFI_STATE_INVALID_CONFIG, + NETWORK_WIFI_STATE_FAILED, + NETWORK_WIFI_STATE_CONNECTED +} wifi_connect_state_t; + +void network_wifi_set_connect_state(wifi_connect_state_t state); +wifi_connect_state_t network_wifi_get_connect_state(); @@ -312,6 +323,7 @@ void network_manager_initialise_mdns(); bool network_is_wifi_prioritized(); void network_set_timer(uint16_t duration, const char * tag); void network_set_hostname(esp_netif_t * netif); +char * alloc_get_host_name(); esp_err_t network_get_ip_info_for_netif(esp_netif_t* netif, tcpip_adapter_ip_info_t* ipInfo); void network_start_stop_dhcp_client(esp_netif_t* netif, bool start); void network_start_stop_dhcps(esp_netif_t* netif, bool start); diff --git a/components/wifi-manager/network_manager_handlers.c b/components/wifi-manager/network_manager_handlers.c index d05390a7..c6d5eec4 100644 --- a/components/wifi-manager/network_manager_handlers.c +++ b/components/wifi-manager/network_manager_handlers.c @@ -1,7 +1,10 @@ #ifdef NETWORK_HANDLERS_LOG_LEVEL #define LOG_LOCAL_LEVEL NETWORK_HANDLERS_LOG_LEVEL + #pragma message("Log Level overwritten to " LOG_LOCAL_LEVEL) +#else + #pragma message("Log Level set to " LOG_LOCAL_LEVEL) #endif -#include "network_manager.h" +#include "network_manager.h" #include #include #include @@ -42,6 +45,7 @@ #include "tools.h" #include "http_server_handlers.h" #include "network_manager.h" +#include "improv.h" static const char TAG[]="network_handlers"; @@ -387,6 +391,7 @@ static state_machine_result_t NETWORK_ETH_ACTIVE_STATE_handler(state_machine_t* case EN_SCAN: ESP_LOGW(TAG,"Wifi scan cannot be executed in this state"); network_wifi_built_known_ap_list(); + result = EVENT_HANDLED; break; case EN_DELETE: { @@ -418,7 +423,9 @@ static state_machine_result_t ETH_CONNECTING_NEW_STATE_entry_handler(state_machi network_t* const nm = (network_t *)State_Machine; network_handler_entry_print(State_Machine,true); network_start_stop_dhcp_client(nm->wifi_netif, true); - network_wifi_connect(nm->event_parameters->ssid,nm->event_parameters->password); + if(network_wifi_connect(nm->event_parameters->ssid,nm->event_parameters->password) == ESP_ERR_INVALID_ARG){ + network_async_fail(); + } FREE_AND_NULL(nm->event_parameters->ssid); FREE_AND_NULL(nm->event_parameters->password); NETWORK_EXECUTE_CB(State_Machine); @@ -430,6 +437,11 @@ static state_machine_result_t ETH_CONNECTING_NEW_STATE_handler(state_machine_t* network_handler_print(State_Machine,true); state_machine_result_t result = EVENT_HANDLED; switch (State_Machine->Event) { + case EN_FAIL: + ESP_LOGW(TAG,"Error connecting to access point"); + network_status_update_ip_info(UPDATE_FAILED_ATTEMPT); + result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_STATE],__FUNCTION__); + break; case EN_GOT_IP: result= local_traverse_state(State_Machine, &network_states[WIFI_CONNECTED_STATE],__FUNCTION__); break; @@ -621,6 +633,7 @@ static state_machine_result_t NETWORK_WIFI_CONFIGURING_ACTIVE_STATE_entry_handle nm->wifi_ap_netif = network_wifi_config_ap(); dns_server_start(nm->wifi_ap_netif); network_wifi_start_scan(); + NETWORK_EXECUTE_CB(State_Machine); network_handler_entry_print(State_Machine,false); return EVENT_HANDLED; } @@ -649,6 +662,9 @@ static state_machine_result_t NETWORK_WIFI_CONFIGURING_ACTIVE_STATE_handler(stat case EN_ETH_GOT_IP: network_interface_coexistence(State_Machine); break; + case EN_TIMER: + result= EVENT_HANDLED; + break; default: result =EVENT_UN_HANDLED; } @@ -696,7 +712,9 @@ static state_machine_result_t WIFI_CONFIGURING_CONNECT_STATE_entry_handler(state network_t* const nm = (network_t *)State_Machine; network_handler_entry_print(State_Machine,true); network_start_stop_dhcp_client(nm->wifi_netif, true); - network_wifi_connect(nm->event_parameters->ssid,nm->event_parameters->password); + if(network_wifi_connect(nm->event_parameters->ssid,nm->event_parameters->password) == ESP_ERR_INVALID_ARG){ + network_async_fail(); + } FREE_AND_NULL(nm->event_parameters->ssid); FREE_AND_NULL(nm->event_parameters->password); NETWORK_EXECUTE_CB(State_Machine); @@ -718,13 +736,16 @@ static state_machine_result_t WIFI_CONFIGURING_CONNECT_STATE_handler(state_machi network_status_update_ip_info(UPDATE_CONNECTION_OK); result= local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_CONNECT_SUCCESS_STATE],__FUNCTION__); break; + case EN_FAIL: + ESP_LOGW(TAG,"Error connecting to access point"); + result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_CONNECT_FAILED_STATE],__FUNCTION__); + break; case EN_LOST_CONNECTION: if(nm->event_parameters->disconnected_event->reason == WIFI_REASON_ASSOC_LEAVE) { ESP_LOGI(TAG,"Wifi was disconnected from previous access point. Waiting to connect."); } else { - network_status_update_ip_info(UPDATE_FAILED_ATTEMPT); - result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_STATE],__FUNCTION__); + result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_CONNECT_FAILED_STATE],__FUNCTION__); } break; case EN_TIMER: @@ -747,6 +768,43 @@ static state_machine_result_t WIFI_CONFIGURING_CONNECT_STATE_exit_handler(state_ return EVENT_HANDLED; } +/********************************************************************************************* + * WIFI_CONFIGURING_CONNECT_FAILED_STATE + */ +static state_machine_result_t WIFI_CONFIGURING_CONNECT_FAILED_STATE_entry_handler(state_machine_t* const State_Machine) { + network_handler_entry_print(State_Machine,true); + network_status_update_ip_info(UPDATE_FAILED_ATTEMPT); + ESP_LOGE(TAG, "Connecting Failed."); + NETWORK_EXECUTE_CB(State_Machine); + network_async_fail(); + network_handler_entry_print(State_Machine,false); + return EVENT_HANDLED; +} +static state_machine_result_t WIFI_CONFIGURING_CONNECT_FAILED_STATE_handler(state_machine_t* const State_Machine) { + network_handler_print(State_Machine,true); + state_machine_result_t result = EVENT_HANDLED; + network_t* const nm = (network_t *)State_Machine; + switch (State_Machine->Event) { + case EN_FAIL: + result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_STATE],__FUNCTION__); + break; + default: + result= EVENT_HANDLED; + } + // Process global handler at the end, since we want to overwrite + // UPDATE_STATUS with our own logic above + HANDLE_GLOBAL_EVENT(State_Machine); + network_handler_print(State_Machine,false); + return result; +} +static state_machine_result_t WIFI_CONFIGURING_CONNECT_FAILED_STATE_exit_handler(state_machine_t* const State_Machine) { + network_exit_handler_print(State_Machine,true); + network_set_timer(0,NULL); + network_exit_handler_print(State_Machine,false); + return EVENT_HANDLED; +} + + /********************************************************************************************* * WIFI_CONFIGURING_CONNECT_SUCCESS_STATE */ @@ -827,6 +885,7 @@ static state_machine_result_t WIFI_CONNECTING_STATE_handler(state_machine_t* con } else if(nm->event_parameters->disconnected_event->reason != WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT) { network_status_update_ip_info(UPDATE_FAILED_ATTEMPT); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_FAILED); result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONFIGURING_STATE],__FUNCTION__); } break; @@ -838,6 +897,7 @@ static state_machine_result_t WIFI_CONNECTING_STATE_handler(state_machine_t* con } static state_machine_result_t WIFI_CONNECTING_STATE_exit_handler(state_machine_t* const State_Machine) { network_exit_handler_print(State_Machine,true); + network_set_timer(0,NULL); network_exit_handler_print(State_Machine,false); return EVENT_HANDLED; } @@ -849,7 +909,9 @@ static state_machine_result_t WIFI_CONNECTING_NEW_STATE_entry_handler(state_mach network_t* const nm = (network_t *)State_Machine; network_handler_entry_print(State_Machine,true); network_start_stop_dhcp_client(nm->wifi_netif, true); - network_wifi_connect(nm->event_parameters->ssid,nm->event_parameters->password); + if(network_wifi_connect(nm->event_parameters->ssid,nm->event_parameters->password) == ESP_ERR_INVALID_ARG){ + network_async_fail(); + } FREE_AND_NULL(nm->event_parameters->ssid); FREE_AND_NULL(nm->event_parameters->password); NETWORK_EXECUTE_CB(State_Machine); @@ -859,8 +921,13 @@ static state_machine_result_t WIFI_CONNECTING_NEW_STATE_entry_handler(state_mach static state_machine_result_t WIFI_CONNECTING_NEW_STATE_handler(state_machine_t* const State_Machine) { HANDLE_GLOBAL_EVENT(State_Machine); network_handler_print(State_Machine,true); + network_t* const nm = (network_t *)State_Machine; state_machine_result_t result = EVENT_HANDLED; switch (State_Machine->Event) { + case EN_FAIL: + ESP_LOGW(TAG,"Error connecting to access point"); + result = local_traverse_state(State_Machine, &Wifi_Configuring_State[WIFI_CONNECTING_NEW_FAILED_STATE],__FUNCTION__); + break; case EN_GOT_IP: network_status_update_ip_info(UPDATE_CONNECTION_OK); result= local_traverse_state(State_Machine, &Wifi_Active_State[WIFI_CONNECTED_STATE],__FUNCTION__); @@ -901,6 +968,7 @@ static state_machine_result_t WIFI_CONNECTING_NEW_STATE_exit_handler(state_machi static state_machine_result_t WIFI_CONNECTING_NEW_FAILED_STATE_entry_handler(state_machine_t* const State_Machine) { network_t* const nm = (network_t *)State_Machine; network_handler_entry_print(State_Machine,true); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_FAILED); if (nm->wifi_connected ) { // Wifi was already connected to an existing access point. Restore connection network_connect_active_ssid(State_Machine); @@ -1140,18 +1208,18 @@ static state_machine_result_t ETH_ACTIVE_CONNECTED_STATE_exit_handler(state_mach static state_machine_result_t local_switch_state(state_machine_t* state_machine, const state_t* const target_state, const char * caller) { const state_t* source = state_machine->State; - NETWORK_PRINT_TRANSITION(true, "BEGIN SWITCH", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true,caller); + NETWORK_PRINT_TRANSITION(true, "switch.begin", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true,caller); state_machine_result_t result = switch_state(state_machine, target_state); - NETWORK_PRINT_TRANSITION( false,"BEGIN SWITCH", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true,caller); + NETWORK_PRINT_TRANSITION( false,"switch.end", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true,caller); ((network_t *)state_machine)->source_state = source; return result; } static state_machine_result_t local_traverse_state(state_machine_t* const state_machine, const state_t* const target_state, const char * caller) { const state_t * source = state_machine->State; - NETWORK_PRINT_TRANSITION( true,"BEGIN TRAVERSE", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true, caller); + NETWORK_PRINT_TRANSITION( true,"traverse.begin", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true, caller); state_machine_result_t result = traverse_state(state_machine, target_state); - NETWORK_PRINT_TRANSITION( false,"END TRAVERSE", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true,caller); + NETWORK_PRINT_TRANSITION( false,"traverse.end", ((network_t *)state_machine)->source_state, target_state, state_machine->Event, true,caller); ((network_t *)state_machine)->source_state = source; return result; } diff --git a/components/wifi-manager/network_services.h b/components/wifi-manager/network_services.h index 31c7e951..f92c45f9 100644 --- a/components/wifi-manager/network_services.h +++ b/components/wifi-manager/network_services.h @@ -36,20 +36,23 @@ extern "C" { #define ALL_WIFI_CONFIGURING_STATE(PARENT, LEVEL)\ ADD_LEAF(WIFI_CONFIGURING_STATE,PARENT,LEVEL)\ ADD_LEAF(WIFI_CONFIGURING_CONNECT_STATE,PARENT,LEVEL)\ + ADD_LEAF(WIFI_CONFIGURING_CONNECT_FAILED_STATE,PARENT,LEVEL)\ ADD_LEAF(WIFI_CONFIGURING_CONNECT_SUCCESS_STATE,PARENT,LEVEL) + + typedef enum { ALL_NM_STATE TOTAL_NM_STATE } nm_state_t; -typedef enum { - ALL_WIFI_STATE(,) - TOTAL_WIFI_ACTIVE_STATE -} mn_wifi_active_state_t; typedef enum { ALL_ETH_STATE(,) TOTAL_ETH_ACTIVE_STATE } mn_eth_active_state_t; +typedef enum { + ALL_WIFI_STATE(,) + TOTAL_WIFI_ACTIVE_STATE +} mn_wifi_active_state_t; typedef enum { ALL_WIFI_CONFIGURING_STATE(,) TOTAL_WIFI_CONFIGURING_STATE diff --git a/components/wifi-manager/network_status.c b/components/wifi-manager/network_status.c index 8fa1398c..0b02cee6 100644 --- a/components/wifi-manager/network_status.c +++ b/components/wifi-manager/network_status.c @@ -14,6 +14,7 @@ #include "platform_esp32.h" #include "tools.h" #include "trace.h" +#include "messaging.h" #ifndef CONFIG_SQUEEZELITE_ESP32_RELEASE_URL #pragma message "Defaulting release url" #define CONFIG_SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases" @@ -33,7 +34,11 @@ static uint16_t lms_server_cport = 0; static void (*chained_notify)(in_addr_t, u16_t, u16_t); static void connect_notify(in_addr_t ip, u16_t hport, u16_t cport); #define STA_IP_LEN sizeof(char) * IP4ADDR_STRLEN_MAX - +static update_reason_code_t last_reason_code = -1; +void * get_http_server(int *port); +update_reason_code_t get_last_reason_code(){ + return last_reason_code; +} void init_network_status() { chained_notify = server_notify; server_notify = connect_notify; @@ -169,6 +174,11 @@ void network_status_safe_reset_sta_ip_string() { char* network_status_get_sta_ip_string() { return network_status_ip_address; } +char * network_status_alloc_get_system_url(){ + int port=0; + void * server = get_http_server(&port); + return messaging_alloc_format_string("http://%s:%d/",network_status_ip_address,port); +} void set_lms_server_details(in_addr_t ip, u16_t hport, u16_t cport) { strncpy(lms_server_ip, inet_ntoa(ip), sizeof(lms_server_ip)); lms_server_ip[sizeof(lms_server_ip) - 1] = '\0'; @@ -297,11 +307,13 @@ void network_status_update_address(cJSON* root, esp_netif_ip_info_t* ip_info) { ESP_LOGE(TAG, "Cannor update IP address. JSON structure or ip_info is null"); return; } + network_status_safe_update_sta_ip_string(&ip_info->ip); network_update_cjson_string(&root, "ip", ip4addr_ntoa((ip4_addr_t*)&ip_info->ip)); network_update_cjson_string(&root, "netmask", ip4addr_ntoa((ip4_addr_t*)&ip_info->netmask)); network_update_cjson_string(&root, "gw", ip4addr_ntoa((ip4_addr_t*)&ip_info->gw)); } void network_status_update_ip_info(update_reason_code_t update_reason_code) { + last_reason_code = update_reason_code; ESP_LOGV(TAG, "network_status_update_ip_info called"); esp_netif_ip_info_t ip_info; if (network_status_lock_json_buffer(portMAX_DELAY)) { diff --git a/components/wifi-manager/network_status.h b/components/wifi-manager/network_status.h index 19a7dfb4..e8dee7fe 100644 --- a/components/wifi-manager/network_status.h +++ b/components/wifi-manager/network_status.h @@ -54,6 +54,8 @@ cJSON* network_status_get_basic_info(cJSON** old); void network_status_update_basic_info(); void network_status_clear_ip(); void network_status_safe_reset_sta_ip_string(); +update_reason_code_t get_last_reason_code(); +char * network_status_alloc_get_system_url(); #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/components/wifi-manager/network_wifi.c b/components/wifi-manager/network_wifi.c index 3412d215..dd82c0d7 100644 --- a/components/wifi-manager/network_wifi.c +++ b/components/wifi-manager/network_wifi.c @@ -739,6 +739,7 @@ static void network_wifi_event_handler(void* arg, esp_event_base_t event_base, i wifi_event_sta_connected_t* s = (wifi_event_sta_connected_t*)event_data; char* bssid = network_manager_alloc_get_mac_string(s->bssid); char* ssid = strdup_psram((char*)s->ssid); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_CONNECTED); if (bssid && ssid) { ESP_LOGD(TAG, "WIFI_EVENT_STA_CONNECTED. Channel: %d, Access point: %s, BSSID: %s ", s->channel, STR_OR_BLANK(ssid), (bssid)); } @@ -773,6 +774,7 @@ static void network_wifi_event_handler(void* arg, esp_event_base_t event_base, i ESP_LOGI(TAG, "WiFi Roaming to new access point"); } else { network_async_lost_connection((wifi_event_sta_disconnected_t*)event_data); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_FAILED); } } break; @@ -841,8 +843,10 @@ void network_wifi_generate_access_points_json(cJSON** ap_list) { known_access_point_t* it; if (*ap_list == NULL) return; + improv_wifi_list_allocate(ap_num); for (int i = 0; i < ap_num; i++) { network_wifi_add_access_point_json(*ap_list, &accessp_records[i]); + improv_wifi_list_add(ap_ssid_string(&accessp_records[i]),accessp_records[i].rssi, accessp_records[i].authmode!=WIFI_AUTH_OPEN); } SLIST_FOREACH(it, &s_ap_list, next) { if (!network_wifi_was_ssid_seen(it->ssid)) { @@ -1126,10 +1130,12 @@ esp_err_t network_wifi_connect(const char* ssid, const char* password) { ESP_LOGD(TAG, "network_wifi_connect"); if (!is_wifi_up()) { messaging_post_message(MESSAGING_WARNING, MESSAGING_CLASS_SYSTEM, "Wifi not started. Cannot connect"); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_DOWN); return ESP_FAIL; } if (!ssid || !password || strlen(ssid) == 0) { ESP_LOGE(TAG, "Cannot connect wifi. wifi config is null!"); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_INVALID_CONFIG); return ESP_ERR_INVALID_ARG; } @@ -1159,12 +1165,17 @@ esp_err_t network_wifi_connect(const char* ssid, const char* password) { config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; if ((err = esp_wifi_set_config(WIFI_IF_STA, &config)) != ESP_OK) { + network_wifi_set_connect_state(NETWORK_WIFI_STATE_FAILED); ESP_LOGE(TAG, "Failed to set STA configuration. Error %s", esp_err_to_name(err)); } if (err == ESP_OK) { ESP_LOGI(TAG, "Wifi Connecting to %s...", ssid); if ((err = esp_wifi_connect()) != ESP_OK) { ESP_LOGE(TAG, "Failed to initiate wifi connection. Error %s", esp_err_to_name(err)); + network_wifi_set_connect_state(NETWORK_WIFI_STATE_FAILED); + } + else{ + network_wifi_set_connect_state(NETWORK_WIFI_STATE_CONNECTING); } } return err; diff --git a/components/wifi-manager/network_wifi.h b/components/wifi-manager/network_wifi.h index 8293d3d2..75bb6843 100644 --- a/components/wifi-manager/network_wifi.h +++ b/components/wifi-manager/network_wifi.h @@ -70,6 +70,7 @@ size_t network_wifi_get_known_count_in_range(); esp_err_t network_wifi_built_known_ap_list(); esp_err_t network_wifi_connect_next_in_range(); const wifi_sta_config_t* network_wifi_load_active_config(); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/components/wifi-manager/wifi_manager_http_server.c b/components/wifi-manager/wifi_manager_http_server.c index 147f36dd..29034294 100644 --- a/components/wifi-manager/wifi_manager_http_server.c +++ b/components/wifi-manager/wifi_manager_http_server.c @@ -130,7 +130,6 @@ void register_regular_handlers(httpd_handle_t server){ } - esp_err_t http_server_start() { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f98926be..d55fb805 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register(SRC_DIRS . - PRIV_REQUIRES _override esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets + PRIV_REQUIRES _override esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets driver_bt EMBED_FILES ../server_certs/github.pem LDFRAGMENTS "linker.lf" ) diff --git a/otadata.bin b/otadata.bin new file mode 100644 index 00000000..d840af79 Binary files /dev/null and b/otadata.bin differ diff --git a/squeezelite.cmake b/squeezelite.cmake index a72713d6..f96d35f7 100644 --- a/squeezelite.cmake +++ b/squeezelite.cmake @@ -143,6 +143,7 @@ add_custom_command( COMMAND xtensa-esp32-elf-objcopy --globalize-symbol find_command_by_name ${build_dir}/esp-idf/console/libconsole.a VERBATIM ) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/otadata.bin ${build_dir}/ota_data_initial.bin COPYONLY) add_custom_command( TARGET squeezelite.elf PRE_LINK