Skip to content

Commit 4666f4c

Browse files
committed
Initial commit
0 parents  commit 4666f4c

21 files changed

+1917
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
config.yaml
2+
venv
3+
__pycache__
4+
data
5+
**/*.log
6+
archive/**
7+
test/**
8+
.vscode
9+
.vscode/**

LICENSE.md

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
## A MQTT bridge for Solis solar inverters. ##
2+
3+
4+
5+
Foreword
6+
========
7+
I want to start by extending my deepest gratitute to incub who owns the [solis2mqtt](https://github.com/incub77/solis2mqtt) repository which served as a base for this one (from now on I call his repo the *original*).
8+
9+
I made several changes:
10+
1. The python script will run single pass, single task. Rationale behind this is that all initializations, cleanup, et cetera is taken care of by the Operation System and is done at **every** run of the script.
11+
2. In situations that the inverter is unreachable, the sun altitude is used to determine whether this is normal or not. Normal: long wait time, not normal: try again soon.
12+
3. An additional run.sh is added to do the repeating. Sleep duractions are based on the errorlevel of the script.
13+
4. Additional sensors are added to the solis_modbus.yaml file, like the v_dc1, i_dc1, grid frequency
14+
5. The On/Off switch and Power limitation are removed because of these changes. They require a continuous listener and that does not fit this design principle. If you need this, use the *original*.
15+
16+
All text marked with an * is the original text from *incub*.
17+
18+
19+
Introduction*
20+
============
21+
22+
Solis solar inverters are equipped with an RS485 interface, through which telemetry values can be read and also control
23+
commands can be sent. The manufacturer offers LAN and WLAN sticks in combination with a software solution to access the
24+
interface. Unfortunately, this is always coupled with a connection to the manufacturer's cloud and integration into a
25+
home automation system is only possible in a detoured manner.
26+
This software acts as a bridge between the RS485 interface and a MQTT broker to allow easy integration into a
27+
home automation (with special support for Home Assistant), and furthermore without cloud constraints.
28+
29+
30+
31+
Hardware
32+
========
33+
34+
* The inverter uses a proprietary(?) RS485 plug, with the following pin-out (at least on my wifi stick, it is different than described in the *original*):
35+
```
36+
/-----\
37+
| 1 4 |
38+
| 2 3 |
39+
\--^--/
40+
```
41+
42+
1. +5V
43+
2. GND
44+
3. DATA+ (A)
45+
4. DATA- (B)
46+
47+
* Any RS485 adapter should do I think. FYI: I use the [USB 2.0 RS485 interface FTDI](https://www.benselectronics.nl/usb-20-rs485-interface-ftdi.html). ![usbrs485](img/usb-20-rs485-interface-ftdi.jpg)
48+
* I highly recommend using a proper connector, which can be found on
49+
[ebay](https://www.ebay.nl/itm/234632173812) (search for "RS485 Solis" or
50+
"Exceedconn EC04681-2014-BF") and solder the wires to it.
51+
* I run the software on a Raspberry Pi 3 Model B Rev 1.2B, but any Linux box should do I guess.
52+
53+
Installation
54+
============
55+
56+
* Download and install to /opt/solis2mqtt
57+
58+
`wget https://github.com/hvoerman/solis2mqtt/archive/main.tar.gz -O - | sudo tar -xvzf - --one-top-level=/opt/solis2mqtt --strip 1`
59+
60+
* Execute setup.sh. This will basically install dependencies, add a system user for the daemon to run in and setup
61+
systemd.
62+
63+
`sudo bash /opt/solis2mqtt/setup.sh`
64+
65+
* To see whether or not the converter is connected, use:
66+
67+
`lsusb`
68+
69+
My output is
70+
71+
```
72+
Bus 001 Device 004: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
73+
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
74+
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
75+
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
76+
```
77+
78+
The `Future Technology Devices International, Ltd FT232 Serial (UART) IC` is the one we're looking for. I use `/dev/ttyUSB0` in my config.yaml
79+
80+
* Edit `config.yaml`. See section [Basic Configuration](#basic-Configuration).
81+
82+
e.g. `sudo vi /opt/solis2mqtt/config.yaml`
83+
84+
* A reboot is necessary for user rights (access to /dev/ttyUSB*) to become effective.
85+
86+
`sudo reboot`
87+
88+
Usage*
89+
=====
90+
91+
Solis2MQTT is intended to run as a system service. A log file is written to `/opt/solis2mqtt/solis2mqtt.log`. To control
92+
the service, the usual systemd commands are used:
93+
* Start: `sudo systemctl start solis2mqtt`
94+
* Stop: `sudo systemctl stop solis2mqtt`
95+
* Restart: `sudo systemctl restart solis2mqtt`
96+
* Disable start on boot: `sudo systemctl disable solis2mqtt`
97+
* Enable start on boot: `sudo systemctl enable solis2mqtt`
98+
99+
To check if the service is running you can do a `ps -efH | grep solis2mqtt`. The output should look something like this:
100+
```
101+
solis2m+ 460 1 0 22:53 ? 00:00:08 /usr/bin/python3 solis2mqtt.py -d
102+
pi 559 501 0 23:13 pts/0 00:00:00 grep --color=auto solis2mqtt
103+
```
104+
105+
If Solis2MQTT doesn't start up to a point where the log file is written you can check `/var/log/syslog` for clues.
106+
107+
For development/debugging Solis2MQTT can also be started directly. Make sure to change to the working directory before doing so.
108+
```
109+
cd /opt/solis2mqtt
110+
python3 ./solis2mqtt_v2.py
111+
```
112+
The following command line arguments are implemented:
113+
* `-v` Verbose mode. Will output debug logging messages.
114+
* `--help` ...
115+
116+
Basic Configuration
117+
===================
118+
119+
Configuration is read from `config.yaml`, that has to contain at least these entries:
120+
121+
```yaml
122+
device: /dev/ttyUSB0
123+
latitude: <latitude>
124+
longitude: <longitude>
125+
mqtt:
126+
url: hassio.local
127+
user: whoami
128+
passwd: secret
129+
```
130+
131+
This is a complete config example:
132+
133+
```yaml
134+
device: /dev/ttyUSB0
135+
slave_address: 1
136+
latitude: <latitude>
137+
longitude: <longitude>
138+
inverter:
139+
name: solis2mqtt
140+
manufacturer: Ginlong Technologies
141+
model: solis2mqtt
142+
mqtt:
143+
url: hassio.local
144+
port: 1883
145+
use_ssl: false
146+
validate_cert: false
147+
user: whoami
148+
passwd: secret
149+
```
150+
151+
* `device`: [Required] The path to your RS485 adapter
152+
* `slave_address`: [Optional] The modbus slave address, default is _1_
153+
* `latitude`: latitude of the solar plant
154+
* `longitude`: longitude of the solar plant
155+
* `inverter`:
156+
* `name`: [Optional] Used as a base path in MQTT, default is _solis2mqtt_
157+
* `manufacturer`: [Optional] Used for device info in Home Assistant, default is _solis2mqtt_
158+
* `model`: [Optional] Used for device info in Home Assistant, default is _solis2mqtt_
159+
* `mqtt`:
160+
* `url`: [Required] URL to your MQTT broker
161+
* `port`: [Optional] Port of your MQTT broker, default is _1883_
162+
* `use_ssl`: [Optional] Use SSL for MQTT traffic encryption, default is _false_
163+
* `validate_cert` [Optional] Validate certificate for SSL encryption, default is _false_
164+
* `user`: [Required] User for MQTT broker login
165+
* `passwd`: [Required] Password for MQTT broker login
166+
167+
Inverter configuration*
168+
======================
169+
170+
The file `solis_modbus.yaml` contains a list of entries, that describe the values to read from
171+
(and write to) the inverter.\
172+
You can add your own entries if you want to read other metrics from the inverter.
173+
Especially if it comes to writing to the inverter - use at your own risk :-)\
174+
This is an example of an entry:
175+
```yaml
176+
- name: inverter_temp
177+
description: Inverter temperature
178+
unit: "°C"
179+
active: true
180+
modbus:
181+
register: 3041
182+
read_type: register
183+
function_code: 4
184+
number_of_decimals: 1
185+
signed: false
186+
homeassistant:
187+
device: sensor
188+
state_class: measurement
189+
device_class: temperature
190+
```
191+
192+
The following options are available:
193+
194+
* `name`: [Required] Has to be unique. Used in MQTT path and together with the inverter name (from config.yaml) as part
195+
of Home Assistant unique_id
196+
* `description`: [Required] Used for generating log messages and as display name in Home Assistant
197+
* `unit`: [Optional] Added to log messages and used for Home Assistant
198+
* `active` [Required] Set to `false` if the entry should be ignored
199+
* `modbus`: [Required]
200+
* `register`: [Required] The modbus register address to read/write
201+
* `read_type`: [Required] The [modbus data type](https://minimalmodbus.readthedocs.io/en/stable/modbusdetails.html).
202+
Currently `register` and `long` are supported. Additionally `composed_datetime` can also be used here (see
203+
[below](#special-case-for-datetime))
204+
* `function_code`: [Required] The
205+
[modbus function code](https://minimalmodbus.readthedocs.io/en/stable/modbusdetails.html#implemented-functions) to read
206+
the register
207+
* `write_function_code`: [Optional] The function code to write to the register
208+
* `number_of_decimals`: [Optional] Can only be used in combination with `ready_type: register`. Used for automatic
209+
content conversion, e.g. 101 with `number_of_decimals: 1` is read as 10.1
210+
* `signed`: [Required] Whether the data should be interpreted as signed or unsigned
211+
* `homeassistant`: [Optional]
212+
* `device`: [Required] Used for [Home Assistant MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/).
213+
Can either be `sensor`, `number` or `switch`
214+
* `state_class`: [Optional]
215+
[State class](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes) for Home Assistant
216+
sensors
217+
* `device_class`: [Optional] [Device class](https://www.home-assistant.io/integrations/sensor/#device-class) for
218+
Home Assistant sensors
219+
* `payload_on`: [Optional] In combination with `device: switch` required. Specifies the payload that indicates the
220+
switch is in 'on' position
221+
* `payload_off`: [Optional] In combination with `device: switch` required. Specifies the payload that indicates the
222+
'off' position
223+
* `min`: [Optional] In combination with `device: number` required. Specifies minimum value.
224+
* `max`: [Optional] In combination with `device: number` required. Specifies maximum value.
225+
* `step`: [Optional] In combination with `device: number` required. Specifies step value. Smallest value _0.001_.
226+
227+
Special case for datetime
228+
-------------------------
229+
230+
As the datetime information is stored in several registers, there is a special `read_type` to read this as one ISO
231+
datetime string.
232+
233+
```
234+
- name: system_datetime
235+
description: System DateTime
236+
unit:
237+
active: true
238+
modbus:
239+
register: [3072, 3073, 3074, 3075, 3076, 3077] # [year, month, day, hour, minute, seconds]
240+
read_type: composed_datetime
241+
function_code: 4
242+
homeassistant:
243+
device: sensor
244+
state_class:
245+
device_class: timestamp
246+
```
247+
248+
249+
Screenshots
250+
===========
251+
252+
These are taken rather soon after I got stuff to work, so they are not fully representative. I will replace these screenshots with more representative ones in the near future.
253+
254+
Simple dashboard with APEX chart
255+
-----------------------------------
256+
![simple ha dashboard with apex charts](img/Screenshot1.png)
257+
258+
APEX chart code is
259+
260+
```
261+
type: custom:apexcharts-card
262+
graph_span: 48h
263+
span:
264+
start: day
265+
offset: '-1day'
266+
show:
267+
loading: true
268+
last_updated: true
269+
header:
270+
show: true
271+
title: Solar vermogen gisteren en vandaag
272+
show_states: true
273+
colorize_states: true
274+
series:
275+
- entity: sensor.active_power
276+
stroke_width: 1
277+
type: line
278+
curve: stepline
279+
extend_to: false
280+
name: huidige productie
281+
unit: W
282+
```
283+
284+
* `Solar vermogen gisteren en vandaag` means: *Solar power yesterday and today*
285+
* `huidige productie` means *current production*
286+
287+
MQTT Broker Integration Overview
288+
--------------------------------
289+
290+
![Integration details](img/Screenshot2.png)
291+
292+
Energy Dashboard
293+
----------------
294+
295+
![Energy dashboard](img/Screenshot3.png)
296+
297+
Just add the `Inverter total power generation` sensor to the Solar Panels stuff and add (if you like) the forecast to it.

config.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import yaml
2+
3+
4+
class DefaultDict(dict):
5+
def __init__(self, arg_dict, defaults_dict):
6+
super().__init__(arg_dict)
7+
self.defaults_dict = defaults_dict
8+
9+
for key, value in self.items():
10+
if type(value) is dict:
11+
self[key] = DefaultDict(value, self.defaults_dict[key])
12+
13+
def get(self, key):
14+
return super().get(key) if super().get(key) is not None else self.defaults_dict.get(key)
15+
16+
def __getitem__(self, key):
17+
return (
18+
super().__getitem__(key)
19+
if key in self and super().__getitem__(key) is not None
20+
else self.defaults_dict[key]
21+
)
22+
23+
24+
class Config(DefaultDict):
25+
def __init__(self, cfg_file, defaults_cfg_file="defaults_config.yaml"):
26+
with open(cfg_file) as yaml_cfg_file:
27+
with open(defaults_cfg_file) as yaml_defaults_cfg_file:
28+
super().__init__(
29+
yaml.load(yaml_cfg_file, yaml.Loader),
30+
yaml.load(yaml_defaults_cfg_file, yaml.Loader),
31+
)

config.yaml.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
device: /dev/ttyUSB0
2+
slave_address: 1
3+
latitude:
4+
longitude:
5+
inverter:
6+
name: solis2mqtt
7+
manufacturer: Ginlong Technologies
8+
model:
9+
mqtt:
10+
url: hassio.local
11+
port: 1883
12+
use_ssl: true
13+
validate_cert: true
14+
user: whoami
15+
passwd: secret

config.yaml.minimal

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
device: <path>
2+
latitude: <latitude>
3+
longitude: <longitude>
4+
mqtt:
5+
url: <url>
6+
user: <user>
7+
passwd: <password>

defaults_config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
device:
2+
slave_address: 1
3+
latitude:
4+
longitude:
5+
inverter:
6+
name: solis2mqtt
7+
manufacturer: solis2mqtt
8+
model: solis2mqtt
9+
mqtt:
10+
url:
11+
port: 1883
12+
use_ssl: true
13+
validate_cert: true
14+
user:
15+
passwd:

0 commit comments

Comments
 (0)