Light control (on/off/brightness/color temp/color change) using Ikea Styrbar wireless remote

Summary

User interface:

  • UP = turn light on
  • DOWN = turn light off
  • HOLD UP = gradually increase light brightness when held down
  • HOLD DOWN = gradually decrease light brightness when held down
  • LEFT / RIGHT = cycle through White / Various colours
  • HOLD LEFT = gradually change colour temperature to cool when held down
  • HOLD RIGHT = gradually change colour temperature to warm when held down
  • When the increase / decrease of setting has reached its max/min value, blink the light to inform the user
  • When the light is in colour mode, changing brightness or colour temperature will reset the light to white mode. This behaviour is from the light itself. No special coding to make it work this way.

Implementation:

Things file:

Thing mqtt:topic:masterbedroom-dimmer "Master Bed Room Dimmer" (mqtt:broker:mosquitto) [ availabilityTopic="zigbee/masterbedroom-dimmer/availability", payloadAvailable="online", payloadNotAvailable="offline" ] {
    Channels:
         Type string        : action       [ stateTopic="zigbee/masterbedroom-dimmer/action", commandTopic="zigbee/masterbedroom-dimmer/action/openhab-command"  ]
         Type number        : linkquality  [ stateTopic="zigbee/masterbedroom-dimmer/linkquality" ]
         Type number        : battery      [ stateTopic="zigbee/masterbedroom-dimmer/battery" ]
}

items file:

Group gDimmers

String MasterBedRoom_Dimmer_Action (gDimmers, MasterBedRoom_Light1_Bulb) ["Control"] { channel="mqtt:topic:masterbedroom-dimmer:action" }
Number MasterBedRoom_Dimmer_Battery "Master Bed Room Dimmer Battery [%d%%]" <battery> (gBatteries) { channel="mqtt:topic:masterbedroom-dimmer:battery", expire="3h" }
Number MasterBedRoom_Dimmer_Link "Master Bed Room Dimmer Link" <network>          (gSignalStrength) { channel="mqtt:topic:masterbedroom-dimmer:linkquality" }

Note: MasterBedRoom_Light1_Bulb is the light to control - it’s an equipment in the openhab semantic model
To augment the semantic model, a custom tag is used for the Color item, since no such property exists in the official openhab semantic model

lightbulb items:

Group   MasterBedRoom_Light1_Bulb    "Master Bed Room Light1 Bulb"    <light>       (gMasterBedRoom, gMasterBedRoomLights)  ["Lightbulb"]
Switch  MasterBedRoom_Light1         "Master Bed Room Light1"         <light>       (MasterBedRoom_Light1_Bulb)             ["Control", "Power"]             
Dimmer  MasterBedRoom_Light1_Dimmer  "Master Bed Room Light1 Dimmer"                (MasterBedRoom_Light1_Bulb)             ["Control", "Light"]             
Dimmer  MasterBedRoom_Light1_CT      "Master Bed Room Light1 CT"                    (MasterBedRoom_Light1_Bulb)             ["Control", "ColorTemperature"]  
Color   MasterBedRoom_Light1_Color   "Master Bed Room Light1 Color"   <colorwheel>  (MasterBedRoom_Light1_Bulb)             ["Control", "Color"]             

Rule

# frozen_string_literal: true

module ButtonAction
  ON = 'on'
  OFF = 'off'

  BRIGHTNESS_UP = 'brightness_move_up'
  BRIGHTNESS_DOWN = 'brightness_move_down'
  BRIGHTNESS_STOP = 'brightness_stop'

  LEFT = 'arrow_left_click'
  LEFT_HOLD = 'arrow_left_hold'
  LEFT_RELEASE = 'arrow_left_release'

  RIGHT = 'arrow_right_click'
  RIGHT_HOLD = 'arrow_right_hold'
  RIGHT_RELEASE = 'arrow_right_release'
end

COLORS = {
  white: '#ffffff',
  red: '#ff0000',
  yellow: '#FFFF00',
  green: '#00FF00',
  aqua: '#00FFFF',
  blue: '#0000FF',
  fuchsia: '#FF00FF'
}.freeze

def blink(dimmer)
  power = dimmer.points(Semantics::Power).first
  power.off
ensure
  after(350.ms) do
    power.on
  end
end

@colors = Hash.new { |hash, key| hash[key] = COLORS.values }

def adjust(dimmer, delta)
  return timers[dimmer]&.cancel if delta.zero?

  dimmer.points(Semantics::Power).first&.ensure.on

  after(0.ms, id: dimmer) do |timer|
    next blink(dimmer) if (dimmer == '100 %' && delta.positive?) || (dimmer <= '1 %' && delta.negative?)

    dimmer << (dimmer.to_i + delta).clamp(1, 100)
    timer.reschedule 100.ms
  end
end

rule 'Ikea Dimmer Handler' do
  updated gDimmers.members
  triggered do |action|
    light = action.equipment
    next unless light

    dimmer = light.points(Semantics::Light).first
    power = light.points(Semantics::Power).first
    ct = light.points(Semantics::ColorTemperature).first
    color = light.tagged('Color').first

    case action.state
    when ButtonAction::ON then power.ensure.on
    when ButtonAction::OFF then power.ensure.off
    when ButtonAction::BRIGHTNESS_UP then adjust(dimmer, 5)
    when ButtonAction::BRIGHTNESS_DOWN then adjust(dimmer, -5)
    when ButtonAction::BRIGHTNESS_STOP then adjust(dimmer, 0)
    when ButtonAction::LEFT then color << @colors[dimmer].rotate!.first
    when ButtonAction::RIGHT then color << @colors[dimmer].rotate!(-1).first
    when ButtonAction::LEFT_HOLD then adjust(ct, -10)
    when ButtonAction::RIGHT_HOLD then adjust(ct, 10)
    when ButtonAction::RIGHT_RELEASE, ButtonAction::LEFT_RELEASE then adjust(ct, 0)
    end
  end
end
2 Likes

Hey there, it’s me again. ^^

I would like to replicate your example as I also use STYRBAR remotes. I have two main differences:

  • I connect the remote via the deconz binding and not via zigbee2mqtt
  • The number of lights which get controlled by a remote is dynamic. Currently I have 2 remotes. Remote ‘A’ controls 1 light and remote ‘B’ for which I brought some items below controls 3 lights. In the future I will have more remotes maybe which either control 1 or multiple lights…

I could get it already running by 50%. Means: I can switch on/off the lights and change the color temperature. However, none of the fading features work. Both for brightness and for changing the color temperature.

Let’s start with the items, 3 lamps combined in one group. The semantic model only knows all lamps as one group:

// *** Deckenlampen Hobbyraum ***
Group           Deckenlampen_Hobbyraum
                (Ort_Hobbyraum)
                ["Lightbulb"]

Group:Switch    Deckenlampe_Hobbyraum_Einschaltstatus
                (Deckenlampen_Hobbyraum)
                ["Control", "Light"]

Group:Dimmer    Deckenlampe_Hobbyraum_Helligkeit
                (Deckenlampen_Hobbyraum)
                ["Control", "Level"]

Group:Number    Deckenlampe_Hobbyraum_Farbtemperatur                
                (Deckenlampen_Hobbyraum)
                ["Control", "ColorTemperature"]

// Deckenlampe Hobbyraum Buecherregal
Group           Deckenlampe_Hobbyraum_Buecherregal_Homekit
                "Deckenlampe Hobbyraum Bücherregal Homekit"                              
                {
                    homekit="Lighting"
                }

Switch          Deckenlampe_Hobbyraum_Buecherregal_Einschaltstatus
                "Einschaltstatus Deckenlampe Hobbyraum Bücherregal [MAP(de.map):%s]"
                (Deckenlampe_Hobbyraum_Buecherregal_Homekit, Deckenlampe_Hobbyraum_Einschaltstatus)                
                {
                    homekit="Lighting.OnState"
                }

Dimmer          Deckenlampe_Hobbyraum_Buecherregal_Helligkeit
                "Helligkeit Deckenlampe Hobbyraum Bücherregal [%d]"
                (Deckenlampe_Hobbyraum_Buecherregal_Homekit, Deckenlampe_Hobbyraum_Helligkeit)                
                {
                    channel ="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Buecherregal:brightness",
                    homekit="Lighting.Brightness"
                }

Number          Deckenlampe_Hobbyraum_Buecherregal_Farbtemperatur
                "Farbtemperatur Deckenlampe Hobbyraum Bücherregal [%d]"
                (Deckenlampe_Hobbyraum_Buecherregal_Homekit, Deckenlampe_Hobbyraum_Farbtemperatur)                
                {
                    channel ="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Buecherregal:color_temperature" [ profile="ruby:mired_to_kelvin" ],
                    homekit="Lighting.ColorTemperature" [minValue=250, maxValue=454],
                    unit="mired"
                }

// Deckenlampe Hobbyraum Mitte
Group           Deckenlampe_Hobbyraum_Mitte_Homekit
                "Deckenlampe Hobbyraum Mitte Homekit"                              
                {
                    homekit="Lighting"
                }

Switch          Deckenlampe_Hobbyraum_Mitte_Einschaltstatus
                "Einschaltstatus Deckenlampe Hobbyraum Mitte [MAP(de.map):%s]"
                (Deckenlampe_Hobbyraum_Mitte_Homekit, Deckenlampe_Hobbyraum_Einschaltstatus)                
                {
                    homekit="Lighting.OnState"
                }

Dimmer          Deckenlampe_Hobbyraum_Mitte_Helligkeit
                "Helligkeit Deckenlampe Hobbyraum Mitte [%d]"
                (Deckenlampe_Hobbyraum_Mitte_Homekit, Deckenlampe_Hobbyraum_Helligkeit)                
                {
                    channel ="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Mitte:brightness",
                    homekit="Lighting.Brightness"
                }

Number          Deckenlampe_Hobbyraum_Mitte_Farbtemperatur
                "Farbtemperatur Deckenlampe Hobbyraum Mitte [%d]"
                (Deckenlampe_Hobbyraum_Mitte_Homekit, Deckenlampe_Hobbyraum_Farbtemperatur)                
                {
                    channel ="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Mitte:color_temperature" [ profile="ruby:mired_to_kelvin" ],
                    homekit="Lighting.ColorTemperature" [minValue=250, maxValue=454],
                    unit="mired"
                }

// Deckenlampe Hobbyraum Schreibtisch
Group           Deckenlampe_Hobbyraum_Schreibtisch_Homekit
                "Deckenlampe Hobbyraum Schreibtisch Homekit"                              
                {
                    homekit="Lighting"
                }

Switch          Deckenlampe_Hobbyraum_Schreibtisch_Einschaltstatus
                "Einschaltstatus Deckenlampe Hobbyraum Schreibtisch [MAP(de.map):%s]"
                (Deckenlampe_Hobbyraum_Schreibtisch_Homekit, Deckenlampe_Hobbyraum_Einschaltstatus)                
                {
                    homekit="Lighting.OnState"
                }

Dimmer          Deckenlampe_Hobbyraum_Schreibtisch_Helligkeit
                "Helligkeit Deckenlampe Hobbyraum Schreibtisch [%d]"
                (Deckenlampe_Hobbyraum_Schreibtisch_Homekit, Deckenlampe_Hobbyraum_Helligkeit)                
                {
                    channel ="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Schreibtisch:brightness",
                    homekit="Lighting.Brightness"
                }

Number          Deckenlampe_Hobbyraum_Schreibtisch_Farbtemperatur
                "Farbtemperatur Deckenlampe Hobbyraum Schreibtisch [%d]"
                (Deckenlampe_Hobbyraum_Schreibtisch_Homekit, Deckenlampe_Hobbyraum_Farbtemperatur)                
                {
                    channel ="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Schreibtisch:color_temperature" [ profile="ruby:mired_to_kelvin" ],
                    homekit="Lighting.ColorTemperature" [minValue=250, maxValue=454],
                    unit="mired"
                }

// *** Fernbedienungen ***
Group:Number    Fernbedienungen
                "Styrbar Fernbedienungen"

// Fernbedienung Hobbyraum
Number          Fernbedienung_Hobbyraum
                "Gedrückte Taste an Fernbedienung Keller [%d]"
                (Fernbedienungen, Deckenlampen_Hobbyraum)
                { channel="deconz:switch:raspi4:Fernbedienung_Hobbyraum:button" }

My slightly adapted rule. Adapted because I receive numbers as commands from the remote:

module ButtonAction
  ACTIONS = {
    1002 => 'on',
    2002 => 'off',
    1001 => 'brightness_move_up',
    2001 => 'brightness_move_down',
    1003 => 'brightness_stop',
    2003 => 'brightness_stop',
    3002 => 'arrow_left_click',
    3001 => 'arrow_left_hold',
    3003 => 'arrow_left_release',
    4002 => 'arrow_right_click',
    4001 => 'arrow_right_hold',
    4003 => 'arrow_right_release'
  }.freeze

  def self.action_name(action_code)
    ACTIONS[action_code]
  end
end 

COLORS = {
  cold: 250,
  middle: 352,
  warm: 454
}.freeze

def blink(dimmer)
  power = dimmer.points(Semantics::Level).first
  power.off
ensure
  after(350.ms) do
    power.on
  end
end

@colors = Hash.new { |hash, key| hash[key] = COLORS.values }

def adjust(dimmer, delta)
  return timers[dimmer]&.cancel if delta.zero?

  dimmer.points(Semantics::Level).first&.ensure.on

  after(0.ms, id: dimmer) do |timer|
    next blink(dimmer) if (dimmer == '100 %' && delta.positive?) || (dimmer <= '1 %' && delta.negative?)

    dimmer << (dimmer.to_i + delta).clamp(1, 100)
    timer.reschedule 100.ms
  end
end

rule 'Ikea Dimmer Handler' do
  updated Fernbedienungen.members
  triggered do |action|        
    light = action.equipment    
    next unless light
    
    dimmer = light.points(Semantics::Level).first
    switch = light.points(Semantics::Level).first
    ct = light.points(Semantics::ColorTemperature).first    

    action_name = ButtonAction.action_name(action.state.to_i) # Get the action name
    
    logger.info("Ikea Dimmer Handler - button pressed: #{action_name}")

    case action_name
    when 'on' then switch.ensure.on
    when 'off' then switch.ensure.off
    when 'brightness_move_up' then adjust(dimmer, 5)
    when 'brightness_move_down' then adjust(dimmer, -5)
    when 'brightness_stop' then adjust(dimmer, 0)  # Update to adjust(dimmer, 0) or adjust(ct, 0) based on your needs
    when 'arrow_left_click' then ct << @colors[dimmer].rotate!.first
    when 'arrow_right_click' then ct << @colors[dimmer].rotate!(-1).first
    when 'arrow_left_hold' then adjust(ct, -10)
    when 'arrow_right_hold' then adjust(ct, 10)
    when 'arrow_right_release', 'arrow_left_release' then adjust(ct, 0)
    end
  end
end

So clicking left/right/up/down works as expected. All three lights are synchronously handled.
But this is what the logs look like when holding down buttons:

2023-08-24 23:05:53.382 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: arrow_right_hold
2023-08-24 23:05:53.501 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
org.jruby.exceptions.NameError: (NameError) undefined method `<=' for class `Kernel'
	at org.jruby.RubyModule.instance_method(org/jruby/RubyModule.java:3005) ~[?:?]
	at uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.delegate.method_missing(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/delegate.rb:85) ~[?:?]
	at RUBY.adjust(/openhab/conf/automation/jsr223/Lichter.rb:54) ~[?:?]
	at RUBY.execute(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:133) ~[?:?]
	at openhab.conf.automation.ruby.$_dot_gem.$9_dot_3_dot_10_dot_0.gems.openhab_minus_scripting_minus_5_dot_5_dot_0.lib.openhab.dsl.thread_local.thread_local(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/dsl/thread_local.rb:44) ~[?:?]
	at RUBY.execute(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:133) ~[?:?]
	at RUBY.initialize(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:52) ~[?:?]
2023-08-24 23:05:53.846 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: arrow_right_release
2023-08-24 23:05:53.863 [ERROR] [on.jrubyscripting.rule.Lichter.rb:61] - undefined method `[]' for #<OpenHAB::DSL::TimerManager:0xa340193> (NoMethodError)
/openhab/conf/automation/jsr223/Lichter.rb:49:in `adjust'
/openhab/conf/automation/jsr223/Lichter.rb:85:in `block in <main>'
2023-08-24 23:05:53.895 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: arrow_right_hold
2023-08-24 23:05:54.021 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
org.jruby.exceptions.NameError: (NameError) undefined method `<=' for class `Kernel'
	at org.jruby.RubyModule.instance_method(org/jruby/RubyModule.java:3005) ~[?:?]
	at uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.delegate.method_missing(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/delegate.rb:85) ~[?:?]
	at RUBY.adjust(/openhab/conf/automation/jsr223/Lichter.rb:54) ~[?:?]
	at RUBY.execute(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:133) ~[?:?]
	at openhab.conf.automation.ruby.$_dot_gem.$9_dot_3_dot_10_dot_0.gems.openhab_minus_scripting_minus_5_dot_5_dot_0.lib.openhab.dsl.thread_local.thread_local(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/dsl/thread_local.rb:44) ~[?:?]
	at RUBY.execute(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:133) ~[?:?]
	at RUBY.initialize(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:52) ~[?:?]
2023-08-24 23:05:59.528 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: arrow_left_hold
2023-08-24 23:05:59.648 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
org.jruby.exceptions.NameError: (NameError) undefined method `<=' for class `Kernel'
	at org.jruby.RubyModule.instance_method(org/jruby/RubyModule.java:3005) ~[?:?]
	at uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.delegate.method_missing(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/delegate.rb:85) ~[?:?]
	at RUBY.adjust(/openhab/conf/automation/jsr223/Lichter.rb:54) ~[?:?]
	at RUBY.execute(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:133) ~[?:?]
	at openhab.conf.automation.ruby.$_dot_gem.$9_dot_3_dot_10_dot_0.gems.openhab_minus_scripting_minus_5_dot_5_dot_0.lib.openhab.dsl.thread_local.thread_local(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/dsl/thread_local.rb:44) ~[?:?]
	at RUBY.execute(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:133) ~[?:?]
	at RUBY.initialize(/openhab/conf/automation/ruby/.gem/9.3.10.0/gems/openhab-scripting-5.5.0/lib/openhab/core/timer.rb:52) ~[?:?]
2023-08-24 23:06:00.714 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: arrow_left_release
2023-08-24 23:06:00.728 [ERROR] [on.jrubyscripting.rule.Lichter.rb:61] - undefined method `[]' for #<OpenHAB::DSL::TimerManager:0xa340193> (NoMethodError)
/openhab/conf/automation/jsr223/Lichter.rb:49:in `adjust'
/openhab/conf/automation/jsr223/Lichter.rb:75:in `block in <main>'
2023-08-24 23:06:09.368 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: brightness_move_down
2023-08-24 23:06:09.387 [ERROR] [on.jrubyscripting.rule.Lichter.rb:61] - undefined method `on' for nil:NilClass (NoMethodError)
/openhab/conf/automation/jsr223/Lichter.rb:51:in `adjust'
/openhab/conf/automation/jsr223/Lichter.rb:79:in `block in <main>'
2023-08-24 23:06:13.143 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: brightness_stop
2023-08-24 23:06:13.157 [ERROR] [on.jrubyscripting.rule.Lichter.rb:61] - undefined method `[]' for #<OpenHAB::DSL::TimerManager:0xa340193> (NoMethodError)
/openhab/conf/automation/jsr223/Lichter.rb:49:in `adjust'
/openhab/conf/automation/jsr223/Lichter.rb:80:in `block in <main>'
2023-08-24 23:06:17.036 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: brightness_move_up
2023-08-24 23:06:17.052 [ERROR] [on.jrubyscripting.rule.Lichter.rb:61] - undefined method `on' for nil:NilClass (NoMethodError)
/openhab/conf/automation/jsr223/Lichter.rb:51:in `adjust'
/openhab/conf/automation/jsr223/Lichter.rb:78:in `block in <main>'
2023-08-24 23:06:19.598 [INFO ] [on.jrubyscripting.rule.Lichter.rb:61] - Ikea Dimmer Handler - button pressed: brightness_stop
2023-08-24 23:06:19.647 [ERROR] [on.jrubyscripting.rule.Lichter.rb:61] - undefined method `[]' for #<OpenHAB::DSL::TimerManager:0xa340193> (NoMethodError)
/openhab/conf/automation/jsr223/Lichter.rb:49:in `adjust'
/openhab/conf/automation/jsr223/Lichter.rb:80:in `block in <main>'

Any idea what the issue might be here? I am using OH 3.4.5 with openhab-scripting=~>5.0.

Bonus question:
You can see that homekit requires an OnState property which must be a Switch item. Otherwise homekit refuses to work. This item always must be synchronized with the Dimmer item.
If the Dimmer was changed by the user above 0%, then the Switch item must be switched on and vice versa. So it is possible that both items can be changed by a user or by the system.
Is there a convenient way how all equipments of that type can be synchronized?
It is the same for the item

Group:Switch    Deckenlampe_Hobbyraum_Einschaltstatus
                (Deckenlampen_Hobbyraum)
                ["Control", "Light"]

Currently, if I switch it off, nothing happens. I need to sync it with the Dimmer item controlling the brightness.

The example above is old, written for helper library v4 (I think), so it has some compatibility issues, primarily that in v5:

  • A QuantityType cannot be directly compared against a string anymore, so DimensionedItem.state > "1 %" is no longer supported. Instead, it needs to be compared against another QuantityType, i.e. DimensionedItem.state > 1 | "%"
  • Managed timers have been refactored to be more thread-safe. So timers[timerid] is not valid now. Instead, to cancel a timer, it should be timers.cancel(timerid)

Another problem in your implementation is that it cannot get the dimmer’s state, because it’s a Group without a calculation function. As a result, their state is always NULL. This can be fixed in several different ways. The simplest way is to change the group definitions to:

Group:Dimmer:AVG    Deckenlampe_Hobbyraum_Helligkeit
                (Deckenlampen_Hobbyraum)
                ["Control", "Level"]

Group:Number:AVG    Deckenlampe_Hobbyraum_Farbtemperatur                
                (Deckenlampen_Hobbyraum)
                ["Control", "ColorTemperature"]

This way their state will reflect the avg value. Another way is to have the jruby code calculate the average value based on the members. My code suggestion below can handle it either way.

I also noticed these lines in your code. Were they deliberate or a mistake?

  dimmer.points(Semantics::Level).first&.ensure.on # Should this be Semantics::Light ? We're trying to turn on the light because some devices remain off when changing their dimmer level

...

    switch = light.points(Semantics::Level).first # Should this be Semantics::Light ?

So here’s my suggested code. I’ve also changed your button code → name lookup a bit.

BUTTON_ACTIONS = {
  1002 => :on,
  2002 => :off,
  1001 => :brightness_up,
  2001 => :brightness_down,
  1003 => :brightness_stop,
  2003 => :brightness_stop,
  3002 => :arrow_left_click,
  3001 => :arrow_left_hold,
  3003 => :arrow_left_release,
  4002 => :arrow_right_click,
  4001 => :arrow_right_hold,
  4003 => :arrow_right_release
}.freeze

COLORS = {
  cold: 250,
  middle: 352,
  warm: 454
}.freeze

def blink(dimmer)
  power = dimmer.points(Semantics::Level)
  power.off
ensure
  after(350.ms) { power.on }
end

@colors = Hash.new { |hash, key| hash[key] = COLORS.values }

def adjust(dimmer, delta)
  return timers.cancel(dimmer) if delta.zero?

  dimmer.points(Semantics::Light).first&.ensure&.on

  after(0.ms, id: dimmer) do |timer|
    current_level = if dimmer.is_a?(GroupItem) && dimmer.state.nil?
                      dimmer.members.map(&:state).map(&:to_f).sum / dimmer.members.size
                    else
                      dimmer.state&.to_f || 0
                    end

    next blink(dimmer) if (current_level >= 100 && delta.positive?) || (current_level <= 1 && delta.negative?)

    new_level = (current_level.to_i + delta).clamp(1, 100)
    dimmer << new_level
    timer.reschedule 100.ms
  end
end

rule "Ikea Dimmer Handler" do
  updated Fernbedienungen.members
  run do |event|
    light = event.item.equipment
    next unless light

    dimmer = light.points(Semantics::Level).first
    switch = light.points(Semantics::Light).first
    ct = light.points(Semantics::ColorTemperature).first

    action_name = BUTTON_ACTIONS[event.state.to_i] # Get the action name

    logger.info("Ikea Dimmer Handler - button pressed: #{action_name}")

    case action_name
    when :on then switch.ensure.on
    when :off then switch.ensure.off
    when :brightness_up then adjust(dimmer, 5)
    when :brightness_down then adjust(dimmer, -5)
    when :brightness_stop then adjust(dimmer, 0)  # Update to adjust(dimmer, 0) or adjust(ct, 0) based on your needs
    when :arrow_left_click then ct << @colors[dimmer].rotate!.first
    when :arrow_right_click then ct << @colors[dimmer].rotate!(-1).first
    when :arrow_left_hold then adjust(ct, -10)
    when :arrow_right_hold then adjust(ct, 10)
    when :arrow_right_release, :arrow_left_release then adjust(ct, 0)
    end
  end
end

I haven’t tested this code, so my apologies for any errors. Let me know how it goes.

EDIT: I just realised that the received_command trigger might not work with deconz binding, so I’ve changed it back to updated trigger

Have you tried linking the OnState item to the same channel as your Dimmer item?

e.g.

Switch          Deckenlampe_Hobbyraum_Schreibtisch_Einschaltstatus
                "Einschaltstatus Deckenlampe Hobbyraum Schreibtisch [MAP(de.map):%s]"
                (Deckenlampe_Hobbyraum_Schreibtisch_Homekit, Deckenlampe_Hobbyraum_Einschaltstatus)                
                {
                    channel="deconz:colortemperaturelight:raspi4:Deckenlampe_Hobbyraum_Schreibtisch:brightness",
                    homekit="Lighting.OnState"
                }
1 Like

As usual, your hints are very helpful!
The Styrbar remote works perfectly and it was super easy to add another Styrbar in a different room. I just had to create the items and put them in the correct groups and boom: no rule change required. It worked immediately. Perfect!

And for your second hint linking Lighting.OnState to the brightness channel: it works… You do not want to know how many hours I wasted into this syncing topic. Sometimes we do not see the wood for the trees. Yikes!

Now, I will continue my refactoring journey. Probably you will hear soon from me. :innocent:

BTW, I’ve changed my implementation since I posted the original sample.

Previously: up button only turns on, and down button only turns off.
To change brightness, you must press and hold up/down.

I found this a bit annoying, because there’s a bit of a lag between holding down / releasing and the resulting effect, so it’s not a very precise way of adjusting brightness.

Now it behaves a bit more complex
When the Light is off OFF → UP will turn it on to 100%. OFF → DOWN will turn it on at 1%. This is so when I entered a dark bedroom and just want a little light without waking people up, I can just hit the DOWN button to have a dim light.

Once the light is on, up/down changes brightness as usual.

and when you hold, UP will go 100%, and DOWN = off.

The disadvantage is it doesn’t really remember the last state, but in my case I rarely cared about the last state.

This is the relevant code below. One of these days I’ll update the original post with the full code.

Anyway, I realise that this is use-case dependent.

    case event.command
    when ButtonAction::ON
      next adjust(dimmer, 10) if power.on?

      dimmer << 100
      power.ensure.on
    when ButtonAction::OFF
      next adjust(dimmer, -10) if power.on?

      dimmer << 1
      power.ensure.on
    when ButtonAction::BRIGHTNESS_UP then dimmer << 100
    when ButtonAction::BRIGHTNESS_DOWN then power.ensure.off
...

Hi, I’m trying to achieve the same configuration (note: I work UI-based).
Up to now, I have the remote connected to zigbee2mqtt and can see the messages when a button is pressed via MQTT.
I have defined the thing for the remote with the channels action/linkquality/battery as above. I also have an item defined, linked to the action-channel. But so far, I can’t see any changes to the item in openHAB. I would have expected that the status changes from NULL to the respective string. AM I wrong? What am I missing?

show us your thing and item config

Figured out my mistake: I was using the zigbee2mqtt/remote/action topic but it works now with the zigbee2mqtt/remote (without “action”) and JSON transform. Is this supposed to be so? I thought there was a dedicated “action” topic in zigbee2mqtt

is your output type set to attribute ?

No, it is not…

Since I’m new to scripting in jruby:

  1. I’ve installed jRuby Scripting Add-On
  2. I placed the file in $conf$/automation/jruby/remote_handler.rb

Can I see (or edit) my rule somewhere in the UI?

Is that correct so far?

This should be ..../automation/ruby/...... not /jruby/

File-based rules can be viewed in the UI but not edited. You’ll have to edit file-based rules using a file editor outside the UI.

You can create rules in UI. In this case, ignore the path above, since you’ll create rules completely in the UI

There’s a third option, but this isn’t yet documented: create “file-based style rules” using UI, so for now, pretend this option doesn’t exist.

Ho do I view them in the UI? They don’t show up under “rules”… Do I have to “load” them somehow before they are active?

Perhaps because your rule file was in the wrong path, they didn’t get loaded.

If you put them in $CONF/automation/ruby/ and your rule has no syntax errors (check the log when you saved the file), the rule will show up in the UI Settings -> Rules

No, as soon as you’ve saved the file in the correct path, openhab will detect the new file / file changes, and will automatically load the script file.

OK, apparently “require ‘openhab’” was the problem, “no such file”, I removed the line and now it shows up!

I’ve updated the original post. require "openhab" was for version 4 of the helper library. It is no longer needed in v5.

OK, so I try to capture the key presses in the log with

action_name = BUTTON_ACTIONS[event.state.to_i] # Get the action name
logger.info("Ikea Dimmer Handler - button pressed: #{action_name}")

like above. In which log-file should the events appear?

It should go into userdata/logs/openhab.log or you can see it in the karaf console too using log:tail command

The log prefix is org.openhab.automation.jrubyscripting

If you can’t see the log, paste the output of log:get karaf command here.