PyPlumIO 0.5.32__py3-none-any.whl → 0.5.34__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,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyPlumIO
3
- Version: 0.5.32
3
+ Version: 0.5.34
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
@@ -1,16 +1,16 @@
1
1
  pyplumio/__init__.py,sha256=ditJTIOFGJDg60atHzOpiggdUrZHpSynno7MtpZUGVk,3299
2
2
  pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
3
- pyplumio/_version.py,sha256=G1xfKG6efA0TsiygMdo8hJeE0DmA_V8-y12DGNZw7do,413
3
+ pyplumio/_version.py,sha256=NdVLEG4QoQ2gCknxsIGvrBsx6YVY3lCufzs3YmFEJws,413
4
4
  pyplumio/connection.py,sha256=6mUbcjGxxEhMVIbzZgCqH-Ez-fcYoRj7ZbVSzpikpNA,5949
5
5
  pyplumio/const.py,sha256=LyXa5aVy2KxnZq7H7F8s5SYsAgEC2UzZYMMRauliB2E,5502
6
6
  pyplumio/exceptions.py,sha256=Wn-y5AJ5xfaBlHhTUVKB27_0Us8_OVHqh-sicnr9sYA,700
7
7
  pyplumio/filters.py,sha256=AMW1zHQ1YjJfHX7e87Dhv7AGixJ3y9Vn-_JAQn7vIsg,12526
8
8
  pyplumio/protocol.py,sha256=VRxrj8vZ1FMawqblKkyxg_V61TBSvVynd9u0JXYnMUU,8090
9
9
  pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- pyplumio/stream.py,sha256=mtMpnUR3TfEmL5JUGXr6GnpPGBwzCokqIKDWp4vYiVg,4654
10
+ pyplumio/stream.py,sha256=Ne-mWkO6FpILAjGdagbAh_VL3QEla-eDiT2N-kOc5o4,4883
11
11
  pyplumio/utils.py,sha256=TnBzRopinyp92wruguijxcIYmaeyNVTFX0dygI5FCMU,823
12
- pyplumio/devices/__init__.py,sha256=BEUpL2XxEk4YDfp4w0VX_LI4JQwK27eGWoJ6al_e1nE,8038
13
- pyplumio/devices/ecomax.py,sha256=ybFLJN7O3unBcyzuVmYTssBv86bPiiTGvFpFJezwUE4,15478
12
+ pyplumio/devices/__init__.py,sha256=aOn9G7uo-CXM8BPqfm1GjmBLmWuekw-qyiEXjYG30Ps,8083
13
+ pyplumio/devices/ecomax.py,sha256=LWDIlnC2lIOF2L5obLwpHKY0KTxp81TbrMGAzk7GlSw,15528
14
14
  pyplumio/devices/ecoster.py,sha256=jNWli7ye9T6yfkcFJZhhUHH7KOv-L6AgYFp_dKyv3OM,263
15
15
  pyplumio/devices/mixer.py,sha256=CnHWrJELtFgs2YTHGpQwKr2UTRdetX76OvLBA2PH-fs,3207
16
16
  pyplumio/devices/thermostat.py,sha256=-CZNRyywoDU6csFu85KSmQ5woVXY0x6peXkeOsi_fqg,2617
@@ -21,9 +21,9 @@ pyplumio/frames/responses.py,sha256=Ch1AVBmD6Ek7BazoEMDDEa6ad_fUdUXf4bNssQOu0sI,
21
21
  pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,31
22
22
  pyplumio/helpers/data_types.py,sha256=nB3afOLmppgSCWkZoX1-1yWPNMMNSem77x7XQ1Mi8H8,9103
23
23
  pyplumio/helpers/event_manager.py,sha256=xQOfiP_nP1Pz5zhB6HU5gXyyJXjhisYshL8_HRxDgt8,6412
24
- pyplumio/helpers/factory.py,sha256=6ArzJDq3MiiMaRpMEP0kC6wJWsoqOqe32V1RCxg1478,1005
25
- pyplumio/helpers/parameter.py,sha256=I0LJ4d29kLRt83ukfumWP6NJXfEuPS7vRlS_1HeZHT4,12306
26
- pyplumio/helpers/schedule.py,sha256=PnVEkgthg6tHpHvZK9fXJz9VKNDyQ_7BFT4TTVEwNhI,5310
24
+ pyplumio/helpers/factory.py,sha256=v07s9DyihfkNUzt7ndyJbNd_DLS8UpRkut_xkGrbi6c,1123
25
+ pyplumio/helpers/parameter.py,sha256=UWv4cQ-Mk2hBD3F2amS-6OIL0ulcMmEZcnogOXcXhng,12373
26
+ pyplumio/helpers/schedule.py,sha256=rlq3O2Xflqks6V1rMoHKy_lww8zJJpuHFFouM2pikJw,5417
27
27
  pyplumio/helpers/task_manager.py,sha256=HAd69yGTRL0zQsu-ywnbLu1UXiJzgHWuhYWA--vs4lQ,1181
28
28
  pyplumio/helpers/timeout.py,sha256=JAhWNtIpcXyVILIwHWVy5mYofqbbRDGKLdTUKkQuajs,772
29
29
  pyplumio/helpers/uid.py,sha256=J7gN8i8LE0g6tfL66BJbwsQQqzBBxWx7giyvqaJh4BM,976
@@ -53,8 +53,8 @@ pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJ
53
53
  pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
54
54
  pyplumio/structures/thermostat_parameters.py,sha256=QA-ZyulBG3P10sqgdI7rmpQYlKm9SJIXxBxAXs8Bwow,8295
55
55
  pyplumio/structures/thermostat_sensors.py,sha256=8e1TxYIJTQKT0kIGO9gG4hGdLOBUpIhiPToQyOMyeNE,3237
56
- PyPlumIO-0.5.32.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
57
- PyPlumIO-0.5.32.dist-info/METADATA,sha256=9VeND8m5qvmZeIwWG3ne9GcBET6IAr5aDdzNtMNbHT8,5510
58
- PyPlumIO-0.5.32.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
59
- PyPlumIO-0.5.32.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
60
- PyPlumIO-0.5.32.dist-info/RECORD,,
56
+ PyPlumIO-0.5.34.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
57
+ PyPlumIO-0.5.34.dist-info/METADATA,sha256=fKpSdOn-36ISpqOsoM7wiwnbJn0jHfvdHnwyIge5KQM,5510
58
+ PyPlumIO-0.5.34.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
59
+ PyPlumIO-0.5.34.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
60
+ PyPlumIO-0.5.34.dist-info/RECORD,,
pyplumio/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.5.32'
16
- __version_tuple__ = version_tuple = (0, 5, 32)
15
+ __version__ = version = '0.5.34'
16
+ __version_tuple__ = version_tuple = (0, 5, 34)
@@ -82,7 +82,7 @@ class Device(ABC, EventManager):
82
82
  """
83
83
  parameter = await self.get(name, timeout)
84
84
  if not isinstance(parameter, Parameter):
85
- raise TypeError(f"{name} is not valid parameter")
85
+ raise TypeError(f"The parameter '{name}' is not valid or does not exist.")
86
86
 
87
87
  return await parameter.set(value, retries=retries)
88
88
 
@@ -143,7 +143,7 @@ class PhysicalDevice(Device, ABC):
143
143
  and not self.has_frame_version(frame_type, version)
144
144
  ):
145
145
  _LOGGER.debug(
146
- "Updating frame %s to version %i", repr(frame_type), version
146
+ "Updating frame %s to version %i", frame_type, version
147
147
  )
148
148
  request = await Request.create(frame_type, recipient=self.address)
149
149
  self.queue.put_nowait(request)
@@ -151,15 +151,12 @@ class PhysicalDevice(Device, ABC):
151
151
 
152
152
  self.subscribe(ATTR_FRAME_VERSIONS, update_frame_versions)
153
153
 
154
- def has_frame_version(self, frame_type: int, version: int | None = None) -> bool:
154
+ def has_frame_version(self, frame_type: FrameType | int, version: int) -> bool:
155
155
  """Return True if frame data is up to date, False otherwise."""
156
- if frame_type not in self._frame_versions:
157
- return False
158
-
159
- if version is None or self._frame_versions[frame_type] == version:
160
- return True
161
-
162
- return False
156
+ return (
157
+ frame_type in self._frame_versions
158
+ and self._frame_versions[frame_type] == version
159
+ )
163
160
 
164
161
  def supports_frame_type(self, frame_type: int) -> bool:
165
162
  """Check if frame type is supported by the device."""
@@ -206,7 +203,10 @@ class PhysicalDevice(Device, ABC):
206
203
  except asyncio.TimeoutError:
207
204
  retries -= 1
208
205
 
209
- raise ValueError(f'could not request "{name}"', frame_type)
206
+ raise ValueError(
207
+ f"Failed to request parameter '{name}' with frame type '{frame_type}' "
208
+ f"after {retries} retries."
209
+ )
210
210
 
211
211
  @classmethod
212
212
  async def create(cls, device_type: int, **kwargs: Any) -> PhysicalDevice:
@@ -98,6 +98,8 @@ SETUP_FRAME_TYPES: tuple[DataFrameDescription, ...] = (
98
98
 
99
99
  _LOGGER = logging.getLogger(__name__)
100
100
 
101
+ ecomax_control_error = "ecoMAX control is not available. Please try again later."
102
+
101
103
 
102
104
  class EcoMAX(PhysicalDevice):
103
105
  """Represents an ecoMAX controller."""
@@ -192,6 +194,7 @@ class EcoMAX(PhysicalDevice):
192
194
  values,
193
195
  product.model,
194
196
  )
197
+ return
195
198
 
196
199
  handler = (
197
200
  EcomaxSwitch
@@ -393,7 +396,7 @@ class EcoMAX(PhysicalDevice):
393
396
  ecomax_control: EcomaxSwitch = self.data[ATTR_ECOMAX_CONTROL]
394
397
  return await ecomax_control.turn_on()
395
398
  except KeyError:
396
- _LOGGER.error("ecoMAX control isn't available, please try later")
399
+ _LOGGER.error(ecomax_control_error)
397
400
  return False
398
401
 
399
402
  async def turn_off(self) -> bool:
@@ -402,7 +405,7 @@ class EcoMAX(PhysicalDevice):
402
405
  ecomax_control: EcomaxSwitch = self.data[ATTR_ECOMAX_CONTROL]
403
406
  return await ecomax_control.turn_off()
404
407
  except KeyError:
405
- _LOGGER.error("ecoMAX control isn't available, please try later")
408
+ _LOGGER.error(ecomax_control_error)
406
409
  return False
407
410
 
408
411
  def turn_on_nowait(self) -> None:
@@ -26,9 +26,12 @@ async def create_instance(class_path: str, cls: type[T], **kwargs: Any) -> T:
26
26
  module = await _import_module(module_name)
27
27
  instance = getattr(module, class_name)(**kwargs)
28
28
  if not isinstance(instance, cls):
29
- raise TypeError(f"class '{class_name}' should be derived from {cls}")
29
+ raise TypeError(
30
+ f"Expected instance of '{cls.__name__}', but got "
31
+ f"'{type(instance).__name__}' from '{class_name}'"
32
+ )
30
33
 
31
34
  return instance
32
35
  except Exception:
33
- _LOGGER.error("Failed to load module (%s)", class_path)
36
+ _LOGGER.exception("Failed to create instance for class path '%s'", class_path)
34
37
  raise
@@ -189,7 +189,8 @@ class Parameter(ABC):
189
189
  value = _normalize_parameter_value(value)
190
190
  if value < self.values.min_value or value > self.values.max_value:
191
191
  raise ValueError(
192
- f"Value must be between '{self.min_value}' and '{self.max_value}'"
192
+ f"Invalid value: {value}. Must be between "
193
+ f"{self.min_value} and {self.max_value}."
193
194
  )
194
195
 
195
196
  return value
@@ -215,9 +216,10 @@ class Parameter(ABC):
215
216
  self._pending_update = True
216
217
  while self.pending_update:
217
218
  if retries <= 0:
218
- _LOGGER.error(
219
- "Timed out while trying to set '%s' parameter",
219
+ _LOGGER.warning(
220
+ "Failed to set parameter '%s' after %d retries",
220
221
  self.description.name,
222
+ retries,
221
223
  )
222
224
  return False
223
225
 
@@ -50,8 +50,8 @@ def _get_time_range(
50
50
 
51
51
  if end_dt <= start_dt:
52
52
  raise ValueError(
53
- f"Invalid interval ({start}, {end}). "
54
- "Lower boundary must be less than upper."
53
+ f"Invalid time range: start time ({start}) must be earlier "
54
+ f"than end time ({end})."
55
55
  )
56
56
 
57
57
  def _dt_to_index(dt: dt.datetime) -> int:
@@ -107,7 +107,10 @@ class ScheduleDay(MutableMapping):
107
107
  ) -> None:
108
108
  """Set a schedule interval state."""
109
109
  if state not in get_args(ScheduleState):
110
- raise ValueError(f'state "{state}" is not allowed')
110
+ raise ValueError(
111
+ f"Invalid state '{state}'. Allowed states are: "
112
+ f"{', '.join(get_args(ScheduleState))}"
113
+ )
111
114
 
112
115
  for index in _get_time_range(start, end):
113
116
  self._intervals[index] = True if state in ON_STATES else False
pyplumio/stream.py CHANGED
@@ -45,7 +45,7 @@ class FrameWriter:
45
45
  """Send the frame and wait until send buffer is empty."""
46
46
  self._writer.write(frame.bytes)
47
47
  await self._writer.drain()
48
- _LOGGER.debug("Sent frame: %s", frame)
48
+ _LOGGER.debug("Sent frame: %s, bytes: %s", frame, frame.bytes)
49
49
 
50
50
  async def close(self) -> None:
51
51
  """Close the frame writer."""
@@ -53,7 +53,9 @@ class FrameWriter:
53
53
  self._writer.close()
54
54
  await self.wait_closed()
55
55
  except (OSError, asyncio.TimeoutError):
56
- _LOGGER.exception("Unexpected error, while closing the writer")
56
+ _LOGGER.exception(
57
+ "Failed to close the frame writer due to an unexpected error"
58
+ )
57
59
 
58
60
  @timeout(WRITER_TIMEOUT)
59
61
  async def wait_closed(self) -> None:
@@ -96,7 +98,7 @@ class FrameReader:
96
98
  buffer += await self._reader.readexactly(HEADER_SIZE - DELIMITER_SIZE)
97
99
  except IncompleteReadError as e:
98
100
  raise ReadError(
99
- f"Got incomplete header, while trying to read {e.expected} bytes"
101
+ f"Incomplete header, expected {e.expected} bytes"
100
102
  ) from e
101
103
 
102
104
  return Header(*struct_header.unpack_from(buffer)[DELIMITER_SIZE:]), buffer
@@ -123,18 +125,21 @@ class FrameReader:
123
125
  raise UnknownDeviceError(f"Unknown sender type ({sender})")
124
126
 
125
127
  if frame_length > MAX_FRAME_LENGTH or frame_length < MIN_FRAME_LENGTH:
126
- raise ReadError(f"Unexpected frame length ({frame_length})")
128
+ raise ReadError(
129
+ f"Unexpected frame length ({frame_length}), expected between "
130
+ f"{MIN_FRAME_LENGTH} and {MAX_FRAME_LENGTH}"
131
+ )
127
132
 
128
133
  try:
129
134
  buffer += await self._reader.readexactly(frame_length - HEADER_SIZE)
130
135
  except IncompleteReadError as e:
131
- raise ReadError(
132
- f"Got incomplete frame, while trying to read {e.expected} bytes"
133
- ) from e
136
+ raise ReadError(f"Incomplete frame, expected {e.expected} bytes") from e
134
137
 
135
138
  if (checksum := bcc(buffer[:-2])) and checksum != buffer[-2]:
136
139
  raise ChecksumError(
137
- f"Incorrect frame checksum ({checksum} != {buffer[-2]})"
140
+ f"Incorrect frame checksum: calculated {checksum}, "
141
+ f"expected {buffer[-2]}. "
142
+ f"Frame data: {buffer.hex()}"
138
143
  )
139
144
 
140
145
  frame = await Frame.create(
@@ -145,6 +150,6 @@ class FrameReader:
145
150
  econet_version=econet_version,
146
151
  message=buffer[HEADER_SIZE + 1 : -2],
147
152
  )
148
- _LOGGER.debug("Received frame: %s", frame)
153
+ _LOGGER.debug("Received frame: %s, bytes: %s", frame, buffer.hex())
149
154
 
150
155
  return frame