Skip to content

Commit

Permalink
Merge pull request #331 from diyhue/master-yeelightmusic
Browse files Browse the repository at this point in the history
Merge Yeelight Music Mode (#329)
  • Loading branch information
mariusmotea authored Mar 31, 2020
2 parents a096ee1 + e6c00a3 commit d52e8b2
Show file tree
Hide file tree
Showing 9 changed files with 543 additions and 360 deletions.
2 changes: 1 addition & 1 deletion BridgeEmulator/HueEmulator3.py
Original file line number Diff line number Diff line change
Expand Up @@ -1948,7 +1948,7 @@ def run(https, server_class=ThreadingSimpleServer, handler_class=S):
Thread(target=ssdpBroadcast, args=[HOST_IP, HOST_HTTP_PORT, mac]).start()
Thread(target=schedulerProcessor).start()
Thread(target=syncWithLights, args=[bridge_config["lights"], bridge_config["lights_address"], bridge_config["config"]["whitelist"], bridge_config["groups"], off_if_unreachable]).start()
Thread(target=entertainmentService, args=[bridge_config["lights"], bridge_config["lights_address"], bridge_config["groups"]]).start()
Thread(target=entertainmentService, args=[bridge_config["lights"], bridge_config["lights_address"], bridge_config["groups"], HOST_IP]).start()
Thread(target=run, args=[False]).start()
if not args.no_serve_https:
Thread(target=run, args=[True]).start()
Expand Down
19 changes: 15 additions & 4 deletions BridgeEmulator/functions/colors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
def convert_rgb_xy(red,green,blue):
def rgbBrightness(rgb, brightness):
r = sorted((0, int(rgb[0] * brightness) >> 8, 255))[1] #calculate with brightness and clamp
g = sorted((0, int(rgb[1] * brightness) >> 8, 255))[1]
b = sorted((0, int(rgb[2] * brightness) >> 8, 255))[1]
return [r, g, b]

def clampRGB(rgb):
r = sorted((0, int(rgb[0]), 255))[1]
g = sorted((0, int(rgb[1]), 255))[1]
b = sorted((0, int(rgb[2]), 255))[1]
return [r, g, b]

def convert_rgb_xy(red, green, blue):
red = pow((red + 0.055) / (1.0 + 0.055), 2.4) if red > 0.04045 else red / 12.92
green = pow((green + 0.055) / (1.0 + 0.055), 2.4) if green > 0.04045 else green / 12.92
blue = pow((blue + 0.055) / (1.0 + 0.055), 2.4) if blue > 0.04045 else blue / 12.92
Expand Down Expand Up @@ -51,7 +63,7 @@ def convert_xy(x, y, bri): #needed for milight hub that don't work with xy value
r = 0 if r < 0 else r
g = 0 if g < 0 else g
b = 0 if b < 0 else b
return [int(r * bri), int(g * bri), int(b * bri)]
return clampRGB([r * bri, g * bri, b * bri])

def hsv_to_rgb(h, s, v):
s = float(s / 254)
Expand Down Expand Up @@ -84,5 +96,4 @@ def hsv_to_rgb(h, s, v):
g = 0
b = x

r, g, b = int(r * 255), int(g * 255), int(b * 255)
return r, g, b
return clampRGB([r * 255, g * 255, b * 255])
54 changes: 29 additions & 25 deletions BridgeEmulator/functions/entertainment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
from functions.colors import convert_rgb_xy, convert_xy
from functions.lightRequest import sendLightRequest

def entertainmentService(lights, addresses, groups):
def entertainmentService(lights, addresses, groups, host_ip):
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.settimeout(3) #Set a packet timeout that we catch later
serverSocket.bind(('127.0.0.1', 2101))
fremeID = 0
frameID = 0
lightStatus = {}
syncing = False #Flag to check whether or not we had been syncing when a timeout occurs
while True:
Expand All @@ -26,38 +26,45 @@ def entertainmentService(lights, addresses, groups):
r = int((data[i+3] * 256 + data[i+4]) / 256)
g = int((data[i+5] * 256 + data[i+6]) / 256)
b = int((data[i+7] * 256 + data[i+8]) / 256)
proto = addresses[str(lightId)]["protocol"]
if lightId not in lightStatus:
lightStatus[lightId] = {"on": False, "bri": 1}
if r == 0 and g == 0 and b == 0:
lights[str(lightId)]["state"]["on"] = False
else:
lights[str(lightId)]["state"].update({"on": True, "bri": int((r + g + b) / 3), "xy": convert_rgb_xy(r, g, b), "colormode": "xy"})
if addresses[str(lightId)]["protocol"] in ["native", "native_multi", "native_single"]:
if proto in ["native", "native_multi", "native_single"]:
if addresses[str(lightId)]["ip"] not in nativeLights:
nativeLights[addresses[str(lightId)]["ip"]] = {}
nativeLights[addresses[str(lightId)]["ip"]][addresses[str(lightId)]["light_nr"] - 1] = [r, g, b]
if addresses[str(lightId)]["protocol"] == "esphome":
if proto == "esphome":
if addresses[str(lightId)]["ip"] not in esphomeLights:
esphomeLights[addresses[str(lightId)]["ip"]] = {}
bri = int(max(r,g,b))
esphomeLights[addresses[str(lightId)]["ip"]]["color"] = [r, g, b, bri]
else:
if fremeID == 24: # => every seconds, increase in case the destination device is overloaded
if r == 0 and g == 0 and b == 0:
if frameID == 24: # => every seconds, increase in case the destination device is overloaded
gottaSend = False
yee = proto == "yeelight"
brABS = abs(int((r + b + g) / 3) - lightStatus[lightId]["bri"])
if r == 0 and g == 0 and b == 0: #Turn off if color is black
if lightStatus[lightId]["on"]:
sendLightRequest(str(lightId), {"on": False, "transitiontime": 3}, lights, addresses)
sendLightRequest(str(lightId), {"on": False, "transitiontime": 3}, lights, addresses, None, host_ip)
lightStatus[lightId]["on"] = False
elif lightStatus[lightId]["on"] == False:
sendLightRequest(str(lightId), {"on": True, "transitiontime": 3}, lights, addresses)
lightStatus[lightId]["on"] = True
elif abs(int((r + b + g) / 3) - lightStatus[lightId]["bri"]) > 50: # to optimize, send brightness only of difference is bigger than this value
sendLightRequest(str(lightId), {"bri": int((r + b + g) / 3), "transitiontime": 3}, lights, addresses)
lightStatus[lightId]["bri"] = int((r + b + g) / 3)
else:
sendLightRequest(str(lightId), {"xy": convert_rgb_xy(r, g, b), "transitiontime": 3}, lights, addresses)
fremeID += 1
if fremeID == 25:
fremeID = 0
if lightStatus[lightId]["on"] == False: #Turn on if color is not black
sendLightRequest(str(lightId), {"on": True, "transitiontime": 3}, lights, addresses, None, host_ip)
lightStatus[lightId]["on"] = True
elif brABS > 50: # to optimize, send brightness only if difference is bigger than this value
sendLightRequest(str(lightId), {"bri": int((r + b + g) / 3), "transitiontime": 150 / brABS}, lights, addresses, None, host_ip)
lightStatus[lightId]["bri"] = int((r + b + g) / 3)
else:
gottaSend = True
if gottaSend or yee:
sendLightRequest(str(lightId), {"xy": convert_rgb_xy(r, g, b), "transitiontime": 3}, lights, addresses, [r, g, b], host_ip)
frameID += 1
if frameID == 25:
frameID = 0
i = i + 9
elif data[14] == 1: #cie colorspace
i = 16
Expand All @@ -79,16 +86,13 @@ def entertainmentService(lights, addresses, groups):
if addresses[str(lightId)]["protocol"] == "esphome":
if addresses[str(lightId)]["ip"] not in esphomeLights:
esphomeLights[addresses[str(lightId)]["ip"]] = {}
color = convert_xy(x, y, bri)
r = int(color[0])
g = int(color[1])
b = int(color[2])
r, g, b = convert_xy(x, y, bri)
esphomeLights[addresses[str(lightId)]["ip"]]["color"] = [r, g, b, bri]
else:
fremeID += 1
if fremeID == 24 : #24 = every seconds, increase in case the destination device is overloaded
sendLightRequest(str(lightId), {"xy": [x,y]}, lights, addresses)
fremeID = 0
frameID += 1
if frameID == 24 : #24 = every seconds, increase in case the destination device is overloaded
sendLightRequest(str(lightId), {"xy": [x,y]}, lights, addresses, None, host_ip)
frameID = 0
i = i + 9
if len(nativeLights) is not 0:
for ip in nativeLights.keys():
Expand Down
41 changes: 30 additions & 11 deletions BridgeEmulator/functions/lightRequest.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import logging, json
from functions.request import sendRequest
from functions.colors import convert_rgb_xy, convert_xy
from functions.colors import convert_rgb_xy, convert_xy, rgbBrightness
from subprocess import check_output
from protocols import protocols
from datetime import datetime
from datetime import datetime, timedelta
from time import sleep
from functions.updateGroup import updateGroupStats

def sendLightRequest(light, data, lights, addresses):
def sendLightRequest(light, data, lights, addresses, rgb = None, entertainmentHostIP = None):
payload = {}
if light in addresses:
protocol_name = addresses[light]["protocol"]
for protocol in protocols:
if "protocols." + protocol_name == protocol.__name__:
try:
light_state = protocol.set_light(addresses[light], lights[light], data)
if entertainmentHostIP and protocol_name == "yeelight":
protocol.enableMusic(addresses[light]["ip"], entertainmentHostIP)
if protocol_name in ["yeelight", "mi_box", "esphome", "tasmota"]:
protocol.set_light(addresses[light], lights[light], data, rgb)
else:
protocol.set_light(addresses[light], lights[light], data)
except Exception as e:
lights[light]["state"]["reachable"] = False
logging.warning(lights[light]["name"] + " light not reachable: %s", e)
Expand Down Expand Up @@ -70,7 +75,10 @@ def sendLightRequest(light, data, lights, addresses):
color_data["t"] = ct255
elif colormode == "xy":
color_data["m"] = 3
(color_data["r"], color_data["g"], color_data["b"]) = convert_xy(xy[0], xy[1], 255)
if rgb:
(color_data["r"], color_data["g"], color_data["b"]) = rgbBrightness(rgb, bri)
else:
(color_data["r"], color_data["g"], color_data["b"]) = convert_xy(xy[0], xy[1], bri)
url += "&color="+json.dumps(color_data)
url += "&brightness=" + str(round(float(bri)/255*100))

Expand Down Expand Up @@ -105,7 +113,10 @@ def sendLightRequest(light, data, lights, addresses):
payload["saturation"] = value * 100 / 255
elif key == "xy":
payload["color"] = {}
(payload["color"]["r"], payload["color"]["g"], payload["color"]["b"]) = convert_xy(value[0], value[1], lights[light]["state"]["bri"])
if rgb:
payload["color"]["r"], payload["color"]["g"], payload["color"]["b"] = rgbBrightness(rgb, lights[light]["state"]["bri"])
else:
payload["color"]["r"], payload["color"]["g"], payload["color"]["b"] = convert_xy(value[0], value[1], lights[light]["state"]["bri"])
logging.info(json.dumps(payload))

elif addresses[light]["protocol"] == "ikea_tradfri": #IKEA Tradfri bulb
Expand Down Expand Up @@ -166,8 +177,11 @@ def sendLightRequest(light, data, lights, addresses):
logging.info(pretty_json(data))
bri = data["bri"] if "bri" in data else lights[light]["state"]["bri"]
xy = data["xy"] if "xy" in data else lights[light]["state"]["xy"]
rgb = convert_xy(xy[0], xy[1], bri)
msg = bytearray([0x41, rgb[0], rgb[1], rgb[2], 0x00, 0xf0, 0x0f])
if rgb:
color = rgbBrightness(rgb, bri)
else:
color = convert_xy(xy[0], xy[1], bri)
msg = bytearray([0x41, color[0], color[1], color[2], 0x00, 0xf0, 0x0f])
checksum = sum(msg) & 0xFF
msg.append(checksum)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
Expand Down Expand Up @@ -301,8 +315,13 @@ def syncWithLights(lights, addresses, users, groups, off_if_unreachable): #updat
i = 0
while i < 300: #sync with lights every 300 seconds or instant if one user is connected
for user in users.keys():
if users[user]["last use date"] == datetime.now().strftime("%Y-%m-%dT%H:%M:%S"):
i = 300
break
lu = users[user]["last use date"]
try: #in case if last use is not a proper datetime
lu = datetime.strptime(lu, "%Y-%m-%dT%H:%M:%S")
if abs(datetime.now() - lu) <= timedelta(seconds = 2):
i = 300
break
except:
pass
i += 1
sleep(1)
11 changes: 9 additions & 2 deletions BridgeEmulator/functions/network_OpenWrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ def get_interface_ip(ifname):
bytes(ifname[:15], 'utf-8')))[20:24])

def getIpAddress():
ip = socket.gethostbyname(socket.gethostname())
if ip.startswith("127.") and os.name != "nt":
ip = None

try:
ip = socket.gethostbyname(socket.gethostname())
except:
pass

if (not ip or ip.startswith("127.")) and os.name != "nt":
interfaces = [
"br0",
"br-lan",
"eth0",
"eth1",
Expand Down
Loading

0 comments on commit d52e8b2

Please sign in to comment.