Page 1 of 1

Python realtime Chromecast application detect script

Posted: Sunday 14 January 2018 23:10
by triton
Hello,

Don't know if anybody wrote something like this already but I like to share a python script I wrote for detecting application changes for Chromecast devices and update a text device in Domoticz to match the application connected in realtime.

My previous script was a python script running avahi-browse requesting information from all network connected Chromecast devices and parse the information to grab the connected application (Netflix, Spotify etc.). Had this scheduled to run every X minutes in a cron schedule. Having a Domoticz light scene to switch some lights when watching Netflix I got a bit annoyed with the delay. I’m watching Netflix, and depending on the check delay the lights will change while I was watching already.

This got me thinking, since Chromecast is using Multicast DNS (mDNS) and the avahi-browse command I was using is seen by any device connected to your network, including the answer from the Chromecast device itself. Why not monitor the network and trigger on the network event? Now I’ve seen other posts from people using pychromecast module for this, don’t know if they trigger as fast as this or are using a cron schedule as well.

To get this running you need a few python modules extra from the default installed. You need:

Code: Select all

python-pcapy, python-dpkt, python-requests and python-json
I’ve checked and they should all be available on Raspberry PI and any other Debian like systems. Install those, copy the script change some settings to match your configuration and Chromecast devices. I put my script in /usr/local/bin and made it executable. If you do that you can use the systemctl service config file listed below and put it in /etc/systemd/system/ as chromecast_detect.service and have that startup on boot.

Code: Select all

[Unit]
Description=Chromecast Domoticz detect Service
After=multi-user.target
 
[Service]
User=root
Type=idle
ExecStart=/usr/local/bin/chromecast_detect.py
 
[Install]
WantedBy=multi-user.target
And finally the chromecast_detect.py script itself below. Curious what you think. Just to note, there is an odd count variable in the script. Each detection for a Chromecast packet gave me 2 matches. So a quick fix was just if count 1, do stuff and if count 2 ignore. I don’t consider myself to be a real programmer, but I get the job done :)

Wrote the script this weekend, it’s running almost 24 hours now. Bugs? probably, it's the first version. I have it configured to log to a file in /var/log/chromecast.log. It should log python errors as well. To give you an idea on the logging:

Code: Select all

01/14/2018 09:34:13 PM Starting Chromecast Domoticz detect
01/14/2018 09:34:21 PM Chromecast Slaapkamer now casting: Netflix
01/14/2018 09:34:21 PM Starting new HTTP connection (1): 127.0.0.1
01/14/2018 09:34:21 PM http://127.0.0.1:8080 "GET /json.htm?type=devices&rid=185 HTTP/1.1" 200 489
01/14/2018 09:34:21 PM Updating domoticz information for chromecast: Slaapkamer
01/14/2018 09:34:21 PM Starting new HTTP connection (1): 127.0.0.1
01/14/2018 09:34:21 PM http://127.0.0.1:8080 "GET /json.htm?type=command&param=udevice&idx=185&nvalue=0&svalue=Netflix HTTP/1.1" 200 53
01/14/2018 09:36:06 PM Chromecast Slaapkamer now casting: Nothing
01/14/2018 09:36:06 PM Starting new HTTP connection (1): 127.0.0.1
01/14/2018 09:36:06 PM http://127.0.0.1:8080 "GET /json.htm?type=devices&rid=185 HTTP/1.1" 200 488
01/14/2018 09:36:06 PM Updating domoticz information for chromecast: Slaapkamer
01/14/2018 09:36:06 PM Starting new HTTP connection (1): 127.0.0.1
01/14/2018 09:36:06 PM http://127.0.0.1:8080 "GET /json.htm?type=command&param=udevice&idx=185&nvalue=0&svalue=Nothing HTTP/1.1" 200 53
chromecast_detect.py:

Code: Select all

#!/usr/bin/env python
# File : chromecast_detect.py
# Author: Triton
# Date: 14-Jan-2018
# Description : Monitor network for Multicast DNS _googlecast._tcp.local, update devices with application playing
#               information Domoticz accordingly.
# Version : 1.0
#
# Required in Domoticz: text devices for each chromecast, set IDX below.
#
# Configuration (Change here):
DOMOTICZ_HOST = 'http://127.0.0.1:8080'
DOMOTICZ_USER = ''
DOMOTICZ_PASS = ''
URL_GET = '/json.htm?type=devices&rid=PARAM_IDX'
URL_POST = '/json.htm?type=command&param=udevice&idx=PARAM_IDX&nvalue=0&svalue=PARAM_STR'
logOutFilename = '/var/log/chromecast.log'  # Disable for console output

# Change Chromecast name and matching IDX for Domoticz:
CHROMECAST_IDX = {
    '<YOUR_CHROMECAST_1>': 1,
    '<YOUR_CHROMECAST_2>': 2,
}

# Nothing to edit under this line ##############################################################
import pcapy
import dpkt
import logging
import sys
import requests
import json


class StreamToLogger(object):
    """
    Fake file-like stream object that redirects writes to a logger instance.
    """
    def __init__(self, logger, log_level=logging.INFO):
        self.logger = logger
        self.log_level = log_level
        self.linebuf = ''

    def write(self, buf):
        for line in buf.rstrip().splitlines():
            self.logger.log(self.log_level, line.rstrip())


class StdLogger:
    def __init__(self,
                 filename,
                 level=logging.DEBUG,
                 formattting='%(asctime)s %(message)s',
                 datefmt='%m/%d/%Y %I:%M:%S %p',
                 filemode='a'
                 ):
        logging.basicConfig(
            level=level,
            format=formattting,
            filename=filename,
            datefmt=datefmt,
            filemode=filemode,
        )

        stdout_logger = logging.getLogger('STDOUT')
        sl = StreamToLogger(stdout_logger, logging.INFO)
        sys.stdout = sl

        stderr_logger = logging.getLogger('STDERR')
        sl = StreamToLogger(stderr_logger, logging.ERROR)
        sys.stderr = sl


class NetworkMonitor:
    def __init__(self):
        self.count = 1
        pass

    def start(self):
        dev = pcapy.findalldevs()[0]
        p = pcapy.open_live(dev, 262144, False, 1)
        p.setfilter('port 5353')
        p.loop(-1, self.handle_packet)

    def handle_packet(self, header, data):
        eth = dpkt.ethernet.Ethernet(data)
        if eth.type == dpkt.ethernet.ETH_TYPE_IP:
            ip = eth.data
            ip_data = ip.data
            if isinstance(ip_data, dpkt.udp.UDP):
                udp = ip_data
                if udp.dport == 5353:
                    mdns = dpkt.dns.DNS(udp.data)
                    if mdns.an:
                        for answer in mdns.an:
                            if answer.type == 16 and '_googlecast._tcp.local' in answer.name:
                                # Getting the same packet twice, need to filter that with a count
                                if self.count == 1:
                                    # print("PTR request", answer.name, "response", answer)
                                    result = answer.text
                                    chromecast_name = result[6].split('=')[1]
                                    status = result[11].split('=')[1] or 'Nothing'
                                    print('Chromecast %s now casting: %s' % (chromecast_name, status))

                                    url_get = DOMOTICZ_HOST + URL_GET
                                    url_get = url_get.replace('PARAM_IDX', str(CHROMECAST_IDX[chromecast_name]))
                                    data = requests.get(url_get, auth=(DOMOTICZ_USER, DOMOTICZ_PASS))
                                    old_status = json.loads(data.text)['result'][0]['Data']

                                    if old_status != status:
                                        print('Updating domoticz information for chromecast: %s' % chromecast_name)
                                        url_post = DOMOTICZ_HOST + URL_POST
                                        url_post = url_post.replace('PARAM_IDX', str(CHROMECAST_IDX[chromecast_name]))
                                        url_post = url_post.replace('PARAM_STR', str(status))
                                        requests.get(url_post, auth=(DOMOTICZ_USER, DOMOTICZ_PASS))
                                    else:
                                        print('Domoticz matching chromecast, no need to update')
                                    self.count = 2
                                else:
                                    self.count = 1


def main():
    try:
        if 'logOutFilename' in globals():
            StdLogger(logOutFilename)
        print('Starting Chromecast Domoticz detect')
        NetworkMonitor().start()
    except KeyboardInterrupt:
        sys.exit(0)

if __name__ == "__main__":
    main()

Re: Python realtime Chromecast application detect script

Posted: Thursday 25 January 2018 18:07
by triton
For those using init.d, made a quick script for that. When placed in /etc/init.d, run the following command to enable it during boot.

Code: Select all

update-rc.d chromecast_detect defaults
update-rc.d chromecast_detect enable
/etc/init.d/chromecast_detect

Code: Select all

#!/bin/bash
### BEGIN INIT INFO
# Provides:          Domoticz Chromecast Detect
# Required-Start:    $network $remote_fs $syslog
# Required-Stop:     $network $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Home Automation Chromecast Detect System
# Description:       This daemon will start the Domoticz Chromecast Detect service
### END INIT INFO

set -e

DAEMON_PATH="/usr/local/bin/"

DAEMON=/usr/local/bin/chromecast_detect.py
DAEMONOPTS=""

NAME=chromecast_detect
DESC="Domoticz Chromecast Detect"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

case "$1" in
start)
        printf "%-50s" "Starting $NAME..."
        cd $DAEMON_PATH
        PID=`$DAEMON $DAEMONOPTS > /dev/null 2>&1 & echo $!`
        #echo "Saving PID" $PID " to " $PIDFILE
        if [ -z $PID ]; then
            printf "%s\n" "Fail"
        else
            echo $PID > $PIDFILE
            printf "%s\n" "Ok"
        fi
;;
status)
        printf "%-50s" "Checking $NAME..."
        if [ -f $PIDFILE ]; then
            PID=`cat $PIDFILE`
            if [ -z "`ps axf | grep ${PID} | grep -v grep`" ]; then
                printf "%s\n" "Process dead but pidfile exists"
            else
                echo "Running"
            fi
        else
            printf "%s\n" "Service not running"
        fi
;;
stop)
        printf "%-50s" "Stopping $NAME"
            PID=`cat $PIDFILE`
            cd $DAEMON_PATH
        if [ -f $PIDFILE ]; then
            kill -HUP $PID
            printf "%s\n" "Ok"
            rm -f $PIDFILE
        else
            printf "%s\n" "pidfile not found"
        fi
;;

restart)
        $0 stop
        $0 start
;;

Re: Python realtime Chromecast application detect script

Posted: Thursday 25 January 2018 18:10
by dextm80
What i can do with this script? Only status?

Re: Python realtime Chromecast application detect script

Posted: Friday 26 January 2018 17:31
by triton
Yes, only status. It updates a custom text device with whatever a connected application passes as string. For example if you start casting a Netflix movie the device is updated from 'Nothing' to 'Netflix'. If you connect with Spotify it will be updated to 'Spotify'. If you disconnect from the Chromecast it is set back to 'Nothing'.

I'm using it as a trigger to switch on my Denon Receiver, can't listen to music or watch movies without the receiver being on. When casting Netflix a light scene is activated. It can't control (play, pause etc.) the chromecast if you are looking for that.

Re: Python realtime Chromecast application detect script

Posted: Friday 26 January 2018 23:47
by sincze
Thanks for this :D .
At least now I know if the device is doing something ;-\) and add it the the Kodi light scene as well 8-) (dimm the lights when streaming)

For debian-stretch I needed to add:

Code: Select all

python-pcapy python-dpkt

Code: Select all

01/27/2018 12:05:27 AM Starting Chromecast Domoticz detect
01/27/2018 12:05:45 AM Chromecast Bedroom now casting: YouTube
01/27/2018 12:05:47 AM Starting new HTTP connection (1): 127.0.0.1
01/27/2018 12:05:47 AM http://127.0.0.1:8080 "GET /json.htm?type=devices&rid=22 HTTP/1.1" 200 566
01/27/2018 12:05:47 AM Updating domoticz information for chromecast: Bedroom
01/27/2018 12:05:47 AM Starting new HTTP connection (1): 127.0.0.1
01/27/2018 12:05:47 AM http://127.0.0.1:8080 "GET /json.htm?type=command&param=udevice&idx=22&nvalue=0&svalue=YouTube HTTP/1.1" 200 53
01/27/2018 12:07:09 AM Chromecast Google Home now casting: Spotify
01/27/2018 12:07:09 AM Starting new HTTP connection (1): 127.0.0.1
01/27/2018 12:07:09 AM http://127.0.0.1:8080 "GET /json.htm?type=devices&rid=21 HTTP/1.1" 200 559
01/27/2018 12:07:09 AM Updating domoticz information for chromecast: Google Home
01/27/2018 12:07:09 AM Starting new HTTP connection (1): 127.0.0.1
01/27/2018 12:07:09 AM http://127.0.0.1:8080 "GET /json.htm?type=command&param=udevice&idx=21&nvalue=0&svalue=Spotify HTTP/1.1" 200 53
01/27/2018 12:07:21 AM Chromecast Bedroom now casting: Nothing
01/27/2018 12:07:21 AM Starting new HTTP connection (1): 127.0.0.1
01/27/2018 12:07:21 AM http://127.0.0.1:8080 "GET /json.htm?type=devices&rid=22 HTTP/1.1" 200 557
01/27/2018 12:07:21 AM Updating domoticz information for chromecast: Bedroom
01/27/2018 12:07:21 AM Starting new HTTP connection (1): 127.0.0.1
01/27/2018 12:07:21 AM http://127.0.0.1:8080 "GET /json.htm?type=command&param=udevice&idx=22&nvalue=0&svalue=Nothing HTTP/1.1" 200 53
Google Chromecast.JPG
Google Chromecast.JPG (35.49 KiB) Viewed 4423 times

Re: Python realtime Chromecast application detect script

Posted: Sunday 23 September 2018 16:37
by oldfantome
I have one error:
ImportError: No module named 'pcapy'

Re: Python realtime Chromecast application detect script

Posted: Sunday 23 September 2018 22:00
by triton
You can probably solve this by installing the python pcapy module. According to your profile you are using an Raspberry PI, correct?

trying issueing the following command, and try the script again:

Code: Select all

sudo apt install python-pcapy
That should install the pcapy module required for the script. Also see the post above yours. Pcapy is the package capture module that does the dirty work. It looks for multicast network packets and tries to identify if something is playing or not.

Cheers,

Triton

Re: Python realtime Chromecast application detect script

Posted: Sunday 23 September 2018 22:11
by oldfantome
this has been done. and I have never encountered any problems while installing python-pcapy.
but I do not know that I run the script I meet the error message

pi@raspberrypi:~ $ sudo apt install python-pcapy
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'état... Fait
python-pcapy is already the newest version (0.10.8-1).
0 mis à jour, 0 nouvellement installés, 0 à enlever et 0 non mis à jour.

Re: Python realtime Chromecast application detect script

Posted: Sunday 23 September 2018 23:26
by oldfantome
I resolve with pip3

Re: Python realtime Chromecast application detect script

Posted: Monday 15 October 2018 21:54
by Frixzon
Hey i have tried your Phython script and it looks cool. I do however get an error message after a while. The logoutput can be seen below incase someone knows how it can be resolved.

Code: Select all

10/15/2018 09:30:50 PM Traceback (most recent call last):
10/15/2018 09:30:50 PM   File "/usr/local/bin/chromecast_detect.py", line 130, in <module>
10/15/2018 09:30:50 PM main()
10/15/2018 09:30:50 PM   File "/usr/local/bin/chromecast_detect.py", line 125, in main
10/15/2018 09:30:50 PM NetworkMonitor().start()
10/15/2018 09:30:50 PM   File "/usr/local/bin/chromecast_detect.py", line 80, in start
10/15/2018 09:30:50 PM p.loop(-1, self.handle_packet)
10/15/2018 09:30:50 PM   File "/usr/local/bin/chromecast_detect.py", line 90, in handle_packet
10/15/2018 09:30:50 PM mdns = dpkt.dns.DNS(udp.data)
10/15/2018 09:30:50 PM   File "/usr/local/lib/python2.7/dist-packages/dpkt/dpkt.py", line 89, in __init__
10/15/2018 09:30:50 PM self.unpack(args[0])
10/15/2018 09:30:50 PM   File "/usr/local/lib/python2.7/dist-packages/dpkt/dns.py", line 369, in unpack
10/15/2018 09:30:50 PM rr, off = self.unpack_rr(buf, off)
10/15/2018 09:30:50 PM   File "/usr/local/lib/python2.7/dist-packages/dpkt/dns.py", line 353, in unpack_rr
10/15/2018 09:30:50 PM rr.unpack_rdata(buf, off)
10/15/2018 09:30:50 PM   File "/usr/local/lib/python2.7/dist-packages/dpkt/dns.py", line 325, in unpack_rdata
10/15/2018 09:30:50 PM raise dpkt.UnpackError('RR type %s is not supported' % self.type)
10/15/2018 09:30:50 PM dpkt.dpkt
10/15/2018 09:30:50 PM .
10/15/2018 09:30:50 PM UnpackError
10/15/2018 09:30:50 PM :
10/15/2018 09:30:50 PM RR type 47 is not supported

Re: Python realtime Chromecast application detect script

Posted: Wednesday 24 October 2018 22:43
by moengiant
Hey Triton - great script - however I will try to add a couple of lines to dump all the Chromecast statuses to a Domoticz user defined var. Having this info in a var makes it real easy to get all Chromecast statuses via the API using javascript.

{
"idx1": [{
"name": "Den TV",
"status": "None"
}],
"idx2": [{
"name": "Bedroom TV",
"status": "None"
}],
"idx3": [{
"name": "Living Room Stero",
"status": "Goldfrapp - Fly Me Away"
}]
}

Re: Python realtime Chromecast application detect script

Posted: Friday 07 December 2018 8:02
by DewGew
With this script is it possible to get more information from chromecast. I only get the app name not the episode or channel im playing?

Re: Python realtime Chromecast application detect script

Posted: Sunday 09 December 2018 13:15
by Ewaldharmsen