rtc craziness

This commit is contained in:
Thaddeus Hughes
2026-03-11 09:01:32 -05:00
parent e2451fce78
commit f4077e5e26
40 changed files with 45559 additions and 131 deletions

View File

@@ -60,7 +60,7 @@
// ---------------------------------------------------------------------------
#define TAG "BT_HID"
#define BT_HID_SCAN_DURATION_S 5
#define BT_HID_SCAN_DURATION_S 3 /* ceiling only — scan stops early on first HID hit */
#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 */
@@ -103,8 +103,13 @@ static portMUX_TYPE s_mux = portMUX_INITIALIZER_UNLOCKED;
static SemaphoreHandle_t s_scan_sem = NULL;
/* Set once we have a saved BDA from NVS (direct reconnect path). */
static bool s_has_saved_bda = false;
static esp_bd_addr_t s_saved_bda = {0};
static bool s_has_saved_bda = false;
static esp_bd_addr_t s_saved_bda = {0};
static esp_ble_addr_type_t s_saved_addr_type = BLE_ADDR_TYPE_RANDOM;
/* Address type used for the most recent esp_hidh_dev_open() call.
* Set immediately before open so the OPEN callback can persist it. */
static esp_ble_addr_type_t s_connect_addr_type = BLE_ADDR_TYPE_RANDOM;
// ---------------------------------------------------------------------------
// Scan-result list (heap-allocated, freed after each scan round)
@@ -121,6 +126,8 @@ typedef struct scan_result_s {
static scan_result_t *s_scan_results = NULL;
static size_t s_num_scan_results = 0;
static TaskHandle_t s_scan_task_handle = NULL;
static scan_result_t *find_scan_result(const esp_bd_addr_t bda)
{
scan_result_t *r = s_scan_results;
@@ -163,23 +170,29 @@ static void free_scan_results(void)
// NVS helpers — store / load the last-connected BDA
// ---------------------------------------------------------------------------
static void nvs_save_bda(const esp_bd_addr_t bda)
static void nvs_save_bda(const esp_bd_addr_t bda, esp_ble_addr_type_t addr_type)
{
nvs_handle_t h;
if (nvs_open(BT_HID_NVS_NAMESPACE, NVS_READWRITE, &h) != ESP_OK) return;
nvs_set_blob(h, BT_HID_NVS_BDA_KEY, bda, sizeof(esp_bd_addr_t));
nvs_set_u8(h, "addr_type", (uint8_t)addr_type);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Saved BDA %02x:%02x:%02x:%02x:%02x:%02x to NVS",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
ESP_LOGI(TAG, "Saved BDA %02x:%02x:%02x:%02x:%02x:%02x (addr_type=%d) to NVS",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], (int)addr_type);
}
static bool nvs_load_bda(esp_bd_addr_t out_bda)
static bool nvs_load_bda(esp_bd_addr_t out_bda, esp_ble_addr_type_t *out_addr_type)
{
nvs_handle_t h;
if (nvs_open(BT_HID_NVS_NAMESPACE, NVS_READONLY, &h) != ESP_OK) return false;
size_t len = sizeof(esp_bd_addr_t);
bool ok = (nvs_get_blob(h, BT_HID_NVS_BDA_KEY, out_bda, &len) == ESP_OK);
if (ok) {
uint8_t at = (uint8_t)BLE_ADDR_TYPE_RANDOM;
nvs_get_u8(h, "addr_type", &at); /* missing key → stays RANDOM, safe default */
*out_addr_type = (esp_ble_addr_type_t)at;
}
nvs_close(h);
return ok;
}
@@ -229,7 +242,7 @@ static void hidh_callback(void *handler_args,
memcpy(s_remote.bda, bda, sizeof(esp_bd_addr_t));
taskEXIT_CRITICAL(&s_mux);
nvs_save_bda(bda);
nvs_save_bda(bda, s_connect_addr_type);
} else {
ESP_LOGE(TAG, "OPEN failed, status=%d", p->open.status);
}
@@ -339,6 +352,9 @@ 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;
}
@@ -405,7 +421,8 @@ static void bt_hid_scan_task(void *pvParameters)
* BLE_ADDR_TYPE_RANDOM is the most common for consumer remotes; if it
* fails the outer scan loop will find it correctly.
*/
esp_hidh_dev_open(s_saved_bda, ESP_HID_TRANSPORT_BLE, BLE_ADDR_TYPE_RANDOM);
s_connect_addr_type = s_saved_addr_type;
esp_hidh_dev_open(s_saved_bda, ESP_HID_TRANSPORT_BLE, s_saved_addr_type);
vTaskDelay(pdMS_TO_TICKS(BT_HID_BOND_TIMEOUT_MS));
/* If the open succeeded the state will be CONNECTED — skip to main loop. */
@@ -461,6 +478,7 @@ static void bt_hid_scan_task(void *pvParameters)
best->bda[3], best->bda[4], best->bda[5],
best->name, best->rssi);
s_connect_addr_type = best->addr_type;
esp_hidh_dev_open(best->bda, ESP_HID_TRANSPORT_BLE, best->addr_type);
free_scan_results();
@@ -536,7 +554,7 @@ esp_err_t bt_hid_init(void)
}
/* Try to load a previously bonded device address from NVS. */
s_has_saved_bda = nvs_load_bda(s_saved_bda);
s_has_saved_bda = nvs_load_bda(s_saved_bda, &s_saved_addr_type);
if (s_has_saved_bda) {
ESP_LOGI(TAG, "Found saved BDA in NVS — will try direct reconnect first");
}
@@ -551,8 +569,24 @@ esp_err_t bt_hid_init(void)
* Priority 4: above webserver (typically 56 on core 0) is fine; below
* the FSM (10) and RF task (5) so control always wins CPU.
*/
xTaskCreate(bt_hid_scan_task, "bt_hid_scan", 6 * 1024, NULL, 4, NULL);
xTaskCreate(bt_hid_scan_task, "bt_hid_scan", 6 * 1024, NULL, 4, &s_scan_task_handle);
ESP_LOGI(TAG, "BLE HID host initialised");
return ESP_OK;
}
void bt_hid_stop(void)
{
if (s_scan_task_handle != NULL) {
vTaskSuspend(s_scan_task_handle);
ESP_LOGI(TAG, "BT HID scan task suspended");
}
}
void bt_hid_resume(void)
{
if (s_scan_task_handle != NULL) {
vTaskResume(s_scan_task_handle);
ESP_LOGI(TAG, "BT HID scan task resumed");
}
}

View File

@@ -27,5 +27,7 @@
#define BT_HID_REPEAT_MS 50
esp_err_t bt_hid_init(void);
void bt_hid_stop(void); // Suspend BT scan task (soft idle)
void bt_hid_resume(void); // Resume BT scan task (wake from soft idle)
#endif /* BT_HID_H */

View File

@@ -70,7 +70,9 @@ static inline void set_timer(uint64_t us) {
static inline bool timer_done() { return fsm_now >= timer_end; }
void pulse_override(fsm_override_t cmd) {
if (soft_idle_is_active()) return;
if (current_state == STATE_IDLE) {
rtc_reset_shutdown_timer();
override_cmd = cmd;
override_time = fsm_now + get_param_value_t(PARAM_RF_PULSE_LENGTH).u32;
}
@@ -84,8 +86,13 @@ int64_t fsm_get_cal_e(){return fsm_cal_e;}
void fsm_request(fsm_cmd_t cmd)
{
if (fsm_cmd_queue != NULL)
xQueueSend(fsm_cmd_queue, &cmd, 0); // Safe from any context
// STOP always goes through (safety). All other commands are blocked during soft idle —
// the device must be woken by physical button or alarm before remote/RF movement is allowed.
if (cmd != FSM_CMD_STOP && soft_idle_is_active()) return;
rtc_reset_shutdown_timer(); // any accepted command extends the wake period
if (fsm_cmd_queue != NULL)
xQueueSend(fsm_cmd_queue, &cmd, 0); // safe from any context
}
int8_t fsm_get_current_progress(int8_t denominator) {

View File

@@ -103,31 +103,29 @@ void drive_leds(led_state_t state) {
}
}
RTC_DATA_ATTR bool first_boot = true;
void app_main(void) {esp_task_wdt_add(NULL);
//run_all_log_tests();
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
rtc_restore_time(); // Restore time from RTC backup if we crashed
// Say hello; turn on the lights
esp_sleep_wakeup_cause_t cause = rtc_wakeup_cause();
if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED");
i2c_set_relays((relay_port_t){.raw=0});
drive_leds(LED_STATE_BOOTING);
ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING);
ESP_LOGI(TAG, "Version: %s", FIRMWARE_VERSION);
ESP_LOGI(TAG, "Branch: %s", FIRMWARE_BRANCH);
ESP_LOGI(TAG, "Built: %s", BUILD_DATE);
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
rtc_restore_time(); // Recover time from RTC domain if we crashed
// Say hello; turn on the lights
rtc_wakeup_cause(); // log wakeup cause (informational only)
if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED");
i2c_set_relays((relay_port_t){.raw=0});
drive_leds(LED_STATE_BOOTING);
// Check for factory reset condition: Cold boot + button held
// This is a cold boot (power-on or hard reset)
// Check if button is being held (pin is LOW)
if (first_boot && gpio_get_level(GPIO_NUM_13) == 0) {
// Check for factory reset condition: Cold boot (power-on/ext-reset) + button held
esp_reset_reason_t boot_reset_reason = esp_reset_reason();
if ((boot_reset_reason == ESP_RST_POWERON || boot_reset_reason == ESP_RST_EXT)
&& gpio_get_level(GPIO_NUM_13) == 0) {
ESP_LOGW(TAG, "FACTORY RESET TRIGGERED - Button held on cold boot");
// Flash LED pattern to indicate factory reset
@@ -209,26 +207,7 @@ void app_main(void) {esp_task_wdt_add(NULL);
//write_dummy_log_1();
// Check wake reasons
// If button held, we stay #woke
// If not it must've been the RTC - check alarms
// If there's an alarm or the button was held, do a full boot
// Full boot will handle things from there
// only truly wake up if we saw it on EXT0, or from alarm
/*if (!rtc_is_set()) {
//ESP_LOGI("MAIN", "RTC is not set. Can't sleep til then.");
} else */if (cause == ESP_SLEEP_WAKEUP_EXT0) {
ESP_LOGI("MAIN", "Woke from button press");
} else {
if (!rtc_alarm_tripped() && !first_boot) {
rtc_enter_deep_sleep();
}
}
first_boot = false;
/*** FULL BOOT ***/
/*** FULL BOOT — always, every boot ***/
if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED");
//if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED");
if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED");
@@ -252,8 +231,10 @@ void app_main(void) {esp_task_wdt_add(NULL);
i2c_poll_buttons();
if (i2c_get_button_state(0))
if (i2c_get_button_state(0)) {
rtc_reset_shutdown_timer();
soft_idle_exit();
}
switch (fsm_get_state()) {
case STATE_IDLE:
@@ -285,8 +266,8 @@ void app_main(void) {esp_task_wdt_add(NULL);
i2c_set_led1(state);
}
// when not actively moving we log at a low frequency
if ((esp_timer_get_time() > last_bat_log_time + DEEP_SLEEP_US))
// when not actively moving we log at a low frequency (every 120s)
if ((esp_timer_get_time() > last_bat_log_time + 120000000ULL))
send_bat_log();
if(i2c_get_button_ms(0) > 2100)
@@ -340,13 +321,17 @@ 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();
}
solar_run_fsm();
rtc_save_time(); // Keep RTC backup fresh so crashes don't lose time
rtc_check_shutdown_timer();
esp_task_wdt_reset();
}

View File

@@ -8,6 +8,7 @@
*/
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
@@ -15,58 +16,55 @@
#include "rtc.h"
#include "control_fsm.h"
#include "esp_sleep.h"
#include "i2c.h" // for lcd_off()
#include "esp_timer.h"
#include "i2c.h"
#include "driver/gpio.h"
#include "rtc_wdt.h"
#include "esp_sleep.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "i2c.h"
#include "rtc_wdt.h"
// External 32kHz crystal enabled via CONFIG_RTC_CLK_SRC_EXT_CRYS in sdkconfig.defaults
#include "driver/rtc_io.h" // For RTC I/O handling (optional but recommended for pin configuration)
#include "soc/rtc.h" // For rtc_clk_slow_src_get()
#include "soc/rtc.h"
#include "solar.h"
#include "storage.h"
#include "webserver.h"
#include "bt_hid.h"
#define PIN_BTN_INTERRUPT GPIO_NUM_13
// Return microseconds from the RTC hardware timer.
// Used ONLY in rtc_restore_time() for crash-recovery (survives panics/WDT via RTC domain).
// RC oscillator drift (~150 kHz, ±5%) is negligible over a <30s crash restart (~1.5s worst case).
static uint64_t rtc_hw_time_us(void)
{
uint32_t cal = rtc_clk_cal(RTC_CAL_RTC_MUX, 20);
uint64_t ticks = rtc_time_get();
return (ticks * (uint64_t)cal) >> 19;
}
uint64_t last_activity_tick = 0;
// RTC_DATA_ATTR keeps these in RTC memory; persists across deep sleep AND software resets (panics, WDT)
RTC_DATA_ATTR int64_t next_alarm_time_s = -1;
RTC_DATA_ATTR bool rtc_set = false;
RTC_DATA_ATTR int64_t rtc_backup_s = 0; // Crash-safe time snapshot
// RTC_DATA_ATTR keeps these in RTC memory; persists across software resets (panics, WDT)
RTC_DATA_ATTR int64_t next_alarm_time_s = -1;
RTC_DATA_ATTR bool rtc_set = false;
RTC_DATA_ATTR int64_t sync_unix_us = 0; // Unix time in µs at last rtc_set_s() call
RTC_DATA_ATTR uint64_t sync_rtc_us = 0; // rtc_hw_time_us() at last rtc_set_s() call (for crash recovery)
// esp_timer value at last rtc_set_s() call. NOT RTC_DATA_ATTR — resets on every boot;
// rtc_restore_time() reinitialises it from the RTC hardware counter on crash recovery.
static uint64_t sync_esp_us = 0;
static bool in_soft_idle = false;
bool rtc_is_set() {
return rtc_set;
}
esp_err_t rtc_xtal_init(void) {
/* ---- Wake sources ---- */
esp_sleep_enable_ext0_wakeup(PIN_BTN_INTERRUPT, 0);
// 32kHz crystal no longer used — time tracking via esp_timer (40MHz APB crystal).
// Just configure the button GPIO; no sleep wakeup sources needed.
gpio_set_direction(PIN_BTN_INTERRUPT, GPIO_MODE_INPUT);
gpio_set_pull_mode(PIN_BTN_INTERRUPT, GPIO_PULLUP_ONLY);
esp_sleep_enable_ext0_wakeup(PIN_BTN_INTERRUPT, 0);
/* ---- Enable External 32 kHz Oscillator ---- */
// Configure RTC I/O pins for crystal (hold in reset initially if needed)
rtc_gpio_init(GPIO_NUM_32);
rtc_gpio_init(GPIO_NUM_33);
rtc_gpio_set_direction(GPIO_NUM_32, RTC_GPIO_MODE_DISABLED);
rtc_gpio_set_direction(GPIO_NUM_33, RTC_GPIO_MODE_DISABLED);
// 32kHz crystal selected as slow clock source via CONFIG_RTC_CLK_SRC_EXT_CRYS.
// Verify at runtime that the clock source is actually the external crystal.
soc_rtc_slow_clk_src_t slow_src = rtc_clk_slow_src_get();
if (slow_src == SOC_RTC_SLOW_CLK_SRC_XTAL32K) {
ESP_LOGI("RTC", "Slow clock: external 32kHz crystal");
} else {
ESP_LOGW("RTC", "Slow clock source is %d — expected XTAL32K (%d). Check sdkconfig.",
(int)slow_src, (int)SOC_RTC_SLOW_CLK_SRC_XTAL32K);
}
return ESP_OK;
}
@@ -76,54 +74,84 @@ void rtc_reset_shutdown_timer(void)
rtc_wdt_feed();
}
void rtc_enter_deep_sleep(void)
void soft_idle_enter(void)
{
//close_current_log();
fsm_request(FSM_CMD_STOP);
i2c_stop();
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_US);
esp_deep_sleep_start();
if (in_soft_idle) return;
in_soft_idle = true;
ESP_LOGI("RTC", "Entering soft idle (WiFi/BT off, LEDs off)");
webserver_stop();
bt_hid_stop();
i2c_set_led1(0);
}
bool soft_idle_is_active(void) { return in_soft_idle; }
void soft_idle_exit(void)
{
if (!in_soft_idle) return;
in_soft_idle = false;
ESP_LOGI("RTC", "Exiting soft idle");
webserver_restart_wifi();
bt_hid_resume();
rtc_reset_shutdown_timer();
}
int64_t rtc_get_s(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec;
if (!rtc_set) return 0;
return (int64_t)(sync_unix_us / 1000000LL)
+ (int64_t)((esp_timer_get_time() - sync_esp_us) / 1000000LL);
}
void rtc_set_s(int64_t tv_sec)
{
rtc_set = true;
settimeofday(&(struct timeval){.tv_sec = tv_sec, .tv_usec=0}, NULL);
rtc_backup_s = tv_sec;
sync_unix_us = tv_sec * 1000000LL;
sync_rtc_us = rtc_hw_time_us(); // kept for crash recovery in rtc_restore_time()
sync_esp_us = (uint64_t)esp_timer_get_time();
rtc_set = true;
// Keep stdlib (gmtime_r etc.) in sync
settimeofday(&(struct timeval){.tv_sec = tv_sec, .tv_usec = 0}, NULL);
solar_reset_fsm();
rtc_schedule_next_alarm();
uint64_t ts_ms = (uint64_t)tv_sec * 1000ULL;
log_write((uint8_t*)&ts_ms, sizeof(ts_ms), LOG_TYPE_TIME_SET);
// Parseable marker used by logtool/rtc_test.py to compare device vs host time
ESP_LOGI("RTC", "TIME unix=%lld src=SYNC uptime=%llds",
(long long)tv_sec,
(long long)(esp_timer_get_time() / 1000000ULL));
}
void rtc_save_time(void)
{
if (rtc_set)
rtc_backup_s = rtc_get_s();
// No-op: time is always derivable from sync_unix_us + rtc_hw_time_us() delta,
// both of which survive deep sleep and crashes via RTC_DATA_ATTR / RTC hardware.
}
void rtc_restore_time(void)
{
if (rtc_set && rtc_backup_s > 0) {
settimeofday(&(struct timeval){.tv_sec = rtc_backup_s, .tv_usec = 0}, NULL);
ESP_LOGI("RTC", "Time restored from crash backup: %lld", (long long)rtc_backup_s);
}
if (!rtc_set) return;
// Recover time via RTC hardware counter (survives panics/WDT resets via RTC domain).
// RC drift during a <30s crash restart is ~1.5s worst case — acceptable.
int64_t t = (sync_unix_us + (int64_t)(rtc_hw_time_us() - sync_rtc_us)) / 1000000LL;
// Anchor esp_timer tracking to recovered time — APB timer resets on every boot.
sync_unix_us = t * 1000000LL;
sync_esp_us = (uint64_t)esp_timer_get_time();
// Re-sync the stdlib clock (gettimeofday) for gmtime_r() etc.
settimeofday(&(struct timeval){.tv_sec = t, .tv_usec = 0}, NULL);
ESP_LOGI("RTC", "TIME unix=%lld src=CRASH uptime=%llds",
(long long)t,
(long long)(esp_timer_get_time() / 1000000ULL));
}
int64_t rtc_get_ms(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
if (!rtc_set) return 0;
return sync_unix_us / 1000LL
+ (int64_t)((esp_timer_get_time() - sync_esp_us) / 1000LL);
}
int64_t rtc_get_s_in_day(void)
@@ -147,10 +175,9 @@ esp_sleep_wakeup_cause_t rtc_wakeup_cause(void)
/* -------------------------------------------------------------------------- */
void rtc_check_shutdown_timer(void)
{
TickType_t elapsed = xTaskGetTickCount() - last_activity_tick;
if (elapsed * portTICK_PERIOD_MS >= POWER_INACTIVITY_TIMEOUT_MS)
rtc_enter_deep_sleep();
soft_idle_enter();
}
/* -------------------------------------------------------------------------- */
@@ -252,4 +279,76 @@ bool rtc_alarm_tripped() {
return false;
}
return rtc_get_s() > next_alarm_time_s;
}
static const char *reset_reason_str(esp_reset_reason_t r) {
switch (r) {
case ESP_RST_POWERON: return "POWER_ON";
case ESP_RST_EXT: return "EXT_PIN";
case ESP_RST_SW: return "SOFTWARE";
case ESP_RST_PANIC: return "PANIC";
case ESP_RST_INT_WDT: return "INT_WDT";
case ESP_RST_TASK_WDT: return "TASK_WDT";
case ESP_RST_WDT: return "OTHER_WDT";
case ESP_RST_DEEPSLEEP: return "DEEP_SLEEP";
case ESP_RST_BROWNOUT: return "BROWNOUT";
case ESP_RST_SDIO: return "SDIO";
default: return "UNKNOWN";
}
}
static const char *wakeup_cause_str(esp_sleep_wakeup_cause_t c) {
switch (c) {
case ESP_SLEEP_WAKEUP_UNDEFINED: return "UNDEFINED (normal boot/reset)";
case ESP_SLEEP_WAKEUP_EXT0: return "EXT0 (button)";
case ESP_SLEEP_WAKEUP_TIMER: return "TIMER";
default: return "OTHER";
}
}
void rtc_print_debug(void)
{
int64_t now_s = rtc_get_s();
int64_t uptime = (int64_t)(esp_timer_get_time() / 1000000ULL);
// Human-readable timestamps
char now_str[32] = "N/A";
char sync_str[32] = "N/A";
char alarm_str[32] = "N/A";
if (rtc_set) {
time_t t;
struct tm tm;
t = (time_t)now_s; gmtime_r(&t, &tm);
strftime(now_str, sizeof(now_str), "%Y-%m-%d %H:%M:%S", &tm);
t = (time_t)(sync_unix_us / 1000000LL); gmtime_r(&t, &tm);
strftime(sync_str, sizeof(sync_str), "%Y-%m-%d %H:%M:%S", &tm);
if (next_alarm_time_s > 0) {
t = (time_t)next_alarm_time_s; gmtime_r(&t, &tm);
strftime(alarm_str, sizeof(alarm_str), "%Y-%m-%d %H:%M:%S", &tm);
}
}
esp_reset_reason_t reset = esp_reset_reason();
esp_sleep_wakeup_cause_t wake = esp_sleep_get_wakeup_cause();
printf("\n=== RTC DEBUG ===\n");
printf(" reset_reason: %s (%d)\n", reset_reason_str(reset), (int)reset);
printf(" wakeup_cause: %s (%d)\n", wakeup_cause_str(wake), (int)wake);
printf(" time_source: esp_timer (40MHz APB crystal, ~20ppm)\n");
printf(" 32kHz_xtal: NOT USED (deep sleep disabled)\n");
printf("\n");
uint64_t esp_us_now = (uint64_t)esp_timer_get_time();
uint64_t elapsed_s = rtc_set ? (esp_us_now - sync_esp_us) / 1000000ULL : 0;
printf(" rtc_set: %s\n", rtc_set ? "true" : "false");
printf(" current_time: %lld (%s UTC)\n", (long long)now_s, now_str);
printf(" sync_time: %lld (%s UTC)\n", (long long)(sync_unix_us / 1000000LL), sync_str);
printf(" elapsed_since_sync:%llus\n", (unsigned long long)elapsed_s);
printf(" next_alarm_s: %lld (%s UTC)\n", (long long)next_alarm_time_s, alarm_str);
printf("\n");
printf(" uptime: %llds\n", (long long)uptime);
printf(" esp_timer_us: %llu\n", (unsigned long long)esp_us_now);
printf(" soft_idle: %s\n", in_soft_idle ? "YES" : "no");
printf("=================\n\n");
}

View File

@@ -20,7 +20,6 @@
#define POWER_INACTIVITY_TIMEOUT_MS 180000
#define DEEP_SLEEP_US 120000000ULL /* 120 seconds in deep sleep */
/* -------------------------------------------------------------------------- */
/* Public API */
@@ -33,7 +32,9 @@ bool rtc_is_set();
void rtc_check_shutdown_timer();
void rtc_reset_shutdown_timer(); // reset shutoff timer
void rtc_enter_deep_sleep();
void soft_idle_enter(void);
void soft_idle_exit(void);
bool soft_idle_is_active(void);
esp_sleep_wakeup_cause_t rtc_wakeup_cause();
/*void adjust_rtc_hour(char *key, int8_t dir);
@@ -43,13 +44,15 @@ int64_t rtc_get_s (void);
int64_t rtc_get_ms(void);
void rtc_set_s(int64_t);
void rtc_save_time(void); // Snapshot current time to RTC memory (call periodically)
void rtc_restore_time(void); // Restore time from RTC backup after a crash reboot
void rtc_save_time(void); // No-op: time is always live via rtc_get_s()
void rtc_restore_time(void); // Re-syncs stdlib clock on boot; emits TIME log marker
void rtc_schedule_next_alarm(void);
int64_t rtc_get_next_alarm_s();
bool rtc_alarm_tripped();
void rtc_print_debug(void); // Dump full RTC state to stdout (UART)
/* -------------------------------------------------------------------------- */
#endif /* MAIN_RTC_H_ */

View File

@@ -98,10 +98,10 @@ static void cmd_post(char *json_data) {
}
if (should_sleep) {
printf("\nEntering deep sleep in 2 seconds...\n");
printf("\nEntering soft idle in 2 seconds...\n");
fflush(stdout);
vTaskDelay(pdMS_TO_TICKS(2000));
rtc_enter_deep_sleep();
soft_idle_enter();
}
}
@@ -111,6 +111,7 @@ static void cmd_help(void) {
printf("\nCommands:\n");
printf(" GET - Get complete system status (JSON)\n");
printf(" POST: {json} - Send command or update parameters (JSON)\n");
printf(" RTCDEBUG - Dump RTC timekeeping state (time, backup, sleep entry, clock source)\n");
printf(" HELP - Show this help\n");
printf("\nPOST JSON Format:\n");
printf("{\n");
@@ -186,6 +187,9 @@ static void process_command(char *cmd) {
else if (strcmp(command, "HELP") == 0) {
cmd_help();
}
else if (strcmp(command, "RTCDEBUG") == 0) {
rtc_print_debug();
}
else {
printf("ERROR: Unknown command '%s'\n", command);
printf("Type 'HELP' for available commands\n");

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
#include <Arduino.h>
const unsigned char PROGMEM html_content_gz[] = {
0x1f, 0x8b, 0x08, 0x00, 0x4f, 0x8d, 0xa8, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x69, 0x77, 0xdb, 0x46,
0x1f, 0x8b, 0x08, 0x00, 0x6c, 0xcb, 0xb0, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x69, 0x77, 0xdb, 0x46,
0x92, 0x9f, 0x27, 0xbf, 0xa2, 0xa5, 0x38, 0x0a, 0x60, 0x83, 0x20, 0x29, 0xc9, 0x49, 0x86, 0x14,
0xa8, 0x91, 0x25, 0x7a, 0xe2, 0xd8, 0x3a, 0x9e, 0x0e, 0x27, 0xb3, 0x1a, 0x3d, 0x01, 0x24, 0x9a,
0x22, 0x22, 0x10, 0x60, 0x00, 0x50, 0xb2, 0x06, 0xe2, 0x7f, 0xdf, 0xaa, 0x3e, 0x80, 0xc6, 0x41,

View File

@@ -529,10 +529,10 @@ static esp_err_t post_handler(httpd_req_t *req) {
}
if (should_sleep) {
ESP_LOGI(TAG, "Sleeping in 2 seconds...");
ESP_LOGI(TAG, "Entering soft idle in 2 seconds...");
vTaskDelay(pdMS_TO_TICKS(2000));
rtc_enter_deep_sleep();
return ESP_OK; // Never reached
soft_idle_enter();
return ESP_OK;
}
err = httpd_resp_set_hdr(req, "Connection", "close");
@@ -794,6 +794,7 @@ httpd_uri_t uris[] = {{
**********************************************************/
bool server_running = false;
static bool s_wifi_running = false;
static esp_err_t start_http_server(void) {
if (server_running) return ESP_OK;
@@ -999,6 +1000,7 @@ static esp_err_t launch_soft_ap(void) {
ESP_LOGE(TAG, "Failed to start WiFi: %s", esp_err_to_name(err));
return err;
}
s_wifi_running = true;
// Start DNS server with your specific hostname
err = simple_dns_server_start("192.168.4.1");
@@ -1040,11 +1042,23 @@ static esp_err_t launch_soft_ap(void) {
return ESP_OK;
}
esp_err_t webserver_stop(void) {
stop_http_server();
if (s_wifi_running) {
esp_wifi_stop();
s_wifi_running = false;
}
return ESP_OK;
}
esp_err_t webserver_restart_wifi(void) {
ESP_LOGI(TAG, "Restarting WiFi AP with updated params...");
stop_http_server();
esp_wifi_stop();
if (s_wifi_running) {
esp_wifi_stop();
s_wifi_running = false;
}
wifi_config_t wifi_config = {
.ap = {
@@ -1068,6 +1082,7 @@ esp_err_t webserver_restart_wifi(void) {
esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
esp_wifi_start();
s_wifi_running = true;
start_http_server();
ESP_LOGI(TAG, "WiFi AP restarted. New SSID: %s, Channel: %d",

View File

@@ -1,4 +1,5 @@
#include "esp_err.h"
esp_err_t webserver_init(void);
esp_err_t webserver_restart_wifi(void); // Reconfigure and restart AP with current params
esp_err_t webserver_restart_wifi(void); // Reconfigure and restart AP with current params
esp_err_t webserver_stop(void); // Stop HTTP server and WiFi (for soft idle)