diff --git a/README.md b/README.md
index b2071dc..099e28e 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@ On first connection the tool will query your inverter, retrieve the model and re
Register information based on official documentation: Communication Protocol of PV Grid-Connected String Inverters and Communication Protocol of Residential Hybrid Inverters
Has multiple export locations out of the box:
+* Full Home Assistant Support, as HassIO add-on
* Console - Log directly to screen
* MQTT - Load into MQTT, and optionally Home Assistance including Discovery
* PVOutput - Load into PVOutput.org
@@ -53,9 +54,6 @@ I have learned a lot from the following projects, THANK YOU
(back to top)
-## Roadmap / TO DO
-* Full Home Assistant integration, as HACS addon
-
## Built With
@@ -174,6 +172,10 @@ For SG* Models this is calculated from meter_power if -ve value, returned as a p
### Home Assistant setup
+#### The following are manual steps if you are running SunGather separate,
+#### For the easy way see: Hassio Repository
+
+
In the SunGather config.yaml you need to set the smart_meter if you have one
_For Hybrid Models smart_meter doesn't need to get enabled_
@@ -182,46 +184,20 @@ _For Hybrid Models smart_meter doesn't need to get enabled_
smart_meter: True
```
-HA sensors created by the MQTT auto-discovery:
+Enable MQTT, configure the server and HA AutoDiscovery
```
-sensor.inverter_active_power
-sensor.inverter_daily_generation
-sensor.inverter_export_to_grid
-sensor.inverter_import_from_grid
-sensor.inverter_load_power
-sensor.inverter_meter_power
-
+ - name: mqtt
+ enabled: True
+ host: 192.168.1.200
+ homeassistant: True
```
-The Inverter reports Power (W), but HA needs Energy (Wh).
-You can use a "Riemann sum integral" (called integration) to convert it.
-Put the following into your sensors.yaml
-```
-sensor:
- - platform: integration
- source: sensor.inverter_active_power
- name: Solar Production (Sungather)
- - platform: integration
- source: sensor.inverter_export_to_grid
- name: Return to Grid (Sungather)
- - platform: integration
- source: sensor.inverter_import_from_grid
- name: Grid Consumption (Sungather)
-```
+Electricity Grid > Grid Consumption -> Daily Import from Grid
+Electricity Grid > Return to Grid -> Daily Export to Grid
+Solar Panels > Solar Production -> Daily Generation
-The additional sensors then setup in HA are the following with Wh unit of measurement for the Energy platform.
+
-Solar Production (Sungather)
-Return to Grid (Sungather)
-Grid Consumption (Sungather)
-
-Restart Home Assistant for the sensors.yaml to be loaded.
-Make sure SunGather is running, wait 5 minutes for initial data to populate.
-Navigate to the Energy platform (Configuration > Energy). Add these sensors to the following areas:
-
-Electricity Grid > Grid Consumption -> Grid Consumption (Sungather)
-Electricity Grid > Return to Grid -> Return to Grid (Sungather)
-Solar Panels > Solar Production -> Solar Production (Sungather)
(back to top)
diff --git a/SunGather/config-example.yaml b/SunGather/config-example.yaml
index babffdc..95aafe8 100644
--- a/SunGather/config-example.yaml
+++ b/SunGather/config-example.yaml
@@ -5,7 +5,7 @@ inverter:
# retries: 3 # [Optional] Default is 3, how many times to retry if connection fails
# slave: 0x01 # [Optional] Default is 0x01
# scan_interval: 30 # [Optional] Default is 30
- connection: modbus # [Required] options: modbus, sungrow, http
+ connection: modbus # [Required] options: modbus, sungrow, http
# 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
# serial: xxxxxxxxxx # [Optional] This is autodetected on startup, only needed if detection issues or for testing, used as a unique ID
@@ -24,7 +24,7 @@ inverter:
exports:
# Print Registers to console, good for debugging / troubleshooting
- name: console
- enabled: False # [Optional] Default is False
+ enabled: True # [Optional] Default is False
# Runs a simple Webserver showing Config and last read registers
# Access at http://localhost:8080 or http://[serverip]:8080
@@ -38,7 +38,6 @@ exports:
enabled: False # [Optional] Default is False
# port: 8082 # [Optional] Default is 8080
-
# Output data to InfluxDB
- name: influxdb
enabled: False # [Optional] Default is False
@@ -69,53 +68,60 @@ exports:
enabled: False # [Optional] Default is False
host: 192.168.1.200 # [Required] IP or Hostname of MQTT Server
# port: 1883 # [Optional] Default 1883
- # topic: "inverter/{model}/registers" # [Optional] Default: "inverter/{model}/registers", Variable {model} will be replaced with model number of inverter
+ # topic: "SunGather/{serial_number}" # [Optional] Default: "SunGather/{serial_number}"
# username: # [Optional] Username is MQTT server requires it
# password: # [Optional] Password is MQTT server requires it
- # client_id: # [Optional] Client id for mqtt connection. Defaults to "SunGather", need to be set to different values if you want to run multiple instances of sungather.
- homeassistant: False # [Optional] Default False, Publish discovery and sensors for HomeAssistant
- ha_sensors: # [Optional] / [Required] homeassistant: True, Topics to enable discovery for HA
+ # client_id: # [Optional] Client id for mqtt connection. Defaults to Serial Number.
+ homeassistant: True
+ ha_sensors:
- name: "Daily Generation"
sensor_type: sensor
register: daily_power_yields
- dev_class: energy
+ device_class: energy
state_class: total_increasing
- - name: "Active Power"
+ icon: "mdi:solar-power"
+ - name: "Daily Export to Grid"
sensor_type: sensor
- register: total_active_power
- dev_class: power
- state_class: measurement
- - name: "Load Power"
+ register: daily_export_to_grid
+ device_class: energy
+ state_class: total_increasing
+ icon: "mdi:transmission-tower-export"
+ value_template: "{{ value_json.daily_export_to_grid | round(2) }}"
+ last_reset_value_template: "{{ value_json.last_reset }}"
+ - name: "Daily Import from Grid"
sensor_type: sensor
- register: load_power
- dev_class: power
- state_class: measurement
- - name: "Meter Power"
- sensor_type: sensor
- register: meter_power
- dev_class: power
- state_class: measurement
- - name: "Export to Grid"
- sensor_type: sensor
- register: export_to_grid
- dev_class: power
- state_class: measurement
- - name: "Import from Grid"
- sensor_type: sensor
- register: import_from_grid
- dev_class: power
- state_class: measurement
+ register: daily_import_from_grid
+ device_class: energy
+ state_class: total_increasing
+ icon: "mdi:transmission-tower-import"
+ value_template: "{{ value_json.daily_import_from_grid | round(2) }}"
+ last_reset_value_template: "{{ value_json.last_reset }}"
- name: "Temperature"
sensor_type: sensor
register: internal_temperature
- dev_class: temperature
+ device_class: temperature
state_class: measurement
- name: "Power State"
sensor_type: binary_sensor
register: run_state
- dev_class: running
+ device_class: running
payload_on: "ON"
payload_off: "OFF"
+ - name: "Active Generation"
+ sensor_type: sensor
+ register: total_active_power
+ dev_class: power
+ state_class: measurement
+ - name: "Active Export to Grid"
+ sensor_type: sensor
+ register: export_to_grid
+ dev_class: power
+ state_class: measurement
+ - name: "Active Import from Grid"
+ sensor_type: sensor
+ register: import_from_grid
+ dev_class: power
+ state_class: measurement
# Publish Registers to PVOutput
- name: pvoutput
diff --git a/SunGather/exports/mqtt.py b/SunGather/exports/mqtt.py
index 4838fa8..3050502 100644
--- a/SunGather/exports/mqtt.py
+++ b/SunGather/exports/mqtt.py
@@ -8,15 +8,19 @@ class export_mqtt(object):
self.sensor_topic = None
self.mqtt_queue = []
self.ha_discovery_published = False
+ # Exclude ones linked to register lookups; unit_of_measurement
+ self.ha_variables = ["action_topic", "action_template", "automation_type", "aux_command_topic", "aux_state_template", "aux_state_topic", "available_tones", "availability", "availability_mode", "availability_topic", "availability_template", "away_mode_command_topic", "away_mode_state_template", "away_mode_state_topic", "blue_template", "brightness_command_topic", "brightness_command_template", "brightness_scale", "brightness_state_topic", "brightness_template", "brightness_value_template", "color_temp_command_template", "battery_level_topic", "battery_level_template", "charging_topic", "charging_template", "color_temp_command_topic", "color_temp_state_topic", "color_temp_template", "color_temp_value_template", "color_mode", "color_mode_state_topic", "color_mode_value_template", "cleaning_topic", "cleaning_template", "command_off_template", "command_on_template", "command_topic", "command_template", "code_arm_required", "code_disarm_required", "code_trigger_required", "current_temperature_topic", "current_temperature_template", "device", "device_class", "docked_topic", "docked_template", "encoding", "enabled_by_default", "entity_category", "entity_picture", "error_topic", "error_template", "fan_speed_topic", "fan_speed_template", "fan_speed_list", "flash_time_long", "flash_time_short", "effect_command_topic", "effect_command_template", "effect_list", "effect_state_topic", "effect_template", "effect_value_template", "expire_after", "fan_mode_command_template", "fan_mode_command_topic", "fan_mode_state_template", "fan_mode_state_topic", "force_update", "green_template", "hold_command_template", "hold_command_topic", "hold_state_template", "hold_state_topic", "hs_command_topic", "hs_state_topic", "hs_value_template", "icon", "image_encoding", "initial", "target_humidity_command_topic", "target_humidity_command_template", "target_humidity_state_topic", "target_humidity_state_template", "json_attributes", "json_attributes_topic", "json_attributes_template", "latest_version_topic", "latest_version_template", "last_reset_topic", "last_reset_value_template", "max", "min", "max_mireds", "min_mireds", "max_temp", "min_temp", "max_humidity", "min_humidity", "mode", "mode_command_template", "mode_command_topic", "mode_state_template", "mode_state_topic", "modes", "name", "object_id", "off_delay", "on_command_type", "options", "optimistic", "oscillation_command_topic", "oscillation_command_template", "oscillation_state_topic", "oscillation_value_template", "percentage_command_topic", "percentage_command_template", "percentage_state_topic", "percentage_value_template", "pattern", "payload", "payload_arm_away", "payload_arm_home", "payload_arm_custom_bypass", "payload_arm_night", "payload_arm_vacation", "payload_press", "payload_reset", "payload_available", "payload_clean_spot", "payload_close", "payload_disarm", "payload_home", "payload_install", "payload_lock", "payload_locate", "payload_not_available", "payload_not_home", "payload_off", "payload_on", "payload_open", "payload_oscillation_off", "payload_oscillation_on", "payload_pause", "payload_stop", "payload_start", "payload_start_pause", "payload_return_to_base", "payload_reset_humidity", "payload_reset_mode", "payload_reset_percentage", "payload_reset_preset_mode", "payload_turn_off", "payload_turn_on", "payload_trigger", "payload_unlock", "position_closed", "position_open", "power_command_topic", "power_state_topic", "power_state_template", "preset_mode_command_topic", "preset_mode_command_template", "preset_mode_state_topic", "preset_mode_value_template", "preset_modes", "red_template", "release_summary", "release_url", "retain", "rgb_command_topic", "rgb_command_template", "rgb_state_topic", "rgb_value_template", "rgbw_command_topic", "rgbw_command_template", "rgbw_state_topic", "rgbw_value_template", "rgbww_command_topic", "rgbww_command_template", "rgbww_state_topic", "rgbww_value_template", "send_command_topic", "send_if_off", "set_fan_speed_topic", "set_position_template", "set_position_topic", "position_topic", "position_template", "speed_range_min", "speed_range_max", "source_type", "state_class", "state_closed", "state_closing", "state_off", "state_on", "state_open", "state_opening", "state_stopped", "state_locked", "state_unlocked", "state_topic", "state_template", "state_value_template", "step", "subtype", "supported_color_modes", "support_duration", "support_volume_set", "supported_features", "swing_mode_command_template", "swing_mode_command_topic", "swing_mode_state_template", "swing_mode_state_topic", "temperature_command_template", "temperature_command_topic", "temperature_high_command_template", "temperature_high_command_topic", "temperature_high_state_template", "temperature_high_state_topic", "temperature_low_command_template", "temperature_low_command_topic", "temperature_low_state_template", "temperature_low_state_topic", "temperature_state_template", "temperature_state_topic", "temperature_unit", "tilt_closed_value", "tilt_command_topic", "tilt_command_template", "tilt_invert_state", "tilt_max", "tilt_min", "tilt_opened_value", "tilt_optimistic", "tilt_status_topic", "tilt_status_template", "title", "topic", "unique_id", "value_template", "white_command_topic", "white_scale", "white_value_command_topic", "white_value_scale", "white_value_state_topic", "white_value_template", "xy_command_topic", "xy_state_topic", "xy_value_template"]
# Configure MQTT
def configure(self, config, inverter):
- model = inverter.getInverterModel(True)
+ self.model = inverter.getInverterModel(True)
+ self.serial_number = inverter.getSerialNumber()
+
self.mqtt_config = {
'host': config.get('host', None),
'port': config.get('port', 1883),
- 'client_id': config.get('client_id', f'SunGather-{model}'),
- 'topic': config.get('topic', f"inverter/{model}/registers"),
+ 'client_id': config.get('client_id', f'{self.serial_number}'),
+ 'topic': config.get('topic', f"SunGather/{self.serial_number}"),
'username': config.get('username', None),
'password': config.get('password',None),
'homeassistant': config.get('homeassistant',False)
@@ -80,35 +84,45 @@ class export_mqtt(object):
#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.debug(f"MQTT: Publishing: {self.mqtt_config['topic']} : {json.dumps(inverter.latest_scrape)}")
- self.mqtt_queue.append(self.mqtt_client.publish(self.mqtt_config['topic'], json.dumps(inverter.latest_scrape).replace('"', '\"'), qos=0).mid)
- logging.info(f"MQTT: Published")
-
if self.mqtt_config['homeassistant'] and not self.ha_discovery_published:
+ # Build Device, this will be the same for every message
+ ha_device = { "name":f"Sungrow {self.model}", "manufacturer":"Sungrow", "model":self.model, "identifiers":self.serial_number, "via_device": "SunGather", "connections":[["address", inverter.getHost() ]]}
+
for ha_sensor in self.ha_sensors:
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') + " }}"
- if inverter.getRegisterUnit(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 not (ha_sensor.get('name', False) and ha_sensor.get('sensor_type', False)):
+ logging.error(f"HomeAssistance Discovery requires at minimum; name, sensor_type")
+ break
+ # Set Defaults, these can be overridden below
+ config_msg['state_topic'] = self.mqtt_config['topic']
+ if ha_sensor.get('register', False):
+ config_msg['value_template'] = "{{ value_json." + ha_sensor.get('register') + " }}"
+
+ for ha_variable in self.ha_variables:
+ if ha_sensor.get(ha_variable):
+ config_msg[ha_variable] = ha_sensor[ha_variable]
+
+ # Set unique_id, include Serial so is unique
+ config_msg['unique_id'] = f"sungather_{self.cleanName(config_msg['name'])}_{self.serial_number}"
+
+ # Variables with links to registers
+ if ha_sensor.get('register', False):
+ if inverter.getRegisterUnit(ha_sensor.get('register')):
+ config_msg['unit_of_measurement'] = inverter.getRegisterUnit(ha_sensor.get('register'))
+
+ config_msg['device'] = ha_device
+
+ # ///config
+ ha_topic = f"homeassistant/{ha_sensor.get('sensor_type')}/{self.serial_number}_{self.cleanName(ha_sensor.get('name'))}/config"
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_published = True
logging.info("MQTT: Published Home Assistant Discovery messages")
+ payload = json.dumps(inverter.inverter_config | inverter.client_config | inverter.latest_scrape).replace('"', '\"')
+ logging.debug(f"MQTT: Publishing Registers: {self.mqtt_config['topic']} : {payload}")
+ self.mqtt_queue.append(self.mqtt_client.publish(self.mqtt_config['topic'], payload, qos=0).mid)
+ logging.info(f"MQTT: Registers Published")
+
return True
diff --git a/SunGather/exports/webserver.py b/SunGather/exports/webserver.py
index 3200918..f4f7778 100644
--- a/SunGather/exports/webserver.py
+++ b/SunGather/exports/webserver.py
@@ -25,7 +25,10 @@ class export_webserver(object):
return True
def publish(self, inverter):
- body = "Sungather v" + __version__ + "
| Address | | Register | Value |
"
+ body = "SunGather v" + __version__ + "
"
+ body += "Need Help? https://github.com/bohdan-s/SunGather
"
+ body += "NEW HomeAssistant Add-on: https://github.com/bohdan-s/SunGather
"
+ body += "| Address | | Register | Value |
"
metrics = ""
for register, value in inverter.latest_scrape.items():
body += f"| {str(inverter.getRegisterAddress(register))} | {str(register)} | {str(value)} {str(inverter.getRegisterUnit(register))} |
"
@@ -53,7 +56,10 @@ class MyServer(BaseHTTPRequestHandler):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
- self.wfile.write(bytes("SunGather", "utf-8"))
+ self.wfile.write(bytes("SunGather", "utf-8"))
+ self.wfile.write(bytes("", "utf-8"))
+ self.wfile.write(bytes('', "utf-8"))
+ self.wfile.write(bytes("", "utf-8"))
self.wfile.write(bytes("", "utf-8"))
self.wfile.write(bytes(export_webserver.html_body, "utf-8"))
self.wfile.write(bytes("
", "utf-8"))
diff --git a/SunGather/sungather.py b/SunGather/sungather.py
index f84a3e4..462237e 100644
--- a/SunGather/sungather.py
+++ b/SunGather/sungather.py
@@ -25,19 +25,29 @@ class SungrowInverter():
"RetryOnEmpty": False,
}
self.inverter_config = {
- "slave": config_inverter.get('slave'),
"model": config_inverter.get('model'),
- "serial": config_inverter.get('serial'),
+ "serial_number": config_inverter.get('serial_number'),
"level": config_inverter.get('level'),
+ "scan_interval": config_inverter.get('scan_interval'),
"use_local_time": config_inverter.get('use_local_time'),
"smart_meter": config_inverter.get('smart_meter'),
- "connection": config_inverter.get('connection')
+ "connection": config_inverter.get('connection'),
+ "slave": config_inverter.get('slave'),
+ "start_time": ""
}
self.client = None
self.registers = [[]]
self.registers.pop() # Remove null value from list
- self.registers_custom = [{'name': 'export_to_grid', 'unit': 'W', 'address': 'vr001'}, {'name': 'import_from_grid', 'unit': 'W', 'address': 'vr002'}, {'name': 'run_state', 'address': 'vr003'}, {'name': 'timestamp', 'address': 'vr004'}]
+ self.registers_custom = [ {'name': 'run_state', 'address': 'vr001'},
+ {'name': 'timestamp', 'address': 'vr002'},
+ {'name': 'last_reset', 'address': 'vr003'},
+ {'name': 'export_to_grid', 'unit': 'W', 'address': 'vr004'},
+ {'name': 'import_from_grid', 'unit': 'W', 'address': 'vr005'},
+ {'name': 'daily_export_to_grid', 'unit': 'kWh', 'address': 'vr006'},
+ {'name': 'daily_import_from_grid', 'unit': 'kWh', 'address': 'vr007'}
+ ]
+
self.register_ranges = [[]]
self.register_ranges.pop() # Remove null value from list
@@ -330,12 +340,19 @@ class SungrowInverter():
def scrape(self):
scrape_start = datetime.now()
- # 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"
+ # Clear previous inverter values, persist some values
+ persist_registers = {
+ "run_state": self.latest_scrape.get("run_state","ON"),
+ "last_reset": self.latest_scrape.get("last_reset",""),
+ "daily_export_to_grid": self.latest_scrape.get("daily_export_to_grid",0),
+ "daily_import_from_grid": self.latest_scrape.get("daily_import_from_grid",0),
+ }
+
self.latest_scrape = {}
self.latest_scrape['device_type_code'] = self.inverter_config['model']
- self.latest_scrape["run_state"] = run_state
+
+ for register, value in persist_registers.items():
+ self.latest_scrape[register] = value
load_registers_count = 0
load_registers_failed = 0
@@ -347,7 +364,7 @@ class SungrowInverter():
load_registers_failed +=1
if load_registers_failed == load_registers_count:
# If every scrape fails, disconnect the client
- logging.warning
+ #logging.warning
self.disconnect()
return False
if load_registers_failed > 0:
@@ -356,52 +373,7 @@ class SungrowInverter():
# Leave connection open, see if helps resolve the connection issues
#self.close()
- # Create a registers for Power imported and exported to/from Grid
- if self.inverter_config['level'] >= 1:
- self.latest_scrape["export_to_grid"] = 0
- self.latest_scrape["import_from_grid"] = 0
-
- if self.validateRegister('meter_power'):
- try:
- 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
- # in this case we connected to a hybrid inverter and need to use export_power_hybrid
- # export_power_hybrid is negative in case of importing from the grid
- elif self.validateRegister('export_power_hybrid'):
- try:
- power = self.latest_scrape.get('export_power_hybrid', 0)
- if power < 0:
- self.latest_scrape["import_from_grid"] = abs(power)
- elif power >= 0:
- self.latest_scrape["export_to_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).contains('Run'):
- self.latest_scrape["run_state"] = "ON"
- else:
- self.latest_scrape["run_state"] = "OFF"
- else:
- self.latest_scrape["run_state"] = "OFF"
- except Exception:
- pass
-
+ ## vr002
if self.inverter_config.get('use_local_time',False):
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")}')
@@ -451,6 +423,85 @@ class SungrowInverter():
except Exception:
pass
+ ### Custom Registers
+ ######################
+
+ ## vr001 - run_state
+ # 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'):
+ logging.info(f"DEBUG: start_stop:{self.latest_scrape.get('start_stop', 'null')} work_state_1:{self.latest_scrape.get('work_state_1', 'null')}")
+ if self.latest_scrape.get('start_stop', False) == 'Start' and self.latest_scrape.get('work_state_1', False).contains('Run'):
+ self.latest_scrape["run_state"] = "ON"
+ else:
+ self.latest_scrape["run_state"] = "OFF"
+ else:
+ logging.info(f"DEBUG: Couldn't read start_stop so run_state is OFF")
+ self.latest_scrape["run_state"] = "OFF"
+ except Exception:
+ pass
+
+ ## vr003 - last_reset
+ date_format = "%Y-%m-%d %H:%M:%S"
+ if not self.latest_scrape.get('last_reset', False):
+ logging.info('Setting Initial Daily registers; daily_export_to_grid, daily_import_from_grid, last_reset')
+ self.latest_scrape["daily_export_to_grid"] = 0
+ self.latest_scrape["daily_import_from_grid"] = 0
+ self.latest_scrape['last_reset'] = self.latest_scrape["timestamp"]
+ elif datetime.strptime(self.latest_scrape['last_reset'], date_format).date() < datetime.strptime(self.latest_scrape['timestamp'], date_format).date():
+ logging.info('last_reset: ' + self.latest_scrape['last_reset'] + ', timestamp: ' + self.latest_scrape['timestamp'])
+ logging.info('Resetting Daily registers; daily_export_to_grid, daily_import_from_grid, last_reset')
+ self.latest_scrape["daily_export_to_grid"] = 0
+ self.latest_scrape["daily_import_from_grid"] = 0
+ self.latest_scrape['last_reset'] = self.latest_scrape["timestamp"]
+
+ ## vr004 - import_from_grid, vr005 - export_to_grid
+ # Create a registers for Power imported and exported to/from Grid
+ if self.inverter_config['level'] >= 1:
+ self.latest_scrape["export_to_grid"] = 0
+ self.latest_scrape["import_from_grid"] = 0
+
+ if self.validateRegister('meter_power'):
+ try:
+ 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
+ # in this case we connected to a hybrid inverter and need to use export_power_hybrid
+ # export_power_hybrid is negative in case of importing from the grid
+ elif self.validateRegister('export_power_hybrid'):
+ try:
+ power = self.latest_scrape.get('export_power_hybrid', 0)
+ if power < 0:
+ self.latest_scrape["import_from_grid"] = abs(power)
+ elif power >= 0:
+ self.latest_scrape["export_to_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
+
+ ## vr004, vr005
+ if not self.latest_scrape.get('daily_export_to_grid', False):
+ self.latest_scrape["daily_export_to_grid"] = 0
+
+ self.latest_scrape["daily_export_to_grid"] += ((self.latest_scrape["export_to_grid"] / 1000) * (self.inverter_config['scan_interval'] / 60 / 60) )
+
+ ## vr005
+ if not self.latest_scrape.get('daily_import_from_grid', False):
+ self.latest_scrape["daily_import_from_grid"] = 0
+
+ self.latest_scrape["daily_import_from_grid"] += ((self.latest_scrape["import_from_grid"] / 1000) * (self.inverter_config['scan_interval'] / 60 / 60) )
+
scrape_end = datetime.now()
logging.info(f'Inverter: Successfully scraped in {(scrape_end - scrape_start).seconds}.{(scrape_end - scrape_start).microseconds} secs')
@@ -502,6 +553,8 @@ def main():
runonce = True
logging.info(f'Starting SunGather {__version__}')
+ logging.info(f'Need Help? https://github.com/bohdan-s/SunGather')
+ logging.info(f'NEW HomeAssistant Add-on: https://github.com/bohdan-s/hassio-repository')
try:
configfile = yaml.safe_load(open(configfilename, encoding="utf-8"))
diff --git a/SunGather/version.py b/SunGather/version.py
index efc7796..222c11c 100644
--- a/SunGather/version.py
+++ b/SunGather/version.py
@@ -1 +1 @@
-__version__ = '0.3.9'
\ No newline at end of file
+__version__ = '0.4.0'
\ No newline at end of file
diff --git a/img/HomeAssistant-SunGrow_Device.png b/img/HomeAssistant-SunGrow_Device.png
new file mode 100644
index 0000000..6c4bd30
Binary files /dev/null and b/img/HomeAssistant-SunGrow_Device.png differ