Remove request_tou_settings; there is no MQTT TOU read - #125
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The REST example is not executable as written, and two public docstrings still describe TOU responses as reads.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Removes the unsafe, nonfunctional MQTT TOU read API and directs schedule reads to REST.
Changes:
- Removes
request_tou_settingsfrom both MQTT layers. - Reframes
tou/rdas write-confirmation only. - Updates tests, examples, documentation, and migration notes.
File summaries
| File | Description |
|---|---|
CHANGELOG.rst |
Documents the breaking removal and migration. |
src/nwp500/mqtt/control.py |
Removes the device-level command. |
src/nwp500/mqtt/_control_commands.py |
Removes the public delegation method. |
src/nwp500/mqtt/subscriptions.py |
Updates TOU subscription documentation. |
src/nwp500/mqtt/_device_subscriptions.py |
Updates delegated subscription documentation. |
src/nwp500/models/tou.py |
Clarifies write-confirmation model semantics. |
docs/how-to/optimize-tou.rst |
Replaces MQTT reads with a REST example. |
docs/reference/python_api/mqtt_client.rst |
Removes the obsolete API reference. |
examples/advanced/tou_schedule.py |
Removes the invalid read-back request. |
examples/advanced/firmware_payload_capture.py |
Stops issuing a TOU write-shaped request. |
tests/test_public_api.py |
Guards the removed public API surface. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| :class:`~nwp500.models.TOUReservationSchedule` when the device | ||
| confirms a TOU write (triggered by :meth:`configure_tou_schedule`). | ||
| There is no MQTT read to subscribe to; to read the current schedule, | ||
| use the REST :meth:`~nwp500.NavienAPIClient.get_tou_info`. |
There was a problem hiding this comment.
Fixed in 6d7fd96 — the summary line now reads "Subscribe to Time-of-Use schedule write confirmations with automatic parsing." Done on both layers, and docs/reference/python_api/mqtt_client.rst had the same stale summary ("Subscribe to parsed TOU schedule responses"), which is corrected too.
| tou_info = await api_client.get_tou_info( | ||
| mac_address=device.device_info.mac_address, | ||
| additional_value=device.device_info.additional_value, | ||
| controller_id=controller_serial, | ||
| ) |
There was a problem hiding this comment.
Both halves were wrong, and fixed in 6d7fd96.
There is no REST source for the controller serial — I checked the HAR captures, and the vendor app sends /device/tou a controllerId it already holds. The only thing that publishes it is the MQTT device-info response (DeviceFeature.controller_serial_number).
So the section no longer claims the whole flow is MQTT-free. It now says the read itself is REST but is keyed by the controller serial, which is a hardware property: fetch it once, cache it, and every later read is pure REST. Example 3 is self-contained — a get_controller_serial() helper that connects, reads device info and disconnects, and a check_tou_settings(controller_serial=None) that skips it when you pass a cached value. The parameter list for get_tou_info says where the serial comes from.
Separately: Example 1 read it as feature.controllerSerialNumber, which the model has no such attribute for. Corrected to controller_serial_number while in the same file.
| :class:`~nwp500.models.TOUReservationSchedule` when the device | ||
| confirms a TOU write (triggered by | ||
| :meth:`~nwp500.NavienMqttClient.configure_tou_schedule`). There is no | ||
| MQTT read to subscribe to; to read the current schedule, use the REST | ||
| :meth:`~nwp500.NavienAPIClient.get_tou_info`. |
There was a problem hiding this comment.
Fixed in 6d7fd96 — the summary line now reads "Subscribe to Time-of-Use schedule write confirmations with automatic parsing." Done on both layers, and docs/reference/python_api/mqtt_client.rst had the same stale summary ("Subscribe to parsed TOU schedule responses"), which is corrected too.
6d7fd96 to
4279a09
Compare
request_tou_settings published a TOU_RESERVATION message to ctrl/tou/rd carrying only controllerSerialNumber, then waited for a reply on res/tou/rd. That reply never comes. On a live device with TOU fully provisioned - program_reservation_use true, valid controller serial, and a PG&E plan the REST API returns in full - the request produced silence for 45 seconds. ctrl/tou/rd with that command code is the *write*. It is what configure_tou_schedule publishes, and what the vendor app publishes from its TOU editor (TouScheduleViewmodel.setPublishMgppControlTou passes the on/off flag and the interval list). The device replies on res/tou/rd to confirm a write; it does not answer a request with no schedule in it. The vendor app reads TOU over REST - /device/tou and /device/tou/convert are the only TOU reads in any of the HAR captures. So the method could not have worked, and it published a write-shaped command while pretending to read. This device ignores it - touStatus was unchanged across repeated calls - but a firmware that took it at face value could read it as "store an empty TOU schedule", which is not a risk worth carrying for a call that returns nothing. Reads go through NavienAPIClient.get_tou_info. subscribe_tou_response keeps working for write confirmations, and set_tou_enabled is a separate command pair and is untouched. The docs and the two examples that called it are updated, including the payload-capture example, which should not have been publishing a control command during a read-only capture. Claude-Session: https://claude.ai/code/session_01XVj9BYvuLj7Th3iFUVoeCn
Two subscribe_tou_response docstrings kept a summary line calling these 'schedule read responses' while the body below said write confirmations; the summaries now say write confirmations too, as does the method entry in the client reference. The rewritten Example 3 used an undefined controller_serial while claiming no MQTT connection was needed. Both halves are now honest: the read is REST, but it is keyed by the controller serial, and the MQTT device-info response is the only thing that publishes it. The example fetches it once in a helper and takes a cached value; the parameter list says where it comes from. No REST endpoint returns it - the vendor app sends /device/tou a serial it already holds. Example 1 read it as feature.controllerSerialNumber, an attribute the model does not have; corrected while in the same file. Claude-Session: https://claude.ai/code/session_01XVj9BYvuLj7Th3iFUVoeCn
4279a09 to
4d96cd5
Compare
Breaking: removes
NavienMqttClient.request_tou_settings().Evidence
The method published
CommandCode.TOU_RESERVATION(0x02000007) toctrl/tou/rdwith onlycontrollerSerialNumber, then waited for a reply onres/tou/rd. That reply never comes.Tested against a live device with TOU fully provisioned —
program_reservation_usetrue, valid controller serial,touStatus: 1, and a PG&E "Electric Vehicle EV (Sch) Rate A" plan that REST/device/toureturns in full. The request publishes; nothing comes back in 45 seconds.The reason is in the vendor app:
ctrl/tou/rd+TOU_RESERVATIONis the write — whatconfigure_tou_schedulepublishes, and what the app publishes from its TOU editor. The device answers onres/tou/rdto confirm such a write; it does not answer a request carrying no schedule. The app reads TOU over REST:/device/touand/device/tou/convertare the only TOU reads in any of the HAR captures inreference/.Beyond returning nothing, the call published a write-shaped command with no schedule attached. This device ignores it —
touStatuswas unchanged across repeated calls — but a firmware that took it at face value could read it as "store an empty TOU schedule". Not a risk worth carrying for a call that returns nothing.Changes
request_tou_settingsremoved frommqtt/control.pyandmqtt/_control_commands.py.subscribe_tou_response(both layers) andTOUReservationSchedulenow say the payload is a write confirmation and point at the REST read.docs/how-to/optimize-tou.rst: the "MQTT: Request TOU Settings" section becomes "REST: Read the Current TOU Schedule", with a note on why there is no MQTT read; Example 3 is rewritten againstget_tou_info(and usesTOUSchedule.intervals' raw dicts, which the old example got wrong).docs/reference/python_api/mqtt_client.rst: method entry dropped,subscribe_tou_responsedescription corrected.examples/advanced/tou_schedule.py: drops the bogus read-back after the write.examples/advanced/firmware_payload_capture.py: drops its TOU step — a read-only capture tool should never have been publishing a control command.Migration
NavienAPIClient.get_tou_info(mac_address, additional_value, controller_id)returns the stored plan (rate name, utility, ZIP, seasonal pricing intervals) and needs no MQTT connection.subscribe_tou_responseis unaffected and still delivers write confirmations.set_tou_enabled(TOU_ON/TOU_OFF, 0x0200002B/2C) is a separate path and is untouched — that's what the Home Assistant TOU switch uses, which is why it kept working while the read never did.Tests
tests/test_public_api.py::test_mqtt_tou_read_is_gonefollows the existing "removed things stay removed" pattern and records why. Full suite: 709 passed.https://claude.ai/code/session_01XVj9BYvuLj7Th3iFUVoeCn