|
6 | 6 | import types
|
7 | 7 | from typing import Generator, Optional, Type
|
8 | 8 |
|
9 |
| -from dali.address import DeviceShort, InstanceNumber |
| 9 | +from dali.address import DeviceShort, InstanceNumber, DeviceBroadcast, DeviceBroadcastUnaddressed |
10 | 10 | from dali.command import Command, Response
|
11 | 11 | from dali.device.general import (
|
| 12 | + Compare, |
12 | 13 | DTR0,
|
13 | 14 | DTR1,
|
14 | 15 | DTR2,
|
15 | 16 | EventScheme,
|
| 17 | + Initialise, |
16 | 18 | InstanceEventFilter,
|
17 |
| - QueryResolution, |
18 |
| - QueryInputValue, |
19 |
| - QueryInputValueLatch, |
| 19 | + ProgramShortAddress, |
| 20 | + QueryDeviceStatus, |
20 | 21 | QueryEventFilterH,
|
21 | 22 | QueryEventFilterL,
|
22 | 23 | QueryEventFilterM,
|
23 | 24 | QueryEventScheme,
|
24 | 25 | QueryEventSchemeResponse,
|
| 26 | + QueryInputValue, |
| 27 | + QueryInputValueLatch, |
| 28 | + QueryResolution, |
| 29 | + Randomise, |
| 30 | + SearchAddrH, |
| 31 | + SearchAddrL, |
| 32 | + SearchAddrM, |
25 | 33 | SetEventFilter,
|
26 | 34 | SetEventScheme,
|
| 35 | + SetShortAddress, |
| 36 | + Terminate, |
| 37 | + VerifyShortAddress, |
| 38 | + Withdraw, |
27 | 39 | )
|
28 | 40 | from dali.device.helpers import check_bad_rsp
|
29 |
| -from dali.exceptions import DALISequenceError |
| 41 | +from dali.exceptions import DALISequenceError, ProgramShortAddressFailure |
| 42 | +from dali.sequences import progress, sleep |
30 | 43 |
|
31 | 44 |
|
32 | 45 | def SetEventSchemes(
|
@@ -275,3 +288,113 @@ def query_input_value(
|
275 | 288 | value >>= 8 - resolution
|
276 | 289 |
|
277 | 290 | return value
|
| 291 | + |
| 292 | +def _find_next(low, high): |
| 293 | + yield SearchAddrH((high >> 16) & 0xff) |
| 294 | + yield SearchAddrM((high >> 8) & 0xff) |
| 295 | + yield SearchAddrL(high & 0xff) |
| 296 | + |
| 297 | + r = yield Compare() |
| 298 | + |
| 299 | + if low == high: |
| 300 | + if r.value is True: |
| 301 | + return "clash" if r.raw_value.error else low |
| 302 | + return |
| 303 | + |
| 304 | + if r.value is True: |
| 305 | + midpoint = (low + high) // 2 |
| 306 | + res = yield from _find_next(low, midpoint) |
| 307 | + if res is not None: |
| 308 | + return res |
| 309 | + return (yield from _find_next(midpoint + 1, high)) |
| 310 | + |
| 311 | + |
| 312 | +def Commissioning(available_addresses=None, readdress=False, |
| 313 | + dry_run=False): |
| 314 | + """Assign short addresses to control gear |
| 315 | +
|
| 316 | + If available_addresses is passed, only the specified addresses |
| 317 | + will be assigned; otherwise all short addresses are considered to |
| 318 | + be available. |
| 319 | +
|
| 320 | + if "readdress" is set, all existing short addresses will be |
| 321 | + cleared; otherwise, only control gear that is currently |
| 322 | + unaddressed will have short addresses assigned. |
| 323 | +
|
| 324 | + If "dry_run" is set then no short addresses will actually be set. |
| 325 | + This can be useful for testing. |
| 326 | + """ |
| 327 | + if available_addresses is None: |
| 328 | + available_addresses = list(range(64)) |
| 329 | + else: |
| 330 | + available_addresses = list(available_addresses) |
| 331 | + |
| 332 | + if readdress: |
| 333 | + if dry_run: |
| 334 | + yield progress(message="dry_run is set: not deleting existing " |
| 335 | + "short addresses") |
| 336 | + else: |
| 337 | + yield DTR0(255) |
| 338 | + yield SetShortAddress(DeviceBroadcast()) |
| 339 | + else: |
| 340 | + # We need to know which short addresses are already in use |
| 341 | + for a in range(0, 64): |
| 342 | + if a in available_addresses: |
| 343 | + in_use = yield QueryDeviceStatus(Short(a)) |
| 344 | + if in_use.value: |
| 345 | + available_addresses.remove(a) |
| 346 | + yield progress( |
| 347 | + message=f"Available addresses: {available_addresses}") |
| 348 | + |
| 349 | + yield Terminate() |
| 350 | + yield Initialise(0xff if readdress else 0x7f) |
| 351 | + |
| 352 | + finished = False |
| 353 | + # We loop here to cope with multiple devices picking the same |
| 354 | + # random search address; when we discover that, we |
| 355 | + # re-randomise and begin again. Devices that have already |
| 356 | + # received addresses are unaffected. |
| 357 | + while not finished: |
| 358 | + yield Randomise() |
| 359 | + # Randomise can take up to 100ms |
| 360 | + yield sleep(0.1) |
| 361 | + |
| 362 | + low = 0 |
| 363 | + high = 0xffffff |
| 364 | + |
| 365 | + while low is not None: |
| 366 | + yield progress(completed=low, size=high) |
| 367 | + low = yield from _find_next(low, high) |
| 368 | + if low == "clash": |
| 369 | + yield progress(message="Multiple ballasts picked the same " |
| 370 | + "random address; restarting") |
| 371 | + break |
| 372 | + if low is None: |
| 373 | + finished = True |
| 374 | + break |
| 375 | + yield progress( |
| 376 | + message=f"Ballast found at address {low:#x}") |
| 377 | + if available_addresses: |
| 378 | + new_addr = available_addresses.pop(0) |
| 379 | + if dry_run: |
| 380 | + yield progress( |
| 381 | + message="Not programming short address " |
| 382 | + f"{new_addr} because dry_run is set") |
| 383 | + else: |
| 384 | + yield progress( |
| 385 | + message=f"Programming short address {new_addr}") |
| 386 | + yield ProgramShortAddress(new_addr) |
| 387 | + r = yield VerifyShortAddress(new_addr) |
| 388 | + if r.value is not True: |
| 389 | + raise ProgramShortAddressFailure(new_addr) |
| 390 | + else: |
| 391 | + yield progress( |
| 392 | + message="Device found but no short addresses left") |
| 393 | + yield Withdraw() |
| 394 | + if low < high: |
| 395 | + low = low + 1 |
| 396 | + else: |
| 397 | + low = None |
| 398 | + finished = True |
| 399 | + yield Terminate() |
| 400 | + yield progress(message="Addressing complete") |
0 commit comments