python-dali 0.14__tar.gz → 0.16__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 (70) hide show
  1. {python_dali-0.14/python_dali.egg-info → python_dali-0.16}/PKG-INFO +2 -2
  2. {python_dali-0.14 → python_dali-0.16}/dali/driver/serial.py +36 -16
  3. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_serial_driver.py +50 -1
  4. {python_dali-0.14 → python_dali-0.16/python_dali.egg-info}/PKG-INFO +2 -2
  5. {python_dali-0.14 → python_dali-0.16}/setup.cfg +2 -2
  6. {python_dali-0.14 → python_dali-0.16}/COPYING +0 -0
  7. {python_dali-0.14 → python_dali-0.16}/COPYING.LESSER +0 -0
  8. {python_dali-0.14 → python_dali-0.16}/LICENSE +0 -0
  9. {python_dali-0.14 → python_dali-0.16}/README.rst +0 -0
  10. {python_dali-0.14 → python_dali-0.16}/dali/__init__.py +0 -0
  11. {python_dali-0.14 → python_dali-0.16}/dali/address.py +0 -0
  12. {python_dali-0.14 → python_dali-0.16}/dali/command.py +0 -0
  13. {python_dali-0.14 → python_dali-0.16}/dali/device/__init__.py +0 -0
  14. {python_dali-0.14 → python_dali-0.16}/dali/device/general.py +0 -0
  15. {python_dali-0.14 → python_dali-0.16}/dali/device/general_sensor.py +0 -0
  16. {python_dali-0.14 → python_dali-0.16}/dali/device/helpers.py +0 -0
  17. {python_dali-0.14 → python_dali-0.16}/dali/device/light.py +0 -0
  18. {python_dali-0.14 → python_dali-0.16}/dali/device/occupancy.py +0 -0
  19. {python_dali-0.14 → python_dali-0.16}/dali/device/pushbutton.py +0 -0
  20. {python_dali-0.14 → python_dali-0.16}/dali/device/sequences.py +0 -0
  21. {python_dali-0.14 → python_dali-0.16}/dali/driver/__init__.py +0 -0
  22. {python_dali-0.14 → python_dali-0.16}/dali/driver/atxled.py +0 -0
  23. {python_dali-0.14 → python_dali-0.16}/dali/driver/base.py +0 -0
  24. {python_dali-0.14 → python_dali-0.16}/dali/driver/daliserver.py +0 -0
  25. {python_dali-0.14 → python_dali-0.16}/dali/driver/hasseb.py +0 -0
  26. {python_dali-0.14 → python_dali-0.16}/dali/driver/hid.py +0 -0
  27. {python_dali-0.14 → python_dali-0.16}/dali/driver/trace_logging.py +0 -0
  28. {python_dali-0.14 → python_dali-0.16}/dali/driver/tridonic.py +0 -0
  29. {python_dali-0.14 → python_dali-0.16}/dali/driver/unipi.py +0 -0
  30. {python_dali-0.14 → python_dali-0.16}/dali/driver/usb.py +0 -0
  31. {python_dali-0.14 → python_dali-0.16}/dali/exceptions.py +0 -0
  32. {python_dali-0.14 → python_dali-0.16}/dali/frame.py +0 -0
  33. {python_dali-0.14 → python_dali-0.16}/dali/gear/__init__.py +0 -0
  34. {python_dali-0.14 → python_dali-0.16}/dali/gear/colour.py +0 -0
  35. {python_dali-0.14 → python_dali-0.16}/dali/gear/converter.py +0 -0
  36. {python_dali-0.14 → python_dali-0.16}/dali/gear/emergency.py +0 -0
  37. {python_dali-0.14 → python_dali-0.16}/dali/gear/general.py +0 -0
  38. {python_dali-0.14 → python_dali-0.16}/dali/gear/incandescent.py +0 -0
  39. {python_dali-0.14 → python_dali-0.16}/dali/gear/led.py +0 -0
  40. {python_dali-0.14 → python_dali-0.16}/dali/gear/sequences.py +0 -0
  41. {python_dali-0.14 → python_dali-0.16}/dali/memory/__init__.py +0 -0
  42. {python_dali-0.14 → python_dali-0.16}/dali/memory/diagnostics.py +0 -0
  43. {python_dali-0.14 → python_dali-0.16}/dali/memory/emergency.py +0 -0
  44. {python_dali-0.14 → python_dali-0.16}/dali/memory/energy.py +0 -0
  45. {python_dali-0.14 → python_dali-0.16}/dali/memory/info.py +0 -0
  46. {python_dali-0.14 → python_dali-0.16}/dali/memory/location.py +0 -0
  47. {python_dali-0.14 → python_dali-0.16}/dali/memory/maintenance.py +0 -0
  48. {python_dali-0.14 → python_dali-0.16}/dali/memory/oem.py +0 -0
  49. {python_dali-0.14 → python_dali-0.16}/dali/sequences.py +0 -0
  50. {python_dali-0.14 → python_dali-0.16}/dali/tests/__init__.py +0 -0
  51. {python_dali-0.14 → python_dali-0.16}/dali/tests/fakes.py +0 -0
  52. {python_dali-0.14 → python_dali-0.16}/dali/tests/fakes_serial.py +0 -0
  53. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_colour.py +0 -0
  54. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_command.py +0 -0
  55. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_device_sequences.py +0 -0
  56. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_dummy.py +0 -0
  57. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_emergency.py +0 -0
  58. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_events.py +0 -0
  59. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_exceptions.py +0 -0
  60. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_fakes.py +0 -0
  61. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_frame.py +0 -0
  62. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_general_sensor.py +0 -0
  63. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_memory.py +0 -0
  64. {python_dali-0.14 → python_dali-0.16}/dali/tests/test_sequences.py +0 -0
  65. {python_dali-0.14 → python_dali-0.16}/pyproject.toml +0 -0
  66. {python_dali-0.14 → python_dali-0.16}/python_dali.egg-info/SOURCES.txt +0 -0
  67. {python_dali-0.14 → python_dali-0.16}/python_dali.egg-info/dependency_links.txt +0 -0
  68. {python_dali-0.14 → python_dali-0.16}/python_dali.egg-info/requires.txt +0 -0
  69. {python_dali-0.14 → python_dali-0.16}/python_dali.egg-info/top_level.txt +0 -0
  70. {python_dali-0.14 → python_dali-0.16}/setup.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-dali
3
- Version: 0.14
3
+ Version: 0.16
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: "LGPL-3.0-or-later"
8
+ License: LGPL-3.0-or-later
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Programming Language :: Python :: 3
@@ -660,18 +660,30 @@ class DriverLubaRs232(DriverSerialBase):
660
660
  )
661
661
  if confirm.error_code is not None:
662
662
  raise TransmissionError(confirm.error_code)
663
+ if (
664
+ hasattr(confirm.message, "frame")
665
+ and confirm.message.frame != tx.frame
666
+ ):
667
+ # A confirmation for a different frame can only be a
668
+ # stale one for an earlier send whose confirmation
669
+ # arrived after it had already timed out: sends are
670
+ # serialised under '_tx_lock', so nothing later is in
671
+ # flight yet. Drop it and keep waiting for this frame's
672
+ # own confirmation, otherwise every subsequent send
673
+ # inherits the one-off mismatch and the stream never
674
+ # resynchronises.
675
+ _LOG.warning(
676
+ "Discarding stale LUBA confirmation for '%s' "
677
+ "while awaiting '%s'",
678
+ confirm.message,
679
+ tx,
680
+ )
681
+ continue
663
682
  if hasattr(confirm.message, "frame"):
664
- if confirm.message.frame == tx.frame:
665
- _LOG.trace(
666
- f"LUBA device reports message '{tx}' sent, "
667
- f"with ID {confirm.tx_id}"
668
- )
669
- else:
670
- _LOG.error(
671
- "Expected LUBA device to confirm transmission "
672
- f"of message '{tx}', but got a confirmation "
673
- f"for '{confirm.message}'"
674
- )
683
+ _LOG.trace(
684
+ f"LUBA device reports message '{tx}' sent, "
685
+ f"with ID {confirm.tx_id}"
686
+ )
675
687
  else:
676
688
  _LOG.debug(
677
689
  f"Unable to decode message id {confirm.tx_id}, but "
@@ -1317,7 +1329,13 @@ class DriverLubaRs232(DriverSerialBase):
1317
1329
  self._transport: Optional[serialx.SerialTransport] = None
1318
1330
  self._protocol: Optional[DriverLubaRs232.LubaProtocol] = None
1319
1331
 
1320
- async def connect(self, *, scan_dev_inst: bool = False) -> None:
1332
+ async def connect(
1333
+ self, *, scan_dev_inst: bool = False, apply_settings: bool = True
1334
+ ) -> None:
1335
+ # ``apply_settings=False`` connects without writing the device settings,
1336
+ # leaving the interface's hardware configuration (including its bus-power
1337
+ # state) untouched. Use it to probe a discovered interface read-only so
1338
+ # the probe cannot depower a bus the integrated supply is already driving.
1321
1339
  if self.is_connected:
1322
1340
  _LOG.warning(
1323
1341
  f"'connect()' called but luba232 driver already connected"
@@ -1346,8 +1364,9 @@ class DriverLubaRs232(DriverSerialBase):
1346
1364
  for attempt in range(1, DriverLubaRs232.handshake_attempts + 1):
1347
1365
  try:
1348
1366
  await self._protocol.send_device_info_query()
1349
- self._protocol.bus_power = self.bus_power
1350
- await self._protocol.send_device_settings()
1367
+ if apply_settings:
1368
+ self._protocol.bus_power = self.bus_power
1369
+ await self._protocol.send_device_settings()
1351
1370
  break
1352
1371
  except (asyncio.TimeoutError, TimeoutError):
1353
1372
  if attempt >= DriverLubaRs232.handshake_attempts:
@@ -1366,8 +1385,9 @@ class DriverLubaRs232(DriverSerialBase):
1366
1385
 
1367
1386
  # After switching on the integrated power supply the bus voltage takes
1368
1387
  # a moment to rise; sending DALI frames before then fails, so wait for
1369
- # the bus to be powered before scanning.
1370
- if self.bus_power:
1388
+ # the bus to be powered before scanning. Skipped when the settings were
1389
+ # not applied, as the bus-power state was then left untouched.
1390
+ if apply_settings and self.bus_power:
1371
1391
  await self._wait_for_bus_power()
1372
1392
 
1373
1393
  # Scan the bus for control devices, and create a mapping of addresses
@@ -6,11 +6,12 @@ from unittest.mock import AsyncMock, MagicMock
6
6
 
7
7
  import pytest
8
8
 
9
- from dali.address import DeviceShort, InstanceNumber
9
+ from dali.address import DeviceShort, GearShort, InstanceNumber
10
10
  from dali.command import NumericResponse
11
11
  from dali.device.general import QueryInstanceType
12
12
  from dali.device.helpers import check_bad_rsp
13
13
  from dali.driver.serial import _DALI_FRAMING_ERROR, DriverLubaRs232
14
+ from dali.gear.general import QueryActualLevel
14
15
 
15
16
 
16
17
  @pytest.mark.asyncio
@@ -93,6 +94,25 @@ async def test_connect_retries_handshake_on_timeout(monkeypatch):
93
94
  assert protocol.reset_luba_response.call_count == 2
94
95
 
95
96
 
97
+ @pytest.mark.asyncio
98
+ async def test_connect_without_applying_settings_leaves_hardware_untouched(
99
+ monkeypatch,
100
+ ):
101
+ """connect(apply_settings=False) must not write the device settings.
102
+
103
+ A read-only probe of a discovered interface must not overwrite the
104
+ hardware configuration, otherwise it could depower a bus the integrated
105
+ supply is already driving.
106
+ """
107
+ driver, protocol = _connecting_driver(monkeypatch, [None])
108
+
109
+ await driver.connect(apply_settings=False)
110
+
111
+ assert driver.is_connected
112
+ assert protocol.send_device_info_query.await_count == 1
113
+ assert protocol.send_device_settings.await_count == 0
114
+
115
+
96
116
  @pytest.mark.asyncio
97
117
  async def test_connect_raises_after_exhausting_handshake_attempts(monkeypatch):
98
118
  """When every handshake attempt times out, connect must give up and raise."""
@@ -149,6 +169,35 @@ async def test_connect_reopens_the_port_after_a_lost_transport(monkeypatch):
149
169
  assert driver._protocol is replacement
150
170
 
151
171
 
172
+ @pytest.mark.asyncio
173
+ async def test_send_dali_command_resyncs_after_stale_confirmation(caplog):
174
+ """A stale confirmation must not desync every following transmission.
175
+
176
+ A weak bus can let a send's confirmation arrive after it timed out; the
177
+ next send then finds that stale confirmation queued ahead of its own. It
178
+ must be discarded and the matching one awaited, so the stream recovers
179
+ instead of every later frame inheriting the one-off mismatch.
180
+ """
181
+ protocol = DriverLubaRs232.LubaProtocol()
182
+ protocol.transport = MagicMock()
183
+ protocol.rx_idle.set()
184
+
185
+ command = QueryActualLevel(GearShort(0))
186
+ stale = QueryActualLevel(GearShort(1))
187
+ protocol._queue_tx_conf.put_nowait(
188
+ protocol.LubaMsgTxConf(tx_id=1, message=stale)
189
+ )
190
+ protocol._queue_tx_conf.put_nowait(
191
+ protocol.LubaMsgTxConf(tx_id=2, message=command)
192
+ )
193
+
194
+ with caplog.at_level(logging.WARNING, logger="dali.driver"):
195
+ await protocol.send_dali_command(command)
196
+
197
+ assert protocol._queue_tx_conf.empty()
198
+ assert any("stale LUBA confirmation" in r.getMessage() for r in caplog.records)
199
+
200
+
152
201
  @pytest.mark.asyncio
153
202
  async def test_reset_luba_response_drains_all(caplog):
154
203
  """Stale LUBA command replies must be cleared before a handshake retry."""
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-dali
3
- Version: 0.14
3
+ Version: 0.16
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: "LGPL-3.0-or-later"
8
+ License: LGPL-3.0-or-later
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Programming Language :: Python :: 3
@@ -1,13 +1,13 @@
1
1
  [metadata]
2
2
  name = python-dali
3
- version = 0.14
3
+ version = 0.16
4
4
  description = Interface to DALI lighting systems
5
5
  long_description = file: README.rst
6
6
  long_description_content_type = text/x-rst
7
7
  author = Stephen Early
8
8
  author_email = steve@assorted.org.uk
9
9
  url = https://github.com/sde1000/python-dali
10
- license = "LGPL-3.0-or-later"
10
+ license = LGPL-3.0-or-later
11
11
  license_files =
12
12
  COPYING
13
13
  COPYING.LESSER
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes