Open raffiniert opened 3 years ago
Not having a dual inverter setup myself I am not entirely sure how they work in a Fronius context but it sounds like it is a matter of two independent inverters which both have the Fronius API enabled, but each need to be queried separately and the values added together to get the overall values?
Would you be able to query each of the two inverters in the way you have above and check that the output looks effectively the same? The way TWCManager is structured this would actually be doable out of the box if the inverters were two separate makes/models (as our default way of dealing with multiple EMS modules enabled is to AND them together) but we do not instantiate multiple instances of the same inverter.
That said, it wouldn't be hard to make the module accept multiple inverters and AND them internally, however... we need to be sure first that they both interface the same way because if the APIs aren't identical it might need a different module anyway
thank you so much!
So, I found the API of the 2nd inverter, which returns the following:
{ "Body" : { "Data" : { "Inverters" : { "1" : { "DT" : 1, "P" : 434 } }, "Site" : { "BatteryStandby" : false, "E_Day" : null, "E_Total" : null, "E_Year" : null, "Meter_Location" : "unknown", "Mode" : "produce-only", "P_Akku" : null, "P_Grid" : null, "P_Load" : null, "P_PV" : 472.55364990234375, "rel_Autonomy" : null, "rel_SelfConsumption" : null }, "Version" : "12" } }, "Head" : { "RequestArguments" : {}, "Status" : { "Code" : 0, "Reason" : "", "UserMessage" : "" }, "Timestamp" : "2021-10-29T07:22:26+00:00" } }
So I think this looks exactly the same as the first for P_PV, but P_Load is only provided by the first inverter (I suspect only the 1st is connected to the smart meter), thus only one of them provides P_LOAD.
Does that help already?
@ngardiner any info missing from my side? Thanks! :-)
I have a similar PV installation with 24.79 kWp:
I adapted the Fronius.py
file as follows:
config.json
points to the Gen24, "serverIP2" points to the second inverterupdate
gets "P_PV" and "P_Akku" from the Gen24 inverter and "P_PV" from the second inverter.
It also gets "PowerReal_P_Sum" and "Voltage_AC_Phase_1" from the smart metergetGeneration
returns the sum of "P_PV" from both invertersgetConsumption
takes the result of getGeneration
and adds "PowerReal_P_Sum" from Smart Meter plus "P_Akku" from Gen24. This works since both of these value can get negative when power is going to the grid or into the battery.This has been working very reliably for about 10 days here.
You can find my modified Fronius.py
file here:
# Fronius Datamanager Solar.API Integration (Inverter Web Interface)
import logging
import requests
import time
logger = logging.getLogger(__name__.rsplit(".")[-1])
class Fronius:
cacheTime = 10
config = None
configConfig = None
configFronius = None
consumedW = 0
fetchFailed = False
generatedW = 0
generatedW2 = 0
akku = 0
importW = 0
exportW = 0
lastFetch = 0
master = None
serverIP = None
serverPort = 80
serverIP2 = None
serverPort2 = 80
status = False
timeout = 5
voltage = 0
def __init__(self, master):
self.master = master
self.config = master.config
try:
self.configConfig = master.config["config"]
except KeyError:
self.configConfig = {}
try:
self.configFronius = master.config["sources"]["Fronius"]
except KeyError:
self.configFronius = {}
self.status = self.configFronius.get("enabled", False)
self.serverIP = self.configFronius.get("serverIP", None)
self.serverPort = self.configFronius.get("serverPort", "80")
self.serverIP2 = self.configFronius.get("serverIP2", None)
self.serverPort2 = self.configFronius.get("serverPort2", "80")
# Unload if this module is disabled or misconfigured
if (not self.status) or (not self.serverIP) or (int(self.serverPort) < 1):
self.master.releaseModule("lib.TWCManager.EMS", "Fronius")
return None
def getConsumption(self):
if not self.status:
logger.debug("Fronius EMS Module Disabled. Skipping getConsumption")
return 0
# Perform updates if necessary
#self.update()
generated = self.getGeneration()
if not self.akku:
self.akku = 0
consumed = generated + float(self.consumedW) + float(self.akku)
return consumed
def getGeneration(self):
if not self.status:
logger.debug("Fronius EMS Module Disabled. Skipping getGeneration")
return 0
# Perform updates if necessary
self.update()
# Return generation value
if not self.generatedW:
self.generatedW = 0
if not self.generatedW2:
self.generatedW2 = 0
return float(self.generatedW) + float(self.generatedW2)
def getInverterData(self):
url = "http://" + self.serverIP + ":" + self.serverPort
url = (
url
+ "/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=1&DataCollection=CommonInverterData"
)
return self.getInverterValue(url, False)
def getInverterValue(self, url, shouldSetFailed):
# Fetch the specified URL from the Fronius Inverter and return the data
try:
r = requests.get(url, timeout=self.timeout)
except requests.exceptions.ConnectionError as e:
logger.log(
logging.INFO4,
"Error connecting to Fronius Inverter to fetch sensor value",
)
logger.debug(str(e))
if shouldSetFailed:
self.fetchFailed = True
return False
r.raise_for_status()
jsondata = r.json()
return jsondata
def getMeterData(self, ip, port, shouldSetFailed):
url = "http://" + ip + ":" + port
url = url + "/solar_api/v1/GetPowerFlowRealtimeData.fcgi?Scope=System"
return self.getInverterValue(url, shouldSetFailed)
def getSmartMeterData(self):
url = "http://" + self.serverIP + ":" + self.serverPort
url = url + "/solar_api/v1/GetMeterRealtimeData.cgi?Scope=System"
return self.getInverterValue(url, False)
def update(self):
if (int(time.time()) - self.lastFetch) > self.cacheTime:
# Cache has expired. Fetch values from Fronius inverter.
self.fetchFailed = False
#inverterData = self.getInverterData()
#if inverterData:
# try:
# if "UAC" in inverterData["Body"]["Data"]:
# self.voltage = inverterData["Body"]["Data"]["UAC"]["Value"]
# except (KeyError, TypeError) as e:
# logger.log(
# logging.INFO4, "Exception during parsing Inveter Data (UAC)"
# )
# logger.debug(e)
meterData = self.getMeterData(self.serverIP, self.serverPort, True)
if meterData:
try:
self.generatedW = meterData["Body"]["Data"]["Site"]["P_PV"]
self.akku = meterData["Body"]["Data"]["Site"]["P_Akku"]
except (KeyError, TypeError) as e:
self.generatedW = 0
self.akku = 0
logger.log(
logging.INFO4,
"Exception during parsing Meter Data (Generation)",
)
logger.debug(e)
meterData2 = self.getMeterData(self.serverIP2, self.serverPort2, False)
if meterData2:
try:
self.generatedW2 = meterData2["Body"]["Data"]["Site"]["P_PV"]
except (KeyError, TypeError) as e:
self.generatedW2 = 0
logger.log(
logging.INFO4,
"Exception during parsing Meter Data 2 (Generation)",
)
logger.debug(e)
smartMeterData = self.getSmartMeterData()
if smartMeterData:
try:
self.consumedW = smartMeterData["Body"]["Data"]["0"]["PowerReal_P_Sum"]
self.voltage = smartMeterData["Body"]["Data"]["0"]["Voltage_AC_Phase_1"]
except (KeyError, TypeError) as e:
self.consumedW = 0
logger.log(
logging.INFO4,
"Exception during parsing Smart Meter Data (Consumption)",
)
logger.debug(e)
# Update last fetch time
if self.fetchFailed is not True:
self.lastFetch = int(time.time())
return True
else:
# Cache time has not elapsed since last fetch, serve from cache.
return False
As an alternative, I just sent a PR to add support for Open Energy Monitor (openenergymonitor.org). You can pull your inverter data into that, then combine the generation feeds into a new feed aggregating production for both, before passing the data to TWCManager.
I have a complex setup with 4 inverters + 2 standalone chargers, and this works well.
thanks @blach & @deece ! Will look into it these days. @ngardiner no more updates from your side? Did I not send all the information you need?
@blach I was finally able to install it correctly, I edited the config.json to include both IPs of both inverters, and I replaced the Fronius.py with your new code. Thank you so much so far!
However, I get a plain "0" on all meters on the web-dashboard. While it's already late and only some fraction of power comes in, it's still around 100W in total.
I checked the following responses:
http://[ip]/solar_api/v1/GetPowerFlowRealtimeData.fcgi http://[ip]/solar_api/v1/GetPowerFlowRealtimeData.fcgi http://[ip]/solar_api/v1/GetMeterRealtimeData.cgi?Scope=System
and the corresponding attributes in the JSONs, and they return what I would expect.
Any idea where I could look?
Oh, I also dropped the loglevel to 17 - where is it supposed to log? To console?
Maybe I have to add that it is still not connected to my TWC - is that the culprit?
Seems I get an update on the numbers when I press the buttons on the bottom of the page, like "charge now"... looked in the code, there is a cache, but it's only set to 10s?
Did you connect the software to your Tesla wall connector yet? If I remember correctly, it didn't query the inverters for me before it successfully communicated with the TWCs.
It's also necessary that the policy is set to "Solar Surplus". If it isn't, the inverters aren't queried either.
I assume you have a Fronius Smart Meter connected to your Gen24? Make sure that serverIP points to the Gen24 and serverIP2 points to the Symo 7.0.
If that still doesn't work, one difference might be that I have a battery in my system and you don't. Not sure if that would cause different inverter outputs.
Maybe I have to add that it is still not connected to my TWC - is that the culprit?
Oh, I just saw that. Yes, that is most likely the problem.
awesome. Sounds like I'll be ready to connect it tomorrow... thanks so much!
@blach I will go outside and connect it now for the first time, though I still suspect something is off. I added both inverter-IPs to config.json, I exchanged Fronius.py with your file, but I only see the wattage of the first inverter on the web-dashboard. Don't understand this yet...
Oh, and should I be able to set the policy to "solar surplus" on the web dashboard? I cannot change anything there on those 3 tabs, it's on "fixed rate/amps" and the others are unclickable.
this is my Fronius-entry in the /etc/TWCManager/config.json:
` "Fronius": { "enabled": true, "serverIP": "10.0.x.x", "serverIP2": "10.0.x.y" },
`
and yes, the IPs are correct...
Oh, and should I be able to set the policy to "solar surplus" on the web dashboard? I cannot change anything there on those 3 tabs, it's on "fixed rate/amps" and the others are unclickable.
No, it's selected according to your settings. I have set "Non-Scheduled power action" to "Track Green Energy".
I just saw this, thanks! This part seems okay now.
Solar tracking is still only working for one of the inverters... :-(
@blach I will go outside and connect it now for the first time, though I still suspect something is off. I added both inverter-IPs to config.json, I exchanged Fronius.py with your file, but I only see the wattage of the first inverter on the web-dashboard. Don't understand this yet...
Are you sure it is using the modified file? Did you install it using setup.py or how are you running it?
Maybe you can add a log statement to Fronius.py and see it if is printed to the log to make sure it using the right file.
I seem to be having some issues with basic UNIX :-) sorry. Basically, I mounted the raspi-"disk" via afp to my Mac to modify files. I did so by using netatalk.
I am now ssh'ing into the file system to look at the file.
Do I need to run setup.py again after the file is changed?! :-o
I don't know. I don't use Docker.
While developing I ran it directly from the directory using sudo -u twcmanager python -m TWCManager
.
I uninstalled docker as well and am running a manual install now! I am running it using the service, like:
sudo cp contrib/twcmanager.service /etc/systemd/system/twcmanager.service sudo systemctl enable twcmanager.service --now
That runs the package installed in the system using setup.py.
To debug the problem, try to stop the service and run it directly from the terminal from the twcmanager directory using the command I posted above.
Something like:
sudo systemctl stop twcmanager
cd TWCManager # change to the directory containing TWCManager.py
sudo -u twcmanager python -m TWCManager
After a moment you'll see the log output in the terminal.
You can then quit the program using Ctrl-C.
I did that, thanks. I've set the loglvl to 16, added a log-stmt to see the values, and now the whole thing is bricked and doesn't start up. git diff doesn't show anything that looks wrong.
Really upset about myself I don't seem to be able to handle this.
getting this on startup:
` Traceback (most recent call last): File "/usr/local/lib/python3.7/dist-packages/commentjson-0.9.0-py3.7.egg/commentjson/commentjson.py", line 180, in loads parsed = _remove_trailing_commas(parser.parse(text)) File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/lark.py", line 311, in parse return self.parser.parse(text, start=start) File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/parser_frontends.py", line 89, in parse return self._parse(token_stream, start, [sps] if sps is not NotImplemented else []) File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/parser_frontends.py", line 54, in _parse return self.parser.parse(input, start, args) File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/parsers/lalr_parser.py", line 36, in parse return self.parser.parse(*args) File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/parsers/lalr_parser.py", line 84, in parse for token in stream: File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/lexer.py", line 373, in lex for x in l.lex(stream, self.root_lexer.newline_types, self.root_lexer.ignore_types): File "/usr/local/lib/python3.7/dist-packages/lark_parser-0.7.8-py3.7.egg/lark/lexer.py", line 174, in lex raise UnexpectedCharacters(stream, line_ctr.char_pos, line_ctr.line, line_ctr.column, allowed=allowed, state=self.state, token_history=last_token and [last_token]) lark.exceptions.UnexpectedCharacters: No terminal defined for 'X' at line 224 col 1
X # a URL such as:
^
Expecting: {'RBRACE', 'RSQB', 'TRAILING_COMMA', 'COLON'}
Previous tokens: Token(ESCAPED_STRING, '"/dev/serial0"')`
While it is of course true that I had to change the interface from USB to serial (because I'm using a RS485 hat), I don't see anything wrong with that line:
"port": "/dev/serial0"
Sounds like there is a stray "X" character in line 224 of config.json after the port setting?
no, there isn't... and I removed the whole TMCManager-directory now, checked it out from github again, make install ... same error.
This is driving my crazy :-D
Maybe you can attach your /etc/twcmanager/config.json
file.
hell no...
why is there one config.json at /etc/twcmanager/config.json
and one at /TWCManager/etc/twcmanager/config.json
?! :-D That maybe could be the issue
Running and up again. Anyway, maybe then I'm also editing the wrong Fronius.py?
Btw., I would like to buy you a beer. Or a few of them, tbh.
oh man! I think I am onto it... I need to run make install
after the Fronius.py is changed, as this was compiled by that process into pyc, correct?
If so, I'm sorted...
Yes.
still the same. I re-ran make install after changing the file, same result. Only one inverter (the first) is queried.
Then I can only recommend to add some logging statements to see what's going on.
I think the files are cached somewhere else... if I add log-statements, they aren't fired, so it seems it doesn't matter what I change on the Fronius.py Maybe easiest if I start all over again (empty disk) and change the Fronius.py before the first make install...
To debug the problem, try to stop the service and run it directly from the terminal from the twcmanager directory using the command I posted above.
Something like:
sudo systemctl stop twcmanager cd TWCManager # change to the directory containing TWCManager.py sudo -u twcmanager python -m TWCManager
After a moment you'll see the log output in the terminal.
You can then quit the program using Ctrl-C.
This should still work. It doesn't used the precompiled package but directly uses the .py files.
yes, it does.
What I did:
logger.info( "generatedW", self.generatedW )
&
logger.info( "generatedW2", self.generatedW2 )
this is seemingly not the correct way to log, but the output is still readable:
TypeError: not all arguments converted during string formatting ... Message: 'generatedW' Arguments: (None,)
&
TypeError: not all arguments converted during string formatting ... Message: 'generatedW2' Arguments: (0.0,)
while the json of 1 is returning "P_PV" : null,
and 2 is "P_PV" : 0.0,
so I think it might be valid now (it's dark outside) :-D
it's working now! All good.
Can I buy you a beer?
it's working now! All good.
Can I buy you a beer?
Great to hear that it works now. No worries, I was struggling with very similar issues before it worked for me and I'm just glad that I can help someone else by sharing what I found.
I'm curious: what was the problem in the end? Did you have to change something in the config file or Fronius.py?
New user here. I just got TWCManager up and going. I have a similar use-case. I have a Fronius Primo (6.6kW of PV) and a Redback battery system (5.8kW PV, 13kWh of batts). I have the Fronius smart meter installer, but obviously the generation is incorrect. Forgive me as I'm very new to TWCManager and how it works. But for use-cases where mine where one system is not metered, would it be possible to make TWCManager use the power at the grid connection instead? For example, you are charging at 6kW, the Fronius smart meter measures 2kW export to the grid, therefore TWCManager can increase the charge power to 8kW. I'm not sure if this is possible. Perhaps it should be split to a separate feature.
Edit: I just had a look at what blach did above. I'm going to try something similar by estimating the generation as 2 * generation (similar sized systems) and back-calculate the consumption from the grid power measured by the meter. I'll trial this for a few days and see how it goes. As soon as I deployed it, clouds rolled over. Haha
Hey
we have a big PV system on the roof (22kWp) and use 2 inverters for it:
As I understand it, when I query the API (v1/GetPowerFlowRealtimeData.fcgi), I only get data from one of them. Currently, I get this:
{ "Body" : { "Data" : { "Inverters" : { "1" : { "DT" : 105, "E_Day" : 24256, "E_Total" : 9652380, "E_Year" : 8519768, "P" : 863 } }, "Site" : { "E_Day" : 24256, "E_Total" : 9652380, "E_Year" : 8519768, "Meter_Location" : "grid", "Mode" : "meter", "P_Akku" : null, "P_Grid" : -1691.22, "P_Load" : 828.22000000000003, "P_PV" : 863, "rel_Autonomy" : 100, "rel_SelfConsumption" : 0 }, "Smartloads" : { "Ohmpilots" : { "720896" : { "P_AC_Total" : 0, "State" : "normal", "Temperature" : 65.799999999999997 } } }, "Version" : "12" } }, "Head" : { "RequestArguments" : {}, "Status" : { "Code" : 0, "Reason" : "", "UserMessage" : "" }, "Timestamp" : "2021-10-28T17:23:57+02:00" } }
Is there a way from the API-side or from TWC-side to query both inverters?
Generally, both inverters produce almost exactly the same energy, as east- and westside of the cells is split evenly across them.
Thanks! Raphi