diff --git a/conf/interfacer_examples/FroniusWebAPI/FroniusWebAPI.emonhub.conf b/conf/interfacer_examples/FroniusWebAPI/FroniusWebAPI.emonhub.conf new file mode 100644 index 0000000..f24efce --- /dev/null +++ b/conf/interfacer_examples/FroniusWebAPI/FroniusWebAPI.emonhub.conf @@ -0,0 +1,14 @@ + + + +### This interfacer manages connections to froinus Inverter via webapi calls +[[FroniusAPI]] + Type = EmonHubFroniusAPIInterfacer + [[[init_settings]]] + webAPI_IP = 192.168.1.11 + webAPI_port = 80 + [[[runtimesettings]]] + rName = Inverter_status,AC_power_watts,AC_LifetimekWh,DayWh,mppt1,mppt2,Vmppt1,Vmppt2,PhVphA,PhVphB,PhVphC + nodeId = 12 + interval = 20 # time in seconds between checks, This is in addition to emonhub_interfacer.run() sleep time of .01 + pubchannels = ToEmonCMS, diff --git a/conf/interfacer_examples/FroniusWebAPI/readme.md b/conf/interfacer_examples/FroniusWebAPI/readme.md new file mode 100644 index 0000000..3d7bd76 --- /dev/null +++ b/conf/interfacer_examples/FroniusWebAPI/readme.md @@ -0,0 +1,155 @@ +# Fronius Web API interface + +This interface starts a http connection and retrieves inverter and power meter information for publishing via mqtt to emonCMS +The Web API is documented in detail on the Fronius Web site. The PDF can be downloaded here https://www.fronius.com/en/solar-energy/installers-partners/products/all-products/system-monitoring/open-interfaces/fronius-solar-api-json- + +## Usage and configuration + +There is a sample FroniusWebAPI.emonhub.conf file located in this directory. + +### Sample interfacer config within emonhub.conf + +Sample configuration for modbus TCP clients +All inputs are derived from the webapi json output + +``` + +### This interfacer manages connections to fronius inverters via webapi +[[FroniusAPI]] + Type = EmonHubFroniusAPIInterfacer + [[[init_settings]]] + webAPI_IP = 192.168.1.11 # ip address of the inverter. + webAPI_port = 80 # http port the inverter listens on. default is 80 unless changed on the inverter settings. + [[[runtimesettings]]] + nodeId = 12 + interval = 20 # time in seconds between checks, This is in addition to emonhub_interfacer.run() sleep time of .01 + pubchannels = ToEmonCMS, + +``` + +### Sample Node declaration in emonhub.conf +Node ID must match node ID set in interfacer definition above + +``` +[[12]] + nodename = froniusAPI +``` +## Sample web api responses. + +/solar_api/v1/GetInverterInfo.cgi +``` +{ + "Body" : { + "Data" : { + "1" : { + "CustomName" : "Symo 5.0-3-M (1)", + "DT" : 122, + "ErrorCode" : 0, + "PVPower" : 6600, + "Show" : 1, + "StatusCode" : 7, + "UniqueID" : "0" + } + } + }, + "Head" : { + "RequestArguments" : {}, + "Status" : { + "Code" : 0, + "Reason" : "", + "UserMessage" : "" + }, + "Timestamp" : "2024-12-28T14:01:39+08:00" + } +} +``` +/solar_api/v1/GetPowerFlowRealtimeData.fcgi + +``` +{ + "Body" : { + "Data" : { + "Inverters" : { + "1" : { + "DT" : 122, + "E_Day" : 21288, + "E_Total" : 93495504, + "E_Year" : 9019699, + "P" : 4797 + } + }, + "Site" : { + "E_Day" : 21288, + "E_Total" : 93495504, + "E_Year" : 9019699, + "Meter_Location" : "grid", + "Mode" : "meter", + "P_Akku" : null, + "P_Grid" : -3829.8899999999999, + "P_Load" : -967.11000000000013, + "P_PV" : 4797, + "rel_Autonomy" : 100, + "rel_SelfConsumption" : 20.160725453408382 + }, + "Version" : "12" + } + }, + "Head" : { + "RequestArguments" : {}, + "Status" : { + "Code" : 0, + "Reason" : "", + "UserMessage" : "" + }, + "Timestamp" : "2024-12-28T12:42:44+08:00" + } +} +``` + +/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=1&DataCollection=3PInverterData&DeviceId=1 +``` +{ + "Body" : { + "Data" : { + "IAC_L1" : { + "Unit" : "A", + "Value" : 6.4699999999999998 + }, + "IAC_L2" : { + "Unit" : "A", + "Value" : 6.5700000000000003 + }, + "IAC_L3" : { + "Unit" : "A", + "Value" : 6.54 + }, + "UAC_L1" : { + "Unit" : "V", + "Value" : 242.30000000000001 + }, + "UAC_L2" : { + "Unit" : "V", + "Value" : 245 + }, + "UAC_L3" : { + "Unit" : "V", + "Value" : 245.5 + } + } + }, + "Head" : { + "RequestArguments" : { + "DataCollection" : "3PInverterData", + "DeviceClass" : "Inverter", + "DeviceId" : "1", + "Scope" : "Device" + }, + "Status" : { + "Code" : 0, + "Reason" : "", + "UserMessage" : "" + }, + "Timestamp" : "2024-12-28T12:47:39+08:00" + } +} +``` diff --git a/src/interfacers/EmonHubFroniusAPIInterfacer.py b/src/interfacers/EmonHubFroniusAPIInterfacer.py new file mode 100644 index 0000000..136d00a --- /dev/null +++ b/src/interfacers/EmonHubFroniusAPIInterfacer.py @@ -0,0 +1,161 @@ +import time +import json +import Cargo +import requests +import emonhub_coder + +from emonhub_interfacer import EmonHubInterfacer + +"""class EmonFroniusAPIInterfacer + +Template interfacer for use in development + +""" + +class EmonHubFroniusAPIInterfacer(EmonHubInterfacer): + + def __init__(self, name, webAPI_IP='192.168.1.13', webAPI_port=0): + """Initialize Interfacer + + """ + + # Initialization + super().__init__(name) + + # add or alter any default settings for this interfacer + # defaults previously defined in inherited emonhub_interfacer + # here we are just changing the batchsize from 1 to 100 + # and the interval from 0 to 30 + # self._defaults.update({'batchsize': 100,'interval': 30}) + + # This line will stop the default values printing to logfile at start-up + #self._settings.update(self._defaults) + + # Interfacer specific settings + # (settings not included in the inherited EmonHubInterfacer) + # The set method below is called from emonhub.py on + # initialisation and settings change and copies the + # interfacer specific settings over to _settings + #self._template_settings = {'read_interval': 10.0} + # self._template_settings = {'webAPI_IP': '192.168.1.13'} + # #self._template_settings = ('webAPI_port': 69} + # try: + # webAPI_IP = self.init_settings['webAPI_IP'] + # webAPI_port = self.init_settings['webAPI_port'] + # except Exception as err: + # print("Other error occurred: " + str(err)) + self.webAPI_IP = webAPI_IP + self.webAPI_port = webAPI_port + self._log.debug("EmonModbusTcpInterfacer args: %s - %s", webAPI_IP, webAPI_port) + #print(self.webAPI_IP) + #print(self.webAPI_port) + + + def read(self): + """Read data and process + + Return data as a list: [NodeID, val1, val2] + + """ + self._log.info("starting read") + self._log.debug("EmonHubAPIInterfacer args: %s - %s", self.webAPI_IP, self.webAPI_port) + + # create a new cargo object, set data values + time.sleep(float(self._settings['interval'])) + f = [] + c = Cargo.new_cargo(rawdata="") + + #get inverter status + url = "http://" +self.init_settings["webAPI_IP"] + "/solar_api/v1/GetInverterInfo.cgi" + try: + self._log.debug("Status URL: " + url) + response = requests.get(url) + response.raise_for_status() + except HTTPError as http_err: + self._log.error("HTTP error occurred: "+ str(http_err)) + except Exception as err: + self._log.error("Other error occurred: " + str(err)) + else: + + response_dict = response.json() + Inverter_status=response_dict["Body"]["Data"]["1"]["StatusCode"] + self._log.info("webAPI inverter status: " + str(Inverter_status)) + self._log.debug("Inverter Status Code " + str(Inverter_status)) + self._log.info("webAPI Device Offline") + + t = emonhub_coder.encode('H',Inverter_status) + f = f + list(t) + + # get local grid power data + + url = "http://"+self.init_settings["webAPI_IP"]+"/solar_api/v1/GetPowerFlowRealtimeData.fcgi" + try: + self._log.debug("Status URL: " + url) + response = requests.get(url) + response.raise_for_status() + except HTTPError as http_err: + self._log.error("HTTP error occurred: "+ str(http_err)) + except Exception as err: + self._log.error("Other error occurred: " + st(err)) + else: + response_dict = response.json() + AC_power_watts = response_dict["Body"]["Data"]["Inverters"]["1"]["P"] + t = emonhub_coder.encode('f',AC_power_watts * 10) + f = f + list(t) + AC_LifetimekWh = response_dict["Body"]["Data"]["Inverters"]["1"]["E_Total"] + t = emonhub_coder.encode('f',AC_LifetimekWh * 10) + f = f + list(t) + DayWh = response_dict["Body"]["Data"]["Inverters"]["1"]["E_Day"] + t = emonhub_coder.encode('f',DayWh * 1) + f = f + list(t) + + # send dummy results for invertstring data as its not available in the api + for x in range(4): + t = emonhub_coder.encode('h',0) + f = f + list(t) + + PhVphA = 0 + PhVphB = 0 + PhVphC = 0 + + if Inverter_status == 7 : # 7 = Inverter running + + # get inverter 3phase data phase amps and phase voltage + url = "http://"+self.init_settings["webAPI_IP"]+"/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=1&DataCollection=3PInverterData&DeviceId=1" + try: + self._log.debug("Status URL: " + url) + response = requests.get(url) + response.raise_for_status() + except HTTPError as http_err: + self._log.error("HTTP error occurred: "+ str(http_err)) + except Exception as err: + self._log.error("Other error occurred: " + st(err)) + else: + response_dict = response.json() + PhVphA = response_dict["Body"]["Data"]["UAC_L1"]["Value"] + PhVphB = response_dict["Body"]["Data"]["UAC_L2"]["Value"] + PhVphC = response_dict["Body"]["Data"]["UAC_L3"]["Value"] + t = emonhub_coder.encode('f',PhVphA * 10) + f = f + list(t) + t = emonhub_coder.encode('f',PhVphB * 10) + f = f + list(t) + t = emonhub_coder.encode('f',PhVphC * 10) + f = f + list(t) + + + self._log.debug("reporting data: " + str(f)) + if int(self._settings['nodeId']): + c.nodeid = int(self._settings['nodeId']) + c.realdata = f + else: + c.nodeid = int(12) + c.realdata = f + self._log.debug("Return from read data: " + str(c.realdata)) + return c + + + def set(self, **kwargs): + for key in kwargs: + setting = kwargs[key] + self._settings[key] = setting + self._log.debug("Setting %s %s: %s", self.name, key, setting)