python-qube-heatpump 1.2.3__py3-none-any.whl → 1.3.0__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,3 +1,39 @@
1
+ """Python library for Qube Heat Pump Modbus communication."""
2
+
1
3
  from .client import QubeClient
4
+ from .const import (
5
+ DataType,
6
+ ModbusType,
7
+ StatusCode,
8
+ STATUS_CODE_MAP,
9
+ get_status_code,
10
+ )
11
+ from .entities import (
12
+ BINARY_SENSORS,
13
+ EntityDef,
14
+ InputType,
15
+ Platform,
16
+ SENSORS,
17
+ SWITCHES,
18
+ )
19
+ from .models import QubeState
2
20
 
3
- __all__ = ["QubeClient"]
21
+ __all__ = [
22
+ # Client
23
+ "QubeClient",
24
+ # State
25
+ "QubeState",
26
+ # Entity definitions
27
+ "BINARY_SENSORS",
28
+ "EntityDef",
29
+ "InputType",
30
+ "Platform",
31
+ "SENSORS",
32
+ "SWITCHES",
33
+ # Constants
34
+ "DataType",
35
+ "ModbusType",
36
+ "StatusCode",
37
+ "STATUS_CODE_MAP",
38
+ "get_status_code",
39
+ ]
@@ -1,12 +1,16 @@
1
1
  """Client for Qube Heat Pump."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
4
6
  import struct
5
- from typing import Optional
7
+ from typing import Any
6
8
 
7
9
  from pymodbus.client import AsyncModbusTcpClient
8
10
 
9
11
  from . import const
12
+ from .entities import BINARY_SENSORS, SENSORS, SWITCHES, EntityDef
13
+ from .entities.base import DataType, InputType
10
14
  from .models import QubeState
11
15
 
12
16
  _LOGGER = logging.getLogger(__name__)
@@ -76,7 +80,7 @@ class QubeClient:
76
80
 
77
81
  return state
78
82
 
79
- async def read_value(self, definition: tuple) -> Optional[float]:
83
+ async def read_value(self, definition: tuple) -> float | None:
80
84
  """Read a single value based on the constant definition."""
81
85
  address, reg_type, data_type, scale, offset = definition
82
86
 
@@ -156,3 +160,255 @@ class QubeClient:
156
160
  except Exception as e:
157
161
  _LOGGER.error("Exception reading address %s: %s", address, e)
158
162
  return None
163
+
164
+ async def read_entity(self, entity: EntityDef) -> Any:
165
+ """Read a single entity value based on EntityDef.
166
+
167
+ Args:
168
+ entity: The entity definition to read.
169
+
170
+ Returns:
171
+ The read value (float, int, or bool depending on entity type).
172
+ """
173
+ # Determine register count based on data type
174
+ if entity.data_type in (DataType.FLOAT32, DataType.UINT32, DataType.INT32):
175
+ count = 2
176
+ else:
177
+ count = 1
178
+
179
+ try:
180
+ # Read based on input type
181
+ if entity.input_type == InputType.COIL:
182
+ result = await self._client.read_coils(
183
+ entity.address, count=1, slave=self.unit
184
+ )
185
+ if result.isError():
186
+ _LOGGER.warning("Error reading coil %s", entity.address)
187
+ return None
188
+ return bool(result.bits[0])
189
+
190
+ if entity.input_type == InputType.DISCRETE_INPUT:
191
+ result = await self._client.read_discrete_inputs(
192
+ entity.address, count=1, slave=self.unit
193
+ )
194
+ if result.isError():
195
+ _LOGGER.warning("Error reading discrete input %s", entity.address)
196
+ return None
197
+ return bool(result.bits[0])
198
+
199
+ if entity.input_type == InputType.INPUT_REGISTER:
200
+ result = await self._client.read_input_registers(
201
+ entity.address, count, slave=self.unit
202
+ )
203
+ else: # HOLDING_REGISTER
204
+ result = await self._client.read_holding_registers(
205
+ entity.address, count, slave=self.unit
206
+ )
207
+
208
+ if result.isError():
209
+ _LOGGER.warning("Error reading address %s", entity.address)
210
+ return None
211
+
212
+ regs = result.registers
213
+ val: float | int = 0
214
+
215
+ # Decode based on data type
216
+ if entity.data_type == DataType.FLOAT32:
217
+ int_val = (regs[1] << 16) | regs[0]
218
+ val = struct.unpack(">f", struct.pack(">I", int_val))[0]
219
+ elif entity.data_type == DataType.INT16:
220
+ val = regs[0]
221
+ if val > 32767:
222
+ val -= 65536
223
+ elif entity.data_type == DataType.UINT16:
224
+ val = regs[0]
225
+ elif entity.data_type == DataType.UINT32:
226
+ val = (regs[1] << 16) | regs[0]
227
+ elif entity.data_type == DataType.INT32:
228
+ val = (regs[1] << 16) | regs[0]
229
+ if val > 2147483647:
230
+ val -= 4294967296
231
+
232
+ # Apply scale and offset
233
+ if entity.scale is not None:
234
+ val = val * entity.scale
235
+ if entity.offset is not None:
236
+ val = val + entity.offset
237
+
238
+ return val
239
+
240
+ except Exception as e:
241
+ _LOGGER.error("Exception reading entity %s: %s", entity.key, e)
242
+ return None
243
+
244
+ async def read_sensor(self, key: str) -> float | int | None:
245
+ """Read a sensor value by key.
246
+
247
+ Args:
248
+ key: The sensor key (e.g., 'temp_supply').
249
+
250
+ Returns:
251
+ The sensor value, or None if not found or error.
252
+ """
253
+ entity = SENSORS.get(key)
254
+ if entity is None:
255
+ _LOGGER.warning("Unknown sensor key: %s", key)
256
+ return None
257
+ return await self.read_entity(entity)
258
+
259
+ async def read_binary_sensor(self, key: str) -> bool | None:
260
+ """Read a binary sensor value by key.
261
+
262
+ Args:
263
+ key: The binary sensor key (e.g., 'dout_srcpmp_val').
264
+
265
+ Returns:
266
+ The binary sensor value, or None if not found or error.
267
+ """
268
+ entity = BINARY_SENSORS.get(key)
269
+ if entity is None:
270
+ _LOGGER.warning("Unknown binary sensor key: %s", key)
271
+ return None
272
+ return await self.read_entity(entity)
273
+
274
+ async def read_switch(self, key: str) -> bool | None:
275
+ """Read a switch state by key.
276
+
277
+ Args:
278
+ key: The switch key (e.g., 'bms_summerwinter').
279
+
280
+ Returns:
281
+ The switch state, or None if not found or error.
282
+ """
283
+ entity = SWITCHES.get(key)
284
+ if entity is None:
285
+ _LOGGER.warning("Unknown switch key: %s", key)
286
+ return None
287
+ return await self.read_entity(entity)
288
+
289
+ async def read_all_sensors(self) -> dict[str, Any]:
290
+ """Read all sensor values.
291
+
292
+ Returns:
293
+ Dictionary mapping sensor keys to their values.
294
+ """
295
+ result: dict[str, Any] = {}
296
+ for key, entity in SENSORS.items():
297
+ result[key] = await self.read_entity(entity)
298
+ return result
299
+
300
+ async def read_all_binary_sensors(self) -> dict[str, bool | None]:
301
+ """Read all binary sensor values.
302
+
303
+ Returns:
304
+ Dictionary mapping binary sensor keys to their values.
305
+ """
306
+ result: dict[str, bool | None] = {}
307
+ for key, entity in BINARY_SENSORS.items():
308
+ result[key] = await self.read_entity(entity)
309
+ return result
310
+
311
+ async def read_all_switches(self) -> dict[str, bool | None]:
312
+ """Read all switch states.
313
+
314
+ Returns:
315
+ Dictionary mapping switch keys to their states.
316
+ """
317
+ result: dict[str, bool | None] = {}
318
+ for key, entity in SWITCHES.items():
319
+ result[key] = await self.read_entity(entity)
320
+ return result
321
+
322
+ async def write_switch(self, key: str, value: bool) -> bool:
323
+ """Write a switch state by key.
324
+
325
+ Args:
326
+ key: The switch key (e.g., 'bms_summerwinter').
327
+ value: True to turn on, False to turn off.
328
+
329
+ Returns:
330
+ True if write succeeded, False otherwise.
331
+ """
332
+ entity = SWITCHES.get(key)
333
+ if entity is None:
334
+ _LOGGER.warning("Unknown switch key: %s", key)
335
+ return False
336
+
337
+ if not entity.writable:
338
+ _LOGGER.warning("Switch %s is not writable", key)
339
+ return False
340
+
341
+ try:
342
+ result = await self._client.write_coil(
343
+ entity.address, value, slave=self.unit
344
+ )
345
+ if result.isError():
346
+ _LOGGER.warning("Error writing switch %s", key)
347
+ return False
348
+ return True
349
+ except Exception as e:
350
+ _LOGGER.error("Exception writing switch %s: %s", key, e)
351
+ return False
352
+
353
+ async def write_setpoint(self, key: str, value: float) -> bool:
354
+ """Write a setpoint value by key.
355
+
356
+ Args:
357
+ key: The sensor key for the setpoint (e.g., 'setpoint_dhw').
358
+ value: The value to write.
359
+
360
+ Returns:
361
+ True if write succeeded, False otherwise.
362
+ """
363
+ entity = SENSORS.get(key)
364
+ if entity is None:
365
+ _LOGGER.warning("Unknown sensor key: %s", key)
366
+ return False
367
+
368
+ if not entity.writable:
369
+ _LOGGER.warning("Sensor %s is not writable", key)
370
+ return False
371
+
372
+ if entity.input_type != InputType.HOLDING_REGISTER:
373
+ _LOGGER.warning("Sensor %s is not a holding register", key)
374
+ return False
375
+
376
+ try:
377
+ # Reverse scale/offset if needed
378
+ write_value = value
379
+ if entity.offset is not None:
380
+ write_value = write_value - entity.offset
381
+ if entity.scale is not None:
382
+ write_value = write_value / entity.scale
383
+
384
+ # Encode based on data type
385
+ if entity.data_type == DataType.FLOAT32:
386
+ # Pack as big-endian float, then split into two registers
387
+ packed = struct.pack(">f", write_value)
388
+ int_val = struct.unpack(">I", packed)[0]
389
+ regs = [int_val & 0xFFFF, (int_val >> 16) & 0xFFFF]
390
+ result = await self._client.write_registers(
391
+ entity.address, regs, slave=self.unit
392
+ )
393
+ elif entity.data_type == DataType.INT16:
394
+ if write_value < 0:
395
+ write_value = int(write_value) + 65536
396
+ result = await self._client.write_register(
397
+ entity.address, int(write_value), slave=self.unit
398
+ )
399
+ elif entity.data_type == DataType.UINT16:
400
+ result = await self._client.write_register(
401
+ entity.address, int(write_value), slave=self.unit
402
+ )
403
+ else:
404
+ _LOGGER.warning("Unsupported data type for writing: %s", entity.data_type)
405
+ return False
406
+
407
+ if result.isError():
408
+ _LOGGER.warning("Error writing setpoint %s", key)
409
+ return False
410
+ return True
411
+
412
+ except Exception as e:
413
+ _LOGGER.error("Exception writing setpoint %s: %s", key, e)
414
+ return False
@@ -20,6 +20,42 @@ class DataType(str, Enum):
20
20
  UINT32 = "uint32"
21
21
 
22
22
 
23
+ class StatusCode(str, Enum):
24
+ """Heat pump status codes."""
25
+
26
+ STANDBY = "standby"
27
+ ALARM = "alarm"
28
+ KEYBOARD_OFF = "keyboard_off"
29
+ COMPRESSOR_STARTUP = "compressor_startup"
30
+ COMPRESSOR_SHUTDOWN = "compressor_shutdown"
31
+ COOLING = "cooling"
32
+ HEATING = "heating"
33
+ START_FAIL = "start_fail"
34
+ HEATING_DHW = "heating_dhw"
35
+ UNKNOWN = "unknown"
36
+
37
+
38
+ # Map numeric status codes to StatusCode enum values
39
+ STATUS_CODE_MAP: dict[int, StatusCode] = {
40
+ 1: StatusCode.STANDBY,
41
+ 2: StatusCode.ALARM,
42
+ 6: StatusCode.KEYBOARD_OFF,
43
+ 8: StatusCode.COMPRESSOR_STARTUP,
44
+ 9: StatusCode.COMPRESSOR_SHUTDOWN,
45
+ 14: StatusCode.STANDBY,
46
+ 15: StatusCode.COOLING,
47
+ 16: StatusCode.HEATING,
48
+ 17: StatusCode.START_FAIL,
49
+ 18: StatusCode.STANDBY,
50
+ 22: StatusCode.HEATING_DHW,
51
+ }
52
+
53
+
54
+ def get_status_code(code: int) -> StatusCode:
55
+ """Convert numeric status code to StatusCode enum."""
56
+ return STATUS_CODE_MAP.get(code, StatusCode.UNKNOWN)
57
+
58
+
23
59
  # Register definitions (Address, Type, Data Type, Scale, Offset)
24
60
  # Scale/Offset are None if not used.
25
61
  # Format: KEY = (Address, ModbusType, DataType, Scale, Offset)
@@ -0,0 +1,16 @@
1
+ """Entity definitions for Qube Heat Pump."""
2
+
3
+ from .base import DataType, EntityDef, InputType, Platform
4
+ from .binary_sensors import BINARY_SENSORS
5
+ from .sensors import SENSORS
6
+ from .switches import SWITCHES
7
+
8
+ __all__ = [
9
+ "BINARY_SENSORS",
10
+ "DataType",
11
+ "EntityDef",
12
+ "InputType",
13
+ "Platform",
14
+ "SENSORS",
15
+ "SWITCHES",
16
+ ]
@@ -0,0 +1,77 @@
1
+ """Base classes and enums for entity definitions."""
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+
6
+
7
+ class InputType(str, Enum):
8
+ """Modbus input type for reading values."""
9
+
10
+ COIL = "coil"
11
+ DISCRETE_INPUT = "discrete_input"
12
+ INPUT_REGISTER = "input"
13
+ HOLDING_REGISTER = "holding"
14
+
15
+
16
+ class DataType(str, Enum):
17
+ """Data type for register values."""
18
+
19
+ FLOAT32 = "float32"
20
+ INT16 = "int16"
21
+ UINT16 = "uint16"
22
+ INT32 = "int32"
23
+ UINT32 = "uint32"
24
+
25
+
26
+ class Platform(str, Enum):
27
+ """Home Assistant platform type."""
28
+
29
+ SENSOR = "sensor"
30
+ BINARY_SENSOR = "binary_sensor"
31
+ SWITCH = "switch"
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class EntityDef:
36
+ """Definition of a Qube heat pump entity.
37
+
38
+ This dataclass defines the protocol-level properties of an entity.
39
+ Home Assistant-specific metadata (device_class, state_class, etc.)
40
+ should be added by the integration, not here.
41
+ """
42
+
43
+ # Identity
44
+ key: str
45
+ """Unique identifier, e.g., 'temp_supply'."""
46
+
47
+ name: str
48
+ """Human-readable name, e.g., 'Supply temperature'."""
49
+
50
+ # Modbus specifics
51
+ address: int
52
+ """Register or coil address."""
53
+
54
+ input_type: InputType
55
+ """How to read from device (coil, discrete_input, input, holding)."""
56
+
57
+ data_type: DataType | None = None
58
+ """Data type for registers. None for coils/discrete inputs."""
59
+
60
+ # Platform hint
61
+ platform: Platform = Platform.SENSOR
62
+ """Which HA platform this entity belongs to."""
63
+
64
+ # Value transformation
65
+ scale: float | None = None
66
+ """Multiply raw value by this factor."""
67
+
68
+ offset: float | None = None
69
+ """Add this to the scaled value."""
70
+
71
+ # Unit (protocol-level)
72
+ unit: str | None = None
73
+ """Unit of measurement, e.g., '°C', 'kWh', 'W'."""
74
+
75
+ # Write capability
76
+ writable: bool = False
77
+ """Whether this entity can be written to."""
@@ -0,0 +1,269 @@
1
+ """Binary sensor entity definitions for Qube Heat Pump."""
2
+
3
+ from .base import EntityDef, InputType, Platform
4
+
5
+ _BINARY_SENSOR_DEFS: tuple[EntityDef, ...] = (
6
+ EntityDef(
7
+ key="dout_srcpmp_val",
8
+ name="Source pump power supply",
9
+ address=0,
10
+ input_type=InputType.DISCRETE_INPUT,
11
+ platform=Platform.BINARY_SENSOR,
12
+ ),
13
+ EntityDef(
14
+ key="dout_usrpmp_val",
15
+ name="User pump power supply",
16
+ address=1,
17
+ input_type=InputType.DISCRETE_INPUT,
18
+ platform=Platform.BINARY_SENSOR,
19
+ ),
20
+ EntityDef(
21
+ key="dout_fourwayvlv_val",
22
+ name="Four-way valve control",
23
+ address=2,
24
+ input_type=InputType.DISCRETE_INPUT,
25
+ platform=Platform.BINARY_SENSOR,
26
+ ),
27
+ EntityDef(
28
+ key="dout_cooling_val",
29
+ name="Cooling active output",
30
+ address=3,
31
+ input_type=InputType.DISCRETE_INPUT,
32
+ platform=Platform.BINARY_SENSOR,
33
+ ),
34
+ EntityDef(
35
+ key="dout_threewayvlv_val",
36
+ name="Three-way valve output (CV/DHW)",
37
+ address=4,
38
+ input_type=InputType.DISCRETE_INPUT,
39
+ platform=Platform.BINARY_SENSOR,
40
+ ),
41
+ EntityDef(
42
+ key="dout_bufferpmp_val",
43
+ name="Buffer pump output",
44
+ address=5,
45
+ input_type=InputType.DISCRETE_INPUT,
46
+ platform=Platform.BINARY_SENSOR,
47
+ ),
48
+ EntityDef(
49
+ key="dout_heaterstep1_val",
50
+ name="Heater 1 output",
51
+ address=6,
52
+ input_type=InputType.DISCRETE_INPUT,
53
+ platform=Platform.BINARY_SENSOR,
54
+ ),
55
+ EntityDef(
56
+ key="dout_heaterstep2_val",
57
+ name="Heater 2 output",
58
+ address=7,
59
+ input_type=InputType.DISCRETE_INPUT,
60
+ platform=Platform.BINARY_SENSOR,
61
+ ),
62
+ EntityDef(
63
+ key="dout_heaterstep3_val",
64
+ name="Heater 3 output",
65
+ address=8,
66
+ input_type=InputType.DISCRETE_INPUT,
67
+ platform=Platform.BINARY_SENSOR,
68
+ ),
69
+ EntityDef(
70
+ key="keybonoff",
71
+ name="Heat pump keypad status",
72
+ address=9,
73
+ input_type=InputType.DISCRETE_INPUT,
74
+ platform=Platform.BINARY_SENSOR,
75
+ ),
76
+ EntityDef(
77
+ key="al_maxtime_antileg_active",
78
+ name="Alarm: Max time anti-legionella exceeded",
79
+ address=10,
80
+ input_type=InputType.DISCRETE_INPUT,
81
+ platform=Platform.BINARY_SENSOR,
82
+ ),
83
+ EntityDef(
84
+ key="al_maxtime_dhw_active",
85
+ name="Alarm: Max time DHW exceeded",
86
+ address=11,
87
+ input_type=InputType.DISCRETE_INPUT,
88
+ platform=Platform.BINARY_SENSOR,
89
+ ),
90
+ EntityDef(
91
+ key="al_dewpoint_active",
92
+ name="Alarm: Dewpoint activated",
93
+ address=12,
94
+ input_type=InputType.DISCRETE_INPUT,
95
+ platform=Platform.BINARY_SENSOR,
96
+ ),
97
+ EntityDef(
98
+ key="al_underfloorsafety_active",
99
+ name="Alarm: Supply too hot (CV)",
100
+ address=13,
101
+ input_type=InputType.DISCRETE_INPUT,
102
+ platform=Platform.BINARY_SENSOR,
103
+ ),
104
+ EntityDef(
105
+ key="alrm_flw",
106
+ name="Alarm: Flow CV",
107
+ address=15,
108
+ input_type=InputType.DISCRETE_INPUT,
109
+ platform=Platform.BINARY_SENSOR,
110
+ ),
111
+ EntityDef(
112
+ key="usralrms",
113
+ name="Alarm: Collective alarm CV",
114
+ address=16,
115
+ input_type=InputType.DISCRETE_INPUT,
116
+ platform=Platform.BINARY_SENSOR,
117
+ ),
118
+ EntityDef(
119
+ key="coolingalrms",
120
+ name="Alarm: Collective alarm cooling",
121
+ address=17,
122
+ input_type=InputType.DISCRETE_INPUT,
123
+ platform=Platform.BINARY_SENSOR,
124
+ ),
125
+ EntityDef(
126
+ key="heatingalrms",
127
+ name="Alarm: Collective alarm heating",
128
+ address=18,
129
+ input_type=InputType.DISCRETE_INPUT,
130
+ platform=Platform.BINARY_SENSOR,
131
+ ),
132
+ EntityDef(
133
+ key="alarmmng_al_workinghour",
134
+ name="Alarm: Working hours",
135
+ address=19,
136
+ input_type=InputType.DISCRETE_INPUT,
137
+ platform=Platform.BINARY_SENSOR,
138
+ ),
139
+ EntityDef(
140
+ key="srsalrm",
141
+ name="Alarm: Collective alarm source",
142
+ address=20,
143
+ input_type=InputType.DISCRETE_INPUT,
144
+ platform=Platform.BINARY_SENSOR,
145
+ ),
146
+ EntityDef(
147
+ key="glbal",
148
+ name="Global Alarm",
149
+ address=21,
150
+ input_type=InputType.DISCRETE_INPUT,
151
+ platform=Platform.BINARY_SENSOR,
152
+ ),
153
+ EntityDef(
154
+ key="alarmmng_al_pwrplus",
155
+ name="Alarm: Collective alarm compressor",
156
+ address=22,
157
+ input_type=InputType.DISCRETE_INPUT,
158
+ platform=Platform.BINARY_SENSOR,
159
+ ),
160
+ EntityDef(
161
+ key="roomprb_en",
162
+ name="Room sensor activated",
163
+ address=23,
164
+ input_type=InputType.DISCRETE_INPUT,
165
+ platform=Platform.BINARY_SENSOR,
166
+ ),
167
+ EntityDef(
168
+ key="plantdemand",
169
+ name="Plant sensor demand",
170
+ address=25,
171
+ input_type=InputType.DISCRETE_INPUT,
172
+ platform=Platform.BINARY_SENSOR,
173
+ ),
174
+ EntityDef(
175
+ key="en_dhwpid",
176
+ name="DHW controller enabled",
177
+ address=26,
178
+ input_type=InputType.DISCRETE_INPUT,
179
+ platform=Platform.BINARY_SENSOR,
180
+ ),
181
+ EntityDef(
182
+ key="plantprb_en",
183
+ name="Plant sensor activated",
184
+ address=27,
185
+ input_type=InputType.DISCRETE_INPUT,
186
+ platform=Platform.BINARY_SENSOR,
187
+ ),
188
+ EntityDef(
189
+ key="bufferprb_en",
190
+ name="Buffer sensor activated",
191
+ address=28,
192
+ input_type=InputType.DISCRETE_INPUT,
193
+ platform=Platform.BINARY_SENSOR,
194
+ ),
195
+ EntityDef(
196
+ key="id_demand",
197
+ name="Digital input demand",
198
+ address=29,
199
+ input_type=InputType.DISCRETE_INPUT,
200
+ platform=Platform.BINARY_SENSOR,
201
+ ),
202
+ EntityDef(
203
+ key="id_summerwinter",
204
+ name="Digital input summer mode activated",
205
+ address=30,
206
+ input_type=InputType.DISCRETE_INPUT,
207
+ platform=Platform.BINARY_SENSOR,
208
+ ),
209
+ EntityDef(
210
+ key="dewpoint",
211
+ name="Digital input dewpoint activated",
212
+ address=31,
213
+ input_type=InputType.DISCRETE_INPUT,
214
+ platform=Platform.BINARY_SENSOR,
215
+ ),
216
+ EntityDef(
217
+ key="boostersecurity",
218
+ name="Digital input booster activated",
219
+ address=32,
220
+ input_type=InputType.DISCRETE_INPUT,
221
+ platform=Platform.BINARY_SENSOR,
222
+ ),
223
+ EntityDef(
224
+ key="srcflw",
225
+ name="Digital input source flow activated",
226
+ address=34,
227
+ input_type=InputType.DISCRETE_INPUT,
228
+ platform=Platform.BINARY_SENSOR,
229
+ ),
230
+ EntityDef(
231
+ key="daynightmode",
232
+ name="Day/night mode status",
233
+ address=37,
234
+ input_type=InputType.DISCRETE_INPUT,
235
+ platform=Platform.BINARY_SENSOR,
236
+ ),
237
+ EntityDef(
238
+ key="thermostatdemand",
239
+ name="Internal thermostat demand",
240
+ address=38,
241
+ input_type=InputType.DISCRETE_INPUT,
242
+ platform=Platform.BINARY_SENSOR,
243
+ ),
244
+ EntityDef(
245
+ key="req_antileg_1",
246
+ name="Anti-legionella enabled",
247
+ address=39,
248
+ input_type=InputType.DISCRETE_INPUT,
249
+ platform=Platform.BINARY_SENSOR,
250
+ ),
251
+ # Coil-based binary sensors
252
+ EntityDef(
253
+ key="bms_demand",
254
+ name="Heat demand active",
255
+ address=19,
256
+ input_type=InputType.COIL,
257
+ platform=Platform.BINARY_SENSOR,
258
+ ),
259
+ EntityDef(
260
+ key="surplus_pv",
261
+ name="PV surplus active",
262
+ address=64,
263
+ input_type=InputType.COIL,
264
+ platform=Platform.BINARY_SENSOR,
265
+ ),
266
+ )
267
+
268
+ # Export as dict for easy lookup by key
269
+ BINARY_SENSORS: dict[str, EntityDef] = {e.key: e for e in _BINARY_SENSOR_DEFS}
@@ -0,0 +1,468 @@
1
+ """Sensor entity definitions for Qube Heat Pump."""
2
+
3
+ from .base import DataType, EntityDef, InputType, Platform
4
+
5
+ _SENSOR_DEFS: tuple[EntityDef, ...] = (
6
+ # Holding register sensors (configuration/setpoints readable as sensors)
7
+ EntityDef(
8
+ key="thermostat_heatsetp_day",
9
+ name="Room setpoint heating (day)",
10
+ address=27,
11
+ input_type=InputType.HOLDING_REGISTER,
12
+ data_type=DataType.FLOAT32,
13
+ platform=Platform.SENSOR,
14
+ unit="°C",
15
+ ),
16
+ EntityDef(
17
+ key="thermostat_heatsetp_night",
18
+ name="Room setpoint heating (night)",
19
+ address=29,
20
+ input_type=InputType.HOLDING_REGISTER,
21
+ data_type=DataType.FLOAT32,
22
+ platform=Platform.SENSOR,
23
+ unit="°C",
24
+ ),
25
+ EntityDef(
26
+ key="thermostat_coolsetp_day",
27
+ name="Room setpoint cooling (day)",
28
+ address=31,
29
+ input_type=InputType.HOLDING_REGISTER,
30
+ data_type=DataType.FLOAT32,
31
+ platform=Platform.SENSOR,
32
+ unit="°C",
33
+ ),
34
+ EntityDef(
35
+ key="thermostat_coolsetp_night",
36
+ name="Room setpoint cooling (night)",
37
+ address=33,
38
+ input_type=InputType.HOLDING_REGISTER,
39
+ data_type=DataType.FLOAT32,
40
+ platform=Platform.SENSOR,
41
+ unit="°C",
42
+ ),
43
+ EntityDef(
44
+ key="tapw_timeprogram_dt_bms",
45
+ name="dT flow temperature DHW",
46
+ address=43,
47
+ input_type=InputType.HOLDING_REGISTER,
48
+ data_type=DataType.INT16,
49
+ platform=Platform.SENSOR,
50
+ unit="°C",
51
+ ),
52
+ EntityDef(
53
+ key="tapw_timeprogram_dhws",
54
+ name="Minimum temperature DHW",
55
+ address=44,
56
+ input_type=InputType.HOLDING_REGISTER,
57
+ data_type=DataType.FLOAT32,
58
+ platform=Platform.SENSOR,
59
+ unit="°C",
60
+ ),
61
+ EntityDef(
62
+ key="tapw_timeprogram_dhws_prog",
63
+ name="DHW temperature (active program)",
64
+ address=46,
65
+ input_type=InputType.HOLDING_REGISTER,
66
+ data_type=DataType.FLOAT32,
67
+ platform=Platform.SENSOR,
68
+ unit="°C",
69
+ ),
70
+ EntityDef(
71
+ key="regulation_buffersetp_min",
72
+ name="Minimum setpoint buffer regulation",
73
+ address=99,
74
+ input_type=InputType.HOLDING_REGISTER,
75
+ data_type=DataType.FLOAT32,
76
+ platform=Platform.SENSOR,
77
+ unit="°C",
78
+ ),
79
+ EntityDef(
80
+ key="usr_pid_heatsetp",
81
+ name="Heat setpoint (no curve)",
82
+ address=101,
83
+ input_type=InputType.HOLDING_REGISTER,
84
+ data_type=DataType.FLOAT32,
85
+ platform=Platform.SENSOR,
86
+ unit="°C",
87
+ ),
88
+ EntityDef(
89
+ key="usr_pid_coolsetp",
90
+ name="Cooling setpoint (no curve)",
91
+ address=103,
92
+ input_type=InputType.HOLDING_REGISTER,
93
+ data_type=DataType.FLOAT32,
94
+ platform=Platform.SENSOR,
95
+ unit="°C",
96
+ ),
97
+ EntityDef(
98
+ key="regulation_buffersetp_max",
99
+ name="Max buffer setpoint (cooling)",
100
+ address=169,
101
+ input_type=InputType.HOLDING_REGISTER,
102
+ data_type=DataType.FLOAT32,
103
+ platform=Platform.SENSOR,
104
+ unit="°C",
105
+ ),
106
+ EntityDef(
107
+ key="tapw_timeprogram_dhwsetp_nolinq",
108
+ name="User-defined DHW setpoint",
109
+ address=173,
110
+ input_type=InputType.HOLDING_REGISTER,
111
+ data_type=DataType.FLOAT32,
112
+ platform=Platform.SENSOR,
113
+ unit="°C",
114
+ writable=True, # This setpoint can be written via Modbus
115
+ ),
116
+ # Input register sensors (read-only)
117
+ EntityDef(
118
+ key="aout_usrpmp_val",
119
+ name="User (CV) pump control %",
120
+ address=4,
121
+ input_type=InputType.INPUT_REGISTER,
122
+ data_type=DataType.FLOAT32,
123
+ platform=Platform.SENSOR,
124
+ unit="%",
125
+ scale=-1.0,
126
+ offset=100.0,
127
+ ),
128
+ EntityDef(
129
+ key="aout_srcpmp_val",
130
+ name="Source pump control %",
131
+ address=6,
132
+ input_type=InputType.INPUT_REGISTER,
133
+ data_type=DataType.FLOAT32,
134
+ platform=Platform.SENSOR,
135
+ unit="%",
136
+ scale=-1.0,
137
+ offset=100.0,
138
+ ),
139
+ EntityDef(
140
+ key="aout_srcvalve_val",
141
+ name="Source valve control %",
142
+ address=8,
143
+ input_type=InputType.INPUT_REGISTER,
144
+ data_type=DataType.FLOAT32,
145
+ platform=Platform.SENSOR,
146
+ unit="%",
147
+ ),
148
+ EntityDef(
149
+ key="dhw_regreq",
150
+ name="DHW regulation demand %",
151
+ address=14,
152
+ input_type=InputType.INPUT_REGISTER,
153
+ data_type=DataType.FLOAT32,
154
+ platform=Platform.SENSOR,
155
+ unit="%",
156
+ ),
157
+ EntityDef(
158
+ key="comppwrreq",
159
+ name="Compressor demand %",
160
+ address=16,
161
+ input_type=InputType.INPUT_REGISTER,
162
+ data_type=DataType.FLOAT32,
163
+ platform=Platform.SENSOR,
164
+ unit="%",
165
+ ),
166
+ EntityDef(
167
+ key="flow",
168
+ name="Measured Flow",
169
+ address=18,
170
+ input_type=InputType.INPUT_REGISTER,
171
+ data_type=DataType.FLOAT32,
172
+ platform=Platform.SENSOR,
173
+ unit="L/min",
174
+ ),
175
+ # Core temperature sensors (mapped to QubeState typed fields)
176
+ EntityDef(
177
+ key="temp_supply",
178
+ name="Supply temperature CV",
179
+ address=20,
180
+ input_type=InputType.INPUT_REGISTER,
181
+ data_type=DataType.FLOAT32,
182
+ platform=Platform.SENSOR,
183
+ unit="°C",
184
+ ),
185
+ EntityDef(
186
+ key="temp_return",
187
+ name="Return temperature CV",
188
+ address=22,
189
+ input_type=InputType.INPUT_REGISTER,
190
+ data_type=DataType.FLOAT32,
191
+ platform=Platform.SENSOR,
192
+ unit="°C",
193
+ ),
194
+ EntityDef(
195
+ key="temp_source_in",
196
+ name="Source temperature from roof",
197
+ address=24,
198
+ input_type=InputType.INPUT_REGISTER,
199
+ data_type=DataType.FLOAT32,
200
+ platform=Platform.SENSOR,
201
+ unit="°C",
202
+ ),
203
+ EntityDef(
204
+ key="temp_source_out",
205
+ name="Source temperature to roof",
206
+ address=26,
207
+ input_type=InputType.INPUT_REGISTER,
208
+ data_type=DataType.FLOAT32,
209
+ platform=Platform.SENSOR,
210
+ unit="°C",
211
+ ),
212
+ EntityDef(
213
+ key="temp_room",
214
+ name="Room temperature",
215
+ address=28,
216
+ input_type=InputType.INPUT_REGISTER,
217
+ data_type=DataType.FLOAT32,
218
+ platform=Platform.SENSOR,
219
+ unit="°C",
220
+ ),
221
+ EntityDef(
222
+ key="temp_dhw",
223
+ name="DHW temperature",
224
+ address=30,
225
+ input_type=InputType.INPUT_REGISTER,
226
+ data_type=DataType.FLOAT32,
227
+ platform=Platform.SENSOR,
228
+ unit="°C",
229
+ ),
230
+ EntityDef(
231
+ key="temp_outside",
232
+ name="Outside temperature",
233
+ address=32,
234
+ input_type=InputType.INPUT_REGISTER,
235
+ data_type=DataType.FLOAT32,
236
+ platform=Platform.SENSOR,
237
+ unit="°C",
238
+ ),
239
+ EntityDef(
240
+ key="cop_calc",
241
+ name="COP (calculated)",
242
+ address=34,
243
+ input_type=InputType.INPUT_REGISTER,
244
+ data_type=DataType.FLOAT32,
245
+ platform=Platform.SENSOR,
246
+ ),
247
+ EntityDef(
248
+ key="power_thermic",
249
+ name="Current power",
250
+ address=36,
251
+ input_type=InputType.INPUT_REGISTER,
252
+ data_type=DataType.FLOAT32,
253
+ platform=Platform.SENSOR,
254
+ unit="W",
255
+ ),
256
+ EntityDef(
257
+ key="status_code",
258
+ name="Status code",
259
+ address=38,
260
+ input_type=InputType.INPUT_REGISTER,
261
+ data_type=DataType.UINT16,
262
+ platform=Platform.SENSOR,
263
+ ),
264
+ EntityDef(
265
+ key="regsetp",
266
+ name="Calculated heat pump setpoint",
267
+ address=39,
268
+ input_type=InputType.INPUT_REGISTER,
269
+ data_type=DataType.FLOAT32,
270
+ platform=Platform.SENSOR,
271
+ unit="°C",
272
+ ),
273
+ EntityDef(
274
+ key="coolsetp_1",
275
+ name="Cooling setpoint",
276
+ address=41,
277
+ input_type=InputType.INPUT_REGISTER,
278
+ data_type=DataType.FLOAT32,
279
+ platform=Platform.SENSOR,
280
+ unit="°C",
281
+ ),
282
+ EntityDef(
283
+ key="heatsetp_1",
284
+ name="Heating setpoint",
285
+ address=43,
286
+ input_type=InputType.INPUT_REGISTER,
287
+ data_type=DataType.FLOAT32,
288
+ platform=Platform.SENSOR,
289
+ unit="°C",
290
+ ),
291
+ EntityDef(
292
+ key="compressor_speed",
293
+ name="Current compressor speed",
294
+ address=45,
295
+ input_type=InputType.INPUT_REGISTER,
296
+ data_type=DataType.FLOAT32,
297
+ platform=Platform.SENSOR,
298
+ unit="rpm",
299
+ scale=60.0,
300
+ ),
301
+ EntityDef(
302
+ key="dhw_setp",
303
+ name="DHW calculated setpoint",
304
+ address=47,
305
+ input_type=InputType.INPUT_REGISTER,
306
+ data_type=DataType.FLOAT32,
307
+ platform=Platform.SENSOR,
308
+ unit="°C",
309
+ ),
310
+ EntityDef(
311
+ key="workinghours_dhw_hrsret",
312
+ name="Working hours DHW",
313
+ address=50,
314
+ input_type=InputType.INPUT_REGISTER,
315
+ data_type=DataType.INT16,
316
+ platform=Platform.SENSOR,
317
+ unit="h",
318
+ ),
319
+ EntityDef(
320
+ key="workinghours_heat_hrsret",
321
+ name="Working hours heating",
322
+ address=52,
323
+ input_type=InputType.INPUT_REGISTER,
324
+ data_type=DataType.INT16,
325
+ platform=Platform.SENSOR,
326
+ unit="h",
327
+ ),
328
+ EntityDef(
329
+ key="workinghours_cool_hrsret",
330
+ name="Working hours cooling",
331
+ address=54,
332
+ input_type=InputType.INPUT_REGISTER,
333
+ data_type=DataType.INT16,
334
+ platform=Platform.SENSOR,
335
+ unit="h",
336
+ ),
337
+ EntityDef(
338
+ key="workinghours_heater1_hrs",
339
+ name="Working hours heater 1",
340
+ address=56,
341
+ input_type=InputType.INPUT_REGISTER,
342
+ data_type=DataType.INT16,
343
+ platform=Platform.SENSOR,
344
+ unit="h",
345
+ ),
346
+ EntityDef(
347
+ key="workinghours_heater2_hrs",
348
+ name="Working hours heater 2",
349
+ address=58,
350
+ input_type=InputType.INPUT_REGISTER,
351
+ data_type=DataType.INT16,
352
+ platform=Platform.SENSOR,
353
+ unit="h",
354
+ ),
355
+ EntityDef(
356
+ key="workinghours_heater3_hrs",
357
+ name="Working hours heater 3",
358
+ address=60,
359
+ input_type=InputType.INPUT_REGISTER,
360
+ data_type=DataType.INT16,
361
+ platform=Platform.SENSOR,
362
+ unit="h",
363
+ ),
364
+ EntityDef(
365
+ key="power_electric",
366
+ name="Total electric power (calculated)",
367
+ address=61,
368
+ input_type=InputType.INPUT_REGISTER,
369
+ data_type=DataType.FLOAT32,
370
+ platform=Platform.SENSOR,
371
+ unit="W",
372
+ ),
373
+ EntityDef(
374
+ key="plantsetp",
375
+ name="Plant regulation setpoint",
376
+ address=65,
377
+ input_type=InputType.INPUT_REGISTER,
378
+ data_type=DataType.FLOAT32,
379
+ platform=Platform.SENSOR,
380
+ unit="°C",
381
+ ),
382
+ EntityDef(
383
+ key="energy_total_electric",
384
+ name="Total electric consumption (excl. standby)",
385
+ address=69,
386
+ input_type=InputType.INPUT_REGISTER,
387
+ data_type=DataType.FLOAT32,
388
+ platform=Platform.SENSOR,
389
+ unit="kWh",
390
+ ),
391
+ EntityDef(
392
+ key="energy_total_thermic",
393
+ name="Total thermic yield",
394
+ address=71,
395
+ input_type=InputType.INPUT_REGISTER,
396
+ data_type=DataType.FLOAT32,
397
+ platform=Platform.SENSOR,
398
+ unit="kWh",
399
+ ),
400
+ EntityDef(
401
+ key="modbus_roomtemp",
402
+ name="Linq room temperature",
403
+ address=75,
404
+ input_type=InputType.INPUT_REGISTER,
405
+ data_type=DataType.FLOAT32,
406
+ platform=Platform.SENSOR,
407
+ unit="°C",
408
+ ),
409
+ # Aliases for QubeState compatibility (same addresses as core sensors)
410
+ EntityDef(
411
+ key="flow_rate",
412
+ name="Flow rate",
413
+ address=18,
414
+ input_type=InputType.INPUT_REGISTER,
415
+ data_type=DataType.FLOAT32,
416
+ platform=Platform.SENSOR,
417
+ unit="L/min",
418
+ ),
419
+ EntityDef(
420
+ key="setpoint_room_heat_day",
421
+ name="Room setpoint heating (day)",
422
+ address=27,
423
+ input_type=InputType.HOLDING_REGISTER,
424
+ data_type=DataType.FLOAT32,
425
+ platform=Platform.SENSOR,
426
+ unit="°C",
427
+ ),
428
+ EntityDef(
429
+ key="setpoint_room_heat_night",
430
+ name="Room setpoint heating (night)",
431
+ address=29,
432
+ input_type=InputType.HOLDING_REGISTER,
433
+ data_type=DataType.FLOAT32,
434
+ platform=Platform.SENSOR,
435
+ unit="°C",
436
+ ),
437
+ EntityDef(
438
+ key="setpoint_room_cool_day",
439
+ name="Room setpoint cooling (day)",
440
+ address=31,
441
+ input_type=InputType.HOLDING_REGISTER,
442
+ data_type=DataType.FLOAT32,
443
+ platform=Platform.SENSOR,
444
+ unit="°C",
445
+ ),
446
+ EntityDef(
447
+ key="setpoint_room_cool_night",
448
+ name="Room setpoint cooling (night)",
449
+ address=33,
450
+ input_type=InputType.HOLDING_REGISTER,
451
+ data_type=DataType.FLOAT32,
452
+ platform=Platform.SENSOR,
453
+ unit="°C",
454
+ ),
455
+ EntityDef(
456
+ key="setpoint_dhw",
457
+ name="User-defined DHW setpoint",
458
+ address=173,
459
+ input_type=InputType.HOLDING_REGISTER,
460
+ data_type=DataType.FLOAT32,
461
+ platform=Platform.SENSOR,
462
+ unit="°C",
463
+ writable=True,
464
+ ),
465
+ )
466
+
467
+ # Export as dict for easy lookup by key
468
+ SENSORS: dict[str, EntityDef] = {e.key: e for e in _SENSOR_DEFS}
@@ -0,0 +1,65 @@
1
+ """Switch entity definitions for Qube Heat Pump."""
2
+
3
+ from .base import EntityDef, InputType, Platform
4
+
5
+ _SWITCH_DEFS: tuple[EntityDef, ...] = (
6
+ EntityDef(
7
+ key="bms_summerwinter",
8
+ name="Enable summer mode (cooling)",
9
+ address=22,
10
+ input_type=InputType.COIL,
11
+ platform=Platform.SWITCH,
12
+ writable=True,
13
+ ),
14
+ EntityDef(
15
+ key="tapw_timeprogram_bms_forced",
16
+ name="Start DHW heating",
17
+ address=23,
18
+ input_type=InputType.COIL,
19
+ platform=Platform.SWITCH,
20
+ writable=True,
21
+ ),
22
+ EntityDef(
23
+ key="antilegionella_frcstart_ant",
24
+ name="Start anti-legionella",
25
+ address=45,
26
+ input_type=InputType.COIL,
27
+ platform=Platform.SWITCH,
28
+ writable=True,
29
+ ),
30
+ EntityDef(
31
+ key="en_plantsetp_compens",
32
+ name="Enable heating curve",
33
+ address=62,
34
+ input_type=InputType.COIL,
35
+ platform=Platform.SWITCH,
36
+ writable=True,
37
+ ),
38
+ EntityDef(
39
+ key="bms_sgready_a",
40
+ name="SG Ready A",
41
+ address=65,
42
+ input_type=InputType.COIL,
43
+ platform=Platform.SWITCH,
44
+ writable=True,
45
+ ),
46
+ EntityDef(
47
+ key="bms_sgready_b",
48
+ name="SG Ready B",
49
+ address=66,
50
+ input_type=InputType.COIL,
51
+ platform=Platform.SWITCH,
52
+ writable=True,
53
+ ),
54
+ EntityDef(
55
+ key="modbus_demand",
56
+ name="Activate heat demand",
57
+ address=67,
58
+ input_type=InputType.COIL,
59
+ platform=Platform.SWITCH,
60
+ writable=True,
61
+ ),
62
+ )
63
+
64
+ # Export as dict for easy lookup by key
65
+ SWITCHES: dict[str, EntityDef] = {e.key: e for e in _SWITCH_DEFS}
@@ -1,37 +1,55 @@
1
1
  """Models for Qube Heat Pump."""
2
2
 
3
- from dataclasses import dataclass
4
- from typing import Optional
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
5
 
6
6
 
7
7
  @dataclass
8
8
  class QubeState:
9
- """Representation of the Qube Heat Pump state."""
9
+ """Representation of the Qube Heat Pump state.
10
+
11
+ Typed fields for core sensors (used by official HA integration).
12
+ Extended dict for additional entities (used by HACS integration).
13
+ """
10
14
 
11
15
  # Temperatures
12
- temp_supply: Optional[float] = None
13
- temp_return: Optional[float] = None
14
- temp_source_in: Optional[float] = None
15
- temp_source_out: Optional[float] = None
16
- temp_room: Optional[float] = None
17
- temp_dhw: Optional[float] = None
18
- temp_outside: Optional[float] = None
16
+ temp_supply: float | None = None
17
+ temp_return: float | None = None
18
+ temp_source_in: float | None = None
19
+ temp_source_out: float | None = None
20
+ temp_room: float | None = None
21
+ temp_dhw: float | None = None
22
+ temp_outside: float | None = None
19
23
 
20
24
  # Power/Energy
21
- power_thermic: Optional[float] = None
22
- power_electric: Optional[float] = None
23
- energy_total_electric: Optional[float] = None
24
- energy_total_thermic: Optional[float] = None
25
- cop_calc: Optional[float] = None
25
+ power_thermic: float | None = None
26
+ power_electric: float | None = None
27
+ energy_total_electric: float | None = None
28
+ energy_total_thermic: float | None = None
29
+ cop_calc: float | None = None
26
30
 
27
31
  # Operation
28
- status_code: Optional[int] = None
29
- compressor_speed: Optional[float] = None
30
- flow_rate: Optional[float] = None
32
+ status_code: int | None = None
33
+ compressor_speed: float | None = None
34
+ flow_rate: float | None = None
31
35
 
32
36
  # Setpoints (Read/Write)
33
- setpoint_room_heat_day: Optional[float] = None
34
- setpoint_room_heat_night: Optional[float] = None
35
- setpoint_room_cool_day: Optional[float] = None
36
- setpoint_room_cool_night: Optional[float] = None
37
- setpoint_dhw: Optional[float] = None
37
+ setpoint_room_heat_day: float | None = None
38
+ setpoint_room_heat_night: float | None = None
39
+ setpoint_room_cool_day: float | None = None
40
+ setpoint_room_cool_night: float | None = None
41
+ setpoint_dhw: float | None = None
42
+
43
+ # Extended dict for additional entities not covered by typed fields
44
+ _extended: dict[str, Any] = field(default_factory=dict)
45
+
46
+ def get(self, key: str, default: Any = None) -> Any:
47
+ """Get a value by key, checking typed fields first, then _extended."""
48
+ if hasattr(self, key) and key != "_extended":
49
+ value = getattr(self, key)
50
+ return value if value is not None else default
51
+ return self._extended.get(key, default)
52
+
53
+ def set_extended(self, key: str, value: Any) -> None:
54
+ """Set a value in the extended dict."""
55
+ self._extended[key] = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-qube-heatpump
3
- Version: 1.2.3
3
+ Version: 1.3.0
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
@@ -0,0 +1,13 @@
1
+ python_qube_heatpump/__init__.py,sha256=VnHFpnpnRzpxmNPAGM29FQkrcPcBAaIniWjHu68-7WA,669
2
+ python_qube_heatpump/client.py,sha256=wg02PX5bIhXhgUKeJKtHKw6wPQOFUiZUoEVYxHEE30U,15158
3
+ python_qube_heatpump/const.py,sha256=oAwKCIpmXPh5dRwX1dhdmGTJZKMlTVDnspUqAgYGZk0,4651
4
+ python_qube_heatpump/models.py,sha256=vbJdY93JkQ6ea30qhGjuCknUxRJqTiz0mTjPsb2r4xk,1841
5
+ python_qube_heatpump/entities/__init__.py,sha256=rGEqLY-KcsIlmEaM7LbriDMEqedn7NLA4sgKDI6sYM8,342
6
+ python_qube_heatpump/entities/base.py,sha256=5itTSm6cxklHsPr1qz3UWmr1kYBeCVOAugk182ybf3s,1867
7
+ python_qube_heatpump/entities/binary_sensors.py,sha256=lsYC0MEHjJ7JmJUgYzdHndkrZ1Ekxz4j9HqYDMEE_IA,7649
8
+ python_qube_heatpump/entities/sensors.py,sha256=Lros4QS1xzapR26n40GeAUDyiBipKGPuHyWDUbzgIiw,13176
9
+ python_qube_heatpump/entities/switches.py,sha256=MAaK-63dgVZEm9Ch_CW7umYM_mPqMoPYl8i0ZpZIaYE,1676
10
+ python_qube_heatpump-1.3.0.dist-info/METADATA,sha256=QLiTaA-h8TeWQ7eFSIJKzftmMPGzp1-Rv7vhM1ezVDU,950
11
+ python_qube_heatpump-1.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ python_qube_heatpump-1.3.0.dist-info/licenses/LICENSE,sha256=qpuQXN7QwpILG9GYcmqrd3Ax5CxBZUBoT295xTgKnOM,1062
13
+ python_qube_heatpump-1.3.0.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- python_qube_heatpump/__init__.py,sha256=eW8_tyAweg7YTOI599pi8gf3mt7aXW0SCw5ZyrBVJpU,57
2
- python_qube_heatpump/client.py,sha256=BgCJbQBV57cfsWMfakjbs2VXgrQ8BZH8KnX1pnTke7k,6083
3
- python_qube_heatpump/const.py,sha256=sXvXRcDb1uvBgzH2lV67Me_XtUErKA3fXMLO9gEGrlQ,3680
4
- python_qube_heatpump/models.py,sha256=3I5U9_rEe1r73hwY4sNAMXfIxyPjBOtx06c_mAURLDk,1141
5
- python_qube_heatpump-1.2.3.dist-info/METADATA,sha256=P-_IjdAl1jsiU4xYN_6oqlYK6gPvN8mvxM3qwU5tGmY,950
6
- python_qube_heatpump-1.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
- python_qube_heatpump-1.2.3.dist-info/licenses/LICENSE,sha256=qpuQXN7QwpILG9GYcmqrd3Ax5CxBZUBoT295xTgKnOM,1062
8
- python_qube_heatpump-1.2.3.dist-info/RECORD,,