From 2aa8d86c049f91709abc9c555be04080ef31d6a1 Mon Sep 17 00:00:00 2001 From: Alex Brasetvik Date: Fri, 3 May 2024 01:21:24 +0200 Subject: [PATCH 1/4] Ruff as pre-commit hook --- .pre-commit-config.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b34815e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +default_language_version: + python: python3.12 +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.2 + hooks: + - id: ruff + args: + - --ignore=F401,E712 + - --fix + - id: ruff-format + args: + - --line-length=120 From f30ae739abced989c30e9fac1fa909a6295b34d8 Mon Sep 17 00:00:00 2001 From: Alex Brasetvik Date: Fri, 3 May 2024 01:21:35 +0200 Subject: [PATCH 2/4] Enable Ruff in VSCode --- .vscode/settings.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 99ab9f7..08ac480 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,4 +5,10 @@ "-c", "./pytest.ini" ], -} \ No newline at end of file + "ruff.enable": true, + "ruff.fixAll": true, + "ruff.lint.enable": true, + "ruff.format.args": [ + "--line-length=120" + ] +} From 21274c987f3741b754f9f972e6e264243d46e6a8 Mon Sep 17 00:00:00 2001 From: Alex Brasetvik Date: Fri, 3 May 2024 01:23:11 +0200 Subject: [PATCH 3/4] Two manual ruff exceptions --- xcomfort/connection.py | 2 +- xcomfort/room.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xcomfort/connection.py b/xcomfort/connection.py index 4e4fbd1..fa64d5f 100644 --- a/xcomfort/connection.py +++ b/xcomfort/connection.py @@ -27,7 +27,7 @@ def generateSalt(): return ''.join(secrets.choice(alphabet) for i in range(12)) -def hash(deviceId, authKey, salt): +def hash(deviceId, authKey, salt): # noqa: A001 - builtin shadow hasher = SHA256.new() hasher.update(deviceId) hasher.update(authKey) diff --git a/xcomfort/room.py b/xcomfort/room.py index 094e16b..a0f00f3 100644 --- a/xcomfort/room.py +++ b/xcomfort/room.py @@ -21,7 +21,7 @@ class RctState(Enum): Active = 2 class RctModeRange: - def __init__(self, min:float, max:float): + def __init__(self, min: float, max: float): # noqa: A002 self.Min = min self.Max = max From efed090d61d532fc660d16288307bbd61aa7eb1a Mon Sep 17 00:00:00 2001 From: Alex Brasetvik Date: Fri, 3 May 2024 01:29:39 +0200 Subject: [PATCH 4/4] Apply Ruff formatting and lint fixes All changes in this commit as generated by running `pre-commit run --all-files --hook-stage manual` --- setup.py | 8 +--- tests/test_light.py | 11 ++--- tests/test_rctouch.py | 11 ++++- xcomfort/bridge.py | 99 +++++++++++++++++++++++++----------------- xcomfort/comp.py | 2 + xcomfort/connection.py | 78 ++++++++++++++++----------------- xcomfort/constants.py | 4 +- xcomfort/devices.py | 29 ++++++++----- xcomfort/room.py | 84 +++++++++++++++++++++++++---------- 9 files changed, 192 insertions(+), 134 deletions(-) diff --git a/setup.py b/setup.py index a376a2a..8349bbd 100644 --- a/setup.py +++ b/setup.py @@ -19,10 +19,6 @@ "Operating System :: OS Independent", "Development Status :: 2 - Pre-Alpha", ], - python_requires='>=3.7', - install_requires=[ - "aiohttp", - "rx", - "pycryptodome" - ], + python_requires=">=3.7", + install_requires=["aiohttp", "rx", "pycryptodome"], ) diff --git a/tests/test_light.py b/tests/test_light.py index 9a1d53f..12d197b 100644 --- a/tests/test_light.py +++ b/tests/test_light.py @@ -2,7 +2,7 @@ from mock import patch, Mock import json from xcomfort.bridge import Bridge -from xcomfort.devices import (Light, LightState) +from xcomfort.devices import Light, LightState from xcomfort.constants import Messages @@ -27,10 +27,7 @@ async def test_light_switch_on(): def test_lightstate_switch_on(): device = Light(None, 1, "", True) - payload = { - "switch": True, - "dimmvalue": 50 - } + payload = {"switch": True, "dimmvalue": 50} device.handle_state(payload) @@ -41,9 +38,7 @@ def test_lightstate_switch_on(): def test_lightstate_switch_on_when_not_dimmable(): device = Light(None, 1, "", False) - payload = { - "switch": True - } + payload = {"switch": True} device.handle_state(payload) diff --git a/tests/test_rctouch.py b/tests/test_rctouch.py index 92e8f93..f661026 100644 --- a/tests/test_rctouch.py +++ b/tests/test_rctouch.py @@ -1,9 +1,16 @@ import pytest import json -from xcomfort.devices import (RcTouch) +from xcomfort.devices import RcTouch + def test_rctouchstate(): - payload = {"deviceId":17,"info":[{"text":"1222","type":2,"value":"20.9"},{"text":"1223","type":2,"icon":1,"value":"42.5"}]} + payload = { + "deviceId": 17, + "info": [ + {"text": "1222", "type": 2, "value": "20.9"}, + {"text": "1223", "type": 2, "icon": 1, "value": "42.5"}, + ], + } device = RcTouch(None, 1, "", "1") diff --git a/xcomfort/bridge.py b/xcomfort/bridge.py index c883ebd..f70b262 100644 --- a/xcomfort/bridge.py +++ b/xcomfort/bridge.py @@ -4,10 +4,21 @@ from enum import Enum from .connection import SecureBridgeConnection, setup_secure_connection from .constants import ComponentTypes, DeviceTypes, Messages -from .devices import (BridgeDevice, DoorSensor, Light, RcTouch, Heater, Rocker, Shade, WindowSensor) +from .devices import ( + BridgeDevice, + DoorSensor, + Light, + RcTouch, + Heater, + Rocker, + Shade, + WindowSensor, +) + # Some HA code relies on bridge having imported these: -from .room import Room, RoomState, RctMode, RctState, RctModeRange # noqa -from .comp import Comp, CompState # noqa +from .room import Room, RoomState, RctMode, RctState, RctModeRange # noqa +from .comp import Comp, CompState # noqa + class State(Enum): Uninitialized = 0 @@ -15,6 +26,7 @@ class State(Enum): Ready = 2 Closing = 10 + class Bridge: def __init__(self, ip_address: str, authkey: str, session=None): self.ip_address = ip_address @@ -30,11 +42,13 @@ def __init__(self, ip_address: str, authkey: str, session=None): self._closeSession = closeSession # Values determined from using setpoint slider in app. - self.rctsetpointallowedvalues = dict({ - RctMode.Cool: RctModeRange(5.0,20.0), - RctMode.Eco: RctModeRange(10.0,30.0), - RctMode.Comfort: RctModeRange(18.0,40.0) - }) + self.rctsetpointallowedvalues = dict( + { + RctMode.Cool: RctModeRange(5.0, 20.0), + RctMode.Eco: RctModeRange(10.0, 30.0), + RctMode.Comfort: RctModeRange(18.0, 40.0), + } + ) self._comps = {} self._devices = {} self._rooms = {} @@ -51,7 +65,7 @@ async def run(self): while self.state != State.Closing: try: - #self.logger(f"Connecting...") + # self.logger(f"Connecting...") await self._connect() await self.connection.pump() @@ -88,26 +102,26 @@ def _add_room(self, room): def _handle_SET_DEVICE_STATE(self, payload): try: - device = self._devices[payload['deviceId']] + device = self._devices[payload["deviceId"]] device.handle_state(payload) except KeyError: return def _handle_SET_STATE_INFO(self, payload): - for item in payload['item']: - if 'deviceId' in item: - deviceId = item['deviceId'] + for item in payload["item"]: + if "deviceId" in item: + deviceId = item["deviceId"] device = self._devices[deviceId] device.handle_state(item) - elif 'roomId' in item: - roomId = item['roomId'] + elif "roomId" in item: + roomId = item["roomId"] room = self._rooms[roomId] room.handle_state(item) - elif 'compId' in item: - compId = item['compId'] + elif "compId" in item: + compId = item["compId"] comp = self._comps[compId] comp.handle_state(item) @@ -115,15 +129,15 @@ def _handle_SET_STATE_INFO(self, payload): self.logger(f"Unknown state info: {payload}") def _create_comp_from_payload(self, payload): - comp_id = payload['compId'] - name = payload['name'] + comp_id = payload["compId"] + name = payload["name"] comp_type = payload["compType"] return Comp(self, comp_id, comp_type, name, payload) def _create_device_from_payload(self, payload): - device_id = payload['deviceId'] - name = payload['name'] + device_id = payload["deviceId"] + name = payload["name"] dev_type = payload["devType"] comp_id = payload["compId"] if dev_type in (DeviceTypes.ACTUATOR_SWITCH, DeviceTypes.ACTUATOR_DIMM): @@ -157,13 +171,13 @@ def _create_device_from_payload(self, payload): return BridgeDevice(self, device_id, name) def _create_room_from_payload(self, payload): - room_id = payload['roomId'] - name = payload['name'] + room_id = payload["roomId"] + name = payload["name"] return Room(self, room_id, name) def _handle_comp_payload(self, payload): - comp_id = payload['compId'] + comp_id = payload["compId"] comp = self._comps.get(comp_id) @@ -178,7 +192,7 @@ def _handle_comp_payload(self, payload): comp.handle_state(payload) def _handle_device_payload(self, payload): - device_id = payload['deviceId'] + device_id = payload["deviceId"] device = self._devices.get(device_id) @@ -193,7 +207,7 @@ def _handle_device_payload(self, payload): device.handle_state(payload) def _handle_room_payload(self, payload): - room_id = payload['roomId'] + room_id = payload["roomId"] room = self._rooms.get(room_id) @@ -208,31 +222,31 @@ def _handle_room_payload(self, payload): room.handle_state(payload) def _handle_SET_ALL_DATA(self, payload): - if 'lastItem' in payload: + if "lastItem" in payload: self.state = State.Ready - if 'devices' in payload: - for device_payload in payload['devices']: + if "devices" in payload: + for device_payload in payload["devices"]: try: self._handle_device_payload(device_payload) except Exception as e: self.logger(f"Failed to handle device payload: {str(e)}") - if 'comps' in payload: + if "comps" in payload: for comp_payload in payload["comps"]: try: self._handle_comp_payload(comp_payload) except Exception as e: self.logger(f"Failed to handle comp payload: {str(e)}") - if 'rooms' in payload: + if "rooms" in payload: for room_payload in payload["rooms"]: try: self._handle_room_payload(room_payload) except Exception as e: self.logger(f"Failed to handle room payload: {str(e)}") - if 'roomHeating' in payload: + if "roomHeating" in payload: for room_payload in payload["roomHeating"]: try: self._handle_room_payload(room_payload) @@ -244,25 +258,28 @@ def _handle_UNKNOWN(self, message_type, payload): pass def _onMessage(self, message): - - if 'payload' in message: + if "payload" in message: # self.logger(f"Message: {message}") - message_type = Messages(message['type_int']) - method_name = '_handle_' + message_type.name + message_type = Messages(message["type_int"]) + method_name = "_handle_" + message_type.name - method = getattr(self, method_name, - lambda p: self._handle_UNKNOWN(message_type, p)) + method = getattr( + self, method_name, lambda p: self._handle_UNKNOWN(message_type, p) + ) try: - method(message['payload']) + method(message["payload"]) except Exception as e: self.logger(f"Unknown error with: {method_name}: {str(e)}") else: self.logger(f"Not known: {message}") async def _connect(self): - self.connection = await setup_secure_connection(self._session, self.ip_address, self.authkey) + self.connection = await setup_secure_connection( + self._session, self.ip_address, self.authkey + ) self.connection_subscription = self.connection.messages.subscribe( - self._onMessage) + self._onMessage + ) async def close(self): self.state = State.Closing diff --git a/xcomfort/comp.py b/xcomfort/comp.py index 5931758..7b25968 100644 --- a/xcomfort/comp.py +++ b/xcomfort/comp.py @@ -1,5 +1,6 @@ import rx + class CompState: def __init__(self, raw): self.raw = raw @@ -9,6 +10,7 @@ def __str__(self): __repr__ = __str__ + class Comp: def __init__(self, bridge, comp_id, comp_type, name: str, payload: dict): self.bridge = bridge diff --git a/xcomfort/connection.py b/xcomfort/connection.py index fa64d5f..4fcab5b 100644 --- a/xcomfort/connection.py +++ b/xcomfort/connection.py @@ -24,7 +24,7 @@ class ConnectionState(IntEnum): def generateSalt(): alphabet = string.ascii_letters + string.digits - return ''.join(secrets.choice(alphabet) for i in range(12)) + return "".join(secrets.choice(alphabet) for i in range(12)) def hash(deviceId, authKey, salt): # noqa: A001 - builtin shadow @@ -42,7 +42,7 @@ def hash(deviceId, authKey, salt): # noqa: A001 - builtin shadow def _pad_string(value): length = len(value) pad_size = AES.block_size - (length % AES.block_size) - return value.ljust(length + pad_size, b'\x00') + return value.ljust(length + pad_size, b"\x00") async def setup_secure_connection(session, ip_address, authkey): @@ -62,33 +62,36 @@ async def __send(ws, data): try: msg = await __receive(ws) - #{'type_int': 0, 'ref': -1, 'info': 'no client-connection available (all used)!'} - if msg['type_int'] == Messages.NACK: + # {'type_int': 0, 'ref': -1, 'info': 'no client-connection available (all used)!'} + if msg["type_int"] == Messages.NACK: raise Exception(msg["info"]) - deviceId = msg['payload']['device_id'] - connectionId = msg['payload']['connection_id'] - - await __send(ws, { - "type_int": 11, - "mc": -1, - "payload": { - "client_type": "shl-app", - "client_id": "c956e43f999f8004", - "client_version": "3.0.0", - "connection_id": connectionId - } - }) + deviceId = msg["payload"]["device_id"] + connectionId = msg["payload"]["connection_id"] + + await __send( + ws, + { + "type_int": 11, + "mc": -1, + "payload": { + "client_type": "shl-app", + "client_id": "c956e43f999f8004", + "client_version": "3.0.0", + "connection_id": connectionId, + }, + }, + ) msg = await __receive(ws) - if msg['type_int'] == Messages.CONNECTION_DECLINED: + if msg["type_int"] == Messages.CONNECTION_DECLINED: raise Exception(msg["payload"]["error_message"]) await __send(ws, {"type_int": 14, "mc": -1}) msg = await __receive(ws) - publicKey = msg['payload']['public_key'] + publicKey = msg["payload"]["public_key"] rsa = RSA.import_key(publicKey) @@ -96,8 +99,7 @@ async def __send(ws, data): iv = get_random_bytes(16) cipher = PKCS1_v1_5.new(rsa) - secret = b64encode(cipher.encrypt( - (key.hex() + ":::" + iv.hex()).encode())) + secret = b64encode(cipher.encrypt((key.hex() + ":::" + iv.hex()).encode())) # print(f"secret: {secret}") secret = secret.decode() # print(f"secret: {secret}") @@ -110,24 +112,22 @@ async def __send(ws, data): msg = await connection.receive() - if msg['type_int'] != 17: - raise Exception('Failed to establish secure connection') + if msg["type_int"] != 17: + raise Exception("Failed to establish secure connection") salt = generateSalt() password = hash(deviceId.encode(), authkey.encode(), salt.encode()) - await connection.send_message(30, { - "username": "default", - "password": password, - "salt": salt - }) + await connection.send_message( + 30, {"username": "default", "password": password, "salt": salt} + ) msg = await connection.receive() - if msg['type_int'] != 32: + if msg["type_int"] != 32: raise Exception("Login failed") - token = msg['payload']['token'] + token = msg["payload"]["token"] await connection.send_message(33, {"token": token}) # {"type_int":34,"mc":-1,"payload":{"valid":true,"remaining":8640000}} @@ -138,10 +138,10 @@ async def __send(ws, data): msg = await connection.receive() - if msg['type_int'] != 38: + if msg["type_int"] != 38: raise Exception("Login failed") - token = msg['payload']['token'] + token = msg["payload"]["token"] await connection.send_message(33, {"token": token}) @@ -165,9 +165,7 @@ def __init__(self, websocket, key, iv, device_id): self._messageSubject = rx.subject.Subject() self.mc = 0 - self.messages = self._messageSubject.pipe( - ops.as_observable() - ) + self.messages = self._messageSubject.pipe(ops.as_observable()) def __cipher(self): return AES.new(self.key, AES.MODE_CBC, self.iv) @@ -175,7 +173,7 @@ def __cipher(self): def __decrypt(self, data): ct = b64decode(data) data = self.__cipher().decrypt(ct) - data = data.rstrip(b'\x00') + data = data.rstrip(b"\x00") # print(f"Received decrypted: {data}") if not data: @@ -194,11 +192,11 @@ async def pump(self): if msg.type == aiohttp.WSMsgType.TEXT: result = self.__decrypt(msg.data) - if 'mc' in result: + if "mc" in result: # ACK - await self.send({"type_int": 1, "ref": result['mc']}) + await self.send({"type_int": 1, "ref": result["mc"]}) - if 'payload' in result: + if "payload" in result: self._messageSubject.on_next(result) elif msg.type == aiohttp.WSMsgType.ERROR: @@ -225,5 +223,5 @@ async def send(self, data): # print(f"Send raw: {msg}") msg = _pad_string(msg.encode()) msg = self.__cipher().encrypt(msg) - msg = b64encode(msg).decode() + '\u0004' + msg = b64encode(msg).decode() + "\u0004" await self.websocket.send_str(msg) diff --git a/xcomfort/constants.py b/xcomfort/constants.py index 64d366a..23378bb 100644 --- a/xcomfort/constants.py +++ b/xcomfort/constants.py @@ -1,6 +1,7 @@ from enum import IntEnum -class Messages(IntEnum): + +class Messages(IntEnum): NACK = 0 ACK = 1 HEARTBEAT = 2 @@ -99,6 +100,7 @@ class Messages(IntEnum): NACK_INFO_DEVICE_NOT_DIMMABLE = -99 NACK_INFO_UNKNOWN_DEVICE = -100 + class ShadeOperationState(IntEnum): OPEN = 0 CLOSE = 1 diff --git a/xcomfort/devices.py b/xcomfort/devices.py index fed3212..8c50483 100644 --- a/xcomfort/devices.py +++ b/xcomfort/devices.py @@ -3,6 +3,7 @@ import rx from .constants import Messages, ShadeOperationState + class DeviceState: def __init__(self, payload): self.raw = payload @@ -10,6 +11,7 @@ def __init__(self, payload): def __str__(self): return f"DeviceState({self.raw})" + class LightState(DeviceState): def __init__(self, switch, dimmvalue, payload): DeviceState.__init__(self, payload) @@ -21,6 +23,7 @@ def __str__(self): __repr__ = __str__ + class RcTouchState(DeviceState): def __init__(self, temperature, humidity, payload): DeviceState.__init__(self, payload) @@ -32,6 +35,7 @@ def __str__(self): __repr__ = __str__ + class HeaterState(DeviceState): def __init__(self, payload): DeviceState.__init__(self, payload) @@ -43,7 +47,6 @@ def __str__(self): class ShadeState(DeviceState): - def __init__(self): self.raw = {} self.current_state: int | None = None @@ -74,6 +77,7 @@ def is_closed(self) -> bool | None: def __str__(self) -> str: return f"ShadeState(current_state={self.current_state} is_safety_enabled={self.is_safety_enabled} position={self.position} raw={self.raw})" + class BridgeDevice: def __init__(self, bridge, device_id, name): self.bridge = bridge @@ -98,12 +102,11 @@ def interpret_dimmvalue_from_payload(self, switch, payload): if not switch: return self.state.value.dimmvalue if self.state.value is not None else 99 - - return payload['dimmvalue'] + return payload["dimmvalue"] def handle_state(self, payload): - switch = payload['switch'] + switch = payload["switch"] dimmvalue = self.interpret_dimmvalue_from_payload(switch, payload) self.state.on_next(LightState(switch, dimmvalue, payload)) @@ -116,7 +119,7 @@ async def dimm(self, value: int): await self.bridge.slide_device(self.device_id, {"dimmvalue": value}) def __str__(self): - return f"Light({self.device_id}, \"{self.name}\", dimmable: {self.dimmable}, state:{self.state.value})" + return f'Light({self.device_id}, "{self.name}", dimmable: {self.dimmable}, state:{self.state.value})' __repr__ = __str__ @@ -131,12 +134,12 @@ def handle_state(self, payload): print(f"RcTouchState::: {payload}") temperature = None humidity = None - if 'info' in payload: - for info in payload['info']: - if info['text'] == "1222": - temperature = float(info['value']) - if info['text'] == "1223": - humidity = float(info['value']) + if "info" in payload: + for info in payload["info"]: + if info["text"] == "1222": + temperature = float(info["value"]) + if info["text"] == "1223": + humidity = float(info["value"]) if temperature is not None and humidity is not None: self.state.on_next(RcTouchState(temperature, humidity, payload)) @@ -148,6 +151,7 @@ def __init__(self, bridge, device_id, name, comp_id): self.comp_id = comp_id + class Shade(BridgeDevice): def __init__(self, bridge, device_id, name, comp_id, payload): BridgeDevice.__init__(self, bridge, device_id, name) @@ -180,7 +184,8 @@ async def send_state(self, state, **kw): return await self.bridge.send_message( - Messages.SET_DEVICE_SHADING_STATE, {"deviceId": self.device_id, "state": state, **kw} + Messages.SET_DEVICE_SHADING_STATE, + {"deviceId": self.device_id, "state": state, **kw}, ) async def move_down(self): diff --git a/xcomfort/room.py b/xcomfort/room.py index a0f00f3..0c9273b 100644 --- a/xcomfort/room.py +++ b/xcomfort/room.py @@ -8,25 +8,38 @@ from enum import Enum from .connection import SecureBridgeConnection, setup_secure_connection from .constants import Messages -from .devices import (BridgeDevice, Light, RcTouch, Heater, Shade) +from .devices import BridgeDevice, Light, RcTouch, Heater, Shade + class RctMode(Enum): Cool = 1 Eco = 2 Comfort = 3 + class RctState(Enum): Idle = 0 Auto = 1 Active = 2 + class RctModeRange: def __init__(self, min: float, max: float): # noqa: A002 self.Min = min self.Max = max + class RoomState: - def __init__(self, setpoint, temperature, humidity, power, mode:RctMode, state:RctState, raw): + def __init__( + self, + setpoint, + temperature, + humidity, + power, + mode: RctMode, + state: RctState, + raw, + ): self.setpoint = setpoint self.temperature = temperature self.humidity = humidity @@ -40,6 +53,7 @@ def __str__(self): __repr__ = __str__ + class Room: def __init__(self, bridge, room_id, name: str): self.bridge = bridge @@ -57,32 +71,37 @@ def handle_state(self, payload): old_state.raw.update(payload) payload = old_state.raw - setpoint = payload.get('setpoint', None) - temperature = payload.get('temp', None) - humidity = payload.get('humidity', None) - power = payload.get('power', 0.0) + setpoint = payload.get("setpoint", None) + temperature = payload.get("temp", None) + humidity = payload.get("humidity", None) + power = payload.get("power", 0.0) - if 'currentMode' in payload: # When handling from _SET_ALL_DATA - mode = RctMode(payload.get('currentMode', None)) - if 'mode' in payload: # When handling from _SET_STATE_INFO - mode = RctMode(payload.get('mode', None)) + if "currentMode" in payload: # When handling from _SET_ALL_DATA + mode = RctMode(payload.get("currentMode", None)) + if "mode" in payload: # When handling from _SET_STATE_INFO + mode = RctMode(payload.get("mode", None)) # When handling from _SET_ALL_DATA, we get the setpoints for each mode/preset # Store these for later use - if 'modes' in payload: + if "modes" in payload: for mode in payload["modes"]: self.modesetpoints[RctMode(mode["mode"])] = float(mode["value"]) - if 'state' in payload: - currentstate = RctState(payload.get('state', None)) + if "state" in payload: + currentstate = RctState(payload.get("state", None)) - self.state.on_next(RoomState(setpoint,temperature,humidity,power,mode,currentstate,payload)) + self.state.on_next( + RoomState( + setpoint, temperature, humidity, power, mode, currentstate, payload + ) + ) async def set_target_temperature(self, setpoint: float): - # Validate that new setpoint is within allowed ranges. # if above/below allowed values, set to the edge value - setpointrange = self.bridge.rctsetpointallowedvalues[RctMode(self.state.value.mode)] + setpointrange = self.bridge.rctsetpointallowedvalues[ + RctMode(self.state.value.mode) + ] if setpointrange.Max < setpoint: setpoint = setpointrange.Max @@ -93,16 +112,33 @@ async def set_target_temperature(self, setpoint: float): # Store new setpoint for current mode self.modesetpoints[self.state.value.mode.value] = setpoint - await self.bridge.send_message(Messages.SET_HEATING_STATE, {"roomId":self.room_id,"mode":self.state.value.mode.value,"state":self.state.value.rctstate.value,"setpoint":setpoint,"confirmed":False}) - - async def set_mode(self, mode:RctMode): - - #Find setpoint for the mode we are about to set, and use that - #When transmitting heating_state message. + await self.bridge.send_message( + Messages.SET_HEATING_STATE, + { + "roomId": self.room_id, + "mode": self.state.value.mode.value, + "state": self.state.value.rctstate.value, + "setpoint": setpoint, + "confirmed": False, + }, + ) + + async def set_mode(self, mode: RctMode): + # Find setpoint for the mode we are about to set, and use that + # When transmitting heating_state message. newsetpoint = self.modesetpoints.get(mode) - await self.bridge.send_message(Messages.SET_HEATING_STATE, {"roomId":self.room_id,"mode":mode.value,"state":self.state.value.rctstate.value,"setpoint":newsetpoint,"confirmed":False}) + await self.bridge.send_message( + Messages.SET_HEATING_STATE, + { + "roomId": self.room_id, + "mode": mode.value, + "state": self.state.value.rctstate.value, + "setpoint": newsetpoint, + "confirmed": False, + }, + ) def __str__(self): - return f"Room({self.room_id}, \"{self.name}\")" + return f'Room({self.room_id}, "{self.name}")' __repr__ = __str__