Skip to content

Commit 5e11c27

Browse files
author
Pablo Barton
committed
Adding support for the DMX USB Pro Mk2
1 parent 6eb908a commit 5e11c27

5 files changed

Lines changed: 156 additions & 22 deletions

File tree

setup.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,36 @@
22

33
from setuptools import setup
44

5-
requires = ['pyserial']
5+
requires = ["pyserial"]
66

77

88
def load_readme():
9-
with open('README.md') as f:
9+
with open("README.md") as f:
1010
readme = f.read()
1111
return readme
1212

1313

14-
setup(name='DMXEnttecPro',
15-
version='0.4',
16-
description='Python control of the Enttec DMX USB Pro',
17-
author='Paul Barton',
18-
author_email='pablo.barton@gmail.com',
19-
license='GPL3',
20-
package_dir={'': 'src'},
21-
packages=['DMXEnttecPro'],
22-
install_requires=requires,
23-
long_description=load_readme(),
24-
long_description_content_type='text/markdown',
25-
classifiers=["Programming Language :: Python :: 3.5",
26-
"Programming Language :: Python :: 3.6",
27-
"Programming Language :: Python :: 3.7",
28-
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
29-
"Operating System :: OS Independent",
30-
"Development Status :: 4 - Beta",
31-
]
32-
)
14+
setup(
15+
name="DMXEnttecPro",
16+
version="0.4",
17+
description="Python control of the Enttec DMX USB Pro, including Mk2",
18+
author="Paul Barton",
19+
author_email="pablo.barton@gmail.com",
20+
license="GPL3",
21+
package_dir={"": "src"},
22+
packages=["DMXEnttecPro"],
23+
install_requires=requires,
24+
long_description=load_readme(),
25+
long_description_content_type="text/markdown",
26+
classifiers=[
27+
"Programming Language :: Python :: 3.5",
28+
"Programming Language :: Python :: 3.6",
29+
"Programming Language :: Python :: 3.7",
30+
"Programming Language :: Python :: 3.8",
31+
"Programming Language :: Python :: 3.9",
32+
"Programming Language :: Python :: 3.10",
33+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
34+
"Operating System :: OS Independent",
35+
"Development Status :: 4 - Beta",
36+
],
37+
)

src/DMXEnttecPro/controller.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
Defines the Controller
55
"""
6+
from typing import Optional
67

78
import serial
89
from functools import wraps
@@ -94,7 +95,7 @@ def set_dmx_parameters(
9495
output_break_time: int = 9,
9596
mab_time: int = 1,
9697
output_rate: int = 40,
97-
user_defined_bytes=None,
98+
user_defined_bytes: Optional[bytearray] = None,
9899
):
99100
"""
100101
Transmit a message to the Enttec DMX USB Pro to configure some

src/DMXEnttecPro/controllers/__init__.py

Whitespace-only changes.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from enum import Enum
2+
3+
4+
# Message Labels <= 12 correspond to the v1 API. > 12 correspond to v2 API
5+
from typing import Optional
6+
7+
import serial
8+
9+
from DMXEnttecPro.utils import (
10+
least_significant_bit_for_size,
11+
most_significant_bit_for_size,
12+
)
13+
14+
15+
class Mk2MessageLabels(int, Enum):
16+
GET_PORT_WIDGET_PARAMETERS_REQUEST_REPLY_PORT1 = 3
17+
GET_PORT_WIDGET_PARAMETERS_REQUEST_REPLY_PORT2 = 196
18+
SET_PORT_WIDGET_PARAMETERS_REQUEST_PORT1 = 4
19+
SET_PORT_WIDGET_PARAMETERS_REQUEST_PORT2 = 156
20+
RECEIVED_DMX_PACKET_PORT1 = 5
21+
RECEIVED_DMX_PACKET_PORT2 = 210
22+
OUTPUT_ONLY_SEND_DMX_PACKET_REQUEST_PORT1 = 6
23+
OUTPUT_ONLY_SEND_DMX_PACKET_REQUEST_PORT2 = 132
24+
SEND_RDM_PACKET_REQUEST_PORT1 = 7
25+
SEND_RDM_PACKET_REQUEST_PORT2 = 226
26+
RECEIVE_DMX_ON_CHANGE_PORT1 = 8
27+
RECEIVE_DMX_ON_CHANGE_PORT2 = 128
28+
RECEIVED_DMX_CHANGE_OF_STATE_PACKET_PORT1 = 9
29+
RECEIVED_DMX_CHANGE_OF_STATE_PACKET_PORT2 = 22
30+
GET_WIDGET_SERIAL_NUMBER_REQUEST_REPLY = 10
31+
SEND_RDM_DISCOVERY_REQUEST_PORT1 = 11
32+
SEND_RDM_DISCOVERY_REQUEST_PORT2 = 208
33+
RDM_CONTROLLER_RECEIVE_TIMEOUT_PORT1 = 12
34+
RDM_CONTROLLER_RECEIVE_TIMEOUT_PORT2 = 209
35+
SET_API_KEY_REQUEST = 13
36+
QUERY_HARDWARE_VERSION_REQUEST_REPLY = 14
37+
GET_PORT_ASSIGNMENT_REQUEST_REPLY = 220
38+
SET_PORT_ASSIGNMENT_REQUEST = 201
39+
RECEIVED_MIDI = 225
40+
SEND_MIDI_REQUEST = 191
41+
SHOW_QUERY_REQUEST_REPLY = 139
42+
SHOW_BLOCK_ERASE_REQUEST = 129 # Distinguished by subcommand keyword "ERAS"
43+
SHOW_SECTOR_ERASE_REQUEST = 129 # Distinguished by subcommand keyword "ERSE"
44+
SHOW_WRITE_REQUEST = 129 # Distinguished by subcommand keyword "WRIT"
45+
SHOW_READ_REQUEST_REPLY = 203
46+
START_SHOW_REQUEST = 129 # Distinguished by subcommand keyword "STAR"
47+
STOP_SHOW_REQUEST = 129 # Distinguished by subcommand keyword "STOP"
48+
49+
50+
class Mk2Controller(object):
51+
def __init__(
52+
self,
53+
port_string: str,
54+
dmx_size: int = 512,
55+
baudrate: int = 57600,
56+
timeout: int = 1,
57+
auto_submit: bool = False,
58+
):
59+
if not (24 <= dmx_size <= 512):
60+
raise ValueError("Size of DMX channel frame must be between 24 and 512")
61+
self.dmx_size = dmx_size
62+
self.baudrate = baudrate
63+
self.timeout = timeout
64+
self.auto_submit = auto_submit
65+
66+
self._conn = serial.Serial(
67+
port_string, baudrate=self.baudrate, timeout=self.timeout
68+
)
69+
70+
self.channels = bytearray(self.dmx_size)
71+
self._last_submitted_channels = bytearray(self.dmx_size)
72+
self._signal_start = bytearray([0x7E])
73+
self._signal_end = bytearray([0xE7])
74+
75+
def set_port_widget_parameters(
76+
self,
77+
*,
78+
port: int = 1,
79+
break_time: int = 9,
80+
mab_time: int = 1,
81+
rate: int = 40,
82+
user_defined_bytes: Optional[bytearray] = None,
83+
):
84+
if user_defined_bytes is None:
85+
user_defined_bytes = bytearray()
86+
else:
87+
if len(user_defined_bytes) > 512:
88+
raise ValueError(
89+
"Length of user_defined_bytes must not be greater than 512"
90+
)
91+
if port not in (1, 2):
92+
raise ValueError("port must be 1 or 2")
93+
if not (9 <= break_time <= 127):
94+
raise ValueError("output_break_time must be between 9 and 127")
95+
if not (1 <= mab_time <= 127):
96+
raise ValueError("mab_time must be between 1 and 127")
97+
if not (0 <= rate <= 40):
98+
raise ValueError("output_rate must be between 0 and 40")
99+
label = Mk2MessageLabels.SET_PORT_WIDGET_PARAMETERS_REQUEST_PORT1
100+
if port == 2:
101+
label = Mk2MessageLabels.SET_PORT_WIDGET_PARAMETERS_REQUEST_PORT2
102+
udb_len = len(user_defined_bytes)
103+
msg = (
104+
self._signal_start
105+
+ bytearray(
106+
[
107+
label.value,
108+
least_significant_bit_for_size(udb_len + 5),
109+
most_significant_bit_for_size(udb_len + 5),
110+
least_significant_bit_for_size(udb_len),
111+
most_significant_bit_for_size(udb_len),
112+
break_time,
113+
mab_time,
114+
rate,
115+
]
116+
)
117+
+ user_defined_bytes
118+
+ self._signal_end
119+
)
120+
self._conn.write(msg)

src/DMXEnttecPro/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ def get_port_by_product_id(product_id: int) -> str:
4141
raise ValueError("No COM device found with product id {}".format(product_id))
4242

4343

44+
def least_significant_bit_for_size(size: int) -> int:
45+
return size & 0xFF
46+
47+
48+
def most_significant_bit_for_size(size: int) -> int:
49+
return (size >> 8) & 0xFF
50+
51+
4452
def show_port_details():
4553
"""
4654
Print a listing of all COM ports PySerial can find. Useful for determining

0 commit comments

Comments
 (0)