Update Readme to be clearer about HA

This commit is contained in:
bohdan-s
2022-02-02 14:34:30 +11:00
parent 1a2ca4397c
commit 9c96860012
4 changed files with 72 additions and 71 deletions
+3 -1
View File
@@ -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. 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: <a href="https://github.com/bohdan-s/Sungrow-Inverter/blob/main/Modbus%20Information/Communication%20Protocol%20of%20PV%20Grid-Connected%20String%20Inverters_V1.1.37_EN.pdf">Communication Protocol of PV Grid-Connected String Inverters</a> and <a hreh="https://github.com/bohdan-s/Sungrow-Inverter/blob/main/Modbus%20Information/communication-protocol-of-residential-hybrid-inverterv1.0.20-1.pdf">Communication Protocol of Residential Hybrid Inverters</a> Register information based on official documentation: <a href="https://github.com/bohdan-s/Sungrow-Inverter/blob/main/Modbus%20Information/Communication%20Protocol%20of%20PV%20Grid-Connected%20String%20Inverters_V1.1.37_EN.pdf">Communication Protocol of PV Grid-Connected String Inverters</a> and <a href="https://github.com/bohdan-s/Sungrow-Inverter/blob/main/Modbus%20Information/communication-protocol-of-residential-hybrid-inverterv1.0.20-1.pdf">Communication Protocol of Residential Hybrid Inverters</a>
Has multiple export locations out of the box: Has multiple export locations out of the box:
* Console - Log directly to screen * 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 Put the following into your sensors.yaml
``` ```
sensor: sensor:
+62 -63
View File
@@ -143,73 +143,72 @@ class export_pvoutput(object):
return True return True
def publish(self, inverter): def publish(self, inverter):
self.collect_data(inverter) if 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)): any_data = False
any_data = False if inverter.validateLatestScrape('timestamp'):
if inverter.validateLatestScrape('timestamp'): now = datetime.datetime.strptime(inverter.getRegisterValue('timestamp'), "%Y-%m-%d %H:%M:%S")
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"))
data_point = str(now.strftime("%Y%m%d")) + "," + str(now.strftime("%H:%M")) for x in range(1, 13):
for x in range(1, 13): field = 'v' + str(x)
field = 'v' + str(x) if self.collected_data.get(field):
if self.collected_data.get(field): if x == 6 or x == 7: # Round to 1 decimal place
if x == 6 or x == 7: # Round to 1 decimal place value = round(self.collected_data[field] / self.collected_data['count'], 1)
value = round(self.collected_data[field] / self.collected_data['count'], 1) else: # Getting errors when uploading decimals for power/energy so return INT
else: # Getting errors when uploading decimals for power/energy so return INT value = int((self.collected_data[field] / self.collected_data['count']))
value = int((self.collected_data[field] / self.collected_data['count'])) data_point = data_point + "," + str(value)
data_point = data_point + "," + str(value) any_data = True
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
else: else:
payload_data = data data_point = data_point + ","
self.collected_data = {}
payload = {} if any_data:
payload['data'] = payload_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: # Max upload is 30, if over 30 then remove the oldest one
payload['c1'] = self.pvoutput_config['cumulative_flag'] 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: self.batch_count +=1
logging.debug("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload)) if self.batch_count >= self.pvoutput_config['batch_points']:
response = requests.post(url=self.url_addbatchstatus, headers=self.headers, params=payload, timeout=3) if not self.batch_data.__len__() > 0:
self.batch_count = 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: payload = {}
logging.error(f"PVOutput: Upload Failed; {str(response.status_code)} Message; {str(response.text)}") payload['data'] = payload_data
else:
self.batch_data = [] if self.pvoutput_config['cumulative_flag'] > 0:
self.last_publish = time.time() payload['c1'] = self.pvoutput_config['cumulative_flag']
logging.info("PVOutput: Data uploaded")
except Exception as err: try:
logging.error(f"PVOutput: Failed to Upload") logging.debug("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload))
logging.debug(f"{err}") 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: else:
logging.info("PVOutput: Data added to next batch upload") logging.info(f"PVOutput: Data logged, next upload in {int(((self.status_interval) * 60) - (time.time() - self.last_publish))} secs")
else:
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()
+6 -6
View File
@@ -432,7 +432,7 @@ registers:
level: 2 level: 2
address: 5077 address: 5077
datatype: "U32" 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"] 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
@@ -480,32 +480,32 @@ registers:
datatype: "S32" datatype: "S32"
indicator: True indicator: True
smart_meter: 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"] 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" 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" 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" 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" 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
+1 -1
View File
@@ -35,7 +35,7 @@ class SungrowInverter():
self.registers = [[]] self.registers = [[]]
self.registers.pop() # Remove null value from list 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 = [[]]
self.register_ranges.pop() # Remove null value from list self.register_ranges.pop() # Remove null value from list