Skip to content

Commit

Permalink
Control Devices: respect "resolution" when reading "inputValue"
Browse files Browse the repository at this point in the history
The standard (62386-103:2014, chapter 9.7) states that the "resolution"
of an instance is not always 8-bits. If it's any longer, a proper read
involves a transaction and a sequence of commands. If the number is not
divisible by 8, extra trailing bits have to be truncated.

Tested on Lunatone DALI-2 MC (86459532-2) and Lunatone CS-2 (86458670).
The push button coupler is a part-301 device where the standard says
that the on-the-wire values are either 0x00 or 0xff, but the actual
resolution (and therefore the correct numeric value) is either 0 or 1.
The other device, a combined movement sensor with a light meter,
supports 11 bits of resolution for the lux meter and therefore it
requires a "QUERY INPUT VALUE" followed by a "QUERY INPUT VALUE LATCH"
and dropping the extra 5 bits.
  • Loading branch information
jktjkt committed Aug 20, 2023
1 parent 8276c9c commit d70be0f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 3 deletions.
50 changes: 50 additions & 0 deletions dali/device/sequences.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
DTR2,
EventScheme,
InstanceEventFilter,
QueryResolution,
QueryInputValue,
QueryInputValueLatch,
QueryEventFilterH,
QueryEventFilterL,
QueryEventFilterM,
Expand All @@ -23,6 +26,7 @@
SetEventScheme,
)
from dali.device.helpers import check_bad_rsp
from dali.exceptions import DALISequenceError


def SetEventSchemes(
Expand Down Expand Up @@ -225,3 +229,49 @@ def QueryEventFilters(
hi = rsp.value

return filter_type(int.from_bytes((lo, md, hi), "little"))


def query_input_value(
device: DeviceShort,
instance: InstanceNumber,
resolution: Optional[int] = None
) -> Generator[Command, Optional[Response], Optional[int]]:
"""
A generator sequence to retrieve full sensor value from a part-103 control device.
Use with an appropriate DALI driver instance, through the `run_sequence()`
method.
:param device: A DeviceShort address to target
:param instance: An InstanceNumber to target
:param resolution: Number of valid bits that the device provides
"""
# Although the proper types are expected, ints are common enough for
# addresses and their meaning is unambiguous in this context
if isinstance(device, int):
device = DeviceShort(device)
if isinstance(instance, int):
instance = InstanceNumber(instance)

if resolution is None:
resolution = yield QueryResolution(device, instance)
if check_bad_rsp(resolution):
raise DALISequenceError("query_input_value: QueryResolution failed")
resolution = resolution.value

value = yield QueryInputValue(device, instance)
if check_bad_rsp(value):
raise DALISequenceError("query_input_value: QueryInputValue failed")
value = value.value
while resolution > 8:
resolution -= 8
value <<= 8
chunk = yield QueryInputValueLatch(device, instance)
if check_bad_rsp(chunk):
raise DALISequenceError("query_input_value: QueryInputValueLatch failed")
value += chunk.value

if resolution > 0:
# Strip the repeated trailing bytes as per IEC 62386-103:2014, part 9.7.2
value >>= 8 - resolution

return value
7 changes: 4 additions & 3 deletions examples/async-dalitest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
DeviceShort, DeviceBroadcast, InstanceNumber
import dali.gear.general as gg
import dali.device.general as dg
import dali.device.sequences as ds
from dali.gear import emergency
from dali.gear import led
from dali.sequences import QueryDeviceTypes, DALISequenceError
Expand Down Expand Up @@ -85,9 +86,9 @@ async def scan_control_devices(d):
for instance in (InstanceNumber(x) for x in range(instances.value)):
print(f" -{instance}- enabled: {await d.send(dg.QueryInstanceEnabled(addr, instance))}")
print(f" -{instance}- type: {await d.send(dg.QueryInstanceType(addr, instance))}")
print(f" -{instance}- resolution: {await d.send(dg.QueryResolution(addr, instance))}")
print(f" -{instance}- value: {await d.send(dg.QueryInputValue(addr, instance))}")
# XXX read remaining bytes of value
resolution = await d.send(dg.QueryResolution(addr, instance))
print(f" -{instance}- resolution: {resolution}")
print(f" -{instance}- value: {await d.run_sequence(ds.query_input_value(addr, instance, resolution.value))}")
print(f" -{instance}- feature type: {await d.send(dg.QueryFeatureType(addr, instance))}")
#for b in (info.BANK_0, oem.BANK_1):
# try:
Expand Down

0 comments on commit d70be0f

Please sign in to comment.