Esempio n. 1
0
async def test_clients_modified_during_runtime(udp_port, udp_port0):
    loop = asyncio.get_event_loop()

    sender = UmdSender()
    receiver1 = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)
    receiver2 = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port0)

    add_listener1 = EventListener()
    add_listener2 = EventListener()
    tally_listener1 = EventListener()
    tally_listener2 = EventListener()

    async def drain_listeners():
        async def drain(listener):
            while not listener.empty():
                await listener.get()

        coros = [
            drain(add_listener1),
            drain(add_listener2),
            drain(tally_listener1),
            drain(tally_listener2),
        ]
        await asyncio.gather(*coros)

    receiver1.bind_async(
        loop,
        on_tally_added=add_listener1.callback,
        on_tally_updated=tally_listener1.callback,
    )
    receiver2.bind_async(
        loop,
        on_tally_added=add_listener2.callback,
        on_tally_updated=tally_listener2.callback,
    )

    screen_index = 1

    async with receiver1:
        async with receiver2:
            async with sender:
                # Add 10 tallies and add the first receiver to clients after the third one
                for i in range(10):
                    t_id = (screen_index, i)
                    sender.set_tally_text(t_id, f'Tally {i}')
                    if i == 2:
                        sender.clients.add(('127.0.0.1', udp_port))

                # Update the tallies and add the second receiver after the third one
                for i in range(10):
                    t_id = (screen_index, i)
                    tx_tally = sender.tallies[t_id]
                    sender.set_tally_color(t_id, 'rh|lh', 'red')
                    if i == 2:
                        sender.clients.add(('127.0.0.1', udp_port0))

                # Wait for a full update then allow the messages to come in
                await asyncio.sleep(sender.tx_interval)
                await drain_listeners()
                assert receiver1.tallies == receiver2.tallies == sender.tallies
Esempio n. 2
0
    def __init__(self,
                 config: MultiTallyConfig,
                 hostaddr: str = UmdReceiver.DEFAULT_HOST,
                 hostport: int = UmdReceiver.DEFAULT_PORT):

        super().__init__(config)
        self.loop = asyncio.get_event_loop()
        self._screen_indices = set()
        self._tally_keys = set()
        self.receiver = UmdReceiver(hostaddr=hostaddr, hostport=hostport)
        self.receiver.bind(
            on_screen_added=self._on_receiver_screen_added,
            on_tally_added=self._on_receiver_tally_added,
            on_tally_updated=self._on_receiver_tally_updated
        )
Esempio n. 3
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. 4
0
 def __init__(self):
     self._reading_config = False
     self._config_read = asyncio.Event()
     self._connect_lock = asyncio.Lock()
     super().__init__()
     self.receiver = UmdReceiver()
     self.hostaddr = self.receiver.hostaddr
     self.hostport = self.receiver.hostport
     self.receiver.bind_async(self.loop,
         on_tally_added=self._on_receiver_tally_added,
         on_tally_updated=self._on_receiver_tally_updated,
     )
     self.bind_async(self.loop,
         config=self.read_config,
     )
     self.bind(**{prop:self.update_config for prop in ['hostaddr', 'hostport']})
Esempio n. 5
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. 6
0
async def test_rebind(uhs500_msg_bytes, uhs500_msg_parsed, udp_endpoint,
                      udp_port, unused_tcp_port_factory):
    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)

    async with receiver:

        # Send message bytes to receiver and wait for ``on_tally_added`` events
        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)

        # Set up to get ``on_tally_updated`` callbacks
        receiver.unbind(evt_listener)
        receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

        # Change bind address and trigger a change
        await receiver.set_hostaddr('0.0.0.0')
        assert receiver.hostaddr == '0.0.0.0'

        disp = uhs500_msg_parsed.displays[0]
        disp.brightness = 1

        transport.sendto(uhs500_msg_parsed.build_message(),
                         ('0.0.0.0', udp_port))

        evt_args, evt_kwargs = await evt_listener.get()
        evt_tally = evt_args[0]
        assert disp == evt_tally

        # Change bind port and trigger a change
        new_port = unused_tcp_port_factory()
        assert new_port != udp_port

        await receiver.set_hostport(new_port)
        assert receiver.hostport == new_port

        disp.brightness = 2
        transport.sendto(uhs500_msg_parsed.build_message(),
                         ('0.0.0.0', new_port))

        evt_args, evt_kwargs = await evt_listener.get()
        evt_tally = evt_args[0]
        assert disp == evt_tally
Esempio n. 7
0
async def test_scontrol(faker, udp_port):
    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', udp_port)])
    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

    evt_listener = EventListener()
    receiver.bind_async(loop, on_scontrol=evt_listener.callback)
    bc_listener = EventListener()
    receiver.broadcast_screen.bind_async(loop, on_control=bc_listener.callback)

    async with receiver:
        async with sender:
            for i in range(100):
                data_len = faker.pyint(min_value=1, max_value=1024)
                control_data = faker.binary(length=data_len)

                await sender.send_scontrol(screen_index=i, data=control_data)

                evt_args, evt_kwargs = await evt_listener.get()

                rx_screen, rx_data = evt_args
                assert rx_screen.index == i
                assert rx_data == control_data

                # Send broadcast
                await sender.send_broadcast_scontrol(data=control_data)

                # Wait for the broadcast screen
                evt_args, evt_kwargs = await bc_listener.get()
                rx_screen, rx_data = evt_args
                assert rx_screen is receiver.broadcast_screen
                assert rx_data == control_data

                # Wait for each currently existing screen
                num_screens = i + 1
                for j in range(num_screens):
                    evt_args, evt_kwargs = await evt_listener.get()
                    rx_screen, rx_data = evt_args
                    assert rx_data == control_data
Esempio n. 8
0
async def test_scontrol(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_scontrol=evt_listener.callback)

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

            msgobj = Message(screen=i, scontrol=control_data)
            transport.sendto(msgobj.build_message(), ('127.0.0.1', udp_port))

            args, kwargs = await evt_listener.get()

            screen, rx_data = args
            assert screen.index == i
            assert rx_data == control_data
Esempio n. 9
0
async def test_disp_control(faker, udp_port):
    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', udp_port)])
    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

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

    tally_listener = EventListener()
    receiver.bind_async(loop, on_tally_control=tally_listener.callback)

    screen_index = 1

    async with receiver:
        async with sender:
            for i in range(100):
                t_id = (screen_index, i)
                sender.set_tally_text(t_id, f'Tally-{i}')
                tx_tally = sender.tallies[t_id]

                evt_args, evt_kwargs = await add_listener.get()
                rx_tally = evt_args[0]
                assert rx_tally == tx_tally

                data_len = faker.pyint(min_value=1, max_value=1024)
                control_data = faker.binary(length=data_len)

                await sender.send_tally_control(t_id, control_data)
                assert tx_tally.control == control_data

                evt_args, evt_kwargs = await tally_listener.get()
                _rx_tally, rx_data = evt_args
                assert _rx_tally is rx_tally

                assert rx_data == rx_tally.control == tx_tally.control == control_data

            t_id = (screen_index, 200)
            data_len = faker.pyint(min_value=1, max_value=1024)
            control_data = faker.binary(length=data_len)
            await sender.send_tally_control(t_id, control_data)

            tx_tally = sender.tallies[t_id]

            evt_args, evt_kwargs = await add_listener.get()
            rx_tally = evt_args[0]
            assert rx_tally.control == tx_tally.control == control_data
            assert rx_tally == tx_tally
Esempio n. 10
0
async def test_all_off_on_close(faker, udp_port):
    loop = asyncio.get_event_loop()

    sender = UmdSender(
        clients=[('127.0.0.1', udp_port)],
        all_off_on_close=True,
    )
    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

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

    tally_listener = EventListener()
    receiver.bind_async(loop, on_tally_updated=tally_listener.callback)

    async with receiver:
        async with sender:
            for screen_index in range(10):
                for i in range(10):
                    t_id = (screen_index, i)
                    sender.set_tally_text(t_id, f'Tally-{i}')
                    tx_tally = sender.tallies[t_id]

                    evt_args, evt_kwargs = await add_listener.get()
                    rx_tally = evt_args[0]
                    assert rx_tally == tx_tally

                    for ttype in TallyType.all():
                        setattr(tx_tally, ttype.name, TallyColor.RED)

                        evt_args, evt_kwargs = await tally_listener.get()
                        assert getattr(rx_tally, ttype.name) == TallyColor.RED

        # Sender is closed and should have broadcast "all-off"
        _ = await asyncio.wait_for(tally_listener.get(), timeout=1)
        while not tally_listener.empty():
            _ = await tally_listener.get()
        for rx_tally in receiver.tallies.values():
            assert rx_tally.rh_tally == TallyColor.OFF
            assert rx_tally.txt_tally == TallyColor.OFF
            assert rx_tally.lh_tally == TallyColor.OFF
Esempio n. 11
0
class UmdInput(BaseInput, namespace='umd.UmdInput', final=True):
    """Networked tally input using the UMDv5 protocol

    Arguments:
        config(MultiTallyConfig): The initial value for
            :attr:`~tallypi.baseio.BaseIO.config`
        hostaddr(str, optional): The local :attr:`hostaddr` to listen on.
            Defaults to :attr:`tslumd.receiver.UmdReceiver.DEFAULT_HOST`
        hostport(int, optional): The UDP :attr:`hostport` to listen on.
            Defaults to :attr:`tslumd.receiver.UmdReceiver.DEFAULT_PORT`
    """
    receiver: UmdReceiver
    """The tslumd server
    """

    def __init__(self,
                 config: MultiTallyConfig,
                 hostaddr: str = UmdReceiver.DEFAULT_HOST,
                 hostport: int = UmdReceiver.DEFAULT_PORT):

        super().__init__(config)
        self.loop = asyncio.get_event_loop()
        self._screen_indices = set()
        self._tally_keys = set()
        self.receiver = UmdReceiver(hostaddr=hostaddr, hostport=hostport)
        self.receiver.bind(
            on_screen_added=self._on_receiver_screen_added,
            on_tally_added=self._on_receiver_tally_added,
            on_tally_updated=self._on_receiver_tally_updated
        )

    @classmethod
    def get_init_options(cls) -> Tuple[Option]:
        return (
            MultiTallyOption,
            Option(
                name='hostaddr', type=str, required=False,
                default=UmdReceiver.DEFAULT_HOST, title='Listen Address',
            ),
            Option(
                name='hostport', type=int, required=False,
                default=UmdReceiver.DEFAULT_PORT, title='Listen Port',
            ),
        )

    @property
    def hostaddr(self) -> str:
        """The :attr:`~tslumd.receiver.UmdReceiver.hostaddr` of the :attr:`receiver`
        """
        return self.receiver.hostaddr

    @property
    def hostport(self) -> int:
        """The :attr:`~tslumd.receiver.UmdReceiver.hostport` of the :attr:`receiver`
        """
        return self.receiver.hostport

    async def open(self):
        if self.running:
            return
        await self.receiver.open()
        self.running = True

    async def close(self):
        if not self.running:
            return
        await self.receiver.close()

    async def set_hostaddr(self, hostaddr: str):
        """Set the :attr:`hostaddr` on the :attr:`receiver`
        """
        await self.receiver.set_hostaddr(hostaddr)

    async def set_hostport(self, hostport: int):
        """Set the :attr:`hostport` on the :attr:`receiver`
        """
        await self.receiver.set_hostport(hostport)

    def get_screen(self, screen_index: int) -> Optional[Screen]:
        if screen_index not in self._screen_indices:
            return None
        return self.receiver.screens.get(screen_index)

    def get_all_screens(self) -> Iterable[Screen]:
        for ix in self._screen_indices:
            yield self.receiver.screens[ix]

    def get_tally(self, tally_key: TallyKey) -> Optional[Tally]:
        if tally_key not in self._tally_keys:
            return None
        return self.receiver.tallies.get(tally_key)

    def get_all_tallies(self, screen_index: Optional[int] = None) -> Iterable[Tally]:
        if screen_index is not None:
            screen = self.get_screen(screen_index)
            tally_iter = []
            if screen is not None:
                tally_iter = screen
            for tally in tally_iter:
                if tally.id in self._tally_keys:
                    yield tally
        else:
            for tally_key in self._tally_keys:
                yield self.receiver.tallies[tally_key]

    def _on_receiver_screen_added(self, screen: Screen, **kwargs):
        if not screen.is_broadcast and self.screen_matches(screen):
            self._screen_indices.add(screen.index)
            self.emit('on_screen_added', self, screen)

    @logger.catch
    def _on_receiver_tally_added(self, tally, **kwargs):
        if self.tally_matches(tally):
            self._tally_keys.add(tally.id)
            self.emit('on_tally_added', self, tally)

    @logger.catch
    def _on_receiver_tally_updated(self, tally: Tally, props_changed: Set[str], **kwargs):
        if tally.id in self._tally_keys:
            self.emit('on_tally_updated', self, tally, props_changed)
Esempio n. 12
0
async def test_with_uhs_data(uhs500_msg_bytes, uhs500_msg_parsed, 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)

    uhs_screen = uhs500_msg_parsed.screen

    async with receiver:

        # Send message bytes to receiver
        transport.sendto(uhs500_msg_bytes, ('127.0.0.1', udp_port))

        # Wait for all ``on_tally_added`` events
        _ = await evt_listener.get()
        while not evt_listener.empty():
            _ = await evt_listener.get()

        screen = receiver.screens[uhs_screen]

        # Check all receiver tallies against the expected ones
        assert len(receiver.tallies) == len(uhs500_msg_parsed.displays)

        for disp in uhs500_msg_parsed.displays:
            assert disp.index in screen
            tally = screen.tallies[disp.index]
            assert tally.id == (uhs_screen, disp.index)
            assert receiver.tallies[tally.id] is tally
            assert disp == tally

        # Change each display and send the updated message to receiver
        # Then wait for ``on_tally_updated`` events
        receiver.unbind(evt_listener)
        receiver.bind_async(loop, on_tally_updated=evt_listener.callback)
        for disp in uhs500_msg_parsed.displays:
            tally = screen.tallies[disp.index]

            for tally_type in TallyType.all():
                attr = tally_type.name
                cur_value = getattr(disp, attr)
                if cur_value == TallyColor.RED:
                    new_value = TallyColor.GREEN
                else:
                    new_value = TallyColor.RED
                setattr(disp, attr, new_value)
            disp.text = f'{disp.text}-foo'
            disp.brightness = 1

            transport.sendto(uhs500_msg_parsed.build_message(),
                             ('127.0.0.1', udp_port))

            evt_args, evt_kwargs = await evt_listener.get()
            evt_tally = evt_args[0]
            assert evt_tally is tally
            assert disp == tally
Esempio n. 13
0
async def test_with_uhs_data(udp_port):

    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', udp_port)])
    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

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

    screen_index = 1

    async with receiver:
        async with sender:
            # Create initial tallies using text method
            for i in range(100):
                t_id = (screen_index, i)
                sender.set_tally_text(t_id, f'Tally-{i}')
                tx_tally = sender.tallies[t_id]
                screen = sender.screens[screen_index]
                assert screen[i] is tx_tally

                evt_args, evt_kwargs = await evt_listener.get()
                rx_tally = evt_args[0]
                assert rx_tally == tx_tally

            # Create one more tally using ``set_tally_color``
            t_id = (screen_index, 200)
            sender.set_tally_color(t_id, TallyType.lh_tally, TallyColor.GREEN)
            tx_tally = sender.tallies[t_id]
            assert screen[200] is tx_tally
            evt_args, evt_kwargs = await evt_listener.get()
            rx_tally = evt_args[0]
            assert rx_tally == tx_tally

            # Allow the sender to do a full refresh.  Nothing should have changed
            await asyncio.sleep(sender.tx_interval)
            assert evt_listener.empty()

            # Connect to ``on_tally_updated`` events
            receiver.unbind(evt_listener)
            receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

            # Change each tally/tally_type color to red and check the received values
            for tx_tally in sender.tallies.values():
                for tally_type in TallyType.all():
                    sender.set_tally_color(tx_tally.id, tally_type,
                                           TallyColor.RED)

                    evt_args, evt_kwargs = await evt_listener.get()
                    rx_tally = evt_args[0]
                    assert rx_tally is receiver.tallies[tx_tally.id]
                    assert rx_tally == tx_tally

            # Change the text of the extra tally from above and check
            t_id = (screen_index, 200)
            sender.set_tally_text(t_id, 'Tally-200')
            tx_tally = sender.tallies[t_id]
            evt_args, evt_kwargs = await evt_listener.get()
            rx_tally = evt_args[0]
            assert rx_tally == tx_tally

            # Let the sender to another full refresh
            await asyncio.sleep(sender.tx_interval)
            assert evt_listener.empty()

            # Change all tally/tally_type colors, but don't wait for results yet
            for tx_tally in sender.tallies.values():
                for tally_type in TallyType.all():
                    sender.set_tally_color(tx_tally.id, tally_type,
                                           TallyColor.AMBER)
                sender.set_tally_text(tx_tally.id, f'foo-{tx_tally.index}')

            # Wait for updates from last loop to get to the receiver
            # and check the results
            _ = await evt_listener.get()
            while not evt_listener.empty():
                _ = await evt_listener.get()

            for tx_tally in sender.tallies.values():
                rx_tally = receiver.tallies[tx_tally.id]
                assert rx_tally == tx_tally
Esempio n. 14
0
async def test_broadcast_display(udp_port):
    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', udp_port)])
    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

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

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

        await asyncio.sleep(sender.tx_interval)
        assert evt_listener.empty()

    color_kw = {
        attr: TallyColor.RED
        for attr in ['rh_tally', 'txt_tally', 'lh_tally']
    }

    screen_index = 1

    async with receiver:
        async with sender:
            # Create initial tallies
            for i in range(10):
                t_id = (screen_index, i)
                tx_tally = sender.add_tally(t_id, **color_kw)
                screen = sender.screens[screen_index]
                assert screen[i] is tx_tally
                tx_tally.text = f'Tally-{i}'

                evt_args, evt_kwargs = await evt_listener.get()
                rx_tally = evt_args[0]
                assert rx_tally == tx_tally

            # Connect to ``on_tally_updated`` events
            receiver.unbind(evt_listener)
            receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

            # Send a broadcast tally for each color setting all TallyType's to it
            for color in TallyColor:
                color_kw = {k: color for k in color_kw.keys()}
                await sender.send_broadcast_tally(screen_index, **color_kw)
                await wait_for_receiver()

                # Check the tally colors and make sure the text values remained
                for rx_tally in receiver.tallies.values():
                    tx_tally = sender.tallies[rx_tally.id]
                    assert rx_tally.text == tx_tally.text == f'Tally-{rx_tally.index}'
                    assert rx_tally.rh_tally == tx_tally.rh_tally == color
                    assert rx_tally.txt_tally == tx_tally.txt_tally == color
                    assert rx_tally.lh_tally == tx_tally.lh_tally == color

            # Broadcast all colors to "OFF" and set all names to 'foo'
            color_kw = {k: TallyColor.OFF for k in color_kw.keys()}
            await sender.send_broadcast_tally(screen_index,
                                              text='foo',
                                              **color_kw)
            await wait_for_receiver()

            # Check the tally colors and text values
            for rx_tally in receiver.tallies.values():
                tx_tally = sender.tallies[rx_tally.id]
                assert rx_tally.text == tx_tally.text == 'foo'
                assert rx_tally.rh_tally == tx_tally.rh_tally == TallyColor.OFF
                assert rx_tally.txt_tally == tx_tally.txt_tally == TallyColor.OFF
                assert rx_tally.lh_tally == tx_tally.lh_tally == TallyColor.OFF

            # Send broadcast tally control messages
            for control_data in [b'foo', b'bar', b'baz']:
                await sender.send_broadcast_tally(screen_index,
                                                  control=control_data)
                await wait_for_receiver()

                # Check for the correct control data and ensure other values
                # remain unchanged
                for rx_tally in receiver.tallies.values():
                    tx_tally = sender.tallies[rx_tally.id]
                    assert rx_tally.control == tx_tally.control == control_data
                    assert rx_tally.text == tx_tally.text == 'foo'
                    assert rx_tally.rh_tally == tx_tally.rh_tally == TallyColor.OFF
                    assert rx_tally.txt_tally == tx_tally.txt_tally == TallyColor.OFF
                    assert rx_tally.lh_tally == tx_tally.lh_tally == TallyColor.OFF

            # Do the same as above, but using the `sender.send_broadcast_tally_control` method
            # and change one tally color
            for control_data in [b'abc', b'def', b'ghi']:
                await sender.send_broadcast_tally_control(
                    screen_index, control_data, rh_tally=TallyColor.RED)

                await wait_for_receiver()

                # Check for the correct control data and ensure other values
                for rx_tally in receiver.tallies.values():
                    tx_tally = sender.tallies[rx_tally.id]
                    assert rx_tally.control == tx_tally.control == control_data
                    assert rx_tally.text == tx_tally.text == 'foo'
                    assert rx_tally.rh_tally == tx_tally.rh_tally == TallyColor.RED
                    assert rx_tally.txt_tally == tx_tally.txt_tally == TallyColor.OFF
                    assert rx_tally.lh_tally == tx_tally.lh_tally == TallyColor.OFF
Esempio n. 15
0
async def test_tally_type_variations(udp_port):

    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', udp_port)])
    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

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

    tally_listener = EventListener()
    receiver.bind_async(loop, on_tally_updated=tally_listener.callback)

    tally_type_strs = ('rh', 'txt', 'lh')
    tally_types = (TallyType.rh_tally, TallyType.txt_tally, TallyType.lh_tally)

    def get_tally_colors(tally):
        d = {}
        for tt in tally_types:
            d[tt] = tally[tt]
        return d

    async def wait_for_rx(tally_type):
        tally_types = set()
        if not isinstance(tally_type, TallyType):
            tally_type = TallyType.from_str(tally_type)
        if tally_type.is_iterable:
            for tt in tally_type:
                tally_types.add(tt.name)
        else:
            tally_types.add(tally_type.name)
        props = set()
        for _ in range(len(tally_types)):
            evt_args, evt_kwargs = await tally_listener.get()
            props |= evt_args[1]
            if props == tally_types:
                break
        assert props == tally_types

    screen_index = 1

    async with receiver:
        async with sender:

            for i in range(10):
                t_id = (screen_index, i)
                expected = {key: TallyColor.OFF for key in tally_types}

                tally = None
                rx_tally = None

                for tt_str, tt in zip(tally_type_strs, tally_types):

                    sender.set_tally_color(t_id, tt_str, TallyColor.RED)
                    expected[tt] = TallyColor.RED

                    tally = sender.tallies[t_id]
                    assert get_tally_colors(tally) == expected

                    if rx_tally is None:
                        evt_args, evt_kwargs = await evt_listener.get()
                        rx_tally = evt_args[0]
                        assert rx_tally == tally
                    else:
                        await wait_for_rx(tt)
                        assert get_tally_colors(rx_tally) == expected

                    sender.set_tally_color(t_id, tt, TallyColor.GREEN)
                    expected[tt] = TallyColor.GREEN
                    assert get_tally_colors(tally) == expected

                    await wait_for_rx(tt)
                    assert get_tally_colors(rx_tally) == expected

                    sender.set_tally_color(t_id, tt_str, 'off')
                    expected[tt] = TallyColor.OFF
                    assert get_tally_colors(tally) == expected

                    await wait_for_rx(tt)
                    assert get_tally_colors(rx_tally) == expected

                    sender.set_tally_color(t_id, tt, 'red')
                    expected[tt] = TallyColor.RED
                    assert get_tally_colors(tally) == expected

                    await wait_for_rx(tt)
                    assert get_tally_colors(rx_tally) == expected

                sender.set_tally_color(t_id, 'all', 'off')
                expected = {key: TallyColor.OFF for key in tally_types}
                assert get_tally_colors(tally) == expected
                assert tally['all'] == TallyColor.OFF

                await wait_for_rx('rh|txt|lh')
                assert get_tally_colors(rx_tally) == expected

                sender.set_tally_color(t_id, 'lh|rh', 'red')
                expected[TallyType.rh_tally] = TallyColor.RED
                expected[TallyType.lh_tally] = TallyColor.RED
                assert get_tally_colors(tally) == expected
                assert tally['all'] == TallyColor.RED

                await wait_for_rx('lh|rh')
                assert get_tally_colors(rx_tally) == expected

                sender.set_tally_color(t_id, 'txt', 'green')
                expected[TallyType.txt_tally] = TallyColor.GREEN
                assert get_tally_colors(tally) == expected

                await wait_for_rx('txt')
                assert get_tally_colors(rx_tally) == expected

                assert tally['all'] == tally['lh|txt'] == tally[
                    'rh|txt'] == TallyColor.AMBER
Esempio n. 16
0
class UmdIo(Interface):
    """Main UMD interface
    """

    hostaddr: str = Property('0.0.0.0')
    """Alias for :attr:`tslumd.receiver.UmdReceiver.hostaddr`"""

    hostport: int = Property(65000)
    """Alias for :attr:`tslumd.receiver.UmdReceiver.hostport`"""

    device_maps: Dict[int, DeviceMapping] = DictProperty()
    """A ``dict`` of :class:`~.mapper.DeviceMapping` definitions stored with
    their :attr:`~.mapper.DeviceMapping.device_index` as keys
    """

    mapped_devices: Dict[int, MappedDevice] = DictProperty()
    """A ``dict`` of :class:`~.mapper.MappedDevice` stored with the
    ``device_index`` of their :attr:`~.mapper.MappedDevice.map` as keys
    """

    def on_tally_added(self, tally: Tally):
        """Fired when a :class:`tslumd.tallyobj.Tally` instance is
        added to :attr:`tallies`
        """

    def on_tally_updated(self, tally: Tally):
        """Fired when any :class:`tslumd.tallyobj.Tally` instance has
        been updated
        """

    _events_ = ['on_tally_added', 'on_tally_updated']
    interface_name = 'tslumd'
    def __init__(self):
        self._reading_config = False
        self._config_read = asyncio.Event()
        self._connect_lock = asyncio.Lock()
        super().__init__()
        self.receiver = UmdReceiver()
        self.hostaddr = self.receiver.hostaddr
        self.hostport = self.receiver.hostport
        self.receiver.bind_async(self.loop,
            on_tally_added=self._on_receiver_tally_added,
            on_tally_updated=self._on_receiver_tally_updated,
        )
        self.bind_async(self.loop,
            config=self.read_config,
        )
        self.bind(**{prop:self.update_config for prop in ['hostaddr', 'hostport']})

    @property
    def tallies(self) -> Dict[int, Tally]:
        """Alias for :attr:`tslumd.receiver.UmdReceiver.tallies`
        """
        return self.receiver.tallies

    async def set_engine(self, engine: 'jvconnected.engine.Engine'):
        if engine is self.engine:
            return
        if engine.config is not self.config:
            self._config_read.clear()
        await super().set_engine(engine)
        engine.bind_async(
            self.loop,
            on_device_added=self.on_engine_device_added,
            on_device_removed=self.on_engine_device_removed,
        )

    async def open(self):
        async with self._connect_lock:
            if self.running:
                return
            logger.debug('UmdIo.open()')
            if self.config is not None:
                await self._config_read.wait()
            self.running = True
            await self.receiver.open()
            logger.success('UmdIo running')

    async def close(self):
        async with self._connect_lock:
            if not self.running:
                return
            logger.debug('UmdIo.close()')
            self.running = False
            await self.receiver.close()
            logger.success('UmdIo closed')

    async def set_bind_address(self, hostaddr: str, hostport: int):
        """Set the :attr:`hostaddr` and :attr:`hostport` and restart the server
        """
        await self.receiver.set_bind_address(hostaddr, hostport)
        self.hostaddr = self.receiver.hostaddr
        self.hostport = self.receiver.hostport


    async def set_hostaddr(self, hostaddr: str):
        """Set the :attr:`hostaddr` and restart the server
        """
        await self.set_bind_address(hostaddr, self.hostport)

    async def set_hostport(self, hostport: int):
        """Set the :attr:`hostport` and restart the server
        """
        await self.set_bind_address(self.hostaddr, hostport)

    async def _on_receiver_tally_added(self, tally, **kwargs):
        for mapped_device in self.mapped_devices.values():
            if mapped_device.have_tallies:
                continue
            r = mapped_device.get_tallies()
            if r:
                await mapped_device.update_device_tally()
        self.emit('on_tally_added', tally, **kwargs)

    async def _on_receiver_tally_updated(self, tally: Tally, props_changed: Set[str], **kwargs):
        self.emit('on_tally_updated', tally, props_changed, **kwargs)

    def get_device_by_index(self, ix: int) -> Optional['jvconnected.device.Device']:
        device = None
        if self.engine is not None:
            device_conf = self.engine.config.indexed_devices.get_by_index(ix)
            if device_conf is not None:
                device = self.engine.devices.get(device_conf.id)
        return device

    @logger.catch
    async def add_device_mapping(self, device_map: 'DeviceMapping'):
        """Add a :class:`~.mapper.DeviceMapping` definition to :attr:`device_maps`
        and update the :attr:`config`.

        An instance of :class:`~.mapper.MappedDevice` is also created and
        associated with its :class:`~jvconnected.device.Device`
        if found in the :attr:`engine`.
        """
        ix = device_map.device_index
        self.device_maps[ix] = device_map
        mapped_device = self.mapped_devices.get(ix)
        if mapped_device is not None:
            await mapped_device.set_device(None)
            del self.mapped_devices[ix]
        device = self.get_device_by_index(ix)
        mapped_device = MappedDevice(map=device_map, umd_io=self)
        self.mapped_devices[ix] = mapped_device
        await mapped_device.set_device(device)
        self.update_config()

    async def remove_device_mapping(self, device_index: int):
        """Remove a :class:`~.mapper.DeviceMapping` and its associated
        :class:`~.mapper.MappedDevice` by the given device index
        """
        if device_index not in self.device_maps:
            return
        del self.device_maps[device_index]
        mapped_device = self.mapped_devices.get(device_index)
        if mapped_device is not None:
            await mapped_device.set_device(None)
            del self.mapped_devices[device_index]
        self.update_config()

    async def on_engine_device_added(self, device, **kwargs):
        mapped_device = self.mapped_devices.get(device.device_index)
        if mapped_device is not None:
            await mapped_device.set_device(device)

    async def on_engine_device_removed(self, device, reason, **kwargs):
        mapped_device = self.mapped_devices.get(device.device_index)
        if mapped_device is not None:
            await mapped_device.set_device(None)

    def update_config(self, *args, **kwargs):
        """Update the :attr:`config` with current state
        """
        if self._reading_config:
            return
        if self.config is None:
            return
        if not self._config_read.is_set():
            return
        d = self.get_config_section()
        if d is None:
            return
        d['hostaddr'] = self.hostaddr
        d['hostport'] = self.hostport
        m = self.device_maps
        d['device_maps'] = [m[k] for k in sorted(m.keys())]

    @logger.catch
    async def read_config(self, *args, **kwargs):
        d = self.get_config_section()
        if d is None:
            return
        self._reading_config = True
        hostaddr = d.get('hostaddr', self.hostaddr)
        hostport = d.get('hostport', self.hostport)
        coros = []
        for dev_map in d.get('device_maps', []):
            coros.append(self.add_device_mapping(dev_map))
        await asyncio.gather(*coros)
        await self.set_bind_address(hostaddr, hostport)
        self._reading_config = False
        self._config_read.set()