Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created new Interfacer for accessing Fronius inverters and power meters via WEBapi #220

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions conf/interfacer_examples/FroniusWebAPI/FroniusWebAPI.emonhub.conf
Original file line number Diff line number Diff line change
@@ -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,
155 changes: 155 additions & 0 deletions conf/interfacer_examples/FroniusWebAPI/readme.md
Original file line number Diff line number Diff line change
@@ -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"
}
}
```
161 changes: 161 additions & 0 deletions src/interfacers/EmonHubFroniusAPIInterfacer.py
Original file line number Diff line number Diff line change
@@ -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)