Files
Home-Assistant-Addons/dashcast/app_mqtt.py
T
2020-04-03 17:15:16 -04:00

226 lines
8.1 KiB
Python
Executable File

"""
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)