diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index dd41048..6084af8 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -52,7 +52,7 @@ Zielbild für den Host: Befehle an den Master senden; der Master steuert Slaves | BMA456 Interrupt | 10 | `bosch456.c` | | Taster | 12 | `board_input.c` | | LiPo ADC 1 | 1 | `board_input.c` | -| LiPo ADC 2 | 12 | Entfällt wenn = Taster-GPIO | +| LiPo ADC 2 | 11 | `board_input.c` | **UART (Host-Protokoll):** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control. @@ -405,7 +405,7 @@ Ohne Sensor: `bma456_is_ready() == false`, Firmware läuft weiter. `board_input.c`: - **Taster** GPIO 12 — Logging bei Druck. -- **LiPo-ADC** GPIO 1 (und optional 2, wenn nicht Taster). +- **LiPo-ADC** GPIO 1 und GPIO 11. - Master: `master_monitor_task` aktualisiert Master-Batterie alle 30 s. - Slave: `slave_send_battery_report_to_master` nach Join, Heartbeat und Query. diff --git a/goTool/Makefile b/goTool/Makefile new file mode 100644 index 0000000..ccd5efb --- /dev/null +++ b/goTool/Makefile @@ -0,0 +1,2 @@ +win: + GOOS=windows GOARCH=386 go build . diff --git a/goTool/dashboard.go b/goTool/dashboard.go index d2f529c..761d05e 100644 --- a/goTool/dashboard.go +++ b/goTool/dashboard.go @@ -633,7 +633,7 @@ func runBatteryPoller(link *managedSerial, hub *wsHub, interval time.Duration, s case <-stop: return case <-ticker.C: - if hub.clientCount() == 0 { + if !link.IsConnected() { continue } bat, err := link.BatteryStatusPoll(&pb.BatteryStatusRequest{AllClients: true}) diff --git a/goTool/webui/index.html b/goTool/webui/index.html index b307ccb..52a286a 100644 --- a/goTool/webui/index.html +++ b/goTool/webui/index.html @@ -769,21 +769,35 @@ }, applyBatterySamples(samples) { if (!samples?.length) return; + const master = { ...(this.state.master || {}) }; + const clients = [...(this.state.clients || [])]; + let masterChanged = false; + let clientsChanged = false; for (const s of samples) { if (s.client_id === 0) { - if (!this.state.master) this.state.master = {}; - this.state.master.lipo1 = s.lipo1; - this.state.master.lipo2 = s.lipo2; - this.state.master.battery_age_ms = s.age_ms; + master.lipo1 = s.lipo1; + master.lipo2 = s.lipo2; + master.battery_age_ms = s.age_ms; + masterChanged = true; continue; } - const c = (this.state.clients || []).find((x) => x.id === s.client_id); - if (c) { - c.lipo1 = s.lipo1; - c.lipo2 = s.lipo2; - c.battery_age_ms = s.age_ms; + const idx = clients.findIndex((x) => x.id === s.client_id); + if (idx >= 0) { + clients[idx] = { + ...clients[idx], + lipo1: s.lipo1, + lipo2: s.lipo2, + battery_age_ms: s.age_ms, + }; + clientsChanged = true; } } + if (!masterChanged && !clientsChanged) return; + this.state = { + ...this.state, + ...(masterChanged ? { master } : {}), + ...(clientsChanged ? { clients } : {}), + }; }, async refreshBattery() { if (!this.state?.uart_connected) return; diff --git a/main/README.md b/main/README.md index 8e0c112..07bb61b 100644 --- a/main/README.md +++ b/main/README.md @@ -55,7 +55,7 @@ Pins (`powerpod.h`): | BMA456 INT | 10 | | Button (Taster) | 12 | | LiPo sense 1 (ADC) | 1 | -| LiPo sense 2 (ADC) | 12 (skipped if same as button) | +| LiPo sense 2 (ADC) | 11 | > **TODO:** GPIO assignments above are provisional; confirm pinning against the real board before release. @@ -563,7 +563,7 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running ` | `bosch456.c/h` | BMA456H I2C driver, accel poll, on-demand read, tap INT, deadzone filter | | `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 / GPIO12 | +| `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO11 | | `pod_settings.c/h` | NVS persistence (accel deadzone, …) | | `led_ring.c/h` | LED ring (digit display, progress bar) | | `cmd/cmd_led_ring.c` | UART `LED_RING` progress command | diff --git a/main/board_input.c b/main/board_input.c index 84281cc..3ef727e 100644 --- a/main/board_input.c +++ b/main/board_input.c @@ -1,5 +1,6 @@ #include "board_input.h" #include "powerpod.h" +#include "client_registry.h" #include "driver/gpio.h" #include "esp_adc/adc_oneshot.h" #include "esp_log.h" @@ -18,34 +19,51 @@ static const char *TAG_LIPO = "[LIPO]"; #define LIPO_ADC_MAX_RAW 4095 static QueueHandle_t s_button_queue; -static adc_oneshot_unit_handle_t s_adc; -static bool s_lipo1_ok; -static bool s_lipo2_ok; -static adc_channel_t s_lipo1_ch; -static adc_channel_t s_lipo2_ch; -static esp_err_t adc_init_channel(int gpio, adc_channel_t *out_ch, bool *out_ok) { - adc_unit_t unit; - esp_err_t err = adc_oneshot_io_to_channel(gpio, &unit, out_ch); +typedef struct { + adc_oneshot_unit_handle_t unit; + adc_channel_t ch; + bool ok; +} lipo_adc_t; + +static lipo_adc_t s_lipo1; +static lipo_adc_t s_lipo2; + +static esp_err_t adc_init_gpio(int gpio, lipo_adc_t *out) { + out->unit = NULL; + out->ok = false; + + adc_unit_t unit_id; + esp_err_t err = adc_oneshot_io_to_channel(gpio, &unit_id, &out->ch); if (err != ESP_OK) { ESP_LOGW(TAG_LIPO, "GPIO%d not an ADC channel: %s", gpio, esp_err_to_name(err)); - *out_ok = false; return err; } - if (unit != ADC_UNIT_1) { - ESP_LOGW(TAG_LIPO, "GPIO%d on ADC unit %d (expected ADC1)", gpio, (int)unit); + + adc_oneshot_unit_init_cfg_t init_cfg = { + .unit_id = unit_id, + }; + err = adc_oneshot_new_unit(&init_cfg, &out->unit); + if (err != ESP_OK) { + ESP_LOGW(TAG_LIPO, "ADC unit %d init GPIO%d failed: %s", (int)unit_id, gpio, + esp_err_to_name(err)); + return err; } + adc_oneshot_chan_cfg_t chan_cfg = { .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, }; - err = adc_oneshot_config_channel(s_adc, *out_ch, &chan_cfg); + err = adc_oneshot_config_channel(out->unit, out->ch, &chan_cfg); if (err != ESP_OK) { ESP_LOGW(TAG_LIPO, "ADC config GPIO%d failed: %s", gpio, esp_err_to_name(err)); - *out_ok = false; + adc_oneshot_del_unit(out->unit); + out->unit = NULL; return err; } - *out_ok = true; + + out->ok = true; + ESP_LOGI(TAG_LIPO, "GPIO%d ready (ADC unit %d)", gpio, (int)unit_id); return ESP_OK; } @@ -56,15 +74,15 @@ static uint32_t raw_to_mv(int raw) { return (uint32_t)((raw * LIPO_ADC_FULL_SCALE_MV) / LIPO_ADC_MAX_RAW); } -static void sample_one_channel(adc_channel_t ch, bool ok, uint32_t *mv_out, +static void sample_one_channel(const lipo_adc_t *adc, uint32_t *mv_out, bool *valid_out) { *valid_out = false; *mv_out = 0; - if (!ok || s_adc == NULL) { + if (adc == NULL || !adc->ok || adc->unit == NULL) { return; } int raw = 0; - if (adc_oneshot_read(s_adc, ch, &raw) == ESP_OK) { + if (adc_oneshot_read(adc->unit, adc->ch, &raw) == ESP_OK) { *valid_out = true; *mv_out = raw_to_mv(raw); } @@ -75,8 +93,8 @@ void board_input_read_lipo(board_lipo_reading_t *out) { return; } memset(out, 0, sizeof(*out)); - sample_one_channel(s_lipo1_ch, s_lipo1_ok, &out->lipo1_mv, &out->lipo1_valid); - sample_one_channel(s_lipo2_ch, s_lipo2_ok, &out->lipo2_mv, &out->lipo2_valid); + sample_one_channel(&s_lipo1, &out->lipo1_mv, &out->lipo1_valid); + sample_one_channel(&s_lipo2, &out->lipo2_mv, &out->lipo2_valid); } static void lipo_monitor_task(void *param) { @@ -87,6 +105,7 @@ static void lipo_monitor_task(void *param) { while (1) { board_lipo_reading_t reading; board_input_read_lipo(&reading); + client_registry_set_master_battery(&reading); ESP_LOGI(TAG_LIPO, "LIPO1 GPIO%d %s %lu mV LIPO2 GPIO%d %s %lu mV", @@ -166,28 +185,19 @@ static esp_err_t init_button(void) { } static esp_err_t init_lipo_adc(void) { - adc_oneshot_unit_init_cfg_t init_cfg = { - .unit_id = ADC_UNIT_1, - }; - esp_err_t err = adc_oneshot_new_unit(&init_cfg, &s_adc); - if (err != ESP_OK) { - ESP_LOGW(TAG_LIPO, "ADC init failed: %s", esp_err_to_name(err)); - return err; - } + memset(&s_lipo1, 0, sizeof(s_lipo1)); + memset(&s_lipo2, 0, sizeof(s_lipo2)); - adc_init_channel(V_LIPO_1_GPIO, &s_lipo1_ch, &s_lipo1_ok); + adc_init_gpio(V_LIPO_1_GPIO, &s_lipo1); if (V_LIPO_2_GPIO == TASTER_GPIO) { ESP_LOGW(TAG_LIPO, "LIPO2 on GPIO%d skipped (button uses same pin)", V_LIPO_2_GPIO); - s_lipo2_ok = false; } else { - adc_init_channel(V_LIPO_2_GPIO, &s_lipo2_ch, &s_lipo2_ok); + adc_init_gpio(V_LIPO_2_GPIO, &s_lipo2); } - if (!s_lipo1_ok && !s_lipo2_ok) { - adc_oneshot_del_unit(s_adc); - s_adc = NULL; + if (!s_lipo1.ok && !s_lipo2.ok) { return ESP_FAIL; } diff --git a/main/cmd/cmd_battery.c b/main/cmd/cmd_battery.c index 47c1131..fbf7d1d 100644 --- a/main/cmd/cmd_battery.c +++ b/main/cmd/cmd_battery.c @@ -33,18 +33,14 @@ static bool append_battery_sample(alox_BatteryStatusResponse *resp, return lipo1_valid || lipo2_valid; } -static bool append_master_cached(alox_BatteryStatusResponse *resp) { +static bool append_master_sample(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; - } + board_input_read_lipo(&reading); + client_registry_set_master_battery(&reading); return append_battery_sample(resp, 0, reading.lipo1_valid, reading.lipo1_mv, - reading.lipo2_valid, reading.lipo2_mv, age_ms); + reading.lipo2_valid, reading.lipo2_mv, 0); } static bool append_slave_cached(alox_BatteryStatusResponse *resp, @@ -102,7 +98,7 @@ static void handle_battery_status(const uint8_t *data, size_t len) { bool any = false; if (req.all_clients) { - any |= append_master_cached(resp); + any |= append_master_sample(resp); for (size_t i = 0; i < client_registry_count(); i++) { const client_info_t *client = client_registry_at(i); if (client == NULL) { @@ -113,7 +109,7 @@ static void handle_battery_status(const uint8_t *data, size_t len) { ESP_LOGI(TAG, "battery cache all_clients → %u samples", (unsigned)resp->samples_count); } else if (req.client_id == 0) { - any = append_master_cached(resp); + any = append_master_sample(resp); ESP_LOGI(TAG, "battery cache master"); } else { const client_info_t *client = client_registry_find_by_id(req.client_id); diff --git a/main/powerpod.h b/main/powerpod.h index 6cba94d..96a8ee6 100644 --- a/main/powerpod.h +++ b/main/powerpod.h @@ -11,9 +11,8 @@ /** Front-panel button (active low, internal pull-up). */ #define TASTER_GPIO 12 -/** LiPo voltage sense inputs (ADC1-capable GPIOs). */ +/** LiPo voltage sense inputs (ADC-capable GPIOs; GPIO11 = ADC2 on ESP32-S3). */ #define V_LIPO_1_GPIO 1 -/** Shares GPIO with TASTER on current bench wiring; second ADC is skipped in board_input.c. */ -#define V_LIPO_2_GPIO 12 +#define V_LIPO_2_GPIO 11 #endif