(OH 1.x and OH 2.x Rules DSL only] Why have my Rules stopped running? Why Thread::sleep is a bad idea

Udo, great, thank you

It runs!!
I will post the final solution for my PWM timer as soon as I close the debug

only another question, what is the meaning of the question mark in mytimer?.cancel
I used it and I’m surprised it runs… I don’t know very well the syntax of Xtend

myTimer?.cancel

is the same as

if (myTimer !== null) myTimer.cancel

You recommend against lambdas yet your whole post is about how people should put stuff in lambdas :stuck_out_tongue:

I’ve been fine until the latest update to 2.4 and now having regular issues with rules stopping to work. I have an external device that I access through a node script, the device that crashes if it’s accessed too often. So I have both a long running script and locks. I think the script is run thought a lambda function, which probably helped. But how to I set up my rules so say I don’t hammer this device or hammer say the spotify api. What design pattern should I use to replace my locks? With say spotify, there are so many triggers, cron updates, manual buttons, motions sensors, voice, etc. I wanted to eliminate hammering the spotify api, also there are times when 10 rules get qued up and run together which I was trying to limit through locks.

I recommend against using global lambdas. They are not thread safe and do not handle errors well. Lambdas used in timers or forEach or the like are OK because the thread safe problem goes away as a new lambda gets created each time rather than reusing the same lambda object across multiple threads.

Design Pattern: Gate Keeper Complex Examples using Queues.

Using Queues and a single Rule to work off that queue will allow you to queue up a backlog of commands if they occur too fast without consuming a Rules thread for each one waiting for it’s turn to run.

Do you have any more information about global lambdas, I use them allot like functions. e.g. When I want a nice smooth dim of the lights, each light has a rule, which then triggers the lambda. I had actually been trying to introduce them more so I didn’t have duplicate code in each rule.

Cool thanks, that setup was kind of what I was thinking, add things to a que and then just have something run those commands in a que. But this is kind of like a few years ago when I was told to use locks and ended up in this mess. I’m going to give jython another go. The RULE system just isn’t designed for anything other than if x then on. I’m using rules like function, and doing what should really be a in a binding. It’ll take up way less time, run a million times more reliable in the long run using any other language than the rules system.

Why have one Rule per light? Why not have one Rule for ALL lights? That should be your goal. See Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL for several techniques to achieve generic Rules. If you have generic Rules you eliminate the need for global lambdas entirely and end up with more scalable and easier to maintain Rules. They tend to be shorter and easier to read as well.

You should absolutely not have duplicate code in any Rules. But there are “safer” ways to achieve that than using global lambdas. See the Lambdas section of that link for some of the big problems with using global lambdas.

If you are a developer already you almost certainly will be happier there.

Not true. But the Rules DSL is very opinionated about how it wants you to write your Rules and when you deviate from the “preferred” approach it gets really hard and doesn’t work out so well for you. But if you work to make generic Rules instead of generic functions, there is almost nothing you cannot accomplish and accomplish well using the Rules DSL. If you have lots of global lambdas, you are probably fighting against the language. If you have lots of locks and hashmaps and the like you are probably fighting against the language. If you have lots of Items and lots of Group and lots of Rules with Member of triggers, you are probably coding in a way the Rules DSL likes.

I find that people who already have programming experience can not or will not bend their coding style to the language and end up being much happier with one of the JSR223 languages. You may be one such developer.

I use the if it works, don’t touch it rule. And to be honest for years it’s just worked until 2.4 and I have no clue what the actual problem is, it could be the rules, install, etc. No idea how to find what’s causing most of my rules just stop working.

I have been writing more recent rules in a way that there is just one rules that triggers on the group. But it has some downsides like groups taking much longer, lots of random excess work to get the item that called the rule, etc. It feels like really smart to do use that kind of design pattern but can’t touch separate rules in terms of performance/reliability, etc. But I use it whenever I’m rewriting rules just because different copies of code all over the place is a pain. But it’s kinds of depressing rewriting code knowing it wont’ perform was well.

I think the point is for me is that it’s really hard to know what the design rules I should follow, you have design rules for everything. One day your rewrite everything to follow some kind of pattern, only to find out a year later it’s completely wrong. Even the official openhab docs aren’t much use since they are soo out of date.

Anyway enough of my bitching, with jythjon I can just write rules that make logical sense and don’t have to consider one of a million things or design rules I should be following. I’m pretty sure even for non-devs jython would be easier.

This is news to me.

what’s random access about triggeringItem? This implicit variable is set to the item that caused the rule to trigger for any item or Member of triggered rule.

It would be really great to present some metrics explaining what you are seeing as in my experience there is no difference in in performance or latency between an Item triggered rule and a Member of triggered rule. And for a Group triggered rule it depends on the type of trigger.

if you can point to any areas of the docs that are out of date and I will personally submit a pr to fix it. one can say a lot of bad things about the docs but out of date is not one of them. they may be incomplete in areas, they may be hard to understand. but they are kept well up to date.

and the only thing that has really changed in the last year in Rules is the introduction of Member of triggers and triggering Item, both of which are in the docs and have been since 2.3 release.

I welcome you to start contributing to helping new users work with Jython. Since I have helped users with both I can say without a doubt that Rules DSL is FAR easier for non programmers to learn and become productive in compared to Jython. Scott has made great improvements to the Jython libraries which eases this some but Rules DSL is still easier.

@rlkoshak Sorry, I was just venting. My rules are just a bit of a mess, slow, buggy and were crashing altogether and was just frustrated. I’m sorry I didn’t mean to take it out on you. You’re right some of the recent changes like triggeringItem are useful but wasn’t available when I wrote most of my rules.

No problem. I understand when users get frustrated. But for self preservation, when I see users making statements that I believe are false I have to respond to correct the false parts or else future users will come along, read the false or misleading statement, and believe. I spend an inordinate amount of time correcting false impressions of this sort.

So my target audience was less you and more to the future readers of this thread.

I understand your frustration and believe me when I say we have all been where you are. When we first start out and are learning something we end up making a lot of design and approach mistakes because we don’t know any better. This is often called “technical debt”. My first set of Rules were riddled with technical debt. My sensorReporter script on github is currently full of technical debt almost to the point where I’m considering scrapping it starting over.

As you learn a language and a system your Rules start to become better, clearer, shorter, and more elegant. But what about all the old Rules that you wrote when you didn’t know any better? Well, those Rules are technical debt. Technical debt builds up much like any debt that you accrue without paying off, until it becomes unmanageable. At that point you have to either pay it back or declare bankruptcy (i.e. start over from scratch). It sounds like you’ve a lot of technical debt to pay off.

To provide a concrete example, here is the evolution of my presence detection Rules:

First version, didn’t work very well. It was brittle, often failed, and almost 100 lines of code.

import org.openhab.core.library.types.*
import org.openhab.core.library.items.*
import org.openahb.core.persistence.*
import org.openhab.model.script.actions.*
import org.joda.time.*
import org.eclipse.xtext.xbase.lib.*

//-------------------------
// Global Variables
//-------------------------
val String getLocCmd = "{\"_type\":\"cmd\", \"action\":\"reportLocation\"}" 
val int flappingMins = 5

//-----------------------------------------------------------------------------
// Return true if either passed in switch is ON
//-----------------------------------------------------------------------------
val Functions$Function2 isHome = [ SwitchItem net, SwitchItem gps |
	return (net.state == ON || gps.state == ON)
]

//-----------------------------------------------------------------------------
// Ping the phones at startup
//-----------------------------------------------------------------------------
rule "Presence System Startup"
when
	System started
then
	sendCommand(S_V_RichLoc, OFF)
	sendCommand(S_V_JennLoc, OFF)
end
	
//-----------------------------------------------------------------------------
// Check every five minutes to see if I or Jenn are home based on the presence 
// of our phones. If no phones are detected, wait five minutes and check again
// and only then change Presence to OFF
//-----------------------------------------------------------------------------
rule "Periodically check presence"
when
	Time cron "0 */5 * * * ?"
then
	var boolean jennHome = isHome.apply(S_V_JennPhone, S_V_JennLoc) as Boolean
	var boolean richHome = isHome.apply(S_V_RichPhone, S_V_RichLoc) as Boolean
	
	// TODO There should be a way to simplify the rules using these switches
	sendCommand(JennHome, if(jennHome) ON else OFF)
	sendCommand(RichHome, if(richHome) ON else OFF)
	
	// Update Presence state
	if (jennHome || richHome) {
		if(Presence.state == OFF) {
			logInfo("PresenceCheck", "Someone arrived home")
			sendCommand(Presence, ON)
		}
	}
	else {
		// Catch both the case of Presence == ON and == Undefined
		if(Presence.state != OFF) {
			if (S_V_JennPhone.changedSince(now.minusMinutes(flappingMins)) && 
				S_V_JennLoc.changedSince(now.minusMinutes(flappingMins)) &&
				S_V_RichPhone.changedSince(now.minusMinutes(flappingMins)) &&
				S_V_RichLoc.changedSince(now.minusMinutes(flappingMins))) {

				logInfo("PresenceCheck", "Nobody is at home")
				sendCommand(Presence, OFF)
					
				// Poll the GPS to see if we can force an update
				UpdateJennLoc.postUpdate(getLocCmd)
				UpdateRichLoc.postUpdate(getLocCmd)
			}
		}		
	}
	
end

//-----------------------------------------------------------------------------
// Called when one of my or Jenn's cell phones are detected near home
//-----------------------------------------------------------------------------
rule "Near home"
when
	Item Mobiles changed or
	Item Location changed
then
	if(isHome.apply(S_V_RichPhone, S_V_RichLoc) as Boolean) {
		logInfo("PresenceCheck", "Rich is home")		
	}
	if(isHome.apply(S_V_JennPhone, S_V_JennLoc) as Boolean) {
		logInfo("PresenceCheck", "Jenn is home")
	}
	
	if (Presence.state != ON) {
		if(Mobiles.members.filter(s | s.state == ON).size > 0 ||
		   Location.members.filter(s | s.state == ON).size > 0) {
			logInfo("PresenceCheck", "Somebody arrived home")
			sendCommand(Presence, ON)
		}
	}
end

I won’t post the full history of refactoring I’ve done on these Rules. But based on the the commit history, I’ve refactored these Rules at least six times over the past three years. The current version is below. It is less than 50% the length of the original, works WAY better, never failing or causing something else to fail, it can be expanded simply by adding new Items and adding those Items to the gPresent Group. And it does pretty much exactly the same thing as the original.

val logName = "presence"

rule "Reset vPresent to OFF on startup"
when
    System started
then
    vPresent.sendCommand(OFF)
    gPresent.sendCommand(OFF)
end

rule "A presence sensor updated"
when
        Item gPresent changed
then
    logInfo(logName, "gPresent changed to " + gPresent.state)

    if(tPresent.state == ON && gPresent.state == vPresent.state) {
        logInfo(logName, "Timer is running but group and proxy are the same, cancelling timer")
        tPresent.postUpdate(OFF)
    }
    else if(gPresent.state == vPresent.state) return;

    if(gPresent.state == OFF) {
        logInfo(logName, "Everyone is away, setting anti-flapping timer")
        tPresent.sendCommand(ON)
    }
    else if(gPresent.state == ON) {
        logInfo(logName, "Someone came home, setting presence to ON")
        vPresent.sendCommand(ON)
    }

end


rule "Present timer expired, no one is home"
when
    Item tPresent received command OFF
then
    logInfo(logName, "Everyone is still away, setting presence to OFF")
    vPresent.sendCommand(OFF)
end

And even now as I look at it, there are somethings I can change to remove a few lines of code and clean it up a little. But I’m willing to carry that technical debt for now.

So my recommendation is to pick something, preferably something relatively short. Then look at Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL and see how you can apply these techniques to start to refactor and improve your Rules.

3 Likes

My problem is after a few years I have no clue on how to write a rule. I’ve seen so many design patterns and seen people recommend so many different approaches I don’t know what is I’m suppose to do.

Here is just a small part of one file that relates to just turning lights on and off. You can see where I tried to use a timer instead of sleep but it just broke it so I reverted back. Just be warned you’ll probably don’t want to see the code, it’ll make you cry.

val Functions$Function4<GenericItem, HSBType, PercentType,Functions.Function2<void>, HSBType> colorSmooth = [
	inColor,
	passColor,
	inBrightness,
	Functions.Function2<void> sendColor
	 |
	if(db) logInfo ("Lambda Smooth", "Lambda in color [{}] and brightness [{}]", inColor, inBrightness)
	
	

	var PercentType outBrightness = passColor.brightness
	
	var HSBType transitionColor=new HSBType(passColor.hue, passColor.saturation, inBrightness) 
	var HSBType oldColor=inColor.state as HSBType
	if((outBrightness as DecimalType) == 0){

		
		transitionColor=new HSBType(oldColor.hue, oldColor.saturation, inBrightness)
	}
	var HSBType lambdaColor=transitionColor
	var int step=10
	var DecimalType stepSize = new DecimalType(step)
	var PercentType brightJump= new PercentType(step)
	var PercentType dimmingJump= new PercentType (0)
	var PercentType lambdaBrightness = inBrightness
	var PercentType startBrightness = inBrightness
	var int speed = 15
	
	var DecimalType diffBrightness =  new DecimalType(outBrightness - inBrightness)
	var PercentType satStep = new PercentType(0)

	var DecimalType steps = new DecimalType(diffBrightness / stepSize as DecimalType)
	var DecimalType hueStep =  new DecimalType(0)
	if(steps==0){
		steps=new DecimalType(10)
		step=0;
	}
	
	if(steps > 0){
		hueStep =  new DecimalType((passColor.hue - oldColor.hue)/steps)
		satStep =  new DecimalType((passColor.saturation - oldColor.saturation)/steps)
	}
	else{

	}
	
	if ( (inBrightness as DecimalType) < (outBrightness as DecimalType) )
	{
		//logInfo( "Lambda Smooth", "Brighten" )
			brightJump=new PercentType(step)
			dimmingJump=new PercentType(0)
			lambdaBrightness=new PercentType(inBrightness + 1)
	}
	if ( (inBrightness as DecimalType) > (outBrightness as DecimalType) )
	{
			//logInfo( "Lambda Smooth", "Dim values out [{}], pass [{}]", inBrightness, outBrightness )	
			startBrightness=outBrightness
			outBrightness=inBrightness
			brightJump=new PercentType(0)
			dimmingJump=new PercentType(step)
			//logInfo( "Lambda Smooth", "Before start [{}], out [{}] and in [{}]", startBrightness, outBrightness, inBrightness )	
			lambdaBrightness=new PercentType(outBrightness - 1)
	}
	
	if(db) logInfo( "Lambda Smooth", "inBrightness is [{}] to out [{}] and current lambda is [{}]", startBrightness, outBrightness, lambdaBrightness )	
	
	//sendCommand(inColor, lambdaColor)
	if(db) logInfo( "Lambda Smooth", "oldColor {} {}", oldColor, oldColor.hue)
	//var DecimalType dimValue=((dimEnd as DecimalType) - (dimStart as DecimalType)).intValue
	//while(inBrightness < lambdaBrightness && lambdaBrightness < outBrightness)
	
	var DecimalType newHue=oldColor.hue as DecimalType
	var PercentType newSat=oldColor.saturation as PercentType
	if((oldColor.brightness as DecimalType )==0){
		newHue=passColor.hue as DecimalType
		newSat=passColor.saturation as PercentType
		hueStep=new DecimalType(0)
		satStep=new PercentType(0)
	}

	if(db) logInfo( "Lambda Smooth", "pre steps {}", steps )
/*
	timer = createTimer(now, [ |
		// some code
		steps = steps=new DecimalType(steps -1)
		if(db) logInfo( "Lambda Smooth", "Brighten" )
		Thread::sleep(speed)
		lambdaColor=new HSBType(newHue, newSat, lambdaBrightness) 
		if(db) logInfo("lambda smooth", "in trans {}, lambda {}", transitionColor, lambdaColor)
		if(lambdaColor.brightness as DecimalType ==0){
			var HSBType preOff=new HSBType(setColor.hue,setColor.saturation,new PercentType(1)) 
			sendColor.apply(item, preOff.toString)
			Thread::sleep(10)
		}
		sendColor.apply(inColor, lambdaColor)
		//inBrightness=inBrightness + brightJump
		//outBrightness=outBrightness - dimJump
		if((lambdaBrightness+brightJump as DecimalType) > 100)
		{
			brightJump= new PercentType(100 - lambdaBrightness)
		}
		if((lambdaBrightness - dimmingJump as DecimalType) < 0)
		{
			//logInfo( "Lambda Smooth", "lesse than 0 [{}] to out [{}] and start is [{}]", lambdaBrightness, dimmingJump, startBrightness )	
			
			dimmingJump= lambdaBrightness
		}
		lambdaBrightness=new PercentType(lambdaBrightness + brightJump - dimmingJump)
		newHue=new DecimalType(newHue + hueStep)
		newSat=new PercentType(newSat + satStep)
		if((startBrightness as DecimalType) < (lambdaBrightness as DecimalType)  && (lambdaBrightness as DecimalType) < (outBrightness as DecimalType)) timer.reschedule(now.plusMillis(speed))
		else timer = null
	])
*/
	while((startBrightness as DecimalType) < (lambdaBrightness as DecimalType)  && (lambdaBrightness as DecimalType) < (outBrightness as DecimalType))
	{
		steps = steps=new DecimalType(steps -1)
		if(db) logInfo( "Lambda Smooth", "Brighten" )
		Thread::sleep(speed)
		lambdaColor=new HSBType(newHue, newSat, lambdaBrightness) 
		if(db) logInfo("lambda smooth", "in trans {}, lambda {}", transitionColor, lambdaColor)
		if(lambdaColor.brightness as DecimalType ==0){
			var HSBType preOff=new HSBType(setColor.hue,setColor.saturation,new PercentType(1)) 
			sendColor.apply(item, preOff.toString)
			Thread::sleep(10)
		}
		sendColor.apply(inColor, lambdaColor)
		//inBrightness=inBrightness + brightJump
		//outBrightness=outBrightness - dimJump
		if((lambdaBrightness+brightJump as DecimalType) > 100)
		{
			brightJump= new PercentType(100 - lambdaBrightness)
		}
		if((lambdaBrightness - dimmingJump as DecimalType) < 0)
		{
			//logInfo( "Lambda Smooth", "lesse than 0 [{}] to out [{}] and start is [{}]", lambdaBrightness, dimmingJump, startBrightness )	
			
			dimmingJump= lambdaBrightness
		}
		lambdaBrightness=new PercentType(lambdaBrightness + brightJump - dimmingJump)
		newHue=new DecimalType(newHue + hueStep)
		newSat=new PercentType(newSat + satStep)
		Thread::sleep(speed)
		
		//logInfo( "Lambda Smooth", "LOOP LOOP inBrightness is [{}] to out [{}] and current lambda is [{}]", startBrightness, outBrightness, lambdaBrightness )	
	
	}
	if(db) logInfo("Lambda Smooth", "debgug after loop")


	//sendColor.apply(inColor, passColor)
	return lambdaColor
]


rule "Generic light"
when 
	Item lightDetails received command
then
	logInfo("Generic Light", "start {}", receivedCommand)
	var stringCommand= receivedCommand.toString

	var itemName=stringCommand.split("-").get(0)
	var itemNewColor=stringCommand.split("-").get(1)
	var itemOldColor=stringCommand.split("-").get(2)

	if(db) logInfo("Generic Light", "real thing is {} {} {}", itemName, itemNewColor, itemOldColor)
	var updatedLight=gLightColor.members.filter[s|s.lastUpdate() != null].sortBy[lastUpdate()].last

	gLightColor.members.forEach[ light|
	if(light.name.contains(itemName)){
		updatedLight=light
		//logInfo("Generic light", "updating light {}", light)	
	}
	]
	if(db) logInfo("Generic Light", "updated item is {}", updatedLight)
	var prevLight=updatedLight
	var altLight=updatedLight
	var hue=false

	updatedLight.getGroupNames.forEach[room |
			if(room.contains("gHue")){
				if(db) logInfo("GEneric light", "hue")
				hue=true
			}
            if(!room.contains("gLightColor")){
                gColor.members.forEach [ light | 
                    light.getGroupNames.forEach[ group | 
                        if(group.contains(room)){
							prevLight=light 
						}
					]
				]
                gAltColor.members.forEach [ light | 
                    light.getGroupNames.forEach[ group | 
                        if(group.contains(room)){
							altLight=light 
						}
					]
				]
			}
	]


	
	if(!hue){

	if(db) logInfo("Generic Light", "updated item is {}", updatedLight)
	var receivedCommand=updatedLight.state
	var HSBType nextColor	
	if(receivedCommand.toString.matches("\\d+"))
    {
    	logInfo("Generic Light", "match digit {}", receivedCommand)
 		nextColor=new HSBType((updatedLight.state as HSBType).hue,(updatedLight.state as HSBType).saturation, receivedCommand as PercentType) 
        if(db) logInfo( "Generic Light", "on to Scene [{}]", newColor )
    }
	else{
		nextColor = receivedCommand as HSBType
	}	
		var HSBType prevColor = prevLight.state as HSBType
		
		logInfo( "Generic Light", "sending color [{}] to [{}] from [{}]", updatedLight, receivedCommand, prevColor)
		
		//perBrightness=(HueLivingRoomColor.state as HSBType).brightness

		var newBrightness = nextColor.brightness

		if((prevColor.brightness as DecimalType) == 0 && Integer::parseInt(newBrightness.toString) > 30){
			if(db) logInfo("Transition", "TIF")
			prevColor=new HSBType(prevColor.hue, prevColor.saturation, new PercentType(30))
			//HueLivingRoom.sendCommand(prevColor)
		}


		if(prevColor.brightness > 0 || Integer::parseInt(newBrightness.toString) > 0)
		{
			logInfo( "Generic Light", "sending perBrightness [{}] to [{}]", nextColor, updatedLight)
			newColor=colorSmooth.apply(prevLight, nextColor, prevColor.brightness, sendColor)
			if(db) logInfo( "Generic Light", "sending newColor [{}] to [{}]", newColor, updatedLight)
            
		}
		sendColor.apply(altLight, nextColor)
		Thread::sleep(100)
        sendColor.apply(prevLight, nextColor)
		
	}



end


rule "Lamba test"
when
Channel "amazondashbutton:dashbutton:88-71-e5-74-0a-6a:press" triggered
then


oldColor=new HSBType(new DecimalType(0),new PercentType(0),new PercentType(0)) 

perBrightness=new PercentType(0)
decHue=new DecimalType(0)


		Thread::sleep(500)
		
end

rule "All rooms rule"
	when
		Item ProxyAllColor received command
	then

    sendCommand(ProxyLivingRoomColor, receivedCommand)
	sendCommand(ProxyHallwayColor, receivedCommand)
    sendCommand(ProxyBathroomColor, receivedCommand)	
    sendCommand(HueBedroom, receivedCommand)
    sendCommand(HueStriplight, receivedCommand)
	Thread::sleep(500)
	sendCommand(HueAllColor, receivedCommand)

    end

Anyway I’ve got the jython up and running pretty well, I’ve even integrated a spotify api that can be loaded as a module which is much better than doing some kind of exec command. I’m pretty sure I’ll be much happier there, and spent 1/10 of time and write 10* better code.

1 Like

I had no idea we could do this:

sendCommand(JennHome, if(jennHome) ON else OFF)

thank you!

I reckon a bit of insight is that there is not One True Path. Always more than one way, which way is “right” depends on the situation. That’s unique for each openHAB user, and it does depend on your own skills too.

Like building a wall. There’s a wrong way - don’t start at the top! But you can build up one layer at a time, or start at one end. Someone will say “you MUST use a plumbline!”, but you don’t have to really.

Use ideas that make sense to you, avoid or ask about stuff that doesn’t.

Normally I agree 100%, but with Rules DSL, for many users, particularly developers, what makes sense didn’t work well in Rules DSL or is impossible. For example, a developer’s first inclination will be to break a problem up into functions. But Rules DSL’s support for functions are problematic (e. g. not thread safe, don’t handle errors well). A programmer would be inclined to build data structures but Rules DSL has no real support for that except faking it using maps and lists.

This is why I always push developers to JSR223. What makes sense to them is either a bad idea or not possible in Rules DSL.

That doesn’t mean that Rules DSL isn’t capable. But it requires an approach to coding that didn’t come naturally to developers.

As someone who codes in embedded systems , as well as open systems but clearly embedded requires robust concrete design decisions consequent to limited and finite resources. This has allowed me to both experience and apply a number of techniques, mostly in design and deployment of run-time systems for embedded applications, process management, watchdogs, interrupt handlers, message passing system, distributed processing semaphores and so on.

I cant help feeling that the root cause here is the DSL model, and i do not claim to be any expert in this. However, let us explore the synopsis provided above.

The DSL allows 5 rules to be executed “concurrently”, i use the quotes because unless using transputer architecture and a codification in something like OCCAM, there’s no such thing as concurrency , moreover, proccesses are time sliced either by software (govenor/supervisor/kernel) or by hardware (interrupts).

So accepting this paradigm, we observe that DSL is acting as a govenor and not relying on hardware interrupts, which it probably could not use as it is unlikely to run as supervisor code, but usermode. Notwithstanding, DSL appears to define an arbitrary “concurrent thread” count of 5 with a FIFO backing queue. There appears to be no prioritisation of these “threads” or inherent process attributes which can be configured and managed at a supervisor level. In other words , the above synopsis would seem to me to infer that Thread::sleep is a blocking operation, is it really? Surely the operation would put a process to backing store (whatever that is in DSL/OH and underlying hardware, and implement some method of waking the process. But from reading the above, perhaps incorrectly, it would seem this is not the case.

DSL granted may not have been designed with full blown process management, I don’t know, but to me it seems that it’s weakness is that very same point, and that is driving design decisions in OH rules. Surely this is less than ideal. Users should be abstracted from underlying design layers and reasonably expect that a process management (rules scheduling) is constrained not by the user design, but rather the resources allocated to the user either directly through codification, or implicitly by a robust process manager capable of effective management of potentially unlimited processes (rules).

I repeat i am no expert in DSL but the very fact that this OP exists to guide users through “best practice” is in itself testament that the process engine isnt the best and could OH be improved by migrating to an alternative rules based applications?

I think the USA conjured up an apt and accurate paradigm named “duckduck” that refers to the simpe fact that if a system possesses weaknesses then “ignorant” users will still find them and break the system, typically unwittingly.

just a thought

5 was chosen as a happy medium in terms of required resources and OH’s ability to handle a typical amount of events in a timely fashion.

And I don’t think the queue is FIFO as if your events come in too fast the order of processing is not guaranteed.

Yes, Thread::sleep blocks the thread the Rule is running in, preventing that thread from being returned to the thread pool until the sleep completes and the Rule exits.

This is indeed not the case. And this is implemented by the underlying Java and Java Runtime Environment. There are constructs that do put the process “offline” but Thread::sleep isn’t one of them. It is possible to interrupt a sleeping thread, but you need to have a handle to that thread in the first place and we don’t have access to any of that stuff in the Rules.

Rules DSL is to be deprecated in OH 3. The default will be Scripted Automation using Python. None of the limitations above apply to Scripted Automation. You can sleep all day long in a Rule and it will have no impact on any of your other Rules, though it will consume resources and eventually you may run out of RAM if you have hundreds or thousands of Rules sleeping at the same time.

A thought we’ve had for years and are finally able to implement. This is why I am actively pushing people to use Scripted Automation over Rules DSL (see the reply I just made to another one of your posts).

The problem is known. A solution is mostly implemented. The solution will become the default in about a year when OH 3 comes out. You don’t have to wait that long though. You can start using it now.

ah…
Python, well a version of it running over the JVM? as per your other post to me Ritch.

I see

The penny is dropping bud, i see the light. I’ll look at joining the beta tester programme as i do have some years of Python experience albeit hush hush from my university who better not find out I’ve “joined the dark side” ,:grin::grin:

I learned from somebody else about an alternative way to implement timers; however, I’m wondering which option is more efficient in terms of system performance, that alternative or Timers?

The alternative way works like this:

  1. When Action 1 takes place (e.g., motion detected) a DateTime item is updated.
  2. A recurring rule checks whether it has been X /seconds/minutes/hours/… after Action 1 took place (i.e., after the time set in the DateTime item).
rule "Motion detected"
when
    Member of group_Sensors_Motion_Triggered changed from OFF to ON
then
     //Control variable is updated with the timestamp of moment when Action 1 took place
     MBR_Sensor_Motion_LastActive.state = MBR_Sensor_Motion_LastUpdate.state
end


rule "Check motion"
when
    Time cron "0 0/5 * ? * *"
then
     if (now.minusMinutes(3).isAfter((MBR_Sensor_Motion_LastActive.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli))
                    {
                    //Action 2 after desired time has elapsed
                }
end

The Check Motion rule below checks every 5 minutes if it has been 3 minutes after the timestamp set on item MBR_Sensor_Motion_LastActive.

Something I like about this approach is that it can be made resistant to system reboots by checking if the Item MBR_Sensor_Motion_LastActive is NULL and assigning the current time. That way, next time the periodic rule runs, it will work without problems and without having to take any manual actions.

However, there are 2 trade-offs:

  1. A periodic rule is required
  2. The action (2) to be taken after the desired time has elapsed will not be executed exactly 3 minutes after Action 1 took place. It will be executed between 3 and 7 (wanted time [3] + cron time [5]) minutes after Action 1 took place.

Is it possible to use something like:

  • createTimer(now.plusMilliseconds(1000)

or

  • createTimer(now.plusSeconds(.5)

?

I tried decimals and there were errors. And the API doesn’t mention anything about Milliseconds, so I would assume the answer is no. Sometimes I just need a quick pause between actions in a rule to allow hardware/processes to catch up, for example when I’m sending IR signals to turn down the TV volume a specific number of times. It takes too long/is weird when I have to wait a second in between stepping the volume levels up or down to a specific level automatically. But I can’t just send the signals out immediately, one after another, without it slipping up and missing the mark. I just need a very brief, < 1 second pause to allow things to catch up. Is there a code that will work here?

An alternative is for me to use the JSR223/Jython/ECMAScript/“Next-Generation” rule engine (why does everything have to be so complicated/have four names?? :grimacing: :exploding_head: ), and to use this sleep/pause code:

  • java.lang.Thread.sleep(5000);

which does allow for milliseconds…but then I’m not able to make use of variables/other basic data transformations/conversions/manipulations in my rule, right? :thinking: :face_with_raised_eyebrow: :roll_eyes:

Yes, but the method is .plusMillis()

No, the method requires an integer argument.

It’s really worth using VSCode editor + openHAB extension for rules editing. Not only does it validate and highlight errors like this, it autosuggests appropriate methods as you type.

1 Like