Extend the functionality of Philips Hue lamps with Jython (incl. phue)

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.