Rules issue in OH2. Is it my rules or OH2?

My issue is the rules are not firing reliably. I figured maybe it was an issue of still being in bata as these exact same rules run perfectly fine unmodified under OH1. I keep upgrading to the latest snapshot but every attempt to move to OH2 fails, HARD! There’s really nothing special about my rules and OH2 should be far enough along where basic rules wouldn’t be a problem (please correct me if i’m wrong). So either my rules are shitty, i found a bug, or something changed in rule execution that my rules can’t adjust to. I’m not a programmer and these rules represent my best attempt so the issue could very well be in my rules.

Below are 2 of my rules. The first rule is one of 4 rules that respond to sensors (PIR and door contacts). They respond to different sensors and each sets a unique value. The second rule responds to the changes of the item the 1st rule(s) handles and this particular rule is for the ceiling lights. I have similar rules as the second rule for Security, Media, and heating and they all respond simultaneously to the first rule(s). They’re essentially the same syntax as my ceiling example thanks to my naming convention and the find and replace function in notepad++.

Now that you have a little back story, here are the details:
As you can see the first rule sends a command to the string item “TVRoomPresenceSwitch”. The snippet I provided writes “Active” to the string. There’s a rule block to write Inactive, Standby, etc. This works, checking the logs, REST, and the database (i’m using JDBC MySQL for persistence), this first part seems to work as expected every time.

The second rule(s) is supposed to trigger when TVRoomPresenceSwitch receives a command. This is were the problem is. This does not always happen. For instance, my TV room ceiling lights were on all night because when TVRoomPresenceSwitch was changed to Inactive, the rule never triggered to change the lighting mode to off. However around 3:00AM when (i’m assuming) my wife was in the TV room. Not only did all the rules fire as expected when she approached and entered the room, but when she was done the lights turned off on their own as expected. The logs confirms this. During testing simply doing a touch on the affected rule file from command line would trigger all the rules that should have triggered but didn’t. For ease of programming and to keep things as modular as possible, I have rules very similar to the second one for every device i’m controlling. I opted to not have one rule block control all the lights at this level. Going back to last night, my ceiling lights and under cabinet lights remained on, while my floor lamp and projector fixtures turned off. These are 4 independently controlled lights both physically and via OH. They’re a mix of Hue and Zwave. The rule files are basically the same as I created it once and did a find and replace to make 4 unique files. For some reason, 2 worked last night and 2 didn’t. It’s not always the same 2, and the failure isn’t always in the same place. For instance, there have been times where upon entering the room one or more lights didn’t turn on, which would indicate the rules didn’t notice TVRoomPresenceSwitch was changed to Active. There are also times when everything works as expected, just not for long (hours).

Hopefully you’re still with me after all that reading. If it’s an OH2 issue then that’s okay and i’ll help where I can. But I guess I would like you all to look at the rule blocks so I can rule out a run-time issue and focus on improving the rules. I’m also hoping you all can advise on how to make the rules more OH2 compatible (if that’s even a thing one could do).

Rule 1:

rule "TVRoom Active Presence Detected"
when
        System started or
        Item TVRoomPresenceGroup received update or
	//Item TVRoomPresenceGroup changed
then
	logDebug("TVRoom", "Active Presence Detection - Presence Starting")
	if(TVRoomPresenceOverride.state != ON){
             logDebug("TVRoom", "Active Presence Detection - Room Override OFF")
	     if (!TVRoomPresenceSwitch.state.toString.equals("Active")) {
                  logDebug("TVRoom", "Active Presence Detection - Room NOT Active")
		  if(TVRoomPresenceGroup.members.filter(s | s.state == ON).size > 0) {
                      logDebug("TVRoom", "Active Presence Detection - ELSE - Room Sensors ACTIVE")
		      logDebug("TVRoom", "Active Presence Detection - Room set to ACTIVE")
                      sendCommand(TVRoomPresenceSwitch, ("Active"))
                }
         }
    }
end

Rule2:

rule "TVRoom Ceiling Light Mode Selection"
when
	System started or
	Item TVRoomPresenceSwitch received command
then
	logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Starting")
	if(TVRoomPresenceSwitch.state.toString.equals("Startup")){
   		logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Startup Mode")
		TVRoom_Ceiling_LightingAutomation.sendCommand(1)
		logInfo("TVRoom", "Lighting Ceiling - Mode Selection - Mode 1")
	}
else
   	if(TVRoomPresenceSwitch.state.toString.equals("Standby")){
		logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Standby Mode")
		TVRoom_Ceiling_LightingAutomation.sendCommand(12)
		logInfo("TVRoom", "Lighting Ceiling - Mode Selection - Mode 12")
		if(TVRoom_Ceiling_Lights_OUT_counter!=null){
			logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Standby Mode - TVRoom Lights OUT Timer Canceled")
			TVRoom_Ceiling_Lights_OUT_counter.cancel
			TVRoom_Ceiling_Lights_OUT_counter = null
		}
	}
else
	if(TVRoomPresenceSwitch.state.toString.equals("Active")){
		logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Active Mode")
		TVRoom_Ceiling_LightingAutomation.sendCommand(2)
		logInfo("TVRoom", "Lighting Ceiling - Mode Selection - Mode 2")
		if(TVRoom_Ceiling_Lights_OUT_counter!=null){
			logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Active Mode - TVRoom Lights OUT Timer Canceled")
			TVRoom_Ceiling_Lights_OUT_counter.cancel
			TVRoom_Ceiling_Lights_OUT_counter = null
		}		
	}
else
	if(TVRoomPresenceSwitch.state.toString.equals("Manual")){
   		logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Manual Mode")
		TVRoom_Ceiling_LightingAutomation.sendCommand(9)
		logInfo("TVRoom", "Lighting Ceiling - Mode Selection - Mode 9")
		if(TVRoom_Ceiling_Lights_OUT_counter!=null){
			logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Manual Mode - TVRoom Lights OUT Timer Canceled")
			TVRoom_Ceiling_Lights_OUT_counter.cancel
			TVRoom_Ceiling_Lights_OUT_counter = null
		}
	}
else
	if(TVRoomPresenceSwitch.state.toString.equals("Inactive")){
   		logDebug("TVRoom", "Lighting Ceiling - Mode Selection - Inactive Mode")
		TVRoom_Ceiling_LightingAutomation.sendCommand(10)
		logInfo("TVRoom", "Lighting Ceiling - Mode Selection - Mode 10")
	}
end

I had a similar issue, it was caused (in my case) by me messing up the braces, I had one extra closing, The rule loaded with no error.

An additional issue I found was that when OH2 starts, it didn’t read my rule file, I had to open / re-save to get it to read the file.

Not sure if this will help in your case.

Thanks for the ultra fast response!! That’s the kind of thing i’m looking for. I don’t think I have a brace issue but I’m suspecting my issue may be something along the same lines and I may just need a fresh set of eyes to spot the problem. I’ve looked these rules over more times than I can count searching for a typo like that with no success. I can post the entire rule file but there are 2 of them and they’re kind of long. If you guys want or need them I will provide them.

I have a rule block that sets a couple items just in case persistence fails on startup for some weird reason. It’s just a precaution. Anyway, thanks to “logDebug(“TVRoom”, “Presence Mode Initialize - Starting”)” I know the rules are loading on startup. They usually work fine immediately after saving or touching the rule. once cycle through the room modes and they get buggy.

Remember, if I take these rule files exactly how they are and copy them to my OH1 system, they work with zero issues (at least none that I have noticed).

Syntax looks wrong - no braces for second parameter.

As a generalism, item.sendCommand(XX) works more reliably where XX may need type conversion .

Looking at this:

        System started or
        Item TVRoomPresenceGroup received update or
   //Item TVRoomPresenceGroup changed

Isn’t the second “or” incorrect, given that the third line is commented out?

Dan

1 Like

I’ve seen something similar and it looked to me as if there’s some sort of race condition between the rule firing based on a command and the actual state of the item being updated. So sometimes when you get the state of the item it’s the value before the command is applied.

I fixed this by changing my rules to trigger on update or by using the recievedCommand variable instead of the item’s state.

Mike

Please please please use Designer people. All of the problems mentioned so far would have been pointed out in Designer before you even saved the file. The error messages in OH are too cryptic to efficiently work without it.

Have you been following the migration tutorial. In particular the “Necessary Changes” section tell you what you need to modify in your rules between OH 1 and OH 2.

You should look into lambdas so your logic only has to be written once. It greatly reduces the chances of messing something up with “copy and paste find and replace” coding.

What I don’t see posted are any logs. When you see this sort of behavior add a ton of logging to your rules, log out the states of everything relevant, and log every other line so you can see where in the rule a rule may have failed.

You say the rules are not working but without the logs we don’t know whether the rule simply never fired which would indicate one type of problem, or failed in the middle of execution which would be another type of error.

Also, you should post your Items because sending “Active” to an Item named “Switch” is counter intuitive. I assume this is a String Item but if not that line would be another error.

  • extraneous or after `Item TVRoomPresenceGroup received update
  • not an error but the following is simpler and therefore easier to understand if(TVRoomPresenceSwitch.state != "Active)
  • not an error but it is safer to use TVRoomPresenceSwitch.sendCommand(“Active”) instead of the Action; it is a long explanation I won’t go into here as @rossko57 indicated. However, while it is very odd to use them , the ( ) in this case is not an error.

Except for the extraneous “or” I see nothing particularly wrong with this rule. This rule will be triggered more than once for every update to an Item that is a member of TVRoomPresenceGroup but that should not cause the

Rule 2:

  • what is TVRoom_Ceiling_Lights_OUT_counter? It looks like a Timer. Where is this Timer created?

I see nothing else glaringly wrong. The code itself can use some tightening up (see below) but nothing glaringly wrong. This lends evidence to @MikeJMajor’s guess. The sometimes it works and sometimes it doesn’t also lends evidence to this conclusion.

If this is the case, the “problem” is that OH 2 is much more efficient at processing commands than it was in OH 1.

What hardware are you running on?

Rewrite of Rule 2 so show what I mean about how it can be tighter:

rule "TVRoom Ceiling Light Mode Selection"
when
    System started or
    Item TVRoomPresenceSwitch received command
then
    logDebug("TVRooom", "Lighting Ceiling - Mode Selection - Starting")

    var mode = 0
    switch TVRoomPresenceSwitch.state {
        case "Startup":  mode = 1
        case "Standby":  mode = 12
        case "Active":   mode = 2
        case "Manual":   mode = 9
        case "Inactive": mode = 10
    }

    logDebug("TVRoom", "Lighting Ceiling - Mode Selection - " + TVRoomPresenceSwitch.state + " Mode Number " + mode)
    TVRoom_Ceiling_LightingAutomation.sendCommand(mode)

    switch mode {
        case 12, case 2, case 9: {
            logDebug("TVRoom", "Lighting Ceiling - Mode Selection " + TVRoomPresenceSwitch.state + " Mode - TVRoom Lights OUT Timer Canceled")
            TVRoom_Ceiling_Lights_Out_counter.cancel
            TVRoom_Ceiling_Lights_Out_counter = null
        }
    }
end

51 lines of code to 27. The fewer the lines of code usually the easier it is to understand, debug, and update. In the above the only functionality removed is the second log statement after the sendCommand.

Note that a Switch is a way to write a bunch of if else if statements in a more readable manner.

An personally I would create a map file and replace the first switch statement with:

transform("MAP", "roompresence.map", TVRoomPresenceSwitch.state)

With the contents of transformations/roompresence.map:

"Startup"=1
"Standby"=12
"Active"=2
"Manual"=9
"Inactive"=10

which would bring the new code count down to 20.

Alternatively I would update TVRoom_Ceiling_LightingAutomation to take the “Active”, “Standby” et al instead of a number for the state and eliminate the need for the switch or transform in the first place.

2 Likes

Wow! Have I mentioned how much I love this forum! So informative. Plus every time I read a post from Rich I feel like I’ve learned something.

Mike and Rich you hit it on the head, there was a race condition. It was all happening so fast the If statement in the second rule was seeing the previous state instead of the new state. I added a few pauses to confirm this then I replaced everything with the tighter rule that Rich supplied. I’m back in business on OH2!! Thanks guys!!!

To address a few points:

I usually always use the Designer however it’s not working for me in OH2 and it never has. I can’t create rules from habmin either. I tried both after running into trouble so I assumed there was a piece there still being worked on by the development teams. Also remember, this all worked in OH1 where I used designer and there were no errors in my code. When loading these files in designer while pointing at OH2, literally every line shows an error. I couldn’t even get suggestions and it looks like designer can’t see my items.

Followed the Migration guide but nothing applied to me. Admittedly, I checked the docs pretty early on and I have been all that disciplined at revisiting it.

Lambdas. You’ve mentioned this to me before. Please don’t laugh, but if i’m being honest I have no clue what that is and how to use it. It’s on my list of things to learn (it’s a pretty long list but after this i’ll bump it up a little higher on the priority). As I mentioned I’m not a programmer, usually I can read code but it’s harder for me to write it from scratch.

Logs. So I guess I set you all up for failure by omitting the logs. However i was pretty sure they weren’t going to help much as there we no errors in the logs. As far as I can tell from the logs, the event just didn’t happen.

The presence switch was an actual switch. I quickly (relatively quickly) realized that a simple switch limited what I was trying to accomplish and with my coding skills would have ended with an even longer more complex rules file to accomplish the same thing. My immediate solution was to simply change “Switch” to “String” in the item file, then I learned that an IF statement for a switch uses different syntax than a string. At the end of the day, I was so far down the rabbit hole and the items still had the word “switch” in it so I figured f^&k it, i’m the only one that’s going to see it anyway…

Not sure where that extra or came from. Must have been part of my trouble shooting efforts that missed. It’s not in my actual rule.

As I mentioned earlier the syntax if(TVRoomPresenceSwitch.state != “Active”) didn’t work for me. Don’t remember the actual error I was getting but using “if(!TVRoomPresenceSwitch.state.toString.equals(“Active”))” worked out the gate.

Yes, that is a timer and it’s declared at the start of the file as there are multiple rule blocks that use it. It’s unlikely that it will be used at the same time by multiple rule blocks so I declared only one and it’s outside of the rule block to make it global. There are also rule blocks that will need access to the timer to cancel it if those rule blocks are triggered.

All my OH instances are virtual running on a vsphere 6 hyper-visor backed by a Dell PowerEdge with a 3.0 Xeon. My virtual infrastructure was already in place when I started with OH and I had the resources available to host OH.

I love that you did this!! I love it so much I decided to address this in it’s own post. Thank you!

The reason TVRoom_Ceiling_LightingAutomation even exist and it’s a number item is to provide a wider range of lighting modes. I guess I could have done it with a string but for some reason I found this easier on my brain. The idea is that with a room status of Active (or anything really) I can have virtually unlimited lighting modes by simply sending a number to TVRoom_Ceiling_LightingAutomation using logic or the UI.

I’m kind of hijacking my own thread here, although it’s kind of related…

Is there any way to send commands to multiple items using the map file you suggested? As I’m sure you can imagine the rules being driven by TVRoom_Ceiling_LightingAutomation can get crazy long and complicated. I unfortunately don’t know any other way to do it.

A small snippet of the rule:

rule "TVRoom Ceiling Light Modes"
when
	System started or
	Item TVRoom_Ceiling_LightingAutomation received command
then
//Default
	if(receivedCommand == 1){
    	TVRoom_Ceiling_Light_Adjuster.sendCommand("5")
		TVRoom_Ceiling_Light_Adjuster_Timer.sendCommand("1000")
		if (now.getHourOfDay() < 05) {
     		sendCommand(Light_FF_TVRoom_Ceiling_level, "1")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer, "5")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer_OR, "20")
     		logInfo("Lighting", "TVRoom Ceiling Level Updated 01")
     		logInfo("Lighting", "TVRoom Ceiling Timer Updated 5")
     	}	
		else
     	if (now.getHourOfDay() >= 05 && now.getHourOfDay() < 23) {
     		sendCommand(Light_FF_TVRoom_Ceiling_level, "45")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer, "5")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer_OR, "60")     		
     		logInfo("Lighting", "TVRoom Ceiling Timer Updated 5")
     		logInfo("Lighting", "TVRoom Ceiling Level Updated 45")
		}		
 	   else
    	if (now.getHourOfDay() >= 23) {
     		sendCommand(Light_FF_TVRoom_Ceiling_level, "2")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer, "5")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer_OR, "20")          		
     		logInfo("Lighting", "TVRoom Ceiling Timer Updated 5")
     		logInfo("Lighting", "TVRoom Ceiling Level Updated 02")
     	}
     }	 
//Energy
	if(receivedCommand == 2){
		TVRoom_Ceiling_Light_Adjuster.sendCommand("1")
		TVRoom_Ceiling_Light_Adjuster_Timer.sendCommand("5000")
		if (now.getHourOfDay() < 05) {
     		sendCommand(Light_FF_TVRoom_Ceiling_level, "0")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer, "5")
     		sendCommand(TVRoom_Ceiling_LightingAutomationTimer_OR, "20")
     		logInfo("Lighting", "TVRoom Ceiling Level Updated 00")
     		logInfo("Lighting", "TVRoom Ceiling Timer Updated 5")
     	}	
		else
     ETC... ETC....ETC....

In case you need it:
At least for the lighting, all those rules are there to setup the star of the show. This rule block does the actual work.

rule "Ceiling Lighting adjuster"
when
    System started or
	Item Light_FF_TVRoom_Ceiling_level received command or
	Item Light_FF_TVRoom_Ceiling_Control changed from OFF to ON
then
Thread::sleep(500)
	logDebug("TVRoom", "Ceiling Light Adjuster - Starting")
	if(TVRoom_Ceiling_LightingAutomation.state!=8 || TVRoom_Ceiling_LightingAutomation.state!=9){
		logDebug("TVRoom", "Ceiling Light Adjuster - Automation Enabled")
		if(Light_FF_TVRoom_Ceiling.state != Light_FF_TVRoom_Ceiling_level.state){
			logDebug("TVRoom", "Ceiling Light Adjuster - Target:" +Light_FF_TVRoom_Ceiling_level.state + "Actual:" +Light_FF_TVRoom_Ceiling.state)
			if(Light_FF_TVRoom_Ceiling.state < Light_FF_TVRoom_Ceiling_level.state){
				logDebug("TVRoom", "Ceiling Light Adjuster - Disabling Override Detection")
				Light_FF_TVRoom_Ceiling_OR_Bypass.sendCommand("ON")
				logDebug("TVRoom", "Ceiling Light Adjuster - Target:" +Light_FF_TVRoom_Ceiling_level.state + " Actual:" +Light_FF_TVRoom_Ceiling.state)
				logDebug("TVRoom", "Ceiling Light Adjuster - Turning Lights UP")
				var ceilingLightAdjusterActual = Light_FF_TVRoom_Ceiling.state
				var ceilingLightAdjusterTarget = Light_FF_TVRoom_Ceiling_level.state
				while(ceilingLightAdjusterActual<ceilingLightAdjusterTarget){
					var ceilingLightAdjusterWorker = (Light_FF_TVRoom_Ceiling.state as DecimalType) + TVRoom_Ceiling_Light_Adjuster.state as DecimalType
					if(Light_FF_TVRoom_Ceiling.state < Light_FF_TVRoom_Ceiling_level.state){
						Light_FF_TVRoom_Ceiling_Control.sendCommand("ON")
						Light_FF_TVRoom_Ceiling.sendCommand(ceilingLightAdjusterWorker)
						logDebug("TVRoom", "Ceiling Light Adjuster - Ceiling lights: " + Light_FF_TVRoom_Ceiling.state)
						Thread::sleep((TVRoom_Ceiling_Light_Adjuster_Timer.state as DecimalType).intValue)			
						ceilingLightAdjusterActual = Light_FF_TVRoom_Ceiling.state
						ceilingLightAdjusterTarget = Light_FF_TVRoom_Ceiling_level.state
					}
				}
				logDebug("TVRoom", "Ceiling Light Adjuster - Enabling Override Detection")
				Light_FF_TVRoom_Ceiling_OR_Bypass.sendCommand("OFF")
			}
			else
			if(Light_FF_TVRoom_Ceiling.state > Light_FF_TVRoom_Ceiling_level.state){
				logDebug("TVRoom", "Ceiling Light Adjuster - Disabling Override Detection")
				Light_FF_TVRoom_Ceiling_OR_Bypass.sendCommand("ON")
				logDebug("TVRoom", "Ceiling Light Adjuster - Target:" +Light_FF_TVRoom_Ceiling_level.state + " Actual:" +Light_FF_TVRoom_Ceiling.state)
				logDebug("TVRoom", "Ceiling Light Adjuster - Turning Lights DOWN")
				var ceilingLightAdjusterActual = Light_FF_TVRoom_Ceiling.state
				var ceilingLightAdjusterTarget = Light_FF_TVRoom_Ceiling_level.state
				while(ceilingLightAdjusterActual>ceilingLightAdjusterTarget){
					var ceilingLightAdjusterWorker = (Light_FF_TVRoom_Ceiling.state as DecimalType) - TVRoom_Ceiling_Light_Adjuster.state as DecimalType
					if(Light_FF_TVRoom_Ceiling.state > Light_FF_TVRoom_Ceiling_level.state){
						Light_FF_TVRoom_Ceiling_Control.sendCommand("ON")
						Light_FF_TVRoom_Ceiling.sendCommand(ceilingLightAdjusterWorker)
						logDebug("TVRoom", "Ceiling Light Adjuster - Ceiling lights: " + Light_FF_TVRoom_Ceiling.state)
						Thread::sleep((TVRoom_Ceiling_Light_Adjuster_Timer.state as DecimalType).intValue)
						ceilingLightAdjusterActual = Light_FF_TVRoom_Ceiling.state
						ceilingLightAdjusterTarget = Light_FF_TVRoom_Ceiling_level.state
					}
				}
				logDebug("TVRoom", "Ceiling Light Adjuster - Enabling Override Detection")
				Light_FF_TVRoom_Ceiling_OR_Bypass.sendCommand("OFF")
			}	
		}
	}
end

You downloaded ESH Designer, right?

I’m thinking you checked it when it was just a list of a bunch of things that are different between the two. Now it is a full blown step by step tutorial.

The link above is to a very simple example with lots of notes. It is basically a function, i.e. a chunk of code you can call with a set of passed in parameters.

I don’t think so. The problem you have is you have different numbers that you send based on the time of day. The map file for this would get pretty complicated if not completely unworkable.

One thing that would help things a little though is to move the time testing out of the rules here and use a state machine instead, as documented here.

Another would be to use a lambda.

import org.eclipse.xtext.xbase.lib.*

val Functions$Function5<String, String, String, String, String, Boolean> tvLightAuto = [ adjuster, adjusterTimer, ceilingLevel, autoTimer, autoTimer_or |
    TVRoom_Ceiling_Light_Adjuster.sendCommand(adjuster)
    TVRoom_Ceiling_Light_Adjuster_Timer.sendCommand(adjusterTimer)
    Light_FF_TVRoom_Ceiling_level.sendCommand(ceilingLevel)
    TVRoom_Ceiling_LightingAutomationTimer.sendCommand(autoTimer)
    TVRoom_Ceiling_LightingAutomationTimer_OR.sendCommand(autoTimer_or)
    logInfo("Lighting", "TVRoom Ceiling Level Udated to " + ceilingLevel)
    logInfo("Lighting", "TVRoom Ceiling Level Updated to " + autoTimer)

    true
]

rule "TVRoom Ceiling Light Modes"
when
    System started or
    Item TVRoom_Ceiling_LightingAutomation received command
then
    val TimeOfDay = "DAY"
    if(now.getHourOfDay < 05) TimeOfDay = "BED"
    else if(now.getHourOfDay >= 23) "NIGHT"

    switch receivedCommand {
        case 1: {
            switch TimeOfDay:
                case "BED":  tvLightAuto.apply("5", "1000", "1", "5", "20")
                case "DAY": tvLightAuto.apply("5", "1000", "45", "5", "60")
                case "NIGHT": tvLightAuto.apply("5", "1000", "2", "5", "20")
        }
        case 2: {
            switch TimeOfDay:
                case "BED":  tvLightAuto.apply("1", "1000", "0", "5", "20")
                ...
        }
        ...
    }
end

Apply the same principles to the second rule. Do common calculations first (e.g. like I calculated the TimeOfDay). If the only difference between the clauses are the new states, move the calling of sendCommand and logging to a lambda and pass in the new states. At a minimum move the while loops to a lambda.

However, you might be able to make something work using Groups. For example, I have a much simpler use case but it could be expanded.

roup:Switch:OR(ON,OFF) gLights_ALL "All Lights"

Group:Switch:OR(ON, OFF) gLights_ON
Group:Switch:OR(ON, OFF) gLights_OFF
Group:Switch:OR(ON, OFF) gLights_ON_MORNING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_MORNING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_DAY        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_DAY       (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_AFTERNOON  (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_AFTERNOON (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_EVENING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_EVENING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_NIGHT      (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_NIGHT     (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_BED        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_BED       (gLights_OFF)

Switch aFrontLamp "Front Room Lamp"
  (gLights_ALL, gLights_ON_MORNING, gLights_OFF_DAY, gLights_OFF_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node3:switch_binary" }
  
Switch aFamilyLamp "Family Room Lamp"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node10:switch_binary" }
  
Switch aPorchLight "Front Porch"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_OFF_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node6:switch_binary" }

Each light belongs to a set of Groups which correspond with my TimeOfDay state. I have an ON and OFF group. So, for example, a member of gLights_ON_MORNING should turn on when MORNING starts and a member of gLights_OFF_AFTERNOON should turn off when AFTERNOON starts.

And because I’ve done this the rule is as simple as:

val logName = "lights"

rule "Set lights based on Time of Day"
when
  Item vTimeOfDay received update
then
  val offGroupName = "gLights_OFF_"+vTimeOfDay.state.toString
  val onGroupName = "gLights_ON_"+vTimeOfDay.state.toString

  logInfo(logName, "Turning off lights in " + offGroupName)
  val GroupItem offItems = gLights_OFF.members.filter[g|g.name == offGroupName].head as GroupItem
  offItems.members.filter[l|l.state != OFF].forEach[l | l.sendCommand(OFF)
  ]

  logInfo(logName, "Turning on lights for " + onGroupName)
  val GroupItem onItems = gLights_ON.members.filter[g|g.name == onGroupName].head as GroupItem
  onItems.members.filter[l|l.state != ON].forEach[l | l.sendCommand(ON)]
  
end

I don’t know if this sparks any ideas or not.