We have a couple of Audio and Video ChromeCasts in use. For an upcoming project I need to figure out if any of them is currently used. That is different for the Audio and the Video devices: the Audio just goes idle=ON, the Video devices however load the Backdrop app and show pictures when idling. Google for whatever reason does not want the attached monitor to go in powersafe mode. So much for saving energy …
ChromeCast
All of the action needs between a few milliseconds and 2-3 seconds once you start/stop using the ChromeCast.
The first question to ask: which Item to monitor for changes. Three of them make sense:
_appName: the current application used by the ChromeCast (UNDEF if an Audio CC is not used, Backdrop if a Video CC is not used)
_appId: the application ID used by the ChromeCast (UNDEF if an Audio CC is not used, E8C28D3C is the Backdrop application ID on a Video CC)
_idling: either ON (currently not used) or OFF (device is in use)
Why not all three of them? They will change at almost the same time, however this will trigger three different events. I’m using a timer to wait a second, and then see what the final status is. I’m also locking the rule against parallel execution.
I have all the ChromeCasts defined in an Ansible template in a list variable, this way I can easily loop over all devices and generate code for each of them. The setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
importjava.util.concurrent.locks.ReentrantLock{%setchromecasts=[('LivingRoom','chromecast:chromecast:ab123def'),('WorkingRoom','chromecast:chromecast:ab456def'),('WorkingRoomAudio','chromecast:audio:ab789def')]%}// need to define the timers (variables) before the rules{%forccinchromecasts%}varTimertimer{{cc[0]|replace(' ','')}}_usage=nullvalReentrantLocklock_{{cc[0]|replace(' ','')}}_usage=newReentrantLockvarBooleancc_{{cc[0]|replace(' ','')}}_previously_used=null{%endfor%}
This will result in the following code in /etc/openhab2/rules/chromecast.rules:
1
2
3
4
5
6
7
8
9
10
11
12
importjava.util.concurrent.locks.ReentrantLock// need to define the timers (variables) before the rulesvarTimertimerLivingRoom_usage=nullvalReentrantLocklock_LivingRoom_usage=newReentrantLockvarBooleancc_LivingRoom_previously_used=nullvarTimertimerWorkingRoom_usage=nullvalReentrantLocklock_WorkingRoom_usage=newReentrantLockvarBooleancc_WorkingRoom_previously_used=nullvarTimertimerWorkingRoomAudio_usage=nullvalReentrantLocklock_WorkingRoomAudio_usage=newReentrantLockvarBooleancc_WorkingRoomAudio_previously_used=null
Short intermezzo: I want to monitor the ChromeCast, and possibly react on status changes in other rules. For this I need an Item which shows the current ChromeCast status - a “Switch” comes handy here:
The resulting /etc/openhab2/items/chromecast.items file:
1
SwitchCC_LivingRoom_used
The Switch variable does not need a status text, as it is never shown somewhere on a website. It’s just for internal use. Now back to the Rules:
In the rules section I loop over all the devices in the variable $chromecasts, and create a rule for each of them:
1
2
3
4
5
6
7
8
9
10
{%forccinchromecasts%}// Monitor the usage of the ChromeCast// {{ cc[0] }} - {{ cc[1] }}// couple items will change at the same time:// _appName will change from "Backdrop" to "UNDEF", then to new the application// _appId will change from "E8C28D3C" (Backdrop) to "UNDEF", then to the new application// rules will fire in parallel, lock themrule"ChromeCast Usage {{ cc[0] }} status changed"
The when block monitors all three Items I mentioned above:
The code block for the rule first logs some debugging information (logging first, so I see if something is going wrong with the locking), and then locks the rule against parallel execution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
thenlogInfo("ChromeCast Status Debug","ChromeCast Status {{ cc[0] }} changed: {} changed from {} to {}",triggeringItem.name,previousState,newState)lock_{{cc[0]|replace(' ','')}}_usage.lock()// have the lock, regular code goes heretry{...}if(lock_{{cc[0]|replace(' ','')}}_usage.isHeldByCurrentThread()){lock_{{cc[0]|replace(' ','')}}_usage.unlock()}end{%endfor%}
The inner block inside the try{} does a couple things:
Start a timer - if the timer is not yet started (important that the rule is locked, otherwise multiple parallel threads can start the timer)
Define the lambda function which is doing the actual work, once the timer finishes
if(timer{{cc[0]|replace(' ','')}}_usage===null){logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} timer started")// first update in a short timetimer{{cc[0]|replace(' ','')}}_usage=createTimer(now.plusSeconds(1),[|logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} timer expired")timer{{cc[0]|replace(' ','')}}_usage.cancel()timer{{cc[0]|replace(' ','')}}_usage=null// try to figure out the current status of the device// all changes should have been triggered by nowvarBooleandevice_used=nullvarBooleanis_changed=nullif({{cc[1]|replace(':','_')}}_appName.state.toString=="Backdrop"){// Video ChromeCast backdrop picturesdevice_used=false}elseif({{cc[1]|replace(':','_')}}_appName.state.toString=="UNDEF"){device_used=false}else{logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} appName: {}",{{cc[1]|replace(':','_')}}_appName.state.toString)device_used=true}if({{cc[1]|replace(':','_')}}_appId.state.toString=="E8C28D3C"){// Video ChromeCast backdrop picturesdevice_used=false}elseif({{cc[1]|replace(':','_')}}_appId.state.toString=="UNDEF"){device_used=false}else{logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} appId: {}",{{cc[1]|replace(':','_')}}_appId.state.toString)device_used=true}if({{cc[1]|replace(':','_')}}_idling.state.toString=="ON"&&device_used===null){device_used=false}elseif({{cc[1]|replace(':','_')}}_idling.state.toString=="OFF"&&device_used===null){device_used=true}if(device_used===null){logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }}: not able to figure out new status!")return}if(cc_{{cc[0]|replace(' ','')}}_previously_used===null){// no previous statuslogInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} previous status: not available")logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} new status: {}",device_used)is_changed=true}elseif(cc_{{cc[0]|replace(' ','')}}_previously_used==device_used){logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} previous status same as new state")is_changed=false}else{logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} previous status: {}",cc_{{cc[0]|replace(' ','')}}_previously_used)logInfo("ChromeCast Status Debug","ChromeCast {{ cc[0] }} new status: {}",device_used)is_changed=true}cc_{{cc[0]|replace(' ','')}}_previously_used=device_usedif(is_changed==true){if(device_used==true){// new status is ON, do something hereCC_{{cc[0]|replace(' ','')}}_used.postUpdate(ON)}else{// new status is OFF, do something hereCC_{{cc[0]|replace(' ','')}}_used.postUpdate(OFF)}}])}if(lock_{{cc[0]|replace(' ','')}}_usage.isHeldByCurrentThread()){lock_{{cc[0]|replace(' ','')}}_usage.unlock()}
This translates to the following function (for one ChromeCast):
if(timerLivingRoom_usage===null){logInfo("ChromeCast Status Debug","ChromeCast Living Room timer started")// first update in a short timetimerLivingRoom_usage=createTimer(now.plusSeconds(1),[|logInfo("ChromeCast Status Debug","ChromeCast Living Room timer expired")timerLivingRoom_usage.cancel()timerLivingRoom_usage=null// try to figure out the current status of the device// all changes should have been triggered by nowvarBooleandevice_used=nullvarBooleanis_changed=nullif(chromecast_chromecast_ab123def_appName.state.toString=="Backdrop"){// Video ChromeCast backdrop picturesdevice_used=false}elseif(chromecast_chromecast_ab123def_appName.state.toString=="UNDEF"){device_used=false}else{logInfo("ChromeCast Status Debug","ChromeCast Living Room appName: {}",chromecast_chromecast_ab123def_appName.state.toString)device_used=true}if(chromecast_chromecast_ab123def_appId.state.toString=="E8C28D3C"){// Video ChromeCast backdrop picturesdevice_used=false}elseif(chromecast_chromecast_ab123def_appId.state.toString=="UNDEF"){device_used=false}else{logInfo("ChromeCast Status Debug","ChromeCast Living Room appId: {}",chromecast_chromecast_ab123def_appId.state.toString)device_used=true}if(chromecast_chromecast_ab123def_idling.state.toString=="ON"&&device_used===null){device_used=false}elseif(chromecast_chromecast_ab123def_idling.state.toString=="OFF"&&device_used===null){device_used=true}if(device_used===null){logInfo("ChromeCast Status Debug","ChromeCast Living Room: not able to figure out new status!")return}if(cc_LivingRoom_previously_used===null){// no previous statuslogInfo("ChromeCast Status Debug","ChromeCast Living Room previous status: not available")logInfo("ChromeCast Status Debug","ChromeCast Living Room new status: {}",device_used)is_changed=true}elseif(cc_LivingRoom_previously_used==device_used){logInfo("ChromeCast Status Debug","ChromeCast Living Room previous status same as new state")is_changed=false}else{logInfo("ChromeCast Status Debug","ChromeCast Living Room previous status: {}",cc_LivingRoom_previously_used)logInfo("ChromeCast Status Debug","ChromeCast Living Room new status: {}",device_used)is_changed=true}cc_LivingRoom_previously_used=device_usedif(is_changed==true){if(device_used==true){// new status is ON, do something hereCC_LivingRoom_used.postUpdate(ON)}else{// new status is OFF, do something hereCC_LivingRoom_used.postUpdate(OFF)}}])}}if(lock_LivingRoom_usage.isHeldByCurrentThread()){lock_LivingRoom_usage.unlock()}
After loading everything onto the openHAB system, I can see how the rules are triggered. It’s a bit different between Audio and Video ChromeCasts: the Video switches from Backdrop to UNDEF, and then to the new application. And the same way back. The Audio just goes on and off. For this reason the Video Chromecast sometimes fires the rules two times - that is the reason why the rule checks if the status is changing. This can be avoided by increasing the timer, from 1 to about 3 seconds. But this will in turn delay every ChromeCast, and make the rule act slower. By using one second I can already trigger the action I want, and later on just verify during the subsequent run(s) that nothing changed. At the end, the Switch is also updated, and set to ON or OFF, depending on the ChromeCast status.