Skip to content

[shelly] Tolerate third-party BLE script event data in NotifyEvent frames - #21385

Open
ML19821 wants to merge 3 commits into
openhab:mainfrom
ML19821:fix/shelly-notifyevent-data-shape
Open

[shelly] Tolerate third-party BLE script event data in NotifyEvent frames#21385
ML19821 wants to merge 3 commits into
openhab:mainfrom
ML19821:fix/shelly-notifyevent-data-shape

Conversation

@ML19821

@ML19821 ML19821 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #21384.

Gen2/Gen3 devices running a third-party BLE-proxy script (e.g. the one Home Assistant's Shelly integration installs to use the device as a Bluetooth proxy) emit NotifyEvent frames with event ble.scan_result whose data member is an array. Shelly2NotifyEvent hard-typed data as Shelly2NotifyBluEventData (the object shape of the binding's own oh-blu.* scanner script), so Gson failed the whole frame with Expected BEGIN_OBJECT but was BEGIN_ARRAY, discarding every event in it — including unrelated ones such as button pushes sharing the frame — and dumping the full payload into the DEBUG log several times per second per affected device.

Changes:

  • Shelly2NotifyEvent.data is now a raw JsonElement with a lazy getBluData(Gson) accessor that returns the BLU payload only when the element actually is a JSON object. The two call sites that read the old field were converted; the binding's own oh-blu.* object format parses exactly as before (same plain Gson instance, no custom adapters in the bundle).
  • ble.scan_result events are skipped explicitly in Shelly2RpcSocket.onMessage; since the second review round the frame is forwarded to onNotifyEvent once, after the loop, and only when it carries a regular event (a scan-only frame is not forwarded at all). A belt-and-braces no-op case in Shelly2ApiRpc.onNotifyEvent's switch covers the mixed oh-blu.* path. At most a trace log — the DEBUG log stays quiet.

Side effect worth noting: a frame carrying both a ble.scan_result and a real event (e.g. a button push) was previously discarded wholesale; the real event is now processed.

Testing

  • New Shelly2NotifyEventDataShapeTest covers the three data shapes (object → populated BLU data; Home-Assistant-style array → parses without exception, no BLU data; absent → no BLU data). Full bundle suite: 715 tests, 0 failures (710 at submission, +1 in review round 1, +4 mixed-frame tests in round 2); spotless/checkstyle/PMD/SpotBugs pass. The pre-existing BLU tests double as the regression proof for the oh-blu.* object format.
  • Live test on an openHAB 5.2.1 installation (5.2.x backport sideloaded) with three Gen2/Gen3 devices running Home Assistant's BLE-proxy script: before, hundreds of discarded frames per minute with the Gson error; after, zero parse errors, all things online, and 660 ble.scan_result events skipped in the first two minutes (visible at TRACE).

Closing:

  • 21384

Signed-off-by (in commit): Martin Littkovsky 2018turtle@proton.me

@ML19821
ML19821 requested a review from markus7017 as a code owner August 13, 2026 21:33
…ames

Gen2/Gen3 devices running a third-party BLE-proxy script (e.g. Home
Assistant's) emit NotifyEvent frames with event "ble.scan_result" whose
"data" member is an array. Shelly2NotifyEvent hard-typed that member to
Shelly2NotifyBluEventData, so Gson failed the whole frame with "Expected
BEGIN_OBJECT but was BEGIN_ARRAY", discarding every event in it (including
unrelated ones such as button pushes) and logging the full payload several
times per second on affected devices.

The "data" member is now kept as a raw JsonElement and resolved lazily via
getBluData(), which returns the BLU payload only when the element actually
is a JSON object; the binding's own oh-blu.* scanner format parses exactly
as before. ble.scan_result events are skipped explicitly so they are
neither re-parsed per event nor logged as unhandled.

Signed-off-by: Martin Littkovsky <2018turtle@proton.me>
AI-assisted-by: Claude Code
@ML19821
ML19821 force-pushed the fix/shelly-notifyevent-data-shape branch from 488043d to 11d6a06 Compare August 14, 2026 05:11
@wborn
wborn requested a balanced review from Copilot August 14, 2026 20:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates Shelly Gen2/Gen3 event handling to tolerate third-party BLE scanner payloads without discarding entire frames.

Changes:

  • Stores polymorphic event data as JsonElement and lazily parses BLU payloads.
  • Silently skips ble.scan_result events.
  • Adds tests for object, array, and absent data shapes.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Shelly2ApiJsonDTO.java Adds raw event data handling and BLE scan constant.
Shelly2RpcSocket.java Routes BLU data lazily and skips third-party scans.
Shelly2ApiRpc.java Adds fallback handling for skipped scans.
ShellyBluApi.java Uses lazy BLU payload conversion.
Shelly2NotifyEventDataShapeTest.java Tests supported event data shapes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…data

Deserializing the BLU payload directly through Gson let a JsonSyntaxException
escape for an object-shaped but malformed payload. Before this PR the whole
frame went through ShellyUtils.fromJson, which wraps that into the checked
ShellyApiException - and that is what both call sites catch, so the unchecked
exception would have escaped Shelly2RpcSocket.onMessage and
ShellyBluApi.onNotifyEvent instead of being logged with the payload.

getBluData() now routes the conversion through the same helper and declares the
checked exception. A malformed object therefore fails exactly as it did before,
rather than silently returning no BLU data: the device did send a BLU event, and
dropping it without a trace would hide a real problem.

The array-shaped case that this PR is about is unaffected - it returns before
any conversion happens.

Signed-off-by: Martin Littkovsky <2018turtle@proton.me>
AI-assisted-by: Claude Code

@wborn wborn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI review before manual maintainer review.

The ble.scan_result parsing fix looks good, and the follow-up commit correctly preserves the existing ShellyApiException boundary. One issue remains around mixed NotifyEvent frames, plus one smaller robustness suggestion.

A mixed-frame routing test covering ble.scan_result together with multiple regular events would also help guard the behavior this PR introduces.

…once

onNotifyEvent() walks the whole frame itself, but the frame was handed to it
once per non-BLU event in that frame. A frame carrying two regular events was
therefore processed twice, so both events counted twice - a button press
counted as two.

This is older than the polymorphic data handling, but that change is what makes
mixed frames parseable in the first place: a third-party ble.scan_result next
to regular events used to fail the whole frame, so this path was never reached
for them. The frame is now forwarded once, after the loop has determined that
it contains at least one regular event.

ShellyBluApi additionally deserialized the data of every event as a BLU payload
before looking at the event name. Only oh-blu.* events carry one; unrelated
payloads were misread, and since the complete hub frame reaches this handler,
a malformed unrelated payload could abort the whole frame.

Adds routing tests for a frame with several regular events, a mixed frame, a
scan-result-only frame and a single-event frame.

Signed-off-by: Martin Littkovsky <2018turtle@proton.me>
AI-assisted-by: Claude Code

@wborn wborn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The two previous AI review findings are addressed in 009219c7: NotifyEvent frames are now forwarded to the hub handler only once, and BLU payload conversion is restricted to oh-blu.* events. The added mixed-frame routing tests cover the duplication regression and the new ble.scan_result handling.

AI found no further issues in the current changes. A human maintainer should still review the PR before merge.

@lsiepel lsiepel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, LGTM

Awaiting confirmation from @markus7017

@lsiepel lsiepel added the enhancement An enhancement or new feature for an existing add-on label Aug 15, 2026
@markus7017

Copy link
Copy Markdown
Contributor

I come back here when #21287 is merged, from a first quick check it looks ok

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

Labels

enhancement An enhancement or new feature for an existing add-on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[shelly] NotifyEvent frames from third-party BLE-proxy scripts are discarded (data shape mismatch)

5 participants