diff --git a/BridgeEmulator/HueObjects/Device.py b/BridgeEmulator/HueObjects/Device.py new file mode 100644 index 000000000..b9d04ff7f --- /dev/null +++ b/BridgeEmulator/HueObjects/Device.py @@ -0,0 +1,562 @@ +import uuid +import logManager +import weakref +from sensors.sensor_types import sensorTypes +from lights.light_types import lightTypes, archetype +from HueObjects import genV2Uuid, StreamEvent +from datetime import datetime, timezone +from pprint import pprint + +logging = logManager.logger.get_logger(__name__) + + +class Device(): + def __init__(self, data): + self.id_v2 = data["id"] if "id" in data else genV2Uuid() + self.id_v1 = self.id_v2 # used for config save + self.name = data["name"] + self.type = data["type"] if "type" in data else "ZLLLight" + self.elements = {} + self.modelid = data["modelid"] + self.group_v1 = data["group_v1"] if "group_v1" in data else "sensors" + self.protocol = data["protocol"] if "protocol" in data else "none" + self.protocol_cfg = data["protocol_cfg"] if "protocol_cfg" in data else { + } + + def __del__(self): + logging.info(self.name + " device was destroyed.") + streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "data": [{"id": self.getDevice()["id"], "type": "device"}], + "id": str(uuid.uuid4()), + "type": "delete" + } + StreamEvent(streamMessage) + for element, obj in self.elements.items(): + del obj + + def add_element(self, type, element): + self.elements[type] = weakref.ref(element) + self.id_v2 = str(uuid.uuid5( + uuid.NAMESPACE_URL, element.id_v2 + 'device')) + # device + streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), + "data": [self.getDevice()], + "id": str(uuid.uuid4()), + "type": "add" + } + StreamEvent(streamMessage) + # zigbee_connectivity + streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), + "data": [self.getZigBee()], + "id": str(uuid.uuid4()), + "type": "add" + } + StreamEvent(streamMessage) + + if self.group_v1 == "lights": + # entertainment + streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), + "data": [{"id": str(uuid.uuid5( + uuid.NAMESPACE_URL, self.id_v2 + 'entertainment')), "type": "entertainent"}], + "id": str(uuid.uuid4()), + "type": "add" + } + streamMessage["id_v1"] = "/lights/" + self.id_v1 + streamMessage["data"][0].update(self.getV2Entertainment()) + StreamEvent(streamMessage) + + def firstElement(self): + rootKey = list(self.elements.keys())[0] + return self.elements[rootKey]() + + def getSML001data(self): + result = {} + result["product_data"] = { + "certified": True, + "manufacturer_name": "Signify Netherlands B.V.", + "model_id": self.modelid, + "product_archetype": "unknown_archetype", + "product_name": "Hue motion sensor", + "software_version": "1.1.27575" + } + result["services"] = [ + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'motion')), + "rtype": "motion" + }, + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), + "rtype": "device_power" + }, + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), + "rtype": "zigbee_connectivity" + }, + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'light_level')), + "rtype": "light_level" + }, + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'temperature')), + "rtype": "temperature" + }] + return result + + def setDevice(self, type, data): + obj = None + if type == "device": + obj = self + else: + obj = self.elements[type]() + if "metadata" in data and "name" in data["metadata"]: + obj.name = data["metadata"]["name"] + if type == "device": + self.firstElement().name = data["metadata"]["name"] + if "enabled" in data: + obj.config["on"] = data["enabled"] + + streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), + "data": [data], + "id": str(uuid.uuid4()), + "type": "update" + } + streamMessage["data"][0].update( + {"id_v1": "/sensors/" + obj.id_v1, "owner": {"rid": self.id_v2, "rtype": "device"}}) + StreamEvent(streamMessage) + + def getButtons(self): + result = [] + start, buttonsCount = 0, 0 + if self.modelid in ["RWL022", "RWL021", "RWL020", "RDM002"]: + buttonsCount = 4 + + for button in range(start, buttonsCount): + result.append({ + "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button' + str(button + 1))), + "id_v1": "/sensors/" + self.id_v1, + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "metadata": { + "control_id": button + 1 + }, + "button": { + "repeat_interval": 800, + "event_values": [ + "initial_press", + "repeat", + "short_release", + "long_release", + "long_press" + ] + }, + "type": "button" + }) + + return result + + def getRWL02xData(self): + result = {} + result["product_data"] = {"model_id": self.modelid, + "manufacturer_name": "Signify Netherlands B.V.", + "product_name": "Hue dimmer switch", + "product_archetype": "unknown_archetype", + "certified": True, + "software_version": "2.44.0", + "hardware_platform_type": "100b-119" + } + result["services"] = [] + for button in self.getButtons(): + result["services"].append( + { + "rid": button["id"], + "rtype": "button" + } + ) + + result["services"].extend([{ + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), + "rtype": "device_power" + }, { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), + "rtype": "zigbee_connectivity" + }]) + return result + + def getRDM002Data(self): + result = {} + result["product_data"] = {"model_id": self.modelid, + "manufacturer_name": "Signify Netherlands B.V.", + "product_name": "Hue tap dial switch", + "product_archetype": "unknown_archetype", + "certified": True, + "software_version": "2.59.25", + "hardware_platform_type": "100b-119" + } + + result["services"] = [] + for button in self.getButtons: + result["services"].append( + { + "rid": button["id"], + "rtype": "button" + } + ) + + result["services"].extend([{ + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), + "rtype": "device_power" + }, { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), + "rtype": "zigbee_connectivity" + }]) + return result + + def getContactData(self): + result = {} + result["product_data"] = {"model_id": self.modelid, + "product_name": "Hue secure contact sensor", + "manufacturer_name": "Signify Netherlands B.V.", + "product_archetype": "unknown_archetype", + "certified": True, + "software_version": "2.67.9", + "hardware_platform_type": "100b-125" + } + result["services"] = [ + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), + "rtype": "device_power" + }, { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), + "rtype": "zigbee_connectivity" + }, { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'contact')), + "rtype": "contact" + }, { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'tamper')), + "rtype": "tamper" + } + ] + return result + + def getLightData(self): + result = {} + result["product_data"] = lightTypes[self.modelid]["device"] + result["product_data"]["model_id"] = self.modelid + result["services"] = [ + { + "rid": self.firstElement().id_v2, + "rtype": "light" + }, + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), + "rtype": "zigbee_connectivity" + }, + { + "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'entertainment')), + "rtype": "entertainment" + } + ] + return result + + def getDevice(self): + result = {} + if self.modelid == "SML001": + result = self.getSML001data() + elif self.modelid in ["RWL022", "RWL021", "RWL020"]: + result = self.getRWL02xData() + elif self.modelid == "RDM002": + result = self.getRDM002Data() + elif self.modelid == "SOC001": + result = self.getContactData() + elif self.group_v1 == "lights": + result = self.getLightData() + + result["metadata"] = {"name": self.name, + "archetype": "unknown_archetype"} # for sensors + if self.group_v1 == "lights": + result["metadata"]["archetype"] = archetype[self.firstElement( + ).config["archetype"]] + result["metadata"]["function"] = "mixed" + + result["id"] = self.id_v2 + if "invisible_v1" not in self.firstElement().protocol_cfg: + result["id_v1"] = "/" + self.type + "/" + self.firstElement().id_v1 + result["identify"] = {} + result["type"] = "device" + return result + + def getMotion(self): + result = None + if self.modelid == "SML001": + try: + print(self.elements["ZLLPresence"]().id_v1) + except: + print("fail with " + self.name) + result = {"id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'motion')), + "id_v1": "/sensors/" + self.elements["ZLLPresence"]().id_v1, + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "enabled": self.elements["ZLLPresence"]().config["on"], + "motion": { + "motion": self.elements["ZLLPresence"]().state["presence"], + "motion_valid": True, + "motion_report": { + "changed": self.elements["ZLLPresence"]().state["lastupdated"], + "motion": self.elements["ZLLPresence"]().state["presence"] + } + }, + "sensitivity": { + "status": "set", + "sensitivity": 2, + "sensitivity_max": 2 + }, + "type": "motion" + } + + return result + + def getTemperature(self): + result = None + if self.modelid == "SML001": + temperature = self.elements["ZLLTemperature"]( + ).state["temperature"] + result = {"id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'temperature')), + "id_v1": "/sensors/" + self.elements["ZLLTemperature"]().id_v1, + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "enabled": self.elements["ZLLTemperature"]().config["on"], + "temperature": { + "temperature": temperature, + "temperature_valid": True, + "temperature_report": { + "changed": self.elements["ZLLTemperature"]().state["lastupdated"], + "temperature": temperature + } + }, + "type": "temperature" + } + return result + + def getContact(self): + result = None + if self.modelid == "SOC001": + result = {"id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'contact')), + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "enabled": self.elements["ZLLContact"]().config["on"], + "contact_report": { + "changed": self.elements["ZLLContact"]().state["lastupdated"], + "state": "contact" if self.elements["ZLLContact"]().state["contact"] else "no_contact" + }, + "type": "contact" + } + return result + + def getTamper(self): + result = None + if self.modelid == "SOC001": + result = {"id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'tamper')), + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "enabled": self.elements["ZLLTamper"]().config["on"], + "tamper_reports": [{ + "changed": "2024-09-06T21:16:17.512Z", + "source": "battery_door", + "state": "not_tampered" + }], + "type": "tamper" + } + return result + + def getLightLevel(self): + result = None + if self.modelid == "SML001": + result = { + "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'light_level')), + "id_v1": "/sensors/" + self.elements["ZLLLightLevel"]().id_v1, + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "enabled": self.elements["ZLLLightLevel"]().config["on"], + "light": { + "light_level": self.elements["ZLLLightLevel"]().state["lightlevel"], + "light_level_valid": True, + "light_level_report": { + "changed": self.elements["ZLLLightLevel"]().state["lastupdated"], + "light_level": self.elements["ZLLLightLevel"]().state["lightlevel"] + } + }, + "type": "light_level" + } + return result + + def getZigBee(self): + result = {} + result["id"] = str(uuid.uuid5( + uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')) + result["id_v1"] = "/" + self.type + "/" + self.firstElement().id_v1 + result["owner"] = { + "rid": self.id_v2, + "rtype": "device" + } + result["type"] = "zigbee_connectivity" + result["mac_address"] = self.firstElement().uniqueid[:23] + result["status"] = "connected" + return result + + def getRotary(self): + result = [] + if self.modelid == "RDM002" and self.type == "ZLLRelativeRotary": + result.append({ + "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'relative_rotary')), + "id_v1": "/sensors/" + self.id_v1, + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "rotary_report": { + "updated": self.state["lastupdated"], + "action": "start" if self.state["rotaryevent"] == 1 else "repeat", + "rotation": { + "direction": "right", # self.state["direction"], + "steps": self.state["expectedrotation"], + "duration": self.state["expectedeventduration"] + } + }, + "type": "relative_rotary" + }) + return result + + def getDevicePower(self): + result = None + if "battery" in self.firstElement().config: + result = { + "id": str(uuid.uuid5( + uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), + "id_v1": "/" + self.firstElement().getObjectPath()["resource"] + "/" + self.firstElement().getObjectPath()["id"], + "owner": { + "rid": self.id_v2, + "rtype": "device" + }, + "power_state": {}, + "type": "device_power" + } + if self.firstElement().config["battery"]: + result["power_state"].update({"battery_level": self.firstElement().config["battery"], + "battery_state": "normal" + }) + return result + + def getV2Entertainment(self): + if self.group_v1 != "lights": + return None + entertainmenUuid = str(uuid.uuid5( + uuid.NAMESPACE_URL, self.id_v2 + 'entertainment')) + result = { + "equalizer": True, + "id": entertainmenUuid, + "id_v1": "/lights/" + self.firstElement().id_v1, + "proxy": lightTypes[self.modelid]["v1_static"]["capabilities"]["streaming"]["proxy"], + "renderer": lightTypes[self.modelid]["v1_static"]["capabilities"]["streaming"]["renderer"], + "renderer_reference": { + "rid": self.firstElement().id_v2, + "rtype": "light" + } + } + result["owner"] = { + "rid": self.id_v2, "rtype": "device"} + result["segments"] = { + "configurable": False + } + if self.modelid == "LCX002": + result["segments"]["max_segments"] = 7 + result["segments"]["segments"] = [ + { + "length": 2, + "start": 0 + }, + { + "length": 2, + "start": 2 + }, + { + "length": 4, + "start": 4 + }, + { + "length": 4, + "start": 8 + }, + { + "length": 4, + "start": 12 + }, + { + "length": 2, + "start": 16 + }, + { + "length": 2, + "start": 18 + }] + elif self.modelid in ["915005987201", "LCX004", "LCX006"]: + result["segments"]["max_segments"] = 10 + result["segments"]["segments"] = [ + { + "length": 3, + "start": 0 + }, + { + "length": 4, + "start": 3 + }, + { + "length": 3, + "start": 7 + } + ] + else: + result["segments"]["max_segments"] = 1 + result["segments"]["segments"] = [{ + "length": 1, + "start": 0 + }] + result["type"] = "entertainment" + return result + + def update_attr(self, newdata): + for key, value in newdata.items(): + updateAttribute = getattr(self, key) + if isinstance(updateAttribute, dict): + updateAttribute.update(value) + setattr(self, key, updateAttribute) + else: + setattr(self, key, value) + + def save(self): + result = {} + result["name"] = self.name + result["id"] = self.id_v2 # for config save compatibility + result["elements"] = [] + result["type"] = self.type + result["group_v1"] = self.group_v1 + result["modelid"] = self.modelid + result["protocol"] = self.protocol + result["protocol_cfg"] = self.protocol_cfg + for key, element in self.elements.items(): + if element(): + result["elements"].append(element().id_v1) + else: + return None + return result diff --git a/BridgeEmulator/HueObjects/Group.py b/BridgeEmulator/HueObjects/Group.py index ca9cd6eab..10f824903 100644 --- a/BridgeEmulator/HueObjects/Group.py +++ b/BridgeEmulator/HueObjects/Group.py @@ -9,12 +9,9 @@ class Group(): def __init__(self, data): - self.name = data["name"] if "name" in data else "Group " + \ - data["id_v1"] + self.name = data["name"] if "name" in data else "Group " + data["id_v1"] self.id_v1 = data["id_v1"] self.id_v2 = data["id_v2"] if "id_v2" in data else genV2Uuid() - if "owner" in data: - self.owner = data["owner"] self.icon_class = data["class"] if "class" in data else "Other" self.lights = [] self.action = {"on": False, "bri": 100, "hue": 0, "sat": 254, "effect": "none", "xy": [ @@ -31,15 +28,17 @@ def __init__(self, data): } StreamEvent(streamMessage) - def groupZeroStream(self, rooms, lights): + def groupZeroStream(self, groups, lights): streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"children": [], "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'bridge_home')), "id_v1":"/groups/0", "type": "bridge_home"}], - "id": str(uuid.uuid4()), - "type": "update" - } - for room in rooms: - streamMessage["data"][0]["children"].append( - {"rid": room, "rtype": "room"}) + "data": [{"children": [], "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'bridge_home')), "id_v1":"/groups/0", "type": "bridge_home"}], + "id": str(uuid.uuid4()), + "type": "update" + } + for group in groups: + if group.type == "Room": + streamMessage["data"][0]["children"].append({"rid": group.getV2Room()["id"], "rtype": "room"}) + elif group.type == "Zone": + streamMessage["data"][0]["children"].append({"rid": group.getV2Zone()["id"], "rtype": "zone"}) for light in lights: streamMessage["data"][0]["children"].append( {"rid": light, "rtype": "light"}) @@ -86,11 +85,12 @@ def add_light(self, light): StreamEvent(streamMessage) groupChildrens = [] groupServices = [] - for light in self.lights: - if light(): + for device in self.lights: + if device() and device().group_v1 == "lights": + light = device().firstElement() groupChildrens.append( - {"rid": light().getDevice()["id"], "rtype": "device"}) - groupServices.append({"rid": light().id_v2, "rtype": "light"}) + {"rid": device().getDevice()["id"], "rtype": "device"}) + groupServices.append({"rid": light.id_v2, "rtype": "light"}) groupServices.append({"rid": self.id_v2, "rtype": "grouped_light"}) streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "data": [{"children": groupChildrens, "id": elementId, "id_v1": "/groups/" + self.id_v1, "services": groupServices, "type": elementType}], @@ -99,9 +99,6 @@ def add_light(self, light): } StreamEvent(streamMessage) - def add_sensor(self, sensor): - self.sensors.append(weakref.ref(sensor)) - def update_attr(self, newdata): if "lights" in newdata: # update of the lights must be done using add_light function del newdata["lights"] @@ -133,11 +130,12 @@ def update_state(self): lights_on = 0 if len(self.lights) == 0: all_on = False - for light in self.lights: - if light(): - if light().state["on"]: + for device in self.lights: + if device() and device().group_v1 == "lights": + light = device().firstElement() + if light.state["on"]: any_on = True - bri = bri + light().state["bri"] + bri = bri + light.state["bri"] lights_on = lights_on + 1 else: all_on = False @@ -156,10 +154,11 @@ def setV1Action(self, state, scene=None): self.genStreamEvent(v2State) def genStreamEvent(self, v2State): - for light in self.lights: - if light(): + for device in self.lights: + if device() and device().group_v1 == "lights": + light = device().firstElement() streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": light().id_v2, "id_v1": "/lights/" + light().id_v1, "owner": {"rid": light().getDevice()["id"], "rtype":"device"}, "type": "light"}], + "data": [{"id": light.id_v2, "id_v1": "/lights/" + light.id_v1, "owner": {"rid": device().getDevice()["id"], "rtype":"device"}, "type": "light"}], "id": str(uuid.uuid4()), "type": "update" } @@ -182,16 +181,14 @@ def genStreamEvent(self, v2State): def getV1Api(self): result = {} result["name"] = self.name - if hasattr(self, "owner"): - result["owner"] = self.owner.username lights = [] - for light in self.lights: - if light(): - lights.append(light().id_v1) sensors = [] - for sensor in self.sensors: - if sensor(): - sensors.append(sensor().id_v1) + for device in self.lights: + if device(): + if device().group_v1 == "lights": + lights.append(device().firstElement().id_v1) + elif device().group_v1 == "sensors": + sensors.append(device().firstElement().id_v1) result["lights"] = lights result["sensors"] = sensors result["type"] = self.type.capitalize() @@ -209,31 +206,18 @@ def getV1Api(self): def getV2Room(self): result = {"children": [], "services": []} - for light in self.lights: - if light(): + for device in self.lights: + if device(): result["children"].append({ - "rid": str(uuid.uuid5( - uuid.NAMESPACE_URL, light().id_v2 + 'device')), + "rid": device().id_v2, "rtype": "device" }) - - #result["grouped_services"].append({ - # "rid": self.id_v2, - # "rtype": "grouped_light" - #}) result["id"] = str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'room')) result["id_v1"] = "/groups/" + self.id_v1 result["metadata"] = { "archetype": self.icon_class.replace(" ", "_").replace("'", "").lower(), "name": self.name } - for light in self.lights: - if light(): - result["services"].append({ - "rid": light().id_v2, - "rtype": "light" - }) - result["services"].append({ "rid": self.id_v2, "rtype": "grouped_light" @@ -244,27 +228,22 @@ def getV2Room(self): def getV2Zone(self): result = {"children": [], "services": []} - for light in self.lights: - if light(): + for device in self.lights: + if device() and device().group_v1 == "lights": result["children"].append({ - "rid": light().id_v2, + "rid": device().firstElement().id_v2, "rtype": "light" }) - - #result["grouped_services"].append({ - # "rid": self.id_v2, - # "rtype": "grouped_light" - #}) result["id"] = str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zone')) result["id_v1"] = "/groups/" + self.id_v1 result["metadata"] = { "archetype": self.icon_class.replace(" ", "_").replace("'", "").lower(), "name": self.name } - for light in self.lights: - if light(): + for device in self.lights: + if device() and device().group_v1 == "lights": result["services"].append({ - "rid": light().id_v2, + "rid": device().firstElement().id_v2, "rtype": "light" }) @@ -290,11 +269,13 @@ def getV2GroupedLight(self): result["id"] = self.id_v2 result["id_v1"] = "/groups/" + self.id_v1 result["on"] = {"on": self.update_state()["any_on"]} + if self.id_v1 == 0: + result["owner"] = {"rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'bridge_home')), "rtype": "bridge_home"} + elif self.type == "Room": + result["owner"] = {"rid": self.getV2Room()["id"], "rtype": "room"} + elif self.type == "Zone": + result["owner"] = {"rid": self.getV2Zone()["id"], "rtype": "zone"} result["type"] = "grouped_light" - if hasattr(self, "owner"): - result["owner"] = {"rid": self.owner.username, "rtype": "device"} - else: - result["owner"] = {"rid": self.id_v2, "rtype": "device"} result["signaling"] = {"signal_values": [ "no_signal", "on_off"]} @@ -307,9 +288,8 @@ def getObjectPath(self): def save(self): result = {"id_v2": self.id_v2, "name": self.name, "class": self.icon_class, "lights": [], "action": self.action, "type": self.type} - if hasattr(self, "owner"): - result["owner"] = self.owner.username - for light in self.lights: - if light(): - result["lights"].append(light().id_v1) + if self.id_v1 != "0": + for device in self.lights: + if device(): + result["lights"].append(device().id_v2) return result diff --git a/BridgeEmulator/HueObjects/Light.py b/BridgeEmulator/HueObjects/Light.py index d177aa0a1..98fc903cd 100644 --- a/BridgeEmulator/HueObjects/Light.py +++ b/BridgeEmulator/HueObjects/Light.py @@ -29,25 +29,6 @@ def __init__(self, data): self.effect = "no_effect" self.function = data["function"] if "function" in data else "mixed" - # entertainment - streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": str(uuid.uuid5( - uuid.NAMESPACE_URL, self.id_v2 + 'entertainment')), "type": "entertainent"}], - "id": str(uuid.uuid4()), - "type": "add" - } - streamMessage["id_v1"] = "/lights/" + self.id_v1 - streamMessage["data"][0].update(self.getV2Entertainment()) - StreamEvent(streamMessage) - - # zigbee_connectivity - streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [self.getZigBee()], - "id": str(uuid.uuid4()), - "type": "add" - } - StreamEvent(streamMessage) - # light streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), "data": [self.getV2Api()], @@ -56,14 +37,6 @@ def __init__(self, data): } StreamEvent(streamMessage) - # device - streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [self.getDevice()], - "id": str(uuid.uuid4()), - "type": "add" - } - streamMessage["data"][0].update(self.getDevice()) - StreamEvent(streamMessage) def __del__(self): ## light ## @@ -75,33 +48,6 @@ def __del__(self): streamMessage["id_v1"] = "/lights/" + self.id_v1 StreamEvent(streamMessage) - ## device ## - streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": self.getDevice()["id"], "type": "device"}], - "id": str(uuid.uuid4()), - "type": "delete" - } - streamMessage["id_v1"] = "/lights/" + self.id_v1 - StreamEvent(streamMessage) - - # Zigbee Connectivity - streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": self.getZigBee()["id"], "type": "zigbee_connectivity"}], - "id": str(uuid.uuid4()), - "type": "delete" - } - streamMessage["id_v1"] = "/lights/" + self.id_v1 - StreamEvent(streamMessage) - - # Entertainment - streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": self.getV2Entertainment()["id"], "type": "entertainment"}], - "id": str(uuid.uuid4()), - "type": "delete" - } - streamMessage["id_v1"] = "/lights/" + self.id_v1 - StreamEvent(streamMessage) - logging.info(self.name + " light was destroyed.") def update_attr(self, newdata): @@ -112,12 +58,12 @@ def update_attr(self, newdata): setattr(self, key, updateAttribute) else: setattr(self, key, value) - streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [self.getDevice()], - "id": str(uuid.uuid4()), - "type": "update" - } - StreamEvent(streamMessage) + #streamMessage = {"creationtime": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), + # "data": [self.getDevice()], + # "id": str(uuid.uuid4()), + # "type": "update" + # } + #StreamEvent(streamMessage) def getV1Api(self): result = lightTypes[self.modelid]["v1_static"] @@ -211,58 +157,10 @@ def genStreamEvent(self, v2State): } streamMessage["id_v1"] = "/lights/" + self.id_v1 streamMessage["data"][0].update(v2State) - streamMessage["data"][0].update( - {"owner": {"rid": self.getDevice()["id"], "rtype": "device"}}) + #streamMessage["data"][0].update( + # {"owner": {"rid": self.getDevice()["id"], "rtype": "device"}}) StreamEvent(streamMessage) - def getDevice(self): - result = {"id": str(uuid.uuid5( - uuid.NAMESPACE_URL, self.id_v2 + 'device'))} - result["id_v1"] = "/lights/" + self.id_v1 - result["identify"] = {} - result["metadata"] = { - "archetype": archetype[self.config["archetype"]], - "name": self.name - } - result["product_data"] = lightTypes[self.modelid]["device"] - result["product_data"]["model_id"] = self.modelid - - result["services"] = [ - { - "rid": self.id_v2, - "rtype": "light" - }, - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), - "rtype": "zigbee_connectivity" - }, - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'entertainment')), - "rtype": "entertainment" - } - ] - result["type"] = "device" - return result - - def getZigBee(self): - result = {} - result["id"] = str(uuid.uuid5(uuid.NAMESPACE_URL, - self.id_v2 + 'zigbee_connectivity')) - result["id_v1"] = "/lights/" + self.id_v1 - result["mac_address"] = self.uniqueid[:23] - result["owner"] = { - "rid": self.getDevice()["id"], - "rtype": "device" - } - result["status"] = "connected" if self.state["reachable"] else "connectivity_issue" - result["type"] = "zigbee_connectivity" - return result - - def getBridgeHome(self): - return { - "rid": self.id_v2, - "rtype": "light" - } def getV2Api(self): result = {} @@ -357,81 +255,6 @@ def getV2Api(self): result["type"] = "light" return result - def getV2Entertainment(self): - entertainmenUuid = str(uuid.uuid5( - uuid.NAMESPACE_URL, self.id_v2 + 'entertainment')) - result = { - "equalizer": True, - "id": entertainmenUuid, - "id_v1": "/lights/" + self.id_v1, - "proxy": lightTypes[self.modelid]["v1_static"]["capabilities"]["streaming"]["proxy"], - "renderer": lightTypes[self.modelid]["v1_static"]["capabilities"]["streaming"]["renderer"], - "renderer_reference": { - "rid": self.id_v2, - "rtype": "light" - } - } - result["owner"] = { - "rid": self.getDevice()["id"], "rtype": "device"} - result["segments"] = { - "configurable": False - } - if self.modelid == "LCX002": - result["segments"]["max_segments"] = 7 - result["segments"]["segments"] = [ - { - "length": 2, - "start": 0 - }, - { - "length": 2, - "start": 2 - }, - { - "length": 4, - "start": 4 - }, - { - "length": 4, - "start": 8 - }, - { - "length": 4, - "start": 12 - }, - { - "length": 2, - "start": 16 - }, - { - "length": 2, - "start": 18 - }] - elif self.modelid in ["915005987201", "LCX004", "LCX006"]: - result["segments"]["max_segments"] = 10 - result["segments"]["segments"] = [ - { - "length": 3, - "start": 0 - }, - { - "length": 4, - "start": 3 - }, - { - "length": 3, - "start": 7 - } - ] - else: - result["segments"]["max_segments"] = 1 - result["segments"]["segments"] = [{ - "length": 1, - "start": 0 - }] - result["type"] = "entertainment" - return result - def getObjectPath(self): return {"resource": "lights", "id": self.id_v1} diff --git a/BridgeEmulator/HueObjects/Scene.py b/BridgeEmulator/HueObjects/Scene.py index f6a9bf836..456dc5316 100644 --- a/BridgeEmulator/HueObjects/Scene.py +++ b/BridgeEmulator/HueObjects/Scene.py @@ -31,7 +31,10 @@ def __init__(self, data): self.status = data["status"] if "status" in data else "inactive" if "group" in data: self.storelightstate() - self.lights = self.group().lights + self.lights = [] + for device in self.group().lights: + if device(): + self.lights.append(device().elements["light"]) streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "data": [self.getV2Api()], "id": str(uuid.uuid4()), @@ -65,13 +68,14 @@ def activate(self, data): Thread(target=light().dynamicScenePlay, args=[ self.palette, lightIndex]).start() lightIndex += 1 + return - if data["recall"]["action"] == "deactivate": + elif data["recall"]["action"] == "deactivate": self.status = "inactive" - return + return + self.status = data["recall"]["action"] queueState = {} - self.status = data["recall"]["action"] for light, state in self.lightstates.items(): logging.debug(state) light.state.update(state) @@ -199,9 +203,9 @@ def getV2Api(self): def storelightstate(self): lights = [] if self.type == "GroupScene": - for light in self.group().lights: - if light(): - lights.append(light) + for device in self.group().lights: + if device(): + lights.append(weakref.ref(device().firstElement())) else: for light in self.lightstates.keys(): if light(): diff --git a/BridgeEmulator/HueObjects/Sensor.py b/BridgeEmulator/HueObjects/Sensor.py index 89fee505a..4424a8920 100644 --- a/BridgeEmulator/HueObjects/Sensor.py +++ b/BridgeEmulator/HueObjects/Sensor.py @@ -50,48 +50,14 @@ def __init__(self, data): self.swversion = data["swversion"] if "swversion" in data else None self.recycle = data["recycle"] if "recycle" in data else False self.uniqueid = data["uniqueid"] if "uniqueid" in data else None - if self.getDevice() != None: - streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": self.id_v2, "type": "device"}], - "id": str(uuid.uuid4()), - "type": "add" - } - streamMessage["data"][0].update(self.getDevice()) - StreamEvent(streamMessage) + def __del__(self): - if self.modelid in ["SML001", "RWL022"]: - streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "data": [{"id": self.getDevice()["id"], "type": "device"}], - "id": str(uuid.uuid4()), - "type": "delete" - } - streamMessage["id_v1"] = "/sensors/" + self.id_v1 - StreamEvent(streamMessage) logging.info(self.name + " sensor was destroyed.") def setV1State(self, state): self.state.update(state) - def getBridgeHome(self): - if self.modelid == "SML001": - if self.type == "ZLLPresence": - rtype = "motion" - elif self.type == "ZLLLightLevel": - rtype = "light_level" - elif self.type == "ZLLTemperature": - rtype = "temperature" - return { - "rid": self.id_v2, - "rtype": rtype - } - else: - return { - "rid": self.id_v2, - "rtype": 'device' - } - return False - def getV1Api(self): result = {} if self.modelid in sensorTypes: @@ -114,296 +80,6 @@ def getV1Api(self): def getObjectPath(self): return {"resource": "sensors", "id": self.id_v1} - def getDevice(self): - result = None - if self.modelid == "SML001" and self.type == "ZLLPresence": - result = {"id": self.id_v2, "id_v1": "/sensors/" + self.id_v1, "type": "device"} - result["identify"] = {} - result["metadata"] = { - "archetype": "unknown_archetype", - "name": self.name - } - result["product_data"] = { - "certified": True, - "manufacturer_name": "Signify Netherlands B.V.", - "model_id": self.modelid, - "product_archetype": "unknown_archetype", - "product_name": "Hue motion sensor", - "software_version": "1.1.27575" - } - result["services"] = [ - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'motion')), - "rtype": "motion" - }, - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), - "rtype": "device_power" - }, - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), - "rtype": "zigbee_connectivity" - }, - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'light_level')), - "rtype": "light_level" - }, - { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'temperature')), - "rtype": "temperature" - }] - result["type"] = "device" - elif self.modelid == "RWL022" or self.modelid == "RWL021" or self.modelid == "RWL020": - result = {"id": self.id_v2, "id_v1": "/sensors/" + self.id_v1, "type": "device"} - result["identify"] = {} - result["product_data"] = {"model_id": self.modelid, - "manufacturer_name": "Signify Netherlands B.V.", - "product_name": "Hue dimmer switch", - "product_archetype": "unknown_archetype", - "certified": True, - "software_version": "2.44.0", - "hardware_platform_type": "100b-119" - } - result["metadata"] = { - "archetype": "unknown_archetype", - "name": self.name - } - result["services"] = [{ - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button1')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button2')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button3')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button4')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), - "rtype": "device_power" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), - "rtype": "zigbee_connectivity" - }] - result["type"] = "device" - elif self.modelid == "RDM002" and self.type != "ZLLRelativeRotary": - result = {"id": self.id_v2, "id_v1": "/sensors/" + self.id_v1, "type": "device"} - result["identify"] = {} - result["product_data"] = {"model_id": self.modelid, - "manufacturer_name": "Signify Netherlands B.V.", - "product_name": "Hue tap dial switch", - "product_archetype": "unknown_archetype", - "certified": True, - "software_version": "2.59.25", - "hardware_platform_type": "100b-119" - } - result["metadata"] = { - "archetype": "unknown_archetype", - "name": self.name - } - result["services"] = [{ - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button1')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button2')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button3')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button4')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), - "rtype": "device_power" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), - "rtype": "zigbee_connectivity" - }] - result["type"] = "device" - elif self.modelid == "RDM002" and self.type == "ZLLRelativeRotary": - result = {"id": self.id_v2, "id_v1": "/sensors/" + self.id_v1, "type": "device"} - result["identify"] = {} - result["product_data"] = {"model_id": self.modelid, - "manufacturer_name": "Signify Netherlands B.V.", - "product_name": "Hue tap dial switch", - "product_archetype": "unknown_archetype", - "certified": True, - "software_version": "2.59.25", - "hardware_platform_type": "100b-119" - } - result["metadata"] = { - "archetype": "unknown_archetype", - "name": self.name - } - result["services"] = [{ - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button3')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button4')), - "rtype": "button" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), - "rtype": "device_power" - }, { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')), - "rtype": "zigbee_connectivity" - }] - result["type"] = "device" - return result - - def getMotion(self): - result = None - if self.modelid == "SML001" and self.type == "ZLLPresence": - result = { - "enabled": self.config["on"], - "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'motion')), - "id_v1": "/sensors/" + self.id_v1, - "motion": { - "motion_report": { - "changed": self.state["lastupdated"], - "motion": True if self.state["presence"] else False, - } - }, - "sensitivity": { - "status": "set", - "sensitivity": 2, - "sensitivity_max": 2 - }, - "owner": { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device')), - "rtype": "device" - }, - "type": "motion"} - return result - - def getTemperature(self): - result = None - if self.modelid == "SML001" and self.type == "ZLLTemperature": - result = { - "enabled": self.config["on"], - "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'temperature')), - "id_v1": "/sensors/" + self.id_v1, - "temperature": { - "temperature_report":{ - "changed": self.state["lastupdated"], - "temperature": self.state["temperature"]/100 - } - }, - "owner": { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device')), - "rtype": "device" - }, - "type": "temperature"} - return result - - def getLightlevel(self): - result = None - if self.modelid == "SML001" and self.type == "ZLLLightLevel": - result = { - "enabled": self.config["on"], - "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'light_level')), - "id_v1": "/sensors/" + self.id_v1, - "light": { - "light_level_report":{ - "changed": self.state["lastupdated"], - "light_level": self.state["lightlevel"] - } - }, - "owner": { - "rid": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'device')), - "rtype": "device" - }, - "type": "light_level"} - return result - - def getZigBee(self): - result = None - if self.modelid == "SML001" and self.type != "ZLLPresence": - return None - if not self.uniqueid: - return None - result = {} - result["id"] = str(uuid.uuid5( - uuid.NAMESPACE_URL, self.id_v2 + 'zigbee_connectivity')) - result["id_v1"] = "/sensors/" + self.id_v1 - result["owner"] = { - "rid": self.id_v2, - "rtype": "device" - } - result["type"] = "zigbee_connectivity" - result["mac_address"] = self.uniqueid[:23] - result["status"] = "connected" - return result - - def getButtons(self): - result = [] - if self.modelid == "RWL022" or self.modelid == "RWL021" or self.modelid == "RWL020" or self.modelid == "RDM002" and self.type != "ZLLRelativeRotary": - for button in range(4): - result.append({ - "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'button' + str(button + 1))), - "id_v1": "/sensors/" + self.id_v1, - "owner": { - "rid": self.id_v2, - "rtype": "device" - }, - "metadata": { - "control_id": button + 1 - }, - "button": { - "button_report": { - "updated": self.state["lastupdated"], - "event": "initial_press" - } - }, - "type": "button" - }) - return result - - def getRotary(self): - result = [] - if self.modelid == "RDM002" and self.type == "ZLLRelativeRotary": - result.append({ - "id": str(uuid.uuid5(uuid.NAMESPACE_URL, self.id_v2 + 'relative_rotary')), - "id_v1": "/sensors/" + self.id_v1, - "owner": { - "rid": self.id_v2, - "rtype": "device" - }, - "rotary_report": { - "updated": self.state["lastupdated"], - "action": "start" if self.state["rotaryevent"] == 1 else "repeat", - "rotation": { - "direction": "right",#self.state["direction"], - "steps": self.state["expectedrotation"], - "duration": self.state["expectedeventduration"] - } - }, - "type": "relative_rotary" - }) - return result - - def getDevicePower(self): - result = None - if "battery" in self.config: - result = { - "id": str(uuid.uuid5( - uuid.NAMESPACE_URL, self.id_v2 + 'device_power')), - "id_v1": "/sensors/" + self.id_v1, - "owner": { - "rid": self.id_v2, - "rtype": "device" - }, - "power_state": {}, - "type": "device_power" - } - if self.config["battery"]: - result["power_state"].update({"battery_level": self.config["battery"], - "battery_state": "normal" - }) - return result def update_attr(self, newdata): if self.id_v1 == "1" and "config" in newdata: # manage daylight sensor diff --git a/BridgeEmulator/HueObjects/__init__.py b/BridgeEmulator/HueObjects/__init__.py index 6ddd724b9..08946b274 100644 --- a/BridgeEmulator/HueObjects/__init__.py +++ b/BridgeEmulator/HueObjects/__init__.py @@ -59,9 +59,9 @@ def setGroupAction(group, state, scene=None): else: state = incProcess(group.action, state) - for light in group.lights: - if light(): - lightsState[light().id_v1] = state + for device in group.lights: + if device(): + lightsState[device().firstElement().id_v1] = state if "xy" in state: group.action["colormode"] = "xy" elif "ct" in state: @@ -73,35 +73,34 @@ def setGroupAction(group, state, scene=None): group.state["any_on"] = state["on"] group.state["all_on"] = state["on"] group.action.update(state) - queueState = {} - for light in group.lights: - if light() and light().id_v1 in lightsState: # apply only if the light belong to this group - for key, value in lightsState[light().id_v1].items(): - if key in light().state: - light().state[key] = value - light().updateLightState(lightsState[light().id_v1]) - # apply max and min brightness limis - if "bri" in lightsState[light().id_v1]: - if "min_bri" in light().protocol_cfg and light().protocol_cfg["min_bri"] > lightsState[light().id_v1]["bri"]: - lightsState[light().id_v1]["bri"] = light().protocol_cfg["min_bri"] - if "max_bri" in light().protocol_cfg and light().protocol_cfg["max_bri"] < lightsState[light().id_v1]["bri"]: - lightsState[light().id_v1]["bri"] = light().protocol_cfg["max_bri"] - if light().protocol == "mqtt" and not light().state["on"]: - continue - # end limits - if light().protocol in ["native_multi", "mqtt"]: - if light().protocol_cfg["ip"] not in queueState: - queueState[light().protocol_cfg["ip"]] = { - "object": light(), "lights": {}} - if light().protocol == "native_multi": - queueState[light().protocol_cfg["ip"]]["lights"][light( - ).protocol_cfg["light_nr"]] = lightsState[light().id_v1] - elif light().protocol == "mqtt": - queueState[light().protocol_cfg["ip"]]["lights"][light( - ).protocol_cfg["command_topic"]] = lightsState[light().id_v1] - else: - light().setV1State(lightsState[light().id_v1]) + for device in group.lights: + if device(): + light = device().firstElement() + if light.id_v1 in lightsState: # apply only if the light belong to this group + for key, value in lightsState[light.id_v1].items(): + if key in light.state: + light.state[key] = value + light.updateLightState(lightsState[light.id_v1]) + # apply max and min brightness limis + if "bri" in lightsState[light.id_v1]: + if "min_bri" in light.protocol_cfg and light.protocol_cfg["min_bri"] > lightsState[light.id_v1]["bri"]: + lightsState[light.id_v1]["bri"] = light.protocol_cfg["min_bri"] + if "max_bri" in light.protocol_cfg and light.protocol_cfg["max_bri"] < lightsState[light.id_v1]["bri"]: + lightsState[light.id_v1]["bri"] = light.protocol_cfg["max_bri"] + if light.protocol == "mqtt" and not light.state["on"]: + continue + # end limits + if light.protocol in ["native_multi", "mqtt"]: + if light.protocol_cfg["ip"] not in queueState: + queueState[light.protocol_cfg["ip"]] = { + "object": light, "lights": {}} + if light.protocol == "native_multi": + queueState[light.protocol_cfg["ip"]]["lights"][light.protocol_cfg["light_nr"]] = lightsState[light.id_v1] + elif light.protocol == "mqtt": + queueState[light.protocol_cfg["ip"]]["lights"][light.protocol_cfg["command_topic"]] = lightsState[light.id_v1] + else: + light.setV1State(lightsState[light.id_v1]) for device, state in queueState.items(): state["object"].setV1State(state) diff --git a/BridgeEmulator/configManager/configHandler.py b/BridgeEmulator/configManager/configHandler.py index 641f6272c..706a65ac9 100644 --- a/BridgeEmulator/configManager/configHandler.py +++ b/BridgeEmulator/configManager/configHandler.py @@ -7,7 +7,8 @@ import yaml import uuid import weakref -from HueObjects import Light, Group, EntertainmentConfiguration, Scene, ApiUser, Rule, ResourceLink, Schedule, Sensor, BehaviorInstance, SmartScene +from time import sleep +from HueObjects import Light, Group, EntertainmentConfiguration, Scene, ApiUser, Rule, ResourceLink, Schedule, Sensor, BehaviorInstance, SmartScene, Device try: from time import tzset except ImportError: @@ -38,7 +39,7 @@ def __init__(self): os.makedirs(self.configDir) def load_config(self): - self.yaml_config = {"apiUsers": {}, "lights": {}, "groups": {}, "scenes": {}, "config": {}, "rules": {}, "resourcelinks": {}, "schedules": {}, "sensors": {}, "behavior_instance": {}, "geofence_clients": {}, "smart_scene": {}, "temp": {"eventstream": [], "scanResult": {"lastscan": "none"}, "detectedLights": [], "gradientStripLights": {}}} + self.yaml_config = {"apiUsers": {}, "lights": {}, "groups": {}, "scenes": {}, "config": {}, "rules": {}, "resourcelinks": {}, "schedules": {}, "sensors": {}, "behavior_instance": {}, "geofence_clients": {}, "smart_scene": {}, "behavior_instance": {}, "device": {}, "temp": {"eventstream": [], "scanResult": {"lastscan": "none"}, "detectedLights": [], "gradientStripLights": {}}} try: #load config if os.path.exists(self.configDir + "/config.yaml"): @@ -77,18 +78,18 @@ def load_config(self): config["zigbee_device_discovery_info"] = {"status": "ready"} if "swupdate2" not in config: config["swupdate2"] = {"autoinstall": { - "on": False, - "updatetime": "T14:00:00" - }, - "bridge": { - "lastinstall": "2020-12-11T17:08:55", - "state": "noupdates" - }, - "checkforupdate": False, - "lastchange": "2020-12-13T10:30:15", - "state": "noupdates", - "install": False - } + "on": False, + "updatetime": "T14:00:00" + }, + "bridge": { + "lastinstall": "2020-12-11T17:08:55", + "state": "noupdates" + }, + "checkforupdate": False, + "lastchange": "2020-12-13T10:30:15", + "state": "noupdates", + "install": False + } if int(config["swversion"]) < 1958077010: config["swversion"] = "1965111030" @@ -146,35 +147,82 @@ def load_config(self): for light, data in lights.items(): data["id_v1"] = light self.yaml_config["lights"][light] = Light.Light(data) - #self.yaml_config["groups"]["0"].add_light(self.yaml_config["lights"][light]) + #sensors + if os.path.exists(self.configDir + "/sensors.yaml"): + sensors = _open_yaml(self.configDir + "/sensors.yaml") + for sensor, data in sensors.items(): + data["id_v1"] = sensor + self.yaml_config["sensors"][sensor] = Sensor.Sensor(data) + else: + data = {"modelid": "PHDL00", "name": "Daylight", "type": "Daylight", "id_v1": "1"} + self.yaml_config["sensors"]["1"] = Sensor.Sensor(data) + + #device + if os.path.exists(self.configDir + "/device.yaml"): + devices = _open_yaml(self.configDir + "/device.yaml") + for device, data in devices.items(): + newDevice = Device.Device(data) + self.yaml_config["device"][device] = newDevice + for element in data["elements"]: + obj = self.yaml_config[data["group_v1"]][element] + if data["group_v1"] == "lights": + self.yaml_config["device"][device].add_element("light", obj) + else: + self.yaml_config["device"][device].add_element(obj.type, obj) + else: # temporary for config migration + processedMotionsSensors = [] + for key, sensor in self.yaml_config["sensors"].items(): + if sensor.uniqueid and ":" in sensor.uniqueid: + if sensor.type not in ["ZLLLightLevel", "ZLLTemperature"]: + data = {"name": sensor.name, "modelid": sensor.modelid, "type": "sensors", "id_v2": str(uuid.uuid5(uuid.NAMESPACE_URL, sensor.id_v2 + 'device')), "protocol": sensor.protocol, "protocol_cfg": sensor.protocol_cfg} + newDevice = Device.Device(data) + self.yaml_config["device"][newDevice.id_v2] = newDevice + if sensor.modelid == "SML001": + if sensor.id_v1 not in processedMotionsSensors: + # find all 3 sensors + for key, i in self.yaml_config["sensors"].items(): + if i.modelid == "SML001": + if i.uniqueid[:26] == sensor.uniqueid[:26]: + newDevice.add_element(i.type, i) + processedMotionsSensors.append(i.id_v1) + else: + newDevice.add_element(sensor.type, sensor) + for key, light in self.yaml_config["lights"].items(): + data = {"name": light.name, "modelid": light.modelid, "type": "lights", "id_v2": str(uuid.uuid5(uuid.NAMESPACE_URL, light.id_v2 + 'device')), "protocol": light.protocol, "protocol_cfg": light.protocol_cfg} + newDevice = Device.Device(data) + newDevice.group_v1 = "lights" + newDevice.add_element(key, light) + self.yaml_config["groups"]["0"].add_light(newDevice) + self.yaml_config["device"][newDevice.id_v2] = newDevice #groups - #create group 0 - self.yaml_config["groups"]["0"] = Group.Group({"name":"Group 0","id_v1": "0","type":"LightGroup","state":{"all_on":False,"any_on":True},"recycle":False,"action":{"on":False,"bri":165,"hue":8418,"sat":140,"effect":"none","xy":[0.6635,0.2825],"ct":366,"alert":"select","colormode":"hs"}}) - for key, light in self.yaml_config["lights"].items(): - self.yaml_config["groups"]["0"].add_light(light) # create groups if os.path.exists(self.configDir + "/groups.yaml"): groups = _open_yaml(self.configDir + "/groups.yaml") + if "0" not in list(groups.keys()): + self.yaml_config["groups"]["0"] = Group.Group({"id_v1": "0"}) for group, data in groups.items(): data["id_v1"] = group if data["type"] == "Entertainment": self.yaml_config["groups"][group] = EntertainmentConfiguration.EntertainmentConfiguration(data) - for light in data["lights"]: - self.yaml_config["groups"][group].add_light(self.yaml_config["lights"][light]) + for device in data["lights"]: + self.yaml_config["groups"][group].add_light(self.yaml_config["device"][device]) if "locations" in data: for light, location in data["locations"].items(): lightObj = self.yaml_config["lights"][light] self.yaml_config["groups"][group].locations[lightObj] = location else: - if "owner" in data and isinstance(data["owner"], dict): - data["owner"] = self.yaml_config["apiUsers"][list(self.yaml_config["apiUsers"])[0]] - elif "owner" not in data: - data["owner"] = self.yaml_config["apiUsers"][list(self.yaml_config["apiUsers"])[0]] - else: - data["owner"] = self.yaml_config["apiUsers"][data["owner"]] self.yaml_config["groups"][group] = Group.Group(data) - for light in data["lights"]: - self.yaml_config["groups"][group].add_light(self.yaml_config["lights"][light]) + for device in data["lights"]: + if device in self.yaml_config["device"]: + self.yaml_config["groups"][group].add_light(self.yaml_config["device"][device]) + self.yaml_config["groups"]["0"].add_light(self.yaml_config["device"][device]) + else: # temporary! iterate lights to get the device of the light + for key, devices in self.yaml_config["device"].items(): + if devices.group_v1 == "lights" and devices.id_v2 == device: + self.yaml_config["groups"][group].add_light(devices) + self.yaml_config["groups"]["0"].add_light(devices) + else: + self.yaml_config["groups"]["0"] = Group.Group({"id_v1": "0"}) #scenes if os.path.exists(self.configDir + "/scenes.yaml"): @@ -216,17 +264,6 @@ def load_config(self): for schedule, data in schedules.items(): data["id_v1"] = schedule self.yaml_config["schedules"][schedule] = Schedule.Schedule(data) - #sensors - if os.path.exists(self.configDir + "/sensors.yaml"): - sensors = _open_yaml(self.configDir + "/sensors.yaml") - for sensor, data in sensors.items(): - data["id_v1"] = sensor - self.yaml_config["sensors"][sensor] = Sensor.Sensor(data) - self.yaml_config["groups"]["0"].add_sensor(self.yaml_config["sensors"][sensor]) - else: - data = {"modelid": "PHDL00", "name": "Daylight", "type": "Daylight", "id_v1": "1"} - self.yaml_config["sensors"]["1"] = Sensor.Sensor(data) - self.yaml_config["groups"]["0"].add_sensor(self.yaml_config["sensors"]["1"]) #resourcelinks if os.path.exists(self.configDir + "/resourcelinks.yaml"): resourcelinks = _open_yaml(self.configDir + "/resourcelinks.yaml") @@ -264,17 +301,16 @@ def save_config(self, backup=False, resource="all"): return saveResources = [] if resource == "all": - saveResources = ["lights", "groups", "scenes", "rules", "resourcelinks", "schedules", "sensors", "behavior_instance", "smart_scene"] + saveResources = ["lights", "groups", "scenes", "rules", "resourcelinks", "schedules", "sensors", "behavior_instance", "smart_scene", "device"] else: saveResources.append(resource) for object in saveResources: filePath = path + object + ".yaml" dumpDict = {} for element in self.yaml_config[object]: - if element != "0": - savedData = self.yaml_config[object][element].save() - if savedData: - dumpDict[self.yaml_config[object][element].id_v1] = savedData + savedData = self.yaml_config[object][element].save() + if savedData: + dumpDict[self.yaml_config[object][element].id_v1] = savedData _write_yaml(filePath, dumpDict) logging.debug("Dump config file " + filePath) diff --git a/BridgeEmulator/flaskUI/restful.py b/BridgeEmulator/flaskUI/restful.py index 618356030..a3698c8f7 100644 --- a/BridgeEmulator/flaskUI/restful.py +++ b/BridgeEmulator/flaskUI/restful.py @@ -27,13 +27,13 @@ bridgeConfig = configManager.bridgeConfig.yaml_config def GroupZeroMessage(): - rooms = [] + groups = [] lights = [] for group, obj in bridgeConfig["groups"].items(): - rooms.append(obj.id_v2) + groups.append(obj) for light, obj in bridgeConfig["lights"].items(): - lights.append(obj.id_v2) - bridgeConfig["groups"]["0"].groupZeroStream(rooms, lights) + lights.append(obj) + bridgeConfig["groups"]["0"].groupZeroStream(groups, lights) def authorize(username, resource='', resourceId='', resourceParam=''): if username not in bridgeConfig["apiUsers"] and request.remote_addr != "127.0.0.1": @@ -112,7 +112,7 @@ def get(self, username): for resource in ["lights", "groups", "scenes", "rules", "resourcelinks", "schedules", "sensors"]: result[resource] = {} for resource_id in bridgeConfig[resource]: - if resource_id != "0": + if resource_id != "0" and not (resource == "sensors" and "invisible_v1" in bridgeConfig[resource][resource_id].protocol_cfg): result[resource][resource_id] = bridgeConfig[resource][resource_id].getV1Api().copy() return result @@ -127,7 +127,8 @@ def get(self, username, resource): response = {} if resource in ["lights", "groups", "scenes", "rules", "resourcelinks", "schedules", "sensors", "apiUsers"]: for object in bridgeConfig[resource]: - response[object] = bridgeConfig[resource][object].getV1Api().copy() + if resource != "sensors" and "invisible_v1" not in bridgeConfig[resource][object].protocol_cfg: + response[object] = bridgeConfig[resource][object].getV1Api().copy() elif resource == "config": response = buildConfig() return response diff --git a/BridgeEmulator/flaskUI/v2restapi.py b/BridgeEmulator/flaskUI/v2restapi.py index 644d00420..f93cb6917 100644 --- a/BridgeEmulator/flaskUI/v2restapi.py +++ b/BridgeEmulator/flaskUI/v2restapi.py @@ -1,6 +1,6 @@ import configManager import logManager -from HueObjects import Group, EntertainmentConfiguration, Scene, BehaviorInstance, GeofenceClient, SmartScene, StreamEvent +from HueObjects import Group, EntertainmentConfiguration, Scene, BehaviorInstance, GeofenceClient, SmartScene, StreamEvent, Device import uuid import json import weakref @@ -21,12 +21,12 @@ bridgeConfig = configManager.bridgeConfig.yaml_config v2Resources = {"light": {}, "scene": {}, "smart_scene": {}, "grouped_light": {}, "room": {}, "zone": { -}, "entertainment": {}, "entertainment_configuration": {}, "zigbee_connectivity": {}, "zigbee_device_discovery": {}, "device": {}, "device_power": {}, -"geofence_client": {}, "motion": {}, "light_level": {}, "temperature": {}, "relative_rotary": {}, "button": {}} +}, "entertainment": {}, "entertainment_configuration": {}, "zigbee_connectivity": {}, "zigbee_device_discovery": {}, "device_power": {}, +"geofence_client": {}, "motion": {}, "light_level": {}, "temperature": {}, "relative_rotary": {}, "button": {}, "contact": {}, "tamper": {}} def getObject(element, v2uuid): - if element in ["behavior_instance"]: + if element in ["behavior_instance", "device"]: return bridgeConfig[element][v2uuid] elif element in v2Resources and v2uuid in v2Resources[element]: logging.debug("Cache Hit for " + element) @@ -38,9 +38,9 @@ def getObject(element, v2uuid): v2Resources[element][v2uuid] = weakref.ref(obj) logging.debug("Cache Miss " + element) return obj - elif element in ["entertainment"]: - for key, obj in bridgeConfig["lights"].items(): - if str(uuid.uuid5(uuid.NAMESPACE_URL, obj.id_v2 + 'entertainment')) == v2uuid: + elif element in ["entertainment", "zigbee_connectivity", "motion", "light_level", "temperature", "relative_rotary", "button", "tamper", "contact"]: + for key, obj in bridgeConfig["device"].items(): + if str(uuid.uuid5(uuid.NAMESPACE_URL, obj.id_v2 + element)) == v2uuid: v2Resources[element][v2uuid] = weakref.ref(obj) return obj else: @@ -130,46 +130,35 @@ def v2GeofenceClient(): } return result +def v2Bridge(): + bridge_id = bridgeConfig["config"]["bridgeid"] + return { + "bridge_id": bridge_id.lower(), + "id": str(uuid.uuid5(uuid.NAMESPACE_URL, bridge_id + 'bridge')), + "id_v1": "", + "owner": {"rid": str(uuid.uuid5(uuid.NAMESPACE_URL, bridge_id + 'device')), "rtype": "device"}, + "time_zone": {"time_zone": bridgeConfig["config"]["timezone"]}, + "type": "bridge" + } def v2BridgeHome(): result = {} result["children"] = [] - result["children"].append({"rid": str(uuid.uuid5(uuid.NAMESPACE_URL, bridgeConfig["config"]["bridgeid"] + 'device')), "rtype": "device"}) - #result["grouped_services"] = [] - #if len(bridgeConfig["lights"]) > 0: - # result["grouped_services"].append({ - # "rid": bridgeConfig["groups"]["0"].id_v2, - # "rtype": "grouped_light" - # }) + result["children"].append({"rid": v2Bridge()["id"], "rtype": "device"}) # the bridge + result["id"] = str(uuid.uuid5(uuid.NAMESPACE_URL, bridgeConfig["groups"]["0"].id_v2 + 'bridge_home')) result["id_v1"] = "/groups/0" - result["services"] = [] - result["type"] = "bridge_home" - for key, light in bridgeConfig["lights"].items(): - result["services"].append(light.getBridgeHome()) - result["children"].append({"rid": light.getDevice()["id"], "rtype": "device"}) + for key, device in bridgeConfig["device"].items(): + result["children"].append({"rid": device.id_v2, "rtype": "device"}) for key, group in bridgeConfig["groups"].items(): if group.type == "Room": result["children"].append({"rid": group.getV2Room()["id"], "rtype": "room"}) - for key, sensor in bridgeConfig["sensors"].items(): - if sensor.getBridgeHome(): - result["services"].append(sensor.getBridgeHome()) + result["services"] = [] result["services"].append({"rid": bridgeConfig["groups"]["0"].id_v2 ,"rtype": "grouped_light"}) + result["type"] = "bridge_home" return result - -def v2Bridge(): - bridge_id = bridgeConfig["config"]["bridgeid"] - return { - "bridge_id": bridge_id.lower(), - "id": str(uuid.uuid5(uuid.NAMESPACE_URL, bridge_id + 'bridge')), - "id_v1": "", - "owner": {"rid": str(uuid.uuid5(uuid.NAMESPACE_URL, bridge_id + 'device')), "rtype": "device"}, - "time_zone": {"time_zone": bridgeConfig["config"]["timezone"]}, - "type": "bridge" - } - def geoLocation(): return { "id": str(uuid.uuid5(uuid.NAMESPACE_URL, bridgeConfig["config"]["bridgeid"] + 'geolocation')), @@ -238,26 +227,22 @@ def get(self): data.append(v2HomeKit()) # device data.append(v2BridgeDevice()) - for key, light in bridgeConfig["lights"].items(): - data.append(light.getDevice()) - for key, sensor in bridgeConfig["sensors"].items(): - if sensor.getDevice() != None: - data.append(sensor.getDevice()) + for key, device in bridgeConfig["device"].items(): + data.append(device.getDevice()) # bridge data.append(v2Bridge()) data.append(v2DiyHueBridge()) # zigbee data.append(v2BridgeZigBee()) - for key, light in bridgeConfig["lights"].items(): - data.append(light.getZigBee()) - for key, sensor in bridgeConfig["sensors"].items(): - if sensor.getZigBee() != None: - data.append(sensor.getZigBee()) + for key, device in bridgeConfig["device"].items(): + data.append(device.getZigBee()) data.append(v2BridgeZigBeeDiscovery()) # entertainment data.append(v2BridgeEntertainment()) - for key, light in bridgeConfig["lights"].items(): - data.append(light.getV2Entertainment()) + for key, device in bridgeConfig["device"].items(): + entertainment = device.getV2Entertainment() + if entertainment != None: + data.append(entertainment) # scenes for key, scene in bridgeConfig["scenes"].items(): data.append(scene.getV2Api()) @@ -289,27 +274,33 @@ def get(self): data.append(geoLocation()) for script in behaviorScripts(): data.append(script) - for key, sensor in bridgeConfig["sensors"].items(): - motion = sensor.getMotion() + for key, device in bridgeConfig["device"].items(): + motion = device.getMotion() if motion != None: data.append(motion) - buttons = sensor.getButtons() + buttons = device.getButtons() if len(buttons) != 0: for button in buttons: data.append(button) - power = sensor.getDevicePower() + power = device.getDevicePower() if power != None: data.append(power) - rotarys = sensor.getRotary() + rotarys = device.getRotary() if len(rotarys) != 0: for rotary in rotarys: data.append(rotary) - temperature = sensor.getTemperature() + temperature = device.getTemperature() if temperature != None: data.append(temperature) - lightlevel = sensor.getLightlevel() + lightlevel = device.getLightLevel() if lightlevel != None: data.append(lightlevel) + contact = device.getContact() + if contact != None: + data.append(contact) + tamper = device.getTamper() + if tamper != None: + data.append(tamper) return {"errors": [], "data": data} @@ -360,12 +351,8 @@ def get(self, resource): if group.type == "Entertainment": response["data"].append(group.getV2Api()) elif resource == "device": - for key, light in bridgeConfig["lights"].items(): - response["data"].append(light.getDevice()) - for key, sensor in bridgeConfig["sensors"].items(): - device = sensor.getDevice() - if device != None: - response["data"].append(device) + for key, device in bridgeConfig["device"].items(): + response["data"].append(device.getDevice()) response["data"].append(v2BridgeDevice()) # the bridge elif resource == "zigbee_device_discovery": response["data"].append(v2BridgeZigBeeDiscovery()) @@ -379,8 +366,6 @@ def get(self, resource): response["data"].append(v2HomeKit()) elif resource == "geolocation": response["data"].append(geoLocation()) - elif resource == "matter": - response["data"].append(matter()) elif resource == "behavior_instance": for key, instance in bridgeConfig["behavior_instance"].items(): response["data"].append(instance.getV2Api()) @@ -390,35 +375,35 @@ def get(self, resource): for script in behaviorScripts(): response["data"].append(script) elif resource == "motion": - for key, sensor in bridgeConfig["sensors"].items(): - motion = sensor.getMotion() + for key, device in bridgeConfig["device"].items(): + motion = device.getMotion() if motion != None: response["data"].append(motion) elif resource == "device_power": - for key, sensor in bridgeConfig["sensors"].items(): - power = sensor.getDevicePower() + for key, device in bridgeConfig["device"].items(): + power = device.getDevicePower() if power != None: response["data"].append(power) elif resource == "button": - for key, sensor in bridgeConfig["sensors"].items(): - buttons = sensor.getButtons() + for key, device in bridgeConfig["device"].items(): + buttons = device.getButtons() if len(buttons) != 0: for button in buttons: response["data"].append(button) elif resource == "relative_rotary": - for key, sensor in bridgeConfig["sensors"].items(): - rotarys = sensor.getRotary() + for key, device in bridgeConfig["device"].items(): + rotarys = device.getRotary() if len(rotarys) != 0: for rotary in rotarys: response["data"].append(rotary) elif resource == "temperature": - for key, sensor in bridgeConfig["sensors"].items(): - temperature = sensor.getTemperature() + for key, device in bridgeConfig["device"].items(): + temperature = device.getTemperature() if temperature != None: response["data"].append(temperature) elif resource == "light_level": - for key, sensor in bridgeConfig["sensors"].items(): - lightlevel = sensor.getLightlevel() + for key, device in bridgeConfig["device"].items(): + lightlevel = device.getLightLevel() if lightlevel != None: response["data"].append(lightlevel) else: @@ -518,7 +503,6 @@ def post(self, resource): objCreation = { "id_v1": new_object_id, "name": postDict["metadata"]["name"], - "owner": bridgeConfig["apiUsers"][request.headers["hue-application-key"]], } objCreation["type"] = "Room" if resource == "room" else "Zone" if "archetype" in postDict["metadata"]: @@ -598,7 +582,7 @@ def get(self, resource, resourceid): elif resource == "temperature": return {"errors": [], "data": [object.getTemperature()]} elif resource == "light_level": - return {"errors": [], "data": [object.getLightlevel()]} + return {"errors": [], "data": [object.getLightLevel()]} def put(self, resource, resourceid): logging.debug(request.headers) @@ -665,9 +649,7 @@ def put(self, resource, resourceid): v1Api["icon_class"] = putDict["metadata"]["archetype"].replace("_", " ").capitalize() if "children" in putDict: for children in putDict["children"]: - obj = getObject( - children["rtype"], children["rid"]) - object.add_light(obj) + object.add_light(getObject(children["rtype"], children["rid"])) object.update_attr(v1Api) elif resource == 'geofence_client': attrs = {} @@ -683,11 +665,13 @@ def put(self, resource, resourceid): Thread(target=scanForLights).start() elif resource == "device": if "identify" in putDict and putDict["identify"]["action"] == "identify": - object.setV1State({"alert": "select"}) + object.firstElement().setV1State({"alert": "select"}) if "metadata" in putDict: if "name" in putDict["metadata"]: if object: object.name = putDict["metadata"]["name"] + for element in object.elements.items(): + element.name = putDict["metadata"]["name"] elif resourceid == v2BridgeDevice()["id"]: bridgeConfig["config"]["name"] = putDict["metadata"]["name"] streamMessage = {"creationtime": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), @@ -704,8 +688,15 @@ def put(self, resource, resourceid): StreamEvent(streamMessage) configManager.bridgeConfig.save_config(backup=False, resource="config") elif resource == "motion": - if "enabled" in putDict: - object.update_attr({"config": {"on": putDict["enabled"]}}) + object.setDevice("ZLLPresence", putDict) + elif resource == "light_level": + object.setDevice("ZLLLightLevel", putDict) + elif resource == "temperature": + object.setDevice("ZLLTemperature", putDict) + elif resource == "contact": + object.setDevice("ZLLContact", putDict) + elif resource == "tamper": + object.setDevice("ZLLTamper", putDict) else: return { "errors": [{ @@ -726,6 +717,10 @@ def delete(self, resource, resourceid): if "user" not in authorisation: return "", 403 object = getObject(resource, resourceid) + + if resource == "device": + for element in object.elements: + del bridgeConfig[element.getObjectPath()["resource"]][element.getObjectPath()["id"]] if hasattr(object, 'getObjectPath'): del bridgeConfig[object.getObjectPath()["resource"] diff --git a/BridgeEmulator/functions/behavior_instance.py b/BridgeEmulator/functions/behavior_instance.py index e5e5a0efc..05110c47b 100755 --- a/BridgeEmulator/functions/behavior_instance.py +++ b/BridgeEmulator/functions/behavior_instance.py @@ -32,28 +32,53 @@ def findGroup(rid, rtype): if str(uuid.uuid5(uuid.NAMESPACE_URL, obj.id_v2 + rtype)) == rid: return obj logging.info("Group not found!!!!") + + +def findLight(rid, rtype): + for key, obj in bridgeConfig["lights"].items(): + if str(uuid.uuid5(uuid.NAMESPACE_URL, obj.id_v2)) == rid: + return obj + logging.info("Light not found!!!!") -def threadNoMotion(actionsToExecute, device, group): +def threadDelayAction(actionsToExecute, device, monitoredKey, monitoredValue, groupsAndLights): secondsCounter = 0 if "after" in actionsToExecute: if "minutes" in actionsToExecute["after"]: secondsCounter = actionsToExecute["after"]["minutes"] * 60 if "seconds" in actionsToExecute["after"]: secondsCounter += actionsToExecute["after"]["seconds"] - while device.state["presence"] == False: + elif "timer" in actionsToExecute: + if "minutes" in actionsToExecute["timer"]["duration"]: + secondsCounter = actionsToExecute["timer"]["duration"]["minutes"] * 60 + if "seconds" in actionsToExecute["timer"]["duration"]: + secondsCounter += actionsToExecute["timer"]["duration"]["seconds"] + print("to wait " + str(secondsCounter)) + while device.state[monitoredKey] == monitoredValue: if secondsCounter == 0: - if "recall_single" in actionsToExecute: - for action in actionsToExecute["recall_single"]: - if action["action"] == "all_off": - group.setV1Action({"on": False, "transistiontime": 100}) - logging.info("No motion, turning lights off" ) - return + executeActions(actionsToExecute, groupsAndLights) + return secondsCounter -= 1 sleep(1) logging.info("Motion detected, cancel the counter..." ) +def executeActions(actionsToExecute, groupsAndLights): + recall = "recall" + if "recall_single" in actionsToExecute: # need to discover the differences between recall and recall_single + recall = ["recall_single"] + logging.info("execute routine action") + if recall in actionsToExecute: + for action in actionsToExecute[recall]: + if action["action"] == "all_off": + for resource in groupsAndLights: + resource.setV1Action({"on": False, "transistiontime": 100}) + logging.info("routine turning lights off " + resource.name) + elif "recall" in action["action"] and action["action"]["recall"]["rtype"] == "scene": + callScene(action["action"]["recall"]["rid"]) + + def checkBehaviorInstances(device): + print("enter checkBehaviorInstances") deviceUuid = device.id_v2 matchedInstances = [] for key, instance in bridgeConfig["behavior_instance"].items(): @@ -67,20 +92,28 @@ def checkBehaviorInstances(device): matchedInstances.append(instance) except KeyError: pass + + for instance in matchedInstances: - if device.type == "ZLLSwitch": #Hue dimmer switch + lightsAndGroups = [] + for resource in instance.configuration["where"]: + if "group" in resource: + lightsAndGroups.append(findGroup(resource["group"]["rid"], resource["group"]["rtype"])) + elif "light" in resource: + lightsAndGroups.append(findLight(resource["light"]["rid"], resource["light"]["rtype"])) + if device.modelid in ["RWL022", "RWL021", "RWL020"]: #Hue dimmer switch button = None - if device.state["buttonevent"] < 2000: + if device.firstElement().state["buttonevent"] < 2000: button = str(uuid.uuid5(uuid.NAMESPACE_URL, device.id_v2 + 'button1')) - elif device.state["buttonevent"] < 3000: + elif device.firstElement().state["buttonevent"] < 3000: button = str(uuid.uuid5(uuid.NAMESPACE_URL, device.id_v2 + 'button2')) - elif device.state["buttonevent"] < 4000: + elif device.firstElement().state["buttonevent"] < 4000: button = str(uuid.uuid5(uuid.NAMESPACE_URL, device.id_v2 + 'button3')) else: button = str(uuid.uuid5(uuid.NAMESPACE_URL, device.id_v2 + 'button4')) if button in instance.configuration["buttons"]: - lastDigit = device.state["buttonevent"] % 1000 + lastDigit = device.firstElement().state["buttonevent"] % 1000 buttonAction = None if lastDigit == 0: buttonAction = "on_short_press" @@ -93,12 +126,14 @@ def checkBehaviorInstances(device): if buttonAction in instance.configuration["buttons"][button]: if "time_based" in instance.configuration["buttons"][button][buttonAction]: any_on = False - for resource in instance.configuration["where"]: - if "group" in resource: - group = findGroup(resource["group"]["rid"], resource["group"]["rtype"]) - if group.update_state()["any_on"] == True: + for resource in lightsAndGroups: + if "any_on" in resource.state: # is group + if resource.state["any_on"] == True: any_on = True - group.setV1Action({"on": False}) + if "on" in resource.state: # is light + if resource.state["on"] == True: + any_on = True + resource.setV1Action({"on": False}) if any_on == True: return allTimes = [] @@ -111,30 +146,27 @@ def checkBehaviorInstances(device): elif "scene_cycle" in instance.configuration["buttons"][button][buttonAction]: callScene(random.choice(instance.configuration["buttons"][button][buttonAction]["scene_cycle"])[0]["action"]["recall"]["rid"]) elif "action" in instance.configuration["buttons"][button][buttonAction]: - for resource in instance.configuration["where"]: - if "group" in resource: - group = findGroup(resource["group"]["rid"], resource["group"]["rtype"]) - if instance.configuration["buttons"][button][buttonAction]["action"] == "all_off": - group.setV1Action({"on": False}) - elif instance.configuration["buttons"][button][buttonAction]["action"] == "dim_up": - group.setV1Action({"bri_inc": +30}) - elif instance.configuration["buttons"][button][buttonAction]["action"] == "dim_down": - group.setV1Action({"bri_inc": -30}) + for resource in lightsAndGroups: + if instance.configuration["buttons"][button][buttonAction]["action"] == "all_off": + resource.setV1Action({"on": False}) + elif instance.configuration["buttons"][button][buttonAction]["action"] == "dim_up": + resource.setV1Action({"bri_inc": +30}) + elif instance.configuration["buttons"][button][buttonAction]["action"] == "dim_down": + resource.setV1Action({"bri_inc": -30}) - elif device.type == "ZLLPresence": # Motion Sensor - #if "settings" in instance.configuration: - # if "daylight_sensitivity" in instance.configuration["settings"]: - # if instance.configuration["settings"]["daylight_sensitivity"]["dark_threshold"] < device.state["lightlevel"]: - # print("Light ok") - # else: - # print("Light ko") - # return - motion = device.state["presence"] + elif device.modelid == "SML001": # Motion Sensor + if "settings" in instance.configuration: + if "daylight_sensitivity" in instance.configuration["settings"]: + if instance.configuration["settings"]["daylight_sensitivity"]["dark_threshold"] >= device.elements["ZLLLightLevel"]().state["lightlevel"]: + print("Light ok") + else: + print("Light ko") + return + motion = device.elements["ZLLPresence"]().state["presence"] any_on = False - for resource in instance.configuration["where"]: - if "group" in resource: - group = findGroup(resource["group"]["rid"], resource["group"]["rtype"]) - if group.update_state()["any_on"] == True: + for resource in lightsAndGroups: + if "any_on" in resource.state: # is group + if resource.update_state()["any_on"] == True: any_on = True if "timeslots" in instance.configuration["when"]: @@ -144,12 +176,72 @@ def checkBehaviorInstances(device): actions = findTriggerTime(allSlots) if motion: if any_on == False: # motion triggeredand lights are off - if "recall_single" in actions["on_motion"]: - for action in actions["on_motion"]["recall_single"]: - if "recall" in action["action"]: - if action["action"]["recall"]["rtype"] == "scene": - callScene(action["action"]["recall"]["rid"]) + logging.info("Trigger motion routine " + instance.name) + executeActions(actions["on_motion"],[]) else: logging.info("no motion") if any_on: - Thread(target=threadNoMotion, args=[actions["on_no_motion"], device, group]).start() + Thread(target=threadDelayAction, args=[actions["on_no_motion"], device.elements["ZLLPresence"](), "presence", False, lightsAndGroups]).start() + elif device.modelid == "SOC001": # secure contact sensor + actions = {} + if "timeslots" in instance.configuration["when"]: + allSlots = [] + for slot in instance.configuration["when"]["timeslots"]: + allSlots.append({"hour": slot["start_time"]["time"]["hour"], "minute": slot["start_time"]["time"]["minute"], "actions": {"on_open": slot["on_open"], "on_close": slot["on_close"]}}) + actions = findTriggerTime(allSlots) + elif "always" in instance.configuration["when"]: + actions = {"on_open": instance.configuration["when"]["always"]["on_open"], "on_close": instance.configuration["when"]["always"]["on_close"]} + contact = "on_close" if device.elements["ZLLContact"]().state["contact"] == "contact" else "on_open" + if "timer" in actions[contact]: + monitoredValue = "contact" if contact == "on_close" else "no_contact" + logging.info("Trigger timer routine " + instance.name) + Thread(target=threadDelayAction, args=[actions[contact], device.elements["ZLLContact"](), "contact", monitoredValue, lightsAndGroups]).start() + else: + logging.info("Trigger routine " + instance.name) + executeActions(actions[contact], lightsAndGroups) + elif device.modelid == "RDM002": # Hue rotary switch + buttonDevice = device.elements["ZLLSwitch"]() + rotaryDevice = device.elements["ZLLRelativeRotary"]() + button = None + if buttonDevice.state["buttonevent"] < 2000: + action = 'button1' + elif buttonDevice.state["buttonevent"] < 3000: + button = 'button2' + elif buttonDevice.state["buttonevent"] < 4000: + button = 'button3' + else: + button = 'button4' + if button in instance.configuration: + lastDigit = buttonDevice.state["buttonevent"] % 1000 + buttonAction = None + if lastDigit == 0: + buttonAction = "on_short_press" + elif lastDigit == 1: + buttonAction = "on_repeat" + elif lastDigit == 2: + buttonAction = "on_short_release" + elif lastDigit == 3: + buttonAction = "on_long_press" + if buttonAction in instance.configuration[button]: + lightsAndGroups = [] + for resource in instance.configuration[button]["where"]: + if "group" in resource: + lightsAndGroups.append(findGroup(resource["group"]["rid"], resource["group"]["rtype"])) + elif "light" in resource: + lightsAndGroups.append(findLight(resource["light"]["rid"], resource["light"]["rtype"])) + if "time_based_extended" in instance.configuration[button][buttonAction]: + if "slots" in instance.configuration[button][buttonAction]["time_based_extended"]: + allSlots = [] + for slot in instance.configuration[button][buttonAction]["time_based_extended"]["slots"]: + allSlots.append({"hour": slot["start_time"]["hour"], "minute": slot["start_time"]["minute"], "actions": slot["actions"]}) + actions = findTriggerTime(allSlots) + executeActions(actions,lightsAndGroups) + + elif "time_based" in instance.configuration[button][buttonAction]: + if "slots" in instance.configuration[button][buttonAction]["time_based"]: + + + + + + diff --git a/BridgeEmulator/functions/scripts.py b/BridgeEmulator/functions/scripts.py index 9f58a4c65..7dabc26cb 100644 --- a/BridgeEmulator/functions/scripts.py +++ b/BridgeEmulator/functions/scripts.py @@ -66,9 +66,27 @@ def triggerScript(behavior_instance): if behavior_instance.configuration["end_state"] == "turn_off": group.setV1Action(state={"on": False}) logging.debug("Finish Go to Sleep") + # Timer + elif behavior_instance.script_id == "e73bc72d-96b1-46f8-aa57-729861f80c78": + logging.debug("Start Timer " + behavior_instance.name) + secondsToCount = 0 + if "duration" in behavior_instance.configuration: + if "minutes" in behavior_instance.configuration["duration"]: + secondsToCount = behavior_instance.configuration["duration"]["minutes"] * 60 + if "seconds" in behavior_instance.configuration["duration"]: + secondsToCount += behavior_instance.configuration["duration"]["seconds"] + sleep(secondsToCount) + for element in behavior_instance.configuration["where"]: + if "group" in element: + group = findGroup(element["group"]["rid"]) + group.setV1Action(state={"on": True, "bri": 254, "ct": 370}) # currently we apply Bright scene + behavior_instance.enabled = False + behavior_instance.active = False + + # Activate scene - elif behavior_instance.script_id == "7238c707-8693-4f19-9095-ccdc1444d228": + elif behavior_instance.script_id == "7238c707-8693-4f19-9095-ccdc1444d228": logging.debug("Start routine " + behavior_instance.name) for element in behavior_instance.configuration["what"]: if "group" in element: diff --git a/BridgeEmulator/lights/discover.py b/BridgeEmulator/lights/discover.py index a7ed27817..cad6fff2d 100644 --- a/BridgeEmulator/lights/discover.py +++ b/BridgeEmulator/lights/discover.py @@ -7,7 +7,7 @@ from datetime import datetime, timezone from lights.protocols import tpkasa, wled, mqtt, hyperion, yeelight, hue, deconz, native_multi, tasmota, shelly, esphome, tradfri, elgato from services import homeAssistantWS -from HueObjects import Light, StreamEvent +from HueObjects import Light, StreamEvent, Device from functions.core import nextFreeId from lights.light_types import lightTypes logging = logManager.logger.get_logger(__name__) @@ -65,15 +65,19 @@ def addNewLight(modelid, name, protocol, protocol_cfg): light["protocol_cfg"] = protocol_cfg newObject = Light.Light(light) bridgeConfig["lights"][newLightID] = newObject - bridgeConfig["groups"]["0"].add_light(newObject) + light["group_v1"] = "lights" + newDevice = Device.Device(light) + newDevice.add_element("light", newObject) + bridgeConfig["groups"]["0"].add_light(newDevice) + bridgeConfig["device"][newDevice.id_v2] = newDevice # trigger stream messages - rooms = [] + groups = [] lights = [] for group, obj in bridgeConfig["groups"].items(): - rooms.append(obj.id_v2) + groups.append(obj) for light, obj in bridgeConfig["lights"].items(): - lights.append(obj.id_v2) - bridgeConfig["groups"]["0"].groupZeroStream(rooms, lights) + lights.append(obj) + bridgeConfig["groups"]["0"].groupZeroStream(groups, lights) configManager.bridgeConfig.save_config(backup=False, resource="lights") return newLightID diff --git a/BridgeEmulator/sensors/discover.py b/BridgeEmulator/sensors/discover.py index c45363b94..7c160eef4 100644 --- a/BridgeEmulator/sensors/discover.py +++ b/BridgeEmulator/sensors/discover.py @@ -1,6 +1,6 @@ import logManager import configManager -from HueObjects import Sensor +from HueObjects import Sensor, Device import random from functions.core import nextFreeId @@ -12,17 +12,30 @@ def generate_unique_id(): rand_bytes = [random.randrange(0, 256) for _ in range(3)] return "00:17:88:01:03:%02x:%02x:%02x" % (rand_bytes[0], rand_bytes[1], rand_bytes[2]) + def addHueMotionSensor(name, protocol, protocol_cfg): uniqueid = generate_unique_id() motion_sensor_id = nextFreeId(bridgeConfig, "sensors") - motion_sensor = {"name": "Hue motion " + name[:21], "id_v1": motion_sensor_id, "protocol": protocol, "modelid": "SML001", "type": "ZLLPresence", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0406"} - bridgeConfig["sensors"][motion_sensor_id] = Sensor.Sensor(motion_sensor) + motion_sensor = {"name": "Hue motion " + name[:21], "id_v1": motion_sensor_id, "protocol": protocol, + "modelid": "SML001", "type": "ZLLPresence", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0406"} + newMotionObj = Sensor.Sensor(motion_sensor) + bridgeConfig["sensors"][motion_sensor_id] = newMotionObj new_sensor_id = nextFreeId(bridgeConfig, "sensors") - light_sensor = {"name": "Hue ambient light " + name[:14], "id_v1": new_sensor_id, "protocol": protocol, "modelid": "SML001", "type": "ZLLLightLevel", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0400"} - bridgeConfig["sensors"][new_sensor_id] = Sensor.Sensor(light_sensor) + light_sensor = {"name": "Hue ambient light " + name[:14], "id_v1": new_sensor_id, "protocol": protocol, + "modelid": "SML001", "type": "ZLLLightLevel", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0400"} + newLightObj = Sensor.Sensor(light_sensor) + bridgeConfig["sensors"][new_sensor_id] = newLightObj new_sensor_id = nextFreeId(bridgeConfig, "sensors") - temp_sensor = {"name": "Hue temperature " + name[:16], "id_v1": new_sensor_id, "protocol": protocol, "modelid": "SML001", "type": "ZLLTemperature", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0402"} - bridgeConfig["sensors"][new_sensor_id] = Sensor.Sensor(temp_sensor) + temp_sensor = {"name": "Hue temperature " + name[:16], "id_v1": new_sensor_id, "protocol": protocol, + "modelid": "SML001", "type": "ZLLTemperature", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0402"} + newTemperatureObj = Sensor.Sensor(temp_sensor) + bridgeConfig["sensors"][new_sensor_id] = newTemperatureObj + newDeviceObj = Device.Device(motion_sensor) + newDeviceObj.add_element("ZLLPresence", newMotionObj) + newDeviceObj.add_element("ZLLLightLevel", newLightObj) + newDeviceObj.add_element("ZLLTemperature", newTemperatureObj) + bridgeConfig["device"][newDeviceObj.id_v2] = newDeviceObj + return @@ -34,17 +47,47 @@ def addHueSwitch(uniqueid, sensorsType): uniqueid += "0" + new_sensor_id + ":4d:c6-02-fc00" else: uniqueid += new_sensor_id + ":4d:c6-02-fc00" - deviceData = {"id_v1": new_sensor_id, "state": {"buttonevent": 0, "lastupdated": "none"}, "config": {"on": True, "battery": 100, "reachable": True}, "name": "Dimmer Switch" if sensorsType == "ZLLSwitch" else "Tap Switch", "type": sensorsType, "modelid": "RWL021" if sensorsType == "ZLLSwitch" else "ZGPSWITCH", "manufacturername": "Philips", "swversion": "5.45.1.17846" if sensorsType == "ZLLSwitch" else "", "uniqueid": uniqueid} + deviceData = {"id_v1": new_sensor_id, "state": {"buttonevent": 0, "lastupdated": "none"}, "config": {"on": True, "battery": 100, "reachable": True}, "name": "Dimmer Switch" if sensorsType == "ZLLSwitch" else "Tap Switch", + "type": sensorsType, "modelid": "RWL021" if sensorsType == "ZLLSwitch" else "ZGPSWITCH", "manufacturername": "Philips", "swversion": "5.45.1.17846" if sensorsType == "ZLLSwitch" else "", "uniqueid": uniqueid} bridgeConfig["sensors"][new_sensor_id] = Sensor.Sensor(deviceData) - return(bridgeConfig["sensors"][new_sensor_id]) + newDeviceObj = Device.Device(deviceData) + newDeviceObj.add_element( + deviceData["type"], bridgeConfig["sensors"][new_sensor_id]) + bridgeConfig["device"][newDeviceObj.id_v2] = newDeviceObj + return (bridgeConfig["sensors"][new_sensor_id]) + def addHueRotarySwitch(protocol_cfg): uniqueid = generate_unique_id() button_id = nextFreeId(bridgeConfig, "sensors") - button = {"name": "Hue tap dial switch", "id_v1": button_id, "modelid": "RDM002", "type": "ZLLSwitch", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0406"} + button = {"name": "Hue tap dial switch", "id_v1": button_id, "modelid": "RDM002", + "type": "ZLLSwitch", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0406"} bridgeConfig["sensors"][button_id] = Sensor.Sensor(button) - rotary_id = nextFreeId(bridgeConfig, "sensors") - rotary = {"name": "Hue tap dial switch", "id_v1": rotary_id, "modelid": "RDM002", "type": "ZLLRelativeRotary", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0406"} + rotary = {"name": "Hue tap dial switch", "id_v1": rotary_id, "modelid": "RDM002", + "type": "ZLLRelativeRotary", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0406"} bridgeConfig["sensors"][rotary_id] = Sensor.Sensor(rotary) + newDeviceObj = Device.Device(button) + newDeviceObj.add_element("ZLLSwitch", bridgeConfig["sensors"][button_id]) + newDeviceObj.add_element( + "ZLLRelativeRotary", bridgeConfig["sensors"][rotary_id]) + bridgeConfig["device"][newDeviceObj.id_v2] = newDeviceObj + return + + +def addHueSecureContactSensor(name, protocol, protocol_cfg): + protocol_cfg["invisible_v1"] = True + uniqueid = generate_unique_id() + contact_id = nextFreeId(bridgeConfig, "sensors") + contact = {"name": "Hue contact " + name, "id_v1": contact_id, "protocol": protocol, "modelid": "SOC001", + "type": "ZLLContact", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0402", "state": {"contact": "contact"}} + bridgeConfig["sensors"][contact_id] = Sensor.Sensor(contact) + tamper_id = nextFreeId(bridgeConfig, "sensors") + tamper = {"name": "Hue tamper " + name, "id_v1": tamper_id, "protocol": protocol, "modelid": "SOC001", + "type": "ZLLTamper", "protocol_cfg": protocol_cfg, "uniqueid": uniqueid + "-02-0404"} + bridgeConfig["sensors"][tamper_id] = Sensor.Sensor(tamper) + newDeviceObj = Device.Device(contact) + newDeviceObj.add_element("ZLLContact", bridgeConfig["sensors"][contact_id]) + newDeviceObj.add_element("ZLLTamper", bridgeConfig["sensors"][tamper_id]) + bridgeConfig["device"][newDeviceObj.id_v2] = newDeviceObj return diff --git a/BridgeEmulator/services/eventStreamer.py b/BridgeEmulator/services/eventStreamer.py index 6a568c3cc..538426cfe 100644 --- a/BridgeEmulator/services/eventStreamer.py +++ b/BridgeEmulator/services/eventStreamer.py @@ -19,7 +19,7 @@ def messageBroker(): @stream.route('/eventstream/clip/v2') def streamV2Events(): def generate(): - counter = 1000 + counter = 2000 # 400 seconds yield f": hi\n\n" while counter > 0: # ensure we stop at some point if len(HueObjects.eventstream) > 0: diff --git a/BridgeEmulator/services/homeAssistantWS.py b/BridgeEmulator/services/homeAssistantWS.py index 3365573c1..47528c9dd 100644 --- a/BridgeEmulator/services/homeAssistantWS.py +++ b/BridgeEmulator/services/homeAssistantWS.py @@ -4,6 +4,8 @@ import threading from ws4py.client.threadedclient import WebSocketClient +from pprint import pprint + logging = logManager.logger.get_logger(__name__) @@ -58,6 +60,7 @@ def closed(self, code, reason=None): def received_message(self, m): # logging.debug("Received message: {}".format(m)) message_text = m.data.decode(m.encoding) + print(message_text) message = json.loads(message_text) if message.get('type', None) == "auth_required": self.do_auth_required(message) @@ -173,11 +176,16 @@ def do_event(self, message): logging.exception("No event_type in event") def do_state_changed(self, message): + pprint(message) try: entity_id = message['event']['data']['entity_id'] new_state = message['event']['data']['new_state'] if self._should_include(new_state): - logging.debug("State update recevied for {}, new state {}".format( + logging.debug("State update recevied for light {}, new state {}".format( + entity_id, new_state)) + latest_states[entity_id] = new_state + if self._should_include_sensor(new_state): + logging.debug("State update recevied for sensor {}, new state {}".format( entity_id, new_state)) latest_states[entity_id] = new_state except KeyError as e: @@ -277,7 +285,6 @@ def discover(detectedLights): # This only loops over discovered devices so we have already filtered out what we don't want for entity_id in latest_states.keys(): ha_state = latest_states[entity_id] - device_new = True lightName = ha_state["attributes"]["friendly_name"] if "friendly_name" in ha_state["attributes"] else entity_id logging.info("HomeAssistant_ws: found light {}".format(lightName)) diff --git a/BridgeEmulator/services/mqtt.py b/BridgeEmulator/services/mqtt.py index 94cd5a6d6..b7ede09ed 100644 --- a/BridgeEmulator/services/mqtt.py +++ b/BridgeEmulator/services/mqtt.py @@ -4,13 +4,13 @@ import math import weakref import ssl -from HueObjects import Sensor +from HueObjects import Sensor, Device import paho.mqtt.client as mqtt from datetime import datetime, timezone from threading import Thread from time import sleep from functions.core import nextFreeId -from sensors.discover import addHueMotionSensor +from sensors.discover import addHueMotionSensor, addHueSecureContactSensor from sensors.sensor_types import sensorTypes from lights.discover import addNewLight from functions.rules import rulesProcessor @@ -199,7 +199,6 @@ def getClient(): return client def longPressButton(sensor, buttonevent): - print("running.....") logging.info("long press detected") sleep(1) while sensor.state["buttonevent"] == buttonevent: @@ -224,19 +223,15 @@ def getObject(friendly_name): logging.debug("Cache Hit for " + friendly_name) return devices_ids[friendly_name]() else: - for resource in ["sensors", "lights"]: - for key, device in bridgeConfig[resource].items(): - if device.protocol == "mqtt": - if "friendly_name" in device.protocol_cfg and device.protocol_cfg["friendly_name"] == friendly_name: - if device.modelid == "SML001" and device.type != "ZLLPresence": - continue - devices_ids[friendly_name] = weakref.ref(device) - logging.debug("Cache Miss " + friendly_name) - return device - elif "state_topic" in device.protocol_cfg and device.protocol_cfg["state_topic"] == "zigbee2mqtt/" + friendly_name: - devices_ids[friendly_name] = weakref.ref(device) - logging.debug("Cache Miss " + friendly_name) - return device + for key, device in bridgeConfig["device"].items(): + if device.protocol == "mqtt": + if "friendly_name" in device.protocol_cfg and device.protocol_cfg["friendly_name"] == friendly_name: + devices_ids[friendly_name] = weakref.ref(device) + return device + elif "state_topic" in device.protocol_cfg and device.protocol_cfg["state_topic"] == "zigbee2mqtt/" + friendly_name: + devices_ids[friendly_name] = weakref.ref(device) + logging.debug("Cache Miss " + friendly_name) + return device logging.debug("Device not found for " + friendly_name) return False @@ -285,6 +280,11 @@ def on_autodiscovery_light(msg): addNewLight(modelid, lightName, "mqtt", protocol_cfg) +def notifyEmail(sensorName): + if bridgeConfig["config"]["alarm"]["enabled"] and bridgeConfig["config"]["alarm"]["lasttriggered"] + 300 < datetime.now().timestamp(): + logging.info("Alarm triggered, sending email...") + requests.post("https://diyhue.org/cdn/mailNotify.php", json={"to": bridgeConfig["config"]["alarm"]["email"], "sensor": sensorName}, timeout=10) + bridgeConfig["config"]["alarm"]["lasttriggered"] = int(datetime.now().timestamp()) def on_state_update(msg): logging.debug("MQTT: got state message on " + msg.topic) @@ -296,7 +296,6 @@ def on_state_update(msg): def on_message(client, userdata, msg): if bridgeConfig["config"]["mqtt"]["enabled"]: try: - current_time = datetime.now() logging.debug("MQTT: got state message on " + msg.topic) data = json.loads(msg.payload) logging.debug(msg.payload) @@ -304,24 +303,43 @@ def on_message(client, userdata, msg): on_autodiscovery_light(msg) elif msg.topic == "zigbee2mqtt/bridge/devices": for key in data: - if "model_id" in key and (key["model_id"] in standardSensors or key["model_id"] in motionSensors): # Sensor is supported - if getObject(key["friendly_name"]) == False: ## Add the new sensor - logging.info("MQTT: Add new mqtt sensor " + key["friendly_name"]) - if key["model_id"] in standardSensors: + if "model_id" in key: + if getObject(key["friendly_name"]) == False: ## Sensor not present in bridge config + logging.info("MQTT: Found new mqtt sensor " + key["friendly_name"]) + ### check if is a motion sensor + hadMotion, hasLightLevel, hasTemperature, hasContact = False, False, False, False + for feature in key["definition"]["exposes"]: + if "property" in feature: + if feature["property"] == "occupancy": + hadMotion = True + elif feature["property"] == "illuminance": + hasLightLevel = True + elif feature["property"] == "temperature": + hasTemperature = True + elif feature["property"] == "contact": + hasContact = True + if hadMotion: + logging.info("MQTT: add new motion sensor " + key["model_id"]) + addHueMotionSensor(key["friendly_name"], "mqtt", {"modelid": key["model_id"], "lightSensor": "off" if hasLightLevel else "on", "friendly_name": key["friendly_name"]}) + elif hasContact: + logging.info("MQTT: add new contact sensor " + key["model_id"]) + addHueSecureContactSensor(key["friendly_name"], "mqtt", {"modelid": key["model_id"], "friendly_name": key["friendly_name"]}) + elif key["model_id"] in standardSensors: for sensor_type in sensorTypes[key["model_id"]].keys(): new_sensor_id = nextFreeId(bridgeConfig, "sensors") #sensor_type = sensorTypes[key["model_id"]][sensor] uniqueid = convertHexToMac(key["ieee_address"]) + "-01-1000" sensorData = {"name": key["friendly_name"], "protocol": "mqtt", "modelid": key["model_id"], "type": sensor_type, "uniqueid": uniqueid,"protocol_cfg": {"friendly_name": key["friendly_name"], "ieeeAddr": key["ieee_address"], "model": key["definition"]["model"]}, "id_v1": new_sensor_id} bridgeConfig["sensors"][new_sensor_id] = Sensor.Sensor(sensorData) - ### TRADFRI Motion Sensor, Xiaomi motion sensor, etc - elif key["model_id"] in motionSensors: - logging.info("MQTT: add new motion sensor " + key["model_id"]) - addHueMotionSensor(key["friendly_name"], "mqtt", {"modelid": key["model_id"], "lightSensor": "on", "friendly_name": key["friendly_name"]}) + newDeviceObj = Device.Device(sensorData) + newDeviceObj.type = "sensors" # to look for another name + newDeviceObj.add_element(sensorData["type"], bridgeConfig["sensors"][new_sensor_id]) + bridgeConfig["device"][newDeviceObj.id_v2] = newDeviceObj + else: logging.info("MQTT: unsupported sensor " + key["model_id"]) elif msg.topic == "zigbee2mqtt/bridge/log": - light = getObject(data["meta"]["friendly_name"]) + light = getObject(data["meta"]["friendly_name"]).firstElement() if data["type"] == "device_announced": if light.config["startup"]["mode"] == "powerfail": logging.info("set last state for " + light.name) @@ -335,24 +353,26 @@ def on_message(client, userdata, msg): device_friendlyname = msg.topic[msg.topic.index("/") + 1:] device = getObject(device_friendlyname) if device != False: - if device.getObjectPath()["resource"] == "sensors": + if device.group_v1 == "sensors": + current_time = datetime.now() + lastupdated = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z") if "battery" in data and isinstance(data["battery"], int): - device.config["battery"] = data["battery"] - if device.config["on"] == False: + device.firstElement().config["battery"] = data["battery"] + if device.firstElement().config["on"] == False: return - convertedPayload = {"lastupdated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")} if ("action" in data and data["action"] == "") or ("click" in data and data["click"] == ""): return ### If is a motion sensor update the light level and temperature - if device.modelid in motionSensors: - convertedPayload["presence"] = data["occupancy"] - lightPayload = {"lastupdated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")} - lightSensor = findLightSensor(device) - if "temperature" in data: - tempSensor = findTempSensor(device) - tempSensor.state = {"temperature": int(data["temperature"] * 100), "lastupdated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")} + if "occupancy" in data: + motionSensor = device.elements["ZLLPresence"]() + motionSensor.state = {"presence": data["occupancy"], "lastupdated": lastupdated} + # send email if alarm is enabled: + notifyEmail(motionSensor.name) + if "illuminance_lux" in data: + lightSensor = device.elements["ZLLLightLevel"]() + lightPayload = {"lastupdated": lastupdated} if "illuminance_lux" in data: - hue_lightlevel = int(10000 * math.log10(data["illuminance_lux"])) if data["illuminance_lux"] != 0 else 0 + hue_lightlevel = int(10000 * math.log10(data["illuminance_lux"]) + 1) if data["illuminance_lux"] != 0 else 0 if hue_lightlevel > lightSensor.config["tholddark"]: lightPayload["dark"] = False else: @@ -360,10 +380,7 @@ def on_message(client, userdata, msg): lightPayload["lightlevel"] = hue_lightlevel elif lightSensor.protocol_cfg["lightSensor"] == "on": lightPayload["dark"] = not bridgeConfig["sensors"]["1"].state["daylight"] - if lightPayload["dark"]: - lightPayload["lightlevel"] = 6000 - else: - lightPayload["lightlevel"] = 25000 + lightPayload["lightlevel"] = 6000 if lightPayload["dark"] else 25000 else: # is always dark lightPayload["dark"] = True lightPayload["lightlevel"] = 6000 @@ -371,55 +388,43 @@ def on_message(client, userdata, msg): if lightPayload["dark"] != lightSensor.state["dark"]: lightSensor.dxState["dark"] = current_time lightSensor.state.update(lightPayload) - # send email if alarm is enabled: - if data["occupancy"] and bridgeConfig["config"]["alarm"]["enabled"] and bridgeConfig["config"]["alarm"]["lasttriggered"] + 300 < current_time.timestamp(): - logging.info("Alarm triggered, sending email...") - requests.post("https://diyhue.org/cdn/mailNotify.php", json={"to": bridgeConfig["config"]["alarm"]["email"], "sensor": device.name}, timeout=10) - bridgeConfig["config"]["alarm"]["lasttriggered"] = int(current_time.timestamp()) + if "temperature" in data: + tempSensor = device.elements["ZLLTemperature"]() + tempSensor.state = {"temperature": int(data["temperature"] * 100), "lastupdated": lastupdated} + if "contact" in data: + contactSensor = device.elements["ZLLContact"]() + contactSensor.state = {"contact": "contact" if data["contact"] else "no_contact", "lastupdated": lastupdated} elif device.modelid in standardSensors: + convertedPayload = {"lastupdated": lastupdated} convertedPayload.update(standardSensors[device.modelid]["dataConversion"][data[standardSensors[device.modelid]["dataConversion"]["rootKey"]]]) - for key in convertedPayload.keys(): - if device.state[key] != convertedPayload[key]: - device.dxState[key] = current_time - device.state.update(convertedPayload) - logging.debug(convertedPayload) - if "buttonevent" in convertedPayload and convertedPayload["buttonevent"] in [1001, 2001, 3001, 4001, 5001]: - Thread(target=longPressButton, args=[device, convertedPayload["buttonevent"]]).start() - rulesProcessor(device, current_time) + for key in convertedPayload.keys(): + if device.firstElement().state[key] != convertedPayload[key]: + device.firstElement().dxState[key] = current_time + device.firstElement().state.update(convertedPayload) + logging.debug(convertedPayload) + if "buttonevent" in convertedPayload and convertedPayload["buttonevent"] in [1001, 2001, 3001, 4001, 5001]: + Thread(target=longPressButton, args=[device.firstElement(), convertedPayload["buttonevent"]]).start() + rulesProcessor(device.firstElement(), current_time) checkBehaviorInstances(device) - elif device.getObjectPath()["resource"] == "lights": + elif device.group_v1 == "lights": + light = device.firstElement() state = {"reachable": True} v2State = {} if "state" in data: - if data["state"] == "ON": - state["on"] = True - else: - state["on"] = False + state["on"] = True if data["state"] == "ON" else False v2State.update({"on":{"on": state["on"]}}) - device.genStreamEvent(v2State) + light.genStreamEvent(v2State) if "brightness" in data: state["bri"] = data["brightness"] v2State.update({"dimming": {"brightness": round(state["bri"] / 2.54, 2)}}) - device.genStreamEvent(v2State) - device.state.update(state) + light.genStreamEvent(v2State) + light.state.update(state) streamGroupEvent(device, v2State) on_state_update(msg) except Exception as e: logging.info("MQTT Exception | " + str(e)) -def findLightSensor(sensor): - lightSensorUID = sensor.uniqueid[:-1] + "0" - for key, obj in bridgeConfig["sensors"].items(): - if obj.uniqueid == lightSensorUID: - return obj - -def findTempSensor(sensor): - lightSensorUID = sensor.uniqueid[:-1] + "2" - for key, obj in bridgeConfig["sensors"].items(): - if obj.uniqueid == lightSensorUID: - return obj - def convertHexToMac(hexValue): s = '{0:016x}'.format(int(hexValue,16)) s = ':'.join(s[i:i + 2] for i in range(0, 16, 2)) diff --git a/BridgeEmulator/services/scheduler.py b/BridgeEmulator/services/scheduler.py index 0dcdfb16f..bdc2ab98a 100644 --- a/BridgeEmulator/services/scheduler.py +++ b/BridgeEmulator/services/scheduler.py @@ -17,7 +17,7 @@ def runScheduler(): while True: - for schedule, obj in bridgeConfig["schedules"].items(): + for schedule, obj in bridgeConfig["schedules"].items(): #v1 api try: delay = 0 if obj.status == "enabled": @@ -58,7 +58,7 @@ def runScheduler(): except Exception as e: logging.info("Exception while processing the schedule " + schedule + " | " + str(e)) - for instance, obj in bridgeConfig["behavior_instance"].items(): + for instance, obj in bridgeConfig["behavior_instance"].items(): #v2 api try: delay = 0 if obj.enabled: @@ -83,7 +83,7 @@ def runScheduler(): seconds=fade_duration["seconds"] if "seconds" in fade_duration else 0) time_object = time_object + delta if "turn_lights_off_after" in obj.configuration and obj.active else time_object - delta if datetime.now().second == time_object.second and datetime.now().minute == time_object.minute and datetime.now().hour == time_object.hour: - logging.info("execute timmer: " + obj.name) + logging.info("execute routine: " + obj.name) Thread(target=triggerScript, args=[obj]).start() elif "when_extended" in obj.configuration: @@ -97,8 +97,13 @@ def runScheduler(): minute = triggerTime["minute"], second = triggerTime["second"] if "second" in triggerTime else 0) if datetime.now().second == time_object.second and datetime.now().minute == time_object.minute and datetime.now().hour == time_object.hour: - logging.info("execute timmer: " + obj.name) + logging.info("execute routine: " + obj.name) Thread(target=triggerScript, args=[obj]).start() + elif "duration" in obj.configuration: + if obj.active == False and obj.enabled == True: + logging.info("execute timer: " + obj.name) + obj.active = True + Thread(target=triggerScript, args=[obj]).start() except Exception as e: