"""Bridge between emulated_roku and Home Assistant.""" import logging from emulated_roku import EmulatedRokuCommandHandler, EmulatedRokuServer from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import CoreState, EventOrigin LOGGER = logging.getLogger(__package__) EVENT_ROKU_COMMAND = "roku_command" ATTR_COMMAND_TYPE = "type" ATTR_SOURCE_NAME = "source_name" ATTR_KEY = "key" ATTR_APP_ID = "app_id" ROKU_COMMAND_KEYDOWN = "keydown" ROKU_COMMAND_KEYUP = "keyup" ROKU_COMMAND_KEYPRESS = "keypress" ROKU_COMMAND_LAUNCH = "launch" class EmulatedRoku: """Manages an emulated_roku server.""" def __init__( self, hass, name, host_ip, listen_port, advertise_ip, advertise_port, upnp_bind_multicast, ): """Initialize the properties.""" self.hass = hass self.roku_usn = name self.host_ip = host_ip self.listen_port = listen_port self.advertise_port = advertise_port self.advertise_ip = advertise_ip self.bind_multicast = upnp_bind_multicast self._api_server = None self._unsub_start_listener = None self._unsub_stop_listener = None async def setup(self): """Start the emulated_roku server.""" class EventCommandHandler(EmulatedRokuCommandHandler): """emulated_roku command handler to turn commands into events.""" def __init__(self, hass): self.hass = hass def on_keydown(self, roku_usn, key): """Handle keydown event.""" self.hass.bus.async_fire( EVENT_ROKU_COMMAND, { ATTR_SOURCE_NAME: roku_usn, ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYDOWN, ATTR_KEY: key, }, EventOrigin.local, ) def on_keyup(self, roku_usn, key): """Handle keyup event.""" self.hass.bus.async_fire( EVENT_ROKU_COMMAND, { ATTR_SOURCE_NAME: roku_usn, ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYUP, ATTR_KEY: key, }, EventOrigin.local, ) def on_keypress(self, roku_usn, key): """Handle keypress event.""" self.hass.bus.async_fire( EVENT_ROKU_COMMAND, { ATTR_SOURCE_NAME: roku_usn, ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYPRESS, ATTR_KEY: key, }, EventOrigin.local, ) def launch(self, roku_usn, app_id): """Handle launch event.""" self.hass.bus.async_fire( EVENT_ROKU_COMMAND, { ATTR_SOURCE_NAME: roku_usn, ATTR_COMMAND_TYPE: ROKU_COMMAND_LAUNCH, ATTR_APP_ID: app_id, }, EventOrigin.local, ) LOGGER.debug( "Initializing emulated_roku %s on %s:%s", self.roku_usn, self.host_ip, self.listen_port, ) handler = EventCommandHandler(self.hass) self._api_server = EmulatedRokuServer( self.hass.loop, handler, self.roku_usn, self.host_ip, self.listen_port, advertise_ip=self.advertise_ip, advertise_port=self.advertise_port, bind_multicast=self.bind_multicast, ) async def emulated_roku_stop(event): """Wrap the call to emulated_roku.close.""" LOGGER.debug("Stopping emulated_roku %s", self.roku_usn) self._unsub_stop_listener = None await self._api_server.close() async def emulated_roku_start(event): """Wrap the call to emulated_roku.start.""" try: LOGGER.debug("Starting emulated_roku %s", self.roku_usn) self._unsub_start_listener = None await self._api_server.start() except OSError: LOGGER.exception( "Failed to start Emulated Roku %s on %s:%s", self.roku_usn, self.host_ip, self.listen_port, ) # clean up inconsistent state on errors await emulated_roku_stop(None) else: self._unsub_stop_listener = self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, emulated_roku_stop ) # start immediately if already running if self.hass.state == CoreState.running: await emulated_roku_start(None) else: self._unsub_start_listener = self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, emulated_roku_start ) return True async def unload(self): """Unload the emulated_roku server.""" LOGGER.debug("Unloading emulated_roku %s", self.roku_usn) if self._unsub_start_listener: self._unsub_start_listener() self._unsub_start_listener = None if self._unsub_stop_listener: self._unsub_stop_listener() self._unsub_stop_listener = None await self._api_server.close() return True