python-dali 0.10__tar.gz → 0.12__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.12}/PKG-INFO +30 -14
- {python-dali-0.10 → python_dali-0.12}/README.rst +16 -2
- {python-dali-0.10 → python_dali-0.12}/dali/device/__init__.py +2 -0
- {python-dali-0.10 → python_dali-0.12}/dali/device/helpers.py +1 -2
- {python-dali-0.10 → python_dali-0.12}/dali/device/sequences.py +50 -0
- python_dali-0.12/dali/driver/atxled.py +154 -0
- {python-dali-0.10 → python_dali-0.12}/dali/driver/base.py +0 -114
- {python-dali-0.10 → python_dali-0.12}/dali/driver/hid.py +110 -31
- {python-dali-0.10 → python_dali-0.12}/dali/driver/serial.py +636 -7
- {python-dali-0.10 → python_dali-0.12}/dali/driver/tridonic.py +2 -2
- {python-dali-0.10 → python_dali-0.12}/dali/driver/unipi.py +18 -8
- python_dali-0.12/dali/driver/usb.py +114 -0
- {python-dali-0.10 → python_dali-0.12}/dali/frame.py +5 -20
- {python-dali-0.10 → python_dali-0.12}/dali/gear/__init__.py +1 -0
- {python-dali-0.10 → python_dali-0.12}/dali/gear/colour.py +31 -0
- python_dali-0.12/dali/gear/converter.py +173 -0
- {python-dali-0.10 → python_dali-0.12}/dali/gear/general.py +4 -0
- {python-dali-0.10 → python_dali-0.12}/dali/gear/led.py +19 -2
- {python-dali-0.10 → python_dali-0.12}/dali/gear/sequences.py +30 -2
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_device_sequences.py +97 -3
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_dummy.py +3 -2
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_frame.py +0 -27
- {python-dali-0.10 → python_dali-0.12/python_dali.egg-info}/PKG-INFO +30 -14
- {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/SOURCES.txt +3 -1
- {python-dali-0.10 → python_dali-0.12}/setup.cfg +3 -5
- python-dali-0.10/dali/version.py +0 -1
- {python-dali-0.10 → python_dali-0.12}/COPYING +0 -0
- {python-dali-0.10 → python_dali-0.12}/COPYING.LESSER +0 -0
- {python-dali-0.10 → python_dali-0.12}/LICENSE +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/__init__.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/address.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/command.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/device/general.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/device/light.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/device/occupancy.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/device/pushbutton.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/driver/__init__.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/driver/daliserver.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/driver/hasseb.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/driver/trace_logging.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/exceptions.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/gear/emergency.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/gear/incandescent.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/__init__.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/diagnostics.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/energy.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/info.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/location.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/maintenance.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/memory/oem.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/sequences.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/__init__.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/fakes.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/fakes_serial.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_colour.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_command.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_events.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_exceptions.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_fakes.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_memory.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/dali/tests/test_sequences.py +0 -0
- {python-dali-0.10 → python_dali-0.12}/pyproject.toml +0 -0
- {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/dependency_links.txt +0 -0
- {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/requires.txt +2 -2
- {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/top_level.txt +0 -0
- {python-dali-0.10 → python_dali-0.12}/setup.py +0 -0
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-dali
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12
|
|
4
4
|
Summary: Interface to DALI lighting systems
|
|
5
5
|
Home-page: https://github.com/sde1000/python-dali
|
|
6
6
|
Author: Stephen Early
|
|
7
7
|
Author-email: steve@assorted.org.uk
|
|
8
|
-
License:
|
|
9
|
-
Platform: UNKNOWN
|
|
8
|
+
License: "LGPL-3.0-or-later"
|
|
10
9
|
Classifier: Development Status :: 3 - Alpha
|
|
11
10
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
|
-
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
|
14
11
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.8
|
|
16
13
|
Description-Content-Type: text/x-rst
|
|
17
|
-
Provides-Extra: driver-serial
|
|
18
|
-
Provides-Extra: driver-unipi
|
|
19
|
-
Provides-Extra: test
|
|
20
14
|
License-File: COPYING
|
|
21
15
|
License-File: COPYING.LESSER
|
|
22
16
|
License-File: LICENSE
|
|
17
|
+
Provides-Extra: driver-unipi
|
|
18
|
+
Requires-Dist: pyusb; extra == "driver-unipi"
|
|
19
|
+
Requires-Dist: pymodbus; extra == "driver-unipi"
|
|
20
|
+
Provides-Extra: driver-serial
|
|
21
|
+
Requires-Dist: pyserial-asyncio; extra == "driver-serial"
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest; extra == "test"
|
|
24
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
25
|
+
Requires-Dist: pyserial-asyncio; extra == "test"
|
|
26
|
+
Dynamic: license-file
|
|
23
27
|
|
|
24
28
|
python-dali — lighting control interface
|
|
25
29
|
========================================
|
|
@@ -44,7 +48,9 @@ This library has been written with reference to the following documents:
|
|
|
44
48
|
- IEC 62386-201:2009 (fluorescent lamps)
|
|
45
49
|
- IEC 62386-202:2009 (self-contained emergency lighting)
|
|
46
50
|
- IEC 62386-205:2009 (supply voltage controller for incandescent lamps)
|
|
51
|
+
- IEC 62386-206:2009 (conversion from digital signal into DC voltage)
|
|
47
52
|
- IEC 62386-207:2009 (LED modules)
|
|
53
|
+
- IEC 62386-209:2012 (colour control)
|
|
48
54
|
- IEC 62386-301:2017 (particular requirements for push button input devices)
|
|
49
55
|
- IEC 62386-303:2017 (particular requirements for occupancy sensor input devices)
|
|
50
56
|
|
|
@@ -64,7 +70,7 @@ Interface Alliance`_:
|
|
|
64
70
|
Python versions supported
|
|
65
71
|
-------------------------
|
|
66
72
|
|
|
67
|
-
This library currently requires Python version 3.
|
|
73
|
+
This library currently requires Python version 3.8 or later.
|
|
68
74
|
|
|
69
75
|
Stability
|
|
70
76
|
---------
|
|
@@ -108,6 +114,8 @@ Library structure
|
|
|
108
114
|
|
|
109
115
|
- ``serial`` - asyncio-based driver for Lunatone LUBA RS232 interfaces
|
|
110
116
|
|
|
117
|
+
- ``atxled`` - Driver for ATX LED SERIAL DALI HAT
|
|
118
|
+
|
|
111
119
|
- ``exceptions`` - DALI related exceptions
|
|
112
120
|
|
|
113
121
|
- ``frame`` - Forward and backward frames; stable
|
|
@@ -150,11 +158,21 @@ Contributors
|
|
|
150
158
|
|
|
151
159
|
- Sean Lanigan, Wallace Building Systems Pty Ltd
|
|
152
160
|
|
|
161
|
+
- Markus Becker
|
|
162
|
+
|
|
163
|
+
- Jan Kundrát
|
|
164
|
+
|
|
165
|
+
- Olivier Pieters
|
|
166
|
+
|
|
167
|
+
- David Russell
|
|
168
|
+
|
|
169
|
+
- Nikolay Korotkiy
|
|
170
|
+
|
|
153
171
|
|
|
154
172
|
Copyright
|
|
155
173
|
---------
|
|
156
174
|
|
|
157
|
-
python-dali is Copyright (C) 2013–
|
|
175
|
+
python-dali is Copyright (C) 2013–2026 Stephen Early <steve@assorted.org.uk>
|
|
158
176
|
and other contributors.
|
|
159
177
|
|
|
160
178
|
It is distributed under the terms of the GNU Lesser General Public
|
|
@@ -172,5 +190,3 @@ not, see `this link`_.
|
|
|
172
190
|
|
|
173
191
|
.. _this link: https://www.gnu.org/licenses/
|
|
174
192
|
.. _Digital Illumination Interface Alliance: https://www.dali-alliance.org/dali/data.html
|
|
175
|
-
|
|
176
|
-
|
|
@@ -21,7 +21,9 @@ This library has been written with reference to the following documents:
|
|
|
21
21
|
- IEC 62386-201:2009 (fluorescent lamps)
|
|
22
22
|
- IEC 62386-202:2009 (self-contained emergency lighting)
|
|
23
23
|
- IEC 62386-205:2009 (supply voltage controller for incandescent lamps)
|
|
24
|
+
- IEC 62386-206:2009 (conversion from digital signal into DC voltage)
|
|
24
25
|
- IEC 62386-207:2009 (LED modules)
|
|
26
|
+
- IEC 62386-209:2012 (colour control)
|
|
25
27
|
- IEC 62386-301:2017 (particular requirements for push button input devices)
|
|
26
28
|
- IEC 62386-303:2017 (particular requirements for occupancy sensor input devices)
|
|
27
29
|
|
|
@@ -41,7 +43,7 @@ Interface Alliance`_:
|
|
|
41
43
|
Python versions supported
|
|
42
44
|
-------------------------
|
|
43
45
|
|
|
44
|
-
This library currently requires Python version 3.
|
|
46
|
+
This library currently requires Python version 3.8 or later.
|
|
45
47
|
|
|
46
48
|
Stability
|
|
47
49
|
---------
|
|
@@ -85,6 +87,8 @@ Library structure
|
|
|
85
87
|
|
|
86
88
|
- ``serial`` - asyncio-based driver for Lunatone LUBA RS232 interfaces
|
|
87
89
|
|
|
90
|
+
- ``atxled`` - Driver for ATX LED SERIAL DALI HAT
|
|
91
|
+
|
|
88
92
|
- ``exceptions`` - DALI related exceptions
|
|
89
93
|
|
|
90
94
|
- ``frame`` - Forward and backward frames; stable
|
|
@@ -127,11 +131,21 @@ Contributors
|
|
|
127
131
|
|
|
128
132
|
- Sean Lanigan, Wallace Building Systems Pty Ltd
|
|
129
133
|
|
|
134
|
+
- Markus Becker
|
|
135
|
+
|
|
136
|
+
- Jan Kundrát
|
|
137
|
+
|
|
138
|
+
- Olivier Pieters
|
|
139
|
+
|
|
140
|
+
- David Russell
|
|
141
|
+
|
|
142
|
+
- Nikolay Korotkiy
|
|
143
|
+
|
|
130
144
|
|
|
131
145
|
Copyright
|
|
132
146
|
---------
|
|
133
147
|
|
|
134
|
-
python-dali is Copyright (C) 2013–
|
|
148
|
+
python-dali is Copyright (C) 2013–2026 Stephen Early <steve@assorted.org.uk>
|
|
135
149
|
and other contributors.
|
|
136
150
|
|
|
137
151
|
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
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import threading
|
|
2
|
-
import usb
|
|
3
|
-
|
|
4
|
-
|
|
5
1
|
###############################################################################
|
|
6
2
|
# driver contracts
|
|
7
3
|
###############################################################################
|
|
@@ -124,113 +120,3 @@ class Listener(Backend):
|
|
|
124
120
|
"""
|
|
125
121
|
raise NotImplementedError(
|
|
126
122
|
'Abstract ``Listener`` does not implement ``listen``')
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
###############################################################################
|
|
130
|
-
# USB backends
|
|
131
|
-
###############################################################################
|
|
132
|
-
|
|
133
|
-
class USBBackend(Backend):
|
|
134
|
-
"""Backend implementation for communicating with USB devices.
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
def __init__(self, vendor, product, bus=None,
|
|
138
|
-
port_numbers=None, interface=0):
|
|
139
|
-
self._device = None
|
|
140
|
-
# lookup devices by vendor and product
|
|
141
|
-
devices = [dev for dev in usb.core.find(
|
|
142
|
-
find_all=True,
|
|
143
|
-
idVendor=vendor,
|
|
144
|
-
idProduct=product
|
|
145
|
-
)]
|
|
146
|
-
# use first device if bus or port_numers not defined
|
|
147
|
-
if bus is None or port_numbers is None:
|
|
148
|
-
self._device = devices[0]
|
|
149
|
-
else:
|
|
150
|
-
for dev in devices:
|
|
151
|
-
if dev.bus == bus and dev.port_numbers == port_numbers:
|
|
152
|
-
self._device = dev
|
|
153
|
-
break
|
|
154
|
-
# if queried device not found, raise
|
|
155
|
-
if not self._device:
|
|
156
|
-
raise usb.core.USBError('Device not found')
|
|
157
|
-
# detach kernel driver if necessary
|
|
158
|
-
if self._device.is_kernel_driver_active(interface) is True:
|
|
159
|
-
self._device.detach_kernel_driver(interface)
|
|
160
|
-
# set device configuration
|
|
161
|
-
self._device.set_configuration()
|
|
162
|
-
# claim interface
|
|
163
|
-
usb.util.claim_interface(self._device, interface)
|
|
164
|
-
# get active configuration
|
|
165
|
-
cfg = self._device.get_active_configuration()
|
|
166
|
-
intf = cfg[(0, 0)]
|
|
167
|
-
# get write end point
|
|
168
|
-
def match_ep_out(e):
|
|
169
|
-
return usb.util.endpoint_direction(e.bEndpointAddress) == \
|
|
170
|
-
usb.util.ENDPOINT_OUT
|
|
171
|
-
self._ep_write = usb.util.find_descriptor(
|
|
172
|
-
intf, custom_match=match_ep_out)
|
|
173
|
-
# get read end point
|
|
174
|
-
def match_ep_in(e):
|
|
175
|
-
return usb.util.endpoint_direction(e.bEndpointAddress) == \
|
|
176
|
-
usb.util.ENDPOINT_IN
|
|
177
|
-
self._ep_read = usb.util.find_descriptor(
|
|
178
|
-
intf, custom_match=match_ep_in)
|
|
179
|
-
|
|
180
|
-
def read(self, timeout=None):
|
|
181
|
-
"""Read data from USB device.
|
|
182
|
-
"""
|
|
183
|
-
return self._ep_read.read(self._ep_read.wMaxPacketSize, timeout=timeout)
|
|
184
|
-
|
|
185
|
-
def write(self, data):
|
|
186
|
-
"""Write data to USB device.
|
|
187
|
-
"""
|
|
188
|
-
return self._ep_write.write(data)
|
|
189
|
-
|
|
190
|
-
def close(self):
|
|
191
|
-
"""Close connection to USB device.
|
|
192
|
-
"""
|
|
193
|
-
usb.util.dispose_resources(self._device)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
class USBListener(USBBackend, Listener):
|
|
197
|
-
"""Listener implementation for communicating with USB devices.
|
|
198
|
-
"""
|
|
199
|
-
|
|
200
|
-
def __init__(self, driver, vendor, product, bus=None,
|
|
201
|
-
port_numbers=None, interface=0):
|
|
202
|
-
super(USBListener, self).__init__(
|
|
203
|
-
vendor,
|
|
204
|
-
product,
|
|
205
|
-
bus=bus,
|
|
206
|
-
port_numbers=port_numbers,
|
|
207
|
-
interface=interface
|
|
208
|
-
)
|
|
209
|
-
self.driver = driver
|
|
210
|
-
# flag whether actually disconnecting from device
|
|
211
|
-
self._disconnecting = False
|
|
212
|
-
# event to stop listening
|
|
213
|
-
self._stop_listening = threading.Event()
|
|
214
|
-
# create and start listener thread
|
|
215
|
-
self._listener = threading.Thread(target=self.listen)
|
|
216
|
-
self._listener.start()
|
|
217
|
-
|
|
218
|
-
def listen(self):
|
|
219
|
-
"""Poll data from USB device.
|
|
220
|
-
"""
|
|
221
|
-
while not self._stop_listening.is_set():
|
|
222
|
-
try:
|
|
223
|
-
self.driver.receive(self.read())
|
|
224
|
-
except usb.core.USBError as e:
|
|
225
|
-
# read timeout
|
|
226
|
-
if e.errno == 110:
|
|
227
|
-
continue
|
|
228
|
-
if not self._disconnecting:
|
|
229
|
-
self.close()
|
|
230
|
-
|
|
231
|
-
def close(self):
|
|
232
|
-
"""Close connection to USB device.
|
|
233
|
-
"""
|
|
234
|
-
self._disconnecting = True
|
|
235
|
-
self._stop_listening.set()
|
|
236
|
-
super(USBListener, self).close()
|