Realtime uitlezen APSystems in Domoticz
Sinds kort zijn wij trotse bezitters van zonnepanelen. Omdat het energieverbruik reeds werd bijgehouden in Domoticz en geen voorstander ben van verschillende portals/apps om alles in bij te houden wilde ik ook de opbrengst van de zonnepanelen weergegeven hebben in Domoticz.
Nadat de leverancier alle panelen op het dak had gemonteerd en het systeem draaide vroeg ik of de ECU-R ook uit te lezen was via Domoticz. Helaas, dit bleek afgeschermd te zijn. Maar er was wel een app (EMA App) en een webportal waarmee je het verbruik kon uitlezen. Beide opties gaven een vertraging van circa vijf minuten. Er was ook nog de ECUAPP. Door op de zijkant van de router te klikken zend deze een uur lang een WiFi-signaal uit. Door met je telefoon/tablet te verbinden met deze WiFi-hotspot kun je met de ECUAPP de realtime waarden uitlezen.
Op Tweakers.net waren ze hier ook achter gekomen en zijn een aantal slimme koppen bezig geweest om al een groot deel van de data via een Python-script uit te lezen. Hierbij dient te worden opgemerkt dat dit alleen werkt via WiFi.
Een aantal leden van Tweakers maakt gebruik van Home Assistant. De plug-in dient dus (iets) aangepast te worden waardoor het gebruikt kan voor Domoticz.
Plug-in installeren
Naast onderstaande code heb je ook de plug-in nodig welke te downloaden is van Github.
Om de python scripts uit te kunnen voeren dient op de Raspberry Pi Python 3.x te worden geïnstalleerd.
#!/usr/bin/env python3
from APSystemsECUR import APSystemsECUR
import time
import asyncio
import urllib.request
import urllib.parse
import urllib
from pprint import pprint
ecu_ip = "<ECU-R-IP>"
sleep = 300
url = '<DomoticzIP>:8080/json.htm?'
puntcomma = '\u003B'
loop = asyncio.get_event_loop()
ecu = APSystemsECUR(ecu_ip)
while True:
try:
data = loop.run_until_complete(ecu.async_query_ecu())
print(data)
lifetime_energy = str(data.get('lifetime_energy')*1000)
today_energy_kwh = str(data.get('today_energy')*1000)
current_power = str(data.get('current_power'))
print('current_power: ' + current_power)
generated_energy = (current_power + puntcomma + lifetime_energy)
print('output: ' + today_energy_kwh + ';' + current_power)
#pwr = .format(today_energy_kwh, current_power)
#print('PWR: ' + pwr)
print('Today energy [kWh]: ' + today_energy_kwh)
if (float(today_energy_kwh) >= 0 or float(current_power) >= 0):
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1606, 'svalue': (generated_energy)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
print(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1610, 'svalue': data.get('timestamp')}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#print(url + urllib.parse.urlencode(getVars))
#inverter values
inverters = data.get('inverters')
#count number of inverters
Inverter_qty = len(data.get('inverters'))
print('Inverter_cnt: ' + str(Inverter_qty))
# loop trough all inverters and get the data
for i in range(Inverter_qty):
Inverter = list(inverters.keys())[i]
print('InverterId: ' + Inverter)
InverterOnline = data['inverters'][Inverter]['online']
print('Online: ' + str(InverterOnline))
InverterTemperature = data['inverters'][Inverter]['temperature']
print('Temperature: ' + str(InverterTemperature))
nVoltage = len(data['inverters'][Inverter]['voltage'])
nPower = len(data['inverters'][Inverter]['power'])
for x in range(nPower):
voltage = data['inverters'][Inverter]['voltage'][x]
power = data['inverters'][Inverter]['power'][x]
print('Power inverter ' + str(i + 1) + ' panel ' + str(x + 1) + ': ' + str(power) + ' W')
#upload values to Domoticz voor inverter 1
if (i == 0) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1624, 'svalue': voltage}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1609, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if (InverterOnline == True) and (voltage != 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1607, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
elif (InverterOnline == True) and (voltage == 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1607, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1607, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload values to Domoticz voor inverter 2
if (i == 1) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1614, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if (InverterOnline == True) and (voltage != 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1611, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
elif (InverterOnline == True) and (voltage == 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1611, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1611, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload values to Domoticz voor inverter 3
if (i == 2) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1615, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if (InverterOnline == True) and (voltage != 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1612, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
elif (InverterOnline == True) and (voltage == 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1612, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1612, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload values to Domoticz voor inverter 4
if (i == 3) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1616, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if (InverterOnline == True) and (voltage != 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1613, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
elif (InverterOnline == True) and (voltage == 0) :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1613, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1613, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload power values to Domoticz voor inverter 1
if (i == 0) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1608, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 0) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1617, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 0) and (x == 2) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 198, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 0) and (x == 3) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 199, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#upload power values to Domoticz voor inverter 2
if (i == 1) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1618, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 1) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1619, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 1) and (x == 2) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 202, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 1) and (x == 3) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 203, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#upload power values to Domoticz voor inverter 3
if (i == 2) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1620, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 2) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1621, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 2) and (x == 2) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 206, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 2) and (x == 3) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 207, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#upload power values to Domoticz voor inverter 4
if (i == 3) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1622, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 3) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1623, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 3) and (x == 2) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 210, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 3) and (x == 3) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 211, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
except Exception as err:
print(f"[ERROR]", {err})
#print(f"Sleeping for {sleep} sec")
time.sleep(sleep)
- Plaats de plug-in en bovenstaand script in je Domoticz map onder scripts/python/ECU-R.
- Maak een Dummy sensor aan in Domoticz en noem deze ‘Virtual switches’ of een andere duidelijke naam.
Dummy sensoren toevoegen in Domoticz
- Maak een virtuele sensor aan van het type ‘Elektra (Actueel + teller)’. Geef de sensor de naam ‘Opwekking zonnepanelen’.
- Maak een virtuele sensor aan van het type ‘Schakelaar’ en noem deze Inverter [nummer inverter].
- Herhaal deze stap voor het aantal micro-omvormers dat je hebt.
- Maak een virtuele sensor aan van het type ‘Verbruik (Elektra)’ en noem deze Inverter [nummer inverter] – Power [paneelnummer].
- Herhaal deze stap voor het aantal panelen per micro-omvormer.
- Maak een virtuele sensor aan van het type ‘Temperatuur’ en noem deze Inverter [nummer inverter] – Temperatuur.
- Herhaal deze stap voor het aantal micro-omvormers dat je hebt.
- Maak een virtuele sensor aan van het type ‘Tekst’ en noem deze Timestamp.
- Ga naar ‘Apparaten’ en pas in bovenstaand script de idx-en aan naar de idx van de virtuele sensoren welke je net hebt aangemaakt en sla het script op.
- Start het script op met het commando
python3 <Domoticz map>/scripts/python/ECU-R/ECU-R.py
. - Om het script na elke reboot van de Raspberry Pi automatisch laten opstarten dien je de volgende regel toe te voegen in crontab:
@reboot python3 /home/pi/domoticz/scripts/python/ECU-R/ECU-R.py
.
Update 25-01-2023
Gebruiker Sebastiaan Terlouw heeft het script iets aangepast waardoor deze nu ook compatible is voor de DS3 regelaar.
Update 15-02-2023
Sebastiaan Terlouw heeft het netvoltage toegevoegd aan het script.
Dit delen:
- Klik om te delen met Twitter (Wordt in een nieuw venster geopend)
- Klik om te delen op Facebook (Wordt in een nieuw venster geopend)
- Zoeken
- Klik om dit te e-mailen naar een vriend (Wordt in een nieuw venster geopend)
- Klik om op LinkedIn te delen (Wordt in een nieuw venster geopend)
- Gebruiker (Wordt in een nieuw venster geopend)
- Front-end SEO-inspecteur (Wordt in een nieuw venster geopend)
- Statistieken (Wordt in een nieuw venster geopend)
- Meldingen (Wordt in een nieuw venster geopend)
Hello,
When I execute the script, I have this error :
[ERROR] {TypeError(“object of type ‘NoneType’ has no len()”)}
Any idea of what’s wrong ?
Hi JulienG,
Probably the variable data contains no data. You can test this by adding the following code between
data = loop.run_until_complete(ecu.async_query_ecu()) and print(data)
This is row 23 and 24.
if data is not None:
# Voer de rest van de code uit die afhankelijk is van data
lifetime_energy = str(data.get(‘lifetime_energy’)1000)
today_energy_kwh = str(data.get(‘today_energy’)1000)
current_power = str(data.get(‘current_power’))
# enzovoort…
else:
print(“[ERROR] Geen gegevens ontvangen van ECU”)
Hello,
I use the script but it displays only one inverter.
Did you made all the inverters in Domoticz? And does all the idx correspondent within your script?
Hi,
yes it’s me, it matches my idx, the script doesn’t display an error message, it just does the sleep.
Hoi Bjorn,
Ik gebruik het script een half jaar ongeveer en het werkte perfect, nog bedankt!
Maar sinds 2 dagen heb ik een error waardoor het niet meer werkt, misschien iemand een idee?
Alvast bedankt.
[ERROR] {APSystemsInvalidData(“Unable to convert binary to int location=184 data=b’4150533131 enz. lange string
Hoi Paul,
Het lijkt erop dat de data (of een deel van de data) van de inverters niet kan worden omgezet. Je kunt testen of dit door het script handmatig op te starten (bijv. in een Windows omgeving en controleren hoe de waarden binnen komen en welke waarden wel goed binnenkomen.
Alles werkt weer Bjorn, bedankt voor de tip!
Hallo Bjorn,
Bedank voor het delen van je informatie.
Ik loop helaas vast bij de volgende stap: “Ga naar ‘Apparaten’ en pas in bovenstaand script de idx-en aan naar de idx van de virtuele sensoren welke je net hebt aangemaakt en sla het script op.”
De sensoren die ik heb aangemaakt in Domoticz hebben nummers van 43 tot 67. Moet ik deze als IDX’en beschouwen en in het reeds opgeslagen script ECU-R.py met VI aanpassen / invullen? Waar in het script?
Groeten,
Anton
Hoi Anton,
Elk apparaat heeft een eigen IDX (identificatienummer).
Het script, dat begint met #!/usr/bin/env python3 dien je op te slaan in dezelfde directory als het script ECU-R.py. Vervolgens pas je met een editor (bijv. Nano of VI de idx-en in het script aan naar de idx-en welke jouw apparaten hebben.
Groet,
Björn
Hoi Björn, vandaag is er een update geweest naar Domoticz 2023.1
Deze versie is default voorzien van (admin) User en Password.
Sindsdien werkt het ECU-R.py script niet meer (HTTPError 401: ‘Únauthorized’
Weet jij of het mogelijk is om hier inlog gegevens mee te geven?
Tot vandaag heeft het een jaar probleemloos gewerkt.
Groetjes Willem van Oosterhout
Hoi Willem,
Ik heb Domoticz bij mijzelf niet meer draaien (zou ik opnieuw moeten installeren om te testen). Maar kun je niet op basis van een (lokaal) IP-adres aangeven dat je niet hoeft in te loggen? Zie voor meer info de Wiki van Domoticz.
Helaas, nog niet kunnen vinden.
Terug naar de vorige versie dan maar.
Iig bedankt voor je reactie.
Idd mogelijk om lokaal netwerk als vertrouwd op te geven, Werkt weer. Bedankt!
Super. Veel succes en plezier met de panelen!
en jouw script ook wat aangepast
ik zag dat hij bij mij 4 regelaars x 2 panelen.
dat hij 16x een Switch omzette terwijl dat met 4x ook wel voldoende was:
Ook wordt de regelaar ‘uit’ gezet in domoticz als de Temp 0 is (want dat zie ik gebeuren als de regelaar afschakelt)
#!/usr/bin/env python3
from APSystemsECUR import APSystemsECUR
import time
import asyncio
import urllib.request
import urllib.parse
import urllib
from pprint import pprint
ecu_ip = “192.168.0.80”
sleep = 300
url = ‘http://192.168.0.200:8080/json.htm?’
puntcomma = ‘\u003B’
loop = asyncio.get_event_loop()
ecu = APSystemsECUR(ecu_ip)
while True:
try:
data = loop.run_until_complete(ecu.async_query_ecu())
#pprint(data)
lifetime_energy = str(data.get('lifetime_energy')*1000)
today_energy_kwh = str(data.get('today_energy')*1000)
current_power = str(data.get('current_power'))
print('current_power: ' + current_power)
generated_energy = (current_power + puntcomma + lifetime_energy)
print('output: ' + today_energy_kwh + ';' + current_power)
#pwr = .format(today_energy_kwh, current_power)
#print('PWR: ' + pwr)
print('Today energy [kWh]: ' + today_energy_kwh)
if (float(today_energy_kwh) >= 0 or float(current_power) >= 0):
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1606, 'svalue': (generated_energy)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
print(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1610, 'svalue': data.get('timestamp')}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#print(url + urllib.parse.urlencode(getVars))
#inverter values
inverters = data.get('inverters')
#count number of inverters
Inverter_qty = len(data.get('inverters'))
print('Inverter_cnt: ' + str(Inverter_qty))
# loop trough all inverters and get the data
for i in range(Inverter_qty):
Inverter = list(inverters.keys())[i]
print('InverterId: ' + Inverter)
InverterOnline = data['inverters'][Inverter]['online']
print('Online: ' + str(InverterOnline))
InverterTemperature = data['inverters'][Inverter]['temperature']
print('Temperature: ' + str(InverterTemperature))
nPower = len(data['inverters'][Inverter]['power'])
for x in range(nPower):
power = data['inverters'][Inverter]['power'][x]
print('Power inverter ' + str(i + 1) + ' panel ' + str(x + 1) + ': ' + str(power) + ' W')
#upload values to Domoticz voor inverter 1
if (i == 0) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1609, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if InverterOnline == True :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1607, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1607, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload values to Domoticz voor inverter 2
if (i == 1) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1614, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if InverterOnline == True :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1611, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1611, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload values to Domoticz voor inverter 3
if (i == 2) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1615, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if InverterOnline == True :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1612, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1612, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload values to Domoticz voor inverter 4
if (i == 3) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1616, 'svalue': InverterTemperature}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
if InverterOnline == True :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1613, 'switchcmd': 'On', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
else :
getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': 1613, 'switchcmd': 'Off', 'level': 0, 'passcode': '' }
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
#upload power values to Domoticz voor inverter 1
if (i == 0) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1608, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 0) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1617, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 0) and (x == 2) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 198, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 0) and (x == 3) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 199, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#upload power values to Domoticz voor inverter 2
if (i == 1) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1618, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 1) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1619, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 1) and (x == 2) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 202, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 1) and (x == 3) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 203, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#upload power values to Domoticz voor inverter 3
if (i == 2) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1620, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 2) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1621, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 2) and (x == 2) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 206, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 2) and (x == 3) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 207, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#upload power values to Domoticz voor inverter 4
if (i == 3) and (x == 0) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1622, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
elif (i == 3) and (x == 1) :
getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 1623, 'svalue': (power)}
webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 3) and (x == 2) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 210, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
#elif (i == 3) and (x == 3) :
#getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': 211, 'svalue': (power)}
#webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
except Exception as err:
print(f"[ERROR]", {err})
#print(f"Sleeping for {sleep} sec")
time.sleep(sleep)
APSystemsECUR aangepast zodat hij compatible is met de DS3 regelaar:
#!/usr/bin/env python3
import asyncio
import socket
import binascii
import datetime
import json
import logging
_LOGGER = logging.getLogger(name)
from pprint import pprint
class APSystemsInvalidData(Exception):
pass
class APSystemsECUR:
def __init__(self, ipaddr, port=8899, raw_ecu=None, raw_inverter=None):
self.ipaddr = ipaddr
self.port = port
# what do we expect socket data to end in
self.recv_suffix = b'END\n'
# how long to wait on socket commands until we get our recv_suffix
self.timeout = 5
# how many times do we try the same command in a single update before failing
self.cmd_attempts = 3
# how big of a buffer to read at a time from the socket
self.recv_size = 4096
self.qs1_ids = [ "802", "801" ]
self.yc600_ids = [ "406", "407", "408", "409" ]
self.yc1000_ids = [ "501", "502", "503", "504" ]
self.ds3_ids = [ "703", "704" ]
self.cmd_suffix = "END\n"
self.ecu_query = "APS1100160001" + self.cmd_suffix
self.inverter_query_prefix = "APS1100280002"
self.inverter_query_suffix = self.cmd_suffix
self.inverter_signal_prefix = "APS1100280030"
self.inverter_signal_suffix = self.cmd_suffix
self.inverter_byte_start = 26
self.ecu_id = None
self.qty_of_inverters = 0
self.lifetime_energy = 0
self.current_power = 0
self.today_energy = 0
self.inverters = []
self.firmware = None
self.timezone = None
self.last_update = None
self.ecu_raw_data = raw_ecu
self.inverter_raw_data = raw_inverter
self.inverter_raw_signal = None
self.read_buffer = b''
self.reader = None
self.writer = None
def dump(self):
print(f"ECU : {self.ecu_id}")
print(f"Firmware : {self.firmware}")
print(f"TZ : {self.timezone}")
print(f"Qty of inverters : {self.qty_of_inverters}")
async def async_read_from_socket(self):
self.read_buffer = b''
end_data = None
while end_data != self.recv_suffix:
self.read_buffer += await self.reader.read(self.recv_size)
size = len(self.read_buffer)
end_data = self.read_buffer[size-4:]
return self.read_buffer
async def async_send_read_from_socket(self, cmd):
current_attempt = 0
while current_attempt < self.cmd_attempts:
current_attempt += 1
self.writer.write(cmd.encode('utf-8'))
await self.writer.drain()
try:
return await asyncio.wait_for(self.async_read_from_socket(),
timeout=self.timeout)
except Exception as err:
pass
self.writer.close()
raise APSystemsInvalidData(f"Incomplete data from ECU after {current_attempt} attempts, cmd='{cmd.rstrip()}' data={self.read_buffer}")
async def async_query_ecu(self):
self.reader, self.writer = await asyncio.open_connection(self.ipaddr, self.port)
_LOGGER.info(f"Connected to {self.ipaddr} {self.port}")
cmd = self.ecu_query
self.ecu_raw_data = await self.async_send_read_from_socket(cmd)
self.process_ecu_data()
cmd = self.inverter_query_prefix + self.ecu_id + self.inverter_query_suffix
self.inverter_raw_data = await self.async_send_read_from_socket(cmd)
cmd = self.inverter_signal_prefix + self.ecu_id + self.inverter_signal_suffix
self.inverter_raw_signal = await self.async_send_read_from_socket(cmd)
self.writer.close()
data = self.process_inverter_data()
data["ecu_id"] = self.ecu_id
data["today_energy"] = self.today_energy
data["lifetime_energy"] = self.lifetime_energy
data["current_power"] = self.current_power
return(data)
def query_ecu(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.ipaddr,self.port))
sock.sendall(self.ecu_query.encode('utf-8'))
self.ecu_raw_data = sock.recv(self.recv_size)
self.process_ecu_data()
cmd = self.inverter_query_prefix + self.ecu_id + self.inverter_query_suffix
sock.sendall(cmd.encode('utf-8'))
self.inverter_raw_data = sock.recv(self.recv_size)
cmd = self.inverter_signal_prefix + self.ecu_id + self.inverter_signal_suffix
sock.sendall(cmd.encode('utf-8'))
self.inverter_raw_signal = sock.recv(self.recv_size)
sock.shutdown(socket.SHUT_RDWR)
sock.close()
data = self.process_inverter_data()
data["ecu_id"] = self.ecu_id
data["today_energy"] = self.today_energy
data["lifetime_energy"] = self.lifetime_energy
data["current_power"] = self.current_power
return(data)
def aps_int(self, codec, start):
try:
return int(binascii.b2a_hex(codec[(start):(start+2)]), 16)
except ValueError as err:
debugdata = binascii.b2a_hex(codec)
raise APSystemsInvalidData(f"Unable to convert binary to int location={start} data={debugdata}")
def aps_short(self, codec, start):
try:
return int(binascii.b2a_hex(codec[(start):(start+1)]), 8)
except ValueError as err:
debugdata = binascii.b2a_hex(codec)
raise APSystemsInvalidData(f"Unable to convert binary to short int location={start} data={debugdata}")
def aps_double(self, codec, start):
try:
return int (binascii.b2a_hex(codec[(start):(start+4)]), 16)
except ValueError as err:
debugdata = binascii.b2a_hex(codec)
raise APSystemsInvalidData(f"Unable to convert binary to double location={start} data={debugdata}")
def aps_bool(self, codec, start):
return bool(binascii.b2a_hex(codec[(start):(start+2)]))
def aps_uid(self, codec, start):
return str(binascii.b2a_hex(codec[(start):(start+12)]))[2:14]
def aps_str(self, codec, start, amount):
return str(codec[start:(start+amount)])[2:(amount+2)]
def aps_timestamp(self, codec, start, amount):
timestr=str(binascii.b2a_hex(codec[start:(start+amount)]))[2:(amount+2)]
return timestr[0:4]+"-"+timestr[4:6]+"-"+timestr[6:8]+" "+timestr[8:10]+":"+timestr[10:12]+":"+timestr[12:14]
def check_ecu_checksum(self, data, cmd):
datalen = len(data) - 1
try:
checksum = int(data[5:9])
except ValueError as err:
debugdata = binascii.b2a_hex(data)
raise APSystemsInvalidData(f"Error getting checksum int from '{cmd}' data={debugdata}")
if datalen != checksum:
debugdata = binascii.b2a_hex(data)
raise APSystemsInvalidData(f"Checksum on '{cmd}' failed checksum={checksum} datalen={datalen} data={debugdata}")
start_str = self.aps_str(data, 0, 3)
end_str = self.aps_str(data, len(data) - 4, 3)
if start_str != 'APS':
debugdata = binascii.b2a_hex(data)
raise APSystemsInvalidData(f"Result on '{cmd}' incorrect start signature '{start_str}' != APS data={debugdata}")
if end_str != 'END':
debugdata = binascii.b2a_hex(data)
raise APSystemsInvalidData(f"Result on '{cmd}' incorrect end signature '{end_str}' != END data={debugdata}")
return True
def process_ecu_data(self, data=None):
if not data:
data = self.ecu_raw_data
self.check_ecu_checksum(data, "ECU Query")
self.ecu_id = self.aps_str(data, 13, 12)
self.qty_of_inverters = self.aps_int(data, 46)
self.firmware = self.aps_str(data, 55, 15)
self.timezone = self.aps_str(data, 70, 9)
self.lifetime_energy = self.aps_double(data, 27) / 10
self.today_energy = self.aps_double(data, 35) / 100
self.current_power = self.aps_double(data, 31)
def process_signal_data(self, data=None):
signal_data = {}
if not data:
data = self.inverter_raw_signal
self.check_ecu_checksum(data, "Signal Query")
if not self.qty_of_inverters:
return signal_data
location = 15
for i in range(0, self.qty_of_inverters):
uid = self.aps_uid(data, location)
location += 6
strength = data[location]
location += 1
strength = int((strength / 255) * 100)
signal_data[uid] = strength
return signal_data
def process_inverter_data(self, data=None):
if not data:
data = self.inverter_raw_data
self.check_ecu_checksum(data, "Inverter data")
output = {}
timestamp = self.aps_timestamp(data, 19, 14)
inverter_qty = self.aps_int(data, 17)
self.last_update = timestamp
output["timestamp"] = timestamp
output["inverter_qty"] = inverter_qty
output["inverters"] = {}
# this is the start of the loop of inverters
location = self.inverter_byte_start
signal = self.process_signal_data()
inverters = {}
for i in range(0, inverter_qty):
inv={}
inverter_uid = self.aps_uid(data, location)
inv["uid"] = inverter_uid
location += 6
inv["online"] = self.aps_bool(data, location)
location += 1
inv["unknown"] = self.aps_str(data, location, 2)
location += 2
inv["frequency"] = self.aps_int(data, location) / 10
location += 2
inv["temperature"] = self.aps_int(data, location) - 100
location += 2
inv["signal"] = signal.get(inverter_uid, 0)
# the first 3 digits determine the type of inverter
inverter_type = inverter_uid[0:3]
if inverter_type in self.yc600_ids:
(channel_data, location) = self.process_yc600(data, location)
inv.update(channel_data)
elif inverter_type in self.qs1_ids:
(channel_data, location) = self.process_qs1(data, location)
inv.update(channel_data)
elif inverter_type in self.yc1000_ids:
(channel_data, location) = self.process_yc1000(data, location)
inv.update(channel_data)
elif inverter_type in self.ds3_ids:
(channel_data, location) = self.process_ds3(data, location)
inv.update(channel_data)
else:
raise APSystemsInvalidData(f"Unsupported inverter type {inverter_type}")
inverters[inverter_uid] = inv
output["inverters"] = inverters
return (output)
def process_yc1000(self, data, location):
power = []
voltages = []
power.append(self.aps_int(data, location))
location += 2
voltage = self.aps_int(data, location)
location += 2
power.append(self.aps_int(data, location))
location += 2
voltage = self.aps_int(data, location)
location += 2
power.append(self.aps_int(data, location))
location += 2
voltage = self.aps_int(data, location)
location += 2
power.append(self.aps_int(data, location))
location += 2
voltages.append(voltage)
output = {
"model" : "YC1000",
"channel_qty" : 4,
"power" : power,
"voltage" : voltages
}
return (output, location)
def process_qs1(self, data, location):
power = []
voltages = []
power.append(self.aps_int(data, location))
location += 2
voltage = self.aps_int(data, location)
location += 2
power.append(self.aps_int(data, location))
location += 2
power.append(self.aps_int(data, location))
location += 2
power.append(self.aps_int(data, location))
location += 2
voltages.append(voltage)
output = {
"model" : "QS1",
"channel_qty" : 4,
"power" : power,
"voltage" : voltages
}
return (output, location)
def process_yc600(self, data, location):
power = []
voltages = []
for i in range(0, 2):
power.append(self.aps_int(data, location))
location += 2
voltages.append(self.aps_int(data, location))
location += 2
output = {
"model" : "YC600",
"channel_qty" : 2,
"power" : power,
"voltage" : voltages,
}
return (output, location)
def process_ds3(self, data, location):
power = []
voltages = []
for i in range(0, 2):
power.append(self.aps_int(data, location))
location += 2
voltages.append(self.aps_int(data, location))
location += 2
output = {
"model" : "ds3",
"channel_qty" : 2,
"power" : power,
"voltage" : voltages,
}
return (output, location)
Hallo Björn,
Ik krijg onderstaande foutmeldingen.
Weet jij wat dat kan zijn?
File “/home/pi/domoticz/scripts/python/ECU-R/ECU-R.py”, line 23, in
data = loop.run_until_complete(ecu.async_query_ecu())
File “/usr/lib/python3.7/asyncio/base_events.py”, line 571, in run_until_complete
self.run_forever()
File “/usr/lib/python3.7/asyncio/base_events.py”, line 539, in run_forever
self._run_once()
File “/usr/lib/python3.7/asyncio/base_events.py”, line 1775, in _run_once
handle._run()
File “/usr/lib/python3.7/asyncio/events.py”, line 88, in _run
self._context.run(self._callback, *self._args)
File “/home/pi/domoticz/scripts/python/ECU-R/APSystemsECUR.py”, line 80, in async_read_from_socket
self.read_buffer += await self.reader.read(self.recv_size)
File “/usr/lib/python3.7/asyncio/streams.py”, line 644, in read
del self._buffer[:n]
Alvast bedankt.
Groetjes Jaden
Hoi Jaden,
Ik durf het niet te zeggen wat hiervan de oorzaak is. Geeft Google misschien wat duidelijkheid?
Groet,
Björn.
Dank voor het werk wat je er in hebt gestoken.
Ook ik kreeg de foutmelding dat “today_energy_kwh” niet gedefinieerd was.
Bij het goed doornemen van de code zag ik dat op regel 27 staat:
‘today_energy_kWh = str(data.get(‘today_energy’)*1000)’
In de rest van de code wordt “today_energy_kwh” gebruikt (met een kleine ‘w’)
Na het aanpassen van regel 27 werkt het als een trein.
Nogmaals bedankt!
Hoi Fred,
Dank voor het doorgeven en je oplettendheid. Heb het script aangepast.
Groet,
Björn
Hallo Björn,
Had even beter moeten lezen, maar het klopt toch dat de Raspberry geconnect moet worden met de AP van de ECU-R? De data ophalen via het lokale toegewezen ip-adres van mijn router, werkt zeker niet helaas!
Groet,
Albert
Hoi Albert,
Staat de ECU wel via op WiFi ingesteld? Het werkt niet indien deze via een ethernetkabel is ingesteld.
Hallo Björn,
Zou jij ook kunnen aangeven hoe je de plugin installeert, ik gebruik Domoticz en geen Home-Assistant.
Groet,
Albert
Hoi Albert,
Het is geen plug-in, maar een script wat op de achtergrond draait. Het script stuurt de data via JSON naar Domoticz.
Er dient alleen een APSystemsECUR plug-in te worden gedownload van de Github pagina. Ik zag dat de plug-in geüpdatet is en van naam is veranderd. Ik heb de file nu beschikbaar gesteld via mijn site.
Het bestand APSystemsECUR.py dient in dezelfde map te worden opgeslagen als het script welke hierboven staat.
Indien je verder nog vragen hebt hoor ik het graag.
Sorry voor deze late beantwoording, ik had je script inmiddels werkend gekregen. Gevonden op:
https://gathering.tweakers.net/forum/list_messages/2032302/3
Vanaf met plezier alles zien werken tot vanmiddag, toen is het gestopt, ik weet nog niet wat het probleem is maar ga weer zoeken. Iig bedankt voor de reactie en de energie die je erin stopt.
M.v.g. Willem
Hoi Willem,
Aan de zijkant van de ECU zit een knopje, meestal is het zo dat als je daar op drukt dat hij het weer doet.
Hoi Bjorn, ik krijg dezelfde foutmelding als Michael:
[ERROR] {NameError(“name ‘today_energy_kwh’ is not defined”)}
current_power: 202
Kun je misschien aangeven waar deze variabele gedfinieerd moet worden?
Bij mij draait Python 3.7 op de Pi.
Alvast hartelijk dank,
Willem
Hoi Willem,
Ik zag dat de variabele niet in het script stond. Ik heb deze nu toegevoegd.
str(data.get('lifetime_energy')*1000)
Laat maar weten of het gelukt is.
Groet,
Björn
Hoi Bjorn, Ik krijg dezelfde foutmelding als Michael.
[ERROR] {NameError(“name ‘today_energy_kwh’ is not defined”)}
Kun je ook aangeven waar deze variabele gedefinieerd zou moeten worden?
Al vast bedankt voor je hulp.
Met vriendelijke groet,
Willem
Hoi Björn,
Ik kan niet goed uit je tekst halen of dit nu gaat werken, of dat er nog eea aangepast moet worden in het script?
Gr
Hoi Sietse,
In principe zou het script moeten werken indien je 16 panelen hebt en 4 verschillende omvormers. Indien dit in jouw geval anders is, dient het script iets te worden aangepast. Kun je aangeven hoe de situatie in jouw geval is?
Hallo Bjorn,
Super bedankt voor je script !!
Ik denk dat ik alle stappen zoals in jouw voorbeeld genomen hebt, alleen blijf ik bij het uitvoeren toch nog een fout krijgen.
Heb jij enig idee waar ik het zoeken moet?
Bestaat today_energy_kwh bijvoorbeeld niet in de APS Ecu-R applicatie? of heb ik in Domoticz de virtuele schakkelaar verkeerd aangemaakt?
pi@Domo:~/domoticz/scripts/python/ECU-R $ python3 ECU-R.py
current_power: 354
[ERROR] {NameError(“name ‘today_energy_kwh’ is not defined”)}
current_power: 354
[ERROR] {NameError(“name ‘today_energy_kwh’ is not defined”)}
current_power: 354
[ERROR] {NameError(“name ‘today_energy_kwh’ is not defined”)}
current_power: 354
[ERROR] {NameError(“name ‘today_energy_kwh’ is not defined”)}
Al vast bedankt voor je hulp.
Gr
Michael
Hoi Michael,
Weet je zeker dat je python 3 gebruikt?
Een andere optie is misschien dat de variabele ‘today_energy_kwh’ niet (goed) is gedefinieerd.