import math import time import serial def clamp(x, ab): (a, b) = ab return max(a, min(b, x)) def rescale(x, from_limits, to_limits): (a, b) = from_limits x_0_1 = (x - a) / (b - a) (c, d) = to_limits return c + (d - c) * x_0_1 class MovingHead: def __init__(self, start_addr): self.start_addr = start_addr self.pan = 0 # -3pi/2 to 3pi/2 self.tilt = 0 # -pi/2 to pi/2 self.speed = 0 self.dimmer = 0 # 0 to 1 self.rgbw = (0, 0, 0, 0) def __str__(self): return ( f"MovingHead({self.start_addr}): pan={self.pan!r}, " f"tilt={self.tilt!r}, speed={self.speed!r}, " f"dimmer={self.dimmer!r}, rgbw={self.rgbw!r}" ) def render(self, dst): pan = rescale(self.pan, (-1.5 * math.pi, 1.5 * math.pi), (255, 0)) pan = clamp(int(pan), (0, 255)) pan_fine = 0 tilt = rescale(self.tilt, (-0.5 * math.pi, 0.5 * math.pi), (0, 255)) tilt = clamp(int(tilt), (0, 255)) tilt_fine = 0 dimmer = clamp(7 + int(127 * self.dimmer), (7, 134)) (r, g, b, w) = self.rgbw channels = [ pan, pan_fine, tilt, tilt_fine, self.speed, dimmer, r, g, b, w, 0, # color mode 0, # auto jump speed 0, # control mode 0, # reset ] offset = self.start_addr - 1 dst[offset : offset + len(channels)] = channels if __name__ == "__main__": lighting = MovingHead(43) lighting.tilt = -0.5 * math.pi lighting.rgbw = (0, 0, 0, 0xFF) lighting.dimmer = 1 head = MovingHead(1) head.rgbw = (0x00, 0x00, 0xFF, 0) head.tilt = -0.5 * math.pi head.dimmer = 1 dmx_data = bytearray(512) with serial.Serial("/dev/ttyUSB0", 500_000) as ser: def sync(): # wait for sync while True: b = ser.readline() if b.strip() == b"Sync.": return print("syncing") sync() t0 = time.time() left = -0.5 * math.pi right = 0.5 * math.pi while True: now = time.time() - t0 if int(now) % 10 < 5: head.tilt = left head.rgbw = (0xFF, 0x00, 0x00, 0) else: head.tilt = right head.rgbw = (0x00, 0xFF, 0x00, 0) head.render(dmx_data) lighting.render(dmx_data) ser.write(dmx_data) ser.flush() response = ser.readline() if response.strip() != b"Ack.": print(f"received bad response: {response!r}") sync()