Skip to content

Commit

Permalink
updates and fixes
Browse files Browse the repository at this point in the history
Remove unused import
Add .000 to lastupdate
Add diyhue info to apiV2 response
Update apiV2 response
Fix grouped.light error for entertainment #1032
Update openwrt install
  • Loading branch information
hendriksen-mark committed Sep 2, 2024
1 parent 52ad547 commit 6511864
Show file tree
Hide file tree
Showing 38 changed files with 276 additions and 163 deletions.
2 changes: 0 additions & 2 deletions BridgeEmulator/HueEmulator3.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python
from flask import Flask
from flask.json import jsonify
from flask_cors import CORS
from flask_restful import Api
from threading import Thread
Expand All @@ -15,7 +14,6 @@
from flaskUI.Credits import Credits
from werkzeug.serving import WSGIRequestHandler
from functions.daylightSensor import daylightSensor
from pprint import pprint

bridgeConfig = configManager.bridgeConfig.yaml_config
logging = logManager.logger.get_logger(__name__)
Expand Down
1 change: 0 additions & 1 deletion BridgeEmulator/HueObjects/BehaviorInstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def getV2Api(self):
result = {"configuration": self.configuration,
"dependees": [],
"enabled": self.enabled,
"active": self.active,
"id": self.id_v2,
"last_error": "",
"metadata": {
Expand Down
37 changes: 22 additions & 15 deletions BridgeEmulator/HueObjects/Group.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,21 @@ def update_attr(self, newdata):
def update_state(self):
all_on = True
any_on = False
bri = 0
lights_on = 0
if len(self.lights) == 0:
all_on = False
for light in self.lights:
if light():
if light().state["on"]:
any_on = True
bri = bri + light().state["bri"]
lights_on = lights_on + 1
else:
all_on = False
return {"all_on": all_on, "any_on": any_on}
if any_on:
bri = (((bri/lights_on)/254)*100)
return {"all_on": all_on, "any_on": any_on, "avr_bri": int(bri)}

def setV2Action(self, state):
v1State = v2StateToV1(state)
Expand Down Expand Up @@ -197,12 +203,12 @@ def getV1Api(self):
result["lightlevel"] = {"state": {"dark": None, "dark_all": None, "daylight": None, "daylight_any": None,
"lightlevel": None, "lightlevel_min": None, "lightlevel_max": None, "lastupdated": "none"}}
else:
result["class"] = self.icon_class.capitalize()
result["class"] = self.icon_class.capitalize() if len(self.icon_class) > 2 else self.icon_class.upper()
result["action"] = self.action
return result

def getV2Room(self):
result = {"children": [], "grouped_services": [], "services": []}
result = {"children": [], "services": []}
for light in self.lights:
if light():
result["children"].append({
Expand All @@ -211,11 +217,10 @@ def getV2Room(self):
"rtype": "device"
})

result["grouped_services"].append({
"rid": self.id_v2,
"rtype": "grouped_light"

})
#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"] = {
Expand All @@ -238,19 +243,18 @@ def getV2Room(self):
return result

def getV2Zone(self):
result = {"children": [], "grouped_services": [], "services": []}
result = {"children": [], "services": []}
for light in self.lights:
if light():
result["children"].append({
"rid": light().id_v2,
"rtype": "light"
})

result["grouped_services"].append({
"rid": self.id_v2,
"rtype": "grouped_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"] = {
Expand Down Expand Up @@ -280,7 +284,7 @@ def getV2GroupedLight(self):
]
}
result["color"] = {}
result["dimming"] = {}
result["dimming"] = {"brightness": self.update_state()["avr_bri"]}
result["dimming_delta"] = {}
result["dynamics"] = {}
result["id"] = self.id_v2
Expand All @@ -291,6 +295,9 @@ def getV2GroupedLight(self):
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"]}

return result

Expand Down
12 changes: 9 additions & 3 deletions BridgeEmulator/HueObjects/Light.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self, data):
self.streaming = False
self.dynamics = deepcopy(lightTypes[self.modelid]["dynamics"])
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"),
Expand Down Expand Up @@ -164,6 +165,8 @@ def setV1State(self, state, advertise=True):
self.config[key] = value
if key == "name":
self.name = value
if key == "function":
self.function = value
if "bri" in state:
if "min_bri" in self.protocol_cfg and self.protocol_cfg["min_bri"] > state["bri"]:
state["bri"] = self.protocol_cfg["min_bri"]
Expand Down Expand Up @@ -195,6 +198,8 @@ def setV2State(self, state):
v1State["archetype"] = state["metadata"]["archetype"]
if "name" in state["metadata"]:
v1State["name"] = state["metadata"]["name"]
if "function" in state["metadata"]:
v1State["function"] = state["metadata"]["function"]
self.setV1State(v1State, advertise=False)
self.genStreamEvent(state)

Expand Down Expand Up @@ -305,7 +310,7 @@ def getV2Api(self):
}
result["color_temperature"]["mirek_valid"] = True if self.state[
"ct"] != None and self.state["ct"] < 500 and self.state["ct"] > 153 else False
result["color_temperature_delta"] = {}
#result["color_temperature_delta"] = {}
if "bri" in self.state:
bri_value = self.state["bri"]
if bri_value is None or bri_value == "null":
Expand All @@ -332,7 +337,7 @@ def getV2Api(self):
result["identify"] = {}
result["id"] = self.id_v2
result["id_v1"] = "/lights/" + self.id_v1
result["metadata"] = {"name": self.name, "function": "mixed",
result["metadata"] = {"name": self.name, "function": self.function,
"archetype": archetype[self.config["archetype"]]}
result["mode"] = "normal"
if "mode" in self.state and self.state["mode"] == "streaming":
Expand All @@ -348,6 +353,7 @@ def getV2Api(self):
result["signaling"] = {"signal_values": [
"no_signal",
"on_off"]}
result["powerup"] = {"preset": "last_on_state"}
result["type"] = "light"
return result

Expand Down Expand Up @@ -473,6 +479,6 @@ def dynamicScenePlay(self, palette, index):
logging.debug("Dynamic Scene " + self.name + " stopped.")

def save(self):
result = {"id_v2": self.id_v2, "name": self.name, "modelid": self.modelid, "uniqueid": self.uniqueid,
result = {"id_v2": self.id_v2, "name": self.name, "modelid": self.modelid, "uniqueid": self.uniqueid, "function": self.function,
"state": self.state, "config": self.config, "protocol": self.protocol, "protocol_cfg": self.protocol_cfg}
return result
30 changes: 20 additions & 10 deletions BridgeEmulator/HueObjects/Scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(self, data):
self.speed = data["speed"] if "speed" in data else self.DEFAULT_SPEED
self.group = data["group"] if "group" in data else None
self.lights = data["lights"] if "lights" in data else []
self.status = data["status"] if "status" in data else "inactive"
if "group" in data:
self.storelightstate()
self.lights = self.group().lights
Expand All @@ -54,17 +55,23 @@ def add_light(self, light):

def activate(self, data):
# activate dynamic scene
if "recall" in data and data["recall"]["action"] == "dynamic_palette":
lightIndex = 0
for light in self.lights:
if light():
light().dynamics["speed"] = self.speed
Thread(target=light().dynamicScenePlay, args=[
self.palette, lightIndex]).start()
lightIndex += 1

if "recall" in data:
if data["recall"]["action"] == "dynamic_palette":
self.status = data["recall"]["action"]
lightIndex = 0
for light in self.lights:
if light():
light().dynamics["speed"] = self.speed
Thread(target=light().dynamicScenePlay, args=[
self.palette, lightIndex]).start()
lightIndex += 1

if data["recall"]["action"] == "deactivate":
self.status = "inactive"
return

queueState = {}
self.status = data["recall"]["action"]
for light, state in self.lightstates.items():
logging.debug(state)
light.state.update(state)
Expand Down Expand Up @@ -149,7 +156,7 @@ def getV2Api(self):
v2State["dimming"] = {
"brightness": round(float(bri_value) / 2.54, 2)
}
v2State["dimming_delta"] = {}
#v2State["dimming_delta"] = {}

if "xy" in state:
v2State["color"] = {
Expand Down Expand Up @@ -184,6 +191,9 @@ def getV2Api(self):
if self.palette:
result["palette"] = self.palette
result["speed"] = self.speed
result["auto_dynamic"] = False
result["status"] = {"active": self.status}
result["recall"] = {}
return result

def storelightstate(self):
Expand Down
77 changes: 63 additions & 14 deletions BridgeEmulator/HueObjects/Sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ def getBridgeHome(self):
if self.type == "ZLLPresence":
rtype = "motion"
elif self.type == "ZLLLightLevel":
rtype = "temperature"
elif self.type == "ZLLTemperature":
rtype = "light_level"
elif self.type == "ZLLTemperature":
rtype = "temperature"
return {
"rid": self.id_v2,
"rtype": rtype
Expand Down Expand Up @@ -155,6 +155,7 @@ def getDevice(self):
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",
Expand Down Expand Up @@ -189,6 +190,7 @@ def getDevice(self):
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",
Expand Down Expand Up @@ -223,6 +225,7 @@ def getDevice(self):
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",
Expand Down Expand Up @@ -254,21 +257,67 @@ def getDevice(self):
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": True if self.state["presence"] else False,
"motion_valid": True
},
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"
},
"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
Expand Down
3 changes: 0 additions & 3 deletions BridgeEmulator/configManager/configHandler.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from configManager import configInit
from configManager.argumentHandler import parse_arguments
from datetime import datetime
import os
import pathlib
import subprocess
import json
import logManager
import yaml
import uuid
import weakref
from time import sleep
from HueObjects import Light, Group, EntertainmentConfiguration, Scene, ApiUser, Rule, ResourceLink, Schedule, Sensor, BehaviorInstance, SmartScene
try:
from time import tzset
Expand Down
2 changes: 1 addition & 1 deletion BridgeEmulator/configManager/runtimeConfigHandler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from dataclasses import dataclass, field
from dataclasses import dataclass
from configManager.argumentHandler import parse_arguments


Expand Down
3 changes: 1 addition & 2 deletions BridgeEmulator/flaskUI/core/forms.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Form Based Imports
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo
from wtforms import ValidationError
from wtforms.validators import DataRequired,Email

class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
Expand Down
2 changes: 1 addition & 1 deletion BridgeEmulator/flaskUI/espDevices.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def get(self):
result = {"fail": "unknown device"}
obj.dxState["lastupdated"] = current_time
obj.state["lastupdated"] = datetime.now(timezone.utc).strftime(
"%Y-%m-%dT%H:%M:%S")
"%Y-%m-%dT%H:%M:%S.000Z")
rulesProcessor(obj, current_time)
result = {"success": "command applied"}
else:
Expand Down
Loading

3 comments on commit 6511864

@chrivers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @hendriksen-mark :)

Nice work! It looks like some of these fixes are based on the testing we did with the bifrost data models?

I'm glad they were useful :)

@hendriksen-mark
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @chrivers
yes they are indeed.
its very useful, hope to work together again in the future.

@chrivers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - glad it helped 👍 :)

Please sign in to comment.