Skip to content

Commit 6f650e1

Browse files
committed
wip: more RDM
1 parent f918955 commit 6f650e1

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Features
9898
| 15-bit port addresses | :heavy_check_mark: | :heavy_check_mark: |
9999
| >4 ports (bindIndex) on same IP | :heavy_check_mark: | :heavy_check_mark: |
100100
| merge-mode (LTP/HTP) in reciever | - | - |
101-
| RDM commands to enumerate fixtures | - | - |
101+
| RDM commands to enumerate fixtures | :heavy_check_mark: | :heavy_check_mark: |
102102
| Timecode | - | - |
103103
| Multi-universe sync message | - | - |
104104
| local node reconfigure by API | :heavy_check_mark: | :heavy_check_mark: |
@@ -119,9 +119,11 @@ Implemented Messages
119119
| ArtDiagData | - | - |
120120
| ArtTimeCode | - | - |
121121
| ArtCommand | - | - |
122+
| ArtNzs | - | - |
122123
| ArtTrigger | - | - |
123124
| ArtSync | - | - |
124-
| RDM ArtTodRequest / ArtTodData / ArtTodControl / ArtRdm / ArtRdmSub | - | - |
125+
| RDM ArtTodRequest / ArtTodData / ArtTodControl | :heavy_check_mark: | :heavy_check_mark: |
126+
| RDM ArtRdm / ArtRdmSub | - | - |
125127

126128

127129
Art-Net
@@ -136,9 +138,13 @@ This application aims to be fully compatible with Art-Net devices. We have teste
136138

137139
![Art-Net logo](./docs/art-net-master-logo.svg) Art-Net™ Designed by and Copyright Artistic Licence Engineering Ltd.
138140

139-
RDM
141+
DMX Remote Device Management (RDM) Support
140142
---
141143

144+
The Art-Net protocol can carry RDM messages to allow for table-of-device enumeration, and then inspection of the devices by uid.
145+
146+
https://www.rdmprotocol.org/rdm/wp-content/uploads/2011/09/logo2.jpg
147+
142148
See https://www.rdmprotocol.org/rdm/
143149

144150
RDM specs are https://tsp.esta.org/tsp/documents/published_docs.php

aioartnet/aio_artnet.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import struct
77
import time
88
from collections import defaultdict
9-
from typing import Any, Optional, Tuple, Union, cast
9+
from typing import Any, Optional, Sequence, Tuple, Union, cast
1010

1111
from .network import AF_PACKET, getifaddrs
12+
from .rdm import RDMDevice
1213

1314
# Art-Net implementation for Python asyncio
1415
# Any page references to 'spec' refer to
@@ -110,7 +111,7 @@ def __init__(self, portaddress: int):
110111
self._last_publish: float = 0.0
111112
self._last_tod_request: float = 0.0
112113
self.publisherseq: dict[Tuple[DGAddr, int], int] = {}
113-
self.tod_uuids: set[bytes] = set()
114+
self._tod: dict[bytes, RDMDevice] = {}
114115

115116
def split(self) -> Tuple[int, int, int]:
116117
# name net:sub_net:universe
@@ -124,13 +125,16 @@ def __repr__(self) -> str:
124125
net, sub_net, universe = self.split()
125126
return f"{net}:{sub_net}:{universe}"
126127

127-
def set_dmx(self, data: bytes):
128+
def set_dmx(self, data: bytes) -> None:
128129
assert len(data) == DMX_UNIVERSE_SIZE
129130
self.last_data[:] = data[:]
130131

131132
def get_dmx(self) -> bytes:
132133
return self.last_data
133134

135+
def get_rdm_uuids(self) -> Sequence[bytes]:
136+
return list(self._tod.keys())
137+
134138

135139
class ArtNetPort:
136140
def __init__(
@@ -140,6 +144,7 @@ def __init__(
140144
media: int,
141145
portaddr: int,
142146
universe: ArtNetUniverse,
147+
flags: int,
143148
):
144149
self.node = node
145150
self.is_input = is_input
@@ -265,7 +270,9 @@ def on_art_poll_reply(self, addr: DGAddr, data: bytes) -> None:
265270

266271
# iterate through the ports and create ports and universes
267272
portList = []
268-
for _type, _in, _out, _swin, _swout in zip(ptype, ins, outs, swin, swout):
273+
for _type, _in, _out, _swin, _swout, _goodout in zip(
274+
ptype, ins, outs, swin, swout, goodout
275+
):
269276
in_port_addr = (
270277
((netsw & 0x7F) << 8) + ((subsw & 0x0F) << 4) + (_swin & 0x0F)
271278
)
@@ -275,11 +282,13 @@ def on_art_poll_reply(self, addr: DGAddr, data: bytes) -> None:
275282
if _type & 0b10000000:
276283
outu = self.client._get_create_universe(out_port_addr)
277284
portList.append(
278-
ArtNetPort(nn, False, _type & 0x1F, out_port_addr, outu)
285+
ArtNetPort(nn, False, _type & 0x1F, out_port_addr, outu, _goodout)
279286
)
280287
if _type & 0b01000000:
281288
inu = self.client._get_create_universe(in_port_addr)
282-
portList.append(ArtNetPort(nn, True, _type & 0x1F, in_port_addr, inu))
289+
portList.append(
290+
ArtNetPort(nn, True, _type & 0x1F, in_port_addr, inu, 0)
291+
)
283292

284293
# track which 'pages' of port bindings we have seen
285294
old_ports = nn._portBinds[bindindex]
@@ -375,7 +384,7 @@ def on_art_tod_data(self, addr: DGAddr, data: bytes) -> None:
375384
# I think tot_uid and block_count are *paging* related if more tod UIDs than can fit in a packet
376385
for i in range(uid_count):
377386
uid = data[18 + i * 6 : 18 + (i + 1) * 6]
378-
u.tod_uuids.add(uid)
387+
u._tod[uid] = RDMDevice(uid)
379388
logger.info(
380389
f"Received Art-Net TOD entry: universe {portaddress} uid={uid.hex()} from {addr}"
381390
)
@@ -515,7 +524,7 @@ def _send_art_dmx_subscriber(
515524
if self.transport:
516525
self.transport.sendto(message, addr=(node.ip, node.udpport))
517526

518-
def _send_art_tod_request(self, universe: ArtNetUniverse):
527+
def _send_art_tod_request(self, universe: ArtNetUniverse) -> None:
519528
logger.debug(f"sending art tod request for {universe}")
520529
universe._last_tod_request = time.time()
521530

@@ -685,7 +694,12 @@ def set_port_config(
685694

686695
if is_input or is_output:
687696
port = ArtNetPort(
688-
node=self, is_input=is_input, media=0, portaddr=port_addr, universe=u
697+
node=self,
698+
is_input=is_input,
699+
media=0,
700+
portaddr=port_addr,
701+
universe=u,
702+
flags=0,
689703
)
690704
self.ports.append(port)
691705
logger.info(f"configured own port {port}")

aioartnet/rdm.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from dataclasses import dataclass
2+
from typing import Sequence
3+
4+
# Can be subclassed to mock responses to more complex RDM queries. Built in has:
5+
# RDM uuid
6+
# DMX base address
7+
# DMX profile name
8+
# DMX channel count
9+
10+
11+
@dataclass
12+
class RDMDevice:
13+
def __init__(self, uuid: bytes):
14+
self.uuid = uuid
15+
self.dmx_base_address = 0
16+
self.dmx_profile_name = ""
17+
self.dmx_profile_width = 1
18+
19+
20+
# the RDM interrogator is installed into the ArtNetClient, since it might need to be configurable
21+
# by default, we get the device uuids and ignore them. We also have a standard one that will fetch
22+
# the base address, profile name and channel count of any discovered fixtures, and dispatch events
23+
# for them.
24+
class RDMInterrogator:
25+
def on_uuids(self, uuids: Sequence[bytes]) -> None:
26+
self.uuids = uuids
27+
28+
def poll(self) -> None:
29+
# do nothing
30+
pass
31+
32+
def on_rdm_response(self, data: bytes) -> None:
33+
pass
34+
35+
36+
class RDMResponder:
37+
def __init__(self) -> None:
38+
self.devices: list[RDMDevice] = []
39+
40+
def get_tod_uuids(self) -> Sequence[RDMDevice]:
41+
return self.devices
42+
43+
def answer_rdm(self, data: bytes) -> bytes:
44+
return bytes()

0 commit comments

Comments
 (0)