import logging import threading try: import BaseHTTPServer except ImportError: import http.server as BaseHTTPServer from urllib.request import urlopen, URLError import urllib.parse as urlparse from .baseplatform import BasePlatform # Magic Mirror platform # ----------------------------------------------------------------------------------------------------------------- # This device platform is made to integrate with the Magic Mirror project: https://github.com/MichMich/MagicMirror # Specifically, this communicates with the MMM-AlexaPi module: https://github.com/dgonano/MMM-AlexaPi # The two systems communicate over GET requests which, # - Update the Magic Mirror display with AlexaPi's status (No connection, idle, listening, processing, speaking) # - Allow the Magic Mirror to trigger a 'start listening' request logger = logging.getLogger(__name__) class MagicmirrorPlatform(BasePlatform): def __init__(self, config): logger.debug("Initialising Magic Mirror platorm") super(MagicmirrorPlatform, self).__init__(config, 'magicmirror') self.host_name = self._pconfig['hostname'] self.port_number = self._pconfig['port'] self.mm_host = self._pconfig['mm_hostname'] self.mm_port = self._pconfig['mm_port'] self.hb_timer = self._pconfig['hb_timer'] self.shutdown = False self.httpd = None self.serverthread = None def setup(self): logger.debug("Setting up Magic Mirror platform") logger.info("Magic Mirror HTTP Server - %s:%s", self.host_name, self.port_number) # Setup http server self.httpd = CallbackHTTPServer((self.host_name, self.port_number), MMHTTPHandler) self.httpd.set_callback(self.http_callback) self.serverthread = threading.Thread(target=self.httpd.serve_forever) self.serverthread.daemon = True def indicate_failure(self): logger.debug("Indicating Failure") self.update_mm("failure") def indicate_success(self): logger.debug("Indicating Success") self.update_mm("success") def after_setup(self, trigger_callback=None): self._trigger_callback = trigger_callback logger.debug("Starting Magic Mirror platform HTTP Server") self.serverthread.start() logger.debug("Starting Magic Mirror heartbeat with %s second interval", self.hb_timer) self.mm_heartbeat() def indicate_recording(self, state=True): logger.debug("Indicate Start Recording" if state else "Indicate Stop Recording") self.update_mm("recording" if state else "idle") def indicate_playback(self, state=True): logger.debug("Indicate Start Playing" if state else "Indicate Stop Playing") self.update_mm("playback" if state else "idle") def indicate_processing(self, state=True): logger.debug("Indicate Start Processing" if state else "Indicate Stop Processing") self.update_mm("processing" if state else "idle") def force_recording(self): return False def update_mm(self, status): address = ("http://" + self.mm_host + ":" + self.mm_port + "/alexapi?action=AVSSTATUS&status=" + status) logger.debug("Calling URL: %s", address) try: response = urlopen(address).read() except URLError as err: logger.error("URLError: %s", err.reason) return logger.debug("Response: %s", response) def mm_heartbeat(self): # Check if stop or set next timer if self.shutdown: return threading.Timer(self.hb_timer, self.mm_heartbeat).start() address = ("http://" + self.mm_host + ":" + self.mm_port + "/alexapi?action=AVSHB") logger.debug("Sending MM Heatbeat") try: response = urlopen(address).read() except URLError as err: logger.error("URLError: %s", err.reason) return logger.debug("Response: %s", response) def http_callback(self, query_dict): if (query_dict['action'][0] == "requestrecord"): if self._trigger_callback: self._trigger_callback() return True return False def cleanup(self): logger.debug("Cleaning up Magic Mirror platform") self.httpd.shutdown() self.shutdown = True # Subclass HTTPServer with additional callback class CallbackHTTPServer(BaseHTTPServer.HTTPServer): def set_callback(self, callback): self.RequestHandlerClass.set_callback(callback) # Subclass Request Handler to use callback class MMHTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): @classmethod def set_callback(cls, callback): cls.callback = callback def do_HEAD(self): # pylint: disable=invalid-name self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() def do_GET(self): # pylint: disable=invalid-name self.send_response(200) self.end_headers() query = urlparse.urlsplit(self.path).query query_dict = urlparse.parse_qs(query) if 'action' in query_dict.keys(): if (self.callback(query_dict)): self.wfile.write('{"status":"success"}') else: self.wfile.write('{"status":"error", "reason":"unknown_action"}') else: self.wfile.write('{"status":"error", "reason":"unknown_command"}')