AP System API down?

si tu tombe dans except c’est que ton problème est avant …

n’oublie pas d’encadrer tes code avec </> pour que ce soit plus lisible et pas déformé.

essai

ecu = APSystemsECUR(ecu_ip)
#try:
data = loop.run_until_complete(ecu.async_query_ecu())
print(data)
#except Exception as err:
#print(f"[ERROR] {err}")

pour avoir l’erreur en ssh

Voilà ce que ça donne :

root@Jeedom228:/var/www/html/plugins/script/data/APSystemPython# python /var/www/html/plugins/script/data/APSystemPython/Mesures.PY
Traceback (most recent call last):
  File "/var/www/html/plugins/script/data/APSystemPython/Mesures.PY", line 3, in <module>
    from APSystemsECUR import APSystemsECUR
  File "/var/www/html/plugins/script/data/APSystemPython/APSystemsECUR.py", line 54
    async def async_read_from_socket(self):
    ^
IndentationError: unexpected indent

#!/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.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)    

            else:
                raise APSystemsInvalidData(f"Unsupported inverter type {inverter_type}")

            inverters[inverter_uid] = inv

        output["inverters"] = inverters
        return (output)

    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)



Salut @rennais35000

Pour le python chez moi le code fonctionne bien avec python3.
Effectivement ca ne fonctionne pas avec python2 mais du fait de « #!/usr/bin/env python3 » ca devrait fonctionner avec python3 automatiquement.
Pour les inverters « [ERROR] Unsupported inverter type 502. » la ca risque d’être plus compliqué.
Tu as quoi comme inverter?

OvO
OvO

Salut, C’est quoi cet inverter ?
Cordialement
Heddy

Un onduleur

ah ok, merci olive :smiley:
je ne comprenais pas la question car comme je ne vais pas jusqu’à charger le json, ils ne sont théoriquement pas concerné.
@ovomaltin j’ai des Inverter YC1000 qui sont compatible à priori.
@otello86 a semble t’il réussi à faire fonctionner ton script, j’aimerais bien avoir son retour pour voir s’il a eu des difficultés.
Je bloque bien sur la version de python puisque si je ne modifie pas print(f"[ERROR] {err}") en
print("[ERROR] {}.".format(err)) je ne passe pas l’étape du Mesures.PY
Idem dans le APSystemsECUR.py ou il y a des print(f"[ERROR] {err}") que je dois modifier pour avancer et ensuite ça bloque à async def async_read_from_socket(self):
^
SyntaxError: invalid syntax

Hello,

je viens d’arriver à faire fonctionner ceci … j’ai repris le python test.py et j’ai un peu customisé pour récupérer du json, du vrai :

#!/usr/bin/env python3

from APSystemsECUR import APSystemsECUR
import time
import asyncio
import json
from pprint import pprint

ecu_ip = "192.168.1.148"
sleep = 60

loop = asyncio.get_event_loop()
ecu = APSystemsECUR(ecu_ip)

try:
    data = loop.run_until_complete(ecu.async_query_ecu())
    with open('/mnt/Syno/Jeedom/production/current.json', 'w') as f:
        f.write(json.dumps(data))
except Exception as err:
   print(f"[ERROR] {err}")

Ensuite sous jeedom j’ai parsé le json pour alimenter une zone puissance dans un virtuel :

// création du fichier de données

$destFile="/mnt/Syno/Jeedom/production/current.json";
shell_exec ("rm $destFile");
//$scenario->setLog("Création du fichier pour la date $date");
shell_exec ("python3 /home/dromobile/apsystems_ecur/getData.py");
> 
//Récupération des données du json créé
$json = file_get_contents($destFile);
//$scenario->setLog("Taille ".sizeof($json) .$json);
$arr = json_decode($json, true);
$error=json_last_error();

$scenario->setLog("Error ". $error);
// récupération des colonnes à traiter (time et power)
$time  = $arr["timestamp"];
$power = $arr["current_power"];
//$inverters = $arr["invverters"];

$scenario->setLog("power : " . $power);

cmd::byString("#[Technique][APSystem][PowerApi]#")->event($power);
cmd::byString("#[Technique][APSystem][Last]#")->event($power);
cmd::byString("#[Technique][APSystem][LastMaj]#")->event($time);

et ça le fait depuis 12h45 ce jour :

Bonjour,
Pas de chance (ou de talent) moi avec python :thinking:
j’ai fait ça :

#!/usr/bin/env python3
from APSystemsECUR import APSystemsECUR
import time
import asyncio
import json
from pprint import pprint

ecu_ip = "192.168.1.10"
sleep = 60

loop = asyncio.get_event_loop()
ecu = APSystemsECUR(ecu_ip)

 try:
    data = loop.run_until_complete(ecu.async_query_ecu())
    with open(’/var/www/html/plugins/script/data/APSystemPython/current.json’, ‹ w ›) as f:
        f.write(json.dumps(data))
except Exception as err:
    print(f"[ERROR] {err}")

Et j’ai de suite cette erreur :

root@Jeedom228:/var/www/html/plugins/script/data# python3 test.py
  File "test.py", line 3
    from APSystemsECUR import APSystemsECUR
    ^
IndentationError: unexpected indent

Ben c"'est tout simple
les identations compte en python
cela fait partie de la syntaxe
et le décalage en marge dés le début ne devrait pas être la …

ça devrait être comme ça

#!/usr/bin/env python3
from APSystemsECUR import APSystemsECUR
import time
import asyncio
import json
..........

Une petite vidéo pour bien voir comment ça fonctionne.

Ah ben merci, j’avais toujours pensé que c’était pour faciliter la lecture.
Donc ça va plus loin, maintenant j’ai :

 with open(’/var/www/html/plugins/script/data/APSystemPython/current.json’, ‹ w ›) as f:
              ^
SyntaxError: invalid character in identifier

essai avec des " ou des'

with open('/var/www/html/plugins/script/data/APSystemPython/current.json', 'w') as f:

Je sais pas ou tu a été piocher ton code mais < W > …

Capture d’écran du 2021-04-27 19-39-40

juste au dessus dans le post de @Bandenabos, il a fait un copié collé qui modifie le code de base.
Du coup quand on est pas développeur … je n’ai que ma bonne volonté et ta patience :slight_smile:
Avec le double cote ça va mieux.
J’en suis à :
except Exception as err:
^
SyntaxError: invalid syntax

le problème c’est que @Bandenabos n’a pas utilisé la balise </>
community a déformer le message initial
et toi tu à recopier …

je vous fait pas un dessin de pourquoi utiliser </> pour mettre du code les gars …

voilà j’ai éditer son post ça devrait aller mieux comme ça !

je ne garantie en rien que ce code fonctionne …

j’ai juste refait de la remise en forme.

oui tu m’avais fait avec raison le reproche une fois de ne pas utiliser les </> et maintenant j’y fais attention.
Dans le code que tu viens de corriger sur son post, j’ai testé :

 File "test.py", line 18
    f.write(json.dumps(data))
    ^
IndentationError: expected an indented block

remet 4 espaces devant le
f.write(json.dumps(data))

j’ai corrigé audessus , la l’identation est justifié puisque : a la fin de la ligne d’avant.

Ok j’avais fait la correction de mon coté. Je comprend un peu quand même :slight_smile:
Du coup ça marche en tout cas à ce niveau.
Maintenant j’ai un message me disant qu’il n’arrive pas à se connecter à mon ECU-R mais bon je vais donc chercher pourquoi.
Je te remercie de ce cours particulier, on est pas tous au même niveau malheureusement. J’ai mis tard le pied à l’étrier de jeedom et j’y passe beaucoup de temps pour compenser mon manque de compétence en codage. Pas toujours avec succès donc parfois je contourne un pb pour ne pas rester bloqué.
Bonne soirée à toi.
Heddy

1 « J'aime »

Oups, désolé c’est vrai que j’ai merdouillé, pas encore assez l’habitude …

et franchement python c’est merdique pour quelqu’un comme moi qui vient du monde Java, ces histoires d’indentations ça prend la tête !

Pour l’ECU-R faut bien qu’il soit connecté en wifi et pas en ethernet sur ton réseau (et avec ton IP) … moi j’avais ce problème et du moment que je l’ai passé en wifi ça fonctionne …

Ne pas hésiter si besoin !

Je vais me pencher maintenant sur la récupération des infos onduleurs / panneaux, ça peut être intéressant de checker la santé de tout ça pour identifier un pb. J’ai eu un panneau qui a merdé et c’est l’application apsema qui m’a permis de voir qu’il y avait un problème.
Une fois les infos dans jeedom je pourrais déclencher des alertes si je rencontre des écarts.

Je posterai tout ça (proprement ce coup ci) pour faire partager ma galère d’implémentation en php sous jeedom !

DaN

Hello,
Ah oui toutes les infos de panneaux etc … ce sera intéressant pour les alertes et ne nous en cachons pas pour le fun :slight_smile:
Pour le moment j’ai toujours connexion impossible. Pourtant je suis bien en wifi.
Normalement je suis sur de mon Ip :tipping_hand_man:
Je vais essayer de relancer l’installation de l’ecu-r avec ECUAPP pour voir.

Ton ECU-R a bien le voyant « cloud » qui est allumé (celui avec un nuage et une flèche) ?
Tu as bien les infos qui remontent sur l’application apsema web ?