fix(ha): send target as a sibling of service_data in websocket call_service - #4669
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The current implementation mutates the queued service_data dict in-place (affecting cross-thread state/logging), and the new documentation sentence is misleading about how non-target fields are sent.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes Home Assistant WebSocket call_service framing so that target (entity/device/area addressing) is sent as a top-level sibling of service_data, matching HA’s expected schema and unblocking service-based inverter control flows (notably input_boolean bridge automations).
Changes:
- Update
HAInterface.socketLoop()to extracttargetfrom queuedservice_dataand send it as the top-leveltargetfield in the outgoingcall_serviceframe. - Add a regression test asserting the outbound WebSocket frame shape for queued commands containing a nested
target. - Document the supported
target:form in the service API configuration docs.
File summaries
| File | Description |
|---|---|
| apps/predbat/ha.py | Adjusts WebSocket call_service payload construction to place target at the correct top-level location. |
| apps/predbat/tests/test_hainterface_websocket.py | Adds a regression test to verify correct target framing in queued WebSocket service calls. |
| docs/inverter-setup.md | Documents target: syntax support for service API definitions. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
springfall2008
added a commit
that referenced
this pull request
Aug 23, 2026
- Pop target from a copy of service_data, not the dict itself - it's the same object async_call_service_websocket_command() logs on failure, so popping in place was silently dropping target from that diagnostic. - Reword the docs note: only target is elevated to a top-level sibling field, other keys (entity_id, device_id, option, etc.) still travel inside service_data as before. - Extend the regression test to assert the original queued service_data is left unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ervice HA's call_service websocket command expects target (entity_id/device_id/ area_id addressing) as a sibling of service_data, not nested inside it. A service configured with the common target: entity_id: ... syntax (e.g. input_boolean.turn_on/turn_off for a generic Service API bridge) landed with target nested inside service_data, which HA rejects with invalid_format: extra keys not allowed @ data['target'] - silently blocking every charge/discharge service call while Predbat's plan and status kept reporting normally. Fixes #4662
- Pop target from a copy of service_data, not the dict itself - it's the same object async_call_service_websocket_command() logs on failure, so popping in place was silently dropping target from that diagnostic. - Reword the docs note: only target is elevated to a top-level sibling field, other keys (entity_id, device_id, option, etc.) still travel inside service_data as before. - Extend the regression test to assert the original queued service_data is left unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
springfall2008
force-pushed
the
fix/input-boolean-service-target-4662
branch
from
August 23, 2026 17:44
510db38 to
1bf1d9e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
call_servicewebsocket command expectstarget(entity_id/device_id/area_id addressing) as a sibling ofservice_data, not nested inside it. Predbat'scall_service_template()/call_service()forwarded any key from a dict-style service definition verbatim intoservice_data, so a service configured with the commontarget: entity_id: ...syntax (matching standard HA action/automation syntax, and easy to reach for when wiring up a genericinput_booleanService API bridge) ended up withtargetnested insideservice_data.invalid_format: extra keys not allowed @ data['target'], which silently blocked every charge/discharge/freeze service call while Predbat's internal plan andpredbat.statuskept reporting normally, since the failure is only logged as aWarn:and never propagated back to plan/status state.socketLoop()'s queued-command send, poptargetout ofservice_dataand send it as its own top-level field in the outgoingcall_serviceframe.target:addressing form as supported alongside the existing flatentity_id:form inhas_service_api.Fixes #4662
Test plan
test_hainterface_socketloop_call_service_target_fielddrives a realsocketLoop()iteration with a queuedinput_boolean.turn_oncommand carrying a nestedtarget, and asserts the frame sent over the websocket hastargetas a sibling field, not nested insideservice_data. Confirmed RED before the fix, GREEN after../run_all --test hainterface_service --test hainterface_websocket --test hainterface_lifecycle --test hainterface_state --test hainterface_api --test inverter— all pass../run_all --quick— 155 passed, identical to a cleanmaincheckout (one pre-existing, order-dependenttest_web_functions.pycrash reproduces identically on unmodifiedmain, unrelated to this change).pre-commit run --all-files(ruff, black, cspell, markdownlint, etc.) — all pass.🤖 Generated with Claude Code