Skip to content

Commit 22ed8e4

Browse files
committed
wip: art-net RDM table-of-devices support
1 parent c9542ff commit 22ed8e4

File tree

3 files changed

+88
-3
lines changed

3 files changed

+88
-3
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,5 +134,12 @@ This application aims to be fully compatible with Art-Net devices. We have teste
134134
* iOS [DMX Monitor app](https://apps.apple.com/us/app/dmx-monitor/id1544911427)
135135
* Many other iOS apps
136136

137-
138137
![Art-Net logo](./docs/art-net-master-logo.svg) Art-Net™ Designed by and Copyright Artistic Licence Engineering Ltd.
138+
139+
RDM
140+
---
141+
142+
See https://www.rdmprotocol.org/rdm/
143+
144+
RDM specs are https://tsp.esta.org/tsp/documents/published_docs.php
145+

aioartnet/aio_artnet.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ def __init__(self, portaddress: int):
108108
self.last_data = bytearray(DMX_UNIVERSE_SIZE)
109109
self._last_seq = 1
110110
self._last_publish: float = 0.0
111+
self._last_tod_request: float = 0.0
111112
self.publisherseq: dict[Tuple[DGAddr, int], int] = {}
113+
self.tod_uuids: set[bytes] = set()
112114

113115
def split(self) -> Tuple[int, int, int]:
114116
# name net:sub_net:universe
@@ -159,6 +161,8 @@ def __init__(self, client: "ArtNetClient"):
159161
0x2000: self.on_art_poll,
160162
0x2100: self.on_art_poll_reply,
161163
0x5000: self.on_art_dmx,
164+
0x8000: self.on_art_tod_request,
165+
0x8100: self.on_art_tod_data,
162166
}
163167
client.protocol = self
164168
self.node_report_counter = 0
@@ -176,7 +180,7 @@ def datagram_received(self, data: bytes, addr: DGAddr) -> None:
176180
if h:
177181
h(addr, data[10:])
178182
else:
179-
logger.debug(
183+
logger.info(
180184
f"Received unsupported Art-Net: op {hex(opcode)} from {addr}: {data[10:]!r}"
181185
)
182186
else:
@@ -335,6 +339,44 @@ def on_art_dmx(self, addr: DGAddr, data: bytes) -> None:
335339
# Only two sources are allowed to contribute to the values in the universe
336340
u.last_data[0:chlen] = data[8 : 8 + chlen]
337341

342+
def on_art_tod_request(self, addr: DGAddr, data: bytes) -> None:
343+
(ver,) = struct.unpack("<H", data[0:2])
344+
net = data[11]
345+
cmd = data[12]
346+
address_count = data[13]
347+
# net+address => universe address
348+
for universe in [int(x) + net << 8 for x in data[14 : 14 + address_count]]:
349+
logger.debug(
350+
f"Received Art-Net TOD request: ver {ver} cmd {cmd} universe {universe} from {addr}"
351+
)
352+
# TODO: if we are an output, answer this with our cached tod table by sending
353+
# art_tod_data packets
354+
355+
def on_art_tod_data(self, addr: DGAddr, data: bytes) -> None:
356+
ver, rdm_ver, port = struct.unpack("<HBB", data[0:4])
357+
# 6 bytes spare
358+
bind_index, net, response, address, tot_uid, block_count, uid_count = (
359+
struct.unpack("<BBBBHBB", data[10:18])
360+
)
361+
portaddress = net << 8 + address
362+
TOD_FULL = 0
363+
364+
if response != TOD_FULL:
365+
logger.info(
366+
"Got unexpected art_tod_data with response {response} from {addr}"
367+
)
368+
return
369+
370+
u = self.client._get_create_universe(portaddress)
371+
372+
# I think tot_uid and block_count are *paging* related if more tod UIDs than can fit in a packet
373+
for i in range(uid_count):
374+
uid = data[18 + i * 6 : 18 + (i + 1) * 6]
375+
u.tod_uuids.add(uid)
376+
logger.info(
377+
f"Received Art-Net TOD entry: universe {portaddress} uid={uid.hex()} from {addr}"
378+
)
379+
338380
async def art_poll_task(self) -> None:
339381
while True:
340382
await asyncio.sleep(0.1)
@@ -343,6 +385,8 @@ async def art_poll_task(self) -> None:
343385
for u in self.client._publishing:
344386
if t > u._last_publish + 1.0:
345387
self._send_art_dmx(u)
388+
if t > u._last_tod_request + 5.0:
389+
self._send_art_tod_request(u)
346390

347391
if t > self._last_poll + 2.0:
348392
self._send_art_poll()
@@ -468,6 +512,37 @@ def _send_art_dmx_subscriber(
468512
if self.transport:
469513
self.transport.sendto(message, addr=(node.ip, node.udpport))
470514

515+
def _send_art_tod_request(self, universe: ArtNetUniverse):
516+
logger.debug(f"sending art tod request for {universe}")
517+
universe._last_tod_request = time.time()
518+
519+
subuni = universe.portaddress & 0xFF
520+
net = universe.portaddress >> 8
521+
message = ARTNET_PREFIX + struct.pack(
522+
"<HBBBBBBBBBBBBBBB",
523+
0x8000,
524+
0,
525+
14,
526+
0,
527+
0,
528+
0,
529+
0,
530+
0,
531+
0,
532+
0,
533+
0,
534+
0,
535+
net,
536+
0, # command TodFull
537+
1,
538+
subuni,
539+
)
540+
541+
if self.transport:
542+
# send direct to node?
543+
# self.transport.sendto(message, addr=(node.ip, node.udpport))
544+
self.transport.sendto(message, addr=(self.client.broadcast_ip, ARTNET_PORT))
545+
471546
def error_received(self, exc: Exception) -> None:
472547
logger.warn("Error received:", exc)
473548

@@ -586,6 +661,7 @@ def set_port_config(
586661
universe: UniverseKey,
587662
is_input: bool = False,
588663
is_output: bool = False,
664+
rdm: bool = False,
589665
) -> ArtNetUniverse:
590666
port_addr = self._parse_universe(universe)
591667

aioartnet/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ async def main(client: ArtNetClient) -> None:
3030
parser.add_argument("-i", "--interface")
3131
parser.add_argument("-n", "--portName")
3232
parser.add_argument("-p", "--publish", action="store_true")
33+
parser.add_argument("-r", "--rdm", action="store_true")
34+
3335
args = parser.parse_args()
3436

3537
level = {False: logging.INFO, True: logging.DEBUG}[args.verbose]
@@ -42,7 +44,7 @@ async def main(client: ArtNetClient) -> None:
4244
kwargs["portName"] = args.portName
4345
client = ArtNetClient(**kwargs)
4446
if args.publish:
45-
u1 = client.set_port_config("0:0:0", is_input=True)
47+
u1 = client.set_port_config("0:0:0", is_input=True, rdm=args.rdm)
4648
u1.set_dmx(bytes(list(range(128)) * 4))
4749
asyncio.run(main(client))
4850
asyncio.get_event_loop().run_forever()

0 commit comments

Comments
 (0)