"""Support for SCSGate components.""" import logging from threading import Lock from scsgate.connection import Connection from scsgate.messages import ScenarioTriggeredMessage, StateMessage from scsgate.reactor import Reactor from scsgate.tasks import GetStatusTask import voluptuous as vol from homeassistant.const import CONF_DEVICE, CONF_NAME, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) CONF_SCS_ID = "scs_id" DOMAIN = "scsgate" CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_DEVICE): cv.string})}, extra=vol.ALLOW_EXTRA ) SCSGATE_SCHEMA = vol.Schema( {vol.Required(CONF_SCS_ID): cv.string, vol.Optional(CONF_NAME): cv.string} ) def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the SCSGate component.""" device = config[DOMAIN][CONF_DEVICE] scsgate = None try: scsgate = SCSGate(device=device, logger=_LOGGER) scsgate.start() except Exception as exception: # pylint: disable=broad-except _LOGGER.error("Cannot setup SCSGate component: %s", exception) return False def stop_monitor(event): """Stop the SCSGate.""" _LOGGER.info("Stopping SCSGate monitor thread") scsgate.stop() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor) hass.data[DOMAIN] = scsgate return True class SCSGate: """The class for dealing with the SCSGate device via scsgate.Reactor.""" def __init__(self, device, logger): """Initialize the SCSGate.""" self._logger = logger self._devices = {} self._devices_to_register = {} self._devices_to_register_lock = Lock() self._device_being_registered = None self._device_being_registered_lock = Lock() connection = Connection(device=device, logger=self._logger) self._reactor = Reactor( connection=connection, logger=self._logger, handle_message=self.handle_message, ) def handle_message(self, message): """Handle a messages seen on the bus.""" self._logger.debug(f"Received message {message}") if not isinstance(message, StateMessage) and not isinstance( message, ScenarioTriggeredMessage ): msg = f"Ignored message {message} - not relevant type" self._logger.debug(msg) return if message.entity in self._devices: new_device_activated = False with self._devices_to_register_lock: if message.entity == self._device_being_registered: self._device_being_registered = None new_device_activated = True if new_device_activated: self._activate_next_device() try: self._devices[message.entity].process_event(message) except Exception as exception: # pylint: disable=broad-except msg = f"Exception while processing event: {exception}" self._logger.error(msg) else: self._logger.info( "Ignoring state message for device {} because unknown".format( message.entity ) ) @property def devices(self): """Return a dictionary with known devices. Key is device ID, value is the device itself. """ return self._devices def add_device(self, device): """Add the specified device. The list contain already registered ones. Beware: this is not what you usually want to do, take a look at `add_devices_to_register` """ self._devices[device.scs_id] = device def add_devices_to_register(self, devices): """List of devices to be registered.""" with self._devices_to_register_lock: for device in devices: self._devices_to_register[device.scs_id] = device self._activate_next_device() def _activate_next_device(self): """Start the activation of the first device.""" with self._devices_to_register_lock: while self._devices_to_register: device = self._devices_to_register.popitem()[1] self._devices[device.scs_id] = device self._device_being_registered = device.scs_id self._reactor.append_task(GetStatusTask(target=device.scs_id)) def is_device_registered(self, device_id): """Check whether a device is already registered or not.""" with self._devices_to_register_lock: if device_id in self._devices_to_register: return False with self._device_being_registered_lock: if device_id == self._device_being_registered: return False return True def start(self): """Start the scsgate.Reactor.""" self._reactor.start() def stop(self): """Stop the scsgate.Reactor.""" self._reactor.stop() def append_task(self, task): """Register a new task to be executed.""" self._reactor.append_task(task)