# DALI ESP-IDF Component A lightweight C++ implementation of the DALI stack used in `lib/dali/*.dart` for gateway type 1 (USB UART) traffic. The component mirrors Dart method names so it can be used as an embedded replacement target. ## Quick Start 1. Add the component to your ESP-IDF project (e.g., via `EXTRA_COMPONENT_DIRS`). 2. Provide UART callbacks when constructing `DaliComm`: ```cpp DaliComm comm( /* send */ [](const uint8_t* data, size_t len) { // write bytes to the gateway UART return my_uart_write(data, len) == ESP_OK; }, /* read (optional) */ [](size_t len, uint32_t timeoutMs) -> std::vector { return my_uart_read(len, timeoutMs); }, /* transact */ [](const uint8_t* data, size_t len) -> std::vector { my_uart_write(data, len); return my_uart_read_response(); // should return the raw gateway reply }); Dali dali(comm); ``` 3. Use the API just like the Dart version: ```cpp dali.base.setBright(5, 200); // direct arc power control dali.base.off(5); dali.base.dtSelect(8); dali.dt8.setColorTemperature(5, 4000); // Kelvin std::vector rgb = dali.dt8.getColourRGB(5); ``` ## Behaviour Parity - Frame formats match the Dart implementation: `[0x10, addr, cmd]` (send), `[0x11, addr, cmd]` (extended), `[0x12, addr, cmd]` (query with `[0xFF, data]` response). - Address encoding matches Dart helpers: `dec*2` for direct arc, `dec*2+1` for command/query addresses. - Colour conversion utilities (`rgb2xy`, `xy2rgb`, XYZ/LAB helpers) are ported from `lib/dali/color.dart`. - Public APIs from `base.dart`, `dt8.dart`, `dt1.dart`, `addr.dart`, and `decode.dart` are exposed with matching method names. - App-side model parity modules are included for `device.dart`, `sequence.dart`, and `sequence_store.dart` via `device.hpp`, `sequence.hpp`, and `sequence_store.hpp`. - Utility APIs from `errors.dart`, `log.dart`, `query_scheduler.dart`, and `bus_monitor.dart` are available as embedded-friendly C++ headers. ## Generic Bridge Layer The component now includes a protocol-agnostic bridge layer for mapping external fieldbus models into DALI operations. ### Bridge Building Blocks - `bridge_model.hpp` defines the strongly typed mapping model: protocol kind, external point, DALI target, default operation, and value transform. - `bridge.hpp` provides `DaliBridgeEngine`, which resolves models and dispatches requests into `DaliComm`, `DaliBase`, and `DaliDT8`. - `bridge_provisioning.hpp` provides `BridgeProvisioningStore` for persisting bridge models and shared protocol config in ESP-IDF NVS. - Modbus runtime support is owned by the native gateway project in `gateway/components/gateway_modbus`. - `bacnet_bridge.hpp` provides a BACnet skeleton adapter keyed by object type, instance, and property bindings. ### Example Model Mapping ```cpp BridgeModel brightness; brightness.id = "line-1-brightness"; brightness.name = "Line 1 brightness"; brightness.dali.shortAddress = 1; brightness.operation = BridgeOperation::setBrightness; brightness.valueTransform.clampMin = 0; brightness.valueTransform.clampMax = 254; DaliBridgeEngine engine(comm); engine.upsertModel(brightness); DaliBridgeRequest request; request.modelID = "line-1-brightness"; request.value = 180; engine.execute(request); ``` ### Supported Bridge Operations - `send` - `send_ext` - `query` - `set_brightness` - `set_brightness_percent` - `on` - `off` - `recall_max_level` - `recall_min_level` - `set_color_temperature` - `get_brightness` - `get_status` - `get_color_temperature` - `get_color_status` - `get_emergency_level` - `get_emergency_status` - `get_emergency_failure_status` - `start_emergency_function_test` - `stop_emergency_test` Query-style operations return `data` when available and may include decoded flags in `meta`. ## Notes - Query support works with either a `read` callback or a `transact` callback. When `read` is available, queries return as soon as the reply bytes arrive instead of waiting behind a fixed pre-read delay. - `Dali` facade in `include/dali.hpp` mirrors `lib/dali/dali.dart` and wires `base`, `decode`, `dt1`, `dt8`, and `addr` together. - The `t`, `d`, and `g` parameters in Dart are not required here; timing/gateway selection is driven by your callbacks. - `DaliCloudBridge` now uses the shared bridge engine internally, so MQTT downlinks can target either raw DALI frames or registered bridge models. ## Bridge Provisioning via NVS Use `BridgeProvisioningStore` to persist bridge models and shared protocol-specific config such as BACnet: ```cpp BridgeRuntimeConfig runtime; runtime.models.push_back(brightness); BridgeProvisioningStore store; store.save(runtime); BridgeRuntimeConfig loaded; if (store.load(&loaded) == ESP_OK) { // register loaded.models with DaliBridgeEngine } ``` The gateway project stores Modbus TCP settings in the same persisted JSON shape, but parses and applies that section in `gateway/components/gateway_modbus` rather than in this standalone DALI component. ## Cloud Bridge (ESP32 Gateway) The component now includes `DaliCloudBridge` in `include/gateway_cloud.hpp` to connect ESP32 gateways to the backend MQTT broker. ### Topics - Downlink: `devices//down` - Uplink: `devices//up` - Status: `devices//status` - Register: `devices//register` ### Downlink JSON Envelope ```json { "type": "dali_cmd", "seq": "123", "model": "modbus-light-1", "op": "send|send_ext|query", "addr": 5, "cmd": 160, "shortAddress": 1, "value": 180 } ``` `model`, `shortAddress`, and `value` are optional. If `model` is provided, the bridge resolves its mapped DALI target and default operation. Successful query responses may also include a `meta` object with decoded status flags. ### Uplink JSON Envelope ```json { "type": "dali_resp", "seq": "123", "op": "query", "ok": true, "data": 255 } ``` ### Usage ```cpp GatewayCloudConfig cfg; cfg.brokerURI = "mqtt://192.168.1.100:1883"; cfg.deviceID = "A1B2C3D4E5F6"; cfg.username = "device"; cfg.password = "A1B2C3D4E5F6"; DaliCloudBridge bridge(comm); if (bridge.start(cfg)) { bridge.publishStatus("online"); } ``` ### Provisioning via NVS Use `GatewayProvisioningStore` to persist cloud connection settings: ```cpp GatewayProvisioningStore store; GatewayCloudConfig cfg; cfg.brokerURI = "mqtt://192.168.1.100:1883"; cfg.deviceID = "A1B2C3D4E5F6"; cfg.username = "device"; cfg.password = "A1B2C3D4E5F6"; store.save(cfg); GatewayCloudConfig loaded; if (store.load(&loaded) == ESP_OK) { DaliCloudBridge bridge(comm); bridge.start(loaded); } ``` ## ESP32-S3 Example Project A standalone ESP-IDF example app is available at `examples/esp32s3_bridge/`. ### Environment Setup Source the helper script from your shell so the exported ESP-IDF variables stay in your session: ```bash . ./scripts/export_esp_idf.sh ``` The helper script sources `~/esp/v5.5.2/esp-idf/export.sh`. ### Build the Example ```bash cd examples/esp32s3_bridge idf.py set-target esp32s3 idf.py build ``` The example persists its bridge config in NVS, starts a real Modbus TCP listener, registers Modbus and BACnet bridge models, and routes Modbus writes through the shared bridge engine. The DALI gateway callbacks are still placeholders where you should connect your UART or transport driver.