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__ + "

" + 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
RegisterValue
" metrics = "" for register, value in inverter.latest_scrape.items(): body += f"" @@ -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("
Address
RegisterValue
{str(inverter.getRegisterAddress(register))}{str(register)}{str(value)} {str(inverter.getRegisterUnit(register))}
", "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