foobazdmx/dmx.py

183 lines
5.4 KiB
Python
Raw Normal View History

2022-07-20 22:27:40 +02:00
import socket
import struct
2022-07-22 17:51:29 +02:00
import sys
2022-07-20 22:27:40 +02:00
from threading import Thread
from time import sleep
from typing import Union
2022-07-20 22:27:40 +02:00
from animation import Animation, Off, Steady, FadeTo, RotatingRainbow, Chase
2022-07-20 22:55:37 +02:00
def ledlog(value):
return int(pow(float(value) / 255.0, 2) * 255)
2022-07-20 22:55:37 +02:00
2022-07-20 22:27:40 +02:00
class RGB:
2023-05-19 19:43:45 +02:00
def __init__(self, slot, offset=0):
2022-07-20 22:27:40 +02:00
self.slot = slot
self.offset = offset
2023-05-19 19:43:45 +02:00
def rgb(self, dmx, color):
2022-07-20 22:27:40 +02:00
(r, g, b) = color
2023-05-19 19:43:45 +02:00
dmx.set(self.slot + self.offset + 0, ledlog(r))
dmx.set(self.slot + self.offset + 1, ledlog(g))
dmx.set(self.slot + self.offset + 2, ledlog(b))
2022-07-20 22:27:40 +02:00
class Bar252(RGB):
def __init__(self, dmx, slot=1):
super(Bar252, self).__init__(dmx, slot, 2)
2023-05-19 19:43:45 +02:00
def rgb(self, dmx, color):
super().rgb(dmx, color)
dmx.set(self.slot + 0, 81)
dmx.set(self.slot + 1, 0)
2022-07-20 22:27:40 +02:00
class REDSpot18RGB(RGB):
def __init__(self, dmx, slot=1):
super(REDSpot18RGB, self).__init__(dmx, slot, 1)
dmx.set(self.slot + 0, 0)
2023-05-19 19:43:45 +02:00
def rgb(self, dmx, color):
super().rgb(dmx, color)
dmx.set(self.slot + 0, 81)
2022-07-20 22:27:40 +02:00
class StairvilleLedPar56(RGB):
def __init__(self, dmx, slot=1):
super(StairvilleLedPar56, self).__init__(dmx, slot, 0)
dmx.set(self.slot + 3, 0)
dmx.set(self.slot + 4, 0)
dmx.set(self.slot + 5, 0)
dmx.set(self.slot + 6, 255)
2022-07-20 22:27:40 +02:00
2023-05-19 19:43:45 +02:00
class Group:
def __init__(self, rgbs : list[RGB]):
self._rgbs = rgbs
self._color = (0, 0, 0)
self._animation = FadeTo((255, 255, 255))
@property
def animation(self) -> Animation:
return self._animation
@animation.setter
def animation(self, animation: Animation):
self._animation = animation
@property
def color(self) -> list[int]:
return self._color
@color.setter
def color(self, color: list[int]):
self._color = color
2022-07-20 22:27:40 +02:00
class DMX:
def __init__(self, host, port=0x1936, universe=1, maxchan=512):
self._host = host
self._port = port
self._universe = universe
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
self._socket.setblocking(False)
self._data = bytearray(maxchan)
2022-07-20 22:27:40 +02:00
packet = bytearray()
packet.extend(map(ord, "Art-Net"))
packet.append(0x00) # Null terminate Art-Net
2022-07-20 22:27:40 +02:00
packet.extend([0x00, 0x50]) # Opcode ArtDMX 0x5000 (Little endian)
packet.extend([0x00, 0x0e]) # Protocol version 14
self._header = packet
self._sequence = 1
2023-05-19 19:43:45 +02:00
self._groups = []
self._thread = None
self._updating = False
2022-07-20 22:27:40 +02:00
2023-05-19 19:43:45 +02:00
def group(self, group: int, rgbs : list[RGB]):
self._groups[group] = Group(rgbs)
2022-07-20 22:27:40 +02:00
def start(self):
if self._thread and self._thread.is_alive():
2022-07-20 22:55:37 +02:00
return
self._thread = Thread(daemon=True, target=self.background)
self._updating = True
self._thread.start()
2022-07-20 22:27:40 +02:00
def background(self):
while self._updating:
2022-07-20 22:27:40 +02:00
self.update()
# print("updating")
2022-07-20 22:55:37 +02:00
# print(self.data)
# break
sleep(1.0 / 30)
2022-07-20 22:27:40 +02:00
def update(self):
2023-05-19 19:43:45 +02:00
if not self._groups[0]._animation:
return
2023-05-19 19:43:45 +02:00
for g in self._groups:
for i in range(0, len(g._rgbs)):
g._rgbs[i].rgb(self, g._animation.update(i, len(g._rgbs)))
packet = self._header[:]
packet.append(self._sequence) # Sequence,
packet.append(0x00) # Physical
packet.append(self._universe & 0xFF) # Universe LowByte
packet.append(self._universe >> 8 & 0xFF) # Universe HighByte
2022-07-20 22:27:40 +02:00
packet.extend(struct.pack('>h', len(self._data))) # Pack the number of channels Big endian
packet.extend(self._data)
self._socket.sendto(packet, (self._host, self._port))
# print(f"sent {len(packet)} bytes, {threading.get_native_id()}")
self._sequence += 1
if self._sequence > 255:
self._sequence = 1
2022-07-20 22:27:40 +02:00
def set(self, slot, value):
self._data[slot - 1] = value
@property
def animation(self) -> str:
2023-05-19 19:43:45 +02:00
return self._groups[0]._animation.name()
@animation.setter
def animation(self, animation: Union[Animation, str]):
if isinstance(animation, str):
if animation == "off":
animation = Off()
elif animation == "chase":
2023-05-19 19:43:45 +02:00
animation = Chase(self._groups[0]._color)
elif animation == "fade":
2023-05-19 19:43:45 +02:00
animation = FadeTo(self._groups[0]._color)
elif animation == "rainbow":
animation = RotatingRainbow()
elif animation == "steady":
2023-05-19 19:43:45 +02:00
animation = Steady(self._groups[0]._color)
else:
raise ValueError(f"No such animation {animation}")
2023-05-19 19:43:45 +02:00
self._groups[0]._animation = animation
if isinstance(animation, Off):
self._updating = False
if self._thread and self._thread.is_alive():
self._thread.join()
# one frame black
2023-05-19 19:43:45 +02:00
self._groups[0]._animation = Steady((0, 0, 0))
self.update()
2023-05-19 19:43:45 +02:00
self._groups[0]._animation = Off()
else:
self.start()
print(f"Animation: {animation}", file=sys.stderr)
@property
def color(self):
2023-05-19 19:43:45 +02:00
return self._groups[0].color
@color.setter
def color(self, color):
2023-05-19 19:43:45 +02:00
if self._groups[0]._color != color:
self._groups[0].color = color
self.animation = self.animation
2023-05-19 19:43:45 +02:00