docs(sacn): Sender fuer die Abnahme, README und Bruno-Anfrage
This commit is contained in:
@@ -65,12 +65,39 @@ Verfügbare Befehle in der Konsole: `help`.
|
||||
gespeicherten Zugangsdaten). Im Fallback-AP (Router war > 5 min weg, Lampe hat aber bereits
|
||||
Zugangsdaten) ist der Token-Header wie bei jedem anderen Endpunkt erforderlich.
|
||||
Nach Erfolg schließt der AP nach 60 s. Ohne Verbindung für 5 min öffnet die Lampe den AP zusätzlich (Fallback).
|
||||
- REST auf Port 3333, Präfix `/api/v1`: `GET|POST device/state`, `GET system/info`, `GET|PUT system/settings` und `PUT system/command` (beide mit Header `Authorization: <token>`, Default-Token `aabbccddeeff0011223344`).
|
||||
- REST auf Port 3333, Präfix `/api/v1`: `GET|POST device/state`, `GET system/info`, `GET|PUT system/settings` (kennt u.a. den Typ `sacn`, siehe unten) und `PUT system/command` (beide mit Header `Authorization: <token>`, Default-Token `aabbccddeeff0011223344`).
|
||||
- Beispiel: `curl -X POST http://ninlight-681f.local:3333/api/v1/device/state -d '{"on":true,"brightness":40,"color_temp":2700}'`
|
||||
- Suchen: `dns-sd -B _ninlight._tcp` (macOS), Details `dns-sd -L ninlight-681f _ninlight._tcp`.
|
||||
- Bruno: `bruno/ninlight.yml` importieren, Environment „EVK (mDNS)" bzw. „Onboarding-AP" wählen.
|
||||
- Konsole: `net` zeigt Modus, SSID, IP, RSSI und den letzten Onboarding-Fehler.
|
||||
|
||||
### sACN / E1.31
|
||||
|
||||
Die Lampe empfängt sACN (E1.31) auf UDP-Port 5568 — per Multicast auf
|
||||
`239.255.<universe_hi>.<universe_lo>` und per Unicast direkt an ihre IP. Nach einem
|
||||
Update ist der Empfang aktiv: Universe 1, Startslot 1.
|
||||
|
||||
Slots ab `start_slot`: **Dimmer, R, G, B, CW, WW** (Boards ohne RGB: **Dimmer, CW, WW**).
|
||||
Wieviele es sind, steht als `slots` in `GET system/settings`.
|
||||
|
||||
Während Pakete fließen, hat sACN Vorrang; die Werte gehen ohne Fade direkt an die
|
||||
Kanäle, und der per API gesetzte Zustand bleibt unangetastet. 2,5 s nach dem letzten
|
||||
Paket endet die Übernahme — je nach `on_timeout` fällt die Lampe auf den per API
|
||||
gesetzten Zustand zurück (`release`, Vorgabe) oder behält die letzten Werte (`hold`).
|
||||
Ein Befehl an `device/state` holt die Kontrolle in beiden Fällen sofort zurück.
|
||||
|
||||
Umstellen:
|
||||
|
||||
curl -X PUT http://<lampe>:3333/api/v1/system/settings \
|
||||
-H 'Authorization: <token>' \
|
||||
-d '{"type":"sacn","universe":12,"start_slot":7,"on_timeout":"hold"}'
|
||||
|
||||
Der Port ist offen und nicht authentifiziert — so sieht das Protokoll es vor. Wer im
|
||||
WLAN sitzt, kann die Lampe damit leuchten lassen, aber nichts einstellen und nichts
|
||||
speichern. Zum Abschalten: `{"type":"sacn","enabled":false}`.
|
||||
|
||||
Zum Ausprobieren liegt ein Sender bei: `tools/sacn_send.py --values 255,255,0,0,0,0 --seconds 5`.
|
||||
|
||||
## Firmware-Update
|
||||
|
||||
Der Build erzeugt pro Gerät zwei Images:
|
||||
|
||||
+23
-1
@@ -328,10 +328,32 @@ items:
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
- info:
|
||||
name: Set sACN
|
||||
type: http
|
||||
seq: 7
|
||||
http:
|
||||
method: PUT
|
||||
url: '{{device_address}}:{{device_port}}/api/v1/system/settings'
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"type": "sacn",
|
||||
"enabled": true,
|
||||
"universe": 1,
|
||||
"start_slot": 1,
|
||||
"on_timeout": "release"
|
||||
}
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
- info:
|
||||
name: Commands
|
||||
type: folder
|
||||
seq: 7
|
||||
seq: 8
|
||||
items:
|
||||
- info:
|
||||
name: Ping
|
||||
|
||||
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