Not getting error statements when loading/parsing rules file

I’m partway through the journey - I have it working as I wish it to work using the legacy rules DSL. I’m still considering stepping into the new language, and haven’t decided which language would suit me most. It needs to wait until I’m less busy at work.

In case anyone is having similar issues or otherwise comes to this thread looking for help, I’m posting the full rules file that is now working quite well. Key areas of change were:

  • declare most of my procedures as elements within a hash. This is ugly, but it means that I don’t have to keep passing all the procedures into every procedure call - the rules DSL doesn’t deal with globally declared procedures well and if you want one procedure to call another procedure (lambda really I guess) you have to explicitly pass it in. I still kept a couple outside the hash, because the downside of the hash is calling them directly is ugly - and my event based rules call into these directly - so a couple are outside the hash, a few inside the hash, and some kind of live in both places.
  • similarly, declaring my various config data all within one big hash called “StaticData”, which also holds those procedures above. This avoids passing lots of parameters around
  • removing a lot of types from things. Visual Studio Code (actually the DSL I think) complains that things are untyped, so in the past I’d put types on them. But you can just ignore those warnings, and it’s less likely to tie itself in knots and declare your syntax to be invalid. This means casting many things when you need to use them
  • generally cleaning up the code to generalise some concepts better. In particular I didn’t want to turn outside lights on, then walk past a motion sensor, and have the motion sensor turn that light off (I turned it on for a reason). So I wrote a concept of triggers - a light can be turned on by multiple events, it doesn’t turn off until all those triggers clear. But, I do want to turn lights off by a switch different than the one that turned it on…so the trigger concept applies to all timers and motion sensors, but when I turn a light off with a switch I want it to go off now, not wait for the timers
  • other than syntax, the main thing I cannot easily do is generalise the channel calls - the Shellys trigger on a channel for the button presses rather than being a switch event or similar. That means you can’t use a group to handle them as a job lot. There are ways, but in the end it was easier to just enumerate them as a list of rules than it was to mess with that. Somewhere I’d have had to enumerate them anyway, so I’d really just be shifting that from rules to items. It feels better, but it’s not really less work

All good fun, and other than usual syntax messing around, it works pretty cleanly now. The Shellys are a bit more fussy than I might have expected, losing their config and needing restarting. They seem to have settled down now that I’m not changing them, so we’ll see if they’re stable across events like power cuts.

import java.util.HashMap
import java.util.List

// Static Config
val HashMap StaticData = newHashMap(
  "SinglePress" -> newHashMap(
    "SensorDaybed"  -> (newArrayList ("SensorDaybed", "SensorDining")),
    "SensorStudy"   -> (newArrayList ("SensorStudy", "SensorLaundry", "SensorBoot")),
    "SensorLaundry" -> (newArrayList ("SensorLaundry", "SensorStudy", "SensorBoot")),
    "SensorAtrium"  -> (newArrayList ("SensorAtrium")),
    "SensorBoot"    -> (newArrayList ("SensorBoot", "SensorKitchen", "SensorGarage", "SensorDining")),
    "SensorGarage"  -> (newArrayList ("SensorGarage", "SensorKitchen", "SensorBoot", "SensorDining")),
    "SensorKitchen" -> (newArrayList ("SensorKitchen", "SensorGarage", "SensorDining")),
    "SensorDining"  -> (newArrayList ("SensorDining", "SensorKitchen", "SensorGarage", "SensorDaybed")),
    "FloodLounge"   -> (newArrayList ("FloodLounge")),
    "FloodMaster"   -> (newArrayList ("FloodMaster")),
    "FloodTractor"  -> (newArrayList ("FloodTractor")),
    "LightGarage"   -> (newArrayList ("LightGarage")),
    "LightBootDoor" -> (newArrayList ("LightBootDoor", "LightBootHall")),
    "LightBootHall" -> (newArrayList ("LightBootHall", "LightBootDoor")),
    "Triple"        -> (newArrayList ("SensorDaybed", "SensorDining", "SensorStudy", "SensorAtrium", "SensorLaundry", "SensorBoot", "SensorGarage", "SensorKitchen", "FloodLounge", "FloodMaster", "FloodTractor")),
    "AllOff"        -> (newArrayList ("SensorDaybed", "SensorDining", "SensorStudy", "SensorAtrium", "SensorLaundry", "SensorBoot", "SensorGarage", "SensorKitchen", "FloodLounge", "FloodMaster", "FloodTractor", "LightBootHall", "LightBootDoor"))
  ),

  "DoublePress" -> newHashMap(
    "SensorDaybed"  -> (newArrayList ("SensorDaybed", "SensorDining", "FloodLounge", "FloodMaster", "FloodTractor")),
    "SensorStudy"   -> (newArrayList ("SensorStudy", "SensorLaundry", "SensorBoot", "SensorDaybed", "SensorDining", "FloodLounge", "FloodMaster", "FloodTractor")),
    "SensorLaundry" -> (newArrayList ("SensorLaundry", "SensorStudy", "SensorBoot", "SensorAtrium")),
    "SensorAtrium"  -> (newArrayList ("SensorAtrium", "SensorStudy", "SensorLaundry", "SensorBoot")),
    "SensorBoot"    -> (newArrayList ("SensorBoot", "SensorStudy", "SensorLaundry", "SensorKitchen", "SensorGarage", "SensorDining", "FloodLounge", "FloodMaster", "FloodTractor")),
    "SensorGarage"  -> (newArrayList ("SensorGarage", "SensorKitchen", "SensorDining", "SensorBoot", "LightBootDoor")),
    "SensorKitchen" -> (newArrayList ("SensorKitchen", "SensorGarage", "SensorDining", "FloodLounge")),
    "SensorDining"  -> (newArrayList ("SensorKitchen", "SensorGarage", "SensorDining", "SensorDaybed", "FloodLounge", "FloodMaster", "FloodTractor")),
    "FloodLounge"   -> (newArrayList ("FloodLounge", "FloodMaster", "FloodTractor", "SensorDining", "SensorDaybed")),
    "FloodMaster"   -> (newArrayList ("SensorDaybed", "SensorDining", "SensorStudy", "SensorLaundry", "SensorAtrium", "SensorBoot", "SensorGarage", "SensorKitchen", "FloodLounge", "FloodMaster", "FloodTractor")),
    "FloodTractor"  -> (newArrayList ("FloodTractor", "FloodMaster", "FloodLounge", "SensorDining", "SensorDaybed")),
    "LightGarage"   -> (newArrayList ("LightGarage", "SensorKitchen", "SensorGarage", "SensorBoot", "LightBootDoor")),
    "LightBootDoor" -> (newArrayList ("LightBootDoor", "LightBootHall", "SensorBoot", "SensorKitchen", "SensorGarage")),
    "LightBootHall" -> (newArrayList ("LightBootHall", "LightBootDoor"))
  ),

  "LightTime" -> newHashMap(
    "SensorDaybed"  -> 10,
    "SensorStudy"   -> 10,
    "SensorLaundry" -> 10,
    "SensorAtrium"  -> 5,
    "SensorBoot"    -> 5,
    "SensorGarage"  -> 10,
    "SensorKitchen" -> 10,
    "SensorDining"  -> 5,
    "FloodLounge"   -> 15,
    "FloodMaster"  -> 5,
    "FloodTractor"  -> 5,
    "LightGarage"   -> 5,
    "LightBootDoor" -> 10,
    "LightBootHall" -> 10,
    "Triple"        -> 10
  ),

  "LightTimers" -> newHashMap(),

  "LightTriggers" -> newHashMap(
    "SensorDaybed"  -> (newHashMap ()),
    "SensorStudy"   -> (newHashMap ()),
    "SensorLaundry" -> (newHashMap ()),
    "SensorAtrium"  -> (newHashMap ()),
    "SensorBoot"    -> (newHashMap ()),
    "SensorGarage"  -> (newHashMap ()),
    "SensorKitchen" -> (newHashMap ()),
    "SensorDining"  -> (newHashMap ()),
    "FloodLounge"   -> (newHashMap ()),
    "FloodMaster"   -> (newHashMap ()),
    "FloodTractor"  -> (newHashMap ()),
    "LightGarage"   -> (newHashMap ()),
    "LightBootDoor" -> (newHashMap ()),
    "LightBootHall" -> (newHashMap ())  
  ),

  "MotionLights" -> newHashMap(
    "MotionBoot"     -> (newArrayList("SensorBoot", "SensorGarage", "SensorKitchen", "LightBootDoor", "SensorLaundry", "SensorStudy")),
    "MotionKitchen"  -> (newArrayList("SensorKitchen", "SensorDining", "SensorGarage")),
    "MotionDining"   -> (newArrayList("SensorDining", "SensorDaybed", "SensorKitchen")),
    "MotionStudy"    -> (newArrayList("SensorStudy", "SensorLaundry", "SensorBoot", "SensorDaybed")),
    "MotionDaybed"   -> (newArrayList("SensorDaybed", "SensorDining"))
  ),

  /*
  Logic flow for button presses

  Light already off:
    Short - add this button to the triggers for each of the short lights, set a timer for the specified length to turn them off again
    Double - add this button to the triggers for each of the long lights, set a timer for the specified length to turn them off again
    Long - add this button to the triggers for each of the short lights, no timer
    Triple - add this button to the triggers for all outside lights, set a timer

  Light already on:
    Short - remove triggers from all lights for this button, cancel timer, then recalc lights
    Double - add this button to the triggers for each of the long lights, reset the timer
    Long - cancel timer, leave lights on
    Triple - turn all outside lights off irrespective of how they were turned on, clear timer

  End of timer:
    Turn off all lights that were turned on by this switch (based on the LightTriggers list)

  Timers need to be tied to the button that initiated them, so we can cancel them again. That implies a 
  hashmap that holds them all.
  */


  /* 
    Set a light timer to turn lights off based on the configured timer length for this light switch.
    If there is a pre-existing timer, cancel it first
  */

  "SetLightTimer" -> [ 
    switchName,
    StaticData |

    logDebug( 'lights', 'Setting a timer for ' + switchName)

    val org.eclipse.xtext.xbase.lib.Procedures$Procedure2 LightTimerEnd = (StaticData as HashMap).get("LightTimerEnd")
    val LightTimers = (StaticData as HashMap).get("LightTimers") as HashMap<String, Timer>
    val LightTime = (StaticData as HashMap).get("LightTime") as HashMap<String, Long>

    if( LightTimers.get(switchName) !== null ){
      LightTimers.get(switchName).cancel
      LightTimers.put(switchName, null)
    }

    LightTimers.put( switchName, createTimer(new DateTimeType(ZonedDateTime.now()).getZonedDateTime().plusSeconds(LightTime.get(switchName) * 60)) [|{
      // at end of timer, turn off all lights that may have been turned on
      logDebug( 'lights', 'Timer turning off lights for ' + switchName)
      LightTimerEnd.apply( switchName, StaticData )
    }])
  ],


  /*
    When a light timer ends, remove this trigger from the LightTriggers for each light, then
    recalc light state for each light (i.e. turn off the lights we turned on).
    Note that light triggers can be motion as well as switches, hence sourceName
  */

  "LightTimerEnd" -> [ 
    sourceName,
    StaticData |
    logDebug( 'lights', 'Processing timer end for ' + sourceName )

    val org.eclipse.xtext.xbase.lib.Procedures$Procedure1 RecalcLights = (StaticData as HashMap).get("RecalcLights")
    val LightTriggers = (StaticData as HashMap).get("LightTriggers") as HashMap<String, HashMap<String, String>>
    val LightTimers = (StaticData as HashMap).get("LightTimers") as HashMap<String, Timer>

    LightTriggers.keySet.forEach[ triggerKey | 
      LightTriggers.get(triggerKey).remove(sourceName)
    ]
    if( LightTimers.get(sourceName) !== null ){
      LightTimers.get(sourceName).cancel()
      LightTimers.put(sourceName, null) 
    }
    RecalcLights.apply( StaticData )
    if(sourceName == "Triple"){
      swLightTriplePress.postUpdate(OFF)
    }
    if(sourceName == "FloodLounge"){
      swFloodLights.postUpdate(OFF)
    }
  ],

  /*
    When a switch is used to turn off then we actually turn off every light
    this switch controls, whether this switch turned it on or not. That is
    what we think people expect. Then we recalc light state for each light.
  */

  "SwitchOff" -> [ 
    switchName,
    StaticData |
    logDebug( 'lights', 'Processing switch off (from short press or from UI) for ' + switchName )
    
    val org.eclipse.xtext.xbase.lib.Procedures$Procedure1 RecalcLights = (StaticData as HashMap).get("RecalcLights")
    val LightTriggers = (StaticData as HashMap).get("LightTriggers") as HashMap<String, HashMap<String, String>>
    val LightTimers = (StaticData as HashMap).get("LightTimers") as HashMap<String, Timer>
    val DoublePress = ((StaticData as HashMap).get("DoublePress") as HashMap<String, List<String>>)

    DoublePress.get(switchName).forEach[ triggerKey | 
      LightTriggers.get(triggerKey).clear()
    ]
    if( LightTimers.get(switchName) !== null ){
      LightTimers.get(switchName).cancel()
      LightTimers.put(switchName, null) 
    }
    RecalcLights.apply( StaticData )
  ],

  /*
    Recalc light state for each light (turning it on or off as appropriate based on whether it has any
    triggers remaining open)
  */

  "RecalcLights" -> [
    StaticData |
    logDebug( 'lights', 'Recalculating lights' )

    val LightTriggers = (StaticData as HashMap).get("LightTriggers") as HashMap<String, HashMap<String, String>>

    var String sDebug = ""
    LightTriggers.keySet.forEach[ triggerKey | 
      var SwitchItem swLightOutput = LightOutputs.members.filter( ou | ou.name.startsWith( 'sw' + triggerKey ) ).last as SwitchItem
      var SwitchItem swLightInput = LightInputs.members.filter( in | in.name.startsWith( 'sw' + triggerKey ) ).last as SwitchItem

      sDebug = sDebug.concat( triggerKey + ":" + LightTriggers.get(triggerKey).size() + " ")
      if( LightTriggers.get(triggerKey).size() == 0 ) {
        swLightOutput.sendCommand(OFF)
        swLightInput.postUpdate(OFF)
      } else {
        swLightOutput.sendCommand(ON)
      }
    ]
    logDebug( 'lights', sDebug )
  ]
)

/*
  Calculate what to do based on a light button press
*/
val ProcessButtonPress = [
  switchName,
  sEvent,
  StaticData |
  logDebug( 'lights', 'Processing button press for ' + switchName + ' event was ' + sEvent )

  val org.eclipse.xtext.xbase.lib.Procedures$Procedure2 SetLightTimer = (StaticData as HashMap).get("SetLightTimer")
  val org.eclipse.xtext.xbase.lib.Procedures$Procedure2 SwitchOff = (StaticData as HashMap).get("SwitchOff")
  val org.eclipse.xtext.xbase.lib.Procedures$Procedure1 RecalcLights = (StaticData as HashMap).get("RecalcLights")
  val SinglePress = (StaticData as HashMap).get("SinglePress") as HashMap<String, List<String>>
  val DoublePress = (StaticData as HashMap).get("DoublePress") as HashMap<String, List<String>>
  val LightTriggers = (StaticData as HashMap).get("LightTriggers") as HashMap<String, HashMap<String, String>>
  val LightTimers = (StaticData as HashMap).get("LightTimers") as HashMap<String, Timer>

  var SwitchItem swLightOutput = LightOutputs.members.filter( ou | ou.name.startsWith( 'sw' + SinglePress.get(switchName).get(0))).last as SwitchItem

  // take action depending on what sort of button press we got
  switch sEvent {
    case 'SHORT_PRESSED': {
      // add this button to the triggers for each of the short lights, set a timer for the specified length to turn them off again
      logDebug( 'lights', 'We got a short press')
      if( swLightOutput.state == OFF){
        SinglePress.get(switchName).forEach [ lightOutput |
          LightTriggers.get(lightOutput).put(switchName, switchName)
        ]
        RecalcLights.apply( StaticData )
        SetLightTimer.apply( switchName, StaticData )
      } else {  // remove triggers from all lights for this button, cancel timer, then recalc lights
        logDebug( 'lights', 'Light currently on, turning off all lights turned on by ' + switchName)
        SwitchOff.apply(switchName, StaticData)
      }
    }

    case 'DOUBLE_PRESSED': {
      // turn on all the double press lights and set a timer
      logDebug( 'lights', 'We got a double press')

      DoublePress.get(switchName).forEach[ lightOutput |
        LightTriggers.get(lightOutput).put(switchName, switchName)
      ]
        RecalcLights.apply( StaticData )
      SetLightTimer.apply( switchName, StaticData )
    }

    case 'LONG_PRESSED': {
      // cancel a timer if there is one, turn on the small set of lights. If the large set of lights are on, they'll stay on
      logDebug( 'lights', 'We got a long press')
      if( swLightOutput.state == ON){
        if( LightTimers.get(switchName) !== null ){
          LightTimers.get(switchName).cancel()
          LightTimers.put(switchName, null) 
        } else {
          logInfo( 'lights', 'Long press for light thats already on ' + switchName + ' and there was no timer, which could be a problem')
        }
      }
      SinglePress.get(switchName).forEach [ lightOutput |
        LightTriggers.get(lightOutput).put(switchName, switchName)
      ]
      RecalcLights.apply( StaticData )
    }

    case 'TRIPLE_PRESSED': {
      // If this light isn't on, turn on all outside lights with a timer. If this light is on, turn off all lights and clear all triggers and all timers
      logDebug( 'lights', 'We got a triple press')
      if( swLightOutput.state == ON){
        LightTimers.keySet.forEach[ timerKey | 
          if( LightTimers.get(switchName) !== null ){
            LightTimers.get(switchName).cancel()
            LightTimers.put(switchName, null) 
          }
        ]
        SinglePress.get("AllOff").forEach[ light |
          LightTriggers.get(light).clear()
        ]
        RecalcLights.apply( StaticData )
      } else {
        SinglePress.get("Triple").forEach[ lightOutput |
          LightTriggers.get(lightOutput).put('Triple', switchName)
        ]
        RecalcLights.apply( StaticData )
        SetLightTimer.apply( 'Triple', StaticData )
      }
    }

    default: {
      logInfo( 'lights', 'Received unhandled event ' + sEvent + ' on item ' + switchName )
    }
  }
]


/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
  MOTION SENSORS
*/

// Static Config



/*
Logic flow for motion sensors

Timer already active:
  Cancel timer, restart it. Keep the list of lights turned on

Timer not already active:
  For each light
    Light already on - do nothing
    Light not on - turn it on, and remember it was turned on for timer turn off

End of timer:
  Turn off all lights that were turned on by this motion sensor

Sensitivity:
  Keep track of every new turn on. If it happens more than 3 times in a half hour, turn down sensitivity by 20 points, set 
  a timer to adjust it back up by 20 points in 4 hours
  When adjust back up, if it's not at maxSensitivity, then set another timer to adjust it up another 20 points (or to max sensitivity)
*/

val ProcessMotion = [
  motionName,
  StaticData |
  logDebug( 'motion', 'Processing motion event for ' + motionName )

  val org.eclipse.xtext.xbase.lib.Procedures$Procedure1 RecalcLights = (StaticData as HashMap).get("RecalcLights")
  val HashMap< String, List<String>> MotionLights = (StaticData as HashMap).get("MotionLights")
  val LightTriggers = (StaticData as HashMap).get("LightTriggers") as HashMap<String, HashMap<String, String>>

  var StringItem strIllumination = MotionIlluminations.members.filter( il | il.name.startsWith( 'str' + motionName )).last as StringItem

  // turn on lights    
  if(strIllumination.state != "dark") { 
    logDebug( 'motion', "It's not dark out, it's " + strIllumination.state + " don't turn on lights for " + motionName)
  } else {
    logDebug( 'motion', "It's dark out, turn on lights")
    MotionLights.get(motionName).forEach[ lightOutput |
      LightTriggers.get(lightOutput).put(motionName, motionName)
    ]
    RecalcLights.apply( StaticData )
  }

  // still need to write logic to check the list of trigger events and dial down sensitivity, and set timer to dial sensitivity back up

]

// sensor now inactive, turn off each light that was turned on
val EndMotion = [
  String motionName,
  StaticData |

  logDebug( 'motion', 'Processing end of motion event for ' + motionName )

  val org.eclipse.xtext.xbase.lib.Procedures$Procedure2 LightTimerEnd = (StaticData as HashMap).get("LightTimerEnd")
  LightTimerEnd.apply( motionName, StaticData )  
]




rule "Shelly button flood lounge triggered"
when
  Channel "shelly:shellyplus1:a8032aba16f8:relay#button" triggered 
then
  ProcessButtonPress.apply( "FloodLounge", receivedEvent, StaticData )
end

rule "Shelly button sensor dining triggered"
when
  Channel "shelly:shellyplus1:441793aa6bf8:relay#button" triggered 
then
  ProcessButtonPress.apply( "SensorDining", receivedEvent, StaticData )
end

rule "Shelly button sensor study triggered"
when
  Channel "shelly:shellyplus1:a8032aba1704:relay#button" triggered 
then
  ProcessButtonPress.apply( "SensorStudy", receivedEvent, StaticData )
end

rule "Shelly button sensor laundry triggered"
when
  Channel "shelly:shellyplus1:441793a950cc:relay#button" triggered 
then
  ProcessButtonPress.apply( "SensorLaundry", receivedEvent, StaticData )
end

rule "Shelly button sensor atrium triggered"
when
  Channel "shelly:shellyplus1:441793a86d40:relay#button" triggered 
then
  ProcessButtonPress.apply( "SensorAtrium", receivedEvent, StaticData )
end

rule "Shelly button sensor boot triggered"
when
  Channel "shelly:shellyplus1:441793a94fe8:relay#button" triggered 
then
  ProcessButtonPress.apply( "SensorBoot", receivedEvent, StaticData )
end

rule "Shelly button sensor kitchen triggered"
when
  Channel "shelly:shellyplus1:083af2028610:relay#button" triggered 
then
  ProcessButtonPress.apply( "SensorKitchen", receivedEvent, StaticData )
end

rule "Shelly button flood tractor triggered"
when
  Channel "shelly:shellyplus1:441793aa9040:relay#button" triggered 
then
  ProcessButtonPress.apply( "FloodTractor", receivedEvent, StaticData )
end

rule "Shelly button light boot door triggered"
when
  Channel "shelly:shellyplus1:083af202a350:relay#button" triggered 
then
  ProcessButtonPress.apply( "LightBootDoor", receivedEvent, StaticData )
end

rule "Shelly button light boot hall triggered"
when
  Channel "shelly:shellyplus1:083af2029730:relay#button" triggered 
then
  ProcessButtonPress.apply( "LightBootHall", receivedEvent, StaticData )
end

rule "Shelly button sensor daybed triggered"
when
  Channel "shelly:shellyplus2pm-relay:80646fdbe00c:relay1#button" triggered 
then
  ProcessButtonPress.apply( "SensorDaybed", receivedEvent, StaticData )
end

rule "Shelly button flood master triggered"
when
  Channel "shelly:shellyplus2pm-relay:80646fdbe00c:relay2#button" triggered 
then
  ProcessButtonPress.apply( "FloodMaster", receivedEvent, StaticData )
end

rule "Shelly button sensor garage triggered"
when
  Channel "shelly:shellyplus2pm-relay:80646fdb89e0:relay1#button" triggered 
then
  ProcessButtonPress.apply( "SensorGarage", receivedEvent, StaticData )
end

rule "Shelly button light garage triggered"
when
  Channel "shelly:shellyplus2pm-relay:80646fdb89e0:relay2#button" triggered 
then
  ProcessButtonPress.apply( "LightGarage", receivedEvent, StaticData )
end

rule "Open garage door, turn on garage lights with timer"
when
  Item swGarageDoor changed to ON
then
  logDebug( 'lights', 'Garage door opened, considering turning on lights, its currently ' + strMotionBootIllumination.state)
  if( strMotionBootIllumination.state == 'dark'){
    ProcessButtonPress.apply( "LightGarage", "SHORT_PRESSED", StaticData )
  }
end

rule "Close garage door, turn off garage lights"
when
  Item swGarageDoor changed to OFF
then
  logDebug( 'lights', 'Garage door closed, turning off lights' )
  val org.eclipse.xtext.xbase.lib.Procedures$Procedure2 LightTimerEnd = StaticData.get("LightTimerEnd")
  LightTimerEnd.apply( "LightGarage", StaticData )
end

rule "Manual light turn on from UI"
when
  Member of LightInputs received command ON
then
  val LightInput = triggeringItem
  logDebug( 'lights', 'Light manually turned on for ' + LightInput.name )
  
  val switchName = LightInput.name.substring(2, LightInput.name.length() - 5)
  ProcessButtonPress.apply( switchName, "SHORT_PRESSED", StaticData )
end

rule "Manual light turn off from UI"
when
  Member of LightInputs received command OFF
then
  val LightInput = triggeringItem
  logDebug( 'lights', 'Light manually turned off for ' + LightInput.name )
  
  val switchName = LightInput.name.substring(2, LightInput.name.length() - 5)
  val org.eclipse.xtext.xbase.lib.Procedures$Procedure2 SwitchOff = StaticData.get("SwitchOff")

  SwitchOff.apply( switchName, StaticData )
end

rule "Manual all lights from UI"
// we can triple press on and triple press off, so no need to distinguish on vs off
when
  Item swLightTriplePress received command
then
  logDebug( 'lights', 'Toggle all outside lights (triple press from UI)' )
  
  ProcessButtonPress.apply( 'FloodMaster', 'TRIPLE_PRESSED', StaticData )
end

rule "Manual flood lights from UI"
// we double press on, but we want to single press off
when
  Item swFloodLights received command ON
then
  logDebug( 'lights', 'Turn on flood lights from UI' )
  
  ProcessButtonPress.apply( 'FloodLounge', 'DOUBLE_PRESSED', StaticData )
end

rule "Manual flood lights from UI"
// we double press on, but we want to single press off
when
  Item swFloodLights received command OFF
then
  logDebug( 'lights', 'Turn off flood lights from UI' )
  
  ProcessButtonPress.apply( 'FloodLounge', 'SHORT_PRESSED', StaticData )
end



// Motion rules

rule "Shelly motion boot triggered on"
when
  Item swMotionBootActive changed to ON 
then
  ProcessMotion.apply( "MotionBoot", StaticData )
end

rule "Shelly motion boot triggered off"
when
  Item swMotionBootActive changed to OFF 
then
  EndMotion.apply( "MotionBoot", StaticData )
end

rule "Shelly motion dining triggered on"
when
  Item swMotionDiningActive changed to ON 
then
  ProcessMotion.apply( "MotionDining", StaticData )
end

rule "Shelly motion dining triggered off"
when
  Item swMotionDiningActive changed to OFF 
then
  EndMotion.apply( "MotionDining", StaticData )
end

rule "Shelly motion kitchen triggered on"
when
  Item swMotionKitchenActive changed to ON 
then
  ProcessMotion.apply( "MotionKitchen", StaticData )
end

rule "Shelly motion kitchen triggered off"
when
  Item swMotionKitchenActive changed to OFF 
then
  EndMotion.apply( "MotionKitchen", StaticData )
end

And the items:

Group MotionSensors
Group MotionIlluminations
Group MotionBatteryLevels
Group MotionBatteryLows
Group MotionActives
Group MotionLastMotions

Group    MotionDining                 "Motion Sensor Dining"                       (MotionSensors)
String   strMotionDiningIllumination  "Motion Sensor Dining Illumination [%s]"     (MotionDining, MotionIlluminations)    { channel="shelly:shellymotion:2c1165cb0d29:sensors#illumination" }
Number   numMotionDiningBatteryLevel  "Motion Sensor Dining Battery Level [%d%%]"  (MotionDining, MotionBatteryLevels)    { channel="shelly:shellymotion:2c1165cb0d29:battery#batteryLevel" }
Switch   swMotionDiningBatteryLow     "Motion Sensor Dining Battery Low"           (MotionDining, MotionBatteryLows)      { channel="shelly:shellymotion:2c1165cb0d29:battery#lowBattery" }
Switch   swMotionDiningActive         "Motion Sensor Dining Active"                (MotionDining, MotionActives)          { channel="shelly:shellymotion:2c1165cb0d29:sensors#motion" }
DateTime dtMotionDiningLastMotion     "Motion Sensor Dining Last Motion [%1$td %1$tb %1$tr]" <clock>  (MotionDining, MotionLastMotions)  { channel="shelly:shellymotion:2c1165cb0d29:sensors#motionTimestamp"}

Group    MotionKitchen                "Motion Sensor Kitchen"                      (MotionSensors)
String   strMotionKitchenIllumination "Motion Sensor Kitchen Illumination [%s]"    (MotionKitchen, MotionIlluminations)   { channel="shelly:shellymotion:8cf681cd2138:sensors#illumination" }
Number   numMotionKitchenBatteryLevel "Motion Sensor Kitchen Battery Level [%d%%]" (MotionKitchen, MotionBatteryLevels)   { channel="shelly:shellymotion:8cf681cd2138:battery#batteryLevel" }
Switch   swMotionKitchenBatteryLow    "Motion Sensor Kitchen Battery Low"          (MotionKitchen, MotionBatteryLows)     { channel="shelly:shellymotion:8cf681cd2138:battery#lowBattery" }
Switch   swMotionKitchenActive        "Motion Sensor Kitchen Active"               (MotionKitchen, MotionActives)         { channel="shelly:shellymotion:8cf681cd2138:sensors#motion" }
DateTime dtMotionKitchenLastMotion    "Motion Sensor Kitchen Last Motion [%1$td %1$tb %1$tr]" <clock>  (MotionKitchen, MotionLastMotions)  { channel="shelly:shellymotion:8cf681cd2138:sensors#motionTimestamp"}

Group    MotionBoot                   "Motion Sensor Boot"                         (MotionSensors)
String   strMotionBootIllumination    "Motion Sensor Boot Illumination [%s]"       (MotionBoot, MotionIlluminations)      { channel="shelly:shellymotion:2c1165cb07b9:sensors#illumination" }
Number   numMotionBootBatteryLevel    "Motion Sensor Boot Battery Level [%d%%]"    (MotionBoot, MotionBatteryLevels)      { channel="shelly:shellymotion:2c1165cb07b9:battery#batteryLevel" }
Switch   swMotionBootBatteryLow       "Motion Sensor Boot Battery Low"             (MotionBoot, MotionBatteryLows)        { channel="shelly:shellymotion:2c1165cb07b9:battery#lowBattery" }
Switch   swMotionBootActive           "Motion Sensor Boot Active"                  (MotionBoot, MotionActives)            { channel="shelly:shellymotion:2c1165cb07b9:sensors#motion" }
DateTime dtMotionBootLastMotion       "Motion Sensor Boot Last Motion [%1$td %1$tb %1$tr]" <clock>  (MotionBoot, MotionLastMotions)  { channel="shelly:shellymotion:2c1165cb07b9:sensors#motionTimestamp"}


Group  Lights
Group  LightInputs 
Group  LightOutputs 
Group  LightLastEvents

Switch swLightTriplePress           "All outside lights (panic)"       (Lights)
Switch swFloodLights                "Flood lights"             (Lights)
// shelly 2 master - second channel
Group  SensorDaybed                 "Daybed Sensor Light"      (Lights)                     
Switch swSensorDaybedInput          "Daybed Sensor Switch"     (SensorDaybed, LightInputs)     
Switch swSensorDaybedOutput         "Daybed Sensor Output"     (SensorDaybed, LightOutputs)      {channel="shelly:shellyplus2pm-relay:80646fdbe00c:relay1#output"}
String stringSensorDaybedLastEvent  "Daybed Sensor Last Event" (SensorDaybed, LightLastEvents)    {channel="shelly:shellyplus2pm-relay:80646fdbe00c:relay1#lastEvent"}

// shelly 2 master - first channel
Group  FloodMaster                  "Master Flood Light"       (Lights)                     
Switch swFloodMasterInput           "Master Flood Switch"      (FloodMaster, LightInputs)      
Switch swFloodMasterOutput          "Master Flood Output"      (FloodMaster, LightOutputs)       {channel="shelly:shellyplus2pm-relay:80646fdbe00c:relay2#output"}
String stringFloodMasterLastEvent   "Master Flood Last Event"  (FloodMaster, LightLastEvents)    {channel="shelly:shellyplus2pm-relay:80646fdbe00c:relay2#lastEvent"}

Group  SensorStudy                  "Study Sensor Light"       (Lights)                     
Switch swSensorStudyInput           "Study Sensor Switch"      (SensorStudy, LightInputs)      
Switch swSensorStudyOutput          "Study Sensor Output"      (SensorStudy, LightOutputs)       {channel="shelly:shellyplus1:a8032aba1704:relay#output"}
String stringSensorStudyLastEvent   "Study Sensor Last Event"  (SensorStudy, LightLastEvents)    {channel="shelly:shellyplus1:a8032aba1704:relay#lastEvent"}

Group  SensorLaundry                "Laundry Sensor Light"      (Lights)                     
Switch swSensorLaundryInput         "Laundry Sensor Switch"     (SensorLaundry, LightInputs)   
Switch swSensorLaundryOutput        "Laundry Sensor Output"     (SensorLaundry, LightOutputs)    {channel="shelly:shellyplus1:441793a950cc:relay#output"}
String stringSensorLaundryLastEvent "Laundry Sensor Last Event" (SensorLaundry, LightLastEvents) {channel="shelly:shellyplus1:441793a950cc:relay#lastEvent"}

Group  SensorAtrium                 "Atrium Sensor Light"       (Lights)                     
Switch swSensorAtriumInput          "Atrium Sensor Switch"      (SensorAtrium, LightInputs)    
Switch swSensorAtriumOutput         "Atrium Sensor Output"      (SensorAtrium, LightOutputs)     {channel="shelly:shellyplus1:441793a86d40:relay#output"}
String stringSensorAtriumLastEvent  "Atrium Sensor Last Event"  (SensorAtrium, LightLastEvents)  {channel="shelly:shellyplus1:441793a86d40:relay#lastEvent"}

Group  SensorBoot                   "Boot Sensor Light"         (Lights)                     
Switch swSensorBootInput            "Boot Sensor Switch"        (SensorBoot, LightInputs)      
Switch swSensorBootOutput           "Boot Sensor Output"        (SensorBoot, LightOutputs)       {channel="shelly:shellyplus1:441793a94fe8:relay#output"}
String stringSensorBootLastEvent    "Boot Sensor Last Event"    (SensorAtrium, LightLastEvents)  {channel="shelly:shellyplus1:441793a94fe8:relay#lastEvent"}

Group  SensorKitchen                "Kitchen Sensor Light"      (Lights)                     
Switch swSensorKitchenInput         "Kitchen Sensor Switch"     (SensorKitchen, LightInputs)   
Switch swSensorKitchenOutput        "Kitchen Sensor Output"     (SensorKitchen, LightOutputs)    {channel="shelly:shellyplus1:083af2028610:relay#output"}
String stringSensorKitchenLastEvent "Kitchen Sensor Last Event" (SensorKitchen, LightLastEvents) {channel="shelly:shellyplus1:083af2028610:relay#lastEvent"}

Group  SensorDining                 "Dining Sensor Light"       (Lights)                     
Switch swSensorDiningInput          "Dining Sensor Switch"      (SensorDining, LightInputs)    
Switch swSensorDiningOutput         "Dining Sensor Output"      (SensorDining, LightOutputs)     {channel="shelly:shellyplus1:441793aa6bf8:relay#output"}
String stringSensorDiningLastEvent  "Dining Sensor Last Event"  (SensorDining, LightLastEvents)  {channel="shelly:shellyplus1:441793aa6bf8:relay#lastEvent"}

Group  FloodLounge                  "Lounge Flood Light"        (Lights)                     
Switch swFloodLoungeInput           "Lounge Flood Switch"       (FloodLounge, LightInputs)     
Switch swFloodLoungeOutput          "Lounge Flood Output"       (FloodLounge, LightOutputs)      {channel="shelly:shellyplus1:a8032aba16f8:relay#output"}
String stringFloodLoungeLastEvent   "Lounge Flood Last Event"   (FloodLounge, LightLastEvents)   {channel="shelly:shellyplus1:a8032aba16f8:relay#lastEvent"}

Group  FloodTractor                 "Tractor Flood Light"       (Lights)                     
Switch swFloodTractorInput          "Tractor Flood Switch"      (FloodTractor, LightInputs)    
Switch swFloodTractorOutput         "Tractor Flood Output"      (FloodTractor, LightOutputs)     {channel="shelly:shellyplus1:441793aa9040:relay#output"}
String stringFloodTractorLastEvent  "Tractor Flood Last Event"  (FloodTractor, LightLastEvents)  {channel="shelly:shellyplus1:441793aa9040:relay#lastEvent"}

// second channel of shelly 2 in garage
Group  SensorGarage                 "Garage Sensor Light"       (Lights)                     
Switch swSensorGarageInput          "Garage Sensor Switch"      (SensorGarage, LightInputs)    
Switch swSensorGarageOutput         "Garage Sensor Output"      (SensorGarage, LightOutputs)     {channel="shelly:shellyplus2pm-relay:80646fdb89e0:relay2#output"}
String stringSensorGarageLastEvent  "Garage Sensor Last Event"  (SensorGarage, LightLastEvents)  {channel="shelly:shellyplus2pm-relay:80646fdb89e0:relay2#lastEvent"}

// first channel of shelly 2 in garage
Group  LightGarage                  "Garage Internal Light"     (Lights)                     
Switch swLightGarageInput           "Garage Light Switch"       (LightGarage, LightInputs)     
Switch swLightGarageOutput          "Garage Light Output"       (LightGarage, LightOutputs)      {channel="shelly:shellyplus2pm-relay:80646fdb89e0:relay1#output"}
String stringLightGarageLastEvent   "Garage Light Last Event"   (LightGarage, LightLastEvents)   {channel="shelly:shellyplus2pm-relay:80646fdb89e0:relay1#lastEvent"}

Group  LightBootDoor                "Boot Door Light"           (Lights)                     
Switch swLightBootDoorInput         "Boot Door Light Switch"    (LightBootDoor, LightInputs)   
Switch swLightBootDoorOutput        "Boot Door Light Output"    (LightBootDoor, LightOutputs)    {channel="shelly:shellyplus1:083af202a350:relay#output"}
String stringLightBootDoorLastEvent "Boot Door Light Last Event" (LightBootDoor, LightLastEvents)  {channel="shelly:shellyplus1:083af202a350:relay#lastEvent"}

Group  LightBootHall                "Boot Hall Light"           (Lights)                     
Switch swLightBootHallInput         "Boot Hall Light Switch"    (LightBootHall, LightInputs)   
Switch swLightBootHallOutput        "Boot Hall Light Output"    (LightBootHall, LightOutputs)    {channel="shelly:shellyplus1:083af2029730:relay#output"}
String stringLightBootHallLastEvent "Boot Hall Light Last Event" (LightBootHall, LightLastEvents)  {channel="shelly:shellyplus1:083af2029730:relay#lastEvent"}