Lunatone DALI-2 USB (LunatoneLuba)
DALI-2 bus master through a Lunatone DALI-2 USB interface. One connector controls every piece of DALI control gear on the interface's bus: set levels, read status and LED failure flags, read and write the DALI configuration, read D4i identity / energy / diagnostics banks, and give gear short addresses.
The driver talks Lunatone's LUBA protocol over the interface's USB virtual COM port (38400 baud, 8N1). Tier-1 free JavaScript driver, distributed as a .muxdriver package. Needs Muxit 0.43.1 or later: LUBA frames contain bytes ≥ 0x80, which older builds could not send from a JS driver. On an older build the writeMode property reads "text" and those frames fail with a clear error instead of going out corrupted.
Connector config
The driver's template is a connector named dali that pairs with the DALI status dashboard and the examples/dali-monitor script in the example workspace:
// connectors/dali.js
export default {
driver: "LunatoneLuba",
config: {
port: "COM5", // Required. The interface's COM port ("/dev/ttyACM0" on Linux).
line: 0, // Optional. DALI line of the interface. Default 0.
busPower: true, // Optional. Switch the interface's own bus supply on (true) or off (false).
// Omit to leave it as it is.
timeoutMs: 500, // Optional. Timeout per LUBA request. Default 500.
priority: 1, // Optional. DALI frame priority 1-5. Default 1.
autoOpen: true, // Optional. Open the port when the connector starts. Default true.
},
};The full template adds, on top of the driver's own properties and actions:
- Dashboard properties, computed from the cached
busStatusresult, so polling them costs no bus traffic:busState(OK/FAIL/NO BUS/NO UNITS/NO DATA/OFFLINE),statusText,failuresText,unitCount,busPowered,levels(array, one level per unit) andlastUpdate.broadcastLevelis writable: writing it sets every unit. - Short methods:
refreshandrescan(update the status, return the text summary),overview(rescan?)andsummary(rescan?),allOff(),level(address, level),actual(address),status(address),settings(address),configure(address, settings),d4i(address),bank(address, bank, first?, last?),linear(address),identify(address), andhandOver()/takeBack()to release and re-open the port.
Create the connector from the Extensions panel to get it, or copy connectors/_examples/dali.js.
A DALI bus needs a supply. If no gear on the bus provides one, set busPower: true so the interface supplies it; busStatus reports NO VOLTAGE otherwise.
Addresses
Actions that send a command take address as a short address 0–63, a group "g0"–"g15", or "broadcast". Queries and reads need a short address. Levels are DALI arc power values 0–254 (0 = off); the gear clamps them to its MIN/MAX level.
Properties
| Property | Type | Description |
|---|---|---|
connected | bool | COM port open and LUBA interface configured |
port | string | Serial port of the interface |
units | object | Result of the last busStatus call (cached, no bus traffic); null before the first call |
interfaceSettings | object | {mode, eventFilter, hardware, busPowerOn} as applied at open |
writeMode | string | "raw" (binary-safe), "hex" (test transport) or "text" (Muxit older than 0.43.1) |
lastError | string | Last bus or protocol error with ISO timestamp; empty when none |
All properties are read-only. Polling them costs no bus traffic.
Actions
Overview and port
| Action | Args | Returns |
|---|---|---|
busStatus | rescan? | {ok, time, interface, units, text}: per-unit level and failures plus a readable summary. Never throws for bus problems. First call (or rescan: true) scans addresses 0–63 (~2 s). |
open | — | "OK". Open the port and configure the interface. |
release | — | "OK". Close the port so another program (e.g. MultiOne) can use the interface. |
interfaceInfo | — | {gtin, id, pcbVersion, assemblyVersion, articleNumber, info, raw} |
lineStatus | — | {line, available, nextId, framesInBuffer, busVoltageError, initialiseMode, quiescentMode} |
scan | — | [{address, duplicate}]: short addresses that answer QUERY CONTROL GEAR PRESENT |
Light level
| Action | Args | Returns |
|---|---|---|
setLevel | address, level | {frame, result, answer}. Direct Arc Power 0–254. |
off / recallMax / recallMin | address | {frame, result, answer} |
identify | address | {frame, result, answer}. The unit signals itself for ~10 s (DALI-2). |
Status and settings (short address only)
| Action | Args | Returns |
|---|---|---|
queryActualLevel | address | 0–254, or null when no gear answers |
queryStatus | address | QUERY STATUS decoded into flags |
readStatus | address | Status flags, actual level, gear failure, DT6 LED failure flags, failures (list of strings) and ok |
readSettings | address | DALI version, device types, light source type, MIN/MAX/power-on/system-failure levels, fade, groups; dimming curve for DT6 gear |
query | address, opcode | The 8-bit answer to any DALI query opcode, or null |
queryDimmingCurve | address | "logarithmic", "linear", or null |
readMemoryBank | address, bank, first?, last? | {bank, implemented, bytes, hex, decoded?} |
readD4i | address | Banks 0, 1, 202, 205 and 206 decoded: identity, luminaire data, energy, gear and light-source diagnostics (~200 transactions) |
Changing stored configuration
| Action | Args | Returns |
|---|---|---|
storeSettings | address, settings | Writes maxLevel, minLevel, powerOnLevel, systemFailureLevel, fadeTime, fadeRate, groups; for a short address, reads back and lists mismatches |
setDimmingCurve | address, curve | DT6: "linear" or "logarithmic" |
reset | address | DALI RESET: levels, fade, groups and scenes back to reset values (short address kept) |
setShortAddress | address, newAddress | Change or delete (null) one unit's short address; refuses an address in use |
assignAddresses | mode?, first? | [{address, random}]. "unaddressed" (default) numbers gear without a short address; "all" deletes every short address first. ~3–4 s per unit. |
sendRaw16 | hex, answer?, twice? | Send any 16-bit forward frame, e.g. "FFA0" |
These change what the gear stores, and assignAddresses with mode: "all" renumbers the whole bus. The template's ai.instructions tell the AI to ask before calling them. The driver documents each action's full return format in its details, which the AI reads on demand.
Streams
| Stream | Content |
|---|---|
busEvents | Bus-level events as JSON strings: busError, systemFailure, busRestored, busSupplyWarning |
Bind a terminal widget to dali:busEvents to watch them.
Example script
const dali = connector("dali");
const st = dali.busStatus({ rescan: true });
log.info(st.text);
dali.setLevel("broadcast", 128);
const d4i = dali.readD4i(0);
log.info(`unit 0: ${d4i.energy?.activeEnergyWh} Wh, ${d4i.gearDiagnostics?.temperatureC} °C`);Sharing the interface with MultiOne
Settings that are not part of DALI (such as a Philips driver's output current) need the vendor's tool. Call release to free the COM port, use the other program, then open to take the interface back.
Notes
- All bus access is serialised: one transaction, or one multi-frame sequence such as DTR0 + STORE or a memory-bank read, at a time.
- D4i decoding follows IEC 62386-102 ed2 (bank 0) and DiiA Parts 251–253. The raw bytes are always returned next to the decoded fields; check them against your gear before relying on a decoded value in limits.
- Revision history:
drivers/js/lunatone-luba/CHANGELOG.md.