merge: sACN/E1.31-Fernsteuerung
Die Lampe laesst sich per E1.31 ueber UDP fernsteuern, ohne dass der von Home Assistant gesetzte Zustand angetastet wird. Abgenommen an einer E27 RGB, siehe docs/acceptance-sacn.md - dort steht auch, dass Multicast derzeit am WLAN haengenbleibt und nicht an der Firmware. * feat/sacn: docs(sacn): Multicast-Ursache geklaert - die Lampe tritt bei, das WLAN filtert feat(sacn): Ergebnis des Multicast-Beitritts sichtbar machen docs(sacn): Abnahmeprotokoll der Hardwarepruefung docs(light,e131): zwei Stellen benennen, die sonst wie Versehen aussehen fix(sacn): ein nicht gestarteter Empfangs-Task darf die Lampe nicht unbrauchbar machen fix(console): Licht-Befehl gibt den DMX-Override frei - die Konsole ist der Rettungsweg fix(settings): neue Felder erst ab dem alten sizeof - sonst war sACN nach dem Update aus docs(sacn): Sender fuer die Abnahme, README und Bruno-Anfrage feat(sacn): UDP-Empfangs-Task und Verdrahtung in App und API fix(sacn): validate universe/start_slot via get_int(), reject fractional input feat(sacn): Endpunkt zum Einstellen und Statusfeld in system/info fix(sacn): sequence handling on priority change and start_slot clamp feat(sacn): Uebernahme-Schicht mit Timeout, Prioritaet und Sequenz feat(sacn): Override-Zweig in der Licht-Engine feat(sacn): Slotzahl je Board und vier Settings-Felder fix(sacn): validate universe range (1..63999), add boundary tests feat(sacn): E1.31-Pakete pruefen und zerlegen
This commit is contained in:
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Schickt sACN/E1.31-Pakete an eine ninlight-Lampe - fuer die Abnahme von Hand.
|
||||
|
||||
Aufruf:
|
||||
tools/sacn_send.py --universe 1 --start-slot 1 --values 255,255,0,0,0,0 --seconds 5
|
||||
tools/sacn_send.py --host 192.168.2.122 --values 255,0,255,0,0,0 --seconds 5
|
||||
tools/sacn_send.py --values 255,255,255,255,255,255 --seconds 3 --terminate
|
||||
|
||||
Ohne --host geht es per Multicast an 239.255.<hi>.<lo>, mit --host per Unicast direkt an
|
||||
die Lampe. Die Werte sind der Slot-Satz ab --start-slot: Dimmer, R, G, B, CW, WW (Boards
|
||||
ohne RGB: Dimmer, CW, WW).
|
||||
"""
|
||||
import argparse
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
|
||||
E131_PORT = 5568
|
||||
ACN_PID = b"ASC-E1.17\0\0\0"
|
||||
|
||||
|
||||
def packet(universe, sequence, values, start_slot, priority, terminated):
|
||||
slots = bytearray(512)
|
||||
for i, v in enumerate(values):
|
||||
slots[start_slot - 1 + i] = v
|
||||
count = start_slot - 1 + len(values)
|
||||
data = bytes(slots[:count])
|
||||
total = 126 + count
|
||||
|
||||
p = bytearray(total)
|
||||
struct.pack_into(">HH", p, 0, 0x0010, 0x0000)
|
||||
p[4:16] = ACN_PID
|
||||
struct.pack_into(">H", p, 16, 0x7000 | (total - 16))
|
||||
struct.pack_into(">I", p, 18, 0x00000004)
|
||||
p[22:38] = b"ninlight-abnahme" # CID, 16 Byte
|
||||
struct.pack_into(">H", p, 38, 0x7000 | (total - 38))
|
||||
struct.pack_into(">I", p, 40, 0x00000002)
|
||||
p[44:44 + 13] = b"ninlight test"
|
||||
p[108] = priority
|
||||
p[111] = sequence & 0xFF
|
||||
p[112] = 0x40 if terminated else 0x00
|
||||
struct.pack_into(">H", p, 113, universe)
|
||||
struct.pack_into(">H", p, 115, 0x7000 | (total - 115))
|
||||
p[117] = 0x02
|
||||
p[118] = 0xA1
|
||||
struct.pack_into(">HH", p, 119, 0x0000, 0x0001)
|
||||
struct.pack_into(">H", p, 123, count + 1)
|
||||
p[125] = 0x00
|
||||
p[126:] = data
|
||||
return bytes(p)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--universe", type=int, default=1)
|
||||
ap.add_argument("--start-slot", type=int, default=1)
|
||||
ap.add_argument("--values", default="255,255,255,0,0,0")
|
||||
ap.add_argument("--priority", type=int, default=100)
|
||||
ap.add_argument("--seconds", type=float, default=5.0)
|
||||
ap.add_argument("--fps", type=float, default=30.0)
|
||||
ap.add_argument("--host", help="Unicast statt Multicast")
|
||||
ap.add_argument("--terminate", action="store_true",
|
||||
help="zum Schluss ein Paket mit gesetztem stream_terminated-Bit")
|
||||
a = ap.parse_args()
|
||||
|
||||
values = [int(v) for v in a.values.split(",")]
|
||||
dest = a.host or "239.255.%d.%d" % (a.universe >> 8, a.universe & 0xFF)
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 8)
|
||||
|
||||
seq, deadline, step = 0, time.time() + a.seconds, 1.0 / a.fps
|
||||
while time.time() < deadline:
|
||||
s.sendto(packet(a.universe, seq, values, a.start_slot, a.priority, False),
|
||||
(dest, E131_PORT))
|
||||
seq += 1
|
||||
time.sleep(step)
|
||||
if a.terminate:
|
||||
s.sendto(packet(a.universe, seq, values, a.start_slot, a.priority, True),
|
||||
(dest, E131_PORT))
|
||||
print("%d Pakete an %s:%d, Universe %d" % (seq, dest, E131_PORT, a.universe))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user