Modify the invalidation mechanism to make it more flexibile
This commit is contained in:
parent
336862d0f1
commit
7143061cf6
|
@ -127,3 +127,6 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# vscode
|
||||
.vscode/
|
||||
|
|
14
README.md
14
README.md
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -97,7 +97,9 @@ parameters:
|
|||
rule: 3
|
||||
registers: [0x003F,0x0040]
|
||||
icon: 'mdi:solar-power'
|
||||
invalid: 0.0
|
||||
validation:
|
||||
min: 0.1
|
||||
invalidate_all:
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue