improv-wifi initial commit

This commit is contained in:
Sebastien L
2022-11-29 11:04:27 -05:00
parent b20c8306fa
commit f74ecf5e60
21 changed files with 1223 additions and 68 deletions

10
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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", "<t>", "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>", "SSID of AP");
improv_args.password = arg_str0(NULL, NULL, "<pass>", "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();
}

View File

@@ -0,0 +1,162 @@
#include "platform_console.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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<len;i++){
printf(" %c ",isprint(buff[i])?buff[i]:'.');
}
printf("\n%s (%d): ",prefix, len);
for(int i=0;i<len;i++){
printf("0x%03x ",buff[i]);
}
printf("\n");
}
#else
#define dump_buffer(prefix,buff,size)
#endif
bool improv_send_callback(uint8_t * buffer, size_t length){
dump_buffer("send", (const char *) buffer, length);
uart_write_bytes(CONFIG_ESP_CONSOLE_UART_NUM,buffer,length );
return true;
}
void improv_console_init(){
ESP_LOGI(TAG,"Initializing improv callbacks");
network_register_state_callback(NETWORK_WIFI_ACTIVE_STATE,WIFI_CONNECTED_STATE, "improv_got_ip", &cb_improv_got_ip);
network_register_state_callback(NETWORK_WIFI_ACTIVE_STATE,WIFI_CONNECTING_NEW_FAILED_STATE, "improv_disconnect", &cb_improv_disconnected);
network_register_state_callback(NETWORK_WIFI_CONFIGURING_ACTIVE_STATE,WIFI_CONFIGURING_CONNECT_FAILED_STATE, "improv_disconnect", &cb_improv_disconnected);
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "network_manager.h"
#include "improv.h"
#include "freertos/FreeRTOS.h"
#include <stdio.h>
#include <stdlib.h>
#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();

View File

@@ -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);
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 (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;
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);
}
} else if (xRingbufferCanRead(stdin_redir.handle, activated)) {
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();

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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]) {

View File

@@ -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);

View File

@@ -1,5 +1,8 @@
#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 <stdbool.h>
@@ -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;
}

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -130,7 +130,6 @@ void register_regular_handlers(httpd_handle_t server){
}
esp_err_t http_server_start()
{

View File

@@ -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"
)

BIN
otadata.bin Normal file

Binary file not shown.

View File

@@ -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