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>
120 lines
3.7 KiB
C
120 lines
3.7 KiB
C
#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);
|
|
}
|