AP System API down?

Oui tout est bien allumé et j’ai bien toutes mes remontées sur la page web https://apsystemsema.com
J’ai relancé la configuration de l’ecu en appuyant sur son bouton. Je me suis connecté sur son wifi pour vérifier un peu tout ce que je pouvais. Tout m’a l’air normal.
L’ip quand on est connecté sur son wifi n’est bien sur pas celle qui est vu de mon réseau maison.
Bon maintenant quand je lance en ssh j’ai un message :
heddy@Jeedom228:/var/www/html/plugins/script/data$ python3 test.py
[ERROR] Unsupported inverter type 502.
Pourtant mes inverteurs sont des YC1000 à priori supporté par le APSystemsECUR.py en tout cas ils sont traités dedans.
Bref je ne comprend pas :tipping_hand_man:

Quand je Sauvegarde le script dans le plugin script de Jeedom, j’ai un beau bandeau rouge.

Erreur sur python /var/www/html/plugins/script/data/test.py 2>&1 valeur retournée : 1. Détails : Traceback (most recent call last): File "/var/www/html/plugins/script/data/test.py", line 3, in from APSystemsECUR import APSystemsECUR File "/var/www/html/plugins/script/data/APSystemsECUR.py", line 76 async def async_read_from_socket(self): ^ SyntaxError: invalid syntax

Cette erreur en sauvegarde du script dans jeedom étant la même que j’obtiens quand le lance le script sous ssh en python au lieu de python3 je me demande si le plugin script lance bien en python3 ?

root@Jeedom228:/var/www/html/plugins/script/data# python test.py
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    from APSystemsECUR import APSystemsECUR
  File "/var/www/html/plugins/script/data/APSystemsECUR.py", line 76
    async def async_read_from_socket(self):
            ^
SyntaxError: invalid syntax

la déclaration d’une function en pyhon commence par def
bizard le async en tête ???

from APSystemsECUR import APSystemsECUR

la il te manque la lib APSystemsECUR

Bonjour olive, je vais finir par t’offrir une bouteille :smiley:
Voici le fichier en espérant qu’il ne soit pas trop long.

#!/usr/bin/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("ECU {}.".format(self.ecu_id))
        print("Firmware {}.".format(self.firmware))
        print("TZ {}.".format(self.timezone))
        print("Qty of inverters {}.".format(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)

c’est peut-être juste le fichier de la librairie qu’il te manque dans le dossier

Mais ou vais-je trouver ça :innocent:
L’adaptation a été faite par @ovomaltin et @Bandenabos à partir de ce qui vient de HA ici :
https://github.com/ksheumaker/homeassistant-apsystems_ecur#readme
Moi ce que j’ai compris c’est qu’on lance le script python test.py qui lui lance le script APSystemsECUR.py pour l’importation du json

Ce que je trouve bizarre c’est que dans mon fichier zip tu as les fichiers que j’utilise, donc je ne comprends pas pourquoi ceux la ne fonctionnent pas…

A priori les online python code checker que j’ai testé semble dire que ton code est ok.
C’est vraiment bizarre ce qui se passe chez toi…

Hello,
C’est clair que je ne comprends pas non plus. Un variant breton surement :roll_eyes:
Je persévère car je me dis que si j’arrive à faire marcher j’aurais tellement galéré que cela évitera peu-être les pièges à d’autres.

1 « J'aime »

Sur la premiere ligne de du code python moi j’ai

#!/usr/bin/env python3

alors que tu as

#!/usr/bin/python3

pour moi si je comprends bien si tu tapes python test.py tu prends celui de env par defaut donc je suppose que tu dois executer en python 2 et non en 3.
Essai de modifier les premieres lignes de test.py et APSystemsECUR.py avec celle que je t’ai donné.

OvO

Les deux entête font la même erreur.

#!/usr/bin/python3

c’est olive qui m’avait demandé d’essayer.

dans le dossier ou se trouve test.py
a tu un fichier

APSystemsECUR.py

???

En ssh essaie les commandes:
type -a python
Et ensuite verifie ce que tu as dans le repertoire /usr/bin comme fichiers commenceant par pyth*

Je vais essayer de copier ton code sur mon raspi et je te tiens au courant

Oui,
-rwxrwxr-x 1 www-data www-data 11631 avril 28 20:22 APSystemsECUR.py
-rwxrwxr-x 1 www-data www-data 2042 févr. 1 2017 calcul.php
-rwxrwxr-x 1 www-data www-data 3573 janv. 30 2017 IDeviceLocmodified.php
-rwxrwxr-x 1 www-data www-data 4216 févr. 1 2017 iDeviceLoc.php
-rwxrwxr-x 1 www-data www-data 4221 janv. 30 2017 Idevice.php
-rwxrwxr-x 1 www-data www-data 1489 févr. 1 2017 ping.sh
drwxrwxr-x 2 www-data www-data 4096 avril 28 20:22 pycache
-rwxrwx— 1 www-data www-data 472 avril 28 20:23 test.py

root@Jeedom228:/var/www/html/plugins/script/data# type -a python
python is /usr/bin/python
python is /bin/python

J’en perds mon latin:


Je viens de creer le fichier APSystemsECUR.py en faisant un copier/coller de ton code.
:dizzy_face:

Peut-être que tu peux effacer le fichier APSystemsECUR.py.
En creer un nouveau avec un copier/coller de ton code plus haut :slight_smile:

On va effectivement passer au latin, car en effaçant le fichier et en le recréant avec le même code et bien ça marche :ok_hand:

en ssh c’est bon, j’ai le données par contre quand je lance le script dans le plugin script jeedom je suis sur qu’il me le lance en python et pas en python3 car j’ai la même erreur que si je le lance en python en ssh.
@olive je crois que tu avais constaté le pb ?

Python3 dans un plugin-script

Oulà que de messages … alors j’suis pas un pro de linux/python.

La seule chose que je peux dire c’est que mon système est sur un Debian 10, ça aide peut être …

Renais35000, du coup ça fonctionne c’est ça si tu changes le contenu du fichier ?

Si ça ne fonctionne pas sous jeedom c’est peut être juste un problème de droit sur le répertoire dans lequel se trouve ton code python ?

Perso voici les droits de mon dossier image pour un user autre que jeedom ou root sur cette machine.