diff --git a/.github/workflows/run tests.yml b/.github/workflows/run tests.yml index 8aebdd7..d2d031d 100644 --- a/.github/workflows/run tests.yml +++ b/.github/workflows/run tests.yml @@ -7,10 +7,10 @@ jobs: steps: - uses: actions/checkout@master - - name: Set up Python 3.10.12 + - name: Set up Python 3.11.8 uses: actions/setup-python@v1 with: - python-version: 3.10.12 + python-version: 3.11.8 - name: Run test run: python -m unittest discover --v \ No newline at end of file diff --git a/stupidArtnet/StupidArtnet.py b/stupidArtnet/StupidArtnet.py index a4d5ec5..fd98bf0 100644 --- a/stupidArtnet/StupidArtnet.py +++ b/stupidArtnet/StupidArtnet.py @@ -20,7 +20,7 @@ class StupidArtnet(): UDP_PORT = 6454 def __init__(self, target_ip='127.0.0.1', universe=0, packet_size=512, fps=30, - even_packet_size=True, broadcast=False): + even_packet_size=True, broadcast=False, artsync=False): """Initializes Art-Net Client. Args: @@ -30,6 +30,7 @@ def __init__(self, target_ip='127.0.0.1', universe=0, packet_size=512, fps=30, fps - transmition rate even_packet_size - Some receivers enforce even packets broadcast - whether to broadcast in local sub + artsync - if we want to synchronize buffer Returns: None @@ -41,6 +42,7 @@ def __init__(self, target_ip='127.0.0.1', universe=0, packet_size=512, fps=30, self.physical = 0 self.universe = universe self.subnet = 0 + self.if_sync = artsync self.net = 0 self.packet_size = put_in_range(packet_size, 2, 512, even_packet_size) self.packet_header = bytearray() @@ -61,13 +63,19 @@ def __init__(self, target_ip='127.0.0.1', universe=0, packet_size=512, fps=30, self.fps = fps self.__clock = None - self.make_header() + self.make_artdmx_header() + + if self.if_sync: + self.artsync_header = bytearray() + self.make_artsync_header() + def __del__(self): """Graceful shutdown.""" self.stop() self.close() + def __str__(self): """Printable object state.""" state = "===================================\n" @@ -82,7 +90,8 @@ def __str__(self): return state - def make_header(self): + + def make_artdmx_header(self): """Make packet header.""" # 0 - id (7 x bytes + Null) self.packet_header = bytearray() @@ -125,6 +134,34 @@ def make_header(self): self.packet_header.append(msb) self.packet_header.append(lsb) + + def make_artsync_header(self): + """Make ArtSync header""" + self.artsync_header = bytearray() # Initialize as empty bytearray + # ID: Array of 8 characters, the final character is a null termination. + self.artsync_header.extend(bytearray('Art-Net', 'utf8')) + self.artsync_header.append(0x0) + # OpCode: Defines the class of data within this UDP packet. Transmitted low byte first. + self.artsync_header.append(0x00) + self.artsync_header.append(0x52) + # ProtVerHi and ProtVerLo: Art-Net protocol revision number. Current value =14. + # Controllers should ignore communication with nodes using a protocol version lower than =14. + self.artsync_header.append(0x0) + self.artsync_header.append(14) + # Aux1 and Aux2: Should be transmitted as zero. + self.artsync_header.append(0x0) + self.artsync_header.append(0x0) + + + def send_artsync(self): + """Send Artsync""" + self.make_artsync_header() + try: + self.socket_client.sendto(self.artsync_header, (self.target_ip, self.UDP_PORT)) + except socket.error as error: + print(f"ERROR: Socket error with exception: {error}") + + def show(self): """Finally send data.""" packet = bytearray() @@ -132,11 +169,14 @@ def show(self): packet.extend(self.buffer) try: self.socket_client.sendto(packet, (self.target_ip, self.UDP_PORT)) + if self.if_sync: # if we want to send artsync + self.send_artsync() except socket.error as error: print(f"ERROR: Socket error with exception: {error}") finally: self.sequence = (self.sequence + 1) % 256 + def close(self): """Close UDP socket.""" self.socket_client.close() @@ -150,6 +190,7 @@ def start(self): self.__clock.daemon = True self.__clock.start() + def stop(self): """Stops thread clock.""" if self.__clock is not None: @@ -169,7 +210,8 @@ def set_universe(self, universe): self.universe = put_in_range(universe, 0, 255, False) else: self.universe = put_in_range(universe, 0, 15, False) - self.make_header() + self.make_artdmx_header() + def set_subnet(self, sub): """Setter for subnet address (0 - 15). @@ -177,7 +219,8 @@ def set_subnet(self, sub): Set simplify to false to use """ self.subnet = put_in_range(sub, 0, 15, False) - self.make_header() + self.make_artdmx_header() + def set_net(self, net): """Setter for net address (0 - 127). @@ -185,12 +228,13 @@ def set_net(self, net): Set simplify to false to use """ self.net = put_in_range(net, 0, 127, False) - self.make_header() + self.make_artdmx_header() + def set_packet_size(self, packet_size): """Setter for packet size (2 - 512, even only).""" self.packet_size = put_in_range(packet_size, 2, 512, self.make_even) - self.make_header() + self.make_artdmx_header() # SETTERS - DATA # @@ -198,6 +242,7 @@ def clear(self): """Clear DMX buffer.""" self.buffer = bytearray(self.packet_size) + def set(self, value): """Set buffer.""" if len(value) != self.packet_size: @@ -205,6 +250,7 @@ def set(self, value): return self.buffer = value + def set_16bit(self, address, value, high_first=False): """Set single 16bit value in DMX buffer.""" if address > self.packet_size: @@ -223,6 +269,7 @@ def set_16bit(self, address, value, high_first=False): self.buffer[address - 1] = (value) & 0xFF # low self.buffer[address] = (value >> 8) & 0xFF # high + def set_single_value(self, address, value): """Set single value in DMX buffer.""" if address > self.packet_size: @@ -233,6 +280,7 @@ def set_single_value(self, address, value): return self.buffer[address - 1] = put_in_range(value, 0, 255, False) + def set_single_rem(self, address, value): """Set single value while blacking out others.""" if address > self.packet_size: @@ -244,6 +292,7 @@ def set_single_rem(self, address, value): self.clear() self.buffer[address - 1] = put_in_range(value, 0, 255, False) + def set_rgb(self, address, red, green, blue): """Set RGB from start address.""" if address > self.packet_size: @@ -268,6 +317,7 @@ def send(self, packet): self.set(packet) self.show() + def set_simplified(self, simplify): """Builds Header accordingly. @@ -279,21 +329,25 @@ def set_simplified(self, simplify): if simplify == self.is_simplified: return self.is_simplified = simplify - self.make_header() + self.make_artdmx_header() + def see_header(self): """Show header values.""" print(self.packet_header) + def see_buffer(self): """Show buffer values.""" print(self.buffer) + def blackout(self): """Sends 0's all across.""" self.clear() self.show() + def flash_all(self, delay=None): """Sends 255's all across.""" self.set([255] * self.packet_size) @@ -311,7 +365,7 @@ def flash_all(self, delay=None): UNIVERSE_TO_SEND = 15 # see docs PACKET_SIZE = 20 # it is not necessary to send whole universe - a = StupidArtnet(TARGET_IP, UNIVERSE_TO_SEND, PACKET_SIZE) + a = StupidArtnet(TARGET_IP, UNIVERSE_TO_SEND, PACKET_SIZE, artsync=True) a.set_simplified(False) a.set_net(129) a.set_subnet(16)