PyPlumIO 0.4.0__py3-none-any.whl → 0.4.1__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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPlumIO
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
5
  Author-email: Denis Paavilainen <denpa@denpa.pro>
6
6
  License: MIT License
7
- Project-URL: Documentation, https://pypulmio.denpa.pro
7
+ Project-URL: Documentation, https://pyplumio.denpa.pro
8
8
  Project-URL: Source Code, https://github.com/denpamusic/PyPlumIO
9
9
  Project-URL: Bug Tracker, https://github.com/denpamusic/PyPlumIO/issues
10
10
  Keywords: home,automation,heating
@@ -1,15 +1,15 @@
1
1
  pyplumio/__init__.py,sha256=HZuhLA98rflVEU5cc2-UpboDSJUW1e6Eocfdok9MHZM,1273
2
2
  pyplumio/__main__.py,sha256=j8fh3P6yGwB1qumdUlW8X22-DOwisdo4IcUEmlYPGbM,497
3
- pyplumio/_version.py,sha256=QiCQVGOicQZY3DBr4tYPU9egzT1VgzOJGfLt9Q26uyU,160
3
+ pyplumio/_version.py,sha256=0_dB-SwRXzCHowA3kD_9MmecjmUqQiIbDwJmtaXjluI,160
4
4
  pyplumio/connection.py,sha256=ABeJFbnqTq89i5J1TRR2sdQeRrq1Pewdi_TaDjzSOOI,5494
5
- pyplumio/const.py,sha256=WfV68bd99IWsKHQ2bWW9Rl8xHvgfWrt8cyb2kjs5od4,2965
5
+ pyplumio/const.py,sha256=XGuoUVPnKSeguNk01mtTXNbAzUBQITRaoAumBoaIyD4,3007
6
6
  pyplumio/exceptions.py,sha256=cpLGRl9db6cU1Z6WXhQ7yl1V7Te193QymWiOoRF8KAI,798
7
7
  pyplumio/filters.py,sha256=X_Q_DE9M_yyw2OWyr_-IargtAWpW9CAI3jIhoIXKhaA,5648
8
8
  pyplumio/protocol.py,sha256=h90uhCcqscukJ0GVX6rYv_LH5UsE2om87ZXo3EWZ77s,6223
9
9
  pyplumio/stream.py,sha256=n1dae3oKnnU0XWsVgzVszXPdVXkYChdw-bUTKcHr05s,3738
10
10
  pyplumio/util.py,sha256=oKKO9eJmrmXf0XHZG5P4fGR_R1jMSU_BAXU82ZnDS8Y,2603
11
- pyplumio/devices/__init__.py,sha256=R1q2YPhpJk0C-6kEP8sDv5ywxdgGJqLRSCdrhv2MzWQ,4904
12
- pyplumio/devices/ecomax.py,sha256=bR5An1qTTLeJhSb2Vq07dHYAhH9OpoX2754LThDKOXk,14693
11
+ pyplumio/devices/__init__.py,sha256=DIT3wV0BDU8y1zaM5j9EAhNd5MbTsuCElLZBvZ-3GH8,4871
12
+ pyplumio/devices/ecomax.py,sha256=L74fUjFE3q4lsu4CccsMxCFenljmY0sF1D1CYLoRBDc,15223
13
13
  pyplumio/devices/ecoster.py,sha256=IqyyLDqasDSK9taFGlxDoX8Dl8fYOKDL00RB4zX-tZs,314
14
14
  pyplumio/devices/mixer.py,sha256=iODTCYHt29XNCam0hnGMdP8VN1uS24b489KD5-9M6-M,1919
15
15
  pyplumio/devices/thermostat.py,sha256=J8Iq5jfRmvoFQH2nU-mXNJeGMekp4lqJhtdS2qTVKRM,1701
@@ -30,14 +30,14 @@ pyplumio/helpers/uid.py,sha256=Gt1l9gs-RMummmpfu5IoRokGurxRzJeWZrfwiyeGR9c,1044
30
30
  pyplumio/structures/__init__.py,sha256=imqgigigBkwDpHKLwiLTrAh0vqaYcvnm8Y5676uT07Y,1482
31
31
  pyplumio/structures/alerts.py,sha256=_AIsA_Uvp2xm8JiQ7a3jt7VuZWcFiNHYtZTOHBAFIkQ,2644
32
32
  pyplumio/structures/data_schema.py,sha256=wOWEtoqjtn2qq-Gxb-HParxTnmAjsOjIBxp211Qn66I,1174
33
- pyplumio/structures/ecomax_parameters.py,sha256=slqV3vntCb6SvKnHb47azuUlMbChD-E1owNzvqNXpxc,15870
33
+ pyplumio/structures/ecomax_parameters.py,sha256=8y1ZFN9NKoiKt_zxW-_m6QBsWssQ2A0uK6oQv6E4LcA,16082
34
34
  pyplumio/structures/fan_power.py,sha256=nlurG1-sPyUQbasuxZChwDW4ml3Mp_Jc5RkUpv5NDzg,859
35
35
  pyplumio/structures/frame_versions.py,sha256=LqqlC7CVyRDfBMw1Y2Tt0ZaDdlj1K3nlWbb3C3LwzD4,1183
36
36
  pyplumio/structures/fuel_consumption.py,sha256=LxQf0Vg7-XnikD0otXvWep6nIP8mK3j_P4cB2xjWwSk,961
37
37
  pyplumio/structures/fuel_level.py,sha256=_u6WMLpnIl0W3jA1QKYG708s5PdaaIM35yeEZZ8Srik,800
38
38
  pyplumio/structures/lambda_sensor.py,sha256=gIsonwMOtKXhp_v55yKxUm1XNeUGvRkHUI0JwDSOAsY,1315
39
39
  pyplumio/structures/load.py,sha256=wltZnHmEE4yk9DzYpwPRs4a8Z8YS7IhBqG81Kpe5c-s,772
40
- pyplumio/structures/mixer_parameters.py,sha256=W2cG3QpJ1lp3z_LYfx1LBPq2puGR6sWBFMJcjRhrfxo,6417
40
+ pyplumio/structures/mixer_parameters.py,sha256=gShc2ST0IQcfHFhmKVgkz8s1DUdqgVBwIZl_PSXJry4,6115
41
41
  pyplumio/structures/mixer_sensors.py,sha256=4AEFPlV-seESz4DDNn6kUl5OJWCaXRejEAtSX2_JdvU,1729
42
42
  pyplumio/structures/modules.py,sha256=jFO723D-40Ac1rqchDj7ULWARx-nnx_3i9Tjnn_Llxo,2225
43
43
  pyplumio/structures/network_info.py,sha256=-Pz9nuAsTA2vcwNaBkgkvsTfavpikctYiUmrMa6EjCc,3469
@@ -51,10 +51,10 @@ pyplumio/structures/regulator_data.py,sha256=GOfnFcoLN-3vhSIg4GLRDh_5uEWZHYk9E5Y
51
51
  pyplumio/structures/schedules.py,sha256=YFDHYaiygc60mQm9EzrXXnMic9EKQsi-CwDM1jBECUQ,6135
52
52
  pyplumio/structures/statuses.py,sha256=BeSOabFIewiUBxAXgsuxzjRi4Zl-_O2qDbn61w7DtUU,1036
53
53
  pyplumio/structures/temperatures.py,sha256=lPRYCCciaGTBU_ShWwz_bWHvsfIRL3n16CnTbZK9iC4,2298
54
- pyplumio/structures/thermostat_parameters.py,sha256=zQERSx_Ut6V3oFtl_dDxqyDpNu02LhrBhMrgqPHLG_c,6664
54
+ pyplumio/structures/thermostat_parameters.py,sha256=M-VpUb7z-0rsES-PqUKpdmWP8TcV9IACkS6yPx4ivqo,6399
55
55
  pyplumio/structures/thermostat_sensors.py,sha256=yU5F2KXnpOMyHSdAfoIY6JmEIlSYMf05qmyvN0F4A5c,2236
56
- PyPlumIO-0.4.0.dist-info/LICENSE,sha256=g56wJgobsehVtkJD8MhM6isAnsdOUmRPv7fIaXI4Joo,1067
57
- PyPlumIO-0.4.0.dist-info/METADATA,sha256=BgWvmFmfiZc8_uo0U74HrcQ0NtH24cZOLKcxOkoIH88,4792
58
- PyPlumIO-0.4.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
59
- PyPlumIO-0.4.0.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
60
- PyPlumIO-0.4.0.dist-info/RECORD,,
56
+ PyPlumIO-0.4.1.dist-info/LICENSE,sha256=g56wJgobsehVtkJD8MhM6isAnsdOUmRPv7fIaXI4Joo,1067
57
+ PyPlumIO-0.4.1.dist-info/METADATA,sha256=aUm-xRNBU8Yc7KumW9AmOLWd07CD6jMoIFZYvRC-3YA,4792
58
+ PyPlumIO-0.4.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
59
+ PyPlumIO-0.4.1.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
60
+ PyPlumIO-0.4.1.dist-info/RECORD,,
pyplumio/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '0.4.0'
4
- __version_tuple__ = version_tuple = (0, 4, 0)
3
+ __version__ = version = '0.4.1'
4
+ __version_tuple__ = version_tuple = (0, 4, 1)
pyplumio/const.py CHANGED
@@ -12,6 +12,7 @@ STATE_OFF: Final = "off"
12
12
  ATTR_CONNECTED: Final = "connected"
13
13
  ATTR_CURRENT_TEMP: Final = "current_temp"
14
14
  ATTR_DEVICE_INDEX: Final = "device_index"
15
+ ATTR_FRAME_ERRORS: Final = "frame_errors"
15
16
  ATTR_INDEX: Final = "index"
16
17
  ATTR_LOADED: Final = "loaded"
17
18
  ATTR_OFFSET: Final = "offset"
@@ -6,7 +6,7 @@ import logging
6
6
  from typing import ClassVar
7
7
 
8
8
  from pyplumio import util
9
- from pyplumio.const import ATTR_LOADED, DeviceType, FrameType
9
+ from pyplumio.const import ATTR_FRAME_ERRORS, ATTR_LOADED, DeviceType, FrameType
10
10
  from pyplumio.exceptions import ParameterNotFoundError, UnknownDeviceError
11
11
  from pyplumio.frames import DataFrameDescription, Frame, Request, get_frame_handler
12
12
  from pyplumio.helpers.event_manager import EventManager
@@ -99,22 +99,23 @@ class Addressable(Device):
99
99
 
100
100
  async def async_setup(self) -> bool:
101
101
  """Setup addressable device object."""
102
- try:
103
- await asyncio.gather(
104
- *{
105
- self.create_task(
106
- self.request(description.provides, description.frame_type)
107
- )
108
- for description in self._frame_types
109
- },
110
- return_exceptions=False,
111
- )
112
- await self.dispatch(ATTR_LOADED, True)
113
- return True
114
- except ValueError as e:
115
- _LOGGER.error("Request failed: %s", e)
116
- await self.dispatch(ATTR_LOADED, False)
117
- return False
102
+ results = await asyncio.gather(
103
+ *{
104
+ self.create_task(
105
+ self.request(description.provides, description.frame_type)
106
+ )
107
+ for description in self._frame_types
108
+ },
109
+ return_exceptions=True,
110
+ )
111
+
112
+ errors = [
113
+ result.args[1] for result in results if isinstance(result, ValueError)
114
+ ]
115
+
116
+ await self.dispatch(ATTR_FRAME_ERRORS, errors)
117
+ await self.dispatch(ATTR_LOADED, True)
118
+ return True
118
119
 
119
120
  async def request(
120
121
  self,
@@ -136,7 +137,7 @@ class Addressable(Device):
136
137
  except asyncio.TimeoutError:
137
138
  retries -= 1
138
139
 
139
- raise ValueError(f'could not request "{name}" with "{frame_type.name}"')
140
+ raise ValueError(f'could not request "{name}"', frame_type)
140
141
 
141
142
 
142
143
  class SubDevice(Device):
@@ -8,6 +8,7 @@ import time
8
8
  from typing import ClassVar, Final
9
9
 
10
10
  from pyplumio.const import (
11
+ ATTR_FRAME_ERRORS,
11
12
  ATTR_PASSWORD,
12
13
  ATTR_SENSORS,
13
14
  ATTR_STATE,
@@ -140,12 +141,24 @@ class EcoMAX(Addressable):
140
141
 
141
142
  super().handle_frame(frame)
142
143
 
144
+ def _has_frame_version(self, frame_type: FrameType | int, version: int) -> bool:
145
+ """Check if device instance has a version of the frame."""
146
+ return (
147
+ frame_type in self._frame_versions
148
+ and self._frame_versions[frame_type] == version
149
+ )
150
+
151
+ def _frame_is_supported(self, frame_type: FrameType | int) -> bool:
152
+ """Check if frame is supported by the device."""
153
+ return frame_type not in self.data.get(ATTR_FRAME_ERRORS, [])
154
+
143
155
  async def _update_frame_versions(self, versions: dict[int, int]) -> None:
144
156
  """Check versions and fetch outdated frames."""
145
157
  for frame_type, version in versions.items():
146
- if is_known_frame_type(frame_type) and (
147
- frame_type not in self._frame_versions
148
- or self._frame_versions[frame_type] != version
158
+ if (
159
+ is_known_frame_type(frame_type)
160
+ and self._frame_is_supported(frame_type)
161
+ and not self._has_frame_version(frame_type, version)
149
162
  ):
150
163
  # We don't have this frame or it's version has changed.
151
164
  request = factory(get_frame_handler(frame_type), recipient=self.address)
@@ -10,13 +10,15 @@ from pyplumio.devices import Addressable
10
10
  from pyplumio.frames import Request
11
11
  from pyplumio.helpers.factory import factory
12
12
  from pyplumio.helpers.parameter import BinaryParameter, Parameter, ParameterDescription
13
- from pyplumio.helpers.typing import EventDataType, ParameterDataType, ParameterValueType
13
+ from pyplumio.helpers.typing import EventDataType, ParameterValueType
14
14
  from pyplumio.structures import StructureDecoder, ensure_device_data
15
15
  from pyplumio.structures.thermostat_parameters import ATTR_THERMOSTAT_PROFILE
16
16
 
17
17
  ATTR_ECOMAX_CONTROL: Final = "ecomax_control"
18
18
  ATTR_ECOMAX_PARAMETERS: Final = "ecomax_parameters"
19
19
 
20
+ ECOMAX_PARAMETER_SIZE: Final = 3
21
+
20
22
 
21
23
  class EcomaxParameter(Parameter):
22
24
  """Represents ecoMAX parameter."""
@@ -301,22 +303,33 @@ THERMOSTAT_PROFILE_PARAMETER = EcomaxParameterDescription(name=ATTR_THERMOSTAT_P
301
303
  class EcomaxParametersStructure(StructureDecoder):
302
304
  """Represents ecoMAX parameters data structure."""
303
305
 
306
+ _offset: int
307
+
308
+ def _ecomax_parameter(self, message: bytearray, start: int, end: int):
309
+ """Yields ecoMAX parameters."""
310
+ for index in range(start, start + end):
311
+ if parameter := util.unpack_parameter(message, self._offset):
312
+ yield (index, parameter)
313
+
314
+ self._offset += ECOMAX_PARAMETER_SIZE
315
+
304
316
  def decode(
305
317
  self, message: bytearray, offset: int = 0, data: EventDataType | None = None
306
318
  ) -> tuple[EventDataType, int]:
307
319
  """Decode bytes and return message data and offset."""
308
- first_index = message[offset + 1]
309
- last_index = message[offset + 2]
310
- offset += 3
311
- ecomax_parameters: list[tuple[int, ParameterDataType]] = []
312
- for index in range(first_index, first_index + last_index):
313
- parameter = util.unpack_parameter(message, offset)
314
- if parameter is not None:
315
- ecomax_parameters.append((index, parameter))
316
320
 
317
- offset += 3
321
+ start = message[offset + 1]
322
+ end = message[offset + 2]
323
+ self._offset = offset + 3
318
324
 
319
325
  return (
320
- ensure_device_data(data, {ATTR_ECOMAX_PARAMETERS: ecomax_parameters}),
321
- offset,
326
+ ensure_device_data(
327
+ data,
328
+ {
329
+ ATTR_ECOMAX_PARAMETERS: list(
330
+ self._ecomax_parameter(message, start, end)
331
+ )
332
+ },
333
+ ),
334
+ self._offset,
322
335
  )
@@ -1,7 +1,6 @@
1
1
  """Contains mixer parameter structure decoder."""
2
2
  from __future__ import annotations
3
3
 
4
- from collections.abc import Iterable
5
4
  from dataclasses import dataclass
6
5
  from typing import TYPE_CHECKING, Final
7
6
 
@@ -18,6 +17,8 @@ if TYPE_CHECKING:
18
17
 
19
18
  ATTR_MIXER_PARAMETERS: Final = "mixer_parameters"
20
19
 
20
+ MIXER_PARAMETER_SIZE: Final = 3
21
+
21
22
 
22
23
  class MixerParameter(Parameter):
23
24
  """Represents mixer parameter."""
@@ -117,48 +118,41 @@ ECOMAX_I_MIXER_PARAMETERS: tuple[MixerParameterDescription, ...] = (
117
118
  )
118
119
 
119
120
 
120
- def _decode_mixer_parameters(
121
- message: bytearray, offset: int, indexes: Iterable
122
- ) -> tuple[list[tuple[int, ParameterDataType]], int]:
123
- """Decode parameters for a single mixer."""
124
- parameters: list[tuple[int, ParameterDataType]] = []
125
- for index in indexes:
126
- parameter = util.unpack_parameter(message, offset)
127
- if parameter is not None:
128
- parameters.append((index, parameter))
121
+ class MixerParametersStructure(StructureDecoder):
122
+ """Represent mixer parameters data structure."""
129
123
 
130
- offset += 3
124
+ _offset: int
131
125
 
132
- return parameters, offset
126
+ def _mixer_parameter(self, message: bytearray, start: int, end: int):
127
+ """Yields mixer parameters."""
128
+ for index in range(start, start + end):
129
+ if (parameter := util.unpack_parameter(message, self._offset)) is not None:
130
+ yield (index, parameter)
133
131
 
134
-
135
- class MixerParametersStructure(StructureDecoder):
136
- """Represent mixer parameters data structure."""
132
+ self._offset += MIXER_PARAMETER_SIZE
137
133
 
138
134
  def decode(
139
135
  self, message: bytearray, offset: int = 0, data: EventDataType | None = None
140
136
  ) -> tuple[EventDataType, int]:
141
137
  """Decode bytes and return message data and offset."""
142
- first_index = message[offset + 1]
143
- last_index = message[offset + 2]
144
- mixer_count = message[offset + 3]
145
- parameter_count_per_mixer = first_index + last_index
146
- offset += 4
138
+ start = message[offset + 1]
139
+ end = message[offset + 2]
140
+ mixers = message[offset + 3]
141
+ self._offset = offset + 4
142
+
147
143
  mixer_parameters: dict[int, list[tuple[int, ParameterDataType]]] = {}
148
- for index in range(mixer_count):
149
- parameters, offset = _decode_mixer_parameters(
150
- message,
151
- offset,
152
- range(first_index, parameter_count_per_mixer),
153
- )
154
- if parameters:
155
- mixer_parameters[index] = parameters
156
-
157
- if not mixer_parameters:
158
- # No mixer parameters detected.
159
- return ensure_device_data(data, {ATTR_MIXER_PARAMETERS: None}), offset
144
+ for mixer in range(mixers):
145
+ if parameters := list(self._mixer_parameter(message, start, end)):
146
+ mixer_parameters[mixer] = parameters
160
147
 
161
148
  return (
162
- ensure_device_data(data, {ATTR_MIXER_PARAMETERS: mixer_parameters}),
163
- offset,
149
+ ensure_device_data(
150
+ data,
151
+ {
152
+ ATTR_MIXER_PARAMETERS: (
153
+ None if not mixer_parameters else mixer_parameters
154
+ )
155
+ },
156
+ ),
157
+ self._offset,
164
158
  )
@@ -1,7 +1,6 @@
1
1
  """Contains thermostat parameter structure decoder."""
2
2
  from __future__ import annotations
3
3
 
4
- from collections.abc import Iterable
5
4
  from dataclasses import dataclass
6
5
  from typing import TYPE_CHECKING, Final
7
6
 
@@ -14,12 +13,15 @@ from pyplumio.helpers.typing import EventDataType, ParameterDataType, ParameterV
14
13
  from pyplumio.structures import StructureDecoder, ensure_device_data
15
14
  from pyplumio.structures.thermostat_sensors import ATTR_THERMOSTAT_COUNT
16
15
 
16
+ if TYPE_CHECKING:
17
+ from pyplumio.devices.thermostat import Thermostat
18
+
19
+
17
20
  ATTR_THERMOSTAT_PROFILE: Final = "thermostat_profile"
18
21
  ATTR_THERMOSTAT_PARAMETERS: Final = "thermostat_parameters"
19
22
  ATTR_THERMOSTAT_PARAMETERS_DECODER: Final = "thermostat_parameters_decoder"
20
23
 
21
- if TYPE_CHECKING:
22
- from pyplumio.devices.thermostat import Thermostat
24
+ THERMOSTAT_PARAMETER_SIZE: Final = 3
23
25
 
24
26
 
25
27
  class ThermostatParameter(Parameter):
@@ -107,72 +109,68 @@ THERMOSTAT_PARAMETERS: tuple[ThermostatParameterDescription, ...] = (
107
109
  )
108
110
 
109
111
 
110
- def _decode_thermostat_parameters(
111
- message: bytearray, offset: int, indexes: Iterable
112
- ) -> tuple[list[tuple[int, ParameterDataType]], int]:
113
- """Decode parameters for a single thermostat."""
114
- parameters: list[tuple[int, ParameterDataType]] = []
115
- for index in indexes:
116
- description = THERMOSTAT_PARAMETERS[index]
117
- parameter = util.unpack_parameter(message, offset, size=description.size)
118
- if parameter is not None:
119
- parameters.append((index, parameter))
120
-
121
- offset += 3 * description.size
122
-
123
- return parameters, offset
112
+ def _empty_response(
113
+ offset: int, data: EventDataType | None = None
114
+ ) -> tuple[EventDataType, int]:
115
+ """Return empty response."""
116
+ return (
117
+ ensure_device_data(
118
+ data,
119
+ {ATTR_THERMOSTAT_PARAMETERS: None, ATTR_THERMOSTAT_PROFILE: None},
120
+ ),
121
+ offset,
122
+ )
124
123
 
125
124
 
126
125
  class ThermostatParametersStructure(StructureDecoder):
127
126
  """Represent thermostat parameters data structure."""
128
127
 
128
+ _offset: int
129
+
130
+ def _thermostat_parameter(
131
+ self, message: bytearray, thermostats: int, start: int, end: int
132
+ ):
133
+ """Yields thermostat parameters."""
134
+ for index in range(start, (start + end) // thermostats):
135
+ description = THERMOSTAT_PARAMETERS[index]
136
+ if (
137
+ parameter := util.unpack_parameter(
138
+ message, self._offset, size=description.size
139
+ )
140
+ ) is not None:
141
+ yield (index, parameter)
142
+
143
+ self._offset += THERMOSTAT_PARAMETER_SIZE * description.size
144
+
129
145
  def decode(
130
146
  self, message: bytearray, offset: int = 0, data: EventDataType | None = None
131
147
  ) -> tuple[EventDataType, int]:
132
148
  """Decode bytes and return message data and offset."""
133
149
  data = ensure_device_data(data)
134
- thermostat_count = data.get(ATTR_THERMOSTAT_COUNT, 0)
135
- if thermostat_count == 0:
136
- return (
137
- ensure_device_data(
138
- data,
139
- {ATTR_THERMOSTAT_PARAMETERS: None, ATTR_THERMOSTAT_PROFILE: None},
140
- ),
141
- offset,
142
- )
143
-
144
- first_index = message[offset + 1]
145
- last_index = message[offset + 2]
150
+ thermostats = data.get(ATTR_THERMOSTAT_COUNT, 0)
151
+ if thermostats == 0:
152
+ return _empty_response(offset, data)
153
+
154
+ start = message[offset + 1]
155
+ end = message[offset + 2]
146
156
  thermostat_profile = util.unpack_parameter(message, offset + 3)
147
- parameter_count_per_thermostat = (first_index + last_index) // thermostat_count
148
- offset += 6
157
+ self._offset = offset + 6
149
158
  thermostat_parameters: dict[int, list[tuple[int, ParameterDataType]]] = {}
150
- for index in range(thermostat_count):
151
- parameters, offset = _decode_thermostat_parameters(
152
- message,
153
- offset,
154
- range(first_index, parameter_count_per_thermostat),
155
- )
156
- if parameters:
157
- thermostat_parameters[index] = parameters
158
-
159
- if not thermostat_parameters:
160
- # No thermostat parameters detected.
161
- return (
162
- ensure_device_data(
163
- data,
164
- {ATTR_THERMOSTAT_PARAMETERS: None, ATTR_THERMOSTAT_PROFILE: None},
165
- ),
166
- offset,
167
- )
159
+ for thermostat in range(thermostats):
160
+ if parameters := list(
161
+ self._thermostat_parameter(message, thermostats, start, end)
162
+ ):
163
+ thermostat_parameters[thermostat] = parameters
168
164
 
169
165
  return (
170
166
  ensure_device_data(
171
167
  data,
172
168
  {
173
169
  ATTR_THERMOSTAT_PROFILE: thermostat_profile,
174
- ATTR_THERMOSTAT_PARAMETERS: thermostat_parameters,
170
+ ATTR_THERMOSTAT_PARAMETERS: None
171
+ if not thermostat_parameters
172
+ else thermostat_parameters,
175
173
  },
176
174
  ),
177
- offset,
175
+ self._offset,
178
176
  )