From 9c968600120f60c1e266053be1acccb8bc298b5f Mon Sep 17 00:00:00 2001 From: bohdan-s Date: Wed, 2 Feb 2022 14:34:30 +1100 Subject: [PATCH] Update Readme to be clearer about HA --- README.md | 4 +- SunGather/exports/pvoutput.py | 125 +++++++++++++++++----------------- SunGather/registers.yaml | 12 ++-- SunGather/sungather.py | 2 +- 4 files changed, 72 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index f1f5ae9..8d918ec 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Access ModBus data from almost any network connected Sungow Inverter. On first connection the tool will query your inverter, retrieve the model and return the correct registers for your device. No more searching registers or creating model files. -Register information based on official documentation: Communication Protocol of PV Grid-Connected String Inverters and Communication Protocol of Residential Hybrid Inverters +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: * Console - Log directly to screen @@ -219,6 +219,8 @@ sensor.inverter_meter_power ``` +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: diff --git a/SunGather/exports/pvoutput.py b/SunGather/exports/pvoutput.py index 3bf7cd3..035eeb7 100644 --- a/SunGather/exports/pvoutput.py +++ b/SunGather/exports/pvoutput.py @@ -143,73 +143,72 @@ class export_pvoutput(object): return True def publish(self, inverter): - self.collect_data(inverter) - - # Process data points every status_interval - if((time.time() - self.last_publish) >= (self.status_interval * 60)): - any_data = False - if inverter.validateLatestScrape('timestamp'): - now = datetime.datetime.strptime(inverter.getRegisterValue('timestamp'), "%Y-%m-%d %H:%M:%S") - data_point = str(now.strftime("%Y%m%d")) + "," + str(now.strftime("%H:%M")) - for x in range(1, 13): - 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 = {} - - if any_data: - self.batch_data.append(data_point) - else: - logging.warning(f"PVOutput: No data collected in last {(self.status_interval * 60)} minutes") - - # Max upload is 30, if over 30 then remove the oldest one - if self.batch_data.__len__() > 30: - logging.warning(f"PVOutput: Over 30 data points scheduled to upload. max is 30 so removing oldest data point") - self.batch_data.pop(0) - - self.batch_count +=1 - if self.batch_count >= self.pvoutput_config['batch_points']: - if not self.batch_data.__len__() > 0: - logging.warning(f"PVOutput: No data collected in last {((self.status_interval * 60) * self.batch_count)} minutes, Skipping upload") - 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 + if self.collect_data(inverter): + # Process data points every status_interval + if((time.time() - self.last_publish) >= (self.status_interval * 60)): + any_data = False + if inverter.validateLatestScrape('timestamp'): + now = datetime.datetime.strptime(inverter.getRegisterValue('timestamp'), "%Y-%m-%d %H:%M:%S") + data_point = str(now.strftime("%Y%m%d")) + "," + str(now.strftime("%H:%M")) + for x in range(1, 13): + 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: - payload_data = data + data_point = data_point + "," + self.collected_data = {} - payload = {} - payload['data'] = payload_data + if any_data: + self.batch_data.append(data_point) + else: + logging.warning(f"PVOutput: No data collected in last {(self.status_interval * 60)} minutes") - if self.pvoutput_config['cumulative_flag'] > 0: - payload['c1'] = self.pvoutput_config['cumulative_flag'] + # Max upload is 30, if over 30 then remove the oldest one + if self.batch_data.__len__() > 30: + logging.warning(f"PVOutput: Over 30 data points scheduled to upload. max is 30 so removing oldest data point") + self.batch_data.pop(0) - try: - logging.debug("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload)) - response = requests.post(url=self.url_addbatchstatus, headers=self.headers, params=payload, timeout=3) - self.batch_count = 0 + self.batch_count +=1 + if self.batch_count >= self.pvoutput_config['batch_points']: + if not self.batch_data.__len__() > 0: + logging.warning(f"PVOutput: No data collected in last {((self.status_interval * 60) * self.batch_count)} minutes, Skipping upload") + 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 response.status_code != requests.codes.ok: - logging.error(f"PVOutput: Upload Failed; {str(response.status_code)} Message; {str(response.text)}") - else: - self.batch_data = [] - self.last_publish = time.time() - logging.info("PVOutput: Data uploaded") - except Exception as err: - logging.error(f"PVOutput: Failed to Upload") - logging.debug(f"{err}") + payload = {} + payload['data'] = payload_data + + if self.pvoutput_config['cumulative_flag'] > 0: + payload['c1'] = self.pvoutput_config['cumulative_flag'] + + try: + logging.debug("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload)) + response = requests.post(url=self.url_addbatchstatus, headers=self.headers, params=payload, timeout=3) + self.batch_count = 0 + + if response.status_code != requests.codes.ok: + logging.error(f"PVOutput: Upload Failed; {str(response.status_code)} Message; {str(response.text)}") + else: + self.batch_data = [] + self.last_publish = time.time() + logging.info("PVOutput: Data uploaded") + except Exception as err: + logging.error(f"PVOutput: Failed to Upload") + logging.debug(f"{err}") + else: + logging.info("PVOutput: Data added to next batch upload") else: - logging.info("PVOutput: Data added to next batch upload") - 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() \ No newline at end of file + self.last_run = time.time() \ No newline at end of file diff --git a/SunGather/registers.yaml b/SunGather/registers.yaml index c8bebab..c33afa2 100644 --- a/SunGather/registers.yaml +++ b/SunGather/registers.yaml @@ -432,7 +432,7 @@ registers: level: 2 address: 5077 datatype: "U32" - unit: "w" + 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"] - name: "reactive_power_regulation_setpoint" level: 2 @@ -480,32 +480,32 @@ registers: datatype: "S32" indicator: True smart_meter: True - unit: "w" + unit: "W" models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"] - name: "meter_a_phase_power" level: 2 address: 5085 datatype: "S32" - unit: "w" + unit: "W" models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"] - name: "meter_b_phase_power" level: 2 address: 5087 datatype: "S32" - unit: "w" + unit: "W" models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"] - name: "meter_c_phase_power" level: 2 address: 5089 datatype: "S32" - unit: "w" + unit: "W" models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"] - name: "load_power" level: 1 address: 5091 datatype: "S32" smart_meter: True - unit: "w" + unit: "W" models: ["SG5KTL-MT","SG6KTL-MT","SG8KTL-M","SG10KTL-M","SG10KTL-MT","SG12KTL-M","SG15KTL-M","SG17KTL-M","SG20KTL-M"] - name: "daily_export_energy" level: 1 diff --git a/SunGather/sungather.py b/SunGather/sungather.py index aed06dc..33f60b7 100644 --- a/SunGather/sungather.py +++ b/SunGather/sungather.py @@ -35,7 +35,7 @@ class SungrowInverter(): 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.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