Skip to content

Commit 6014e44

Browse files
committed
Slim mqtt/client.py toward a thin facade (#99)
Extract the ~40 device control command proxies and the typed subscribe_*/ unsubscribe_* device-subscription proxies from NavienMqttClient into two focused mixins (DeviceControlCommandsMixin in _control_commands.py and DeviceSubscriptionsMixin in _device_subscriptions.py). NavienMqttClient now inherits both, keeping its public API unchanged while client.py shrinks from ~1572 to ~1196 lines and reads as connection orchestration plus a public facade. No behavior change.
1 parent 14b0878 commit 6014e44

4 files changed

Lines changed: 461 additions & 391 deletions

File tree

CHANGELOG.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ Changed
2525
is byte-for-byte unchanged (verified by golden capture and the existing
2626
CLI tests); energy output now renders a summary table plus a breakdown
2727
table entirely in Rich rather than printing plain text and a Rich table.
28+
- **mqtt/client.py slimmed toward a thin façade** (`#99
29+
<https://github.com/eman/nwp500-python/issues/99>`_): the ~40 device
30+
control command proxies and the typed ``subscribe_*``/``unsubscribe_*``
31+
device-subscription proxies were moved out of ``NavienMqttClient`` into
32+
two focused mixins, ``DeviceControlCommandsMixin``
33+
(``mqtt/_control_commands.py``) and ``DeviceSubscriptionsMixin``
34+
(``mqtt/_device_subscriptions.py``). ``NavienMqttClient`` now inherits
35+
both, so its public API is unchanged, while ``mqtt/client.py`` shrinks
36+
from ~1572 to ~1196 lines and reads more clearly as connection
37+
orchestration plus a public façade. No behavior change.
2838
- **Event system relationship clarified and de-duplicated** (`#102
2939
<https://github.com/eman/nwp500-python/issues/102>`_): ``events.py`` and
3040
``mqtt_events.py`` are not two competing event mechanisms.
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
"""Device control command proxies for :class:`NavienMqttClient`.
2+
3+
This mixin holds the thin public convenience methods that forward Navien
4+
device-control commands to :class:`~nwp500.mqtt.control.MqttDeviceController`.
5+
Splitting them out of ``client.py`` keeps :class:`NavienMqttClient` focused on
6+
connection orchestration while preserving its public API surface.
7+
"""
8+
9+
from collections.abc import Sequence
10+
from typing import TYPE_CHECKING, Any
11+
12+
from .control import MqttDeviceController
13+
14+
if TYPE_CHECKING:
15+
from ..models import (
16+
Device,
17+
OtaCommitPayload,
18+
RecirculationSchedule,
19+
WeeklyReservationSchedule,
20+
)
21+
22+
__author__ = "Emmanuel Levijarvi"
23+
__copyright__ = "Emmanuel Levijarvi"
24+
__license__ = "MIT"
25+
26+
27+
class DeviceControlCommandsMixin:
28+
"""Public device-control commands that delegate to the controller."""
29+
30+
# Provided by NavienMqttClient.__init__.
31+
_device_controller: MqttDeviceController
32+
33+
async def request_device_status(self, device: Device) -> int:
34+
"""Request general device status."""
35+
return await self._device_controller.request_device_status(device)
36+
37+
async def request_device_info(self, device: Device) -> int:
38+
"""Request device information (features, firmware, etc.)."""
39+
return await self._device_controller.request_device_info(device)
40+
41+
async def set_power(self, device: Device, power_on: bool) -> int:
42+
"""Turn device on or off."""
43+
return await self._device_controller.set_power(device, power_on)
44+
45+
async def set_dhw_mode(
46+
self, device: Device, mode_id: int, vacation_days: int | None = None
47+
) -> int:
48+
"""Set DHW operation mode."""
49+
return await self._device_controller.set_dhw_mode(
50+
device, mode_id, vacation_days
51+
)
52+
53+
async def enable_anti_legionella(
54+
self, device: Device, period_days: int
55+
) -> int:
56+
"""Enable Anti-Legionella disinfection."""
57+
return await self._device_controller.enable_anti_legionella(
58+
device, period_days
59+
)
60+
61+
async def disable_anti_legionella(self, device: Device) -> int:
62+
"""Disable the Anti-Legionella disinfection cycle."""
63+
return await self._device_controller.disable_anti_legionella(device)
64+
65+
async def set_dhw_temperature(
66+
self, device: Device, temperature: float
67+
) -> int:
68+
"""Set DHW target temperature in the user's preferred unit."""
69+
return await self._device_controller.set_dhw_temperature(
70+
device, temperature
71+
)
72+
73+
async def update_reservations(
74+
self,
75+
device: Device,
76+
reservations: Sequence[dict[str, Any]],
77+
*,
78+
enabled: bool = True,
79+
) -> int:
80+
"""Update programmed reservations."""
81+
return await self._device_controller.update_reservations(
82+
device, reservations, enabled=enabled
83+
)
84+
85+
async def request_reservations(self, device: Device) -> int:
86+
"""Request the current reservation program from the device."""
87+
return await self._device_controller.request_reservations(device)
88+
89+
async def configure_tou_schedule(
90+
self,
91+
device: Device,
92+
controller_serial_number: str,
93+
periods: Sequence[dict[str, Any]],
94+
*,
95+
enabled: bool = True,
96+
) -> int:
97+
"""Configure the Time-of-Use rate schedule."""
98+
return await self._device_controller.configure_tou_schedule(
99+
device, controller_serial_number, periods, enabled=enabled
100+
)
101+
102+
async def request_tou_settings(
103+
self, device: Device, controller_serial_number: str
104+
) -> int:
105+
"""Request the current TOU settings from the device."""
106+
return await self._device_controller.request_tou_settings(
107+
device, controller_serial_number
108+
)
109+
110+
async def set_tou_enabled(self, device: Device, enabled: bool) -> int:
111+
"""Enable or disable Time-of-Use optimization."""
112+
return await self._device_controller.set_tou_enabled(device, enabled)
113+
114+
async def request_energy_usage(
115+
self, device: Device, year: int, months: list[int]
116+
) -> int:
117+
"""Request daily energy usage data for specified month(s)."""
118+
return await self._device_controller.request_energy_usage(
119+
device, year, months
120+
)
121+
122+
async def signal_app_connection(self, device: Device) -> int:
123+
"""Signal that the app has connected."""
124+
return await self._device_controller.signal_app_connection(device)
125+
126+
async def enable_demand_response(self, device: Device) -> int:
127+
"""Enable utility demand response participation."""
128+
return await self._device_controller.enable_demand_response(device)
129+
130+
async def disable_demand_response(self, device: Device) -> int:
131+
"""Disable utility demand response participation."""
132+
return await self._device_controller.disable_demand_response(device)
133+
134+
async def reset_air_filter(self, device: Device) -> int:
135+
"""Reset air filter maintenance timer."""
136+
return await self._device_controller.reset_air_filter(device)
137+
138+
async def set_vacation_days(self, device: Device, days: int) -> int:
139+
"""Set vacation/away mode duration (1-30 days)."""
140+
return await self._device_controller.set_vacation_days(device, days)
141+
142+
async def update_weekly_reservation(
143+
self, device: Device, schedule: WeeklyReservationSchedule
144+
) -> int:
145+
"""Configure the weekly temperature reservation schedule."""
146+
return await self._device_controller.update_weekly_reservation(
147+
device, schedule
148+
)
149+
150+
async def configure_reservation_water_program(self, device: Device) -> int:
151+
"""Enable/configure water program reservation mode."""
152+
controller = self._device_controller
153+
return await controller.configure_reservation_water_program(device)
154+
155+
async def configure_recirculation_schedule(
156+
self, device: Device, schedule: RecirculationSchedule
157+
) -> int:
158+
"""Configure the recirculation pump timed schedule."""
159+
return await self._device_controller.configure_recirculation_schedule(
160+
device, schedule
161+
)
162+
163+
async def set_recirculation_mode(self, device: Device, mode: int) -> int:
164+
"""Set recirculation pump operation mode (1-4)."""
165+
return await self._device_controller.set_recirculation_mode(
166+
device, mode
167+
)
168+
169+
async def trigger_recirculation_hot_button(self, device: Device) -> int:
170+
"""Manually trigger the recirculation pump hot button."""
171+
return await self._device_controller.trigger_recirculation_hot_button(
172+
device
173+
)
174+
175+
async def check_firmware_update(self, device: Device) -> int:
176+
"""Check for available over-the-air firmware updates."""
177+
return await self._device_controller.check_firmware_update(device)
178+
179+
async def commit_firmware_update(
180+
self, device: Device, payload: OtaCommitPayload
181+
) -> int:
182+
"""Commit a previously downloaded firmware update."""
183+
return await self._device_controller.commit_firmware_update(
184+
device, payload
185+
)
186+
187+
async def reconnect_wifi(self, device: Device) -> int:
188+
"""Trigger a WiFi reconnection on the device."""
189+
return await self._device_controller.reconnect_wifi(device)
190+
191+
async def reset_wifi(self, device: Device) -> int:
192+
"""Reset WiFi settings to factory defaults."""
193+
return await self._device_controller.reset_wifi(device)
194+
195+
async def set_freeze_protection_temperature(
196+
self, device: Device, temperature: float
197+
) -> int:
198+
"""Set the freeze protection activation temperature."""
199+
return await self._device_controller.set_freeze_protection_temperature(
200+
device, temperature
201+
)
202+
203+
async def run_smart_diagnostic(self, device: Device) -> int:
204+
"""Trigger the smart diagnostic routine on the device."""
205+
return await self._device_controller.run_smart_diagnostic(device)
206+
207+
async def enable_intelligent_scheduling(self, device: Device) -> int:
208+
"""Enable intelligent/adaptive heating mode."""
209+
return await self._device_controller.enable_intelligent_scheduling(
210+
device
211+
)
212+
213+
async def disable_intelligent_scheduling(self, device: Device) -> int:
214+
"""Disable intelligent/adaptive heating mode."""
215+
return await self._device_controller.disable_intelligent_scheduling(
216+
device
217+
)

0 commit comments

Comments
 (0)