diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index 9d45b59ef..1d9cf8264 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -587,11 +587,12 @@ def parse_device_info(data): **PACKET_BATTERY** (0x0C): ``` Byte 0: 0x0C -Bytes 1-2: Battery Level (16-bit little-endian, percentage 0-100) +Bytes 1-2: Battery Milli Volts (16-bit little-endian) Optional (if data size > 3): Bytes 3-6: Used Storage (32-bit little-endian, KB) Bytes 7-10: Total Storage (32-bit little-endian, KB) +Bytes 11: Charging/External Power Flag (uint8, 0=no, 1=yes) ``` **Parsing Pseudocode**: @@ -600,15 +601,18 @@ def parse_battery(data): if len(data) < 3: return None - level = int.from_bytes(data[1:3], 'little') - info = {'level': level} + milli_volts = int.from_bytes(data[1:3], 'little') + info = {'milli_volts': milli_volts} - if len(data) > 3: + if len(data) >= 11: used_kb = int.from_bytes(data[3:7], 'little') total_kb = int.from_bytes(data[7:11], 'little') info['used_kb'] = used_kb info['total_kb'] = total_kb + if len(data) >= 12: + info['is_charging'] = data[11] == 1 + return info ``` diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 87d3091a0..b933924d7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2,6 +2,10 @@ #include // needed for PlatformIO #include +#if defined(NRF52_PLATFORM) + #include + #include +#endif #define CMD_APP_START 1 #define CMD_SEND_TXT_MSG 2 @@ -1309,15 +1313,38 @@ void MyMesh::handleCmdFrame(size_t len) { } board.reboot(); } else if (cmd_frame[0] == CMD_GET_BATT_AND_STORAGE) { - uint8_t reply[11]; + uint8_t reply[12]; int i = 0; reply[i++] = RESP_CODE_BATT_AND_STORAGE; uint16_t battery_millivolts = board.getBattMilliVolts(); uint32_t used = _store->getStorageUsedKb(); uint32_t total = _store->getStorageTotalKb(); + // Optional extra byte for companion clients: + // 0 = not externally powered, 1 = externally powered/charging source present. + bool external_powered = board.isExternalPowered(); +#if defined(NRF52_PLATFORM) + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + uint32_t usb_status = 0; + if (sd_enabled) { + sd_power_usbregstatus_get(&usb_status); + } else { + usb_status = NRF_POWER->USBREGSTATUS; + } + + // Some nRF52 boards report VBUS correctly in USBREGSTATUS while + // board.isExternalPowered() may still resolve false in practice. + uint32_t usb_power_mask = POWER_USBREGSTATUS_VBUSDETECT_Msk; +#ifdef POWER_USBREGSTATUS_OUTPUTRDY_Msk + usb_power_mask |= POWER_USBREGSTATUS_OUTPUTRDY_Msk; +#endif + external_powered = external_powered || ((usb_status & usb_power_mask) != 0); +#endif + uint8_t is_charging = external_powered ? 1 : 0; memcpy(&reply[i], &battery_millivolts, 2); i += 2; memcpy(&reply[i], &used, 4); i += 4; memcpy(&reply[i], &total, 4); i += 4; + reply[i++] = is_charging; _serial->writeFrame(reply, i); } else if (cmd_frame[0] == CMD_EXPORT_PRIVATE_KEY) { #if ENABLE_PRIVATE_KEY_EXPORT diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 1db858f50..09cc76afd 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -67,6 +67,11 @@ void NRF52Board::initPowerMgr() { } bool NRF52Board::isExternalPowered() { + uint32_t usb_power_mask = POWER_USBREGSTATUS_VBUSDETECT_Msk; +#ifdef POWER_USBREGSTATUS_OUTPUTRDY_Msk + usb_power_mask |= POWER_USBREGSTATUS_OUTPUTRDY_Msk; +#endif + // Check if SoftDevice is enabled before using its API uint8_t sd_enabled = 0; sd_softdevice_is_enabled(&sd_enabled); @@ -74,9 +79,9 @@ bool NRF52Board::isExternalPowered() { if (sd_enabled) { uint32_t usb_status; sd_power_usbregstatus_get(&usb_status); - return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0; + return (usb_status & usb_power_mask) != 0; } else { - return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0; + return (NRF_POWER->USBREGSTATUS & usb_power_mask) != 0; } } diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 0d390f798..e73e03516 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -20,4 +20,23 @@ void T1000eBoard::begin() { Wire.begin(); delay(10); // give sx1262 some time to power up -} \ No newline at end of file +} + +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. + bool externalPowerDetected = false; + bool chargingDetected = false; + +#ifdef EXT_PWR_DETECT + // EXT_PWR_DETECT is high when external power rail is present. + externalPowerDetected = digitalRead(EXT_PWR_DETECT) == HIGH; +#endif + +#ifdef EXT_CHRG_DETECT + // Charge detect is typically active-low on PMIC status lines. + chargingDetected = digitalRead(EXT_CHRG_DETECT) == LOW; +#endif + + return externalPowerDetected || chargingDetected || NRF52Board::isExternalPowered(); +} diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 492236077..aa00de5d0 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -11,6 +11,7 @@ class T1000eBoard : public NRF52BoardDCDC { public: T1000eBoard() : NRF52Board("T1000E_OTA") {} void begin(); + bool isExternalPowered() override; uint16_t getBattMilliVolts() override { #ifdef BATTERY_PIN