Python realtime Chromecast application detect script

Python and python framework

Moderator: leecollings

Post Reply
triton
Posts: 15
Joined: Monday 03 April 2017 15:01
Target OS: Linux
Domoticz version: 4.9701
Location: Netherlands
Contact:

Python realtime Chromecast application detect script

Post 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()
triton
Posts: 15
Joined: Monday 03 April 2017 15:01
Target OS: Linux
Domoticz version: 4.9701
Location: Netherlands
Contact:

Re: Python realtime Chromecast application detect script

Post 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
;;
dextm80
Posts: 117
Joined: Tuesday 24 October 2017 18:32
Target OS: Linux
Domoticz version: 4.10159
Contact:

Re: Python realtime Chromecast application detect script

Post by dextm80 »

What i can do with this script? Only status?
Domoticz on AsRock j3455-ITX 8gb ram - Aeotec ZWave Usb Stick - RFLink 433Mhz
1x Fibaro Wall Plug
1x Fibaro Motion Sensor
x NeoCoolcam Wall Plug
Netatmo Weather Station - Netatmo Thermostat
Philips Hue Bridge
triton
Posts: 15
Joined: Monday 03 April 2017 15:01
Target OS: Linux
Domoticz version: 4.9701
Location: Netherlands
Contact:

Re: Python realtime Chromecast application detect script

Post 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.
User avatar
sincze
Posts: 1299
Joined: Monday 02 June 2014 22:46
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.4
Location: Netherlands / Breda Area
Contact:

Re: Python realtime Chromecast application detect script

Post 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 4416 times
Pass2php
LAN: RFLink, P1, OTGW, MySensors
USB: RFXCom, ZWave, Sonoff 3
MQTT: ZIgbee2MQTT,
ZWAVE: Zwave-JS-UI
WIFI: Mi-light, Tasmota, Xiaomi Shelly
Solar: Omnik, PVOutput
Video: Kodi, Harmony HUB, Chromecast
Sensors: You name it I got 1.
oldfantome
Posts: 12
Joined: Wednesday 19 September 2018 22:14
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python realtime Chromecast application detect script

Post by oldfantome »

I have one error:
ImportError: No module named 'pcapy'
triton
Posts: 15
Joined: Monday 03 April 2017 15:01
Target OS: Linux
Domoticz version: 4.9701
Location: Netherlands
Contact:

Re: Python realtime Chromecast application detect script

Post 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
oldfantome
Posts: 12
Joined: Wednesday 19 September 2018 22:14
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python realtime Chromecast application detect script

Post 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.
oldfantome
Posts: 12
Joined: Wednesday 19 September 2018 22:14
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python realtime Chromecast application detect script

Post by oldfantome »

I resolve with pip3
Frixzon
Posts: 14
Joined: Saturday 13 August 2016 10:09
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python realtime Chromecast application detect script

Post 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
moengiant
Posts: 28
Joined: Monday 25 January 2016 6:33
Target OS: Windows
Domoticz version:
Contact:

Re: Python realtime Chromecast application detect script

Post 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"
}]
}
I'd rather have a bottle in front of me than a frontal lobotomy
User avatar
DewGew
Posts: 579
Joined: Thursday 21 April 2016 12:01
Target OS: Raspberry Pi / ODroid
Domoticz version: V4.10618
Location: Sweden
Contact:

Re: Python realtime Chromecast application detect script

Post 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?
Raspberry Pi 3 | domoticz | Aeon Labs Z-Stick GEN5 | RFlink gateway
NanoPi NEO-air | REGO6XX interface | Machinon theme | Homebridge | Domoticz Google Assistant | ideAlarm
Ewaldharmsen
Posts: 130
Joined: Tuesday 07 February 2017 15:00
Target OS: Linux
Domoticz version:
Contact:

Re: Python realtime Chromecast application detect script

Post by Ewaldharmsen »

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest