Dashcast add-on

This commit is contained in:
Bogdan Vlaicu
2020-04-03 17:15:16 -04:00
parent 7a6b582298
commit 7cc1327814
5 changed files with 258 additions and 2 deletions
+26
View File
@@ -0,0 +1,26 @@
# This Dockerfile will only work with host networking enabled.
# Use an official Python runtime as a parent image
FROM python:3
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
ADD . /app
# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt
# Define environment variables
ENV DEFAULT_DASHBOARD_URL "https://darksky.net"
ENV DEFAULT_DASHBOARD_URL_FORCE "False"
ENV DISPLAY_NAME ""
ENV IGNORE_CEC "True"
ENV MQTT_SERVER "iot.eclipse.org"
ENV MQTT_USERNAME ""
ENV MQTT_PASSWORD ""
# Run app.sh when the container launches
CMD ["./app.sh"]
+2
View File
@@ -5,6 +5,8 @@
This is a Dashcast add-on for Home Assistant. This is a Dashcast add-on for Home Assistant.
Dascast is an app that displays dashboard web pages on your Chromecast device. Dascast is an app that displays dashboard web pages on your Chromecast device.
Credit to [ragingcomputer/dashcast-docker-MQTT](https://github.com/ragingcomputer/dashcast-docker-MQTT) for the docker code.
## Installation ## Installation
The installation of this add-on is pretty straightforward and not different in The installation of this add-on is pretty straightforward and not different in
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Keep the python app alive.
while true; do python -u app_mqtt.py --show-debug; done
+225
View File
@@ -0,0 +1,225 @@
"""
Run DashCast semi-persistently on a Chromecast while allowing other
Chromecast apps to work also by only launching when idle.
"""
from __future__ import print_function
import time
import os
import sys
import logging
import json
import paho.mqtt.client as mqtt
import pychromecast
import pychromecast.controllers.dashcast as dashcast
print('DashCast')
print('Searching for Chromecasts...')
DEFAULT_DASHBOARD_URL = os.getenv('DEFAULT_DASHBOARD_URL', 'https://home-assistant.io')
DEFAULT_DASHBOARD_URL_FORCE = os.getenv('DEFAULT_DASHBOARD_URL_FORCE') == 'True'
DISPLAY_NAME = os.getenv('DISPLAY_NAME')
IGNORE_CEC = os.getenv('IGNORE_CEC') == 'True'
SUBSCRIBE = 'chromecast/' + str(DISPLAY_NAME) + '/command/dashcast'
MQTT_SERVER = os.getenv('MQTT_SERVER', 'iot.eclipse.org')
MQTT_USERNAME = os.getenv('MQTT_USERNAME')
MQTT_PASSWORD = os.getenv('MQTT_PASSWORD')
if IGNORE_CEC:
print('Ignoring CEC for Chromecast', DISPLAY_NAME)
pychromecast.IGNORE_CEC.append(DISPLAY_NAME)
if '--show-debug' in sys.argv:
logging.basicConfig(level=logging.DEBUG)
class DashboardLauncher():
def __init__(self, device, dashboard_url='https://home-assistant.io',
dashboard_url_force=False, dashboard_app_name='DashCast'):
self.device = device
print('DashboardLauncher', self.device.name)
# register dashcast controller
self.controller = dashcast.DashCastController()
self.device.register_handler(self.controller)
# Breaking Change pychromecast 3.0
# Do not automatically start worker thread and connect in Chromecast constructor
# https://github.com/balloob/pychromecast/pull/271
self.device.wait()
# register status listener
receiver_controller = device.socket_client.receiver_controller
receiver_controller.register_status_listener(self)
# register connection listener
self.device.socket_client.media_controller.register_status_listener(self)
self.device.register_connection_listener(self)
# set default url and dashboard app name
self.dashboard_url = dashboard_url
self.dashboard_url_force = dashboard_url_force
self.dashboard_app_name = dashboard_app_name
self.takeover = False
# Check status on init.
self.new_cast_status(self.device.status)
# Launch dashboard on init.
self.launch_dashboard(self.dashboard_url, self.dashboard_url_force)
# While dashboard is launching, start MQTT connection
print("Starting MQTT")
client = mqtt.Client("client_" + str(DISPLAY_NAME)) # create new instance
client.on_message = self.on_message # attach function to callback
client.on_connect = self.on_connect
print("Connecting to Broker: " + MQTT_SERVER)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
client.connect(MQTT_SERVER) # connect to broker
# wait for dashboard to finish loading, then start MQTT client loop
time.sleep(10)
client.loop_start()
# keep_looping is the control variable.
# If it is False, loop stops, program cleans up and exits
self.keep_looping = True
# if launch_it is set to true, the dashboard is relaunched
# otherwise sleep a second and check again
self.launch_it = False
while self.keep_looping:
if self.launch_it:
print('launch_it', self.launch_it)
self.launch_dashboard(self.dashboard_url, self.dashboard_url_force)
self.launch_it = False
time.sleep(10)
time.sleep(1)
client.loop_stop()
# define MQTT client callbacks
def on_connect(self, client, userdata, flags, rc):
print("Connected with result code "+str(rc))
# Subscribing in on_connect() means that if we lose the connection and
# reconnect then subscriptions will be renewed.
print("Subscribing to topic ", SUBSCRIBE)
client.subscribe(SUBSCRIBE)
def on_message(self, client, userdata, message):
print("Message received: ", str(message.payload.decode("utf-8")))
print("-Topic: ", message.topic)
print("-QOS: ", message.qos)
print("-Retain: ", message.retain)
# try decoding received message
try:
json_decode = str(message.payload.decode("utf-8", "ignore"))
print("Decoding Json")
parsed_json = json.loads(json_decode)
except json.decoder.JSONDecodeError:
print("Error passing JSON")
return
# set dashboard_url to received data and
# set to relaunch/update dashboard
print("Chromecast: " + DISPLAY_NAME)
if 'url' in parsed_json:
print("Url: " + parsed_json["url"])
self.dashboard_url = parsed_json["url"]
if 'force' in parsed_json:
print("Force: " + str(parsed_json["force"]))
self.dashboard_url_force = parsed_json["force"]
else:
self.dashboard_url_force = False
if 'takeover' in parsed_json:
print("Takeover: " + str(parsed_json["takeover"]))
self.takeover = parsed_json["takeover"]
else:
self.takeover = False
self.dashboard_url = parsed_json["url"]
if self.is_dashboard_active() or self.takeover:
self.launch_it = True
# Define pychromecast Callback Receivers
def new_cast_status(self, cast_status):
""" Called when a new cast status has been received. """
print('new_cast_status', self.device.name, cast_status)
def should_launch():
""" If the device is active, the dashboard is not already active, and no other app is active. """
print('should launch', self.is_device_active(),
not self.is_dashboard_active(), not self.is_other_app_active())
return (self.is_device_active()
and not self.is_dashboard_active()
and not self.is_other_app_active())
if should_launch():
self.launch_it = True
def new_media_status(self, media_status):
"""Called when a new MediaStatus is received."""
print('new_media_status', self.device.name, media_status)
def new_connection_status(self, connection_status):
"""Called when a new ConnectionStatus is received."""
print('new_connection_status', self.device.name, connection_status)
def is_device_active(self):
""" Returns if there is currently an app running and (maybe) visible. """
return (self.device.status is not None
#and self.device.app_id is not None
and (self.device.status.is_active_input or self.device.ignore_cec)
#and (not self.device.status.is_stand_by and not self.device.ignore_cec)
)
def is_dashboard_active(self):
""" Returns if the dashboard is (probably) visible. """
return self.device.app_display_name == self.dashboard_app_name
def is_other_app_active(self):
""" Returns if an app other than the dashboard or the Backdrop is (probably) visible. """
return self.device.app_display_name not in ('Backdrop', self.dashboard_app_name)
def launch_dashboard(self, url_to_load, force):
print('Launching dashboard on Chromecast', self.device.name)
def callback(response):
print('callback called', response)
try:
self.controller.load_url(url_to_load, force, callback_function=callback)
except Exception as e:
print(e)
pass
casts = pychromecast.get_chromecasts()
if len(casts) == 0:
print('No Devices Found')
exit()
for cc in casts:
print('enumerate-casts', cc.device.friendly_name)
cast = next(cc for cc in casts if DISPLAY_NAME in (None, '') or cc.device.friendly_name == DISPLAY_NAME)
if not cast:
print('Chromecast with name', DISPLAY_NAME, 'not found')
exit()
DashboardLauncher(cast, dashboard_url=DEFAULT_DASHBOARD_URL, dashboard_url_force=DEFAULT_DASHBOARD_URL_FORCE)
# Keep running
while True:
time.sleep(1)
+1 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "Dashcast", "name": "Dashcast",
"version": "latest", "version": "0.0.1",
"slug": "dashcast", "slug": "dashcast",
"description": "Display dashboard web pages on your Chromecast device", "description": "Display dashboard web pages on your Chromecast device",
"url": "https://github.com/ragingcomputer/dashcast-docker-MQTT", "url": "https://github.com/ragingcomputer/dashcast-docker-MQTT",
@@ -23,7 +23,6 @@
"MQTT_USERNAME": "mqtt", "MQTT_USERNAME": "mqtt",
"MQTT_PASSWORD": "testpassword" "MQTT_PASSWORD": "testpassword"
}, },
"image": "ragingcomputer/dashcast-docker-mqtt",
"options": { "options": {
"log_level": "info", "log_level": "info",
"chromecast_name": "Livingroom Chromecast", "chromecast_name": "Livingroom Chromecast",