#include "wifi_manager.h" #include "wifi_configuration.h" #include "wifi_event_handler.h" #include "wifi_storage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ssdp.h" #include "wifi_api.h" #define TAG __FILENAME__ typedef struct wifi_ctx_t { SemaphoreHandle_t lock; TaskHandle_t task; union { struct { wifi_ap_record_t *ap; wifi_manager_scan_done_cb cb; void *arg; uint16_t total_aps; uint16_t nr_aps_in_channel; uint8_t max_ap; } scan; struct { ip_event_got_ip_t *event; uint8_t need_unlock; /* used when trigger connection from wifi_manager instead of wifi_api */ } conn; }; struct { uint8_t is_endless_connect: 1; uint8_t auto_reconnect: 1; uint8_t do_fast_connect: 1; /* 0 delay connect on boot or just disconnected, else 5 seconds delay from each connection try */ uint8_t is_sta_connected: 1; uint8_t reserved: 4; }; TaskHandle_t delayed_stopAP_task; TaskHandle_t delayed_startAP_task; uint32_t try_connect_count; wifi_apsta_mode_e permanent_mode; wifi_mode_t mode; int ap_on_delay_tick; int ap_off_delay_tick; } wifi_ctx_t; static esp_netif_t *ap_netif; static esp_netif_t *sta_netif; static wifi_ctx_t ctx; static void set_sta_cred(const char *ssid, const char *password); static void disconn_handler(void); static int set_default_sta_cred(void); static int set_wifi_mode(wifi_apsta_mode_e mode); static void handle_wifi_connected(); /* got IP */ static void wifi_led_init(); static void wifi_led_set_blink(); static void wifi_led_set_on(); void wifi_manager_init(void) { esp_err_t err; uint8_t do_connect = 0; wifi_led_init(); wifi_led_set_blink(); ap_netif = esp_netif_create_default_wifi_ap(); assert(ap_netif); sta_netif = esp_netif_create_default_wifi_sta(); assert(sta_netif); ESP_ERROR_CHECK(esp_netif_set_hostname(ap_netif, WIFI_DEFAULT_HOSTNAME)); ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, WIFI_DEFAULT_HOSTNAME)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &ip_event_handler, NULL, NULL)); ctx.is_sta_connected = false; ctx.ap_on_delay_tick = pdMS_TO_TICKS(5000); ctx.ap_off_delay_tick = pdMS_TO_TICKS(10000); ctx.delayed_stopAP_task = NULL; ctx.delayed_startAP_task = NULL; ctx.try_connect_count = 0; err = wifi_data_get_wifi_mode(&ctx.permanent_mode); ESP_LOGI(TAG, "use wifi mode: %d", ctx.permanent_mode); if (err) { ctx.permanent_mode = WIFI_AP_AUTO_STA_ON; ctx.mode = WIFI_MODE_APSTA; } else { ctx.mode = ctx.permanent_mode & 0x3; /* 0b1XX */ if (ctx.mode == WIFI_MODE_STA || ctx.mode == WIFI_MODE_NULL) { ctx.mode = WIFI_MODE_APSTA; } } ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(esp_wifi_set_mode(ctx.mode)); ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); wifi_credential_t cred; err = wifi_data_get_ap_credential(&cred); if (err || strlen(cred.password) < 8) { ESP_LOGI(TAG, "use default AP credential"); strncpy((char *)cred.ssid, WIFI_DEFAULT_AP_SSID, 32); strncpy((char *)cred.password, WIFI_DEFAULT_AP_PASS, 64); } else { ESP_LOGI(TAG, "use AP credential %s, %s", cred.ssid, cred.password); } wifi_manager_set_ap_credential(&cred); if (set_default_sta_cred() == 0) { ESP_LOGI(TAG, "STA connect to saved cred"); do_connect = 1; ctx.do_fast_connect = 1; } wifi_api_sta_ap_static_info_t static_info; err = wifi_data_get_static(&static_info); if (err == ESP_OK) { wifi_manager_sta_set_static_conf(&static_info); } /* TODO: Read from nvs */ esp_netif_ip_info_t ip_info; IP4_ADDR_EXPAND(&ip_info.ip, WIFI_DEFAULT_AP_IP); IP4_ADDR_EXPAND(&ip_info.gw, WIFI_DEFAULT_AP_GATEWAY); IP4_ADDR_EXPAND(&ip_info.netmask, WIFI_DEFAULT_AP_NETMASK); err = esp_netif_dhcps_stop(ap_netif); if (err) { ESP_LOGE(TAG, "dhcps stop %s", esp_err_to_name(err)); } err = esp_netif_set_ip_info(ap_netif, &ip_info); if (err) { ESP_LOGE(TAG, "net if set info %s", esp_err_to_name(err)); } err = esp_netif_dhcps_start(ap_netif); if (err) { ESP_LOGE(TAG, "dhcps start %s", esp_err_to_name(err)); } ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "wifi started"); esp_log_level_set("wifi", ESP_LOG_WARN); ctx.lock = xSemaphoreCreateBinary(); ctx.is_endless_connect = 0; ctx.auto_reconnect = 1; xSemaphoreGive(ctx.lock); wifi_event_set_disco_handler(disconn_handler); if (do_connect) { disconn_handler(); } ssdp_init(); ssdp_start(); } void wifi_led_init() { #if WIFI_LED_ENABLE ledc_timer_config_t ledc_timer = { .duty_resolution = LEDC_TIMER_14_BIT, // resolution of PWM duty .freq_hz = 5, // frequency of PWM signal .speed_mode = LEDC_LOW_SPEED_MODE, // timer mode .timer_num = LEDC_TIMER_0, // timer index .clk_cfg = LEDC_USE_APB_CLK, // Auto select the source clock }; ledc_timer_config(&ledc_timer); ledc_channel_config_t ledc_channel = { .channel = LEDC_CHANNEL_0, .duty = 0, .gpio_num = WIFI_LED_PIN, .speed_mode = LEDC_LOW_SPEED_MODE, .hpoint = 1, .timer_sel = LEDC_TIMER_0, .flags.output_invert = 0 }; ledc_channel_config(&ledc_channel); ledc_fade_func_install(0); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[WIFI_LED_PIN], PIN_FUNC_GPIO); gpio_set_direction(WIFI_LED_PIN, GPIO_MODE_OUTPUT_OD); gpio_set_direction(WIFI_LED_PIN, GPIO_FLOATING); esp_rom_gpio_connect_out_signal(WIFI_LED_PIN, ledc_periph_signal[LEDC_LOW_SPEED_MODE].sig_out0_idx + LEDC_CHANNEL_0, 1, 0); #endif } static void wifi_led_set_blink() { #if WIFI_LED_ENABLE ledc_set_duty_and_update(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1 << 12), 0); #endif } static void wifi_led_set_on() { #if WIFI_LED_ENABLE ledc_set_duty_and_update(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1 << 14) - 1, 0); #endif } /** * @brief called by wifi_event_handler on scan done * */ static void wifi_event_scan_channel_done(uint16_t number, wifi_ap_record_t *aps) { ctx.scan.nr_aps_in_channel = number; ctx.scan.total_aps += number; if (ctx.task) { xTaskNotifyGive(ctx.task); } } static int scan_loop() { uint32_t ret; uint16_t number; ctx.scan.total_aps = 0; for (int scan_channel = 1; scan_channel <= 13; ++scan_channel) { number = ctx.scan.max_ap - ctx.scan.total_aps; if (wifi_event_trigger_scan(scan_channel, wifi_event_scan_channel_done, number, &ctx.scan.ap[ctx.scan.total_aps])) { ESP_LOGE(TAG, "trigger scan %d error", scan_channel); return 1; } /* shadow wifi_event_scan_channel_done() called */ vTaskDelay(pdMS_TO_TICKS(100)); ret = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)); if (ret == 0) { /* timeout */ ESP_LOGE(TAG, "scan channel %d timeout", scan_channel); return 1; } } return 0; } int wifi_manager_get_scan_list(uint16_t *number, wifi_ap_record_t *aps) { int err; int broke_endless_connect = 0; if (xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(0)) != pdTRUE) { if (ctx.is_endless_connect == 0) { return 1; } /* Is in connecting mode */ ESP_LOGI(TAG, "deleting connecting %p", ctx.task); vTaskDelete(ctx.task); /* in case lock is released when deleting the task */ xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(0)); esp_wifi_disconnect(); ctx.is_endless_connect = 0; broke_endless_connect = 1; } else if (ctx.is_endless_connect == 1) { ESP_LOGI(TAG, "deleting delay %p", ctx.task); vTaskDelete(ctx.task); ctx.is_endless_connect = 0; broke_endless_connect = 1; } ctx.scan.ap = aps; ctx.scan.max_ap = *number; ctx.scan.cb = NULL; ctx.task = xTaskGetCurrentTaskHandle(); err = scan_loop(); xSemaphoreGive(ctx.lock); *number = ctx.scan.total_aps; if (broke_endless_connect) { if (set_default_sta_cred() == 0) { disconn_handler(); } } return err; } void *wifi_manager_get_ap_netif() { return ap_netif; } void *wifi_manager_get_sta_netif() { return sta_netif; } static void try_connect_done(void *arg, ip_event_got_ip_t *event) { ctx.conn.event = event; if (ctx.task) { xTaskNotifyGive(ctx.task); } if (ctx.conn.need_unlock) { ctx.conn.need_unlock = 0; xSemaphoreGive(ctx.lock); } } int set_default_sta_cred() { int err; wifi_credential_t credential; err = wifi_data_get_sta_last_conn_cred(&credential); if (err) { return err; } set_sta_cred(credential.ssid, credential.password); return 0; } int wifi_manager_connect(const char *ssid, const char *password) { if (xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(0)) != pdTRUE) { if (ctx.is_endless_connect == 0) { return 1; } vTaskDelete(ctx.task); ctx.is_endless_connect = 0; } int ret; set_sta_cred(ssid, password); ctx.task = xTaskGetCurrentTaskHandle(); ctx.auto_reconnect = 1; wifi_event_trigger_connect(2, try_connect_done, NULL); ret = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10000)); xSemaphoreGive(ctx.lock); /* check connect done status */ if (ret == 0 || ctx.conn.event == NULL) { ESP_LOGI(TAG, "conn error"); /* try to connect to last connected sta */ if (set_default_sta_cred() == 0) { disconn_handler(); } return 1; } /* connection success: overwrite last connected credential */ wifi_led_set_on(); handle_wifi_connected(); wifi_credential_t credential; memcpy(credential.ssid, ssid, 32); memcpy(credential.password, password, 64); ret = wifi_data_save_sta_ap_credential(&credential); if (ret) { ESP_LOGE(TAG, "nvs save error: %s", esp_err_to_name(ret)); } return 0; } int wifi_manager_change_mode(wifi_apsta_mode_e mode) { int err; if (ctx.permanent_mode == mode) { return 0; } err = xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(2000)); if (err != pdTRUE) { ESP_LOGE(TAG, "set mode take lock"); return 1; } if (ctx.delayed_stopAP_task) { xTaskNotifyGive(ctx.delayed_stopAP_task); } if (ctx.delayed_startAP_task) { xTaskNotifyGive(ctx.delayed_startAP_task); } err = set_wifi_mode(mode); xSemaphoreGive(ctx.lock); if (err) return err; if (mode >= WIFI_AP_STOP){ return ESP_OK; } return wifi_data_save_wifi_mode(mode); } int set_wifi_mode(wifi_apsta_mode_e mode) { uint32_t new_mode = ctx.mode; printf("change mode: %d\n", mode); switch (mode) { default: return 1; case WIFI_AP_AUTO_STA_ON: if (ctx.is_sta_connected) { new_mode = WIFI_MODE_STA; } else { new_mode = WIFI_MODE_APSTA; } ctx.permanent_mode = mode; break; case WIFI_AP_STA_OFF: case WIFI_AP_ON_STA_OFF: case WIFI_AP_OFF_STA_ON: case WIFI_AP_STA_ON: ctx.permanent_mode = mode; new_mode = mode & (~WIFI_AP_STA_OFF); break; case WIFI_AP_STOP: new_mode &= ~WIFI_MODE_AP; break; case WIFI_AP_START: new_mode |= WIFI_MODE_AP; break; case WIFI_STA_STOP: new_mode &= ~WIFI_MODE_STA; break; case WIFI_STA_START: new_mode |= WIFI_MODE_STA; break; } printf("set mode to %lx\n", new_mode); if (new_mode == ctx.mode) { return 0; } int err = esp_wifi_set_mode(new_mode); if (err) { printf("set mode ret err %x\n", err); return err; } /* AP -> APSTA */ if (!(ctx.mode & WIFI_MODE_STA) && (new_mode & WIFI_MODE_STA)) { printf("set mode reco\n"); xSemaphoreGive(ctx.lock); disconn_handler(); } ctx.mode = new_mode; printf("set mode ret %x\n", err); return err; } static void set_sta_cred(const char *ssid, const char *password) { wifi_config_t wifi_config = { .sta = { .threshold.rssi = -80, .sort_method = WIFI_CONNECT_AP_BY_SIGNAL, }, }; memcpy((char *)wifi_config.sta.ssid, ssid, 32); memcpy((char *)wifi_config.sta.password, password, 64); int err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); if (err) { ESP_LOGE(TAG, "%s", esp_err_to_name(err)); } } static void delayed_set_ap_stop(void *arg) { uint32_t ret = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(30000)); xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(10000)); /* timeout: connected to STA without disconnection: close AP */ if (ret == 0) { set_wifi_mode(WIFI_AP_STOP); ctx.try_connect_count = 0; } ctx.delayed_stopAP_task = NULL; xSemaphoreGive(ctx.lock); vTaskDelete(NULL); } static void delayed_set_ap_start(void *arg) { uint32_t ret = 0; if (!(ctx.try_connect_count > 5)) { ret = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10000)); } xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(10000)); /* timeout: connected to STA without disconnection for 10 sec: close AP */ /* or consecutive connect/disconnect */ if (ret == 0 && ctx.permanent_mode == WIFI_AP_AUTO_STA_ON) { set_wifi_mode(WIFI_AP_START); ctx.try_connect_count = 0; } ctx.delayed_startAP_task = NULL; xSemaphoreGive(ctx.lock); vTaskDelete(NULL); } static void handle_wifi_connected() { ctx.is_sta_connected = true; if (ctx.delayed_startAP_task) { printf("clear start ap task"); xTaskNotifyGive(ctx.delayed_startAP_task); } if (ctx.permanent_mode != WIFI_AP_AUTO_STA_ON) { return; } printf("stop ap task"); xTaskCreate(delayed_set_ap_stop, "stop ap", 4096, NULL, tskIDLE_PRIORITY + 1, &ctx.delayed_stopAP_task); } static void handle_wifi_disconnected() { ctx.is_sta_connected = false; if (ctx.delayed_stopAP_task) { printf("clear stop ap task"); xTaskNotifyGive(ctx.delayed_stopAP_task); } if (ctx.permanent_mode != WIFI_AP_AUTO_STA_ON) { return; } printf("start ap task"); ctx.try_connect_count++; xTaskCreate(delayed_set_ap_start, "start ap", 4096, NULL, tskIDLE_PRIORITY + 1, &ctx.delayed_startAP_task); } static void reconnection_task(void *arg) { int err; ctx.is_endless_connect = 1; ctx.task = xTaskGetCurrentTaskHandle(); do { ESP_LOGI(TAG, "reco task: try connect, task %p", xTaskGetCurrentTaskHandle()); if (ctx.do_fast_connect) { ctx.do_fast_connect = 0; err = wifi_event_trigger_connect(3, try_connect_done, NULL); } else { err = wifi_event_trigger_connect(0, try_connect_done, NULL); } if (err) { ESP_LOGE(TAG, "trigger connect err: %s", esp_err_to_name(err)); break; } ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20000)); if (ctx.conn.event || ctx.auto_reconnect == 0) { /* reconnection successful or stop reconnect */ if (ctx.conn.event) { /* sta connected */ wifi_led_set_on(); handle_wifi_connected(); } break; } /* long wait to not spam try to reconnect */ xSemaphoreGive(ctx.lock); ESP_LOGI(TAG, "retry connection in 5 seconds"); vTaskDelay(pdMS_TO_TICKS(5 * 1000)); if (xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(0)) != pdTRUE) { ESP_LOGE(TAG, "reconnection failed"); break; } } while (1); ctx.is_endless_connect = 0; xSemaphoreGive(ctx.lock); vTaskDelete(NULL); } static void disconn_handler(void) { /* disconnected * 1. WI-FI AP is far away * 2. WI-FI AP or AP device is closed * 3. this device is ejected by AP * */ wifi_led_set_blink(); handle_wifi_disconnected(); if (ctx.auto_reconnect == 0) { return; } if (xSemaphoreTake(ctx.lock, pdMS_TO_TICKS(0)) != pdTRUE) { ESP_LOGE(TAG, "disconn_handler failed"); return; } ESP_LOGI(TAG, "start reconn task"); ctx.do_fast_connect = 1; xTaskCreate(reconnection_task, "reconn task", 4 * 1024, NULL, 7, NULL); } /** * @brief kill reconnection task * @return */ int wifi_manager_disconnect(void) { ctx.auto_reconnect = 0; return esp_wifi_disconnect(); } int wifi_manager_get_ap_auto_delay(int *ap_on_delay, int *ap_off_delay) { *ap_on_delay = pdTICKS_TO_MS(ctx.ap_on_delay_tick); *ap_off_delay = pdTICKS_TO_MS(ctx.ap_off_delay_tick); return 0; } int wifi_manager_set_ap_auto_delay(int *ap_on_delay, int *ap_off_delay) { ctx.ap_on_delay_tick = pdMS_TO_TICKS(*ap_on_delay); ctx.ap_on_delay_tick = pdMS_TO_TICKS(*ap_off_delay); return wifi_manager_get_ap_auto_delay(ap_on_delay, ap_off_delay); } int wifi_manager_get_mode(wifi_apsta_mode_e *mode, wifi_mode_t *status) { *mode = ctx.permanent_mode; *status = ctx.mode; return 0; } int wifi_manager_set_ap_credential(wifi_credential_t *cred) { wifi_config_t ap_config = {0}; wifi_mode_t mode; strncpy((char *)ap_config.ap.ssid, cred->ssid, 32); strncpy((char *)ap_config.ap.password, cred->password, 64); ap_config.ap.authmode = WIFI_AUTH_WPA2_WPA3_PSK; ap_config.ap.max_connection = 4; ap_config.ap.channel = 6; ap_config.ap.ssid_hidden = 0; if (esp_wifi_get_mode(&mode)) { return 1; } if (mode & WIFI_MODE_AP) { int err = esp_wifi_set_config(WIFI_IF_AP, &ap_config); if (err) { ESP_LOGE(TAG, "ap conf %s", esp_err_to_name(err)); } } return 0; } int wifi_manager_sta_set_static_conf(wifi_api_sta_ap_static_info_t *static_info) { if (static_info->static_ip_en) { esp_netif_ip_info_t ip_info; ip_info.ip.addr = static_info->ip.addr; ip_info.gw.addr = static_info->gateway.addr; ip_info.netmask.addr = static_info->netmask.addr; esp_netif_dhcpc_stop(wifi_manager_get_sta_netif()); esp_netif_set_ip_info(wifi_manager_get_sta_netif(), &ip_info); } else { esp_netif_dhcpc_start(wifi_manager_get_sta_netif()); } if (static_info->static_dns_en) { esp_netif_dns_info_t dns_info; dns_info.ip.type = ESP_IPADDR_TYPE_V4; dns_info.ip.u_addr.ip4.addr = static_info->dns_main.addr; esp_netif_set_dns_info(wifi_manager_get_sta_netif(), ESP_NETIF_DNS_MAIN, &dns_info); dns_info.ip.u_addr.ip4.addr = static_info->dns_backup.addr; esp_netif_set_dns_info(wifi_manager_get_sta_netif(), ESP_NETIF_DNS_BACKUP, &dns_info); } else { esp_netif_ip_info_t ip_info; esp_netif_dns_info_t dns_info; dns_info.ip.type = ESP_IPADDR_TYPE_V4; esp_netif_get_ip_info(wifi_manager_get_sta_netif(), &ip_info); dns_info.ip.u_addr.ip4.addr = ip_info.gw.addr; dns_info.ip.type = ESP_NETIF_DNS_MAIN; esp_netif_set_dns_info(wifi_manager_get_sta_netif(), ESP_NETIF_DNS_MAIN, &dns_info); } return 0; }