Add LiPo battery monitoring with ESP-NOW cache and dashboard API.

Slaves report pack voltages every 30s; the master caches them for fast
BATTERY_STATUS reads. goTool exposes REST/WebSocket and shows values in
the dashboard, with a nanopb fix so optional lipo submessages encode.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 20:14:28 +02:00
co-authored by Cursor
parent eb67a46158
commit 3cb0b5bbe9
30 changed files with 1618 additions and 150 deletions
+63
View File
@@ -14,6 +14,11 @@ typedef struct {
static client_slot_t s_clients[CLIENT_REGISTRY_MAX];
static struct {
board_lipo_reading_t reading;
uint32_t updated_at;
} s_master_battery;
uint32_t client_registry_now_ms(void) {
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
@@ -320,6 +325,64 @@ esp_err_t client_registry_update_accel(const uint8_t mac[CLIENT_MAC_LEN],
return ESP_OK;
}
void client_registry_set_master_battery(const board_lipo_reading_t *reading) {
if (reading == NULL) {
return;
}
s_master_battery.reading = *reading;
s_master_battery.updated_at = now_ms();
}
bool client_registry_get_master_battery(board_lipo_reading_t *reading_out,
uint32_t *age_ms_out) {
if (reading_out == NULL) {
return false;
}
*reading_out = s_master_battery.reading;
if (age_ms_out != NULL) {
*age_ms_out = client_registry_ms_since(s_master_battery.updated_at);
}
return s_master_battery.updated_at != 0;
}
esp_err_t client_registry_update_battery(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t slave_id, bool lipo1_valid,
uint32_t lipo1_mv, bool lipo2_valid,
uint32_t lipo2_mv) {
if (mac == NULL) {
return ESP_ERR_INVALID_ARG;
}
client_slot_t *slot = find_slot(mac);
if (slot == NULL) {
bool is_new = false;
esp_err_t err = client_registry_upsert(mac, slave_id, 0, true, false, &is_new);
if (err != ESP_OK) {
return err;
}
slot = find_slot(mac);
if (slot == NULL) {
return ESP_ERR_NOT_FOUND;
}
ESP_LOGI(TAG, "battery auto-registered id=%lu (report before heartbeat)",
(unsigned long)slave_id);
}
if (slot->info.id != slave_id) {
ESP_LOGW(TAG, "battery id %lu → %lu for mac %02x:…:%02x",
(unsigned long)slot->info.id, (unsigned long)slave_id, mac[0],
mac[5]);
slot->info.id = slave_id;
}
slot->info.lipo1_valid = lipo1_valid;
slot->info.lipo2_valid = lipo2_valid;
slot->info.lipo1_mv = lipo1_mv;
slot->info.lipo2_mv = lipo2_mv;
slot->info.battery_updated_at = now_ms();
return ESP_OK;
}
const client_info_t *client_registry_at(size_t index) {
size_t n = 0;
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {