Skip to content

openHAB: detect if a ChromeCast is currently used or idle

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 ...

All of the action needs between a few milliseconds and 2-3 seconds once you start/stop using the ChromeCast.

A previous blog post explains how I add the ChromeCast devices. And I have monitoring in place.

 

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:

import java.util.concurrent.locks.ReentrantLock

{% set chromecasts = [('Living Room', 'chromecast:chromecast:ab123def'),
                      ('Working Room', 'chromecast:chromecast:ab456def'),
                      ('Working Room Audio', 'chromecast:audio:ab789def')
                     ] %}

// need to define the timers (variables) before the rules
{% for cc in chromecasts %}
var Timer timer{{ cc[0]|replace(' ','') }}_usage = null
val ReentrantLock lock_{{ cc[0]|replace(' ','') }}_usage = new ReentrantLock
var Boolean cc_{{ cc[0]|replace(' ','') }}_previously_used = null
{% endfor %}

This will result in the following code in "/etc/openhab2/rules/chromecast.rules":

import java.util.concurrent.locks.ReentrantLock

// need to define the timers (variables) before the rules
var Timer timerLivingRoom_usage = null
val ReentrantLock lock_LivingRoom_usage = new ReentrantLock
var Boolean cc_LivingRoom_previously_used = null
var Timer timerWorkingRoom_usage = null
val ReentrantLock lock_WorkingRoom_usage = new ReentrantLock
var Boolean cc_WorkingRoom_previously_used = null
var Timer timerWorkingRoomAudio_usage = null
val ReentrantLock lock_WorkingRoomAudio_usage = new ReentrantLock
var Boolean cc_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:

{% for cc in chromecasts %}
Switch		CC_{{ cc[0]|replace(' ','') }}_used
{% endfor %}

The resulting "/etc/openhab2/items/chromecast.items" file:

Switch          CC_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:

{% for cc in chromecasts %}

// 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 them
rule "ChromeCast Usage {{ cc[0] }} status changed"

 

The "when" block monitors all three Items I mentioned above:

when
    Item {{ cc[1]|replace(':','_') }}_appName changed or
    Item {{ cc[1]|replace(':','_') }}_appId changed or
    Item {{ cc[1]|replace(':','_') }}_idling changed

 

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:

then
    logInfo("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 here
    try {

...

    }
    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
  • Unlock the timer
        if (timer{{ cc[0]|replace(' ','') }}_usage === null) {
            logInfo("ChromeCast Status Debug", "ChromeCast {{ cc[0] }} timer started")
            // first update in a short time
            timer{{ 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 now
                var Boolean device_used = null
                var Boolean is_changed = null

                if ({{ cc[1]|replace(':','_') }}_appName.state.toString == "Backdrop") {
                    // Video ChromeCast backdrop pictures
                    device_used = false
                } else if ({{ 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 pictures
                    device_used = false
                } else if ({{ 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
                } else if ({{ 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 status
                    logInfo("ChromeCast Status Debug", "ChromeCast {{ cc[0] }} previous status: not available")
                    logInfo("ChromeCast Status Debug", "ChromeCast {{ cc[0] }} new status: {}", device_used)
                    is_changed = true
                } else if (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_used
                if (is_changed == true) {
                    if (device_used == true) {
                        // new status is ON, do something here
                        CC_{{ cc[0]|replace(' ','') }}_used.postUpdate(ON)
                    } else {
                        // new status is OFF, do something here
                        CC_{{ 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 time
            timerLivingRoom_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 now
                var Boolean device_used = null
                var Boolean is_changed = null

                if (chromecast_chromecast_ab123def_appName.state.toString == "Backdrop") {
                    // Video ChromeCast backdrop pictures
                    device_used = false
                } else if (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 pictures
                    device_used = false
                } else if (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
                } else if (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 status
                    logInfo("ChromeCast Status Debug", "ChromeCast Living Room previous status: not available")
                    logInfo("ChromeCast Status Debug", "ChromeCast Living Room new status: {}", device_used)
                    is_changed = true
                } else if (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_used
               if (is_changed == true) {
                    if (device_used == true) {
                        // new status is ON, do something here
                        CC_LivingRoom_used.postUpdate(ON)
                    } else {
                        // new status is OFF, do something here
                        CC_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.

Audio ChromeCast on:

2020-06-22 17:37:32.474 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Working Room Audio changed: chromecast_audio_ab789def_appId changed from UNDEF to CC32E753
2020-06-22 17:37:32.471 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Working Room Audio changed: chromecast_audio_ab789def_appName changed from UNDEF to Spotify
2020-06-22 17:37:32.484 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Working Room Audio changed: chromecast_audio_ab789def_idling changed from ON to OFF
2020-06-22 17:37:32.491 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio timer started
2020-06-22 17:37:33.502 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio timer expired
2020-06-22 17:37:33.524 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio appName: Spotify
2020-06-22 17:37:33.539 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio appId: CC32E753
2020-06-22 17:37:33.562 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio previous status: false
2020-06-22 17:37:33.565 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio new status: true

Audio ChromeCast off:

2020-06-22 17:38:46.050 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Working Room Audio changed: chromecast_audio_ab789def_appName changed from Spotify to UNDEF
2020-06-22 17:38:46.057 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Working Room Audio changed: chromecast_audio_ab789def_appId changed from CC32E753 to UNDEF
2020-06-22 17:38:46.065 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio timer started
2020-06-22 17:38:46.075 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Working Room Audio changed: chromecast_audio_ab789def_idling changed from OFF to ON
2020-06-22 17:38:47.181 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio timer expired
2020-06-22 17:38:47.204 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio previous status: true
2020-06-22 17:38:47.208 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Working Room Audio new status: false

Video ChromeCast on:

2020-06-22 17:39:38.743 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_appId changed from E8C28D3C to CC32E753
2020-06-22 17:39:38.735 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_appName changed from Backdrop to Spotify
2020-06-22 17:39:38.759 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_idling changed from ON to OFF
2020-06-22 17:39:38.763 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room timer started
2020-06-22 17:39:39.771 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room timer expired
2020-06-22 17:39:39.788 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room appName: Spotify
2020-06-22 17:39:39.801 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room appId: CC32E753
2020-06-22 17:39:39.824 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room previous status: not available
2020-06-22 17:39:39.829 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room new status: true

Vide ChromeCast off:

2020-06-22 17:40:27.349 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_appName changed from Spotify to UNDEF
2020-06-22 17:40:27.351 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_appId changed from CC32E753 to UNDEF
2020-06-22 17:40:27.357 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room timer started
2020-06-22 17:40:27.357 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_idling changed from OFF to ON
2020-06-22 17:40:28.366 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room timer expired
2020-06-22 17:40:28.416 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room previous status: true
2020-06-22 17:40:28.423 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room new status: false
2020-06-22 17:40:29.481 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_appName changed from UNDEF to Backdrop
2020-06-22 17:40:29.481 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Status Living Room changed: chromecast_chromecast_ab123def_appId changed from UNDEF to E8C28D3C
2020-06-22 17:40:29.504 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room timer started
2020-06-22 17:40:30.514 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room timer expired
2020-06-22 17:40:30.540 [INFO ] [model.script.ChromeCast Status Debug] - ChromeCast Living Room previous status same as new state

 

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.
Form options