Add support for reading serial

This commit is contained in:
bohdan-s
2022-12-12 22:29:15 +11:00
parent 3898c0ba53
commit 6bb564d962
3 changed files with 207 additions and 1 deletions
+200
View File
@@ -0,0 +1,200 @@
import logging
import requests
import datetime
import time
class export_hassio(object):
def __init__(self):
self.api_base = "http://supervisor/core/api"
self.url_addbatchstatus = self.url_base + "addbatchstatus.jsp"
self.url_jointeam = self.url_base + "jointeam.jsp"
self.url_leaveteam = self.url_base + "leaveteam.jsp"
self.url_getsystem = self.url_base + "getsystem.jsp"
self.tid = '1618'
self.status_interval = 5
@property
def headers(self):
return {
"X-Pvoutput-Apikey": self.pvoutput_config['api'],
"X-Pvoutput-SystemId": self.pvoutput_config['sid'],
"Content-Type": "application/x-www-form-urlencoded",
"cache-control": "no-cache",
}
def configure(self, config, inverter):
self.pvoutput_config = {
'api': config.get('api', None),
'sid': config.get('sid', None),
'join_team': config.get('join_team', True),
'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.batch_data = []
self.batch_count = 0
self.last_run = 0
self.last_publish = 0
for parameter in config.get('parameters'):
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:
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)
logging.debug(f"PVOutput: Response; {str(response.status_code)} Message; {str(response.content)}")
if response.status_code == 200:
system = response.text.split(';')[0]
teams = response.text.split(';')[2]
invertername = system.split(',')[0]
self.status_interval = int(system.split(',')[15])
team_member = False
for team in teams.split(','):
if team == self.tid:
team_member = True
break
else:
logging.error(f"PVOutput: System Status Failed; {str(response.status_code)} Message; {str(response.content)}")
except Exception as err:
logging.error(f"PVOutput: Failed to configure")
logging.debug(f"{err}")
return False
try:
if not team_member and self.pvoutput_config['join_team']:
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)
logging.debug(f"PVOutput: Response; {str(response.status_code)} Message; {str(response.content)}")
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}'")
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)}")
except Exception as err:
pass
logging.info(f"PVOutput: Configured export to {invertername} every {self.status_interval} minutes")
return True
def collect_data(self, inverter):
# Check all required registers have been returned by the inverter
if not inverter.validateLatestScrape('timestamp'):
logging.error(f"PVOutput: Skipped collecting data, Timestamp missing from last scrape")
return False
for parameter in self.pvoutput_parameters:
if not inverter.validateLatestScrape(parameter['register']):
logging.error(f"PVOutput: Skipped collecting data, {parameter['register']} missing from last scrape")
return False
# Add new data to old data and increase count of data points
for parameter in self.pvoutput_parameters:
value = inverter.getRegisterValue(parameter.get('register'))
if parameter.get('multiple'):
value = value * parameter.get('multiple')
# If using Cumulative Energy we just need the last data point, not the average
if parameter.get('name') == 'v1' and (self.pvoutput_config['cumulative_flag'] == 1 or self.pvoutput_config['cumulative_flag'] == 2):
self.collected_data[parameter.get('name')] = value
elif parameter.get('name') == 'v3' and (self.pvoutput_config['cumulative_flag'] == 1 or self.pvoutput_config['cumulative_flag'] == 3):
self.collected_data[parameter.get('name')] = value
# Add the last data point to the previous data point if exists, otherwise set as the last data point
elif self.collected_data.get(parameter.get('name'),False):
self.collected_data[parameter.get('name')] = round(self.collected_data[parameter.get('name')] + value,3)
else:
self.collected_data[parameter.get('name')] = value
if self.collected_data.get('count',False):
self.collected_data['count'] +=1
else:
self.collected_data['count'] = 1
logging.debug(f'PVOutput: Data Logged: {self.collected_data}')
return True
def publish(self, inverter):
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 == 1 and (self.pvoutput_config['cumulative_flag'] == 1 or self.pvoutput_config['cumulative_flag'] == 2):
value = int(self.collected_data[field])
elif x == 3 and (self.pvoutput_config['cumulative_flag'] == 1 or self.pvoutput_config['cumulative_flag'] == 3):
value = int(self.collected_data[field])
elif 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
else:
payload_data = data
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)}")
logging.error("PVOutput: Request; " + self.url_addbatchstatus + ", " + str(self.headers) + " : " + str(payload))
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(f"PVOutput: Data logged, next upload in {int(((self.status_interval) * 60) - (time.time() - self.last_publish))} secs")
self.last_run = time.time()
+1 -1
View File
@@ -23,7 +23,7 @@ registers:
datatype: "U16"
models: ["SH5K-20","SH3K6","SH4K6","SH5K-V13","SH5K-30","SH3K6-30","SH4K6-30","SH5.0RS","SH3.6RS","SH4.6RS","SH6.0RS","SH10RT","SH10RT-V112","SH8.0RT","SH6.0RT","SH5.0RT"]
- name: "serial_number"
level: 3
level: 1
address: 4990
datatype: "UTF-8"
- name: "device_type_code"
+6
View File
@@ -230,6 +230,12 @@ class SungrowInverter():
register_value = (register_value + u32_value * 0x10000 - 0xffffffff -1)
else:
register_value = register_value + u32_value * 0x10000
elif register.get('datatype') == "UTF-8": # This seems to be Serial only, 10 bytes
utf_value = register_value.to_bytes(2, 'big')
for x in range(1,5):
utf_value += rr.registers[num+x].to_bytes(2, 'big')
register_value = utf_value.decode()
# We convert a system response to a human value
if register.get('datarange'):