Hello all,
I have again a simple solution how to get more out of the Philips Hue lamps in openHAB. To put it simply, you write Jython rules that use the phue library, among others.
First, of course, you have to install phue via pip:
sudo -u openhab python -m pip install phue --user
The items example
Normally your items looks like this:
Group Light
// Bulb1
Switch Light1_Toggle { channel="hue:0210:1:bulb1:color" } (Light)
Dimmer Light1_Dimmer { channel="hue:0210:1:bulb1:color" } (Light)
Color Light1_Color { channel="hue:0210:1:bulb1:color" } (Light)
Dimmer Light1_ColorTemp { channel="hue:0210:1:bulb1:color_temperature" } (Light)
String Light1_Alert { channel="hue:0210:1:bulb1:alert" } (Light)
Switch Light1_Effect { channel="hue:0210:1:bulb1:effect" } (Light)
// Bulb2
Switch Light2_Toggle { channel="hue:0210:2:bulb2:color" } (Light)
Dimmer Light2_Dimmer { channel="hue:0210:2:bulb2:color" } (Light)
Color Light2_Color { channel="hue:0210:2:bulb2:color" } (Light)
Dimmer Light2_ColorTemp { channel="hue:0210:2:bulb2:color_temperature" } (Light)
String Light2_Alert { channel="hue:0210:2:bulb2:alert" } (Light)
Switch Light2_Effect { channel="hue:0210:2:bulb2:effect" } (Light)
...
Now you have to add following items to the first bulb (and maybe to all other bulbs):
String Light1_Color_RGB
String Light1_Toggle_Transition
String Light1_Color_Transition
String Light1_Color_RGB_Transition
String Light1_Toggle_Schedule
For creating items which should control all lamps a group you have to create:
Switch Lights_Toggle
Dimmer Lights_Dimmer
Color Lights_Color
Dimmer Lights_ColorTemp
String Lights_Alert
Switch Lights_Effect
You don’t end up having to define groups in the Philips Hue app, you can do it in openHAB. This reduces the dependency of this app, which is only used to configure lamps.
Rules for scenes I still have to think up in more detail, but can be implemented in openHAB just as easily.
The rules
The RGB to HSB and HSB to RGB conversion rules
For this use case phue is not used!
In the first rules I have a simple conversion from HSB to RGB and RGB to HSB. My Philips Hue lamps have both as State HSB values, as well as that I send HSB values as Command to my Color item. Now when I access it via the REST API because I want to work with it differently, I have found that in many systems RGB would be beneficial instead. This is the case in Unity for example. In my lab, there are several projects that access items via the REST API. And in some of these projects, this is then converted from HSB to RGB in this software itself, then processed further and later converted back to HSB and a command is triggered in openHAB. And that’s what you can simplify. If openHAB can do all this, then you can leave out this conversion in very many projects. To get more to the point: It is adopted in openHAB uniformly for all projects, instead of a conversion in several projects separately.
My example rule looks like this:
from core.rules import rule
from core.triggers import when
def convert_rgb_to_hsb(red, green, blue):
r = red / 255.0
g = green / 255.0
b = blue / 255.0
max_value = max(r, g, b)
min_value = min(r, g, b)
delta = max_value - min_value
if delta == 0:
hue = 0
elif max_value == r:
hue = ((g - b) / delta) % 6
elif max_value == g:
hue = ((b - r) / delta) + 2
else:
hue = ((r - g) / delta) + 4
hue *= 60
saturation = 0 if max_value == 0 else delta / max_value
brightness = max_value
return "{}, {}, {}".format(hue, saturation * 100, brightness * 100)
def convert_hsb_to_rgb(hue, saturation, brightness):
hue /= 60
saturation /= 100
brightness /= 100
chroma = brightness * saturation
x = chroma * (1 - abs((hue % 2) - 1))
m = brightness - chroma
if hue < 1:
red, green, blue = chroma, x, 0
elif hue < 2:
red, green, blue = x, chroma, 0
elif hue < 3:
red, green, blue = 0, chroma, x
elif hue < 4:
red, green, blue = 0, x, chroma
elif hue < 5:
red, green, blue = x, 0, chroma
else:
red, green, blue = chroma, 0, x
red += m
green += m
blue += m
red = int(red * 255)
green = int(green * 255)
blue = int(blue * 255)
return "{}, {}, {}".format(red, green, blue)
@rule("Convert RGB to HSB Rule Light1_Color_RGB")
@when("Item Light1_Color_RGB received command")
def convert_rgb_to_hsb_Light1_Color_RGB(event):
rgb_value = str(event.itemCommand).replace(" ", "").split(",")
hsb_value = convert_rgb_to_hsb(int(rgb_value[0]), int(rgb_value[1]), int(rgb_value[2]))
events.sendCommand("Light1_Color", hsb_value)
@rule("Convert HSB to RGB Rule Light1_Color")
@when("Item Light1_Color changed")
def convert_hsb_to_rgb_Light1_Color(event):
hsb_value = str(event.itemState).replace(" ", "").split(",")
rgb_value = convert_hsb_to_rgb(float(hsb_value[0]), float(hsb_value[1]), float(hsb_value[2]))
events.postUpdate("Light1_Color_RGB", rgb_value)
... (further rules)
To avoid an endless loop it is important to work once with sendCommand and once with postUpdate, as well as with received command and changed as triggers.
That is, if I change an RGB value whether by a sendCommand (which I can also trigger via the REST API), the Color Item changes with the HSB value. Each changing HSB value calculates its state for the string item that contains the associated RGB value of the color lamp and represents this RGB value. It doesn’t matter if I change the color value by another item (e.g. by dimming), change it via the Philips Hue app or e.g. via the openHAB Sitemap.
Since you need several rules for several lamps, because this depends on the trigger, the two functions for the conversion are just outsourced as such a function and not written directly in a rule. This algorithm remains the same and can then be reused in the other rules.
The group rules for hue lamps
For this use case phue is not used!
What I want to implement is that I assign e.g. all items the same brightness or the same color or that this alarm flashes on all of them at the same time and so on.
The rules looks like this:
import core
from core.rules import rule
from core.triggers import when
import threading
import time
@rule("lamps init")
@when("System started")
def lamps_init(event):
items = [
"Lights_Toggle",
"Lights_Dimmer",
"Lights_Color",
"Lights_ColorTemp",
"Lights_Alert",
"Lights_Effect"
]
states = [
"0, 0, 0",
"0",
"0",
"",
"OFF",
"OFF"
]
threads = []
for i in range(0, 6):
thread = threading.Thread(target=events.postUpdate, args=(items[i], states[i]))
threads.append(thread)
time.sleep(1)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
@rule("lamps on/off")
@when("Item Lights_Toggle changed")
def lamps_switch(event):
lamps = [
"Light1_Toggle",
"Light2_Toggle"
]
threads = []
for lamp in lamps:
if ir.getItem("Lights_Toggle").getState() == ON:
thread = threading.Thread(target=events.sendCommand, args=(lamp, "ON"))
else:
thread = threading.Thread(target=events.sendCommand, args=(lamp, "OFF"))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
@rule("lamps effect on/off")
@when("Item Lights_Effect changed")
def lamps_effect(event):
lamps = [
"Light1_Effect",
"Light2_Effect"
]
threads = []
for lamp in lamps:
if ir.getItem("Lights_Effect").getState() == ON:
thread = threading.Thread(target=events.sendCommand, args=(lamp, "ON"))
else:
thread = threading.Thread(target=events.sendCommand, args=(lamp, "OFF"))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
@rule("lamps color")
@when("Item Lights_Color changed")
def lamps_color(event):
lamps = [
"Light1_Color",
"Light2_Color"
]
threads = []
for lamp in lamps:
thread = threading.Thread(target=events.sendCommand, args=(lamp, str(ir.getItem("Lights_Color").state)))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
@rule("lamps color temperature")
@when("Item Lights_ColorTemp changed")
def lamps_color_temperature(event):
lamps = [
"Light1_ColorTemp",
"Light2_ColorTemp"
]
threads = []
for lamp in lamps:
thread = threading.Thread(target=events.sendCommand, args=(lamp, str(ir.getItem("Lights_ColorTemp").state)))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
@rule("lamps dimmer")
@when("Item Lights_Dimmer changed")
def lamps_helligkeit(event):
lamps = [
"Light1_Dimmer",
"Light2_Dimmer"
]
threads = []
for lamp in lamps:
thread = threading.Thread(target=events.sendCommand, args=(lamp, str(ir.getItem("Lights_Dimmer").state)))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
To get this done as parallel as possible, I run threads. Also, you can see that the unbound items I created for group control are best initialized to any null values at system startup, because otherwise it could lead to a misbehavior, since Jython can’t execute a trigger at NULL.
The transition rules
I have created the transitions, as you can see for switch items that toggle ON/OFF and to the color values, whether by HSB or RGB. A transition means, I send next to ON/OFF a time, how long I want to execute this command. This transition time will lead to a visually very nice effect, especially with the color values. A state is changed softly. The rules look e.g. as follows:
from core import osgi
from core.rules import rule
from core.triggers import when
from core.actions import LogAction
from phue import Bridge
bridge_ip = "<your_ip_to_your_hue_bridge>"
username = "<your_username_for_your_hue_bridge>"
def get_lamp_id(item):
ItemChannelLinkRegistry = osgi.get_service("org.openhab.core.thing.link.ItemChannelLinkRegistry")
channels = ItemChannelLinkRegistry.getBoundChannels(item)
lamp_id = None
for channel in channels:
LogAction.logInfo("rules", "Thing {} changed", channel)
lamp_id = str(channel).split(":")[-2]
return lamp_id
def convert_to_hue_hue(openhab_hue):
hue_hue = int((openhab_hue / 360) * 65535)
return hue_hue
def convert_to_hue_saturation(openhab_saturation):
hue_saturation = int(openhab_saturation * 2.55)
return hue_saturation
def convert_to_hue_brightness(openhab_brightness):
hue_brightness = int(openhab_brightness * 2.55)
return hue_brightness
def set_transition_switch(lamp_id, command, transition_time):
bridge = Bridge(ip=bridge_ip, username=username)
lights = bridge.get_light_objects("id")
light = lights[int(lamp_id)]
light.transitiontime = transition_time * 10
if command == "ON":
light.on = True
else:
light.on = False
def set_transition_hsb(lamp_id, hue, saturation, brightness, transition_time):
bridge = Bridge(ip=bridge_ip, username=username)
lights = bridge.get_light_objects("id")
light = lights[int(lamp_id)]
light.on = True
light.hue = convert_to_hue_hue(hue)
light.saturation = convert_to_hue_saturation(saturation)
light.brightness = convert_to_hue_brightness(brightness)
light.transitiontime = transition_time * 10
def convert_rgb_to_hsb(red, green, blue):
r = red / 255.0
g = green / 255.0
b = blue / 255.0
max_value = max(r, g, b)
min_value = min(r, g, b)
delta = max_value - min_value
if delta == 0:
hue = 0
elif max_value == r:
hue = ((g - b) / delta) % 6
elif max_value == g:
hue = ((b - r) / delta) + 2
else:
hue = ((r - g) / delta) + 4
hue *= 60
saturation = 0 if max_value == 0 else delta / max_value
brightness = max_value
return "{}, {}, {}".format(hue, saturation * 100, brightness * 100)
@rule("Light1_Toggle_Transition rule")
@when("Item Light1_Toggle_Transition received command")
def Light1_Toggle_Transition(event):
item_name = str(event.itemName)
item = item_name.replace("_Transition", "")
received = str(event.itemCommand).replace(" ", "").split(",")
command = received[0]
transition_time = float(received[1])
lamp_id = get_lamp_id(item)
set_transition_switch(lamp_id, command, transition_time)
@rule("Light1_Color_Transition rule")
@when("Item Light1_Color_Transition received command")
def Light1_Color_Transition(event):
item_name = str(event.itemName)
item = item_name.replace("_Transition", "")
hsb_value = str(event.itemCommand).replace(" ", "").split(",")
lamp_id = get_lamp_id(item)
set_transition_hsb(lamp_id, float(hsb_value[0]), float(hsb_value[1]), float(hsb_value[2]), float(hsb_value[3]))
@rule("Light1_Color_RGB_Transition rule")
@when("Item Light1_Color_RGB_Transition received command")
def Light1_Color_RGB_Transition(event):
item_name = str(event.itemName)
item = item_name.replace("RGBTransition", "")
rgb_value = str(event.itemCommand).replace(" ", "").split(",")
hsb_value = convert_rgb_to_hsb(int(rgb_value[0]), int(rgb_value[1]), int(rgb_value[2]))
hsb_value = hsb_value.split(",")
transition_time = float(rgb_value[3])
lamp_id = get_lamp_id(item)
set_transition_hsb(lamp_id, float(hsb_value[0]), float(hsb_value[1]), float(hsb_value[2]), int(transition_time))
Here we use the phue library at the end to exercise the transitions. This is simply because the openHAB binding for Philips Hue, does not support this functionality. Using a trick with org.openhab.core.thing.link.ItemChannelLinkRegistry we get the ID of the lamp from the Thing of the item that was triggered.
We can again reuse the conversion from RGB to HSB. However, phue and the openHAB binding for Philips Hue has different thresholds. Means another calculation must take place so that the expected values is actually sent to the color lamp.
The schedule rules
I’ve left that part out so far because it just hasn’t worked yet. I am unsure if it is because of my lamps. Through phue I can also set a time to send data to my lamp.
An alternative can be found here.
What else is planned
I would like to implement the examples from the phue library in openHAB, of course, so that you can possibly run them just-for-fun. Certainly comes good at parties. I also have other applications related to Philips Hue that I would like to add here.