Hue bulbs and groups

Hi all,

After a lot of pain trying to get my hue spotlights into OH properly I’m finally working on the rules to make them all work.

So, the premise:

I have 8 hue spotlights in my living room, I want to control them via both a hue dimmer switch and also Alexa, no problem in principle.

I’ve read some posts on here about the dimmer switch issues with 2.5 so I’m starting with some rules.

I’ve started with a few tester rules to get a feel for how they work and have come up to an issue.

I have a simple rule which turns on 4 of the spotlights to 100 brightness and a colour temp of 14, so far so good.

The first rule I made runs a sendCommand(“100”) to each spotlight individually, (the brightness dimmer channel), and it runs fine.

However, when I convert them into a group I’m noticing that while the logs show that the items receive the same command the physical lights don’t always follow the same. Sometimes I get 2 lights work as expected, 1 will be on but at a random brightness and the other will be off. Other times its fine, and other times 3 will work and 1 not etc etc etc, ie it’s random.

Is there an issue with hue items in groups? Is there a delay I need to add to the rule or a maximum number of items / commands the hue binding / api can take???

No

Almost certainly yes. Several technologies have this sort of limitation. If you send them commands too quickly some of the commands get lost.

See Design Pattern: Gate Keeper for an example for how to add the delay or a link to a library if you are using Python Rules.

Perfect thanks, I’ll work some of that in to my rules. I didn’t want to have to do it if it was pointless or if I had a different problem.

Hi,

I’ve read through the gate keeper DP and get the concept, I’m missing one part that I need to get my head around to help me design the rules and associated rules I will need…

I have 8 hue spotlights in the living room.
They will have various ‘scenes’ that I will trigger via some switch, (virtual, voice or physical - it doesn’t matter for this example),

ie
Scene 1 is all 8 on
Scene 2 is left 4 on
Scene 3 is right 4 on
Scene 4 is 2 above tv on
Scene 5 in nightlight mode

And I’m sure there will be more. My current approach is to put them all into a case statement triggered by a dummy item with the appropriate scene value, ie 1, 2, 3 etc.

For each case statement that I have, I ‘sendCommand’ to the appropriate group of lights(ie gScene1 has 8 lights as members, gScene2 has the left 4 as members etc), as determined by groups in the .items file.

This works but as stated in the first post, often the lights do weird things when using groups, and I think it is because the commands are going to quickly to the hue api to keep up. I ‘could’ get rid of groups and command each item individually but want to avoid that as much as possible for many obviously reasons, (groups are far neater and easier to manage for two).

So, moving onto the gatekeeper example, I know that I cant use groups to command it so I’m happy to set up dummy switches to do that job but…and here’s where I’m rambling towards…is there a way to feed into the gatekeeper rule the correct item group for the specific scene called?

Could I just create a dummy item for each scene each called ‘LightScene_1’ ‘LightScene_2’ etc and then split the triggered.item by the _ to feed into the gatekeeper rule the correct scene, and then if I named the group ‘LightGroup_1’ etc for each group I could append the 1, 2, 3 to the end to get the correct group and so run the for each members part of the group correctly?

The other issue I have is that the lights are dimmers and the scenes aren’t all on/off, some will have different brightness levels…

Am I starting to make things more complicated than they need to be by trying to include the gatekeeper rule when perhaps a thread:sleep or similar for each scene would suffice???

Not looking for code to be written, just some thoughts / advice / guidance would be appreciated before I dive in…

So in visual form:

I currently have rules that update item vSpotLightScene with a number…this in turn runs the rule:

rule "Select Spotlight Scene Rule" 
    when
        Item vSpotLightScene changed
    then
    // Run the Spotlight Scene Selector
        switch vSpotLightScene.state {
        case 0 : {
            // Turn off all lights
                gSpots_LR_All.sendCommand("0")
                logInfo(logName, "vSpotLightScene 0 - Turn Off All Lights")
            }                                
        case 1 : {
            // Turn on all lights
                gSpots_LR_All.sendCommand("100")
                gSpots_LR_All_CT.sendCommand("14")
                logInfo(logName, "vSpotLightScene 1 - Turn On All Lights")
            } 
        case 2 : {
            // Turn on these lights
                gSpots_LR_These.sendCommand("100")
                gSpots_LR_These_CT.sendCommand("14")
                logInfo(logName, "vSpotLightScene 2 - Turn On These Lights")
            }    
        case 3 : {
            // Turn on sofa lights
                gSpots_LR_Sofa.sendCommand("100")
                gSpots_LR_Sofa_CT.sendCommand("14")
                logInfo(logName, "vSpotLightScene 3 - Turn On Sofa Lights")
            } 
        case 4 : {
            // case 4
            }
        case 5 : {
            // case 5
            }
        case 6 : {
            // case 6
            }
        }      
end

The reason for doing this is so I can just call a scene from any rule I wish without repeating code all the time. I’ve done it in a case statement as one scene might require less brightness and a different colour temp for example and I can expand as needed.

So how could I integrate the gate keeper rule in one rule to cover all these eventualities?

Of course, you can feed it anything. The example uses a String and to achieve what you want still feeding it a String, but the String would be the name of the Group containing the Lights that should be turned ON/OFF/Dimmed plus the new state. The Gatekeeper will then parse the Group name and the command from that String and use Design Pattern: Associated Items to get the Group by name and then feed each member of the Group to the Queue to be turned ON/OFF in turn.

See the link above.

That get’s more complicated. Do they all get set to the same dimmer value or are there different dimmer values for individual lights in the same scene? If the former, it’s no big deal, and the code above handles it. If the former, you might want to look to moving to Scripted Automation and looking at using Item metadata. If you did so, there are already libraries that can handle much of this for you.

The reason Gatekeeper exists is because of Why have my Rules stopped running? Why Thread::sleep is a bad idea. If you are using Scripted Automation there isn’t the same limitation so a Thread sleep isn’t a problem. But if you are using Rules DSL, you run a high risk of killing your Rules when you have long running Rules.

The only thing the GK Rules do is add some time between commands in a way that doesn’t cause long running Rules. So instead of:

        case 3 : {
            // Turn on sofa lights
                gSpots_LR_Sofa.sendCommand("100")
                gSpots_LR_Sofa_CT.sendCommand("14")
                logInfo(logName, "vSpotLightScene 3 - Turn On Sofa Lights")
            } 

you would use

        case 3 : {
            // Turn on sofa lights
                LightsGatekeeper.sendCommand("gSpots_LR_Sofa#100")
                LightsGatekeeper.sendCommand("gSpots_LR_Sofa_CT#14")
                logInfo(logName, "vSpotLightScene 3 - Turn On Sofa Lights")
            } 

That Gatekeeper Rule will

import org.eclipse.smarthome.model.script.ScriptServiceUtil

...
    // Add the commands to the Queue
    val groupName = receivedCommand.split("#").get(0)
    val cmd = receivedCommand.split("#").get(1)
    val group = ScriptServiceUtil.getItemRegistry.getItem(groupName)
    group.members.forEach[ light | commands.add(light.name+"#"+cmd) ]


    // Timer
    ... 
        if(commands.peek !== null){
            val parts = commands.poll.split("#")
            sendCommand(parts.get(0), parts.get(1)
            ...

Your scene handling Rules remain the same. The only thing GK does is enforce a little bit of delay between commands to the Lights. So the only difference is instead of commanding the Groups directly, you command a special Gatekeeper Item which triggers a rule to enforce those delays.

1 Like

Hi,

That’s a brilliant explanation, thanks so much.

I’ve written a rule that does almost everything I want it to do and almost works perfectly…

Item:

// Alexa command item
Switch aGF_LR_Lights_All        "Living Room Lights"              (gAlexa_Lights)     {alexa="Switchable"}
// Groups to control items
Group:Dimmer  gSpots_LR_B_All                   "Living Room Spots All ON"
Group:Dimmer  gSpots_LR_CT_All                  "Living Room Spots All CT"
// Actual items
Dimmer      Spot_GF_LR_1_Brightness             "GF LR Spot 1 Brightness"               (gSpots_LR_B_All)          {channel="hue:blah:brightness"}
Dimmer      Spot_GF_LR_1_ColorTemp              "GF LR Spot 1 ColorTemp"                (gSpots_LR_CT_All)    {channel="hue:blah:color_temperature"}

Rule:

rule "Select Spotlight Scene Rule" 
    when
        Member of gAlexa_Lights changed
    then
        val caseItem = triggeringItem.name.split("_").get(3)
        logInfo(logName, "caseItem - " + caseItem)
        val caseValue = triggeringItem.state
        logInfo(logName, "caseValue - " + caseValue)

        if(caseValue == OFF)
        {
            val caseBrightness = 0
            val caseColorTemp = 0
            logInfo(logName, "OFF - caseBrightness set to " + caseBrightness + " / caseColorTemp to " + caseColorTemp)
        }
        else
        { 
            // Run the Spotlight Scene Selector
            switch caseItem {
            case "All" : {
                // Settings for ALL LIGHTS
                val caseBrightness = 100
                val caseColorTemp = 14
                logInfo(logName, "ALL - caseBrightness set to " + caseBrightness + " / caseColorTemp to " + caseColorTemp)
            }                                
            case "These" : {
                // Settings for THESE LIGHTS
                val caseBrightness = 100
                val caseColorTemp = 14
                logInfo(logName, "THESE - caseBrightness set to " + caseBrightness + " / caseColorTemp to " + caseColorTemp)
            } 
            case "Sofa" : {
                // Settings for SOFA LIGHTS
                val caseBrightness = 100
                val caseColorTemp = 14
                logInfo(logName, "SOFA - caseBrightness set to " + caseBrightness + " / caseColorTemp to " + caseColorTemp)
            }    
            case "3" : {
                // case 3 
            } 
            case "4" : {
                // case 4
            }
            }
        } 

        // Create the string to send to GateKeeper

        val SpotGroupB = ("gSpots_LR_B_" + caseItem + "#" + caseBrightness)
        logInfo(logName, "SpotGroupB - " + SpotGroupB)
        val SpotGroupTC = ("gSpots_LR_TC_" + caseItem + "#" + caseColorTemp)
        logInfo(logName, "SpotGroupTC - " + SpotGroupTC)

end

It triggers when any member of my gAlexa_Lights changes, (that group consists of Alexa exposed dummy items switches)

It then gets the triggereditem name and state to let me work out which alexa command was called, (my idea is that the commands will be used to turn on the different scenes I need ‘alexa, turn on the living room lights’, ‘alexa, turn on the sofa lights’ etc).

It then runs an if statement it see if any are calling OFF, in which case turn all the lights off, else run a case statements to see what brightness and colortemp it should use.

It then gets all the different vals and puts them into a string, matching the relevant item group, to use to send to the GateKeeper.

All is working perfectly, (see the logs below), except the bit where I get the brightness to add onto the string for after the # for the GateKeeper command as per your comment:

When I tell Alexa, ‘turn on the living room lights’ I get the following in the logs:

2020-04-29 19:49:13.399 [INFO ] [clipse.smarthome.model.script.lights] - caseItem - All
2020-04-29 19:49:13.403 [INFO ] [clipse.smarthome.model.script.lights] - caseValue - ON
2020-04-29 19:49:13.410 [INFO ] [clipse.smarthome.model.script.lights] - ALL - caseBrightness set to 100 / caseColorTemp to 14
2020-04-29 19:49:13.416 [INFO ] [clipse.smarthome.model.script.lights] - SpotGroupB - gSpots_LR_B_All
2020-04-29 19:49:13.422 [INFO ] [clipse.smarthome.model.script.lights] - SpotGroupTC - gSpots_LR_TC_All
2020-04-29 19:49:13.428 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Select Spotlight Scene Rule': The name 'caseBrightness' cannot be resolved to an item or type; line 128, column 61, length 14

Everything populates ready except the caseBrightness item…my brainwave in creating this has passed and now I’m failing to see what is wrong. Once I’ve got this bit it’s onto adding the GateKeeper rule and passing it the string created here, and Bob should be my metaphorical Uncle…

Does the rule make sense and what have I missed to get the caseBrightness in please?

The curly brackets { } denote a code block.
When the block exits, anything created inside it by val or var ceases to exist.

You’ll have to do something like -

       var caseBrightness = null

        if(caseValue == OFF)
        {
            caseBrightness = 0
            ...
        }

for things that need a continued existence outside of a { block }

When you initialize a var to null, you need to give it a type, in this case Number. This is one of those annoying and inconsistent places where Rules DSL doesn’t handle type very well. It’s understandable though in this case because the Rules Engine determines the type of caseBrightness when it’s declared. It’s not able to look ahead to see the = 0 to figure out it needs to be a Number.

Another approach would be to initialize it to -1 and then later on check for < 0 instead of === null.

1 Like

Thanks guys, couldn’t see the wood for the trees…I also used val when it should have been var for caseBrightness and caseColorTemp…think I got a little copy and paste happy!

That all works now, log:

2020-04-29 21:12:03.657 [INFO ] [clipse.smarthome.model.script.lights] - caseItem - All
2020-04-29 21:12:03.661 [INFO ] [clipse.smarthome.model.script.lights] - caseValue - OFF
2020-04-29 21:12:03.669 [INFO ] [clipse.smarthome.model.script.lights] - OFF - caseBrightness set to 0 / caseColorTemp to 0
2020-04-29 21:12:03.674 [INFO ] [clipse.smarthome.model.script.lights] - SpotGroupB - gSpots_LR_B_All#0
2020-04-29 21:12:03.680 [INFO ] [clipse.smarthome.model.script.lights] - SpotGroupTC - gSpots_LR_TC_All#0

So I’ve got my commands to pass to the GateKeeper sorted, now onto the GateKeeper itself!

As I’m already there with this, would you still suggest putting the GateKeeper into a separate rule or just tagging it to the end of this one?

If this is the only rule you will have that controls these lights, tack it to the end of this rule. If you control the Hue lights from any other Rules, put it into a separate Rule and make sure that all of your Rules that control Hue lights go through the GK. That’s the only way to ensure that the Hue will never drop a command no matter what is going on in your system at any given time. Otherwise two separate Rules could run at the same time and try to command Hue stuff too close together and one of the commands would get lost.

GK is intended to be the only thing that actually sends commands to the Hue Devices (in this case). If you bypass it than you are going to have problems at some point.

I’ll keep it separate for the moment, not sure if it will the only rule so separate might be the way to go to future proof it…

So, GateKeeper Rule:

import org.eclipse.smarthome.model.script.ScriptServiceUtil
val logName = "GateKeeper"
val deviceQueue = new ConcurrentLinkedQueue()
var Timer timer = null
val delay = 1000  // Delay in milliseconds between commands

// GK for Living Room Lights Brightness

rule "GK Living Room Lights Brightness"
    when
        Item vLightsGatekeeper_B changed
    then
    // Add the commands to the Queue
    val groupName = receivedCommand.split("#").get(0)
    val cmd = receivedCommand.split("#").get(1)
    val group = ScriptServiceUtil.getItemRegistry.getItem(groupName)

    group.members.forEach[ light | commands.add(light.name+"#"+cmd) ]

    // Timer
    if(timer === null) 
    {
        timer = createTimer(now, [ |
            if(commands.peek !== null) 
            {
                val parts = commands.poll.split("#")
                sendCommand(parts.get(0), parts.get(1))
                logInfo(logName, parts)
                lastCommand = now.millis
            }

            val deltaTime = now.millis - lastCommand
            timer.reschedule(now.plusMillis(if(deltaTime<100) 100-deltaTime else 0)) // 0 will reschedule the timer to run immediately
        ])
    }
end

Log here, it shows the process working now to create a string value for the dummy item vGateKeeper_B in the lights rule

        vLightsGatekeeper_B.sendCommand(SpotGroupB)
        vLightsGatekeeper_TC.sendCommand(SpotGroupTC)

Logs show these items being populated from the lights rule

2020-04-29 21:51:54.069 [ome.event.ItemCommandEvent] - Item 'vLightsGatekeeper_B' received command gSpots_LR_B_All#0
2020-04-29 21:51:54.079 [ome.event.ItemCommandEvent] - Item 'vLightsGatekeeper_TC' received command gSpots_LR_TC_All#0

However I get the following log

2020-04-29 21:51:56.756 [WARN ] [me.internal.engine.RuleContextHelper] - Variable 'deviceQueue' on rule file 'GK.rules' cannot be initialized with value 'org.eclipse.xtext.xbase.impl.XConstructorCallImplCustom@190b283 (invalidFeatureIssueCode: null, validFeature: false, explicitConstructorCall: true, anonymousClassConstructorCall: false)': An error occurred during the script execution: null
2020-04-29 21:51:56.763 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'GK Living Room Lights Brightness': The name 'receivedCommand' cannot be resolved to an item or type; line 14, column 21, length 15
2020-04-29 21:51:56.763 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'GK Living Room Lights ColorTemp': The name 'receivedCommand' cannot be resolved to an item or type; line 45, column 21, length 15

This error is way above my knowledge base to know where to start I’m afraid?

First part sorted, forgot to add

import java.util.concurrent.ConcurrentLinkedQueue to the top of the file, now to sort the receivedCommand issue

This implicit variable is only available in rules actually triggered by a command

2 Likes

Thanks…getting there I think, I’ve bunged a load of logfile entries to check each step…

Rule:

Rule "GK Living Room Lights Brightness"
    when
        Item vLightsGatekeeper_B received command
    then
    // Add the commands to the Queue
    val GKcommand = vLightsGatekeeper_B.state.toString
    logInfo(logName, "GK_B GKCommand - " + GKcommand)
    val groupName = GKcommand.split("#").get(0)
    logInfo(logName, "GK_B groupName - " + groupName)
    val cmd = GKcommand.split("#").get(1)
    logInfo(logName, "GK_B cmd - " + cmd)
    val group = ScriptServiceUtil.getItemRegistry.getItem(groupName)
    logInfo(logName, "GK_B group - " + group)
    group.members.forEach[ light | commands.add(light.name+"#"+cmd) ]

    // Timer
    if(timer === null) 
    {
        timer = createTimer(now, [ |
            if(commands.peek !== null) 
            {
                val parts = commands.poll.split("#")
                sendCommand(parts.get(0), parts.get(1))
                logInfo(logName, "GK_B Parts - " + parts)
                lastCommand = now.millis

                val deltaTime = now.millis - lastCommand
                timer.reschedule(now.plusMillis(if(deltaTime<100) 100-deltaTime else 0)) // 0 will reschedule the timer to run immediately
            }
            else
            {
                timer = null
            }
        ])
    }
end

LOG

2020-04-29 22:21:24.561 [INFO ] [se.smarthome.model.script.GateKeeper] - GK_B GKCommand - gSpots_LR_B_All#100
2020-04-29 22:21:24.566 [INFO ] [se.smarthome.model.script.GateKeeper] - GK_B groupName - gSpots_LR_B_All
2020-04-29 22:21:24.579 [INFO ] [se.smarthome.model.script.GateKeeper] - GK_B cmd - 100
2020-04-29 22:21:24.583 [INFO ] [se.smarthome.model.script.GateKeeper] - GK_B group - gSpots_LR_B_All (Type=GroupItem, BaseType=DimmerItem, Members=4, State=100, Label=Living Room Spots All ON, Category=null)
2020-04-29 22:21:24.586 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'GK Living Room Lights Brightness': 'members' is not a member of 'org.eclipse.smarthome.core.items.Item'; line 23, column 5, length 13

Now failing on the groups part…which in my limited knowledge of groups looks ok to me? @rlkoshak

It’s been a long time since I’ve used Rules DSL. Looks like you need to tell it that the Item you pull from the registry is a GroupItem. It’s returning just a plain old Item which is a parent of GroupItem.

val GroupItem = ScriptServiceUtil.getItemRegistry.getItem(groupName)

The logs show that it does know that group is a GroupItem as the logs show it as such

val group = ScriptServiceUtil.getItemRegistry.getItem(groupName)
2020-04-29 22:21:24.583 [INFO ] [se.smarthome.model.script.GateKeeper] - GK_B group - gSpots_LR_B_All (Type=GroupItem, BaseType=DimmerItem, Members=4, State=100, Label=Living Room Spots All ON, Category=null)

It even states there are 4 members?

EDIT

It got me thinking, I’ve declared it as GroupItem at the end, which it seems happy with…

val group = ScriptServiceUtil.getItemRegistry.getItem(groupName) as GroupItem

I now however, get another error message in the logs…

2020-04-30 00:28:08.745 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'GK Living Room Lights Brightness': The name 'commands' cannot be resolved to an item or type; line 23, column 36, length 8

Rule as is now:

rule "GK Living Room Lights Brightness"
    when
        Item vLightsGatekeeper_B received command
    then
    // Add the commands to the Queue
    val GKcommand = vLightsGatekeeper_B.state.toString
    logInfo(logName, "GK_B GKCommand - " + GKcommand)
    val groupName = GKcommand.split("#").get(0)
    logInfo(logName, "GK_B groupName - " + groupName)
    val cmd = GKcommand.split("#").get(1)
    logInfo(logName, "GK_B cmd - " + cmd)
    val group = ScriptServiceUtil.getItemRegistry.getItem(groupName) as GroupItem
    logInfo(logName, "GK_B group - " + group)
    group.members.forEach[ light | commands.add(light.name+"#"+cmd) ]

    // Timer
    if(timer === null) 
    {
        timer = createTimer(now, [ |
            if(commands.peek !== null) 
            {
                val parts = commands.poll.split("#")
                sendCommand(parts.get(0), parts.get(1))
                logInfo(logName, "GK_B Parts - " + parts)
                lastCommand = now.millis

                val deltaTime = now.millis - lastCommand
                timer.reschedule(now.plusMillis(if(deltaTime<100) 100-deltaTime else 0)) // 0 will reschedule the timer to run immediately
            }
            else
            {
                timer = null
            }
        ])
    }
end

This is quite interesting, I had a play with retrieving a Group using
val indirect = ScriptServiceUtil.getItemRegistry.getItem(somegroup.name)

As you note, it does claim to have a Type property of GroupItem.

You can use its method to ask it what is
indirect.getType().toString
and it says its a Group (this is different to GroupItem)

You can test with
if (indirect instanceof GroupItem) {
and that’s true as well, which ought to be idiot proof.

But it ain’t a real Group, it has no .members method. Somehow that does not carry across the indirection, but you woud expect it to be instance of genericItem or something.

The fix is what Rich suggested, casting the type -

val indirect = ScriptServiceUtil.getItemRegistry.getItem(somegroup.name) as GroupItem

and then indirect.members will work.

You haven’t declared a variable called commands.
Look again at Rich’s Design Pattern DSL example, there is a global to declare at top of rule file. And it is a somewhat special one too.

Ah man, I am getting tired…how could I miss that!

Thanks again…I’ll update in the morning with fresh eyes and mind!