Rules no longer triggering after a change in an unrelated rule or item file

  • Platform information:
    • Hardware: x64 VM
    • OS: Debian 10
    • Java Runtime Environment: JDK11
    • openHAB version: 3.0
  • Issue of the topic:
    I have noticed that When I am working in rules it can cause existing running rules to stop triggering.
    You can try and manually run the rule or even restart OH but it will stay dead.
    Until you alter the rule and save it again. Even just adding a empty line and saving it will make the rule work again with no issues.
    The log doesn’t show any output and OH otherwise appears to work just fine, it only forgets about rules it seems.

Now this is an issue you can work around by going around your rules altering them all slightly and then saving again. But that is still inconvenient, and obviously if you forget a rule that rule will no longer be triggered.

Below a couple of examples of the kind of rules that get affected by this.

// Imports
import java.util.concurrent.locks.ReentrantLock

// Global Variables
var ReentrantLock genericThermostatLock = new ReentrantLock()

rule "Central Heater"
when
  Item Radiators changed
then
  if (Gas_Heater.state.toString != Radiators.state.toString) {
    if ( Gas_Heater.changedSince(now.minusMinutes(1))) {
      createTimer(now.plusSeconds(30), [| Gas_Heater.sendCommand(Radiators.state.toString) ] )
    } else {
      Gas_Heater.sendCommand(Radiators.state.toString)
    }
    Heating.sendCommand(Radiators.state.toString)
  }
end

rule "Generic Thermostat"
when
  Item Thermostats changed or
  Item Thermometers changed
then
  if (genericThermostatLock.tryLock()) {
    Thermostats.members.forEach[thermostat|
      // in order for this generic rule to work the naming of thermometer, Radiator, fan, and thermostat items needs to follow the convention
      // <RoomName>_<temp, valve, fan, target>
      val thermostatNameStart = thermostat.name.split("_").get(0)
      val temperatureName = thermostatNameStart + "_temp"
      val targetName = thermostatNameStart + "_target"
      val valveName = thermostatNameStart + "_valve"
      val stateName = thermostatNameStart + "_state"
      val current_temperature = Thermometers.members.filter[ tt| tt.name == temperatureName ].head as NumberItem
      val target_temperature = Thermostats.members.filter[ th|th.name == targetName ].head as NumberItem
      val hvac_state = Thermostats.members.filter[ th|th.name == stateName ].head as StringItem
      val radiatorvalve = Radiators.members.filter[ h|h.name == valveName ].head as SwitchItem
      if ((current_temperature.state as Number) < (target_temperature.state as Number) - 0.35 ) {
        if (radiatorvalve.state.toString == "OFF") {
          radiatorvalve.sendCommand("ON")
          hvac_state.sendCommand("Heating")
          return;
        }
      }
      if ((current_temperature.state as Number) > (target_temperature.state as Number) + 0.35 ) {
        if ( radiatorvalve.state.toString == "ON" ) {
          radiatorvalve.sendCommand("OFF")
          hvac_state.sendCommand("Idle")
          return;
        }
      }
      val fanName = thermostatNameStart + "_fan"
      val roomventilation = RoomVentilators.members.filter[ h|h.name == fanName ].head as SwitchItem
      val fan_target_temperature = if ( (target_temperature.state as Number) < (FanThermostat.state as Number)) {
            FanThermostat
        } else {
            target_temperature
        }
      if ( (current_temperature.state as Number) > (fan_target_temperature.state as Number) + 3.35 ) {
        if (roomventilation.state.toString == "OFF") {
          roomventilation.sendCommand("ON")
          hvac_state.sendCommand("Cooling")
          return;
        }
      }
      if ( (current_temperature.state as Number) < (fan_target_temperature.state as Number) + 2.65 ) {
        if ( roomventilation.state.toString == "ON" ) {
          roomventilation.sendCommand("OFF")
          hvac_state.sendCommand("Idle")
          return;
        }
      }
    ]
  }
end

rule "Thermostat Checker and Setter"
when
  Time cron "0 */2 * ? * *"
then
  Thermostats.members.forEach[thermostat|
    if (thermostat.state == NULL || (thermostat.state as Number) < 10) {
        thermostat.sendCommand(10)
    } else {
      if ((thermostat.state as Number) > 21) {
        thermostat.sendCommand(21)
      }
    }
  ]
  if (FanThermostat.state == NULL || (FanThermostat.state as Number) < 18) {
      FanThermostat.sendCommand(18)
  } else {
    if ((FanThermostat.state as Number) > 32) {
        FanThermostat.sendCommand(32)
    }
  }
end
// Imports
import java.util.concurrent.locks.ReentrantLock
// Global Variables
var ReentrantLock genericZoneLock = new ReentrantLock()
var motionsensorstate = "IDLE"

rule "Generic Zone"
when
  Item MotionSensor_1_TestZone received update
then
  if (genericZoneLock.tryLock()) {
    SecurityZones.members.forEach[zone|
      motionsensorstate = "IDLE"
      (zone  as GroupItem).members.filter[ m| m.name.startsWith("MotionSensor")].forEach[motionsensor|
        if (motionsensor.state.toString != "OPEN" ) {
          motionsensorstate = "ACTIVE"
        }
      ]
      val zonestate = (zone  as GroupItem).members.filter[ m| m.name.startsWith("State")].head  as StringItem
      val armed = (zone  as GroupItem).members.filter[ m| m.name.startsWith("Armed")].head  as SwitchItem
      val maintennance = (zone  as GroupItem).members.filter[ m| m.name.startsWith("Maintennance")].head  as SwitchItem
      if (motionsensorstate == "ACTIVE" && armed.state.toString != "OFF" && maintennance.state.toString != "ON") {
        zonestate.sendCommand("ALARM")
      }
      if (motionsensorstate == "ACTIVE" && (armed.state.toString == "OFF" || maintennance.state.toString == "ON")) {
        zonestate.sendCommand("OCUPIED")
      }
      if (motionsensorstate == "IDLE") {
        zonestate.sendCommand("IDLE")
      }
    ]
  }
end

Yes.

The first red flag that stands out is the ReentrantLock. In OH 2.5, use of a reentrant lock was always exceptionally dangerous to use. If an error occurs after you acquire the lock and before you return the lock, it will remain locked forever, or at least until the rule is reloaded. At best that will cause the rule to never be able to run the locked code gain. At worst, if you wait for the lock it can use up all your rules execution threads waiting for a lock that will never be returned and all your rules will stop running.

In OH 3, a reentrant lock is far less dangerous and in truth it’s pointless. No more than one instance of a given rule can run at the same time so if everything is working, the rule will never encounter a case where it can’t acquire the lock. However, by using the lock the above still applies. If there is an error before returning the lock the lock will remain locked and the rule can’t run the locked code until the rule is reloaded.

So, since you are on OH 3, I suggest your first eliminate the locks. They are don’t do anything and only open you up to problems like these.

1 Like

This is good to know, that indeed changes the way you design and expect rules to behave.
I will remove them and see if I still notice the same behavior.

I don’t think this is the same issue because the rule is not gone from the UI and I can still try to manually trigger it. Also upon restarting OH the rules stay in the UI.
They are just no longer triggering.
I also mainly define the items in the UI and not in files so the item file names and the rule files names do not match. The preference of OH3 seems to be defining things/items/rules in the UI so i try and stick with that as much as possible.

I only use rule files because the UI options for designing rules though rather nice are not quite as complete as the rules files. I quite like the mainly UI direction. But a lot of the examples are still based on OH 2 so which require some fiddling in files.

I may be encountering a similar issue here. I just noticed that none of my rules seem to be triggering anymore however I cannot really fix it by saving rules again:

When I’m in the UI looking at the rule while switching the light via a physical light switch which is used as rule trigger I don’t see the rule executing (remains idle). However after I save the rule once more unchanged I see that the rule is running once when I’m using the switch again (rule changes to running state). However the actions of the rule are not executed and every following switching (the physical wall switch) does not trigger the rule unless I have saved it once more.

Restarting OH (Docker Container) or rebooting the host does not solve the problem. The events log shows that all switching events are registered by OH. The openhab.log does not show anything unusual.

Edit: I was completely on the wrong track… I could basically just delete everything I wrote above but I will try to explain. „None of my rules“ was simply not true. The rules that I realised were not working were all relying on my night time item to be true. For whatever reasons my system did not switch this item to true tonight thus resulting in my night rules not being executed. I still don’t understand why OH was still thinking it was daytime but at least the rules are working as expected.

Just a quick hint when looking into problems like this. It’s not always reliable to watch the UI to see your rules running because, especially when blocked by a condition, it transitions so fast you wouldn’t see it. So if you want to know for sure whether your rules are running you have two options.

  1. Open the developer sidebar (alt-shift-d) and start the event stream. This will show you all the events happening on OH which includes rules transitioning from idle to running and then back again.

  2. Modify log4j2.xml and change the entry for the RuleStatusInfoEvent from ERROR to INFO and that will add the rule events to events.log.

1 Like