mirror of
https://github.com/alexhopeoconnor/SunGather.git
synced 2026-10-04 02:48:12 +10:00
Merge branch 'main' of https://github.com/bohdan-s/SunGather into bohdan-s-main
This commit is contained in:
@@ -35,9 +35,10 @@ Has multiple export locations out of the box:
|
|||||||
* PVOutput - Load into PVOutput.org
|
* PVOutput - Load into PVOutput.org
|
||||||
* InfluxDB - Load data directly into InfluxDB
|
* InfluxDB - Load data directly into InfluxDB
|
||||||
* Simple webserver showing collected data
|
* Simple webserver showing collected data
|
||||||
|
* Rasberry Pi Docker support
|
||||||
* and more coming....
|
* and more coming....
|
||||||
|
|
||||||
I have borrowed HEAVILY from the following projects, THANK YOU
|
I have learned a lot from the following projects, THANK YOU
|
||||||
* [solariot](https://github.com/meltaxa/solariot)
|
* [solariot](https://github.com/meltaxa/solariot)
|
||||||
* [modbus4mqtt](https://github.com/tjhowse/modbus4mqtt)
|
* [modbus4mqtt](https://github.com/tjhowse/modbus4mqtt)
|
||||||
* [ModbusTCP2MQTT](https://github.com/TenySmart/ModbusTCP2MQTT)
|
* [ModbusTCP2MQTT](https://github.com/TenySmart/ModbusTCP2MQTT)
|
||||||
@@ -45,12 +46,18 @@ I have borrowed HEAVILY from the following projects, THANK YOU
|
|||||||
<p align="right">(<a href="#top">back to top</a>)</p>
|
<p align="right">(<a href="#top">back to top</a>)</p>
|
||||||
|
|
||||||
## Raodmap / TO DO
|
## Raodmap / TO DO
|
||||||
* Rasberry Pi Docker support
|
|
||||||
* Full Home Assistant integration, as HACS addon
|
* Full Home Assistant integration, as HACS addon
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
|
**0.3.0**
|
||||||
|
**IMPORTANT: If updating from v0.1.x or v0.2.x please check config against config-example. some options for MQTT and PVOutput have changed**
|
||||||
|
* Heaps bug fixes
|
||||||
|
* Fixed PVOutput not working after midnight
|
||||||
|
* MQTT now auto-reconnects
|
||||||
|
* MQTT/PVOutput now verify values exist before trying to publish
|
||||||
|
* Rasberry Pi Docker support
|
||||||
|
* lots more...
|
||||||
|
|
||||||
|
|
||||||
**0.2.2**
|
**0.2.2**
|
||||||
* Minor bug fixes
|
* Minor bug fixes
|
||||||
@@ -254,11 +261,19 @@ SH5K-20, SH3K6, SH4K6, SH5K-V13, SH5K-30, SH3K6-30, SH4K6-30, SH5.0RS, SH3.6RS,
|
|||||||
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
Multi Arch (BuildX) Including Raspberry Pi
|
||||||
|
```sh
|
||||||
|
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||||
|
docker buildx create --platform linux/amd64,linux/arm64/v8 --use
|
||||||
|
docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t bohdans/sungather:latest -t bohdans/sungather:v<version> .
|
||||||
|
```
|
||||||
|
Current Arch (docker build)
|
||||||
```sh
|
```sh
|
||||||
docker build --no-cache --rm -t bohdans/sungather:latest -t bohdans/sungather:v<version> .
|
docker build --no-cache --rm -t bohdans/sungather:latest -t bohdans/sungather:v<version> .
|
||||||
docker push bohdans/sungather -a
|
docker push bohdans/sungather -a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
<p align="right">(<a href="#top">back to top</a>)</p>
|
<p align="right">(<a href="#top">back to top</a>)</p>
|
||||||
<!-- LICENSE -->
|
<!-- LICENSE -->
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
inverter:
|
inverter:
|
||||||
host: 192.168.1.100 # [Required] IP Address of the Inverter or Dongle
|
host: 192.168.1.100 # [Required] IP Address of the Inverter or Dongle
|
||||||
# port: 502 # [Optional] Default for modbus is 502, for http is 8082
|
# port: 502 # [Optional] Default for modbus is 502, for http is 8082
|
||||||
|
# timeout: 10 # [Optional] Default is 10, how long to wait for a connection
|
||||||
|
# retries: 3 # [Optional] Default is 3, how many times to retry if connection fails
|
||||||
# slave: 0x01 # [Optional] Default is 0x01
|
# slave: 0x01 # [Optional] Default is 0x01
|
||||||
# timeout: 10 # [Optional] Default is 10
|
|
||||||
# scan_interval: 30 # [Optional] Default is 30
|
# scan_interval: 30 # [Optional] Default is 30
|
||||||
connection: sungrow # [Required] options: modbus, sungrow, http
|
connection: sungrow # [Required] options: modbus, sungrow, http
|
||||||
# model: "SG7.0RT" # [Optional] This is autodetected on startup, only needed if detection issues or for testing
|
# model: "SG7.0RT" # [Optional] This is autodetected on startup, only needed if detection issues or for testing
|
||||||
# See model list here: https://github.com/bohdan-s/SunGather#supported
|
# See model list here: https://github.com/bohdan-s/SunGather#supported
|
||||||
# smart_meter: True # [Optional] Default is False, Set to true if inverter supports reading grind / house consumption
|
# smart_meter: True # [Optional] Default is False, Set to true if inverter supports reading grind / house consumption
|
||||||
# use_local_time: False # [Optional] Default False, Uses Inventer time, if try uses PC time when updating timestamps (e.g. PVOutput)
|
# use_local_time: False # [Optional] Default False, Uses Inventer time, if true it uses PC time when updating timestamps (e.g. PVOutput)
|
||||||
# manual_load: False # [Optional] Manually calculate load total_active_power + meter_power if the inverter does not supply it
|
|
||||||
# logging: 30 # [Optional] 10 = Debug, 20 = Info, 30 = Warning (default), 40 = Error
|
# logging: 30 # [Optional] 10 = Debug, 20 = Info, 30 = Warning (default), 40 = Error
|
||||||
# level: 1 # [Optional] Set the amount of information to gather
|
# level: 1 # [Optional] Set the amount of information to gather
|
||||||
# 0 = Model and Solar Generation,
|
# 0 = Model and Solar Generation,
|
||||||
@@ -68,43 +68,36 @@ exports:
|
|||||||
- name: "Daily Generation"
|
- name: "Daily Generation"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: daily_power_yields
|
register: daily_power_yields
|
||||||
unit: kWh
|
|
||||||
dev_class: energy
|
dev_class: energy
|
||||||
state_class: total_increasing
|
state_class: total_increasing
|
||||||
- name: "Active Power"
|
- name: "Active Power"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: total_active_power
|
register: total_active_power
|
||||||
unit: W
|
|
||||||
dev_class: power
|
dev_class: power
|
||||||
state_class: measurement
|
state_class: measurement
|
||||||
- name: "Load Power"
|
- name: "Load Power"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: load_power
|
register: load_power
|
||||||
unit: W
|
|
||||||
dev_class: power
|
dev_class: power
|
||||||
state_class: measurement
|
state_class: measurement
|
||||||
- name: "Meter Power"
|
- name: "Meter Power"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: meter_power
|
register: meter_power
|
||||||
unit: W
|
|
||||||
dev_class: power
|
dev_class: power
|
||||||
state_class: measurement
|
state_class: measurement
|
||||||
- name: "Export to Grid"
|
- name: "Export to Grid"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: export_to_grid
|
register: export_to_grid
|
||||||
unit: W
|
|
||||||
dev_class: power
|
dev_class: power
|
||||||
state_class: measurement
|
state_class: measurement
|
||||||
- name: "Import from Grid"
|
- name: "Import from Grid"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: import_from_grid
|
register: import_from_grid
|
||||||
unit: W
|
|
||||||
dev_class: power
|
dev_class: power
|
||||||
state_class: measurement
|
state_class: measurement
|
||||||
- name: "Temperature"
|
- name: "Temperature"
|
||||||
sensor_type: sensor
|
sensor_type: sensor
|
||||||
register: internal_temperature
|
register: internal_temperature
|
||||||
unit: °C
|
|
||||||
dev_class: temperature
|
dev_class: temperature
|
||||||
state_class: measurement
|
state_class: measurement
|
||||||
- name: "Power State"
|
- name: "Power State"
|
||||||
@@ -121,6 +114,7 @@ exports:
|
|||||||
sid: "xxxxx" # [Optional] System ID, Settings > Registered Systems > System ID
|
sid: "xxxxx" # [Optional] System ID, Settings > Registered Systems > System ID
|
||||||
# join_team: False # [Optional] Default True, This will join the SunGather team in PVOutput, Setting to False will leave the team if previously joined
|
# join_team: False # [Optional] Default True, This will join the SunGather team in PVOutput, Setting to False will leave the team if previously joined
|
||||||
rate_limit: 60 # [Optional] Default 60, 60 for regular accounts, 300 for donation accounts
|
rate_limit: 60 # [Optional] Default 60, 60 for regular accounts, 300 for donation accounts
|
||||||
|
cumulative_flag: 2 # If using v2 & v4 set to 1, of using only v1 set to 2 (if daily totals)
|
||||||
batch_points: 1 # [Optional] Default 1, how many data points to batch upload,
|
batch_points: 1 # [Optional] Default 1, how many data points to batch upload,
|
||||||
# Time between uploads will be status_interval * batch_points. e.g. status_invterval of 5min, and batch_points of 12 will upload to PVOutput Hourly (5 * 12 = 60 mins)
|
# Time between uploads will be status_interval * batch_points. e.g. status_invterval of 5min, and batch_points of 12 will upload to PVOutput Hourly (5 * 12 = 60 mins)
|
||||||
parameters: # [Required] v1 & v3 or v2 & v4 minimum. See: https://pvoutput.org/help/api_specification.html#power-and-energy-calculation
|
parameters: # [Required] v1 & v3 or v2 & v4 minimum. See: https://pvoutput.org/help/api_specification.html#power-and-energy-calculation
|
||||||
@@ -137,8 +131,6 @@ exports:
|
|||||||
# register: internal_temperature #
|
# register: internal_temperature #
|
||||||
- name: v6 # Voltage
|
- name: v6 # Voltage
|
||||||
register: phase_a_voltage # Read voltage from first Phase
|
register: phase_a_voltage # Read voltage from first Phase
|
||||||
- name: c1 # Cumulative Flag,
|
|
||||||
value: 2 # If using v2 & v4 set to 1, of using only v1 set to 2 (if daily totals)
|
|
||||||
# - name: "v7" # Extended Value v7 - Donation Only
|
# - name: "v7" # Extended Value v7 - Donation Only
|
||||||
# register:
|
# register:
|
||||||
# - name: "v8" # Extended Value v8 - Donation Only
|
# - name: "v8" # Extended Value v8 - Donation Only
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
class export_console(object):
|
class export_console(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
return
|
pass
|
||||||
|
|
||||||
# Configure Console
|
# Configure Console
|
||||||
def configure(self, config, config_inverter):
|
def configure(self, config, inverter):
|
||||||
logging.info("Console: Configured")
|
print("+----------------------------------------------+")
|
||||||
print("{:<20} {:<25}".format('Config','Value'))
|
print("{:<46} {:<1}".format("| " + 'Inverter Configuration Settings',"|"))
|
||||||
for setting in config_inverter:
|
print("+----------------------------------------------+")
|
||||||
print("{:<40} {:<25}".format(setting,str(config_inverter.get(setting))))
|
print("{:<20} {:<25} {:<1}".format("| " + 'Config',"| " + 'Value', "|"))
|
||||||
|
print("+--------------------+-------------------------+")
|
||||||
|
for setting, value in inverter.client_config.items():
|
||||||
|
print("{:<20} {:<25} {:<1}".format("| " + str(setting), "| " + str(value), "|"))
|
||||||
|
for setting, value in inverter.inverter_config.items():
|
||||||
|
print("{:<20} {:<25} {:<1}".format("| " + str(setting), "| " + str(value), "|"))
|
||||||
|
print("+----------------------------------------------+")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def publish(self, inverter):
|
def publish(self, inverter):
|
||||||
|
print("+----------------------------------------------------------------------+")
|
||||||
|
print("| {:<7} | {:<35} | {:<20} |".format('Address', 'Register','Value'))
|
||||||
|
print("+---------+-------------------------------------+----------------------+")
|
||||||
|
for register, value in inverter.latest_scrape.items():
|
||||||
|
print("| {:<7} | {:<35} | {:<20} |".format(str(inverter.getRegisterAddress(register)), str(register), str(value) + " " + str(inverter.getRegisterUnit(register))))
|
||||||
|
print("+----------------------------------------------------------------------+")
|
||||||
|
print(f"Logged {len(inverter.latest_scrape)} registers to Console")
|
||||||
|
|
||||||
print("{:<40} {:<25}".format('Register','Value'))
|
return True
|
||||||
for register in inverter:
|
|
||||||
print("{:<40} {:<25}".format(register,str(inverter.get(register))))
|
|
||||||
|
|
||||||
print(f"Logged {len(inverter)} registers to Console")
|
|
||||||
|
|
||||||
return
|
|
||||||
@@ -4,51 +4,60 @@ from influxdb_client.client.write_api import SYNCHRONOUS
|
|||||||
|
|
||||||
class export_influxdb(object):
|
class export_influxdb(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._isConfigured = False
|
|
||||||
self.client = None
|
self.client = None
|
||||||
self.write_api = None
|
self.write_api = None
|
||||||
self.bucket = None
|
|
||||||
self.measurements = []
|
|
||||||
|
|
||||||
# Configure InfluxDB
|
# Configure InfluxDB
|
||||||
def configure(self, config, config_inverter):
|
def configure(self, config, inverter):
|
||||||
|
self.influxdb_config = {
|
||||||
if not config.get('token') and config.get('org') and config.get('bucket') and config.get('measurements'):
|
'url': config.get('url', "http://localhost:8086"),
|
||||||
|
'token': config.get('token', None),
|
||||||
|
'username': config.get('username', None),
|
||||||
|
'password': config.get('password', None),
|
||||||
|
'org': config.get('org',None),
|
||||||
|
'bucket': config.get('bucket',None)
|
||||||
|
}
|
||||||
|
self.influxdb_measurements = [{}]
|
||||||
|
self.influxdb_measurements.pop() # Remove null value from list
|
||||||
|
|
||||||
|
if not self.influxdb_config['org'] or not self.influxdb_config['bucket'] or not (self.influxdb_config['token'] or (self.influxdb_config['username'] and self.influxdb_config['password'])):
|
||||||
logging.warning(f"InfluxDB: Please check configuration")
|
logging.warning(f"InfluxDB: Please check configuration")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if config.get('token', False):
|
if self.influxdb_config['token']:
|
||||||
self.client = influxdb_client.InfluxDBClient(
|
self.client = influxdb_client.InfluxDBClient(
|
||||||
url=config.get('url', 'http://localhost:8086'),
|
url=self.influxdb_config['url'],
|
||||||
token=config.get('token'),
|
token=self.influxdb_config['token'],
|
||||||
org=config.get('org')
|
org=self.influxdb_config['org']
|
||||||
)
|
)
|
||||||
elif config.get('username',False) and config.get('password',False):
|
elif config.get('username',False) and config.get('password',False):
|
||||||
self.client = influxdb_client.InfluxDBClient(
|
self.client = influxdb_client.InfluxDBClient(
|
||||||
url=config.get('url', 'http://localhost:8086'),
|
url=self.influxdb_config['url'],
|
||||||
token=f"{config.get('username')}:{config.get('password')}",
|
token=f"{self.influxdb_config['username']}:{self.influxdb_config['password']}",
|
||||||
org=config.get('org')
|
org=self.influxdb_config['org']
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error(f"InfluxDB: Error: {err}")
|
logging.error(f"InfluxDB: Error: {err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.bucket=config.get('bucket')
|
|
||||||
|
|
||||||
for measurement in config.get('measurements'):
|
for measurement in config.get('measurements'):
|
||||||
self.measurements.append(measurement)
|
if not inverter.validateRegister(measurement['register']):
|
||||||
|
logging.error(f"InfluxDB: Configured to use {measurement['register']} but not configured to scrape this register")
|
||||||
|
return False
|
||||||
|
self.influxdb_measurements.append(measurement)
|
||||||
|
|
||||||
self.write_api = self.client.write_api(write_options=SYNCHRONOUS)
|
self.write_api = self.client.write_api(write_options=SYNCHRONOUS)
|
||||||
logging.info(f"InfluxDB: Configured: {self.client.url}")
|
logging.info(f"InfluxDB: Configured: {self.client.url}")
|
||||||
|
|
||||||
self._isConfigured = True
|
return True
|
||||||
return self._isConfigured
|
|
||||||
|
|
||||||
def publish(self, inverter):
|
def publish(self, inverter):
|
||||||
if not self._isConfigured:
|
#HEAD
|
||||||
logging.info("InfluxDB: Skipped, Initial Configuration Failed")
|
'''if not self._isConfigured:
|
||||||
return False
|
# logging.info("InfluxDB: Skipped, Initial Configuration Failed")
|
||||||
|
# return False
|
||||||
|
|
||||||
# Setting a Standard Measurement Name and Inverter as a tag for later filtering
|
# Setting a Standard Measurement Name and Inverter as a tag for later filtering
|
||||||
# could be extended by using values from config.yaml or SerialNo or ...
|
# could be extended by using values from config.yaml or SerialNo or ...
|
||||||
@@ -61,8 +70,21 @@ class export_influxdb(object):
|
|||||||
# remove last ","
|
# remove last ","
|
||||||
sequence=sequence[:-1]
|
sequence=sequence[:-1]
|
||||||
logging.debug(f'InfluxDB: Sequence; {sequence}')
|
logging.debug(f'InfluxDB: Sequence; {sequence}')
|
||||||
|
'''
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
sequence = []
|
||||||
|
|
||||||
|
for measurement in self.influxdb_measurements:
|
||||||
|
if not inverter.validateLatestScrape(measurement['register']):
|
||||||
|
logging.error(f"InfluxDB: Skipped collecting data, {measurement['register']} missing from last scrape")
|
||||||
|
return False
|
||||||
|
sequence.append(f"{measurement['point']},inverter={inverter.getInverterModel(True)} {measurement['register']}={inverter.getRegisterValue(measurement['register'])}")
|
||||||
|
logging.debug(f'InfluxDB: Sequence; {sequence}')
|
||||||
|
|
||||||
|
# f11ee5fe75be6aa1bc8046901443122e4116b702
|
||||||
try:
|
try:
|
||||||
self.write_api.write(self.bucket, self.client.org, sequence)
|
self.write_api.write(self.influxdb_config['bucket'], self.client.org, sequence)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error("InfluxDB: " + str(err))
|
logging.error("InfluxDB: " + str(err))
|
||||||
|
|
||||||
|
|||||||
+65
-56
@@ -6,91 +6,100 @@ class export_mqtt(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mqtt_client = None
|
self.mqtt_client = None
|
||||||
self.sensor_topic = None
|
self.sensor_topic = None
|
||||||
self.homeassistant = False
|
|
||||||
self.ha_discovery = True
|
|
||||||
self.ha_sensors = []
|
|
||||||
self.model = None
|
|
||||||
self.model_clean = None
|
|
||||||
self.inverter_ip = None
|
|
||||||
self.mqtt_queue = []
|
self.mqtt_queue = []
|
||||||
|
self.ha_discovery_published = False
|
||||||
|
|
||||||
# Configure MQTT
|
# Configure MQTT
|
||||||
def configure(self, config, config_inverter):
|
def configure(self, config, inverter):
|
||||||
|
self.mqtt_config = {
|
||||||
|
'host': config.get('host', None),
|
||||||
|
'port': config.get('port', 1883),
|
||||||
|
'topic': config.get('topic', "inverter/{model}/registers").replace('{model}', inverter.getInverterModel(True)),
|
||||||
|
'username': config.get('username', None),
|
||||||
|
'password': config.get('password',None),
|
||||||
|
'homeassistant': config.get('homeassistant',False)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ha_sensors = [{}]
|
||||||
|
self.ha_sensors.pop() # Remove null value from list
|
||||||
|
|
||||||
|
if not self.mqtt_config['host']:
|
||||||
|
logging.info(f"MQTT: Host config is required")
|
||||||
|
return False
|
||||||
|
|
||||||
self.mqtt_client = mqtt.Client(client_id="SunGather")
|
self.mqtt_client = mqtt.Client(client_id="SunGather")
|
||||||
self.mqtt_client.on_connect = self.on_connect
|
self.mqtt_client.on_connect = self.on_connect
|
||||||
self.mqtt_client.on_disconnect = self.on_disconnect
|
self.mqtt_client.on_disconnect = self.on_disconnect
|
||||||
self.mqtt_client.on_publish = self.on_publish
|
self.mqtt_client.on_publish = self.on_publish
|
||||||
|
|
||||||
if config.get('username') and config.get('password'):
|
if self.mqtt_config['username'] and self.mqtt_config['password']:
|
||||||
self.mqtt_client.username_pw_set(config.get('username'), config.get('password'))
|
self.mqtt_client.username_pw_set(self.mqtt_config['username'], self.mqtt_config['password'])
|
||||||
|
|
||||||
if config.get('port') == 8883:
|
if self.mqtt_config['port'] == 8883:
|
||||||
self.mqtt_client.tls_set()
|
self.mqtt_client.tls_set()
|
||||||
|
|
||||||
self.mqtt_client.connect_async(config.get('host'), port=config.get('port', 1883), keepalive=60)
|
self.mqtt_client.connect_async(self.mqtt_config['host'], port=self.mqtt_config['port'], keepalive=60)
|
||||||
self.mqtt_client.loop_start()
|
self.mqtt_client.loop_start()
|
||||||
|
|
||||||
self.inverter_ip = config_inverter.get('host')
|
if self.mqtt_config['homeassistant']:
|
||||||
self.sensor_topic = config.get('topic', 'inverter/{model}/registers')
|
|
||||||
self.homeassistant = config.get('homeassistant', False)
|
|
||||||
|
|
||||||
if self.homeassistant:
|
|
||||||
for ha_sensor in config.get('ha_sensors'):
|
for ha_sensor in config.get('ha_sensors'):
|
||||||
self.ha_sensors.append(ha_sensor)
|
if not inverter.validateRegister(ha_sensor['register']):
|
||||||
|
logging.error(f"MQTT: Configured to use {ha_sensor['register']} but not configured to scrape this register")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.ha_sensors.append(ha_sensor)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_connect(self, client, userdata, flags, rc):
|
def on_connect(self, client, userdata, flags, rc):
|
||||||
logging.info(f"MQTT: Connected {client._host}:{client._port}")
|
logging.info(f"MQTT: Connected to {client._host}:{client._port}")
|
||||||
|
|
||||||
def on_disconnect(self, client, userdata, rc):
|
def on_disconnect(self, client, userdata, rc):
|
||||||
logging.info(f"MQTT: Server Disconnected code:{rc}")
|
logging.info(f"MQTT: Server Disconnected code: {rc}")
|
||||||
|
|
||||||
def on_publish(self, client, userdata, mid):
|
def on_publish(self, client, userdata, mid):
|
||||||
self.mqtt_queue.remove(mid)
|
try:
|
||||||
|
self.mqtt_queue.remove(mid)
|
||||||
|
except Exception as err:
|
||||||
|
pass
|
||||||
logging.info(f"MQTT: Message {mid} Published")
|
logging.info(f"MQTT: Message {mid} Published")
|
||||||
|
|
||||||
|
def cleanName(self, name):
|
||||||
|
return name.lower().replace(' ','_')
|
||||||
|
|
||||||
def publish(self, inverter):
|
def publish(self, inverter):
|
||||||
if not self.mqtt_client.is_connected():
|
if not self.mqtt_client.is_connected():
|
||||||
logging.warning(f'MQTT: Server Disconnected; {self.mqtt_queue.__len__()} messages queued')
|
logging.warning(f'MQTT: Server Disconnected; {self.mqtt_queue.__len__()} messages queued, will automatically attempt to reconnect')
|
||||||
elif self.mqtt_queue.__len__() > 10:
|
elif self.mqtt_queue.__len__() > 10:
|
||||||
logging.warning(f'MQTT: {self.mqtt_queue.__len__()} messages queued, this may be due to a MQTT server issue')
|
logging.warning(f'MQTT: {self.mqtt_queue.__len__()} messages queued, this may be due to a MQTT server issue')
|
||||||
|
|
||||||
if not self.model:
|
logging.debug(f"MQTT: Publishing: {self.mqtt_config['topic']} : {json.dumps(inverter.latest_scrape)}")
|
||||||
self.model = inverter.get('device_type_code', 'unknown')
|
self.mqtt_queue.append(self.mqtt_client.publish(self.mqtt_config['topic'], json.dumps(inverter.latest_scrape).replace('"', '\"'), qos=0).mid)
|
||||||
self.model_clean = self.model.replace('.','').replace('-','')
|
|
||||||
self.sensor_topic = self.sensor_topic.replace('{model}', self.model_clean)
|
|
||||||
|
|
||||||
logging.debug(f'MQTT: Sensor Topic = {self.sensor_topic}')
|
if self.mqtt_config['homeassistant'] and not self.ha_discovery_published:
|
||||||
logging.debug(f"MQTT: Publishing: {self.sensor_topic} : {json.dumps(inverter)}")
|
for ha_sensor in self.ha_sensors:
|
||||||
self.mqtt_queue.append(self.mqtt_client.publish(self.sensor_topic, json.dumps(inverter).replace('"', '\"'), qos=1).mid)
|
config_msg = {}
|
||||||
|
if ha_sensor.get('name'):
|
||||||
|
ha_topic = 'homeassistant/' + ha_sensor.get('sensor_type', 'sensor') + '/inverter/' + self.cleanName(ha_sensor.get('name')) + '/config'
|
||||||
|
config_msg['name'] = "Inverter " + ha_sensor.get('name')
|
||||||
|
config_msg['unique_id'] = "inverter_" + self.cleanName(ha_sensor.get('name'))
|
||||||
|
config_msg['state_topic'] = self.mqtt_config['topic']
|
||||||
|
config_msg['value_template'] = "{{ value_json." + ha_sensor.get('register') + " }}"
|
||||||
|
config_msg['unit_of_measurement'] = inverter.getRegisterUnit(ha_sensor.get('register'))
|
||||||
|
if ha_sensor.get('dev_class'):
|
||||||
|
config_msg['device_class'] = ha_sensor.get('dev_class')
|
||||||
|
if ha_sensor.get('state_class'):
|
||||||
|
config_msg['state_class'] = ha_sensor.get('state_class')
|
||||||
|
if ha_sensor.get('payload_on'):
|
||||||
|
config_msg['payload_on'] = ha_sensor.get('payload_on')
|
||||||
|
if ha_sensor.get('payload_off'):
|
||||||
|
config_msg['payload_off'] = ha_sensor.get('payload_off')
|
||||||
|
config_msg['ic'] = "mdi:solar-power"
|
||||||
|
config_msg['device'] = { "name":"Solar Inverter", "mf":"Sungrow", "mdl":inverter.getInverterModel(), "connections":[["address", inverter.getHost() ]]}
|
||||||
|
|
||||||
if self.homeassistant:
|
logging.debug(f'MQTT: Topic; {ha_topic}, Message: {config_msg}')
|
||||||
if self.ha_discovery:
|
self.mqtt_queue.append(self.mqtt_client.publish(ha_topic, json.dumps(config_msg), retain=True, qos=1).mid)
|
||||||
for ha_sensor in self.ha_sensors:
|
self.ha_discovery_published = True
|
||||||
config_msg = {}
|
logging.info("MQTT: Published Home Assistant Discovery messages")
|
||||||
if ha_sensor.get('name'):
|
|
||||||
ha_topic = 'homeassistant/' + ha_sensor.get('sensor_type', 'sensor') + '/inverter/' + ha_sensor.get('name').lower().replace(' ','_') + '/config'
|
|
||||||
config_msg['name'] = "Inverter " + ha_sensor.get('name')
|
|
||||||
config_msg['unique_id'] = "inverter_" + ha_sensor.get('name').lower().replace(' ','_')
|
|
||||||
config_msg['state_topic'] = self.sensor_topic
|
|
||||||
config_msg['value_template'] = "{{ value_json." + ha_sensor.get('register') + " }}"
|
|
||||||
if ha_sensor.get('unit'):
|
|
||||||
config_msg['unit_of_measurement'] = ha_sensor.get('unit')
|
|
||||||
if ha_sensor.get('dev_class'):
|
|
||||||
config_msg['device_class'] = ha_sensor.get('dev_class')
|
|
||||||
if ha_sensor.get('state_class'):
|
|
||||||
config_msg['state_class'] = ha_sensor.get('state_class')
|
|
||||||
if ha_sensor.get('payload_on'):
|
|
||||||
config_msg['payload_on'] = ha_sensor.get('payload_on')
|
|
||||||
if ha_sensor.get('payload_off'):
|
|
||||||
config_msg['payload_off'] = ha_sensor.get('payload_off')
|
|
||||||
config_msg['ic'] = "mdi:solar-power"
|
|
||||||
config_msg['device'] = { "name":"Solar Inverter", "mf":"Sungrow", "mdl":self.model, "connections":[["address", self.inverter_ip ]]}
|
|
||||||
|
|
||||||
logging.debug(f'MQTT: Topic; {ha_topic}, Message: {config_msg}')
|
|
||||||
self.mqtt_queue.append(self.mqtt_client.publish(ha_topic, json.dumps(config_msg), retain=True, qos=1).mid)
|
|
||||||
self.ha_discovery = False
|
|
||||||
logging.info("MQTT: Published Home Assistant Discovery messages")
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
+117
-105
@@ -3,45 +3,75 @@ import requests
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# Configure PVOutput
|
"""
|
||||||
|
See: https://pvoutput.org/help/api_specification.html#add-status-service
|
||||||
|
Parameter Field Required Format Unit Example Donation
|
||||||
|
d Output Date Yes yyyymmdd date 20210228
|
||||||
|
t Time Yes hh:mm time 14:00
|
||||||
|
v1 Energy Generation No number wh 10000
|
||||||
|
v2 Power Generation No number watts 2000
|
||||||
|
v3 Energy Consumption No number wh 10000
|
||||||
|
v4 Power Consumption No number watts 2000
|
||||||
|
# At least one of the values v1, v2, v3 or v4 must be present.
|
||||||
|
v5 Temperature No decimal celsius 23.4
|
||||||
|
v6 Voltage No decimal volts 239.2
|
||||||
|
c1 Cumulative Flag No number 1
|
||||||
|
# 1 - Both v1 and v3 values are lifetime energy values. Consumption and generation energy is reset to 0 at the start of the day.
|
||||||
|
# 2 - Only v1 generation is a lifetime energy value.
|
||||||
|
# 3 - Only v3 consumption is a lifetime energy value.
|
||||||
|
n Net Flag No number 1
|
||||||
|
# n parameter when set to 1 will indicate that the power values passed are net export/import rather than gross generation/consumption. This option is used for devices that are unable to report gross consumption data. The provided import/export data is merged with existing generation data to derive consumption.
|
||||||
|
v7 Extended Value v7 No number User Defined Yes
|
||||||
|
v8 Extended Value v8 No number User Defined Yes
|
||||||
|
v9 Extended Value v9 No number User Defined Yes
|
||||||
|
v10 Extended Value v10 No number User Defined Yes
|
||||||
|
v11 Extended Value v11 No number User Defined Yes
|
||||||
|
v12 Extended Value v12 No number User Defined Yes
|
||||||
|
m1 Text Message 1 No text 30 chars max Yes
|
||||||
|
"""
|
||||||
class export_pvoutput(object):
|
class export_pvoutput(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._isConfigured = False
|
|
||||||
self.api_key = None
|
|
||||||
self.system_id = None
|
|
||||||
self.url_base = "https://pvoutput.org/service/r2/"
|
self.url_base = "https://pvoutput.org/service/r2/"
|
||||||
self.url_addbatchstatus = self.url_base + "addbatchstatus.jsp"
|
self.url_addbatchstatus = self.url_base + "addbatchstatus.jsp"
|
||||||
self.url_jointeam = self.url_base + "jointeam.jsp"
|
self.url_jointeam = self.url_base + "jointeam.jsp"
|
||||||
self.url_leaveteam = self.url_base + "leaveteam.jsp"
|
self.url_leaveteam = self.url_base + "leaveteam.jsp"
|
||||||
self.url_getsystem = self.url_base + "getsystem.jsp"
|
self.url_getsystem = self.url_base + "getsystem.jsp"
|
||||||
self.rate_limit = None
|
|
||||||
self.parameters = []
|
|
||||||
self.last_run = None
|
|
||||||
self.tid = '1618'
|
self.tid = '1618'
|
||||||
|
self.status_interval = 5
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def headers(self):
|
def headers(self):
|
||||||
return {
|
return {
|
||||||
"X-Pvoutput-Apikey": self.api_key,
|
"X-Pvoutput-Apikey": self.pvoutput_config['api'],
|
||||||
"X-Pvoutput-SystemId": self.system_id,
|
"X-Pvoutput-SystemId": self.pvoutput_config['sid'],
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"cache-control": "no-cache",
|
"cache-control": "no-cache",
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure(self, config, config_inverter):
|
def configure(self, config, inverter):
|
||||||
self.api_key = config.get('api')
|
self.pvoutput_config = {
|
||||||
self.system_id = config.get('sid')
|
'api': config.get('api', None),
|
||||||
self.rate_limit = config.get('rate_limit', 60)
|
'sid': config.get('sid', None),
|
||||||
self.status_interval = config.get('status_interval',5)
|
'join_team': config.get('join_team', True),
|
||||||
self.batch_points = config.get('batch_points',1)
|
'rate_limit': config.get('rate_limit', 60),
|
||||||
|
'cumulative_flag': config.get('cumulative_flag',0),
|
||||||
|
'batch_points': config.get('batch_points',1)
|
||||||
|
}
|
||||||
|
self.pvoutput_parameters = [{}]
|
||||||
|
self.pvoutput_parameters.pop() # Remove null value from list
|
||||||
|
|
||||||
self.collected_data = {}
|
self.collected_data = {}
|
||||||
self.payload_data = None
|
self.batch_data = []
|
||||||
self.batch_count = 0
|
self.batch_count = 0
|
||||||
self.last_run = 0
|
self.last_run = 0
|
||||||
self.last_publish = 0
|
self.last_publish = 0
|
||||||
|
|
||||||
for parameter in config.get('parameters'):
|
for parameter in config.get('parameters'):
|
||||||
self.parameters.append(parameter)
|
if not inverter.validateRegister(parameter['register']):
|
||||||
|
logging.error(f"PVOutput: Configured to use {parameter['register']} but not configured to scrape this register")
|
||||||
|
return False
|
||||||
|
self.pvoutput_parameters.append(parameter)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.debug(f"PVOutput: Get System ; {self.url_getsystem}, {str(self.headers)}, 'teams': '1'")
|
logging.debug(f"PVOutput: Get System ; {self.url_getsystem}, {str(self.headers)}, 'teams': '1'")
|
||||||
response = requests.post(url=self.url_getsystem,headers=self.headers, params={'teams': '1'}, timeout=3)
|
response = requests.post(url=self.url_getsystem,headers=self.headers, params={'teams': '1'}, timeout=3)
|
||||||
@@ -58,6 +88,7 @@ class export_pvoutput(object):
|
|||||||
for team in teams.split(','):
|
for team in teams.split(','):
|
||||||
if team == self.tid:
|
if team == self.tid:
|
||||||
team_member = True
|
team_member = True
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
logging.error(f"PVOutput: System Status Failed; {str(response.status_code)} Message; {str(response.content)}")
|
logging.error(f"PVOutput: System Status Failed; {str(response.status_code)} Message; {str(response.content)}")
|
||||||
|
|
||||||
@@ -67,76 +98,40 @@ class export_pvoutput(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
join_team = config.get('join_team',True)
|
if not team_member and self.pvoutput_config['join_team']:
|
||||||
if not team_member and join_team:
|
|
||||||
logging.debug(f"PVOutput: Join Team; {self.url_jointeam}, {str(self.headers)}, 'tid': '{self.tid}'")
|
logging.debug(f"PVOutput: Join Team; {self.url_jointeam}, {str(self.headers)}, 'tid': '{self.tid}'")
|
||||||
response = requests.post(url=self.url_jointeam,headers=self.headers, params={'tid': self.tid}, timeout=3)
|
response = requests.post(url=self.url_jointeam,headers=self.headers, params={'tid': self.tid}, timeout=3)
|
||||||
logging.debug(f"PVOutput: Response; {str(response.status_code)} Message; {str(response.content)}")
|
logging.debug(f"PVOutput: Response; {str(response.status_code)} Message; {str(response.content)}")
|
||||||
elif team_member and not join_team:
|
elif team_member and not self.pvoutput_config['join_team']:
|
||||||
logging.debug(f"PVOutput: Leave Team; {self.url_leaveteam}, {str(self.headers)}, 'tid': '{self.tid}'")
|
logging.debug(f"PVOutput: Leave Team; {self.url_leaveteam}, {str(self.headers)}, 'tid': '{self.tid}'")
|
||||||
response = requests.post(url=self.url_leaveteam,headers=self.headers, params={'tid': self.tid}, timeout=3)
|
response = requests.post(url=self.url_leaveteam,headers=self.headers, params={'tid': self.tid}, timeout=3)
|
||||||
logging.debug(f"PVOutput: Response; {str(response.status_code)} Message; {str(response.content)}")
|
logging.debug(f"PVOutput: Response; {str(response.status_code)} Message; {str(response.content)}")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.debug(f"{err}")
|
pass
|
||||||
|
|
||||||
logging.info(f"PVOutput: Configured export to {invertername} every {self.status_interval} minutes")
|
logging.info(f"PVOutput: Configured export to {invertername} every {self.status_interval} minutes")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def publish(self, inverter):
|
def collect_data(self, inverter):
|
||||||
"""
|
|
||||||
See: https://pvoutput.org/help/api_specification.html#add-status-service
|
|
||||||
Parameter Field Required Format Unit Example Donation
|
|
||||||
d Output Date Yes yyyymmdd date 20210228
|
|
||||||
t Time Yes hh:mm time 14:00
|
|
||||||
v1 Energy Generation No number wh 10000
|
|
||||||
v2 Power Generation No number watts 2000
|
|
||||||
v3 Energy Consumption No number wh 10000
|
|
||||||
v4 Power Consumption No number watts 2000
|
|
||||||
# At least one of the values v1, v2, v3 or v4 must be present.
|
|
||||||
v5 Temperature No decimal celsius 23.4
|
|
||||||
v6 Voltage No decimal volts 239.2
|
|
||||||
c1 Cumulative Flag No number 1
|
|
||||||
# 1 - Both v1 and v3 values are lifetime energy values. Consumption and generation energy is reset to 0 at the start of the day.
|
|
||||||
# 2 - Only v1 generation is a lifetime energy value.
|
|
||||||
# 3 - Only v3 consumption is a lifetime energy value.
|
|
||||||
n Net Flag No number 1
|
|
||||||
# n parameter when set to 1 will indicate that the power values passed are net export/import rather than gross generation/consumption. This option is used for devices that are unable to report gross consumption data. The provided import/export data is merged with existing generation data to derive consumption.
|
|
||||||
v7 Extended Value v7 No number User Defined Yes
|
|
||||||
v8 Extended Value v8 No number User Defined Yes
|
|
||||||
v9 Extended Value v9 No number User Defined Yes
|
|
||||||
v10 Extended Value v10 No number User Defined Yes
|
|
||||||
v11 Extended Value v11 No number User Defined Yes
|
|
||||||
v12 Extended Value v12 No number User Defined Yes
|
|
||||||
m1 Text Message 1 No text 30 chars max Yes
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check all required registers have been returned by the inverter
|
# Check all required registers have been returned by the inverter
|
||||||
if not inverter.get('timestamp', False):
|
if not inverter.validateLatestScrape('timestamp'):
|
||||||
logging.warning('PVOutput: Skipping, Timestamp missing from last scrape')
|
logging.error(f"PVOutput: Skipped collecting data, Timestamp missing from last scrape")
|
||||||
return False
|
return False
|
||||||
for parameter in self.parameters:
|
for parameter in self.pvoutput_parameters:
|
||||||
if parameter.get('name')[0] == 'v':
|
if not inverter.validateLatestScrape(parameter['register']):
|
||||||
if not inverter.get(parameter.get('register', False)) and not inverter.get(parameter.get('register', False)) == 0:
|
logging.error(f"PVOutput: Skipped collecting data, {parameter['register']} missing from last scrape")
|
||||||
logging.warning(f"PVOutput: Skipping, {parameter.get('name')} configured to use {parameter.get('register')} but inverter is not returning this register")
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
now = datetime.datetime.strptime(inverter.get('timestamp'), "%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
# Add new data to old data and increase count of data points
|
# Add new data to old data and increase count of data points
|
||||||
for parameter in self.parameters:
|
for parameter in self.pvoutput_parameters:
|
||||||
if parameter.get('name')[0] == 'v':
|
value = inverter.getRegisterValue(parameter.get('register'))
|
||||||
if self.collected_data.get(parameter.get('name'),False):
|
if parameter.get('multiple'):
|
||||||
if parameter.get('multiple'):
|
value = value * parameter.get('multiple')
|
||||||
self.collected_data[parameter.get('name')] = round(self.collected_data[parameter.get('name')] + (inverter.get(parameter.get('register')) * parameter.get('multiple')),3)
|
|
||||||
else:
|
if self.collected_data.get(parameter.get('name'),False):
|
||||||
self.collected_data[parameter.get('name')] = round(self.collected_data[parameter.get('name')] + inverter.get(parameter.get('register')),3)
|
self.collected_data[parameter.get('name')] = round(self.collected_data[parameter.get('name')] + value,3)
|
||||||
else:
|
else:
|
||||||
if parameter.get('multiple'):
|
self.collected_data[parameter.get('name')] = value
|
||||||
self.collected_data[parameter.get('name')] = round(inverter.get(parameter.get('register')) * parameter.get('multiple'),3)
|
|
||||||
else:
|
|
||||||
self.collected_data[parameter.get('name')] = inverter.get(parameter.get('register'))
|
|
||||||
elif parameter.get('name') == 'c1':
|
|
||||||
cumulative_energy = parameter.get('value')
|
|
||||||
|
|
||||||
if self.collected_data.get('count',False):
|
if self.collected_data.get('count',False):
|
||||||
self.collected_data['count'] +=1
|
self.collected_data['count'] +=1
|
||||||
@@ -145,41 +140,58 @@ class export_pvoutput(object):
|
|||||||
|
|
||||||
logging.debug(f'PVOutput: Data Logged: {self.collected_data}')
|
logging.debug(f'PVOutput: Data Logged: {self.collected_data}')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def publish(self, inverter):
|
||||||
|
self.collect_data(inverter)
|
||||||
|
|
||||||
# Process data points every status_interval
|
# Process data points every status_interval
|
||||||
if((time.time() - self.last_publish) >= (self.status_interval * 60)):
|
if((time.time() - self.last_publish) >= (self.status_interval * 60)):
|
||||||
for v in self.collected_data:
|
any_data = False
|
||||||
if v[0] == 'v' and not self.collected_data[v] == 0:
|
if inverter.validateLatestScrape('timestamp'):
|
||||||
if v[1] == '6' or v[1] == '7': # Round to 1 decimal place
|
now = datetime.datetime.strptime(inverter.getRegisterValue('timestamp'), "%Y-%m-%d %H:%M:%S")
|
||||||
self.collected_data[v] = round(self.collected_data[v] / self.collected_data['count'], 1)
|
data_point = str(now.strftime("%Y%m%d")) + "," + str(now.strftime("%H:%M"))
|
||||||
else: # Getting errors when uploading decimals for power/energy so return INT
|
for x in range(1, 13):
|
||||||
self.collected_data[v] = int((self.collected_data[v] / self.collected_data['count']))
|
field = 'v' + str(x)
|
||||||
|
if self.collected_data.get(field):
|
||||||
|
if x == 6 or x == 7: # Round to 1 decimal place
|
||||||
|
value = round(self.collected_data[field] / self.collected_data['count'], 1)
|
||||||
|
else: # Getting errors when uploading decimals for power/energy so return INT
|
||||||
|
value = int((self.collected_data[field] / self.collected_data['count']))
|
||||||
|
data_point = data_point + "," + str(value)
|
||||||
|
any_data = True
|
||||||
|
else:
|
||||||
|
data_point = data_point + ","
|
||||||
|
self.collected_data = {}
|
||||||
|
|
||||||
data_point = str(now.strftime("%Y%m%d")) + "," + str(now.strftime("%H:%M"))
|
if any_data:
|
||||||
|
self.batch_data.append(data_point)
|
||||||
|
else:
|
||||||
|
logging.warning(f"PVOutput: No data collected in last {(self.status_interval * 60)} minutes")
|
||||||
|
|
||||||
for x in range(1, 13):
|
# Max upload is 30, if over 30 then remove the oldest one
|
||||||
v = 'v' + str(x)
|
if self.batch_data.__len__() > 30:
|
||||||
if self.collected_data.get(v):
|
logging.warning(f"PVOutput: Over 30 data points scheduled to upload. max is 30 so removing oldest data point")
|
||||||
data_point = data_point + "," + str(self.collected_data[v])
|
self.batch_data.pop(0)
|
||||||
else:
|
|
||||||
data_point = data_point + ","
|
|
||||||
|
|
||||||
self.collected_data = {}
|
|
||||||
|
|
||||||
if self.payload_data:
|
|
||||||
self.payload_data = self.payload_data + ";"
|
|
||||||
else:
|
|
||||||
self.payload_data = ""
|
|
||||||
|
|
||||||
self.payload_data = self.payload_data + data_point
|
|
||||||
|
|
||||||
self.batch_count +=1
|
self.batch_count +=1
|
||||||
|
if self.batch_count >= self.pvoutput_config['batch_points']:
|
||||||
if self.batch_count >= self.batch_points:
|
if not self.batch_data.__len__() > 0:
|
||||||
payload = {}
|
logging.warning(f"PVOutput: No data collected in last {((self.status_interval * 60) * self.batch_count)} minutes, Skipping upload")
|
||||||
payload['data'] = self.payload_data
|
return False
|
||||||
|
elif self.batch_data.__len__() >= 1:
|
||||||
|
payload_data = None
|
||||||
|
for data in self.batch_data:
|
||||||
|
if payload_data:
|
||||||
|
payload_data = payload_data + ";" + data
|
||||||
|
else:
|
||||||
|
payload_data = data
|
||||||
|
|
||||||
if 'cumulative_energy' in locals():
|
payload = {}
|
||||||
payload['c1'] = cumulative_energy
|
payload['data'] = payload_data
|
||||||
|
|
||||||
|
if self.pvoutput_config['cumulative_flag'] > 0:
|
||||||
|
payload['c1'] = self.pvoutput_config['cumulative_flag']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.debug("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload))
|
logging.debug("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload))
|
||||||
@@ -187,9 +199,9 @@ class export_pvoutput(object):
|
|||||||
self.batch_count = 0
|
self.batch_count = 0
|
||||||
|
|
||||||
if response.status_code != requests.codes.ok:
|
if response.status_code != requests.codes.ok:
|
||||||
logging.error(f"PVOutput: Upload Failed; {str(response.status_code)} Message; {str(response.content)}")
|
logging.error(f"PVOutput: Upload Failed; {str(response.status_code)} Message; {str(response.text)}")
|
||||||
else:
|
else:
|
||||||
self.payload_data = None
|
self.batch_data = []
|
||||||
self.last_publish = time.time()
|
self.last_publish = time.time()
|
||||||
logging.info("PVOutput: Data uploaded")
|
logging.info("PVOutput: Data uploaded")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -198,6 +210,6 @@ class export_pvoutput(object):
|
|||||||
else:
|
else:
|
||||||
logging.info("PVOutput: Data added to next batch upload")
|
logging.info("PVOutput: Data added to next batch upload")
|
||||||
else:
|
else:
|
||||||
logging.info(f"PVOutput: Data logged, next upload in {int((self.status_interval * 60) - (time.time() - self.last_publish))} secs")
|
logging.info(f"PVOutput: Data logged, next upload in {int(((self.status_interval) * 60) - (time.time() - self.last_publish))} secs")
|
||||||
|
|
||||||
self.last_run = time.time()
|
self.last_run = time.time()
|
||||||
@@ -8,39 +8,33 @@ import logging
|
|||||||
class export_webserver(object):
|
class export_webserver(object):
|
||||||
html_body = "Pending Data Retrieval"
|
html_body = "Pending Data Retrieval"
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._isConfigured = False
|
False
|
||||||
return
|
|
||||||
|
|
||||||
# Configure Webserver
|
# Configure Webserver
|
||||||
def configure(self, config, config_inverter):
|
def configure(self, config, inverter):
|
||||||
try:
|
try:
|
||||||
self.webServer = HTTPServer(('', config.get('port',8080)), MyServer)
|
self.webServer = HTTPServer(('', config.get('port',8080)), MyServer)
|
||||||
self.t = Thread(target=self.webServer.serve_forever)
|
self.t = Thread(target=self.webServer.serve_forever)
|
||||||
self.t.daemon = True # Make it a deamon, so if main loop ends the webserver dies
|
self.t.daemon = True # Make it a deamon, so if main loop ends the webserver dies
|
||||||
self.t.start()
|
self.t.start()
|
||||||
self.config_inverter = config_inverter
|
|
||||||
logging.info(f"Webserver: Configured")
|
logging.info(f"Webserver: Configured")
|
||||||
self._isConfigured = True
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error(f"Webserver: Error: {err}")
|
logging.error(f"Webserver: Error: {err}")
|
||||||
|
return False
|
||||||
return self._isConfigured
|
return True
|
||||||
|
|
||||||
def publish(self, inverter):
|
def publish(self, inverter):
|
||||||
if not self._isConfigured:
|
body = "<h3>Sungather v" + __version__ + "</h3></p><table><th>Address</th><tr><th>Register</th><th>Value</th></tr>"
|
||||||
logging.info("Webserver: Skipped, Initial Configuration Failed")
|
for register, value in inverter.latest_scrape.items():
|
||||||
return False
|
body = body + f"<tr><td>{str(inverter.getRegisterAddress(register))}</td><td>{str(register)}</td><td>{str(value)} {str(inverter.getRegisterUnit(register))}</td></tr>"
|
||||||
|
export_webserver.html_body = body + f"</table><p>Total {len(inverter.latest_scrape)} registers"
|
||||||
|
|
||||||
body = "<h3>Sungather v" + __version__ + "</h3></p><table><tr><th>Register</th><th>Value</th>"
|
body = "</p></p><table><tr><th>Configuration</th><th>Value</th></tr>"
|
||||||
for register in inverter:
|
for setting, value in inverter.client_config.items():
|
||||||
body = body + f"<tr><td>{register}</td><td>{inverter.get(register)}</td></tr>"
|
body = body + f"<tr><td>{str(setting)}</td><td>{str(value)}</td></tr>"
|
||||||
export_webserver.html_body = body + f"</table><p>Total {len(inverter)} registers"
|
for setting, value in inverter.inverter_config.items():
|
||||||
|
body = body + f"<tr><td>{str(setting)}</td><td>{str(value)}</td></tr>"
|
||||||
body = "</p></p><table><tr><th>Configuration</th><th>Value</th>"
|
|
||||||
for config in self.config_inverter:
|
|
||||||
body = body + f"<tr><td>{config}</td><td>{self.config_inverter.get(config)}</td></tr>"
|
|
||||||
export_webserver.html_body = export_webserver.html_body + body + f"</table></p>"
|
export_webserver.html_body = export_webserver.html_body + body + f"</table></p>"
|
||||||
logging.info("Webserver: Content Updated")
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
+204
-93
@@ -5,7 +5,7 @@ registers:
|
|||||||
address: 4990
|
address: 4990
|
||||||
datatype: "UTF-8"
|
datatype: "UTF-8"
|
||||||
- name: "device_type_code"
|
- name: "device_type_code"
|
||||||
level: 0
|
level: 3
|
||||||
address: 5000
|
address: 5000
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
datarange:
|
datarange:
|
||||||
@@ -178,7 +178,8 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5001
|
address: 5001
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "kW"
|
||||||
- name: "output_type"
|
- name: "output_type"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5002
|
address: 5002
|
||||||
@@ -194,96 +195,114 @@ registers:
|
|||||||
level: 0
|
level: 0
|
||||||
address: 5003
|
address: 5003
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
- name: "total_power_yields"
|
- name: "total_power_yields"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5004
|
address: 5004
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
unit: "KWh"
|
||||||
- name: "total_running_time"
|
- name: "total_running_time"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5006
|
address: 5006
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
|
unit: "h"
|
||||||
- name: "internal_temperature"
|
- name: "internal_temperature"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5008
|
address: 5008
|
||||||
datatype: "S16"
|
datatype: "S16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "°C"
|
||||||
- name: "total_apparent_power"
|
- name: "total_apparent_power"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5009
|
address: 5009
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
models: ["SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT", "SG33K3J", "SG36KTL-M", "SG40KTL-M", "SG50KTL", "SG50KTL-M", "SG60KTL", "SG60KTL-M", "SG60KU-M", "SG80KTL", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG33CX", "SG40CX", "SG50CX", "SG110CX", "SG250HX", "SG30CX", "SG36CX-US", "SG60CX-US", "SG250HX-US", "SG250HX-IN", "SG25CX-SA", "SG100CX", "SG75CX", "SG225HX"]
|
models: ["SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT", "SG33K3J", "SG36KTL-M", "SG40KTL-M", "SG50KTL", "SG50KTL-M", "SG60KTL", "SG60KTL-M", "SG60KU-M", "SG80KTL", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG33CX", "SG40CX", "SG50CX", "SG110CX", "SG250HX", "SG30CX", "SG36CX-US", "SG60CX-US", "SG250HX-US", "SG250HX-IN", "SG25CX-SA", "SG100CX", "SG75CX", "SG225HX"]
|
||||||
|
unit: "VA"
|
||||||
- name: "mppt_1_voltage"
|
- name: "mppt_1_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5011
|
address: 5011
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
- name: "mppt_1_current"
|
- name: "mppt_1_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5012
|
address: 5012
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
- name: "mppt_2_voltage"
|
- name: "mppt_2_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5013
|
address: 5013
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG30KTL","SG10KTL","SG12KTL","SG15KTL","SG20KTL","SG30KU","SG36KTL","SG36KU","SG40KTL","SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG34KJ","SG50KTL-M-20","SG60KU-M","SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG5K-D","SG8K-D"]
|
models: ["SG30KTL","SG10KTL","SG12KTL","SG15KTL","SG20KTL","SG30KU","SG36KTL","SG36KU","SG40KTL","SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG34KJ","SG50KTL-M-20","SG60KU-M","SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG5K-D","SG8K-D"]
|
||||||
- name: "mppt_2_current"
|
- name: "mppt_2_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5014
|
address: 5014
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG30KTL","SG10KTL","SG12KTL","SG15KTL","SG20KTL","SG30KU","SG36KTL","SG36KU","SG40KTL","SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG34KJ","SG50KTL-M-20","SG60KU-M","SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG5K-D","SG8K-D"]
|
models: ["SG30KTL","SG10KTL","SG12KTL","SG15KTL","SG20KTL","SG30KU","SG36KTL","SG36KU","SG40KTL","SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG34KJ","SG50KTL-M-20","SG60KU-M","SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG5K-D","SG8K-D"]
|
||||||
- name: "mppt_3_voltage"
|
- name: "mppt_3_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5015
|
address: 5015
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX"]
|
models: ["SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX"]
|
||||||
- name: "mppt_3_current"
|
- name: "mppt_3_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5016
|
address: 5016
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX","SG8K-D"]
|
models: ["SG40KTL-M","SG50KTL-M","SG60KTL-M","SG30KTL-M","SG30KTL-M-V31","SG33KTL-M","SG36KTL-M","SG33K3J","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG30CX","SG33CX","SG36CX-US","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG25CX-SA","SG75CX","SG8K-D"]
|
||||||
- name: "total_dc_power"
|
- name: "total_dc_power"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5017
|
address: 5017
|
||||||
datatype: "S32" # Documentation says Unsigned, but seems to be returning Signed 32bit
|
datatype: "S32" # Documentation says Unsigned, but seems to be returning Signed 32bit
|
||||||
|
unit: "W"
|
||||||
- name: "phase_a_voltage"
|
- name: "phase_a_voltage"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5019
|
address: 5019
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
- name: "phase_b_voltage"
|
- name: "phase_b_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5020
|
address: 5020
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
- name: "phase_c_voltage"
|
- name: "phase_c_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5021
|
address: 5021
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
- name: "phase_a_current"
|
- name: "phase_a_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5022
|
address: 5022
|
||||||
datatype: "S16" # Documentation says Unsigned, but seems to be returning Signed 16bit
|
datatype: "S16" # Documentation says Unsigned, but seems to be returning Signed 16bit
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "phase_b_current"
|
- name: "phase_b_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5023
|
address: 5023
|
||||||
datatype: "S16" # Documentation says Unsigned, but seems to be returning Signed 16bit
|
datatype: "S16" # Documentation says Unsigned, but seems to be returning Signed 16bit
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "phase_c_current"
|
- name: "phase_c_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5024
|
address: 5024
|
||||||
datatype: "S16" # Documentation says Unsigned, but seems to be returning Signed 16bit
|
datatype: "S16" # Documentation says Unsigned, but seems to be returning Signed 16bit
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5025
|
# address: 5025
|
||||||
@@ -298,21 +317,24 @@ registers:
|
|||||||
level: 0
|
level: 0
|
||||||
address: 5031
|
address: 5031
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
unit: "W"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT","SG5K-D","SG8K-D"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT","SG5K-D","SG8K-D"]
|
||||||
- name: "total_reactive_power"
|
- name: "total_reactive_power"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5033
|
address: 5033
|
||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
|
unit: "Var"
|
||||||
- name: "power_factor" # >0 means leading, <0 means lagging
|
- name: "power_factor" # >0 means leading, <0 means lagging
|
||||||
level: 2
|
level: 2
|
||||||
address: 5035
|
address: 5035
|
||||||
datatype: "S16"
|
datatype: "S16"
|
||||||
multiple: 0.001
|
accuracy: 0.001
|
||||||
- name: "grid_frequency"
|
- name: "grid_frequency"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5036
|
address: 5036
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "Hz"
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5037
|
# address: 5037
|
||||||
# datatype: "U16"
|
# datatype: "U16"
|
||||||
@@ -389,7 +411,8 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5049
|
address: 5049
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "kVar"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT","SG5K-D","SG8K-D"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT","SG5K-D","SG8K-D"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5050 - 5070
|
# address: 5050 - 5070
|
||||||
@@ -398,6 +421,7 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5071
|
address: 5071
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "k-ohm"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5072
|
# address: 5072
|
||||||
@@ -408,11 +432,13 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5077
|
address: 5077
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
unit: "w"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "reactive_power_regulation_setpoint"
|
- name: "reactive_power_regulation_setpoint"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5079
|
address: 5079
|
||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
|
unit: "Var"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "work_state_2" # See Appendix 2
|
- name: "work_state_2" # See Appendix 2
|
||||||
level: 2
|
level: 2
|
||||||
@@ -454,119 +480,151 @@ registers:
|
|||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
indicator: True
|
indicator: True
|
||||||
smart_meter: True
|
smart_meter: True
|
||||||
|
unit: "w"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "meter_a_phase_power"
|
- name: "meter_a_phase_power"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5085
|
address: 5085
|
||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
|
unit: "w"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "meter_b_phase_power"
|
- name: "meter_b_phase_power"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5087
|
address: 5087
|
||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
|
unit: "w"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "meter_c_phase_power"
|
- name: "meter_c_phase_power"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5089
|
address: 5089
|
||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
|
unit: "w"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "load_power"
|
- name: "load_power"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5091
|
address: 5091
|
||||||
datatype: "S32"
|
datatype: "S32"
|
||||||
smart_meter: True
|
smart_meter: True
|
||||||
|
unit: "w"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "daily_export_energy"
|
- name: "daily_export_energy"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5093
|
address: 5093
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
||||||
- name: "total_export_energy"
|
- name: "total_export_energy"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5095
|
address: 5095
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
||||||
- name: "daily_import_energy"
|
- name: "daily_import_energy"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5097
|
address: 5097
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
||||||
- name: "total_import_energy"
|
- name: "total_import_energy"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5099
|
address: 5099
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
||||||
- name: "daily_direct_energy_consumption"
|
- name: "daily_direct_energy_consumption"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5101
|
address: 5101
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
||||||
- name: "total_direct_energy_consumption"
|
- name: "total_direct_energy_consumption"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5103
|
address: 5103
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG5K-D","SG8K-D"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5105 - 5112
|
# address: 5105 - 5112
|
||||||
|
- name: "daily_running_time"
|
||||||
|
level: 1
|
||||||
|
address: 5113
|
||||||
|
datatype: "U16"
|
||||||
|
unit: "min"
|
||||||
- name: "mppt_4_voltage"
|
- name: "mppt_4_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5115
|
address: 5115
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG50KTL-M","SG60KTL-M","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG50KTL-M","SG60KTL-M","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_4_current"
|
- name: "mppt_4_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5116
|
address: 5116
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG50KTL-M","SG60KTL-M","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG50KTL-M","SG60KTL-M","SG49K5J","SG50KTL-M-20","SG60KU-M","SG80KTL-M","SG40CX","SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_5_voltage"
|
- name: "mppt_5_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5117
|
address: 5117
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_5_current"
|
- name: "mppt_5_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5118
|
address: 5118
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG50CX","SG60CX-US","SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_6_voltage"
|
- name: "mppt_6_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5119
|
address: 5119
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_6_current"
|
- name: "mppt_6_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5120
|
address: 5120
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_7_voltage"
|
- name: "mppt_7_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5121
|
address: 5121
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_7_current"
|
- name: "mppt_7_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5122
|
address: 5122
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_8_voltage"
|
- name: "mppt_8_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5123
|
address: 5123
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_8_current"
|
- name: "mppt_8_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5124
|
address: 5124
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5125
|
# address: 5125
|
||||||
@@ -576,55 +634,64 @@ registers:
|
|||||||
level: 1
|
level: 1
|
||||||
address: 5128
|
address: 5128
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "mppt_9_voltage"
|
- name: "mppt_9_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5130
|
address: 5130
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_9_current"
|
- name: "mppt_9_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5131
|
address: 5131
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
models: ["SG110CX","SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN","SG75CX"]
|
||||||
- name: "mppt_10_voltage"
|
- name: "mppt_10_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5132
|
address: 5132
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
||||||
- name: "mppt_10_current"
|
- name: "mppt_10_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5133
|
address: 5133
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
||||||
- name: "mppt_11_voltage"
|
- name: "mppt_11_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5134
|
address: 5134
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
||||||
- name: "mppt_11_current"
|
- name: "mppt_11_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5135
|
address: 5135
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
||||||
- name: "mppt_12_voltage"
|
- name: "mppt_12_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5136
|
address: 5136
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
||||||
- name: "mppt_12_current"
|
- name: "mppt_12_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5137
|
address: 5137
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "A"
|
||||||
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
models: ["SG250HX","SG250HX-US","SG100CX","SG100CX-JP","SG250HX-IN"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5138 - 5139
|
# address: 5138 - 5139
|
||||||
@@ -640,25 +707,29 @@ registers:
|
|||||||
level: 1
|
level: 1
|
||||||
address: 5144
|
address: 5144
|
||||||
datatype: "U32"
|
datatype: "U32"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "kWh"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG80KTL-M","SG111HV","SG125HV","SG125HV-20","SG33CX","SG40CX","SG50CX","SG110CX","SG250HX","SG30CX","SG36CX-US","SG60CX-US","SG250HX-US","SG250HX-IN","SG25CX-SA","SG100CX","SG75CX","SG225HX","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG80KTL-M","SG111HV","SG125HV","SG125HV-20","SG33CX","SG40CX","SG50CX","SG110CX","SG250HX","SG30CX","SG36CX-US","SG60CX-US","SG250HX-US","SG250HX-IN","SG25CX-SA","SG100CX","SG75CX","SG225HX","SG5K-D","SG8K-D"]
|
||||||
- name: "negative_voltage_to_the_ground"
|
- name: "negative_voltage_to_the_ground"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5146
|
address: 5146
|
||||||
datatype: "S16"
|
datatype: "S16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "bus_voltage"
|
- name: "bus_voltage"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5147
|
address: 5147
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "V"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "grid_frequency"
|
- name: "grid_frequency"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5148
|
address: 5148
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
|
unit: "Hz"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG80KTL-M","SG111HV","SG125HV","SG125HV-20","SG33CX","SG40CX","SG50CX","SG110CX","SG250HX","SG30CX","SG36CX-US","SG60CX-US","SG250HX-US","SG250HX-IN","SG25CX-SA","SG100CX","SG75CX","SG225HX","SG5K-D","SG8K-D"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M","SG3.0RT","SG4.0RT","SG5.0RT","SG6.0RT","SG7.0RT","SG8.0RT","SG10RT","SG12RT","SG15RT","SG17RT","SG20RT","SG80KTL-M","SG111HV","SG125HV","SG125HV-20","SG33CX","SG40CX","SG50CX","SG110CX","SG250HX","SG30CX","SG36CX-US","SG60CX-US","SG250HX-US","SG250HX-IN","SG25CX-SA","SG100CX","SG75CX","SG225HX","SG5K-D","SG8K-D"]
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5149
|
# address: 5149
|
||||||
@@ -791,145 +862,170 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 7013
|
address: 7013
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
|
unit: "A"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "string_2_current"
|
- name: "string_2_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7014
|
address: 7014
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
|
unit: "A"
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
||||||
- name: "string_3_current"
|
- name: "string_3_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7015
|
address: 7015
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", ]
|
||||||
- name: "string_4_current"
|
- name: "string_4_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7016
|
address: 7016
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", ]
|
||||||
- name: "string_5_current"
|
- name: "string_5_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7017
|
address: 7017
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", ]
|
||||||
- name: "string_6_current"
|
- name: "string_6_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7018
|
address: 7018
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", ]
|
||||||
- name: "string_7_current"
|
- name: "string_7_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7019
|
address: 7019
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_8_current"
|
- name: "string_8_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7020
|
address: 7020
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_9_current"
|
- name: "string_9_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7021
|
address: 7021
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KU", "SG36KTL", "SG36KU", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG33K3J", "SG49K5J", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_10_current"
|
- name: "string_10_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7022
|
address: 7022
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG30KU", "SG36KTL", "SG36KU", "SG50KTL-M", "SG60KTL-M", "SG49K5J", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_11_current"
|
- name: "string_11_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7023
|
address: 7023
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG50KTL-M", "SG60KTL-M", "SG49K5J", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_12_current"
|
- name: "string_12_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7024
|
address: 7024
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG50KTL-M", "SG60KTL-M", "SG49K5J", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_13_current"
|
- name: "string_13_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7025
|
address: 7025
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG60KTL-M", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", ]
|
||||||
- name: "string_14_current"
|
- name: "string_14_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7026
|
address: 7026
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG60KTL-M", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG30CX", "SG33CX", "SG36CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", ]
|
||||||
- name: "string_15_current"
|
- name: "string_15_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7027
|
address: 7027
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG60KTL-M", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX"]
|
||||||
- name: "string_16_current"
|
- name: "string_16_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7028
|
address: 7028
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG60KTL-M", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG80KTL-M", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX"]
|
||||||
- name: "string_17_current"
|
- name: "string_17_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7029
|
address: 7029
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG80KTL", "SG80KTL-20", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX"]
|
||||||
- name: "string_18_current"
|
- name: "string_18_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7030
|
address: 7030
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG80KTL", "SG80KTL-20", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG75CX"]
|
||||||
- name: "string_19_current"
|
- name: "string_19_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7031
|
address: 7031
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN"]
|
||||||
- name: "string_20_current"
|
- name: "string_20_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7032
|
address: 7032
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN"]
|
||||||
- name: "string_21_current"
|
- name: "string_21_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7033
|
address: 7033
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN"]
|
||||||
- name: "string_22_current"
|
- name: "string_22_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7034
|
address: 7034
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN"]
|
||||||
- name: "string_23_current"
|
- name: "string_23_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7035
|
address: 7035
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
models: ["SG30KTL", "SG10KTL", "SG12KTL", "SG15KTL", "SG20KTL", "SG30KU", "SG36KTL", "SG36KU", "SG40KTL", "SG40KTL-M", "SG50KTL-M", "SG60KTL-M", "SG60KU", "SG30KTL-M", "SG30KTL-M-V31", "SG33KTL-M", "SG36KTL-M", "SG33K3J", "SG49K5J", "SG34KJ", "LP_P34KSG", "SG50KTL-M-20", "SG60KTL", "SG80KTL", "SG80KTL-20", "SG60KU-M", "SG5KTL-MT", "SG6KTL-MT", "SG8KTL-M", "SG10KTL-M", "SG10KTL-MT", "SG12KTL-M", "SG15KTL-M", "SG17KTL-M", "SG20KTL-M", "SG80KTL-M", "SG111HV", "SG125HV", "SG125HV-20", "SG30CX", "SG33CX", "SG36CX-US", "SG40CX", "SG50CX", "SG60CX-US", "SG110CX", "SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN", "SG25CX-SA", "SG75CX", "SG3.0RT", "SG4.0RT", "SG5.0RT", "SG6.0RT", "SG7.0RT", "SG8.0RT", "SG10RT", "SG12RT", "SG15RT", "SG17RT", "SG20RT"]
|
unit: "A"
|
||||||
|
models: ["SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN"]
|
||||||
- name: "string_24_current"
|
- name: "string_24_current"
|
||||||
level: 2
|
level: 2
|
||||||
address: 7036
|
address: 7036
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.01
|
accuracy: 0.01
|
||||||
|
unit: "A"
|
||||||
|
models: ["SG250HX", "SG250HX-US", "SG100CX", "SG100CX-JP", "SG250HX-IN"]
|
||||||
- name: "system_state"
|
- name: "system_state"
|
||||||
level: 2
|
level: 2
|
||||||
address: 13000
|
address: 13000
|
||||||
@@ -1002,30 +1098,32 @@ registers:
|
|||||||
level: 0
|
level: 0
|
||||||
address: 5000
|
address: 5000
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "YYYY"
|
||||||
- name: "month"
|
- name: "month"
|
||||||
level: 0
|
level: 0
|
||||||
address: 5001
|
address: 5001
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "MM"
|
||||||
- name: "day"
|
- name: "day"
|
||||||
level: 0
|
level: 0
|
||||||
address: 5002
|
address: 5002
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "DD"
|
||||||
- name: "hour"
|
- name: "hour"
|
||||||
level: 0
|
level: 0
|
||||||
address: 5003
|
address: 5003
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
- name: "hour"
|
unit: "HH"
|
||||||
level: 0
|
|
||||||
address: 5003
|
|
||||||
datatype: "U16"
|
|
||||||
- name: "minute"
|
- name: "minute"
|
||||||
level: 0
|
level: 0
|
||||||
address: 5004
|
address: 5004
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "MM"
|
||||||
- name: "second"
|
- name: "second"
|
||||||
level: 0
|
level: 0
|
||||||
address: 5005
|
address: 5005
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "SS"
|
||||||
- name: "start_stop"
|
- name: "start_stop"
|
||||||
level: 1
|
level: 1
|
||||||
address: 5006
|
address: 5006
|
||||||
@@ -1048,6 +1146,8 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5008
|
address: 5008
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "%"
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5009
|
# address: 5009
|
||||||
# datatype: "U16"
|
# datatype: "U16"
|
||||||
@@ -1071,11 +1171,13 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5012
|
address: 5012
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "A"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "current_transformer_range"
|
- name: "current_transformer_range"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5013
|
address: 5013
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
unit: "A"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "current_transformer"
|
- name: "current_transformer"
|
||||||
level: 2
|
level: 2
|
||||||
@@ -1091,17 +1193,21 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5015
|
address: 5015
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "%"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "installed_pv_power"
|
- name: "installed_pv_power"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5016
|
address: 5016
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
|
accuracy: 0.01
|
||||||
|
unit: "KW"
|
||||||
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"]
|
||||||
- name: "power_factor_setting"
|
- name: "power_factor_setting"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5019
|
address: 5019
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.001
|
accuracy: 0.001
|
||||||
- name: "scheduling_achieve_active_overload"
|
- name: "scheduling_achieve_active_overload"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5020
|
address: 5020
|
||||||
@@ -1144,17 +1250,22 @@ registers:
|
|||||||
level: 2
|
level: 2
|
||||||
address: 5037
|
address: 5037
|
||||||
datatype: "S16"
|
datatype: "S16"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "%"
|
||||||
# - name: "Reserved"
|
# - name: "Reserved"
|
||||||
# address: 5038
|
# address: 5038
|
||||||
- name: "power_limitation_adjustment"
|
- name: "power_limitation_adjustment"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5039
|
address: 5039
|
||||||
datatype: "U16"
|
datatype: "U16"
|
||||||
multiple: 0.1
|
accuracy: 0.1
|
||||||
|
unit: "kW"
|
||||||
- name: "reactive_power_adjustment"
|
- name: "reactive_power_adjustment"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5040
|
address: 5040
|
||||||
datatype: "S16"
|
datatype: "S16"
|
||||||
|
accuracy: 0.1
|
||||||
|
unit: "kVar"
|
||||||
- name: "pid_recovery"
|
- name: "pid_recovery"
|
||||||
level: 2
|
level: 2
|
||||||
address: 5041
|
address: 5041
|
||||||
|
|||||||
+403
-300
@@ -3,7 +3,6 @@
|
|||||||
from SungrowModbusTcpClient import SungrowModbusTcpClient
|
from SungrowModbusTcpClient import SungrowModbusTcpClient
|
||||||
from SungrowModbusWebClient import SungrowModbusWebClient
|
from SungrowModbusWebClient import SungrowModbusWebClient
|
||||||
from pymodbus.client.sync import ModbusTcpClient
|
from pymodbus.client.sync import ModbusTcpClient
|
||||||
from threading import Thread
|
|
||||||
from version import __version__
|
from version import __version__
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -15,208 +14,378 @@ import yaml
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
config = None
|
|
||||||
registers = None
|
|
||||||
register_ranges = None
|
|
||||||
inverter = None
|
|
||||||
client = None
|
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)s %(levelname)-8s %(message)s',
|
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||||
level=20,
|
level=20,
|
||||||
datefmt='%Y-%m-%d %H:%M:%S')
|
datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
def connect_inverter():
|
class SungrowInverter():
|
||||||
client_payload = {
|
def __init__(self, config_inverter):
|
||||||
"host": config['inverter'].get('host', '127.0.0.1'),
|
self.client_config = {
|
||||||
"port": config['inverter'].get('port', 502),
|
"host": config_inverter.get('host'),
|
||||||
"timeout": config['inverter'].get('timeout', 10),
|
"port": config_inverter.get('port'),
|
||||||
"retries": config['inverter'].get('retries', 3),
|
"timeout": config_inverter.get('timeout'),
|
||||||
"RetryOnEmpty": config['inverter'].get('RetryOnEmpty', False),
|
"retries": config_inverter.get('retries'),
|
||||||
}
|
"RetryOnEmpty": False,
|
||||||
if config['inverter'].get("connection") == "http":
|
}
|
||||||
client_payload['port'] = config['inverter'].get('port', '8082')
|
self.inverter_config = {
|
||||||
new_client = SungrowModbusWebClient.SungrowModbusWebClient(**client_payload)
|
"slave": config_inverter.get('slave'),
|
||||||
elif config['inverter'].get("connection") == "sungrow":
|
"model": config_inverter.get('model'),
|
||||||
new_client = SungrowModbusTcpClient.SungrowModbusTcpClient(**client_payload)
|
"level": config_inverter.get('level'),
|
||||||
elif config['inverter'].get("connection") == "modbus":
|
"use_local_time": config_inverter.get('use_local_time'),
|
||||||
new_client = ModbusTcpClient(**client_payload)
|
"smart_meter": config_inverter.get('smart_meter'),
|
||||||
else:
|
"connection": config_inverter.get('connection')
|
||||||
logging.warning(f'inverter > connection not specified, defaulting to ModbusTcpClient')
|
}
|
||||||
new_client = ModbusTcpClient(**client_payload)
|
self.client = None
|
||||||
logging.info("Connection: " + str(new_client))
|
|
||||||
|
self.registers = [[]]
|
||||||
|
self.registers.pop() # Remove null value from list
|
||||||
|
self.registers_custom = [{'name': 'export_to_grid', 'unit': 'w'}, {'name': 'import_from_grid', 'unit': 'w'}, {'name': 'run_state'}]
|
||||||
|
self.register_ranges = [[]]
|
||||||
|
self.register_ranges.pop() # Remove null value from list
|
||||||
|
|
||||||
if not config['inverter'].get("connection") == "http":
|
self.latest_scrape = {}
|
||||||
new_client.connect()
|
|
||||||
new_client.close()
|
|
||||||
|
|
||||||
return new_client
|
def connect(self):
|
||||||
|
if self.client:
|
||||||
def scrape_inverter():
|
if(self.client.connect()):
|
||||||
global inverter
|
return True
|
||||||
|
|
||||||
scrape_start = datetime.now()
|
|
||||||
client.connect()
|
|
||||||
|
|
||||||
load_registers_count = 0
|
|
||||||
load_registers_failed = 0
|
|
||||||
|
|
||||||
for range in register_ranges:
|
|
||||||
load_registers_count +=1
|
|
||||||
logging.debug(f'Scraping: {range.get("type")}, {range.get("start")}:{range.get("range")}')
|
|
||||||
if not load_registers(range.get('type'), int(range.get('start')), int(range.get('range'))):
|
|
||||||
load_registers_failed +=1
|
|
||||||
if load_registers_failed == load_registers_count:
|
|
||||||
return False
|
|
||||||
if load_registers_failed > 0:
|
|
||||||
logging.info(f'Scraping: {load_registers_failed}/{load_registers_count} registers failed to scrape')
|
|
||||||
|
|
||||||
client.close()
|
|
||||||
|
|
||||||
# Create a registers for Power imported and exported to/from Grid
|
|
||||||
if config['inverter'].get('level',1) >= 1:
|
|
||||||
try:
|
|
||||||
inverter["export_to_grid"] = 0
|
|
||||||
inverter["import_from_grid"] = 0
|
|
||||||
power = inverter.get('meter_power', inverter.get('export_power', 0))
|
|
||||||
if power < 0:
|
|
||||||
inverter["export_to_grid"] = abs(power)
|
|
||||||
elif power >= 0:
|
|
||||||
inverter["import_from_grid"] = power
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try: # If inverter is returning no data for load_power, we can calculate it manually
|
|
||||||
if config['inverter'].get('manual_load', False):
|
|
||||||
inverter["load_power"] = int(inverter.get('total_active_power')) + int(inverter.get('meter_power'))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# See if the inverter is running, This is added to inverters so can be read via MQTT etc...
|
|
||||||
# It is also used below, as some registers hold the last value on 'stop' so we need to set to 0
|
|
||||||
# to help with graphing.
|
|
||||||
try:
|
|
||||||
if inverter.get('start_stop'):
|
|
||||||
if inverter.get('start_stop', False) == 'Start' and inverter.get('work_state_1', False) == 'Run':
|
|
||||||
inverter["run_state"] = "ON"
|
|
||||||
else:
|
else:
|
||||||
inverter["run_state"] = "OFF"
|
self.disconnect()
|
||||||
else:
|
|
||||||
inverter["run_state"] = "OFF"
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if config['inverter'].get('use_local_time',False):
|
if self.inverter_config['connection'] == "http":
|
||||||
inverter["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
self.client_config['port'] = '8082'
|
||||||
logging.debug(f'Using Local Computer Time: {inverter.get("timestamp")}')
|
self.client = SungrowModbusWebClient.SungrowModbusWebClient(**self.client_config)
|
||||||
del inverter["year"]
|
elif self.inverter_config['connection'] == "sungrow":
|
||||||
del inverter["month"]
|
self.client = SungrowModbusTcpClient.SungrowModbusTcpClient(**self.client_config)
|
||||||
del inverter["day"]
|
elif self.inverter_config['connection'] == "modbus":
|
||||||
del inverter["hour"]
|
self.client = ModbusTcpClient(**self.client_config)
|
||||||
del inverter["minute"]
|
else:
|
||||||
del inverter["second"]
|
logging.warning(f"Inverter: Unknown connection type {self.inverter_config['connection']}, Valid options are http, sungrow or modbus")
|
||||||
else:
|
return False
|
||||||
|
logging.info("Connection: " + str(self.client))
|
||||||
|
|
||||||
|
retval = self.client.connect()
|
||||||
|
# Wait 3 seconds, fixes timing issues
|
||||||
|
time.sleep(3)
|
||||||
|
if not self.inverter_config['connection'] == "http":
|
||||||
|
self.client.close()
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
logging.debug("Closed session: " + str(self.client))
|
||||||
|
self.client.close()
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
logging.info("Disconnected: " + str(self.client))
|
||||||
|
self.client.close()
|
||||||
|
self.client = None
|
||||||
|
|
||||||
|
def configure_registers(self,registersfile):
|
||||||
|
# Check model so we can load only valid registers
|
||||||
|
if self.inverter_config.get('model'):
|
||||||
|
logging.info(f"Bypassing Model Detection, Using config: {self.inverter_config.get('model')}")
|
||||||
|
else:
|
||||||
|
# Load just the register to detect model, then we can load the rest of registers based on returned model
|
||||||
|
for register in registersfile['registers'][0]['read']:
|
||||||
|
if register.get('name') == "device_type_code":
|
||||||
|
register['type'] = "read"
|
||||||
|
self.registers.append(register)
|
||||||
|
if self.load_registers(register['type'], register['address'] -1, 1): # Needs to be address -1
|
||||||
|
if isinstance(self.latest_scrape.get('device_type_code'),int):
|
||||||
|
logging.warning(f"Unknown Type Code Detected: {self.latest_scrape.get('device_type_code')}")
|
||||||
|
else:
|
||||||
|
self.inverter_config['model'] = self.latest_scrape.get('device_type_code')
|
||||||
|
logging.info(f"Detected Model: {self.inverter_config.get('model')}")
|
||||||
|
else:
|
||||||
|
logging.info(f'Model detection failed, please set model in config.py')
|
||||||
|
self.registers.pop()
|
||||||
|
break
|
||||||
|
|
||||||
|
# Load register list based on name and value after checking model
|
||||||
|
for register in registersfile['registers'][0]['read']:
|
||||||
|
if register.get('level',3) <= self.inverter_config.get('level') or self.inverter_config.get('level') == 3:
|
||||||
|
register['type'] = "read"
|
||||||
|
register.pop('level')
|
||||||
|
if register.get('smart_meter') and self.inverter_config.get('smart_meter'):
|
||||||
|
register.pop('models')
|
||||||
|
self.registers.append(register)
|
||||||
|
elif register.get('models') and not self.inverter_config.get('level') == 3:
|
||||||
|
for supported_model in register.get('models'):
|
||||||
|
if supported_model == self.inverter_config.get('model'):
|
||||||
|
register.pop('models')
|
||||||
|
self.registers.append(register)
|
||||||
|
else:
|
||||||
|
self.registers.append(register)
|
||||||
|
|
||||||
|
for register in registersfile['registers'][1]['hold']:
|
||||||
|
if register.get('level',3) <= self.inverter_config.get('level') or self.inverter_config.get('level') == 3:
|
||||||
|
register['type'] = "hold"
|
||||||
|
register.pop('level')
|
||||||
|
if register.get('smart_meter') and self.inverter_config.get('smart_meter'):
|
||||||
|
register.pop('models')
|
||||||
|
self.registers.append(register)
|
||||||
|
elif register.get('models') and not self.inverter_config.get('level',1) == 3:
|
||||||
|
for supported_model in register.get('models'):
|
||||||
|
if supported_model == self.inverter_config.get('model'):
|
||||||
|
register.pop('models')
|
||||||
|
self.registers.append(register)
|
||||||
|
else:
|
||||||
|
self.registers.append(register)
|
||||||
|
|
||||||
|
# Load register list based om name and value after checking model
|
||||||
|
for register_range in registersfile['scan'][0]['read']:
|
||||||
|
register_range_used = False
|
||||||
|
register_range['type'] = "read"
|
||||||
|
for register in self.registers:
|
||||||
|
if register.get("type") == register_range.get("type"):
|
||||||
|
if register.get('address') >= register_range.get("start") and register.get('address') <= (register_range.get("start") + register_range.get("range")):
|
||||||
|
register_range_used = True
|
||||||
|
continue
|
||||||
|
if register_range_used:
|
||||||
|
self.register_ranges.append(register_range)
|
||||||
|
|
||||||
|
|
||||||
|
for register_range in registersfile['scan'][1]['hold']:
|
||||||
|
register_range_used = False
|
||||||
|
register_range['type'] = "hold"
|
||||||
|
for register in self.registers:
|
||||||
|
if register.get("type") == register_range.get("type"):
|
||||||
|
if register.get('address') >= register_range.get("start") and register.get('address') <= (register_range.get("start") + register_range.get("range")):
|
||||||
|
register_range_used = True
|
||||||
|
continue
|
||||||
|
if register_range_used:
|
||||||
|
self.register_ranges.append(register_range)
|
||||||
|
|
||||||
|
def load_registers(self, register_type, start, count=100):
|
||||||
try:
|
try:
|
||||||
inverter["timestamp"] = "%s-%s-%s %s:%02d:%02d" % (
|
logging.debug(f'load_registers: {register_type}, {start}:{count}')
|
||||||
inverter["year"],
|
if register_type == "read":
|
||||||
inverter["month"],
|
rr = self.client.read_input_registers(start,count=count, unit=self.inverter_config['slave'])
|
||||||
inverter["day"],
|
elif register_type == "hold":
|
||||||
inverter["hour"],
|
rr = self.client.read_holding_registers(start,count=count, unit=self.inverter_config['slave'])
|
||||||
inverter["minute"],
|
else:
|
||||||
inverter["second"],
|
raise RuntimeError(f"Unsupported register type: {type}")
|
||||||
)
|
except Exception as err:
|
||||||
logging.debug(f'Using Inverter Time: {inverter.get("timestamp")}')
|
logging.warning(f'No data returned for {register_type}, {start}:{count}\n\t\t\t\t{str(err)}')
|
||||||
del inverter["year"]
|
return False
|
||||||
del inverter["month"]
|
|
||||||
del inverter["day"]
|
if rr.isError():
|
||||||
del inverter["hour"]
|
logging.warning(f"Modbus connection failed: {rr}")
|
||||||
del inverter["minute"]
|
return False
|
||||||
del inverter["second"]
|
|
||||||
|
if not hasattr(rr, 'registers'):
|
||||||
|
logging.warning("No registers returned")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(rr.registers) != count:
|
||||||
|
logging.warning(f"Mismatched number of registers read {len(rr.registers)} != {count}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for num in range(0, count):
|
||||||
|
run = int(start) + num + 1
|
||||||
|
|
||||||
|
for register in self.registers:
|
||||||
|
if register_type == register['type'] and register['address'] == run:
|
||||||
|
register_name = register['name']
|
||||||
|
|
||||||
|
register_value = None
|
||||||
|
# Return the 32bit value if needed
|
||||||
|
if register.get('datatype') == "U32" or register.get('datatype') == "S32":
|
||||||
|
u32_value = rr.registers[num+1]
|
||||||
|
if u32_value and register_value:
|
||||||
|
register_value = (u32_value * 65535) + register_value
|
||||||
|
|
||||||
|
# We convert a system response to a human value
|
||||||
|
if register.get('datarange'):
|
||||||
|
for value in register.get('datarange'):
|
||||||
|
if value['response'] == rr.registers[num]:
|
||||||
|
register_value = value['value']
|
||||||
|
if not register_value:
|
||||||
|
register_value = rr.registers[num]
|
||||||
|
|
||||||
|
# Adjust the value if needed
|
||||||
|
if register.get('indicator'):
|
||||||
|
indicator_value = rr.registers[num+1]
|
||||||
|
if indicator_value == 65535:
|
||||||
|
register_value = -1 * (65535 - register_value)
|
||||||
|
|
||||||
|
# If xFF (U 65535 / S 32767) then change to 0, looks better when logging / graphing
|
||||||
|
if register.get('datatype') == 'S16' and (register_value == 32767 or register_value == 65535):
|
||||||
|
register_value = 0
|
||||||
|
elif (register.get('datatype') == 'U16' or register.get('datatype') == 'S32' or register.get('datatype') == 'U32') and register_value == 65535:
|
||||||
|
register_value = 0
|
||||||
|
|
||||||
|
if register.get('accuracy'):
|
||||||
|
register_value = round(register_value * register.get('accuracy'),2)
|
||||||
|
|
||||||
|
# Set the final register value with adjustments above included
|
||||||
|
self.latest_scrape[register_name] = register_value
|
||||||
|
break
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validateRegister(self, check_register):
|
||||||
|
for register in self.registers:
|
||||||
|
if check_register == register['name']:
|
||||||
|
return True
|
||||||
|
for register in self.registers_custom:
|
||||||
|
if check_register == register['name']:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getRegisterAddress(self, check_register):
|
||||||
|
for register in self.registers:
|
||||||
|
if check_register == register['name']:
|
||||||
|
return register['address']
|
||||||
|
return '----'
|
||||||
|
|
||||||
|
def getRegisterUnit(self, check_register):
|
||||||
|
for register in self.registers:
|
||||||
|
if check_register == register['name']:
|
||||||
|
return register.get('unit','')
|
||||||
|
for register in self.registers_custom:
|
||||||
|
if check_register == register['name']:
|
||||||
|
return register.get('unit','')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def validateLatestScrape(self, check_register):
|
||||||
|
for register, value in self.latest_scrape.items():
|
||||||
|
if check_register == register:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getRegisterValue(self, check_register):
|
||||||
|
for register, value in self.latest_scrape.items():
|
||||||
|
if check_register == register:
|
||||||
|
return value
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getHost(self):
|
||||||
|
return self.client_config['host']
|
||||||
|
|
||||||
|
def getInverterModel(self, clean=False):
|
||||||
|
if clean:
|
||||||
|
return self.inverter_config['model'].replace('.','').replace('-','')
|
||||||
|
else:
|
||||||
|
return self.inverter_config['model']
|
||||||
|
|
||||||
|
def scrape(self):
|
||||||
|
scrape_start = datetime.now()
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
# Clear previous inverter values, keep the model and run state
|
||||||
|
if self.latest_scrape.get("run_state"): run_state = self.latest_scrape.get("run_state")
|
||||||
|
else: run_state = "ON"
|
||||||
|
self.latest_scrape = {}
|
||||||
|
self.latest_scrape['device_type_code'] = self.inverter_config['model']
|
||||||
|
self.latest_scrape["run_state"] = run_state
|
||||||
|
|
||||||
|
load_registers_count = 0
|
||||||
|
load_registers_failed = 0
|
||||||
|
|
||||||
|
for range in self.register_ranges:
|
||||||
|
load_registers_count +=1
|
||||||
|
logging.debug(f'Scraping: {range.get("type")}, {range.get("start")}:{range.get("range")}')
|
||||||
|
if not self.load_registers(range.get('type'), int(range.get('start')), int(range.get('range'))):
|
||||||
|
load_registers_failed +=1
|
||||||
|
if load_registers_failed == load_registers_count:
|
||||||
|
# If every scrape fails, disconnect the client
|
||||||
|
self.disconnect()
|
||||||
|
return False
|
||||||
|
if load_registers_failed > 0:
|
||||||
|
logging.info(f'Scraping: {load_registers_failed}/{load_registers_count} registers failed to scrape')
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
# Create a registers for Power imported and exported to/from Grid
|
||||||
|
if self.inverter_config['level'] >= 1:
|
||||||
|
try:
|
||||||
|
self.latest_scrape["export_to_grid"] = 0
|
||||||
|
self.latest_scrape["import_from_grid"] = 0
|
||||||
|
power = self.latest_scrape.get('meter_power', self.latest_scrape.get('export_power', 0))
|
||||||
|
if power < 0:
|
||||||
|
self.latest_scrape["export_to_grid"] = abs(power)
|
||||||
|
elif power >= 0:
|
||||||
|
self.latest_scrape["import_from_grid"] = power
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # If inverter is returning no data for load_power, we can calculate it manually
|
||||||
|
if not self.latest_scrape["load_power"]:
|
||||||
|
self.latest_scrape["load_power"] = int(self.latest_scrape.get('total_active_power')) + int(self.latest_scrape.get('meter_power'))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# See if the inverter is running, This is added to inverters so can be read via MQTT etc...
|
||||||
|
# It is also used below, as some registers hold the last value on 'stop' so we need to set to 0
|
||||||
|
# to help with graphing.
|
||||||
|
try:
|
||||||
|
if self.latest_scrape.get('start_stop'):
|
||||||
|
if self.latest_scrape.get('start_stop', False) == 'Start' and self.latest_scrape.get('work_state_1', False) == 'Run':
|
||||||
|
self.latest_scrape["run_state"] = "ON"
|
||||||
|
else:
|
||||||
|
self.latest_scrape["run_state"] = "OFF"
|
||||||
|
else:
|
||||||
|
self.latest_scrape["run_state"] = "OFF"
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
scrape_end = datetime.now()
|
if self.inverter_config.get('use_local_time',False):
|
||||||
logging.info(f'Inverter: Successfully scraped in {(scrape_end - scrape_start).seconds}.{(scrape_end - scrape_start).microseconds} secs')
|
self.latest_scrape["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
logging.debug(f'Using Local Computer Time: {self.latest_scrape.get("timestamp")}')
|
||||||
return True
|
del self.latest_scrape["year"]
|
||||||
|
del self.latest_scrape["month"]
|
||||||
def load_registers(register_type, start, count=100):
|
del self.latest_scrape["day"]
|
||||||
global inverter
|
del self.latest_scrape["hour"]
|
||||||
|
del self.latest_scrape["minute"]
|
||||||
try:
|
del self.latest_scrape["second"]
|
||||||
logging.debug(f'load_registers: {register_type}, {start}:{count}')
|
|
||||||
if register_type == "read":
|
|
||||||
rr = client.read_input_registers(start,count=count, unit=config['inverter'].get('slave', 0x01))
|
|
||||||
elif register_type == "hold":
|
|
||||||
rr = client.read_holding_registers(start,count=count, unit=config['inverter'].get('slave', 0x01))
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Unsupported register type: {type}")
|
try:
|
||||||
except Exception as err:
|
self.latest_scrape["timestamp"] = "%s-%s-%s %s:%02d:%02d" % (
|
||||||
logging.warning(f'No data returned for {register_type}, {start}:{count}\n\t\t\t\t{str(err)}')
|
self.latest_scrape["year"],
|
||||||
return False
|
self.latest_scrape["month"],
|
||||||
|
self.latest_scrape["day"],
|
||||||
|
self.latest_scrape["hour"],
|
||||||
|
self.latest_scrape["minute"],
|
||||||
|
self.latest_scrape["second"],
|
||||||
|
)
|
||||||
|
logging.debug(f'Using Inverter Time: {self.latest_scrape.get("timestamp")}')
|
||||||
|
del self.latest_scrape["year"]
|
||||||
|
del self.latest_scrape["month"]
|
||||||
|
del self.latest_scrape["day"]
|
||||||
|
del self.latest_scrape["hour"]
|
||||||
|
del self.latest_scrape["minute"]
|
||||||
|
del self.latest_scrape["second"]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if rr.isError():
|
# If alarm state exists then convert to timestamp, otherwise remove it
|
||||||
logging.warning(f"Modbus connection failed: {rr}")
|
try:
|
||||||
return False
|
if self.latest_scrape["pid_alarm_code"]:
|
||||||
|
self.latest_scrape["alarm_timestamp"] = "%s-%s-%s %s:%02d:%02d" % (
|
||||||
|
self.latest_scrape["alarm_time_year"],
|
||||||
|
self.latest_scrape["alarm_time_month"],
|
||||||
|
self.latest_scrape["alarm_time_day"],
|
||||||
|
self.latest_scrape["alarm_time_hour"],
|
||||||
|
self.latest_scrape["alarm_time_minute"],
|
||||||
|
self.latest_scrape["alarm_time_second"],
|
||||||
|
)
|
||||||
|
del self.latest_scrape["alarm_time_year"]
|
||||||
|
del self.latest_scrape["alarm_time_month"]
|
||||||
|
del self.latest_scrape["alarm_time_day"]
|
||||||
|
del self.latest_scrape["alarm_time_hour"]
|
||||||
|
del self.latest_scrape["alarm_time_minute"]
|
||||||
|
del self.latest_scrape["alarm_time_second"]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if not hasattr(rr, 'registers'):
|
scrape_end = datetime.now()
|
||||||
logging.warning("No registers returned")
|
logging.info(f'Inverter: Successfully scraped in {(scrape_end - scrape_start).seconds}.{(scrape_end - scrape_start).microseconds} secs')
|
||||||
return False
|
|
||||||
|
|
||||||
if len(rr.registers) != count:
|
return True
|
||||||
logging.warning(f"Mismatched number of registers read {len(rr.registers)} != {count}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
for num in range(0, count):
|
|
||||||
run = int(start) + num + 1
|
|
||||||
|
|
||||||
for register in registers:
|
|
||||||
if register_type == register.get('type') and register['address'] == run:
|
|
||||||
register_name = register['name']
|
|
||||||
|
|
||||||
register_value = None
|
|
||||||
# Return the 32bit value if needed
|
|
||||||
if register.get('datatype') == "U32" or register.get('datatype') == "S32":
|
|
||||||
u32_value = rr.registers[num+1]
|
|
||||||
if u32_value and register_value:
|
|
||||||
register_value = (u32_value * 65535) + register_value
|
|
||||||
|
|
||||||
# We convert a system response to a human value
|
|
||||||
if register.get('datarange'):
|
|
||||||
for value in register.get('datarange'):
|
|
||||||
if value['response'] == rr.registers[num]:
|
|
||||||
register_value = value['value']
|
|
||||||
if not register_value:
|
|
||||||
register_value = rr.registers[num]
|
|
||||||
|
|
||||||
# Adjust the value if needed
|
|
||||||
if register.get('indicator'):
|
|
||||||
indicator_value = rr.registers[num+1]
|
|
||||||
if indicator_value == 65535:
|
|
||||||
register_value = -1 * (65535 - register_value)
|
|
||||||
|
|
||||||
# If xFF (U 65535 / S 32767) then change to 0, looks better when logging / graphing
|
|
||||||
if register.get('datatype') == 'S16' and (register_value == 32767 or register_value == 65535):
|
|
||||||
register_value = 0
|
|
||||||
elif (register.get('datatype') == 'U16' or register.get('datatype') == 'S32' or register.get('datatype') == 'U32') and register_value == 65535:
|
|
||||||
register_value = 0
|
|
||||||
|
|
||||||
if register.get('multiple'):
|
|
||||||
register_value = round(register_value * register.get('multiple'),2)
|
|
||||||
|
|
||||||
# Set the final register value with adjustments above included
|
|
||||||
inverter[register_name] = register_value
|
|
||||||
break
|
|
||||||
return True
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global config
|
configfilename = 'config.yaml'
|
||||||
global registers
|
|
||||||
global register_ranges
|
|
||||||
global inverter
|
|
||||||
global client
|
|
||||||
|
|
||||||
configfile = 'config.yaml'
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:],"hc:v:", "runonce")
|
opts, args = getopt.getopt(sys.argv[1:],"hc:v:", "runonce")
|
||||||
@@ -237,7 +406,7 @@ def main():
|
|||||||
print(f'python3 sungather.py -c /full/path/config.yaml\n')
|
print(f'python3 sungather.py -c /full/path/config.yaml\n')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
elif opt == '-c':
|
elif opt == '-c':
|
||||||
configfile = arg
|
configfilename = arg
|
||||||
elif opt == '-v':
|
elif opt == '-v':
|
||||||
if arg.isnumeric():
|
if arg.isnumeric():
|
||||||
if int(arg) >= 0 and int(arg) <= 50:
|
if int(arg) >= 0 and int(arg) <= 50:
|
||||||
@@ -254,152 +423,86 @@ def main():
|
|||||||
logging.info(f'Starting SunGather {__version__}')
|
logging.info(f'Starting SunGather {__version__}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = yaml.safe_load(open(configfile))
|
configfile = yaml.safe_load(open(configfilename))
|
||||||
logging.info(f"Loaded config: {configfile}")
|
logging.info(f"Loaded config: {configfilename}")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error(f"Failed: Loading config: {configfile} \n\t\t\t {err}")
|
logging.error(f"Failed: Loading config: {configfilename} \n\t\t\t {err}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not config.get('inverter'):
|
if not configfile.get('inverter'):
|
||||||
logging.error(f"Failed Loading config, missing Inverter settings")
|
logging.error(f"Failed Loading config, missing Inverter settings")
|
||||||
sys.exit(1)
|
sys.exit(f"Failed Loading config, missing Inverter settings")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
registers_raw = yaml.safe_load(open('registers.yaml'))
|
registersfile = yaml.safe_load(open('registers.yaml'))
|
||||||
logging.info(f"Loaded registers: {os.getcwd()}/registers.yaml")
|
logging.info(f"Loaded registers: {os.getcwd()}/registers.yaml")
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error(f"Failed: Loading registers: {os.getcwd()}/registers.yaml {err}")
|
logging.error(f"Failed: Loading registers: {os.getcwd()}/registers.yaml {err}")
|
||||||
sys.exit(1)
|
sys.exit(f"Failed: Loading registers: {os.getcwd()}/registers.yaml {err}")
|
||||||
|
|
||||||
|
config_inverter = {
|
||||||
|
"host": configfile['inverter'].get('host',None),
|
||||||
|
"port": configfile['inverter'].get('port',502),
|
||||||
|
"timeout": configfile['inverter'].get('timeout',10),
|
||||||
|
"retries": configfile['inverter'].get('retries',3),
|
||||||
|
"slave": configfile['inverter'].get('slave',0x01),
|
||||||
|
"scan_interval": configfile['inverter'].get('scan_interval',15),
|
||||||
|
"connection": configfile['inverter'].get('connection',"modbus"),
|
||||||
|
"model": configfile['inverter'].get('model',None),
|
||||||
|
"smart_meter": configfile['inverter'].get('smart_meter',False),
|
||||||
|
"use_local_time": configfile['inverter'].get('use_local_time',False),
|
||||||
|
"manual_load": configfile['inverter'].get('manual_load',False),
|
||||||
|
"logging": configfile['inverter'].get('logging',30),
|
||||||
|
"level": configfile['inverter'].get('level',1)
|
||||||
|
}
|
||||||
|
|
||||||
if 'loglevel' in locals():
|
if 'loglevel' in locals():
|
||||||
logging.getLogger().setLevel(loglevel)
|
logging.getLogger().setLevel(loglevel)
|
||||||
else:
|
else:
|
||||||
logging.getLogger().setLevel(config['inverter'].get('logging',30))
|
logging.getLogger().setLevel(config_inverter['logging'])
|
||||||
|
logging.debug(f'Inverter Config Loaded: {config_inverter}')
|
||||||
|
|
||||||
|
if config_inverter.get('host'):
|
||||||
|
inverter = SungrowInverter(config_inverter)
|
||||||
|
else:
|
||||||
|
logging.error(f"Error: host option in config is required")
|
||||||
|
sys.exit("Error: host option in config is required")
|
||||||
|
|
||||||
|
if not inverter.connect():
|
||||||
|
logging.error(f"Error: Connection to inverter failed: {config_inverter.get('host')}:{config_inverter.get('port')}")
|
||||||
|
sys.exit(f"Error: Connection to inverter failed: {config_inverter.get('host')}:{config_inverter.get('port')}")
|
||||||
|
|
||||||
|
inverter.configure_registers(registersfile)
|
||||||
|
|
||||||
|
# Now we know the inverter is working, lets load the exports
|
||||||
exports = []
|
exports = []
|
||||||
if config.get('exports'):
|
if configfile.get('exports'):
|
||||||
for export in config.get('exports'):
|
for export in configfile.get('exports'):
|
||||||
try:
|
try:
|
||||||
if export.get('enabled', False):
|
if export.get('enabled', False):
|
||||||
export_load = importlib.import_module("exports." + export.get('name'))
|
export_load = importlib.import_module("exports." + export.get('name'))
|
||||||
logging.info(f"Loading Export: exports\{export.get('name')}")
|
logging.info(f"Loading Export: exports\{export.get('name')}")
|
||||||
exports.append(getattr(export_load, "export_" + export.get('name'))())
|
exports.append(getattr(export_load, "export_" + export.get('name'))())
|
||||||
exports[-1].configure(export, config['inverter'])
|
retval = exports[-1].configure(export, inverter)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logging.error(f"Failed loading export: {err}" +
|
logging.error(f"Failed loading export: {err}" +
|
||||||
f"\n\t\t\t Please make sure {export.get('name')}.py exists in the exports folder")
|
f"\n\t\t\t Please make sure {export.get('name')}.py exists in the exports folder")
|
||||||
|
|
||||||
client = connect_inverter()
|
scan_interval = config_inverter.get('scan_interval')
|
||||||
inverter = {}
|
|
||||||
registers = [[]]
|
|
||||||
registers.pop()
|
|
||||||
register_ranges = [[]]
|
|
||||||
register_ranges.pop()
|
|
||||||
|
|
||||||
# Inverter Model Scanning
|
# Core polling loop
|
||||||
model = 'unknown'
|
|
||||||
if config['inverter'].get('model'):
|
|
||||||
model = config['inverter'].get('model')
|
|
||||||
logging.info(f'Bypassing Model Detection, Using config: {model}')
|
|
||||||
else:
|
|
||||||
# Load just the register to detect model, then we can load the rest of registers based on returned model
|
|
||||||
for register in registers_raw['registers'][0]['read']:
|
|
||||||
if register.get('name') == "device_type_code":
|
|
||||||
register['type'] = "read"
|
|
||||||
registers.append(register)
|
|
||||||
break
|
|
||||||
if load_registers("read", 4999, 1):
|
|
||||||
registers.pop() # Remove the register, as we only needed it once
|
|
||||||
if isinstance(inverter.get('device_type_code'),int):
|
|
||||||
logging.warning(f"Unknown Type Code Detected: {inverter.get('device_type_code')}")
|
|
||||||
else:
|
|
||||||
model = inverter.get('device_type_code')
|
|
||||||
logging.info(f"Detected Model: {model}")
|
|
||||||
else:
|
|
||||||
logging.info(f'Model detection failed, please set model in config.py')
|
|
||||||
|
|
||||||
# Load register list based om name and value after checking model
|
|
||||||
for register in registers_raw['registers'][0]['read']:
|
|
||||||
if register.get('level',3) <= config['inverter'].get('level',1) or config['inverter'].get('level',1) == 3:
|
|
||||||
register['type'] = "read"
|
|
||||||
register.pop('level')
|
|
||||||
if register.get('smart_meter') and config['inverter'].get('smart_meter'):
|
|
||||||
register.pop('models')
|
|
||||||
registers.append(register)
|
|
||||||
elif register.get('models') and not config['inverter'].get('level',1) == 3:
|
|
||||||
for supported_model in register.get('models'):
|
|
||||||
if supported_model == inverter.get('device_type_code'):
|
|
||||||
register.pop('models')
|
|
||||||
registers.append(register)
|
|
||||||
else:
|
|
||||||
registers.append(register)
|
|
||||||
|
|
||||||
for register in registers_raw['registers'][1]['hold']:
|
|
||||||
if register.get('level',3) <= config['inverter'].get('level',1) or config['inverter'].get('level',1) == 3:
|
|
||||||
register['type'] = "hold"
|
|
||||||
register.pop('level')
|
|
||||||
if register.get('smart_meter') and config['inverter'].get('smart_meter'):
|
|
||||||
register.pop('models')
|
|
||||||
registers.append(register)
|
|
||||||
elif register.get('models') and not config['inverter'].get('level',1) == 3:
|
|
||||||
for supported_model in register.get('models'):
|
|
||||||
if supported_model == inverter.get('device_type_code'):
|
|
||||||
register.pop('models')
|
|
||||||
registers.append(register)
|
|
||||||
else:
|
|
||||||
registers.append(register)
|
|
||||||
|
|
||||||
|
|
||||||
# Load register list based om name and value after checking model
|
|
||||||
for register_range in registers_raw['scan'][0]['read']:
|
|
||||||
register_range_used = False
|
|
||||||
register_range['type'] = "read"
|
|
||||||
for register in registers:
|
|
||||||
if register.get("type") == register_range.get("type"):
|
|
||||||
if register.get('address') >= register_range.get("start") and register.get('address') <= (register_range.get("start") + register_range.get("range")):
|
|
||||||
register_range_used = True
|
|
||||||
continue
|
|
||||||
if register_range_used:
|
|
||||||
register_ranges.append(register_range)
|
|
||||||
|
|
||||||
|
|
||||||
for register_range in registers_raw['scan'][1]['hold']:
|
|
||||||
register_range_used = False
|
|
||||||
register_range['type'] = "hold"
|
|
||||||
for register in registers:
|
|
||||||
if register.get("type") == register_range.get("type"):
|
|
||||||
if register.get('address') >= register_range.get("start") and register.get('address') <= (register_range.get("start") + register_range.get("range")):
|
|
||||||
register_range_used = True
|
|
||||||
continue
|
|
||||||
if register_range_used:
|
|
||||||
register_ranges.append(register_range)
|
|
||||||
|
|
||||||
scan_interval = config['inverter'].get('scan_interval', 30)
|
|
||||||
|
|
||||||
# Core monitoring loop
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
loop_start = datetime.now()
|
loop_start = datetime.now()
|
||||||
# Clear previous inverter values, keep the model and run state
|
|
||||||
if inverter.get("run_state"): run_state = inverter["run_state"]
|
|
||||||
else: run_state = "ON"
|
|
||||||
inverter = {}
|
|
||||||
inverter['device_type_code'] = model
|
|
||||||
inverter["run_state"] = run_state
|
|
||||||
|
|
||||||
if not client:
|
inverter.connect()
|
||||||
client = connect_inverter()
|
|
||||||
time.sleep(3) # Wait 3 secs for connection, resvoles issues with modbus not reconnecting on failure
|
|
||||||
|
|
||||||
# Scrape the inverter
|
# Scrape the inverter
|
||||||
|
success = inverter.scrape()
|
||||||
success = scrape_inverter()
|
|
||||||
|
|
||||||
if(success):
|
if(success):
|
||||||
for export in exports:
|
for export in exports:
|
||||||
export.publish(inverter)
|
export.publish(inverter)
|
||||||
else:
|
else:
|
||||||
client.close()
|
inverter.disconnect()
|
||||||
client = None
|
|
||||||
logging.warning(f"Data collection failed, skipped exporting data. Retying in {scan_interval} secs")
|
logging.warning(f"Data collection failed, skipped exporting data. Retying in {scan_interval} secs")
|
||||||
|
|
||||||
loop_end = datetime.now()
|
loop_end = datetime.now()
|
||||||
@@ -420,4 +523,4 @@ def main():
|
|||||||
if __name__== "__main__":
|
if __name__== "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = '0.2.10'
|
__version__ = '0.3.1'
|
||||||
+1
-1
@@ -3,5 +3,5 @@ requests>=2.26.0
|
|||||||
paho-mqtt>=1.5.1
|
paho-mqtt>=1.5.1
|
||||||
pymodbus>=2.4.0
|
pymodbus>=2.4.0
|
||||||
SungrowModbusTcpClient>=0.1.5
|
SungrowModbusTcpClient>=0.1.5
|
||||||
SungrowModbusWebClient>=0.2.6
|
SungrowModbusWebClient>=0.2.7
|
||||||
influxdb-client>=1.24.0
|
influxdb-client>=1.24.0
|
||||||
|
|||||||
Reference in New Issue
Block a user