From 4f4964e454f3d48da26f7144221c20bc24edfdc5 Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Sat, 11 Apr 2026 18:14:30 +1000 Subject: [PATCH] fix: add battery reporting toggle for Heltec V3 and clean up VS Code metadata --- .../feature-support-request.yml | 82 +++++++++++++++++++ README.md | 1 + docs/custom-cli.md | 10 +++ examples/simple_repeater/MyMesh.cpp | 2 + examples/simple_room_server/MyMesh.cpp | 2 + examples/simple_sensor/SensorMesh.cpp | 2 + src/MeshCore.h | 3 + src/helpers/CommonCLI.cpp | 30 ++++++- src/helpers/CommonCLI.h | 1 + variants/heltec_v3/HeltecV3Board.h | 32 +++++++- variants/heltec_v3/platformio.ini | 2 +- 11 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/feature-support-request.yml diff --git a/.github/ISSUE_TEMPLATE/feature-support-request.yml b/.github/ISSUE_TEMPLATE/feature-support-request.yml new file mode 100644 index 00000000..70435798 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-support-request.yml @@ -0,0 +1,82 @@ +name: Feature / Support Request +description: Request a feature or report a board-specific support need. +title: "[Feature/Support]: " +labels: + - enhancement +body: + - type: markdown + attributes: + value: | + Use this form for feature requests and board-specific support needs. + + - type: textarea + id: summary + attributes: + label: Summary + description: What do you want to add or what do you need help with? + placeholder: Briefly describe the request or issue. + validations: + required: true + + - type: input + id: board + attributes: + label: Board + description: Board model and revision, if known. + placeholder: e.g. Heltec WiFi LoRa 32 V3.2 + validations: + required: false + + - type: input + id: firmware + attributes: + label: Firmware Target + description: Firmware environment or binary name, if known. + placeholder: e.g. Heltec_v3_repeater_mqtt + validations: + required: false + + - type: input + id: power + attributes: + label: Power Source + description: How is the board powered? + placeholder: e.g. USB only, LiPo only, USB + LiPo + validations: + required: false + + - type: textarea + id: behavior + attributes: + label: Current Behavior + description: What is happening now? + placeholder: Describe the current behavior, readings, LEDs, errors, etc. + validations: + required: false + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What did you expect instead? + placeholder: Describe the expected result. + validations: + required: false + + - type: textarea + id: comparison + attributes: + label: Comparison + description: If relevant, does upstream MeshCore or another firmware behave differently? + placeholder: Include repo/branch/target details if you compared multiple builds. + validations: + required: false + + - type: textarea + id: logs + attributes: + label: Logs Or Screenshots + description: Paste logs or attach screenshots/photos if helpful. + placeholder: Add CLI output, boot logs, screenshots, or photos here. + validations: + required: false diff --git a/README.md b/README.md index f035babc..ee3b8573 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ uv run --group docs zensical build - CLI controls for: - WiFi credentials - WiFi powersaving + - board battery reporting on supported targets - MQTT endpoint enablement - MQTT packet and raw publishing - owner public key and email diff --git a/docs/custom-cli.md b/docs/custom-cli.md index dff8b02a..d7e14a0c 100644 --- a/docs/custom-cli.md +++ b/docs/custom-cli.md @@ -102,6 +102,16 @@ Legacy dotted aliases are also accepted: - `stats-packets` - shows packet receive/send totals, flood/direct breakdown, and receive errors +### Board Battery Reporting + +- `get battery.reporting` + - shows whether board battery reporting is enabled + - support is board-dependent +- `set battery.reporting on|off` + - enables or disables battery voltage reporting on supported boards + - currently useful for Heltec V3 boards where USB-only power can produce misleading battery readings + - if your board needs this too, open an issue and support can be added board-by-board + ## Web Panel Allowlisted Commands When the repeater web panel is enabled, it only allows a limited command set. diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 3f8e7ead..da232be3 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -898,6 +898,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier + _prefs.battery_reporting_enabled = 1; #if defined(USE_SX1262) || defined(USE_SX1268) #ifdef SX126X_RX_BOOSTED_GAIN @@ -943,6 +944,7 @@ void MyMesh::begin(FILESYSTEM *fs) { updateFloodAdvertTimer(); board.setAdcMultiplier(_prefs.adc_multiplier); + board.setBatteryReporting(_prefs.battery_reporting_enabled); #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 3bf2f58c..b807df77 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -631,6 +631,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + _prefs.battery_reporting_enabled = 1; next_post_idx = 0; next_client_idx = 0; @@ -654,6 +655,7 @@ void MyMesh::begin(FILESYSTEM *fs) { updateFloodAdvertTimer(); board.setAdcMultiplier(_prefs.adc_multiplier); + board.setBatteryReporting(_prefs.battery_reporting_enabled); #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 387c5d58..7efb356e 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -729,6 +729,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + _prefs.battery_reporting_enabled = 1; } void SensorMesh::begin(FILESYSTEM* fs) { @@ -746,6 +747,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { updateFloodAdvertTimer(); board.setAdcMultiplier(_prefs.adc_multiplier); + board.setBatteryReporting(_prefs.battery_reporting_enabled); #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); diff --git a/src/MeshCore.h b/src/MeshCore.h index 2db1d4c3..bbde30a9 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -44,6 +44,9 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual bool supportsBatteryReporting() const { return false; } + virtual bool setBatteryReporting(bool enabled) { (void)enabled; return false; } + virtual bool isBatteryReportingEnabled() const { return true; } virtual float getMCUTemperature() { return NAN; } virtual bool setAdcMultiplier(float multiplier) { return false; }; virtual float getAdcMultiplier() const { return 0.0f; } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d66fc899..37204b84 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -87,8 +87,13 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 - file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290 - // next: 291 + if (file.available() >= (int)sizeof(_prefs->battery_reporting_enabled)) { + file.read((uint8_t *)&_prefs->battery_reporting_enabled, sizeof(_prefs->battery_reporting_enabled)); // 290 + } + if (file.available() >= (int)sizeof(_prefs->rx_boosted_gain)) { + file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 291 + } + // next: 292 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -112,6 +117,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1); + _prefs->battery_reporting_enabled = constrain(_prefs->battery_reporting_enabled, 0, 1); _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); @@ -178,8 +184,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 - file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290 - // next: 291 + file.write((uint8_t *)&_prefs->battery_reporting_enabled, sizeof(_prefs->battery_reporting_enabled)); // 290 + file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 291 + // next: 292 file.close(); } @@ -421,6 +428,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { sprintf(reply, "> %.3f", adc_mult); } + } else if (memcmp(config, "battery.reporting", 17) == 0) { + if (_board->supportsBatteryReporting()) { + sprintf(reply, "> %s", _board->isBatteryReportingEnabled() ? "on" : "off"); + } else { + strcpy(reply, "Error: unsupported by this board"); + } // Power management commands } else if (memcmp(config, "pwrmgt.support", 14) == 0) { #ifdef NRF52_POWER_MANAGEMENT @@ -717,6 +730,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->adc_multiplier = 0.0f; strcpy(reply, "Error: unsupported by this board"); }; + } else if (memcmp(config, "battery.reporting ", 18) == 0) { + bool enabled = memcmp(&config[18], "on", 2) == 0; + if (_board->setBatteryReporting(enabled)) { + _prefs->battery_reporting_enabled = enabled ? 1 : 0; + savePrefs(); + sprintf(reply, "OK - battery reporting %s", enabled ? "on" : "off"); + } else { + strcpy(reply, "Error: unsupported by this board"); + } } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index a2ce0a7f..5c0f50cb 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -57,6 +57,7 @@ struct NodePrefs { // persisted to file uint32_t discovery_mod_timestamp; float adc_multiplier; char owner_info[120]; + uint8_t battery_reporting_enabled; // boolean uint8_t rx_boosted_gain; // power settings uint8_t path_hash_mode; // which path mode to use when sending uint8_t loop_detect; diff --git a/variants/heltec_v3/HeltecV3Board.h b/variants/heltec_v3/HeltecV3Board.h index afdaf639..1fc4c8cf 100644 --- a/variants/heltec_v3/HeltecV3Board.h +++ b/variants/heltec_v3/HeltecV3Board.h @@ -19,11 +19,14 @@ class HeltecV3Board : public ESP32Board { private: bool adc_active_state; + bool battery_reporting_enabled; public: RefCountedDigitalPin periph_power; - HeltecV3Board() : periph_power(PIN_VEXT_EN) { } + HeltecV3Board() : adc_active_state(false), + battery_reporting_enabled(true), + periph_power(PIN_VEXT_EN) { } void begin() { ESP32Board::begin(); @@ -77,6 +80,10 @@ public: } uint16_t getBattMilliVolts() override { + if (!battery_reporting_enabled) { + return 0; + } + analogReadResolution(10); digitalWrite(PIN_ADC_CTRL, adc_active_state); @@ -88,7 +95,28 @@ public: digitalWrite(PIN_ADC_CTRL, !adc_active_state); - return (5.42 * (3.3 / 1024.0) * raw) * 1000; + uint16_t mv = (5.42 * (3.3 / 1024.0) * raw) * 1000; + + // Heltec V3 USB-powered boards without a battery can bias or float the + // VBAT sense rail, which shows up as an impossible LiPo voltage. + if (mv > 4400) { + return 0; + } + + return mv; + } + + bool supportsBatteryReporting() const override { + return true; + } + + bool setBatteryReporting(bool enabled) override { + battery_reporting_enabled = enabled; + return true; + } + + bool isBatteryReportingEnabled() const override { + return battery_reporting_enabled; } const char* getManufacturerName() const override { diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 803ee683..fd50b549 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -376,4 +376,4 @@ build_flags = build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/kiss_modem/> lib_deps = - ${Heltec_lora32_v3.lib_deps} \ No newline at end of file + ${Heltec_lora32_v3.lib_deps}