Prevent roller shutter rule from firing more than once + inconsistent Z-Wave state reported

I created a set of rules to automate our roof roller shutters by using proxy items, so I can intercept UP/DOWN commands when the temperature is too low (e.g. when it’s freezing). The roller shutters are Fakro ARZ Z-Wave (although OpenHAB invariably reports them as Fakro ZWS12 Chain Actuator - but that’s another story).

In the morning, I use a sequence of trigger events (civil dawn start, sunrise start, sunrise end) to try to raise the roller shutters in case the outside temperature is still too low.

The rules get fired as expected, but I can’t prevent them from firing again (3 roller shutter open commands sent in a row, although the roller shutters are already open from an earlier attempt).

I tried to verify the state of the roller shutters and their proxy items, but I get inconsistent state information all the time. And that also shows up in the UI as the reported roller shutter state rarely matches the actual state.

Is this a limitation of the Z-Wave binding, or is there something else I could try?

Below you’ll find a very verbose "Proxy item received command" rule:

import org.eclipse.smarthome.model.script.ScriptServiceUtil

// Send notification through the OH cloud binding:
val String notificationRecipient = "your_email_address_here"


/* All roof roller shutters are controlled by proxy items, in order to allow checking the outside temperature prior to operating the roller shutters.
 */
rule "Proxy item received command"
when
	Member of gShutterProxy received command
then
	val String ruleTitle = "Proxy roller shutter item received command"
	val Number MIN_TEMP = 3.0|°C

	// The proxy item has "Proxy_" as prefix to the 'real' item name:
	val String itemName = triggeringItem.getName().substringAfter("Proxy_")
	logInfo(ruleTitle, "Proxy Item '{}' (state = '{}' or '{}') received command '{}' --> Proxy for Item '{}'",
		triggeringItem.getName(), triggeringItem.state, triggeringItem.state.toString, receivedCommand, itemName)
	logInfo(ruleTitle, "Proxy Item '{}' - full details: [{}]",
		triggeringItem.getName(), triggeringItem)

	// Retrieve the 'real' item based on its name: 
	val GenericItem item = ScriptServiceUtil.getItemRegistry.getItem(itemName) as GenericItem
	logInfo(ruleTitle, "Item '{}' (state = '{}' or '{}') - full details: [{}]",
		item.getName(), item.state, item.state.toString, item)

	switch (receivedCommand) {
		case STOP: { // Directly forward 'STOP' command to the 'real' item
			logInfo(ruleTitle, "Proxy Item '{}' received command '{}' --> Fowarding the command to Item '{}'",
				triggeringItem.getName(), receivedCommand, itemName)

			item.sendCommand(receivedCommand)
		}
		case UP,
		case DOWN: {
			if (Wx_OWM_Current_Temperature.state >= MIN_TEMP) {
				// Here's my current failing attempt at avoiding multiple rule firings:
				if (triggeringItem.state != item.state) {
					logWarn(ruleTitle, "Proxy item '{" + triggeringItem.getName() + "}' state (" + triggeringItem.state
						+ ") differs from real item '" + itemName + "' state (" + item.state + ") - Will nonetheless send '" + receivedCommand
						+ "' to '" + itemName + "' as temperature ≥ " + MIN_TEMP)
					sendNotification(notificationRecipient, "⚠️ Proxy item '{" + triggeringItem.getName() + "}' state (" + triggeringItem.state
						+ ") differs from real item '" + itemName + "' state (" + item.state + ") - Will nonetheless send '" + receivedCommand
						+ "' to '" + itemName + "' as temperature ≥ " + MIN_TEMP)
					item.sendCommand(receivedCommand)
				} else if ( ((receivedCommand == UP) && (item.state == 0)) || ((receivedCommand == DOWN) && (item.state == 100)) ) {
					val String shutterCurrentStatus = ( if (item.state == 0) { "open" } else { "closed" } )
					logInfo(ruleTitle, "Proxy Item '{}' received command '{}' --> already OK (Item '{}': state = {}, meaning {})",
						triggeringItem.getName(), receivedCommand, itemName, item.state, shutterCurrentStatus)
					sendNotification(notificationRecipient, "ℹ️ '" + itemName + "' already " + shutterCurrentStatus)
				} else {
					logInfo(ruleTitle, "Proxy Item '{}' received command '{}' --> Fowarding the command to Item '{}' as temperature ≥ {}",
						triggeringItem.getName(), receivedCommand, itemName, MIN_TEMP)
					sendNotification(notificationRecipient, "ℹ️ Sent '" + receivedCommand + "' to '" + itemName + "' as temperature ≥ " + MIN_TEMP)
					item.sendCommand(receivedCommand)
				}

			} else {
				logWarn(ruleTitle, "Proxy Item '{}' received command '{}' --> Discarding the command to Item '{}' as temperature < {}",
					triggeringItem.getName(), receivedCommand, itemName, MIN_TEMP)
				sendNotification(notificationRecipient, "⚠️ Cannot send '" + receivedCommand + "' to '" + itemName + "' as temperature < " + MIN_TEMP)

				postUpdate(triggeringItem, item.state)
			}
		}
		default: {
			logError(ruleTitle, "Proxy Item '{}' received unexpected command '{}' -- ignoring",
				triggeringItem.getName(), receivedCommand, itemName)
			sendNotification(notificationRecipient, "⚠️ Cannot send unexpected command '" + receivedCommand + "' to '" + itemName + "'")

			postUpdate(triggeringItem, item.state)
		}
	}
end

rule "Sunrise shutter rule NEW"
when
	Channel "astro:sun:local:civilDawn#event" triggered START
	or Channel "astro:sun:local:rise#event" triggered	// In case we missed the civil dawn due to temperature below automation threshold
	or Channel "astro:sun:local:noon#event" triggered	// In case we missed the morning opening due to cold weather
then
	val String ruleTitle = "SunriseShutterRule"
	logInfo(ruleTitle, "Rule has been triggered")
	gShutterProxy.allMembers.forEach[ s | s.sendCommand(UP) ]
end

rule "Sunset shutter rule NEW"
when
	Channel "astro:sun:local:civilDusk#event" triggered END
then
	val String ruleTitle = "SunsetShutterRule"
	logInfo(ruleTitle, "Rule has been triggered")
	gShutterProxy.allMembers.forEach[ s | s.sendCommand(DOWN) ]
end

No. Actuators have to actively report status to the controller after changes else OH cannot know their state.
If that does not work it’s either because of wrong configuration of the actuators (association group ?) or even a malfunction of these. Use zwave debugging to find out if/when OH receives that information.
It’s known to be a tricky thing. With Fibaro actuators it even required the binding to implement a proprietary protocol of theirs for the whole thing to work.

PS: it does not do harm to send another ‘UP’ even if blinds already are up. So the most simple solution might be to just ignore that.

The use of allMembers is odd. Why not send the command to the group? Or are there groups under gShutterProxy? Please post your Item definition. Seeing the log where you are seeing the three commands being sent would be helpful too.

For inconsistent state infomation, take a look at the Command Polling interval. You may need to increase it, or disable it, if the devices report their own state.

That’s the problem I have with Z-Wave. I find it rather cumbersome to properly configure.

I know, but then I get duplicate notifications.

Not really. For the most part it’s more a matter of lack of proper documentation of the devices and their behavior. Particularly with those vendors who rather treat this to be an addon to “their” system and not a core smart home technology.

Many ways of getting around that:
Use a variable to track if you’ve already sent a message.
Use a separate rule to send notifications to trigger on shutter state changes

I was first sending the commands directly to the roller shutter proxy items and then I wanted to do the same through the gShutterProxy group. It didn’t work, probably due to the mess in the roller shutter states.

The items are defined as follows:

Group:Rollershutter:OR(UP, DOWN)	gShutter			"Roller Shutter"		<rollershutter>		(Home)				["Rollershutter"]

Group:Rollershutter:OR(UP, DOWN) AT_Shutters "All roof roller shutters" <rollershutter> (Home) ["Rollershutter"]

Rollershutter	AT_Shutter_N	"Roller Shutter North (direct)"				<rollershutter>   (AT_GuestRoom, gShutter, AT_Shutters)		["Rollershutter"]            {
    channel="zwave:device:88a05a4f:node2:blinds_control"
}
Rollershutter	AT_Shutter_SE	"Roller Shutter South East (direct)"		<rollershutter>   (AT_GuestRoom, gShutter, AT_Shutters)		["Rollershutter"]            {
    channel="zwave:device:88a05a4f:node3:blinds_control"
}
Rollershutter	AT_Shutter_SW	"Roller Shutter South West (direct)"		<rollershutter>   (AT_Office, gShutter, AT_Shutters)		["Rollershutter"]            {
    channel="zwave:device:88a05a4f:node4:blinds_control"
}

// Automation should only use the proxy items - rules will take care of controlling the actual items
Group:Rollershutter:OR(UP, DOWN)	gShutterProxy			"Roller Shutter"		<rollershutter>		(Home)				["Rollershutter"]

Rollershutter   Proxy_AT_Shutter_N      "Roller Shutter North"          <rollershutter>     (gShutterProxy)
Rollershutter   Proxy_AT_Shutter_SE     "Roller Shutter South East"     <rollershutter>     (gShutterProxy)
Rollershutter   Proxy_AT_Shutter_SW     "Roller Shutter South West"     <rollershutter>     (gShutterProxy)

And here’s what the log file is telling (I reverted back to sending the commands to the group item, and I temporarily commented out my state-checking attempt:

2019-03-11 17:08:56.726 [ome.event.ItemCommandEvent] - Item 'gShutterProxy' received command UP
2019-03-11 17:08:56.733 [ome.event.ItemCommandEvent] - Item 'Proxy_AT_Shutter_N' received command UP
2019-03-11 17:08:56.740 [ome.event.ItemCommandEvent] - Item 'Proxy_AT_Shutter_SE' received command UP
2019-03-11 17:08:56.755 [ome.event.ItemCommandEvent] - Item 'Proxy_AT_Shutter_SW' received command UP
2019-03-11 17:08:56.789 [vent.ItemStateChangedEvent] - Proxy_AT_Shutter_N changed from 100.0 to 0
2019-03-11 17:08:56.801 [vent.ItemStateChangedEvent] - Proxy_AT_Shutter_SE changed from 100.0 to 0
2019-03-11 17:08:56.805 [vent.ItemStateChangedEvent] - Proxy_AT_Shutter_SW changed from 100.0 to 0
2019-03-11 17:08:56.839 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_SE' received command UP
2019-03-11 17:08:56.842 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_N' received command UP
2019-03-11 17:08:56.856 [nt.ItemStatePredictedEvent] - AT_Shutter_SE predicted to become UP
2019-03-11 17:08:56.878 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_SW' received command UP
2019-03-11 17:08:56.888 [nt.ItemStatePredictedEvent] - AT_Shutter_N predicted to become 100.0
2019-03-11 17:08:56.891 [GroupItemStateChangedEvent] - gShutter changed from 100 to 0 through AT_Shutter_SE
2019-03-11 17:08:56.893 [vent.ItemStateChangedEvent] - AT_Shutter_SE changed from 100 to 0
2019-03-11 17:08:56.897 [GroupItemStateChangedEvent] - AT_Shutters changed from 100 to 0 through AT_Shutter_SE
2019-03-11 17:08:56.901 [nt.ItemStatePredictedEvent] - AT_Shutter_SW predicted to become UP
2019-03-11 17:08:56.908 [vent.ItemStateChangedEvent] - AT_Shutter_SW changed from 100 to 0
2019-03-11 17:08:57.487 [hingStatusInfoChangedEvent] - 'zwave:device:88a05a4f:node2' changed from OFFLINE (COMMUNICATION_ERROR): Node is not communicating with controller to ONLINE
2019-03-11 17:08:58.504 [vent.ItemStateChangedEvent] - AT_Shutter_SE changed from 0 to 100
2019-03-11 17:08:58.603 [vent.ItemStateChangedEvent] - AT_Shutter_SW changed from 0 to 100
2019-03-11 17:08:58.606 [GroupItemStateChangedEvent] - AT_Shutters changed from 0 to 100 through AT_Shutter_SW
2019-03-11 17:08:58.608 [GroupItemStateChangedEvent] - gShutter changed from 0 to 100 through AT_Shutter_SW
2019-03-11 17:09:12.976 [ome.event.ItemCommandEvent] - Item 'Proxy_AT_Shutter_N' received command DOWN
2019-03-11 17:09:13.000 [vent.ItemStateChangedEvent] - Proxy_AT_Shutter_N changed from 0 to 100
2019-03-11 17:09:13.080 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_N' received command DOWN
2019-03-11 17:09:13.088 [nt.ItemStatePredictedEvent] - AT_Shutter_N predicted to become DOWN

The command polling interval is set at the default values (1500ms), and the device minimum polling interval is also still the default 1 day.

Is this just from one flip of the gShutterProxy switch? The event logs are helpful, but not of much use without the rule logs to know what was happening in the rule to cause the events. If that doesn’t help, you’d need to combine them with the zwave log. Which is Node2? Everything seems to get sideways after it comes back online.

You’re using states in a received command triggered rule. This will mess things up, since the Item likely has not yet reached the state that was received in the command, but it could while the rule is running. There’s no way to tell.

Are you set on using the proxy Items? They seem to be more hassle than their worth. All three of these rules can be collapsed into one that accomplishes the same thing, and you coud include the temperature into the triggers as well, so that the shutters would close any time the temp was too low. Something like…

rule "Shutter rule"
when
    Channel "astro:sun:local:civilDawn#event" triggered START
    or Channel "astro:sun:local:rise#event" triggered// In case we missed the civil dawn due to temperature below automation threshold
    or Channel "astro:sun:local:noon#event" triggered// In case we missed the morning opening due to cold weather
    or Channel "astro:sun:local:civilDusk#event" triggered END
    or Item Wx_OWM_Current_Temperature changed
then
    val Number MIN_TEMP = 3.0|°C
    val String ruleTitle = "SunriseShutterRule"
    logInfo(ruleTitle, "Rule has been triggered")
    if (gShutter.state == DOWN && now.isBefore(new DateTime(INSERT_CIVILDUSK_ITEM_HERE.state.toString)) && Wx_OWM_Current_Temperature.state >= MIN_TEMP && Wx_OWM_Current_Temperature.minimumSince(now.minusMinutes(30)).state >= MIN_TEMP) {
        gShutter.sendCommand(UP)
        logInfo(ruleTitle, "Raised shutters")
    }
    else if (gShutter.state == UP) {
        gShutter.sendCommand(DOWN)
        logInfo(ruleTitle, "Lowered shutters")
    }
end

It is one group “UP” sequence followed with one single shutter “DOWN” event. Here’s the relevant openhab.log, where I separated both events with a blank line:

2019-03-11 17:08:56.774 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_N' (state = '100.0' or '100.0') received command 'UP' --> Proxy for Item 'AT_Shutter_N'
2019-03-11 17:08:56.778 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_SE' (state = '100.0' or '100.0') received command 'UP' --> Proxy for Item 'AT_Shutter_SE'
2019-03-11 17:08:56.789 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_N' - full details: [Proxy_AT_Shutter_N (Type=RollershutterItem, State=0, Label=Roller Shutter North, Category=rollershutter, Groups=[gShutterProxy])]
2019-03-11 17:08:56.797 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_SE' - full details: [Proxy_AT_Shutter_SE (Type=RollershutterItem, State=0, Label=Roller Shutter South East, Category=rollershutter, Groups=[gShutterProxy])]
2019-03-11 17:08:56.808 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_SW' (state = '0' or '0') received command 'UP' --> Proxy for Item 'AT_Shutter_SW'
2019-03-11 17:08:56.811 [INFO ] [roller shutter item received command] - Item 'AT_Shutter_N' (state = '100.0' or '100.0') - full details: [AT_Shutter_N (Type=RollershutterItem, State=100.0, Label=Roller Shutter North (direct), Category=rollershutter, Tags=[Rollershutter], Groups=[AT_GuestRoom, gShutter, AT_Shutters])]
2019-03-11 17:08:56.816 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_SW' - full details: [Proxy_AT_Shutter_SW (Type=RollershutterItem, State=0, Label=Roller Shutter South West, Category=rollershutter, Groups=[gShutterProxy])]
2019-03-11 17:08:56.816 [INFO ] [roller shutter item received command] - Item 'AT_Shutter_SE' (state = '100' or '100') - full details: [AT_Shutter_SE (Type=RollershutterItem, State=100, Label=Roller Shutter South East (direct), Category=rollershutter, Tags=[Rollershutter], Groups=[AT_GuestRoom, gShutter, AT_Shutters])]
2019-03-11 17:08:56.824 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_N' received command 'UP' --> Fowarding the command to Item 'AT_Shutter_N' as temperature ≥ 3.0 °C
2019-03-11 17:08:56.825 [INFO ] [roller shutter item received command] - Item 'AT_Shutter_SW' (state = '100' or '100') - full details: [AT_Shutter_SW (Type=RollershutterItem, State=100, Label=Roller Shutter South West (direct), Category=rollershutter, Tags=[Rollershutter], Groups=[AT_Office, gShutter, AT_Shutters])]
2019-03-11 17:08:56.825 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_SE' received command 'UP' --> Fowarding the command to Item 'AT_Shutter_SE' as temperature ≥ 3.0 °C
2019-03-11 17:08:56.838 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_SW' received command 'UP' --> Fowarding the command to Item 'AT_Shutter_SW' as temperature ≥ 3.0 °C

2019-03-11 17:09:13.029 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_N' (state = '100' or '100') received command 'DOWN' --> Proxy for Item 'AT_Shutter_N'
2019-03-11 17:09:13.037 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_N' - full details: [Proxy_AT_Shutter_N (Type=RollershutterItem, State=100, Label=Roller Shutter North, Category=rollershutter, Groups=[gShutterProxy])]
2019-03-11 17:09:13.046 [INFO ] [roller shutter item received command] - Item 'AT_Shutter_N' (state = '100.0' or '100.0') - full details: [AT_Shutter_N (Type=RollershutterItem, State=100.0, Label=Roller Shutter North (direct), Category=rollershutter, Tags=[Rollershutter], Groups=[AT_GuestRoom, gShutter, AT_Shutters])]
2019-03-11 17:09:13.058 [INFO ] [roller shutter item received command] - Proxy Item 'Proxy_AT_Shutter_N' received command 'DOWN' --> Fowarding the command to Item 'AT_Shutter_N' as temperature ≥ 3.0 °C

That is what I feared. It’s probably some event / state race condition at play.

That looks indeed much simpler than my approach.

I’ll give it a go and report.

Quick update.

I had to adapt your rule tom make it work on OH2.5M1 with my particular setup as I found out:

  1. The value of gShutter.state is never equal to DOWN or UP but always numeric (between 0 and 100). This is most definitely due to the incorrect detection of my Z-Wave roller shutters as being seen as window openers.
    → I fixed this by replacing DOWN with 100 and UP with 0 when checking state values (it works fine when sending commands with UP and DOWN).
  2. The OH1.x expression Wx_OWM_Current_Temperature.minimumSince(now.minusMinutes(30)).state >= MIN_TEMP clashes with the OH2 UoM Number<Temperature> constant (MIN_TEMP) and there’s no easy way to fix this (unless removing all UoM from the rule). There’s no simple type cast that will do the job, so the temperature check will always return false
    → I fixed this by removing the “30 minutes prior” temperature check.

I like the idea of following the temperature evolution in the rule to raise the roller shutters even after sunrise when the temperature remains low. The only drawback is that I can no longer manually override the shutters as whenever the temperature is updated, the shutters will open again during the day :sunglasses: (but that’s of course a different rule scenario)

Alas, the rule doesn’t work. Instead the shutters constantly toggle between open and close whenever the rule is triggered.

This is not an OH1.x expression, but a persistence extension. Unfortunately, persistence is still using compat1.x, so it currently has no understanding of QuantityType. Constructing QuanityType in the DSL is currently not working, we should get an issue logged for that, but you can work around this by comparing the persisted value to a number. Just make sure the persisted data is in the unit that you are comparing to. You could also manually persist the data, but that’s messy. Another option is to compare to a range, like…

(Wx_OWM_Current_Temperature.state >= MIN_TEMP.plus(5|°C) ||  Wx_OWM_Current_Temperature.state <= MIN_TEMP.minus(5|°C))

The point of this is to prevent flapping, but outside temperature readings are pretty stable, so may not be needed.

In my automations, I set dimmers to 99 and then don’t turn them off if 100. Let’s get it working first, then look into a manual override.

Opps… this was a silly mistake on my part… I’m a bit rusty with the DSL, and much prefer Jython. Try this one.

rule "Shutter rule"
when
    Channel "astro:sun:local:civilDawn#event" triggered START
    or Channel "astro:sun:local:rise#event" triggered// In case we missed the civil dawn due to temperature below automation threshold
    or Channel "astro:sun:local:noon#event" triggered// In case we missed the morning opening due to cold weather
    or Channel "astro:sun:local:civilDusk#event" triggered END
    or Item Wx_OWM_Current_Temperature changed
then
    val MIN_TEMP = 3.0|°C
    val ruleTitle = "SunriseShutterRule"
    logInfo(ruleTitle, "Rule has been triggered")
    if (now.isBefore(new DateTime(INSERT_CIVILDUSK_ITEM_HERE.state.toString)) && Wx_OWM_Current_Temperature.state >= MIN_TEMP && Wx_OWM_Current_Temperature.minimumSince(now.minusMinutes(30)).state >= MIN_TEMP.doubleValue) {
        if (gShutter.state == 100) {
            gShutter.sendCommand(UP)
            logInfo(ruleTitle, "Raised shutters")
        }
    }
    else if (gShutter.state == 0) {
        gShutter.sendCommand(DOWN)
        logInfo(ruleTitle, "Lowered shutters")
    }
end

I stand corrected. Thanks for the clarification.

It will be a great and welcome addition to the rules DSL.

Alas, here again, gShutter cannot reliably report its state.

I got the following (intentionally very verbose) rule now, which will fire every time the temperature is updated:

rule "Roof shutter rule"
when
	Channel "astro:sun:local:civilDawn#event" triggered START
	or Channel "astro:sun:local:rise#event" triggered// In case we missed the civil dawn due to temperature below automation threshold
	or Channel "astro:sun:local:noon#event" triggered// In case we missed the morning opening due to cold weather
	or Channel "astro:sun:local:civilDusk#event" triggered END
	// or Channel "astro:sun:local:nauticDusk#event" triggered END
	or Item Wx_OWM_Current_Temperature changed
then
	val String ruleTitle = "RoofShutterRule"

	val Number MIN_TEMP = 3.0|°C

	val boolean temperatureOk = if (
		(Wx_OWM_Current_Temperature.state >= MIN_TEMP)
		&& (Wx_OWM_Current_Temperature.minimumSince(now.minusMinutes(30)).state >= MIN_TEMP.doubleValue) ) { true } else { false }
	val String temperatureInfo = "(now: " + Wx_OWM_Current_Temperature.state.toString + (if (temperatureOk) {" ≥ "} else { " < " }) + MIN_TEMP +
		", lowest in the past 30 minutes: " + Wx_OWM_Current_Temperature.minimumSince(now.minusMinutes(30)).state + " °C"
		+ (if (temperatureOk) {" ≥ "} else { " < " }) + MIN_TEMP + ")"

	val String gShutterState = ( if ( gShutter.state == 100 ) { "DOWN" }
		else if ( gShutter.state == 0 ) { "UP" }
		else { gShutter.state.toString } )

	logInfo(ruleTitle, "Rule has been triggered - gShutter.state is '{}', Temperature is {} {}",
		gShutterState,
		( if ( temperatureOk === true ) { "OK" } else { "not OK" } ),
		temperatureInfo
	)

	if ( now.isBefore(new DateTime(Astro_Civil_Dusk_End.state.toString)) ) {
		// Anytime before the earliest 'shutters down' time: 'fire shutters UP' scenario
		logInfo(ruleTitle, "In Shutter Opening rule scenario - gShutter.state is " + gShutterState + " " + temperatureInfo)
		if ( temperatureOk === true ) {
			gShutter.sendCommand(UP)
			logInfo(ruleTitle, "Raised shutters " + temperatureInfo)
			sendNotification(notificationRecipient, "Raised shutters " + temperatureInfo)
		} else {
			logWarn(ruleTitle, "Shutters not raised, temperature condition not met " + temperatureInfo)
		}
	}
	else {
		// At or after the first 'shutters down' time: 'fire shutters DOWN' scenario
		logInfo(ruleTitle, "In Shutter Closing rule scenario - gShutter.state is " + gShutterState + " " + temperatureInfo)
		if ( temperatureOk === true ) {
			gShutter.sendCommand(DOWN)
			logInfo(ruleTitle, "Lowered shutters")
			sendNotification(notificationRecipient, "Lowered shutters " + temperatureInfo)
		} else {
			logWarn(ruleTitle, "Shutters not lowered, temperature condition not met " + temperatureInfo)
		}

	}
end```

I'm clueless about why it's not working.

Meanwhile I resorted on using a Switch to persist whether the up or down event fired. I also fixed the time check at the start of the rule since it would open the roller shutters at midnight.

The business logic of the rule now reads:

	if ( now.isBefore(new DateTime(Astro_Civil_Dusk_End.state.toString))
		&& now.isAfter(new DateTime(Astro_Civil_Dawn_Start.state.toString)) ) {
		// Anytime before the earliest 'shutters down' time and after the earliest 'shutters up' time:
		// 'fire shutters UP' scenario
		if (AT_Automation_RollerShutters_Opened.state == ON) {
			logDebug(ruleTitle, "Shutters already raised")
		} else {
			logInfo(ruleTitle, "In Shutter Opening rule scenario - gShutter.state is " + gShutterState + " " + temperatureInfo)
			if ( temperatureOk === true ) {
				gShutter.sendCommand(UP)
				logInfo(ruleTitle, "Raised shutters " + temperatureInfo)
				AT_Automation_RollerShutters_Opened.postUpdate(ON)
				sendNotification(notificationRecipient, "Raised shutters " + temperatureInfo)
			} else {
				logWarn(ruleTitle, "Shutters not raised, temperature condition not met " + temperatureInfo)
			}
		}
	}
	else {
		// At or after the first 'shutters down' time: 'fire shutters DOWN' scenario
		if (AT_Automation_RollerShutters_Opened.state == OFF) {
			logDebug(ruleTitle, "Shutters already lowered")
		} else {
			logInfo(ruleTitle, "In Shutter Closing rule scenario - gShutter.state is " + gShutterState + " " + temperatureInfo)
			if ( temperatureOk === true ) {
				gShutter.sendCommand(DOWN)
				logInfo(ruleTitle, "Lowered shutters")
				AT_Automation_RollerShutters_Opened.postUpdate(OFF)
				sendNotification(notificationRecipient, "Lowered shutters " + temperatureInfo)
			} else {
				logWarn(ruleTitle, "Shutters not lowered, temperature condition not met " + temperatureInfo)
			}
		}
	}

The Item I’m using:

Switch AT_Automation_RollerShutters_Opened "Roof shutters opened automatically"

When OpenHAB restarts, AT_Automation_RollerShutters_Opened is NULL which doesn’t adversely interfere with the rule. This is a workaround that seems to do the job.

As you can see from the log,

  • The rule works
  • State of gShutter remains bogus and unreliable (it reports DOWN where the shutters are all UP)
2019-03-13 15:15:24.338 [INFO ] [arthome.model.script.RoofShutterRule] - Rule has been triggered - gShutter.state is 'DOWN', Temperature is OK (now: 8.79 °C ≥ 3.0 °C, lowest in the past 30 minutes: 8.79 °C ≥ 3.0 °C)
2019-03-13 15:15:24.362 [WARN ] [arthome.model.script.RoofShutterRule] - Shutters already raised
2019-03-13 15:20:55.533 [INFO ] [arthome.model.script.RoofShutterRule] - Rule has been triggered - gShutter.state is 'DOWN', Temperature is OK (now: 8.79 °C ≥ 3.0 °C, lowest in the past 30 minutes: 8.79 °C ≥ 3.0 °C)
2019-03-13 15:20:55.641 [INFO ] [arthome.model.script.RoofShutterRule] - In Shutter Opening rule scenario - gShutter.state is DOWN (now: 8.79 °C ≥ 3.0 °C, lowest in the past 30 minutes: 8.79 °C ≥ 3.0 °C)
2019-03-13 15:20:55.648 [INFO ] [arthome.model.script.RoofShutterRule] - Raised shutters (now: 8.79 °C ≥ 3.0 °C, lowest in the past 30 minutes: 8.79 °C ≥ 3.0 °C)
2019-03-13 15:30:51.864 [INFO ] [arthome.model.script.RoofShutterRule] - Rule has been triggered - gShutter.state is 'DOWN', Temperature is OK (now: 8.62 °C ≥ 3.0 °C, lowest in the past 30 minutes: 8.62 °C ≥ 3.0 °C)
2019-03-13 15:30:51.879 [WARN ] [arthome.model.script.RoofShutterRule] - Shutters already raised
2019-03-13 15:40:51.025 [INFO ] [arthome.model.script.RoofShutterRule] - Rule has been triggered - gShutter.state is 'DOWN', Temperature is OK (now: 8.68 °C ≥ 3.0 °C, lowest in the past 30 minutes: 8.62 °C ≥ 3.0 °C)
2019-03-13 15:40:51.046 [WARN ] [arthome.model.script.RoofShutterRule] - Shutters already raised

Is your Command Polling Interval still set to the default? You would need zwave logs to determine exactly what it shiuld be set to, but you could just set it to the max and see if that fixes it.

I’m not at all surprised. Items of type Rollershutter have states 0-100, not UP/DOWN

Good to know. I replaced ‘UP’ with 0 and ‘DOWN’ with 100, but I still get inconsistent states.

You’ll probably have to elaborate on that, with event.log maybe

What exactly did you replace ? You cannot use OR in the group definition with values 0-100.
I didn’t follow your code but to rely on auto computed group state is an approach destined to fail for a number of reasons (value range being one of these, non-consistent behavior among devices being another one). You need to work on each member’s individual state instead.

Just FWIW, here’s a framework:

Granted it’s not elegant in terms of code, but as a matter of fact it is easier in the shutter use context to not use groups as there’s many “but what if” exceptions to apply to ever-differing groups of devices only.

Here’s what event.log told me this morning:

2019-03-14 06:28:01.070 [vent.ChannelTriggeredEvent] - astro:sun:local:nauticDawn#event triggered END
2019-03-14 06:28:01.074 [vent.ChannelTriggeredEvent] - astro:sun:local:civilDawn#event triggered START
2019-03-14 06:28:01.213 [ome.event.ItemCommandEvent] - Item 'gShutter' received command UP
2019-03-14 06:28:01.218 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_N' received command UP
2019-03-14 06:28:01.247 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_SE' received command UP
2019-03-14 06:28:01.264 [ome.event.ItemCommandEvent] - Item 'AT_Shutter_SW' received command UP
2019-03-14 06:28:01.268 [nt.ItemStatePredictedEvent] - AT_Shutter_N predicted to become UP
2019-03-14 06:28:01.273 [nt.ItemStatePredictedEvent] - AT_Shutter_SE predicted to become UP
2019-03-14 06:28:01.278 [vent.ItemStateChangedEvent] - AT_Automation_RollerShutters_Opened changed from OFF to ON
2019-03-14 06:28:01.280 [nt.ItemStatePredictedEvent] - AT_Shutter_SW predicted to become UP
2019-03-14 06:28:01.283 [vent.ItemStateChangedEvent] - AT_Shutter_SE changed from 100 to 0
2019-03-14 06:28:01.286 [vent.ItemStateChangedEvent] - AT_Shutter_SW changed from 100 to 0

As you can see, AT_Shutter_N apparently didn’t report its state change, although the closed shutter opened. But now the states are apparently more properly reported.

I replaced the OR(UP,DOWN) in the group definitions.

What I infer from your reply, is that it’s not reliable to infer group state with OR, so I’ll probably have to use a rule to set a group state proxy item whenever a member of gShutter changed state, in case I want to be able to assess the consolidated state.

You already said that but didn’t say what you’ve replaced it with.

Almost but not exactly. The point is that group state isn’t a meaningful value with range like data types such as a roller shutter (no matter if you use OR, SUM, AVG or whatever).
As I said I didn’t dive into your code to see how you use it and what you would need to change that into. No time for that sorry. But don’t use group state please, it’ll flaw your logic.
And before you hit that pitfall: mind that OPEN is not equivalent to (state == 0). Some shutters never reach 0 when driven up, even after calibration. YMMV as it depends on the devices but from experience, many people stumble across this sooner or later.