python-qube-heatpump 1.4.2__tar.gz → 1.4.4__tar.gz

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.
Files changed (28) hide show
  1. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/PKG-INFO +1 -1
  2. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/pyproject.toml +1 -1
  3. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/client.py +52 -28
  4. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  5. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  6. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  7. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/.github/workflows/ci.yml +0 -0
  8. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/.github/workflows/python-publish.yml +0 -0
  9. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/.gitignore +0 -0
  10. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/AGENTS.md +0 -0
  11. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/CLAUDE.md +0 -0
  12. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/LICENSE +0 -0
  13. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/README.md +0 -0
  14. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/pytest.ini +0 -0
  15. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/__init__.py +0 -0
  16. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/const.py +0 -0
  17. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/entities/__init__.py +0 -0
  18. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/entities/base.py +0 -0
  19. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/entities/binary_sensors.py +0 -0
  20. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/entities/sensors.py +0 -0
  21. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/entities/switches.py +0 -0
  22. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/models.py +0 -0
  23. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/src/python_qube_heatpump/py.typed +0 -0
  24. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/tests/conftest.py +0 -0
  25. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/tests/test_client.py +0 -0
  26. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/tests/test_const.py +0 -0
  27. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/tests/test_entities.py +0 -0
  28. {python_qube_heatpump-1.4.2 → python_qube_heatpump-1.4.4}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-qube-heatpump
3
- Version: 1.4.2
3
+ Version: 1.4.4
4
4
  Summary: Async Modbus client for Qube Heat Pumps
5
5
  Project-URL: Homepage, https://github.com/MattieGit/python-qube-heatpump
6
6
  Project-URL: Bug Tracker, https://github.com/MattieGit/python-qube-heatpump/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-qube-heatpump"
7
- version = "1.4.2"
7
+ version = "1.4.4"
8
8
  authors = [
9
9
  { name="MattieGit", email="6250046+MattieGit@users.noreply.github.com" },
10
10
  ]
@@ -162,18 +162,14 @@ class QubeClient:
162
162
  # Full 32-bit int: (Reg1 << 16) | Reg0
163
163
  # Then pack as >I (Big Endian 32-bit int) and unpack as >f (Big Endian float)?
164
164
  #
165
- # Wait, PyModbus BinaryPayloadDecoder.fromRegisters(registers, byteorder=Endian.Big, wordorder=Endian.Little)
166
- # ByteOrder Big: Normal network byte order per register.
167
- # WordOrder Little: The first register is the least significant word.
168
- #
169
- # So:
170
- # 32-bit value = (regs[1] << 16) | regs[0]
171
- # Then interpret that 32-bit integer as a float.
172
- # To interpret int bits as float in Python: struct.unpack('!f', struct.pack('!I', int_val))[0]
165
+ # Qube uses Big Endian word order (ABCD format):
166
+ # regs[0] = MSW (Most Significant Word)
167
+ # regs[1] = LSW (Least Significant Word)
168
+ # 32-bit value = (regs[0] << 16) | regs[1]
173
169
 
174
170
  if data_type == const.DataType.FLOAT32:
175
- # Combine 2 registers, Little Endian Word Order
176
- int_val = (regs[1] << 16) | regs[0]
171
+ # Combine 2 registers, Big Endian Word Order
172
+ int_val = (regs[0] << 16) | regs[1]
177
173
  val = struct.unpack(">f", struct.pack(">I", int_val))[0]
178
174
  elif data_type == const.DataType.INT16:
179
175
  val = regs[0]
@@ -183,9 +179,11 @@ class QubeClient:
183
179
  elif data_type == const.DataType.UINT16:
184
180
  val = regs[0]
185
181
  elif data_type == const.DataType.UINT32:
186
- val = (regs[1] << 16) | regs[0]
182
+ int_val = (regs[0] << 16) | regs[1]
183
+ val = int_val
187
184
  elif data_type == const.DataType.INT32:
188
- val = (regs[1] << 16) | regs[0]
185
+ int_val = (regs[0] << 16) | regs[1]
186
+ val = int_val
189
187
  if val > 2147483647:
190
188
  val -= 4294967296
191
189
  else:
@@ -212,14 +210,23 @@ class QubeClient:
212
210
  The read value (float, int, or bool depending on entity type).
213
211
  """
214
212
  # Determine register count based on data type
215
- if entity.data_type in (DataType.FLOAT32, DataType.UINT32, DataType.INT32):
213
+ # Use string comparison to handle potential enum class differences
214
+ data_type_str = entity.data_type.value if entity.data_type else None
215
+ if data_type_str in ("float32", "uint32", "int32"):
216
216
  count = 2
217
217
  else:
218
218
  count = 1
219
219
 
220
+ _LOGGER.debug(
221
+ "read_entity: key=%s addr=%s input_type=%s data_type=%s count=%s",
222
+ entity.key, entity.address, entity.input_type, data_type_str, count
223
+ )
224
+
220
225
  try:
221
- # Read based on input type
222
- if entity.input_type == InputType.COIL:
226
+ # Read based on input type (use string comparison for safety)
227
+ input_type_str = entity.input_type.value if entity.input_type else None
228
+
229
+ if input_type_str == "coil":
223
230
  result = await self._client.read_coils(
224
231
  entity.address, count=1, device_id=self.unit
225
232
  )
@@ -228,7 +235,7 @@ class QubeClient:
228
235
  return None
229
236
  return bool(result.bits[0])
230
237
 
231
- if entity.input_type == InputType.DISCRETE_INPUT:
238
+ if input_type_str == "discrete_input":
232
239
  result = await self._client.read_discrete_inputs(
233
240
  entity.address, count=1, device_id=self.unit
234
241
  )
@@ -237,11 +244,11 @@ class QubeClient:
237
244
  return None
238
245
  return bool(result.bits[0])
239
246
 
240
- if entity.input_type == InputType.INPUT_REGISTER:
247
+ if input_type_str == "input":
241
248
  result = await self._client.read_input_registers(
242
249
  entity.address, count=count, device_id=self.unit
243
250
  )
244
- else: # HOLDING_REGISTER
251
+ else: # holding
245
252
  result = await self._client.read_holding_registers(
246
253
  entity.address, count=count, device_id=self.unit
247
254
  )
@@ -253,20 +260,31 @@ class QubeClient:
253
260
  regs = result.registers
254
261
  val: float | int = 0
255
262
 
256
- # Decode based on data type
257
- if entity.data_type == DataType.FLOAT32:
258
- int_val = (regs[1] << 16) | regs[0]
263
+ _LOGGER.debug(
264
+ "read_entity: key=%s raw_regs=%s", entity.key, regs
265
+ )
266
+
267
+ # Decode based on data type (use string comparison for safety)
268
+ # Qube uses big endian word order (ABCD): regs[0]=MSW, regs[1]=LSW
269
+ if data_type_str == "float32":
270
+ int_val = (regs[0] << 16) | regs[1]
259
271
  val = struct.unpack(">f", struct.pack(">I", int_val))[0]
260
- elif entity.data_type == DataType.INT16:
272
+ _LOGGER.debug(
273
+ "read_entity: key=%s float32 int_val=%s decoded=%s",
274
+ entity.key, int_val, val
275
+ )
276
+ elif data_type_str == "int16":
261
277
  val = regs[0]
262
278
  if val > 32767:
263
279
  val -= 65536
264
- elif entity.data_type == DataType.UINT16:
280
+ elif data_type_str == "uint16":
265
281
  val = regs[0]
266
- elif entity.data_type == DataType.UINT32:
267
- val = (regs[1] << 16) | regs[0]
268
- elif entity.data_type == DataType.INT32:
269
- val = (regs[1] << 16) | regs[0]
282
+ elif data_type_str == "uint32":
283
+ int_val = (regs[0] << 16) | regs[1]
284
+ val = int_val
285
+ elif data_type_str == "int32":
286
+ int_val = (regs[0] << 16) | regs[1]
287
+ val = int_val
270
288
  if val > 2147483647:
271
289
  val -= 4294967296
272
290
 
@@ -276,6 +294,11 @@ class QubeClient:
276
294
  if entity.offset is not None:
277
295
  val = val + entity.offset
278
296
 
297
+ _LOGGER.debug(
298
+ "read_entity: key=%s final_value=%s (scale=%s, offset=%s)",
299
+ entity.key, val, entity.scale, entity.offset
300
+ )
301
+
279
302
  return val
280
303
 
281
304
  except Exception as e:
@@ -425,9 +448,10 @@ class QubeClient:
425
448
  # Encode based on data type
426
449
  if entity.data_type == DataType.FLOAT32:
427
450
  # Pack as big-endian float, then split into two registers
451
+ # Big Endian word order: regs[0]=MSW, regs[1]=LSW
428
452
  packed = struct.pack(">f", write_value)
429
453
  int_val = struct.unpack(">I", packed)[0]
430
- regs = [int_val & 0xFFFF, (int_val >> 16) & 0xFFFF]
454
+ regs = [(int_val >> 16) & 0xFFFF, int_val & 0xFFFF]
431
455
  result = await self._client.write_registers(
432
456
  entity.address, regs, device_id=self.unit
433
457
  )