From af02fbb1175149a63f062f19ef9a2454d728c1f1 Mon Sep 17 00:00:00 2001 From: Thaddeus Hughes Date: Wed, 11 Mar 2026 10:05:13 -0500 Subject: [PATCH] move nvs init around, more graceful bt/wifi bringup/down, led indicatorrs --- main/bt_hid.c | 18 +++++++++++---- main/main.c | 44 ++++++++++++++++++++++++------------ main/rf_433.c | 12 ++++++---- main/rtc.c | 3 ++- main/rtc.h | 1 + main/storage.c | 17 +++++++++++--- main/webserver.c | 58 ++++++++++++++++++++++++++---------------------- 7 files changed, 101 insertions(+), 52 deletions(-) diff --git a/main/bt_hid.c b/main/bt_hid.c index 7add0f1..33e4ed6 100644 --- a/main/bt_hid.c +++ b/main/bt_hid.c @@ -60,7 +60,8 @@ // --------------------------------------------------------------------------- #define TAG "BT_HID" -#define BT_HID_SCAN_DURATION_S 3 /* ceiling only — scan stops early on first HID hit */ +#define BT_HID_SCAN_DURATION_S 3 +#define BT_HID_MIN_RSSI (-70) /* dBm — ignore devices weaker than this */ #define BT_HID_RECONNECT_MS 2000 #define BT_HID_BOND_TIMEOUT_MS 5000 /* wait for saved device before scanning */ #define BT_HID_CONNECT_WAIT_MS 5000 /* wait after open() before next loop */ @@ -245,6 +246,9 @@ static void hidh_callback(void *handler_args, nvs_save_bda(bda, s_connect_addr_type); } else { ESP_LOGE(TAG, "OPEN failed, status=%d", p->open.status); + /* Free the failed device handle — not doing so leaks a BT + * resource and can block future connection attempts. */ + esp_hidh_dev_free(p->open.dev); } break; } @@ -342,6 +346,15 @@ static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, memcpy(name, name_d, copy); } + if (param->scan_rst.rssi < BT_HID_MIN_RSSI) { + ESP_LOGD(TAG, "SCAN %02x:%02x:%02x:%02x:%02x:%02x RSSI:%d '%s' (too weak, skipping)", + param->scan_rst.bda[0], param->scan_rst.bda[1], + param->scan_rst.bda[2], param->scan_rst.bda[3], + param->scan_rst.bda[4], param->scan_rst.bda[5], + param->scan_rst.rssi, name); + break; + } + ESP_LOGI(TAG, "SCAN %02x:%02x:%02x:%02x:%02x:%02x RSSI:%d '%s' <<< HID", param->scan_rst.bda[0], param->scan_rst.bda[1], param->scan_rst.bda[2], param->scan_rst.bda[3], @@ -352,9 +365,6 @@ static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, param->scan_rst.ble_addr_type, name, param->scan_rst.rssi); - - /* Stop scanning immediately — we have what we need. */ - esp_ble_gap_stop_scanning(); break; } diff --git a/main/main.c b/main/main.c index 3466a16..6c99fea 100644 --- a/main/main.c +++ b/main/main.c @@ -229,8 +229,30 @@ void app_main(void) {esp_task_wdt_add(NULL); while(true) { vTaskDelayUntil(&xLastWakeTime, xFrequency); + /* In soft idle: slow poll (5s) via direct GPIO, no I2C. */ + if (soft_idle_is_active()) { + //vTaskDelay(pdMS_TO_TICKS(1000)); + if (soft_idle_button_raw()) { + rtc_reset_shutdown_timer(); + soft_idle_exit(); + i2c_poll_buttons(); /* sync TCA9555 state after idle */ + xLastWakeTime = xTaskGetTickCount(); + } + if (rtc_alarm_tripped()) { + soft_idle_exit(); + xLastWakeTime = xTaskGetTickCount(); + vTaskDelay(pdMS_TO_TICKS(500)); + fsm_request(FSM_CMD_START); + rtc_schedule_next_alarm(); + } + solar_run_fsm(); + rtc_check_shutdown_timer(); + esp_task_wdt_reset(); + continue; + } + i2c_poll_buttons(); - + if (i2c_get_button_state(0)) { rtc_reset_shutdown_timer(); soft_idle_exit(); @@ -248,22 +270,22 @@ void app_main(void) {esp_task_wdt_add(NULL); } else if (i2c_get_button_ms(0) > 100){ drive_leds(LED_STATE_START1); } else { - /*if ( + if ( rtc_is_set() && - !efuse_is_tripped(BRIDGE_JACK) && - !efuse_is_tripped(BRIDGE_AUX) && - !efuse_is_tripped(BRIDGE_DRIVE) && + efuse_get(BRIDGE_JACK)==EFUSE_OK && + efuse_get(BRIDGE_AUX)==EFUSE_OK && + efuse_get(BRIDGE_DRIVE)==EFUSE_OK && fsm_get_error() == ESP_OK ) { drive_leds(LED_STATE_AWAKE); } else { drive_leds(LED_STATE_ERROR); - }*/ + } - int8_t state = 0b001; + /*int8_t state = 0b001; if (get_is_safe()) state |= 0b010; if (get_sensor(SENSOR_SAFETY)) state |= 0b100; - i2c_set_led1(state); + i2c_set_led1(state);*/ } // when not actively moving we log at a low frequency (every 120s) @@ -321,12 +343,6 @@ void app_main(void) {esp_task_wdt_add(NULL); if (rtc_alarm_tripped()) { - bool was_idle = soft_idle_is_active(); - soft_idle_exit(); - if (was_idle) { - // Give WiFi softAP time to come up before movement begins - vTaskDelay(pdMS_TO_TICKS(500)); - } fsm_request(FSM_CMD_START); rtc_schedule_next_alarm(); } diff --git a/main/rf_433.c b/main/rf_433.c index 15316b9..884c882 100644 --- a/main/rf_433.c +++ b/main/rf_433.c @@ -61,17 +61,21 @@ static void rf_433_receiver_task(void* param) { rmt_channel_handle_t rx_channel = NULL; rmt_symbol_word_t symbols[64]; rmt_rx_done_event_data_t rx_data; - + rmt_receive_config_t rx_config = { - .signal_range_min_ns = 2000, + /* Hardware filter max on ESP32 is ~3187 ns (8-bit APB counter). + * Filters sub-3µs glitches; can't filter longer noise pulses in HW. + * Buffer overflow from other 433 MHz devices is benign — the decoder + * only reads the first 24 symbols regardless of total length. */ + .signal_range_min_ns = 3000, .signal_range_max_ns = 1250000, }; - + rmt_rx_channel_config_t rx_ch_conf = { .gpio_num = (gpio_num_t)RF_PIN, .clk_src = RMT_CLK_SRC_DEFAULT, .resolution_hz = 1000000, - .mem_block_symbols = 64, + .mem_block_symbols = 64, /* ESP32 non-DMA RMT: 1 block = 64 symbols max */ .flags = { .invert_in = false, .with_dma = false, diff --git a/main/rtc.c b/main/rtc.c index ae9080c..513934e 100644 --- a/main/rtc.c +++ b/main/rtc.c @@ -84,7 +84,8 @@ void soft_idle_enter(void) i2c_set_led1(0); } -bool soft_idle_is_active(void) { return in_soft_idle; } +bool soft_idle_is_active(void) { return in_soft_idle; } +bool soft_idle_button_raw(void) { return gpio_get_level(PIN_BTN_INTERRUPT) == 0; } void soft_idle_exit(void) { diff --git a/main/rtc.h b/main/rtc.h index a55d465..637cf2b 100644 --- a/main/rtc.h +++ b/main/rtc.h @@ -35,6 +35,7 @@ void rtc_reset_shutdown_timer(); // reset shutoff timer void soft_idle_enter(void); void soft_idle_exit(void); bool soft_idle_is_active(void); +bool soft_idle_button_raw(void); /* direct GPIO read, no I2C */ esp_sleep_wakeup_cause_t rtc_wakeup_cause(); /*void adjust_rtc_hour(char *key, int8_t dir); diff --git a/main/storage.c b/main/storage.c index 6774de6..b279ac8 100644 --- a/main/storage.c +++ b/main/storage.c @@ -9,6 +9,7 @@ #include "storage.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" +#include "nvs_flash.h" #include "version.h" #define TAG "STORAGE" @@ -423,9 +424,19 @@ esp_err_t factory_reset(void) { // ============================================================================ esp_err_t storage_init(void) { ESP_LOGI(TAG, "Initializing storage system..."); - - - + + // NVS must be initialized before WiFi and BT + esp_err_t nvs_err = nvs_flash_init(); + if (nvs_err == ESP_ERR_NVS_NO_FREE_PAGES || nvs_err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "NVS partition needs erasing, performing erase..."); + nvs_err = nvs_flash_erase(); + if (nvs_err == ESP_OK) nvs_err = nvs_flash_init(); + } + if (nvs_err != ESP_OK) { + ESP_LOGE(TAG, "nvs_flash_init failed: %s", esp_err_to_name(nvs_err)); + return nvs_err; + } + log_mutex = xSemaphoreCreateMutex(); if (log_mutex == NULL) { ESP_LOGE(TAG, "Failed to create log mutex"); diff --git a/main/webserver.c b/main/webserver.c index e112183..7f47119 100644 --- a/main/webserver.c +++ b/main/webserver.c @@ -432,6 +432,9 @@ static esp_err_t get_handler(httpd_req_t *req) { * } * } */ +static void soft_idle_enter_cb(void *arg) { soft_idle_enter(); } +static void webserver_restart_wifi_cb(void *arg) { webserver_restart_wifi(); } + /** * Unified POST handler - handles commands, parameter updates, time updates */ @@ -523,15 +526,39 @@ static esp_err_t post_handler(httpd_req_t *req) { } if (should_restart_wifi) { - vTaskDelay(pdMS_TO_TICKS(500)); // Let the TCP response flush - webserver_restart_wifi(); + /* Same deadlock risk as should_sleep — httpd_stop() inside + * webserver_restart_wifi() cannot be called from within a handler. */ + static esp_timer_handle_t s_wifi_restart_timer = NULL; + if (s_wifi_restart_timer == NULL) { + esp_timer_create_args_t ta = { + .callback = webserver_restart_wifi_cb, + .name = "wifi_restart", + }; + esp_timer_create(&ta, &s_wifi_restart_timer); + } + if (s_wifi_restart_timer != NULL) { + esp_timer_start_once(s_wifi_restart_timer, 500 * 1000); /* 500 ms in µs */ + } return ESP_OK; } if (should_sleep) { ESP_LOGI(TAG, "Entering soft idle in 2 seconds..."); - vTaskDelay(pdMS_TO_TICKS(2000)); - soft_idle_enter(); + /* Cannot call soft_idle_enter() (→ httpd_stop()) from within an httpd + * handler — httpd_stop() waits for all handlers to finish, causing a + * deadlock. Schedule via a one-shot timer so this handler returns + * first and the httpd task is free. */ + static esp_timer_handle_t s_sleep_timer = NULL; + if (s_sleep_timer == NULL) { + esp_timer_create_args_t ta = { + .callback = soft_idle_enter_cb, + .name = "soft_idle", + }; + esp_timer_create(&ta, &s_sleep_timer); + } + if (s_sleep_timer != NULL) { + esp_timer_start_once(s_sleep_timer, 2000 * 1000); /* 2 s in µs */ + } return ESP_OK; } @@ -881,29 +908,8 @@ static esp_err_t launch_soft_ap(void) { ESP_LOGI(TAG, "AP LAUNCHING"); - err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { - // NVS partition was truncated and needs to be erased - ESP_LOGW(TAG, "NVS partition needs erasing, performing erase..."); - err = nvs_flash_erase(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to erase NVS: %s", esp_err_to_name(err)); - return err; - } - // Retry init after erase - err = nvs_flash_init(); - } - - ESP_LOGI(TAG, "AP LAUNCHING..."); - - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize NVS: %s", esp_err_to_name(err)); - return err; - } - - ESP_LOGI(TAG, "HI THERE"); - + err = esp_netif_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize netif: %s", esp_err_to_name(err));