Modify the invalidation mechanism to make it more flexibile

This commit is contained in:
Stephan Joubert 2022-12-13 11:51:35 +02:00
parent 336862d0f1
commit 7143061cf6
9 changed files with 1286 additions and 33 deletions

3
.gitignore vendored
View File

@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker
.pyre/
# vscode
.vscode/

View File

@ -1,7 +1,7 @@
# Solarman integration
This is a Home Assistant component for interacting with Solarman data collectors used with a variety of inverters. The integration allows Home Assistant to connect in direct-mode over the local network to the collector to extract the information, and no cables are required.
It has been tested with a 5kW DEYE/SUNSYNK inverter. The collector is reported to be used in Omnik, Hosola, Goodwe, Solax, Ginlong, Samil, Sofar and Power-One Solar inverters, you may get success from any of these as well.
It has been created with a 5kW DEYE/SUNSYNK inverter and since integrated with a veriety of other inverters that uses the Solarman data collector.
This component uses version 5 of the communication protocol. If your collector is older and this component does not work, try one of the other integrations that uses version 4 of the protocol.
@ -31,8 +31,7 @@ custom_components
│ ├── solarman.py
│ ├── sensor.py
│ └── inverter_definitions
│ ├── parameters.yaml
│ └── custom_parameters.yaml
│ ├── {inverter-definition yaml files}
├── {other components}
```
@ -103,8 +102,11 @@ sensor:
| --- | --- | --- |
| deye_hybrid.yaml | DEYE/Sunsynk/SolArk Hybrid inverters | used when no lookup specified
| deye_string.yaml | DEYE/Sunsynk/SolArk String inverters | eg. SUN-4/5/6/7/8/10/12K-G03 Plus
| deye_4mppt.yaml | DEYE Microinverter
| sofar_lsw3.yaml | SOFAR Inverters
| sofar_hyd3k-6k.yaml | SOFAR Hybrid inverter | HYD 6000 or rebranded ex. ZCS AZZURRO
| solis_hybrid.yaml | SOLIS Hybrid inverter
| solid_1p8k-5g | SOLIS 1P8K-5G
# Auto-discovery
The component has the option to auto-discover the logger IP and serial number.
@ -115,6 +117,8 @@ NOTE:
This should be used as a temporary or debug measure since the discovery only happens when the component starts and, if the logger is inaccessible at that point, the entities will unavailable until restart. This will not be the case when the IP and serial was specified.
## Manual
The section below shows an example configuration done using manual configuration. This is an option for those that want to customize the component for inverters not supported out of the box.
~~~ YAML
sensor:
@ -127,7 +131,7 @@ sensor:
lookup_file: deye_hybrid.yaml
~~~
## Config-flow
![Autodicover](./flow_init.png)
![Autodiscover](./flow_init.png)
# Entities
Once the component is running, it will add the following entities to Home Assistant
@ -143,4 +147,4 @@ The entities includes the device classes to enable it to be added to the [Energy
To configure the energy dashboard with the infirmation provided by this component, see [configuring energy dashboard](energy.md)
# Customization
This integration was tested agains the DEYE 5kW inverter, and it is possible that the parameter-definitions for other inverters may differ. If you want to try your hand at it, refer to [customizing parameters.yaml](customization.md)
This integration was tested against the DEYE 5kW inverter, and it is possible that the parameter-definitions for other inverters may differ. If you want to try your hand at it, refer to [customizing parameters.yaml](customization.md)

View File

@ -5,7 +5,7 @@ DOMAIN = 'solarman'
DEFAULT_PORT_INVERTER = 8899
DEFAULT_INVERTER_MB_SLAVEID = 1
DEFAULT_LOOKUP_FILE = 'deye_hybrid.yaml'
LOOKUP_FILES = ['deye_4mppt.yaml', 'deye_hybrid.yaml', 'deye_string.yaml', 'sofar_lsw3.yaml', 'sofar_wifikit.yaml', 'solis_hybrid.yaml', 'sofar_g3hyd.yaml', 'custom_parameters.yaml']
LOOKUP_FILES = ['deye_4mppt.yaml', 'deye_hybrid.yaml', 'deye_string.yaml', 'sofar_lsw3.yaml', 'sofar wifikit.yaml', 'solis_hybrid.yaml', 'solis_1p8k-5g.yaml', 'sofar_g3hyd.yaml' , 'sofar_hyd3k-6k.yaml', 'custom_parameters.yaml']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)

View File

@ -97,7 +97,9 @@ parameters:
rule: 3
registers: [0x003F,0x0040]
icon: 'mdi:solar-power'
invalid: 0.0
validation:
min: 0.1
invalidate_all:

View File

@ -59,7 +59,9 @@ parameters:
rule: 3
registers: [0x003F,0x0040]
icon: 'mdi:solar-power'
invalid: 0.0
validation:
min: 0.1
invalidate_all:
@ -177,9 +179,9 @@ parameters:
icon: 'mdi:home-lightning-bolt'
- name: "Output Active Power"
class: "power"
class: "energy"
state_class: "measurement"
uom: "W"
uom: "kWh"
scale: 0.1
rule: 3
registers: [0x0056, 0x0057]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,204 @@
# Solis Single Phase Inverter
# 1P8K-5G
# Modbus information derived by test and comparing to Solis Cloud
# Gedger V.0.1 May 2022
#
requests:
- start: 2999
end: 3024
mb_functioncode: 0x04
- start: 3035
end: 3043
mb_functioncode: 0x04
- start: 3071
end: 3071
mb_functioncode: 0x04
parameters:
- group: InverterStatus
items:
- name: "Inverter Status"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 6
registers: [3043]
icon: 'mdi:home-lightning-bolt'
- name: "Operating Status"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 6
registers: [3071]
icon: 'mdi:home-lightning-bolt'
- name: "Inverter Temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 2
registers: [3041]
icon: 'mdi:thermometer'
# - name: "Inverter ID"
# class: ""
# state_class: ""
# uom: ""
# scale: 1
# rule: 5
# registers: [33004,33005,33006,33007,33008,33009,33010,33011,33012,33013,33014,33015,33016,33017,33018,33019]
# isstr: true
- name: "Product Model"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 6
registers: [2999]
isstr: true
- name: "DSP Software Version"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 6
registers: [3000]
isstr: true
- name: "LCD Software Version"
class: ""
state_class: ""
uom: ""
scale: 1
rule: 6
registers: [3001]
isstr: true
- group: InverterDC
items:
- name: "PV1 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [3021]
icon: 'mdi:solar-power'
- name: "PV2 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [3023]
icon: 'mdi:solar-power'
- name: "PV1 Current"
class: "current"
uom: "A"
scale: 0.1
rule: 1
registers: [3022]
icon: 'mdi:current-dc'
- name: "PV2 Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.1
rule: 1
registers: [3024]
icon: 'mdi:current-dc'
- name: "Total DC Power"
class: "power"
state_class: "measurement"
uom: "kW"
scale: 0.001
rule: 3
registers: [3007, 3006]
icon: 'mdi:solar-power'
- group: InverterAC
items:
- name: "Inverter AC Power"
class: "power"
state_class: "measurement"
uom: "kW"
scale: 0.001
rule: 3
registers: [3005, 3004]
icon: 'mdi:solar-power'
- name: "Inverter Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [3035]
icon: 'mdi:transmission-tower'
- name: "Inverter Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.1
rule: 1
registers: [3038]
icon: 'mdi:current-ac'
- name: "Inverter Frequency"
class: "frequency"
state_class: "measurement"
uom: "Hz"
scale: 0.01
rule: 1
registers: [3042]
icon: 'mdi:sine-wave'
- group: Generation
items:
- name: "Daily Generation"
class: "energy"
state_class: "measurement"
uom: "kWh"
scale: 0.1
rule: 1
registers: [3014]
icon: 'mdi:solar-power'
- name: "Monthly Generation"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 1
rule: 3
registers: [3011, 3010]
icon: 'mdi:solar-power'
- name: "Yearly Generation"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 1
rule: 3
registers: [3017, 3016]
icon: 'mdi:solar-power'
- name: "Total Generation"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 1
rule: 3
registers: [3009, 3008]
icon: 'mdi:solar-power'

View File

@ -1,9 +1,5 @@
import yaml
import struct
import math
# The parameters start in the "business field"
# just after the first two bytes.
@ -41,6 +37,21 @@ class ParameterParser:
elif rule == 6:
self.try_parse_bits(rawData,definition, start, length)
return
def do_validate(self, title, value, rule):
if 'min' in rule:
if rule['min'] > value:
if 'invalide_all' in rule:
raise ValueError(f'Invalidate complete dataset ({title} ~ {value})')
return False
if 'max' in rule:
if rule['max'] < value:
if 'invalide_all' in rule:
raise ValueError(f'Invalidate complete dataset ({title} ~ {value})')
return False
return True
def try_parse_signed (self, rawData, definition, start, length):
title = definition['name']
@ -69,14 +80,15 @@ class ParameterParser:
else:
value = value * scale
if 'validation' in definition:
if not self.do_validate(title, value, definition['validation']):
return
if self.is_integer_num (value):
self.result[title] = int(value)
else:
self.result[title] = value
if 'invalid' in definition:
if math.fabs(definition['invalid'] - value) < 0.001:
raise ValueError(f'Invalidate complete dataset ({title} ~ {value})')
return
def try_parse_unsigned (self, rawData, definition, start, length):
@ -102,14 +114,15 @@ class ParameterParser:
value = value - definition['offset']
value = value * scale
if 'validation' in definition:
if not self.do_validate(title, value, definition['validation']):
return
if self.is_integer_num (value):
self.result[title] = int(value)
else:
self.result[title] = value
if 'invalid' in definition:
if math.fabs(definition['invalid'] - value) < 0.001:
raise ValueError(f'Invalidate complete dataset ({title} ~ {value})')
return

View File

@ -63,21 +63,41 @@ The group just groups parameters that belong together. The induvidual parameter-
### Parameter-item
|field|description|
|:----------:|----------|
|name|The *name* field of the home-assistant entity #|
|class|The *class* field of the home-assistant entity #|
|state_class|The *state_class* field of the home assistant entity ##|
|uom|The *unit_of_measurement* field of the home-assistant entity #|
|icon|The *icon* field of the home-assistant entity #|
|field||description|
|-|-|-|
|name||The *name* field of the home-assistant entity #|
|class||The *class* field of the home-assistant entity #|
|state_class||The *state_class* field of the home assistant entity ##|
|uom||The *unit_of_measurement* field of the home-assistant entity #|
|icon||The *icon* field of the home-assistant entity #|
|| **The fields below define how the value from the logger is parsed** |
|scale|Scaling factor for the value read from the logger|
|rule|Method to interpret the data from the logger ###|
|registers|Array of register fields that comprises the value. If the value is placed in a number of registers, this array will contain more than one item.|
|lookup|Defines a key-value pair for values where an integer maps to a string field|
|invalid|Optional validation against a reference value, which invalidate complete dataset. Could be used, if the inverter delivers sometimes non usable data (e.g. Total Production == 0.0)|
|scale||Scaling factor for the value read from the logger|
|rule||Method to interpret the data from the logger ###|
|registers||Array of register fields that comprises the value. If the value is placed in a number of registers, this array will contain more than one item.|
|lookup||Defines a key-value pair for values where an integer maps to a string field|
||**The following is optional and could be used, if the inverter delivers sometimes non usable data (e.g. Total Production == 0.0)**|
|validation| ||
||min|Spefifies the minimum value to accept|
||max|Specifies the maximum value to accept|
||invalidate_all| Optional: invalidate complete dataset if specified. If not specified, it will only invalidate the specific parameter|
Example yaml file for the example mentioned above:
~~~ YAML
- name: "Total Production"
class: "energy"
state_class: "total_increasing"
uom: "kWh"
scale: 0.1
rule: 3
registers: [0x003F,0x0040]
icon: 'mdi:solar-power'
validation:
min: 0.1
invalidate_all:
~~~
\# (see) https://developers.home-assistant.io/docs/core/entity/
\## see https://developers.home-assistant.io/docs/core/entity/sensor/#entities-representing-a-total-amount