|
| 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).  |
| 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 | + |
| 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 | + |
| 291 | +
|
| 292 | +Energy Dashboard |
| 293 | +---------------- |
| 294 | +
|
| 295 | + |
| 296 | +
|
| 297 | +Just add the `Inverter total power generation` sensor to the Solar Panels stuff and add (if you like) the forecast to it. |
0 commit comments