Repository navigation
Add MQTT battery dispatch controls and Home Assistant inputs - #184
Conversation
…dispatch # Conflicts: # RELEASE.md
There was a problem hiding this comment.
🟡 Changes recommended
Command coalescing can dispatch with stale prepared values, and concurrent state publications can show values different from those Start uses.
2 open issues
What changed in this PR
Extends the existing local battery dispatch API to MQTT and Home Assistant, with a separate opt-in control bit that preserves existing masks.
Changes:
- Adds per-gateway prepared power/duration inputs and dispatch/resume commands using local v1r connections.
- Shares HTTP/MQTT parameter validation and adds discovery, command-handling and write-lock regression coverage.
- Documents configuration, command payloads, safety warnings and release notes.
| File | Description |
|---|---|
| tests/test_mqtt_controls.py | Tests dispatch configuration, discovery, validation and message handling. |
| tests/test_battery_dispatch.py | Tests shared HTTP/MQTT write locking across timeouts. |
| RELEASE.md | Adds upcoming MQTT dispatch release notes. |
| README.md | Introduces dispatch controls and safety guidance. |
| MQTT.md | Documents dispatch topics, prepared inputs and risks. |
| mqtt-tools/README.md | Updates Home Assistant setup guidance. |
| app/utils/control.py | Adds shared dispatch parameter validation. |
| app/mqtt/publisher.py | Handles prepared inputs and local dispatch/resume commands. |
| app/mqtt/ha_discovery.py | Adds dispatch input and button discovery payloads. |
| app/config.py | Adds dispatch bit 32 and expands supported masks. |
| app/api/legacy.py | Reuses shared dispatch validation. |
| API.md | Documents MQTT dispatch configuration and cross-references. |
Review effort: Balanced
jasonacox
left a comment
There was a problem hiding this comment.
Thank you, @longzheng! You've already thought about safety carefully here:
- a separate
MQTT_CONTROLSbit (32), so existing15/31masks don't change; confirm:true;- retained commands are rejected;
- no fallback to cloud;
- the shared write lock across both APIs;
- shared validation.
That's thoughtful work, and the test coverage is excellent.
Before this goes in, I want MQTT dispatch to get the same treatment as the Console and HTTP API in #185. We aren't trying to be pedantic, but we want to be clear about how serious this control is. A dispatch takes direct control of the battery through an undocumented Tesla command and overrides the backup reserve and export settings. We have no idea whether incorrect use could damage someone's home, Powerwall system or the grid. MQTT makes it easier still to run unattended from automations, so it should only be used by people who know what they're doing, and the docs should say so loudly.
1. Require the PW_BATTERY_DISPATCH opt-in as well (#185). Please rebase onto #185 once it lands. Bit 32 alone shouldn't enable dispatch: commands need both PW_BATTERY_DISPATCH=yes and bit 32. Without the opt-in, please:
- don't announce the dispatch entities;
- reject dispatch commands with a log line that names
PW_BATTERY_DISPATCH; - have the startup log say why.
Resume can stay available, as it is over HTTP, so turning dispatch off never strands a running dispatch.
2. "Use at your own risk" everywhere it's documented. Please add the same "mqtt-tools/README.md. Also add a line to the existing MQTT controls warning in README naming bit 32: it can drain below the reserve, export despite Grid Export, and charge from the grid.
3. Home Assistant entities off by default. The Start button dispatches with one tap, with no confirmation in Home Assistant. Please announce the dispatch entities with "enabled_by_default": false, so a user has to enable them deliberately in Home Assistant, and say so in MQTT.md.
4. Copilot's two findings.
- The first can make Start charge instead of discharge: in a burst of
dispatch_power=500, then Start, thendispatch_power=-1000, coalescing drops the first input and Start runs with the previous power. Please make Start a coalescing barrier for its gateway's prepared inputs, and add the message-loop regression test Copilot describes. - The second is a stale display: please serialize the prepared-state publications per gateway, so Home Assistant always shows the values Start will use.
5. One "dispatch running" state. #185 records the dispatch the server last sent and shows it in the Console banner (dispatch_active in /control/status). Please move that into a shared helper (app/utils/control.py fits) and record MQTT dispatches and resumes there too, so the Console shows a dispatch started from Home Assistant.
6. A short live run. Please run start, resume, a retained-message rejection and the opt-in off through a real broker and Home Assistant, on your system or with @BJReplay's help. Short durations are fine.
Thanks again, Long. MQTT dispatch is a natural next step, and with these guardrails it'll be something people can use with confidence.
|
@longzheng, thank you for raising jasonacox/pypowerwall#450! The warranty catch is exactly the kind of thing that protects our users, and I really appreciate you flagging it right away. 🙏 Battery dispatch now warns that it could void or limit your Tesla Powerwall warranty (jasonacox/pypowerwall#451, #190). Please add the same warning here before this merges:
Please copy #190's wording ("could void or limit", and "the Powerwall warranty quoted in #450"), so we don't claim more than that warranty shows. If #190 merges first, a rebase brings the shared docs along. Thanks again for all your work on this. MQTT dispatch is going to be a great addition for Home Assistant users. |
|
Hi @longzheng, checking in on this one! The MQTT dispatch controls are a great follow-up to #180, and the careful safety design (a separate bit Three things are left before it can merge:
If you're busy, I'm happy to finish it on this branch: rebase, add the warning, fix both races with tests, and keep you credited as the author. Just let me know which you'd prefer. If I don't hear back by Nov 15, I'll go ahead and push the changes here. Thank you! |
|
@jasonacox sorry I'm a bit busy, if you can finish off that would be great. |
Conflicts: - publisher.py: keep both the prepared dispatch parameters and the retained-topic tracking; announce state uses main's _gateway_can_v1r_write plus this branch's _gateway_can_dispatch. - API.md: main's dispatch warranty/opt-in block, then the MQTT paragraph. - RELEASE.md: the MQTT dispatch note moves to a new [0.16.0] - Upcoming. - tests/test_battery_dispatch.py: keep both sets of tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finishes jasonacox#184 on top of 0.15.1 (thanks @longzheng): - PW_BATTERY_DISPATCH=yes is required for MQTT dispatch and the prepared inputs, like HTTP dispatch; resume works without it so turning it off can't strand a running dispatch. Home Assistant only gets the dispatch entities when it's on, and the startup log says when bit 32 is set but the opt-in is off. - Warranty warning (could void or limit your Tesla Powerwall warranty, the warranty quoted in pypowerwall#450) in MQTT.md, README.md, mqtt-tools/README.md and the startup WARNING, matching jasonacox#190. - Burst ordering: dispatch and resume are coalescing barriers, so a burst of "power 500, Start, power -1000" dispatches at 500 instead of the previous prepared power. Each Start/Resume in a burst runs in order; repeated presses with nothing between them still collapse to one. - Prepared-input publishing takes a per-gateway lock and snapshots under it, so a poll that started earlier can't publish a stale duration after an accepted input. Tests: regression tests for both races, the barrier coalescing, the opt-in (commands, discovery, startup log) and the warranty log line; all fail on the previous code. 1360 tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
MQTT publishing logs false or mismatched dispatch acknowledgements as successful without confirming the requested operation, requiring method-specific validation and regression coverage.
1 open issue
2 resolved since last review
Review effort: Balanced
MQTT logged any non-error dict as acknowledged, including
{"resume_battery_operation": false} or a dispatch reply with another
power or duration (Copilot on jasonacox#184). HTTP already checks these, so the
check moves to app/utils/control.dispatch_acknowledged() and both paths
use it: matching power and duration for a dispatch (bools are not
numbers here), resume_battery_operation True for a resume.
Tests: false and mismatched replies log as failed, matching replies as
acknowledged; the old {"ok": true} mock in the stay-local test now
returns real library replies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Battery dispatch via MQTT/Home Assistant can bypass reserve and export protections, warranting hardware-aware human review because broker, Home Assistant, and hardware behavior have not been validated live.
No open issues 🎉
1 resolved since last review
Review effort: Balanced
|
Thanks @longzheng, happy to! You built a careful, well-tested feature here: the separate bit
Every new test fails on the previous code. All 1,366 tests pass, CI is green, and Copilot has no open issues. On our 2x Powerwall 3 (v1r, read-only), the branch serves every endpoint and publishes 63 Home Assistant entities to a test broker. We don't run control writes in our hardware checks, so the dispatch path itself hasn't been exercised on hardware since these changes. @BJReplay, your earlier test run was really helpful. If you're up for another, a quick check of this build would be great: set |
|
On it, @jasonacox |
- MQTT / Home Assistant battery dispatch and resume, opt-in with MQTT_CONTROLS bit 32 plus PW_BATTERY_DISPATCH=yes (#184) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Testing: git pulled my existing clone of Ran a dispatch (with the value first changed from -500 to 500). Confirmed in the pypowerwall-server web console that the battery commenced a 500W dispatch (was idle as it was fully charged, it commenced discharging, and increased total discharge to the grid). Confirmed working. The hash 0a5db17 should be the same hash that you've used for the release build merged back into main. |
|
@jasonacox @jasonacox-sam @longzheng just successfully tested with the jasonacox/pypowerwall-server:latest docker image (v0.16.0). Confirmed working. Nice work all around. |
|
Awesome! Thanks @BJReplay ! |
|
@jasonacox Sorry, didn't notice the request for resume until now. PC shut down, but have tested resume from my phone, and confirmed that the battery started discharging, and then stopped quicker than 30 seconds, so I'm going to call that a pass, too. I can read logs from my phone, but it is way too hard. |
|
Thank you so much @BJReplay! You tested the final build, then the published 0.16.0 image, and even tested Resume from your phone. Seeing a 500 W dispatch acknowledged and the battery actually discharging on your system was exactly the live check this feature needed. That's now in the 0.16.0 release credits, and reading logs on a phone is above and beyond. 🙏 And thanks again @longzheng for building MQTT dispatch so carefully in the first place. It's out in 0.16.0! |




Follow-up to #180: MQTT clients and Home Assistant can now start a timed local battery dispatch and resume normal operation using a separate
MQTT_CONTROLSbit32. Existing15/31masks keep their previous behavior;63enables all controls.Home Assistant discovers power and duration inputs plus Start Dispatch and Resume buttons. Input changes only prepare the next command, with defaults of -500 W for 30 seconds; prepared values are per gateway, survive broker reconnects and reset on server restart. Automations can send
{"power_watts":-500,"duration_seconds":30,"confirm":true}to{prefix}/{gateway}/control/dispatch/set, or{}to.../control/resume/set.Dispatch and discovery follow each gateway's live v1r connection, including hybrid configurations. Commands reuse the HTTP write lock and timeout handling, never fall back to cloud or retry, and retain the existing payload limits, burst coalescing and retained-command rejection/clearing. HTTP and MQTT share parameter validation. Logs report acknowledgements without claiming actual battery power.
Dispatch requires
confirm:true; the Home Assistant Start button sends it when pressed. Documentation explains that discharge can export despite Grid Export settings and drain below Backup Reserve, that Home Assistant has no extra confirmation dialog, and that prepared values are not active-dispatch telemetry. API.md, MQTT.md, the setup guides and upcoming release notes are updated.Validation:
pytest -q: 1,215 passed against pypowerwall 0.22.1.git diff --checkpasses; changed Python sections follow Black formatting.