34d2d9caa0
- Introduced GatewayModbusSerialConfig structure to encapsulate serial communication settings. - Added clamping functions for integer and size values to ensure valid configuration ranges. - Updated GatewayModbusConfigFromValue to parse serial configuration from JSON input. - Implemented transport type checking functions for TCP, RTU, ASCII, and Serial. - Enhanced GatewayModbusConfigToValue to include serial configuration in output. Signed-off-by: Tony <tonylu@tony-cloud.com>
361 lines
13 KiB
Markdown
361 lines
13 KiB
Markdown
# Gateway Modbus
|
|
|
|
The native gateway exposes each DALI channel as a Modbus server. Supported transports are:
|
|
|
|
- `tcp-server`: Modbus TCP server, default TCP port `1502`.
|
|
- `rtu-server`: Modbus RTU slave/server on an ESP-IDF UART.
|
|
- `ascii-server`: Modbus ASCII slave/server on an ESP-IDF UART.
|
|
|
|
The generated Modbus map is identical for TCP, RTU, and ASCII. Only the frame transport changes.
|
|
|
|
## Configuration
|
|
|
|
Compile-time defaults live in `apps/gateway/main/Kconfig.projbuild`:
|
|
|
|
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP`
|
|
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU`
|
|
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII`
|
|
- `GATEWAY_MODBUS_TCP_PORT`
|
|
- `GATEWAY_MODBUS_UNIT_ID`
|
|
- `GATEWAY_MODBUS_SERIAL_UART_PORT`
|
|
- `GATEWAY_MODBUS_SERIAL_TX_PIN`
|
|
- `GATEWAY_MODBUS_SERIAL_RX_PIN`
|
|
- `GATEWAY_MODBUS_SERIAL_BAUDRATE`
|
|
- `GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS`
|
|
- `GATEWAY_MODBUS_SERIAL_RS485_ENABLED`
|
|
- `GATEWAY_MODBUS_SERIAL_RS485_DE_PIN`
|
|
- `GATEWAY_MODBUS_ALLOW_UART0`
|
|
|
|
UART0 is reserved for the ESP-IDF console by default. Select `GATEWAY_MODBUS_ALLOW_UART0` only when the console has been moved away from UART0 or the deployment intentionally repurposes it. The app validates the Kconfig default UART against DALI serial PHY assignments at boot.
|
|
|
|
Runtime bridge config keeps the existing top-level `modbus` object. The serial fields are nested under `serial`:
|
|
|
|
```json
|
|
{
|
|
"modbus": {
|
|
"transport": "rtu-server",
|
|
"unitID": 1,
|
|
"port": 1502,
|
|
"serial": {
|
|
"uartPort": 1,
|
|
"txPin": 17,
|
|
"rxPin": 18,
|
|
"baudrate": 9600,
|
|
"dataBits": 8,
|
|
"parity": "none",
|
|
"stopBits": 1,
|
|
"rxBufferBytes": 512,
|
|
"txBufferBytes": 512,
|
|
"responseTimeoutMs": 20,
|
|
"interFrameGapUs": 4000,
|
|
"rs485": {
|
|
"enabled": true,
|
|
"dePin": 16
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
`unitID` accepts aliases `unitId` and `unit_id`. A configured `unitID` of `0` keeps the gateway compatibility behavior where incoming unit ids are accepted as a wildcard. Incoming serial requests addressed to unit id `0` are treated as Modbus broadcast and do not generate a response.
|
|
|
|
## UART Management Commands
|
|
|
|
When a Modbus serial transport is active, the UART also accepts management lines before Modbus frame parsing. A management line starts with `@DALIGW `, contains JSON, and ends with newline.
|
|
|
|
Examples:
|
|
|
|
```text
|
|
@DALIGW {"action":"modbus_status","gw":3}
|
|
@DALIGW {"action":"modbus_stop","gw":3}
|
|
@DALIGW {"action":"modbus_config","gw":3,"modbus":{"transport":"ascii-server","unitID":1,"serial":{"uartPort":1,"txPin":17,"rxPin":18,"baudrate":9600,"rs485":{"enabled":true,"dePin":16}}}}
|
|
```
|
|
|
|
Responses are newline-terminated `@DALIGW` JSON lines. A successful `modbus_config` command is saved to NVS and restarts the Modbus transport after the response is written.
|
|
|
|
HTTP still supports the bridge routes:
|
|
|
|
- `GET /bridge?action=status&gw=N`
|
|
- `GET /bridge?action=config&gw=N`
|
|
- `GET /bridge?action=modbus&gw=N`
|
|
- `POST /bridge?action=config&gw=N`
|
|
- `POST /bridge?action=modbus_start&gw=N`
|
|
- `POST /bridge?action=modbus_stop&gw=N`
|
|
|
|
## Function Codes
|
|
|
|
Supported function codes are:
|
|
|
|
| Code | Name | Map space |
|
|
| --- | --- | --- |
|
|
| `0x01` | Read coils | Coils |
|
|
| `0x02` | Read discrete inputs | Discrete inputs |
|
|
| `0x03` | Read holding registers | Holding registers |
|
|
| `0x04` | Read input registers | Input registers |
|
|
| `0x05` | Write single coil | Coils |
|
|
| `0x06` | Write single holding register | Holding registers |
|
|
| `0x0F` | Write multiple coils | Coils |
|
|
| `0x10` | Write multiple holding registers | Holding registers |
|
|
|
|
Limits are `2000` read bits, `125` read registers, `1968` write bits, `123` write registers, and `252` PDU bytes.
|
|
|
|
Exception codes used by the gateway:
|
|
|
|
| Code | Meaning |
|
|
| --- | --- |
|
|
| `0x01` | Unsupported function |
|
|
| `0x02` | Unmapped address or read failure |
|
|
| `0x03` | Invalid quantity, value, or request shape |
|
|
| `0x04` | DALI execution failure |
|
|
| `0x0B` | Unit id mismatch |
|
|
|
|
## Address Calculation
|
|
|
|
Modbus protocol frames use zero-based wire offsets. Most tools show human addresses with traditional bases:
|
|
|
|
| Space | Human base | Function codes |
|
|
| --- | ---: | --- |
|
|
| Coil | `1` | `0x01`, `0x05`, `0x0F` |
|
|
| Discrete input | `10001` | `0x02` |
|
|
| Input register | `30001` | `0x04` |
|
|
| Holding register | `40001` | `0x03`, `0x06`, `0x10` |
|
|
|
|
For the main generated slice, every DALI short address `0-63` gets a stride of `32` points in each space:
|
|
|
|
```text
|
|
human = base + shortAddress * 32 + offset
|
|
wire = human - base
|
|
```
|
|
|
|
Example: short address `5` brightness is holding-register offset `0`:
|
|
|
|
```text
|
|
human = 40001 + 5 * 32 + 0 = 40161
|
|
wire = 40161 - 40001 = 160
|
|
```
|
|
|
|
The diagnostic discrete-input extension starts after the first `64 * 32` discrete inputs:
|
|
|
|
```text
|
|
diagnosticBase = 10001 + 64 * 32 = 12049
|
|
human = 12049 + shortAddress * 128 + diagnosticOffset
|
|
wire = human - 10001
|
|
```
|
|
|
|
Example: short address `5`, diagnostic offset `105` (`DT8 xy out of range`):
|
|
|
|
```text
|
|
human = 12049 + 5 * 128 + 105 = 12794
|
|
wire = 12794 - 10001 = 2793
|
|
```
|
|
|
|
## Main Generated Map
|
|
|
|
Generated points exist for all short addresses `0-63`, even when no device has been discovered. Unknown numeric values read as `0xFFFF`; unknown booleans read as false unless inventory or cache state proves otherwise. Generated reads prefer gateway cache state and do not poll the DALI bus for every Modbus read.
|
|
|
|
### Coils
|
|
|
|
| Offset | Address formula | Access | Function |
|
|
| ---: | --- | --- | --- |
|
|
| `0` | `1 + short * 32 + 0` | Write | On / recall max |
|
|
| `1` | `1 + short * 32 + 1` | Write | Off |
|
|
| `2` | `1 + short * 32 + 2` | Write | Recall max |
|
|
| `3` | `1 + short * 32 + 3` | Write | Recall min |
|
|
|
|
### Discrete Inputs
|
|
|
|
| Offset | Address formula | Function |
|
|
| ---: | --- | --- |
|
|
| `0` | `10001 + short * 32 + 0` | Discovered |
|
|
| `1` | `10001 + short * 32 + 1` | Online |
|
|
| `2` | `10001 + short * 32 + 2` | Supports DT1 |
|
|
| `3` | `10001 + short * 32 + 3` | Supports DT4 |
|
|
| `4` | `10001 + short * 32 + 4` | Supports DT5 |
|
|
| `5` | `10001 + short * 32 + 5` | Supports DT6 |
|
|
| `6` | `10001 + short * 32 + 6` | Supports DT8 |
|
|
| `7` | `10001 + short * 32 + 7` | Group mask known |
|
|
| `8` | `10001 + short * 32 + 8` | Actual level known |
|
|
| `9` | `10001 + short * 32 + 9` | Scene known |
|
|
| `10` | `10001 + short * 32 + 10` | Settings known |
|
|
| `16` | `10001 + short * 32 + 16` | Control gear present |
|
|
| `17` | `10001 + short * 32 + 17` | Lamp failure |
|
|
| `18` | `10001 + short * 32 + 18` | Lamp power on |
|
|
| `19` | `10001 + short * 32 + 19` | Limit error |
|
|
| `20` | `10001 + short * 32 + 20` | Fading completed |
|
|
| `21` | `10001 + short * 32 + 21` | Reset state |
|
|
| `22` | `10001 + short * 32 + 22` | Missing short address |
|
|
| `23` | `10001 + short * 32 + 23` | Power supply fault |
|
|
|
|
### Holding Registers
|
|
|
|
| Offset | Address formula | Access | Function |
|
|
| ---: | --- | --- | --- |
|
|
| `0` | `40001 + short * 32 + 0` | Read/write | Brightness |
|
|
| `1` | `40001 + short * 32 + 1` | Write, cache-read if known | Color temperature |
|
|
| `2` | `40001 + short * 32 + 2` | Read/write | Group mask |
|
|
| `3` | `40001 + short * 32 + 3` | Read/write | Power-on level |
|
|
| `4` | `40001 + short * 32 + 4` | Read/write | System-failure level |
|
|
| `5` | `40001 + short * 32 + 5` | Read/write | Minimum level |
|
|
| `6` | `40001 + short * 32 + 6` | Read/write | Maximum level |
|
|
| `7` | `40001 + short * 32 + 7` | Read/write | Fade time |
|
|
| `8` | `40001 + short * 32 + 8` | Read/write | Fade rate |
|
|
|
|
### Input Registers
|
|
|
|
| Offset | Address formula | Function |
|
|
| ---: | --- | --- |
|
|
| `0` | `30001 + short * 32 + 0` | Inventory state: `0` never seen, `1` offline, `2` online |
|
|
| `1` | `30001 + short * 32 + 1` | Primary DALI device type |
|
|
| `2` | `30001 + short * 32 + 2` | Device type mask |
|
|
| `3` | `30001 + short * 32 + 3` | Actual level |
|
|
| `4` | `30001 + short * 32 + 4` | Scene id |
|
|
| `5` | `30001 + short * 32 + 5` | Raw status |
|
|
| `6` | `30001 + short * 32 + 6` | Group mask |
|
|
| `7` | `30001 + short * 32 + 7` | Power-on level |
|
|
| `8` | `30001 + short * 32 + 8` | System-failure level |
|
|
| `9` | `30001 + short * 32 + 9` | Minimum level |
|
|
| `10` | `30001 + short * 32 + 10` | Maximum level |
|
|
| `11` | `30001 + short * 32 + 11` | Fade time |
|
|
| `12` | `30001 + short * 32 + 12` | Fade rate |
|
|
|
|
## Diagnostic Discrete Inputs
|
|
|
|
Diagnostic addresses use `12049 + short * 128 + offset`.
|
|
|
|
| Offset | Function |
|
|
| ---: | --- |
|
|
| `0` | DT1 circuit failure |
|
|
| `1` | DT1 battery duration failure |
|
|
| `2` | DT1 battery failure |
|
|
| `3` | DT1 emergency lamp failure |
|
|
| `4` | DT1 function test max delay exceeded |
|
|
| `5` | DT1 duration test max delay exceeded |
|
|
| `6` | DT1 function test failed |
|
|
| `7` | DT1 duration test failed |
|
|
| `8` | DT1 inhibit mode |
|
|
| `9` | DT1 function test result valid |
|
|
| `10` | DT1 duration test result valid |
|
|
| `11` | DT1 battery fully charged |
|
|
| `12` | DT1 function test request pending |
|
|
| `13` | DT1 duration test request pending |
|
|
| `14` | DT1 identification active |
|
|
| `15` | DT1 physically selected |
|
|
| `16` | DT1 rest mode active |
|
|
| `17` | DT1 normal mode active |
|
|
| `18` | DT1 emergency mode active |
|
|
| `19` | DT1 extended emergency mode active |
|
|
| `20` | DT1 function test in progress |
|
|
| `21` | DT1 duration test in progress |
|
|
| `22` | DT1 hardwired inhibit active |
|
|
| `23` | DT1 hardwired switch on |
|
|
| `24` | DT1 integral emergency gear |
|
|
| `25` | DT1 maintained gear |
|
|
| `26` | DT1 switched maintained gear |
|
|
| `27` | DT1 auto test capability |
|
|
| `28` | DT1 adjustable emergency level |
|
|
| `29` | DT1 hardwired inhibit supported |
|
|
| `30` | DT1 physical selection supported |
|
|
| `31` | DT1 relight in rest mode supported |
|
|
| `32` | DT4 leading edge running |
|
|
| `33` | DT4 trailing edge running |
|
|
| `34` | DT4 reference measurement running |
|
|
| `35` | DT4 non-log curve active |
|
|
| `36` | DT4 can query load over-current shutdown |
|
|
| `37` | DT4 can query open circuit |
|
|
| `38` | DT4 can query load decrease |
|
|
| `39` | DT4 can query load increase |
|
|
| `40` | DT4 can query thermal shutdown |
|
|
| `41` | DT4 can query thermal overload |
|
|
| `42` | DT4 physical selection supported |
|
|
| `43` | DT4 can query temperature |
|
|
| `44` | DT4 can query supply voltage |
|
|
| `45` | DT4 can query supply frequency |
|
|
| `46` | DT4 can query load voltage |
|
|
| `47` | DT4 can query load current |
|
|
| `48` | DT4 can query real load power |
|
|
| `49` | DT4 can query load rating |
|
|
| `50` | DT4 can query current overload |
|
|
| `51` | DT4 can select non-log curve |
|
|
| `52` | DT4 can query unsuitable load |
|
|
| `53` | DT4 load over-current shutdown |
|
|
| `54` | DT4 open circuit detected |
|
|
| `55` | DT4 load decrease detected |
|
|
| `56` | DT4 load increase detected |
|
|
| `57` | DT4 thermal shutdown |
|
|
| `58` | DT4 thermal overload reduction |
|
|
| `59` | DT4 reference failed |
|
|
| `60` | DT4 unsuitable load |
|
|
| `61` | DT4 supply voltage out of limits |
|
|
| `62` | DT4 supply frequency out of limits |
|
|
| `63` | DT4 load voltage out of limits |
|
|
| `64` | DT4 load current overload |
|
|
| `65` | DT5 output range selectable |
|
|
| `66` | DT5 pull-up selectable |
|
|
| `67` | DT5 fault detection selectable |
|
|
| `68` | DT5 mains relay |
|
|
| `69` | DT5 output level queryable |
|
|
| `70` | DT5 non-log curve supported |
|
|
| `71` | DT5 output-loss selection supported |
|
|
| `72` | DT5 selection switch supported |
|
|
| `73` | DT5 output fault detected |
|
|
| `74` | DT5 0-10V operation |
|
|
| `75` | DT5 pull-up on |
|
|
| `76` | DT5 non-log curve active |
|
|
| `77` | DT6 power supply integrated |
|
|
| `78` | DT6 LED module integrated |
|
|
| `79` | DT6 AC supply possible |
|
|
| `80` | DT6 DC supply possible |
|
|
| `81` | DT6 PWM possible |
|
|
| `82` | DT6 AM possible |
|
|
| `83` | DT6 current control possible |
|
|
| `84` | DT6 high current possible |
|
|
| `85` | DT6 can query short circuit |
|
|
| `86` | DT6 can query open circuit |
|
|
| `87` | DT6 can query load decrease |
|
|
| `88` | DT6 can query load increase |
|
|
| `89` | DT6 can query current protector |
|
|
| `90` | DT6 can query thermal shutdown |
|
|
| `91` | DT6 can query thermal overload |
|
|
| `92` | DT6 short circuit |
|
|
| `93` | DT6 open circuit |
|
|
| `94` | DT6 load decrease |
|
|
| `95` | DT6 load increase |
|
|
| `96` | DT6 current protector active |
|
|
| `97` | DT6 thermal shutdown |
|
|
| `98` | DT6 thermal overload |
|
|
| `99` | DT6 reference failed |
|
|
| `100` | DT6 PWM active |
|
|
| `101` | DT6 AM active |
|
|
| `102` | DT6 current controlled output |
|
|
| `103` | DT6 high current active |
|
|
| `104` | DT6 non-log curve active |
|
|
| `105` | DT8 xy out of range |
|
|
| `106` | DT8 color-temperature out of range |
|
|
| `107` | DT8 auto calibration active |
|
|
| `108` | DT8 auto calibration success |
|
|
| `109` | DT8 xy active |
|
|
| `110` | DT8 color-temperature active |
|
|
| `111` | DT8 primary-N active |
|
|
| `112` | DT8 RGBWAF active |
|
|
| `113` | DT8 xy capable |
|
|
| `114` | DT8 color-temperature capable |
|
|
| `115` | DT8 primary-N capable |
|
|
| `116` | DT8 RGBWAF capable |
|
|
| `117` | DT6 physical selection supported |
|
|
| `118` | DT6 current protector enabled |
|
|
| `119` | DT1 control gear failure |
|
|
|
|
## Provisioned Overrides
|
|
|
|
Provisioned bridge models are applied after the generated map. If a Modbus model uses the same space and human address as a generated point, the model replaces that generated point.
|
|
|
|
For Modbus models:
|
|
|
|
- `external.objectType` must be `coil`, `discrete_input`, `input_register`, or `holding_register`.
|
|
- `external.registerAddress` is the human address, not the zero-based wire offset.
|
|
- `external.bitIndex` exposes one bit from a numeric read result for boolean objects.
|
|
- DALI targets use short addresses `0-63`, groups as `64 + group`, and broadcast as `127`.
|
|
- DALI query/read operations must target short addresses only.
|
|
- `valueTransform` scaling, offset, rounding, and clamps are applied by the bridge engine.
|
|
|
|
Use `GET /bridge?action=modbus&gw=N` to inspect the effective generated and provisioned bindings served by a running gateway. |