Merge branch 'main' of https://github.com/bohdan-s/SunGather into bohdan-s-main

This commit is contained in:
benni336
2022-01-25 18:28:14 +01:00
11 changed files with 893 additions and 628 deletions
+19 -4
View File
@@ -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
+4 -12
View File
@@ -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
+22 -15
View File
@@ -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
+44 -22
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+13 -19
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +1 @@
__version__ = '0.2.10' __version__ = '0.3.1'
+1 -1
View File
@@ -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