I was fine tuning the rules that handle audio-video control (switching devices on and off, changing inputs so the entire systems gets ready for TV, music or movie with one single click) and updating the “controller” rule dedicated to the videoprojector when I figured out that, once more, I was implementing the same logic inside a script. So my idea to suggest here 2 patterns.
The patterns rely on virtual items. The logic is to interact with a virtual item that wraps the real actuator / device and that takes care of the transitional states of the hardware device.
First pattern applies to hardware devices that need time to switch from one state to another. Audio video hardware (delay at switch on before they can process an IR command such as video projectors, amplifiers or even TV), computers (wait for Kodi to be ready), blinds, garage door, property gate to name the most common ones.
Applicaton for this first pattern is a video-projector device that accepts ON and OFF commands and has a turn-on/turn-off delays (preheating/cooling steps). This timed behavior is similar to blinds or garage doors would do.
Pattern consists in:
- A virtual item that wraps the video-projector device:
Number AV_mRDC_Salon_vp "Projecteur salon [%s]" <_switch> (AV, mRDC_salon, AV_device) {autoupdate="false"}
- 2 rules that handle the commands received and updates of the virtual device
var Timer vp_task = null //timer that will take care of device delays
var Integer vp_on_delay = 20 //delay for switching on the device (or getting to the )
var Integer vp_off_delay = 40 // delay for switching off the device
var Integer vp_state_target = null
rule "Projector command (processor)"
when
Item AV_mRDC_Salon_vp received command
then
//AV_mRDC_Salon_vp states : 0=OFF, 10=Preheating, 90=Shutting-down, 100=0N
//IR_mRDC_Salon_command is the IRTrans item that send IR command to hardware device
switch(AV_mRDC_Salon_vp.state.toString + ">" + receivedCommand.toString){
//cases when ON command received
case "0>100": {
vp_state_target = 100 // ON
AV_mRDC_Salon_vp.postUpdate(10) //Preheating: transitional state
IR_mRDC_Salon_command.sendCommand("Jvc_vp,poweron")
vp_task?.cancel()
vp_task = createTimer(now.plusSeconds(vp_on_delay)) [|
AV_mRDC_Salon_vp.postUpdate(100) //ON
vp_task = null
]
}
case "90>100": { //Shutting-down>ON: wait for state to change to OFF before sending the ON command
vp_state_target = 100 // ON
AV_mRDC_Salon_vp.postUpdate(10) //Preheating: transitional state
}
//cases when OFF command received
case "100>0": {
vp_state_target = 0 //OFF
AV_mRDC_Salon_vp.postUpdate(90) //Shutting-down: transitional state
IR_mRDC_Salon_command.sendCommand("Jvc_vp,poweroff")
vp_task?.cancel()
vp_task = createTimer(now.plusSeconds(vp_off_delay)) [|
AV_mRDC_Salon_vp.postUpdate(0) //OFF
vp_task = null
]
}
case "10>100": { //Preheating>OFF: wait for state to change to ON before sending the OFF command
vp_state_target = 0 //OFF
AV_mRDC_Salon_vp.postUpdate(90) //Shutting-down: transitional state
}
}
end
rule "Projector state (reporter)"
when
Item AV_mRDC_Salon_vp changed
then
if (AV_mRDC_Salon_vp.state != vp_state_target) { AV_mRDC_Salon_vp.sendCommand(vp_state_target.toString)}
end
The example has 2 states but can be generalized to multistate (for example, having an AV device that accepts commands “OFF”, “HDMI input” and “SPDIF” which wraps the change of source)
Now, What is going on if the projector receives an OFF command while it is heating? Well, mine does not take it into account: you have to wait it is ON before sending an IR command. The wrapping takes care of acting “intelligently” in that case and wait. But if the command is part of a sequence of commands inside a script, then this wait is is an issue as other devices involved should “wait” (or not…) for the projector to be in the expected state to continue the processing. This is where the 2nd pattern comes.
Second pattern creates a kind of dependency between layers of items (real or virtual). In a certain way, this pattern can be seen as a generic extension of Group items behavior where the aggregator would be defined by the user and where the items could be of any type. It helps to manage asynchronicity between commands sent to a device and their actual execution by the device.
It behaves in a complementary way of the Associated items pattern.
As an example for the 2nd pattern, I added :
one virtual item that wraps the projector screen to handle the delay to open/close, similar to the projector
one virtual item that handles the audio-video mode for the room: modes are OFF, TV or projector
Number AV_mRDC_Salon_screen "Ecran cinéma [%s]" <_screen> (AV, mRDC_salon, AV_device, glAV_mRDC_Salon_videomode ) {autoupdate="false"}
Number AV_mRDC_Salon_vp "Projecteur salon [%s]" <_switch> (AV, mRDC_salon, AV_device, glAV_mRDC_Salon_videomode) {autoupdate="false"}
Number AV_mRDC_Salon_videomode "Mode vidéo [%s]" <_video> (mRDC_salon, AV) {mqtt=">[cheznous:/openHAB/out/AV_mRDC_Salon_videomode/state:state:*:default],<[cheznous:/openHAB/in/AV_mRDC_Salon_videomode/command:command:default]", autoupdate="false"}
Switch AV_mRDC_Salon_tv "TV salon [%s]" <_switch> (AV, mRDC_salon, AV_device, glAV_mRDC_Salon_videomode) {autoupdate="false"}
Two rules create the “glue” between the devices of the underlying layer:
rule "Command video mode (command processor)"
when
Item AV_mRDC_Salon_videomode received command
then
val Integer vptotv_fullvisibility_delay=20 //delay when screen is rolling up when the TV set is fully visible
val Integer vptotv_irvisibility_delay=5 //delay when the IR receiver of the TV is visible and can accept a command
val Integer tvtovp_playbackvisibility_delay=25 //delay screen roll-down until projector is ON if playback on-going on TV
//Command processor: forwards commands to underlying items "glued" to virtual item AV_mRDC_Salon_videomode
//"Glued" items are those belonging to group gAV_mRDC_Salon_videomode (see av.items)
//States for AV_mRDC_Salon_videomode: 0=OFF, 1=tv, 2=projector, 3=projector>tv, 4=projector>tv, 5=off>projector, 6=projector>off
videomode_timer?.cancel
switch(AV_mRDC_Salon_videomode.state.toString + receivedCommand.toString){
case '10':{ //tv>off
AV_mRDC_Salon_tv.sendCommand(OFF)
AV_mRDC_Salon_vp.sendCommand(0) //OFF: in case underlying devices state would be unsynced with this layer
AV_mRDC_Salon_screen.sendCommand(0) //OFF: in case underlying devices state would be unsynced with this layer
}
case '20':{ //projector>off
AV_mRDC_Salon_tv.sendCommand(OFF) //OFF: in case underlying devices state would be unsynced with this layer
AV_mRDC_Salon_screen.sendCommand(0)
AV_mRDC_Salon_videomode.postUpdate(6)//projector>off: transitional state until the TV set behind the projector is visible again to switch video from projector to tv
videomode_timer = createTimer(now.plusSeconds(vptotv_fullvisibility_delay)) [|
AV_mRDC_Salon_videomode.postUpdate(1)//tv is visible again so AV_mRDC_Salon_videomode is temporarily set to tv
AV_mRDC_Salon_videomode.sendCommand(0)// to off
videomode_timer = null
]
}
case '21':{ //projector>tv
AV_mRDC_Salon_screen.sendCommand(0) //OFF: in case underlying devices state would be unsynced with this layer
AV_mRDC_Salon_videomode.postUpdate(3) ///projector>tv: transitional state until the screen is rolled-up high enough so TV set is fully visible
videomode_timer = createTimer(now.plusSeconds(vptotv_fullvisibility_delay)) [|
AV_mRDC_Salon_tv.sendCommand(ON) // TV IR receiver should not be hidden at this point
AV_mRDC_Salon_vp.sendCommand(0) //OFF: switches projector OFF only when TV set is fully visible
videomode_timer = null
]
}
// #### OTHER PROCESSOR CASE{} REMOVED FOR READABILITY ###
}
end
rule "State video mode (aggregator)"
//custom state aggregator
// 1- monitor updates on items in group glAV_mRDC_Salon_videomode (those are "glued" with item AV_mRDC_Salon_videomode)
// 2- calculates and updates the resulting state of the overlying virtual item AV_mRDC_Salon_videomode
when
Member of glAV_mRDC_Salon_videomode received update
then
logDebug("rules.av_modes.video_state", "Triggerred by " + triggeringItem.name + ", AV_mRDC_Salon_screen=" + AV_mRDC_Salon_screen.state.toString + ", AV_mRDC_Salon_vp=" + AV_mRDC_Salon_vp.state.toString + ", AV_mRDC_Salon_tv=" + AV_mRDC_Salon_tv.state.toString )
logDebug("rules.av_modes.video_state", AV_mRDC_Salon_screen.state.toString + "#" + AV_mRDC_Salon_vp.state.toString + "#" + AV_mRDC_Salon_tv.state.toString )
logDebug("rules.av_modes.video_state", (AV_mRDC_Salon_videomode.state != 0).toString )
if((triggeringItem.name == 'AV_mRDC_Salon_vp') || (triggeringItem.name == 'AV_mRDC_Salon_tv')){
//handle state changes due to tv or projector items
switch (AV_mRDC_Salon_screen.state.toString + "#" + AV_mRDC_Salon_vp.state.toString + "#" + AV_mRDC_Salon_tv.state.toString){
case '0#0#OFF':{ if(AV_mRDC_Salon_videomode.state as Number != 0){ AV_mRDC_Salon_videomode.postUpdate(0) }} // OFF
case '100#100#OFF':{ if(AV_mRDC_Salon_videomode.state as Number != 2){ AV_mRDC_Salon_videomode.postUpdate(2) }} // projector
case '100#100#ON':{ if(AV_mRDC_Salon_videomode.state as Number != 2){ AV_mRDC_Salon_videomode.postUpdate(2) }} // projector
case '0#0#ON':{ if(AV_mRDC_Salon_videomode.state as Number != 1){ AV_mRDC_Salon_videomode.postUpdate(1) }} // tv
}
}
else if(triggeringItem.name == 'AV_mRDC_Salon_screen'){
//handle transitional states related to screen rollling up / down
switch (AV_mRDC_Salon_screen.state.toString + "#" + AV_mRDC_Salon_videomode.state.toString){//vp>tv
case '0#3':{ if(AV_mRDC_Salon_videomode.state as Number != 1){ AV_mRDC_Salon_videomode.postUpdate(1) }}// off#vp_tv: state becomes tv
case '100#4':{ if(AV_mRDC_Salon_videomode.state as Number != 2){ AV_mRDC_Salon_videomode.postUpdate(2) }}// on#tv_vp: state becomes projector
case '100#5':{ if(AV_mRDC_Salon_videomode.state as Number != 2){ AV_mRDC_Salon_videomode.postUpdate(2) }}// on#off_vp: state becomes projector
case '0#6':{ if(AV_mRDC_Salon_videomode.state as Number != 0){ AV_mRDC_Salon_videomode.postUpdate(0) }}////off#vp_off: state becomes off
}
}
end
One may think this is much coding to do barely what a group could do.
But when things involve more devices, these 2 patterns are really helpful in layering the processing and making code easier to maintain. For AV, In my real setup, there are 4 layers:
- first layer wraps each device to handle the on/off and source switching delays (with first pattern)
- 2nd layer has 2 virtual items: one is dedicated to switch audio mode (2.0, 5.1, zone 2 as my audio setup is quite complicated with several amplifiers and source switching) and “glues” audio devices. The other virtual item switches video mode between (TV or projector) and “glues” the video devices.
- 3rd layer handles the global AV mode of the room and glues the 2 items of the 2nd layer + Kodi virtual item: when AV_mode is set to TV, audio mode is set to zone 2 and video mode. When AV_mode is set to movie, audio mode is set to multichannel and video mode is set to projector. When AV_mode is set to audiophile, audio_mode is set to 2.0 (using different amplifier), video mode is set to OFF
- 4th layer handles the entire movie scenery and has 4 states : OFF, ready for movie and movie playing: it glues the AV_mode, the blinds of the room, the lights in the room, the presence mode of the house and the notification subsystem (speech OH notifications are redirected to OSD).
Only virtual items of layers 3 (av_mode) and 4 (start movie scene) are exposed in the UIs so a command takes the entire system to the correct state, whatever the current state was (even a transitional state). And the cascading and delaying makes all the magic happen in a smart way.
This example focused on audio-video but applies to blinds, gate opening when light or other actuators are involved, even virtual (alarm, presence).
Example is in DSL-rule style and I am sorry for that as I know it will be deprecated in the future but the logic of the pattern remains the same whatever the language and my purpose if focused on the architecture of that layering more than the syntax itself (would be nice to store the turn-on/off delay in metadata for example).
Can we imagine a type of “brick” in the NextGen script designer to “wrap” a hardware device with the logic of the first pattern and then “glue” together several devices which states depend on each other? I wish this can happen. Gluing is indeed a bit more complicated than my example as it could precedence management between items with criteria such as "when all / at least one / exactly one item changed to state “AAA”. I believe that such generic patterns can help in simplifying scripting for beginners and provide brick to obtain behaviors which are not many and quite standard at the end regarding home automation.
One could argue that this should be dealt at binding level. For sure, but we are not all capable or willing to write a binding just to drive the ON/OFF state.
Feel free to give your feedback on this, especially if any feature I miss in OH permits obtaining the same result