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.
Files changed (66) hide show
  1. {python-dali-0.10/python_dali.egg-info → python_dali-0.12}/PKG-INFO +30 -14
  2. {python-dali-0.10 → python_dali-0.12}/README.rst +16 -2
  3. {python-dali-0.10 → python_dali-0.12}/dali/device/__init__.py +2 -0
  4. {python-dali-0.10 → python_dali-0.12}/dali/device/helpers.py +1 -2
  5. {python-dali-0.10 → python_dali-0.12}/dali/device/sequences.py +50 -0
  6. python_dali-0.12/dali/driver/atxled.py +154 -0
  7. {python-dali-0.10 → python_dali-0.12}/dali/driver/base.py +0 -114
  8. {python-dali-0.10 → python_dali-0.12}/dali/driver/hid.py +110 -31
  9. {python-dali-0.10 → python_dali-0.12}/dali/driver/serial.py +636 -7
  10. {python-dali-0.10 → python_dali-0.12}/dali/driver/tridonic.py +2 -2
  11. {python-dali-0.10 → python_dali-0.12}/dali/driver/unipi.py +18 -8
  12. python_dali-0.12/dali/driver/usb.py +114 -0
  13. {python-dali-0.10 → python_dali-0.12}/dali/frame.py +5 -20
  14. {python-dali-0.10 → python_dali-0.12}/dali/gear/__init__.py +1 -0
  15. {python-dali-0.10 → python_dali-0.12}/dali/gear/colour.py +31 -0
  16. python_dali-0.12/dali/gear/converter.py +173 -0
  17. {python-dali-0.10 → python_dali-0.12}/dali/gear/general.py +4 -0
  18. {python-dali-0.10 → python_dali-0.12}/dali/gear/led.py +19 -2
  19. {python-dali-0.10 → python_dali-0.12}/dali/gear/sequences.py +30 -2
  20. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_device_sequences.py +97 -3
  21. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_dummy.py +3 -2
  22. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_frame.py +0 -27
  23. {python-dali-0.10 → python_dali-0.12/python_dali.egg-info}/PKG-INFO +30 -14
  24. {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/SOURCES.txt +3 -1
  25. {python-dali-0.10 → python_dali-0.12}/setup.cfg +3 -5
  26. python-dali-0.10/dali/version.py +0 -1
  27. {python-dali-0.10 → python_dali-0.12}/COPYING +0 -0
  28. {python-dali-0.10 → python_dali-0.12}/COPYING.LESSER +0 -0
  29. {python-dali-0.10 → python_dali-0.12}/LICENSE +0 -0
  30. {python-dali-0.10 → python_dali-0.12}/dali/__init__.py +0 -0
  31. {python-dali-0.10 → python_dali-0.12}/dali/address.py +0 -0
  32. {python-dali-0.10 → python_dali-0.12}/dali/command.py +0 -0
  33. {python-dali-0.10 → python_dali-0.12}/dali/device/general.py +0 -0
  34. {python-dali-0.10 → python_dali-0.12}/dali/device/light.py +0 -0
  35. {python-dali-0.10 → python_dali-0.12}/dali/device/occupancy.py +0 -0
  36. {python-dali-0.10 → python_dali-0.12}/dali/device/pushbutton.py +0 -0
  37. {python-dali-0.10 → python_dali-0.12}/dali/driver/__init__.py +0 -0
  38. {python-dali-0.10 → python_dali-0.12}/dali/driver/daliserver.py +0 -0
  39. {python-dali-0.10 → python_dali-0.12}/dali/driver/hasseb.py +0 -0
  40. {python-dali-0.10 → python_dali-0.12}/dali/driver/trace_logging.py +0 -0
  41. {python-dali-0.10 → python_dali-0.12}/dali/exceptions.py +0 -0
  42. {python-dali-0.10 → python_dali-0.12}/dali/gear/emergency.py +0 -0
  43. {python-dali-0.10 → python_dali-0.12}/dali/gear/incandescent.py +0 -0
  44. {python-dali-0.10 → python_dali-0.12}/dali/memory/__init__.py +0 -0
  45. {python-dali-0.10 → python_dali-0.12}/dali/memory/diagnostics.py +0 -0
  46. {python-dali-0.10 → python_dali-0.12}/dali/memory/energy.py +0 -0
  47. {python-dali-0.10 → python_dali-0.12}/dali/memory/info.py +0 -0
  48. {python-dali-0.10 → python_dali-0.12}/dali/memory/location.py +0 -0
  49. {python-dali-0.10 → python_dali-0.12}/dali/memory/maintenance.py +0 -0
  50. {python-dali-0.10 → python_dali-0.12}/dali/memory/oem.py +0 -0
  51. {python-dali-0.10 → python_dali-0.12}/dali/sequences.py +0 -0
  52. {python-dali-0.10 → python_dali-0.12}/dali/tests/__init__.py +0 -0
  53. {python-dali-0.10 → python_dali-0.12}/dali/tests/fakes.py +0 -0
  54. {python-dali-0.10 → python_dali-0.12}/dali/tests/fakes_serial.py +0 -0
  55. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_colour.py +0 -0
  56. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_command.py +0 -0
  57. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_events.py +0 -0
  58. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_exceptions.py +0 -0
  59. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_fakes.py +0 -0
  60. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_memory.py +0 -0
  61. {python-dali-0.10 → python_dali-0.12}/dali/tests/test_sequences.py +0 -0
  62. {python-dali-0.10 → python_dali-0.12}/pyproject.toml +0 -0
  63. {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/dependency_links.txt +0 -0
  64. {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/requires.txt +2 -2
  65. {python-dali-0.10 → python_dali-0.12}/python_dali.egg-info/top_level.txt +0 -0
  66. {python-dali-0.10 → python_dali-0.12}/setup.py +0 -0
@@ -1,25 +1,29 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-dali
3
- Version: 0.10
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: LGPL3+
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.7
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.7 or later.
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–2022 Stephen Early <steve@assorted.org.uk>
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.7 or later.
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–2022 Stephen Early <steve@assorted.org.uk>
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
@@ -7,3 +7,5 @@ part 103 ("General requirements - Control devices") and parts 3xx
7
7
  import dali.device.general
8
8
  import dali.device.sequences
9
9
  import dali.device.pushbutton # noqa: F401
10
+ import dali.device.occupancy # noqa: F401
11
+ import dali.device.light # noqa: F401
@@ -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.input_device_error
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()