Design Pattern: Switch & Dimmer

Please see Design Pattern: What is a Design Pattern and How Do I Use Them to understand the scope and purpose of this and other Design Patterns.

Problem Statement

When using a switch to control a dimmable light, one may want to achieve the same behavior than classical switches with an electrical relay behind the scenes:

  • a short press on the switch turns the light on or off
  • holding down the switch for a longer time will alternating increase or decrease the light

In order to avoid this complex behavior within a rule that controls the light, we want to be able to react on the different states of the switch individually:

  • Short press of the switch (push & release within a certain amount of time, e.g. 500ms)
  • Holding down the switch (recurrent iterations, e.g. after each 500ms while held down)

Concept

Use Unbound Item (aka Virtual Item) for the events and separate the concern of controlling the switch with individual rules.

Simple Example

In this example we will show how to handle a single switch.

Things

Typically, one has a Rocker Switch, e.g. the enocean FT55 (single or double). Let’s assume we have a single switch with on channel.

Items

// Real Rocker Switch
Switch MySwitch1 "Intelligent Switch 1" <switch> {channel="enocean:rockerSwitch:********:********:rockerswitchA"}

// Virtual items
Switch MySwitch1Up "Switch 1 Up" <switch>
Switch MySwitch1UpShortPress "Switch 1 Up: Short Press" <switch>
Switch MySwitch1UpLongPress "Switch 1 Up: Long Press" <switch>
Switch MySwitch1Down "Switch 1 Down" <switch>
Switch MySwitch1DownShortPress "Switch 1 Down: Short Press" <switch>
Switch MySwitch1DownLongPress "Switch 1 Down: Long Press" <switch>

Rules

import java.util.Map

val Map<String, Timer> Timers = newHashMap
val Map<String, DateTime> Timestamps = newHashMap


val SwitchPressed = [ Map<String, Timer> Timers, Map<String, DateTime> Timestamps, String id |
	Timestamps.put(id, now)
	sendCommand(id, "ON")

	// Start timer if switch is held down
	val offset = now.plusMillis(500)
	Timers.put(id, createTimer(offset, [|
		// Handle long press
		sendCommand(id + "LongPress", "ON")
		sendCommand(id + "LongPress", "OFF")
	
		// Reschedule timer for this switch
	// (max. 10 times, according to the times used in this example)
	// You may want to change the times or remove the if statement completely.
		if (now.isBefore(Timestamps.get(id)?.plusSeconds(5))) {
			val offset = now.plusMillis(500)
			Timers.get(id)?.reschedule(offset)
		}
	]))
]

val SwitchReleased = [ Map<String, Timer> Timers, Map<String, DateTime> Timestamps, String id |
	// Deactivate the timer (if running)
	Timers.get(id)?.cancel()
	Timers.remove(id)

	// Get timestamp of switch pressed down
	var pressed = Timestamps.get(id)
	Timestamps.remove(id)

	// Handle short press
	val offset = pressed?.plusMillis(500)
	if (now.isBefore(offset)) {
		sendCommand(id + "ShortPress", "ON")
		sendCommand(id + "ShortPress", "OFF")
	}

	sendCommand(id, "OFF")
]

rule "Switch1Events"
when
	Channel "enocean:rockerSwitch:********:********:rockerswitchA" triggered
then
	switch (receivedEvent.event) {
		case "DIR1_PRESSED"  : { SwitchPressed.apply(Timers, Timestamps, "MySwitch1Up") }
		case "DIR1_RELEASED"  : { SwitchReleased.apply(Timers, Timestamps, "MySwitch1Up") }
		case "DIR2_PRESSED"  : { SwitchPressed.apply(Timers, Timestamps, "MySwitch1Down") }
		case "DIR2_RELEASED"  : { SwitchReleased.apply(Timers, Timestamps, "MySwitch1Down") }
	}
end

Applications

On/Off & Dimmer Switch

This example of applications show, how we can use a single switch to control a color light with the following rules:

  • A short press on the switch will turn the light on or off
  • Holding down the switch will alternating increase or decrease the brightness of the light
  • When the light is switched off, the next dimmer action will be set to “increase”
  • When the light is switched on, the next dimmer action will be set to “decrease”

This is the most natural behavior according to classical dimmer switches with a relay.

Note: If you are using persistence, then you can replace the global command variable and simply compare the last two brightness values of the light.

// May be replaced by previousState if persistence is available!
var String MyColorLightCommand = "INCREASE"

rule "MyColorLight_OnOff"
when
	Item MySwitch1UpShortPress received command ON
then
	val color = MyColorLight.state as HSBType
	if (color.brightness == 0) {
		MyColorLightCommand = "INCREASE"
		MyColorLight.sendCommand(ON)
	} else {
		MyColorLightCommand = "DECREASE"
		MyColorLight.sendCommand(OFF)
	}
end

rule "MyColorLight_Dimmer"
when
	Item MySwitch1UpLongPress received command ON
then
	MyColorLight.sendCommand(MySwitch1Command)
end

rule "MyColorLight_CommandToggle"
when
	Item MySwitch1Up received command OFF
then
	if (MyColorLightCommand == "INCREASE") {
		MyColorLightCommand = "DECREASE"
	} else {
		MyColorLightCommand = "INCREASE"
	}
end

Theory of Operation

Every time the switch is used the special rules for handling of the switch will determine the state of the switch (whether or not it is pressed and release immediately or held down). Those rules with set the state of the virtual items accordingly. This clearly separates the concern of the switch from the actual control of the lights or whatever you want to control with the switch and allows to differentiate between short and long presses of the switch.

Advantages and Limitations

The major advantage of this approach is it centralizes the control of the switch and separate it from the actual command that will be send on activation. This allows:

  • avoidance of duplicated code
  • avoidance of typos and coding errors scattered through the code
  • ease of debugging
  • simpler Rules logic for Rules that differentiate between long and short presses of the switch

The major limitation of this approach is that it does introduces a little time delay for the actual execution of the control logic of the light.

Related Design Patterns

Design Pattern How It’s Used
Design Pattern: Unbound Item (aka Virtual Item) vTimeOfDay, Irrigation, and other Items are examples of a Virtual Item
Design Pattern: Separation of Behaviors 7 This DP is a specific implementation of Separation of Behaviors
5 Likes

Thanks for posting and for following my design pattern format. :smiley:

I’ve just a couple of comments.

The import of DateTime is not necessary. It’s imported for you.

This is probably not necessary. The Timer gets cancelled before the variable gets set to null so you should never have a case where this timer body is running and Switch1_Timer is null. But even if this is occurring because of something I don’t understand, you can write it more cleanly using

Switch1_Timer?.reschedule(now.plusMillis(globalDelayMillisForDimmer))

which means the same thing only it only takes one line of code.

Similarly, in your Switch1Released Rule you can use the ? on the cancel.

Switch1_Timer?.cancel
Switch1_Timer = null

Beyond that the code is very clear and easy to follow. Thanks again for posting!

One other limitation of this approach is related to the limitation of Thing Channel triggers. You can’t use something like Member of triggers so that this one Rule could handle ALL of your switches. So unfortunately you would have to repeat these two Rules for each of your switches. So if one has more than a couple such switches, it might be worth creating a “complex example” that puts the logic into a lambda that the Rules triggered by the channel triggers can call. This will also require more than one Timer so you may want to use a Map<String, Timer> to store them.

Hi, thanks for your ideas & comments. Well, I really like the idea of the design patterns and therefore I am happy I can contribute.

I am very new to openhab and therefore have to get around some aspects of the programming model for rules. With your hints I could rephrase the rules a bit, but it is not working, as I am experiencing errors around the Map (missing reference to Object) and functions “put & get” not being available.

Here is my current solution, that should toggle the :

val int globalDelayMillisForDimmer = 500
val Map<String, Timer> SwitchTimers = newHashMap
val Map<String, DateTime> SwitchTimestamps = newHashMap


val SwitchPressed = [ String id |
	logInfo("TMP", "SwitchPressed lambda called for " + id)
	SwitchTimestamps.put(id, now)

	// Start timer if switch is held down
	val offset = now.plusMillis(globalDelayMillisForDimmer)
	SwitchTimers.put(id, createTimer(offset, SwitchHeldDown.apply(id)))
]

val SwitchHeldDown = [ String id |
	logInfo("TMP", "SwitchHeldDown lambda called for " + id)

	// Handle long press
	sendCommand(id + "LongPress", ON)
	sendCommand(id + "LongPress", OFF)
	
	// Reschedule timer for this switch
	val offset = now.plusMillis(globalDelayMillisForDimmer)
	SwitchTimers.get(id)?.reschedule(offset)
]

val SwitchReleased = [ String id |
	logInfo("TMP", "SwitchRelease lambda called for " + id)
	
	// Deactivate the timer (if running)
	SwitchTimers.get(id)?.cancel()
	SwitchTimers.remove(id)

	// Get timestamp of switch pressed down
	var pressed = SwitchTimestamps.get(id)
	SwitchTimestamps.remove(id)

	// Handle short press
	val offset = pressed.plusMillis(globalDelayMillisForDimmer)
	if (offset.isBefore(now)) {
		sendCommand(id + "ShortPress", ON)
		sendCommand(id + "ShortPress", OFF)
	}
]

rule "temp1"
when
	Time cron "0 * * * * ?" or
	Time cron "20 * * * * ?" or
	Time cron "40 * * * * ?"
then
	SwitchPressed.apply("MySwitch1")
end

rule "temp2"
when
	Time cron "5 * * * * ?" or
	Time cron "25 * * * * ?" or
	Time cron "45 * * * * ?"
then
	SwitchReleased.apply("MySwitch1")
end

Maybe one has an idea to fix that Map issue, then I can continue on testing. If those lambdas are ready to go, i think the whole design pattern will be neat to use. :slight_smile:

My bad. You don’t have to import DateTime, but you do need to import java.util.Map.

I tried that already, but it still gives the following errors:

2019-02-07 19:43:25.015 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'temp2': cannot invoke method public abstract java.lang.Object java.util.Map.get(java.lang.Object) on null
2019-02-07 19:43:40.027 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'temp1': cannot invoke method public abstract java.lang.Object java.util.Map.put(java.lang.Object,java.lang.Object) on null

You have to pass the maps into the lambdas. Global lambdas do not have a context. They cannot see any other global vals or vars. You must pass them as arguments.

Ahh, ok, one needs to know that as well. :wink:

Then finally, I finished the sample, but I am still having one issue regarding the call of sendCommand(String, String):

import java.util.Map

val Map<String, Timer> Timers = newHashMap
val Map<String, DateTime> Timestamps = newHashMap


val SwitchPressed = [ Map<String, Timer> Timers, Map<String, DateTime> Timestamps, String id |
	logInfo("TMP", "SwitchPressed lambda called for " + id)
	Timestamps.put(id, now)

	// Start timer if switch is held down
	val offset = now.plusMillis(500)
	Timers.put(id, createTimer(offset, [|
		logInfo("TMP", "SwitchHeldDown lambda called for " + id)

		// Handle long press
		logInfo("TMP", "sendCommand(" + id + "LongPress, ON)")
		//sendCommand(id + "LongPress", ON)
		logInfo("TMP", "sendCommand(" + id + "LongPress, OFF)")
		//sendCommand(id + "LongPress", OFF)
	
		// Reschedule timer for this switch
		val offset = now.plusMillis(500)
		Timers.get(id)?.reschedule(offset)
	]))
]

val SwitchReleased = [ Map<String, Timer> Timers, Map<String, DateTime> Timestamps, String id |
	logInfo("TMP", "SwitchRelease lambda called for " + id)
	
	// Deactivate the timer (if running)
	Timers.get(id)?.cancel()
	Timers.remove(id)

	// Get timestamp of switch pressed down
	var pressed = Timestamps.get(id)
	Timestamps.remove(id)

	// Handle short press
	val offset = pressed?.plusMillis(500)
	if (now.isBefore(offset)) {
		logInfo("TMP", "sendCommand(" + id + "ShortPress, ON)")
		//sendCommand(id + "ShortPress", ON)
		logInfo("TMP", "sendCommand(" + id + "ShortPress, OFF)")
		////sendCommand(id + "ShortPress", OFF)
	}
]

rule "temp1"
when
	Time cron "0 * * * * ?" or
	Time cron "20 * * * * ?" or
	Time cron "40 * * * * ?"
then
	SwitchPressed.apply(Timers, Timestamps, "Switch1")
end

rule "temp2"
when
	Time cron "3 * * * * ?" or
	Time cron "23 * * * * ?" or
	Time cron "43 * * * * ?"
then
	SwitchReleased.apply(Timers, Timestamps, "Switch1")
end

rule "temp3"
when
	Time cron "10 * * * * ?" or
	Time cron "30 * * * * ?" or
	Time cron "50 * * * * ?"
then
	SwitchPressed.apply(Timers, Timestamps, "Switch1")
	SwitchReleased.apply(Timers, Timestamps, "Switch1")
end

When commenting those lines in, I get errors like:

java.lang.IllegalStateException: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.sendCommand(java.lang.String,java.lang.String) on instance: null

For some reasons, it seems that the call for sendCommand within a lambda will fail. Could you give me another hint here? With that in place, you can images from the rules temp1-3 how this could be easily be used for all the Switch Items. :slight_smile:

Solved it, by changing to

sendCommand(id + "ShortPress", "ON")
// etc.

after reading your post at: sendCommand() Documentation :slight_smile:

Obvious, but I missed it, that the call clearly needs two Strings. :wink:

Updated the initial post and rules, plus added an application example for a dimmer switch.

4 Likes

Updated the example and introduced a stop for the rescheduler. So after 10 times, the long press action will stop to reschedule itself. This is due to the fact, that sometimes the RELEASE was not received by openhab and led to endless rescheduling. My bad. :slight_smile:

I’m going through all of the DP postings and posting at a minimum Python versions of the code. Here is a Python version of the above. I never used this DP so this code may contain some errors. Please let me know if you encounter any.

from org.joda.time import DateTime
from core.actions import ScriptExecution
from core.rules import rule
from core.triggers import when

timers = {}
timestamps = {}

def pressed_timer(id):
    events.sendCommand("{}LongPress".format(id), "ON")
    events.sendCommand("{}LongPress".format(id), "OFF")

    if DateTime.now().isBefore(timestamps[i].plusSeconds(5)):
        timers[id].reschedule(DateTime.now().plusMillis(500))

@rule("Switch1Events")
@when("Channel enocean:rockerSwitch:********:********:rockerswitchA triggered")
def switch1(event):

    direction = "Up" if event.event.startswith("DIR1") else "Down"

    # switch pressed
    if event.event.endswith("PRESSED"):
        id = "MySwitch1{}".format(direction)

        timestamps[id] = DateTime.now()
        events.sendCommand(id, "ON")

        timers[id] = ScriptExecution.createTimer(
            DateTime.now().plusMillis(500), 
            lambda: pressed_timer(id))

    # switch released
    else:
        id = "MySwitch1{}".format(direction)

        if id in timers:
            timers[id].cancel()
            del timers[id]

        pressed = timestamps[id]
        del timestamps[id]

        if DateTime.now().isBefore(pressed.plusMillis(500):
            events.sendCommand("{}ShortPress".format(id), "ON")
            events.sendCommand("{}ShortPress".format(id), "OFF")

        events.sendCommand(id, "OFF")

from core.rules import rule
from core.triggers import when

my_color_light_command = "INCREASE"

@rule("MyColorLight_OnOff")
@when("Item MySwitch1UpShortPress received command ON")
def mycolorlight_onoff(event):
    color = items["MyColorLight"]
    my_color_light_command = "DECREASE"
    cmd = "OFF"
    if color.brightness == PercentType(0):
        my_color_light_command = "INCREASE"
        cmd = ON
     events.sendCommand("MyColorLight", cmd)

@rule("MyColorLight_Dimmer")
@when("Item MySwitch1UplongPress received command ON")
def mycolorlight_dimmer(event):
    events.sendCommand("MySwitch1Light", my_color_light_command)

@rule("MyColorLight_CommandToggle"
@when("Item MySwitch1Up received command OFF")
def mycolorlight_toggle(event):
    my_color_light_command = "INCREASE" if my_color_light_command == "DECREASE" else "DECREASE"

I’ve tried the above scripts, but the could not be parsed correctly. Therefore, I remain on my versions. I am currently about to extend the behavior such that one can use it for the following scenarios:

  1. Use as regular switch (short press and long press: actionset1)
  2. Use as double switch (short press: actionset1, long press: actionset2)
  3. Use as Dimmer (short press: actionset1, long press: repeating actionset2)
  4. Use as Scenario Player (short press: enum iteration, long press: actionset2)
  5. Use as Scenario Player (short press: enum iteration, long press: repeating actionset2)

Examples for the use cases:

  1. Control a light (on/off), a power plug (on/off), or e.g. a setting for rules in openhab (activate/deactivate)
  2. Same as 1., but use the long press for another control, i.e. you have technically two switches combined in one. I like to use this for actions that I do not use that often, so that they do not block a switch permanently.
  3. Control a dimmable light (on/off, dim), might also be used for roller shutters to close the shutters as long as you push the button, but I have not tried that.
  4. Control scenarios, e.g. every click iterates over a list of scenarios and activates them accordingly. I use this for my living room having 3 presets defined (ambient light, watching movies, night light). In order to activate a scenario I have to click the switch once, twice or three times. I use the long press to switch the lights in that group alternating on or off.
  5. Same as 4 but instead of switching the lights on/off I use that as a dimmer for the light group.

Any further scenarios? :slight_smile:

Best bechte.

2 Likes

First Draft (still has an error):

import java.util.Map
import org.eclipse.smarthome.model.script.ScriptServiceUtil

val Map<String, Timer> Counters = newHashMap
val Map<String, Timer> Timers = newHashMap
val Map<String, DateTime> Timestamps = newHashMap

// ========================================================================= //

val incrementCounter = [ String id, int maxCount, boolean resetCounter, Map<String, Timer> Counters |
  val item = ScriptServiceUtil.getItemRegistry.getItem(id + "Count")
  val count = item.state as Number
  if (count >= maxCount) {
    postUpdate(id + "Count", 1)
  } else {
    postUpdate(id + "Count", count + 1)
  }

  if (resetCounter) {
    // Deactivate the timer (if running)
    Counters.get(id)?.cancel()
    Counters.remove(id)

    val offset = now.plusSeconds(10)
    Counters.put(id, createTimer(offset, [|
      postUpdate(id + "Count", 0)
    ]))
  }
]

// ========================================================================= //

val simpleSwitch = [ val receivedEvent, String id, int maxCount, boolean resetCounter, Map<String, Timer> Counters, Map<String, Timer> Timers, Map<String, DateTime> Timestamps |
  if (receivedEvent.event.endsWith("PRESSED")) {
    incrementCounter(id, maxCount, resetCounter, Counters)
    sendCommand(id, "ON")
  }
  if (receivedEvent.event.endsWith("RELEASED")) {
    sendCommand(id, "OFF")
  }
]

val doubleSwitch = [ val receivedEvent, String id, int maxCount, boolean resetCounter, Map<String, Timer> Counters, Map<String, Timer> Timers, Map<String, DateTime> Timestamps |
  if (receivedEvent.event.endsWith("PRESSED")) {
    Timestamps.put(id, now)
    sendCommand(id, "ON")
  }
  if (receivedEvent.event.endsWith("RELEASED")) {
    // Get timestamp of switch pressed down
    var pressed = Timestamps.get(id)
    Timestamps.remove(id)

    // Handle short or long press
    val offset = pressed?.plusMillis(700)
    if (now.isBefore(offset)) {
      incrementCounter(id, maxCount, resetCounter, Counters)
      sendCommand(id + "ShortPress", "ON")
      sendCommand(id + "ShortPress", "OFF")
    } else {
      sendCommand(id + "LongPress", "ON")
      sendCommand(id + "LongPress", "OFF")
    }
    sendCommand(id, "OFF")
  }
]

val dimmerSwitch = [ val receivedEvent, String id, int maxCount, boolean resetCounter, Map<String, Timer> Counters, Map<String, Timer> Timers, Map<String, DateTime> Timestamps |
  if (receivedEvent.event.endsWith("PRESSED")) {
    Timestamps.put(id, now)
    sendCommand(id, "ON")

    // Start timer if switch is held down
    val offset = now.plusMillis(700)
    Timers.put(id, createTimer(offset, [|
      // Handle long press
      sendCommand(id + "LongPress", "ON")
      sendCommand(id + "LongPress", "OFF")

      // Reschedule timer for this switch, but max. 10 times
      if (now.isBefore(Timestamps.get(id)?.plusSeconds(7))) {
        val offset = now.plusMillis(700)
        Timers.get(id)?.reschedule(offset)
      }
    ]))
  }
  if (receivedEvent.event.endsWith("RELEASED")) {
    // Deactivate the timer (if running)
    Timers.get(id)?.cancel()
    Timers.remove(id)
  
    // Get timestamp of switch pressed down
    var pressed = Timestamps.get(id)
    Timestamps.remove(id)

    // Handle short or long press
    val offset = pressed?.plusMillis(700)
    if (now.isBefore(offset)) {
      incrementCounter(id, maxCount, resetCounter, Counters)
      sendCommand(id + "ShortPress", "ON")
      sendCommand(id + "ShortPress", "OFF")
    }
    
    sendCommand(id, "OFF")
  }
]

// ========================================================================= //

rule "Configuration Button 1"
when
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchA" triggered DIR1_PRESSED or
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchA" triggered DIR1_RELEASED
then
  simpleSwitch(receivedEvent, "Button_1", 1, false, Counters, Timers, Timestamps)
end

rule "Configuration Button 2"
when
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchB" triggered DIR1_PRESSED or
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchB" triggered DIR1_RELEASED
then
  doubleSwitch(receivedEvent, "Button_2", 1, false, Counters, Timers, Timestamps)
end

rule "Configuration Button 3"
when
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchA" triggered DIR2_PRESSED or
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchA" triggered DIR2_RELEASED
then
  dimmerSwitch(receivedEvent, "Button_3", 1, false, Counters, Timers, Timestamps)
end

rule "Configuration of Button 4"
when
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchB" triggered DIR2_PRESSED or
  Channel "enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchB" triggered DIR2_RELEASED
then
  dimmerSwitch(receivedEvent, "Button_4", 4, true, Counters, Timers, Timestamps)
end

With the following items.xml:

Switch Button_1A "Taster links" <switch> (Switches) {channel="enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchA"}
Switch Button_1B "Taster rechts" <switch> (Switches) {channel="enocean:rockerSwitch:FT1I6L57:0035FA61:rockerswitchB"}

Switch Button_1 "Taster 1" <switch> (Switches)
Switch Button_2 "Taster 2" <switch> (Switches)
Switch Button_3 "Taster 3" <switch> (Switches)
Switch Button_4 "Taster 4" <switch> (Switches)

Switch Button_1ShortPress "Taster 1 (kurz)" <switch> (Switches)
Switch Button_2ShortPress "Taster 2 (kurz)" <switch> (Switches)
Switch Button_3ShortPress "Taster 3 (kurz)" <switch> (Switches)
Switch Button_4ShortPress "Taster 4 (kurz)" <switch> (Switches)

Switch Button_1LongPress "Taster 1 (lang)" <switch> (Switches)
Switch Button_2LongPress "Taster 2 (lang)" <switch> (Switches)
Switch Button_3LongPress "Taster 3 (lang)" <switch> (Switches)
Switch Button_4LongPress "Taster 4 (lang)" <switch> (Switches)

Number Button_1Count "Taster 1 (Anzahl)" <none> (Switches)
Number Button_2Count "Taster 2 (Anzahl)" <none> (Switches)
Number Button_3Count "Taster 3 (Anzahl)" <none> (Switches)
Number Button_4Count "Taster 4 (Anzahl)" <none> (Switches)

Error is:

[WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'Schalter.rules' has errors, therefore ignoring it: [33,39]: mismatched input ',' expecting ']'

Have not yet look into it… but it looks like some syntax misunderstanding on my side :wink:

FYI, people normally post tutorials with working code :roll_eyes:. Please update the OP to inform people that your example is not functional until. I haven’t tested Rich’s Python example, but it looks fine to me. If you tried them and saw validation issues, then you probably added them to a .rules file and never setup the new rule engine with scripted automation, Jython, and the helper libraries.

For your example, use VS Code with the OH extension… it will help to debug the errors. There are several!

And the errors were? When I typed the above in I did test that it at least parses. I wasn’t able to do much functional testing though but I did state as such. I’d like to correct it and/or provide further explanation if you encountered problems.

The mentioned solution above works. I wanted to extend it and just keep everybody informed. If that is not in the sense of this community I can stop posting until its ready to be used. Sorry.

Sorry. I didnt know about the new rule engine. I will setup a second raspPI and test it on version 2.5. i wanted to do this update anyway. But it will take some time. Probably mid February

My apologies… I read too fast and jumped to conclusions. I had thought the latest code you posted, which is full of issues, was an evolution of the code in the OP and that it also had those errors.

BTW, here is a related tutorial for a Jython version (there is also a link in it to a DSL version)…

1 Like

Hi,

  • were you able to finish the extended code that can handle ALL that next to normal short/long/dim ?
  • How fast is the reaction of it? (Lagging?)
  • Is there any “GUI” version (like PaperUI) where all these thing could be set instead of manual coding?

Thanks :slight_smile: