Weiter zum Inhalt
  • Home
  • Aktuell
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

NEWS

  • Monatsrückblick Oktober 2026 ist online!
    BluefoxB
    Bluefox
    9
    1
    226

  • Mitmachen: Sprich „ioBroker“ ein – für ein lokales Aktivierungswort
    BluefoxB
    Bluefox
    6
    1
    457

  • Neuer Blogbeitrag: KI in ioBroker, vom Helfer im Admin bis zum eigenen Smart Assistant
    BluefoxB
    Bluefox
    4
    1
    497

Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

Geplant Angeheftet Gesperrt Verschoben JavaScript
690 Beiträge 24 Kommentatoren 124.3k Aufrufe 22 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • maxclaudiM maxclaudi

    zURLtool_V20 (c) maxclaudi 2026 - Version 2.0 cdh

    Neu in zURLtool V 2.0 dch
    • Sprachauswahl: Deutsch und Englisch sind auswählbar und während der Laufzeit umschaltbar.

    • Reset-Funktion: Geräte können zurückgesetzt werden (Hinweis: Zum vollständigen Abmelden - z.B. bei Verkauf - Gerät auch in der App entfernen!).

    • WLAN-Handling: Das manuelle Setzen des WiFi-Mode entfällt. Ein automatischer Scan zeigt alle vom Zendure-Gerät erreichbaren Netze an. Bei zur config.ini abweichendem, manuell ausgewählten WLAN muss auch das Passwort in der GUI angepasst werden.

    • Offline-Modus: Die Option OFFLINE = 1 in der config.ini überspringt den WLAN-Scan, ideal für Vorbereitungen oder Konfigurationen ohne aktiven Router.

    • Token & User-ID: Gerätespezifischer Token wird in mqtt_creds.txt gesichert. Für die korrekte userId (bzw. consumerId) empfiehlt sich der Einsatz des Zusatztools zAppEmulationDeviceInfo

    • Ein Backup mit den verwendeten Daten in der config.ini wird zusätzlich gespeichert. Beispiel: bak_config_26.10.26_14.51.35.ini

    • Hinweis zu TLS / Port 8883: Das Tool ist ein reiner Konfigurator per Bluetooth und generiert keine TLS-Zertifikate oder Keys.

    00.png

    01-de.png

    Beschreibung im Spoiler.


    zURLtool V 2.0 dch – Lokaler MQTT-Broker für Zendure 2026

    Hallo zusammen, das Update zURLtool V 2.0 dch de/gb ist nun fertig!


    Das Programm ist in seiner Art einzigartig, neu und durch monatelanges Reverse-Engineering, Analysen und intensives Testen entstanden.
    Es handelt sich um ein reines Windows-GUI-Programm, das ganz einfach per Doppelklick gestartet wird.

    Es verwendet keinen Code von anderen bekannten Projekten, die teils fehlerhaften und unvollständigen Code verwenden, mit dem Konfigurationen neuer Geräte nicht möglich sind.

    Technische Voraussetzungen & Basis:

    Vielen Dank an Henrik Blidh für die hervorragende BT-Bibliothek.

    Das Programm nutzt zur Kommunikation ausschließlich die offiziellen Windows-BT-Treiber ab Windows 10/11 und die integrierte Bluetooth-Bibliothek bleak:

    ==================================================

    LICENSE NOTICE FOR THE UTILIZED BLUETOOTH LIBRARY
    This program uses "bleak" for Bluetooth functionality.
    https://github.com/hbldh/bleak
    Copyright (c) 2020, Henrik Blidh
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.

    ==================================================

    Die Bibliothek wurde fest in das Programm eingebunden.

    Weitere Bibliotheken oder Frameworks müssen auf eurem PC nicht installiert werden.

    • Betriebssystem: Windows (10) 11 (64-Bit)
    • Hardware: Interner Bluetooth-Chip oder ein standardmäßiger USB-Bluetooth-Stick am Rechner.
    • Kompatibilität: Getestet mit Zendure-Geräten bis zur 2400er-Serie (sowohl ältere als auch neueste Generationen / Firmware-Stände).

    Inhalt des Programmpakets (zURLtoolV20.zip):
    Das ZIP-Archiv enthält genau drei Dateien, die zwingend im selben Ordner liegen bleiben müssen:
    • zURLtool_V20.exe (Größe: ca. 15.5 MB / 15555 KB)
    • config.ini (Konfigurationsdatei für eure Zugangsdaten)
    • ReadMe_zURLtoolV20.txt (Ausführliche Anleitung in Deutsch & Englisch)

    Was genau macht das zURLtool?
    Das Tool schreibt im Zendure-Gerät die Konfiguration der Broker-URL um.

    • Es wird NICHT in die Firmware eingegriffen und es wird nichts modifiziert. Die originale Firmware von Zendure bleibt zu 100 % unangetastet.

    • Es werden lediglich die standardmäßigen Bluetooth-Befehle verwendet, die die Firmware von Haus aus für die App bereitstellt.

    • Das Skript verhält sich für das Gerät exakt wie die originale App, greift auf die Konfigurationsvariablen im Speicher zu und überschreibt dort die Wi-Fi-Daten und den Eintrag für die Cloud-URL mit der IP eures eigenen lokalen Brokers.

    Es funktioniert quasi wie eine zusätzliche Länderauswahl, bei der man sich einfach mit einem anderen Server verbindet.
    Das bietet die Firmware von Haus aus an, da die Geräte sonst permanent fest an ganz bestimmte AWS-Server gebunden wären.
    Zurück zur Cloud ist auch möglich. Beispiele sind in der config.ini enthalten.

    Die Geräte verbinden sich und können beschrieben werden ohne hartes Reset und ohne hartes Trennen von allen Stromquellen.

    Freigabe vereinfacht.
    Wer schon eine Seriennummer und Freischalt-Code für zURLtool V1.0 dch hat, kann diese verwenden.
    Seriennummer und Freischalt-Code bleiben gültig, eine erneute Anfrage für zURLtool V2.0 ist nicht erforderlich.

    Der Freischalt-Ablauf:
    Damit das Programm funktioniert und um anonyme Massen-Weiterreichungen zu unterbinden, benötigt das Programm eine Seriennummer und einen Freigabe-Code.

    1. Sende mir deinen (Foren-) Namen per privater Nachricht (PN) im Forum oder per E-Mail an: ztool.maxclaudi AT gmail . com
    2. Du erhältst von mir eine persönliche "Seriennummer" und den dazu passenden "Freigabe-Code"
    3. Sichere dir die Seriennummer und den Freigabe-Code unbedingt in einer Textdatei als Backup! Falls du die Datei mal verlierst, hast du deine Seriennummer und deinen Freigabe-Code als Backup griffbereit.
    4. Starte die 'zURLtool_GUI.exe'.

    Trage beide Werte exakt so, wie du sie erhalten hast, in die dafür vorgesehenen Felder im Startfenster ein und klicke auf "Akzeptieren & Starten".
    Nach einmaliger Eingabe startet zURLtool zukünftig vollautomatisch in die Hauptoberfläche.

    Als Notfall hast du zusätzlich Seriennummer und Freigabecode als Backup.
    Vor dem ersten Start muss bzw. kann die config.ini in einem einfachen Text-Editor mit euren echten Daten befüllt werden.
    Auswählbare Beispiele und Erklärungen zu den WLAN-Modi sind direkt darin enthalten.

    Wenn keine config.ini vorhanden ist oder nicht benutzt wird, müssen die Daten in die GUI eingegeben werden.

    Warum ist das Programm Closed Source?
    Zu oft wurden meine Skripte, Codes oder Teile daraus in der Vergangenheit verwendet und/oder modifiziert – ohne Nennung meines Namens, ohne Danksagung / Anerkennung und unter Entfernung meiner originalen Hinweise.
    Entweder wurde (leicht abgeändert) einfach "übernommen" oder u. a. als eigener Erfolg ausgegeben oder kommentiert mit "ich habe einen Weg gefunden".
    Das ist ungerecht.

    Monatelange harte Arbeit sollte zumindest anerkannt werden.

    Sollte das Programm oder der Code in irgendeiner Form gehackt zur Verfügung gestellt oder verwendet werden, dann war dies mein letzter offizieller Beitrag in Form von Code für die Community.
    Das gilt auch wenn das Programm inkl. Seriennummer und Freigabe-Code mehrfach geteilt wird.

    Viel Spaß mit dem zURLtool V2.0 dch!


    01-eng.png

    02-eng.png

    04-eng.png

    05-eng.png


    Bitte ReadMe_zURLtoolV20.txt beachten, enthält die deutsche und englische Beschreibung.

    HINWEIS FÜR BESTEHENDE NUTZER:
    Wer bereits für die Version 1.0 eine Seriennummer und einen Freischalt-Code erhalten hat, kann diese Daten direkt weiterverwenden!
    Sie sind auch für die Version 2.0 gültig.
    Bitte in diesem Fall keine erneute Anfrage.

    RECHTLICHER HINWEIS ZUR FREIGABE:
    Eine Weitergabe von Seriennummer und Freigabe-Code
    sowie deren Mehrfachnutzung durch verschiedene Personen ist ausdrücklich UNZULÄSSIG.


    Please note ReadMe_zURLtoolV20.txt: contains the German and English description.

    NOTE FOR EXISTING USERS:
    If you have already received a serial number and activation code for version 1.0, you can reuse them directly!
    They are valid for version 2.0 as well. Please do not submit a new request in this case.

    LEGAL NOTICE ON ACTIVATION:
    Sharing the serial number and activation code, as well as their multiple use by different individuals, is strictly PROHIBITED.

    maxclaudiM
    maxclaudiM
    maxclaudi
    schrieb am zuletzt editiert von maxclaudi
    #658

    Download zURLtoolV20.zip in 4 Teilen.
    Teil 3v4:

    zURLtoolV20.z02

    Achtung: Falls die Datei nach dem Download einen anderen Namen als zURLtoolV20.z02 hat, benenne sie bitte entsprechend um.
    Es kann und soll gerne ein Virenscan erfolgen!

    HINWEIS ZUM ENTPACKEN (MEHRTEILIGES ARCHIV):
    Das Programmpaket ist auf 4 Teile aufgeteilt. Bitte verwende zum Entpacken
    ein vollwertiges Packprogramm wie 7-Zip, WinRAR oder Bandizip. Das integrierte Windows-eigene ZIP-Programm kann mehrteilige Archive NICHT korrekt zusammensetzen und führt zu Fehlfunktionen oder Fehlermeldungen beim Entpacken!

    Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

    neue Zendure Geräte Cloudless: zURLtool V2.0

    1 Antwort Letzte Antwort
    0
    • maxclaudiM maxclaudi

      zURLtool_V20 (c) maxclaudi 2026 - Version 2.0 cdh

      Neu in zURLtool V 2.0 dch
      • Sprachauswahl: Deutsch und Englisch sind auswählbar und während der Laufzeit umschaltbar.

      • Reset-Funktion: Geräte können zurückgesetzt werden (Hinweis: Zum vollständigen Abmelden - z.B. bei Verkauf - Gerät auch in der App entfernen!).

      • WLAN-Handling: Das manuelle Setzen des WiFi-Mode entfällt. Ein automatischer Scan zeigt alle vom Zendure-Gerät erreichbaren Netze an. Bei zur config.ini abweichendem, manuell ausgewählten WLAN muss auch das Passwort in der GUI angepasst werden.

      • Offline-Modus: Die Option OFFLINE = 1 in der config.ini überspringt den WLAN-Scan, ideal für Vorbereitungen oder Konfigurationen ohne aktiven Router.

      • Token & User-ID: Gerätespezifischer Token wird in mqtt_creds.txt gesichert. Für die korrekte userId (bzw. consumerId) empfiehlt sich der Einsatz des Zusatztools zAppEmulationDeviceInfo

      • Ein Backup mit den verwendeten Daten in der config.ini wird zusätzlich gespeichert. Beispiel: bak_config_26.10.26_14.51.35.ini

      • Hinweis zu TLS / Port 8883: Das Tool ist ein reiner Konfigurator per Bluetooth und generiert keine TLS-Zertifikate oder Keys.

      00.png

      01-de.png

      Beschreibung im Spoiler.


      zURLtool V 2.0 dch – Lokaler MQTT-Broker für Zendure 2026

      Hallo zusammen, das Update zURLtool V 2.0 dch de/gb ist nun fertig!


      Das Programm ist in seiner Art einzigartig, neu und durch monatelanges Reverse-Engineering, Analysen und intensives Testen entstanden.
      Es handelt sich um ein reines Windows-GUI-Programm, das ganz einfach per Doppelklick gestartet wird.

      Es verwendet keinen Code von anderen bekannten Projekten, die teils fehlerhaften und unvollständigen Code verwenden, mit dem Konfigurationen neuer Geräte nicht möglich sind.

      Technische Voraussetzungen & Basis:

      Vielen Dank an Henrik Blidh für die hervorragende BT-Bibliothek.

      Das Programm nutzt zur Kommunikation ausschließlich die offiziellen Windows-BT-Treiber ab Windows 10/11 und die integrierte Bluetooth-Bibliothek bleak:

      ==================================================

      LICENSE NOTICE FOR THE UTILIZED BLUETOOTH LIBRARY
      This program uses "bleak" for Bluetooth functionality.
      https://github.com/hbldh/bleak
      Copyright (c) 2020, Henrik Blidh
      Permission is hereby granted, free of charge, to any person obtaining a copy
      of this software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.

      ==================================================

      Die Bibliothek wurde fest in das Programm eingebunden.

      Weitere Bibliotheken oder Frameworks müssen auf eurem PC nicht installiert werden.

      • Betriebssystem: Windows (10) 11 (64-Bit)
      • Hardware: Interner Bluetooth-Chip oder ein standardmäßiger USB-Bluetooth-Stick am Rechner.
      • Kompatibilität: Getestet mit Zendure-Geräten bis zur 2400er-Serie (sowohl ältere als auch neueste Generationen / Firmware-Stände).

      Inhalt des Programmpakets (zURLtoolV20.zip):
      Das ZIP-Archiv enthält genau drei Dateien, die zwingend im selben Ordner liegen bleiben müssen:
      • zURLtool_V20.exe (Größe: ca. 15.5 MB / 15555 KB)
      • config.ini (Konfigurationsdatei für eure Zugangsdaten)
      • ReadMe_zURLtoolV20.txt (Ausführliche Anleitung in Deutsch & Englisch)

      Was genau macht das zURLtool?
      Das Tool schreibt im Zendure-Gerät die Konfiguration der Broker-URL um.

      • Es wird NICHT in die Firmware eingegriffen und es wird nichts modifiziert. Die originale Firmware von Zendure bleibt zu 100 % unangetastet.

      • Es werden lediglich die standardmäßigen Bluetooth-Befehle verwendet, die die Firmware von Haus aus für die App bereitstellt.

      • Das Skript verhält sich für das Gerät exakt wie die originale App, greift auf die Konfigurationsvariablen im Speicher zu und überschreibt dort die Wi-Fi-Daten und den Eintrag für die Cloud-URL mit der IP eures eigenen lokalen Brokers.

      Es funktioniert quasi wie eine zusätzliche Länderauswahl, bei der man sich einfach mit einem anderen Server verbindet.
      Das bietet die Firmware von Haus aus an, da die Geräte sonst permanent fest an ganz bestimmte AWS-Server gebunden wären.
      Zurück zur Cloud ist auch möglich. Beispiele sind in der config.ini enthalten.

      Die Geräte verbinden sich und können beschrieben werden ohne hartes Reset und ohne hartes Trennen von allen Stromquellen.

      Freigabe vereinfacht.
      Wer schon eine Seriennummer und Freischalt-Code für zURLtool V1.0 dch hat, kann diese verwenden.
      Seriennummer und Freischalt-Code bleiben gültig, eine erneute Anfrage für zURLtool V2.0 ist nicht erforderlich.

      Der Freischalt-Ablauf:
      Damit das Programm funktioniert und um anonyme Massen-Weiterreichungen zu unterbinden, benötigt das Programm eine Seriennummer und einen Freigabe-Code.

      1. Sende mir deinen (Foren-) Namen per privater Nachricht (PN) im Forum oder per E-Mail an: ztool.maxclaudi AT gmail . com
      2. Du erhältst von mir eine persönliche "Seriennummer" und den dazu passenden "Freigabe-Code"
      3. Sichere dir die Seriennummer und den Freigabe-Code unbedingt in einer Textdatei als Backup! Falls du die Datei mal verlierst, hast du deine Seriennummer und deinen Freigabe-Code als Backup griffbereit.
      4. Starte die 'zURLtool_GUI.exe'.

      Trage beide Werte exakt so, wie du sie erhalten hast, in die dafür vorgesehenen Felder im Startfenster ein und klicke auf "Akzeptieren & Starten".
      Nach einmaliger Eingabe startet zURLtool zukünftig vollautomatisch in die Hauptoberfläche.

      Als Notfall hast du zusätzlich Seriennummer und Freigabecode als Backup.
      Vor dem ersten Start muss bzw. kann die config.ini in einem einfachen Text-Editor mit euren echten Daten befüllt werden.
      Auswählbare Beispiele und Erklärungen zu den WLAN-Modi sind direkt darin enthalten.

      Wenn keine config.ini vorhanden ist oder nicht benutzt wird, müssen die Daten in die GUI eingegeben werden.

      Warum ist das Programm Closed Source?
      Zu oft wurden meine Skripte, Codes oder Teile daraus in der Vergangenheit verwendet und/oder modifiziert – ohne Nennung meines Namens, ohne Danksagung / Anerkennung und unter Entfernung meiner originalen Hinweise.
      Entweder wurde (leicht abgeändert) einfach "übernommen" oder u. a. als eigener Erfolg ausgegeben oder kommentiert mit "ich habe einen Weg gefunden".
      Das ist ungerecht.

      Monatelange harte Arbeit sollte zumindest anerkannt werden.

      Sollte das Programm oder der Code in irgendeiner Form gehackt zur Verfügung gestellt oder verwendet werden, dann war dies mein letzter offizieller Beitrag in Form von Code für die Community.
      Das gilt auch wenn das Programm inkl. Seriennummer und Freigabe-Code mehrfach geteilt wird.

      Viel Spaß mit dem zURLtool V2.0 dch!


      01-eng.png

      02-eng.png

      04-eng.png

      05-eng.png


      Bitte ReadMe_zURLtoolV20.txt beachten, enthält die deutsche und englische Beschreibung.

      HINWEIS FÜR BESTEHENDE NUTZER:
      Wer bereits für die Version 1.0 eine Seriennummer und einen Freischalt-Code erhalten hat, kann diese Daten direkt weiterverwenden!
      Sie sind auch für die Version 2.0 gültig.
      Bitte in diesem Fall keine erneute Anfrage.

      RECHTLICHER HINWEIS ZUR FREIGABE:
      Eine Weitergabe von Seriennummer und Freigabe-Code
      sowie deren Mehrfachnutzung durch verschiedene Personen ist ausdrücklich UNZULÄSSIG.


      Please note ReadMe_zURLtoolV20.txt: contains the German and English description.

      NOTE FOR EXISTING USERS:
      If you have already received a serial number and activation code for version 1.0, you can reuse them directly!
      They are valid for version 2.0 as well. Please do not submit a new request in this case.

      LEGAL NOTICE ON ACTIVATION:
      Sharing the serial number and activation code, as well as their multiple use by different individuals, is strictly PROHIBITED.

      maxclaudiM
      maxclaudiM
      maxclaudi
      schrieb am zuletzt editiert von maxclaudi
      #659

      Download zURLtoolV20.zip in 4 Teilen.
      Teil 4v4:

      zURLtoolV20.z03

      Achtung: Falls die Datei nach dem Download einen anderen Namen als zURLtoolV20.z03 hat, benenne sie bitte entsprechend um.
      Es kann und soll gerne ein Virenscan erfolgen!

      HINWEIS ZUM ENTPACKEN (MEHRTEILIGES ARCHIV):
      Das Programmpaket ist auf 4 Teile aufgeteilt. Bitte verwende zum Entpacken
      ein vollwertiges Packprogramm wie 7-Zip, WinRAR oder Bandizip. Das integrierte Windows-eigene ZIP-Programm kann mehrteilige Archive NICHT korrekt zusammensetzen und führt zu Fehlfunktionen oder Fehlermeldungen beim Entpacken!

      Für ein Dankeschön reicht ein Voting HIER aus ;-)

      Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

      neue Zendure Geräte Cloudless: zURLtool V2.0

      1 Antwort Letzte Antwort
      0
      • maxclaudiM maxclaudi

        20.10.2026; 15:18h Fortsetzung von:

        15.10.2026; 16:00 Uhr: Weiterer Bericht zum Betrieb über MQTT-Port 1883 und zenSDK POST #628

        Da es dazu weltweit quasi null Infos gibt, dachte ich, ich teile meine bisherigen Erkenntnisse mal auszugsweise als kleinen Bericht mit euch.

        Die Fortsetzung ist vermutlich gleichzeitig der Abschluss.

        Wer möchte, kann den Bericht im Spoiler lesen;
        für alle anderen ist der Beitrag dann kürzer und kann ignoriert werden. :-)

        Ich habe meine Analysen abgeschlossen und teile ein paar Ergebnisse mit euch.

        TLS Verbindung Port: 8883
        Das wichtigste Ergebnis vorab:
        Der herkömmliche, vorgesehene "Normal-" Betrieb über TLS ist absolut stabil möglich.

        Es gibt keine Verbindungsabbrüche und keine Reconnects mehr – und das gilt auch für den Mosquitto-Bridge-Betrieb, sofern die eigene lokale IP im Zendure-Gerät (mithilfe meines zURLtool) konfiguriert ist.
        Alles läuft extrem schnell und stabil.
        Für mich gibt es dadurch keinen Grund mehr, einen anderen Weg einzuschlagen - vor allem nicht mit zusätzlichen Optionen (mehr dazu folgt weiter unten).
        Durch die Stabilität und Schnelligkeit bei der jetzigen, regulären Betriebsart (TLS-Port 8883) kann ich keinen Mehrwert mehr über eine alternative Verbindung feststellen.
        Zuvor war der ESP anscheinend auch teils durch die ständige Verarbeitung der Disconnects und Reconnects überlastet.
        Jetzt ist alles super.

        Die ständigen Disconnects/Reconnects der 8883-TLS-Verbindungen - seltsamerweise vom Client zum mosquitto-Broker und vermehrt im Bridge-Betrieb - waren für mich ein Kernproblem.
        Laut Log passierte das ja quasi ununterbrochen im Minutentakt, teilweise sogar mehrfach pro Minute, obwohl man es subjektiv nicht richtig bemerkte.
        Dachte immer, dass der ESP32-C überlastet wäre - jedoch explizit durch was?
        Nur weil TLS verwendet wird und mit 1883 gab es keine Probleme.
        Konnte doch nicht sein...
        Nach tagelanger Analyse habe ich des Rätsels Lösung gefunden.
        Im Nachhinein betrachtet fast trivial – aber mein Reverse Engineering hat sich hier definitiv gelohnt.
        Die zuvor beobachteten ständigen Disconnects/Reconnects und OpenSSL-Fehler, die den ESP unbemerkt auslasteten, sind damit behoben.

        Bisher dachte ich, die kursierenden Informationen der Open-Source-Community zu Bluetooth, MQTT & Co. seien korrekt oder nur minimal fehlerbehaftet, weil sie sich an vielen Stellen fast eins zu eins gleichen.
        Dem ist aber nicht so.
        Scheinbar wurde hier oft nur voneinander abgeschrieben.


        Wenn man weiß, wie es geht, eröffnen sich ein paar sehr interessante Optionen:

        • HTTP-Server komplett abschalten (localAPIenable true/false):
          Damit lässt sich der HTTP-Port bzw. der interne Webserver gezielt ein- oder ausschalten.
          Wenn er deaktiviert ist, kann das zenSDK natürlich leider nicht genutzt werden.
          Den reglementierten, „offiziellen lokalen HA-MQTT“ möchte man aber sowieso eher nicht nutzen.
          Der Vorteil:
          Ist der Server aus, wird der ESP32-C zusätzlich entlastet.

        • Freie SoC-Grenzen:
          Es ist möglich, minSoc (5–98 %) und socSet (6–100 %) völlig frei einzustellen.
          Theoretisch beide von 0–100 %, jedoch muss socSet immer größer sein als minSoc!
          Damit lassen sich beispielsweise der Bypass (pass:0 / pass:2) und die Wirkung von socLimit indirekt steuern.
          Wenn der Webserver eingeschaltet bleibt, funktioniert das auch für zenSDK.
          Außer es wird in einer künftigen Firmware wieder geändert.
          Ich hoffe sehr, dass der Hersteller nichts ändert!
          Die Probleme mit den Verschlimmbesserungen beim SoC-Thema sollte endlich ein Ende finden.
          Hier gibt es bisher überhaupt kein Problem.
          Hoffentlich hört das Reglementieren auf..
          Aktuell getestet mit einigen Geräten <= V2.0.2 und V2.0.4 beim SF2400ACpro.


        Cloud- & Bridge-Modus:

        Bisher läuft alles tadellos über den Mosquitto-Broker im Bridgemodus zur Cloud.
        Das Ganze ist absolut flexibel:

        • Bei Bedarf muss man nur eine einzige Zeile auskommentieren und ist innerhalb von Sekunden entweder komplett lokal oder wieder mit der Cloud verbunden.

        • Das Gleiche gilt für den Bridge-Mode: Man kann sich mit der Cloud verbinden – wahlweise mit oder ohne Schreibrechte für die Cloud.

        • Bei deaktiviertem HEMS hat die Cloud von sich aus (bis jetzt) ausschließlich die Uhrzeit aktualisiert. Gut so.


        HEMS- & NVS-Verschleiß (Flash):

        Ich nutze HEMS selbst zwar nicht, habe es aber nun genauer auf dem Schirm.
        So wie es „für mich aktuell“ aussieht, werden bei der Nutzung von hemsEp sehr wohl manche Parameter direkt in den NVS geschrieben.
        Zwar nicht permanent kontinuierlich, aber bestimmte Keys landen dort.
        Das betrifft nicht nur den Hyper, sondern auch mindestens 1600ACplus und SF2400ACpro, vermutlich alle Geräte.
        Wer glaubt, dass alles ausschließlich im RAM landet, soll das gerne glauben.

        Wie geht es weiter?
        Für meinen Teil muss ich nun noch die Dokumentationen vervollständigen und ordnen.
        Werde noch einmal die Geräte – pro forma erneut, frisch – aufsetzen und im reinen, produktiven Testbetrieb beobachten und loggen.
        Evtl. widme ich mich nun noch etwas dem Thema hemsEp NVS/RAM – hat aber keine Priorität für mich, weil ich es nicht nutzen möchte.
        Weil wir gerade beim Thema NVS sind: smartMode: 1 lässt sich für mich schwerer nachverfolgen als hemsEp.
        Manches kann ich bis jetzt nicht eindeutig nachvollziehen, deshalb äußere ich mich auch nicht dazu.

        Das war es dann.

        Wer sich bedanken möchte, weiß ja, wo der Voting-Button ist. Ein extra Post dafür muss nicht sein. ;-)

        Wie bei einem langweiligen Film: Endlich Ende! :-)

        edit 21.10.26; 10:35h
        Keine Tester mehr gesucht.
        Tester und Nuzer denen ich mit mosquitto broker half, bitte per PM oder email melden. Danke.

        Testers and users whom I have assisted while they were working with the Mosquitto broker: please get in touch with me via pm or email. Thanks.

        maxclaudiM
        maxclaudiM
        maxclaudi
        schrieb am zuletzt editiert von maxclaudi
        #660

        maxclaudi sagte:

        20.10.2026; 15:18h Fortsetzung von:

        15.10.2026; 16:00 Uhr: Weiterer Bericht zum Betrieb über MQTT-Port 1883 und zenSDK POST #628

        Da es dazu weltweit quasi null Infos gibt, dachte ich, ich teile meine bisherigen Erkenntnisse mal auszugsweise als kleinen Bericht mit euch.

        Die Fortsetzung ist vermutlich gleichzeitig der Abschluss.

        Wie geht es weiter?
        ..... Evtl. widme ich mich nun noch etwas dem Thema hemsEp NVS/RAM – hat aber keine Priorität für mich, weil ich es nicht nutzen möchte.

        Erste Analysen zu HEMS.
        Interessant ist z. B. Folgendes, was den acMode betrifft, wenn man selbst über ein Script steuert:

        Auszug HEMS - Analyse (vermutlich weltweit die erste Veröffentlichung dazu 27.10.2026; 12:44h):
        Firmware enthält eine explizite zeitliche Sperre bei hemsEP mit 6 Sekunden Mindestabstand zwischen zwei Mode-Wechseln – das ist eine bewusste Entscheidung der Firmware-Autoren für genau diese eine, sicherheitsrelevante Funktion (Lademodus-Wechsel).

        Testers and users whom I have assisted while they were working with the Mosquitto broker: please get in touch with me via pm or email. Thanks.


        Edit 28.10.2026, 11:00 Uhr
        Neu: Soft-Reset – Analyse und Anwendung lokal im ioBroker mittels MQTT und zenSDK möglich – ohne dass in NVS (FLASH) geschrieben wird.

        detaillierter im Spoiler.


        Ich habe den Reset-Verarbeitungspfad in der Firmware ESP32_v8251 detailliert untersucht.
        Der untersuchte Befehl löst einen zeitverzögerten Software-Neustart des ESP32 aus und keinen Factory-Reset.
        Dabei habe ich nachvollzogen, wie der Handler den Verzögerungswert verarbeitet und wie der Timer eingerichtet wird.


        1. Was macht der Reset-Befehl?

        Der Aufruf wird vom entsprechenden Handler verarbeitet.
        Der Wert wird aus dem zugehörigen JSON-Feld gelesen und an eine Funktion übergeben, die einen einmaligen Timer einrichtet.

        Der Timer verwendet den übergebenen Wert in Millisekunden und rechnet ihn für den hardwarenahen Timer-Aufruf in Mikrosekunden um:

        timeout_us = reset × 1000

        Nach Ablauf des Timers wird die interne Neustart-Routine des ESP32 aufgerufen.
        In meinen Tests wurde bei einem eingestellten Wert von 1500 ms anschließend die MQTT-Verbindung unterbrochen und nach dem Neustart wieder aufgebaut.

        2. Was wird durch den Software-Neustart zurückgesetzt?

        Bereich / Komponente Wirkung / Zustand nach dem Soft-Reset
        ESP32-Prozessor und Tasks Der Prozessor wird neu gestartet; Tasks werden beendet und beim Booten neu angelegt.
        RAM und flüchtige Variablen Flüchtige Zustände werden neu initialisiert.
        Timer und laufende Abläufe Laufende Timer und Abläufe werden beendet und beim Booten neu eingerichtet.
        WLAN-Verbindung Wird unterbrochen und beim Neustart erneut aufgebaut.
        MQTT-Verbindung Wird unterbrochen und nach dem Neustart erneut aufgebaut.
        Laufende BLE-Verbindung Wird unterbrochen.
        Persistente Konfiguration (NVS) Wird durch den Reset-Befehl nicht gezielt gelöscht.
        Firmware und gespeicherte Einstellungen Bleiben grundsätzlich erhalten, sofern sie nicht durch einen separaten Vorgang verändert werden.

        Wichtig für die bisherigen Untersuchungen:
        Der Befehl ist kein Ersatz für einen Factory-Reset.
        Im untersuchten Reset-Pfad ist kein Aufruf zum Löschen der NVS Konfiguration erkennbar.
        Persistente Einstellungen, darunter gespeicherte Verbindungsdaten, bleiben erhalten.
        Flüchtige Zustände können nach dem Neustart andere Werte haben, weil die Firmware ihre Initialisierung erneut durchläuft.

        3. Welche Werte für den Reset sind zulässig?

        Die untersuchte Handler-Logik enthält keine erkennbare explizite Mindest- oder Höchstwertprüfung für den Verzögerungswert.
        Dieser wird aus dem JSON-Zahlenfeld gelesen und an die Timer-Funktion übergeben.

        Die Multiplikation mit 1000 erfolgt mit einer 32-Bit-Operation.
        Bei sehr großen Werten kann es deshalb zu einem arithmetischen Überlauf kommen.
        Welche Grenzwerte sich daraus ergeben, hängt davon ab, ob der Wert vorzeichenbehaftet oder vorzeichenlos verarbeitet wird.

        • 0 ms: Kann einen praktisch sofortigen Neustart auslösen. Nicht empfehlenswert.
        • 100 ms: Sehr kurze Verzögerung; wird auch im HTTP-Konfigurationspfad verwendet.
        • 1500 ms: Von mir erfolgreich getestet und für den gezielten Reset geeignet.
        • 3000 ms: Eine längere Verzögerung, wenn vor dem Neustart noch Zeit für Debug-Ausgaben oder andere laufende Vorgänge benötigt wird.
        • 60000 ms: Liegt innerhalb des 32-Bit-Bereichs, sofern die Multiplikation vorzeichenlos erfolgt. Ob dieser Wert als regulärer Anwendungsfall vorgesehen ist, ist nicht belegt.
        • Negative Werte und sehr große positive Werte: Nicht verwenden. Ihr Verhalten hängt von der Typkonvertierung und der Überlaufbehandlung ab. Ein Überlauf kann eine unerwartete Verzögerung / Fehlverhalten verursachen.

        Empfehlung:
        Für den praktischen Einsatz bei 1500 bis 3000 ms bleiben.
        Diese Werte wurden getestet beziehungsweise liegen in einem gut handhabbaren Bereich.

        4. Gibt es denselben Befehl auch über HTTP?

        Im untersuchten Firmware-Code ist die entsprechende Verarbeitung im HTTP-Handler HA.Mqtt.SetConfig zu finden.
        Nach erfolgreicher Verarbeitung der Konfiguration ruft der Handler dieselbe interne Reset-Timer-Funktion mit einem fest eingetragenen Wert von 100 ms auf.

        Damit ist die Verbindung im Code belegt:
        Der HTTP-Konfigurationsweg und der eigenständige MQTT-Reset verwenden dieselbe interne Timer-Funktion. Sie unterscheiden sich beim übergebenen Verzögerungswert.

        • HTTP HA.Mqtt.SetConfig: Löst nach erfolgreicher Konfigurationsverarbeitung einen Reset mit fest codierten 100 ms Verzögerung aus.
        • MQTT-Reset: Verwendet dieselbe Timer-Funktion; die Verzögerung wird über den übergebenen Wert in Millisekunden bestimmt.

        5. Schreib-Schutzmechanismus bei identischen Konfigurationswerten

        Bei der Analyse der Firmware ESP32_v8251 habe ich im Storage-Pfad eine entscheidende Prüfung gefunden:
        Vorhandene Einträge werden anhand des Schlüssels gesucht und mit dem neu übergebenen Wert verglichen.

        Bei den untersuchten Datentypen berücksichtigt die Vergleichsfunktion auch Länge und Inhalt.
        Bei identischen Werten liefert sie den Rückgabewert 0.
        Der Storage-Setter verzweigt daraufhin direkt zum Funktionsende, anstatt die nachgelagerte Schreiboperation aufzurufen.

        Der HTTP-Handler übergibt die Konfigurationswerte an diese Storage-Schicht.
        Werden die betreffenden Werte unverändert übermittelt, greift der beschriebene Short-Circuit.

        Ergebnis für zenSDK HTTP - HA.Mqtt.SetConfig:
        Bei identischen Werten wird die nachgelagerte Schreiboperation dieses Storage-Pfads übersprungen.
        Damit ist belegt, dass für unveränderte Werte kein neuer Schreibvorgang über diesen Pfad angefordert wird.

        Präzisierung:
        Der Short-Circuit ist im untersuchten Codepfad nachvollzogen.
        Die Aussage bezieht sich deshalb auf den nachgelagerten Schreibpfad der untersuchten Storage-Schicht.

        6. Gesamtergebnis der Analyse

        • zenSDK HTTP – HA.Mqtt.SetConfig:
          Werden identische Konfigurationsparameter gesendet, erkennt die Storage-Schicht die unveränderten Werte und überspringt den nachgelagerten Schreibpfad. Anschließend wird ein Soft-Reset mit 100 ms Verzögerung ausgelöst.

        • Zendure-App:
          Die App bietet für MQTT nur die Optionen Ein und Aus. Beim Ausschalten wird enable:false, beim Einschalten enable:true gesetzt. Auch wenn beim Einschalten keine weiteren Konfigurationsparameter geändert werden, unterscheidet sich der Enable-Wert vom vorherigen Zustand. Dadurch greift der Short-Circuit für identische Werte nicht; der Konfigurations-Schreibpfad wird durchlaufen und die Konfiguration in NVS (FLASH) gespeichert.

        • MQTT: Über einen separaten Reset-Befehl lässt sich ein Soft-Reset beispielsweise mit 1500 ms Verzögerung gezielt auslösen. Das ist ein eigenständiger Befehl, dafür muss keine Konfiguration geändert werden und schreibt nichts in NVS (Flash).

        Kurzfassung: Sowohl über HTTP mit HA.Mqtt.SetConfig und identischen Werten als auch über den direkten MQTT-Reset-Befehl lässt sich ein Soft-Reset auslösen, ohne dass dafür ein neuer Konfigurationswert geschrieben werden muss.

        Einschränkung: Dies schließt andere, gegebenenfalls interne Flash-Schreibvorgänge nicht aus, die während eines Neustarts generell stattfinden könnten.
        Belegt ist, dass bei diesen beiden Wegen kein neuer Konfigurationswert über den beschriebenen Schreibpfad (FLASH) geschrieben werden muss.

        Wie ein Soft-Reset ohne Flash-Schreibvorgang über das zenSDK möglich ist, ist hier nun veröffentlicht.
        Das genaue MQTT-Topic und das Payload habe ich per Reverse Engineering exklusiv freigelegt und gebe beides zum Schutz vor "Übernahme" und direktem Kopieren ausschließlich auf Anfrage per E-Mail oder PN weiter.

        Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

        neue Zendure Geräte Cloudless: zURLtool V2.0

        maxclaudiM 1 Antwort Letzte Antwort
        0
        • maxclaudiM maxclaudi

          maxclaudi sagte:

          20.10.2026; 15:18h Fortsetzung von:

          15.10.2026; 16:00 Uhr: Weiterer Bericht zum Betrieb über MQTT-Port 1883 und zenSDK POST #628

          Da es dazu weltweit quasi null Infos gibt, dachte ich, ich teile meine bisherigen Erkenntnisse mal auszugsweise als kleinen Bericht mit euch.

          Die Fortsetzung ist vermutlich gleichzeitig der Abschluss.

          Wie geht es weiter?
          ..... Evtl. widme ich mich nun noch etwas dem Thema hemsEp NVS/RAM – hat aber keine Priorität für mich, weil ich es nicht nutzen möchte.

          Erste Analysen zu HEMS.
          Interessant ist z. B. Folgendes, was den acMode betrifft, wenn man selbst über ein Script steuert:

          Auszug HEMS - Analyse (vermutlich weltweit die erste Veröffentlichung dazu 27.10.2026; 12:44h):
          Firmware enthält eine explizite zeitliche Sperre bei hemsEP mit 6 Sekunden Mindestabstand zwischen zwei Mode-Wechseln – das ist eine bewusste Entscheidung der Firmware-Autoren für genau diese eine, sicherheitsrelevante Funktion (Lademodus-Wechsel).

          Testers and users whom I have assisted while they were working with the Mosquitto broker: please get in touch with me via pm or email. Thanks.


          Edit 28.10.2026, 11:00 Uhr
          Neu: Soft-Reset – Analyse und Anwendung lokal im ioBroker mittels MQTT und zenSDK möglich – ohne dass in NVS (FLASH) geschrieben wird.

          detaillierter im Spoiler.


          Ich habe den Reset-Verarbeitungspfad in der Firmware ESP32_v8251 detailliert untersucht.
          Der untersuchte Befehl löst einen zeitverzögerten Software-Neustart des ESP32 aus und keinen Factory-Reset.
          Dabei habe ich nachvollzogen, wie der Handler den Verzögerungswert verarbeitet und wie der Timer eingerichtet wird.


          1. Was macht der Reset-Befehl?

          Der Aufruf wird vom entsprechenden Handler verarbeitet.
          Der Wert wird aus dem zugehörigen JSON-Feld gelesen und an eine Funktion übergeben, die einen einmaligen Timer einrichtet.

          Der Timer verwendet den übergebenen Wert in Millisekunden und rechnet ihn für den hardwarenahen Timer-Aufruf in Mikrosekunden um:

          timeout_us = reset × 1000

          Nach Ablauf des Timers wird die interne Neustart-Routine des ESP32 aufgerufen.
          In meinen Tests wurde bei einem eingestellten Wert von 1500 ms anschließend die MQTT-Verbindung unterbrochen und nach dem Neustart wieder aufgebaut.

          2. Was wird durch den Software-Neustart zurückgesetzt?

          Bereich / Komponente Wirkung / Zustand nach dem Soft-Reset
          ESP32-Prozessor und Tasks Der Prozessor wird neu gestartet; Tasks werden beendet und beim Booten neu angelegt.
          RAM und flüchtige Variablen Flüchtige Zustände werden neu initialisiert.
          Timer und laufende Abläufe Laufende Timer und Abläufe werden beendet und beim Booten neu eingerichtet.
          WLAN-Verbindung Wird unterbrochen und beim Neustart erneut aufgebaut.
          MQTT-Verbindung Wird unterbrochen und nach dem Neustart erneut aufgebaut.
          Laufende BLE-Verbindung Wird unterbrochen.
          Persistente Konfiguration (NVS) Wird durch den Reset-Befehl nicht gezielt gelöscht.
          Firmware und gespeicherte Einstellungen Bleiben grundsätzlich erhalten, sofern sie nicht durch einen separaten Vorgang verändert werden.

          Wichtig für die bisherigen Untersuchungen:
          Der Befehl ist kein Ersatz für einen Factory-Reset.
          Im untersuchten Reset-Pfad ist kein Aufruf zum Löschen der NVS Konfiguration erkennbar.
          Persistente Einstellungen, darunter gespeicherte Verbindungsdaten, bleiben erhalten.
          Flüchtige Zustände können nach dem Neustart andere Werte haben, weil die Firmware ihre Initialisierung erneut durchläuft.

          3. Welche Werte für den Reset sind zulässig?

          Die untersuchte Handler-Logik enthält keine erkennbare explizite Mindest- oder Höchstwertprüfung für den Verzögerungswert.
          Dieser wird aus dem JSON-Zahlenfeld gelesen und an die Timer-Funktion übergeben.

          Die Multiplikation mit 1000 erfolgt mit einer 32-Bit-Operation.
          Bei sehr großen Werten kann es deshalb zu einem arithmetischen Überlauf kommen.
          Welche Grenzwerte sich daraus ergeben, hängt davon ab, ob der Wert vorzeichenbehaftet oder vorzeichenlos verarbeitet wird.

          • 0 ms: Kann einen praktisch sofortigen Neustart auslösen. Nicht empfehlenswert.
          • 100 ms: Sehr kurze Verzögerung; wird auch im HTTP-Konfigurationspfad verwendet.
          • 1500 ms: Von mir erfolgreich getestet und für den gezielten Reset geeignet.
          • 3000 ms: Eine längere Verzögerung, wenn vor dem Neustart noch Zeit für Debug-Ausgaben oder andere laufende Vorgänge benötigt wird.
          • 60000 ms: Liegt innerhalb des 32-Bit-Bereichs, sofern die Multiplikation vorzeichenlos erfolgt. Ob dieser Wert als regulärer Anwendungsfall vorgesehen ist, ist nicht belegt.
          • Negative Werte und sehr große positive Werte: Nicht verwenden. Ihr Verhalten hängt von der Typkonvertierung und der Überlaufbehandlung ab. Ein Überlauf kann eine unerwartete Verzögerung / Fehlverhalten verursachen.

          Empfehlung:
          Für den praktischen Einsatz bei 1500 bis 3000 ms bleiben.
          Diese Werte wurden getestet beziehungsweise liegen in einem gut handhabbaren Bereich.

          4. Gibt es denselben Befehl auch über HTTP?

          Im untersuchten Firmware-Code ist die entsprechende Verarbeitung im HTTP-Handler HA.Mqtt.SetConfig zu finden.
          Nach erfolgreicher Verarbeitung der Konfiguration ruft der Handler dieselbe interne Reset-Timer-Funktion mit einem fest eingetragenen Wert von 100 ms auf.

          Damit ist die Verbindung im Code belegt:
          Der HTTP-Konfigurationsweg und der eigenständige MQTT-Reset verwenden dieselbe interne Timer-Funktion. Sie unterscheiden sich beim übergebenen Verzögerungswert.

          • HTTP HA.Mqtt.SetConfig: Löst nach erfolgreicher Konfigurationsverarbeitung einen Reset mit fest codierten 100 ms Verzögerung aus.
          • MQTT-Reset: Verwendet dieselbe Timer-Funktion; die Verzögerung wird über den übergebenen Wert in Millisekunden bestimmt.

          5. Schreib-Schutzmechanismus bei identischen Konfigurationswerten

          Bei der Analyse der Firmware ESP32_v8251 habe ich im Storage-Pfad eine entscheidende Prüfung gefunden:
          Vorhandene Einträge werden anhand des Schlüssels gesucht und mit dem neu übergebenen Wert verglichen.

          Bei den untersuchten Datentypen berücksichtigt die Vergleichsfunktion auch Länge und Inhalt.
          Bei identischen Werten liefert sie den Rückgabewert 0.
          Der Storage-Setter verzweigt daraufhin direkt zum Funktionsende, anstatt die nachgelagerte Schreiboperation aufzurufen.

          Der HTTP-Handler übergibt die Konfigurationswerte an diese Storage-Schicht.
          Werden die betreffenden Werte unverändert übermittelt, greift der beschriebene Short-Circuit.

          Ergebnis für zenSDK HTTP - HA.Mqtt.SetConfig:
          Bei identischen Werten wird die nachgelagerte Schreiboperation dieses Storage-Pfads übersprungen.
          Damit ist belegt, dass für unveränderte Werte kein neuer Schreibvorgang über diesen Pfad angefordert wird.

          Präzisierung:
          Der Short-Circuit ist im untersuchten Codepfad nachvollzogen.
          Die Aussage bezieht sich deshalb auf den nachgelagerten Schreibpfad der untersuchten Storage-Schicht.

          6. Gesamtergebnis der Analyse

          • zenSDK HTTP – HA.Mqtt.SetConfig:
            Werden identische Konfigurationsparameter gesendet, erkennt die Storage-Schicht die unveränderten Werte und überspringt den nachgelagerten Schreibpfad. Anschließend wird ein Soft-Reset mit 100 ms Verzögerung ausgelöst.

          • Zendure-App:
            Die App bietet für MQTT nur die Optionen Ein und Aus. Beim Ausschalten wird enable:false, beim Einschalten enable:true gesetzt. Auch wenn beim Einschalten keine weiteren Konfigurationsparameter geändert werden, unterscheidet sich der Enable-Wert vom vorherigen Zustand. Dadurch greift der Short-Circuit für identische Werte nicht; der Konfigurations-Schreibpfad wird durchlaufen und die Konfiguration in NVS (FLASH) gespeichert.

          • MQTT: Über einen separaten Reset-Befehl lässt sich ein Soft-Reset beispielsweise mit 1500 ms Verzögerung gezielt auslösen. Das ist ein eigenständiger Befehl, dafür muss keine Konfiguration geändert werden und schreibt nichts in NVS (Flash).

          Kurzfassung: Sowohl über HTTP mit HA.Mqtt.SetConfig und identischen Werten als auch über den direkten MQTT-Reset-Befehl lässt sich ein Soft-Reset auslösen, ohne dass dafür ein neuer Konfigurationswert geschrieben werden muss.

          Einschränkung: Dies schließt andere, gegebenenfalls interne Flash-Schreibvorgänge nicht aus, die während eines Neustarts generell stattfinden könnten.
          Belegt ist, dass bei diesen beiden Wegen kein neuer Konfigurationswert über den beschriebenen Schreibpfad (FLASH) geschrieben werden muss.

          Wie ein Soft-Reset ohne Flash-Schreibvorgang über das zenSDK möglich ist, ist hier nun veröffentlicht.
          Das genaue MQTT-Topic und das Payload habe ich per Reverse Engineering exklusiv freigelegt und gebe beides zum Schutz vor "Übernahme" und direktem Kopieren ausschließlich auf Anfrage per E-Mail oder PN weiter.

          maxclaudiM
          maxclaudiM
          maxclaudi
          schrieb am zuletzt editiert von maxclaudi
          #661

          Wer zusätzlich einen Reset-Button bzw. einen Datenpunkt für einen Soft-Reset anlegen möchte.
          Hier ist das bisherige zenSDK-Steuerungs-Script, erweitert um eine Soft-Reset-Funktion für das Gerät.
          Der Reset bzw. Neustart wird ausgeführt, ohne in den Flash-Speicher zu schreiben.

          Dazu wird unter control ein neuer Datenpunkt angelegt:
          control.deviceReset

          // ioBroker JavaScript: Zendure zenSDK Adapter-Ersatz für ein Zendure-Gerät.
          // Für alle Geräte ab 2025/2026, die zenSDK unterstützen, wie z. B.:
          // SF800, SF800 PLUS, 800Pro (2), 1600AC, SF2400AC(+) usw.
          // (c) maxclaudi 2026.05.02_00.39h für das ioBroker-Forum; update 24.05.26 00:15h globale Typsicherung.
          // update axios 21.06.26 00:10h. Danke paul53 für die Idee und das Mitwirken.																			  
          // update 22.06.26 20:00h API3 und API2 Unterstützung; update 25.06.26 10.30h control FAN
          // update 06.10.26 02:00h neu set batCalTime, fix acMode/auto_in_out_Limit - Übernehmer von Logiken etc. könnten sich auch mal "wenigstens" bedanken!
          // update 09.10.26 11:30h acMode/auto_in_out_Limit Standardwert Intervall Danke Danitz und petman für die Hinweise.
          // update 16.10.26 21:00h batCalTime zusätzliche Type-Prüfung; fallback 01:00h / control.socCompSwitch ohne Funktion, entfernt
          // update 28.10.26 15:30h deviceReset unter control. Löst einen Geräte Soft-Reset aus ohne in den Flash zu schreiben.
          // 
          // In memory of Daisy 02.05.24 – miss you.
          //
          //
          // 
          // EIN WORT IN EIGENER SACHE:
          // Dieses Skript basiert auf vielen Stunden Hardware-Tests und Analysen (besonders zum Flash-Schutz).
          // In der Vergangenheit wurden meine Erkenntnisse oft ohne Erwähnung in andere Projekte übernommen.
          // Ich teile diesen Code gerne. Wer diese Logik in öffentliche Adapter integriert, ist herzlich 
          // eingeladen – ich bitte jedoch um die Fairness, die Quelle zu nennen. 
          // Das ist der "Lohn" für meine Zeit und Forschung.
          //----------------------------------------------------------------------------------------------------
          //----------------------------------------------------------------------------------------------------
          // Konfiguration
          //
          // Hinweis bei mehreren Zendure-Geräten:
          //   -> Für jedes Gerät ein eigenes Skript mit individueller Konfiguration verwenden!
          //   -> IP-Adresse anpassen.
          //   -> maxInputLimit / maxOutputLimit (abhängig vom Gerätetyp) einstellen.
          //   -> Intervall: getIntervalMs = 5000 ms (Standard). 
          //      Bei Verbindungsproblemen oder WLAN-Lags wird ein Wert von mindestens 8000 ms empfohlen.
          //
          // Empfehlung zur Geräteanzahl:
          //   • Bis zu 3 Geräte: völlig unkritisch.
          //   • 4 Geräte: problemlos möglich.
          //   • Mehr als 4 Geräte: nicht empfohlen. Aber abhängig von Systemleistung und Intervall prüfen.
          //
          // Das offizielle MQTT ist mit einer Aktualisierungsrate von bis zu 90 Sek. zu langsam.
          // Ich empfehle die Nutzung des zenSDK.
          // Hinweis: Das (De-)Aktivieren von MQTT sowie die MQTT-Verbindungsüberwachung werden nicht unterstützt.
          //
          // Damit neue Datenpunkte automatisch aus dem JSON erstellt werden können:
          // -> Instanzen -> JavaScript-Adapter -> Allgemeine Einstellungen -> "Kommando 'setObject' erlauben" aktivieren!
          //
          // Bei kurzem Abfrageintervall:
          // -> Instanzen -> JavaScript-Adapter -> Allgemeine Einstellungen -> 
          //    "Maximale setState-Anfragen pro Minute pro Skript" auf 5000 erhöhen.
          //    (Testweise reicht ein geringerer Wert wie 2000; das Skript wurde optimiert).
          //
          // Datenpunkte im Ordner "control" werden automatisch aktualisiert.
          //----------------------------------------------------------------------------------------------------
          //----------------------------------------------------------------------------------------------------
          // CONFIG
          //----------------------------------------------------------------------------------------------------
           
          // IP Zendure Gerät
          //Beispiel: const IP = "192.168.40.20";
          //const IP = "192.168.40.20";  // IP des Zendure Geräts
          const IP = "192.168.40.20";  // IP des Zendure Geräts
           
          // Bitte sicherstellen:
          // richtige IP verwendet?
          // Im Router dem Zendure-Gerät eine feste, dauerhafte IP zuweisen!
           
          //maximum inputLimit -> Maximal mögliche (unterstützte) Ladeleistung des Zendure-Geräts 
          //Beispiele: SF800 800W / SF800 PRO: 1000W / 1600AC: 1600 / SF2400AC: 2400W
          const maxInputLimit = 2400;
           
          //maximum outputLimit -> Maximal möglich (unterstützte) Entladeleistung des Zendure-Geräts
          //Beispiele: SF800 800W / SF800 PRO: 800W / 1600AC: 1600 / SF2400AC: 2400W
          const maxOutputLimit = 2400;
           
          // Haupt-Verzeichnis für das Zendure-Gerät
          // Der Name für das Hauptverzeichnis kann frei gewählt werden.
          // Bitte kein "+", keine Sonderzeichen und keine Umlaute etc. verwenden!
          // Muss aber bei mehreren Skripten und Zendure-Geräte unterschiedlich sein.
          // Beispiel für "2400ACproZENsdk":
          //const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
          const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
           
          // Timout Handler HTTP GET /POST
          // Wichtig:
          // getTimeoutMs < getIntervalMs
          // postTimeoutMs < getIntervalMs
          // Bei instabiler Verbindung ggf. erhöhen (z. B. 3000–5000 ms).
          const getTimeoutMs = 1500;  // Empfehlung: 2000 ms.
          const postTimeoutMs = 1500; // Empfehlung: 2000 ms.
           
          // GET intervall
          // Empfehlung: >= 5000 ms.
          // Kleinere Werte möglich, aber kleiner 4000 m. M. nach sinnfrei und nicht empfohlen (Last / Stabilität).
          // HINWEIS: Original Firmwarecode verifiziert, dass z.B. bei MQTT Standard alle 5000ms ein report published wird.
          // wenn noch schneller abgefragt wird, kann es vorkommen, dass alte/identische Werte (hardwarebedingt) empfangen werden.
          const getIntervalMs = 5000; // Intervall für GET 5000ms. Bei Problemen erhöhen. NICHT < 5000!
           
          //----------------------------------------------------------------------------------------------------
          // END CONFIG
          //----------------------------------------------------------------------------------------------------
           
          let rxNewRam = "";
          let rxOldRam = "";
          let rxDiffRam = "";
          let txRam = "";
           
          const dpSmartRAM = folderZendureApi + ".control.automaticKonfig.input_output_LimitMode_smartMode_RAM";
          const dpSmartWatcher = folderZendureApi + ".control.automaticKonfig.smartModeWatcher";
          const dpDeviceReset = folderZendureApi + ".control.deviceReset";
           
          // helper
          function getRxNew() { return rxNewRam; }
          function setRxNew(val) {
              rxNewRam = val;
          }
           
          function getRxOld() { return rxOldRam; }
          function setRxOld(val) {
              rxOldRam = val;
          }
           
          function getRxDiff() { return rxDiffRam; }
          function setRxDiff(val) {
              rxDiffRam = val;
          }
           
          function setTx(val) {
              txRam = val;
          }
           
          const axios = require('axios'); 
          const postCooldownMs = 2000;
          const batchWindowMs = 300;
           
          let postActive = false;
          let postQueue = {};
          let batchTimer = null;
          let getTimer = null;
           
          let retryCount = 0; 
          const maxRetries = 2; 
          let getErrorCount = 0; 
          let SN = '';
          
          // Device-Reset (MQTT-Konfig lesen und setzen)
          const resetTimeoutMs = 5000;
          const resetWaitMaxMs = 20000;
          let getActive = false;
          let deviceResetBusy = false;
           
          // Struktur sicherstellen
          setTimeout(() => {
              ensureStructure();
          }, 100);
           
          function ensureStructure() {
              const folders = [
                  folderZendureApi,
                  folderZendureApi + ".properties",
                  folderZendureApi + ".packData",
                  folderZendureApi + ".control",
                  folderZendureApi + ".control.automaticKonfig"
              ];
              folders.forEach(path => {
                  if (!existsObject(path)) {
                      setObject(path, {
                          type: "folder",
                          common: { name: path.split(".").pop() },
                          native: {}
                      });
                  }
              });
              if (!existsState(dpSmartRAM)) {
                  createState(dpSmartRAM, false, { name: "Input/Output Limit Mode SmartMode RAM", type: "boolean", role: "switch", read: true, write: true });
              }
              if (!existsState(dpSmartWatcher)) {
                  createState(dpSmartWatcher, false, { name: "Smart Mode Watcher", type: "boolean", role: "switch", read: true, write: true });
              }
          	if (existsState(dpSmartRAM)) setState(dpSmartRAM, false, true);
          	if (existsState(dpSmartWatcher)) setState(dpSmartWatcher, false, true);
          	if (existsState(dpDeviceReset)) setState(dpDeviceReset, false, true);
          }
           
          function hhmmToMinutes(str) {
              const m = /^(\d{1,2}):(\d{2})$/.exec(String(str).trim());
              if (!m) return 60;
              const total = (parseInt(m[1], 10) * 60) + parseInt(m[2], 10);
              return (total > 1439) ? 60 : total;
          }
          function minutesToHhmm(min) {
              const n = Math.round(Number(min));
              if (!Number.isFinite(n) || n < 0 || n > 1439) return String(min);
              return String(Math.floor(n / 60)).padStart(2, "0") + ":" + String(n % 60).padStart(2, "0");
          }
           
          // Control
          const schemaControl = {
          	acMode: {
                  name: "set acMode; 1: input charge, 2: output discharge",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 2,
                  min: 1,
                  max: 2,
                  apiKey: "acMode",
              },
          	batCalTime: {
                  name: "set Calibration-Start-Time: 00:00 to 23:59 - on error: 01:00h (HH:MM)",
                  role: "text",
          		unit: "h",
                  type: "string",
                  write: true,
          		def: "01:00",
          		pattern: /^([01][0-9]|2[0-3]):[0-5][0-9]$/,
                  apiKey: "batCalTime",
          		transformWrite: (val) => hhmmToMinutes(val),
          		transformRead: (val) => minutesToHhmm(val),
              },
          	chargeMaxLimit: {
                  name: "set Charge Max Limit",
                  unit: "W",
                  role: "level",
                  type: "number",
                  write: true,
          		def: maxInputLimit,
                  min: 0,
                  max: maxInputLimit,
                  apiKey: "chargeMaxLimit",
              },	
          	Fanmode: {
          		name: "set Fanmode; 0: Fan OFF, 1: Fan ON",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 1,
                  min: 0,
                  max: 1,
                  apiKey: "Fanmode",
              },
          	Fanspeed: {
          		name: "set Fanspeed; 0: Automatic, 1: 1st gear, 2: 2nd gear",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 0,
                  min: 0,
                  max: 2,
                  apiKey: "Fanspeed",
              },
          	gridOffMode: {
                  name: "set gridOffMode; 0: Normal mode, 1: Economic mode, 2: OFF",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 2,
                  min: 0,
                  max: 2,
                  apiKey: "gridOffMode",
              },
          	gridReverse: {
                  name: "set gridReverse; 0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 0,
                  min: 0,
                  max: 2,
                  apiKey: "gridReverse",
              },
          	gridStandard: {
                  name: "set gridStandard; 0: Germany 1: France 2: Austria 3: Switzerland 4: Netherlands 5: Spain 6: Belgium 7: Greece 8: Denmark 9: Italy",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 0,
                  min: 0,
                  max: 9,
                  apiKey: "gridStandard",
              },
          	inputLimit: {
                  name: "set inputLimit",
                  unit: "W",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 0,
                  min: 0,
                  max: maxInputLimit,
                  apiKey: "inputLimit",
              },
          	inverseMaxPower: {
                  name: "set inverseMaxPower; Max inverter output Power Limit",
                  unit: "W",
                  role: "level",
                  type: "number",
                  write: true,
          		def: maxOutputLimit,
                  min: 200,
                  max: maxOutputLimit,
          		step: 100,
                  apiKey: "inverseMaxPower",
              },
          	minSoc: {
                  name: "set minSoc; minimum SOC 0-50",
                  unit: "%",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 10,
                  min: 0,
                  max: 50,
                  apiKey: "minSoc",
                  transformWrite: (val) => val * 10
              },
          	outputLimit: {
                  name: "set outputLimit",
                  unit: "W",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 0,
                  min: 0,
                  max: maxOutputLimit,
                  apiKey: "outputLimit",
              },
          	smartMode: {
                  name: "set smartMode; parameter are written to: 1: RAM / 0: Flash",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 0,
                  min: 0,
                  max: 1,
                  apiKey: "smartMode",
              },
          	socSet: {
                  name: "set socSet; SOC Target 70%-100%",
                  unit: "%",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 100,
                  min: 70,
                  max: 100,
                  apiKey: "socSet",
                  transformWrite: (val) => val * 10
              },
          	lampSwitch: {
                  name: "set lamp switch; 0: lamp off / 1: lamp on",
                  role: "level",
                  type: "number",
                  write: true,
          		def: 1,
                  min: 0,
                  max: 1,
                  apiKey: "lampSwitch",
              }
          };
           
          createControlStates();
           
          function createControlStates() {
              const base = folderZendureApi + ".control";
              Object.keys(schemaControl).forEach(key => {
                  const def = schemaControl[key];
                  const dp = `${base}.${key}`;
                  if (!existsState(dp)) {
                      createState(dp, def.type === "string" ? def.def : 0, {
                          name: def.name, type: def.type, role: def.role, unit: def.unit,
                          read: true, write: true, def: def.def, min: def.min, max: def.max
                      });
                  }
              });
          	
          	const extraStates = [
          		{ id: `${base}.auto_inputLimitMode`, name: "Set InputLimit (Automatic)", unit: "W", min: 0, max: maxInputLimit },
          		{ id: `${base}.auto_outputLimitMode`, name: "Set OutputLimit (Automatic)", unit: "W", min: 0, max: maxOutputLimit },
          		{ id: `${base}.auto_in_out_Limit`, name: "Set In/Out-Limit-Automatic: Negative: Charging; Positive: Discharging", unit: "W", min: (maxInputLimit * -1), max: maxOutputLimit }
          	];
          	extraStates.forEach(def => {
          		if (!existsState(def.id)) {
          			createState(def.id, 0, {
          				name: def.name, type: "number", role: "level", unit: def.unit,
          				read: true, write: true, min: def.min, max: def.max
          			});
          		}
          	});
          }
           
          // dp Device-Reset
          if (!existsState(dpDeviceReset)) {
              createState(dpDeviceReset, false, {
                  name: "deviceReset; true: MQTT-Konfiguration per HA.Mqtt.GetConfig lesen und per HA.Mqtt.SetConfig erneut setzen",
                  type: "boolean", role: "button", read: true, write: true, def: false
              });
          }
          
          on({ id: new RegExp(`^${folderZendureApi}\\.control\\.`), change: "ne" }, obj => {
              if (obj.state.ack) return;
              const key = obj.id.split('.').pop();
              if (key === "deviceReset") {
                  if (obj.state.val === true) deviceReset();
                  return;
              }
              if (key === "auto_inputLimitMode") {
                  let val = Math.round(Number(obj.state.val)) || 0;
                  if (val < 0) val = 0;
                  if (val > maxInputLimit) val = maxInputLimit;
                  handleInputLimitMode(val);
                  setState(obj.id, val, true);
                  return;
              }
              if (key === "auto_outputLimitMode") {
                  let val = Math.round(Number(obj.state.val)) || 0;
                  if (val < 0) val = 0;
                  if (val > maxOutputLimit) val = maxOutputLimit;
                  handleOutputLimitMode(val);
                  setState(obj.id, val, true);
                  return;
              }
          	if (key === "auto_in_out_Limit") {
          		let val = Math.round(Number(obj.state.val)) || 0;
          		if (val > maxOutputLimit) val = maxOutputLimit;
          		if ((-val) > maxInputLimit) val = -maxInputLimit;
          		handleAuto_in_out_Limit(val);
          		setState(obj.id, val, true);
          		return;
          	}
              const def = schemaControl[key];
              if (!def) return;
              let val = obj.state.val;
              if (def.type === "number") {
                  val = Math.round(Number(val)) || 0;
              }
              if (def.type === "string" && def.pattern) {
                  val = String(val).trim();
                  if (!def.pattern.test(val)) {
                      log(`${key}: Fehlerhafte Eingabe "${val}"; Standard ${def.def} wird verwendet.`, "info");
                      val = def.def;
                  }
              }
              if (def.min !== undefined && val < def.min) val = def.min;
              if (def.max !== undefined && val > def.max) val = def.max;
          	let sendVal = val;
          	if (def.transformWrite) { sendVal = def.transformWrite(val); }
              if (key === "batCalTime" && getState(folderZendureApi + ".properties.batCalTime")?.val === sendVal) {
                  setState(obj.id, val, true); // Gerät hat den Wert schon -> nichts senden
                  return;
              }
              sendToDevice(def.apiKey, sendVal);
              setState(obj.id, val, true);
          });
           
          function sendToDevice(apiKey, value) {
          	if (!SN || SN === "null" || SN === "") return;
              const payload = { sn: SN, properties: {} };
              payload.properties[apiKey] = value;
              setTx(JSON.stringify(payload));
              queuePost({ [apiKey]: value });
          }
           
          function queuePost(obj) {
              Object.assign(postQueue, obj);
              if (batchTimer) return;
              batchTimer = setTimeout(() => {
                  batchTimer = null;
                  executePost();
              }, batchWindowMs);
          }
           
          function executePost() {
              if (postActive || deviceResetBusy) return; // während Reset bleibt die Queue erhalten
              if (Object.keys(postQueue).length === 0) return;
              postActive = true;
              stopGetLoop();  
              const payload = { sn: SN, properties: { ...postQueue } };
              postQueue = {};
              setTx(JSON.stringify(payload));
              axios
              .post(`http://${IP}/properties/write`, payload, { 
                  timeout: postTimeoutMs, 
                  responseType: "json" 
              })
              .then((response) => {
                  const data = response.data;
                  if (data && data.success === true && data.code === 200) {
                      retryCount = 0;
                      finishPost(true);
                  } else {
                      throw new Error("Zendure success/code invalid"); 
                  }
              })
              .catch((err) => {
                  retryCount++;
                  if (retryCount <= maxRetries) {
                      log(`POST Retry ${retryCount}/${maxRetries} (${err.message || 'Zendure Error'})`, "warn");
                      setTimeout(() => {
                          postQueue = { ...payload.properties, ...postQueue };
                          postActive = false;
                          executePost();
                      }, postCooldownMs);
                      return;
                  }
                  log("POST endgültig fehlgeschlagen", "error");
                  retryCount = 0;
                  finishPost(false);
              });
          }
           
          function finishPost(success) {
              if (!success) { syncControlFromProperties(); }
          	setTimeout(() => {
                  postActive = false;
                  if (deviceResetBusy) return; // Reset übernimmt + startet GET-Zyklus selbst wieder
                  startGetLoop();
              }, postCooldownMs);
          	}
           
          function syncAutoInOutLimit(diffProps) {
              const prop = folderZendureApi + ".properties.";
              const pick = (k) => (diffProps && diffProps[k] !== undefined) ? diffProps[k] : getState(prop + k)?.val;
              const acMode = pick("acMode");
              const inp = pick("inputLimit");
              const out = pick("outputLimit");
              let val;
              if (acMode === 2 && out !== undefined) val = out;
              else if (acMode === 1 && inp !== undefined) val = -inp;
              else if (acMode === 0) val = 0;
              else return;
              val = val || 0; // -0 vermeiden
              const id = folderZendureApi + ".control.auto_in_out_Limit";
              if (getState(id)?.val !== val) setState(id, val, true);
          }
           
          function syncControlFromProperties() {
              const baseCtrl = folderZendureApi + ".control";
              const baseProp = folderZendureApi + ".properties";
              Object.keys(schemaControl).forEach(ctrlKey => {
                  const def = schemaControl[ctrlKey];
                  const apiKey = def.apiKey || ctrlKey;
                  const propState = getState(`${baseProp}.${apiKey}`);
                  if (!propState || propState.val === undefined) return;
                  const ctrlId = `${baseCtrl}.${ctrlKey}`;
          		const ctrlState = getState(ctrlId);
          		const ctrlVal = ctrlState ? ctrlState.val : undefined;
                  const propVal = def.transformRead ? def.transformRead(propState.val) : propState.val;
                  if (ctrlVal !== propVal) { setState(ctrlId, propVal, true); }
              });
              const inputProp = getState(`${baseProp}.inputLimit`)?.val;
              if (inputProp !== undefined) {
                  const id = `${baseCtrl}.auto_inputLimitMode`;
                  if (getState(id)?.val !== inputProp) {
                      setState(id, inputProp, true);
                  }
              }
              const outputProp = getState(`${baseProp}.outputLimit`)?.val;
              if (outputProp !== undefined) {
                  const id = `${baseCtrl}.auto_outputLimitMode`;
                  if (getState(id)?.val !== outputProp) {
                      setState(id, outputProp, true);
                  }
              }
          	syncAutoInOutLimit(null);
          }
           
          function startGetLoop() {
              if (getTimer) { clearInterval(getTimer); }
              getTimer = setInterval(() => {
                  if (postActive || deviceResetBusy || getActive) return;
                  getActive = true;
                  const url = `http://${IP}/properties/report`;
                  axios
                  .get(url, { timeout: getTimeoutMs, responseType: 'text' })
                  .then(function (response) {
                      if (getErrorCount > 0) log("Verbindung wieder OK", "info");
                      getErrorCount = 0;
                      if (!response.data) return;
                      setRxNew(response.data);
                      handleRxNewUpdate(response.data);
                  })
                  .catch(function (err) {
                      const errorMsg = err.response ? err.response.status : err.message;
                      getErrorCount++;
                      if (getErrorCount <= 3) log(`GET Fehler (${getErrorCount}): ${errorMsg}`, "info");
                      if (getErrorCount === 4) log("Keine Verbindung möglich. Zendure-Geräte IP prüfen!", "error");
                  })
                  .finally(function () { getActive = false; });
              }, getIntervalMs);
          }
           
          function stopGetLoop() { if (getTimer) { clearInterval(getTimer); getTimer = null; } }
          
          // Device-Reset: aktuelle Konfiguration lesen und erneut setzen, ohne parallele reguläre GET/POST.
          function waitFor(conditionFn, maxMs, stepMs = 100) {
              return new Promise(resolve => {
                  if (conditionFn()) { resolve(true); return; }
                  const start = Date.now();
                  const t = setInterval(() => {
                      if (conditionFn()) { clearInterval(t); resolve(true); }
                      else if (Date.now() - start >= maxMs) { clearInterval(t); resolve(false); }
                  }, stepMs);
              });
          }
          
          async function deviceReset() {
              if (deviceResetBusy) return;
              deviceResetBusy = true;
              stopGetLoop();
              try {
                  const idle = await waitFor(() => !getActive && !postActive, resetWaitMaxMs);
                  if (!idle) {
                      log("Device-Reset abgebrochen: laufende HTTP-Anfragen wurden nicht rechtzeitig beendet", "warn");
                      return;
                  }
                  if (!SN || SN === "null") {
                      log("Device-Reset abgebrochen: Seriennummer (SN) noch nicht bekannt", "warn");
                      return;
                  }
                  const rpcUrl = `http://${IP}/rpc`;
          
                  const getResponse = await axios.get(rpcUrl + "?method=HA.Mqtt.GetConfig", { timeout: resetTimeoutMs });
                  const response = getResponse.data;
                  if (!response || response.result !== true || !response.data) {
                      log("Device-Reset: ungültige GET-Antwort: " + JSON.stringify(response), "warn");
                      return;
                  }
                  const current = response.data;
                  //log("Device-Reset GET-Konfiguration: " + JSON.stringify({ ...current, password: "***" }), "info");
          
                  const match = String(current.server).match(/^(mqtt|mqtts):\/\/([^:/]+)(?::(\d+))?$/i);
                  if (!match) {
                      log("Device-Reset: Serveradresse nicht erkannt: " + current.server, "warn");
                      return;
                  }
                  const protocol = match[1];
                  const server = match[2];
                  const port = match[3] ? Number(match[3]) : (protocol.toLowerCase() === "mqtts" ? 8883 : 1883);
          
                  const payload = {
                      sn: SN,
                      method: "HA.Mqtt.SetConfig",
                      params: {
                          config: {
                              enable: current.enable,
                              server: server,
                              port: port,
                              protocol: protocol,
                              username: current.username,
                              password: current.password
                          }
                      }
                  };
                  const postResponse = await axios.post(rpcUrl, payload, {
                      headers: { "Content-Type": "application/json" },
                      timeout: resetTimeoutMs
                  });
              } catch (error) {
                  if (error.response) {
                      log("Device-Reset HTTP-Status: " + error.response.status, "warn");
                      log("Device-Reset Antwort: " + JSON.stringify(error.response.data), "warn");
                  } else {
                      log("Device-Reset Fehler: " + error.message, "error");
                  }
              } finally {
                  deviceResetBusy = false;
                  setState(dpDeviceReset, false, true);
                  if (Object.keys(postQueue).length > 0) { executePost(); } else { startGetLoop(); }
              }
          }
           
          function handleInputLimitMode(val) {
              const currentOutput = Number(getState(folderZendureApi + ".properties.outputLimit")?.val);
              const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
              const smartRAMActive = getState(dpSmartRAM)?.val;
              const payload = {};
           
              payload.acMode = 1;
              if (currentOutput !== 0) payload.outputLimit = 0;
              if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
              payload.inputLimit = val;
              sendBatch(payload);
          }
           
          function handleOutputLimitMode(val) {
              const currentInput = Number(getState(folderZendureApi + ".properties.inputLimit")?.val);
              const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
              const smartRAMActive = getState(dpSmartRAM)?.val;
              const payload = {};
           
              payload.acMode = 2;
              if (currentInput !== 0) payload.inputLimit = 0;
              if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
              payload.outputLimit = val;
              sendBatch(payload);
          }
           
          function handleAuto_in_out_Limit(val) {
              const currentInput = Number(getState(folderZendureApi + ".properties.inputLimit")?.val);
              const currentOutput = Number(getState(folderZendureApi + ".properties.outputLimit")?.val);
              const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
              const smartRAMActive = getState(dpSmartRAM)?.val;
              const payload = {};
           
              if (val === 0) {
                  if (currentInput !== 0) payload.inputLimit = 0;
                  if (currentOutput !== 0) payload.outputLimit = 0;
              } else if (val > 0) {
                  payload.acMode = 2;
                  if (currentInput !== 0) payload.inputLimit = 0;
                  payload.outputLimit = val;
              } else if (val < 0) {
                  payload.acMode = 1;
                  if (currentOutput !== 0) payload.outputLimit = 0;
                  payload.inputLimit = Math.abs(val);
              }
              if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
              if (Object.keys(payload).length > 0) sendBatch(payload);
          }
           
          function sendBatch(properties) {
              if (!SN || SN === "null" || SN === "") return;
              const payload = { sn: SN, properties: properties };
              setTx(JSON.stringify(payload)); 
          	queuePost(properties); 
          }
           
          const schema = {
              properties: {
                  BatVolt: {
                      name: "BatVolt - Average Battery Voltage of all Batteries",
                      unit: "V",
                      role: "value.voltage",
                      type: "number"
                  },
          		Fanmode: {
                      name: "Fanmode - 0: Fan off, 1: Fan on",
                      role: "value",
                      type: "number"
                  },	
          		Fanspeed: {
                      name: "Fanspeed - 0: Auto, 1: 1st gear, 2: 2nd gear",
                      role: "value",
                      type: "number"
                  },
          		acMode: {
                      name: "acMode - 1: input charge / 2: output discharge",
                      role: "value",
                      type: "number"
                  },
          		acStatus: {
                      name: "AC state; 0: idle, 1: AC Flow out, 2: AC Flow in",
                      role: "value",
                      type: "number"
                  },
          		batCalTime: {
                      name: "batCalTime - Battery Calibration Start-Time",
          			unit: "min",
                      role: "value",
                      type: "number"
                  },
          		chargeMaxLimit: {
                      name: "chargeMaxLimit - Max charge power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		dataReady: {
                      name: "Data ready flag - 0: Data not ready, 1: Data ready",
                      role: "value",
                      type: "number"
                  },
          		dcStatus: {
                      name: "DC State; 0: idle, 1: DC Flow out, 2: DC Flow in",
                      role: "value",
                      type: "number"
                  },
          		electricLevel: {
                      name: "electricLevel - Average SOC of all Batterys",
                      unit: "%",
                      role: "value.battery",
                      type: "number"
                  },
          		gridInputPower: {
                      name: "gridInputPower - Grid Input Power to Battery",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		gridOffMode: {
                      name: "gridOffMode - 0: Normal mode, 1: Economic mode, 2: OFF",
                      role: "value",
                      type: "number"
                  },
          		gridOffPower: {
                      name: "gridOffPower - Off-grid power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		gridReverse: {
                      name: "gridReverse - 0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow",
                      role: "value",
                      type: "number"
                  },
          		gridStandard: {
                      name: "gridStandard - 0: Germany 1: France 2: Austria 3: Switzerland 4: Netherlands 5: Spain 6: Belgium 7: Greece 8: Denmark 9: Italy",
                      role: "value",
                      type: "number"
                  },
          		gridState: {
                      name: "gridState - Grid connection state, 0: Not connected, 1: Connected",
                      role: "value",
                      type: "number"
                  },
          		heatState: {
                      name: "heatState - Battery Heat State, 0: Not heating, 1: heating",
                      role: "value",
                      type: "number"
                  },
          		hyperTmp: {
                      name: "Temperature (Zendure-Device)",
                      unit: "°C",
                      role: "value.temperature",
                      type: "number"
                  },
          		IOTState: {
                      name: "Network 0: disconnected, 1: is being configured, 2: Connection successful",
                      role: "value",
                      type: "number"
                  },
          		inputLimit: {
                      name: "inputLimit - AC charging power limit to Battery",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		inverseMaxPower: {
                      name: "inverseMaxPower - Max inverter output Power Limit",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		lampSwitch: {
                      name: "lampSwitch - Lamp state, 0: lamp off / 1: lamp on",
                      role: "value",
                      type: "number"
                  },
          		minSoc: {
                      name: "minSoc - Minimum SOC 0%-50%",
                      unit: "%",
                      role: "value",
                      type: "number"
                  },
          		outputHomePower: {
                      name: "outputHomePower - Output to home",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		outputLimit: {
                      name: "outputLimit - Output power limit",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		outputPackPower: {
                      name: "outputPackPower - Battery charge power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		packInputPower: {
                      name: "packInputPower - Battery discharge power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		packNum: {
                      name: "packNum - Number of batteries",
                      role: "value",
                      type: "number"
                  },	
          		packState: {
                      name: "packState - Battery State, 0: Standby, 1: Charging, 2: Discharging",
                      role: "value",
                      type: "number"
                  },
          		pass: {
                      name: "pass - 0: Bypass Automatic, 1: Bypass OFF, 2: Bypass ON, 3: unknown",
                      role: "value",
                      type: "number"
                  },
          		pvStatus: {
                      name: "pvStatus - PV State producing, 0: Stopped, 1: Running",
                      role: "value",
                      type: "number"
                  },
          		remainOutTime: {
                      name: "remainOutTime - Estimated discharge time in minutes, if not predictable: 59940",
          			unit: "min",
                      role: "value",
                      type: "number"
                  },
          		reverseState: {
                      name: "reverseState - Reverse flow, 0: No, 1: Reverse flow",
                      role: "value",
                      type: "number"
                  },
          		rssi: {
                      name: "rssi - Received Signal Strength Indicator",
          			unit: "dBm",
                      role: "value",
                      type: "number"
                  },				  
          		smartMode: {
                      name: "smartMode - 1: writes to RAM / 0: writes to Flash",
                      role: "value",
                      type: "number"
                  },
          		socCompSwitch: { //api:3
                      name: "socCompSwitch; Unauthorized modifications are not recommended",
                      role: "value",
                      type: "number"
                  },					
          		socLimit: {
                      name: "socLimit - 0: normal, 1: Charge limit reached, 2: Discharge limit reached, 3: unknown",
                      role: "value",
                      type: "number"
                  },		
                  socSet: {
                      name: "socSet - SOC Target 70%-100%",
                      unit: "%",
                      role: "value",
                      type: "number"
                  },
          		socStatus: {
                      name: "socStatus - SOC calibration Info, 0: No Calibrating / 1: Calibrating",
                      role: "value",
                      type: "number"
                  },
          		solarInputPower: {
                      name: "solarInputPower - Total Solar Input Power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		solarPower1: {
                      name: "solarPower1 - Solar line 1 input power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		solarPower2: {
                      name: "solarPower2 - Solar line 2 input power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		solarPower3: {
                      name: "solarPower3 - Solar line 3 input power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		solarPower4: {
                      name: "solarPower4 - Solar line 4 input power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		solarPower5: {
                      name: "solarPower5 - Solar line 5 input power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		solarPower6: {
                      name: "solarPower6 - Solar line 6 input power",
          			unit: "W",
                      role: "value.power",
                      type: "number"
                  }
              },
              packData: {
                  batcur: {
                      name: "Battery Current flow - negativ: discharge / positiv: charge",
                      unit: "A",
                      role: "value.current",
                      type: "number"
                  },
          		maxTemp: {
                      name: "Battery - max. Stored temperature value",
                      unit: "°C",
                      role: "value.temperature",
                      type: "number"
                  },
          		maxVol: {
                      name: "maxVol - Max cell voltage",
                      unit: "V",
                      role: "value.voltage",
                      type: "number"
                  },
          		minVol: {
                      name: "minVol - Min cell voltage",
                      unit: "V",
                      role: "value.voltage",
                      type: "number"
                  },
          		packType: {
                      name: "Pack Type",
                      role: "indicator",
                      type: "number"
                  },
          		power: {
                      name: "Battery pack power",
                      unit: "W",
                      role: "value.power",
                      type: "number"
                  },
          		sn: {
                      name: "Battery pack serial number",
                      role: "indicator",
                      type: "string"
                  },
          		socLevel: {
                      name: "socLevel - State of Charge in Percent",
          			unit: "%",
                      role: "value.battery",
                      type: "number"
                  },
          		state: {
          			name: "Battery state, 0: Standby, 1: Charging, 2: Discharging",
                      role: "indicator",
                      type: "number"
                  },
          		totalVol: {
          			name: "Battery Total Voltage",
                      unit: "V",
                      role: "value.voltage",
                      type: "number"
                  }
              }
          };
           
          function formatTime(unix) {
              if (unix === undefined || unix === null) return "";
              const d = new Date(unix * 1000);
              const timeOptions = { timeZone: "Europe/Berlin", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false };
              const dateOptions = { timeZone: "Europe/Berlin", day: "2-digit", month: "2-digit", year: "2-digit" };
              return `${d.toLocaleTimeString("de-DE", timeOptions)}, ${d.toLocaleDateString("de-DE", dateOptions)}`;
          }
           
          function getBatteryType(sn, model) {
              let batType = "";
              if (!sn || typeof sn !== "string") return "unknown";
              if (sn.startsWith("A")) { batType = "AB1000"; } 
              else if (sn.startsWith("B")) { batType = "AB1000S"; } 
              else if (sn.startsWith("C")) {
                  const sub = sn.length > 3 ? sn[3] : "";
                  if (sub === "F") batType = "AB2000S";
                  else if (sub === "E") batType = "AB2000X";
                  else if (sub === "A") batType = "AB2000L";
                  else batType = "AB2000";
              } 
              else if (sn.startsWith("F")) { batType = "AB3000X"; }
              else if (sn.startsWith("J")) {
                  const sub = sn.length > 3 ? sn[3] : "";
                  if (sub === "A") batType = "Internal 2,4kWh";
                  else batType = "unknown internal";
              }
              else if (sn.startsWith("G")) {
                  const sub = sn.length > 3 ? sn[3] : "";
                  if (sub === "A") batType = "AB3000L";
                  else batType = "unknown AB3000?";
              }
              if (model && typeof model === "string" && model.trim()) { batType = model.trim(); }
              return batType || "unknown";
          }
           
          function writeMainKeys(json) {
              const keys = ["timestamp", "messageId", "sn", "version", "product"];
              keys.forEach(key => {
                  if (json[key] === undefined) return;
          		if (key === "sn" && SN === '') { SN = json[key]; }
                  const dp = `${folderZendureApi}.${key}`;
          		const dpName = (key === "version") ? "zenSDK API Version" : key;
                  if (existsState(dp)) {
                      if (getState(dp).val !== json[key]) { setState(dp, json[key], true); }
                  } else {
                      createState(dp, json[key], { name: dpName, type: typeof json[key], role: "info", read: true, write: false });
                  }
              });
          }
           
          function writeProperties(obj) {
              if (!obj) return;
              const base = folderZendureApi + ".properties";
              Object.keys(obj).forEach(key => {
                  const dp = `${base}.${key}`;
                  const val = obj[key];
                  const meta = schema.properties?.[key] || {};
                  if (existsState(dp)) {
                      if (getState(dp).val !== val) { setState(dp, val, true); }
                  } else {
                      createState(dp, val, { name: meta.name || key, type: meta.type || typeof val, role: meta.role || "value", unit: meta.unit, read: true, write: false });
                  }
              });
          }
           
          function writePackData(arr) {
              if (!Array.isArray(arr)) return;
              const base = folderZendureApi + ".packData";
              arr.forEach(pack => {
                  if (!pack.sn) return;
                  const folder = `${base}.${pack.sn}`;
                  if (!existsObject(folder)) { setObject(folder, { type: "folder", common: { name: pack.sn }, native: {} }); }
                  Object.keys(pack).forEach(key => {
                      const dp = `${folder}.${key}`;
                      const val = pack[key];
          			const meta = schema.packData?.[key] || {};
                      if (existsState(dp)) {
                          if (getState(dp).val !== val) { setState(dp, val, true); }
                      } else {
                          createState(dp, val, { name: meta.name || key, type: meta.type || typeof val, role: meta.role || "value", unit: meta.unit, read: true, write: false });
                      }
                  });
              });
          }
           
          function buildDiff(oldJson, newJson) {
              if (!oldJson || !newJson) return null;
              const diff = {};
              const mainKeys = ["timestamp", "messageId", "sn", "version", "product"];
          	mainKeys.forEach(key => {
                  if (newJson[key] !== undefined && newJson[key] !== oldJson[key]) { diff[key] = newJson[key]; }
              });
              if (newJson.properties) {
                  const propDiff = {};
                  Object.keys(newJson.properties).forEach(key => {
                      const newVal = newJson.properties[key];
                      const oldVal = oldJson.properties ? oldJson.properties[key] : undefined;
                      if (newVal !== oldVal) { propDiff[key] = newVal; }
                  });
                  if (Object.keys(propDiff).length > 0) { diff.properties = propDiff; }
              }
              if (Array.isArray(newJson.packData)) {
                  const packDiff = [];
                  const oldMap = {};
                  if (Array.isArray(oldJson.packData)) {
                      oldJson.packData.forEach(p => { if (p.sn) oldMap[p.sn] = p; }); }
                  newJson.packData.forEach(newPack => {
                      if (!newPack.sn) return;
                      const oldPack = oldMap[newPack.sn];
                      const singleDiff = { sn: newPack.sn };
                      let changed = false;
                      Object.keys(newPack).forEach(key => {
                          if (key === "sn") return;
                          const newVal = newPack[key];
                          const oldVal = oldPack ? oldPack[key] : undefined;
                          if (newVal !== oldVal) { singleDiff[key] = newVal; changed = true; }
                      });
                      if (changed) { packDiff.push(singleDiff); }
                  });
                  if (packDiff.length > 0) { diff.packData = packDiff; }
              }
              return Object.keys(diff).length === 0 ? null : diff;
          }
           
          function handleRxNewUpdate(val) {
              if (!val) return;
              let newJson;
              try { newJson = JSON.parse(val); } catch (e) { log("rxNew parse error: " + e, "warn"); return; }
              const smartModeWatcher = getState(dpSmartWatcher)?.val;
              if (smartModeWatcher === true && newJson.properties && newJson.properties.smartMode === 0) {
                  //log("SmartModeWatcher: smartMode ist 0! Erzwinge RAM-Modus (1)...", "info");
                  queuePost({ smartMode: 1 });
              }
              const oldStr = getRxOld(); 
              if (!oldStr || oldStr === "null" || oldStr === "") {
          		let full = transformJson(JSON.parse(JSON.stringify(newJson)));
          		writeMainKeys(full);
          		if (full.properties) {
          			writeProperties(full.properties);
          			const base = folderZendureApi + ".control";
                      Object.keys(schemaControl).forEach(ctrlKey => {
                          const def = schemaControl[ctrlKey];
                          const apiKey = def.apiKey || ctrlKey;
                          if (full.properties[apiKey] !== undefined) { setState(`${base}.${ctrlKey}`, def.transformRead ? def.transformRead(full.properties[apiKey]) : full.properties[apiKey], true); }
                      });
                      if (full.properties.inputLimit !== undefined) { setState(`${base}.auto_inputLimitMode`, full.properties.inputLimit, true); }
                      if (full.properties.outputLimit !== undefined) { setState(`${base}.auto_outputLimitMode`, full.properties.outputLimit, true); }
          			syncAutoInOutLimit(full.properties);
          		} 
          		if (full.packData) { writePackData(full.packData); }
          		setRxOld(val); setRxDiff(""); return;
          	}
              let oldJson;
              try { oldJson = JSON.parse(oldStr); } catch (e) { setRxOld(val); return; }
          	const isNewer = (newJson.timestamp && oldJson.timestamp && newJson.timestamp > oldJson.timestamp) || (!oldJson.timestamp && newJson.timestamp);
              if (!isNewer) return;
              const diff = buildDiff(oldJson, newJson);
              if (diff && Object.keys(diff).length > 0) {
                  const diffStr = JSON.stringify(diff);
                  setRxDiff(diffStr); handleRxDiffUpdate(diffStr); 
              } else { setRxDiff(""); }
              setRxOld(val);
          }
           
          function handleRxDiffUpdate(val) {
              if (!val) return;
              let diff;
              try { diff = JSON.parse(val); } catch (e) { log("rxDiff parse error: " + e, "warn"); return; }
              diff = transformJson(diff);
          	if (diff.properties) { syncControlFromDiff(diff); }							   
              writeMainKeys(diff);
              if (diff.properties) { writeProperties(diff.properties); }
              if (diff.packData) { writePackData(diff.packData); }
          }
           
          function syncControlFromDiff(diff) {
          	if (!diff || !diff.properties) return;
              const base = folderZendureApi + ".control";
              if (diff.properties.acMode !== undefined || diff.properties.inputLimit !== undefined || diff.properties.outputLimit !== undefined) { syncAutoInOutLimit(diff.properties); }
          	if (diff.properties.inputLimit !== undefined) { setState(`${base}.auto_inputLimitMode`, diff.properties.inputLimit, true); }
          	if (diff.properties.outputLimit !== undefined) { setState(`${base}.auto_outputLimitMode`, diff.properties.outputLimit, true); }
          	Object.keys(schemaControl).forEach(ctrlKey => {
          		const def = schemaControl[ctrlKey];
          		const apiKey = def.apiKey || ctrlKey;
          		if (diff.properties[apiKey] !== undefined) { setState(`${base}.${ctrlKey}`, def.transformRead ? def.transformRead(diff.properties[apiKey]) : diff.properties[apiKey], true); }
          	});
          }
           
          function createOrSet(id, val, common) {
              if (existsState(id)) {
                  if (getState(id).val !== val) { setState(id, val, true); }
              } else { createState(id, val, { read: true, write: false, ...common }); }
          }
           
          function transformJson(obj) {
              if (!obj) return obj;
              if (obj.timestamp !== undefined && obj.timestamp !== null) {
                  const formatted = formatTime(obj.timestamp);
                  createOrSet(`${folderZendureApi}.timeUpdateTimestamp`, formatted, { type: "string", role: "text", name: "TimeUpdate (timestamp)" });
              }
              if (obj.properties) {
                  Object.keys(obj.properties).forEach(key => {
                      let val = obj.properties[key];
                      switch (key) {
          				case "BatVolt": val = val / 100; break;
          				case "batCalTime":
          					createOrSet(`${folderZendureApi}.properties.batCalTime_h`, minutesToHhmm(val), { type: "string", role: "text", name: "batCalTime_h - Battery Calibration Time (HH:MM)", unit: "h" });
          					break;
          				case "acCouplingState": {
          					let states = [];
          					if (val & (1 << 0)) states.push("AC-coupled input present");
          					if (val & (1 << 1)) states.push("AC input present flag");
          					if (val & (1 << 2)) states.push("AC-coupled overload");
          					if (val & (1 << 3)) states.push("Excess AC input power");
          					const statusText = states.length > 0 ? states.join(", ") : "Normal / No Flags";
          					createOrSet(`${folderZendureApi}.properties.acCouplingState_String`, statusText, { type: "string", role: "text", name: "AC Coupling State (Text)" });
          				} break;
          				case "hyperTmp": val = (val - 2731) / 10.0; break;
                          case "minSoc":
          				case "socSet": val = val / 10; break;
          				case "socLimit": {
          					if (val >= 16) {
          					val = val & 0x03;
          					}
          				} break;
                          case "ts":
          					if (val !== undefined && val !== null) {
          						const formatted = formatTime(val);
          						createOrSet(`${folderZendureApi}.properties.timeUpdateTs`, formatted, { type: "string", role: "text", name: "TimeUpdate (ts)" });
          					} break;
                      }
                      obj.properties[key] = val;
                  });
              }
              if (Array.isArray(obj.packData)) {
          		obj.packData.forEach(pack => {
          			if (pack.sn) {
          				const batType = getBatteryType(pack.sn, pack.model);
          				createOrSet(`${folderZendureApi}.packData.${pack.sn}.model`, batType, { type: "string", role: "text", name: "Battery Model" });
          			}
          		}); 	
                  obj.packData.forEach(pack => {
                      Object.keys(pack).forEach(key => {
                          let val = pack[key];
                          switch (key) {
          					case "batcur":
          						if (val > 32767) { val = val - 65536; }
          						val = val / 10; break;
                              case "maxTemp": val = (val - 2731) / 10; break;
          					case "maxVol":
          					case "minVol":
          					case "totalVol": val = val / 100; break;
          					case "softVersion":
                                  const major = Math.floor(val / 4096);
                                  const minor = Math.floor((val % 4096) / 256);
                                  const patch = val % 256;
                                  val = `V${major}.${minor}.${patch}`; break;  
                          }
                          pack[key] = val;
                      });
                  });
              }
              return obj;
          }
           
          startGetLoop();
          
          

          Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

          neue Zendure Geräte Cloudless: zURLtool V2.0

          1 Antwort Letzte Antwort
          0
          • D
            D
            Daniel 8
            schrieb am zuletzt editiert von
            #662

            Nachdem ich jetzt ein paar Tage den Fehler Zendure IP überprüfen beobachtet habe, kann ich folgendes feststelln.

            Der Fehler tritt immer auf, wenn das Gerät am Abend mit Sonnenuntergang bzw. PV Eingang 0 Watt hat und wegen dem Wetter so wenig ertrag kam, das er auch gleich auf SOC min ist. Somit werden über sehr lange Zeit keine Steuerbefehle mehr gesendet und ich vermute, das das Gerät dann nach ca.13 Stunden in einen Tiefschlaf geht.
            Es meldet sich dann vom WLAN ab und erst nachdem wieder ein bisschen PV kommt wieder an.
            Die anderen 2 Tage habe ich länger eingespeist und dann erschien der Fehler am nächsten morgen nicht.

            Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

            maxclaudiM 1 Antwort Letzte Antwort
            1
            • D Daniel 8

              Nachdem ich jetzt ein paar Tage den Fehler Zendure IP überprüfen beobachtet habe, kann ich folgendes feststelln.

              Der Fehler tritt immer auf, wenn das Gerät am Abend mit Sonnenuntergang bzw. PV Eingang 0 Watt hat und wegen dem Wetter so wenig ertrag kam, das er auch gleich auf SOC min ist. Somit werden über sehr lange Zeit keine Steuerbefehle mehr gesendet und ich vermute, das das Gerät dann nach ca.13 Stunden in einen Tiefschlaf geht.
              Es meldet sich dann vom WLAN ab und erst nachdem wieder ein bisschen PV kommt wieder an.
              Die anderen 2 Tage habe ich länger eingespeist und dann erschien der Fehler am nächsten morgen nicht.

              maxclaudiM
              maxclaudiM
              maxclaudi
              schrieb am zuletzt editiert von maxclaudi
              #663

              @Daniel-8 sagte:

              Nachdem ich jetzt ein paar Tage den Fehler Zendure IP überprüfen beobachtet habe, kann ich folgendes feststelln.

              Der Fehler tritt immer auf, wenn das Gerät am Abend mit Sonnenuntergang bzw. PV Eingang 0 Watt hat und wegen dem Wetter so wenig ertrag kam, das er auch gleich auf SOC min ist. Somit werden über sehr lange Zeit keine Steuerbefehle mehr gesendet und ich vermute, das das Gerät dann nach ca.13 Stunden in einen Tiefschlaf geht.

              Das ist absolut nachvollziehbar.
              Das Abschalten des WLAN-Moduls oder das Versetzen der MCU in einen tiefen Schlafmodus ist eine gängige Schutzfunktion, da die Netzwerkkommunikation einen nicht unerheblichen Teil des Eigenstrombedarfs des Geräts ausmacht.
              Wenn über Stunden keine Aktivität (weder PV-Ertrag noch Steuerbefehle) registriert wird, greift irgendwann das Energiemanagement, um eine Tiefentladung der Zellen zu verhindern.

              Interessant wäre nun zu wissen:
              • Auf wie viel % steht dein minSoc aktuell?
              • Welchen Wert hat/haben die Batterie(n) dabei bei minVol (niedrigste Zellspannung)?

              Zusätzlich wäre es gut zu beobachten, wie sich die originale App in diesem Zustand verhält:

              • Meldet die App das Gerät zu diesem Zeitpunkt noch als online?
              • Lassen sich über die App noch Werte verändern?

              Falls ja, wäre wichtig festzustellen, ob das Gerät durch die App-Aktivität kurzzeitig aus dem Tiefschlaf erwacht und diese Befehle dann auch tatsächlich ausführt.
              Das würde uns zeigen, ob das Gerät im Schlafmodus noch auf bestimmte Wake-up-Signale (z. B. via Bluetooth oder die Cloud-Schnittstelle) reagiert.

              edit/PS: ist die Akku-Heizung aktiv?

              Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

              neue Zendure Geräte Cloudless: zURLtool V2.0

              D 1 Antwort Letzte Antwort
              0
              • maxclaudiM maxclaudi

                @Daniel-8 sagte:

                Nachdem ich jetzt ein paar Tage den Fehler Zendure IP überprüfen beobachtet habe, kann ich folgendes feststelln.

                Der Fehler tritt immer auf, wenn das Gerät am Abend mit Sonnenuntergang bzw. PV Eingang 0 Watt hat und wegen dem Wetter so wenig ertrag kam, das er auch gleich auf SOC min ist. Somit werden über sehr lange Zeit keine Steuerbefehle mehr gesendet und ich vermute, das das Gerät dann nach ca.13 Stunden in einen Tiefschlaf geht.

                Das ist absolut nachvollziehbar.
                Das Abschalten des WLAN-Moduls oder das Versetzen der MCU in einen tiefen Schlafmodus ist eine gängige Schutzfunktion, da die Netzwerkkommunikation einen nicht unerheblichen Teil des Eigenstrombedarfs des Geräts ausmacht.
                Wenn über Stunden keine Aktivität (weder PV-Ertrag noch Steuerbefehle) registriert wird, greift irgendwann das Energiemanagement, um eine Tiefentladung der Zellen zu verhindern.

                Interessant wäre nun zu wissen:
                • Auf wie viel % steht dein minSoc aktuell?
                • Welchen Wert hat/haben die Batterie(n) dabei bei minVol (niedrigste Zellspannung)?

                Zusätzlich wäre es gut zu beobachten, wie sich die originale App in diesem Zustand verhält:

                • Meldet die App das Gerät zu diesem Zeitpunkt noch als online?
                • Lassen sich über die App noch Werte verändern?

                Falls ja, wäre wichtig festzustellen, ob das Gerät durch die App-Aktivität kurzzeitig aus dem Tiefschlaf erwacht und diese Befehle dann auch tatsächlich ausführt.
                Das würde uns zeigen, ob das Gerät im Schlafmodus noch auf bestimmte Wake-up-Signale (z. B. via Bluetooth oder die Cloud-Schnittstelle) reagiert.

                edit/PS: ist die Akku-Heizung aktiv?

                D
                D
                Daniel 8
                schrieb am zuletzt editiert von
                #664

                @maxclaudi sagte:

                @Daniel-8 sagte:

                Nachdem ich jetzt ein paar Tage den Fehler Zendure IP überprüfen beobachtet habe, kann ich folgendes feststelln.

                Der Fehler tritt immer auf, wenn das Gerät am Abend mit Sonnenuntergang bzw. PV Eingang 0 Watt hat und wegen dem Wetter so wenig ertrag kam, das er auch gleich auf SOC min ist. Somit werden über sehr lange Zeit keine Steuerbefehle mehr gesendet und ich vermute, das das Gerät dann nach ca.13 Stunden in einen Tiefschlaf geht.

                Das ist absolut nachvollziehbar.
                Das Abschalten des WLAN-Moduls oder das Versetzen der MCU in einen tiefen Schlafmodus ist eine gängige Schutzfunktion, da die Netzwerkkommunikation einen nicht unerheblichen Teil des Eigenstrombedarfs des Geräts ausmacht.
                Wenn über Stunden keine Aktivität (weder PV-Ertrag noch Steuerbefehle) registriert wird, greift irgendwann das Energiemanagement, um eine Tiefentladung der Zellen zu verhindern.

                Interessant wäre nun zu wissen:
                • Auf wie viel % steht dein minSoc aktuell?

                Der steht auf 50%

                • Welchen Wert hat/haben die Batterie(n) dabei bei minVol (niedrigste Zellspannung)?

                Das habe ich nicht geschaut. Wenn es dir wichtig ist, kann ich das gerne noch machen.

                Zusätzlich wäre es gut zu beobachten, wie sich die originale App in diesem Zustand verhält:

                • Meldet die App das Gerät zu diesem Zeitpunkt noch als online?

                Nein die App meldet ihn als Offline und versucht per Bluetooth zu verbinden was aber auch nicht funktioniert.

                • Lassen sich über die App noch Werte verändern?

                Ob man Werte verändern kann habe ich nicht getestet. Kann ich aber gerne nachholen.

                Falls ja, wäre wichtig festzustellen, ob das Gerät durch die App-Aktivität kurzzeitig aus dem Tiefschlaf erwacht und diese Befehle dann auch tatsächlich ausführt.
                Das würde uns zeigen, ob das Gerät im Schlafmodus noch auf bestimmte Wake-up-Signale (z. B. via Bluetooth oder die Cloud-Schnittstelle) reagiert.

                Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

                maxclaudiM 1 Antwort Letzte Antwort
                0
                • D Daniel 8

                  @maxclaudi sagte:

                  @Daniel-8 sagte:

                  Nachdem ich jetzt ein paar Tage den Fehler Zendure IP überprüfen beobachtet habe, kann ich folgendes feststelln.

                  Der Fehler tritt immer auf, wenn das Gerät am Abend mit Sonnenuntergang bzw. PV Eingang 0 Watt hat und wegen dem Wetter so wenig ertrag kam, das er auch gleich auf SOC min ist. Somit werden über sehr lange Zeit keine Steuerbefehle mehr gesendet und ich vermute, das das Gerät dann nach ca.13 Stunden in einen Tiefschlaf geht.

                  Das ist absolut nachvollziehbar.
                  Das Abschalten des WLAN-Moduls oder das Versetzen der MCU in einen tiefen Schlafmodus ist eine gängige Schutzfunktion, da die Netzwerkkommunikation einen nicht unerheblichen Teil des Eigenstrombedarfs des Geräts ausmacht.
                  Wenn über Stunden keine Aktivität (weder PV-Ertrag noch Steuerbefehle) registriert wird, greift irgendwann das Energiemanagement, um eine Tiefentladung der Zellen zu verhindern.

                  Interessant wäre nun zu wissen:
                  • Auf wie viel % steht dein minSoc aktuell?

                  Der steht auf 50%

                  • Welchen Wert hat/haben die Batterie(n) dabei bei minVol (niedrigste Zellspannung)?

                  Das habe ich nicht geschaut. Wenn es dir wichtig ist, kann ich das gerne noch machen.

                  Zusätzlich wäre es gut zu beobachten, wie sich die originale App in diesem Zustand verhält:

                  • Meldet die App das Gerät zu diesem Zeitpunkt noch als online?

                  Nein die App meldet ihn als Offline und versucht per Bluetooth zu verbinden was aber auch nicht funktioniert.

                  • Lassen sich über die App noch Werte verändern?

                  Ob man Werte verändern kann habe ich nicht getestet. Kann ich aber gerne nachholen.

                  Falls ja, wäre wichtig festzustellen, ob das Gerät durch die App-Aktivität kurzzeitig aus dem Tiefschlaf erwacht und diese Befehle dann auch tatsächlich ausführt.
                  Das würde uns zeigen, ob das Gerät im Schlafmodus noch auf bestimmte Wake-up-Signale (z. B. via Bluetooth oder die Cloud-Schnittstelle) reagiert.

                  maxclaudiM
                  maxclaudiM
                  maxclaudi
                  schrieb am zuletzt editiert von
                  #665

                  @Daniel-8
                  Deine Rückmeldung bestärkt meine Vermutung.
                  Dein Akku steht ja draußen im Kalten?

                  Wenn ja, tippe ich stark darauf, dass die integrierte Akkuheizung aktiv war.

                  Die Heizung zieht extrem viel Strom.
                  Wenn das Wetter tagsüber so schlecht war, dass der Akku ohnehin kaum geladen wurde, und nachts die Heizung anspringt, ist der Energiebedarf gigantisch.
                  Um zu verhindern, dass die Heizung den Akku im Winter komplett leersaugt (selbst wenn du den Schutz auf 50 % minSoc gestellt hast), greift vermutlich der Sicherheitsmechanismus der Firmware:
                  Bei Erreichen des Limits wird dem gesamten System - inklusive WLAN - komplett der Saft abgedreht, um die Zellen zu schützen.
                  Ein SolarFlow 800 Pro geht normalerweise nicht einfach so grundlos in einen so tiefen Schlaf, dass selbst WLAN abgeschaltet wird – es sei denn, ein interner Großverbraucher wie die Heizung hat das System in den Not-Standby gezwungen.
                  Da die App das Gerät als offline anzeigt, brauchst du auch keine Wertänderungen testen.
                  Falls du doch mal die Gelegenheit hast, schau morgens (wenn das Gerät erwacht) mal auf die Werte und ob Heizung aktiv ist.
                  Die Temperaturen und vor allem die Zellspannungen (minVol) wären dennoch extrem interessant.
                  So kann man sehen, ob die tatsächlichen Werte mit den 50 % SoC übereinstimmen oder ob die SoC-Berechnung aufgrund einer fehlenden Kalibrierung einfach nicht mehr stimmt.

                  Du solltest deinen Akku am besten einpacken (isolieren) oder anderweitig vor den tieferen Temperaturen schützen, um diesen massiven nächtlichen Energieverlust zu verhindern.

                  Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                  neue Zendure Geräte Cloudless: zURLtool V2.0

                  D 1 Antwort Letzte Antwort
                  0
                  • maxclaudiM maxclaudi

                    @Daniel-8
                    Deine Rückmeldung bestärkt meine Vermutung.
                    Dein Akku steht ja draußen im Kalten?

                    Wenn ja, tippe ich stark darauf, dass die integrierte Akkuheizung aktiv war.

                    Die Heizung zieht extrem viel Strom.
                    Wenn das Wetter tagsüber so schlecht war, dass der Akku ohnehin kaum geladen wurde, und nachts die Heizung anspringt, ist der Energiebedarf gigantisch.
                    Um zu verhindern, dass die Heizung den Akku im Winter komplett leersaugt (selbst wenn du den Schutz auf 50 % minSoc gestellt hast), greift vermutlich der Sicherheitsmechanismus der Firmware:
                    Bei Erreichen des Limits wird dem gesamten System - inklusive WLAN - komplett der Saft abgedreht, um die Zellen zu schützen.
                    Ein SolarFlow 800 Pro geht normalerweise nicht einfach so grundlos in einen so tiefen Schlaf, dass selbst WLAN abgeschaltet wird – es sei denn, ein interner Großverbraucher wie die Heizung hat das System in den Not-Standby gezwungen.
                    Da die App das Gerät als offline anzeigt, brauchst du auch keine Wertänderungen testen.
                    Falls du doch mal die Gelegenheit hast, schau morgens (wenn das Gerät erwacht) mal auf die Werte und ob Heizung aktiv ist.
                    Die Temperaturen und vor allem die Zellspannungen (minVol) wären dennoch extrem interessant.
                    So kann man sehen, ob die tatsächlichen Werte mit den 50 % SoC übereinstimmen oder ob die SoC-Berechnung aufgrund einer fehlenden Kalibrierung einfach nicht mehr stimmt.

                    Du solltest deinen Akku am besten einpacken (isolieren) oder anderweitig vor den tieferen Temperaturen schützen, um diesen massiven nächtlichen Energieverlust zu verhindern.

                    D
                    D
                    Daniel 8
                    schrieb am zuletzt editiert von Daniel 8
                    #666

                    @maxclaudi sagte:

                    @Daniel-8
                    Deine Rückmeldung bestärkt meine Vermutung.
                    Dein Akku steht ja draußen im Kalten?

                    Ja der steht draußen.

                    Wenn ja, tippe ich stark darauf, dass die integrierte Akkuheizung aktiv war.

                    Aber war es bis jetzt nicht so, das die Heizung erst angeht, wenn er unter 0° hat und laden will?

                    Ich hatte noch nie gesehen, das die Akkuheizung nachts an war. Zudem diese ja sowieso nur mit AC funktioniert. Man kann die Heizung nicht über Solarenergie betreiben.

                    Die Heizung zieht extrem viel Strom.
                    Wenn das Wetter tagsüber so schlecht war, dass der Akku ohnehin kaum geladen wurde, und nachts die Heizung anspringt, ist der Energiebedarf gigantisch.
                    Um zu verhindern, dass die Heizung den Akku im Winter komplett leersaugt (selbst wenn du den Schutz auf 50 % minSoc gestellt hast), greift vermutlich der Sicherheitsmechanismus der Firmware:
                    Bei Erreichen des Limits wird dem gesamten System - inklusive WLAN - komplett der Saft abgedreht, um die Zellen zu schützen.
                    Ein SolarFlow 800 Pro geht normalerweise nicht einfach so grundlos in einen so tiefen Schlaf, dass selbst WLAN abgeschaltet wird – es sei denn, ein interner Großverbraucher wie die Heizung hat das System in den Not-Standby gezwungen.
                    Da die App das Gerät als offline anzeigt, brauchst du auch keine Wertänderungen testen.
                    Falls du doch mal die Gelegenheit hast, schau morgens (wenn das Gerät erwacht) mal auf die Werte und ob Heizung aktiv ist.

                    Auf welche werte meinst du?

                    Die Temperaturen und vor allem die Zellspannungen (minVol) wären dennoch extrem interessant.
                    So kann man sehen, ob die tatsächlichen Werte mit den 50 % SoC übereinstimmen oder ob die SoC-Berechnung aufgrund einer fehlenden Kalibrierung einfach nicht mehr stimmt.

                    Kann ich mal schauen. Die Kalibrierung ist erst 5 Tage her.

                    Du solltest deinen Akku am besten einpacken (isolieren) oder anderweitig vor den tieferen Temperaturen schützen, um diesen massiven nächtlichen Energieverlust zu verhindern.

                    Ich habe nur ca. 2 Prozent über Nacht Verlust

                    Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

                    maxclaudiM 1 Antwort Letzte Antwort
                    0
                    • B
                      B
                      Berny-K
                      schrieb am zuletzt editiert von
                      #667

                      Hallo guten Morgen,
                      ich hatte mit meunemSF800Pro die Tage das gleiche Problem, dass der offline ging und ich erinnere mich, dass ich das letzten Winter auch schon mal hatte, die gleichen Symptome wie hier beschrieben. Ich habe dann den Netzstecker gezogen, die Module abgeklemmt und neu gestartet. Danach wieder alles abgeklemmt und dann hat wieder alles funktioniert. Mein SF800Pro steht allerdings im warmen, da läuft sicher keine Heizung.

                      D maxclaudiM 2 Antworten Letzte Antwort
                      0
                      • B Berny-K

                        Hallo guten Morgen,
                        ich hatte mit meunemSF800Pro die Tage das gleiche Problem, dass der offline ging und ich erinnere mich, dass ich das letzten Winter auch schon mal hatte, die gleichen Symptome wie hier beschrieben. Ich habe dann den Netzstecker gezogen, die Module abgeklemmt und neu gestartet. Danach wieder alles abgeklemmt und dann hat wieder alles funktioniert. Mein SF800Pro steht allerdings im warmen, da läuft sicher keine Heizung.

                        D
                        D
                        Daniel 8
                        schrieb am zuletzt editiert von
                        #668

                        @Berny-K sagte:

                        Hallo guten Morgen,
                        ich hatte mit meunemSF800Pro die Tage das gleiche Problem, dass der offline ging und ich erinnere mich, dass ich das letzten Winter auch schon mal hatte, die gleichen Symptome wie hier beschrieben. Ich habe dann den Netzstecker gezogen, die Module abgeklemmt und neu gestartet. Danach wieder alles abgeklemmt und dann hat wieder alles funktioniert. Mein SF800Pro steht allerdings im warmen, da läuft sicher keine Heizung.

                        Für mich ist das auch kein Problem. Es war eigentlich nur eine feststellung und auch Mitteilung damit andere auch informiert sind und sich nicht über das verhalten wundern

                        Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

                        maxclaudiM 2 Antworten Letzte Antwort
                        1
                        • D Daniel 8

                          @maxclaudi sagte:

                          @Daniel-8
                          Deine Rückmeldung bestärkt meine Vermutung.
                          Dein Akku steht ja draußen im Kalten?

                          Ja der steht draußen.

                          Wenn ja, tippe ich stark darauf, dass die integrierte Akkuheizung aktiv war.

                          Aber war es bis jetzt nicht so, das die Heizung erst angeht, wenn er unter 0° hat und laden will?

                          Ich hatte noch nie gesehen, das die Akkuheizung nachts an war. Zudem diese ja sowieso nur mit AC funktioniert. Man kann die Heizung nicht über Solarenergie betreiben.

                          Die Heizung zieht extrem viel Strom.
                          Wenn das Wetter tagsüber so schlecht war, dass der Akku ohnehin kaum geladen wurde, und nachts die Heizung anspringt, ist der Energiebedarf gigantisch.
                          Um zu verhindern, dass die Heizung den Akku im Winter komplett leersaugt (selbst wenn du den Schutz auf 50 % minSoc gestellt hast), greift vermutlich der Sicherheitsmechanismus der Firmware:
                          Bei Erreichen des Limits wird dem gesamten System - inklusive WLAN - komplett der Saft abgedreht, um die Zellen zu schützen.
                          Ein SolarFlow 800 Pro geht normalerweise nicht einfach so grundlos in einen so tiefen Schlaf, dass selbst WLAN abgeschaltet wird – es sei denn, ein interner Großverbraucher wie die Heizung hat das System in den Not-Standby gezwungen.
                          Da die App das Gerät als offline anzeigt, brauchst du auch keine Wertänderungen testen.
                          Falls du doch mal die Gelegenheit hast, schau morgens (wenn das Gerät erwacht) mal auf die Werte und ob Heizung aktiv ist.

                          Auf welche werte meinst du?

                          Die Temperaturen und vor allem die Zellspannungen (minVol) wären dennoch extrem interessant.
                          So kann man sehen, ob die tatsächlichen Werte mit den 50 % SoC übereinstimmen oder ob die SoC-Berechnung aufgrund einer fehlenden Kalibrierung einfach nicht mehr stimmt.

                          Kann ich mal schauen. Die Kalibrierung ist erst 5 Tage her.

                          Du solltest deinen Akku am besten einpacken (isolieren) oder anderweitig vor den tieferen Temperaturen schützen, um diesen massiven nächtlichen Energieverlust zu verhindern.

                          Ich habe nur ca. 2 Prozent über Nacht Verlust

                          maxclaudiM
                          maxclaudiM
                          maxclaudi
                          schrieb am zuletzt editiert von
                          #669

                          @Daniel-8 sagte:
                          Auf welche werte meinst du?

                          packData.xxxx.

                          • maxTemp
                          • maxVol
                          • minVol

                          properties:

                          • BatVolt
                          • electricLevel
                          • faultLevel
                          • heatState
                          • hyperTmp
                          • is_error
                          • socStatus
                          • dataReady

                          Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                          neue Zendure Geräte Cloudless: zURLtool V2.0

                          D 1 Antwort Letzte Antwort
                          0
                          • B Berny-K

                            Hallo guten Morgen,
                            ich hatte mit meunemSF800Pro die Tage das gleiche Problem, dass der offline ging und ich erinnere mich, dass ich das letzten Winter auch schon mal hatte, die gleichen Symptome wie hier beschrieben. Ich habe dann den Netzstecker gezogen, die Module abgeklemmt und neu gestartet. Danach wieder alles abgeklemmt und dann hat wieder alles funktioniert. Mein SF800Pro steht allerdings im warmen, da läuft sicher keine Heizung.

                            maxclaudiM
                            maxclaudiM
                            maxclaudi
                            schrieb am zuletzt editiert von
                            #670

                            @Berny-K
                            Nun, das ist – verzeih mir die Ausdrucksweise – Hardcore für die Hardware.
                            Das funktioniert und bekämpft die Symptome, aber es behebt nicht die Ursache ;-)

                            Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                            neue Zendure Geräte Cloudless: zURLtool V2.0

                            B 1 Antwort Letzte Antwort
                            0
                            • D Daniel 8

                              @Berny-K sagte:

                              Hallo guten Morgen,
                              ich hatte mit meunemSF800Pro die Tage das gleiche Problem, dass der offline ging und ich erinnere mich, dass ich das letzten Winter auch schon mal hatte, die gleichen Symptome wie hier beschrieben. Ich habe dann den Netzstecker gezogen, die Module abgeklemmt und neu gestartet. Danach wieder alles abgeklemmt und dann hat wieder alles funktioniert. Mein SF800Pro steht allerdings im warmen, da läuft sicher keine Heizung.

                              Für mich ist das auch kein Problem. Es war eigentlich nur eine feststellung und auch Mitteilung damit andere auch informiert sind und sich nicht über das verhalten wundern

                              maxclaudiM
                              maxclaudiM
                              maxclaudi
                              schrieb am zuletzt editiert von
                              #671

                              @Daniel-8

                              zum Vergleich:
                              aktuell ist das 2400ACpro-Testsystem auf der Terrasse mit
                              • hyperTmp 9°C
                              • maxTemp 8°C
                              Wie es dann in der Nacht aussah kann man sich vorstellen.

                              Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                              neue Zendure Geräte Cloudless: zURLtool V2.0

                              D 1 Antwort Letzte Antwort
                              0
                              • maxclaudiM maxclaudi

                                @Daniel-8

                                zum Vergleich:
                                aktuell ist das 2400ACpro-Testsystem auf der Terrasse mit
                                • hyperTmp 9°C
                                • maxTemp 8°C
                                Wie es dann in der Nacht aussah kann man sich vorstellen.

                                D
                                D
                                Daniel 8
                                schrieb am zuletzt editiert von Daniel 8
                                #672

                                @maxclaudi sagte:

                                @Daniel-8

                                zum Vergleich:
                                aktuell ist das 2400ACpro-Testsystem auf der Terrasse mit
                                • hyperTmp 9°C

                                Bei mir aktuell 20°C

                                • maxTemp 8°C

                                Im Moment auch 7 und 6.

                                Wie es dann in der Nacht aussah kann man sich vorstellen.

                                Nachts vermutlich bisschen weniger. Hatten heute Morgen 2°C Außentemperatur.

                                Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

                                1 Antwort Letzte Antwort
                                0
                                • D Daniel 8

                                  @Berny-K sagte:

                                  Hallo guten Morgen,
                                  ich hatte mit meunemSF800Pro die Tage das gleiche Problem, dass der offline ging und ich erinnere mich, dass ich das letzten Winter auch schon mal hatte, die gleichen Symptome wie hier beschrieben. Ich habe dann den Netzstecker gezogen, die Module abgeklemmt und neu gestartet. Danach wieder alles abgeklemmt und dann hat wieder alles funktioniert. Mein SF800Pro steht allerdings im warmen, da läuft sicher keine Heizung.

                                  Für mich ist das auch kein Problem. Es war eigentlich nur eine feststellung und auch Mitteilung damit andere auch informiert sind und sich nicht über das verhalten wundern

                                  maxclaudiM
                                  maxclaudiM
                                  maxclaudi
                                  schrieb am zuletzt editiert von maxclaudi
                                  #673

                                  @Daniel-8 sagte:
                                  Für mich ist das auch kein Problem. Es war eigentlich nur eine feststellung und auch Mitteilung damit andere auch informiert sind und sich nicht über das verhalten wundern

                                  Finde das für Alle eine wertvolle Information.

                                  Wenn sowieso Standby ist und es absolut nichts zu tun gibt, dann ist dieses Verhalten der Firmware aus Sicht des Energiemanagements doch nahezu ideal, oder nicht?

                                  WLAN und Bluetooth im Dauer-Standby ziehen über die vielen Stunden der Dunkelheit nur unnötig Strom aus den Zellen.
                                  Die MCU komplett schlafen zu legen und morgens rein hardwareseitig über den ersten PV-Strom wieder zu wecken, ist die effizienteste Methode, um den Akku im Winter zu schonen. Man benötigt ja keine Live-Infos auf dem Dashboard, wenn es ohnehin nichts zu steuern gibt. Sobald das Gerät morgens wieder von allein erwacht, ist alles perfekt gelöst.

                                  Hätte man es als Software-Architekt besser lösen können?
                                  Persönlich denke ich: Nein, im Kern nicht. Das harte Abschalten ist die sicherste Methode. Wenn die Verbindung gekappt ist, kann kein (fehlerhaftes) externes Skript oder eine hängende Cloud-Verbindung das System nachts ungewollt wachhalten und den Akku leersaugen.

                                  Das Einzige, was ich als Entwickler zusätzlich implementiert hätte, wäre eine saubere Status-Meldung vor dem Einschlafen: Dass das Gerät einen letzten MQTT- oder HTTP-Befehl absetzt ("Inaktivität - gehe in Deep-Sleep"), bevor es die Funkmodule abschaltet. Dann würde die App oder ein Skript nicht "Verbindung verloren / Fehler" melden, sondern den sauberen Status "Schläft tief (Warten auf Sonne)" ausgeben.

                                  edit/PS:
                                  ich hätte mir allerdings schon zusätzlich Gedanken gemacht, ob es nicht sinnvoll gewesen wäre, eine echte Sicherheitsabfrage einzubauen.
                                  Ideal wäre eine Logik wie: „Standby vorgesehen in X Minuten/Sekunden“. Wenn innerhalb dieses definierten Zeitfensters kein aktiver Wachhaltebefehl (wie z. B. ein neuer Lade- oder Steuerbefehl) vom Skript oder der App reinkommt, fährt das System erst dann kontrolliert in den Deep-Sleep.

                                  So wie es jetzt gelöst ist, ist es eben ein sehr harter, kompromissloser Schnitt der Firmware, ohne dem lokalen System noch eine Chance zur Reaktion zu geben.

                                  Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                                  neue Zendure Geräte Cloudless: zURLtool V2.0

                                  D 1 Antwort Letzte Antwort
                                  0
                                  • maxclaudiM maxclaudi

                                    @Berny-K
                                    Nun, das ist – verzeih mir die Ausdrucksweise – Hardcore für die Hardware.
                                    Das funktioniert und bekämpft die Symptome, aber es behebt nicht die Ursache ;-)

                                    B
                                    B
                                    Berny-K
                                    schrieb am zuletzt editiert von
                                    #674

                                    @maxclaudi
                                    Da gib ich dir absolut recht aber ich wusste mir einfach nicht anders zu helfen. Werde das mal beobachten, wenn es wieder passiert, ob er von selbst wieder online geht sobald genügend PV-Leistung anliegt.

                                    maxclaudiM 1 Antwort Letzte Antwort
                                    0
                                    • B Berny-K

                                      @maxclaudi
                                      Da gib ich dir absolut recht aber ich wusste mir einfach nicht anders zu helfen. Werde das mal beobachten, wenn es wieder passiert, ob er von selbst wieder online geht sobald genügend PV-Leistung anliegt.

                                      maxclaudiM
                                      maxclaudiM
                                      maxclaudi
                                      schrieb am zuletzt editiert von maxclaudi
                                      #675

                                      @Berny-K
                                      Alles gut, man erschrickt ja erst mal, wenn das System plötzlich totzuspringen scheint.
                                      Beobachte das beim nächsten Mal einfach ganz entspannt.
                                      Sobald die Panels morgens wieder um die ca. 40 Watt liefern, sollte der Eigenverbrauch gedeckt sein und das Gerät taucht von alleine wieder im WLAN auf. Der harte Reboot sollte nicht nötig sein.

                                      edit/PS:
                                      Wundere dich aber nicht über die Zahlen in der App: Das System zieht den internen Eigenverbrauch ab und meldet dann meist nur ca. 10 Watt Einspeisung.

                                      edit/PPS:
                                      @Berny-K (und @Daniel-8)
                                      Noch eine Vermutung von mir:
                                      Ich denke nicht, dass so ein radikales Steckerziehen überhaupt nötig ist, wenn man das System mal zwischendurch manuell wecken möchte.
                                      Theoretisch müsste es reichen, das System kurz aufzuwecken, indem man die physische IoT-Taste am Gerät kurz 1 oder 2x betätigt.
                                      Das sollte die MCU und die Funkmodule (zumindest für paar Sekunden/Minuten) manuell triggern, ohne dass man die ganze Hardware unter Last abklemmen muss.
                                      Hat das von euch schon mal jemand in diesem Zustand ausprobiert?

                                      Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                                      neue Zendure Geräte Cloudless: zURLtool V2.0

                                      D B 2 Antworten Letzte Antwort
                                      0
                                      • maxclaudiM maxclaudi

                                        @Daniel-8 sagte:
                                        Für mich ist das auch kein Problem. Es war eigentlich nur eine feststellung und auch Mitteilung damit andere auch informiert sind und sich nicht über das verhalten wundern

                                        Finde das für Alle eine wertvolle Information.

                                        Wenn sowieso Standby ist und es absolut nichts zu tun gibt, dann ist dieses Verhalten der Firmware aus Sicht des Energiemanagements doch nahezu ideal, oder nicht?

                                        WLAN und Bluetooth im Dauer-Standby ziehen über die vielen Stunden der Dunkelheit nur unnötig Strom aus den Zellen.
                                        Die MCU komplett schlafen zu legen und morgens rein hardwareseitig über den ersten PV-Strom wieder zu wecken, ist die effizienteste Methode, um den Akku im Winter zu schonen. Man benötigt ja keine Live-Infos auf dem Dashboard, wenn es ohnehin nichts zu steuern gibt. Sobald das Gerät morgens wieder von allein erwacht, ist alles perfekt gelöst.

                                        Hätte man es als Software-Architekt besser lösen können?
                                        Persönlich denke ich: Nein, im Kern nicht. Das harte Abschalten ist die sicherste Methode. Wenn die Verbindung gekappt ist, kann kein (fehlerhaftes) externes Skript oder eine hängende Cloud-Verbindung das System nachts ungewollt wachhalten und den Akku leersaugen.

                                        Das Einzige, was ich als Entwickler zusätzlich implementiert hätte, wäre eine saubere Status-Meldung vor dem Einschlafen: Dass das Gerät einen letzten MQTT- oder HTTP-Befehl absetzt ("Inaktivität - gehe in Deep-Sleep"), bevor es die Funkmodule abschaltet. Dann würde die App oder ein Skript nicht "Verbindung verloren / Fehler" melden, sondern den sauberen Status "Schläft tief (Warten auf Sonne)" ausgeben.

                                        edit/PS:
                                        ich hätte mir allerdings schon zusätzlich Gedanken gemacht, ob es nicht sinnvoll gewesen wäre, eine echte Sicherheitsabfrage einzubauen.
                                        Ideal wäre eine Logik wie: „Standby vorgesehen in X Minuten/Sekunden“. Wenn innerhalb dieses definierten Zeitfensters kein aktiver Wachhaltebefehl (wie z. B. ein neuer Lade- oder Steuerbefehl) vom Skript oder der App reinkommt, fährt das System erst dann kontrolliert in den Deep-Sleep.

                                        So wie es jetzt gelöst ist, ist es eben ein sehr harter, kompromissloser Schnitt der Firmware, ohne dem lokalen System noch eine Chance zur Reaktion zu geben.

                                        D
                                        D
                                        Daniel 8
                                        schrieb am zuletzt editiert von
                                        #676

                                        @maxclaudi sagte:

                                        Wenn sowieso Standby ist und es absolut nichts zu tun gibt, dann ist dieses Verhalten der Firmware aus Sicht des Energiemanagements doch nahezu ideal, oder nicht?

                                        Ja ideal ist es mehr oder weniger gut Die Zeit mit ca. 13 Stunden ist schon lange. Er ist grad mal so ne Stunde im Tiefschlaf.

                                        Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

                                        1 Antwort Letzte Antwort
                                        0
                                        • maxclaudiM maxclaudi

                                          @Berny-K
                                          Alles gut, man erschrickt ja erst mal, wenn das System plötzlich totzuspringen scheint.
                                          Beobachte das beim nächsten Mal einfach ganz entspannt.
                                          Sobald die Panels morgens wieder um die ca. 40 Watt liefern, sollte der Eigenverbrauch gedeckt sein und das Gerät taucht von alleine wieder im WLAN auf. Der harte Reboot sollte nicht nötig sein.

                                          edit/PS:
                                          Wundere dich aber nicht über die Zahlen in der App: Das System zieht den internen Eigenverbrauch ab und meldet dann meist nur ca. 10 Watt Einspeisung.

                                          edit/PPS:
                                          @Berny-K (und @Daniel-8)
                                          Noch eine Vermutung von mir:
                                          Ich denke nicht, dass so ein radikales Steckerziehen überhaupt nötig ist, wenn man das System mal zwischendurch manuell wecken möchte.
                                          Theoretisch müsste es reichen, das System kurz aufzuwecken, indem man die physische IoT-Taste am Gerät kurz 1 oder 2x betätigt.
                                          Das sollte die MCU und die Funkmodule (zumindest für paar Sekunden/Minuten) manuell triggern, ohne dass man die ganze Hardware unter Last abklemmen muss.
                                          Hat das von euch schon mal jemand in diesem Zustand ausprobiert?

                                          D
                                          D
                                          Daniel 8
                                          schrieb am zuletzt editiert von
                                          #677

                                          @maxclaudi sagte:

                                          Theoretisch müsste es reichen, das System kurz aufzuwecken, indem man die physische IoT-Taste am Gerät kurz 1 oder 2x betätigt.
                                          Das sollte die MCU und die Funkmodule (zumindest für paar Sekunden/Minuten) manuell triggern, ohne dass man die ganze Hardware unter Last abklemmen muss.
                                          Hat das von euch schon mal jemand in diesem Zustand ausprobiert?

                                          Nein weil ich sehr schlecht hin komme wo der Speicher steht.

                                          Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

                                          maxclaudiM 1 Antwort Letzte Antwort
                                          0

                                          Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                                          Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                                          Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                                          Registrieren Anmelden
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          412

                                          Online

                                          33.1k

                                          Benutzer

                                          84.2k

                                          Themen

                                          1.4m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2026
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe