Exemple #1
0
    def try_parse(data: str) -> Optional[List[Input]]:
        """Try to parse the given input text.

        Will return a list of parsed Inputs. The list might be empty (but not
        null).

        :param    data    str:    The input data received from LCN-PCHK

        :return:            The parsed Inputs (never null)
        :rtype:             List with instances of :class:`~pypck.input.Input`
        """
        matcher_pos = PckParser.PATTERN_ACK_POS.match(data)
        if matcher_pos:
            addr = LcnAddr(
                int(matcher_pos.group("seg_id")), int(matcher_pos.group("mod_id"))
            )
            return [ModAck(addr, -1)]

        matcher_neg = PckParser.PATTERN_ACK_NEG.match(data)
        if matcher_neg:
            addr = LcnAddr(
                int(matcher_neg.group("seg_id")), int(matcher_neg.group("mod_id"))
            )
            return [ModAck(addr, int(matcher_neg.group("code")))]

        return None
Exemple #2
0
    def get_address_conn(
        self, addr: LcnAddr
    ) -> Union[ModuleConnection, GroupConnection]:
        """Create and/or return the given LCN module or group.

        The LCN module/group object is used for further communication
        with the module/group (e.g. sending commands).

        :param    addr:    The module's/group's address
        :type     addr:    :class:`~LcnAddr`

        :returns: The address connection object (never null)
        :rtype: `~ModuleConnection` or `~GroupConnection`

        :Example:

        >>> address = LcnAddr(0, 7, False)
        >>> module = pchk_connection.get_address_conn(address)
        >>> module.toggle_output(0, 5)
        """
        if addr.get_seg_id() == 0 and self.local_seg_id != -1:
            addr.seg_id = self.local_seg_id
        address_conn = self.address_conns.get(addr, None)
        if address_conn is None:
            if addr.is_group():
                address_conn = GroupConnection(self, addr.seg_id, addr.addr_id)
            else:
                address_conn = ModuleConnection(self, addr.seg_id, addr.addr_id)

            self.address_conns[addr] = address_conn

        return address_conn
Exemple #3
0
async def test_groups_dynamic_membership_discovery(pchk_server, pypck_client):
    """Test module scan."""
    await pypck_client.async_connect()
    module = pypck_client.get_address_conn(LcnAddr(0, 10, False))

    task = asyncio.create_task(module.request_dynamic_groups())
    assert await pchk_server.received(">M000010.GD")
    await pchk_server.send_message("=M000010.GD008011200051")
    assert await task == {
        LcnAddr(0, 11, True),
        LcnAddr(0, 200, True),
        LcnAddr(0, 51, True),
    }
Exemple #4
0
    def physical_to_logical(self, addr: LcnAddr) -> LcnAddr:
        """Convert the physical segment id of an address to the logical one.

        :param    addr:    The module's/group's address
        :type     addr:    :class:`~LcnAddr`

        :returns:    The module's/group's address
        :rtype:      :class:`~LcnAddr`
        """
        return LcnAddr(
            self.local_seg_id if addr.get_seg_id() == 0 else addr.get_seg_id(),
            addr.get_id(),
            addr.is_group(),
        )
async def test_fire_transmitter_event(hass, entry):
    """Test the transmitter event is fired."""
    await init_integration(hass, entry)

    events = async_capture_events(hass, "lcn_transmitter")

    inp = ModStatusAccessControl(
        LcnAddr(0, 7, False),
        periphery=AccessControlPeriphery.TRANSMITTER,
        code="aabbcc",
        level=0,
        key=0,
        action=KeyAction.HIT,
    )

    lcn_connection = MockPchkConnectionManager.return_value
    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(events) == 1
    assert events[0].event_type == "lcn_transmitter"
    assert events[0].data["code"] == "aabbcc"
    assert events[0].data["level"] == 0
    assert events[0].data["key"] == 0
    assert events[0].data["action"] == "hit"
Exemple #6
0
    def get_module_conn(
        self, addr: LcnAddr, request_serials: bool = True
    ) -> ModuleConnection:
        """Create and/or return the given LCN module.

        The ModuleConnection object is used for further communication
        with the module (e.g. sending commands).

        :param    addr:    The module's address
        :type     addr:    :class:`~LcnAddr`

        :returns: The address connection object (never null)
        :rtype: `~ModuleConnection`

        :Example:

        >>> address = LcnAddr(0, 7, False)
        >>> module = pchk_connection.get_module_conn(address)
        >>> module.toggle_output(0, 5)
        """
        assert not addr.is_group
        if addr.seg_id == 0 and self.local_seg_id != -1:
            addr = LcnAddr(self.local_seg_id, addr.addr_id, addr.is_group)
        address_conn = self.address_conns.get(addr, None)
        if address_conn is None:
            address_conn = ModuleConnection(self, addr)
            if request_serials:
                self.task_registry.create_task(address_conn.request_serials())
            self.address_conns[addr] = address_conn

        return address_conn
Exemple #7
0
async def test_pushed_relay_status_change(hass, entry, lcn_connection):
    """Test the relay light changes its state on status received."""
    device_connection = get_device_connection(hass, (0, 7, False), entry)
    address = LcnAddr(0, 7, False)
    states = [False] * 8

    # push status "on"
    states[0] = True
    inp = ModStatusRelays(address, states)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get("light.light_relay1")
    assert state is not None
    assert state.state == STATE_ON

    # push status "off"
    states[0] = False
    inp = ModStatusRelays(address, states)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get("light.light_relay1")
    assert state is not None
    assert state.state == STATE_OFF
Exemple #8
0
    def try_parse(data: str) -> Optional[List[Input]]:
        """Try to parse the given input text.

        Will return a list of parsed Inputs. The list might be empty (but not
        null).

        :param    data    str:    The input data received from LCN-PCHK

        :return:            The parsed Inputs (never null)
        :rtype:             List with instances of :class:`~pypck.input.Input`
        """
        matcher = PckParser.PATTERN_SEND_KEYS_HOST.match(data)
        if matcher:
            addr = LcnAddr(int(matcher.group("seg_id")), int(matcher.group("mod_id")))
            actions_value = int(matcher.group("actions"))
            keys_value = int(matcher.group("keys"))

            mapping = (
                lcn_defs.SendKeyCommand.DONTSEND,
                lcn_defs.SendKeyCommand.HIT,
                lcn_defs.SendKeyCommand.MAKE,
                lcn_defs.SendKeyCommand.BREAK,
            )

            actions = []
            for idx in range(3):
                action = mapping[(actions_value >> 2 * idx) & 0x03]
                actions.append(action)

            keys = [bool(keys_value >> bit & 0x01) for bit in range(8)]

            return [ModSendKeysHost(addr, actions, keys)]

        return None
Exemple #9
0
    def try_parse(data: str) -> Optional[List[Input]]:
        """Try to parse the given input text.

        Will return a list of parsed Inputs. The list might be empty (but not
        null).

        :param    data    str:    The input data received from LCN-PCHK

        :return:            The parsed Inputs (never null)
        :rtype:             List with instances of :class:`~pypck.input.Input`
        """
        matcher = PckParser.PATTERN_STATUS_LEDSANDLOGICOPS.match(data)
        if matcher:
            addr = LcnAddr(int(matcher.group("seg_id")), int(matcher.group("mod_id")))

            led_states = matcher.group("led_states").upper()
            states_leds = [lcn_defs.LedStatus(led_state) for led_state in led_states]

            logic_op_states = matcher.group("logic_op_states").upper()
            states_logic_ops = [
                lcn_defs.LogicOpStatus(logic_op_state)
                for logic_op_state in logic_op_states
            ]
            return [ModStatusLedsAndLogicOps(addr, states_leds, states_logic_ops)]

        return None
Exemple #10
0
async def test_fire_sendkeys_event(hass, lcn_connection):
    """Test the send_keys event is fired."""
    events = async_capture_events(hass, "lcn_send_keys")

    inp = ModSendKeysHost(
        LcnAddr(0, 7, False),
        actions=[
            SendKeyCommand.HIT, SendKeyCommand.MAKE, SendKeyCommand.DONTSEND
        ],
        keys=[True, True, False, False, False, False, False, False],
    )

    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(events) == 4
    assert events[0].event_type == "lcn_send_keys"
    assert events[0].data["key"] == "a1"
    assert events[0].data["action"] == "hit"
    assert events[1].event_type == "lcn_send_keys"
    assert events[1].data["key"] == "a2"
    assert events[1].data["action"] == "hit"
    assert events[2].event_type == "lcn_send_keys"
    assert events[2].data["key"] == "b1"
    assert events[2].data["action"] == "make"
    assert events[3].event_type == "lcn_send_keys"
    assert events[3].data["key"] == "b2"
    assert events[3].data["action"] == "make"
Exemple #11
0
async def test_pushed_outputs_status_change(hass, entry, lcn_connection):
    """Test the outputs cover changes its state on status received."""
    device_connection = get_device_connection(hass, (0, 7, False), entry)
    address = LcnAddr(0, 7, False)

    state = hass.states.get("cover.cover_outputs")
    state.state = STATE_CLOSED

    # push status "open"
    input = ModStatusOutput(address, 0, 100)
    await device_connection.async_process_input(input)
    await hass.async_block_till_done()

    state = hass.states.get("cover.cover_outputs")
    assert state is not None
    assert state.state == STATE_OPENING

    # push status "stop"
    input = ModStatusOutput(address, 0, 0)
    await device_connection.async_process_input(input)
    await hass.async_block_till_done()

    state = hass.states.get("cover.cover_outputs")
    assert state is not None
    assert state.state not in (STATE_OPENING, STATE_CLOSING)

    # push status "close"
    input = ModStatusOutput(address, 1, 100)
    await device_connection.async_process_input(input)
    await hass.async_block_till_done()

    state = hass.states.get("cover.cover_outputs")
    assert state is not None
    assert state.state == STATE_CLOSING
Exemple #12
0
def test_message_parsing_single_mod_input(message, expected):
    """Test if InputMod parses message correctly."""
    exp = (expected[0])(LcnAddr(0, 10, False), *expected[1:])
    inp = InputParser.parse(message)
    assert len(inp) == 1
    assert type(inp[0]) == type(exp)  # pylint: disable=unidiomatic-typecheck
    assert vars(inp[0]) == vars(exp)
Exemple #13
0
async def module10(
    pypck_client: PchkConnectionManager,
) -> AsyncGenerator[ModuleConnection, None]:
    """Create test module with addr_id 10."""
    lcn_addr = LcnAddr(0, 10, False)
    module = pypck_client.get_module_conn(lcn_addr)
    yield module
    await module.cancel_requests()
Exemple #14
0
async def test_multiple_serial_requests(pchk_server, pypck_client):
    """Test module scan."""
    await pypck_client.async_connect()

    pypck_client.get_address_conn(LcnAddr(0, 10, False))
    pypck_client.get_address_conn(LcnAddr(0, 11, False))
    pypck_client.get_address_conn(LcnAddr(0, 12, False))

    assert await pchk_server.received(">M000010.SN")
    assert await pchk_server.received(">M000011.SN")
    assert await pchk_server.received(">M000012.SN")

    message = "=M000010.SN1AB20A123401FW190B11HW015"
    await pchk_server.send_message(message)
    assert await pypck_client.received(message)

    await pypck_client.async_close()
Exemple #15
0
async def test_physical_to_logical_segment_id(pypck_client):
    """Test conversion from logical to physical segment id."""
    pypck_client.local_seg_id = 20
    module = pypck_client.get_address_conn(LcnAddr(20, 7, False))
    module.async_process_input = AsyncMock()

    with patch("tests.conftest.MockPchkConnectionManager.is_ready",
               result=True):
        inp = ModInput(LcnAddr(20, 7, False))
        await pypck_client.async_process_input(inp)

        inp = ModInput(LcnAddr(0, 7, False))
        await pypck_client.async_process_input(inp)

        inp = ModInput(LcnAddr(4, 7, False))
        await pypck_client.async_process_input(inp)

        assert module.async_process_input.await_count == 3
Exemple #16
0
    def generate_address_header(addr: LcnAddr, local_seg_id: int,
                                wants_ack: bool) -> str:
        """Generate a PCK command address header.

        :param    addr:   The module's/group's address
        :type     addr:   :class:`~LcnAddr`
        :param    int     local_seg_id:    The local segment id
        :param    bool    wants_ack:      Is an acknowledge requested.

        :return:    The PCK address header string.
        :rtype:     str
        """
        return ">{:s}{:03d}{:03d}{:s}".format(
            "G" if addr.is_group() else "M",
            addr.get_physical_seg_id(local_seg_id),
            addr.get_id(),
            "!" if wants_ack else ".",
        )
Exemple #17
0
async def test_add_address_connections(pypck_client):
    """Test if new address connections are added on request."""
    lcn_addr = LcnAddr(0, 10, False)
    assert lcn_addr not in pypck_client.address_conns

    addr_conn = pypck_client.get_address_conn(lcn_addr)
    assert isinstance(addr_conn, ModuleConnection)

    assert lcn_addr in pypck_client.address_conns
Exemple #18
0
async def test_dyn_text(pchk_server, pypck_client, text, parts):
    """dyn_text."""
    await pypck_client.async_connect()
    module = pypck_client.get_address_conn(LcnAddr(0, 10, False))
    task = asyncio.create_task(module.dyn_text(3, text))
    assert all([
        await pchk_server.received(f">M000010!GTDT4{i+1:d}".encode() + part)
        for i, part in enumerate(parts)
    ])
    task.cancel()
Exemple #19
0
async def test_add_address_connections_by_message(pchk_server, pypck_client):
    """Test if new address connections are added by received message."""
    await pypck_client.async_connect()
    lcn_addr = LcnAddr(0, 10, False)
    assert lcn_addr not in pypck_client.address_conns

    message = ":M000010A1050"
    await pchk_server.send_message(message)
    assert await pypck_client.received(message)

    assert lcn_addr in pypck_client.address_conns
Exemple #20
0
async def test_if_fires_on_transmitter_event(hass, calls, entry):
    """Test for transmitter event triggers firing."""
    await init_integration(hass, entry)
    address = (0, 7, False)
    device = get_device(hass, entry, address)

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        CONF_PLATFORM: "device",
                        CONF_DOMAIN: DOMAIN,
                        CONF_DEVICE_ID: device.id,
                        CONF_TYPE: "transmitter",
                    },
                    "action": {
                        "service": "test.automation",
                        "data_template": {
                            "test": "test_trigger_transmitter",
                            "code": "{{ trigger.event.data.code }}",
                            "level": "{{ trigger.event.data.level }}",
                            "key": "{{ trigger.event.data.key }}",
                            "action": "{{ trigger.event.data.action }}",
                        },
                    },
                },
            ]
        },
    )

    inp = ModStatusAccessControl(
        LcnAddr(*address),
        periphery=AccessControlPeriphery.TRANSMITTER,
        code="aabbcc",
        level=0,
        key=0,
        action=KeyAction.HIT,
    )

    lcn_connection = MockPchkConnectionManager.return_value
    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data == {
        "test": "test_trigger_transmitter",
        "code": "aabbcc",
        "level": 0,
        "key": 0,
        "action": "hit",
    }
Exemple #21
0
async def test_dont_fire_on_unknown_module(hass, lcn_connection):
    """Test for no event is fired if an input from an unknown module is received."""
    inp = ModStatusAccessControl(
        LcnAddr(0, 10, False),  # unknown module
        periphery=AccessControlPeriphery.FINGERPRINT,
        code="aabbcc",
    )

    events = async_capture_events(hass, "lcn_fingerprint")
    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()
    assert len(events) == 0
Exemple #22
0
async def test_if_fires_on_send_keys_event(hass, calls, entry):
    """Test for send_keys event triggers firing."""
    await init_integration(hass, entry)
    address = (0, 7, False)
    device = get_device(hass, entry, address)

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        CONF_PLATFORM: "device",
                        CONF_DOMAIN: DOMAIN,
                        CONF_DEVICE_ID: device.id,
                        CONF_TYPE: "send_keys",
                    },
                    "action": {
                        "service": "test.automation",
                        "data_template": {
                            "test": "test_trigger_send_keys",
                            "key": "{{ trigger.event.data.key }}",
                            "action": "{{ trigger.event.data.action }}",
                        },
                    },
                },
            ]
        },
    )

    inp = ModSendKeysHost(
        LcnAddr(*address),
        actions=[
            SendKeyCommand.HIT, SendKeyCommand.DONTSEND,
            SendKeyCommand.DONTSEND
        ],
        keys=[True, False, False, False, False, False, False, False],
    )

    lcn_connection = MockPchkConnectionManager.return_value
    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data == {
        "test": "test_trigger_send_keys",
        "key": "a1",
        "action": "hit",
    }
Exemple #23
0
async def test_dyn_text(pypck_client, text, parts):
    """dyn_text."""
    # await pypck_client.async_connect()
    module = pypck_client.get_address_conn(LcnAddr(0, 10, False))

    with patch.object(ModuleConnection, "send_command") as send_command:
        await module.dyn_text(3, text)

    send_command.assert_awaited()
    await_args = (call.args for call in send_command.await_args_list)
    _, commands = zip(*await_args)

    for i, part in enumerate(parts):
        assert f"GTDT4{i+1:d}".encode() + part in commands
Exemple #24
0
async def test_module_sn_response(pchk_server, pypck_client):
    """Test module scan."""
    await pypck_client.async_connect()
    module = pypck_client.get_address_conn(LcnAddr(0, 7, False))

    message = "=M000007.SN1AB20A123401FW190B11HW015"
    await pchk_server.send_message(message)
    assert await pypck_client.received(message)

    assert await module.serial_known
    assert module.hardware_serial == 0x1AB20A1234
    assert module.manu == 1
    assert module.software_serial == 0x190B11
    assert module.hardware_type.value == 15
Exemple #25
0
    def try_parse(data: str) -> Optional[List[Input]]:
        """Try to parse the given input text.

        Will return a list of parsed Inputs. The list might be empty (but not
        null).

        :param    data    str:    The input data received from LCN-PCHK

        :return:            The parsed Inputs (never null)
        :rtype:             List with instances of :class:`~pypck.input.Input`
        """
        matcher = PckParser.PATTERN_STATUS_GROUPS.match(data)
        if matcher:
            addr = LcnAddr(int(matcher.group("seg_id")), int(matcher.group("mod_id")))
            dynamic = matcher.group("kind") == "D"
            max_groups = int(matcher.group("max_groups"))
            groups = [
                LcnAddr(addr.seg_id, int(group), True)
                for group in matcher.groups()[4:]
                if group is not None
            ]
            return [ModStatusGroups(addr, dynamic, max_groups, groups)]

        return None
Exemple #26
0
async def test_fire_transponder_event(hass, lcn_connection):
    """Test the transponder event is fired."""
    events = async_capture_events(hass, "lcn_transponder")

    inp = ModStatusAccessControl(
        LcnAddr(0, 7, False),
        periphery=AccessControlPeriphery.TRANSPONDER,
        code="aabbcc",
    )

    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(events) == 1
    assert events[0].event_type == "lcn_transponder"
    assert events[0].data["code"] == "aabbcc"
Exemple #27
0
async def test_fire_fingerprint_event(hass, lcn_connection):
    """Test the fingerprint event is fired."""
    events = async_capture_events(hass, "lcn_fingerprint")

    inp = ModStatusAccessControl(
        LcnAddr(0, 7, False),
        periphery=AccessControlPeriphery.FINGERPRINT,
        code="aabbcc",
    )

    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(events) == 1
    assert events[0].event_type == "lcn_fingerprint"
    assert events[0].data["code"] == "aabbcc"
async def test_dont_fire_on_unknown_module(hass, entry):
    """Test for no event is fired if an input from an unknown module is received."""
    await init_integration(hass, entry)

    inp = ModStatusAccessControl(
        LcnAddr(0, 10, False),  # unknown module
        periphery=AccessControlPeriphery.FINGERPRINT,
        code="aabbcc",
    )

    lcn_connection = MockPchkConnectionManager.return_value

    events = async_capture_events(hass, "lcn_transmitter")
    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()
    assert len(events) == 0
Exemple #29
0
    def set_local_seg_id(self, local_seg_id: int) -> None:
        """Set the local segment id.

        :param    int    local_seg_id:    The local segment_id.
        """
        old_local_seg_id = self.local_seg_id

        self.local_seg_id = local_seg_id
        # replace all address_conns with current local_seg_id with new
        # local_seg_id
        for addr in list(self.address_conns):
            if addr.seg_id == old_local_seg_id:
                address_conn = self.address_conns.pop(addr)
                address_conn.addr = LcnAddr(
                    self.local_seg_id, addr.addr_id, addr.is_group
                )
                self.address_conns[address_conn.addr] = address_conn
async def test_if_fires_on_transponder_event(hass, calls, entry,
                                             lcn_connection):
    """Test for transponder event triggers firing."""
    address = (0, 7, False)
    device = get_device(hass, entry, address)

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        CONF_PLATFORM: "device",
                        CONF_DOMAIN: DOMAIN,
                        CONF_DEVICE_ID: device.id,
                        CONF_TYPE: "transponder",
                    },
                    "action": {
                        "service": "test.automation",
                        "data_template": {
                            "test": "test_trigger_transponder",
                            "code": "{{ trigger.event.data.code }}",
                        },
                    },
                },
            ]
        },
    )

    inp = ModStatusAccessControl(
        LcnAddr(*address),
        periphery=AccessControlPeriphery.TRANSPONDER,
        code="aabbcc",
    )

    await lcn_connection.async_process_input(inp)
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data == {
        "test": "test_trigger_transponder",
        "code": "aabbcc",
    }