Skip to content
Open

Typed #160

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
574b461
Add general sensor support for IEC 62386 part 306 with tests
markus-becker-tridonic-com Sep 28, 2026
166caf9
Use serialx instead of pyserial-asyncio for serial drivers
markus-becker-tridonic-com Sep 28, 2026
8ca4312
Add GitHub Actions workflow to publish to PyPI on tag
markus-becker-tridonic-com Sep 28, 2026
5a10124
Fix connection_lost crash after serialx migration
markus-becker-tridonic-com Sep 29, 2026
93ac04c
Add switchable bus power supply support to LUBA driver
markus-becker-tridonic-com Sep 29, 2026
0928a3e
Timeout bus power change.
markus-becker-tridonic-com Sep 29, 2026
bf22404
Fail fast when the LUBA interface rejects a DALI frame
markus-becker-tridonic-com Sep 30, 2026
1bf308d
Log undecodable transmit echoes at debug instead of warning
markus-becker-tridonic-com Sep 30, 2026
21a5fb8
Mark Lunatone article nr. 24138246 as tested
markus-becker-tridonic-com Sep 30, 2026
40b213d
Add DT8 colour and DT6 dimming-curve query sequences
markus-becker-tridonic-com Sep 30, 2026
91b362d
Report timed-out queries with the command's response type
markus-becker-tridonic-com Sep 30, 2026
a07caa9
Drain stale RX DALI frames quietly instead of logging CRITICAL
markus-becker-tridonic-com Sep 30, 2026
b886410
Align Part 306 sensor enums with the IEC 62386-306 FDIS
markus-becker-tridonic-com Oct 1, 2026
a3658ae
Make control-device autodiscover resilient to event collisions
markus-becker-tridonic-com Oct 1, 2026
9beb2e8
Add a control-device commissioning sequence for part 103 devices
markus-becker-tridonic-com Oct 1, 2026
9401136
Retry the LUBA connect handshake on a busy bus
markus-becker-tridonic-com Oct 1, 2026
b6958ac
Cap control-device autodiscover at 32 instances
markus-becker-tridonic-com Oct 1, 2026
91853c5
Add vendor-extension unit codes 55 (dB(A)) and 56
markus-becker-tridonic-com Oct 1, 2026
efb12b6
Add emergency lighting (DT1) memory bank 208 and query helpers
markus-becker-tridonic-com Oct 2, 2026
496069c
Drop Python 3.8 and 3.9 from CI and require Python 3.10+
markus-becker-tridonic-com Oct 5, 2026
1ae2754
Add py.typed markers and annotate the public API surface
markus-becker-tridonic-com Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Publish to PyPI

on:
push:
tags:
- "v*"

permissions:
contents: read

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Verify tag matches package version
run: |
version="$(python setup.py --version)"
tag="${GITHUB_REF_NAME#v}"
if [ "$version" != "$tag" ]; then
echo "Tag $tag does not match package version $version" >&2
exit 1
fi
- name: Build sdist and wheel
run: |
python -m pip install --upgrade pip build
python -m build
- name: Upload distribution artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/python-dali/
permissions:
id-token: write
steps:
- name: Download distribution artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
recursive-include dali py.typed
8 changes: 5 additions & 3 deletions dali/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
7.2.2. Decoding of event messages is currently not implemented.
"""

from __future__ import annotations

from dali.exceptions import IncompatibleFrame


Expand Down Expand Up @@ -261,7 +263,7 @@ class GearShort(GearAddress):
address.
"""

def __init__(self, address):
def __init__(self, address: int) -> None:
if not isinstance(address, int):
raise ValueError("address must be an integer")
if address < 0 or address > 63:
Expand Down Expand Up @@ -305,7 +307,7 @@ class DeviceShort(DeviceAddress):
address.
"""

def __init__(self, address: int):
def __init__(self, address: int) -> None:
if not isinstance(address, int):
raise ValueError("address must be an integer")
if address < 0 or address > 63:
Expand Down Expand Up @@ -379,7 +381,7 @@ def __str__(self):
class _AddressedInstance(Instance):
_flags = None

def __init__(self, value):
def __init__(self, value: int) -> None:
if not isinstance(value, int):
raise ValueError("value must be an integer")
if value < 0 or value > 31:
Expand Down
9 changes: 6 additions & 3 deletions dali/command.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Declaration of base types for dali commands and their responses."""

from __future__ import annotations

from enum import IntEnum
from typing import Any
from dali import address
from dali import frame
from dali.exceptions import MissingResponse
Expand Down Expand Up @@ -46,17 +49,17 @@ class Response:
_expected = False
_error_acceptable = False

def __init__(self, val):
def __init__(self, val: frame.BackwardFrame | None) -> None:
if val is not None and not isinstance(val, frame.BackwardFrame):
raise TypeError("Response must be passed None or a BackwardFrame")
self._value = val

@property
def raw_value(self):
def raw_value(self) -> frame.BackwardFrame | None:
return self._value

@property
def value(self):
def value(self) -> Any:
if self._value is None and self._expected:
raise MissingResponse()
if self._value and self._value.error and not self._error_acceptable:
Expand Down
1 change: 1 addition & 0 deletions dali/device/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
import dali.device.pushbutton # noqa: F401
import dali.device.occupancy # noqa: F401
import dali.device.light # noqa: F401
import dali.device.general_sensor # noqa: F401
8 changes: 5 additions & 3 deletions dali/device/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class _StandardDeviceCommand(_DeviceCommand):
"""
_opcode = None

def __init__(self, device):
def __init__(self, device: address.Address | int) -> None:
if self._opcode is None:
raise NotImplementedError

Expand Down Expand Up @@ -514,7 +514,9 @@ class _StandardInstanceCommand(_DeviceCommand):

_opcode = None

def __init__(self, device, instance):
def __init__(
self, device: address.Address | int, instance: address.Instance
) -> None:
if self._opcode is None:
raise NotImplementedError

Expand Down Expand Up @@ -784,7 +786,7 @@ def __str__(self):


class _SpecialDeviceCommandOneParam(_SpecialDeviceCommand):
def __init__(self, param):
def __init__(self, param: int) -> None:
if not isinstance(param, int):
raise ValueError("parameter must be an integer")
if param < 0 or param > 255:
Expand Down
Loading
Loading