Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nodon trv addon #1831

Merged
merged 7 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Classes/PluginConf.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,14 @@

"Temperature": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"Thermostats": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"thermoSettings": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"ThreadCommunication": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"ThreadDomoticz": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"ThreadForwarder": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"ThreadWriter": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"Timing": { "type": "bool", "default": 1, "current": None, "restart": 0, "hidden": True, "Advanced": True },
"TimeServer": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },

"Transport": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"Transport8000": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
"Transport8002": { "type": "bool", "default": 0, "current": None, "restart": 0, "hidden": False, "Advanced": True },
Expand Down
83 changes: 47 additions & 36 deletions Modules/paramDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,58 +22,69 @@
from Modules.onoff_settings import ONOFF_DEVICE_PARAMETERS
from Modules.philips import PHILIPS_DEVICE_PARAMETERS
from Modules.schneider_wiser import SCHNEIDER_DEVICE_PARAMETERS
from Modules.thermo_settings import THERMOSTAT_DEVICE_PARAMETERS
from Modules.tuya import TUYA_DEVICE_PARAMETERS
from Modules.tuyaSiren import TUYA_SIREN_DEVICE_PARAMETERS
from Modules.tuyaTRV import TUYA_TRV_DEVICE_PARAMETERS
from Modules.tuyaTS011F import TUYA_TS011F_DEVICE_PARAMETERS
from Modules.tuyaTS0601 import ts0601_extract_data_point_infos, ts0601_settings


def initialize_device_settings(self):
"""Initializes device settings by loading general and manufacturer-specific parameters."""
self.device_settings = {}

# Load specific settings
self.device_settings.update(ONOFF_DEVICE_PARAMETERS)
self.device_settings.update(OCCUPANCY_DEVICE_PARAMETERS)
self.device_settings.update(IAS_DEVICE_PARAMETERS)
self.device_settings.update(BALLAST_DEVICE_PARAMETERS)

# Load Manufacturer specific settings
self.device_settings.update(DANFOSS_DEVICE_PARAMETERS)

self.device_settings.update(LEGRAND_DEVICE_PARAMETERS)

self.device_settings.update(LUMI_DEVICE_PARAMETERS)

self.device_settings.update(PHILIPS_DEVICE_PARAMETERS)

self.device_settings.update(SONOFF_DEVICE_PARAMETERS)
# General device parameters
general_parameters = [
ONOFF_DEVICE_PARAMETERS,
OCCUPANCY_DEVICE_PARAMETERS,
IAS_DEVICE_PARAMETERS,
BALLAST_DEVICE_PARAMETERS,
THERMOSTAT_DEVICE_PARAMETERS,
]

# Manufacturer-specific device parameters
manufacturer_parameters = [
DANFOSS_DEVICE_PARAMETERS,
LEGRAND_DEVICE_PARAMETERS,
LUMI_DEVICE_PARAMETERS,
PHILIPS_DEVICE_PARAMETERS,
SONOFF_DEVICE_PARAMETERS,
SUNRICHER_DEVICE_PARAMETERS,
TUYA_DEVICE_PARAMETERS,
TUYA_TS011F_DEVICE_PARAMETERS,
TUYA_TRV_DEVICE_PARAMETERS,
TUYA_SIREN_DEVICE_PARAMETERS,
SCHNEIDER_DEVICE_PARAMETERS,
]

# Update device settings in a single loop
for param_group in general_parameters + manufacturer_parameters:
self.device_settings.update(param_group)

self.device_settings.update(SUNRICHER_DEVICE_PARAMETERS)

self.device_settings.update(TUYA_DEVICE_PARAMETERS)
self.device_settings.update(TUYA_TS011F_DEVICE_PARAMETERS)
self.device_settings.update(TUYA_TRV_DEVICE_PARAMETERS)
self.device_settings.update(TUYA_SIREN_DEVICE_PARAMETERS)

self.device_settings.update(SCHNEIDER_DEVICE_PARAMETERS)

def sanity_check_of_param(self, NwkId):
"""Performs a sanity check on device parameters and applies relevant settings."""

self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {NwkId}")

self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {NwkId}")
device_data = self.ListOfDevices.get(NwkId, {})
param_data = device_data.get("Param", {})
model_name = device_data.get("Model", "")

param_data = self.ListOfDevices.get(NwkId, {}).get("Param", {})
model_name = self.ListOfDevices.get(NwkId, {}).get("Model", "")
dps_mapping = ts0601_extract_data_point_infos(self, model_name)

for param, value in param_data.items():
self.log.logging("Heartbeat", "Debug", f"sanity_check_of_param {param}, {value}")

dps_mapping = ts0601_extract_data_point_infos( self, model_name)
self.log.logging("Heartbeat", "Debug", f"Checking param: {param}, Value: {value}")

if dps_mapping:
ts0601_settings( self, NwkId, dps_mapping, param, value)
ts0601_settings(self, NwkId, dps_mapping, param, value)
continue

param_setting = self.device_settings.get(param)

elif param in self.device_settings:
if callable( self.device_settings[param] ):
self.device_settings[param](self, NwkId, value)
if callable(param_setting):
param_setting(self, NwkId, value)

elif "callable" in self.device_settings[param]:
self.device_settings[param]["callable"](self, NwkId, value)
elif isinstance(param_setting, dict) and "callable" in param_setting and callable(param_setting["callable"]):
param_setting["callable"](self, NwkId, value)
191 changes: 88 additions & 103 deletions Modules/readAttributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ def ReadAttributeReq( self, addr, EpIn, EpOut, Cluster, ListOfAttributes, manufa
normalizedReadAttributeReq(self, addr, EpIn, EpOut, Cluster, shortlist, manufacturer_spec, manufacturer, ackIsDisabled)


def read_manufacturer_specific_attributes(self, nwkid, ep_out, cluster):
""" Request a Read Attributes of Manufacturer specific attributes defined in Config file for this cluster"""
manufacturer_code, manufacturer_attributes = retreive_manufacturer_specifics_attributes(self, nwkid, cluster)
if manufacturer_code and manufacturer_attributes:
# Log the message
attributes_str = " ".join(f"0x{num:04x}" for num in manufacturer_attributes)
self.log.logging("ReadAttributes", "Debug", f"Request Manuf.Specific Attributes for cluster {cluster} for {nwkid} {ep_out} {attributes_str}", nwkid=nwkid)

# Perform the Request
ReadAttributeReq( self, nwkid, ZIGATE_EP, ep_out, "0201", manufacturer_attributes, manufacturer_spec="01", manufacturer=manufacturer_code, ackIsDisabled=is_ack_tobe_disabled(self, nwkid), checkTime=False, )


def split_list(list_in, wanted_parts=1):
"""
Split the list of attrributes in wanted part
Expand Down Expand Up @@ -227,22 +239,42 @@ def retreive_ListOfAttributesByCluster(self, key, Ep, cluster):


def retreive_attributes_based_on_configuration(self, key, cluster):
if "Model" not in self.ListOfDevices[key]:
model_name = self.ListOfDevices[key].get("Model")
if model_name is None:
return None
if self.ListOfDevices[key]["Model"] not in self.DeviceConf:
if model_name not in self.DeviceConf:
return None
if "ReadAttributes" not in self.DeviceConf[self.ListOfDevices[key]["Model"]]:
if "ReadAttributes" not in self.DeviceConf[model_name]:
return None
if cluster not in self.DeviceConf[self.ListOfDevices[key]["Model"]]["ReadAttributes"]:
if cluster not in self.DeviceConf[model_name]["ReadAttributes"]:
return None

return [
int(attr, 16)
for attr in self.DeviceConf[self.ListOfDevices[key]["Model"]][
for attr in self.DeviceConf[model_name][
"ReadAttributes"
][cluster]
]

def retreive_manufacturer_specifics_attributes(self, nwkid, cluster):
model_name = self.ListOfDevices[nwkid].get("Model")
if model_name is None:
return None, None

if model_name not in self.DeviceConf:
return None, None

manufacturer_clusters = self.DeviceConf[model_name].get("ManufacturerAttributes")
manufacturer_code = self.DeviceConf[model_name].get("ManufacturerCode")

if manufacturer_clusters is None or manufacturer_code is None:
return None, None

if cluster not in manufacturer_clusters:
return None, None

return manufacturer_code, [ int(attr, 16) for attr in manufacturer_clusters[ cluster ] ]


def retreive_attributes_from_default_device_list(self, key, Ep, cluster):

Expand Down Expand Up @@ -823,127 +855,80 @@ def ReadAttributeRequest_0201(self, key):
# Thermostat

self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0201 - Key: %s " % key, nwkid=key)
_model = "Model" in self.ListOfDevices[key]
disableAck = True
if "PowerSource" in self.ListOfDevices[key] and self.ListOfDevices[key]["PowerSource"] == "Battery":
disableAck = False

ListOfEp = getListOfEpForCluster(self, key, "0201")
for EPout in ListOfEp:
listAttributes = []
for iterAttr in retreive_ListOfAttributesByCluster(self, key, EPout, "0201"):
if iterAttr not in listAttributes:
listAttributes.append(iterAttr)

if _model and str(self.ListOfDevices[key]["Model"]).find("Super TR") == 0:
self.log.logging("ReadAttributes", "Debug", "- req Attributes for Super TR", nwkid=key)
listAttributes.append(0x0403)
listAttributes.append(0x0405)
listAttributes.append(0x0406)
listAttributes.append(0x0408)
listAttributes.append(0x0409)
_model_name = self.ListOfDevices[key].get("Model","")
manufacturer = self.ListOfDevices[key].get("Manufacturer")
manufacturer_name = self.ListOfDevices[key].get("Manufacturer Name")

eps_list = getListOfEpForCluster(self, key, "0201")
for ep_out in eps_list:
attribute_list = []
for attr in retreive_ListOfAttributesByCluster(self, key, ep_out, "0201"):
if attr not in attribute_list:
attribute_list.append(attr)

if _model_name.find("Super TR") == 0:
self.log.logging("ReadAttributes", "Debug", "- req Attributes for Super TR", nwkid=key)
attribute_list.append(0x0403)
attribute_list.append(0x0405)
attribute_list.append(0x0406)
attribute_list.append(0x0408)
attribute_list.append(0x0409)

# Adjustement before request
listAttrSpecific = []
listAttrGeneric = []
attr_spec_list = []
appt_generic_list = []
manufacturer_code = "0000"

if (
("Manufacturer" in self.ListOfDevices[key] and self.ListOfDevices[key]["Manufacturer"] == "105e")
or (
("Manufacturer" in self.ListOfDevices[key] and self.ListOfDevices[key]["Manufacturer"] == "113c")
or ( "Manufacturer Name" in self.ListOfDevices[key] and self.ListOfDevices[key]["Manufacturer Name"] == "Schneider Electric")
)
):
# We need to break the Read Attribute between Manufacturer specifcs one and teh generic one
if self.ListOfDevices[key]["Manufacturer Name"] == "Schneider Electric":
if manufacturer in ("105e","113c") or manufacturer_name in ("Schneider Electric", "OWON", "CASAIA"):
# We need to break the Read Attribute between Manufacturer specifcs one and the generic one
if manufacturer_name == "Schneider Electric":
manufacturer_code = "105e"

elif self.ListOfDevices[key]["Manufacturer Name"] in ("OWON", "CASAIA"):
elif manufacturer_name in ("OWON", "CASAIA"):
manufacturer_code = "113c"

for _attr in list(listAttributes):
for _attr in list(attribute_list):
if _attr in (0xE011, 0x0E20, 0xFD00):
listAttrSpecific.append(_attr)
appt_generic_list.append(_attr)
else:
listAttrGeneric.append(_attr)
del listAttributes
listAttributes = listAttrGeneric
appt_generic_list.append(_attr)
del attribute_list
attribute_list = appt_generic_list

if ("Manufacturer" in self.ListOfDevices[key] and self.ListOfDevices[key]["Manufacturer"] == "1246") or (
"Manufacturer Name" in self.ListOfDevices[key] and self.ListOfDevices[key]["Manufacturer Name"] == "Danfoss"
):
elif manufacturer == "1246" or manufacturer_name == "Danfoss":
manufacturer_code = "1246"
for _attr in list(listAttributes):
for _attr in list(attribute_list):
if _attr in (0x4000, 0x4010, 0x4011, 0x4015, 0x4020):
listAttrSpecific.append(_attr)
attr_spec_list.append(_attr)
else:
listAttrGeneric.append(_attr)
del listAttributes
listAttributes = listAttrGeneric
appt_generic_list.append(_attr)
del attribute_list
attribute_list = appt_generic_list

if listAttributes:
# self.log.logging( "ReadAttributes", 'Debug', "Request 0201 %s/%s 0201 %s " %(key, EPout, listAttributes), nwkid=key)
self.log.logging(
"ReadAttributes",
"Debug",
"Request Thermostat via Read Attribute request %s/%s " % (key, EPout)
+ " ".join("0x{:04x}".format(num) for num in listAttributes),
nwkid=key,
)
ReadAttributeReq(
self,
key,
ZIGATE_EP,
EPout,
"0201",
listAttributes,
ackIsDisabled=is_ack_tobe_disabled(self, key),
checkTime=False,
)
if attribute_list:
self.log.logging( "ReadAttributes", "Debug", "Request Thermostat via Read Attribute request %s/%s " % (key, ep_out) + " ".join("0x{:04x}".format(num) for num in attribute_list), nwkid=key, )
ReadAttributeReq( self, key, ZIGATE_EP, ep_out, "0201", attribute_list, ackIsDisabled=is_ack_tobe_disabled(self, key), checkTime=False, )

if listAttrSpecific:
# self.log.logging( "ReadAttributes", 'Debug', "Request Thermostat info via Read Attribute request Manuf Specific %s/%s %s" %(key, EPout, str(listAttrSpecific)), nwkid=key)
self.log.logging(
"ReadAttributes",
"Debug",
"Request Thermostat via Read Attribute request Manuf Specific %s/%s " % (key, EPout)
+ " ".join("0x{:04x}".format(num) for num in listAttrSpecific),
nwkid=key,
)
ReadAttributeReq(
self,
key,
ZIGATE_EP,
EPout,
"0201",
listAttrSpecific,
manufacturer_spec="01",
manufacturer=manufacturer_code,
ackIsDisabled=is_ack_tobe_disabled(self, key),
checkTime=False,
)
if attr_spec_list:
self.log.logging( "ReadAttributes", "Debug", "Request Thermostat via Read Attribute request Manuf Specific %s/%s " % (key, ep_out) + " ".join("0x{:04x}".format(num) for num in attr_spec_list), nwkid=key, )
ReadAttributeReq( self, key, ZIGATE_EP, ep_out, "0201", attr_spec_list, manufacturer_spec="01", manufacturer=manufacturer_code, ackIsDisabled=is_ack_tobe_disabled(self, key), checkTime=False, )

read_manufacturer_specific_attributes(self, key, ep_out, "0201")

def ReadAttributeRequest_0201_0012(self, key):

self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0201 - Key: %s " % key, nwkid=key)
_model = False
if "Model" in self.ListOfDevices[key]:
_model = True

disableAck = True
if "PowerSource" in self.ListOfDevices[key] and self.ListOfDevices[key]["PowerSource"] == "Battery":
disableAck = False
def ReadAttributeRequest_0201_0012(self, key):
""" Request attribute 0x0012 (Occupied Setpoint)"""

ListOfEp = getListOfEpForCluster(self, key, "0201")
for EPout in ListOfEp:
listAttributes = [0x0012]
self.log.logging("ReadAttributes", "Debug", "ReadAttributeRequest_0201 / 0x0012 - Key: %s " % key, nwkid=key)
eps_list = getListOfEpForCluster(self, key, "0201")
for EPout in eps_list:

if "0201" in self.ListOfDevices[key]["Ep"][EPout] and "0010" in self.ListOfDevices[key]["Ep"][EPout]["0201"]:
listAttributes.append(0x0010)
cluster_0201 = self.ListOfDevices.get(key, {}).get("Ep", {}).get(EPout, {}).get("0201")
if cluster_0201:
attributes_list = [0x0012,]

ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0201", listAttributes, ackIsDisabled=is_ack_tobe_disabled(self, key))
ReadAttributeReq(self, key, ZIGATE_EP, EPout, "0201", attributes_list, ackIsDisabled=is_ack_tobe_disabled(self, key))


def ReadAttributeRequest_0202(self, key):
Expand Down
Loading