Repository navigation
Typed - #160
Open
markus-becker-tridonic-com wants to merge 21 commits into
Open
Typed#160markus-becker-tridonic-com wants to merge 21 commits into
markus-becker-tridonic-com wants to merge 21 commits into
Conversation
pyserial-asyncio is deprecated. Switch the RS232 serial drivers to serialx, which provides a compatible create_serial_connection and SerialTransport API, and update the driver-serial and test extras. Bump version to 0.12. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build sdist and wheel and publish to PyPI via trusted publishing when a v* tag is pushed. Verify the tag matches the package version before building. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
serialx's SerialTransport has no `loop` attribute, so the LUBA and SCI connection_lost handlers raised AttributeError on disconnect. Calling loop.stop() was also a leftover from the standalone-script era and would stop the shared asyncio event loop of any embedding application. Signal the disconnect through the existing connected event instead, which mirrors connection_made setting it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lunatone DALI interfaces with an integrated power supply (e.g. the DALI-2 USB PS) leave the bus unpowered by default, so every transmission fails with "error in transmission: 1". Add an opt-in `bus_power` option to DriverLubaRs232 that sets HardwareSettings bit 7 when writing the device settings, switching on the interface's bus power supply. To let callers decide safely (a separate DALI power supply must not clash with the interface's own), implement the two queries needed to detect the situation: - QUERY DEVICE DESCRIPTOR (0x28/0x29): exposes whether the interface has a switchable bus power supply (HardwareFeature bit 7). - READ STATUS (0x2C/0x2D): exposes whether the bus currently has a voltage error, i.e. is unpowered (Status bit 7). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the LUBA device reports an "ADD DALI FRAME TO TX BUFFER" error, no frame reaches the bus, so the "frame sent" event that normally confirms a transmission never arrives. send_dali_command() therefore blocked until the confirmation timeout before failing with a generic error. Surface the rejection immediately: enqueue a transmission confirmation carrying the interface's reason code, and raise a new TransmissionError from send_dali_command() as soon as it is dequeued. The reason codes are enumerated in LubaTxError (LUBA documentation section 2.1.1), letting callers act on specific causes such as an unpowered bus (bus voltage error). Also log frames that cannot be decoded from their transmit echo at debug, including the raw bytes, to aid diagnosis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The LUBA device echoes each transmitted frame back so the driver can verify it against what it sent. Some valid frames (for example extended DT8 colour queries) cannot yet be round-tripped through Command.from_frame, but the frame was still sent successfully, as the interface itself confirmed. Emitting a warning for every such frame floods the log during normal polling for a purely cosmetic decode gap, so drop it to debug. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The DALI-2 USB PS (article nr. 24138246) has been exercised successfully with this driver, so add it to the set of tested article numbers alongside 24166096. Collect them in TESTED_ARTICLE_NUMBERS and widen the "untested device" warning to list every tested number. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add sequence helpers that wrap the application-extended queries so callers get the required EnableDeviceType prefix automatically: - QueryDT8ColourTypeFeatures and QueryDT8ColourStatus (DT8 / 62386-209) report which colour types a gear supports and which one is active. - QueryDT6DimmingCurve (DT6 / 62386-207) reports the configured dimming curve so a linear curve can be detected. Sent bare, these commands time out because the gear ignores an application-extended command without a preceding EnableDeviceType. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A query that received no reply defaulted to a bare Response(None). That response passes check_bad_rsp while its value is None, so a timed-out QueryInstanceType leaked None into control-device autodiscovery and crashed on int(None), aborting driver setup. Default a query to the command's own response type instead, so a timeout is reported as a missing value and check_bad_rsp rejects it as expected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Before each transaction the drivers clear any DALI frame left buffered from a previous exchange. A backward frame that arrives after its query's receive timeout is such a leftover and is expected on a busy bus, yet it was logged at CRITICAL on every occurrence, flooding the log. Two bugs made this worse: the queue was reported as holding N items but only a single item was ever discarded, and the SCI info-queue branch drained the raw-frame queue by mistake, so the info queue was never cleared. Drain each queue fully through a shared helper and log the discard at debug level. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MeasurementVariable, UnitOfMeasurement and QuantityName values were taken from an earlier draft whose numbering differs from the published FDIS. Several codes were off, so a sensor's reported quantity or unit decoded to the wrong type. Renumber all three enums to the FDIS tables (including the newly added quantities and units) and rename the MAX/MIN input-value variables to MAX/MIN_MEASURED_VALUE to match Table 17.
A spontaneous event from an input device can corrupt the backward frame of the first QueryDeviceStatus probe during a scan, which made a present device (and all its instances) look absent and get dropped. Let the bus settle after entering quiescent mode, and retry a status probe that comes back as a framing error while still treating a genuinely missing response as an empty address. Both the settle delay and retry count are tunable.
Control gear had a Commissioning sequence but control devices (part 103) did not, so unaddressed sensors and pushbuttons could not be assigned short addresses programmatically. Add a Commissioning generator mirroring the gear one, using the part 103 24-bit randomise/compare/withdraw search and INITIALISE semantics (0xff all, 0x7f unaddressed). The bus is held quiescent for the search, a verify failure is raised only after quiescent mode is exited, and a same-random-address clash re-randomises and retries. The fake device bus gains the addressing commands so the sequence is tested end to end.
The initial device-info handshake can time out while the bus is busy (for example when an input device is streaming events), which failed the whole connect. Retry the handshake a few times, draining any late LUBA reply left in the RX queue between attempts so it is not mistaken for the next command's response, and only give up after the configured attempt count.
A device reporting more than 32 instances from QueryNumberOfInstances (a non-conformant device, or a count whose backward frame collided with a spontaneous event) drove the instance loop past instance number 31 and raised ValueError from InstanceNumber, aborting the whole scan. Instance numbers are 0..31 per IEC 62386-103, so a device has at most 32 instances; cap the count defensively. Non-existent instances are already skipped when they do not answer QueryInstanceEnabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The IEC 62386-306 Annex A, Table A.1 unit-of-measurement list ends at 53 (parts per million). Real hardware (Lunatone sonuSense) reports unit codes beyond that: 55 for A-weighted sound pressure level and 56 for a manufacturer-defined probability instance. Decode them instead of surfacing them as unknown values, clearly marked as non-306 extensions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the IEC 62386-202 self-contained emergency control gear variable definitions and sequence helpers consumed by application controllers: - dali/memory/emergency.py: memory bank 208 (Table 18) with the control gear temperatures, battery charge/maintenance powers, rated duration, recharge/function-test times, and the full set of failure, mode, test and lamp counters. - dali/gear/sequences.py: QueryEmergencyInformation bundles the DT1 status and measurement queries into a single sequence (so the required ENABLE DEVICE TYPE 1 prefix is injected), and EmergencyCommand issues a single DT1 control command. - Fake gear emergency support plus tests for the bank, the information sequence and the control-command helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The driver-serial and test extras now depend on serialx, which requires Python >= 3.10. Installing the test extra therefore fails on 3.8 and 3.9, breaking the "Install dependencies" step of the test matrix. Drop those interpreters from the matrix and raise python_requires to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ship PEP 561 py.typed markers for every public subpackage (dali, device, driver, gear, memory) and include them in the built distribution via MANIFEST.in and include_package_data, so downstream consumers' type checkers follow python-dali as a typed dependency. Annotate the public signatures the consumers call: Response/Command in command.py, the address constructors, the gear/device command bases, the commissioning and query sequences, memory bank read_all, and the typed exceptions. Signatures only; no behavior change. Add `from __future__ import annotations` where PEP 604 unions require it on the 3.7 floor, and fix the malformed dict[(int, int), int] annotation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
markus-becker-tridonic-com
force-pushed
the
typed
branch
from
October 5, 2026 08:44
42fd4da to
1ae2754
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make python-dali a typed (PEP 561) package so downstream consumers' type
checkers follow it as a typed dependency. Ships
py.typedmarkers for everypublic subpackage and annotates the public signatures consumers call.
What's included (top commit)
py.typedmarkers fordali,dali/device,dali/driver,dali/gear,dali/memory, included in the built distribution viaMANIFEST.in+include_package_data.Response/Command(command.py), theaddress constructors, the gear/device command bases, the commissioning and
query sequences, memory-bank
read_all, and the typed exceptions.from __future__ import annotationswhere PEP 604 unions require it on the3.7 floor; fixes the malformed
dict[(int, int), int]→dict[tuple[int, int], int]annotation.This branch is stacked on the full integration feature set (serialx
migration, part 306, DT8 colour sequences, control-device commissioning, DT1).
The typing change is the top commit (
574b461c13cb3606e14aa585f9d4bbf27805ede2); againstmasterthe PRshows ~20 commits because the annotations cover symbols introduced across those
branches and cannot stand alone. Review the top commit, or retarget the PR base
to the appropriate branch once the underlying stack has landed.
Testing
mypywith 0 errors against a non-editable wheel of this branch, andpy.typedis present in the wheelRECORD.