Skip to content

Add JBD BMS BLE driver (multi-unit, webhook, persistence) - #25053

Open
bogdik wants to merge 1 commit into
arendst:developmentfrom
bogdik:upstream-jbd-bms-ble
Open

bogdik wants to merge 1 commit into
arendst:developmentfrom
bogdik:upstream-jbd-bms-ble

Conversation

@bogdik

@bogdik bogdik commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description:

New xsns_119_jbd_bms_ble.ino talks to JBD/Xiaoxiang battery BMS units over BLE (via the existing xdrv_79 BLE stack), tracking up to 4 units in round-robin. Features:

  • Full parsing of basic/cell/version data, quiet-by-default console logging routed through the existing BLEDebug<index> level toggle.
  • Main-page web display with a device picker (add/remove by MAC) and a show/hide checkbox on the /ble config page.
  • Per-unit webhook (WebSend with all parsed fields) fired on fresh data via XdrvRulesProcess(0), since Tasmota Rules' DO clause only supports %value%/%VarN% - not cross-field references into the same JSON object - so a native webhook was needed for "notify on this unit's data" style automations.
  • Config (period, selected units, webhooks) persisted to /jbdbms.cfg across reboot via the filesystem API.

xdrv_79 changes (needed by the driver above, no other consumer):

  • A write-repeat mechanism (op->writerepeat/writerepeatinterval): some BLE peripherals, including JBD BMS units, never notify off a single write but will if the same write is resent a few times on the same connection - resends while waiting for the notify timeout.
  • Multi-fragment notify assembly (op->notifyappend/notifyendbyte): JBD BMS responses can arrive as multiple BLE notify packets; this appends fragments into dataNotify until a JBD-protocol end byte (or declared payload length for 0xDD-framed packets) is seen, instead of only ever keeping the last fragment.
  • A few new per-operation knobs (writenoresponse, notifyresponse, readafterwrite, subscribedelay) and BLEOp param letters (x/y/z/d/q/i) to control them, plus matching JSON status fields.
  • Accessor functions (getSeenDeviceCount/getSeenDeviceInfo) for other drivers to read the BLE scan device list without touching the internal seenDevices vector/mutex directly - used by xsns_119 to build its device picker.
  • Routed a couple of existing hardcoded LOG_LEVEL_INFO calls through the existing BLELogLevel array for quiet-by-default logging, consistent with the rest of the driver.

See JBD_BMS_BLE.md (included in this PR) for commands, JSON fields, and Rules examples.

Related issue (if applicable): none - new hardware support, no existing issue filed.

Checklist:

  • The pull request is done against the latest development branch
  • Only relevant files were touched
  • Only one feature/fix was added per PR and the code change compiles without warnings
  • The code change is tested and works with Tasmota core ESP8266 V.2.7.8
  • The code change is tested and works with Tasmota core ESP32 V.3.3.8 from Platform 2026.05.50
  • I accept the CLA.

NOTE: The code change must pass CI tests. Your PR cannot be merged unless tests pass

🤖 Generated with Claude Code

New xsns_119_jbd_bms_ble.ino talks to JBD/Xiaoxiang battery BMS units
over BLE (via the existing xdrv_79 BLE stack), tracking up to 4 units
in round-robin. Features:
- Full parsing of basic/cell/version data, quiet-by-default console
  logging routed through the existing BLEDebug<index> level toggle.
- Main-page web display with a device picker (add/remove by MAC) and
  a show/hide checkbox on the /ble config page.
- Per-unit webhook (WebSend with all parsed fields) fired on fresh
  data via XdrvRulesProcess(0), since Tasmota Rules' DO clause only
  supports %value%/%VarN% - not cross-field references into the same
  JSON object - so a native webhook was needed for "notify on this
  unit's data" style automations.
- Config (period, selected units, webhooks) persisted to /jbdbms.cfg
  across reboot via the filesystem API.

xdrv_79 changes (needed by the driver above, no consumer besides it):
- A write-repeat mechanism (op->writerepeat/writerepeatinterval):
  some BLE peripherals, including JBD BMS units, never notify off a
  single write but will if the same write is resent a few times on
  the same connection - resends while waiting for the notify timeout.
- Multi-fragment notify assembly (op->notifyappend/notifyendbyte):
  JBD BMS responses can arrive as multiple BLE notify packets; this
  appends fragments into dataNotify until a JBD-protocol end byte (or
  declared payload length for 0xDD-framed packets) is seen, instead
  of only ever keeping the last fragment.
- A few new per-operation knobs (writenoresponse, notifyresponse,
  readafterwrite, subscribedelay) and BLEOp param letters (x/y/z/d/q/i)
  to control them, plus matching JSON status fields.
- Accessor functions (getSeenDeviceCount/getSeenDeviceInfo) for other
  drivers to read the BLE scan device list without touching the
  internal seenDevices vector/mutex directly - used by xsns_119 to
  build its device picker.
- Routed a couple of existing hardcoded LOG_LEVEL_INFO calls through
  the existing BLELogLevel array for quiet-by-default logging,
  consistent with the rest of the driver.

See JBD_BMS_BLE.md for commands, JSON fields, and Rules examples.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

This PR has been automatically marked as stale because it hasn't any activity in last few weeks. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions github-actions Bot added the stale Action - Issue left behind - Used by the BOT to call for attention label Oct 17, 2026
@bogdik

bogdik commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Hi, this functional not needed?

@arendst

arendst commented Oct 19, 2026

Copy link
Copy Markdown
Owner

It may be needed but I see a massive change in BLE driver code of which I currently cannot see it's impact.

@github-actions github-actions Bot removed the stale Action - Issue left behind - Used by the BOT to call for attention label Oct 19, 2026
@bogdik

bogdik commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

The +210/−18 in xdrv_79 looks large, but it is almost all opt-in fields on generic_sensor_t. Defaults keep the previous path, so MI32, EQ3 and existing BLEOp users do not take the new code unless a flag is set. The JBD driver is the first caller that sets them. The knobs themselves are not JBD-specific: they are generic per-operation controls, also available as optional BLEOp letters (x/y/z/d/q/i), so other awkward BLE devices can use them from the console or from another driver without a further change to xdrv_79.

JBD/Xiaoxiang BMS cannot be driven with the current one-shot write + “keep the last notify fragment” model, and the same limits show up on other picky peripherals (write ignored unless resent, notify split across packets, Write Without Response only, subscribe that must be acknowledged, or a write dropped if it follows CCCD subscribe immediately). Three of those forced the driver changes:

  1. Write repeat (writerepeat / writerepeatinterval, default 0).
    After CCCD subscribe, a JBD unit ignores a single write. It answers only if the same command is sent again a few times on the same connection while we are still waiting for the notify. xsns_119 sets 3 extra writes, 2000 ms apart. The same q:/i: letters cover any other device that only answers a repeated write. With the default 0 the wait loop only checks the existing 20 s notify timeout, same as before.

  2. Multi-fragment notify assembly (notifyappend / notifyendbyte, default 0).
    BLEGenNotifyCB currently overwrites dataNotify on every packet and clears notifytimer on the first one, so the operation completes on the first fragment. Any device that splits one reply across several notifications can now append until a chosen end byte. A JBD basic/cell frame is valid only when it ends in 0x77. With notifyappend left at 0 the copy still starts at offset 0, which is the old behaviour. The only frame-specific line is a length check when the buffer starts with 0xDD (JBD: declared length is byte[3] + 7). It does nothing for other devices. Until the end byte or that declared length is seen, notifytimer stays set and the operation keeps waiting.

  3. Per-operation write/subscribe knobs, all defaulting to today’s behaviour, and usable for any device that needs them.

    • writenoresponse=0 keeps write_response = !canWriteNoResponse(), the same expression as the old writeValue() call. Devices whose write characteristic only accepts Write Without Response (JBD does) set this to 1.
    • notifyresponse=0 leaves subscribe’s response argument false. That matches the existing bool response = false (the comment above it explains why EQ3 breaks if this defaults to true). Devices that need an acknowledged subscribe set 1.
    • readafterwrite=0 means the extra read after write never runs. It is there for peripherals that expose the result as a readable characteristic instead of, or as well as, a notify.
    • subscribedelay is the gap after CCCD subscribe before the write. See below.
    • The JSON fields (wrnr, subrsp, rawr, subd, wrep) are only the status surface for the same knobs. Omitting the new letters changes nothing.

getSeenDeviceCount() / getSeenDeviceInfo() are a mutex-protected read of the existing scan list, so another driver can build a “pick a visible device” UI without touching seenDevices. Scan itself is unchanged. JBD’s web picker is the first user; the accessors are general.

Two spots do run for every notify/indicate operation:

  • subscribedelay defaults to 100 ms (JBD uses 500) via vTaskDelay after a successful subscribe. Some devices, JBD included, drop a write issued in the same turn as the CCCD write. Cost for current notify users: 100 ms per connection. I can default this to 0 and have only the devices that need it set a delay, if you want zero change here.

  • BLETaskStartScan(20) right after connect is now skipped when the operation has a notification characteristic. On a single BLE radio that scan was stealing the connection, and the notify never arrived. This is not JBD-only: any notify/indicate session is more reliable if scanning does not overlap it. Read/write operations with no notify UUID still start the scan immediately, as before. For existing notify users (MI32 battery reads and similar) scanning resumes when the operation finishes and disconnects, instead of overlapping the connection.

Logging: new lines, and the old “op addr match but op is not waiting” message, go through BLELogLevel, which is zero-initialised (LOG_LEVEL_NONE) until BLEDebug is set. They stay quiet at the default log level. That particular message used to be LOG_LEVEL_ERROR; once fragments are assembled, a late packet after the operation completed is normal, so it is no longer an error.

The “Show JBD in main web” checkbox is inside #ifdef USE_JBD_BMS_BLE and is not compiled into builds that do not enable the sensor.

Advertisement parsing, MI32 and the BLEOp command syntax are untouched. The new letters are optional.

This branch has not been deployed

No deployments
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.

2 participants