From 4918d7db7579f93525fc3043a52ed39ac928a072 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Wed, 24 Apr 2024 23:10:17 +0100 Subject: [PATCH 1/8] Process description changes --- HCDevice.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 7702043..e20ca42 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -305,6 +305,7 @@ class HCDevice: # self.get("/ro/allDescriptionChanges") self.get("/ro/allMandatoryValues") self.get("/ro/values") + self.get("/ro/allDescriptionChanges") def handle_message(self, buf): msg = json.loads(buf) @@ -349,8 +350,23 @@ class HCDevice: values = msg["data"][0] elif resource == "/ro/descriptionChange" or resource == "/ro/allDescriptionChanges": - # we asked for these but don't know have to parse yet - pass + if "data" in msg and len(msg["data"]) > 0: + for change in msg["data"]: + uid = str(change["uid"]) + if uid in self.features: + if "access" in change: + access = change["access"] + self.features[uid]["access"] = access + self.print(f"Access change for {uid} to {access}") + if "available" in change: + self.features[uid]["available"] = change["available"] + if "min" in change: + self.features[uid]["min"] = change["min"] + if "max" in change: + self.features[uid]["max"] = change["max"] + else: + #We wont have name for this item, so have to be careful when resolving elsewhere + self.features[uid] = change elif resource == "/ni/info": if "data" in msg and len(msg["data"]) > 0: From 8c15284c3271f5d775cc09f2e80f193a18d405e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:14:06 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- HCDevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HCDevice.py b/HCDevice.py index e20ca42..3c0b407 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -365,7 +365,7 @@ class HCDevice: if "max" in change: self.features[uid]["max"] = change["max"] else: - #We wont have name for this item, so have to be careful when resolving elsewhere + # We wont have name for this item, so have to be careful when resolving elsewhere self.features[uid] = change elif resource == "/ni/info": From 8288e848ec7ec7a655ef949481fdad569a80f3ed Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Wed, 24 Apr 2024 23:15:15 +0100 Subject: [PATCH 3/8] Fix linelength --- HCDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HCDevice.py b/HCDevice.py index e20ca42..5436ab1 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -365,7 +365,8 @@ class HCDevice: if "max" in change: self.features[uid]["max"] = change["max"] else: - #We wont have name for this item, so have to be careful when resolving elsewhere + #We wont have name for this item, so have to be careful + #when resolving elsewhere self.features[uid] = change elif resource == "/ni/info": From 367cf405bd406ec4c019cfd03553b86d0706ff48 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:17:15 +0000 Subject: [PATCH 4/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- HCDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 5436ab1..c6b87a2 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -365,8 +365,8 @@ class HCDevice: if "max" in change: self.features[uid]["max"] = change["max"] else: - #We wont have name for this item, so have to be careful - #when resolving elsewhere + # We wont have name for this item, so have to be careful + # when resolving elsewhere self.features[uid] = change elif resource == "/ni/info": From 076b72305ed6bde209dc27cafd36e2b5818ec69a Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 25 Apr 2024 10:02:40 +0100 Subject: [PATCH 5/8] Wrap all access to features within a threadsafe lock --- HCDevice.py | 172 +++++++++++++++++++++++++++------------------------- 1 file changed, 88 insertions(+), 84 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index 5436ab1..e08fa4c 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -59,6 +59,7 @@ def now(): class HCDevice: def __init__(self, ws, device): self.ws = ws + self.features_lock = threading.Lock() self.features = device.get("features") self.name = device.get("name") self.session_id = None @@ -84,9 +85,9 @@ class HCDevice: name = uid status = None - - if uid in self.features: - status = self.features[uid] + with self.features_lock: + if uid in self.features: + status = self.features[uid] if status: name = status["name"] @@ -110,30 +111,31 @@ class HCDevice: # devices.json stores UID as string uid = str(data["program"]) - if uid not in self.features: - raise ValueError( - f"Unable to configure appliance. Program UID {uid} is not valid" - " for this device." - ) + with self.features_lock: + if uid not in self.features: + raise ValueError( + f"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 ValueError( - f"Unable to configure appliance. Program UID {uid} is not a valid" - f" program - {feature['name']}." - ) + 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 ValueError( + f"Unable to configure appliance. Program UID {uid} is not a valid" + f" program - {feature['name']}." + ) - if "options" in data: - for option in data["options"]: - option_uid = option["uid"] - if str(option_uid) not in self.features: - raise ValueError( - f"Unable to configure appliance. Option UID {option_uid} is not" - " valid for this device." - ) + if "options" in data: + for option in data["options"]: + option_uid = option["uid"] + if str(option_uid) not in self.features: + raise ValueError( + f"Unable to configure appliance. Option UID {option_uid} is not" + " valid for this device." + ) # Test the feature of an appliance agains a data object def test_feature(self, data_array): @@ -149,58 +151,59 @@ class HCDevice: # Check if the uid is present for this appliance uid = str(data["uid"]) - if uid not in self.features: - raise Exception(f"Unable to configure appliance. UID {uid} is not valid.") + with self.features_lock: + if uid not in self.features: + raise Exception(f"Unable to configure appliance. UID {uid} is not valid.") - feature = self.features[uid] + feature = self.features[uid] - # check the access level of the feature - self.print(f"Processing feature {feature['name']} with uid {uid}") - if "access" not in feature: - raise Exception( - "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( - "Unable to configure appliance. " - f"Feature {feature['name']} with uid {uid} has got access {feature['access']}." - ) - - # check if selected list with values is allowed - if "values" in feature: - if isinstance(data["value"], int) is False: - raise Exception( - f"Unable to configure appliance. The value {data['value']} must " - f"be an integer. Allowed values are {feature['values']}." - ) - - value = str(data["value"]) - # values are strings in the feature list, - # but always seem to be an integer. An integer must be provided - if value not in feature["values"]: + # check the access level of the feature + self.print(f"Processing feature {feature['name']} with uid {uid}") + if "access" not in feature: raise Exception( "Unable to configure appliance. " - f"Value {data['value']} is not a valid value. " - f"Allowed values are {feature['values']}. " + f"Feature {feature['name']} with uid {uid} does not have access." ) - if "min" in feature: - min = int(feature["min"]) - max = int(feature["max"]) - if ( - isinstance(data["value"], int) is False - or data["value"] < min - or data["value"] > max - ): + access = feature["access"].lower() + if access != "readwrite" and access != "writeonly": raise Exception( "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}." + f"Feature {feature['name']} with uid {uid} has got access {feature['access']}." ) + # check if selected list with values is allowed + if "values" in feature: + if isinstance(data["value"], int) is False: + raise Exception( + f"Unable to configure appliance. The value {data['value']} must " + f"be an integer. Allowed values are {feature['values']}." + ) + + value = str(data["value"]) + # values are strings in the feature list, + # but always seem to be an integer. An integer must be provided + if value not in feature["values"]: + raise Exception( + "Unable to configure appliance. " + f"Value {data['value']} is not a valid value. " + f"Allowed values are {feature['values']}. " + ) + + if "min" in feature: + min = int(feature["min"]) + max = int(feature["max"]) + if ( + isinstance(data["value"], int) is False + or data["value"] < min + or data["value"] > max + ): + raise Exception( + "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}." + ) + def recv(self): try: buf = self.ws.recv() @@ -351,23 +354,24 @@ class HCDevice: elif resource == "/ro/descriptionChange" or resource == "/ro/allDescriptionChanges": if "data" in msg and len(msg["data"]) > 0: - for change in msg["data"]: - uid = str(change["uid"]) - if uid in self.features: - if "access" in change: - access = change["access"] - self.features[uid]["access"] = access - self.print(f"Access change for {uid} to {access}") - if "available" in change: - self.features[uid]["available"] = change["available"] - if "min" in change: - self.features[uid]["min"] = change["min"] - if "max" in change: - self.features[uid]["max"] = change["max"] - else: - #We wont have name for this item, so have to be careful - #when resolving elsewhere - self.features[uid] = change + with self.features_lock: + for change in msg["data"]: + uid = str(change["uid"]) + if uid in self.features: + if "access" in change: + access = change["access"] + self.features[uid]["access"] = access + self.print(f"Access change for {uid} to {access}") + if "available" in change: + self.features[uid]["available"] = change["available"] + if "min" in change: + self.features[uid]["min"] = change["min"] + if "max" in change: + self.features[uid]["max"] = change["max"] + else: + #We wont have name for this item, so have to be careful + #when resolving elsewhere + self.features[uid] = change elif resource == "/ni/info": if "data" in msg and len(msg["data"]) > 0: From 8130124ea72e20ac2bb5296fb215fa1ad73c3bf2 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 25 Apr 2024 10:08:37 +0100 Subject: [PATCH 6/8] Check for existance of name in feature --- HCDevice.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index e08fa4c..f7f2800 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -90,7 +90,8 @@ class HCDevice: status = self.features[uid] if status: - name = status["name"] + if "name" in status: + name = status["name"] if "values" in status and value_str in status["values"]: value = status["values"][value_str] @@ -122,11 +123,14 @@ class HCDevice: # 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 ValueError( - f"Unable to configure appliance. Program UID {uid} is not a valid" - f" program - {feature['name']}." - ) + if "name" in feature: + if ".Program." not in feature["name"]: + raise ValueError( + f"Unable to configure appliance. Program UID {uid} is not a valid" + f" program - {feature['name']}." + ) + else: + self.print(f"Unknown Program UID {uid}") if "options" in data: for option in data["options"]: From 745a32a46044537d438dcca3236d134661880ed8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 09:10:20 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- HCDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HCDevice.py b/HCDevice.py index f7f2800..63fb524 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -373,8 +373,8 @@ class HCDevice: if "max" in change: self.features[uid]["max"] = change["max"] else: - #We wont have name for this item, so have to be careful - #when resolving elsewhere + # We wont have name for this item, so have to be careful + # when resolving elsewhere self.features[uid] = change elif resource == "/ni/info": From ae50b0db0cfe56b6aad32aac07fa29e026390f84 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 25 Apr 2024 10:14:44 +0100 Subject: [PATCH 8/8] Line length --- HCDevice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HCDevice.py b/HCDevice.py index 63fb524..e69a71f 100755 --- a/HCDevice.py +++ b/HCDevice.py @@ -173,7 +173,8 @@ class HCDevice: if access != "readwrite" and access != "writeonly": raise Exception( "Unable to configure appliance. " - f"Feature {feature['name']} with uid {uid} has got access {feature['access']}." + f"Feature {feature['name']} with uid {uid} " + f"has got access {feature['access']}." ) # check if selected list with values is allowed