Files
gateway/MODBUS.md
Tony 34d2d9caa0 Add serial configuration support to Gateway Modbus
- 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>
2026-05-04 14:34:05 +08:00

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 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:

{
  "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=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:

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.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.