Modbus TCP Quickstart Guide
Supplement to the Modbus TCP register documentation. It covers what the station expects from an EMS before charging control over Modbus TCP behaves as expected, and the write sequences we test against. Matches the firmware behavior as of July 2026.
Addressing
Register addresses appear in two notations, and Modbus tools disagree about which one they expect. This guide always gives both:
- the register number (40202, 30027, ...) - the conventional 1-based notation used in the register tables, and
- the PDU address - the value that actually goes on the wire: register number minus 40001 for holding registers, minus 30001 for input registers.
Holding registers (4xxxx) are read with FC03 and written with FC06/FC16. Input registers (3xxxx) are read-only, FC04. If your tool shows an off-by-one, it is almost always mixing up these two notations. A value that looks legal but is rejected is usually the other client-side mismatch - see Value encoding below.
Registers used in this guide:
| Name | Register | PDU (dec) | PDU (hex) | Access |
|---|---|---|---|---|
| ChargeMode | 40107 | 106 | 0x006A | FC03 read, FC06/16 write |
| Set_Charge_Current_Target | 40202 | 201 | 0x00C9 | FC03 read, FC06/16 write |
| Start_Stop_Charging | 40203 | 202 | 0x00CA | FC03 read, FC06/16 write |
| Failsafe_Timeout | 40204 | 203 | 0x00CB | FC03 read, FC06/16 write |
| Failsafe_Current_Target | 40206 | 205 | 0x00CD | FC03 read, FC06/16 write |
| Heartbeat | 40208 | 207 | 0x00CF | FC06/16 write |
| EVCS_State | 30001 | 0 | 0x0000 | FC04 |
| ErrorCode | 30004 | 3 | 0x0003 | FC04 |
| Failsafe_Active | 30027 | 26 | 0x001A | FC04 |
| DaemonStatus | 30050 | 49 | 0x0031 | FC04 |
| Availability | 30101 | 100 | 0x0064 | FC04 |
| OfferedCurrentLimit | 30105 | 104 | 0x0068 | FC04 |
| EV_Present | 30201 | 200 | 0x00C8 | FC04 |
| Charging | 30202 | 201 | 0x00C9 | FC04 |
| CP_Status | 30203 | 202 | 0x00CA | FC04 |
Value encoding
Modbus is big-endian and this gateway follows that exactly, in both directions:
- Byte order: every 16-bit register is transmitted most significant byte
first. A current target of 10 A goes on the wire as
00 0A, never0A 00. - Word order: multi-register values carry the high word at the lower register address. This matters only for the f32 and u32 telemetry registers - every writable control register in this guide is a single u16.
Some clients expose both as per-object settings instead of deriving them from the protocol. Where that is the case, every object needs both set to big-endian, and telemetry that decodes correctly is no evidence that the write objects are set the same way. Word order is harmless for the control registers, being single-register values; byte order is not. A byte-swapped value still arrives, silently transformed: a target of 10 arrives as 2560 and a target of 8 arrives as 2048, both far outside the accepted range, so the write is rejected with exception 0x03 while the value on your screen looks perfectly legal.
The signature is that every current target is rejected whatever value you pick, with 0 the only one that works - a byte swap leaves 0 unchanged. If you cannot see the setting in your client, send 2560 as the current target: byte swapped that is 10, so an accepted write and a charger at 10 A confirm the swap. Disable any scaling or correction table in the client for that test.
The short version
-
Plug in the vehicle. EV_Present 30201 (PDU 200) must read 1, CP_Status 30203 (PDU 202) must be 1, 2 or 3 (IEC 61851 states B/C/D).
-
Switch the charger to manual charge mode: write ChargeMode 40107 (PDU 106) = 2. Do this before writing any target - every mode change resets the current limit to the configured maximum.
-
Start charging with one FC16 frame that writes Set_Charge_Current_Target 40202 (PDU 201) = 6..32 A and Start_Stop_Charging 40203 (PDU 202) = 1 together:
Code -
Keep the session alive. The failsafe watchdog is enabled by default with a 20 s timeout, so write Heartbeat 40208 (PDU 207, any value) at least every 10 s, or rewrite 40202 at that rate.
-
To change the current, write 40202 again. The charger follows within a few seconds; the telemetry registers update at up to 5 s intervals.
-
To stop, write 40203 = 0 (or 40202 = 0).
Send requests to the configured Modbus unit ID (default 1; visible in the web UI). On a master with linked slave boxes, each slave charger answers on its own unit ID behind the master's IP address.
Manual mode, and why the other modes fight you
The Salia has three charge modes: eco (0), power (1) and manual (2). Only manual hands the current limit over to an external controller.
In eco and power mode the internal charge control runs every 3 seconds
(charge_control_interval in the station configuration, default 3.0) and
writes its own result to the same limit channel the Modbus gateway writes to:
- power mode raises the limit by roughly 1 A per tick until it reaches the configured maximum. While a vehicle is connected but not yet charging, it sets the limit straight to the maximum on every tick.
- eco mode moves the limit up or down by 1 A per tick, following the solar surplus. The wandering current in eco mode is the intended behavior, not a fault.
A target written over Modbus therefore survives at most until the next tick. In manual mode the internal control leaves the limit channel alone and the Modbus value stays in force.
Related points worth knowing:
- The mode is persisted by the charger, so 40107 = 2 survives reboots. Solar and eco logic stay off until somebody switches the mode back (web UI or register).
- Every mode change resets the current limit: switching to power or manual sets it to the configured maximum, switching to eco during an active charge sets it to the minimum. Write the mode first, then the target - and after any later mode change, write 40202 again.
- Switching to power mode also disables Eco+ and, on stations with phase switching fitted, forces 3-phase operation.
- The authorization mode (free / RFID / OCPP / key-switch) is a station setting; there is no Modbus register for it (register 30022 / PDU 21 only reports it). On stations run against an OCPP backend the backend can authorize/deauthorize sessions and impose its own limits on the same charger, so for integration tests it is easier to set the authorization mode to "free" in the web UI. A Modbus start (40203 = 1) sets the session authorization on the station by itself.
Other things that can move the current, in any mode
These act even in manual mode. They are intentional and not Modbus errors:
- Mains fuse protection. On installations with a mains meter, the charge control lowers the limit or pauses charging when the building supply runs into its fuse limit. When such a pause is lifted, the limit is set back to the configured maximum - write 40202 again afterwards.
- Load management and grid limit. Linked chargers share a current budget, and the grid operator limit sits on its own channel. The current actually offered to the vehicle is bounded by the lowest active channel, so a second charging vehicle or a grid constraint can pull the delivered current below your target at any time. Requesting 32 A on a station limited to 16 A yields 16 A - that is a clamp, not an error.
The old heartbeat flag (not the Modbus failsafe)
The Salia also has an older, separate heartbeat feature: the heartbeat flag
in the station configuration (saliaconf), toggled over the MQTT topic
port0/salia/heartbeat. When this flag is set and no message has arrived on
that topic for more than 60 s, the charge control forces the current limit to
the configured minimum on every 3 s tick while a vehicle is charging - in
every charge mode, manual included. The Modbus gateway never publishes that
topic.
On a Modbus-controlled station this flag must be false. We have seen stations in the field with it enabled; the telltale sign is the current dipping to exactly the station minimum (typically 6 A) every few seconds.
Checklist before debugging registers
| Check | Where | Expected |
|---|---|---|
| Vehicle plugged | 30201 / PDU 200, 30203 / PDU 202 | 1; CP in B/C/D |
| Station operative | 30101 / PDU 100 | 1 |
| No active fault | 30004 / PDU 3 | 0 (7 means failsafe is active) |
| Charge mode | 40107 / PDU 106 | 2 (manual) |
| Charging commanded | 40203 / PDU 202 | 1 (resets to 0 on restart) |
| Current target | 40202 / PDU 201 | 6..32 (0 = stop) |
| Daemon ready | 30050 / PDU 49, bit 0 | 1 (requests during startup return 0x06) |
| Old heartbeat flag off | saliaconf heartbeat | false (web/service access) |
Pitfalls
- Writing 40202 alone does nothing while 40203 = 0. The gateway then commands 0 A to the charger no matter what target you wrote. Always start with the 40202 + 40203 pair in one FC16 frame.
- 40202 and 40203 are volatile. After a daemon restart or a reboot of the box they are back at 0, and the EMS has to send the start sequence again.
- The failsafe watchdog ships enabled: Failsafe_Timeout 40204 (PDU 203) defaults to 20 s, Failsafe_Current_Target 40206 (PDU 205) defaults to 0 A. If no accepted control write (40202, 40203, 40208, 40209) arrives within the timeout, the charger falls back to the failsafe current, ErrorCode 30004 reads 7 and Failsafe_Active 30027 (PDU 26) reads 1. The next accepted write restores your target. An EMS that writes once a minute will see the current bounce between its target and the fallback. Either send heartbeats at half the timeout, set 40204/40206 to values that fit your control cycle (10..3600 s, 0 or 6..32 A), or disable the watchdog with 40204 = 0. On an idle station with no EMS traffic, ErrorCode 7 with Failsafe_Active 1 is the normal resting state, not a defect.
- The target is a request, not a guarantee. See the limits section above; read OfferedCurrentLimit 30105 (PDU 104), CurrentOffered 30106+ and the meter currents 31009+ (PDU 1008+) to see what is actually offered and drawn.
- Set_Charge_Power_Target 40201 (PDU 200) is not implemented. Writes are accepted and stored but have no effect. Use the current target 40202.
- 30202 ("Charging", PDU 201, FC04) is a read-only status flag and cannot be written. It is easy to confuse with 40202 - same trailing digits, different register space.
- The 40107 read-back can go stale: it is set at daemon start and by EMS writes, but a mode change made later in the web UI is not mirrored back into the register. When in doubt, the web UI shows the mode actually in force.
Symptoms and causes
A useful shortcut: look at the value the current jumps to - it usually tells you which mechanism is involved.
| Symptom | Likely cause |
|---|---|
| Write to 40202 accepted, nothing charges | 40203 = 0, no EV / CP = A, or ErrorCode != 0 |
| Current creeps back toward maximum, ~1 A every 3 s | Charge mode is power -> set 40107 = 2 |
| Current wanders ±1 A around a solar-driven value | Charge mode is eco (by design) -> manual for EMS control |
| Current drops to the failsafe value (default 0 A) ~20 s after the last write, ErrorCode = 7 | Failsafe watchdog tripped -> heartbeat faster, or adjust/disable 40204 |
| Current dips to exactly the station minimum every few seconds | saliaconf heartbeat flag is true -> set it to false |
| Current jumps to maximum right after a mode change | Mode-change limit reset -> write 40202 again after changing 40107 |
| Current lower than target while a linked box also charges | Load-management budget split (not an error) |
| Every write to 40202 rejected whatever the value, only 0 accepted | Client byte order is not big-endian -> see Value encoding |
| Exception 0x03 on write | Value outside 0 / 6..32 (station config may narrow the range). If the value you sent is inside the range, suspect the client byte order - a swapped 10 arrives as 2560 |
| Exception 0x04 when starting (40203 = 1) | Current target 40202 is still 0 - write the target first, best both in one FC16 frame |
| Exception 0x06 | Rate limit (50 requests/s per IP) or daemon still starting (check 30050 bit 0) |
| Worked before a reboot, never resumed after | Volatile 40202/40203 are back at 0 -> send the start sequence again |
Verifying a session
After starting, poll (FC04 unless noted):
- 30001 EVCS_State (PDU 0) = 2 (CHARGING) and 30202 Charging (PDU 201) = 1
- 30004 ErrorCode (PDU 3) = 0 and 30027 Failsafe_Active (PDU 26) = 0
- 30105 OfferedCurrentLimit (PDU 104) and 30106+ CurrentOffered = your target, or the station clamp
- 31009+ meter currents (PDU 1008+) = what the vehicle actually draws; the vehicle drawing less than offered is normal
If what you see does not match this guide, send us the firmware version together with the Salia localhost/api values and we will take a look.

