def test_dmx_data(): packet = DataPacket(cid=tuple(range(0, 16)), sourceName="", universe=1) # test valid lengths for i in range(0, 512): data = tuple(x % 256 for x in range(0, i)) # test property setter packet.dmxData = data assert len(packet.dmxData) == 512 assert packet.length == 638 # test constructor for the same parameter packet2 = DataPacket(tuple(range(0, 16)), sourceName="", universe=1, dmxData=data) assert len(packet2.dmxData) == 512 assert packet2.length == 638 def execute_universes_expect(data: tuple): with pytest.raises(ValueError): packet.dmxData = data with pytest.raises(ValueError): DataPacket(tuple(range(0, 16)), sourceName="", universe=1, dmxData=data) # test for non-int and out of range values values in tuple execute_universes_expect(tuple('string')) execute_universes_expect(tuple(range(255, 257))) # test for tuple-length > 512 execute_universes_expect(tuple(range(0, 513)))
def test_str(): packet = DataPacket(cid=(16, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8), sourceName='Test', universe=62000, priority=195, sequence=34) assert packet.__str__( ) == 'sACN DataPacket: Universe: 62000, Priority: 195, Sequence: 34, CID: (16, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8)'
def test_possible_universes(): receiver, socket = get_receiver() assert receiver.get_possible_universes() == () packet = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) socket.call_on_data(bytes(packet.getBytes()), 0) assert receiver.get_possible_universes() == tuple([1])
def test_first_packet(): _, listener, socket = get_handler() assert listener.on_availability_change_changed is None assert listener.on_availability_change_universe is None assert listener.on_dmx_data_change_packet is None packet = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) socket.call_on_data(bytes(packet.getBytes()), 0) assert listener.on_availability_change_changed == 'available' assert listener.on_availability_change_universe == 1 assert listener.on_dmx_data_change_packet.__dict__ == packet.__dict__
def test_sourceName(): # test string has 25 characters but is 64 bytes (1 too many) when UTF-8 encoded overlength_string = "𔑑覱֪I𤵎⠣Ķ'𫳪爓Û:𢏴㓑ò4𰬀鿹џ>𖬲膬ЩJ𞄇" packet = DataPacket(cid=tuple(range(0, 16)), sourceName="", universe=1) # test property setter with pytest.raises(TypeError): packet.sourceName = 0x33 with pytest.raises(ValueError): packet.sourceName = overlength_string # test constructor with pytest.raises(ValueError): DataPacket(cid=tuple(range(0, 16)), sourceName=overlength_string, universe=1)
def test_send_out_all_universes(): handler, socket, cid, source_name, outputs = get_handler() handler.manual_flush = True current_time = 100.0 outputs[1].multicast = False destination = "1.2.3.4" outputs[1].destination = destination assert handler.manual_flush is True assert socket.send_unicast_called is None assert socket.send_multicast_called is None assert outputs[1].multicast is False # check that send packets due to interval are suppressed socket.call_on_periodic_callback(current_time) assert socket.send_unicast_called is None assert socket.send_multicast_called is None # after calling send_out_all_universes, the DataPackets need to send, as well as one SyncPacket sync_universe = 63999 handler.send_out_all_universes(sync_universe, outputs, current_time) assert socket.send_unicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=0, sync_universe=sync_universe).__dict__ assert socket.send_unicast_called[1] == destination assert socket.send_multicast_called[0].__dict__ == SyncPacket( cid, sync_universe, 0).__dict__ assert socket.send_multicast_called[1] == calculate_multicast_addr( sync_universe)
def test_byte_construction_and_deconstruction(): built_packet = DataPacket(cid=(16, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8), sourceName='Test Name', universe=62000, dmxData=tuple(x % 256 for x in range(0, 512)), priority=195, sequence=34, streamTerminated=True, previewData=True, forceSync=True, sync_universe=12000, dmxStartCode=12) read_packet = DataPacket.make_data_packet(built_packet.getBytes()) assert built_packet.dmxData == read_packet.dmxData assert built_packet == read_packet
def test_abstract_receiver_handler_listener(): listener = ReceiverHandlerListener() with pytest.raises(NotImplementedError): listener.on_availability_change(1, 'test') with pytest.raises(NotImplementedError): listener.on_dmx_data_change( DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1))
def run(self): self.logger.info(f'Started new sACN receiver thread') self.socket.settimeout(0.1) # timeout as 100ms self.enabled_flag = True while self.enabled_flag: # before receiving: check for timeouts self.check_for_timeouts() # receive the data try: raw_data, ip_sender = list(self.socket.recvfrom( 1144)) # 1144 because the longest possible packet # in the sACN standard is the universe discovery packet with a max length of 1144 except socket.timeout: continue # if a timeout happens just go through while from the beginning try: tmp_packet = DataPacket.make_data_packet(raw_data) except: # try to make a DataPacket. If it fails just go over it continue self.logger.debug(f'Received sACN packet:\n{tmp_packet}') self.check_for_stream_terminated_and_refresh_timestamp(tmp_packet) self.refresh_priorities(tmp_packet) if not self.is_legal_priority(tmp_packet): continue if not self.is_legal_sequence( tmp_packet): # check for bad sequence number continue self.fire_callbacks_universe(tmp_packet) self.logger.info('Stopped sACN receiver thread')
def execute_universes_expect(data: tuple): with pytest.raises(ValueError): packet.dmxData = data with pytest.raises(ValueError): DataPacket(tuple(range(0, 16)), sourceName="", universe=1, dmxData=data)
def test_listen_on_availability_change(): receiver, socket = get_receiver() called = False @receiver.listen_on('availability') def callback_available(universe, changed): assert changed == 'available' assert universe == 1 nonlocal called called = True packet = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) socket.call_on_data(bytes(packet.getBytes()), 0) assert called
def test_listen_on_dmx_data_change(): receiver, socket = get_receiver() packetSend = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) called = False @receiver.listen_on('universe', universe=packetSend.universe) def callback_packet(packet): assert packetSend.__dict__ == packet.__dict__ nonlocal called called = True socket.call_on_data(bytes(packetSend.getBytes()), 0) assert called
def test_universe_timeout(): _, listener, socket = get_handler() assert listener.on_availability_change_changed is None assert listener.on_availability_change_universe is None packet = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) socket.call_on_data(bytes(packet.getBytes()), 0) socket.call_on_periodic_callback(0) assert listener.on_availability_change_changed == 'available' assert listener.on_availability_change_universe == 1 # wait the specified amount of time and check, that a timeout was triggered # add 10ms of grace time socket.call_on_periodic_callback((E131_NETWORK_DATA_LOSS_TIMEOUT_ms / 1000) + 0.01) assert listener.on_availability_change_changed == 'timeout' assert listener.on_availability_change_universe == 1
def test_sequence_increment(): # Test that the sequence number can be increased and the wrap around at 255 is correct built_packet = DataPacket(cid=(16, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8), sourceName='Test Name', universe=30) built_packet.sequence = 78 built_packet.sequence_increase() assert built_packet.sequence == 79 built_packet.sequence = 255 built_packet.sequence_increase() assert built_packet.sequence == 0
def test_sequence(): packet = DataPacket(cid=tuple(range(0, 16)), sourceName="", universe=1) # test property setter def property(i): packet.sequence = i # test constructor for the same parameter def constructor(i): DataPacket(tuple(range(0, 16)), sourceName="", universe=1, sequence=i) property_number_range_check(0, 255, property, constructor)
def test_send_out_interval(): handler, socket, cid, source_name, outputs = get_handler() handler.manual_flush = False current_time = 100.0 assert handler.manual_flush is False assert socket.send_unicast_called is None # first send packet due to interval socket.call_on_periodic_callback(current_time) assert socket.send_unicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=0).__dict__ assert socket.send_unicast_called[1] == '127.0.0.1' # interval must be 1 seconds socket.call_on_periodic_callback(current_time + 0.99) assert socket.send_unicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=0).__dict__ socket.call_on_periodic_callback(current_time + 1.01) assert socket.send_unicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=1).__dict__
def activate_output(self, universe: int) -> None: """ Activates a universe that's then starting to sending every second. See http://tsp.esta.org/tsp/documents/docs/E1-31-2016.pdf for more information :param universe: the universe to activate """ check_universe(universe) # check, if the universe already exists in the list: if universe in self._outputs: return # add new sending: new_output = Output(DataPacket(cid=self._sender_handler._CID, sourceName=self._sender_handler._source_name, universe=universe)) self._outputs[universe] = new_output
def test_multicast(): handler, socket, cid, source_name, outputs = get_handler() handler.manual_flush = False current_time = 100.0 outputs[1].multicast = True outputs[1].ttl = 123 assert handler.manual_flush is False assert socket.send_multicast_called is None assert outputs[1].multicast is True assert outputs[1].ttl == 123 # first send packet due to interval socket.call_on_periodic_callback(current_time) assert socket.send_multicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=0).__dict__ assert socket.send_multicast_called[1] == calculate_multicast_addr(1) # only send out on dmx change # test same data as before # TODO: currently there is no "are the values different" check. # If it is implemented, enable the following line: # outputs[1].dmx_data = (0, 0) socket.call_on_periodic_callback(current_time) assert socket.send_multicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=0).__dict__ assert socket.send_multicast_called[1] == calculate_multicast_addr(1) # test change in data as before outputs[1].dmx_data = (1, 2) socket.call_on_periodic_callback(current_time) assert socket.send_multicast_called[0].__dict__ == DataPacket( cid, source_name, 1, sequence=1, dmxData=(1, 2)).__dict__ assert socket.send_multicast_called[1] == calculate_multicast_addr(1) # assert that no unicast was send assert socket.send_unicast_called is None
def test_calculate_multicast_addr(): universe_1 = '239.255.0.1' universe_63999 = '239.255.249.255' assert calculate_multicast_addr(1) == universe_1 assert calculate_multicast_addr(63999) == universe_63999 packet = DataPacket(cid=tuple(range(0, 16)), sourceName="", universe=1) assert packet.calculate_multicast_addr() == universe_1 packet = DataPacket(cid=tuple(range(0, 16)), sourceName="", universe=63999) assert packet.calculate_multicast_addr() == universe_63999
def test_flush(): socket = SenderSocketTest() sync_universe = 1234 sender = sacn.sACNsender(sync_universe=sync_universe, socket=socket) assert socket.send_unicast_called is None # test that non-active universes throw exception with pytest.raises(ValueError): sender.flush([1, 2]) assert socket.send_unicast_called is None # test that no active universes triggers nothing sender.flush() assert socket.send_unicast_called is None # activate universe 1 sender.activate_output(1) assert socket.send_unicast_called is None # test that no parameters triggers flushing of all universes sender.flush() assert socket.send_unicast_called[0].__dict__ == DataPacket( sender._sender_handler._CID, sender._sender_handler._source_name, 1, sync_universe=sync_universe).__dict__ # activate universe 2 sender.activate_output(2) # test that a list with only universe 1 triggers flushing of only this universe sender.flush([1]) assert socket.send_unicast_called[0].__dict__ == DataPacket( sender._sender_handler._CID, sender._sender_handler._source_name, 1, sequence=1, sync_universe=sync_universe).__dict__
def on_data(self, data: bytes, current_time: float) -> None: try: tmp_packet = DataPacket.make_data_packet(data) except TypeError: # try to make a DataPacket. If it fails just ignore it return self.check_for_stream_terminated_and_refresh_timestamp( tmp_packet, current_time) self.refresh_priorities(tmp_packet, current_time) if not self.is_legal_priority(tmp_packet): return if not self.is_legal_sequence( tmp_packet): # check for bad sequence number return self.fire_callbacks_universe(tmp_packet)
def test_universe_stream_terminated(): _, listener, socket = get_handler() assert listener.on_availability_change_changed is None assert listener.on_availability_change_universe is None packet = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) socket.call_on_data(bytes(packet.getBytes()), 0) assert listener.on_availability_change_changed == 'available' assert listener.on_availability_change_universe == 1 packet.sequence_increase() packet.option_StreamTerminated = True socket.call_on_data(bytes(packet.getBytes()), 0) assert listener.on_availability_change_changed == 'timeout' assert listener.on_availability_change_universe == 1
def test_possible_universes(): handler, _, socket = get_handler() assert handler.get_possible_universes() == [] packet = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) # add universe 1 socket.call_on_data(bytes(packet.getBytes()), 0) assert handler.get_possible_universes() == [1] # add universe 2 packet.universe = 2 socket.call_on_data(bytes(packet.getBytes()), 0) assert handler.get_possible_universes() == [1, 2] # remove universe 2 packet.option_StreamTerminated = True socket.call_on_data(bytes(packet.getBytes()), 0) assert handler.get_possible_universes() == [1]
def test_deactivate_output(): socket = SenderSocketTest() sender = sacn.sACNsender(socket=socket) # check that three packets with stream-termination bit set are send out on deactivation sender.activate_output(100) assert socket.send_unicast_called is None sender.deactivate_output(100) assert socket.send_unicast_called[0].__dict__ == DataPacket( sender._sender_handler._CID, sender._sender_handler._source_name, 100, sequence=2, streamTerminated=True).__dict__ # start with no universes active assert list(sender._outputs.keys()) == [] sender.deactivate_output(1) assert list(sender._outputs.keys()) == [] # one universe active sender.activate_output(1) assert list(sender._outputs.keys()) == [1] sender.deactivate_output(1) assert list(sender._outputs.keys()) == [] # two universes active sender.activate_output(10) sender.activate_output(11) assert list(sender._outputs.keys()) == [10, 11] sender.deactivate_output(10) assert list(sender._outputs.keys()) == [11] # deactivate no active universe assert list(sender._outputs.keys()) == [11] sender.deactivate_output(99) assert list(sender._outputs.keys()) == [11]
def get_handler(): cid = tuple(range(0, 16)) source_name = 'test' outputs: Dict[int, Output] = { 1: Output(packet=DataPacket( cid=cid, sourceName=source_name, universe=1, )) } socket = SenderSocketTest() handler = SenderHandler( cid=cid, source_name=source_name, outputs=outputs, bind_address='0.0.0.0', bind_port=5568, fps=30, socket=socket, ) handler.manual_flush = True # wire up listener for tests socket._listener = handler return handler, socket, cid, source_name, outputs
def case_goes_through(sequence_a: int, sequence_b: int): _, listener, socket = get_handler() assert listener.on_dmx_data_change_packet is None packet1 = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16)), sequence=sequence_a) socket.call_on_data(bytes(packet1.getBytes()), 0) assert listener.on_dmx_data_change_packet.__dict__ == packet1.__dict__ packet2 = DataPacket( cid=tuple(range(0, 16)), sourceName='Test', universe=1, # change DMX data to simulate data from another source dmxData=tuple(range(1, 17)), sequence=sequence_b) socket.call_on_data(bytes(packet2.getBytes()), 1) assert listener.on_dmx_data_change_packet.__dict__ == packet2.__dict__
def test_invalid_priority(): # send a lower priority on a second packet _, listener, socket = get_handler() assert listener.on_dmx_data_change_packet is None packet1 = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16)), priority=100) socket.call_on_data(bytes(packet1.getBytes()), 0) assert listener.on_dmx_data_change_packet.__dict__ == packet1.__dict__ packet2 = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16)), priority=99) socket.call_on_data(bytes(packet2.getBytes()), 1) # second packet does not override the previous one assert listener.on_dmx_data_change_packet.__dict__ == packet1.__dict__
def test_remove_listener(): receiver, socket = get_receiver() packetSend = DataPacket(cid=tuple(range(0, 16)), sourceName='Test', universe=1, dmxData=tuple(range(0, 16))) called = 0 def callback_packet(packet): assert packetSend.__dict__ == packet.__dict__ nonlocal called called += 1 # register listener multiple times receiver.register_listener('universe', callback_packet, universe=packetSend.universe) receiver.register_listener('universe', callback_packet, universe=packetSend.universe) socket.call_on_data(bytes(packetSend.getBytes()), 0) assert called == 2 # change DMX data to trigger a change packetSend.dmxData = tuple(range(16, 32)) packetSend.sequence_increase() receiver.remove_listener(callback_packet) # removing a listener does not exist, should do nothing receiver.remove_listener(None) socket.call_on_data(bytes(packetSend.getBytes()), 0) assert called == 2
def test_parse_data_packet(): # Use the example present in the E1.31 spec in appendix B raw_data = [ # preamble size 0x00, 0x10, # postamble size 0x00, 0x00, # ACN packet identifier 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00, # flags and length 0x72, 0x7d, # Root vector 0x00, 0x00, 0x00, 0x04, # CID 0xef, 0x07, 0xc8, 0xdd, 0x00, 0x64, 0x44, 0x01, 0xa3, 0xa2, 0x45, 0x9e, 0xf8, 0xe6, 0x14, 0x3e, # Framing flags and length 0x72, 0x57, # Framing vector 0x00, 0x00, 0x00, 0x02, # Source name 'Source_A' 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # priority 0x64, # sync address 0x1f, 0x1a, # sequence number 0x9a, # options 0x00, # universe 0x00, 0x01, # DMP flags and length 0x72, 0x0d, # DMP vector 0x02, # address type & data type 0xa1, # first property address 0x00, 0x00, # address increment 0x00, 0x01, # property value count 0x02, 0x01, # DMX start code 0x00, ] # DMX data (starting with 0 and incrementing with wrap around at 255) dmx_data = [x % 256 for x in range(0, 512)] raw_data.extend(dmx_data) # parse raw data packet = DataPacket.make_data_packet(raw_data) assert packet.length == 638 assert packet._vector == (0x00, 0x00, 0x00, 0x04) assert packet.cid == (0xef, 0x07, 0xc8, 0xdd, 0x00, 0x64, 0x44, 0x01, 0xa3, 0xa2, 0x45, 0x9e, 0xf8, 0xe6, 0x14, 0x3e) assert packet.sourceName == 'Source_A' assert packet.priority == 100 assert packet.syncAddr == 7962 assert packet.sequence == 154 assert packet.option_ForceSync is False assert packet.option_PreviewData is False assert packet.option_StreamTerminated is False assert packet.universe == 1 assert packet.dmxStartCode == 0 assert packet.dmxData == tuple(dmx_data) # test for invalid data # test for too short data arrays for i in range(1, 126): with pytest.raises(TypeError): DataPacket.make_data_packet([x % 256 for x in range(0, i)]) # test for invalid vectors with pytest.raises(TypeError): DataPacket.make_data_packet([x % 256 for x in range(0, 126)])
def test_property_adjustment_and_deconstruction(): # Converting DataPacket -> bytes -> DataPacket should produce the same result, # but with changed properties that are not the default built_packet = DataPacket(cid=(16, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8), sourceName='Test Name', universe=30) built_packet.cid = tuple(range(16)) built_packet.sourceName = '2nd Test Name' built_packet.universe = 31425 built_packet.dmxData = ((200, ) + tuple(range(255, 0, -1)) + tuple(range(255)) + (0, )) built_packet.priority = 12 built_packet.sequence = 45 built_packet.option_StreamTerminated = True built_packet.option_PreviewData = True built_packet.option_ForceSync = True built_packet.syncAddr = 34003 built_packet.dmxStartCode = 8 read_packet = DataPacket.make_data_packet(built_packet.getBytes()) assert read_packet.cid == tuple(range(16)) assert read_packet.sourceName == '2nd Test Name' assert read_packet.universe == 31425 assert read_packet.dmxData == ((200, ) + tuple(range(255, 0, -1)) + tuple(range(255)) + (0, )) assert read_packet.priority == 12 assert read_packet.sequence == 45 assert read_packet.option_StreamTerminated is True assert read_packet.option_PreviewData is True assert read_packet.option_ForceSync is True assert read_packet.syncAddr == 34003 assert read_packet.dmxStartCode == 8