From bf168621f0a961a969762808bcfb636db8a2ce29 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 19 Mar 2024 20:32:34 +0000 Subject: [PATCH 01/11] Initial attempt --- HCDevice.py | 55 +++++++++++++++++++++++++++++++++++++++++------------ hc2mqtt | 21 ++++++++++++++------ 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 5d4ef86..2829faf 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -92,6 +92,36 @@ class HCDevice: return result + # Based on PR submitted https://github.com/Skons/hcpy/pull/1/files#diff-f68bc58ad15ebfcb2b6ceca7c538e00790a6093c67c7c84ebcefb0c9756d3c0eR112-R139 + def test_program_data(self, data): + if 'program' not in data: + raise Exception("{self.name}. Message data invalid, no program specified.") + + if isinstance(data['program'], str): + try: + data['program'] = int(data['program']) + except Exception as e: + raise Exception("{self.name}. Message data invalid, UID in 'program' must be an integer.") + elif isinstance(data['program'], int) is False: + raise Exception("{self.name}. Message data invalid. UID in 'program' must be an integer.") + + uid = str(data['program']) + if uid not in self.features: + raise Exception(f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid for this device.") + + feature = self.features[uid] + # Diswasher is Dishcare.Dishwasher.Program.{name} + # Hood is Cooking.Common.Program.{name} + # May also be in the format BSH.Common.Program.Favorite.001 + if ".Program." not in feature['name']: + raise Exception(f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid program.") + + if 'options' in data: + ## TODO: Check to see if options is mandatory + for option_uid in data['options']: + if str(option_uid) not in self.features: + raise Exception(f"{self.name}. Unable to configure appliance. Option UID {uid} is not valid for this device.") + # Test the feature of an appliance agains a data object def test_feature(self, data): if "uid" not in data: @@ -156,8 +186,6 @@ class HCDevice: f"The value must be an integer in the range {min} and {max}." ) - return True - def recv(self): try: buf = self.ws.recv() @@ -198,10 +226,13 @@ class HCDevice: if data is not None: if action == "POST": - if self.test_feature(data) is False: - return - msg["data"] = [data] - else: + if 'resource' == '/ro/values': + # Raises exceptions on failure + self.test_feature(data) + elif 'resource' == '/ro/activeProgram': + # Raises exception on failure + self.test_program_data(data) + msg["data"] = [data] try: @@ -297,12 +328,12 @@ class HCDevice: # print(self.name, now(), "services", self.services) # we should figure out which ones to query now - # if "iz" in self.services: - # self.get("/iz/info", version=self.services["iz"]["version"]) - # if "ni" in self.services: - # self.get("/ni/info", version=self.services["ni"]["version"]) - # if "ei" in self.services: - # self.get("/ei/deviceReady", version=self.services["ei"]["version"], action="NOTIFY") + # if "iz" in self.services: + # self.get("/iz/info", version=self.services["iz"]["version"]) + # if "ni" in self.services: + # self.get("/ni/info", version=self.services["ni"]["version"]) + # if "ei" in self.services: + # self.get("/ei/deviceReady", version=self.services["ei"]["version"], action="NOTIFY") # self.get("/if/info") diff --git a/hc2mqtt b/hc2mqtt index 11676d3..bd4f0f8 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -92,10 +92,19 @@ def client_connect(client, device, mqtt_topic): mqtt_state = msg.payload.decode() mqtt_topic = msg.topic.split("/") print(now(), f"received mqtt message {mqtt_state}") + + if len(mqtt_topic) >= 2: + device_name = mqtt_topic[-2] + topic = mqtt_topic[-1] + else: + raise Exception(f"Invalid mqtt topic {msg.topic}") + try: msg = json.loads(mqtt_state) - if "uid" in msg: - dev[mqtt_topic[-2]].get("/ro/values", 1, "POST", msg) + if topic == 'set' and 'uid' in msg: + dev[device_name].get("/ro/values", 1, "POST", msg) + elif topic == 'activeProgram': + dev[device_name].get("/ro/activeProgram", 1, "POST", msg) else: raise Exception(f"Payload {msg} is not correctly formatted") except Exception as e: @@ -147,10 +156,10 @@ def client_connect(client, device, mqtt_topic): if value is None: continue - # new_topic = topics[topic] - # if new_topic == "remaining": - # state["remainingseconds"] = value - # value = "%d:%02d" % (value / 60 / 60, (value / 60) % 60) + # new_topic = topics[topic] + # if new_topic == "remaining": + # state["remainingseconds"] = value + # value = "%d:%02d" % (value / 60 / 60, (value / 60) % 60) new_topic = device_topics[topic] state[new_topic] = value From 8f54490dbbfe4d316ed4cf6e142d953d0e5cecb4 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 19 Mar 2024 20:48:48 +0000 Subject: [PATCH 02/11] Working activeProgram messages --- HCDevice.py | 14 +------------- hc2mqtt | 15 ++++++++++----- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 2829faf..a50d9b1 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -117,7 +117,6 @@ class HCDevice: raise Exception(f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid program.") if 'options' in data: - ## TODO: Check to see if options is mandatory for option_uid in data['options']: if str(option_uid) not in self.features: raise Exception(f"{self.name}. Unable to configure appliance. Option UID {uid} is not valid for this device.") @@ -233,7 +232,7 @@ class HCDevice: # Raises exception on failure self.test_program_data(data) - msg["data"] = [data] + msg["data"] = [data] try: self.ws.send(msg) @@ -325,17 +324,6 @@ class HCDevice: self.services[service["service"]] = { "version": service["version"], } - # print(self.name, now(), "services", self.services) - - # we should figure out which ones to query now - # if "iz" in self.services: - # self.get("/iz/info", version=self.services["iz"]["version"]) - # if "ni" in self.services: - # self.get("/ni/info", version=self.services["ni"]["version"]) - # if "ei" in self.services: - # self.get("/ei/deviceReady", version=self.services["ei"]["version"], action="NOTIFY") - - # self.get("/if/info") else: print(now(), self.name, "Unknown", msg) diff --git a/hc2mqtt b/hc2mqtt index bd4f0f8..b3ea7d3 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -112,8 +112,12 @@ def client_connect(client, device, mqtt_topic): host = device["host"] device_topics = topics + active_program = False for value in device["features"]: + # If the device has the ActiveProgram feature it allows programs to be started and scheduled via /ro/activeProgram + if "BSH.Common.Root.ActiveProgram" == device['features'][value]['name']: + active_program = True if ( "access" in device["features"][value] and "read" in device["features"][value]["access"].lower() @@ -132,6 +136,12 @@ def client_connect(client, device, mqtt_topic): mqtt_set_topic = mqtt_topic + "/set" print(now(), device["name"], f"set topic: {mqtt_set_topic}") client.subscribe(mqtt_set_topic) + + if active_program: + mqtt_active_program_topic = mqtt_topic + "/activeProgram" + print(now(), device["name"], f"program topic: {mqtt_active_program_topic}") + client.subscribe(mqtt_active_program_topic) + client.on_message = on_message while True: @@ -156,11 +166,6 @@ def client_connect(client, device, mqtt_topic): if value is None: continue - # new_topic = topics[topic] - # if new_topic == "remaining": - # state["remainingseconds"] = value - # value = "%d:%02d" % (value / 60 / 60, (value / 60) % 60) - new_topic = device_topics[topic] state[new_topic] = value update = True From 0416926e8e48ebffcfaa42177d42aead82bb48a5 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 19 Mar 2024 20:55:14 +0000 Subject: [PATCH 03/11] Add examples to readme --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 06fc69b..60f05a0 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,24 @@ Synchronize with time server, `false` is disabled {"uid":547,"value":false} ``` +### Starting a Program + +The MQTT client listens on /{prefix}/{devicename}/activeProgram for a JSON message to start a program. The JSON should be in the following format: + +```json +{"program":{uid},"options":[{"uid":{uid},"value":{value}}]} +``` + +To start a dishwasher on eco mode (`Dishcare.Dishwasher.Program.Eco50`): +```json +{"program":8196} +``` + +To start a dishwasher on eco mode in 10 miuntes (`BSH.Common.Option.StartInRelative`): +```json +{"program":8196,"options":[{"uid":558,"value":600}]} +``` + ## FRIDA tools Moved to [`README-frida.md`](README-frida.md) From 2277c7c36f0b7aa8fbae7ad03ef033ff0411347d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 21:00:23 +0000 Subject: [PATCH 04/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- HCDevice.py | 40 +++++++++++++++++++++++++--------------- hc2mqtt | 6 +++--- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index a50d9b1..80ab2db 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -94,32 +94,42 @@ class HCDevice: # Based on PR submitted https://github.com/Skons/hcpy/pull/1/files#diff-f68bc58ad15ebfcb2b6ceca7c538e00790a6093c67c7c84ebcefb0c9756d3c0eR112-R139 def test_program_data(self, data): - if 'program' not in data: + if "program" not in data: raise Exception("{self.name}. Message data invalid, no program specified.") - if isinstance(data['program'], str): + if isinstance(data["program"], str): try: - data['program'] = int(data['program']) + data["program"] = int(data["program"]) except Exception as e: - raise Exception("{self.name}. Message data invalid, UID in 'program' must be an integer.") - elif isinstance(data['program'], int) is False: - raise Exception("{self.name}. Message data invalid. UID in 'program' must be an integer.") + raise Exception( + "{self.name}. Message data invalid, UID in 'program' must be an integer." + ) + elif isinstance(data["program"], int) is False: + raise Exception( + "{self.name}. Message data invalid. UID in 'program' must be an integer." + ) - uid = str(data['program']) + uid = str(data["program"]) if uid not in self.features: - raise Exception(f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid for this device.") + raise Exception( + f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid for this device." + ) feature = self.features[uid] # Diswasher is Dishcare.Dishwasher.Program.{name} # Hood is Cooking.Common.Program.{name} # May also be in the format BSH.Common.Program.Favorite.001 - if ".Program." not in feature['name']: - raise Exception(f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid program.") + if ".Program." not in feature["name"]: + raise Exception( + f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid program." + ) - if 'options' in data: - for option_uid in data['options']: + if "options" in data: + for option_uid in data["options"]: if str(option_uid) not in self.features: - raise Exception(f"{self.name}. Unable to configure appliance. Option UID {uid} is not valid for this device.") + raise Exception( + f"{self.name}. Unable to configure appliance. Option UID {uid} is not valid for this device." + ) # Test the feature of an appliance agains a data object def test_feature(self, data): @@ -225,10 +235,10 @@ class HCDevice: if data is not None: if action == "POST": - if 'resource' == '/ro/values': + if "resource" == "/ro/values": # Raises exceptions on failure self.test_feature(data) - elif 'resource' == '/ro/activeProgram': + elif "resource" == "/ro/activeProgram": # Raises exception on failure self.test_program_data(data) diff --git a/hc2mqtt b/hc2mqtt index b3ea7d3..7d5b1ae 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -101,9 +101,9 @@ def client_connect(client, device, mqtt_topic): try: msg = json.loads(mqtt_state) - if topic == 'set' and 'uid' in msg: + if topic == "set" and "uid" in msg: dev[device_name].get("/ro/values", 1, "POST", msg) - elif topic == 'activeProgram': + elif topic == "activeProgram": dev[device_name].get("/ro/activeProgram", 1, "POST", msg) else: raise Exception(f"Payload {msg} is not correctly formatted") @@ -116,7 +116,7 @@ def client_connect(client, device, mqtt_topic): for value in device["features"]: # If the device has the ActiveProgram feature it allows programs to be started and scheduled via /ro/activeProgram - if "BSH.Common.Root.ActiveProgram" == device['features'][value]['name']: + if "BSH.Common.Root.ActiveProgram" == device["features"][value]["name"]: active_program = True if ( "access" in device["features"][value] From ad2c7f11ea720723aaf79af298051c2de281136f Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 19 Mar 2024 21:11:40 +0000 Subject: [PATCH 05/11] Fix flake8 requirements --- HCDevice.py | 13 ++++++++----- hc2mqtt | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 80ab2db..ad572e0 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -92,7 +92,7 @@ class HCDevice: return result - # Based on PR submitted https://github.com/Skons/hcpy/pull/1/files#diff-f68bc58ad15ebfcb2b6ceca7c538e00790a6093c67c7c84ebcefb0c9756d3c0eR112-R139 + # Based on PR submitted https://github.com/Skons/hcpy/pull/1 def test_program_data(self, data): if "program" not in data: raise Exception("{self.name}. Message data invalid, no program specified.") @@ -100,7 +100,7 @@ class HCDevice: if isinstance(data["program"], str): try: data["program"] = int(data["program"]) - except Exception as e: + except Exception: raise Exception( "{self.name}. Message data invalid, UID in 'program' must be an integer." ) @@ -112,7 +112,8 @@ class HCDevice: uid = str(data["program"]) if uid not in self.features: raise Exception( - f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid for this device." + f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid" + "for this device." ) feature = self.features[uid] @@ -121,14 +122,16 @@ class HCDevice: # May also be in the format BSH.Common.Program.Favorite.001 if ".Program." not in feature["name"]: raise Exception( - f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid program." + f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid" + "program." ) if "options" in data: for option_uid in data["options"]: if str(option_uid) not in self.features: raise Exception( - f"{self.name}. Unable to configure appliance. Option UID {uid} is not valid for this device." + f"{self.name}. Unable to configure appliance. Option UID {uid} is not" + "valid for this device." ) # Test the feature of an appliance agains a data object diff --git a/hc2mqtt b/hc2mqtt index 7d5b1ae..a6b3116 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -115,7 +115,8 @@ def client_connect(client, device, mqtt_topic): active_program = False for value in device["features"]: - # If the device has the ActiveProgram feature it allows programs to be started and scheduled via /ro/activeProgram + # If the device has the ActiveProgram feature it allows programs to be started and + # scheduled via /ro/activeProgram if "BSH.Common.Root.ActiveProgram" == device["features"][value]["name"]: active_program = True if ( From b1f20b82b50e274bb3fcd255f6ec62d66fe86c69 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 21:11:56 +0000 Subject: [PATCH 06/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- HCDevice.py | 6 +++--- hc2mqtt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index ad572e0..d8835b2 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -113,7 +113,7 @@ class HCDevice: if uid not in self.features: raise Exception( f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid" - "for this device." + "for this device." ) feature = self.features[uid] @@ -123,7 +123,7 @@ class HCDevice: if ".Program." not in feature["name"]: raise Exception( f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid" - "program." + "program." ) if "options" in data: @@ -131,7 +131,7 @@ class HCDevice: if str(option_uid) not in self.features: raise Exception( f"{self.name}. Unable to configure appliance. Option UID {uid} is not" - "valid for this device." + "valid for this device." ) # Test the feature of an appliance agains a data object diff --git a/hc2mqtt b/hc2mqtt index a6b3116..6cbad0a 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -115,7 +115,7 @@ def client_connect(client, device, mqtt_topic): active_program = False for value in device["features"]: - # If the device has the ActiveProgram feature it allows programs to be started and + # If the device has the ActiveProgram feature it allows programs to be started and # scheduled via /ro/activeProgram if "BSH.Common.Root.ActiveProgram" == device["features"][value]["name"]: active_program = True From 4d90b0cf418820a082a980d3c2faf2f3eb801404 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 19 Mar 2024 21:40:47 +0000 Subject: [PATCH 07/11] Use relative paths to work in docker and outside --- Dockerfile | 2 +- README.md | 4 ++-- compose.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 59f3582..7606fb7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,4 @@ RUN apt-get update && \ COPY hc2mqtt hc-login HCDevice.py HCSocket.py HCxml2json.py ./ ENTRYPOINT ["python3"] -CMD ["hc2mqtt", "--config", "/config/config.ini"] +CMD ["hc2mqtt", "--config", "./config/config.ini"] diff --git a/README.md b/README.md index 06fc69b..89b63f3 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ your mDNS or DNS server resolves the names correctly. ## Home Connect to MQTT -Use the following config/config.ini example: +Use the following ./config/config.ini example: ``` -devices_file = "/config/devices.json" +devices_file = "./config/devices.json" mqtt_host = "localhost" mqtt_username = "mqtt" mqtt_password = "password" diff --git a/compose.yaml b/compose.yaml index 270ec8a..fc4e35b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,4 +4,4 @@ services: build: . restart: on-failure volumes: - - ./config:/config + - ./config:/app/config From 657503455159be9d8adfa4949c2365de294a584d Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 19 Mar 2024 21:44:05 +0000 Subject: [PATCH 08/11] Fix clone url and add comment about docker --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06fc69b..00b90cb 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ To avoid running into issues later with your default python installs, it's recom ```bash python3 -m venv venv source venv/bin/activate -git clone https://github.com/osresearch/hcpy +git clone https://github.com/hcpy2-0/hcpy cd hcpy pip3 install -r requirements.txt ``` - Install the Python dependencies; the `sslpsk` one is a little weird and we might need to revisit it later. +Alternatively an environment can be built with docker and/or docker-compose which has the necessary dependencies. ### For Mac Users Installing `sslpsk` needs some extra steps: From f127fce93f184f36e224dff471950ced0342275d Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Wed, 20 Mar 2024 11:56:52 +0000 Subject: [PATCH 09/11] Cleanup error checking and exceptions --- HCDevice.py | 64 +++++++++++++++++++++++++---------------------------- hc2mqtt | 37 +++++++++++++++++-------------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index d8835b2..4945042 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -95,25 +95,20 @@ class HCDevice: # Based on PR submitted https://github.com/Skons/hcpy/pull/1 def test_program_data(self, data): if "program" not in data: - raise Exception("{self.name}. Message data invalid, no program specified.") + raise TypeError("Message data invalid, no program specified.") - if isinstance(data["program"], str): - try: - data["program"] = int(data["program"]) - except Exception: - raise Exception( - "{self.name}. Message data invalid, UID in 'program' must be an integer." - ) - elif isinstance(data["program"], int) is False: - raise Exception( - "{self.name}. Message data invalid. UID in 'program' must be an integer." + + if isinstance(data["program"],int) is False: + raise TypeError( + f"Message data invalid, UID in 'program' must be an integer." ) + # devices.json stores UID as string uid = str(data["program"]) if uid not in self.features: - raise Exception( - f"{self.name}. Unable to configure appliance. Program UID {uid} is not valid" - "for this device." + raise ValueError( + f"Unable to configure appliance. Program UID {uid} is not valid" + " for this device." ) feature = self.features[uid] @@ -121,34 +116,34 @@ class HCDevice: # Hood is Cooking.Common.Program.{name} # May also be in the format BSH.Common.Program.Favorite.001 if ".Program." not in feature["name"]: - raise Exception( - f"{self.name}. Unable to configure appliance. Program UID {uid} is not a valid" - "program." + raise ValueError( + f"Unable to configure appliance. Program UID {uid} is not a valid" + f" program - {feature['name']}." ) if "options" in data: for option_uid in data["options"]: if str(option_uid) not in self.features: - raise Exception( - f"{self.name}. Unable to configure appliance. Option UID {uid} is not" - "valid for this device." + raise ValueError( + f"Unable to configure appliance. Option UID {uid} is not" + " valid for this device." ) # Test the feature of an appliance agains a data object def test_feature(self, data): if "uid" not in data: - raise Exception("{self.name}. Unable to configure appliance. UID is required.") + raise Exception("Unable to configure appliance. UID is required.") if isinstance(data["uid"], int) is False: - raise Exception("{self.name}. Unable to configure appliance. UID must be an integer.") + raise Exception("Unable to configure appliance. UID must be an integer.") if "value" not in data: - raise Exception("{self.name}. Unable to configure appliance. Value is required.") + raise Exception("Unable to configure appliance. Value is required.") # Check if the uid is present for this appliance uid = str(data["uid"]) if uid not in self.features: - raise Exception(f"{self.name}. Unable to configure appliance. UID {uid} is not valid.") + raise Exception(f"Unable to configure appliance. UID {uid} is not valid.") feature = self.features[uid] @@ -156,14 +151,14 @@ class HCDevice: print(now(), self.name, f"Processing feature {feature['name']} with uid {uid}") if "access" not in feature: raise Exception( - f"{self.name}. Unable to configure appliance." + "Unable to configure appliance. " f"Feature {feature['name']} with uid {uid} does not have access." ) access = feature["access"].lower() if access != "readwrite" and access != "writeonly": raise Exception( - f"{self.name}. Unable to configure appliance." + "Unable to configure appliance. " f"Feature {feature['name']} with uid {uid} has got access {feature['access']}." ) @@ -171,7 +166,7 @@ class HCDevice: if "values" in feature: if isinstance(data["value"], int) is False: raise Exception( - f"Unable to configure appliance. The value {data['value']} must be an integer." + f"Unable to configure appliance. The value {data['value']} must be an integer. " f"Allowed values are {feature['values']}." ) value = str(data["value"]) @@ -179,9 +174,9 @@ class HCDevice: # but always seem to be an integer. An integer must be provided if value not in feature["values"]: raise Exception( - f"{self.name}. Unable to configure appliance." - f"Value {data['value']} is not a valid value." - f"Allowed values are {feature['values']}." + "Unable to configure appliance. " + f"Value {data['value']} is not a valid value. " + f"Allowed values are {feature['values']}. " ) if "min" in feature: @@ -193,8 +188,8 @@ class HCDevice: or data["value"] > max ): raise Exception( - f"{self.name}. Unable to configure appliance." - f"Value {data['value']} is not a valid value." + "Unable to configure appliance. " + f"Value {data['value']} is not a valid value. " f"The value must be an integer in the range {min} and {max}." ) @@ -238,13 +233,14 @@ class HCDevice: if data is not None: if action == "POST": - if "resource" == "/ro/values": + if resource == "/ro/values": # Raises exceptions on failure self.test_feature(data) - elif "resource" == "/ro/activeProgram": + elif resource == "/ro/activeProgram": # Raises exception on failure self.test_program_data(data) + msg["data"] = [data] try: diff --git a/hc2mqtt b/hc2mqtt index 6cbad0a..9886921 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -88,27 +88,32 @@ dev = {} def client_connect(client, device, mqtt_topic): def on_message(client, userdata, msg): - print(msg.topic) mqtt_state = msg.payload.decode() mqtt_topic = msg.topic.split("/") - print(now(), f"received mqtt message {mqtt_state}") - - if len(mqtt_topic) >= 2: - device_name = mqtt_topic[-2] - topic = mqtt_topic[-1] - else: - raise Exception(f"Invalid mqtt topic {msg.topic}") - + print(now(), f"{msg.topic} received mqtt message {mqtt_state}") + try: - msg = json.loads(mqtt_state) - if topic == "set" and "uid" in msg: - dev[device_name].get("/ro/values", 1, "POST", msg) - elif topic == "activeProgram": - dev[device_name].get("/ro/activeProgram", 1, "POST", msg) + if len(mqtt_topic) >= 2: + device_name = mqtt_topic[-2] + topic = mqtt_topic[-1] else: - raise Exception(f"Payload {msg} is not correctly formatted") + raise Exception(f"Invalid mqtt topic {msg.topic}.") + + try: + msg = json.loads(mqtt_state) + except ValueError as e: + raise ValueError(f"Invalid JSON in message: {mqtt_state}.") from e + + if topic == "set": + resource = "/ro/values" + elif topic == "activeProgram": + resource = "/ro/activeProgram" + else: + raise Exception(f"Payload topic {topic} is unknown.") + + dev[device_name].get(resource, 1, "POST", msg) except Exception as e: - print("ERROR", e, file=sys.stderr) + print(now(), device_name, "ERROR", e, file=sys.stderr) host = device["host"] device_topics = topics From 9f3ce78f26b031581b906103013b95fdcaffa0f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:57:09 +0000 Subject: [PATCH 10/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- HCDevice.py | 8 ++------ hc2mqtt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 4945042..523aa3b 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -97,11 +97,8 @@ class HCDevice: if "program" not in data: raise TypeError("Message data invalid, no program specified.") - - if isinstance(data["program"],int) is False: - raise TypeError( - f"Message data invalid, UID in 'program' must be an integer." - ) + if isinstance(data["program"], int) is False: + raise TypeError(f"Message data invalid, UID in 'program' must be an integer.") # devices.json stores UID as string uid = str(data["program"]) @@ -240,7 +237,6 @@ class HCDevice: # Raises exception on failure self.test_program_data(data) - msg["data"] = [data] try: diff --git a/hc2mqtt b/hc2mqtt index 9886921..dd1237c 100755 --- a/hc2mqtt +++ b/hc2mqtt @@ -91,7 +91,7 @@ def client_connect(client, device, mqtt_topic): mqtt_state = msg.payload.decode() mqtt_topic = msg.topic.split("/") print(now(), f"{msg.topic} received mqtt message {mqtt_state}") - + try: if len(mqtt_topic) >= 2: device_name = mqtt_topic[-2] From 7b6c827bb43effe1652eba8f59690f71a7292b08 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Wed, 20 Mar 2024 12:01:24 +0000 Subject: [PATCH 11/11] Fix linting --- HCDevice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 523aa3b..ffc20e8 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -98,7 +98,7 @@ class HCDevice: raise TypeError("Message data invalid, no program specified.") if isinstance(data["program"], int) is False: - raise TypeError(f"Message data invalid, UID in 'program' must be an integer.") + raise TypeError("Message data invalid, UID in 'program' must be an integer.") # devices.json stores UID as string uid = str(data["program"]) @@ -163,8 +163,8 @@ class HCDevice: if "values" in feature: if isinstance(data["value"], int) is False: raise Exception( - f"Unable to configure appliance. The value {data['value']} must be an integer. " - f"Allowed values are {feature['values']}." + f"Unable to configure appliance. The value {data['value']} must be an integer." + f" Allowed values are {feature['values']}." ) value = str(data["value"]) # values are strings in the feature list,