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:
@@ -0,0 +1,119 @@
|
||||
#include "cmd_battery.h"
|
||||
#include "board_input.h"
|
||||
#include "client_registry.h"
|
||||
#include "esp_log.h"
|
||||
#include "uart_cmd.h"
|
||||
|
||||
static const char *TAG = "[BATTERY]";
|
||||
|
||||
static void fill_lipo(alox_LipoReading *dst, bool *has_dst, bool valid,
|
||||
uint32_t mv) {
|
||||
if (dst == NULL || has_dst == NULL) {
|
||||
return;
|
||||
}
|
||||
*has_dst = true;
|
||||
dst->valid = valid;
|
||||
dst->voltage_mv = valid ? mv : 0;
|
||||
}
|
||||
|
||||
static bool append_battery_sample(alox_BatteryStatusResponse *resp,
|
||||
uint32_t client_id, bool lipo1_valid,
|
||||
uint32_t lipo1_mv, bool lipo2_valid,
|
||||
uint32_t lipo2_mv, uint32_t age_ms) {
|
||||
if (resp->samples_count >=
|
||||
sizeof(resp->samples) / sizeof(resp->samples[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
alox_BatterySample *sample = &resp->samples[resp->samples_count++];
|
||||
sample->client_id = client_id;
|
||||
fill_lipo(&sample->lipo1, &sample->has_lipo1, lipo1_valid, lipo1_mv);
|
||||
fill_lipo(&sample->lipo2, &sample->has_lipo2, lipo2_valid, lipo2_mv);
|
||||
sample->age_ms = age_ms;
|
||||
return lipo1_valid || lipo2_valid;
|
||||
}
|
||||
|
||||
static bool append_master_cached(alox_BatteryStatusResponse *resp) {
|
||||
board_lipo_reading_t reading;
|
||||
uint32_t age_ms = 0;
|
||||
|
||||
if (!client_registry_get_master_battery(&reading, &age_ms)) {
|
||||
board_input_read_lipo(&reading);
|
||||
client_registry_set_master_battery(&reading);
|
||||
age_ms = 0;
|
||||
}
|
||||
|
||||
return append_battery_sample(resp, 0, reading.lipo1_valid, reading.lipo1_mv,
|
||||
reading.lipo2_valid, reading.lipo2_mv, age_ms);
|
||||
}
|
||||
|
||||
static bool append_slave_cached(alox_BatteryStatusResponse *resp,
|
||||
const client_info_t *client) {
|
||||
if (client == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (client->battery_updated_at == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return append_battery_sample(
|
||||
resp, client->id, client->lipo1_valid, client->lipo1_mv,
|
||||
client->lipo2_valid, client->lipo2_mv,
|
||||
client_registry_ms_since(client->battery_updated_at));
|
||||
}
|
||||
|
||||
static void handle_battery_status(const uint8_t *data, size_t len) {
|
||||
alox_BatteryStatusRequest req = alox_BatteryStatusRequest_init_zero;
|
||||
|
||||
if (len > 0) {
|
||||
alox_UartMessage uart_msg;
|
||||
if (uart_cmd_decode(data, len, &uart_msg) == ESP_OK) {
|
||||
const alox_BatteryStatusRequest *req_ptr = UART_CMD_REQ(
|
||||
&uart_msg, alox_UartMessage_battery_status_request_tag,
|
||||
battery_status_request);
|
||||
if (req_ptr != NULL) {
|
||||
req = *req_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alox_UartMessage response;
|
||||
uart_cmd_init_response(&response, alox_MessageType_BATTERY_STATUS,
|
||||
alox_UartMessage_battery_status_response_tag);
|
||||
alox_BatteryStatusResponse *resp =
|
||||
&response.payload.battery_status_response;
|
||||
resp->success = false;
|
||||
resp->samples_count = 0;
|
||||
|
||||
bool any = false;
|
||||
|
||||
if (req.all_clients) {
|
||||
any |= append_master_cached(resp);
|
||||
for (size_t i = 0; i < client_registry_count(); i++) {
|
||||
const client_info_t *client = client_registry_at(i);
|
||||
if (client == NULL) {
|
||||
continue;
|
||||
}
|
||||
any |= append_slave_cached(resp, client);
|
||||
}
|
||||
ESP_LOGI(TAG, "battery cache all_clients → %u samples",
|
||||
(unsigned)resp->samples_count);
|
||||
} else if (req.client_id == 0) {
|
||||
any = append_master_cached(resp);
|
||||
ESP_LOGI(TAG, "battery cache master");
|
||||
} else {
|
||||
const client_info_t *client = client_registry_find_by_id(req.client_id);
|
||||
if (client != NULL) {
|
||||
any = append_slave_cached(resp, client);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "client %lu not in registry", (unsigned long)req.client_id);
|
||||
}
|
||||
}
|
||||
|
||||
resp->success = any;
|
||||
uart_cmd_send(&response, TAG);
|
||||
}
|
||||
|
||||
void cmd_battery_register(void) {
|
||||
uart_cmd_register(alox_MessageType_BATTERY_STATUS, handle_battery_status);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef CMD_BATTERY_H
|
||||
#define CMD_BATTERY_H
|
||||
|
||||
void cmd_battery_register(void);
|
||||
|
||||
#endif
|
||||
@@ -52,6 +52,8 @@ static const char *message_type_name(uint16_t id) {
|
||||
return "ACCEL_SNAPSHOT";
|
||||
case alox_MessageType_ACCEL_STREAM:
|
||||
return "ACCEL_STREAM";
|
||||
case alox_MessageType_BATTERY_STATUS:
|
||||
return "BATTERY_STATUS";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user