- 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>
13 KiB
Gateway Modbus
The native gateway exposes each DALI channel as a Modbus server. Supported transports are:
tcp-server: Modbus TCP server, default TCP port1502.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_TCPGATEWAY_MODBUS_DEFAULT_TRANSPORT_RTUGATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCIIGATEWAY_MODBUS_TCP_PORTGATEWAY_MODBUS_UNIT_IDGATEWAY_MODBUS_SERIAL_UART_PORTGATEWAY_MODBUS_SERIAL_TX_PINGATEWAY_MODBUS_SERIAL_RX_PINGATEWAY_MODBUS_SERIAL_BAUDRATEGATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MSGATEWAY_MODBUS_SERIAL_RS485_ENABLEDGATEWAY_MODBUS_SERIAL_RS485_DE_PINGATEWAY_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:
{
"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:
@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=NGET /bridge?action=config&gw=NGET /bridge?action=modbus&gw=NPOST /bridge?action=config&gw=NPOST /bridge?action=modbus_start&gw=NPOST /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:
human = base + shortAddress * 32 + offset
wire = human - base
Example: short address 5 brightness is holding-register offset 0:
human = 40001 + 5 * 32 + 0 = 40161
wire = 40161 - 40001 = 160
The diagnostic discrete-input extension starts after the first 64 * 32 discrete inputs:
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):
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.objectTypemust becoil,discrete_input,input_register, orholding_register.external.registerAddressis the human address, not the zero-based wire offset.external.bitIndexexposes one bit from a numeric read result for boolean objects.- DALI targets use short addresses
0-63, groups as64 + group, and broadcast as127. - DALI query/read operations must target short addresses only.
valueTransformscaling, 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.