Synchronize 2 dependent items

Hi,

I have a standard CCT LED controller connected via a Phoscon gateway.
Using the deconz binding I can without any issues control the LED via openHAB using the following 2 items:

Number          Farbtemperatur_Kueche
                "Farbtemperatur LED Küche"                             
                { channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:color_temperature" }

Dimmer          Helligkeit_LED_Kueche
                "Helligkeit LED Küche"
                (gKuechenLED)                
                {   channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:brightness" }

The color temperature is stored and expected in Kelvin by the deconz binding
With the brightness channel I am also able to switch on/off the lights.

However, I want to control the LED only via homekit using the respective binding and now there are some problems. I was able to make it work somehow but I am not sure whether I am doing this correctly.

  • Homekit expects color temperatures in Mired instead of Kelvin
  • Homekit expects a dedicated “On/Off state” variable of Switch type to switch lights on/off (it does not accept Dimmer types)

The items now look as follows:

Group           gKuechenLED
                "LED Küchenschrank"    
                {homekit="Lighting"}    

Switch          Kueche_LED_SyncTimer                
                {expire="5s,command=OFF"}

Number          Farbtemperatur_Kueche_homekit
                "Farbtemperatur LED Küche für Homekit"
                (gKuechenLED)              
                { homekit="Lighting.ColorTemperature" [minValue=147, maxValue=455] }

Number          Farbtemperatur_Kueche
                "Farbtemperatur LED Küche"                             
                { channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:color_temperature" }

Dimmer          Helligkeit_LED_Kueche
                "Helligkeit LED Küche"
                (gKuechenLED)                
                {   channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:brightness",
                    homekit="Lighting.Brightness"
                }

Switch          Status_LED_Kueche
                "Status LED Küche"
                (gKuechenLED)
                { homekit="Lighting.OnState" }

I need a mechanism which switches off/on the LEDs based on the “OnState” variable.

Apparently I need to synchronize the dependent items but I have no idea how this is safely done.
What I am doing right now looks as follows. Could you please tell me whether this makes sense or is complete rubbish? I use a sync timer and some sleep commands to make sure that items only get updated when the referenced item was changed by the user. By this I want to avoid an infinite loop of items changing each other. Does this make sense?

rule "Lichter: Status LED Küche in Homekit aktualisieren"
when Item Helligkeit_LED_Kueche changed
then
        logInfo("Lichter: Status LED Küche in Homekit aktualisieren", "Helligkeit_LED_Kueche changed to " + Helligkeit_LED_Kueche.state)
        if(Kueche_LED_SyncTimer.state == ON)
                return;

        if ((Helligkeit_LED_Kueche.state as Number).intValue > 0) {
                Status_LED_Kueche.sendCommand(ON)
        } else {
                Status_LED_Kueche.sendCommand(OFF)
        }
        Kueche_LED_SyncTimer.sendCommand(ON)
        Thread::sleep(2000)
end

rule "Lichter: LED Küche in deconz einschalten"
when Item Status_LED_Kueche changed
then
        logInfo("Lichter: LED Küche in deconz einschalten", "Status_LED_Kueche changed to " + Status_LED_Kueche.state)
        if(Kueche_LED_SyncTimer.state == ON)
                return;
        
        if(Status_LED_Kueche.state == ON) {
                Helligkeit_LED_Kueche.sendCommand(100)                             
        } else {
                Helligkeit_LED_Kueche.sendCommand(0)
        }
        Kueche_LED_SyncTimer.sendCommand(ON)
        Thread::sleep(2000)        
end

The sleeps give me some concerns.

Typically the biggest problem we have is an infinite loop, which you correctly identify. However, you’ve done a lot already to avoid that but only triggering the rules based on a change. The other standard thing to do is to not command an Item to a state it’s already in. So check to see if the Item is ON before commanding it ON.

Beyond that, the sleep and SyncTimer shouldn’t be required. They certainly are not stopping any infinite loops. Something like this would be the minimum required to avoid infinite loops.

rule "Lichter: Status LED Küche in Homekit aktualisieren"
when Item Helligkeit_LED_Kueche changed
then
        logInfo("Lichter: Status LED Küche in Homekit aktualisieren", "Helligkeit_LED_Kueche changed to " + Helligkeit_LED_Kueche.state)

        val command = if(Helligkeit_LED_Kueche.state as Number) > 0) ON else OFF // on liner if/else
        if(Status_LED_Kueche.state != command) Status_LED_Kueche.sendCommand(command)
end

rule "Lichter: LED Küche in deconz einschalten"
when Item Status_LED_Kueche changed
then
        logInfo("Lichter: LED Küche in deconz einschalten", "Status_LED_Kueche changed to " + Status_LED_Kueche.state)

        val command = if(Status_LED_Kueche.state == ON) 100 else 0
        if(Helligkeit_LED_Kueche.state != command) Helligkeit_LED_Kueche.sendCommand(command)
end

That will be sufficient to avoid inifinite loops and limit the number of commands sent particularly in the case where the Helligkeit_LED_Kueche Item changes gradually.

All the sleep will do is delay the time between rule runs since only one instance of a given rule can run at a time and subsequent triggers get queued and run off in turn. And in some ways, that sleep is working against the SyncTimer.

You don’t really need either, but you definitely don’t need both.

1 Like

Rich, as always, thank you for the time you invested in my question. :blush:
This was never clear to me so far.

I will give it a try tomorrow but I‘m pretty sure it will work as expected.

So, I just tested it and Rich’s solution works. It is very elegant!

I have two other items which needed synchronization and I think I finally managed it. They were not so easy to synchronize like the brightness. I will share the example below in case one day someone stumbles upon the same issue.

It is about syncing the color temperature. openHAB expects it in Kelvin and Homekit in Mired. For sure I want both systems always be synchronized. I use two proxy variables defined as global and use them to find out which of the two items (openHAB-Kelvin or Homekit-Miret) were changed by the user. This avoids an infinite loop:

The items

Group:Number    gFarbtemperaturKueche
                "Farbtemperatur Küche"

Number          Farbtemperatur_Kueche_homekit
                "Farbtemperatur LED Küche für Homekit"
                (gKuechenLED, gFarbtemperaturKueche)              
                { homekit="Lighting.ColorTemperature" [minValue=158, maxValue=495] }

Number          Farbtemperatur_Kueche
                "Farbtemperatur LED Küche"      
                (gFarbtemperaturKueche)                       
                { channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:color_temperature" }

The rule

var Integer farbTempKueche = 2200
var Integer farbTempKuecheHomekit = 455

rule "Lichter: Farbtemperatur LED Küche synchronisieren"
when Member of gFarbtemperaturKueche changed
then               
        var String msg = "Farbtemperatur geändert. " + triggeringItem.name + ": " + triggeringItem.state + " "
        if(triggeringItem.name == "Farbtemperatur_Kueche_homekit") {
                farbTempKuecheHomekit = (Farbtemperatur_Kueche_homekit.state as Number).intValue
                farbTempKueche = 1000000 / (Farbtemperatur_Kueche_homekit.state as Number).intValue
        } else {
                farbTempKueche = (Farbtemperatur_Kueche.state as Number).intValue
                farbTempKuecheHomekit = 1000000 / (Farbtemperatur_Kueche.state as Number).intValue                
        }

        if(triggeringItem.name == "Farbtemperatur_Kueche_homekit" && (Farbtemperatur_Kueche.state as Number).intValue != farbTempKueche) {                
                Farbtemperatur_Kueche.sendCommand(farbTempKueche)
                msg += "Mired. Farbtemperatur_Kueche: " + farbTempKueche + " K"                
                logInfo("Lichter: Farbtemperatur LED Küche synchronisieren", msg)   
        } else if(triggeringItem.name == "Farbtemperatur_Kueche" && (Farbtemperatur_Kueche_homekit.state as Number).intValue != farbTempKuecheHomekit) {                
                Farbtemperatur_Kueche_homekit.sendCommand(farbTempKuecheHomekit)
                msg += "K. Farbtemperatur_Kueche_homekit: " + farbTempKuecheHomekit + " Mired"   
                logInfo("Lichter: Farbtemperatur LED Küche synchronisieren", msg)                
        }        
        
end

@JimT if you find the time, please read through the initial post of this topic. Can JRuby help me out here as well to get rid of synchronisation rules? Would profiles help? The idea is to have one item only receiving values in Mired and the profile converts it to Kelvin before sending it out to the thing. Does this make sense?

Group:Number    gFarbtemperaturKueche
                "Farbtemperatur Küche"

Number          Farbtemperatur_Kueche_homekit
                "Farbtemperatur LED Küche für Homekit"
                (gKuechenLED, gFarbtemperaturKueche)              
                { homekit="Lighting.ColorTemperature" [minValue=158, maxValue=495],
                  channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:color_temperature" [ profile="ruby:mired_to_kelvin" ] }

mired_to_kelvin_or_any_name_you_like.rb

profile(:mired_to_kelvin) do |event, callback:, state:, command:|
  case event
  when :command_from_item then callback.handle_command((command | "mired" | "K").to_f)
  when :state_from_handler then callback.send_update((state | "K" | "mired").to_f)
  else next true
  end
  false
end
1 Like

Alternatively (only works on openHAB 4+):

Group:Number    gFarbtemperaturKueche
                "Farbtemperatur Küche"

Number          Farbtemperatur_Kueche_homekit
                "Farbtemperatur LED Küche für Homekit"
                (gKuechenLED, gFarbtemperaturKueche)              
                { homekit="Lighting.ColorTemperature" [minValue=158, maxValue=495],
                  channel="deconz:colortemperaturelight:phoscon-gw:Kuechenschrank:color_temperature" [ profile="transform:RB", toItemScript="|(input.to_f | 'K' | 'mired').to_f", toHandlerScript="|(input.to_f | 'mired' | 'K').to_f" ] }

In this version, you don’t even need a ruby file, but I’d probably prefer the previous version using a Ruby profile if you have to do this on many items.

1 Like

I will need to do in on several items, yes.
I tried to understand your code. Where does the actual conversion take place? 1,000,000 / MiretValue = KelvinValue

Where is this actually calculated?

Well the input (state or command) is a number (DecimalType)

so state | "mired" converts that into a QuantityType with the unit “mired” because that’s the implicit unit of the command coming from Homekit.

Then (state | "mired") | "K" converts that mired QuantityType into its equivalent value in Kelvin unit. Then .to_f converts it to float, stripping the unit.

I’m not 100% sure that callback.handle_command / send_update will accept a float, or whether it needs a DecimalType or String but I think it’ll work as is. The JRuby library is very flexible whenever possible.

See this page about how to use JRuby’s QuantityType support Class: OpenHAB::Core::Types::QuantityType — openHAB JRuby

1 Like

Ah, there you are! I did not know the library can handle these types and can handle the conversion directly. This will shrink my code extensively!
As usual thank you very much! :sunglasses:

I tried it out a little with semi success.
Yes, the conversion works somehow sometimes. But there are 2 open topics.

When sending commands in Mired to the items I receive the following log messages given that I added this line into your profile proposal:

logger.info("Ruby profile #{event}: command: #{command}")

The logs then show:

2023-08-22 22:39:57.542 [INFO ] [ubyscripting.profile.mired_to_kelvin] - Ruby profile command_from_item: command: 454
2023-08-22 22:39:57.564 [INFO ] [ubyscripting.profile.mired_to_kelvin] - Ruby profile state_from_item: command: 
2023-08-22 22:39:57.568 [INFO ] [ubyscripting.profile.mired_to_kelvin] - Ruby profile state_from_handler: command: 
2023-08-22 22:39:57.571 [ERROR] [ubyscripting.profile.mired_to_kelvin] - unexpected return (LocalJumpError)

The lamp however shows normal behaviour. It switched to 2200 K temperature when having send 454 Mired to the item. That is nice.

But when restarting openHAB or commenting out the color temperature item from the item file and adding it again I find the value “2200” stored in the item and not “454” anymore. This confuses the homekit addon and it refuses to work. Can this be avoided?

Are you setting the unit metadata on the Item? If there is no unit the system default is assumed which is K for color temperature.

Sorry. I still get tripped up by this. In the ruby script, replace return with next. I’ve edited the post for future readers

I wrote this for those new to ruby (including myself!) File: Ruby Basics — openHAB JRuby

Change that to

logger.info("Ruby profile #{event}: command: #{command}, state: #{state}")

Nope, I forgot about that one. Good hint!

I shall also read more into the documentation the next days.

Yesterday evening I realized that switching from RuleDSL to JRuby won’t be that easy. The concept on which my current rule set is based on is quite different as I would do it now since it has grown over time. Instead of switching “rule by rule” I come to the conclusion that I need to build up my over the time grown system probably from scratch which will need a little bit more effort than expected.

Once I am over the part here of this thread I will deliver an update.

Take a look at the Semantic model. It is very handy in associating related items without using the “naming pattern” trick. For example, you can send a command to all lights in the room where your motion sensor triggered.

Furthermore, jruby library makes working with semantics easier.

Yeah, I think that makes sense. So far I could not really make sense of it as I am using Homekit to actively interact with my system. But for the rules and examples I found (mostly in the docs and examples in the forum by you) it looks very handy.