12 minutes
Reading Ginlong Solis inverter over serial and importing in Home Assistant over MQTT
Introduction ¶
A few years ago we got some solar panels installed. The installation includes solar panels (duh!), a Ginlong Solis inverter and a Landis+Gyr meter. The meter uploads meter data to the platform of the installer, however it only does so twice a day.
To get real-time data on our solar production, I figured I had 3 options:
- Get the data from the inverter.
While the Solis inverter has modules that can upload the meter data over WiFi or 2G/3G to the cloud portal of Ginlong, our installer didn’t install any as they for some reason chose to use a seperate meter.
However, the COM port is still accessible.
I’d have to figure out how to connect to it, which pins I need, the protocol it speaks and how to decode the data. - Get the data from the meter.
The Landis+Gyr meter has an LED that does 1.000 pulses for 1 kWH.
So I could hook up a light sensor, like the Home Assistant glow, and count pulses. - Hook a clamp onto the power wire entering the fuse box.
A Shelly EM connected to the incoming Live wire would give me an easy Commercial off-the-shelf (COTS) solution.
I already have a 3EM measuring the flow from/to the grid, as I discussed in a previous post.
After following multiple tutorials, like this one from the ESPHome cookbook without much success, I dropped plan no. 2 and decided to take my changes with option 1. If this would fail, I’d give up on DIY and go for option 3: the Shelly EM.
Luckily I found these two tutorials giving me a pretty detailed explanation on how to hook up a Raspberry Pi to the inverter’s COM port using a RS485 serial to UART/USB converter.
After a bit of swearing and a bit of help from Solis’ local help center, I managed to get this solution to work :)
Since I there were a few differences between the tutorials I found and the solution I finally managed to hack together, I thought it may be a good idea to document my process.
Even if it doesn’t help anyone else, it’ll help future me if the solution ever breaks down :p
What we’ll need ¶
Our shopping list is pretty small for this one:
- A RPi Zero W
- (optional) ModMyPi Zero case with GPIO cutout
- microSD card
- A RS485 Pi HAT
Alternatively: RS485 to USB (not tested). - 4-pin socket connector
- USB charger
- micro-USB cable
Preparing the Raspberry Pi ¶
Flashing microSD card ¶
We’ll only need the Raspberry Pi to read the data over UART and send it to Home Assistant over MQTT. This will be done via a simple Python script.
So I just flashed Raspbian Lite on the SD card using balenaEtcher as we don’t need a Graphical User Interface (GUI).
WiFi and SSH ¶
To get our Raspberry Pi to immediately connect to our WiFi, we can add a wpa_supplicant.conf
file in the boot partition on the SD card.
- Plug the microSD card back in the computer after flashing.
- Create a new file and name it
wpa_supplicant.conf
- Add the following code and save the file.
Make sure you modify it to match your situation and ensure you’re using Unix line endings (\n
).
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=BE # changeme
network={
ssid="YOUR_SSID"
psk="YOUR_PASSWORD"
key_mgmt=WPA-PSK
}
To make sure we can SSH into the RPi, create an empty file named ssh
.
No extension, no data.
This must be done before the first boot of the Raspberry Pi.
RS485 HAT ¶
If you got the RS485 HAT, you’ll still need to solder the 2x20 pin header, and optionally the A/B/GND pin header and/or DB9 serial connector.
The HAT should come with a resistor.
Normally the last device in the line in Modbus has a resistor on it, so I soldered mine on as well.

Setting up serial connection ¶
Power the Raspberry Pi with a phone charger (5V A1 is sufficient) so that we can connect to it over SSH.
Before we do anything else, it may also be a good idea to update our Raspberry Pi.
ssh pi@<ip.of.RPi.zero>
# password = raspberry
sudo apt update
sudo apt dist-upgrade -y
sudo apt autoremove -y
The RPi Zero W uses the same serial for the Bluetooth as we want to use for our HAT (which is connected to GPIO 14 and 15). So we need to do a few tweak, which includes enabling hardware UART and moving bluetooth to the secondary UART channel.
sudo raspi-config
# > 3 - Interface Options
# > P6 - Serial Port
# > Would you like a login shell to be accessible over serial?
# > No
# > Would you like the serial port hardware to be enabled?
# > Yes
sudo nano /boot/config.txt
# Before [pi4] add the following lines:
enable_uart=1
dtoverlay=miniuart-bt
force_turbo=1
# Save and close
# Ctrl+X / Y / [Return]
Also check that /boot/cmdline.txt
doesn’t start a terminal over serial.
My file looks like:
console=tty1 root=PARTUUID=92a0ae12-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
Give the Raspberry Pi a reboot.
The serial connection of the HAT should be now connected to /dev/serial0
, which actually is a symbolic link to /dev/ttyAMA0
.
Solis inverter COM port ¶
The COM port of the Solis inverter has 4 pins, conveniently numbered 1-4. We’ll only need pins 3 and 4, which correspond with pins A and B on the RS485 HAT respectively.
I got a connector with a length of wire attached to it already.
That’s why I ‘hacked’ the connection between the HAT and the COM port together using Dupont wires and a DC jack.
In the future I’ll probably de-solder the 3-pin header and solder the COM-cable directly to the A and B holes on the HAT.
If you bought the connector from AliExpress, you’ll need to attach some wire yourself.
A length of RJ11 (telephone) cable or ethernet cable or some speaker wire should suffice.
You can decide yourself whether you want to solder onto the RPi HAT, use a connector (e.g. Wago or DC jack) or crimp some Dupont connectors onto the wires.

Script ¶
Now it’s time for the script that will read the data over serial and forward them over MQTT.
We’ll need a few packages for this script, so let’s install them first.
# ssh pi@<ip.of.RPi.zero>
sudo apt update
sudo apt install -y python3 python3-pip
python3 -m pip install -U pyserial
python3 -m pip install -U minimalmodbus
python3 -m pip install -U mqtt-mqtt
Then create a new file (I called mine solis_meter.py
) and paste the following code.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Read modbus data from Ginlong Solis inverter
# and send over MQTT
#
# Based on https://github.com/rogersia/Solis-4G
# Includes fixes, small modifcations and refactoring. Migrated to Python3.
# See https://sequr.be/blog/2021/08/reading-ginlong-solis-inverter-over-serial-and-importing-in-home-assistant-over-mqtt/
#
import logging
import minimalmodbus
import paho.mqtt.client as mqtt
import serial
import socket
import sys
import time
broker = "192.168.xxx.yyy" # Set to MQTT broker IP (likely your HA IP)
port = 1883 # Set to MQTT broker port
mqttuser = "YOUR_MQTT_USER" # Set to your MQTT user username
mqttpass = "YOUR_MQTT_PASSWORD" # Set to your MQTT user password
client_id = "solis_com"
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
def mqtt_connect():
# callback for mqtt
def on_connect(client, userdata, flags, rc):
logging.debug("MQTT connected with result code {}".format(rc))
client = mqtt.Client(client_id)
client.username_pw_set(mqttuser, mqttpass)
client.on_connect = on_connect
client.connect_async(broker, port, 60)
return client
def mqtt_subscribe(client):
def on_message(client, userdata, msg):
logging.debug("[ {} ({})] {}".format(msg.topic, msg.qos, msg.payload))
client.subscribe("meters/solis_com/command", 2)
client.on_message = on_message
def mqtt_publish(client, data):
def on_publish(client, userdata, mid):
logging.debug("[{}] published ({})".format(mid, userdata))
def send(client, topic, payload="", qos=2, retain=False):
res = client.publish(topic, payload, qos, retain)
res.wait_for_publish()
logging.debug("[{}] status: {} - {}".format(res.mid, res.rc, "Published" if res.is_published() else "Failed"))
time.sleep(0.5)
client.on_publish = on_publish
time.sleep(2)
for k, v in data.items():
send(client, "meters/solis_com/{}".format(k), v)
def modbus_connect():
instrument = minimalmodbus.Instrument('/dev/serial0', 2) # Set to inverter's address
instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity = serial.PARITY_NONE
instrument.serial.stopbits = 1
instrument.serial.timeout = 3
#instrument.debug = True
return instrument
def modbus_read(instrument):
timestamp = time.time()
# get data from solis
Realtime_ACW = instrument.read_long(3004, functioncode=4, signed=False) # Read AC Watts as Unsigned 32-Bit
logging.info("{:<23s}{:10.2f} W".format("AC Watts", Realtime_ACW))
Realtime_DCV = instrument.read_register(3021, number_of_decimals=2, functioncode=4, signed=False) # Read DC Volts as Unsigned 16-Bit
logging.info("{:<23s}{:10.2f} V".format("DC Volt", Realtime_DCV))
Realtime_DCI = instrument.read_register(3022, number_of_decimals=0, functioncode=4, signed=False) # Read DC Current as Unsigned 16-Bit
logging.info("{:<23s}{:10.2f} A".format("DC Current", Realtime_DCI))
Realtime_ACV = instrument.read_register(3035, number_of_decimals=1, functioncode=4, signed=False) # Read AC Volts as Unsigned 16-Bit
logging.info("{:<23s}{:10.2f} V".format("AC Volt", Realtime_ACV))
Realtime_ACI = instrument.read_register(3038, number_of_decimals=1, functioncode=4, signed=False) # Read AC Current as Unsigned 16-Bit
logging.info("{:<23s}{:10.2f} A".format("AC Current", Realtime_ACI))
Realtime_ACF = instrument.read_register(3042, number_of_decimals=2, functioncode=4, signed=False) # Read AC Frequency as Unsigned 16-Bit
logging.info("{:<23s}{:10.2f} Hz".format("AC Frequency", Realtime_ACF))
Inverter_C = instrument.read_register(3041, number_of_decimals=1, functioncode=4, signed=True) # Read Inverter Temperature as Signed 16-Bit
logging.info("{:<23s}{:10.2f} °C".format("Inverter Temperature", Inverter_C))
AlltimeEnergy_KW = instrument.read_long(3008, functioncode=4, signed=False) # Read All Time Energy (KWH Total) as Unsigned 32-Bit
logging.info("{:<23s}{:10.2f} kWh".format("Generated (All time)", AlltimeEnergy_KW))
Today_KW = instrument.read_register(3014, number_of_decimals=1, functioncode=4, signed=False) # Read Today Energy (KWH Total) as 16-Bit
logging.info("{:<23s}{:10.2f} kWh".format("Generated (Today)", Today_KW))
data = {
'online': timestamp,
'acw': Realtime_ACW,
'dcv': Realtime_DCV,
'dci': Realtime_DCI,
'acv': Realtime_ACV,
'aci': Realtime_ACI,
'acf': Realtime_ACF,
'inc': Inverter_C
}
# Fix for 0-values during inverter powerup
if AlltimeEnergy_KW > 0: data["gat"] = AlltimeEnergy_KW
if Today_KW > 0: data["gto"] = Today_KW
return data
def main():
try:
mqttc = mqtt_connect()
mqtt_subscribe(mqttc)
mqttc.loop_start()
modc = modbus_connect()
data = modbus_read(modc)
mqtt_publish(mqttc, data)
except TypeError as err:
logging.error("TypeError:\n{}".format(err))
except ValueError as err:
logging.error("ValueError:\n{}".format(err))
except minimalmodbus.NoResponseError as err:
logging.error("Modbus no response:\n{}".format(err))
except serial.SerialException as err:
logging.error("SerialException:\n{}".format(err))
except Exception as err:
logging.error("Exception:\n{}".format(err))
if __name__ == "__main__":
main()
Make sure you modify the broker IP and MQTT username/password.
You’ll also notice the comment Set inverter's address
.
This is something that had me swearing for way to long …
The scripts I based my configuration on used modbus address 1
for the Solis inverter, however mine appeared to be configured to use address 2
… <insert headbang emoji>.
To find the address of your inverter, follow the steps in this Solis training video (Enter > Settings > Set Address).
Running the script with the RPi connected to the Solis inverter and the inverter being online, should give you something like below.
If not, change the log level at the top of the code to logging.DEBUG
figure out what the issue is.
$ ./solis_meter.py
INFO:root:AC Watts 1233.00 W
INFO:root:DC Volt 28.12 V
INFO:root:DC Current 45.00 A
INFO:root:AC Volt 243.60 V
INFO:root:AC Current 5.10 A
INFO:root:AC Frequency 49.99 Hz
INFO:root:Inverter Temperature 38.00 °C
INFO:root:Generated (All time) 7018.00 kWh
INFO:root:Generated (Today) 5.30 kWh
If you listen to meters/solis_com/#
on your MQTT broker you should see the same values come in.
Cronjob ¶
To run this script every minute, add it to your crontab.
crontab -e
# Add the following line to the end
* * * * * /home/pi/solis_meter.py > /dev/null
Home Assistant ¶
Creating sensors ¶
To get the data into Home Assistant, we’ll need to create some MQTT sensors. This is about the same as we did in the Getting Started with MQTT post.
Add the following config under the sensor:
section, either in configuration.yaml
or in your sensors file if you seperated your config.
- platform: mqtt
name: "Solis meter - Last update"
qos: 1
unique_id: "solis_com_online"
state_topic: "meters/solis_com/online"
device_class: timestamp
value_template: "{{ (value | int | timestamp_local | as_datetime()).isoformat() }}"
- platform: mqtt
name: "Solis meter - AC Watts"
qos: 1
unique_id: "solis_com_acw"
state_topic: "meters/solis_com/acw"
device_class: power
unit_of_measurement: W
- platform: mqtt
name: "Solis meter - DC Volt"
qos: 1
unique_id: "solis_com_dcv"
state_topic: "meters/solis_com/dcv"
device_class: voltage
unit_of_measurement: V
- platform: mqtt
name: "Solis meter - DC Current"
qos: 1
unique_id: "solis_com_dci"
state_topic: "meters/solis_com/dci"
device_class: current
unit_of_measurement: A
- platform: mqtt
name: "Solis meter - AC Volt"
qos: 1
unique_id: "solis_com_acv"
state_topic: "meters/solis_com/acv"
device_class: voltage
unit_of_measurement: V
- platform: mqtt
name: "Solis meter - AC Current"
qos: 1
unique_id: "solis_com_aci"
state_topic: "meters/solis_com/aci"
device_class: current
unit_of_measurement: A
- platform: mqtt
name: "Solis meter - AC Frequency"
qos: 1
unique_id: "solis_com_acf"
state_topic: "meters/solis_com/acf"
unit_of_measurement: Hz
- platform: mqtt
name: "Solis meter - Inverter Temperature"
qos: 1
unique_id: "solis_com_inc"
state_topic: "meters/solis_com/inc"
device_class: temperature
unit_of_measurement: °C
- platform: mqtt
name: "Solis meter - Generated (All time)"
qos: 1
unique_id: "solis_com_gat"
state_topic: "meters/solis_com/gat"
device_class: energy
unit_of_measurement: kWh
state_class: total_increasing
- platform: mqtt
name: "Solis meter - Generated (Today)"
qos: 1
unique_id: "solis_com_gto"
state_topic: "meters/solis_com/gto"
device_class: energy
unit_of_measurement: kWh
state_class: total_increasing
Home Assistant 2021.9.0
HA 2021.9.0 introduced state_class: total_increasing
.
Before this version, we need to add a last reset
timestamp in the sensor’s attributes to be able to add this sensor to the Energy Dashboard.
More info on the Home Assistant Developers Blog.
The old solution was to use the following attributes for the ‘All time’ sensor.
state_class: measurement
last_reset_topic: 'meters/solis_com/gat'
last_reset_value_template: '1970-01-01T00:00:00+00:00'
After saving this config, go to Configuration > Server Controls > YAML configuration reloading and click Manually configured MQTT entities.
Wait for the next MQTT message to come in and enjoy seeing the data appear in Home Assistant :)

type: entities
entities:
- entity: sensor.solis_meter_last_update
- entity: sensor.solis_meter_ac_current
- entity: sensor.solis_meter_ac_volt
- entity: sensor.solis_meter_ac_watts
- entity: sensor.solis_meter_ac_frequency
- entity: sensor.solis_meter_dc_current
- entity: sensor.solis_meter_dc_volt
- entity: sensor.solis_meter_generated_today
- entity: sensor.solis_meter_generated_all_time
- entity: sensor.solis_meter_inverter_temperature
Incorrect values when inverter boots ¶
After running this code for a while, I noticed the inverter would sometimes report 0 (zero) for the Generated (All time) sensor. I figured out this happens when the inverter boots in the morning, as it shuts down when there’s no solar production.
This would cause Home Assistant to see this as a reset of the counter, after which the next (correct) was seen as just a huge production increase. As a result, I would get some very wild readings.
To fix this, I created a filtered version of the Generated (All time) sensor, which ignores readings that vary too much.
Add the following below the sensor configurations and use the clean version of the sensor in your Energy Dashboard.
- platform: filter
name: "Solis meter - Generated (All time) - clean"
entity_id: sensor.solis_meter_generated_all_time
filters:
- filter: outlier
window_size: 3 # Watch last 3 values
radius: 10 # Ignore any change > 10kWh
Note: This issue was fixed later in the Python code on the Raspberry Pi, only sending the gat
and gto
values if they are bigger than 0.
Home Assistant is will detect the drop in the value of the Generated (Today)
output and doesn’t require a 0 (zero) value to be sent first for that day’s counter to be reset.
The value of the Generated (All time)
should never drop.
Home Assistant Energy Dashboard ¶
We can now add our sensor to the Home Assistant Energy Dashboard.
We discussed setting up the Energy Dashboard in a previous blog post. If you don’t know how to use the Energy Dashboard, I advise you to go read that post. It also discusses using a Shelly 3EM to monitor our grid consumption/return.

If you configured the Forecast integration, the dashboard will use historic averages combined with weather forecasting to show you the expected solar production for the day. Using this integration, you’ll get a line graph depicting the forecasted solar production for that day.

Special thanks to the very helpful people at the EU Service team of Solis for helping me answer some questions regarding this setup!
Home Automation Energy Management Electricity Metering Solis MQTT serial Raspberry Pi
2546 Words
2021-08-29 (Last updated: 2023-01-31)