Skip to content

Companion charging flag + board power detection fixes (T1000-E, WisMesh Tag)#1710

Open
Specter242 wants to merge 6 commits intomeshcore-dev:mainfrom
Specter242:feature/companion-battery-charging-flag
Open

Companion charging flag + board power detection fixes (T1000-E, WisMesh Tag)#1710
Specter242 wants to merge 6 commits intomeshcore-dev:mainfrom
Specter242:feature/companion-battery-charging-flag

Conversation

@Specter242
Copy link
Contributor

@Specter242 Specter242 commented Feb 16, 2026

Summary

This PR enables reliable companion charging-state reporting for app battery indicators by:

  • appending an optional is_charging byte to RESP_CODE_BATT_AND_STORAGE (0x0C)
  • fixing board-level external power detection on affected hardware
  • documenting the protocol extension for backward-compatible parsers

Closes #1870.

Problem

Companion battery status was available, but charging state was not consistently surfaced across devices. As a result, charging indicators in client apps could remain off even while externally powered.

Root Cause

  • Protocol payload did not previously include charging state.
  • Platform-specific external-power detection needed board-level fixes for accurate charging classification.

Changes

  • examples/companion_radio/MyMesh.cpp
    • Adds optional trailing is_charging byte in RESP_CODE_BATT_AND_STORAGE.
    • Uses board external-power state, with nRF52 USB-status fallback in the battery response path for WisMesh Tag behavior.
  • variants/t1000-e/T1000eBoard.cpp
    • Improves external-power detection for T1000-E using board-specific power/charge detect pins.
  • src/helpers/NRF52Board.cpp
    • Broadens USB external-power detection mask for nRF52 USB regulator status.
  • docs/companion_protocol.md
    • Documents the optional charging byte and backward-compatible parsing expectations.

Compatibility

  • Backward compatible: is_charging is appended as an optional trailing byte.
  • Legacy clients parsing the original payload length continue to work.
  • Updated clients can read charging state when frame length is >= 12.

Validation

  • Built companion targets:
    • pio run -e t1000e_companion_radio_ble
    • pio run -e RAK_WisMesh_Tag_companion_radio_ble
  • Flashed and validated on WisMesh Tag (/dev/ttyACM2) with BLE companion firmware:
    • adafruit-nrfutil dfu serial --package .pio/build/RAK_WisMesh_Tag_companion_radio_ble/firmware.zip -p /dev/ttyACM2 -b 115200 --singlebank --touch 1200
    • Observed battery response frame length includes optional byte (sz=12), and app-side charging indicator now updates correctly.
  • Validated T1000-E external power detection path for charging-state reporting.

Specter242 and others added 2 commits February 15, 2026 19:37
Append an optional is_charging byte to RESP_CODE_BATT_AND_STORAGE using board external power state so clients can render a charging indicator without breaking older parsers.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use T1000-E specific EXT_PWR_DETECT and EXT_CHRG_DETECT lines in isExternalPowered() so the companion battery charging byte reflects actual hardware charging/power state on this board.

Co-authored-by: Cursor <cursoragent@cursor.com>

bool T1000eBoard::isExternalPowered() {
// T1000-E exposes dedicated detect lines for external power and charge state.
// Use these first, then fall back to NRF52 USB VBUS detection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does NRF52 fallback provide us with anything? Wouldn't it always be false currently? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — you're essentially right, and it's worth explaining the reasoning so it doesn't look accidental.

Why EXT_PWR_DETECT / EXT_CHRG_DETECT already cover everything

Both pins are unconditionally defined in variants/t1000-e/variant.h:

#define EXT_CHRG_DETECT  (35)  // P1.3 — PMIC charge-status, active-LOW
#define EXT_PWR_DETECT   (5)   // P0.5 — external power rail present, active-HIGH

Because neither is behind a conditional, the two #ifdef guards in isExternalPowered() are always satisfied at compile time. Those two pin reads together cover every charging scenario the T1000-E supports (magnetic pogo-pin connector).

Why the NRF52 USB fallback is dead code in practice

NRF52Board::isExternalPowered() reads NRF_POWER->USBREGSTATUS.VBUSDETECT (or its SoftDevice equivalent). The T1000-E's charging PMIC is wired to the pogo connector, not to the nRF52840 USB VBUS line, so in any real charging scenario USBREGSTATUS stays 0 and the fallback contributes nothing.

The only case where it would ever be non-zero is when a USB cable is plugged in for DFU/flashing — not a charging event and not something you'd want to report to the companion app as "externally powered."

Why I left it in

It was originally a defensive backstop for the case where a board derives from T1000eBoard without EXT_PWR_DETECT/EXT_CHRG_DETECT defined, and to keep the call chain consistent with the base class. In hindsight, with both pins unconditionally defined in the canonical variant, it's more confusing than helpful.

I'll remove the NRF52Board::isExternalPowered() tail call from T1000eBoard::isExternalPowered() — the function can just return externalPowerDetected || chargingDetected;. Happy to push that cleanup if that direction works for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed in aade3b9. I removed the NRF52Board::isExternalPowered() tail call here, so T1000-E power reporting now relies only on EXT_PWR_DETECT and EXT_CHRG_DETECT. That makes the variant behavior explicit instead of keeping the generic nRF52 USB/VBUS fallback in the mix.

@Specter242 Specter242 changed the title Add optional charging flag to companion battery response Companion charging flag + board power detection fixes (T1000-E, WisMesh Tag) Feb 27, 2026
uint16_t battery_millivolts = board.getBattMilliVolts();
uint32_t used = _store->getStorageUsedKb();
uint32_t total = _store->getStorageTotalKb();
// Optional extra byte for companion clients:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the extra byte always included? would this break existing parsers of this message type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in this PR the extra byte is always included now. The response is 12 bytes total: battery mV, used storage, total storage, plus the trailing charging/external-power flag. Existing parsers should tolerate the longer frame and ignore the final byte if they do not consume charging state yet. I also pushed f339514 to clarify that in both the inline comment and docs/companion_protocol.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RAK WisMesh Tag companion reports non-charging on USB power

3 participants