Esempio n. 1
0
def test_broadcast_message(faker):
    for i in range(1000):
        # Create Messages with random `screen` value in the non-broadcast range
        # and ensure the `is_broadcast` field is correct in both the
        # instance and its parsed version
        screen = faker.pyint(max_value=0xfffe)
        msgobj = Message(screen=screen)
        msgobj.displays.append(Display(index=i))
        assert not msgobj.is_broadcast

        parsed, remaining = Message.parse(msgobj.build_message())
        assert not msgobj.is_broadcast

    # Create broadcast Messages using both methods and check the `is_broadcast`
    # field on the instances and their parsed versions
    msgobj1 = Message(screen=0xffff)
    msgobj1.displays.append(Display(index=1))
    assert msgobj1.is_broadcast
    parsed1, remaining = Message.parse(msgobj1.build_message())
    assert parsed1.is_broadcast

    msgobj2 = Message.broadcast(displays=[Display(index=1)])
    assert msgobj2.is_broadcast
    parsed2, remaining = Message.parse(msgobj2.build_message())
    assert parsed2.is_broadcast

    assert msgobj1 == msgobj2 == parsed1 == parsed2
Esempio n. 2
0
def test_broadcast(faker):
    for _ in range(1000):
        i = faker.pyint(max_value=0xfffe)
        tally = Tally(i)
        assert not tally.is_broadcast
        assert not Display.from_tally(tally).is_broadcast

    tally1 = Tally(0xffff)
    tally2 = Tally.broadcast()
    assert tally1.is_broadcast
    assert tally2.is_broadcast
    assert Display.from_tally(tally1).is_broadcast
    assert Display.from_tally(tally2).is_broadcast
Esempio n. 3
0
def test_broadcast_display(uhs500_msg_parsed, faker):

    disp_attrs = ('rh_tally', 'txt_tally', 'lh_tally', 'text', 'brightness')
    msgobj = Message()

    for uhs_disp in uhs500_msg_parsed.displays:
        assert not uhs_disp.is_broadcast

        # General kwargs excluding the `index`
        kw = {attr: getattr(uhs_disp, attr) for attr in disp_attrs}

        # Create random Displays within non-broadcast range and check the
        # `is_broadcast` field of the instance and its parsed version
        for _ in range(1000):
            ix = faker.pyint(max_value=0xfffe)
            disp = Display(index=ix, **kw)
            assert not disp.is_broadcast

            parsed, remaining = Display.from_dmsg(msgobj.flags,
                                                  disp.to_dmsg(msgobj.flags))
            assert not parsed.is_broadcast
            assert parsed == disp

        # Create broadcast Displays using both methods and check the
        # `is_broadcast` field on the instances and their parsed versions
        bc_disp1 = Display.broadcast(**kw)
        bc_disp2 = Display(index=0xffff, **kw)
        assert bc_disp1.is_broadcast
        assert bc_disp2.is_broadcast

        parsed1, remaining = Display.from_dmsg(msgobj.flags,
                                               bc_disp1.to_dmsg(msgobj.flags))
        assert parsed1.is_broadcast

        parsed2, remaining = Display.from_dmsg(msgobj.flags,
                                               bc_disp2.to_dmsg(msgobj.flags))
        assert parsed2.is_broadcast

        assert bc_disp1 == bc_disp2 == parsed1 == parsed2

        # Add the broadcast Display to the Message at the top
        msgobj.displays.append(bc_disp1)

    # Check the `is_broadcast` field in the displays after Message building / parsing
    parsed, remaining = Message.parse(msgobj.build_message())
    for parsed_disp, bc_disp in zip(parsed.displays, msgobj.displays):
        assert parsed_disp.is_broadcast
        assert parsed_disp == bc_disp
Esempio n. 4
0
    async def send_broadcast_tally(self, screen_index: int, **kwargs):
        """Send a :attr:`broadcast <.Display.is_broadcast>` update
        to all listening displays

        Arguments:
            screen_index: The screen :attr:`~.Screen.index`
            **kwargs: The keyword arguments to pass to the :class:`~.Tally`
                constructor

        .. versionadded:: 0.0.2

        .. versionchanged:: 0.0.3
            Added the screen_index parameter
        """
        screen = self.get_or_create_screen(screen_index)
        tally = screen.broadcast_tally(**kwargs)
        if tally.text == '' or tally.control != b'':
            msg_type = MessageType.control
        else:
            msg_type = MessageType.display
        msg = self._build_message(screen=screen_index)
        disp = Display.from_tally(tally, msg_type=msg_type)
        msg.displays.append(disp)
        async with self._tx_lock:
            await self.send_message(msg)
            screen.unbind(self)
            for oth_tally in screen:
                oth_tally.update_from_display(disp)
            self._bind_screen(screen)
Esempio n. 5
0
 async def send_screen_update(self, screen: Screen):
     if screen.is_broadcast:
         return
     msg = self._build_message(screen=screen.index)
     for tally in screen:
         disp = Display.from_tally(tally)
         msg.displays.append(disp)
     await self.send_message(msg)
Esempio n. 6
0
async def test_broadcast_display(uhs500_msg_bytes, uhs500_msg_parsed,
                                 udp_endpoint, udp_port, faker):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

    evt_listener = EventListener()
    receiver.bind_async(loop, on_tally_added=evt_listener.callback)

    displays_by_index = {
        disp.index: disp
        for disp in uhs500_msg_parsed.displays
    }

    async with receiver:
        # Populate the receiver's tallies and wait for them to be added
        transport.sendto(uhs500_msg_bytes, ('127.0.0.1', udp_port))
        _ = await evt_listener.get()
        while not evt_listener.empty():
            _ = await evt_listener.get()

        assert len(receiver.tallies) == len(uhs500_msg_parsed.displays)

        receiver.unbind(evt_listener)
        receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

        # Send a broadcast display message for each TallyColor with a random brightness
        # The control field is set so the Tally text field is unchanged
        for color in TallyColor:
            brightness = faker.pyint(max_value=3)
            bc_disp = Display.broadcast(
                control=b'foo',
                rh_tally=color,
                lh_tally=color,
                txt_tally=color,
                brightness=brightness,
            )
            msg = Message(displays=[bc_disp])
            transport.sendto(msg.build_message(), ('127.0.0.1', udp_port))

            _ = await evt_listener.get()
            while not evt_listener.empty():
                _ = await evt_listener.get()

            # Check each of the receiver's tallies against the bc_disp values
            # and make sure the text didn't change
            for tally in receiver.tallies.values():
                assert tally.index < 0xffff
                assert not tally.is_broadcast
                assert tally.rh_tally == color
                assert tally.txt_tally == color
                assert tally.lh_tally == color
                assert tally.control == bc_disp.control
                assert tally.brightness == brightness
                assert tally.text == displays_by_index[tally.index].text
Esempio n. 7
0
 async def on_tally_control(self, tally: Tally, data: bytes, **kwargs):
     if self.running:
         async with self._tx_lock:
             disp = Display.from_tally(tally, msg_type=MessageType.control)
             msg = self._build_message(
                 screen=tally.screen.index,
                 displays=[disp],
             )
             await self.send_message(msg)
Esempio n. 8
0
async def test_parse_errors(faker, udp_endpoint, udp_port):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

    evt_listener = EventListener()
    receiver.bind_async(loop, on_tally_added=evt_listener.callback)

    msgobj = Message()
    disp = Display(index=0, text='foo')
    msgobj.displays.append(disp)

    async with receiver:
        transport.sendto(msgobj.build_message(), ('127.0.0.1', udp_port))

        _ = await evt_listener.get()

        receiver.unbind(evt_listener)
        receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

        screen = receiver.screens[msgobj.screen]
        rx_disp = screen[disp.index]
        assert rx_disp is receiver.tallies[rx_disp.id]
        assert rx_disp == disp

        for i in range(100):
            num_bytes = faker.pyint(min_value=1, max_value=1024)
            bad_bytes = faker.binary(length=num_bytes)

            with pytest.raises(ParseError):
                receiver.parse_incoming(bad_bytes,
                                        ('127.0.0.1', endpoint_port))

            transport.sendto(bad_bytes, ('127.0.0.1', udp_port))

            disp.text = f'foo_{i}'
            transport.sendto(msgobj.build_message(), ('127.0.0.1', udp_port))

            _ = await evt_listener.get()

            assert rx_disp == disp
Esempio n. 9
0
async def test_control_event(faker):
    loop = asyncio.get_event_loop()
    listener = Listener()

    disp = Display(index=0)
    tally = Tally.from_display(disp)

    tally.bind_async(loop, on_control=listener.callback)

    for _ in range(100):
        data_len = faker.pyint(min_value=1, max_value=1024)
        control_data = faker.binary(length=data_len)

        disp = Display(index=0, control=control_data)
        tally.update_from_display(disp)

        _, rx_data = await listener.get()
        assert rx_data == tally.control == disp.control == control_data
        assert disp == tally == Tally.from_display(disp)
Esempio n. 10
0
async def test_control_event_with_text(faker):
    loop = asyncio.get_event_loop()

    text_listener = Listener()
    ctrl_listener = Listener()

    tally_text = 'foo'

    disp = Display(index=0, text=tally_text)
    tally = Tally.from_display(disp)

    assert disp == tally

    tally.bind_async(loop,
        on_update=text_listener.callback,
        on_control=ctrl_listener.callback,
    )

    for _ in range(100):
        for word in faker.words(3):
            data_len = faker.pyint(min_value=1, max_value=1024)
            control_data = faker.binary(length=data_len)

            disp = Display(index=0, control=control_data)
            tally.update_from_display(disp)

            _, rx_data = await ctrl_listener.get()
            assert rx_data == tally.control == disp.control == control_data
            assert tally.text == tally_text

            _, props_changed = await text_listener.get()
            assert set(props_changed) == set(['control'])

            tally_text=word
            disp = Display(index=0, text=tally_text)
            tally.update_from_display(disp)

            _, props_changed = await text_listener.get()
            assert set(props_changed) == set(['text'])

            assert tally.text == tally_text
            assert tally.control == control_data
Esempio n. 11
0
def message_with_lots_of_displays():
    msgobj = Message()
    # Message header byte length: 6
    # Dmsg header byte length: 4
    # Text length: 2(length bytes) + 9(chars) = 11
    # Each Dmsg total: 4 + 11 = 15
    # Dmsg's: 4096 * 15 = 61440
    # 4096 Dmsg's with Message header: 4096 * 15 + 6 = 61446 bytes
    for i in range(4096):
        msgobj.displays.append(Display(index=i, text=f'Foo {i:05d}'))
    return msgobj
Esempio n. 12
0
def test_dmsg_control(uhs500_msg_parsed, faker):
    tested_zero = False
    for _ in range(10):
        msgobj = Message(version=1, screen=5)
        for orig_disp in uhs500_msg_parsed.displays:
            if not tested_zero:
                data_len = 0
                tested_zero = True
            else:
                data_len = faker.pyint(min_value=0, max_value=1024)
            control_data = faker.binary(length=data_len)

            kw = orig_disp.to_dict()
            del kw['text']
            kw['control'] = control_data
            if not len(control_data):
                kw['type'] = MessageType.control
            disp = Display(**kw)

            assert disp.type == MessageType.control

            disp_bytes = disp.to_dmsg(msgobj.flags)
            parsed_disp, remaining = Display.from_dmsg(msgobj.flags,
                                                       disp_bytes)

            assert not len(remaining)
            assert parsed_disp.control == control_data
            assert parsed_disp == disp

            msgobj.displays.append(disp)

        parsed = None
        for msg_bytes in msgobj.build_messages():
            _parsed, remaining = Message.parse(msg_bytes)
            assert not len(remaining)
            if parsed is None:
                parsed = _parsed
            else:
                parsed.displays.extend(_parsed.displays)
        assert parsed == msgobj

        with pytest.raises(ValueError) as excinfo:
            disp = Display(index=1, control=b'foo', text='foo')
        excstr = str(excinfo.value).lower()
        assert 'control' in excstr and 'text' in excstr

        with pytest.raises(ValueError) as excinfo:
            disp = Display(index=1, text='foo', type=MessageType.control)
        excstr = str(excinfo.value).lower()
        assert 'control' in excstr and 'text' in excstr
Esempio n. 13
0
def uhs500_msg_parsed() -> Message:
    """Expected :class:`~tslumd.messages.Message` object
    matching data from :func:`uhs500_msg_bytes`
    """
    data = json.loads(MESSAGE_JSON.read_text())
    data['scontrol'] = b''
    displays = []
    for disp in data['displays']:
        for key in ['rh_tally', 'txt_tally', 'lh_tally']:
            disp[key] = getattr(TallyColor, disp[key])
        displays.append(Display(**disp))
    data['displays'] = displays
    return Message(**data)
Esempio n. 14
0
def test_tally_display_conversion(faker):
    # i = 0
    # if True:
    for _ in range(100):
        i = faker.pyint(max_value=0xfffe)
        disp = Display(index=i)
        tally = Tally(i)
        for tally_type, color in iter_tally_types_and_colors():
            brightness = faker.pyint(max_value=3)
            # print(f'{i=}, {tally_type=}, {color=}')
            disp.brightness = brightness
            tally.brightness = brightness
            assert 0 <= tally.normalized_brightness <= 1
            assert tally.normalized_brightness == brightness / 3
            setattr(tally, tally_type.name, color)
            setattr(disp, tally_type.name, color)
            for word in faker.words(3):
                # print(f'{word=}')
                tally.text = word
                disp.text = word

                assert disp == Tally.from_display(disp) == tally
                assert disp == Display.from_tally(tally) == tally
                assert Tally.from_display(disp).normalized_brightness == tally.normalized_brightness
Esempio n. 15
0
    async def tx_loop(self):
        async def get_queue_item(timeout):
            try:
                item = await asyncio.wait_for(self.update_queue.get(), timeout)
                if item[1] is False:
                    return False
            except asyncio.TimeoutError:
                item = None
            return item

        await self.connected_evt.wait()

        while self.running:
            item = await get_queue_item(self.tx_interval)
            if item is False:
                self.update_queue.task_done()
                break
            elif item is None and not self._tx_lock.locked():
                await self.send_full_update()
            else:
                screen_index, _ = item
                ids = set([item])
                self.update_queue.task_done()
                while not self.update_queue.empty():
                    try:
                        item = self.update_queue.get_nowait()
                    except asyncio.QueueEmpty:
                        break
                    if item is False:
                        self.update_queue.task_done()
                        return
                    _screen_index, _ = item
                    if _screen_index == screen_index:
                        ids.add(item)
                        self.update_queue.task_done()
                    else:
                        await self.update_queue.put(item)
                        break

                msg = self._build_message(screen=screen_index)
                tallies = {i:self.tallies[i] for i in ids}
                async with self._tx_lock:
                    for key in sorted(tallies.keys()):
                        tally = tallies[key]
                        msg.displays.append(Display.from_tally(tally))
                    await self.send_message(msg)
Esempio n. 16
0
def test_messages():
    msgobj = Message(version=1, screen=5)
    rh_tallies = [
        getattr(TallyColor, attr) for attr in ['RED', 'OFF', 'GREEN', 'AMBER']
    ]
    lh_tallies = [
        getattr(TallyColor, attr) for attr in ['GREEN', 'RED', 'OFF', 'RED']
    ]
    txt_tallies = [
        getattr(TallyColor, attr)
        for attr in ['OFF', 'GREEN', 'AMBER', 'GREEN']
    ]
    txts = ['foo', 'bar', 'baz', 'blah']
    indices = [4, 3, 7, 1]
    for i in range(4):
        disp = Display(
            index=indices[i],
            rh_tally=rh_tallies[i],
            lh_tally=lh_tallies[i],
            txt_tally=txt_tallies[i],
            text=txts[i],
            brightness=i,
        )
        msgobj.displays.append(disp)

    parsed, remaining = Message.parse(msgobj.build_message())
    assert not len(remaining)

    for i in range(len(rh_tallies)):
        disp1, disp2 = msgobj.displays[i], parsed.displays[i]
        assert disp1.rh_tally == disp2.rh_tally == rh_tallies[i]
        assert disp1.lh_tally == disp2.lh_tally == lh_tallies[i]
        assert disp1.txt_tally == disp2.txt_tally == txt_tallies[i]
        assert disp1.text == disp2.text == txts[i]
        assert disp1.index == disp2.index == indices[i]
        assert disp1 == disp2

    for attr in ['version', 'flags', 'screen', 'scontrol']:
        assert getattr(msgobj, attr) == getattr(parsed, attr)

    assert msgobj == parsed
Esempio n. 17
0
def test_scontrol(faker):
    for _ in range(100):
        data_len = faker.pyint(min_value=1, max_value=1024)
        control_data = faker.binary(length=data_len)

        msgobj = Message(scontrol=control_data)
        assert msgobj.type == MessageType.control
        assert Flags.SCONTROL in msgobj.flags

        msg_bytes = msgobj.build_message()
        parsed, remaining = Message.parse(msg_bytes)
        assert not len(remaining)

        assert parsed.type == MessageType.control
        assert parsed.scontrol == control_data
        assert parsed == msgobj

        disp = Display(index=1)

        with pytest.raises(ValueError) as excinfo:
            disp_msg = Message(displays=[disp], scontrol=control_data)
        assert 'SCONTROL' in str(excinfo.value)
Esempio n. 18
0
def test_invalid_dmsg_control(uhs500_msg_bytes, faker):
    msg = Message()
    disp = Display(index=1, control=b'foo\x00')
    msg.displays.append(disp)
    msg_bytes = msg.build_message()

    # Clip the length field to the wrong size
    bad_bytes = bytearray(msg_bytes)
    bad_bytes = bad_bytes[:-5]
    bad_byte_count = struct.pack('<H', len(bad_bytes) - 2)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgControlParseError):
        r = Message.parse(bad_bytes)

    # Clip the control bytes to the wrong length
    bad_bytes = bytearray(msg_bytes)
    bad_bytes = bad_bytes[:-2]
    bad_byte_count = struct.pack('<H', len(bad_bytes) - 2)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgControlParseError):
        r = Message.parse(bad_bytes)
Esempio n. 19
0
def test_packet_length(faker, message_with_lots_of_displays):
    msgobj = message_with_lots_of_displays

    # Make sure the 2048 byte limit is respected
    with pytest.raises(MessageLengthError):
        _ = msgobj.build_message()

    # Ensure that the limit can be bypassed
    msg_bytes = msgobj.build_message(ignore_packet_length=True)
    parsed, remaining = Message.parse(msg_bytes)
    assert parsed == msgobj

    # Iterate over individual message packets and make sure we get all displays
    all_parsed_displays = []
    for msg_bytes in msgobj.build_messages():
        assert len(msg_bytes) <= 2048
        parsed, remaining = Message.parse(msg_bytes)
        assert not len(remaining)
        all_parsed_displays.extend(parsed.displays)

    assert len(all_parsed_displays) == len(msgobj.displays)
    for disp, parsed_disp in zip(msgobj.displays, all_parsed_displays):
        assert disp.index == parsed_disp.index
        assert disp.text == parsed_disp.text

    # Create an SCONTROL that exceeds the limit
    msgobj = Message(scontrol=faker.binary(length=2048))
    with pytest.raises(MessageLengthError):
        it = msgobj.build_messages()
        _ = next(it)

    # Create a Dmsg control that exceeds the limit
    msgobj = Message(
        displays=[Display(index=0, control=faker.binary(length=2048))])
    with pytest.raises(MessageLengthError):
        it = msgobj.build_messages()
        _ = next(it)
Esempio n. 20
0
async def test_update_event(faker):
    loop = asyncio.get_event_loop()
    listener = Listener()

    tally = Tally(0)
    tally.bind_async(loop, on_update=listener.callback)
    tally.text = 'foo'

    _, props_changed = await listener.get()
    assert set(props_changed) == set(['text'])

    d = dict(rh_tally=TallyColor.RED, txt_tally=TallyColor.GREEN, lh_tally=TallyColor.AMBER)
    tally.update(**d)

    _, props_changed = await listener.get()
    assert set(props_changed) == set(d.keys())


    disp = Display(index=0, text=tally.text)
    tally.update_from_display(disp)
    assert disp == tally

    _, props_changed = await listener.get()
    assert set(props_changed) == set(['rh_tally', 'txt_tally', 'lh_tally'])


    for tally_type, color in iter_tally_types_and_colors():
        attr = tally_type.name
        should_change = getattr(tally, attr) != color
        # print(f'{tally_type=}, {color=}, {should_change=}')
        setattr(tally, attr, color)
        if should_change:
            _, props_changed = await listener.get()
            assert set(props_changed) == set([attr])
        else:
            await asyncio.sleep(.01)
            assert listener.results.empty()