APsystems ECU-C - Domoticz

Forum pour tous les autres objets : sondes météo, capteurs, actionneurs ...
Vous avez un besoin mais vous ne savez pas quel matériel choisir ? C'est ici.
Jo30
Messages : 5
Inscription : 02 nov. 2023, 10:53

Re: APsystems ECU-C - Domoticz

Message par Jo30 »

max63 a écrit : 07 juin 2023, 08:22 Bonjour,

J'utilise bien domoticz au quotidien et le script utilisé est un fork de la version pour HA mais le module APSystemsECU est commun.
L'originale du script venant du HA j'ai préféré poster sur le github ayant le plus d'activité pour avoir de l'aide.

D'ailleurs tu peux voir dans le message de log d'erreur que j'utilise bien domoticz :)

File "/k8s_storage/domoticz-config-pvc-43977a86-d8db-4206-94e9-fce6d4179d97/scripts/python/ECU/APSystemsECU.py", line 93, in async_read_from_socket
raise APSystemsInvalidData(error)
APSystemsECU.APSystemsInvalidData: End suffix (b'END\n') missing from ECU response end_data=b'1\x00\x00\n' data=b'APS110095000121630005826801\x00\x00\n'
Bonjour,

Je note le même problème depuis une mise à jour de mon ECU_B.

Je n'ai plus de remontée des informations depuis le 27/10/2023.
Sur Domoticz, la dernière information reçue concernant la version indique ECU_B_1.2.26E en date du 27/10/2023.

Aujourd'hui, ayant le temps de me pencher sur la question de cette non remontée des données, je m'aperçois en lançant la commande "nc 192.168.0.110 8899" directement sur mon RaspBerry PI que l'ECU_B répond à la commande "APS1100160001END" (+Entrée) par une réponse sur plusieurs lignes:

pi@DomoticzPi:~/domoticz/scripts/python/ECU $ nc -v 192.168.0.110 8899
Connection to 192.168.0.110 8899 port [tcp/*] succeeded!
APS1100160001END
APS11009500012xxxxxxxxxxxxx
g▒▒▒▒▒▒▒10013ECU_B_1.2.27B009Etc/GMT-8▒▒ ▒END


On voit que la version retournée est 1.2.27B donc la version a changé.
Le code dans APSystemsECU.py utilise "Readline" et retourne donc que la 1ere ligne qui ne se finit pas par "END" comme attendu.

Je continue mon diagnostic...
Pièces jointes
Résultat de la commande APS1100160001END
Résultat de la commande APS1100160001END
Resultat commande APS1100160001END.png (16.48 Kio) Consulté 1998 fois
Dernière version de l'ECU_B reçue
Dernière version de l'ECU_B reçue
ECU_B version avant 27102023.png (9.51 Kio) Consulté 2271 fois
Dernière modification par Jo30 le 10 nov. 2023, 22:40, modifié 1 fois.
grostoto28
Messages : 24
Inscription : 01 nov. 2023, 13:12

Re: APsystems ECU-C - Domoticz

Message par grostoto28 »

mannewman a écrit : 02 nov. 2023, 06:28 As tu vérifié dans Domoticz Configuration /Paramètres/Sécurité le champ "Réseaux de confiance" ?
Il doit contenir l'adresse IP de ton ECU et du Pi , moi j'ai mis une étoile : 192.168.1.*
Je te remercie chaleureusement, cela fonctionne maintenant.

me reste plus que les données inverter1 panneau 1 et panneau 2 qui ne sont pas remonté.
je vais étudier ce détail.

encore merci.

Quand je le fais dans la console, j'obtiens ceci, on voit qu'il ne remonte qu'un seul panneau, je comprend le pb, mais je ne sais pas le résoudre..
ps; j'utilise le code de doudou14

Code : Tout sélectionner

user@domoticz:~ $ python3 /home/user/domoticz/scripts/python/ECU/ECU-B.py
Today energy : 930.0 Wh
Current power: 112 W
Total energy : 2700.0 Wh
http://192.168.1.82:8080/json.htm?type=command&param=udevice&nvalue=0&idx=17707&svalue=112%3B2700.0;0
Number inverter: 1
Inverter Id: 7030xxxxxxxx
Frequency: 50.0 Hz
Signal: 84 %
Temperature: 17 °C
Voltage inverter 1 panel 1: 238 V
Power inverter 1 panel 1: 61 W
Traceback (most recent call last):
  File "/home/user/domoticz/scripts/python/ECU/ECU-B.py", line 97, in <module>
    if InverterOnline == True :
NameError: name 'InverterOnline' is not defined
edit:

j'ai rajouté cette ligne de code:
'InverterOnline = True' (dans la partie 'change your idx", la ou sont défini les variables)
mais dans l'absolue, je ne comprend pas bien à quoi sert cette variable, ni comment le script devrait la définir automatiquement.. j'ai l'impression qu'il manque un truc.
en tout cas, cela me remonte maintenant bien les infos de 2 panneaux solaires dans domoticz.

et j'obtiens ceci:

Code : Tout sélectionner

user@domoticz:~ $ python3 /home/user/domoticz/scripts/python/ECU/ECU-B.py
Today energy : 990.0 Wh
Current power: 89 W
Total energy : 2800.0 Wh
http://192.168.1.82:8080/json.htm?type=command&param=udevice&nvalue=0&idx=17707&svalue=89%3B2800.0;0
Number inverter: 1
Inverter Id: 70300xxxxxxx
Frequency: 49.9 Hz
Signal: 85 %
Temperature: 17 °C
Voltage inverter 1 panel 1: 238 V
Power inverter 1 panel 1: 48 W
Power inverter 1 panel 2: 41 W
Dernière modification par grostoto28 le 02 nov. 2023, 18:07, modifié 1 fois.
grostoto28
Messages : 24
Inscription : 01 nov. 2023, 13:12

Re: APsystems ECU-C - Domoticz

Message par grostoto28 »

Pb résolu..
pour quelqu'un qui n'y connait rien, je me débrouille pas mal...

en comparant les codes de Doudou14 et de Bjorn,
j'en arrive à la conclusion qu'il manque ces 2 lignes

Code : Tout sélectionner

InverterOnline = data['inverters'][Inverter]['online']
   print('Online: ' + str(InverterOnline))
que j'ai donc ajouté dans la fonction '# loop trough all inverters and get the data', ce qui donne, pour cette fonction le code suivant:

Code : Tout sélectionner

for i in range(Inverter_qty):
   Inverter = list(inverters.keys())[i]
   print('Inverter Id: ' + Inverter)
   InverterOnline = data['inverters'][Inverter]['online']
   print('Online: ' + str(InverterOnline))
   InverterFrequency = data['inverters'][Inverter]['frequency']
   print('Frequency: ' + str(InverterFrequency) + ' Hz')
   InverterSignal = data['inverters'][Inverter]['signal']
   print('Signal: ' + str(InverterSignal) + ' %')
   InverterTemperature = data['inverters'][Inverter]['temperature']
   print('Temperature: ' + str(InverterTemperature) + ' °C')
   nPower = len(data['inverters'][Inverter]['power'])
   nVoltage = len(data['inverters'][Inverter]['voltage'])
   voltage = data['inverters'][Inverter]['voltage'][0]
   print('Voltage inverter ' + str(i + 1) + ' panel ' + str(1) + ': ' + str(voltage) + ' V')
   for x in range(nPower):
      power = data['inverters'][Inverter]['power'][x]
      print('Power inverter ' + str(i + 1) + ' panel ' + str(x + 1) + ': ' + str(power) + ' W')
et l'a j'obtiens bien l'effet recherché..:

Code : Tout sélectionner

user@domoticz:~ $ python3 /home/user/domoticz/scripts/python/ECU/ECU-B.py
Today energy : 1030.0 Wh
Current power: 0 W
Total energy : 2800.0 Wh
http://192.168.1.82:8080/json.htm?type=command&param=udevice&nvalue=0&idx=17707&svalue=0%3B2800.0;0
Number inverter: 1
Inverter Id: 703001xxxxxxx
Online: False
Frequency: 0.0 Hz
Signal: 0 %
Temperature: 0 °C
Voltage inverter 1 panel 1: 0 V
Power inverter 1 panel 1: 0 W
Power inverter 1 panel 2: 0 W
plus qu'à savoir si tout va fonctionner quand le mettrai le second onduleur en fonctionnement.
j'espère avoir aider quelqu'un d'autre en détaillant mon travail.
mannewman
Messages : 28
Inscription : 25 mai 2019, 11:43

Re: APsystems ECU-C - Domoticz

Message par mannewman »

Salut Jo30

Si Domoticz décroche, tu débranches électriquement la passerelle autant de fois qu'il décroche et tout devrait rentrer dans l' ordre au bout de qq jours.
J'ai eu la même mise à jour APSytem que toi et çà a refonctionné :D
Bonne soirée
Jo30
Messages : 5
Inscription : 02 nov. 2023, 10:53

Re: APsystems ECU-C - Domoticz

Message par Jo30 »

Salut mannewman,

Merci pour ta réponse.
J'ai redémarré plusieurs fois l'ECU-B et mon Raspberry sans succès.

Peux tu me dire ce que donne les commandes
nc -v 192.168.xxx.yyy 8899
Puis
APS1100160001END
Sur ton poste (Raspberry?) hébergeant Domoticz ?
Peux tu partager tes fichiers .py ?

Merci
mannewman
Messages : 28
Inscription : 25 mai 2019, 11:43

Re: APsystems ECU-C - Domoticz

Message par mannewman »

Ca donne çà chez moi :

pi@raspberrypi:~/domoticz/scripts/python/ECU $ nc -v 192.168.XXX.YYY 8899
Connection to 192.168.XXX.YYY 8899 port [tcp/*] succeeded!
APS1100160001END
APS11009500012163001107260118▒▒▒▒▒▒▒10013ECU_B_1.2.27B009Etc/GMT-8▒▒
▒END

et le fichier Python de configuration :

Code : Tout sélectionner

#!/usr/bin/env python3
 
from APSystemsECU import APSystemsECU
import time
import asyncio
import urllib.request
import urllib.parse
import urllib
from pprint import pprint
 
#////////// START USER CONFIGURATION \\\\\\\\\\

#Change your ECU IP
ecu_ip = "192.168.XXX.YYY"

#Change your Domoticz IP
url = 'http://192.168.XXX.YYY:ZZZZ/json.htm?'

#Change your idx
Timestamp = 4246
SolarGeneration = 4240
SwitchInverter1 = 4241
SwitchInverter2 = 0
ConsumptionPannel1 = 4243  #Inverter 1
ConsumptionPannel2 = 4244  #Inverter 1
ConsumptionPannel3 = 0  #Inverter 1 or 2
ConsumptionPannel4 = 0  #Inverter 1 or 2
TemperatureInverter1 = 4245
TemperatureInverter2 = 0
SignalInverter1 = 4248
SignalInverter2 = 0
VoltageInverter1 = 4249
VoltageInverter2 = 0
FrequencyInverter1 = 4250
FrequencyInverter2 = 0

#\\\\\\\\\\ END USER CONFIGURATION //////////

#Communication delay to ECU (sec)

puntcomma = '\u003B'
loop = asyncio.get_event_loop()
ecu = APSystemsECU(ecu_ip)

data = loop.run_until_complete(ecu.async_query_ecu())
#pprint(data)
lifetime_energy = str(data.get('lifetime_energy')*1000)
today_energy = str(data.get('today_energy')*1000)
print('Today energy : ' + today_energy + ' Wh')
current_power = str(data.get('current_power'))
print('Current power: ' + current_power + ' W')
generated_energy = (current_power + puntcomma + lifetime_energy)
print('Total energy : ' + lifetime_energy + ' Wh')
if (float(today_energy) >= 0 or float(current_power) >= 0):
   getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': SolarGeneration, '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': Timestamp, 'svalue': data.get('timestamp') + ' / ' + data.get('ecu_firmware')}
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('Number inverter: ' + str(Inverter_qty))
# loop trough all inverters and get the data
for i in range(Inverter_qty):
   Inverter = list(inverters.keys())[i]
   print('Inverter Id: ' + Inverter)
   InverterOnline = data['inverters'][Inverter]['online']
   print('Online: ' + str(InverterOnline))
   InverterFrequency = data['inverters'][Inverter]['frequency']
   print('Frequency: ' + str(InverterFrequency) + ' Hz')
   InverterSignal = data['inverters'][Inverter]['signal']
   print('Signal: ' + str(InverterSignal) + ' %')
   InverterTemperature = data['inverters'][Inverter]['temperature']
   print('Temperature: ' + str(InverterTemperature) + ' °C')
   nPower = len(data['inverters'][Inverter]['power'])
   nVoltage = len(data['inverters'][Inverter]['voltage'])
   voltage = data['inverters'][Inverter]['voltage'][0]
   print('Voltage inverter ' + str(i + 1) + ' panel ' + str(1) + ': ' + str(voltage) + ' V')
   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 for inverter 1
      if (i == 0) and (x == 0) :
         if (float(InverterTemperature) > 0):
            getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': TemperatureInverter1, 'svalue': InverterTemperature}
            webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         if (float(InverterFrequency) > 0):
            getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': FrequencyInverter1, 'svalue': InverterFrequency}
            webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': SignalInverter1, 'svalue': InverterSignal}
         webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         if (float(voltage) > 0):
            getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': VoltageInverter1, 'svalue': (voltage)}
            webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
         if InverterOnline == True :
            getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': SwitchInverter1, 'switchcmd': 'On'}
         else :
            getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': SwitchInverter1, 'switchcmd': 'Off'}
         webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': ConsumptionPannel1, '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': ConsumptionPannel2, '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': ConsumptionPannel3, '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': ConsumptionPannel4, 'svalue': (power)}
         webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')

     #upload values to Domoticz for inverter 2
      if (i == 1) and (x == 0) :
         if (float(InverterTemperature) > 0):
            getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': TemperatureInverter2, 'svalue': InverterTemperature}
            webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         if (float(InverterFrequency) > 0):
            getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': FrequencyInverter2, 'svalue': InverterFrequency}
            webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': SignalInverter2, 'svalue': InverterSignal}
         webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         if (float(voltage) > 0):
            getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': VoltageInverter2, 'svalue': (voltage)}
            webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
         if InverterOnline == True :
            getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': SwitchInverter2, 'switchcmd': 'On'}
         else :
            getVars = {'type' : 'command', 'param' : 'switchlight', 'idx': SwitchInverter2, 'switchcmd': 'Off'}
         webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars))
         getVars = {'type' : 'command', 'param' : 'udevice', 'nvalue' : 0, 'idx': ConsumptionPannel3, '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': ConsumptionPannel4, 'svalue': (power)}
         webUrl = urllib.request.urlopen(url + urllib.parse.urlencode(getVars) + (puntcomma) + '0')
Jo30
Messages : 5
Inscription : 02 nov. 2023, 10:53

Re: APsystems ECU-C - Domoticz

Message par Jo30 »

Merci mannewman,

Le résultat de la commande chez toi ressemble bien à mon résultat avec des retours à la ligne.

Je vais adapter le fichier fourni à ma configuration et tester.
Peux-tu me partager également ton fichier APSystemsECU.py stp ?
Je pense que mon problème vient de ce fichier.

Bonne journée
mannewman
Messages : 28
Inscription : 25 mai 2019, 11:43

Re: APsystems ECU-C - Domoticz

Message par mannewman »

Et voila :
Bon week end :roll:

Code : Tout sélectionner

#!/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 APSystemsInvalidInverter(Exception):
    pass


class APSystemsECU:

    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 = 1024

        # how long to wait between socket open/closes
        self.socket_sleep_time = 2.0
        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.ecu_firmware = None
        self.qty_of_inverters = 0
        self.qty_of_online_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.vsl = 0
        self.tsl = 0

        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

        self.socket_open = False

        self.errors = []

    async def async_read_from_socket(self):
        self.read_buffer = b''
        end_data = None

        self.read_buffer = await self.reader.readline()
        if self.read_buffer == b'':
            error = f"Got empty string from socket"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        size = len(self.read_buffer)
        end_data = self.read_buffer[size-4:]
        if end_data != self.recv_suffix:
            error = f"End suffix ({self.recv_suffix}) missing from ECU response end_data={end_data} data={self.read_buffer}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        return self.read_buffer

    async def async_send_read_from_socket(self, cmd):
        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 asyncio.TimeoutError as err:
            await self.async_close_socket()
            msg = "Timeout after {self.timeout}s waiting or ECU data cmd={cmd.rstrip()}. Closing socket."
            self.add_error(msg)
            raise APSystemsInvalidData(error)

    async def async_close_socket(self):
        if self.socket_open:
            self.writer.close()
            await self.writer.wait_closed()
            self.socket_open = False

    async def async_open_socket(self):
        _LOGGER.debug(f"Connecting to ECU on {self.ipaddr} {self.port}")
        self.reader, self.writer = await asyncio.open_connection(self.ipaddr, self.port)
        _LOGGER.debug(f"Connected to ECU {self.ipaddr} {self.port}")
        self.socket_open = True


    async def async_query_ecu(self):
        await self.async_open_socket()
        cmd = self.ecu_query
        self.ecu_raw_data = await self.async_send_read_from_socket(cmd)
        await self.async_close_socket()
        self.process_ecu_data()
        if self.lifetime_energy == 0:
            await self.async_close_socket()
            error = f"ECU returned 0 for lifetime energy, raw data={self.ecu_raw_data}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        # the ECU likes the socket to be closed and re-opened between commands
        await asyncio.sleep(self.socket_sleep_time) 
        await self.async_open_socket()
        cmd = self.inverter_query_prefix + self.ecu_id + self.inverter_query_suffix
        self.inverter_raw_data = await self.async_send_read_from_socket(cmd)
        await self.async_close_socket()

        # the ECU likes the socket to be closed and re-opened between commands
        await asyncio.sleep(self.socket_sleep_time)  
        await self.async_open_socket()
        cmd = self.inverter_signal_prefix + self.ecu_id + self.inverter_signal_suffix
        self.inverter_raw_signal = await self.async_send_read_from_socket(cmd)

        await self.async_close_socket()
        data = self.process_inverter_data()
        data["ecu_id"] = self.ecu_id
        data["ecu_firmware"] = self.firmware
        data["today_energy"] = self.today_energy
        data["lifetime_energy"] = self.lifetime_energy
        data["current_power"] = self.current_power
        data["qty_of_inverters"] = self.qty_of_inverters
        data["qty_of_online_inverters"] = self.qty_of_online_inverters
        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)
            error = f"Unable to convert binary to int location={start} data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)
 
    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)
            error = f"Unable to convert binary to short int location={start} data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

    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)
            error = f"Unable to convert binary to double location={start} data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)
    
    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)
            error = f"Error getting checksum int from '{cmd}' data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        if datalen != checksum:
            debugdata = binascii.b2a_hex(data)
            error = f"Checksum on '{cmd}' failed checksum={checksum} datalen={datalen} data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        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)
            error = f"Result on '{cmd}' incorrect start signature '{start_str}' != APS data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        if end_str != 'END':
            debugdata = binascii.b2a_hex(data)
            error = f"Result on '{cmd}' incorrect end signature '{end_str}' != END data={debugdata}"
            self.add_error(error)
            raise APSystemsInvalidData(error)

        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.lifetime_energy = self.aps_double(data, 27) / 10
        self.current_power = self.aps_double(data, 31)
        self.today_energy = self.aps_double(data, 35) / 100
        if self.aps_str(data,25,2) == "01":
            self.qty_of_inverters = self.aps_int(data, 46)
            self.qty_of_online_inverters = self.aps_int(data, 48)
            self.vsl = int(self.aps_str(data, 52, 3))
            self.firmware = self.aps_str(data, 55, self.vsl)
            self.tsl = int(self.aps_str(data, 55 + self.vsl, 3))
            self.timezone = self.aps_str(data, 58 + self.vsl, self.tsl)
        elif self.aps_str(data,25,2) == "02":
            self.qty_of_inverters = self.aps_int(data, 39)
            self.qty_of_online_inverters = self.aps_int(data, 41)
            self.vsl = int(self.aps_str(data, 49, 3))
            self.firmware = self.aps_str(data, 52, self.vsl)

    def process_signal_data(self, data=None):
        signal_data = {}
        if self.inverter_raw_signal != '' and (self.aps_str(self.inverter_raw_signal,9,4)) == '0030':
            data = self.inverter_raw_signal
            _LOGGER.debug(binascii.b2a_hex(data))
            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
        istr = ''
        cnt2 = self.inverter_byte_start
        signal = self.process_signal_data()
        inverters = {}
        
        for i in range(0, inverter_qty):
            inv={}
            inverter_uid = self.aps_uid(data, cnt2)
            inv["uid"] = inverter_uid
            inv["online"] = bool(self.aps_short(data, cnt2 + 6))
            istr = self.aps_str(data, cnt2 + 7, 2)
            inv["signal"] = signal.get(inverter_uid, 0)
            inv["frequency"] = self.aps_int(data, cnt2 + 9) / 10
            inv["temperature"] = self.aps_int(data, cnt2 + 11) - 100
            if istr == '01' or istr == '04':
                (channel_data, cnt2) = self.process_yc600_ds3(data, cnt2)
                inv.update(channel_data)    
            elif istr == '02':
                (channel_data, cnt2) = self.process_yc1000(data, cnt2)
                inv.update(channel_data)
            elif istr == '03':
                (channel_data, cnt2) = self.process_qs1(data, cnt2)
                inv.update(channel_data)
            else:
                error = f"Unsupported inverter type {inverter_type} please create GitHub issue."
                self.add_error(error)
                raise APSystemsInvalidData(error)
            inverters[inverter_uid] = inv
        self.inverters = inverters
        output["inverters"] = inverters
        return (output)
    
    def process_yc1000(self, data, cnt2):
        power = []
        voltages = []
        power.append(self.aps_int(data, cnt2 + 13))
        voltages.append(self.aps_int(data, cnt2 + 15))
        power.append(self.aps_int(data, cnt2 + 17))
        voltages.append(self.aps_int(data, cnt2 + 19))
        power.append(self.aps_int(data, cnt2 + 21))
        voltages.append(self.aps_int(data, cnt2 + 23))
        power.append(self.aps_int(data, cnt2 + 25))
        output = {
            "model" : "YC1000",
            "channel_qty" : 4,
            "power" : power,
            "voltage" : voltages
        }
        return (output, cnt2)

    def process_qs1(self, data, cnt2):
        power = []
        voltages = []
        power.append(self.aps_int(data, cnt2 + 13))
        voltages.append(self.aps_int(data, cnt2 + 15))
        power.append(self.aps_int(data, cnt2 + 17))
        power.append(self.aps_int(data, cnt2 + 19))
        power.append(self.aps_int(data, cnt2 + 21))
        output = {
            "model" : "QS1",
            "channel_qty" : 4,
            "power" : power,
            "voltage" : voltages
        }
        return (output, cnt2)

    def process_yc600_ds3(self, data, cnt2):
        power = []
        voltages = []
        power.append(self.aps_int(data, cnt2 + 13))
        voltages.append(self.aps_int(data, cnt2 + 15))
        power.append(self.aps_int(data, cnt2 + 17))
        voltages.append(self.aps_int(data, cnt2 + 19))
        output = {
            "model" : "YC60/DS3-D-L",
            "channel_qty" : 2,
            "power" : power,
            "voltage" : voltages,
        }
        return (output, cnt2)

    def add_error(self, error):
        timestamp = datetime.datetime.now()

        self.errors.append("[{timestamp}] {error}")
grostoto28
Messages : 24
Inscription : 01 nov. 2023, 13:12

Re: APsystems ECU-C - Domoticz

Message par grostoto28 »

Aller je relance le sujet...

maintenant que tout fonctionne, il manque un truc..
- j'ai l'affichage de la puissance par panneau
- je n'ai pas le cumul de la puissance par panneau (cela reste à zero).

Comment faire pour que domoticz calcule ce cumul tout au long de la journée ?
Jo30
Messages : 5
Inscription : 02 nov. 2023, 10:53

Re: APsystems ECU-C - Domoticz

Message par Jo30 »

mannewman a écrit : 03 nov. 2023, 18:19 Et voila :
Bon week end :roll:
Bonsoir mannewman,

J'ai enfin pu tester avec les codes fournis et adaptés.
Cela ne change absolument rien et les redémarrages de l'ECU_B non plus.

La commande APS1100160001END donne cela chez toi:
mannewman a écrit : 03 nov. 2023, 08:32 Ca donne çà chez moi :

pi@raspberrypi:~/domoticz/scripts/python/ECU $ nc -v 192.168.XXX.YYY 8899
Connection to 192.168.XXX.YYY 8899 port [tcp/*] succeeded!
APS1100160001END
APS11009500012xxxxxxxxxxxxxx▒▒▒▒▒▒▒10013ECU_B_1.2.27B009Etc/GMT-8▒▒
▒END
Dans mon cas:
pi@DomoticzPi:~ $ nc -v 192.168.0.110 8899
Connection to 192.168.0.110 8899 port [tcp/*] succeeded!
APS1100160001END
APS11009500012xxxxxxxxxxxxxx
▒▒▒▒▒▒▒▒10013ECU_B_1.2.27B009Etc/GMT-8▒▒ ▒END
J'ai une réponse différente de la tienne.
Pour toi la réponse en 1ere ligne contient toutes les informations puis la 2nde ligne se termine par le END.
Dans mon cas les 2 lignes contiennent des informations.
Or, une seule ligne est attendue en ligne 82 du code APSystemsECU.py : self.read_buffer = await self.reader.readline().

Et si j'ajoute un self.read_buffer = await self.reader.readline() c'est par la suite que ça ne passe pas : pour le Checksum.


A voir si d'autres ont le même comportement et/ou s'il existe une modification du fichier APSystemsECU.py qui traite ce cas.

Merci bien et bon weekend.
Pièces jointes
Résultats commande et script
Résultats commande et script
Resultats commande et script - 10112023.png (46.24 Kio) Consulté 1994 fois
Répondre