nwp500-python 9.3.0__py3-none-any.whl → 9.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nwp500/__init__.py CHANGED
@@ -75,7 +75,9 @@ from nwp500.factory import (
75
75
  )
76
76
  from nwp500.models import (
77
77
  ConvertedTOUPlan,
78
+ DescalingInfo,
78
79
  Device,
80
+ DeviceErrorSummary,
79
81
  DeviceFeature,
80
82
  DeviceInfo,
81
83
  DeviceStatus,
@@ -147,6 +149,8 @@ __all__ = [
147
149
  "DeviceInfo",
148
150
  "Location",
149
151
  "Device",
152
+ "DeviceErrorSummary",
153
+ "DescalingInfo",
150
154
  "FirmwareInfo",
151
155
  "ReservationEntry",
152
156
  "ReservationSchedule",
nwp500/cli/__main__.py CHANGED
@@ -143,6 +143,11 @@ def async_command(f: Any) -> Any:
143
143
  await mqtt.disconnect()
144
144
  return 0
145
145
 
146
+ except click.ClickException:
147
+ # A usage/config error a command raised deliberately. Let
148
+ # Click report it as itself; the catch-all below would
149
+ # otherwise log it as an "Unexpected Error" with a traceback.
150
+ raise
146
151
  except (
147
152
  InvalidCredentialsError,
148
153
  AuthenticationError,
@@ -639,14 +644,69 @@ async def tou_apply(
639
644
  _logger.error("API client not available")
640
645
 
641
646
 
642
- @cli.command() # type: ignore[attr-defined]
647
+ def _parse_months(
648
+ ctx: click.Context, param: click.Parameter, value: str | None
649
+ ) -> list[int] | None:
650
+ """Parse ``--months`` at parse time, so bad input is a usage error."""
651
+ if value is None:
652
+ return None
653
+ months = []
654
+ for raw in value.split(","):
655
+ try:
656
+ month = int(raw.strip())
657
+ except ValueError:
658
+ raise click.BadParameter(
659
+ f"{raw.strip()!r} is not a month number; expected a "
660
+ "comma-separated list like 1,2,3"
661
+ ) from None
662
+ if not 1 <= month <= 12:
663
+ raise click.BadParameter(f"{month} is not in the range 1-12")
664
+ months.append(month)
665
+ if not months:
666
+ raise click.BadParameter("at least one month is required")
667
+ return months
668
+
669
+
670
+ class _EnergySelection(click.Command):
671
+ """Rejects an unusable --month/--months combination during parsing.
672
+
673
+ Cross-option checks have no natural home in a per-option callback, and
674
+ the command body is the wrong place: it runs inside ``async_command``,
675
+ after authentication and the MQTT connect, so a usage error there would
676
+ surface only to users whose credentials already work - and then as an
677
+ "Unexpected Error" traceback, because the wrapper's catch-all swallows
678
+ ``ClickException``.
679
+ """
680
+
681
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
682
+ rest = super().parse_args(ctx, args)
683
+ month, months = ctx.params.get("month"), ctx.params.get("months")
684
+ if month is not None and months is not None:
685
+ raise click.UsageError(
686
+ "Use either --month (daily breakdown) or --months "
687
+ "(monthly summary), not both",
688
+ ctx=ctx,
689
+ )
690
+ if month is None and months is None:
691
+ raise click.UsageError(
692
+ "Either --months (for monthly summary) or --month "
693
+ "(for daily breakdown) is required",
694
+ ctx=ctx,
695
+ )
696
+ return rest
697
+
698
+
699
+ @cli.command(cls=_EnergySelection) # type: ignore[attr-defined]
643
700
  @click.option("--year", type=int, required=True, help="Year to query")
644
701
  @click.option(
645
- "--months", required=False, help="Comma-separated months (e.g. 1,2,3)"
702
+ "--months",
703
+ required=False,
704
+ callback=_parse_months,
705
+ help="Comma-separated months for a monthly summary (e.g. 1,2,3)",
646
706
  )
647
707
  @click.option(
648
708
  "--month",
649
- type=int,
709
+ type=click.IntRange(1, 12),
650
710
  required=False,
651
711
  help="Show daily breakdown for a specific month (1-12)",
652
712
  )
@@ -655,26 +715,25 @@ async def energy(
655
715
  mqtt: NavienMqttClient,
656
716
  device: Any,
657
717
  year: int,
658
- months: str | None,
718
+ months: list[int] | None,
659
719
  month: int | None,
660
720
  ) -> None:
661
721
  """Query historical energy usage.
662
722
 
663
723
  Use either --months for monthly summary or --month for daily breakdown.
724
+ Which option was passed decides the view, not how many months it names:
725
+ ``--months 5`` is a one-month summary, ``--month 5`` is a daily
726
+ breakdown of that month.
664
727
  """
728
+ # _EnergySelection rejected every combination but these two during
729
+ # parsing, so exactly one of the options is set here.
665
730
  if month is not None:
666
- # Daily breakdown for a single month
667
- if month < 1 or month > 12:
668
- raise click.ClickException("Month must be between 1 and 12")
669
- await handlers.handle_get_energy_request(mqtt, device, year, [month])
731
+ await handlers.handle_get_energy_request(
732
+ mqtt, device, year, [month], daily=True
733
+ )
670
734
  elif months is not None:
671
- # Monthly summary
672
- month_list = [int(m.strip()) for m in months.split(",")]
673
- await handlers.handle_get_energy_request(mqtt, device, year, month_list)
674
- else:
675
- raise click.ClickException(
676
- "Either --months (for monthly summary) or --month "
677
- "(for daily breakdown) required"
735
+ await handlers.handle_get_energy_request(
736
+ mqtt, device, year, months, daily=False
678
737
  )
679
738
 
680
739
 
nwp500/cli/handlers.py CHANGED
@@ -865,12 +865,16 @@ async def handle_tou_apply_request(
865
865
 
866
866
 
867
867
  async def handle_get_energy_request(
868
- mqtt: NavienMqttClient, device: Device, year: int, months: list[int]
868
+ mqtt: NavienMqttClient,
869
+ device: Device,
870
+ year: int,
871
+ months: list[int],
872
+ daily: bool = False,
869
873
  ) -> None:
870
874
  """Request energy usage data.
871
875
 
872
- If a single month is provided, shows daily breakdown.
873
- If multiple months are provided, shows monthly summary.
876
+ If ``daily`` is set, shows the day-by-day breakdown for ``months[0]``.
877
+ Otherwise shows the monthly summary across the requested months.
874
878
  """
875
879
  try:
876
880
  res: Any = await _wait_for_response(
@@ -880,8 +884,8 @@ async def handle_get_energy_request(
880
884
  action_name="energy usage",
881
885
  timeout=15,
882
886
  )
883
- # If single month requested, show daily breakdown
884
- if len(months) == 1:
887
+ # Daily breakdown only when explicitly requested
888
+ if daily:
885
889
  from .output_formatters import print_daily_energy_usage
886
890
 
887
891
  print_daily_energy_usage(
nwp500/models/__init__.py CHANGED
@@ -14,7 +14,9 @@ from ._converters import (
14
14
  )
15
15
  from .device import (
16
16
  ConnectionStatusField,
17
+ DescalingInfo,
17
18
  Device,
19
+ DeviceErrorSummary,
18
20
  DeviceInfo,
19
21
  FirmwareInfo,
20
22
  Location,
@@ -69,6 +71,8 @@ __all__ = [
69
71
  "DeviceInfo",
70
72
  "Location",
71
73
  "Device",
74
+ "DeviceErrorSummary",
75
+ "DescalingInfo",
72
76
  "FirmwareInfo",
73
77
  "TOUSchedule",
74
78
  "ConvertedTOUPlan",
nwp500/models/device.py CHANGED
@@ -1,10 +1,10 @@
1
1
  from typing import Annotated, Self
2
2
 
3
- from pydantic import BeforeValidator
3
+ from pydantic import BeforeValidator, Field
4
4
 
5
5
  from .._base import NavienBaseModel
6
6
  from ..converters import enum_validator
7
- from ..enums import ConnectionStatus, DeviceType
7
+ from ..enums import ConnectionStatus, DeviceType, ErrorCode
8
8
 
9
9
  ConnectionStatusField = Annotated[
10
10
  ConnectionStatus, BeforeValidator(enum_validator(ConnectionStatus))
@@ -21,6 +21,8 @@ class DeviceInfo(NavienBaseModel):
21
21
  device_name: str = "Unknown"
22
22
  connected: ConnectionStatusField = ConnectionStatus.DISCONNECTED
23
23
  install_type: str | None = None
24
+ model_type_code: int | None = None
25
+ installer_id: str | None = None
24
26
 
25
27
 
26
28
  class Location(NavienBaseModel):
@@ -34,11 +36,56 @@ class Location(NavienBaseModel):
34
36
  altitude: float | None = None
35
37
 
36
38
 
39
+ class DeviceErrorSummary(NavienBaseModel):
40
+ """Last device fault as reported by the REST API.
41
+
42
+ The cloud keeps this independently of the live MQTT status, so it is
43
+ readable without an MQTT connection - including while the device is
44
+ offline, where it reports the fault as of the last time the device
45
+ was heard from.
46
+ """
47
+
48
+ #: ``NO_ERROR`` when the device has no recorded fault, and ``None`` when
49
+ #: the cloud reports no code at all - it sends ``"errorCode": null`` on
50
+ #: some devices, which is not the same claim as "no error". Typed to
51
+ #: accept a bare int as well, following ``device_type``, so neither a
52
+ #: null nor a code the enum does not know can make a whole
53
+ #: ``/device/list`` response unparseable.
54
+ #:
55
+ #: ``union_mode`` matters here: pydantic's default smart union matches an
56
+ #: incoming int against the ``int`` branch exactly and never reaches the
57
+ #: enum, so every code - known or not - would stay a plain int. Trying the
58
+ #: branches left to right instead means a known code becomes an
59
+ #: ``ErrorCode`` member and only an unknown one falls through to ``int``.
60
+ error_code: ErrorCode | int | None = Field(
61
+ default=None, union_mode="left_to_right"
62
+ )
63
+ #: Spelled "Occured" by the API; the Python name is spelled correctly.
64
+ error_occurred_time: str | None = Field(
65
+ default=None, alias="errorOccuredTime"
66
+ )
67
+
68
+
69
+ class DescalingInfo(NavienBaseModel):
70
+ """Descaling window reported by the REST API.
71
+
72
+ Both timestamps are ``None`` on a device with no descaling scheduled
73
+ or recorded.
74
+ """
75
+
76
+ descaling_start_time: str | None = None
77
+ descaling_end_time: str | None = None
78
+
79
+
37
80
  class Device(NavienBaseModel):
38
81
  """Complete device information including location."""
39
82
 
40
83
  device_info: DeviceInfo
41
84
  location: Location
85
+ #: Present on ``/device/list``; absent from ``/device/info``.
86
+ error: DeviceErrorSummary | None = None
87
+ #: Present on both ``/device/list`` and ``/device/info``.
88
+ descaling: DescalingInfo | None = None
42
89
 
43
90
  def with_info(self, info: DeviceInfo) -> Self:
44
91
  """Return a new Device instance with updated DeviceInfo."""
nwp500/models/feature.py CHANGED
@@ -201,7 +201,12 @@ class DeviceFeature(NavienBaseModel):
201
201
  ),
202
202
  )
203
203
  mixing_valve_use: CapabilityFlag = Field(
204
- alias="mixingValveUse",
204
+ # The device spells this "Value", not "Valve" - the misspelling is
205
+ # Navien's own (see KDResponseMgppDid$Feature in the vendor app and
206
+ # docs/reference/protocol/device_features.rst). Correcting it here
207
+ # makes the field silently fall back to its default, because the
208
+ # base model ignores unknown keys.
209
+ alias="mixingValueUse",
205
210
  default=False,
206
211
  description=("Thermostatic mixing valve support (2=supp, 1=not)"),
207
212
  )
nwp500/models/tou.py CHANGED
@@ -145,11 +145,12 @@ class TOUPeriod(NavienBaseModel):
145
145
  class TOUReservationSchedule(NavienBaseModel):
146
146
  """TOU schedule as returned by the MQTT ``tou/rd`` response topic.
147
147
 
148
- This model matches the raw MQTT payload for both
149
- :meth:`~nwp500.NavienMqttClient.request_tou_settings` read responses
150
- and :meth:`~nwp500.NavienMqttClient.configure_tou_schedule` write
151
- confirmations — both use ``CommandCode.TOU_RESERVATION`` and the
152
- ``tou/rd`` response topic.
148
+ This model matches the raw MQTT payload the device sends to confirm a
149
+ :meth:`~nwp500.NavienMqttClient.configure_tou_schedule` write. The device
150
+ has no MQTT read for its TOU schedule - ``ctrl/tou/rd`` with
151
+ ``CommandCode.TOU_RESERVATION`` is the write - so this payload only ever
152
+ arrives as a write confirmation. To read the current schedule, use the
153
+ REST :meth:`~nwp500.NavienAPIClient.get_tou_info`.
153
154
 
154
155
  The payload structure is::
155
156
 
@@ -99,14 +99,6 @@ class DeviceControlCommandsMixin:
99
99
  device, controller_serial_number, periods, enabled=enabled
100
100
  )
101
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
102
  async def set_tou_enabled(self, device: Device, enabled: bool) -> int:
111
103
  """Enable or disable Time-of-Use optimization."""
112
104
  return await self._device_controller.set_tou_enabled(device, enabled)
@@ -194,14 +194,15 @@ class DeviceSubscriptionsMixin:
194
194
  device: Device,
195
195
  callback: Callable[[TOUReservationSchedule], None],
196
196
  ) -> int:
197
- """Subscribe to Time-of-Use schedule read responses with automatic
198
- parsing.
197
+ """Subscribe to Time-of-Use schedule write confirmations with
198
+ automatic parsing.
199
199
 
200
200
  Subscribes to the ``tou/rd`` response topic for the given device.
201
201
  The callback receives a fully-parsed
202
- :class:`~nwp500.models.TOUReservationSchedule` whenever the device
203
- responds to a TOU read or configure request (triggered by
204
- :meth:`request_tou_settings` or :meth:`configure_tou_schedule`).
202
+ :class:`~nwp500.models.TOUReservationSchedule` when the device
203
+ confirms a TOU write (triggered by :meth:`configure_tou_schedule`).
204
+ There is no MQTT read to subscribe to; to read the current schedule,
205
+ use the REST :meth:`~nwp500.NavienAPIClient.get_tou_info`.
205
206
 
206
207
  Args:
207
208
  device: Device whose TOU responses to receive.
nwp500/mqtt/control.py CHANGED
@@ -539,38 +539,6 @@ class MqttDeviceController:
539
539
  reservation=reservation_payload,
540
540
  )
541
541
 
542
- async def request_tou_settings(
543
- self,
544
- device: Device,
545
- controller_serial_number: str,
546
- ) -> int:
547
- """
548
- Request current Time-of-Use schedule from the device.
549
-
550
- Args:
551
- device: Device object
552
- controller_serial_number: Controller serial number
553
-
554
- Returns:
555
- Publish packet ID
556
-
557
- Raises:
558
- ValueError: If controller_serial_number is empty
559
- """
560
- if not controller_serial_number:
561
- raise ParameterValidationError(
562
- "controller_serial_number is required",
563
- parameter="controller_serial_number",
564
- )
565
-
566
- return await self._send_command(
567
- device=device,
568
- command_code=CommandCode.TOU_RESERVATION,
569
- topic_suffix="ctrl/tou/rd",
570
- response_topic_suffix="tou/rd",
571
- controllerSerialNumber=controller_serial_number,
572
- )
573
-
574
542
  @requires_capability("program_reservation_use")
575
543
  async def set_tou_enabled(self, device: Device, enabled: bool) -> int:
576
544
  """Toggle Time-of-Use functionality."""
@@ -811,15 +811,16 @@ class MqttSubscriptionManager:
811
811
  device: Device,
812
812
  callback: Callable[[TOUReservationSchedule], None],
813
813
  ) -> int:
814
- """Subscribe to Time-of-Use schedule read responses with automatic
815
- parsing.
814
+ """Subscribe to Time-of-Use schedule write confirmations with
815
+ automatic parsing.
816
816
 
817
817
  Subscribes to the ``tou/rd`` response topic for the given device.
818
818
  The callback receives a fully-parsed
819
- :class:`~nwp500.models.TOUReservationSchedule` whenever the device
820
- responds to a TOU read or configure request (triggered by
821
- :meth:`~nwp500.NavienMqttClient.request_tou_settings` or
822
- :meth:`~nwp500.NavienMqttClient.configure_tou_schedule`).
819
+ :class:`~nwp500.models.TOUReservationSchedule` when the device
820
+ confirms a TOU write (triggered by
821
+ :meth:`~nwp500.NavienMqttClient.configure_tou_schedule`). There is no
822
+ MQTT read to subscribe to; to read the current schedule, use the REST
823
+ :meth:`~nwp500.NavienAPIClient.get_tou_info`.
823
824
 
824
825
  Args:
825
826
  device: Device whose TOU responses to receive.
nwp500/reservations.py CHANGED
@@ -71,10 +71,12 @@ async def fetch_reservations(
71
71
  try:
72
72
  await mqtt.unsubscribe_reservation_response(device, on_schedule)
73
73
  except Exception:
74
+ from .mqtt.utils import redact_mac
75
+
74
76
  _logger.warning(
75
77
  "Failed to unsubscribe reservations response handler for "
76
78
  "device %s",
77
- device.device_info.mac_address,
79
+ redact_mac(device.device_info.mac_address),
78
80
  exc_info=True,
79
81
  )
80
82
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nwp500-python
3
- Version: 9.3.0
3
+ Version: 9.3.2
4
4
  Summary: A library for controlling Navien NWP500 Water Heaters via NaviLink
5
5
  Home-page: https://github.com/eman/nwp500-python
6
6
  Author: Emmanuel Levijarvi
@@ -1,4 +1,4 @@
1
- nwp500/__init__.py,sha256=PPz3xX_e7dxt4oGb1XFAt4M3aCmvIGzMdlYYrJv8Mac,5337
1
+ nwp500/__init__.py,sha256=CmryiTzxVWP8LEcXTzdWM4L99tmZC4B2zxX9r8-vZq0,5427
2
2
  nwp500/_base.py,sha256=Ey-7qGockhwGnrr8RjI3HAxnowRr-2gj_Tt-61dS1YA,2866
3
3
  nwp500/api_client.py,sha256=4O4hV4mNwxXpbDFu-cEN36-edNW2-fGl9akzF4a437E,18342
4
4
  nwp500/auth.py,sha256=_XRmkCiFtxQ7H_xwF7tF1kJW8tce2qhJMVfKK9bgZo4,33783
@@ -16,46 +16,46 @@ nwp500/field_factory.py,sha256=DMQGOLZU5S-sAdT0Ue5nkmqyMAEMlGvCkEg2C8eCkB8,5200
16
16
  nwp500/mqtt_events.py,sha256=2qWDkWEzoMurZw5G6WwzL9nEtlvJ-GUCs_HvMa0024E,10148
17
17
  nwp500/openei.py,sha256=2gM3hEwrUvUEFykDgN-pKkamWomGuQNVT46XzFwOlxA,7071
18
18
  nwp500/py.typed,sha256=vlGtiygNDDmA_Ji2HjDl1SBJEcvpIC6cANFx_GonPMk,69
19
- nwp500/reservations.py,sha256=i-3WQRWeNhjppoINHtj6CmvciVlRTZyDjtrGk4_wnd8,12493
19
+ nwp500/reservations.py,sha256=0i8QJ5SD8oaXX9VjLAGtx5TNbxXoTVh67lVmGneqpgU,12553
20
20
  nwp500/temperature.py,sha256=O9pyjFaMstzfKxWtQTUDNP12YOJbS9rSwBH76s4p4MY,7982
21
21
  nwp500/topic_builder.py,sha256=ZOQbH89K_oN6Wz0Fia4UDNGNUn6wMFNEVS3k39ZdNaM,2482
22
22
  nwp500/tou_schedule.py,sha256=sPwceU3sXbEqJCrFMsNZsZRg4DE4TsROkgVN-qXzlk0,3675
23
23
  nwp500/unit_system.py,sha256=_UYp7KR4qPNROx2upYovaoR-MU5Blllq5iknOigNTfQ,4456
24
24
  nwp500/utils.py,sha256=rEKvGnUsvea_3owEhbgzA0-uy96WwOOIlxaLUEfz20I,2110
25
25
  nwp500/cli/__init__.py,sha256=hWvrWPWcjpgHqPnPjEauX8KzXqXzf415wBHWLTpbFwI,1530
26
- nwp500/cli/__main__.py,sha256=73zFGSkaIVra58T0dqU92mUkT1LaRWM2sIuiIDtNtpg,22721
27
- nwp500/cli/handlers.py,sha256=nAH1-L3T96IBzoCN1KyCurtPYSFYsvH27IGWLMv4LQc,31112
26
+ nwp500/cli/__main__.py,sha256=m6nv8Pks5-FmOV4rDWtuhC7TH9FjW0qcMf6SDy15Eis,25063
27
+ nwp500/cli/handlers.py,sha256=sXtmMeOMZ7tlVd7H3X14QanPtTohS639WqOm4UIip2o,31164
28
28
  nwp500/cli/monitoring.py,sha256=MAMeV6KCHPTfBUQn_BY2PWK5kuvX3t1WN4lEs6tw46o,1490
29
29
  nwp500/cli/output_formatters.py,sha256=V2Dgat4Augbkm7AOYUxZPTfeoTAIbkCM25gxNXb7ebs,5179
30
30
  nwp500/cli/presentation.py,sha256=VmzB4h-wJUGke8sJIoyMw3VJKBo0SD9w4gpwKnELe0Q,27863
31
31
  nwp500/cli/rich_output.py,sha256=Qy9doaDPnX4IP-Oahu2gfuYfsfpB0WYtdeLfVmMQmdc,22722
32
32
  nwp500/cli/token_storage.py,sha256=zfFGah0l5owWYIk2uA2OTmgCu1nLdhYFYcVA5JEkwCE,2077
33
- nwp500/models/__init__.py,sha256=S6txdvXWhmvpcz5QKluUNzPcMPK0owdr_xB5SV5V0Uc,2127
33
+ nwp500/models/__init__.py,sha256=VAPDE7u3f0lmhL2CnBxbLqf-1EbHSiqEdTkb-JSZwSM,2217
34
34
  nwp500/models/_converters.py,sha256=lNQPigKfsVPE5RvX9-s0_UA3j35tF3hQidcpRNIWwyY,2427
35
- nwp500/models/device.py,sha256=938N3e2nukTsoVeo8tGoZiq79hDbu3THDOLQEitBxpk,1587
35
+ nwp500/models/device.py,sha256=zkq1S54UTd3rbh6zd1BC7qjc0BMXAb7VDidXZmvw9-0,3588
36
36
  nwp500/models/energy.py,sha256=y0eIADWhrC3YWRRBfbD3NNWsFvbLhY8GB5HuqGtlJ2s,2017
37
- nwp500/models/feature.py,sha256=i_8Y0LZ4Czv2Tyi-18iASaCRX4DjPa0UWSZTzJtd6mI,15091
37
+ nwp500/models/feature.py,sha256=7z1pDoW0LQGxv8__AlQn5M35lZRKgeybgwfnw2OCKIM,15433
38
38
  nwp500/models/mqtt_models.py,sha256=1Etfn0lqiWvoUsW2niT6wXdZtNvN0sGjn7F1uPJQ9yU,774
39
39
  nwp500/models/schedule.py,sha256=uI8k-Urwd9XZ9KNj_VVFGAHg3pXZRXgDGb9psrFHFSw,13056
40
40
  nwp500/models/status.py,sha256=FBJpEiw9y7cg-JRW3x5GHO3biztnXLoteujka3WFM7I,36892
41
- nwp500/models/tou.py,sha256=ywHAYcDLIyNmpYwurYxTftU6cRSZ84mHqUIpy7vjUuc,6892
41
+ nwp500/models/tou.py,sha256=wwxhYwCSY4JIWgVDa2SlwUzlCEDqqRBN994NrkqdVg8,7018
42
42
  nwp500/mqtt/__init__.py,sha256=WYdBfpW-3tSLdFMObDWCX4pcLm_IqRBDluJqkPnQBN4,974
43
- nwp500/mqtt/_control_commands.py,sha256=L6ZlnrQivFwj-HbNq1pNUVFXwtvbE8WBiKGQQKSXOTA,8496
44
- nwp500/mqtt/_device_subscriptions.py,sha256=MVu31ImuFn2Mfw1nfNNQz9fuzz2ktBjk36s0YRaxNYs,8115
43
+ nwp500/mqtt/_control_commands.py,sha256=fX49Ym2x4dTTj4cmaHqEnuaDNuVs7zXWPT_SOCZGvU0,8199
44
+ nwp500/mqtt/_device_subscriptions.py,sha256=WpJxaGyssDXdlXU2YTVy9LkVEJ3wg-vnQdhZqExX_YE,8197
45
45
  nwp500/mqtt/client.py,sha256=IoTCbBKPpxmxLCJkRZvH6k55HmfBjoXW8W6QS452yXM,45770
46
46
  nwp500/mqtt/command_queue.py,sha256=wiNxNzQe6psMqa0PQ3wzatf9N6IDrtDwpl4DPq1kDdk,6517
47
47
  nwp500/mqtt/connection.py,sha256=fNrOtBJxsqk054YDUmipnwI3yfBHx1anXb_07e0X9XU,17712
48
- nwp500/mqtt/control.py,sha256=47D3N1x_YXL8CgHn-M7JOSoYCcd3iEe9C5MnQu3JEU0,31041
48
+ nwp500/mqtt/control.py,sha256=KWFlmDGn3GtpX4dimPnrXglsD6YTggXqzPigkBVMh_E,30102
49
49
  nwp500/mqtt/diagnostics.py,sha256=NDkPExS4FObGCWJXAhENqNQUm8Bda5GDBR7_o3ylSAo,15723
50
50
  nwp500/mqtt/periodic.py,sha256=WKyyO61yTZJfOm9JAtBp6Onyk-XNj0U544sYMCKvTZw,14729
51
51
  nwp500/mqtt/reconnection.py,sha256=5SEj1vV5_QWXlApnKnUeFWAT-MgWnnu6M_vhiqTQh14,16129
52
52
  nwp500/mqtt/state_tracker.py,sha256=uZlDFNYn7DOXmXPbk-8jXva_QKEvLES9T_BYbQSkhvM,6364
53
- nwp500/mqtt/subscriptions.py,sha256=o6xLayQzPyjziPVN_xwYmqw05OKJIjMT4eK_LQlbc0g,31921
53
+ nwp500/mqtt/subscriptions.py,sha256=5z5EWJKmgfGkT0gFcw8Wxqki9v-bTbshVc9jnmviwHY,31978
54
54
  nwp500/mqtt/types.py,sha256=86R7gwsc1XabA_ABWeALeC5EImlTziFFn1DrP1x8Yy4,1605
55
55
  nwp500/mqtt/utils.py,sha256=LAp678M8hzqhlgWBXt3Re59TbnMkX9l1fa57WtE2yug,13000
56
- nwp500_python-9.3.0.dist-info/licenses/LICENSE.txt,sha256=EUOsypJ3iEqJ-9tdo_TSZHwB0Rd-7p0JzToEI5CMBJU,1085
57
- nwp500_python-9.3.0.dist-info/METADATA,sha256=3kIiYJiZQHLoNSXBZg1MuKCj6Cg0WCqKwfbQB6P5V4I,5091
58
- nwp500_python-9.3.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
59
- nwp500_python-9.3.0.dist-info/entry_points.txt,sha256=p8vGDEbUIOT_TzDG-XnJ3lw-9yaeHrDPNN7fAx4-78I,52
60
- nwp500_python-9.3.0.dist-info/top_level.txt,sha256=DRPIenxKnssWeaetq428gYfwrKjLGN1z2NOtztIvxZE,7
61
- nwp500_python-9.3.0.dist-info/RECORD,,
56
+ nwp500_python-9.3.2.dist-info/licenses/LICENSE.txt,sha256=EUOsypJ3iEqJ-9tdo_TSZHwB0Rd-7p0JzToEI5CMBJU,1085
57
+ nwp500_python-9.3.2.dist-info/METADATA,sha256=DJnyePJsNABe4Du8W8ngv9Q5IlUbeyA6RPNr5OaQ3iI,5091
58
+ nwp500_python-9.3.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
59
+ nwp500_python-9.3.2.dist-info/entry_points.txt,sha256=p8vGDEbUIOT_TzDG-XnJ3lw-9yaeHrDPNN7fAx4-78I,52
60
+ nwp500_python-9.3.2.dist-info/top_level.txt,sha256=DRPIenxKnssWeaetq428gYfwrKjLGN1z2NOtztIvxZE,7
61
+ nwp500_python-9.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (83.0.0)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5