
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.
Inhoudsopgave
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 = 'http://<DOMOTICZ-IP>: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))
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 temperature 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) and (InverterTemperature != 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 (InverterTemperature == 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 temperature 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 (InverterTemperature != 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 (InverterTemperature == 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 temperature 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 (InverterTemperature != 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 (InverterTemperature == 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 temperature 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 (InverterTemperature != 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 (InverterTemperature == 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.
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)
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.
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?
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 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
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.
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.
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.
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,
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.
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)
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)
Waiting patiently for you to come home and fuck me! http://bitly.ws/znHX