aiocomfoconnect 0.3.1__tar.gz → 0.3.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiocomfoconnect
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: aiocomfoconnect is an asyncio Python 3 library for communicating with a Zehnder ComfoAir Q350/450/600 ventilation system
5
5
  License-File: LICENSE
6
6
  Author: Michaël Arnauts
@@ -84,6 +84,9 @@ $ python -m aiocomfoconnect get-property --host 192.168.1.213 1 1 8 9 # Unit 0x
84
84
  - `async set_sensor_ventmode_humidity_comfort(mode)`: Set the sensor based ventilation humidity comfort setting. (auto / on / off)
85
85
  - `async get_sensor_ventmode_humidity_protection()`: Get the sensor based ventilation humidity protection setting.
86
86
  - `async set_sensor_ventmode_humidity_protection(mode)`: Set the sensor based ventilation humidity protection setting. (auto / on / off)
87
+ - `async get_property(prop)`: Get the value of a property, e.g. `PROPERTY_RMOT_HEATING_THRESHOLD`.
88
+ - `async get_property_constraints(prop)`: Get the raw value of a numeric property, together with the range and step size the unit accepts.
89
+ - `bridge_type`: The type of the bridge (`GatewayType.LANC`, `SEASON` or `PRO`). Discovery reports it, and a ComfoConnect Pro is also recognised once a session has started.
87
90
 
88
91
  ### Low-level API
89
92
 
@@ -67,6 +67,9 @@ $ python -m aiocomfoconnect get-property --host 192.168.1.213 1 1 8 9 # Unit 0x
67
67
  - `async set_sensor_ventmode_humidity_comfort(mode)`: Set the sensor based ventilation humidity comfort setting. (auto / on / off)
68
68
  - `async get_sensor_ventmode_humidity_protection()`: Get the sensor based ventilation humidity protection setting.
69
69
  - `async set_sensor_ventmode_humidity_protection(mode)`: Set the sensor based ventilation humidity protection setting. (auto / on / off)
70
+ - `async get_property(prop)`: Get the value of a property, e.g. `PROPERTY_RMOT_HEATING_THRESHOLD`.
71
+ - `async get_property_constraints(prop)`: Get the raw value of a numeric property, together with the range and step size the unit accepts.
72
+ - `bridge_type`: The type of the bridge (`GatewayType.LANC`, `SEASON` or `PRO`). Discovery reports it, and a ComfoConnect Pro is also recognised once a session has started.
70
73
 
71
74
  ### Low-level API
72
75
 
@@ -13,7 +13,7 @@ from typing import Awaitable, Callable, Dict, Iterator, Optional, Set
13
13
  from google.protobuf.message import DecodeError
14
14
  from google.protobuf.message import Message as ProtobufMessage
15
15
 
16
- from .const import VENTILATION_UNIT_PRODUCT_IDS, ProductId, PdoType
16
+ from .const import VENTILATION_UNIT_PRODUCT_IDS, GatewayType, ProductId, PdoType
17
17
  from .exceptions import (
18
18
  AioComfoConnectNotConnected,
19
19
  AioComfoConnectNotReachable,
@@ -34,7 +34,7 @@ from .protobuf import zehnder_pb2
34
34
  _LOGGER = logging.getLogger(__name__)
35
35
 
36
36
  TIMEOUT = 5
37
- GATEWAY_TYPE_PRO = 2
37
+ GATEWAY_TYPE_PRO = GatewayType.PRO
38
38
 
39
39
  # How long we wait for the bridge to announce the ventilation unit before we give up.
40
40
  NODE_DISCOVERY_TIMEOUT = 5
@@ -126,6 +126,7 @@ class Bridge:
126
126
  ):
127
127
  self.host: str = host
128
128
  self.uuid: str = uuid
129
+ # One of GatewayType. Discovery reports it, and a ComfoConnect Pro also identifies itself when a session starts.
129
130
  self.bridge_type: int = bridge_type
130
131
  self._local_uuid: Optional[str] = None
131
132
 
@@ -490,15 +491,20 @@ class Bridge:
490
491
  except DecodeError as exc:
491
492
  _LOGGER.error("Failed to decode message: %s", exc)
492
493
 
493
- def cmd_start_session(self, take_over: bool = False) -> Awaitable[Message]:
494
+ async def cmd_start_session(self, take_over: bool = False) -> Message:
494
495
  """Starts the session on the device by logging in and optionally disconnecting an already existing session."""
495
496
  _LOGGER.debug("StartSessionRequest")
496
497
  # pylint: disable=no-member
497
- return self._send(
498
+ reply = await self._send(
498
499
  zehnder_pb2.StartSessionRequest,
499
500
  zehnder_pb2.GatewayOperation.StartSessionRequestType,
500
501
  {"takeover": take_over},
501
502
  )
503
+ # A ComfoConnect Pro sets the devicename of a successful session to "ccpro", a LAN C leaves it empty. This also
504
+ # tells us the type of a bridge that was connected to by IP address, without discovery.
505
+ if getattr(reply, "devicename", None) == "ccpro":
506
+ self.bridge_type = GatewayType.PRO
507
+ return reply
502
508
 
503
509
  def cmd_close_session(self) -> Awaitable[Message]:
504
510
  """Stops the current session."""
@@ -37,12 +37,22 @@ from aiocomfoconnect.exceptions import (
37
37
  ComfoConnectNotAllowed,
38
38
  VentilationUnitNotFoundException,
39
39
  )
40
- from aiocomfoconnect.properties import Property
40
+ from aiocomfoconnect.properties import Property, PropertyConstraints
41
41
  from aiocomfoconnect.sensors import Sensor
42
42
  from aiocomfoconnect.util import bytearray_to_bits, bytestring, encode_pdo_value
43
43
 
44
44
  _LOGGER = logging.getLogger(__name__)
45
45
 
46
+ # The size in bytes and the signedness of the numeric property types.
47
+ NUMERIC_PROPERTY_TYPES = {
48
+ PdoType.TYPE_CN_UINT8: (1, False),
49
+ PdoType.TYPE_CN_UINT16: (2, False),
50
+ PdoType.TYPE_CN_UINT32: (4, False),
51
+ PdoType.TYPE_CN_INT8: (1, True),
52
+ PdoType.TYPE_CN_INT16: (2, True),
53
+ PdoType.TYPE_CN_INT64: (8, True),
54
+ }
55
+
46
56
 
47
57
  class ComfoConnect(Bridge):
48
58
  """Abstraction layer over the ComfoConnect LAN C API."""
@@ -269,6 +279,21 @@ class ComfoConnect(Bridge):
269
279
 
270
280
  return result.message
271
281
 
282
+ async def get_property_constraints(self, prop: Property, node_id: Optional[int] = None) -> PropertyConstraints:
283
+ """Get the raw value of a numeric property, together with the range and step size the unit accepts, in one request."""
284
+ if prop.property_type not in NUMERIC_PROPERTY_TYPES:
285
+ raise ValueError(f"Property type {prop.property_type} has no range")
286
+ size, signed = NUMERIC_PROPERTY_TYPES[prop.property_type]
287
+
288
+ # Request the actual value (0x10), the range (0x20) and the step size (0x40) at once.
289
+ result = await self.cmd_rmi_request(bytes([0x01, prop.unit, prop.subunit, 0x70, prop.property_id]), node_id=node_id)
290
+
291
+ message = bytes(result.message)
292
+ if len(message) != 4 * size:
293
+ raise ValueError(f"Unexpected response length {len(message)} for a value with its range and step size")
294
+ value, minimum, maximum, step = (int.from_bytes(message[i : i + size], byteorder="little", signed=signed) for i in range(0, 4 * size, size))
295
+ return PropertyConstraints(value=value, minimum=minimum, maximum=maximum, step=step)
296
+
272
297
  async def get_multiple_properties(self, unit: int, subunit: int, property_ids: List[int], node_id: Optional[int] = None) -> Any:
273
298
  """Get multiple properties."""
274
299
  result = await self.cmd_rmi_request(bytestring([0x02, unit, subunit, 0x01, 0x10 | len(property_ids), bytes(property_ids)]), node_id=node_id)
@@ -32,6 +32,14 @@ VENTILATION_UNIT_PRODUCT_IDS = (ProductId.COMFOAIRQ, ProductId.COMFOAIRFLEX)
32
32
 
33
33
 
34
34
  # PDO Types
35
+ class GatewayType:
36
+ """Defines the type of a bridge, with the values of the GatewayType in the discovery response."""
37
+
38
+ LANC = 0 # ComfoConnect LAN C
39
+ SEASON = 1 # The built-in WiFi module of a ComfoAir Flex
40
+ PRO = 2 # ComfoConnect Pro
41
+
42
+
35
43
  class PdoType:
36
44
  """Defines a PDO type."""
37
45
 
@@ -24,6 +24,16 @@ class Property:
24
24
  property_type: int
25
25
 
26
26
 
27
+ @dataclass
28
+ class PropertyConstraints:
29
+ """The raw value of a numeric property, with the range and step size the unit accepts."""
30
+
31
+ value: int
32
+ minimum: int
33
+ maximum: int
34
+ step: int
35
+
36
+
27
37
  PROPERTY_SERIAL_NUMBER = Property(UNIT_NODE, 0x01, 0x04, PdoType.TYPE_CN_STRING)
28
38
  PROPERTY_FIRMWARE_VERSION = Property(UNIT_NODE, 0x01, 0x06, PdoType.TYPE_CN_UINT32)
29
39
  PROPERTY_MODEL = Property(UNIT_NODE, 0x01, 0x08, PdoType.TYPE_CN_STRING)
@@ -40,6 +50,12 @@ PROPERTY_SENSOR_VENTILATION_HUMIDITY_PROTECTION = Property(UNIT_TEMPHUMCONTROL,
40
50
  # Temperatures are stored in tenths of a degree.
41
51
  PROPERTY_RMOT_HEATING_THRESHOLD = Property(UNIT_TEMPHUMCONTROL, 0x01, 0x02, PdoType.TYPE_CN_INT16)
42
52
  PROPERTY_RMOT_COOLING_THRESHOLD = Property(UNIT_TEMPHUMCONTROL, 0x01, 0x03, PdoType.TYPE_CN_INT16)
53
+ PROPERTY_PROFILE_TEMPERATURE_WARM = Property(UNIT_TEMPHUMCONTROL, 0x01, 0x0A, PdoType.TYPE_CN_INT16)
54
+ PROPERTY_PROFILE_TEMPERATURE_NORMAL = Property(UNIT_TEMPHUMCONTROL, 0x01, 0x0B, PdoType.TYPE_CN_INT16)
55
+ PROPERTY_PROFILE_TEMPERATURE_COOL = Property(UNIT_TEMPHUMCONTROL, 0x01, 0x0C, PdoType.TYPE_CN_INT16)
56
+
57
+ # 0=adaptive (the target follows the RMOT), 1=fixed (the target is the temperature of the active profile).
58
+ PROPERTY_TEMPERATURE_PROFILE_MODE = Property(UNIT_TEMPHUMCONTROL, 0x01, 0x08, PdoType.TYPE_CN_UINT8)
43
59
 
44
60
  # ComfoClime properties. These live on the ComfoClime node, not on the ventilation unit.
45
61
  PROPERTY_CLIME_AUTO_SEASON = Property(UNIT_CLIME_TEMPCONFIG, 0x01, 0x02, PdoType.TYPE_CN_UINT8)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aiocomfoconnect"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "aiocomfoconnect is an asyncio Python 3 library for communicating with a Zehnder ComfoAir Q350/450/600 ventilation system"
5
5
  authors = ["Michaël Arnauts <michael.arnauts@gmail.com>"]
6
6
  readme = "README.md"
File without changes