How to connect a Fronius Symo inverter to Kaa via Teltonika RUT router
Overview
In this tutorial, we will look at how to integrate a [Fronius Symo][fronius-symo] solar inverter with the Kaa IoT platform. You will learn how to poll the inverter over Modbus TCP from a [Teltonika RUT-series router][teltonika-networks], publish the readings to Kaa as time-series, and reach the equipment remotely afterwards.
A solar inverter does not speak MQTT and cannot reach the internet on its own. It exposes its measurements as [SunSpec][sunspec] Modbus registers on the local network and waits to be asked. So the router does the work: it polls the inverter on a timer, formats the readings as JSON, and publishes them to Kaa.
This is a different integration from the [Teltonika tracking device tutorial][connect-teltonika-to-kaa-platform], and it is worth knowing which one you need:
| Tracking devices (FMB series) | Inverters and meters (RUT series) | |
|---|---|---|
| Device sends | Codec 8 binary over raw UDP/TCP | Nothing — the router polls it |
| Gateway | Node-RED parses the binary | RutOS Data to Server, built in |
| Endpoint token | The device IMEI | A Kaa endpoint token |
The practical difference: no Node-RED is required here. RutOS reads the Modbus registers and builds the JSON itself, so the router publishes to Kaa directly.
NOTE: This tutorial was written against a Fronius Symo three-phase inverter and a RUT956 running RutOS 7.x. Any RUT-series router with the Modbus TCP Client and Data to Server services will work, and the register map applies to any Fronius exposing the SunSpec float profile.
Prerequisites
- A Fronius Symo inverter on the same local network as the router
- A Teltonika RUT-series router with internet access, running RutOS 7.x
- An account in the Kaa cloud
Playbook
Configure the inverter
In the inverter’s web interface, open Communication → Modbus and enable Modbus TCP.
Two settings decide everything that follows:
- Data type:
float. Fronius offers bothint + SFandfloat. They are different SunSpec models at different register addresses — model 103 and model 113 respectively. This tutorial uses float. Choosing the other one and keeping these addresses gives you nonsense. - Port 502 and unit ID 1, the defaults.
Then give the inverter a static IP address, set on the inverter itself, outside the router’s DHCP pool.
NOTE: Set the address on the inverter rather than as a DHCP reservation on the router. Modbus polling breaks silently if the address moves, and a reservation is one more thing lost in a router reset.
Configure the Kaa platform
Create an application and an endpoint in the Kaa cloud.
NOTE: Please use this tutorial for help.
Note two values:
- the application version name, for example
b2c3d4e5f6g7h8i9j0k1-v1 - the endpoint token Kaa issues
Unlike the tracking device tutorial, the token here is not derived from the hardware. Kaa issues it, and it is what authenticates everything the router publishes.
Configure the Modbus TCP Client
In the router web interface, go to Services → Modbus → Modbus TCP Client.
Add a server entry describing the inverter:
| Field | Value |
|---|---|
| Name | fronius |
| IP address | the inverter’s static address |
| Port | 502 |
| Server ID | 1 |
| Timeout | 5 s |
| Period | 120 s |
| Reconnect | enabled |
NOTE: The server Period is what actually determines your data resolution. The Data to Server period configured in the next step cannot produce points faster than the Modbus poll delivers them.
Then add one request per value. The request Name becomes the JSON key and therefore the time-series name in Kaa, so name them the way you want your data to read.
These are the values worth having from a Fronius Symo. All are function 3, read holding registers:
| Name | Register | Count | Data type | Unit |
|---|---|---|---|---|
ac_power |
40092 |
2 | 32bit_float1234 |
W |
ac_energy |
40102 |
2 | 32bit_float1234 |
Wh, lifetime |
ac_current |
40072 |
2 | 32bit_float1234 |
A |
phase_a_current |
40074 |
2 | 32bit_float1234 |
A |
phase_b_current |
40076 |
2 | 32bit_float1234 |
A |
phase_c_current |
40078 |
2 | 32bit_float1234 |
A |
voltage_an |
40086 |
2 | 32bit_float1234 |
V, phase to neutral |
voltage_ab |
40080 |
2 | 32bit_float1234 |
V, phase to phase |
line_frequency |
40094 |
2 | 32bit_float1234 |
Hz |
operating_state |
40118 |
1 | 16bit_uint_hi_first |
enum |
max_ac_current |
40144 |
1 | 16bit_uint_hi_first |
raw |
max_ac_current_sf |
40145 |
1 | 16bit_int_hi_first |
scale factor |
Registers 40072–40118 are SunSpec model 113, the three-phase inverter model, which starts at 40070. Registers 40132 upward are model 120, the nameplate ratings.
The complete 35-register configuration — including DC values, per-phase voltages, and the full model 120 nameplate block — is attached as [modbus_client.uci][rut-modbus-client-uci]. You can paste it into /etc/config/modbus_client over SSH instead of filling in the form 35 times.
Scale factors are not applied for you
Model 120 transmits ratings as a raw integer plus a separate scale-factor register, and expects the reader to multiply. max_ac_current reading 3470 with max_ac_current_sf reading -2 means:
3470 x 10^-2 = 34.70 A
RutOS does not do this multiplication. Both keys arrive in Kaa as raw numbers, and anything displaying max_ac_current directly shows 3470 A. Read both keys and apply the factor on the Kaa side.
The model 113 measurements do not have this problem — that is the point of using the float profile.
Two mistakes worth avoiding
These account for most “the numbers are wrong” reports. Both produce plausible-looking output rather than an error.
Off-by-one registers. SunSpec blocks are documented from the model header, but the header is not the first data register. Model 113 starts at 40070; its first measurement, ac_current, is at 40072. Counting from the header shifts every value by one register.
Wrong word order. A 32-bit float spans two registers and vendors disagree about which comes first. RutOS offers 32bit_float1234 and 32bit_float3412. Fronius uses 32bit_float1234. The wrong choice gives wild, unstable numbers that look like a hardware fault.
Configure Data to Server
Go to Services → Data to Server. Three objects work together.
Input — reads the Modbus client’s output:
| Field | Value |
|---|---|
| Plugin | modbus |
| Filter | all |
| Segments | at least your number of requests |
| Format | custom: "%name%":%{data:1:-1}% |
| Delimiter | , |
| N/A string | null |
NOTE: Leave the N/A string at its
N/Adefault and a failed register read emits a bareN/A, which is not valid JSON. One unreadable register can then invalidate the whole message. Usenull.
Output — publishes to Kaa over MQTT:
| Field | Value |
|---|---|
| Plugin | mqtt |
| Host | your Kaa MQTT host, for example mqtt.cloud.kaaiot.com |
| Port | 8883 with TLS |
| Topic | kp1/{application-version}/dcx/{endpoint-token}/json/1 |

The topic is the whole integration:
kp1 / b2c3d4e5f6g7h8i9j0k1-v1 / dcx / YOUR_ENDPOINT_TOKEN / json / 1
└─ application version └─ token from the Kaa step
dcx is the data collection service, which stores time-series. This is the same DCX service the tracking device tutorial publishes to; the difference is that RutOS builds the payload instead of a Node-RED flow.
NOTE: If the application version or the token is wrong, the router publishes successfully into nothing. There is no error on either side. Check the topic first when data does not arrive.
NOTE: The collection’s format string references the input by name. Rename the input and you must update the format string with it, or the payload silently becomes empty.
A working configuration is attached: [data_sender.uci][rut-data-sender-uci].
Sending metadata as well
The setup above sends time-series only. To also set endpoint metadata — a name, a site, a location — publish to the EPMX service:
kp1/{application-version}/epmx/{endpoint-token}/update/keys
Add a second output and collection for it. Metadata is usually worth sending once at startup rather than on every poll.
Verify
Work down the chain. Each step isolates a different failure.
1. The inverter answers. The Modbus TCP Client page shows values rather than timeouts.
2. The values are real. Check them against physics before trusting anything. On a three-phase inverter, phase voltage × current should approximate the reported power:
230.6 V x 77.86 A = 17 954 W reported ac_power: 17 859 W
Within a few percent, so the register addresses and the word order are both correct. If this check fails, revisit the two mistakes above before looking anywhere else.
The three phase currents should also sum to ac_current, and be near-identical on a healthy installation.
3. Data arrives in Kaa. The endpoint shows as connected and time-series appear.

NOTE: Expect one point per Modbus poll period, not per Data to Server period. A 60 s collection against a 120 s poll produces a point every 120 s, not two copies of each.
What the inverter does at night
Worth knowing before you build alerts on any of this.
When the sun goes down the inverter stops generating, but it keeps answering Modbus. On the unit this tutorial was written against, every measurement then reads 0 — voltage, current, frequency and power alike — and operating_state reports 7, which the SunSpec table calls FAULT.
The reporting interval also slows: roughly every two minutes overnight against ten seconds in daylight.
So a rule that alerts on operating_state == 7, or on frequency outside 49.5–50.5 Hz, will fire every single sunset and clear every sunrise. Build conditions on measured quantities instead. Grid live and no power is a real fault and holds regardless of what the state enum means:
line_frequency >= 45 AND ac_power == 0 for 15 minutes
Remote access
Modbus polling is one-way: the router pushes readings out, and nothing reaches the inverter through that path. To administer the equipment later, you need a route in.
Kaa’s remote access service provides one without a public IP, an inbound firewall rule or a port forward. The router dials out to the VPN service, and Kaa reverse-proxies your browser into that tunnel.
Go to Services → VPN → OpenVPN, add a client instance, and upload the .ovpn profile from your Kaa tenant. The profile carries the remote host, the port and the client certificate. Once connected, Status → Overview shows the VPN panel with a tunnel address.

To reach the inverter behind the router rather than the router itself, the firewall needs a forwarding rule from the OpenVPN zone to the LAN zone. Without it you reach the router and nothing past it.
With that in place you can port-forward the inverter’s own web interface over SSH:
ssh -L 8080:INVERTER_LAN_IP:80 root@ROUTER_VPN_IP
and browse http://localhost:8080.
NOTE: A router configured this way has no inbound exposure at all. This is strongly preferable to port-forwarding a Modbus device, which should never be reachable from the internet.
Congratulations, you have connected a Fronius inverter to Kaa over Modbus TCP and can reach it remotely!
Troubleshooting
No data in Kaa, no errors anywhere. Almost always the MQTT topic. Check the application version and the endpoint token character by character.
Values are wrong but stable. Register offset — you are probably counting from the SunSpec model header. Shift by one.
Values are wild and jump erratically. Word order. Switch between 32bit_float1234 and 32bit_float3412.
Values are huge round numbers. A raw model 120 rating with its scale factor unapplied. See Scale factors are not applied for you.
Some values are 65535 or -32768. These are the SunSpec “not implemented” markers, not readings. A PV-only inverter reports them for the storage and reactive-power fields it does not have. Drop them rather than charting them.
Everything reads 0 and the state is 7. Check the time of day before anything else. See What the inverter does at night.
Occasional missing batches. Check the input’s N/A string, as above.
Data stops and does not resume. Check WAN failover. A router with MultiWAN in failover mode but every member disabled has no fallback, and a WAN outage becomes a silent gap.