예제 #1
0
 def __init__(self, hass, config):
     """Initialize the system based on host parameter."""
     self.hass = hass
     self.area = {}
     self.async_add_devices = {}
     self.waiting_devices = {}
     self.host = config[CONF_HOST]
     # Configure the dynalite devices
     self.dynalite_devices = DynaliteDevices(
         new_device_func=self.add_devices_when_registered,
         update_device_func=self.update_device,
     )
     self.dynalite_devices.configure(config)
예제 #2
0
 def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None:
     """Initialize the system based on host parameter."""
     self.hass = hass
     self.async_add_devices: dict[str, Callable] = {}
     self.waiting_devices: dict[str, list[str]] = {}
     self.host = config[CONF_HOST]
     # Configure the dynalite devices
     self.dynalite_devices = DynaliteDevices(
         new_device_func=self.add_devices_when_registered,
         update_device_func=self.update_device,
         notification_func=self.handle_notification,
     )
     self.dynalite_devices.configure(config)
예제 #3
0
 def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
     """Initialize the system based on host parameter."""
     self.hass = hass
     self.area = {}
     self.async_add_devices = {}
     self.waiting_devices = {}
     self.host = config[CONF_HOST]
     # Configure the dynalite devices
     self.dynalite_devices = DynaliteDevices(
         new_device_func=self.add_devices_when_registered,
         update_device_func=self.update_device,
     )
     self.dynalite_devices.configure(convert_config(config))
예제 #4
0
 def __init__(self, opp: OpenPeerPower, config: dict[str, Any]) -> None:
     """Initialize the system based on host parameter."""
     self.opp = opp
     self.area = {}
     self.async_add_devices = {}
     self.waiting_devices = {}
     self.host = config[CONF_HOST]
     # Configure the dynalite devices
     self.dynalite_devices = DynaliteDevices(
         new_device_func=self.add_devices_when_registered,
         update_device_func=self.update_device,
         notification_func=self.handle_notification,
     )
     self.dynalite_devices.configure(convert_config(config))
예제 #5
0
 def __init__(self, request, message_delay_zero):
     """Initialize the class."""
     request.addfinalizer(self.fin)
     self.reader = None
     self.writer = None
     self.server = None
     self.in_buffer = bytearray()
     self.new_dev_func = Mock()
     self.update_dev_func = Mock()
     self.exceptions = []
     if message_delay_zero:
         with patch("dynalite_devices_lib.dynalite.MESSAGE_DELAY", 0):
             self.dyn_dev = DynaliteDevices(
                 new_device_func=self.new_dev_func,
                 update_device_func=self.update_dev_func,
             )
     else:
         self.dyn_dev = DynaliteDevices(
             new_device_func=self.new_dev_func,
             update_device_func=self.update_dev_func,
         )
예제 #6
0
class DynaliteBridge:
    """Manages a single Dynalite bridge."""
    def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None:
        """Initialize the system based on host parameter."""
        self.hass = hass
        self.async_add_devices: dict[str, Callable] = {}
        self.waiting_devices: dict[str, list[str]] = {}
        self.host = config[CONF_HOST]
        # Configure the dynalite devices
        self.dynalite_devices = DynaliteDevices(
            new_device_func=self.add_devices_when_registered,
            update_device_func=self.update_device,
            notification_func=self.handle_notification,
        )
        self.dynalite_devices.configure(config)

    async def async_setup(self) -> bool:
        """Set up a Dynalite bridge."""
        # Configure the dynalite devices
        LOGGER.debug("Setting up bridge - host %s", self.host)
        return await self.dynalite_devices.async_setup()

    def reload_config(self, config: MappingProxyType[str, Any]) -> None:
        """Reconfigure a bridge when config changes."""
        LOGGER.debug("Reloading bridge - host %s, config %s", self.host,
                     config)
        self.dynalite_devices.configure(convert_config(config))

    def update_signal(self, device: DynaliteBaseDevice | None = None) -> str:
        """Create signal to use to trigger entity update."""
        if device:
            signal = f"dynalite-update-{self.host}-{device.unique_id}"
        else:
            signal = f"dynalite-update-{self.host}"
        return signal

    @callback
    def update_device(self, device: DynaliteBaseDevice | None = None) -> None:
        """Call when a device or all devices should be updated."""
        if not device:
            # This is used to signal connection or disconnection, so all devices may become available or not.
            log_string = ("Connected" if self.dynalite_devices.connected else
                          "Disconnected")
            LOGGER.info("%s to dynalite host", log_string)
            async_dispatcher_send(self.hass, self.update_signal())
        else:
            async_dispatcher_send(self.hass, self.update_signal(device))

    @callback
    def handle_notification(self, notification: DynaliteNotification) -> None:
        """Handle a notification from the platform and issue events."""
        if notification.notification == NOTIFICATION_PACKET:
            self.hass.bus.async_fire(
                "dynalite_packet",
                {
                    ATTR_HOST: self.host,
                    ATTR_PACKET: notification.data[NOTIFICATION_PACKET],
                },
            )
        if notification.notification == NOTIFICATION_PRESET:
            self.hass.bus.async_fire(
                "dynalite_preset",
                {
                    ATTR_HOST: self.host,
                    ATTR_AREA: notification.data[dyn_CONF_AREA],
                    ATTR_PRESET: notification.data[dyn_CONF_PRESET],
                },
            )

    @callback
    def register_add_devices(self, platform: str,
                             async_add_devices: Callable) -> None:
        """Add an async_add_entities for a category."""
        self.async_add_devices[platform] = async_add_devices
        if platform in self.waiting_devices:
            self.async_add_devices[platform](self.waiting_devices[platform])

    def add_devices_when_registered(self,
                                    devices: list[DynaliteBaseDevice]) -> None:
        """Add the devices to HA if the add devices callback was registered, otherwise queue until it is."""
        for platform in PLATFORMS:
            platform_devices = [
                device for device in devices if device.category == platform
            ]
            if platform in self.async_add_devices:
                self.async_add_devices[platform](platform_devices)
            else:  # handle it later when it is registered
                if platform not in self.waiting_devices:
                    self.waiting_devices[platform] = []
                self.waiting_devices[platform].extend(platform_devices)
예제 #7
0
class DynaliteBridge:
    """Manages a single Dynalite bridge."""
    def __init__(self, hass, config):
        """Initialize the system based on host parameter."""
        self.hass = hass
        self.area = {}
        self.async_add_devices = {}
        self.waiting_devices = {}
        self.host = config[CONF_HOST]
        # Configure the dynalite devices
        self.dynalite_devices = DynaliteDevices(
            new_device_func=self.add_devices_when_registered,
            update_device_func=self.update_device,
        )
        self.dynalite_devices.configure(config)

    async def async_setup(self):
        """Set up a Dynalite bridge."""
        # Configure the dynalite devices
        LOGGER.debug("Setting up bridge - host %s", self.host)
        return await self.dynalite_devices.async_setup()

    def reload_config(self, config):
        """Reconfigure a bridge when config changes."""
        LOGGER.debug("Reloading bridge - host %s, config %s", self.host,
                     config)
        self.dynalite_devices.configure(config)

    def update_signal(self, device=None):
        """Create signal to use to trigger entity update."""
        if device:
            signal = f"dynalite-update-{self.host}-{device.unique_id}"
        else:
            signal = f"dynalite-update-{self.host}"
        return signal

    @callback
    def update_device(self, device):
        """Call when a device or all devices should be updated."""
        if device == CONF_ALL:
            # This is used to signal connection or disconnection, so all devices may become available or not.
            log_string = ("Connected" if self.dynalite_devices.available else
                          "Disconnected")
            LOGGER.info("%s to dynalite host", log_string)
            async_dispatcher_send(self.hass, self.update_signal())
        else:
            async_dispatcher_send(self.hass, self.update_signal(device))

    @callback
    def register_add_devices(self, platform, async_add_devices):
        """Add an async_add_entities for a category."""
        self.async_add_devices[platform] = async_add_devices
        if platform in self.waiting_devices:
            self.async_add_devices[platform](self.waiting_devices[platform])

    def add_devices_when_registered(self, devices):
        """Add the devices to HA if the add devices callback was registered, otherwise queue until it is."""
        for platform in ENTITY_PLATFORMS:
            platform_devices = [
                device for device in devices if device.category == platform
            ]
            if platform in self.async_add_devices:
                self.async_add_devices[platform](platform_devices)
            else:  # handle it later when it is registered
                if platform not in self.waiting_devices:
                    self.waiting_devices[platform] = []
                self.waiting_devices[platform].extend(platform_devices)
예제 #8
0
class DynaliteBridge:
    """Manages a single Dynalite bridge."""
    def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
        """Initialize the system based on host parameter."""
        self.hass = hass
        self.area = {}
        self.async_add_devices = {}
        self.waiting_devices = {}
        self.area_reg = None
        self.device_reg = None
        self.host = config[CONF_HOST]
        self.areacreate = config[CONF_AREA_CREATE].lower()
        # Configure the dynalite devices
        self.dynalite_devices = DynaliteDevices(
            new_device_func=self.add_devices_when_registered,
            update_device_func=self.update_device,
        )
        self.dynalite_devices.configure(config)

    async def async_setup(self) -> bool:
        """Set up a Dynalite bridge."""
        # Configure the dynalite devices
        LOGGER.debug("Setting up bridge - host %s", self.host)
        self.area_reg = await ar.async_get_registry(self.hass)
        self.device_reg = await dr.async_get_registry(self.hass)
        return await self.dynalite_devices.async_setup()

    def reload_config(self, config: Dict[str, Any]) -> None:
        """Reconfigure a bridge when config changes."""
        LOGGER.debug("Reloading bridge - host %s, config %s", self.host,
                     config)
        self.dynalite_devices.configure(config)

    def update_signal(self, device: "DynaliteBase" = None) -> str:
        """Create signal to use to trigger entity update."""
        if device:
            signal = f"dynalite-update-{self.host}-{device.unique_id}"
        else:
            signal = f"dynalite-update-{self.host}"
        return signal

    @callback
    def update_device(self, device: "DynaliteBase") -> None:
        """Call when a device or all devices should be updated."""
        if device == CONF_ALL:
            # This is used to signal connection or disconnection, so all devices may become available or not.
            log_string = ("Connected" if self.dynalite_devices.connected else
                          "Disconnected")
            LOGGER.info("%s to dynalite host", log_string)
            async_dispatcher_send(self.hass, self.update_signal())
        else:
            async_dispatcher_send(self.hass, self.update_signal(device))

    @callback
    def register_add_devices(self, platform: str,
                             async_add_devices: Callable) -> None:
        """Add an async_add_entities for a category."""
        self.async_add_devices[platform] = async_add_devices
        if platform in self.waiting_devices:
            self.async_add_devices[platform](self.waiting_devices[platform])

    def add_devices_when_registered(self,
                                    devices: List["DynaliteBase"]) -> None:
        """Add the devices to HA if the add devices callback was registered, otherwise queue until it is."""
        for platform in ENTITY_PLATFORMS:
            platform_devices = [
                device for device in devices if device.category == platform
            ]
            if platform in self.async_add_devices:
                self.async_add_devices[platform](platform_devices)
            else:  # handle it later when it is registered
                if platform not in self.waiting_devices:
                    self.waiting_devices[platform] = []
                self.waiting_devices[platform].extend(platform_devices)

    @property
    def available(self):
        return self.dynalite_devices.connected

    async def entity_added_to_ha(self, entity):
        """Call when an entity is added to HA so we can set its area."""
        if self.areacreate == CONF_AREA_CREATE_MANUAL:
            LOGGER.debug("area assignment set to manual - ignoring")
            return  # only need to update the areas if it is 'assign' or 'create'
        assert self.areacreate in [
            CONF_AREA_CREATE_ASSIGN, CONF_AREA_CREATE_AUTO
        ]
        uniqueID = entity.unique_id
        hassArea = entity.get_hass_area
        if hassArea != "":
            LOGGER.debug("assigning hass area %s to entity %s" %
                         (hassArea, uniqueID))
            device = self.device_reg.async_get_device({(DOMAIN, uniqueID)}, ())
            if not device:
                LOGGER.error("uniqueID %s has no device ID", uniqueID)
                return
            areaEntry = self.area_reg._async_is_registered(hassArea)
            if not areaEntry:
                if self.areacreate != CONF_AREA_CREATE_AUTO:
                    LOGGER.debug(
                        "Area %s not registered and " + CONF_AREA_CREATE +
                        ' is not "' + CONF_AREA_CREATE_AUTO + '" - ignoring',
                        hassArea,
                    )
                    return
                else:
                    LOGGER.debug("Creating new area %s", hassArea)
                    areaEntry = self.area_reg.async_create(hassArea)
            LOGGER.debug("assigning deviceid=%s area_id=%s" %
                         (device.id, areaEntry.id))
            self.device_reg.async_update_device(device.id,
                                                area_id=areaEntry.id)
예제 #9
0
class MockGateway:
    """Class to mock a TCP gateway."""
    def __init__(self, request, message_delay_zero):
        """Initialize the class."""
        request.addfinalizer(self.fin)
        self.reader = None
        self.writer = None
        self.server = None
        self.in_buffer = bytearray()
        self.new_dev_func = Mock()
        self.update_dev_func = Mock()
        self.exceptions = []
        if message_delay_zero:
            with patch("dynalite_devices_lib.dynalite.MESSAGE_DELAY", 0):
                self.dyn_dev = DynaliteDevices(
                    new_device_func=self.new_dev_func,
                    update_device_func=self.update_dev_func,
                )
        else:
            self.dyn_dev = DynaliteDevices(
                new_device_func=self.new_dev_func,
                update_device_func=self.update_dev_func,
            )

    async def run_server(self):
        """Run the actual server."""
        async def handle_connection(reader, writer):
            """Run a single session. Assumes only one for tests."""
            assert not self.reader and not self.writer
            self.reader = reader
            self.writer = writer
            while not reader.at_eof():
                data = await reader.read(100)
                addr = writer.get_extra_info("peername")
                dyn_const.LOGGER.debug("Received message from %s - %s", addr,
                                       [int(byte) for byte in data])
                for byte in data:
                    self.in_buffer.append(byte)
            self.reader = self.writer = None

        self.server = await asyncio.start_server(handle_connection,
                                                 "127.0.0.1", 12345)
        addr = self.server.sockets[0].getsockname()
        dyn_const.LOGGER.debug("Serving on %s", addr)
        async with self.server:
            await self.server.serve_forever()

    async def async_setup_server(self):
        """Start the server."""
        def exc_handle(loop, context):
            """Handle exceptions by rethrowing them, which will fail the test."""
            self.exceptions.append(context["exception"])

        asyncio.get_event_loop().set_exception_handler(exc_handle)
        asyncio.create_task(self.run_server())
        await asyncio.sleep(0.01)

    async def check_writes(self, packets):
        """Check that a set of packets was written."""
        await asyncio.sleep(0.01)
        assert len(self.in_buffer) == len(packets) * 8
        received = [
            self.in_buffer[i * 8:i * 8 + 8] for i in range(0, len(packets))
        ]
        for packet in packets:
            assert packet.msg in received
        self.reset()

    async def check_single_write(self, packet):
        """Check that there was only a single packet written."""
        await self.check_writes([packet])

    async def receive_message(self, message):
        """Fake a received message."""
        self.writer.write(message)
        await self.writer.drain()
        await asyncio.sleep(0.01)

    async def receive(self, packet):
        """Fake a received packet."""
        await self.receive_message(packet.msg)

    def configure_dyn_dev(self, config, num_devices=1):
        """Configure the DynaliteDevices."""
        self.new_dev_func.reset_mock()
        self.dyn_dev.configure(config)
        if num_devices == 0:
            self.new_dev_func.assert_not_called()
            return None
        self.new_dev_func.assert_called_once()
        assert len(self.new_dev_func.mock_calls[0][1][0]) == num_devices
        return self.new_dev_func.mock_calls[0][1][0]

    async def async_setup_dyn_dev(self):
        """Set up the internal DynaliteDevices."""
        return await self.dyn_dev.async_setup()

    def reset(self):
        """Reset the in buffer."""
        self.in_buffer = bytearray()

    def reset_connection(self):
        """Reset the current connection."""
        if self.writer:
            self.writer.close()

    async def shutdown(self):
        """Shut down the server."""
        self.reset_connection()
        self.server.close()
        await self.server.wait_closed()

    async def async_fin(self):
        """Shut the gateway down."""
        await self.shutdown()
        await self.dyn_dev.async_reset()

    def fin(self):
        """Run shutdown async."""
        asyncio.get_event_loop().run_until_complete(self.async_fin())
        for ex in self.exceptions:
            raise ex