Skip to content

Spektrum Smart Battery telemetry - #12077

Open
UltraFly wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
UltraFly:pr/srxl2-smart-battery
Open

UltraFly wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
UltraFly:pr/srxl2-smart-battery

Conversation

@UltraFly

Copy link
Copy Markdown

Adds cell voltages and pack temperature from Smart Battery sensor 0x42 to the existing Smart ESC driver, with CLI readout and native CRSF telemetry. The battery pages arrive through the existing ESC telemetry requests; no extra polling is needed.

Use receiver address 0x21 instead of 0x31 so the Avian's battery replies pass the normal destination check. Control frames and telemetry scheduling are unchanged. Avian Lite ESCs, including the 70A and 85A, lack the third battery-data wire. They still provide ESC telemetry, but their placeholder battery pages must not become cell readings.

Tested with a 6S Avian Smart 100A and a Spektrum Smart 4000 mAh 6S Gen 1 pack: cell voltages and pack temperature matched the received data. Normal ESC telemetry and FC-only restart recovery also worked on the 100A and Lite 85A, with the Lite's battery placeholders correctly suppressed. These bench tests used existing local NEXUS support, outside this change. Host checks and MATEKF405SE/MATEKF722 builds pass.

The ESC already returns Smart Battery pages with its ordinary telemetry.
Decode the cells and pack temperature, expose them through the CLI and
send them over CRSF. Require battery metadata before publishing so the
placeholders returned by Lite ESCs do not become cell readings.

Use receiver ID 0x21 in place of 0x31 so the Avian's battery replies pass
the normal address check. Control frames and telemetry scheduling stay
as they were.

Checked with a 6S Avian Smart 100A and a Smart 4000 mAh pack. Normal ESC
telemetry and FC-only restart recovery also worked on the 100A and Lite
85A, with the Lite's battery placeholders suppressed.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add Spektrum Smart Battery telemetry to SRXL2 ESC support

✨ Enhancement 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Decode Smart Battery cells and pack temperature from existing ESC telemetry without extra polling.
• Suppress Lite ESC placeholders and stale readings before exposing battery data.
• Add CLI readout and native CRSF cell-voltage and temperature frames.
Diagram

graph TD
  A["Avian ESC"] --> B["SRXL2 driver"] --> C["Battery decoder"] --> D{"Fresh metadata?"}
  D -->|yes| E["CLI readout"]
  D -->|yes| F["CRSF frames"] --> G["Radio"]
  B -->|raw pages| E
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Integrate cells into the main battery subsystem
  • ➕ Could make cell readings available to other flight-controller consumers.
  • ➖ Would broaden the change into battery-source selection and risk conflating ESC-connected packs with the selected main battery.

Recommendation: Keep the dedicated decoder and CRSF path for this PR. It uses battery pages already received by the ESC driver, preserves main battery-source selection, and limits the impact of absent or stale pack data.

Files changed (9) +439 / -8

Enhancement (7) +393 / -6
cli.cAdd Smart Battery CLI readout +49/-0

Add Smart Battery CLI readout

• Adds 'smartbattery' for available cells and pack temperature, plus 'smartbattery raw' for received pages, ages, and validity.

src/main/fc/cli.c

motor_srxl2.cReceive battery pages through the ESC link +34/-6

Receive battery pages through the ESC link

• Changes the master identity to receiver address '0x21', accepts addressed Smart Battery telemetry, and invalidates cached pages after link interruptions. Exposes decoded and raw battery accessors without changing control-frame or ESC polling behavior.

src/main/io/motor_srxl2.c

motor_srxl2.hExpose SRXL2 Smart Battery accessors +5/-0

Expose SRXL2 Smart Battery accessors

• Includes the battery types and declares accessors for decoded telemetry and raw per-ESC state.

src/main/io/motor_srxl2.h

srxl2_smart_battery.cDecode and validate Smart Battery pages +155/-0

Decode and validate Smart Battery pages

• Tracks pages by battery source, decodes up to eighteen cell voltages and pack temperature, and requires fresh capacity metadata before publishing readings. Retains raw evidence while invalidating decoded data after link loss.

src/main/io/srxl2_smart_battery.c

srxl2_smart_battery.hDefine Smart Battery state and telemetry types +70/-0

Define Smart Battery state and telemetry types

• Defines page storage, two source slots per ESC, freshness limits, decoded measurement fields, and the decoder API.

src/main/io/srxl2_smart_battery.h

crsf.hDefine the CRSF Voltage Group frame type +1/-0

Define the CRSF Voltage Group frame type

• Adds the native Voltage Group frame identifier used for cell telemetry.

src/main/rx/crsf.h

crsf.cTransmit Smart Battery cells and temperature over CRSF +79/-0

Transmit Smart Battery cells and temperature over CRSF

• Adds a scheduled Smart Battery telemetry entry that rotates through ESC battery sources. Sends temperatures when valid and cell voltages only for complete, fresh packs of up to eight cells.

src/main/telemetry/crsf.c

Documentation (1) +44 / -2
Spektrum Smart ESC.mdDocument Smart Battery support and limitations +44/-2

Document Smart Battery support and limitations

• Explains the required battery-data wire, receiver address, CLI commands, freshness behavior, CRSF compatibility, and reported bench checks. Removes an outdated firmware-size estimate.

docs/Spektrum Smart ESC.md

Other (1) +2 / -0
CMakeLists.txtRegister the Smart Battery decoder +2/-0

Register the Smart Battery decoder

• Adds the new decoder source and header to the main source list.

src/main/CMakeLists.txt

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Radios can show an incomplete cell pack 🐞 Bug ≡ Correctness
Description
crsfFrameSmartBattery accepts battery.cellCount even when it came from the highest observed cell
rather than a fresh identification page. If the limits page and first cell page arrive before
identification for a pack with more than six cells, the radio receives those six cells as a complete
pack.
Code

src/main/telemetry/crsf.c[R469-472]

+    const uint8_t count = battery.cellCount;
+    if (!count || count > CRSF_SMART_BATTERY_RADIO_MAX_CELLS
+        || battery.validCells != (1u << count) - 1) {
+        return false;
Evidence
The decoder derives observedCellCount from received cell pages and uses it as out->cellCount
until a fresh type-8 identification page supplies the actual count. Capacity metadata is the
separate publication gate. Thus a type-1 page containing six cells plus a type-9 limits page passes
the CRSF six-cell mask check even if the battery has additional cells whose page has not arrived.

src/main/io/srxl2_smart_battery.c[76-85]
src/main/io/srxl2_smart_battery.c[108-125]
src/main/telemetry/crsf.c[467-480]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CRSF completeness check can treat the highest observed cell number as the pack's total cell count and publish a partial pack.
## Fix Focus Areas
- src/main/telemetry/crsf.c[467-473]
- src/main/io/srxl2_smart_battery.c[108-116]
## Recommended Fix
Require `battery.cellCountValid` before emitting a Voltage Group frame. Keep the observed count available for CLI diagnostics, but do not use it to establish CRSF pack completeness.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. New battery sources stop appearing 🐞 Bug ☼ Reliability
Description
srxl2SmartBatteryReceive permanently reserves both source slots once their identities have been
seen, while invalidation clears only page validity. If a later battery source has a different
secondary or battery ID after both slots have been used, its pages are dropped even after the
earlier sources have gone stale or the link has restarted.
Code

src/main/io/srxl2_smart_battery.c[R53-57]

+    if (!source) {
+        for (unsigned i = 0; i < SRXL2_SMART_BATTERY_SLOTS; i++) {
+            if (!state->source[i].used) {
+                source = &state->source[i];
+                source->used = true;
Evidence
Only !used slots can be allocated, and a full state drops every page for a new identity.
srxl2SmartBatteryInvalidate never clears used; the state is otherwise reset only during motor
initialization, so stale identities continue occupying both slots.

src/main/io/srxl2_smart_battery.c[45-67]
src/main/io/srxl2_smart_battery.c[88-97]
src/main/io/motor_srxl2.c[607-610]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Two previously seen source identities occupy the only slots indefinitely, preventing telemetry from a later distinct source.
## Fix Focus Areas
- src/main/io/srxl2_smart_battery.c[45-67]
- src/main/io/srxl2_smart_battery.c[88-97]
## Recommended Fix
When a new identity arrives and both slots are occupied, reclaim a source only after all of its pages are stale or invalid. Clear its cached pages and observed count before assigning the new identity, while retaining stable slots for sources that are still active.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a substantial, bug-dense telemetry feature spanning protocol decoding, link invalidation, CLI exposure, CRSF framing/scheduling, and multiple ESC/battery paths with several easy-to-miss compatibility and data-validity risks.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/telemetry/crsf.c
Comment on lines +469 to +472
const uint8_t count = battery.cellCount;
if (!count || count > CRSF_SMART_BATTERY_RADIO_MAX_CELLS
|| battery.validCells != (1u << count) - 1) {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Radios can show an incomplete cell pack 🐞 Bug ≡ Correctness

crsfFrameSmartBattery accepts battery.cellCount even when it came from the highest observed cell
rather than a fresh identification page. If the limits page and first cell page arrive before
identification for a pack with more than six cells, the radio receives those six cells as a complete
pack.
Agent Prompt
## Issue description
The CRSF completeness check can treat the highest observed cell number as the pack's total cell count and publish a partial pack.
## Fix Focus Areas
- src/main/telemetry/crsf.c[467-473]
- src/main/io/srxl2_smart_battery.c[108-116]
## Recommended Fix
Require `battery.cellCountValid` before emitting a Voltage Group frame. Keep the observed count available for CLI diagnostics, but do not use it to establish CRSF pack completeness.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +53 to +57
if (!source) {
for (unsigned i = 0; i < SRXL2_SMART_BATTERY_SLOTS; i++) {
if (!state->source[i].used) {
source = &state->source[i];
source->used = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. New battery sources stop appearing 🐞 Bug ☼ Reliability

srxl2SmartBatteryReceive permanently reserves both source slots once their identities have been
seen, while invalidation clears only page validity. If a later battery source has a different
secondary or battery ID after both slots have been used, its pages are dropped even after the
earlier sources have gone stale or the link has restarted.
Agent Prompt
## Issue description
Two previously seen source identities occupy the only slots indefinitely, preventing telemetry from a later distinct source.
## Fix Focus Areas
- src/main/io/srxl2_smart_battery.c[45-67]
- src/main/io/srxl2_smart_battery.c[88-97]
## Recommended Fix
When a new identity arrives and both slots are occupied, reclaim a source only after all of its pages are stale or invalid. Clear its cached pages and observed count before assigning the new identity, while retaining stable slots for sources that are still active.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@MrScothh

MrScothh commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@UltraFly I checked that this PR and #12085 work together, since #12085 answers the ESC during start-up with the board's device ID and this PR changes it to 0x21. They merge without conflicts (dcdc34e on top of 81bde78), and I tried the result on a TBS Lucid H7 Wing with an Avian 70A on UART8, both on one 6S battery:

  • board and ESC powered together: linked, telemetry and arming allowed, 3 out of 3;
  • board on USB, ESC battery plugged right after: linked during start-up, arming allowed about 15 s after power-up;
  • board on USB, ESC battery plugged about 40 s later: linked by the running task, arming allowed 10 s after;
  • motor test at 1150 us afterwards: the motor started.

I have no Smart Battery, so the battery pages are not covered.

@sensei-hacker sensei-hacker added this to the 10.1 milestone Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

RAM / Flash usage vs. base commit f1cd5c6 — commit dcdc34e

Using the nearest available size baseline — the PR's exact base commit has no stored baseline yet.

Target Flash Δ RAM Δ
MATEKF405 ⚠️ +1700 B (+0.24%) CCM: -4 B (-0.01%)
RAM: +1636 B (+1.57%)
MATEKF722 -112 B (-0.02%) ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
TCM: ±0 B (±0.00%)
MATEKF765 -144 B (-0.02%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: ±0 B (±0.00%)
MATEKH743 ⚠️ +2208 B (+0.28%) D2_RAM: ±0 B (±0.00%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: ±0 B (±0.00%)
RAM: +1664 B (+1.17%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

Test firmware build ready — commit dcdc34e

Download firmware for PR #12077

251 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants