SolarEdge firmware is known to silently drop the Modbus TCP listener (port refuses connections while still shown as enabled). Write a connection_state=1 point on first failed poll and 0 on recovery so the outage is visible in Grafana instead of just a data gap. ConnectionException is re-raised from read() - it is fatal for the whole cycle, not a flaky single block. Marker points carry no tags (registers unreadable during outage), so the tile query skips the serial filter. Also includes threshold tweaks on the power stat tiles.
Usage
Installation
The easiest way this is to run the docker container and set it to restart always. This basically makes it a system service that will always run.
To make it safe and avoid passing credentials we're using a configuration file.
mkdir /etc/solaredge_modbus_mqtt
chmod 700 /etc/solaredge_modbus_mqtt
cp config.yaml-dist /etc/solaredge_modbus_mqtt/config.yaml
docker run --detach --name=solaredge_modbus_mqtt --restart=always --volume=/etc/solaredge_modbus_mqtt/config.yaml:/app/config.yaml marijnkoesen/solaredge_modbus_mqtt:latest
Configuration
modbus:
host: 192.168.178.28 # inverter (SunSpec over Modbus TCP)
port: 1502
timeout: 1
unit: 1
influx:
url: http://influx.example.com:8086
db: solaredge
user: ~ # optional basic-auth user with WRITE on the db
pass: ~
measurement: solaredge
poll_interval: 3
The daemon polls the inverter and writes one line-protocol point per cycle directly
to InfluxDB. If InfluxDB is unreachable, points are spooled to spool.txt and
replayed on the next successful write (capped at 20000 lines). Mount a volume for
/app/spool.txt if backlog across container restarts matters.
model and serialnumber are written as tags; everything else as fields.
Off-grid / fail-over
offgrid— boolean, true while the inverter runs islanded (off-grid) — vendor event biterror_code— inverter vendor error bitmask (0 = none), LCD-style codesstorage_control_mode_text— e.g.Backup Only,Maximize Self Consumptionbattery1_*— SOE/SOH, power, voltage, temperatures, status of battery 1
Register addresses were validated by probing a SE10K-RWB48BFN4 (fw 0004.0025.0011) with one battery and one meter. Blocks that fail to read (flaky gateway) are skipped for that cycle; the fields are simply absent from the point.
Grafana dashboard
Import grafana_dashboard.json (Dashboards -> New -> Import). Select your
InfluxDB datasource when prompted (e.g. InfluxDB-HJK); the $serial variable
is filled from the serialnumber tag. InfluxQL only, InfluxDB 1.x.
The daemon also writes derived flow fields (house_load, pv_power,
grid_import, grid_export, battery_charge, battery_discharge,
solar_to_house) so the dashboard's consumption-source panels are plain
mean()/integral() queries.
Sign conventions (verified against the SolarEdge app): meter1_power
positive = export, battery1_power negative = discharge, and power_ac is
the inverter's total AC output (PV + battery, DC-coupled Energy Hub) - which
is why pv_power = power_ac - battery and house_load = power_ac + grid.
If a different system disagrees, flip METER_IMPORT_POSITIVE /
BATTERY_DISCHARGE_POSITIVE in solaredge_modbus_mqtt/__init__.py and
restart - the derived fields follow.
Uninstall
docker update --restart=no solaredge_modbus_mqtt
docker stop solaredge_modbus_mqtt
Update
docker update --restart=no solaredge_modbus_mqtt
docker stop solaredge_modbus_mqtt
docker rm solaredge_modbus_mqtt
docker run --detach --name=solaredge_modbus_mqtt --restart=always --volume=/etc/solaredge_modbus_mqtt/config.yaml:/app/config.yaml marijnkoesen/solaredge_modbus_mqtt:latest
For developers
Building and running the docker image
docker build -t solaredge_modbus_mqtt:latest .
or
docker buildx build --platform linux/amd64 solaredge_modbus_mqtt:latest .
cp config.yaml-dist config.yaml
docker run --volume=$PWD/config.yaml:/app/config.yaml solaredge_modbus_mqtt:latest
Running without docker
pip3 install -r requirements.txt
cp config.yaml-dist config.yaml
python3 solaredge_modbus_mqtt.py
Running tests
pip3 install -r requirements.txt
python3 -m unittest