mirror of
https://github.com/alexhopeoconnor/SunGather.git
synced 2026-10-15 23:51:19 +10:00
Add support for reading serial
This commit is contained in:
@@ -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()
|
||||
@@ -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"
|
||||
|
||||
@@ -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'):
|
||||
|
||||
Reference in New Issue
Block a user