python-dali 0.10__tar.gz → 0.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python-dali-0.10/python_dali.egg-info → python-dali-0.11}/PKG-INFO +12 -2
- {python-dali-0.10 → python-dali-0.11}/README.rst +11 -1
- {python-dali-0.10 → python-dali-0.11}/dali/device/__init__.py +2 -0
- {python-dali-0.10 → python-dali-0.11}/dali/device/helpers.py +1 -2
- {python-dali-0.10 → python-dali-0.11}/dali/device/sequences.py +50 -0
- python-dali-0.11/dali/driver/atxled.py +154 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/hid.py +61 -6
- {python-dali-0.10 → python-dali-0.11}/dali/driver/serial.py +596 -3
- {python-dali-0.10 → python-dali-0.11}/dali/frame.py +5 -20
- {python-dali-0.10 → python-dali-0.11}/dali/gear/colour.py +31 -0
- {python-dali-0.10 → python-dali-0.11}/dali/gear/general.py +4 -0
- {python-dali-0.10 → python-dali-0.11}/dali/gear/led.py +19 -2
- {python-dali-0.10 → python-dali-0.11}/dali/gear/sequences.py +29 -2
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_device_sequences.py +97 -3
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_dummy.py +3 -2
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_frame.py +0 -27
- python-dali-0.11/dali/version.py +1 -0
- {python-dali-0.10 → python-dali-0.11/python_dali.egg-info}/PKG-INFO +12 -2
- {python-dali-0.10 → python-dali-0.11}/python_dali.egg-info/SOURCES.txt +1 -0
- {python-dali-0.10 → python-dali-0.11}/setup.cfg +1 -1
- python-dali-0.10/dali/version.py +0 -1
- {python-dali-0.10 → python-dali-0.11}/COPYING +0 -0
- {python-dali-0.10 → python-dali-0.11}/COPYING.LESSER +0 -0
- {python-dali-0.10 → python-dali-0.11}/LICENSE +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/__init__.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/address.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/command.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/device/general.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/device/light.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/device/occupancy.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/device/pushbutton.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/__init__.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/base.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/daliserver.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/hasseb.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/trace_logging.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/tridonic.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/driver/unipi.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/exceptions.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/gear/__init__.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/gear/emergency.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/gear/incandescent.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/__init__.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/diagnostics.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/energy.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/info.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/location.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/maintenance.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/memory/oem.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/sequences.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/__init__.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/fakes.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/fakes_serial.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_colour.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_command.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_events.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_exceptions.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_fakes.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_memory.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/dali/tests/test_sequences.py +0 -0
- {python-dali-0.10 → python-dali-0.11}/pyproject.toml +0 -0
- {python-dali-0.10 → python-dali-0.11}/python_dali.egg-info/dependency_links.txt +0 -0
- {python-dali-0.10 → python-dali-0.11}/python_dali.egg-info/requires.txt +0 -0
- {python-dali-0.10 → python-dali-0.11}/python_dali.egg-info/top_level.txt +0 -0
- {python-dali-0.10 → python-dali-0.11}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-dali
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11
|
|
4
4
|
Summary: Interface to DALI lighting systems
|
|
5
5
|
Home-page: https://github.com/sde1000/python-dali
|
|
6
6
|
Author: Stephen Early
|
|
@@ -108,6 +108,8 @@ Library structure
|
|
|
108
108
|
|
|
109
109
|
- ``serial`` - asyncio-based driver for Lunatone LUBA RS232 interfaces
|
|
110
110
|
|
|
111
|
+
- ``atxled`` - Driver for ATX LED SERIAL DALI HAT
|
|
112
|
+
|
|
111
113
|
- ``exceptions`` - DALI related exceptions
|
|
112
114
|
|
|
113
115
|
- ``frame`` - Forward and backward frames; stable
|
|
@@ -150,11 +152,19 @@ Contributors
|
|
|
150
152
|
|
|
151
153
|
- Sean Lanigan, Wallace Building Systems Pty Ltd
|
|
152
154
|
|
|
155
|
+
- Markus Becker
|
|
156
|
+
|
|
157
|
+
- Jan Kundrát
|
|
158
|
+
|
|
159
|
+
- Olivier Pieters
|
|
160
|
+
|
|
161
|
+
- David Russell
|
|
162
|
+
|
|
153
163
|
|
|
154
164
|
Copyright
|
|
155
165
|
---------
|
|
156
166
|
|
|
157
|
-
python-dali is Copyright (C) 2013–
|
|
167
|
+
python-dali is Copyright (C) 2013–2024 Stephen Early <steve@assorted.org.uk>
|
|
158
168
|
and other contributors.
|
|
159
169
|
|
|
160
170
|
It is distributed under the terms of the GNU Lesser General Public
|
|
@@ -85,6 +85,8 @@ Library structure
|
|
|
85
85
|
|
|
86
86
|
- ``serial`` - asyncio-based driver for Lunatone LUBA RS232 interfaces
|
|
87
87
|
|
|
88
|
+
- ``atxled`` - Driver for ATX LED SERIAL DALI HAT
|
|
89
|
+
|
|
88
90
|
- ``exceptions`` - DALI related exceptions
|
|
89
91
|
|
|
90
92
|
- ``frame`` - Forward and backward frames; stable
|
|
@@ -127,11 +129,19 @@ Contributors
|
|
|
127
129
|
|
|
128
130
|
- Sean Lanigan, Wallace Building Systems Pty Ltd
|
|
129
131
|
|
|
132
|
+
- Markus Becker
|
|
133
|
+
|
|
134
|
+
- Jan Kundrát
|
|
135
|
+
|
|
136
|
+
- Olivier Pieters
|
|
137
|
+
|
|
138
|
+
- David Russell
|
|
139
|
+
|
|
130
140
|
|
|
131
141
|
Copyright
|
|
132
142
|
---------
|
|
133
143
|
|
|
134
|
-
python-dali is Copyright (C) 2013–
|
|
144
|
+
python-dali is Copyright (C) 2013–2024 Stephen Early <steve@assorted.org.uk>
|
|
135
145
|
and other contributors.
|
|
136
146
|
|
|
137
147
|
It is distributed under the terms of the GNU Lesser General Public
|
|
@@ -127,8 +127,7 @@ class DeviceInstanceTypeMapper:
|
|
|
127
127
|
if isinstance(rsp, QueryDeviceStatusResponse):
|
|
128
128
|
# Make sure the status is OK
|
|
129
129
|
if (
|
|
130
|
-
rsp.
|
|
131
|
-
or rsp.short_address_is_mask
|
|
130
|
+
rsp.short_address_is_mask
|
|
132
131
|
or rsp.reset_state
|
|
133
132
|
):
|
|
134
133
|
continue
|
|
@@ -14,6 +14,9 @@ from dali.device.general import (
|
|
|
14
14
|
DTR2,
|
|
15
15
|
EventScheme,
|
|
16
16
|
InstanceEventFilter,
|
|
17
|
+
QueryResolution,
|
|
18
|
+
QueryInputValue,
|
|
19
|
+
QueryInputValueLatch,
|
|
17
20
|
QueryEventFilterH,
|
|
18
21
|
QueryEventFilterL,
|
|
19
22
|
QueryEventFilterM,
|
|
@@ -23,6 +26,7 @@ from dali.device.general import (
|
|
|
23
26
|
SetEventScheme,
|
|
24
27
|
)
|
|
25
28
|
from dali.device.helpers import check_bad_rsp
|
|
29
|
+
from dali.exceptions import DALISequenceError
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def SetEventSchemes(
|
|
@@ -225,3 +229,49 @@ def QueryEventFilters(
|
|
|
225
229
|
hi = rsp.value
|
|
226
230
|
|
|
227
231
|
return filter_type(int.from_bytes((lo, md, hi), "little"))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def query_input_value(
|
|
235
|
+
device: DeviceShort,
|
|
236
|
+
instance: InstanceNumber,
|
|
237
|
+
resolution: Optional[int] = None
|
|
238
|
+
) -> Generator[Command, Optional[Response], Optional[int]]:
|
|
239
|
+
"""
|
|
240
|
+
A generator sequence to retrieve full sensor value from a part-103 control device.
|
|
241
|
+
Use with an appropriate DALI driver instance, through the `run_sequence()`
|
|
242
|
+
method.
|
|
243
|
+
|
|
244
|
+
:param device: A DeviceShort address to target
|
|
245
|
+
:param instance: An InstanceNumber to target
|
|
246
|
+
:param resolution: Number of valid bits that the device provides
|
|
247
|
+
"""
|
|
248
|
+
# Although the proper types are expected, ints are common enough for
|
|
249
|
+
# addresses and their meaning is unambiguous in this context
|
|
250
|
+
if isinstance(device, int):
|
|
251
|
+
device = DeviceShort(device)
|
|
252
|
+
if isinstance(instance, int):
|
|
253
|
+
instance = InstanceNumber(instance)
|
|
254
|
+
|
|
255
|
+
if resolution is None:
|
|
256
|
+
resolution = yield QueryResolution(device, instance)
|
|
257
|
+
if check_bad_rsp(resolution):
|
|
258
|
+
raise DALISequenceError("query_input_value: QueryResolution failed")
|
|
259
|
+
resolution = resolution.value
|
|
260
|
+
|
|
261
|
+
value = yield QueryInputValue(device, instance)
|
|
262
|
+
if check_bad_rsp(value):
|
|
263
|
+
raise DALISequenceError("query_input_value: QueryInputValue failed")
|
|
264
|
+
value = value.value
|
|
265
|
+
while resolution > 8:
|
|
266
|
+
resolution -= 8
|
|
267
|
+
value <<= 8
|
|
268
|
+
chunk = yield QueryInputValueLatch(device, instance)
|
|
269
|
+
if check_bad_rsp(chunk):
|
|
270
|
+
raise DALISequenceError("query_input_value: QueryInputValueLatch failed")
|
|
271
|
+
value += chunk.value
|
|
272
|
+
|
|
273
|
+
if resolution > 0:
|
|
274
|
+
# Strip the repeated trailing bytes as per IEC 62386-103:2014, part 9.7.2
|
|
275
|
+
value >>= 8 - resolution
|
|
276
|
+
|
|
277
|
+
return value
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from dali.command import Command
|
|
2
|
+
from dali.driver.base import SyncDALIDriver, DALIDriver
|
|
3
|
+
from dali.frame import BackwardFrame
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import serial
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
DALI_PACKET_SIZE = {"j": 8, "h": 16, "l": 24, "m": 25}
|
|
12
|
+
DALI_PACKET_PREFIX = {v: k for k, v in DALI_PACKET_SIZE.items()}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DaliHatSerialDriver(DALIDriver):
|
|
16
|
+
"""Driver for communicating with DALI devices over a serial connection."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, port="/dev/ttyS0", LOG=None):
|
|
19
|
+
"""Initialize the serial connection to the DALI interface."""
|
|
20
|
+
self.port = port
|
|
21
|
+
self.lock = threading.RLock()
|
|
22
|
+
self.buffer = []
|
|
23
|
+
if not LOG:
|
|
24
|
+
self.LOG = logging.getLogger("AtxLedDaliDriver")
|
|
25
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
26
|
+
self.LOG.addHandler(handler)
|
|
27
|
+
else:
|
|
28
|
+
self.LOG = LOG
|
|
29
|
+
try:
|
|
30
|
+
self.conn = serial.Serial(
|
|
31
|
+
port=self.port,
|
|
32
|
+
baudrate=19200,
|
|
33
|
+
parity=serial.PARITY_NONE,
|
|
34
|
+
stopbits=serial.STOPBITS_ONE,
|
|
35
|
+
bytesize=serial.EIGHTBITS,
|
|
36
|
+
timeout=0.2,
|
|
37
|
+
)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self.LOG.exception("Could not open serial connection: %s", e)
|
|
40
|
+
self.conn = None
|
|
41
|
+
|
|
42
|
+
def read_line(self):
|
|
43
|
+
"""Read the next line from the buffer, refilling the buffer if necessary."""
|
|
44
|
+
with self.lock:
|
|
45
|
+
while not self.buffer:
|
|
46
|
+
line = self.conn.read_until(b"\n").decode("ascii")
|
|
47
|
+
if not line:
|
|
48
|
+
return ""
|
|
49
|
+
self.buffer.append(line)
|
|
50
|
+
return self.buffer.pop(0)
|
|
51
|
+
|
|
52
|
+
def construct(self, command):
|
|
53
|
+
"""Construct a DALI command to be sent over the serial connection."""
|
|
54
|
+
assert isinstance(command, Command)
|
|
55
|
+
f = command.frame
|
|
56
|
+
packet_size = len(f)
|
|
57
|
+
prefix = DALI_PACKET_PREFIX[packet_size]
|
|
58
|
+
if command.sendtwice and packet_size == 16:
|
|
59
|
+
prefix = "t"
|
|
60
|
+
data = "".join(["{:02X}".format(byte) for byte in f.pack])
|
|
61
|
+
command_str = (f"{prefix}{data}\n").encode("ascii")
|
|
62
|
+
return command_str
|
|
63
|
+
|
|
64
|
+
def extract(self, data):
|
|
65
|
+
"""Parse the response from the serial device and return the corresponding frame."""
|
|
66
|
+
if data.startswith("J"):
|
|
67
|
+
try:
|
|
68
|
+
data = int(data[1:], 16)
|
|
69
|
+
return BackwardFrame(data)
|
|
70
|
+
except ValueError as e:
|
|
71
|
+
self.LOG.error(f"Failed to parse response '{data}': {e}")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
"""Close the serial connection."""
|
|
76
|
+
if self.conn:
|
|
77
|
+
self.conn.close()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class SyncDaliHatDriver(DaliHatSerialDriver, SyncDALIDriver):
|
|
81
|
+
"""Synchronous DALI driver."""
|
|
82
|
+
|
|
83
|
+
def send(self, command: Command):
|
|
84
|
+
"""Send a command to the DALI interface and wait for a response."""
|
|
85
|
+
with self.lock:
|
|
86
|
+
lines = []
|
|
87
|
+
last_resp = None
|
|
88
|
+
send_twice = command.sendtwice
|
|
89
|
+
cmd = self.construct(command)
|
|
90
|
+
self.LOG.debug("command string sent: %r", cmd)
|
|
91
|
+
self.conn.write(cmd)
|
|
92
|
+
REPS = 5
|
|
93
|
+
i = 0
|
|
94
|
+
already_resent = False
|
|
95
|
+
resent_times = 0
|
|
96
|
+
resp = None
|
|
97
|
+
while i < REPS:
|
|
98
|
+
i += 1
|
|
99
|
+
resp = self.read_line()
|
|
100
|
+
self.LOG.debug("raw response received: %r", resp)
|
|
101
|
+
resend = False
|
|
102
|
+
if cmd[:3] not in ["hB1", "hB3", "hB5"]:
|
|
103
|
+
if resp and resp[0] in {"N", "J"}:
|
|
104
|
+
if send_twice:
|
|
105
|
+
if last_resp:
|
|
106
|
+
if last_resp == resp:
|
|
107
|
+
resp = self.extract(resp)
|
|
108
|
+
break
|
|
109
|
+
resend = True
|
|
110
|
+
last_resp = None
|
|
111
|
+
else:
|
|
112
|
+
last_resp = resp
|
|
113
|
+
else:
|
|
114
|
+
resp = self.extract(resp)
|
|
115
|
+
break
|
|
116
|
+
elif resp and resp[0] in {"X", "Z", ""}:
|
|
117
|
+
time.sleep(0.1)
|
|
118
|
+
collision_bytes = None
|
|
119
|
+
while collision_bytes != "":
|
|
120
|
+
collision_bytes = self.read_line()
|
|
121
|
+
if resp[0] == "X":
|
|
122
|
+
break
|
|
123
|
+
self.LOG.info(
|
|
124
|
+
"got conflict (%s) sending %r, sending again", resp, cmd
|
|
125
|
+
)
|
|
126
|
+
last_resp = None
|
|
127
|
+
resend = True
|
|
128
|
+
elif resp:
|
|
129
|
+
lines.append(resp)
|
|
130
|
+
|
|
131
|
+
resp = None
|
|
132
|
+
if resend and not already_resent:
|
|
133
|
+
self.conn.write((cmd).encode("ascii"))
|
|
134
|
+
REPS += 1 + send_twice
|
|
135
|
+
already_resent = True
|
|
136
|
+
else:
|
|
137
|
+
if resp and resp[0] == "N":
|
|
138
|
+
resp = self.extract(resp)
|
|
139
|
+
break
|
|
140
|
+
elif resp and resp[0] in {"X", "Z", ""}:
|
|
141
|
+
time.sleep(0.1)
|
|
142
|
+
collision_bytes = None
|
|
143
|
+
while collision_bytes != "":
|
|
144
|
+
collision_bytes = self.read_line()
|
|
145
|
+
elif resp:
|
|
146
|
+
last_resp = None
|
|
147
|
+
resend = True
|
|
148
|
+
if resend and resent_times < 5:
|
|
149
|
+
self.conn.write(cmd.encode("ascii"))
|
|
150
|
+
REPS += 1 + send_twice
|
|
151
|
+
resent_times += 1
|
|
152
|
+
if command.is_query:
|
|
153
|
+
return command.response(resp)
|
|
154
|
+
return resp
|
|
@@ -123,18 +123,20 @@ class hid:
|
|
|
123
123
|
path = glob.glob(self._path)
|
|
124
124
|
else:
|
|
125
125
|
path = [self._path]
|
|
126
|
+
ex = None
|
|
126
127
|
if path:
|
|
127
128
|
try:
|
|
128
129
|
if self._glob:
|
|
129
130
|
self._log.debug("trying concrete path %s", path[0])
|
|
130
131
|
self._f = os.open(path[0], os.O_RDWR | os.O_NONBLOCK)
|
|
131
|
-
except:
|
|
132
|
+
except Exception as e:
|
|
132
133
|
self._f = None
|
|
134
|
+
ex = e
|
|
133
135
|
else:
|
|
134
136
|
self._log.debug("path %s not found", self._path)
|
|
135
137
|
if not self._f:
|
|
136
138
|
# It didn't work. Schedule a reconnection attempt if we can.
|
|
137
|
-
self._log.debug("hid failed to open %s - waiting to try again", self._path)
|
|
139
|
+
self._log.debug("hid failed to open %s (%s) - waiting to try again", self._path, ex)
|
|
138
140
|
self._reconnect_task = asyncio.create_task(self._reconnect())
|
|
139
141
|
return False
|
|
140
142
|
self._reconnect_count = 0
|
|
@@ -208,6 +210,29 @@ class hid:
|
|
|
208
210
|
if not in_transaction:
|
|
209
211
|
self.transaction_lock.release()
|
|
210
212
|
|
|
213
|
+
async def power_supply(self, supply_on, in_transaction=False, exceptions=None):
|
|
214
|
+
"""
|
|
215
|
+
TODO
|
|
216
|
+
"""
|
|
217
|
+
if exceptions is None:
|
|
218
|
+
exceptions = self.exceptions_on_send
|
|
219
|
+
|
|
220
|
+
if not in_transaction:
|
|
221
|
+
await self.transaction_lock.acquire()
|
|
222
|
+
try:
|
|
223
|
+
command_sent = False
|
|
224
|
+
while not command_sent:
|
|
225
|
+
try:
|
|
226
|
+
response = await self._power_supply(supply_on)
|
|
227
|
+
command_sent = True
|
|
228
|
+
except CommunicationError:
|
|
229
|
+
if exceptions:
|
|
230
|
+
raise
|
|
231
|
+
return response
|
|
232
|
+
finally:
|
|
233
|
+
if not in_transaction:
|
|
234
|
+
self.transaction_lock.release()
|
|
235
|
+
|
|
211
236
|
async def run_sequence(self, seq, progress=None):
|
|
212
237
|
"""Run a command sequence as a transaction
|
|
213
238
|
"""
|
|
@@ -277,6 +302,7 @@ class tridonic(hid):
|
|
|
277
302
|
_CMD_SET_IOPINS = 0x20
|
|
278
303
|
_CMD_READ_IOPINS = 0x21
|
|
279
304
|
_CMD_IDENTIFY = 0x22
|
|
305
|
+
_CMD_POWERSUPPLY = 0x40
|
|
280
306
|
|
|
281
307
|
# For _CMD_INIT, send in byte 1:
|
|
282
308
|
_CMD_INIT_READVERSION = 0x00
|
|
@@ -298,6 +324,11 @@ class tridonic(hid):
|
|
|
298
324
|
_SEND_MODE_DALI24 = 6
|
|
299
325
|
_SEND_MODE_HELVAR17 = 8
|
|
300
326
|
|
|
327
|
+
# For _CMD_POWERSUPPLY, send in byte 1;
|
|
328
|
+
_POWER_SUPPLY_OFF = 0x00
|
|
329
|
+
_POWER_SUPPLY_ON = 0x01
|
|
330
|
+
_POWER_SUPPLY_TBD = 0xFF # APPLY?
|
|
331
|
+
|
|
301
332
|
# Responses received from the interface
|
|
302
333
|
# Decodes to mode, response type, frame, interval, seq
|
|
303
334
|
_resptmpl = struct.Struct(">BB4sHB55x")
|
|
@@ -358,6 +389,31 @@ class tridonic(hid):
|
|
|
358
389
|
os.write(self._f, self._cmd(
|
|
359
390
|
tridonic._CMD_INIT, tridonic._CMD_INIT_READVERSION))
|
|
360
391
|
|
|
392
|
+
async def _power_supply(self, supply_on):
|
|
393
|
+
await self.connected.wait()
|
|
394
|
+
async with self._command_semaphore:
|
|
395
|
+
data = self._cmd(self._CMD_POWERSUPPLY,
|
|
396
|
+
self._POWER_SUPPLY_ON if supply_on else self._POWER_SUPPLY_OFF)
|
|
397
|
+
|
|
398
|
+
try:
|
|
399
|
+
os.write(self._f, data)
|
|
400
|
+
except OSError:
|
|
401
|
+
# The device has failed. Disconnect, schedule a
|
|
402
|
+
# reconnection, and report this command as failed.
|
|
403
|
+
self._log.debug("fail on transmit, disconnecting")
|
|
404
|
+
self.disconnect(reconnect=True)
|
|
405
|
+
raise CommunicationError
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def _command_mode(frame):
|
|
409
|
+
if len(frame) == 8:
|
|
410
|
+
return tridonic._SEND_MODE_DALI8
|
|
411
|
+
if len(frame) == 16:
|
|
412
|
+
return tridonic._SEND_MODE_DALI16
|
|
413
|
+
if len(frame) == 24:
|
|
414
|
+
return tridonic._SEND_MODE_DALI24
|
|
415
|
+
raise UnsupportedFrameTypeError
|
|
416
|
+
|
|
361
417
|
async def _send_raw(self, command):
|
|
362
418
|
frame = command.frame
|
|
363
419
|
if len(frame) not in (16, 24):
|
|
@@ -376,8 +432,7 @@ class tridonic(hid):
|
|
|
376
432
|
data = self._cmd(
|
|
377
433
|
self._CMD_SEND, seq,
|
|
378
434
|
ctrl=self._SEND_CTRL_SENDTWICE if command.sendtwice else 0,
|
|
379
|
-
mode=self.
|
|
380
|
-
else self._SEND_MODE_DALI24,
|
|
435
|
+
mode=self._command_mode(frame),
|
|
381
436
|
frame=frame.pack_len(4))
|
|
382
437
|
try:
|
|
383
438
|
os.write(self._f, data)
|
|
@@ -473,7 +528,7 @@ class tridonic(hid):
|
|
|
473
528
|
frame = dali.frame.BackwardFrame(raw_frame)
|
|
474
529
|
elif rtype == self._RESPONSE_NO_FRAME:
|
|
475
530
|
frame = "no"
|
|
476
|
-
elif rtype == self.
|
|
531
|
+
elif rtype == self._RESPONSE_INFO \
|
|
477
532
|
and message[5] == self._BUS_STATUS_FRAMING_ERROR:
|
|
478
533
|
frame = dali.frame.BackwardFrameError(255)
|
|
479
534
|
else:
|
|
@@ -502,7 +557,7 @@ class tridonic(hid):
|
|
|
502
557
|
current_command = None
|
|
503
558
|
continue
|
|
504
559
|
else:
|
|
505
|
-
self._log.debug("Failed config command (second frame didn't match): %s",
|
|
560
|
+
self._log.debug("Failed config command (second frame didn't match): %s", current_command)
|
|
506
561
|
self.bus_traffic._invoke(current_command, None, True)
|
|
507
562
|
current_command = None
|
|
508
563
|
# Fall through to continue processing frame
|