Forum

Haltet euch BITTE an die Forumsregeln, jeder der hier Mitglied ist, hat bei der Registrierung den Regeln zugestimmt. Wer sich nicht daran hält, wird aus dem Forum entfernt. 

Unterstützte das Deye Forum

 Deye Hybrid Angebote   Deye Zubehör Produkte

DEYE ohne Cloud (ei...
 
Benachrichtigungen
Alles löschen

DEYE ohne Cloud (eigener Remote-Server)

41 Beiträge
10 Benutzer
6 Reactions
6,896 Ansichten
(@epo_deluxe)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 6 Monaten
Beiträge: 49
 

Veröffentlicht von: @paulmelsec

Mir fehlen noch Akku Daten und alle Statistiken um die App komplett zu ersetzen. Weiß nicht wie ich die vorhandenen Daten einmal pro Tag wegschreibe und variabel Darstelle.

Die Akku Details bekomme hatte ich mir zum testen per Solarassistant geholt. Dann aber in einem anderen Forum einen Entwurf eines Python Scripts zum Auslesen des BMS gefunden und das mit Hilfe meines Sohns für mich angepasst. Das läuft derzeit auf einem extra PI und sendet seine Daten per MQTT an ioBroker. Ergebnis sieht dann so aus:

Das ganze für quasi für jeden einzelnen Battery Pack.

In iobroker gibt es ja unzählige Möglichkeiten, wie auch bei HA, um die Daten zu speichern und zu Visualisieren. Ich bin halt ziemlich lange bei ioBroker dabei und erst wegen der Apple Geräte vor ca. 1 Jahr zusätzlich zu HA gekommen. Mir fällt es leider mangels Zeit schwer mich richtig tief in HA einzuarbeiten, zusätzlich sehe ich aktuell keinen Grund von ioBroker komplett nach HA zu wechseln.

Mit Hausmitteln wäre das History und Flot zum Darstellen von Diagrammen etc. Eventuell gibt es auch schon weiterführende Adapter aber da ich das mit Influx und Grafana mache bin ich diesbezüglich nicht so aktuell.

Gibt auch ne Menge Scripte usw. und Tabellen zu erstellen und/oder die Sachen zusätzlich z.B. noch in ein Excel zu speichern.

2x SMA PV mit Gesamt 21,7kWP
Netzparallel ohne PV Module Deye SUN-12K-SG04LP3-EU mit Felicity 20,48kWH Speicher


   
AntwortZitat
Funkboje
(@funkboje)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 1 Jahr
Beiträge: 37
 

Eine Alternative wäre die Lösung von der Solaranzeige, die auch die Daten auf einen vServer parallel übertragen kann. Hier gibt es dazu weitere Informationen

.

------------------------------------
Ulrich

Administrator des Open Source Projektes "Solaranzeige"
Die Solaranzeige kann auch einige Deye Geräte über MODBUS steuern.


   
anonym 237 reacted
AntwortZitat
(@epo_deluxe)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 6 Monaten
Beiträge: 49
 

@funkboje Bin ichauch schon drüber gestolpert. Liefert die Solaranzeige die einzelnen Akku werte wenn man mehrere hat, das konnte ich irgendwie nirgendwo rauslesen.

2x SMA PV mit Gesamt 21,7kWP
Netzparallel ohne PV Module Deye SUN-12K-SG04LP3-EU mit Felicity 20,48kWH Speicher


   
AntwortZitat
Funkboje
(@funkboje)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 1 Jahr
Beiträge: 37
 

Wenn der BMS in der Liste ist, der Geräte, die ausgelesen werden können, bzw. ein baugleiches Gerät ist, dann ja.

------------------------------------
Ulrich

Administrator des Open Source Projektes "Solaranzeige"
Die Solaranzeige kann auch einige Deye Geräte über MODBUS steuern.


   
AntwortZitat
(@epo_deluxe)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 6 Monaten
Beiträge: 49
 

@funkboje von welcher Liste sprichst du bzw. Wo finde ich die denn?

2x SMA PV mit Gesamt 21,7kWP
Netzparallel ohne PV Module Deye SUN-12K-SG04LP3-EU mit Felicity 20,48kWH Speicher


   
AntwortZitat
(@anonym-237)
PV Profi Neues Mitglied
Mitglied seit: Vor 5 Monaten
Beiträge: 375
 

@epo_deluxe ich benutze auch nur noch influx. der recorder von homeassi ist grottig. dann brauch ich auch nicht in engem raster loggen wenn der nur alle paar muniten dauerhaft speichert und nach wochen die daten verliert


   
AntwortZitat
Funkboje
(@funkboje)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 1 Jahr
Beiträge: 37
 

Veröffentlicht von: @epo_deluxe

@funkboje von welcher Liste sprichst du bzw. Wo finde ich die denn?

 

Die Liste findest du hier:

https://solaranzeige.de/phpBB3/viewtopic.php?t=1069

 

 

------------------------------------
Ulrich

Administrator des Open Source Projektes "Solaranzeige"
Die Solaranzeige kann auch einige Deye Geräte über MODBUS steuern.


   
AntwortZitat
(@epo_deluxe)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 6 Monaten
Beiträge: 49
 

@funkboje danke.

gibt es die Liste auch ohne vorherige Registrierung?

 

Macht ja wenig Sinn wenn ich mich extra anmelde um dann festzustellen das meine Batterien da nicht aufgeführt sind.

2x SMA PV mit Gesamt 21,7kWP
Netzparallel ohne PV Module Deye SUN-12K-SG04LP3-EU mit Felicity 20,48kWH Speicher


   
AntwortZitat
Funkboje
(@funkboje)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 1 Jahr
Beiträge: 37
 

Ja, das gibt es auch hier:

https://solaranzeige.de/phpBB3/viewtopic.php?t=170

Sorry, darauf hätte ich achten müssen.

------------------------------------
Ulrich

Administrator des Open Source Projektes "Solaranzeige"
Die Solaranzeige kann auch einige Deye Geräte über MODBUS steuern.


   
Epo_Deluxe reacted
AntwortZitat
(@jimibondi)
Neuling Neues Mitglied
Mitglied seit: Vor 2 Jahren
Beiträge: 20
 

@epo_deluxe

Hallo, ist genau das was ich suche. Wenn alle Daten inkl. BMS ausgelesen werden können ist dies richtig klasse. Für mein Projekt benötige ich nur den "SOC". Hintergrund ist, dass ich eine Wärmepumpe im April 2025 eingebaut bekomme. Diese ist "SG-Ready" und somit kann bei bei Nutzung einer PV die Energie im Pfufferspeicher eingelagert werden. Ich habe dies bisher so im Probetrieb aufgebaut:

>Deye Sun 12 + 40 kW Abbus sind so eingestellt: Erzeugung in den Haushalt > Überschuss in den Akkus > wenn SOC 100% dann bisher Verkauf

>Die SG-Ready Schnittstelle wird mit einem Node-Red-Script und einem Shelly 3 Pro (220V) gesteuert. Das Script ist so aufgebaut, das es über MQTT den SOC abfrgen soll. Dann ist bis  71% SOC die Schnittstelle auf "Boost Mode" (Kontaktstellung: beide Kontakte geschlossen) geschalten. Ab 51 - 70% wird auf Comfort Mode (Kontakt eins offen, Kontakt 2 geschlossen). Bei SOC 20% (Deye-Einstellung) bis 50% wird der "Normal Mode" aktiviert.  Also beide Kontakte sind geöffnet.

 

Wäre ein zur Verfügungstellung des Scriptes möglich?

 

Danke.

Herbert


   
AntwortZitat
(@epo_deluxe)
Deye Kenner Neues Mitglied
Mitglied seit: Vor 6 Monaten
Beiträge: 49
 

@jimibondi Hi,

sorry war wegen Dienstreise und Gartenumbau bisschen offline.

Hier das Script wie es derzeit bei mir läuft:

 

Spoiler
Script
import logging
import signal
import sys
import paho.mqtt.client as mqtt
import datetime
import time
import os.path
from FelicityBMSManager import FelicityBMSManager

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

#DataItem = namedtuple("DataItem", ["key", "command", "data", "lastdata", "isdirty"])
class DataItem:

    def __init__(self, command : str, clientid : int):
        self.command = command
        self.clientid = clientid
        self.data = None
        self.lastdata = None

class MQTTBroker:

    def __init__(self, name: str, address : str, topic : str):
        self.name = name
        self.address = address
        self.topic = topic
        self.client = None


ioBroker_broker = MQTTBroker('ioBroker', '192.168.178.18', 'felicity/battery')

def on_connect(client, userdata, rc, *extra_params):
    print('ioBroker: Connected with result code ' + str(rc))
    client.subscribe(ioBroker_broker.topic + '/set/#')
    #client.publish(settings.MQTT_TOPIC + '', get_gpio_status(), 1)


ioBroker_broker.client = mqtt.Client()
isrunning = True

def _handle_sigterm(sig, frame):
    logging.info("SIGTERM received. Exiting...")
    isrunning = False
    ioBroker_broker.client.loop_stop()
    exit()

def getFormattedData(command, clientid):
    ws = FelicityBMSManager(command=command, clientid=clientid)
    data = ws.get_data()

    #if websocket_data != last_websocket_data:

    formatted_data = {}

    for inverter_key in data:
        inverter_item = data[inverter_key]
        value = inverter_item.value

        #if value == '--':
        #    value = '0'

        formatted_data[inverter_key] = {"name": inverter_item.name, "value": value, "unit": inverter_item.unit}
        #print(inverter_key, inverter_item.desc, value, inverter_item.unit)

    return formatted_data

def sendData(broker, topic_key, data):
    broker.client.publish(broker.topic + topic_key, data)
    #print(broker.name, broker.address, broker.topic + topic_key, data)

starttime = datetime.datetime.min
resettime = datetime.datetime.min

try:
    signal.signal(signal.SIGTERM, _handle_sigterm)

    ioBroker_broker.client.on_connect = on_connect
    #ioBroker_broker.client.on_message = on_message
    ioBroker_broker.client.username_pw_set("mqtt","ioBroker")
    ioBroker_broker.client.connect(ioBroker_broker.address, 1889, 60)
    ioBroker_broker.client.loop_start()

    starttime = datetime.datetime.min

    battery_information_list = [
        DataItem(command="GetBMSBatteryInformation", clientid = 1),
        DataItem(command="GetBMSBatteryInformation", clientid = 2),
        DataItem(command="GetBMSBatteryInformation", clientid = 3),
        DataItem(command="GetBMSBatteryInformation", clientid = 4)
    ]

    cell_information_list = [DataItem(command="GetBMSCellInformation", clientid = battery_information.clientid) for battery_information in battery_information_list]

    while isrunning:
        if datetime.datetime.now() > starttime + datetime.timedelta(seconds = 10): #org 10
            try:
                isDirtyDefault = False

                if datetime.datetime.now() > resettime + datetime.timedelta(seconds = 60): #org 60
                    isDirtyDefault = True
                    resettime = datetime.datetime.now()

                for battery_index, battery_information in enumerate(battery_information_list):
                    battery_information.lastdata = battery_information.data

                    battery_information.data = getFormattedData(command =  battery_information.command, clientid = battery_information.clientid)
                    time.sleep(1)

                    if isDirtyDefault or battery_information.lastdata != battery_information.data:
                        battery_voltage = battery_information.data["BatteryVoltage"]["value"]
                        battery_current = battery_information.data["BatteryCurrent"]["value"]
                        battery_power = battery_voltage * battery_current * -1 # positiv Ladung, negativ Entladung
                        battery_soc = battery_information.data["SOC"]["value"]

                        sendData(ioBroker_broker, '/' + str(battery_information.clientid) + '/battery_voltage', battery_voltage)
                        sendData(ioBroker_broker, '/' + str(battery_information.clientid) + '/battery_current', battery_current)
                        sendData(ioBroker_broker, '/' + str(battery_information.clientid) + '/battery_power', round(battery_power, 1))
                        sendData(ioBroker_broker, '/' + str(battery_information.clientid) + '/soc', battery_soc)

                    cell_information = cell_information_list[battery_index]
                    cell_information.lastdata = cell_information.data
                    cell_information.data = getFormattedData(command = cell_information.command, clientid = cell_information.clientid)
                    time.sleep(1)

                    if isDirtyDefault or cell_information.lastdata != cell_information.data:
                        for cell_index in range(1, 17):
                            cell_voltage = cell_information.data["Cell" + str(cell_index) + " Voltage"]["value"]
                            sendData(ioBroker_broker, '/' + str(battery_information.clientid) + '/cell_' + str(cell_index) + '_voltage', cell_voltage)

            except Exception as e:
                exc_type, exc_obj, exc_tb = sys.exc_info()
                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                logging.exception("Caught exception")
                print(exc_type, fname, exc_tb.tb_lineno)
                pass

            starttime = datetime.datetime.now()

        time.sleep(1)

#except KeyboardInterrupt:
#    GPIO.cleanup()
#    dmx_stop()
finally:
    print("cleanup")

Und hier die verwendete Library nochmal:

Spoiler
Lib
import asyncio
import serial
import time
import crcmod
import argparse
from terminaltables import AsciiTable  # type: ignore
from collections import namedtuple

InverterItem = namedtuple("InverterItem", ["name", "desc", "value", "unit"])

class FelicityBMSManager:
    """ FelicityBMSManager"""
    def __init__(self, command: str, clientid : int = 1, device: str = "/dev/ttyUSB0", baudrate: int = 9600):
    #def __init__(self, command: str, value: int = None, device: str = "socket://192.168.0.205:4196", baudrate: int = 9600, inverterport: str = "/dev/ttyRS232", inverterbaud: int = 2400):

        clientid_hex = "%0.2x" % int(clientid)

        #print(command, clientid, clientid_hex, device, baudrate)

        self.command: str = command
        self.clientid_hex = clientid_hex
        self.device: str = device
        self.baudrate: int = baudrate

    async def get_data_async(self) -> dict[str, InverterItem]:

        data: dict[str, InverterItem] = {}

        #data["test"] = InverterItem(
        #            name="t1",
        #            desc="desc1",
        #            value="value1",
        #            unit="unit1")

        device = ""
        baud = 0
        no_crc_command = None
        command_result = None

        if self.command == "GetBMSVersion":
            device = self.device
            baud = self.baudrate
            no_crc_command = bytes.fromhex(self.clientid_hex + " 03 F8 0B 00 01") # Battery version
            command_result = self.send_command(device, baud, no_crc_command);
        elif self.command == "GetBMSCellInformation":
            device = self.device
            baud = self.baudrate
            no_crc_command = bytes.fromhex(self.clientid_hex + " 03 13 2A 00 14") # Cell voltage and temperature
            command_result = self.send_command(device, baud, no_crc_command);
        elif self.command == "GetBMSBatteryInformation":
            device = self.device
            baud = self.baudrate
            no_crc_command = bytes.fromhex(self.clientid_hex + " 03 13 02 00 0A") # Battery information
            command_result = self.send_command(device, baud, no_crc_command);
        else:
            print("Invalid command")

        if command_result is not None:

            if self.command == "GetBMSCellInformation":

                #datacount = int(len(command_result) / 2)
                #print(datacount)


                #for i in range(datacount):

                #    cell_key = str(i + 1)
                #    value = command_result[i * 2:i * 2 + 2]

                #    print(value, value.hex());

                #    data["Cell" + cell_key + " Voltage"] = InverterItem(
                #        name="Cell" + cell_key + " Voltage",
                #        desc="Cell" + cell_key + " Voltage",
                #        value=round(int.from_bytes(value, byteorder='big') * 1, 4),
                #        unit="V")

                cellcount = 16

                for i in range (0, cellcount):

                    cell_key = str(i + 1)
                    value = command_result[i * 2:i * 2 + 2]
                    #print(i, value.hex())

                    data["Cell" + cell_key + " Voltage"] = InverterItem(
                        name="Cell" + cell_key + " Voltage",
                        desc="Cell" + cell_key + " Voltage",
                        value=round(int.from_bytes(value, byteorder='big') * 0.001, 4),
                        unit="V")

                for i in range (0, 4):

                    cell_key = str(i + 1)
                    value = command_result[(cellcount + i) * 2:(cellcount + i) * 2 + 2]

                    #value = value * 0.001

                    data["Sensor" + cell_key + " Temperature"] = InverterItem(
                        name="Sensor" + cell_key + " Temperature",
                        desc="Sensor" + cell_key + " Temperature",
                        value=round(int.from_bytes(value, byteorder='big') * 1, 4),
                        unit="°C")

            elif self.command == "GetBMSVersion":
                version = command_result[0:2]
                #print(type(version), version.hex(), int.from_bytes(version, byteorder='big'))

                data["BMSVersion"] = InverterItem(
                    name="BMSVersion",
                    desc="BMSVersion",
                    value=int.from_bytes(command_result[0:2], byteorder='big'),
                    unit="")

            elif self.command == "GetBMSBatteryInformation":
                #soc = command_result[18:20]
                #print(type(soc), soc.hex(), int.from_bytes(soc, byteorder='big'))

                data["BatteryVoltage"] = InverterItem(
                    name="BatteryVoltage",
                    desc="BatteryVoltage",
                    value=round(int.from_bytes(command_result[8:10], byteorder='big') * 0.01, 4),
                    unit="V")

                signedCurrent = int.from_bytes(command_result[10:12], byteorder='big')
                #print(signedCurrent)
                signedCurrent -= (signedCurrent & 0x8000) << 1
                #print(signedCurrent)

                data["BatteryCurrent"] = InverterItem(
                    name="BatteryCurrent",
                    desc="BatteryCurrent",
                    value=round(signedCurrent * 0.1, 2),
                    unit="A")

                data["SOC"] = InverterItem(
                    name="SOC",
                    desc="Ladezustand",
                    value=int.from_bytes(command_result[18:20], byteorder='big'),
                    unit="%")

        return data

    def send_command(self, serial_port, serial_baud, no_crc_command):

        crc = self.modbusCrc(no_crc_command)
        ba = crc.to_bytes(2, byteorder='little')
        full_command = no_crc_command + ba

        #print("full command", full_command.hex())

        try:

            with serial.serial_for_url(serial_port, serial_baud) as s:

#               while True:
#                       response_line = s.read_until(b"\r")
#                       #dataline = s.readline();
#                       print(response_line)

                #print("Executing command via serialio...")
                s.timeout = 1
                s.write_timeout = 1
                s.flushInput()
                s.flushOutput()
                s.write(full_command)
                #s.write(full_command+'\r')
                time.sleep(0.1) # give serial port time to receive the data
#               response_line = s.read_until(b"\r")
#               print("serial response was: %s", response_line)

#               bytes = bytearray(response_line)
#               print(bytes.hex())

                resp = s.read(3)
                #print(resp.hex())

                datalength = resp[2]

                resp = s.read(datalength)
                #print(type(resp), resp.hex())

#               hex_val = resp.hex()
#               byte_val = bytes([int(hex_val[i : i + 2], 16) for i in range(0, len(hex_val), 2)])
#               print(byte_val)

                return resp

                #soc = resp[18:20]
                #print(type(soc), soc.hex(), int.from_bytes(soc, byteorder='big'))


#               return response_line
        except Exception as e:
            print(f"Serial read error: {e}")
            print("Command execution failed")

    def modbusCrc(self, msg:str) -> int:
        crc = 0xFFFF
        for n in range(len(msg)):
            crc ^= msg[n]
            for i in range(8):
                if crc & 1:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc

    def get_data(self) -> dict[str, InverterItem]:
            return asyncio.run(self.get_data_async())

def main():
    """Command line interface to the Felicity BMS"""
    parser = argparse.ArgumentParser(
        description="Retrieve data from Felicity Inverter and BMS"
    )

    parser.add_argument("command", help="Command")
    parser.add_argument("-c", "--clientid", default=1, help="ClientId (DIP Settings)")
    parser.add_argument("-d", "--device", default="/dev/ttyUSB0", help="Device (e.g. /dev/ttyUSB0 or socket://ipadress:port")
    parser.add_argument("-b", "--baudrate", default="9600", help="baudrate")

    #parser.add_argument(
    #    "--details", action="store_true", help="show more details"
    #)
    #parser.add_argument('--version', action='version', version=version)
    args: dict[str, InverterItem] = parser.parse_args()

    data: list[InverterItem] = FelicityBMSManager(args.command, args.clientid, args.device, args.baudrate).get_data()
    #if args.details:
    #    table: list[list[str]] = [["Item", "Value", "ID"]] + [
    #        [item.desc, f"{item.value} {item.unit}", id]
    #        for id, item in data.items()
    #    ]
    #else:
    table = [["Name", "Description", "Value"]] + [
        [item.name, item.desc, f"{item.value} {item.unit}"] for item in data.values()
    ]
    print(AsciiTable(table).table)

if __name__ == "__main__":
 
Diese r Beitrag wurde geändert Vor 3 Wochen von Epo_Deluxe

2x SMA PV mit Gesamt 21,7kWP
Netzparallel ohne PV Module Deye SUN-12K-SG04LP3-EU mit Felicity 20,48kWH Speicher


   
AntwortZitat
Seite 3 / 3