Add software LiPo UV protection with energy-save boot.
Detect under-voltage on GPIO1/11, persist UV state in NVS, enter a minimal power mode without ESP-NOW, and recover after charge; expose LED mode 6 battery-low for host testing via goTool. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -42,6 +42,7 @@ idf_component_register(
|
||||
"esp_now_proto.c"
|
||||
"bosch456.c"
|
||||
"board_input.c"
|
||||
"battery_uv.c"
|
||||
"pod_settings.c"
|
||||
"proto/uart_messages.pb.c"
|
||||
"proto/esp_now_messages.pb.c"
|
||||
@@ -68,3 +69,5 @@ idf_component_register(
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB}
|
||||
PRIVATE "POWERPOD_GIT_HASH=\"${POWERPOD_GIT_HASH}\"")
|
||||
# Optional: disable software UV protection
|
||||
# target_compile_definitions(${COMPONENT_LIB} PRIVATE POWERPOD_BATTERY_UV_ENABLE=0)
|
||||
|
||||
+26
-9
@@ -59,14 +59,16 @@ Pins (`powerpod.h`):
|
||||
|
||||
> **TODO:** GPIO assignments above are provisional; confirm pinning against the real board before release.
|
||||
|
||||
Startup order:
|
||||
Startup order (normal path; UV energy-save skips I2C/BMA456/ESP-NOW/UART/button — see **Software UV protection**):
|
||||
|
||||
1. Read DIP + IO expander → `app_config`
|
||||
2. **I2C bus** — IO expander `0x20`; optional **BMA456H** (`init_bma456`, same bus)
|
||||
3. `esp_now_comm_init(&app_config)` — WiFi + ESP-NOW
|
||||
4. `led_ring_init()`
|
||||
5. `board_input_init()` — button press logs, LiPo ADC logs every **10 s**
|
||||
6. **Master only:** command queue, UART, registered commands (e.g. VERSION)
|
||||
1. `pod_settings_init()` — NVS
|
||||
2. LiPo ADC init + optional UV boot check (`battery_uv.c`)
|
||||
3. Read DIP + IO expander → `app_config`
|
||||
4. **I2C bus** — IO expander `0x20`; optional **BMA456H** (`init_bma456`, same bus)
|
||||
5. `esp_now_comm_init(&app_config)` — WiFi + ESP-NOW
|
||||
6. `led_ring_init()`
|
||||
7. LiPo monitor task + button (`board_input.c`)
|
||||
8. **Master only:** command queue, UART, registered commands (e.g. VERSION)
|
||||
|
||||
## BMA456 accelerometer (`bosch456.c`)
|
||||
|
||||
@@ -440,6 +442,19 @@ Read **cached** LiPo ADC values on the **master** (master local + one entry per
|
||||
# Host / goTool: all_clients returns master (id 0) + slaves from cache
|
||||
```
|
||||
|
||||
### Software UV protection (LiPo)
|
||||
|
||||
When `POWERPOD_BATTERY_UV_ENABLE` is `1` (default in `powerpod.h`, disable via CMake), the firmware monitors ADC pin voltages on GPIO 1 and 11:
|
||||
|
||||
| Condition | ADC threshold | Action |
|
||||
|-----------|---------------|--------|
|
||||
| Under-voltage | any valid channel < **2300 mV** (3.0 V pack) | NVS latch `uv_latch`, restart into energy-save mode |
|
||||
| Charged | all valid channels ≥ **3000 mV** (4.0 V pack) | Clear latch, restart into normal boot |
|
||||
|
||||
**Energy-save boot:** LED mode `6` (first 4 LEDs red ~10 % for 5 s), then no WiFi/ESP-NOW, UART, I2C, BMA456, or button — only LiPo ADC polling every 30 s until charged.
|
||||
|
||||
**Test LED pattern from host:** `led-ring -mode battery-low` (works even when UV feature is disabled).
|
||||
|
||||
### LED_RING command
|
||||
|
||||
Control the 95-LED ring from the host. The firmware **does not** animate digits locally; only UART updates the display.
|
||||
@@ -448,7 +463,7 @@ Control the 95-LED ring from the host. The firmware **does not** animate digits
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `mode` | `0` = clear, `1` = progress, `2` = digit (0–10), `3` = blink, `4` = find-me, `5` = solid color (all LEDs) |
|
||||
| `mode` | `0` = clear, `1` = progress, `2` = digit (0–10), `3` = blink, `4` = find-me, `5` = solid color (all LEDs), `6` = battery-low (first 4 LEDs red ~10 % for 5 s) |
|
||||
| `progress` | 0–100 (% of ring lit, mode `1`) |
|
||||
| `digit` | 0–10 (mode `2`, segment maps in `led_ring.c`) |
|
||||
| `r`, `g`, `b` | Color 0–255 |
|
||||
@@ -470,6 +485,7 @@ go run . -port /dev/ttyUSB0 led-ring -mode blink -g 255 -blink-count 2
|
||||
go run . -port /dev/ttyUSB0 find-me
|
||||
go run . -port /dev/ttyUSB0 find-me -client 16
|
||||
go run . -port /dev/ttyUSB0 led-ring -mode find-me
|
||||
go run . -port /dev/ttyUSB0 led-ring -mode battery-low -client 0
|
||||
go run . -port /dev/ttyUSB0 led-ring -mode color -r 255 -g 0 -b 0 -client 16
|
||||
go run . -port /dev/ttyUSB0 led-ring -mode digit -digit 5 -all
|
||||
```
|
||||
@@ -564,7 +580,8 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
|
||||
| `cmd/cmd_tap_notify.c` | UART `TAP_NOTIFY` — ESP-NOW tap notify config |
|
||||
| `cmd/cmd_cache_status.c` | UART `CACHE_STATUS` — subscribed accel + tap cache poll |
|
||||
| `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO11 |
|
||||
| `pod_settings.c/h` | NVS persistence (accel deadzone, …) |
|
||||
| `battery_uv.c/h` | Software LiPo UV latch, energy-save boot (`POWERPOD_BATTERY_UV_ENABLE`) |
|
||||
| `pod_settings.c/h` | NVS persistence (accel deadzone, UV latch, …) |
|
||||
| `led_ring.c/h` | LED ring (digit display, progress bar) |
|
||||
| `cmd/cmd_led_ring.c` | UART `LED_RING` progress command |
|
||||
| `cmd/cmd_set_log_level.c` | UART `SET_LOG_LEVEL` — runtime ESP-IDF log level |
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "battery_uv.h"
|
||||
|
||||
#if POWERPOD_BATTERY_UV_ENABLE
|
||||
|
||||
#include "led_ring.h"
|
||||
#include "pod_reboot.h"
|
||||
#include "pod_settings.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "[BATTERY_UV]";
|
||||
|
||||
#define UV_DEBOUNCE_SAMPLES 2u
|
||||
#define UV_POLL_INTERVAL_MS 30000u
|
||||
|
||||
static uint8_t s_uv_streak;
|
||||
static uint8_t s_charge_streak;
|
||||
|
||||
bool battery_uv_is_latched(void) { return pod_settings_is_uv_latched(); }
|
||||
|
||||
bool battery_uv_reading_is_under(const board_lipo_reading_t *reading) {
|
||||
if (reading == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reading->lipo1_valid && reading->lipo1_mv < BATTERY_UV_ADC_MV) {
|
||||
return true;
|
||||
}
|
||||
if (reading->lipo2_valid && reading->lipo2_mv < BATTERY_UV_ADC_MV) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool battery_uv_reading_is_charged(const board_lipo_reading_t *reading) {
|
||||
if (reading == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_valid = false;
|
||||
if (reading->lipo1_valid) {
|
||||
any_valid = true;
|
||||
if (reading->lipo1_mv < BATTERY_CHARGE_ADC_MV) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (reading->lipo2_valid) {
|
||||
any_valid = true;
|
||||
if (reading->lipo2_mv < BATTERY_CHARGE_ADC_MV) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return any_valid;
|
||||
}
|
||||
|
||||
bool battery_uv_evaluate_boot(void) {
|
||||
board_lipo_reading_t reading;
|
||||
board_input_read_lipo(&reading);
|
||||
|
||||
if (pod_settings_is_uv_latched() && battery_uv_reading_is_charged(&reading)) {
|
||||
ESP_LOGI(TAG, "charged — clearing UV latch");
|
||||
pod_settings_clear_uv_latched();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pod_settings_is_uv_latched() || battery_uv_reading_is_under(&reading)) {
|
||||
if (!pod_settings_is_uv_latched()) {
|
||||
ESP_LOGW(TAG, "under-voltage at boot — latching UV");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "UV latched — energy-save boot");
|
||||
}
|
||||
pod_settings_set_uv_latched(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void battery_uv_poll(const board_lipo_reading_t *reading) {
|
||||
if (reading == NULL || pod_settings_is_uv_latched()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (battery_uv_reading_is_under(reading)) {
|
||||
s_uv_streak++;
|
||||
ESP_LOGW(TAG, "under-voltage sample %u/%u", (unsigned)s_uv_streak,
|
||||
(unsigned)UV_DEBOUNCE_SAMPLES);
|
||||
if (s_uv_streak >= UV_DEBOUNCE_SAMPLES) {
|
||||
ESP_LOGW(TAG, "UV confirmed — latching and restarting");
|
||||
pod_settings_set_uv_latched(true);
|
||||
pod_schedule_restart();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
s_uv_streak = 0;
|
||||
}
|
||||
|
||||
void battery_uv_run_mode(void) {
|
||||
s_charge_streak = 0;
|
||||
s_uv_streak = 0;
|
||||
|
||||
led_ring_init();
|
||||
led_ring_show_battery_low();
|
||||
|
||||
ESP_LOGW(TAG, "energy-save mode — ADC poll every %u ms", UV_POLL_INTERVAL_MS);
|
||||
|
||||
while (1) {
|
||||
board_lipo_reading_t reading;
|
||||
board_input_read_lipo(&reading);
|
||||
|
||||
if (battery_uv_reading_is_charged(&reading)) {
|
||||
s_charge_streak++;
|
||||
ESP_LOGI(TAG, "charge sample %u/%u", (unsigned)s_charge_streak,
|
||||
(unsigned)UV_DEBOUNCE_SAMPLES);
|
||||
if (s_charge_streak >= UV_DEBOUNCE_SAMPLES) {
|
||||
ESP_LOGI(TAG, "charged — clearing UV latch and restarting");
|
||||
pod_settings_clear_uv_latched();
|
||||
pod_schedule_restart();
|
||||
}
|
||||
} else {
|
||||
s_charge_streak = 0;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(UV_POLL_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef BATTERY_UV_H
|
||||
#define BATTERY_UV_H
|
||||
|
||||
#include "board_input.h"
|
||||
#include "powerpod.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#if POWERPOD_BATTERY_UV_ENABLE
|
||||
|
||||
/** ADC pin voltage (mV) below which UV is triggered (3.0 V pack via 33k/10k divider). */
|
||||
#define BATTERY_UV_ADC_MV 2300u
|
||||
/** ADC pin voltage (mV) at which charging is detected (4.0 V pack, rounded). */
|
||||
#define BATTERY_CHARGE_ADC_MV 3000u
|
||||
|
||||
bool battery_uv_is_latched(void);
|
||||
bool battery_uv_reading_is_under(const board_lipo_reading_t *reading);
|
||||
bool battery_uv_reading_is_charged(const board_lipo_reading_t *reading);
|
||||
|
||||
/** true → enter UV energy-save path (does not return). */
|
||||
bool battery_uv_evaluate_boot(void);
|
||||
|
||||
void battery_uv_poll(const board_lipo_reading_t *reading);
|
||||
|
||||
/** Blocks: LED indicator, then minimal ADC polling until charged. */
|
||||
void battery_uv_run_mode(void);
|
||||
|
||||
#else
|
||||
|
||||
static inline bool battery_uv_is_latched(void) { return false; }
|
||||
|
||||
static inline bool battery_uv_reading_is_under(const board_lipo_reading_t *reading) {
|
||||
(void)reading;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool battery_uv_reading_is_charged(const board_lipo_reading_t *reading) {
|
||||
(void)reading;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool battery_uv_evaluate_boot(void) { return false; }
|
||||
|
||||
static inline void battery_uv_poll(const board_lipo_reading_t *reading) {
|
||||
(void)reading;
|
||||
}
|
||||
|
||||
static inline void battery_uv_run_mode(void) {}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+31
-7
@@ -2,6 +2,9 @@
|
||||
#include "powerpod.h"
|
||||
#include "client_registry.h"
|
||||
#include "driver/gpio.h"
|
||||
#if POWERPOD_BATTERY_UV_ENABLE
|
||||
#include "battery_uv.h"
|
||||
#endif
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -114,6 +117,10 @@ static void lipo_monitor_task(void *param) {
|
||||
reading.lipo2_valid ? "ok" : "n/a",
|
||||
(unsigned long)reading.lipo2_mv);
|
||||
|
||||
#if POWERPOD_BATTERY_UV_ENABLE
|
||||
battery_uv_poll(&reading);
|
||||
#endif
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(LIPO_SAMPLE_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
@@ -184,7 +191,7 @@ static esp_err_t init_button(void) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t init_lipo_adc(void) {
|
||||
static esp_err_t init_lipo_adc_hw(void) {
|
||||
memset(&s_lipo1, 0, sizeof(s_lipo1));
|
||||
memset(&s_lipo2, 0, sizeof(s_lipo2));
|
||||
|
||||
@@ -201,6 +208,16 @@ static esp_err_t init_lipo_adc(void) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t board_input_init_adc_only(void) { return init_lipo_adc_hw(); }
|
||||
|
||||
esp_err_t board_input_start_lipo_monitor(void) {
|
||||
if (!s_lipo1.ok && !s_lipo2.ok) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (xTaskCreate(lipo_monitor_task, "lipo_mon", 3072, NULL, 1, NULL) != pdPASS) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
@@ -208,16 +225,23 @@ static esp_err_t init_lipo_adc(void) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t board_input_init_button(void) { return init_button(); }
|
||||
|
||||
esp_err_t board_input_init(void) {
|
||||
esp_err_t err = init_button();
|
||||
esp_err_t err = board_input_init_adc_only();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG_LIPO, "ADC init failed: %s", esp_err_to_name(err));
|
||||
} else {
|
||||
err = board_input_start_lipo_monitor();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG_LIPO, "monitor task failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
err = board_input_init_button();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG_BTN, "init failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
err = init_lipo_adc();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG_LIPO, "init failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ typedef struct {
|
||||
*/
|
||||
esp_err_t board_input_init(void);
|
||||
|
||||
/** LiPo ADC channels only (no button, no background task). */
|
||||
esp_err_t board_input_init_adc_only(void);
|
||||
|
||||
/** Start 10 s LiPo monitor task (normal boot only). */
|
||||
esp_err_t board_input_start_lipo_monitor(void);
|
||||
|
||||
/** Front-panel button debounce task. */
|
||||
esp_err_t board_input_init_button(void);
|
||||
|
||||
/** On-demand ADC read of both LiPo sense inputs (if configured). */
|
||||
void board_input_read_lipo(board_lipo_reading_t *out);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ static const char *TAG = "[LED_RING_CMD]";
|
||||
#define LED_RING_MODE_BLINK 3
|
||||
#define LED_RING_MODE_FIND_ME 4
|
||||
#define LED_RING_MODE_COLOR 5
|
||||
#define LED_RING_MODE_BATTERY_LOW 6
|
||||
|
||||
static uint8_t clamp_u8(uint32_t v) {
|
||||
if (v > 255) {
|
||||
@@ -90,6 +91,10 @@ bool cmd_led_ring_apply(const alox_LedRingProgressRequest *req) {
|
||||
led_ring_find_me();
|
||||
return true;
|
||||
|
||||
case LED_RING_MODE_BATTERY_LOW:
|
||||
led_ring_show_battery_low();
|
||||
return true;
|
||||
|
||||
case LED_RING_MODE_BLINK:
|
||||
cmd.mode = LED_CMD_BLINK;
|
||||
cmd.r = r;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "led_strip.h"
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -23,8 +24,12 @@ static led_strip_handle_t led_ring;
|
||||
#define LED_RING_FIND_ME_ON_MS 300
|
||||
#define LED_RING_FIND_ME_OFF_MS 150
|
||||
#define LED_RING_FIND_ME_BLINKS_PER_COLOR 3
|
||||
#define LED_RING_BATTERY_LOW_R 255
|
||||
#define LED_RING_BATTERY_LOW_G 0
|
||||
#define LED_RING_BATTERY_LOW_B 0
|
||||
|
||||
static QueueHandle_t led_queue;
|
||||
static SemaphoreHandle_t s_battery_low_done;
|
||||
|
||||
// Led Matrix Maps
|
||||
const uint8_t d0[] = {46, 47, 60, 61, 62, 75, 78, 79,
|
||||
@@ -145,6 +150,22 @@ void vTaskLedRing(void *pvParameters) {
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else if (cmd.mode == LED_CMD_BATTERY_LOW) {
|
||||
uint8_t r = LED_RING_BATTERY_LOW_R;
|
||||
uint8_t g = LED_RING_BATTERY_LOW_G;
|
||||
uint8_t b = LED_RING_BATTERY_LOW_B;
|
||||
led_ring_scale_rgb(&r, &g, &b, LED_RING_BATTERY_LOW_INTENSITY);
|
||||
for (uint32_t i = 0; i < LED_RING_BATTERY_LOW_LEDS; i++) {
|
||||
led_strip_set_pixel(led_ring, i, r, g, b);
|
||||
}
|
||||
led_strip_refresh(led_ring);
|
||||
vTaskDelay(pdMS_TO_TICKS(LED_RING_BATTERY_LOW_HOLD_MS));
|
||||
led_strip_clear(led_ring);
|
||||
led_strip_refresh(led_ring);
|
||||
if (s_battery_low_done != NULL) {
|
||||
xSemaphoreGive(s_battery_low_done);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
led_strip_refresh(led_ring);
|
||||
}
|
||||
@@ -153,6 +174,7 @@ void vTaskLedRing(void *pvParameters) {
|
||||
|
||||
void led_ring_init(void) {
|
||||
led_queue = xQueueCreate(10, sizeof(led_command_t));
|
||||
s_battery_low_done = xSemaphoreCreateBinary();
|
||||
xTaskCreate(vTaskLedRing, "led_task", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
@@ -224,3 +246,13 @@ void led_ring_find_me(void) {
|
||||
led_command_t cmd = {.mode = LED_CMD_FIND_ME};
|
||||
led_ring_send_command(&cmd);
|
||||
}
|
||||
|
||||
void led_ring_show_battery_low(void) {
|
||||
if (led_queue == NULL || s_battery_low_done == NULL) {
|
||||
return;
|
||||
}
|
||||
(void)xSemaphoreTake(s_battery_low_done, 0);
|
||||
led_command_t cmd = {.mode = LED_CMD_BATTERY_LOW};
|
||||
led_ring_send_command(&cmd);
|
||||
(void)xSemaphoreTake(s_battery_low_done, portMAX_DELAY);
|
||||
}
|
||||
|
||||
+9
-1
@@ -7,6 +7,10 @@
|
||||
#define LED_RING_DEFAULT_INTENSITY 13
|
||||
/** Full brightness for find-me and similar alerts. */
|
||||
#define LED_RING_FULL_INTENSITY 255
|
||||
/** ~10 % brightness for battery-low indicator. */
|
||||
#define LED_RING_BATTERY_LOW_INTENSITY 26
|
||||
#define LED_RING_BATTERY_LOW_LEDS 4
|
||||
#define LED_RING_BATTERY_LOW_HOLD_MS 5000
|
||||
|
||||
typedef enum {
|
||||
LED_CMD_CLEAR,
|
||||
@@ -14,7 +18,8 @@ typedef enum {
|
||||
LED_CMD_SET_COLOR,
|
||||
LED_CMD_PROGRESS,
|
||||
LED_CMD_BLINK,
|
||||
LED_CMD_FIND_ME
|
||||
LED_CMD_FIND_ME,
|
||||
LED_CMD_BATTERY_LOW
|
||||
} led_mode_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -45,4 +50,7 @@ void led_ring_ota_failed(void);
|
||||
/** Red / green / blue: 3 blinks each at full intensity. */
|
||||
void led_ring_find_me(void);
|
||||
|
||||
/** First 4 LEDs red at ~10 % for 5 s, then off (blocking in LED task). */
|
||||
void led_ring_show_battery_low(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
static const char *TAG = "[SETTINGS]";
|
||||
static const char *NS = "powerpod";
|
||||
static const char *KEY_ACCEL_DZ = "accel_dz";
|
||||
static const char *KEY_UV_LATCH = "uv_latch";
|
||||
|
||||
#define ACCEL_DEADZONE_MAX 4095u
|
||||
|
||||
@@ -108,3 +109,53 @@ void pod_settings_apply_accel_deadzone(void) {
|
||||
bma456_set_accel_deadzone(deadzone);
|
||||
}
|
||||
}
|
||||
|
||||
bool pod_settings_is_uv_latched(void) {
|
||||
if (!s_nvs_ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NS, NVS_READONLY, &handle);
|
||||
if (err != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t value = 0;
|
||||
err = nvs_get_u8(handle, KEY_UV_LATCH, &value);
|
||||
nvs_close(handle);
|
||||
|
||||
return err == ESP_OK && value != 0;
|
||||
}
|
||||
|
||||
esp_err_t pod_settings_set_uv_latched(bool latched) {
|
||||
esp_err_t err = ensure_nvs();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
nvs_handle_t handle;
|
||||
err = nvs_open(NS, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = nvs_set_u8(handle, KEY_UV_LATCH, latched ? 1u : 0u);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(handle);
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "save uv_latch failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "UV latch %s", latched ? "set" : "cleared");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pod_settings_clear_uv_latched(void) {
|
||||
return pod_settings_set_uv_latched(false);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define POD_SETTINGS_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** Initialize NVS (idempotent) and log stored settings. Call once early in app_main. */
|
||||
@@ -16,4 +17,9 @@ uint32_t pod_settings_load_accel_deadzone(void);
|
||||
/** Apply NVS deadzone to BMA456 when the sensor is present. */
|
||||
void pod_settings_apply_accel_deadzone(void);
|
||||
|
||||
/** LiPo under-voltage latch persisted across reboot (software UV protection). */
|
||||
bool pod_settings_is_uv_latched(void);
|
||||
esp_err_t pod_settings_set_uv_latched(bool latched);
|
||||
esp_err_t pod_settings_clear_uv_latched(void);
|
||||
|
||||
#endif
|
||||
|
||||
+13
-1
@@ -31,6 +31,7 @@
|
||||
#include "led_ring.h"
|
||||
#include "pod_settings.h"
|
||||
#include "uart.h"
|
||||
#include "battery_uv.h"
|
||||
#include <stdint.h>
|
||||
|
||||
enum MASTER_STATES {
|
||||
@@ -78,6 +79,16 @@ void app_main(void) {
|
||||
ESP_LOGW(TAG, "settings NVS init failed; using defaults");
|
||||
}
|
||||
|
||||
if (board_input_init_adc_only() != ESP_OK) {
|
||||
ESP_LOGW(TAG, "LiPo ADC init failed");
|
||||
}
|
||||
|
||||
#if POWERPOD_BATTERY_UV_ENABLE
|
||||
if (battery_uv_evaluate_boot()) {
|
||||
battery_uv_run_mode();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get Master Mode Pin
|
||||
gpio_reset_pin(DIP_MASTER);
|
||||
gpio_set_direction(DIP_MASTER, GPIO_MODE_INPUT);
|
||||
@@ -167,7 +178,8 @@ void app_main(void) {
|
||||
ESP_LOGI(TAG, "Running Partition: %s (OTA slot %d)",
|
||||
app_config.running_partition, ota_slot);
|
||||
|
||||
board_input_init();
|
||||
board_input_start_lipo_monitor();
|
||||
board_input_init_button();
|
||||
|
||||
err = esp_now_comm_init(&app_config);
|
||||
if (err != ESP_OK) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#ifndef POWERPOD_H
|
||||
#define POWERPOD_H
|
||||
|
||||
/** 1 = software LiPo UV latch + energy-save mode; 0 = disabled (LED mode 6 still works). */
|
||||
#ifndef POWERPOD_BATTERY_UV_ENABLE
|
||||
#define POWERPOD_BATTERY_UV_ENABLE 0
|
||||
#endif
|
||||
|
||||
#define DIP_MASTER 4
|
||||
#define I2C_SCL 5
|
||||
#define I2C_SDA 6
|
||||
|
||||
@@ -256,7 +256,7 @@ typedef struct _alox_EspNowEchoPingResponse {
|
||||
} alox_EspNowEchoPingResponse;
|
||||
|
||||
/* Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
|
||||
mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color. */
|
||||
mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color, 6=battery-low. */
|
||||
typedef struct _alox_LedRingProgressRequest {
|
||||
uint32_t mode;
|
||||
/* * 0–100: fraction of ring LEDs to light (mode=progress) */
|
||||
|
||||
@@ -280,7 +280,7 @@ message EspNowEchoPingResponse {
|
||||
}
|
||||
|
||||
// Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
|
||||
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color.
|
||||
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color, 6=battery-low.
|
||||
message LedRingProgressRequest {
|
||||
uint32 mode = 1;
|
||||
/** 0–100: fraction of ring LEDs to light (mode=progress) */
|
||||
|
||||
Reference in New Issue
Block a user