Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

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

Community Forum

  1. ioBroker Community Home
  2. Deutsch
  3. Einsteigerfragen
  4. Einbindung von Geräten
  5. Adapter für Ecoflow Einbindung

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.3k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    1.9k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.3k

Adapter für Ecoflow Einbindung

Geplant Angeheftet Gesperrt Verschoben Einbindung von Geräten
212 Beiträge 42 Kommentatoren 61.4k Aufrufe 41 Watching
  • Ä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.
  • T Offline
    T Offline
    TDahinten
    schrieb am zuletzt editiert von TDahinten
    #157

    Wäre dankbar, wenn hier die notwendigen Schritte evtl. nochmal einfacher erklärt werden würden.
    Bislang habe ich es nur geschafft, den HEX Wert darzustellen und mit https://protobuf-decoder.netlify.app/ zu entschlüsseln.
    Allerdings haben meine Versuche mit dem Senden noch nicht funktioniert.
    Der "Leistungsverbrauch von anderen Verbrauchern" hat sich leider nicht verändert, nach dem Senden.
    Vielleicht baut ja sogar jemand ein fertiges Script ;-)

    Wäre wirklich prima, wenn ich den Leistungsverbrauch dynamisch anpassen könnte. Weitere Werte bräuchte ich
    eigentlich nicht für meinen Zweck.

    W 1 Antwort Letzte Antwort
    0
    • T TDahinten

      Wäre dankbar, wenn hier die notwendigen Schritte evtl. nochmal einfacher erklärt werden würden.
      Bislang habe ich es nur geschafft, den HEX Wert darzustellen und mit https://protobuf-decoder.netlify.app/ zu entschlüsseln.
      Allerdings haben meine Versuche mit dem Senden noch nicht funktioniert.
      Der "Leistungsverbrauch von anderen Verbrauchern" hat sich leider nicht verändert, nach dem Senden.
      Vielleicht baut ja sogar jemand ein fertiges Script ;-)

      Wäre wirklich prima, wenn ich den Leistungsverbrauch dynamisch anpassen könnte. Weitere Werte bräuchte ich
      eigentlich nicht für meinen Zweck.

      W Offline
      W Offline
      Waly_de
      schrieb am zuletzt editiert von Waly_de
      #158

      @tdahinten

      Ok... Zufällig hab ich heute ein Script fertig gestellt, das vermutlich den meisten helfen wird.

      Man muss lediglich die App-Zugangsdaten und Seriennummern eingeben, den Rest macht das Teil hoffentlich von alleine. Ich hab auf den Mqtt-Adapter verzichtet und lieber einen eigenen Client genutzt. Damit gibt es keine "Binary/File Probleme" mehr.
      RAW Daten werden sowohl als HEX also auch dekodiert in States gespeichert. Alle einzelnen bekannten Daten werden automatisch als States angelegt.
      Bisher lässt sich nur ein State, nämlich die Einspeiseleistung schreiben ("...thing_property_set.setAC")

      Dann viel Spaß damit... bin sehr gespannt ob es bei Euch funktioniert!

      Benutzen auf eigene Gefahr !! ;-)

      /**
       * ecoflow-iobroker-connector.js
       * Version:      0.2    
       * Release date: 28.06.2023
       * Autor:        Waly_de 
       * Forum:        https://forum.iobroker.net/topic/54929/adapter-für-ecoflow-einbindung/155
       * 
       *
       * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 
       * It automatically creates known states under 0_userdata.
       * 
       * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 
       * used for the EcoFlow app, as well as the serial numbers of your devices.
       * 
       * Important: The second serial number entry should always correspond to the Powerstream as it is associated with control functions.
       * 
       * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 
       * Configure a history for this state so that the script can determine the lowest power consumption in the last 10 minutes (configurable). 
       * This value will be used to dynamically adjust the Powerstream's feed-in power.
       * 
       * You can adust Powerstream's feed-in power by your self. Just write to the State "...thing_property_set.setAC" (Watt * 10)
       * 
       * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states.
       * By modifying the "protoSource" constant, newly discovered data will also be automatically created.
       * 
       * The raw data of the interface is logged as a HEX string.
       * 
       * Please exercise caution as this is the initial version of the script. Use it at your own risk!
       *
       * Requirements:
       * - Install protobuf using the command "npm install protobufjs" from the terminal console.
       * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt".
       *
       * Note: It is encouraged to discover and publish missing data definitions to improve the script.
       * Suggestions, optimizations, and extensions are welcome at any time.
       *
       * Special thanks to all contributors for their valuable input and support.
       */
      
      /*******  YOUR DATA HERE  ****************/
      var ConfigData = {
          email: "your@mail.com",
          passwort: "yourAppPasswort!",
          seriennummern: [
              { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" }, //1. has to be your PowerStream, you can add more devices...
              { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
          ],
          SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P",
          BasePowerOffset: 50,
          MaxPower: 600,
          MinValueMin: 10,
          statesPrefix: "0_userdata.0.ecoflow",
          Debug: false
      };
      //***************************************/
      
      
      const protoSource = `
      syntax = "proto3";
      message PowerItem {
        optional Meta meta = 1;
        optional uint32 src = 2;
        optional uint32 dest = 3;
        optional uint32 unknown1 = 4;
        optional uint32 unknown2 = 5;
        optional uint32 unknown3 = 6;
        optional uint32 unknown4 = 7;
        optional uint32 cmdFunc = 8;
        optional CmdFunction cmdId = 9;
        optional uint32 unknown5 = 10;
        optional uint32 needAck = 11;
        uint64 timestamp = 14;
        optional uint32 unknown6 = 16; //3 Byte?
        optional uint32 unknown7 = 17; //3 Byte?
        optional string OS = 23;
        optional string serialNumber = 25;
      }
      message PowerMessage {
        PowerItem item = 1;
      }
      message Meta {
        optional int32 value = 1;
        optional int32 plugPower = 10; 
        optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen
        optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen
        optional int32 PV1_Power = 19;
        optional int32 M_Unknown1 = 21;
        optional int32 PV2_Power = 24;
        optional int32 From_Bat_Power = 29;
        optional int32 Batt_Poz = 31;
        optional int32 M_Unknown2 = 33;
        optional int32 M_Unknown3 = 35;
        optional int32 M_Unknown6 = 37;
        optional int32 ToHome_Power = 38;
        optional int32 M_Unknown9 = 43; //prio Power von summe PVx_Power abziehen ?
        optional int32 Needed_Power = 48;
        optional int32 M_Unknown4 = 59;
        optional int32 Bat_Minutes = 60;
      }
      enum CmdFunction {
          Unknown = 0;
          PermanentWattsPack = 129;
          SupplyPriorityPack = 130;
      }
      `;
      
      
      const mqtt = require('mqtt');
      const https = require('https');
      const protobuf = require("protobufjs");
      
      const mqttDaten = {
          UserID: '',
          User: '',
          Passwort: '',
          URL: '',
          Port: '',
          protocol: '',
          clientID: ''
      }
      
      /*=======================================================
        =========             Timer               ============
        =======================================================*/
      //alle 5 minuten
      schedule('*/1 * * * *', function () {
          SetBasePower();
      });
      
      
      
      await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort)
      async function getEcoFlowMqttData(email, password) {
          const options = {
              hostname: 'api.ecoflow.com',
              path: '/auth/login',
              method: 'POST',
              headers: {
                  'Host': 'api.ecoflow.com',
                  'lang': 'de-de',
                  'platform': 'android',
                  'sysversion': '11',
                  'version': '4.1.2.02',
                  'phonemodel': 'SM-X200',
                  'content-type': 'application/json',
                  'user-agent': 'okhttp/3.14.9'
              }
          };
      
          const data = {
              appVersion: "4.1.2.02",
              email: email,
              os: "android",
              osVersion: "30",
              password: Buffer.from(password).toString('base64'),
              scene: "IOT_APP",
              userType: "ECOFLOW"
          };
      
          function httpsRequest(options, data) {
              return new Promise((resolve, reject) => {
                  const req = https.request(options, res => {
                      let data = '';
                      res.on('data', chunk => {
                          data += chunk;
                      });
                      res.on('end', () => {
                          resolve(data);
                      });
                  });
      
                  req.on('error', error => {
                      reject(error);
                  });
      
                  if (data) {
                      req.write(JSON.stringify(data));
                  }
      
                  req.end();
              });
          }
      
          function uuidv4() {
              return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                  var r = Math.random() * 16 | 0,
                      v = c === 'x' ? r : (r & 0x3 | 0x8);
                  return v.toString(16);
              });
          }
      
          let response = await httpsRequest(options, data);
          try {
              let token = JSON.parse(response).data.token;
              let userid = JSON.parse(response).data.user.userId;
          } catch (error) {
              log(response)
              throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
          }
      
          let token = JSON.parse(response).data.token;
          let userid = JSON.parse(response).data.user.userId;
      
          options.path = `/iot-auth/app/certification?userId=${userid}`;
          options.method = 'GET';
          options.headers.authorization = `Bearer ${token}`;
          response = await httpsRequest(options);
          try {
              mqttDaten.Passwort = JSON.parse(response).data.certificatePassword
              mqttDaten.Port = JSON.parse(response).data.port
              mqttDaten.UserID = userid
              mqttDaten.User = JSON.parse(response).data.certificateAccount
              mqttDaten.URL = JSON.parse(response).data.url
              mqttDaten.protocol = JSON.parse(response).data.protocol
              mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid
          } catch (error) {
              log(response)
              throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
          }
          /*    
              console.log("UserID: " + userid);
              console.log("User: " + JSON.parse(response).data.certificateAccount);
              console.log("Passwort: " + JSON.parse(response).data.certificatePassword);
              console.log("URL: " + JSON.parse(response).data.url);
              console.log("Port: " + JSON.parse(response).data.port);
              console.log("protocol: " + JSON.parse(response).data.protocol);
              console.log("clientID: ANDROID_" + uuidv4() + "_" + userid);
          */
      
      }
      
      //################ MQTT Verbindung ##################
      
      // Verbindung herstellen
      const options = {
          port: mqttDaten.Port,
          clientId: mqttDaten.clientID,
          username: mqttDaten.User,
          password: mqttDaten.Passwort,
          protocol: mqttDaten.protocol
      };
      
      
      const client = mqtt.connect("mqtt://" + mqttDaten.URL, options);
      
      // Auf Verbindung hergestellt Ereignis reagieren
      client.on('connect', function () {
          console.log('Verbunden mit dem MQTT-Broker');
      
          ConfigData.seriennummern.forEach(item => {
              client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set');
              client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get');
              client.subscribe('/app/device/property/' + item.seriennummer);
          });
      });
      
      // Auf Nachricht empfangen Ereignis reagieren
      client.on('message', async function (topic, message) {
          var jsonMessage = ""
          const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
          await createMyState(mqState + ".RAW")
          try {
              jsonMessage = JSON.parse(message);
              if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
              generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
          } catch (error) {
              if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
              await createMyState(mqState + ".RAW_HEX")
              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
              //log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
              const messagedecoded = decodeAndPrint(message.toString('hex'))
              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
              generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
          }
      });
      // Callback für Fehler
      client.on('error', function (error) {
          log('Fehler bei der MQTT-Verbindung:' + error, error);
      });
      
      // close connection if script stopped
      onStop(function (callback) {
          if (client) {
              // close connection
              client.end();
              log("MQTT-Client beendet")
          }
          callback();
      }, 2000);
      //*/
      
      
      
      async function createMyState(name) {
          if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) {
              await createStateAsync(ConfigData.statesPrefix + '.' + name, {
                  name: name.split('.').pop(),
                  role: 'state',
                  type: 'string',
                  read: true,
                  write: true
              });
          }
      }
      
      function decodeAndPrint(hexString) {
          const root = protobuf.parse(protoSource).root;
          const PowerMessage = root.lookupType("PowerMessage");
          const message = PowerMessage.decode(Buffer.from(hexString, "hex"));
          const object = PowerMessage.toObject(message, { defaults: false });
          return JSON.stringify(object);
      }
      
      function SendProto(protomsg, topic) {
          const root = protobuf.parse(protoSource).root;
          const PowerMessage = root.lookupType("PowerMessage");
          const message = PowerMessage.create(JSON.parse(protomsg));
          const messageBuffer = PowerMessage.encode(message).finish();
          //log("Modifizierter Hex-String:" +  Buffer.from(messageBuffer).toString("hex"));
          //log("topic:" +  topic);
          client.publish(topic, messageBuffer, { qos: 1 }, function (error) {
              if (error) {
                  console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error);
              } else {
                  if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.');
              }
          });
      }
      
      //############ Funktionen zum setzen von Werten
      
      const musterSetAC = `
      {
        "item": {
          "meta": {
            "value": 1300
          },
          "src": 32,
          "dest": 53,
          "unknown1": 1,
          "unknown2": 1,
          "unknown4": 3,
          "cmdFunc": 20,
          "cmdId": 129,
          "unknown5": 3,
          "needAck": 1,
          "timestamp": {
            "low": 35061401,
            "high": 0,
            "unsigned": true
          },
          "unknown6": 19,
          "unknown7": 1,
          "OS": "ios",
          "serialNumber": "ABCxxxxxxx123"
        }
      }
      `
      await createMyState('app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC')
      on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) {
          setAC(Number(obj.state.val))
          setState(obj.id, obj.state.val, true);
      });
      
      function setAC(Value = 1000) {
          const updatedMusterSetAC = JSON.parse(musterSetAC);
          updatedMusterSetAC.item.timestamp.low = Date.now().toString();
          updatedMusterSetAC.item.meta.value = Value
          updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
          SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
      }
      
      function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0.test") {
          if (!JElements || typeof JElements !== 'object') {
              log('Ungültige JElements übergeben!');
              return;
          }
          for (var JElement in JElements) {
              var AktVal;
              if (typeof JElements[JElement] === "object") {
                  generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset);
              } else {
                  try {
                      if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val;
                  } catch (e) {
                      log("Fehler: " + e);
                  }
                  if (AktVal == null) {
                      createState(preset + "." + id + "." + JElement, JElements[JElement], false);
                      AktVal = JElements[JElement];
                  }
                  if (AktVal != JElements[JElement]) {
                      if (isState2(preset + "." + id + "." + JElement)) setState(preset + "." + id + "." + JElement, JElements[JElement]);
                  }
              }
          }
      }
      
      
      /**
       * Checks if a a given state or part of state is existing.
       * This is a workaround, as getObject() or getState() throw warnings in the log.
       * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
       * See: https://forum.iobroker.net/topic/11354/
       * @param {string}    strStatePath     Input string of state, like 'javascript.0.switches.Osram.Bedroom'
       * @param {boolean}   [strict=false]   Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string
       * @return {boolean}                   true if state exists, false if not
       */
      function isState2(strStatePath, strict = true) {
          let mSelector;
          if (strict) {
              mSelector = $(strStatePath);
          } else {
              mSelector = $(strStatePath + "*");
          }
          if (mSelector.length > 0) {
              return true;
          } else {
              return false;
          }
      }
      
      
      // ########### Grundbedarf steuern
      // Es müssen Historydaten für den Wert des Smartmeters vorhanden sein, damit der niedrigeste wert der letzten x Minuten ermittelt werden kann
      
      function getLowestValue(id, minuten = 120) {
          return new Promise((resolve, reject) => {
              const now = new Date().getTime();
              const range = minuten * 60 * 1000;
              var myresult = 0;
              getHistory(0, {
                  id: id,
                  start: now - range,
                  end: now,
                  aggregate: 'minmax'
              }, function (err, result) {
                  if (err) {
                      reject(err);
                  } else if (result.length) {
                      let lowestValue = result[0].val;
                      for (let i = 1; i < result.length; i++) {
                          if (result[i].val < lowestValue) {
                              lowestValue = result[i].val;
                          }
                      }
                      myresult = Number(lowestValue);
                      resolve(myresult);
                  } else {
                      reject(new Error('No data'));
                  }
              });
          });
      }
      
      function SetBasePower() {
          if (isState2(ConfigData.SmartmeterID)) {
              getLowestValue(ConfigData.SmartmeterID, ConfigData.MinValueMin)
                  .then(lowestValue => {
                      if (lowestValue > 0) {
                          if (ConfigData.Debug) log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue);
                          var AndereVerbraucher = 0
                          const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[0].seriennummer + ".data.item.meta.ToHome_Power"
                          if (isState2(ToHomeId)) {
                              AndereVerbraucher = Number(getState(ToHomeId).val) / 10
                              var NewValue = lowestValue + AndereVerbraucher - ConfigData.BasePowerOffset
                              if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower
                              if (ConfigData.Debug) {
                                  log("AndereVerbraucher: " + AndereVerbraucher)
                                  log("Macht : " + (lowestValue + AndereVerbraucher) + "W Grundbedarf")
                                  log("- Offset von: " + ConfigData.BasePowerOffset + " W = " + (NewValue) + " W Grundbedarf")
                              }
                              setAC(NewValue * 10);
      
                          }
                      }
                  })
                  .catch(error => {
                      log("Fehler beim Abrufen des niedrigsten Werts: " + error);
                  });
          }
      }
      
      
      T A 2 Antworten Letzte Antwort
      1
      • W Waly_de

        @tdahinten

        Ok... Zufällig hab ich heute ein Script fertig gestellt, das vermutlich den meisten helfen wird.

        Man muss lediglich die App-Zugangsdaten und Seriennummern eingeben, den Rest macht das Teil hoffentlich von alleine. Ich hab auf den Mqtt-Adapter verzichtet und lieber einen eigenen Client genutzt. Damit gibt es keine "Binary/File Probleme" mehr.
        RAW Daten werden sowohl als HEX also auch dekodiert in States gespeichert. Alle einzelnen bekannten Daten werden automatisch als States angelegt.
        Bisher lässt sich nur ein State, nämlich die Einspeiseleistung schreiben ("...thing_property_set.setAC")

        Dann viel Spaß damit... bin sehr gespannt ob es bei Euch funktioniert!

        Benutzen auf eigene Gefahr !! ;-)

        /**
         * ecoflow-iobroker-connector.js
         * Version:      0.2    
         * Release date: 28.06.2023
         * Autor:        Waly_de 
         * Forum:        https://forum.iobroker.net/topic/54929/adapter-für-ecoflow-einbindung/155
         * 
         *
         * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 
         * It automatically creates known states under 0_userdata.
         * 
         * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 
         * used for the EcoFlow app, as well as the serial numbers of your devices.
         * 
         * Important: The second serial number entry should always correspond to the Powerstream as it is associated with control functions.
         * 
         * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 
         * Configure a history for this state so that the script can determine the lowest power consumption in the last 10 minutes (configurable). 
         * This value will be used to dynamically adjust the Powerstream's feed-in power.
         * 
         * You can adust Powerstream's feed-in power by your self. Just write to the State "...thing_property_set.setAC" (Watt * 10)
         * 
         * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states.
         * By modifying the "protoSource" constant, newly discovered data will also be automatically created.
         * 
         * The raw data of the interface is logged as a HEX string.
         * 
         * Please exercise caution as this is the initial version of the script. Use it at your own risk!
         *
         * Requirements:
         * - Install protobuf using the command "npm install protobufjs" from the terminal console.
         * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt".
         *
         * Note: It is encouraged to discover and publish missing data definitions to improve the script.
         * Suggestions, optimizations, and extensions are welcome at any time.
         *
         * Special thanks to all contributors for their valuable input and support.
         */
        
        /*******  YOUR DATA HERE  ****************/
        var ConfigData = {
            email: "your@mail.com",
            passwort: "yourAppPasswort!",
            seriennummern: [
                { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" }, //1. has to be your PowerStream, you can add more devices...
                { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
            ],
            SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P",
            BasePowerOffset: 50,
            MaxPower: 600,
            MinValueMin: 10,
            statesPrefix: "0_userdata.0.ecoflow",
            Debug: false
        };
        //***************************************/
        
        
        const protoSource = `
        syntax = "proto3";
        message PowerItem {
          optional Meta meta = 1;
          optional uint32 src = 2;
          optional uint32 dest = 3;
          optional uint32 unknown1 = 4;
          optional uint32 unknown2 = 5;
          optional uint32 unknown3 = 6;
          optional uint32 unknown4 = 7;
          optional uint32 cmdFunc = 8;
          optional CmdFunction cmdId = 9;
          optional uint32 unknown5 = 10;
          optional uint32 needAck = 11;
          uint64 timestamp = 14;
          optional uint32 unknown6 = 16; //3 Byte?
          optional uint32 unknown7 = 17; //3 Byte?
          optional string OS = 23;
          optional string serialNumber = 25;
        }
        message PowerMessage {
          PowerItem item = 1;
        }
        message Meta {
          optional int32 value = 1;
          optional int32 plugPower = 10; 
          optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen
          optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen
          optional int32 PV1_Power = 19;
          optional int32 M_Unknown1 = 21;
          optional int32 PV2_Power = 24;
          optional int32 From_Bat_Power = 29;
          optional int32 Batt_Poz = 31;
          optional int32 M_Unknown2 = 33;
          optional int32 M_Unknown3 = 35;
          optional int32 M_Unknown6 = 37;
          optional int32 ToHome_Power = 38;
          optional int32 M_Unknown9 = 43; //prio Power von summe PVx_Power abziehen ?
          optional int32 Needed_Power = 48;
          optional int32 M_Unknown4 = 59;
          optional int32 Bat_Minutes = 60;
        }
        enum CmdFunction {
            Unknown = 0;
            PermanentWattsPack = 129;
            SupplyPriorityPack = 130;
        }
        `;
        
        
        const mqtt = require('mqtt');
        const https = require('https');
        const protobuf = require("protobufjs");
        
        const mqttDaten = {
            UserID: '',
            User: '',
            Passwort: '',
            URL: '',
            Port: '',
            protocol: '',
            clientID: ''
        }
        
        /*=======================================================
          =========             Timer               ============
          =======================================================*/
        //alle 5 minuten
        schedule('*/1 * * * *', function () {
            SetBasePower();
        });
        
        
        
        await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort)
        async function getEcoFlowMqttData(email, password) {
            const options = {
                hostname: 'api.ecoflow.com',
                path: '/auth/login',
                method: 'POST',
                headers: {
                    'Host': 'api.ecoflow.com',
                    'lang': 'de-de',
                    'platform': 'android',
                    'sysversion': '11',
                    'version': '4.1.2.02',
                    'phonemodel': 'SM-X200',
                    'content-type': 'application/json',
                    'user-agent': 'okhttp/3.14.9'
                }
            };
        
            const data = {
                appVersion: "4.1.2.02",
                email: email,
                os: "android",
                osVersion: "30",
                password: Buffer.from(password).toString('base64'),
                scene: "IOT_APP",
                userType: "ECOFLOW"
            };
        
            function httpsRequest(options, data) {
                return new Promise((resolve, reject) => {
                    const req = https.request(options, res => {
                        let data = '';
                        res.on('data', chunk => {
                            data += chunk;
                        });
                        res.on('end', () => {
                            resolve(data);
                        });
                    });
        
                    req.on('error', error => {
                        reject(error);
                    });
        
                    if (data) {
                        req.write(JSON.stringify(data));
                    }
        
                    req.end();
                });
            }
        
            function uuidv4() {
                return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                    var r = Math.random() * 16 | 0,
                        v = c === 'x' ? r : (r & 0x3 | 0x8);
                    return v.toString(16);
                });
            }
        
            let response = await httpsRequest(options, data);
            try {
                let token = JSON.parse(response).data.token;
                let userid = JSON.parse(response).data.user.userId;
            } catch (error) {
                log(response)
                throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
            }
        
            let token = JSON.parse(response).data.token;
            let userid = JSON.parse(response).data.user.userId;
        
            options.path = `/iot-auth/app/certification?userId=${userid}`;
            options.method = 'GET';
            options.headers.authorization = `Bearer ${token}`;
            response = await httpsRequest(options);
            try {
                mqttDaten.Passwort = JSON.parse(response).data.certificatePassword
                mqttDaten.Port = JSON.parse(response).data.port
                mqttDaten.UserID = userid
                mqttDaten.User = JSON.parse(response).data.certificateAccount
                mqttDaten.URL = JSON.parse(response).data.url
                mqttDaten.protocol = JSON.parse(response).data.protocol
                mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid
            } catch (error) {
                log(response)
                throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
            }
            /*    
                console.log("UserID: " + userid);
                console.log("User: " + JSON.parse(response).data.certificateAccount);
                console.log("Passwort: " + JSON.parse(response).data.certificatePassword);
                console.log("URL: " + JSON.parse(response).data.url);
                console.log("Port: " + JSON.parse(response).data.port);
                console.log("protocol: " + JSON.parse(response).data.protocol);
                console.log("clientID: ANDROID_" + uuidv4() + "_" + userid);
            */
        
        }
        
        //################ MQTT Verbindung ##################
        
        // Verbindung herstellen
        const options = {
            port: mqttDaten.Port,
            clientId: mqttDaten.clientID,
            username: mqttDaten.User,
            password: mqttDaten.Passwort,
            protocol: mqttDaten.protocol
        };
        
        
        const client = mqtt.connect("mqtt://" + mqttDaten.URL, options);
        
        // Auf Verbindung hergestellt Ereignis reagieren
        client.on('connect', function () {
            console.log('Verbunden mit dem MQTT-Broker');
        
            ConfigData.seriennummern.forEach(item => {
                client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set');
                client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get');
                client.subscribe('/app/device/property/' + item.seriennummer);
            });
        });
        
        // Auf Nachricht empfangen Ereignis reagieren
        client.on('message', async function (topic, message) {
            var jsonMessage = ""
            const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
            await createMyState(mqState + ".RAW")
            try {
                jsonMessage = JSON.parse(message);
                if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
            } catch (error) {
                if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                await createMyState(mqState + ".RAW_HEX")
                setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                //log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                const messagedecoded = decodeAndPrint(message.toString('hex'))
                setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
            }
        });
        // Callback für Fehler
        client.on('error', function (error) {
            log('Fehler bei der MQTT-Verbindung:' + error, error);
        });
        
        // close connection if script stopped
        onStop(function (callback) {
            if (client) {
                // close connection
                client.end();
                log("MQTT-Client beendet")
            }
            callback();
        }, 2000);
        //*/
        
        
        
        async function createMyState(name) {
            if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) {
                await createStateAsync(ConfigData.statesPrefix + '.' + name, {
                    name: name.split('.').pop(),
                    role: 'state',
                    type: 'string',
                    read: true,
                    write: true
                });
            }
        }
        
        function decodeAndPrint(hexString) {
            const root = protobuf.parse(protoSource).root;
            const PowerMessage = root.lookupType("PowerMessage");
            const message = PowerMessage.decode(Buffer.from(hexString, "hex"));
            const object = PowerMessage.toObject(message, { defaults: false });
            return JSON.stringify(object);
        }
        
        function SendProto(protomsg, topic) {
            const root = protobuf.parse(protoSource).root;
            const PowerMessage = root.lookupType("PowerMessage");
            const message = PowerMessage.create(JSON.parse(protomsg));
            const messageBuffer = PowerMessage.encode(message).finish();
            //log("Modifizierter Hex-String:" +  Buffer.from(messageBuffer).toString("hex"));
            //log("topic:" +  topic);
            client.publish(topic, messageBuffer, { qos: 1 }, function (error) {
                if (error) {
                    console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error);
                } else {
                    if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.');
                }
            });
        }
        
        //############ Funktionen zum setzen von Werten
        
        const musterSetAC = `
        {
          "item": {
            "meta": {
              "value": 1300
            },
            "src": 32,
            "dest": 53,
            "unknown1": 1,
            "unknown2": 1,
            "unknown4": 3,
            "cmdFunc": 20,
            "cmdId": 129,
            "unknown5": 3,
            "needAck": 1,
            "timestamp": {
              "low": 35061401,
              "high": 0,
              "unsigned": true
            },
            "unknown6": 19,
            "unknown7": 1,
            "OS": "ios",
            "serialNumber": "ABCxxxxxxx123"
          }
        }
        `
        await createMyState('app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC')
        on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) {
            setAC(Number(obj.state.val))
            setState(obj.id, obj.state.val, true);
        });
        
        function setAC(Value = 1000) {
            const updatedMusterSetAC = JSON.parse(musterSetAC);
            updatedMusterSetAC.item.timestamp.low = Date.now().toString();
            updatedMusterSetAC.item.meta.value = Value
            updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
            SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
        }
        
        function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0.test") {
            if (!JElements || typeof JElements !== 'object') {
                log('Ungültige JElements übergeben!');
                return;
            }
            for (var JElement in JElements) {
                var AktVal;
                if (typeof JElements[JElement] === "object") {
                    generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset);
                } else {
                    try {
                        if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val;
                    } catch (e) {
                        log("Fehler: " + e);
                    }
                    if (AktVal == null) {
                        createState(preset + "." + id + "." + JElement, JElements[JElement], false);
                        AktVal = JElements[JElement];
                    }
                    if (AktVal != JElements[JElement]) {
                        if (isState2(preset + "." + id + "." + JElement)) setState(preset + "." + id + "." + JElement, JElements[JElement]);
                    }
                }
            }
        }
        
        
        /**
         * Checks if a a given state or part of state is existing.
         * This is a workaround, as getObject() or getState() throw warnings in the log.
         * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
         * See: https://forum.iobroker.net/topic/11354/
         * @param {string}    strStatePath     Input string of state, like 'javascript.0.switches.Osram.Bedroom'
         * @param {boolean}   [strict=false]   Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string
         * @return {boolean}                   true if state exists, false if not
         */
        function isState2(strStatePath, strict = true) {
            let mSelector;
            if (strict) {
                mSelector = $(strStatePath);
            } else {
                mSelector = $(strStatePath + "*");
            }
            if (mSelector.length > 0) {
                return true;
            } else {
                return false;
            }
        }
        
        
        // ########### Grundbedarf steuern
        // Es müssen Historydaten für den Wert des Smartmeters vorhanden sein, damit der niedrigeste wert der letzten x Minuten ermittelt werden kann
        
        function getLowestValue(id, minuten = 120) {
            return new Promise((resolve, reject) => {
                const now = new Date().getTime();
                const range = minuten * 60 * 1000;
                var myresult = 0;
                getHistory(0, {
                    id: id,
                    start: now - range,
                    end: now,
                    aggregate: 'minmax'
                }, function (err, result) {
                    if (err) {
                        reject(err);
                    } else if (result.length) {
                        let lowestValue = result[0].val;
                        for (let i = 1; i < result.length; i++) {
                            if (result[i].val < lowestValue) {
                                lowestValue = result[i].val;
                            }
                        }
                        myresult = Number(lowestValue);
                        resolve(myresult);
                    } else {
                        reject(new Error('No data'));
                    }
                });
            });
        }
        
        function SetBasePower() {
            if (isState2(ConfigData.SmartmeterID)) {
                getLowestValue(ConfigData.SmartmeterID, ConfigData.MinValueMin)
                    .then(lowestValue => {
                        if (lowestValue > 0) {
                            if (ConfigData.Debug) log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue);
                            var AndereVerbraucher = 0
                            const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[0].seriennummer + ".data.item.meta.ToHome_Power"
                            if (isState2(ToHomeId)) {
                                AndereVerbraucher = Number(getState(ToHomeId).val) / 10
                                var NewValue = lowestValue + AndereVerbraucher - ConfigData.BasePowerOffset
                                if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower
                                if (ConfigData.Debug) {
                                    log("AndereVerbraucher: " + AndereVerbraucher)
                                    log("Macht : " + (lowestValue + AndereVerbraucher) + "W Grundbedarf")
                                    log("- Offset von: " + ConfigData.BasePowerOffset + " W = " + (NewValue) + " W Grundbedarf")
                                }
                                setAC(NewValue * 10);
        
                            }
                        }
                    })
                    .catch(error => {
                        log("Fehler beim Abrufen des niedrigsten Werts: " + error);
                    });
            }
        }
        
        
        T Offline
        T Offline
        TDahinten
        schrieb am zuletzt editiert von TDahinten
        #159

        @waly_de said in Adapter für Ecoflow Einbindung:

        Also erstmal vielen Dank, für die schnelle Unterstützung. :+1:

        Habe soweit alles im Script befüllt. Ebenfalls die mqttDaten.
        Die notwendigen Installationen wurden durchgeführt.

        Folgende Fehler werden im Log gemeldet::

        error javascript.0 (1474) script.js.common.PowerStream_Script: Error: illegal token '{' (line 5)
        error javascript.0 (1474) at decodeAndPrint (script.js.common.PowerStream_Script:629:27)
        error javascript.0 (1474) at MqttClient.<anonymous> (script.js.common.PowerStream_Script:557:32)

        W 1 Antwort Letzte Antwort
        0
        • T TDahinten

          @waly_de said in Adapter für Ecoflow Einbindung:

          Also erstmal vielen Dank, für die schnelle Unterstützung. :+1:

          Habe soweit alles im Script befüllt. Ebenfalls die mqttDaten.
          Die notwendigen Installationen wurden durchgeführt.

          Folgende Fehler werden im Log gemeldet::

          error javascript.0 (1474) script.js.common.PowerStream_Script: Error: illegal token '{' (line 5)
          error javascript.0 (1474) at decodeAndPrint (script.js.common.PowerStream_Script:629:27)
          error javascript.0 (1474) at MqttClient.<anonymous> (script.js.common.PowerStream_Script:557:32)

          W Offline
          W Offline
          Waly_de
          schrieb am zuletzt editiert von
          #160

          @tdahinten hmm ... guck mal bitte ob du ein Komma zu viel oder zu wenig im Bereich der Seriennummern hast. die {} müssen ein Komma getrennt sein:

          seriennummern: [
                  { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" },
                  { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
              ],
          
          1 Antwort Letzte Antwort
          0
          • W Waly_de

            @tdahinten

            Ok... Zufällig hab ich heute ein Script fertig gestellt, das vermutlich den meisten helfen wird.

            Man muss lediglich die App-Zugangsdaten und Seriennummern eingeben, den Rest macht das Teil hoffentlich von alleine. Ich hab auf den Mqtt-Adapter verzichtet und lieber einen eigenen Client genutzt. Damit gibt es keine "Binary/File Probleme" mehr.
            RAW Daten werden sowohl als HEX also auch dekodiert in States gespeichert. Alle einzelnen bekannten Daten werden automatisch als States angelegt.
            Bisher lässt sich nur ein State, nämlich die Einspeiseleistung schreiben ("...thing_property_set.setAC")

            Dann viel Spaß damit... bin sehr gespannt ob es bei Euch funktioniert!

            Benutzen auf eigene Gefahr !! ;-)

            /**
             * ecoflow-iobroker-connector.js
             * Version:      0.2    
             * Release date: 28.06.2023
             * Autor:        Waly_de 
             * Forum:        https://forum.iobroker.net/topic/54929/adapter-für-ecoflow-einbindung/155
             * 
             *
             * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 
             * It automatically creates known states under 0_userdata.
             * 
             * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 
             * used for the EcoFlow app, as well as the serial numbers of your devices.
             * 
             * Important: The second serial number entry should always correspond to the Powerstream as it is associated with control functions.
             * 
             * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 
             * Configure a history for this state so that the script can determine the lowest power consumption in the last 10 minutes (configurable). 
             * This value will be used to dynamically adjust the Powerstream's feed-in power.
             * 
             * You can adust Powerstream's feed-in power by your self. Just write to the State "...thing_property_set.setAC" (Watt * 10)
             * 
             * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states.
             * By modifying the "protoSource" constant, newly discovered data will also be automatically created.
             * 
             * The raw data of the interface is logged as a HEX string.
             * 
             * Please exercise caution as this is the initial version of the script. Use it at your own risk!
             *
             * Requirements:
             * - Install protobuf using the command "npm install protobufjs" from the terminal console.
             * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt".
             *
             * Note: It is encouraged to discover and publish missing data definitions to improve the script.
             * Suggestions, optimizations, and extensions are welcome at any time.
             *
             * Special thanks to all contributors for their valuable input and support.
             */
            
            /*******  YOUR DATA HERE  ****************/
            var ConfigData = {
                email: "your@mail.com",
                passwort: "yourAppPasswort!",
                seriennummern: [
                    { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" }, //1. has to be your PowerStream, you can add more devices...
                    { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
                ],
                SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P",
                BasePowerOffset: 50,
                MaxPower: 600,
                MinValueMin: 10,
                statesPrefix: "0_userdata.0.ecoflow",
                Debug: false
            };
            //***************************************/
            
            
            const protoSource = `
            syntax = "proto3";
            message PowerItem {
              optional Meta meta = 1;
              optional uint32 src = 2;
              optional uint32 dest = 3;
              optional uint32 unknown1 = 4;
              optional uint32 unknown2 = 5;
              optional uint32 unknown3 = 6;
              optional uint32 unknown4 = 7;
              optional uint32 cmdFunc = 8;
              optional CmdFunction cmdId = 9;
              optional uint32 unknown5 = 10;
              optional uint32 needAck = 11;
              uint64 timestamp = 14;
              optional uint32 unknown6 = 16; //3 Byte?
              optional uint32 unknown7 = 17; //3 Byte?
              optional string OS = 23;
              optional string serialNumber = 25;
            }
            message PowerMessage {
              PowerItem item = 1;
            }
            message Meta {
              optional int32 value = 1;
              optional int32 plugPower = 10; 
              optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen
              optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen
              optional int32 PV1_Power = 19;
              optional int32 M_Unknown1 = 21;
              optional int32 PV2_Power = 24;
              optional int32 From_Bat_Power = 29;
              optional int32 Batt_Poz = 31;
              optional int32 M_Unknown2 = 33;
              optional int32 M_Unknown3 = 35;
              optional int32 M_Unknown6 = 37;
              optional int32 ToHome_Power = 38;
              optional int32 M_Unknown9 = 43; //prio Power von summe PVx_Power abziehen ?
              optional int32 Needed_Power = 48;
              optional int32 M_Unknown4 = 59;
              optional int32 Bat_Minutes = 60;
            }
            enum CmdFunction {
                Unknown = 0;
                PermanentWattsPack = 129;
                SupplyPriorityPack = 130;
            }
            `;
            
            
            const mqtt = require('mqtt');
            const https = require('https');
            const protobuf = require("protobufjs");
            
            const mqttDaten = {
                UserID: '',
                User: '',
                Passwort: '',
                URL: '',
                Port: '',
                protocol: '',
                clientID: ''
            }
            
            /*=======================================================
              =========             Timer               ============
              =======================================================*/
            //alle 5 minuten
            schedule('*/1 * * * *', function () {
                SetBasePower();
            });
            
            
            
            await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort)
            async function getEcoFlowMqttData(email, password) {
                const options = {
                    hostname: 'api.ecoflow.com',
                    path: '/auth/login',
                    method: 'POST',
                    headers: {
                        'Host': 'api.ecoflow.com',
                        'lang': 'de-de',
                        'platform': 'android',
                        'sysversion': '11',
                        'version': '4.1.2.02',
                        'phonemodel': 'SM-X200',
                        'content-type': 'application/json',
                        'user-agent': 'okhttp/3.14.9'
                    }
                };
            
                const data = {
                    appVersion: "4.1.2.02",
                    email: email,
                    os: "android",
                    osVersion: "30",
                    password: Buffer.from(password).toString('base64'),
                    scene: "IOT_APP",
                    userType: "ECOFLOW"
                };
            
                function httpsRequest(options, data) {
                    return new Promise((resolve, reject) => {
                        const req = https.request(options, res => {
                            let data = '';
                            res.on('data', chunk => {
                                data += chunk;
                            });
                            res.on('end', () => {
                                resolve(data);
                            });
                        });
            
                        req.on('error', error => {
                            reject(error);
                        });
            
                        if (data) {
                            req.write(JSON.stringify(data));
                        }
            
                        req.end();
                    });
                }
            
                function uuidv4() {
                    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                        var r = Math.random() * 16 | 0,
                            v = c === 'x' ? r : (r & 0x3 | 0x8);
                        return v.toString(16);
                    });
                }
            
                let response = await httpsRequest(options, data);
                try {
                    let token = JSON.parse(response).data.token;
                    let userid = JSON.parse(response).data.user.userId;
                } catch (error) {
                    log(response)
                    throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                }
            
                let token = JSON.parse(response).data.token;
                let userid = JSON.parse(response).data.user.userId;
            
                options.path = `/iot-auth/app/certification?userId=${userid}`;
                options.method = 'GET';
                options.headers.authorization = `Bearer ${token}`;
                response = await httpsRequest(options);
                try {
                    mqttDaten.Passwort = JSON.parse(response).data.certificatePassword
                    mqttDaten.Port = JSON.parse(response).data.port
                    mqttDaten.UserID = userid
                    mqttDaten.User = JSON.parse(response).data.certificateAccount
                    mqttDaten.URL = JSON.parse(response).data.url
                    mqttDaten.protocol = JSON.parse(response).data.protocol
                    mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid
                } catch (error) {
                    log(response)
                    throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                }
                /*    
                    console.log("UserID: " + userid);
                    console.log("User: " + JSON.parse(response).data.certificateAccount);
                    console.log("Passwort: " + JSON.parse(response).data.certificatePassword);
                    console.log("URL: " + JSON.parse(response).data.url);
                    console.log("Port: " + JSON.parse(response).data.port);
                    console.log("protocol: " + JSON.parse(response).data.protocol);
                    console.log("clientID: ANDROID_" + uuidv4() + "_" + userid);
                */
            
            }
            
            //################ MQTT Verbindung ##################
            
            // Verbindung herstellen
            const options = {
                port: mqttDaten.Port,
                clientId: mqttDaten.clientID,
                username: mqttDaten.User,
                password: mqttDaten.Passwort,
                protocol: mqttDaten.protocol
            };
            
            
            const client = mqtt.connect("mqtt://" + mqttDaten.URL, options);
            
            // Auf Verbindung hergestellt Ereignis reagieren
            client.on('connect', function () {
                console.log('Verbunden mit dem MQTT-Broker');
            
                ConfigData.seriennummern.forEach(item => {
                    client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set');
                    client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get');
                    client.subscribe('/app/device/property/' + item.seriennummer);
                });
            });
            
            // Auf Nachricht empfangen Ereignis reagieren
            client.on('message', async function (topic, message) {
                var jsonMessage = ""
                const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
                await createMyState(mqState + ".RAW")
                try {
                    jsonMessage = JSON.parse(message);
                    if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                    setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                    generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
                } catch (error) {
                    if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                    await createMyState(mqState + ".RAW_HEX")
                    setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                    //log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                    const messagedecoded = decodeAndPrint(message.toString('hex'))
                    setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                    generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
                }
            });
            // Callback für Fehler
            client.on('error', function (error) {
                log('Fehler bei der MQTT-Verbindung:' + error, error);
            });
            
            // close connection if script stopped
            onStop(function (callback) {
                if (client) {
                    // close connection
                    client.end();
                    log("MQTT-Client beendet")
                }
                callback();
            }, 2000);
            //*/
            
            
            
            async function createMyState(name) {
                if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) {
                    await createStateAsync(ConfigData.statesPrefix + '.' + name, {
                        name: name.split('.').pop(),
                        role: 'state',
                        type: 'string',
                        read: true,
                        write: true
                    });
                }
            }
            
            function decodeAndPrint(hexString) {
                const root = protobuf.parse(protoSource).root;
                const PowerMessage = root.lookupType("PowerMessage");
                const message = PowerMessage.decode(Buffer.from(hexString, "hex"));
                const object = PowerMessage.toObject(message, { defaults: false });
                return JSON.stringify(object);
            }
            
            function SendProto(protomsg, topic) {
                const root = protobuf.parse(protoSource).root;
                const PowerMessage = root.lookupType("PowerMessage");
                const message = PowerMessage.create(JSON.parse(protomsg));
                const messageBuffer = PowerMessage.encode(message).finish();
                //log("Modifizierter Hex-String:" +  Buffer.from(messageBuffer).toString("hex"));
                //log("topic:" +  topic);
                client.publish(topic, messageBuffer, { qos: 1 }, function (error) {
                    if (error) {
                        console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error);
                    } else {
                        if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.');
                    }
                });
            }
            
            //############ Funktionen zum setzen von Werten
            
            const musterSetAC = `
            {
              "item": {
                "meta": {
                  "value": 1300
                },
                "src": 32,
                "dest": 53,
                "unknown1": 1,
                "unknown2": 1,
                "unknown4": 3,
                "cmdFunc": 20,
                "cmdId": 129,
                "unknown5": 3,
                "needAck": 1,
                "timestamp": {
                  "low": 35061401,
                  "high": 0,
                  "unsigned": true
                },
                "unknown6": 19,
                "unknown7": 1,
                "OS": "ios",
                "serialNumber": "ABCxxxxxxx123"
              }
            }
            `
            await createMyState('app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC')
            on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) {
                setAC(Number(obj.state.val))
                setState(obj.id, obj.state.val, true);
            });
            
            function setAC(Value = 1000) {
                const updatedMusterSetAC = JSON.parse(musterSetAC);
                updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                updatedMusterSetAC.item.meta.value = Value
                updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
            }
            
            function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0.test") {
                if (!JElements || typeof JElements !== 'object') {
                    log('Ungültige JElements übergeben!');
                    return;
                }
                for (var JElement in JElements) {
                    var AktVal;
                    if (typeof JElements[JElement] === "object") {
                        generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset);
                    } else {
                        try {
                            if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val;
                        } catch (e) {
                            log("Fehler: " + e);
                        }
                        if (AktVal == null) {
                            createState(preset + "." + id + "." + JElement, JElements[JElement], false);
                            AktVal = JElements[JElement];
                        }
                        if (AktVal != JElements[JElement]) {
                            if (isState2(preset + "." + id + "." + JElement)) setState(preset + "." + id + "." + JElement, JElements[JElement]);
                        }
                    }
                }
            }
            
            
            /**
             * Checks if a a given state or part of state is existing.
             * This is a workaround, as getObject() or getState() throw warnings in the log.
             * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
             * See: https://forum.iobroker.net/topic/11354/
             * @param {string}    strStatePath     Input string of state, like 'javascript.0.switches.Osram.Bedroom'
             * @param {boolean}   [strict=false]   Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string
             * @return {boolean}                   true if state exists, false if not
             */
            function isState2(strStatePath, strict = true) {
                let mSelector;
                if (strict) {
                    mSelector = $(strStatePath);
                } else {
                    mSelector = $(strStatePath + "*");
                }
                if (mSelector.length > 0) {
                    return true;
                } else {
                    return false;
                }
            }
            
            
            // ########### Grundbedarf steuern
            // Es müssen Historydaten für den Wert des Smartmeters vorhanden sein, damit der niedrigeste wert der letzten x Minuten ermittelt werden kann
            
            function getLowestValue(id, minuten = 120) {
                return new Promise((resolve, reject) => {
                    const now = new Date().getTime();
                    const range = minuten * 60 * 1000;
                    var myresult = 0;
                    getHistory(0, {
                        id: id,
                        start: now - range,
                        end: now,
                        aggregate: 'minmax'
                    }, function (err, result) {
                        if (err) {
                            reject(err);
                        } else if (result.length) {
                            let lowestValue = result[0].val;
                            for (let i = 1; i < result.length; i++) {
                                if (result[i].val < lowestValue) {
                                    lowestValue = result[i].val;
                                }
                            }
                            myresult = Number(lowestValue);
                            resolve(myresult);
                        } else {
                            reject(new Error('No data'));
                        }
                    });
                });
            }
            
            function SetBasePower() {
                if (isState2(ConfigData.SmartmeterID)) {
                    getLowestValue(ConfigData.SmartmeterID, ConfigData.MinValueMin)
                        .then(lowestValue => {
                            if (lowestValue > 0) {
                                if (ConfigData.Debug) log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue);
                                var AndereVerbraucher = 0
                                const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[0].seriennummer + ".data.item.meta.ToHome_Power"
                                if (isState2(ToHomeId)) {
                                    AndereVerbraucher = Number(getState(ToHomeId).val) / 10
                                    var NewValue = lowestValue + AndereVerbraucher - ConfigData.BasePowerOffset
                                    if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower
                                    if (ConfigData.Debug) {
                                        log("AndereVerbraucher: " + AndereVerbraucher)
                                        log("Macht : " + (lowestValue + AndereVerbraucher) + "W Grundbedarf")
                                        log("- Offset von: " + ConfigData.BasePowerOffset + " W = " + (NewValue) + " W Grundbedarf")
                                    }
                                    setAC(NewValue * 10);
            
                                }
                            }
                        })
                        .catch(error => {
                            log("Fehler beim Abrufen des niedrigsten Werts: " + error);
                        });
                }
            }
            
            
            A Offline
            A Offline
            applepro
            schrieb am zuletzt editiert von
            #161

            @waly_de Wow!!! Besten Dank von meiner Seite, ich wollte Ihn gerade testen, jedoch bekomme ich auch eine ähnliche Fehlermedlung:

            08:59:38.144	error	javascript.0 (444) script.js.Solar.Abfrage_MQTT: Error: illegal token '{' (line 3)
            08:59:38.145	error	javascript.0 (444) at decodeAndPrint (script.js.Solar.Abfrage_MQTT:314:27)
            08:59:38.145	error	javascript.0 (444) at MqttClient.<anonymous> (script.js.Solar.Abfrage_MQTT:278:32)
            

            Vielen Dank schon einmal für deine Hilfe :)

            Ich hatte mir in der Zwischenzeit alle States als HEX ausgelesen und mir so behelfsweise eine kurzes Skript geschrieben, welches mit Hilfe meines Powerfox Meters den jeweiligen Buffer sendet und so die Leistung anpasst, leider musste ich immer den aktuellen Einspeisewert fortschreiben, da ich diese ja nicht auslesen konnte.

            W 1 Antwort Letzte Antwort
            0
            • A applepro

              @waly_de Wow!!! Besten Dank von meiner Seite, ich wollte Ihn gerade testen, jedoch bekomme ich auch eine ähnliche Fehlermedlung:

              08:59:38.144	error	javascript.0 (444) script.js.Solar.Abfrage_MQTT: Error: illegal token '{' (line 3)
              08:59:38.145	error	javascript.0 (444) at decodeAndPrint (script.js.Solar.Abfrage_MQTT:314:27)
              08:59:38.145	error	javascript.0 (444) at MqttClient.<anonymous> (script.js.Solar.Abfrage_MQTT:278:32)
              

              Vielen Dank schon einmal für deine Hilfe :)

              Ich hatte mir in der Zwischenzeit alle States als HEX ausgelesen und mir so behelfsweise eine kurzes Skript geschrieben, welches mit Hilfe meines Powerfox Meters den jeweiligen Buffer sendet und so die Leistung anpasst, leider musste ich immer den aktuellen Einspeisewert fortschreiben, da ich diese ja nicht auslesen konnte.

              W Offline
              W Offline
              Waly_de
              schrieb am zuletzt editiert von
              #162

              @applepro Bitte noch mal kopieren... Das Forum hat beim Einfügen in die protoSource lustige Eintrage gemacht... sollte jetzt klappen...

              A 1 Antwort Letzte Antwort
              1
              • W Waly_de

                @applepro Bitte noch mal kopieren... Das Forum hat beim Einfügen in die protoSource lustige Eintrage gemacht... sollte jetzt klappen...

                A Offline
                A Offline
                applepro
                schrieb am zuletzt editiert von applepro
                #163

                @waly_de Läuft alles! Edit: Habe nun mein Skript mit deinen laufenden Daten gefühlt. setAC wird jede Minute neu berechnet und gesetzt. Endlich kann man auch in 1er Schritten und nicht nur in 10er Schritten die Leistung verstellen. Und endlich habe ich eine permanente 0-Einspeisung! :)

                W 1 Antwort Letzte Antwort
                0
                • A applepro

                  @waly_de Läuft alles! Edit: Habe nun mein Skript mit deinen laufenden Daten gefühlt. setAC wird jede Minute neu berechnet und gesetzt. Endlich kann man auch in 1er Schritten und nicht nur in 10er Schritten die Leistung verstellen. Und endlich habe ich eine permanente 0-Einspeisung! :)

                  W Offline
                  W Offline
                  Waly_de
                  schrieb am zuletzt editiert von Waly_de
                  #164

                  @applepro
                  Super das es klappt :blush: :fireworks:

                  hier eine neue Version. Es gab noch ein Problem beim anlegen der States.
                  Und ich versuche hier ein Phänomen zu beseitigen: Der MQTT hört auf Nachrichten zu senden, wenn man die App auf dem Handy beendet... Bin noch nicht sicher ob es jetzt durchläuft...

                  /**
                   * ecoflow-iobroker-connector.js
                   * Version:      0.4    
                   * Release date: 29.06.2023
                   * Autor:        Waly_de 
                   * Forum:        https://forum.iobroker.net/topic/54929/adapter-für-ecoflow-einbindung/155
                   * 
                   *
                   * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 
                   * It automatically creates known states under 0_userdata.
                   * 
                   * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 
                   * used for the EcoFlow app, as well as the serial numbers of your devices.
                   * 
                   * Important: The second serial number entry should always correspond to the Powerstream as it is associated with control functions.
                   * 
                   * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 
                   * Configure a history for this state so that the script can determine the lowest power consumption in the last 10 minutes (configurable). 
                   * This value will be used to dynamically adjust the Powerstream's feed-in power.
                   * 
                   * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states.
                   * By modifying the "protoSource" constant, newly discovered data will also be automatically created.
                   * 
                   * The raw data of the interface is logged as a HEX string.
                   * 
                   * Please exercise caution as this is the initial version of the script. Use it at your own risk!
                   *
                   * Requirements:
                   * - Install protobuf using the command "npm install protobufjs" from the terminal console.
                   * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt".
                   *
                   * Note: It is encouraged to discover and publish missing data definitions to improve the script.
                   * Suggestions, optimizations, and extensions are welcome at any time.
                   *
                   * Special thanks to all contributors for their valuable input and support.
                   * 
                   * Changelog:
                   * -----------------------------------------------------------
                   * (0.4) 29.06.2023
                   * Da der MQTT von ecoflow regelmäßig aufhört zu senden, vor allem, wenn die App genutzt und komplett geschlossen wird, 
                   * habe ich eine Überwachung der letzten ankommenden Nachrichten eingebaut. Kommt 5 Minuten lang nichts neues vom PowerStream,
                   * Wird die Verbindung zum MQTT kompett neu aufgebaut.
                   * 
                   * Ein Fehler bei der Erstellung der States wurde beseitigt
                   * -----------------------------------------------------------
                   * 
                   */
                  
                  /*******  YOUR DATA HERE  ****************
                  var ConfigData = {
                      email: "your@mail.com",
                      passwort: "yourAppPasswort!",
                      seriennummern: [
                          { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" }, //1. has to be your PowerStream, you can add more devices...
                          { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
                      ],
                      SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P",     //State, das den aktuellen Gesamtverbrauch in Watt enthält
                      BasePowerOffset: 50,                                //wird vom aktuellen Verbrauch abgezogen um die Einspeiseleistung zu berechnen 
                      MaxPower: 600,                                      //Der höchst mögliche wert in Watt für die Einspeiseleistung
                      MinValueMin: 10,                                    //Der Zeitraum in Minuten, aus dem der letzte Gesamtverbrauchs-Minimalwert geholt werden soll 
                      ReconnectMin: 30,                                   //Zeit in Minuten, nach der die Anwendung neu gestartet wird, wenn keine neuen Daten eintreffen
                      statesPrefix: "0_userdata.0.ecoflow",
                      Debug: true
                  };
                  //***************************************/
                  
                  const protoSource = `
                  syntax = "proto3";
                  message PowerItem {
                    optional Meta meta = 1;
                    optional uint32 src = 2;
                    optional uint32 dest = 3;
                    optional uint32 unknown1 = 4;
                    optional uint32 unknown2 = 5;
                    optional uint32 unknown3 = 6;
                    optional uint32 unknown4 = 7;
                    optional uint32 cmdFunc = 8;
                    optional CmdFunction cmdId = 9;
                    optional uint32 unknown5 = 10;
                    optional uint32 needAck = 11;
                    uint64 timestamp = 14;
                    optional uint32 unknown6 = 16; //3 Byte?
                    optional uint32 unknown7 = 17; //3 Byte?
                    optional string OS = 23;
                    optional string serialNumber = 25;
                  }
                  message PowerMessage {
                    PowerItem item = 1;
                  }
                  message Meta {
                    optional int32 value = 1;
                    optional int32 plugPower = 10; 
                    optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen
                    optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen
                    optional int32 PV1_Power = 19;
                    optional int32 M_Unknown1 = 21;
                    optional int32 PV2_Power = 24;
                    optional int32 From_Bat_Power = 29;
                    optional int32 Batt_Poz = 31;
                    optional int32 M_Unknown2 = 33;
                    optional int32 M_Unknown3 = 35;
                    optional int32 M_Unknown6 = 37;
                    optional int32 ToHome_Power = 38;
                    optional int32 M_Unknown9 = 43; //prio Power von summe PVx_Power abziehen ?
                    optional int32 Needed_Power = 48;
                    optional int32 M_Unknown4 = 59;
                    optional int32 Bat_Minutes = 60;
                  }
                  enum CmdFunction {
                      Unknown = 0;
                      PermanentWattsPack = 129;
                      SupplyPriorityPack = 130;
                  }
                  `;
                  
                  const mqtt = require('mqtt');
                  const https = require('https');
                  const protobuf = require("protobufjs");
                  
                  const mqttDaten = {
                      UserID: '',
                      User: '',
                      Passwort: '',
                      URL: '',
                      Port: '',
                      protocol: '',
                      clientID: ''
                  }
                  
                  /*=======================================================
                    =========             Timer               ============
                    =======================================================*/
                  //alle 5 minuten
                  schedule('*/1 * * * *', function () {
                      CheckforReconnect(function () {
                          SetBasePower();
                      });
                  });
                  
                  await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort)
                  async function getEcoFlowMqttData(email, password) {
                      const options = {
                          hostname: 'api.ecoflow.com',
                          path: '/auth/login',
                          method: 'POST',
                          headers: {
                              'Host': 'api.ecoflow.com',
                              'lang': 'de-de',
                              'platform': 'android',
                              'sysversion': '11',
                              'version': '4.1.2.02',
                              'phonemodel': 'SM-X200',
                              'content-type': 'application/json',
                              'user-agent': 'okhttp/3.14.9'
                          }
                      };
                  
                      const data = {
                          appVersion: "4.1.2.02",
                          email: email,
                          os: "android",
                          osVersion: "30",
                          password: Buffer.from(password).toString('base64'),
                          scene: "IOT_APP",
                          userType: "ECOFLOW"
                      };
                  
                      function httpsRequest(options, data) {
                          return new Promise((resolve, reject) => {
                              const req = https.request(options, res => {
                                  let data = '';
                                  res.on('data', chunk => {
                                      data += chunk;
                                  });
                                  res.on('end', () => {
                                      resolve(data);
                                  });
                              });
                  
                              req.on('error', error => {
                                  reject(error);
                              });
                  
                              if (data) {
                                  req.write(JSON.stringify(data));
                              }
                  
                              req.end();
                          });
                      }
                  
                      function uuidv4() {
                          return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                              var r = Math.random() * 16 | 0,
                                  v = c === 'x' ? r : (r & 0x3 | 0x8);
                              return v.toString(16);
                          });
                      }
                  
                      let response = await httpsRequest(options, data);
                      try {
                          let token = JSON.parse(response).data.token;
                          let userid = JSON.parse(response).data.user.userId;
                      } catch (error) {
                          log(response)
                          throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                      }
                  
                      let token = JSON.parse(response).data.token;
                      let userid = JSON.parse(response).data.user.userId;
                  
                      options.path = `/iot-auth/app/certification?userId=${userid}`;
                      options.method = 'GET';
                      options.headers.authorization = `Bearer ${token}`;
                      response = await httpsRequest(options);
                      try {
                          mqttDaten.Passwort = JSON.parse(response).data.certificatePassword
                          mqttDaten.Port = JSON.parse(response).data.port
                          mqttDaten.UserID = userid
                          mqttDaten.User = JSON.parse(response).data.certificateAccount
                          mqttDaten.URL = JSON.parse(response).data.url
                          mqttDaten.protocol = JSON.parse(response).data.protocol
                          mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid
                      } catch (error) {
                          log(response)
                          throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                      }
                      /*    
                          console.log("UserID: " + userid);
                          console.log("User: " + JSON.parse(response).data.certificateAccount);
                          console.log("Passwort: " + JSON.parse(response).data.certificatePassword);
                          console.log("URL: " + JSON.parse(response).data.url);
                          console.log("Port: " + JSON.parse(response).data.port);
                          console.log("protocol: " + JSON.parse(response).data.protocol);
                          console.log("clientID: ANDROID_" + uuidv4() + "_" + userid);
                      */
                  
                  }
                  
                  await createMyState("LastTopic")
                  
                  //################ MQTT Verbindung ##################
                  function setupMQTTConnection() {
                      // Verbindung herstellen
                      const options = {
                          port: mqttDaten.Port,
                          clientId: mqttDaten.clientID,
                          username: mqttDaten.User,
                          password: mqttDaten.Passwort,
                          protocol: mqttDaten.protocol
                      };
                  
                      const client = mqtt.connect("mqtt://" + mqttDaten.URL, options);
                  
                      // Event-Handler für Verbindungsaufbau
                      client.on('connect', function () {
                          console.log('Verbunden mit dem Ecoflow MQTT-Broker');
                          SubscribeEco();
                      });
                  
                      // Event-Handler für Fehler
                      client.on('error', function (error) {
                          console.log('Fehler bei der Ecoflow MQTT-Verbindung: ' + error);
                      });
                  
                      function SubscribeEco() {
                          ConfigData.seriennummern.forEach(item => {
                              client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set');
                              client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get');
                              client.subscribe('/app/device/property/' + item.seriennummer);
                              setmusterGetPS();
                          });
                      }
                  
                      // Auf Nachricht empfangen Ereignis reagieren
                      client.on('message', async function (topic, message) {
                          var jsonMessage = ""
                          const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
                          await createMyState(mqState + ".RAW")
                          setState(ConfigData.statesPrefix + ".LastTopic", topic)
                          try {
                              jsonMessage = JSON.parse(message);
                              if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                              generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
                          } catch (error) {
                              if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                              await createMyState(mqState + ".RAW_HEX")
                              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                              //log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                              const messagedecoded = decodeAndPrint(message.toString('hex'))
                              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                              generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
                          }
                      });
                  
                      // Callback für Fehler
                      client.on('error', function (error) {
                          log('Fehler bei der Ecoflow MQTT-Verbindung:' + error, 'warn');
                      });
                  
                      client.on('reconnect', function () {
                          console.log('Reconnecting to Ecoflow MQTT broker...');
                      });
                      // Weitere Event-Handler hier...
                      return client;
                  }
                  
                  // Verbindung herstellen
                  let client = setupMQTTConnection();
                  
                  // Funktion zum Trennen und Neuaufbau der Verbindung
                  function reconnect() {
                      client.end(); // Verbindung trennen
                      setTimeout(function () {
                          client = setupMQTTConnection(); // Neue Verbindung herstellen
                          //log("Ecoflow neuverbindung");
                      }, 2000); // Wartezeit
                  }
                  
                  // close connection if script stopped
                  onStop(function (callback) {
                      if (client) {
                          // close connection
                          client.end();
                          log("Ecoflow MQTT-Client beendet")
                      }
                      callback();
                  }, 2000);
                  
                  function CheckforReconnect(callback) {
                      if (getState(ConfigData.statesPrefix + ".LastTopic")?.ts < Date.now() - ConfigData.ReconnectMin * 60 * 1000) {
                          console.log("Der letzte Eintrag ist älter als " + ConfigData.ReconnectMin + " Minuten. Versuche Neustart.");
                          setState(ConfigData.statesPrefix + ".LastTopic", "Last Action Restart:" + new Date().toLocaleString())
                          runScript();
                          return;
                          // Wenn letzte Powerstream-Meldung älter als 5 min ist, schicke Lebenszeichen 
                      } else if (getState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX')?.ts < Date.now() - 5 * 60 * 1000) {
                          log("Reconnect zu Ecoflow MQTT für PowerStream - Daten")
                          //setmusterGetPS()
                          //.ts Updaten
                          const oldvalue = getState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX').val
                          setState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX', oldvalue)
                          reconnect();
                          return;
                          //runScript();
                      } else {
                          callback();
                      }
                  }
                  
                  //*/
                  
                  async function createMyState(name) {
                      if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) {
                          await createStateAsync(ConfigData.statesPrefix + '.' + name, {
                              name: name.split('.').pop(),
                              role: 'state',
                              type: 'string',
                              read: true,
                              write: true
                          });
                      }
                  }
                  
                  function decodeAndPrint(hexString) {
                      const root = protobuf.parse(protoSource).root;
                      const PowerMessage = root.lookupType("PowerMessage");
                      const message = PowerMessage.decode(Buffer.from(hexString, "hex"));
                      const object = PowerMessage.toObject(message, { defaults: false });
                      return JSON.stringify(object);
                  }
                  
                  function SendProto(protomsg, topic) {
                      const root = protobuf.parse(protoSource).root;
                      const PowerMessage = root.lookupType("PowerMessage");
                      const message = PowerMessage.create(JSON.parse(protomsg));
                      const messageBuffer = PowerMessage.encode(message).finish();
                      //log("Modifizierter Hex-String:" +  Buffer.from(messageBuffer).toString("hex"));
                      //log("topic:" +  topic);
                      client.publish(topic, messageBuffer, { qos: 1 }, function (error) {
                          if (error) {
                              console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error);
                          } else {
                              if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.');
                          }
                      });
                  }
                  
                  //############ Funktionen zum Setzen von Werten
                  
                  const musterSetAC = `
                  {
                    "item": {
                      "meta": {
                        "value": 1300
                      },
                      "src": 32,
                      "dest": 53,
                      "unknown1": 1,
                      "unknown2": 1,
                      "unknown4": 3,
                      "cmdFunc": 20,
                      "cmdId": 129,
                      "unknown5": 3,
                      "needAck": 1,
                      "timestamp": {
                        "low": 35061401,
                        "high": 0,
                        "unsigned": true
                      },
                      "unknown6": 19,
                      "unknown7": 1,
                      "OS": "ios",
                      "serialNumber": "ABCxxxxxxx123"
                    }
                  }
                  `
                  const musterSetAC2 = `
                  {
                    "item": {
                      "meta": {
                        "value": 17477
                      },
                      "src": 32,
                      "dest": 53,
                      "unknown1": 1,
                      "unknown2": 1,
                      "unknown4": 3,
                      "cmdFunc": 32,
                      "cmdId": 11,
                      "unknown5": 4,
                      "needAck": 1,
                      "timestamp": {
                        "low": 105187935,
                        "high": 0,
                        "unsigned": true
                      },
                      "unknown6": 19,
                      "unknown7": 1,
                      "OS": "ios",
                      "serialNumber": "ABCxxxxxxx123"
                    }
                  }
                  `
                  const musterGetPS = `
                  {
                    "item": {
                      "src": 32,
                      "dest": 32,
                      "timestamp": {
                        "low": 95816611,
                        "high": 0,
                        "unsigned": true
                      },
                      "OS": "ios"
                    }
                  }
                  `
                  await createMyState('app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC')
                  on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) {
                      setAC(Number(obj.state.val))
                      setState(obj.id, obj.state.val, true);
                  });
                  
                  // Einstellen der Einspeiseleistung
                  function setAC(Value = 1000) {
                      const updatedMusterSetAC = JSON.parse(musterSetAC);
                      updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                      updatedMusterSetAC.item.meta.value = Value
                      updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                      SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
                  }
                  
                  //Anmeldenachrichten der APP
                  function setmusterGetPS() {
                      var updatedMusterSetAC = JSON.parse(musterGetPS);
                      updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                      SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/get');
                  
                      updatedMusterSetAC = JSON.parse(musterSetAC2);
                      updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                      updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                      SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
                  }
                  
                  
                  function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0") {
                      if (!JElements || typeof JElements !== 'object') {
                          log('Ungültige JElements übergeben!');
                          return;
                      }
                      for (var JElement in JElements) {
                          var AktVal;
                          if (typeof JElements[JElement] === "object") {
                              generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset);
                          } else {
                              try {
                                  if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val; else AktVal = null
                              } catch (e) {
                                  log("Fehler: " + e);
                              }
                              if (AktVal == null) {
                                  createState(preset + "." + id + "." + JElement, JElements[JElement], false);
                                  AktVal = JElements[JElement];
                              }
                              if (AktVal != JElements[JElement]) {
                                  if (isState2(preset + "." + id + "." + JElement)) setState(preset + "." + id + "." + JElement, JElements[JElement]);
                              }
                          }
                      }
                  }
                  
                  /**
                   * Checks if a a given state or part of state is existing.
                   * This is a workaround, as getObject() or getState() throw warnings in the log.
                   * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
                   * See: https://forum.iobroker.net/topic/11354/
                   * @param {string}    strStatePath     Input string of state, like 'javascript.0.switches.Osram.Bedroom'
                   * @param {boolean}   [strict=false]   Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string
                   * @return {boolean}                   true if state exists, false if not
                   */
                  function isState2(strStatePath, strict = true) {
                      let mSelector;
                      if (strict) {
                          mSelector = $(strStatePath);
                      } else {
                          mSelector = $(strStatePath + "*");
                      }
                      if (mSelector.length > 0) {
                          return true;
                      } else {
                          return false;
                      }
                  }
                  
                  
                  // ########### Grundbedarf steuern
                  // Es müssen Historydaten für den Wert des Smartmeters vorhanden sein, damit der niedrigeste wert der letzten x Minuten ermittelt werden kann
                  
                  function getLowestValue(id, minuten = 120) {
                      return new Promise((resolve, reject) => {
                          const now = new Date().getTime();
                          const range = minuten * 60 * 1000;
                          var myresult = 0;
                          getHistory(0, {
                              id: id,
                              start: now - range,
                              end: now,
                              aggregate: 'minmax'
                          }, function (err, result) {
                              if (err) {
                                  reject(err);
                              } else if (result.length) {
                                  let lowestValue = result[0].val;
                                  for (let i = 1; i < result.length; i++) {
                                      if (result[i].val < lowestValue) {
                                          lowestValue = result[i].val;
                                      }
                                  }
                                  myresult = Number(lowestValue);
                                  resolve(myresult);
                              } else {
                                  reject(new Error('No data'));
                              }
                          });
                      });
                  }
                  
                  var OldNewValue = 0
                  function SetBasePower() {
                      if (isState2(ConfigData.SmartmeterID)) {
                          getLowestValue(ConfigData.SmartmeterID, ConfigData.MinValueMin)
                              .then(lowestValue => {
                                  if (lowestValue > 0) {
                                      if (ConfigData.Debug) log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue);
                                      var AndereVerbraucher = 0
                                      const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[0].seriennummer + ".data.item.meta.ToHome_Power"
                                      if (isState2(ToHomeId)) {
                                          AndereVerbraucher = Number(getState(ToHomeId).val) / 10
                                          var NewValue = lowestValue + AndereVerbraucher - ConfigData.BasePowerOffset
                                          if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower
                                          if (ConfigData.Debug) {
                                              log("AndereVerbraucher: " + AndereVerbraucher)
                                              log("Macht : " + (lowestValue + AndereVerbraucher) + "W Grundbedarf")
                                              log("- Offset von: " + ConfigData.BasePowerOffset + " W = " + (NewValue) + " W Grundbedarf")
                                          }
                                          if (OldNewValue != NewValue) setAC(NewValue * 10)
                                          OldNewValue = NewValue
                                      }
                                  }
                              })
                              .catch(error => {
                                  log("Fehler beim Abrufen des niedrigsten Werts: " + error);
                              });
                      }
                  }
                  
                  
                  
                  A M 2 Antworten Letzte Antwort
                  1
                  • T Offline
                    T Offline
                    TDahinten
                    schrieb am zuletzt editiert von
                    #165

                    @waly_de

                    also nochmal herzlichen Dank :clap:
                    Soweit läuft bei mir Script Version 0.2. Die 0.3 Version habe ich aktuell noch nicht eingebunden.
                    Bin echt happy.

                    1 Antwort Letzte Antwort
                    0
                    • W Waly_de

                      @applepro
                      Super das es klappt :blush: :fireworks:

                      hier eine neue Version. Es gab noch ein Problem beim anlegen der States.
                      Und ich versuche hier ein Phänomen zu beseitigen: Der MQTT hört auf Nachrichten zu senden, wenn man die App auf dem Handy beendet... Bin noch nicht sicher ob es jetzt durchläuft...

                      /**
                       * ecoflow-iobroker-connector.js
                       * Version:      0.4    
                       * Release date: 29.06.2023
                       * Autor:        Waly_de 
                       * Forum:        https://forum.iobroker.net/topic/54929/adapter-für-ecoflow-einbindung/155
                       * 
                       *
                       * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 
                       * It automatically creates known states under 0_userdata.
                       * 
                       * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 
                       * used for the EcoFlow app, as well as the serial numbers of your devices.
                       * 
                       * Important: The second serial number entry should always correspond to the Powerstream as it is associated with control functions.
                       * 
                       * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 
                       * Configure a history for this state so that the script can determine the lowest power consumption in the last 10 minutes (configurable). 
                       * This value will be used to dynamically adjust the Powerstream's feed-in power.
                       * 
                       * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states.
                       * By modifying the "protoSource" constant, newly discovered data will also be automatically created.
                       * 
                       * The raw data of the interface is logged as a HEX string.
                       * 
                       * Please exercise caution as this is the initial version of the script. Use it at your own risk!
                       *
                       * Requirements:
                       * - Install protobuf using the command "npm install protobufjs" from the terminal console.
                       * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt".
                       *
                       * Note: It is encouraged to discover and publish missing data definitions to improve the script.
                       * Suggestions, optimizations, and extensions are welcome at any time.
                       *
                       * Special thanks to all contributors for their valuable input and support.
                       * 
                       * Changelog:
                       * -----------------------------------------------------------
                       * (0.4) 29.06.2023
                       * Da der MQTT von ecoflow regelmäßig aufhört zu senden, vor allem, wenn die App genutzt und komplett geschlossen wird, 
                       * habe ich eine Überwachung der letzten ankommenden Nachrichten eingebaut. Kommt 5 Minuten lang nichts neues vom PowerStream,
                       * Wird die Verbindung zum MQTT kompett neu aufgebaut.
                       * 
                       * Ein Fehler bei der Erstellung der States wurde beseitigt
                       * -----------------------------------------------------------
                       * 
                       */
                      
                      /*******  YOUR DATA HERE  ****************
                      var ConfigData = {
                          email: "your@mail.com",
                          passwort: "yourAppPasswort!",
                          seriennummern: [
                              { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" }, //1. has to be your PowerStream, you can add more devices...
                              { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
                          ],
                          SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P",     //State, das den aktuellen Gesamtverbrauch in Watt enthält
                          BasePowerOffset: 50,                                //wird vom aktuellen Verbrauch abgezogen um die Einspeiseleistung zu berechnen 
                          MaxPower: 600,                                      //Der höchst mögliche wert in Watt für die Einspeiseleistung
                          MinValueMin: 10,                                    //Der Zeitraum in Minuten, aus dem der letzte Gesamtverbrauchs-Minimalwert geholt werden soll 
                          ReconnectMin: 30,                                   //Zeit in Minuten, nach der die Anwendung neu gestartet wird, wenn keine neuen Daten eintreffen
                          statesPrefix: "0_userdata.0.ecoflow",
                          Debug: true
                      };
                      //***************************************/
                      
                      const protoSource = `
                      syntax = "proto3";
                      message PowerItem {
                        optional Meta meta = 1;
                        optional uint32 src = 2;
                        optional uint32 dest = 3;
                        optional uint32 unknown1 = 4;
                        optional uint32 unknown2 = 5;
                        optional uint32 unknown3 = 6;
                        optional uint32 unknown4 = 7;
                        optional uint32 cmdFunc = 8;
                        optional CmdFunction cmdId = 9;
                        optional uint32 unknown5 = 10;
                        optional uint32 needAck = 11;
                        uint64 timestamp = 14;
                        optional uint32 unknown6 = 16; //3 Byte?
                        optional uint32 unknown7 = 17; //3 Byte?
                        optional string OS = 23;
                        optional string serialNumber = 25;
                      }
                      message PowerMessage {
                        PowerItem item = 1;
                      }
                      message Meta {
                        optional int32 value = 1;
                        optional int32 plugPower = 10; 
                        optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen
                        optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen
                        optional int32 PV1_Power = 19;
                        optional int32 M_Unknown1 = 21;
                        optional int32 PV2_Power = 24;
                        optional int32 From_Bat_Power = 29;
                        optional int32 Batt_Poz = 31;
                        optional int32 M_Unknown2 = 33;
                        optional int32 M_Unknown3 = 35;
                        optional int32 M_Unknown6 = 37;
                        optional int32 ToHome_Power = 38;
                        optional int32 M_Unknown9 = 43; //prio Power von summe PVx_Power abziehen ?
                        optional int32 Needed_Power = 48;
                        optional int32 M_Unknown4 = 59;
                        optional int32 Bat_Minutes = 60;
                      }
                      enum CmdFunction {
                          Unknown = 0;
                          PermanentWattsPack = 129;
                          SupplyPriorityPack = 130;
                      }
                      `;
                      
                      const mqtt = require('mqtt');
                      const https = require('https');
                      const protobuf = require("protobufjs");
                      
                      const mqttDaten = {
                          UserID: '',
                          User: '',
                          Passwort: '',
                          URL: '',
                          Port: '',
                          protocol: '',
                          clientID: ''
                      }
                      
                      /*=======================================================
                        =========             Timer               ============
                        =======================================================*/
                      //alle 5 minuten
                      schedule('*/1 * * * *', function () {
                          CheckforReconnect(function () {
                              SetBasePower();
                          });
                      });
                      
                      await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort)
                      async function getEcoFlowMqttData(email, password) {
                          const options = {
                              hostname: 'api.ecoflow.com',
                              path: '/auth/login',
                              method: 'POST',
                              headers: {
                                  'Host': 'api.ecoflow.com',
                                  'lang': 'de-de',
                                  'platform': 'android',
                                  'sysversion': '11',
                                  'version': '4.1.2.02',
                                  'phonemodel': 'SM-X200',
                                  'content-type': 'application/json',
                                  'user-agent': 'okhttp/3.14.9'
                              }
                          };
                      
                          const data = {
                              appVersion: "4.1.2.02",
                              email: email,
                              os: "android",
                              osVersion: "30",
                              password: Buffer.from(password).toString('base64'),
                              scene: "IOT_APP",
                              userType: "ECOFLOW"
                          };
                      
                          function httpsRequest(options, data) {
                              return new Promise((resolve, reject) => {
                                  const req = https.request(options, res => {
                                      let data = '';
                                      res.on('data', chunk => {
                                          data += chunk;
                                      });
                                      res.on('end', () => {
                                          resolve(data);
                                      });
                                  });
                      
                                  req.on('error', error => {
                                      reject(error);
                                  });
                      
                                  if (data) {
                                      req.write(JSON.stringify(data));
                                  }
                      
                                  req.end();
                              });
                          }
                      
                          function uuidv4() {
                              return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                                  var r = Math.random() * 16 | 0,
                                      v = c === 'x' ? r : (r & 0x3 | 0x8);
                                  return v.toString(16);
                              });
                          }
                      
                          let response = await httpsRequest(options, data);
                          try {
                              let token = JSON.parse(response).data.token;
                              let userid = JSON.parse(response).data.user.userId;
                          } catch (error) {
                              log(response)
                              throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                          }
                      
                          let token = JSON.parse(response).data.token;
                          let userid = JSON.parse(response).data.user.userId;
                      
                          options.path = `/iot-auth/app/certification?userId=${userid}`;
                          options.method = 'GET';
                          options.headers.authorization = `Bearer ${token}`;
                          response = await httpsRequest(options);
                          try {
                              mqttDaten.Passwort = JSON.parse(response).data.certificatePassword
                              mqttDaten.Port = JSON.parse(response).data.port
                              mqttDaten.UserID = userid
                              mqttDaten.User = JSON.parse(response).data.certificateAccount
                              mqttDaten.URL = JSON.parse(response).data.url
                              mqttDaten.protocol = JSON.parse(response).data.protocol
                              mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid
                          } catch (error) {
                              log(response)
                              throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                          }
                          /*    
                              console.log("UserID: " + userid);
                              console.log("User: " + JSON.parse(response).data.certificateAccount);
                              console.log("Passwort: " + JSON.parse(response).data.certificatePassword);
                              console.log("URL: " + JSON.parse(response).data.url);
                              console.log("Port: " + JSON.parse(response).data.port);
                              console.log("protocol: " + JSON.parse(response).data.protocol);
                              console.log("clientID: ANDROID_" + uuidv4() + "_" + userid);
                          */
                      
                      }
                      
                      await createMyState("LastTopic")
                      
                      //################ MQTT Verbindung ##################
                      function setupMQTTConnection() {
                          // Verbindung herstellen
                          const options = {
                              port: mqttDaten.Port,
                              clientId: mqttDaten.clientID,
                              username: mqttDaten.User,
                              password: mqttDaten.Passwort,
                              protocol: mqttDaten.protocol
                          };
                      
                          const client = mqtt.connect("mqtt://" + mqttDaten.URL, options);
                      
                          // Event-Handler für Verbindungsaufbau
                          client.on('connect', function () {
                              console.log('Verbunden mit dem Ecoflow MQTT-Broker');
                              SubscribeEco();
                          });
                      
                          // Event-Handler für Fehler
                          client.on('error', function (error) {
                              console.log('Fehler bei der Ecoflow MQTT-Verbindung: ' + error);
                          });
                      
                          function SubscribeEco() {
                              ConfigData.seriennummern.forEach(item => {
                                  client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set');
                                  client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get');
                                  client.subscribe('/app/device/property/' + item.seriennummer);
                                  setmusterGetPS();
                              });
                          }
                      
                          // Auf Nachricht empfangen Ereignis reagieren
                          client.on('message', async function (topic, message) {
                              var jsonMessage = ""
                              const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
                              await createMyState(mqState + ".RAW")
                              setState(ConfigData.statesPrefix + ".LastTopic", topic)
                              try {
                                  jsonMessage = JSON.parse(message);
                                  if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                                  setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                                  generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
                              } catch (error) {
                                  if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                                  await createMyState(mqState + ".RAW_HEX")
                                  setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                                  //log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                                  const messagedecoded = decodeAndPrint(message.toString('hex'))
                                  setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                                  generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
                              }
                          });
                      
                          // Callback für Fehler
                          client.on('error', function (error) {
                              log('Fehler bei der Ecoflow MQTT-Verbindung:' + error, 'warn');
                          });
                      
                          client.on('reconnect', function () {
                              console.log('Reconnecting to Ecoflow MQTT broker...');
                          });
                          // Weitere Event-Handler hier...
                          return client;
                      }
                      
                      // Verbindung herstellen
                      let client = setupMQTTConnection();
                      
                      // Funktion zum Trennen und Neuaufbau der Verbindung
                      function reconnect() {
                          client.end(); // Verbindung trennen
                          setTimeout(function () {
                              client = setupMQTTConnection(); // Neue Verbindung herstellen
                              //log("Ecoflow neuverbindung");
                          }, 2000); // Wartezeit
                      }
                      
                      // close connection if script stopped
                      onStop(function (callback) {
                          if (client) {
                              // close connection
                              client.end();
                              log("Ecoflow MQTT-Client beendet")
                          }
                          callback();
                      }, 2000);
                      
                      function CheckforReconnect(callback) {
                          if (getState(ConfigData.statesPrefix + ".LastTopic")?.ts < Date.now() - ConfigData.ReconnectMin * 60 * 1000) {
                              console.log("Der letzte Eintrag ist älter als " + ConfigData.ReconnectMin + " Minuten. Versuche Neustart.");
                              setState(ConfigData.statesPrefix + ".LastTopic", "Last Action Restart:" + new Date().toLocaleString())
                              runScript();
                              return;
                              // Wenn letzte Powerstream-Meldung älter als 5 min ist, schicke Lebenszeichen 
                          } else if (getState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX')?.ts < Date.now() - 5 * 60 * 1000) {
                              log("Reconnect zu Ecoflow MQTT für PowerStream - Daten")
                              //setmusterGetPS()
                              //.ts Updaten
                              const oldvalue = getState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX').val
                              setState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX', oldvalue)
                              reconnect();
                              return;
                              //runScript();
                          } else {
                              callback();
                          }
                      }
                      
                      //*/
                      
                      async function createMyState(name) {
                          if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) {
                              await createStateAsync(ConfigData.statesPrefix + '.' + name, {
                                  name: name.split('.').pop(),
                                  role: 'state',
                                  type: 'string',
                                  read: true,
                                  write: true
                              });
                          }
                      }
                      
                      function decodeAndPrint(hexString) {
                          const root = protobuf.parse(protoSource).root;
                          const PowerMessage = root.lookupType("PowerMessage");
                          const message = PowerMessage.decode(Buffer.from(hexString, "hex"));
                          const object = PowerMessage.toObject(message, { defaults: false });
                          return JSON.stringify(object);
                      }
                      
                      function SendProto(protomsg, topic) {
                          const root = protobuf.parse(protoSource).root;
                          const PowerMessage = root.lookupType("PowerMessage");
                          const message = PowerMessage.create(JSON.parse(protomsg));
                          const messageBuffer = PowerMessage.encode(message).finish();
                          //log("Modifizierter Hex-String:" +  Buffer.from(messageBuffer).toString("hex"));
                          //log("topic:" +  topic);
                          client.publish(topic, messageBuffer, { qos: 1 }, function (error) {
                              if (error) {
                                  console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error);
                              } else {
                                  if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.');
                              }
                          });
                      }
                      
                      //############ Funktionen zum Setzen von Werten
                      
                      const musterSetAC = `
                      {
                        "item": {
                          "meta": {
                            "value": 1300
                          },
                          "src": 32,
                          "dest": 53,
                          "unknown1": 1,
                          "unknown2": 1,
                          "unknown4": 3,
                          "cmdFunc": 20,
                          "cmdId": 129,
                          "unknown5": 3,
                          "needAck": 1,
                          "timestamp": {
                            "low": 35061401,
                            "high": 0,
                            "unsigned": true
                          },
                          "unknown6": 19,
                          "unknown7": 1,
                          "OS": "ios",
                          "serialNumber": "ABCxxxxxxx123"
                        }
                      }
                      `
                      const musterSetAC2 = `
                      {
                        "item": {
                          "meta": {
                            "value": 17477
                          },
                          "src": 32,
                          "dest": 53,
                          "unknown1": 1,
                          "unknown2": 1,
                          "unknown4": 3,
                          "cmdFunc": 32,
                          "cmdId": 11,
                          "unknown5": 4,
                          "needAck": 1,
                          "timestamp": {
                            "low": 105187935,
                            "high": 0,
                            "unsigned": true
                          },
                          "unknown6": 19,
                          "unknown7": 1,
                          "OS": "ios",
                          "serialNumber": "ABCxxxxxxx123"
                        }
                      }
                      `
                      const musterGetPS = `
                      {
                        "item": {
                          "src": 32,
                          "dest": 32,
                          "timestamp": {
                            "low": 95816611,
                            "high": 0,
                            "unsigned": true
                          },
                          "OS": "ios"
                        }
                      }
                      `
                      await createMyState('app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC')
                      on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) {
                          setAC(Number(obj.state.val))
                          setState(obj.id, obj.state.val, true);
                      });
                      
                      // Einstellen der Einspeiseleistung
                      function setAC(Value = 1000) {
                          const updatedMusterSetAC = JSON.parse(musterSetAC);
                          updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                          updatedMusterSetAC.item.meta.value = Value
                          updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                          SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
                      }
                      
                      //Anmeldenachrichten der APP
                      function setmusterGetPS() {
                          var updatedMusterSetAC = JSON.parse(musterGetPS);
                          updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                          SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/get');
                      
                          updatedMusterSetAC = JSON.parse(musterSetAC2);
                          updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                          updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                          SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
                      }
                      
                      
                      function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0") {
                          if (!JElements || typeof JElements !== 'object') {
                              log('Ungültige JElements übergeben!');
                              return;
                          }
                          for (var JElement in JElements) {
                              var AktVal;
                              if (typeof JElements[JElement] === "object") {
                                  generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset);
                              } else {
                                  try {
                                      if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val; else AktVal = null
                                  } catch (e) {
                                      log("Fehler: " + e);
                                  }
                                  if (AktVal == null) {
                                      createState(preset + "." + id + "." + JElement, JElements[JElement], false);
                                      AktVal = JElements[JElement];
                                  }
                                  if (AktVal != JElements[JElement]) {
                                      if (isState2(preset + "." + id + "." + JElement)) setState(preset + "." + id + "." + JElement, JElements[JElement]);
                                  }
                              }
                          }
                      }
                      
                      /**
                       * Checks if a a given state or part of state is existing.
                       * This is a workaround, as getObject() or getState() throw warnings in the log.
                       * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
                       * See: https://forum.iobroker.net/topic/11354/
                       * @param {string}    strStatePath     Input string of state, like 'javascript.0.switches.Osram.Bedroom'
                       * @param {boolean}   [strict=false]   Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string
                       * @return {boolean}                   true if state exists, false if not
                       */
                      function isState2(strStatePath, strict = true) {
                          let mSelector;
                          if (strict) {
                              mSelector = $(strStatePath);
                          } else {
                              mSelector = $(strStatePath + "*");
                          }
                          if (mSelector.length > 0) {
                              return true;
                          } else {
                              return false;
                          }
                      }
                      
                      
                      // ########### Grundbedarf steuern
                      // Es müssen Historydaten für den Wert des Smartmeters vorhanden sein, damit der niedrigeste wert der letzten x Minuten ermittelt werden kann
                      
                      function getLowestValue(id, minuten = 120) {
                          return new Promise((resolve, reject) => {
                              const now = new Date().getTime();
                              const range = minuten * 60 * 1000;
                              var myresult = 0;
                              getHistory(0, {
                                  id: id,
                                  start: now - range,
                                  end: now,
                                  aggregate: 'minmax'
                              }, function (err, result) {
                                  if (err) {
                                      reject(err);
                                  } else if (result.length) {
                                      let lowestValue = result[0].val;
                                      for (let i = 1; i < result.length; i++) {
                                          if (result[i].val < lowestValue) {
                                              lowestValue = result[i].val;
                                          }
                                      }
                                      myresult = Number(lowestValue);
                                      resolve(myresult);
                                  } else {
                                      reject(new Error('No data'));
                                  }
                              });
                          });
                      }
                      
                      var OldNewValue = 0
                      function SetBasePower() {
                          if (isState2(ConfigData.SmartmeterID)) {
                              getLowestValue(ConfigData.SmartmeterID, ConfigData.MinValueMin)
                                  .then(lowestValue => {
                                      if (lowestValue > 0) {
                                          if (ConfigData.Debug) log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue);
                                          var AndereVerbraucher = 0
                                          const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[0].seriennummer + ".data.item.meta.ToHome_Power"
                                          if (isState2(ToHomeId)) {
                                              AndereVerbraucher = Number(getState(ToHomeId).val) / 10
                                              var NewValue = lowestValue + AndereVerbraucher - ConfigData.BasePowerOffset
                                              if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower
                                              if (ConfigData.Debug) {
                                                  log("AndereVerbraucher: " + AndereVerbraucher)
                                                  log("Macht : " + (lowestValue + AndereVerbraucher) + "W Grundbedarf")
                                                  log("- Offset von: " + ConfigData.BasePowerOffset + " W = " + (NewValue) + " W Grundbedarf")
                                              }
                                              if (OldNewValue != NewValue) setAC(NewValue * 10)
                                              OldNewValue = NewValue
                                          }
                                      }
                                  })
                                  .catch(error => {
                                      log("Fehler beim Abrufen des niedrigsten Werts: " + error);
                                  });
                          }
                      }
                      
                      
                      
                      A Offline
                      A Offline
                      applepro
                      schrieb am zuletzt editiert von
                      #166

                      @waly_de ich habe deine Version 0.3 mal einspielt und seit gestern Mittag laufen. Dabei ist mir aufgefallen, das sich trotz minütlicher Änderung des AC-Wertes, ein Wert außerhalb der Range einschleicht. Dies passiert ca. alle 5-15min. AC Wert wird dann auf 1.747w eingestellt, also max 600w. Ich habe darauf mal dein Skript durch geschaut und an folgender Stellen diesen Wert gefunden. Es handelt sich um den Wert: 17477. Ich habe diesen gegen 1400 = 140w getauscht. Aktuell läuft es seit 16h ohne das verstellen des AC-Wertes auf 17477. Kannst ja mal bei bedarf schauen. Vlt habe aber auch ich irgendwo einen Rechenfehler, der sich eingeschlichen hat.

                      const musterSetAC2 = `
                      {
                        "item": {
                          "meta": {
                            "value": 17477 //getauscht gegen 1400
                          },
                          "src": 32,
                          "dest": 53,
                          "unknown1": 1,
                          "unknown2": 1,
                          "unknown4": 3,
                          "cmdFunc": 32,
                          "cmdId": 11,
                          "unknown5": 4,
                          "needAck": 1,
                          "timestamp": {
                            "low": 105187935,
                            "high": 0,
                            "unsigned": true
                          },
                          "unknown6": 19,
                          "unknown7": 1,
                          "OS": "ios",
                          "serialNumber": "ABCxxxxxxx123"
                        }```
                      Gruß
                      Jannick
                      W 1 Antwort Letzte Antwort
                      0
                      • A applepro

                        @waly_de ich habe deine Version 0.3 mal einspielt und seit gestern Mittag laufen. Dabei ist mir aufgefallen, das sich trotz minütlicher Änderung des AC-Wertes, ein Wert außerhalb der Range einschleicht. Dies passiert ca. alle 5-15min. AC Wert wird dann auf 1.747w eingestellt, also max 600w. Ich habe darauf mal dein Skript durch geschaut und an folgender Stellen diesen Wert gefunden. Es handelt sich um den Wert: 17477. Ich habe diesen gegen 1400 = 140w getauscht. Aktuell läuft es seit 16h ohne das verstellen des AC-Wertes auf 17477. Kannst ja mal bei bedarf schauen. Vlt habe aber auch ich irgendwo einen Rechenfehler, der sich eingeschlichen hat.

                        const musterSetAC2 = `
                        {
                          "item": {
                            "meta": {
                              "value": 17477 //getauscht gegen 1400
                            },
                            "src": 32,
                            "dest": 53,
                            "unknown1": 1,
                            "unknown2": 1,
                            "unknown4": 3,
                            "cmdFunc": 32,
                            "cmdId": 11,
                            "unknown5": 4,
                            "needAck": 1,
                            "timestamp": {
                              "low": 105187935,
                              "high": 0,
                              "unsigned": true
                            },
                            "unknown6": 19,
                            "unknown7": 1,
                            "OS": "ios",
                            "serialNumber": "ABCxxxxxxx123"
                          }```
                        Gruß
                        Jannick
                        W Offline
                        W Offline
                        Waly_de
                        schrieb am zuletzt editiert von
                        #167

                        @applepro Das ist interessant. Wie hast Du MinValueMin eingestellt?

                        A 1 Antwort Letzte Antwort
                        0
                        • W Waly_de

                          @applepro Das ist interessant. Wie hast Du MinValueMin eingestellt?

                          A Offline
                          A Offline
                          applepro
                          schrieb am zuletzt editiert von
                          #168

                          @waly_de Habe ich auf 10 gelassen.

                          1 Antwort Letzte Antwort
                          0
                          • W Waly_de

                            @applepro
                            Super das es klappt :blush: :fireworks:

                            hier eine neue Version. Es gab noch ein Problem beim anlegen der States.
                            Und ich versuche hier ein Phänomen zu beseitigen: Der MQTT hört auf Nachrichten zu senden, wenn man die App auf dem Handy beendet... Bin noch nicht sicher ob es jetzt durchläuft...

                            /**
                             * ecoflow-iobroker-connector.js
                             * Version:      0.4    
                             * Release date: 29.06.2023
                             * Autor:        Waly_de 
                             * Forum:        https://forum.iobroker.net/topic/54929/adapter-für-ecoflow-einbindung/155
                             * 
                             *
                             * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 
                             * It automatically creates known states under 0_userdata.
                             * 
                             * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 
                             * used for the EcoFlow app, as well as the serial numbers of your devices.
                             * 
                             * Important: The second serial number entry should always correspond to the Powerstream as it is associated with control functions.
                             * 
                             * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 
                             * Configure a history for this state so that the script can determine the lowest power consumption in the last 10 minutes (configurable). 
                             * This value will be used to dynamically adjust the Powerstream's feed-in power.
                             * 
                             * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states.
                             * By modifying the "protoSource" constant, newly discovered data will also be automatically created.
                             * 
                             * The raw data of the interface is logged as a HEX string.
                             * 
                             * Please exercise caution as this is the initial version of the script. Use it at your own risk!
                             *
                             * Requirements:
                             * - Install protobuf using the command "npm install protobufjs" from the terminal console.
                             * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt".
                             *
                             * Note: It is encouraged to discover and publish missing data definitions to improve the script.
                             * Suggestions, optimizations, and extensions are welcome at any time.
                             *
                             * Special thanks to all contributors for their valuable input and support.
                             * 
                             * Changelog:
                             * -----------------------------------------------------------
                             * (0.4) 29.06.2023
                             * Da der MQTT von ecoflow regelmäßig aufhört zu senden, vor allem, wenn die App genutzt und komplett geschlossen wird, 
                             * habe ich eine Überwachung der letzten ankommenden Nachrichten eingebaut. Kommt 5 Minuten lang nichts neues vom PowerStream,
                             * Wird die Verbindung zum MQTT kompett neu aufgebaut.
                             * 
                             * Ein Fehler bei der Erstellung der States wurde beseitigt
                             * -----------------------------------------------------------
                             * 
                             */
                            
                            /*******  YOUR DATA HERE  ****************
                            var ConfigData = {
                                email: "your@mail.com",
                                passwort: "yourAppPasswort!",
                                seriennummern: [
                                    { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream" }, //1. has to be your PowerStream, you can add more devices...
                                    { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max" }
                                ],
                                SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P",     //State, das den aktuellen Gesamtverbrauch in Watt enthält
                                BasePowerOffset: 50,                                //wird vom aktuellen Verbrauch abgezogen um die Einspeiseleistung zu berechnen 
                                MaxPower: 600,                                      //Der höchst mögliche wert in Watt für die Einspeiseleistung
                                MinValueMin: 10,                                    //Der Zeitraum in Minuten, aus dem der letzte Gesamtverbrauchs-Minimalwert geholt werden soll 
                                ReconnectMin: 30,                                   //Zeit in Minuten, nach der die Anwendung neu gestartet wird, wenn keine neuen Daten eintreffen
                                statesPrefix: "0_userdata.0.ecoflow",
                                Debug: true
                            };
                            //***************************************/
                            
                            const protoSource = `
                            syntax = "proto3";
                            message PowerItem {
                              optional Meta meta = 1;
                              optional uint32 src = 2;
                              optional uint32 dest = 3;
                              optional uint32 unknown1 = 4;
                              optional uint32 unknown2 = 5;
                              optional uint32 unknown3 = 6;
                              optional uint32 unknown4 = 7;
                              optional uint32 cmdFunc = 8;
                              optional CmdFunction cmdId = 9;
                              optional uint32 unknown5 = 10;
                              optional uint32 needAck = 11;
                              uint64 timestamp = 14;
                              optional uint32 unknown6 = 16; //3 Byte?
                              optional uint32 unknown7 = 17; //3 Byte?
                              optional string OS = 23;
                              optional string serialNumber = 25;
                            }
                            message PowerMessage {
                              PowerItem item = 1;
                            }
                            message Meta {
                              optional int32 value = 1;
                              optional int32 plugPower = 10; 
                              optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen
                              optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen
                              optional int32 PV1_Power = 19;
                              optional int32 M_Unknown1 = 21;
                              optional int32 PV2_Power = 24;
                              optional int32 From_Bat_Power = 29;
                              optional int32 Batt_Poz = 31;
                              optional int32 M_Unknown2 = 33;
                              optional int32 M_Unknown3 = 35;
                              optional int32 M_Unknown6 = 37;
                              optional int32 ToHome_Power = 38;
                              optional int32 M_Unknown9 = 43; //prio Power von summe PVx_Power abziehen ?
                              optional int32 Needed_Power = 48;
                              optional int32 M_Unknown4 = 59;
                              optional int32 Bat_Minutes = 60;
                            }
                            enum CmdFunction {
                                Unknown = 0;
                                PermanentWattsPack = 129;
                                SupplyPriorityPack = 130;
                            }
                            `;
                            
                            const mqtt = require('mqtt');
                            const https = require('https');
                            const protobuf = require("protobufjs");
                            
                            const mqttDaten = {
                                UserID: '',
                                User: '',
                                Passwort: '',
                                URL: '',
                                Port: '',
                                protocol: '',
                                clientID: ''
                            }
                            
                            /*=======================================================
                              =========             Timer               ============
                              =======================================================*/
                            //alle 5 minuten
                            schedule('*/1 * * * *', function () {
                                CheckforReconnect(function () {
                                    SetBasePower();
                                });
                            });
                            
                            await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort)
                            async function getEcoFlowMqttData(email, password) {
                                const options = {
                                    hostname: 'api.ecoflow.com',
                                    path: '/auth/login',
                                    method: 'POST',
                                    headers: {
                                        'Host': 'api.ecoflow.com',
                                        'lang': 'de-de',
                                        'platform': 'android',
                                        'sysversion': '11',
                                        'version': '4.1.2.02',
                                        'phonemodel': 'SM-X200',
                                        'content-type': 'application/json',
                                        'user-agent': 'okhttp/3.14.9'
                                    }
                                };
                            
                                const data = {
                                    appVersion: "4.1.2.02",
                                    email: email,
                                    os: "android",
                                    osVersion: "30",
                                    password: Buffer.from(password).toString('base64'),
                                    scene: "IOT_APP",
                                    userType: "ECOFLOW"
                                };
                            
                                function httpsRequest(options, data) {
                                    return new Promise((resolve, reject) => {
                                        const req = https.request(options, res => {
                                            let data = '';
                                            res.on('data', chunk => {
                                                data += chunk;
                                            });
                                            res.on('end', () => {
                                                resolve(data);
                                            });
                                        });
                            
                                        req.on('error', error => {
                                            reject(error);
                                        });
                            
                                        if (data) {
                                            req.write(JSON.stringify(data));
                                        }
                            
                                        req.end();
                                    });
                                }
                            
                                function uuidv4() {
                                    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                                        var r = Math.random() * 16 | 0,
                                            v = c === 'x' ? r : (r & 0x3 | 0x8);
                                        return v.toString(16);
                                    });
                                }
                            
                                let response = await httpsRequest(options, data);
                                try {
                                    let token = JSON.parse(response).data.token;
                                    let userid = JSON.parse(response).data.user.userId;
                                } catch (error) {
                                    log(response)
                                    throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                                }
                            
                                let token = JSON.parse(response).data.token;
                                let userid = JSON.parse(response).data.user.userId;
                            
                                options.path = `/iot-auth/app/certification?userId=${userid}`;
                                options.method = 'GET';
                                options.headers.authorization = `Bearer ${token}`;
                                response = await httpsRequest(options);
                                try {
                                    mqttDaten.Passwort = JSON.parse(response).data.certificatePassword
                                    mqttDaten.Port = JSON.parse(response).data.port
                                    mqttDaten.UserID = userid
                                    mqttDaten.User = JSON.parse(response).data.certificateAccount
                                    mqttDaten.URL = JSON.parse(response).data.url
                                    mqttDaten.protocol = JSON.parse(response).data.protocol
                                    mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid
                                } catch (error) {
                                    log(response)
                                    throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten.");
                                }
                                /*    
                                    console.log("UserID: " + userid);
                                    console.log("User: " + JSON.parse(response).data.certificateAccount);
                                    console.log("Passwort: " + JSON.parse(response).data.certificatePassword);
                                    console.log("URL: " + JSON.parse(response).data.url);
                                    console.log("Port: " + JSON.parse(response).data.port);
                                    console.log("protocol: " + JSON.parse(response).data.protocol);
                                    console.log("clientID: ANDROID_" + uuidv4() + "_" + userid);
                                */
                            
                            }
                            
                            await createMyState("LastTopic")
                            
                            //################ MQTT Verbindung ##################
                            function setupMQTTConnection() {
                                // Verbindung herstellen
                                const options = {
                                    port: mqttDaten.Port,
                                    clientId: mqttDaten.clientID,
                                    username: mqttDaten.User,
                                    password: mqttDaten.Passwort,
                                    protocol: mqttDaten.protocol
                                };
                            
                                const client = mqtt.connect("mqtt://" + mqttDaten.URL, options);
                            
                                // Event-Handler für Verbindungsaufbau
                                client.on('connect', function () {
                                    console.log('Verbunden mit dem Ecoflow MQTT-Broker');
                                    SubscribeEco();
                                });
                            
                                // Event-Handler für Fehler
                                client.on('error', function (error) {
                                    console.log('Fehler bei der Ecoflow MQTT-Verbindung: ' + error);
                                });
                            
                                function SubscribeEco() {
                                    ConfigData.seriennummern.forEach(item => {
                                        client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set');
                                        client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get');
                                        client.subscribe('/app/device/property/' + item.seriennummer);
                                        setmusterGetPS();
                                    });
                                }
                            
                                // Auf Nachricht empfangen Ereignis reagieren
                                client.on('message', async function (topic, message) {
                                    var jsonMessage = ""
                                    const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
                                    await createMyState(mqState + ".RAW")
                                    setState(ConfigData.statesPrefix + ".LastTopic", topic)
                                    try {
                                        jsonMessage = JSON.parse(message);
                                        if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                                        setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                                        generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
                                    } catch (error) {
                                        if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                                        await createMyState(mqState + ".RAW_HEX")
                                        setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                                        //log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                                        const messagedecoded = decodeAndPrint(message.toString('hex'))
                                        setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                                        generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
                                    }
                                });
                            
                                // Callback für Fehler
                                client.on('error', function (error) {
                                    log('Fehler bei der Ecoflow MQTT-Verbindung:' + error, 'warn');
                                });
                            
                                client.on('reconnect', function () {
                                    console.log('Reconnecting to Ecoflow MQTT broker...');
                                });
                                // Weitere Event-Handler hier...
                                return client;
                            }
                            
                            // Verbindung herstellen
                            let client = setupMQTTConnection();
                            
                            // Funktion zum Trennen und Neuaufbau der Verbindung
                            function reconnect() {
                                client.end(); // Verbindung trennen
                                setTimeout(function () {
                                    client = setupMQTTConnection(); // Neue Verbindung herstellen
                                    //log("Ecoflow neuverbindung");
                                }, 2000); // Wartezeit
                            }
                            
                            // close connection if script stopped
                            onStop(function (callback) {
                                if (client) {
                                    // close connection
                                    client.end();
                                    log("Ecoflow MQTT-Client beendet")
                                }
                                callback();
                            }, 2000);
                            
                            function CheckforReconnect(callback) {
                                if (getState(ConfigData.statesPrefix + ".LastTopic")?.ts < Date.now() - ConfigData.ReconnectMin * 60 * 1000) {
                                    console.log("Der letzte Eintrag ist älter als " + ConfigData.ReconnectMin + " Minuten. Versuche Neustart.");
                                    setState(ConfigData.statesPrefix + ".LastTopic", "Last Action Restart:" + new Date().toLocaleString())
                                    runScript();
                                    return;
                                    // Wenn letzte Powerstream-Meldung älter als 5 min ist, schicke Lebenszeichen 
                                } else if (getState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX')?.ts < Date.now() - 5 * 60 * 1000) {
                                    log("Reconnect zu Ecoflow MQTT für PowerStream - Daten")
                                    //setmusterGetPS()
                                    //.ts Updaten
                                    const oldvalue = getState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX').val
                                    setState(ConfigData.statesPrefix + '.app_device_property_' + ConfigData.seriennummern[0].seriennummer + '.RAW_HEX', oldvalue)
                                    reconnect();
                                    return;
                                    //runScript();
                                } else {
                                    callback();
                                }
                            }
                            
                            //*/
                            
                            async function createMyState(name) {
                                if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) {
                                    await createStateAsync(ConfigData.statesPrefix + '.' + name, {
                                        name: name.split('.').pop(),
                                        role: 'state',
                                        type: 'string',
                                        read: true,
                                        write: true
                                    });
                                }
                            }
                            
                            function decodeAndPrint(hexString) {
                                const root = protobuf.parse(protoSource).root;
                                const PowerMessage = root.lookupType("PowerMessage");
                                const message = PowerMessage.decode(Buffer.from(hexString, "hex"));
                                const object = PowerMessage.toObject(message, { defaults: false });
                                return JSON.stringify(object);
                            }
                            
                            function SendProto(protomsg, topic) {
                                const root = protobuf.parse(protoSource).root;
                                const PowerMessage = root.lookupType("PowerMessage");
                                const message = PowerMessage.create(JSON.parse(protomsg));
                                const messageBuffer = PowerMessage.encode(message).finish();
                                //log("Modifizierter Hex-String:" +  Buffer.from(messageBuffer).toString("hex"));
                                //log("topic:" +  topic);
                                client.publish(topic, messageBuffer, { qos: 1 }, function (error) {
                                    if (error) {
                                        console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error);
                                    } else {
                                        if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.');
                                    }
                                });
                            }
                            
                            //############ Funktionen zum Setzen von Werten
                            
                            const musterSetAC = `
                            {
                              "item": {
                                "meta": {
                                  "value": 1300
                                },
                                "src": 32,
                                "dest": 53,
                                "unknown1": 1,
                                "unknown2": 1,
                                "unknown4": 3,
                                "cmdFunc": 20,
                                "cmdId": 129,
                                "unknown5": 3,
                                "needAck": 1,
                                "timestamp": {
                                  "low": 35061401,
                                  "high": 0,
                                  "unsigned": true
                                },
                                "unknown6": 19,
                                "unknown7": 1,
                                "OS": "ios",
                                "serialNumber": "ABCxxxxxxx123"
                              }
                            }
                            `
                            const musterSetAC2 = `
                            {
                              "item": {
                                "meta": {
                                  "value": 17477
                                },
                                "src": 32,
                                "dest": 53,
                                "unknown1": 1,
                                "unknown2": 1,
                                "unknown4": 3,
                                "cmdFunc": 32,
                                "cmdId": 11,
                                "unknown5": 4,
                                "needAck": 1,
                                "timestamp": {
                                  "low": 105187935,
                                  "high": 0,
                                  "unsigned": true
                                },
                                "unknown6": 19,
                                "unknown7": 1,
                                "OS": "ios",
                                "serialNumber": "ABCxxxxxxx123"
                              }
                            }
                            `
                            const musterGetPS = `
                            {
                              "item": {
                                "src": 32,
                                "dest": 32,
                                "timestamp": {
                                  "low": 95816611,
                                  "high": 0,
                                  "unsigned": true
                                },
                                "OS": "ios"
                              }
                            }
                            `
                            await createMyState('app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC')
                            on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + ConfigData.seriennummern[0].seriennummer + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) {
                                setAC(Number(obj.state.val))
                                setState(obj.id, obj.state.val, true);
                            });
                            
                            // Einstellen der Einspeiseleistung
                            function setAC(Value = 1000) {
                                const updatedMusterSetAC = JSON.parse(musterSetAC);
                                updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                                updatedMusterSetAC.item.meta.value = Value
                                updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                                SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
                            }
                            
                            //Anmeldenachrichten der APP
                            function setmusterGetPS() {
                                var updatedMusterSetAC = JSON.parse(musterGetPS);
                                updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                                SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/get');
                            
                                updatedMusterSetAC = JSON.parse(musterSetAC2);
                                updatedMusterSetAC.item.timestamp.low = Date.now().toString();
                                updatedMusterSetAC.item.serialNumber = ConfigData.seriennummern[0].seriennummer
                                SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + ConfigData.seriennummern[0].seriennummer + '/thing/property/set');
                            }
                            
                            
                            function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0") {
                                if (!JElements || typeof JElements !== 'object') {
                                    log('Ungültige JElements übergeben!');
                                    return;
                                }
                                for (var JElement in JElements) {
                                    var AktVal;
                                    if (typeof JElements[JElement] === "object") {
                                        generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset);
                                    } else {
                                        try {
                                            if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val; else AktVal = null
                                        } catch (e) {
                                            log("Fehler: " + e);
                                        }
                                        if (AktVal == null) {
                                            createState(preset + "." + id + "." + JElement, JElements[JElement], false);
                                            AktVal = JElements[JElement];
                                        }
                                        if (AktVal != JElements[JElement]) {
                                            if (isState2(preset + "." + id + "." + JElement)) setState(preset + "." + id + "." + JElement, JElements[JElement]);
                                        }
                                    }
                                }
                            }
                            
                            /**
                             * Checks if a a given state or part of state is existing.
                             * This is a workaround, as getObject() or getState() throw warnings in the log.
                             * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end.
                             * See: https://forum.iobroker.net/topic/11354/
                             * @param {string}    strStatePath     Input string of state, like 'javascript.0.switches.Osram.Bedroom'
                             * @param {boolean}   [strict=false]   Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string
                             * @return {boolean}                   true if state exists, false if not
                             */
                            function isState2(strStatePath, strict = true) {
                                let mSelector;
                                if (strict) {
                                    mSelector = $(strStatePath);
                                } else {
                                    mSelector = $(strStatePath + "*");
                                }
                                if (mSelector.length > 0) {
                                    return true;
                                } else {
                                    return false;
                                }
                            }
                            
                            
                            // ########### Grundbedarf steuern
                            // Es müssen Historydaten für den Wert des Smartmeters vorhanden sein, damit der niedrigeste wert der letzten x Minuten ermittelt werden kann
                            
                            function getLowestValue(id, minuten = 120) {
                                return new Promise((resolve, reject) => {
                                    const now = new Date().getTime();
                                    const range = minuten * 60 * 1000;
                                    var myresult = 0;
                                    getHistory(0, {
                                        id: id,
                                        start: now - range,
                                        end: now,
                                        aggregate: 'minmax'
                                    }, function (err, result) {
                                        if (err) {
                                            reject(err);
                                        } else if (result.length) {
                                            let lowestValue = result[0].val;
                                            for (let i = 1; i < result.length; i++) {
                                                if (result[i].val < lowestValue) {
                                                    lowestValue = result[i].val;
                                                }
                                            }
                                            myresult = Number(lowestValue);
                                            resolve(myresult);
                                        } else {
                                            reject(new Error('No data'));
                                        }
                                    });
                                });
                            }
                            
                            var OldNewValue = 0
                            function SetBasePower() {
                                if (isState2(ConfigData.SmartmeterID)) {
                                    getLowestValue(ConfigData.SmartmeterID, ConfigData.MinValueMin)
                                        .then(lowestValue => {
                                            if (lowestValue > 0) {
                                                if (ConfigData.Debug) log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue);
                                                var AndereVerbraucher = 0
                                                const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[0].seriennummer + ".data.item.meta.ToHome_Power"
                                                if (isState2(ToHomeId)) {
                                                    AndereVerbraucher = Number(getState(ToHomeId).val) / 10
                                                    var NewValue = lowestValue + AndereVerbraucher - ConfigData.BasePowerOffset
                                                    if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower
                                                    if (ConfigData.Debug) {
                                                        log("AndereVerbraucher: " + AndereVerbraucher)
                                                        log("Macht : " + (lowestValue + AndereVerbraucher) + "W Grundbedarf")
                                                        log("- Offset von: " + ConfigData.BasePowerOffset + " W = " + (NewValue) + " W Grundbedarf")
                                                    }
                                                    if (OldNewValue != NewValue) setAC(NewValue * 10)
                                                    OldNewValue = NewValue
                                                }
                                            }
                                        })
                                        .catch(error => {
                                            log("Fehler beim Abrufen des niedrigsten Werts: " + error);
                                        });
                                }
                            }
                            
                            
                            
                            M Offline
                            M Offline
                            mattenausohz
                            schrieb am zuletzt editiert von
                            #169

                            @waly_de Wenn der SetAC Wert sich im Objekt Baum nicht ändert: was kann da das Problem sein? Das Objekt für die aktuelle Einspeisung habe ich konfiguriert.

                            W 1 Antwort Letzte Antwort
                            0
                            • M mattenausohz

                              @waly_de Wenn der SetAC Wert sich im Objekt Baum nicht ändert: was kann da das Problem sein? Das Objekt für die aktuelle Einspeisung habe ich konfiguriert.

                              W Offline
                              W Offline
                              Waly_de
                              schrieb am zuletzt editiert von
                              #170

                              @mattenausohz SetAC ändert sich nur, wenn Du ihn änderst. Im Moment dient er nur zum manuellen Schreiben des Wertes, wenn kein Stromzähler konfiguriert ist. Den aktuellen Wert für die eingestellte Einspeiseleistung (Leistungsbedarf am AC) findest du hier:

                              0_userdata.0.ecoflow.app_device_property_<Seriennummer>.data.item.meta.Needed_Power
                              

                              Und das was grade wirklich in Haus geht hier:

                              0_userdata.0.ecoflow.app_device_property_<Seriennummer>.data.item.meta.ToHome_Power
                              
                              
                              M 1 Antwort Letzte Antwort
                              0
                              • W Waly_de

                                @mattenausohz SetAC ändert sich nur, wenn Du ihn änderst. Im Moment dient er nur zum manuellen Schreiben des Wertes, wenn kein Stromzähler konfiguriert ist. Den aktuellen Wert für die eingestellte Einspeiseleistung (Leistungsbedarf am AC) findest du hier:

                                0_userdata.0.ecoflow.app_device_property_<Seriennummer>.data.item.meta.Needed_Power
                                

                                Und das was grade wirklich in Haus geht hier:

                                0_userdata.0.ecoflow.app_device_property_<Seriennummer>.data.item.meta.ToHome_Power
                                
                                
                                M Offline
                                M Offline
                                mattenausohz
                                schrieb am zuletzt editiert von mattenausohz
                                #171

                                @waly_de Ah ok, verstanden. Danke! :) Noch ein paar Fragen:

                                1. Kannst Du noch mal kurz erläutern was es mit dem BasePower Offset auf sich hat?
                                2. Ich habe derzeit zu 80% einen negativen Netzbezug meist um die -40 bis -50. Habe alle Werte im Standard gelassen. Woran kann das liegen?
                                3. Die Produktion (PV1 und PV2) aktualisieren sich im Vergleich zur App extrem selten und passen auch nicht wirklich zum Gesamt-Wert. PV1 + PV2 scheint nicht die "Solar-Summe" auf der App zu sein.
                                4. Habe jetzt den Herd laufen. Gesamt werden über 1500 W dauerhaft verbraucht. Die Einspeisung steigt aber nicht auf 600 sondern schwankt weiterhin (die vor der Nutzung des Herds) um 500 W herum.

                                Nun sehe ich das auch noch:
                                19:46:30.583 error javascript.0 (788087) Script script.js.common.ecoFlow_PowerStream is calling setState more than 600 times per minute! Stopping Script now! Please check your script!

                                W 1 Antwort Letzte Antwort
                                0
                                • M mattenausohz

                                  @waly_de Ah ok, verstanden. Danke! :) Noch ein paar Fragen:

                                  1. Kannst Du noch mal kurz erläutern was es mit dem BasePower Offset auf sich hat?
                                  2. Ich habe derzeit zu 80% einen negativen Netzbezug meist um die -40 bis -50. Habe alle Werte im Standard gelassen. Woran kann das liegen?
                                  3. Die Produktion (PV1 und PV2) aktualisieren sich im Vergleich zur App extrem selten und passen auch nicht wirklich zum Gesamt-Wert. PV1 + PV2 scheint nicht die "Solar-Summe" auf der App zu sein.
                                  4. Habe jetzt den Herd laufen. Gesamt werden über 1500 W dauerhaft verbraucht. Die Einspeisung steigt aber nicht auf 600 sondern schwankt weiterhin (die vor der Nutzung des Herds) um 500 W herum.

                                  Nun sehe ich das auch noch:
                                  19:46:30.583 error javascript.0 (788087) Script script.js.common.ecoFlow_PowerStream is calling setState more than 600 times per minute! Stopping Script now! Please check your script!

                                  W Offline
                                  W Offline
                                  Waly_de
                                  schrieb am zuletzt editiert von Waly_de
                                  #172

                                  @mattenausohz

                                  1. BasePower Offset ist das, was bei der Berechnung der Einspeiseleistung abgezogen wird. Also wenn Ermittelt wird, dass Dein Verbrauch gerade 400W ist und der Offset auf 50 eingestellt ist, dann wird 350W als Einspeiseleistung eingestellt. Dein Stromzähler sollte also im Idealfall dann 50 W anzeigen.

                                  2. Dann stimmt etwas nicht. Der Wert sollte eher oberhalb des Offset liegen. Es wird ja immer der niedrigste Verbrauchswert der letzten <MinValueMin> Minuten ermittelt und davon der Offset abgezogen...

                                  3. Ja die Werte weichen ab. Ich habe noch keine Erklärung bzw. keine Methode gefunden um das 100% gleich zu ziehen. Im Moment ziehe ich von den PV1 und PV2 Werten je 18W ab um die Summe zu errechnen. Das kam den Werten der App am nächsten. 100% Ist das aber nicht. Vielleicht findet einer von Euch ja raus, wie der Wert genau berechnet werden kann. Wenn du die App benutzt während das Skript läuft, kann es sein das der Empfang der Nachrichten im Skript abbricht. Ich Monitor das schon und starte das Skript neu.. aber das dauert. Wenn Du selbst neu startest, sollte es wieder schneller Daten geben.

                                  4. Denk dran.. es wird der niedrigste wert der letzten <MinValueMin> Minuten ermittelt. daher muss die Leistung von 1500W mindestens <MinValueMin> Minuten dauerhaft anliegen, damit die Einspeisung angehoben wird. (Hast du auch die History für den Verbrauch aktiviert?)

                                  5. Oha.. ja, es kommen eben sehr viele Updates vom MQTT. Ich hatte das auch, aber mit mehr als 1000. Ist aber kein Fehler, sondern leider die Realität. Ich hab das Limit in den Einstellungen des Javascript Adapters auf 5000 Erhöht.... Alternativ könntest du den Empfang der nicht codierten Meldungen (Delta 2 oder Max) abstellen. Die werden für die Funktionalität nicht gebraucht. Dazu muss nur eine Zeile im Script auskommentiert werden. Hier die Funktion mit der auskommentieren Anweisung:

                                  // Auf Nachricht empfangen Ereignis reagieren
                                      client.on('message', async function (topic, message) {
                                          var jsonMessage = ""
                                          const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
                                          await createMyState(mqState + ".RAW")
                                          setState(ConfigData.statesPrefix + ".LastTopic", topic)
                                          try {
                                              jsonMessage = JSON.parse(message);
                                              if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                                              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                                              //generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
                                          } catch (error) {
                                              if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                                              await createMyState(mqState + ".RAW_HEX")
                                              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                                              if (ConfigData.Debug) log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                                              const messagedecoded = decodeAndPrint(message.toString('hex'))
                                              setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                                              generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
                                          }
                                      });
                                  

                                  übrigens, nach diesem Eintrag im Log steht das Script so lange, bis du es Manuell neu startest. Also danach wird nichts mehr getan.

                                  PS: Ich arbeite noch an der Sache. Hab einiges schon ergänzt und verbessert, vor allem die Berechnung der Einspeiseleistung. Aber ist noch nicht reif zum Posten.. dauert noch ein paar Tage...

                                  M J 2 Antworten Letzte Antwort
                                  1
                                  • W Waly_de

                                    @mattenausohz

                                    1. BasePower Offset ist das, was bei der Berechnung der Einspeiseleistung abgezogen wird. Also wenn Ermittelt wird, dass Dein Verbrauch gerade 400W ist und der Offset auf 50 eingestellt ist, dann wird 350W als Einspeiseleistung eingestellt. Dein Stromzähler sollte also im Idealfall dann 50 W anzeigen.

                                    2. Dann stimmt etwas nicht. Der Wert sollte eher oberhalb des Offset liegen. Es wird ja immer der niedrigste Verbrauchswert der letzten <MinValueMin> Minuten ermittelt und davon der Offset abgezogen...

                                    3. Ja die Werte weichen ab. Ich habe noch keine Erklärung bzw. keine Methode gefunden um das 100% gleich zu ziehen. Im Moment ziehe ich von den PV1 und PV2 Werten je 18W ab um die Summe zu errechnen. Das kam den Werten der App am nächsten. 100% Ist das aber nicht. Vielleicht findet einer von Euch ja raus, wie der Wert genau berechnet werden kann. Wenn du die App benutzt während das Skript läuft, kann es sein das der Empfang der Nachrichten im Skript abbricht. Ich Monitor das schon und starte das Skript neu.. aber das dauert. Wenn Du selbst neu startest, sollte es wieder schneller Daten geben.

                                    4. Denk dran.. es wird der niedrigste wert der letzten <MinValueMin> Minuten ermittelt. daher muss die Leistung von 1500W mindestens <MinValueMin> Minuten dauerhaft anliegen, damit die Einspeisung angehoben wird. (Hast du auch die History für den Verbrauch aktiviert?)

                                    5. Oha.. ja, es kommen eben sehr viele Updates vom MQTT. Ich hatte das auch, aber mit mehr als 1000. Ist aber kein Fehler, sondern leider die Realität. Ich hab das Limit in den Einstellungen des Javascript Adapters auf 5000 Erhöht.... Alternativ könntest du den Empfang der nicht codierten Meldungen (Delta 2 oder Max) abstellen. Die werden für die Funktionalität nicht gebraucht. Dazu muss nur eine Zeile im Script auskommentiert werden. Hier die Funktion mit der auskommentieren Anweisung:

                                    // Auf Nachricht empfangen Ereignis reagieren
                                        client.on('message', async function (topic, message) {
                                            var jsonMessage = ""
                                            const mqState = topic.replace(/^\//, '').replace(/\//g, '_')
                                            await createMyState(mqState + ".RAW")
                                            setState(ConfigData.statesPrefix + ".LastTopic", topic)
                                            try {
                                                jsonMessage = JSON.parse(message);
                                                if (ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
                                                setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage))
                                                //generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState)
                                            } catch (error) {
                                                if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));
                                                await createMyState(mqState + ".RAW_HEX")
                                                setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex'))
                                                if (ConfigData.Debug) log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex')))
                                                const messagedecoded = decodeAndPrint(message.toString('hex'))
                                                setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded)
                                                generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState)
                                            }
                                        });
                                    

                                    übrigens, nach diesem Eintrag im Log steht das Script so lange, bis du es Manuell neu startest. Also danach wird nichts mehr getan.

                                    PS: Ich arbeite noch an der Sache. Hab einiges schon ergänzt und verbessert, vor allem die Berechnung der Einspeiseleistung. Aber ist noch nicht reif zum Posten.. dauert noch ein paar Tage...

                                    M Offline
                                    M Offline
                                    mattenausohz
                                    schrieb am zuletzt editiert von mattenausohz
                                    #173

                                    @waly_de Ich hatte übrigens gestern nur an PV1 ein Modul und trotzdem wurden beide Felder mit Werten befüllt. Jetzt noch mal ne Frage zum Script: BasePower wird ja nur neu berechnet wenn "lowestValue" >0 ist. Das heißt, dass wenn ich einen Überschuss habe am Hausanschluss wird nicht runter geregelt. Das ist ok solange man nicht einen Akku noch laden will, richtig? Hab die Abfrage jetzt mal rausgenommen mit dem ">0". Man müsste nun noch den Akkustand mit integrieren:

                                    Statt:

                                    if (lowestValue > 0)
                                    

                                    könnte man sowas wie

                                    if (getState(ConfigData.PowerStreamAkkuStandID).val<100){
                                    

                                    nehmen. Oder?

                                    1 Antwort Letzte Antwort
                                    0
                                    • FelixCrafter83F Offline
                                      FelixCrafter83F Offline
                                      FelixCrafter83
                                      schrieb am zuletzt editiert von
                                      #174

                                      ich bräuchte Hilfe beim einbinden eines Smartplugs
                                      Ich habe hier verschiedene Daten vom MQTT Broker bekommen
                                      es kommen bei /app/device/property/HW52ZDH4SF5J6396 immer zwei verschiedene datensätze:

                                      Der mit der Energiemessung: Zum beispiel hier die muss mit 14W sein (mann muss hier wieder /10 machen):
                                      Field number 10 hat dort den wert 140 -> 140/10 = 10W
                                      20885958-631f-40df-af9c-d9da55e69ecd-image.png

                                      0a 2e 0a 03 50 8c 01 10 35 18 20 20 01 28 01 40 02 48 01 50 03 58 01 80 01 03 88 01 03 ca 01 10 48 57 35 32 5a 44 48 34 53 46 35 4a 36 33 39 36
                                      

                                      Und einen mit generellen infos?:

                                      0a780a4c08ffff0312220880c28da50610051a1803030000000000000000000000000000000000000000000012220880c28da50610061a18163700000000000000000000000000000000000000000000103518202001280140fe014820504c5801800103880103ca0110485735325a4448345346354a36333936
                                      

                                      Das wird immer angefragt bei /app/<userid>/HW52ZDH4SF5J6396/thing/property/get:
                                      Das war bis jetzt immer derselbe datensatz

                                      0a0d1020182070b424ba0103617070
                                      

                                      Das wird beim ein und ausschalten von der App auf /app/<userid>/HW52ZDH4SF5J6396/thing/property/set gepublished:
                                      AUS:

                                      0a 22 10 20 18 35 40 02 48 81 01 58 01 70 8f eb 2b ca 01 10 48 57 35 32 5a 44 48 34 53 46 35 4a 36 33 39 36
                                      

                                      AN:

                                      0a 28 0a 02 08 01 10 20 18 35 40 02 48 81 01 50 02 58 01 70 9f ac 1f ca 01 10 48 57 35 32 5a 44 48 34 53 46 35 4a 36 33 39 36
                                      

                                      Wie seit ihr auf die .proto files gekommen?
                                      habt ihr die im code der app gefunden?
                                      könnte man für die steckdosen auch eine .proto machen?

                                      Vielen Dank

                                      M xNodKaneX 2 Antworten Letzte Antwort
                                      0
                                      • FelixCrafter83F FelixCrafter83

                                        ich bräuchte Hilfe beim einbinden eines Smartplugs
                                        Ich habe hier verschiedene Daten vom MQTT Broker bekommen
                                        es kommen bei /app/device/property/HW52ZDH4SF5J6396 immer zwei verschiedene datensätze:

                                        Der mit der Energiemessung: Zum beispiel hier die muss mit 14W sein (mann muss hier wieder /10 machen):
                                        Field number 10 hat dort den wert 140 -> 140/10 = 10W
                                        20885958-631f-40df-af9c-d9da55e69ecd-image.png

                                        0a 2e 0a 03 50 8c 01 10 35 18 20 20 01 28 01 40 02 48 01 50 03 58 01 80 01 03 88 01 03 ca 01 10 48 57 35 32 5a 44 48 34 53 46 35 4a 36 33 39 36
                                        

                                        Und einen mit generellen infos?:

                                        0a780a4c08ffff0312220880c28da50610051a1803030000000000000000000000000000000000000000000012220880c28da50610061a18163700000000000000000000000000000000000000000000103518202001280140fe014820504c5801800103880103ca0110485735325a4448345346354a36333936
                                        

                                        Das wird immer angefragt bei /app/<userid>/HW52ZDH4SF5J6396/thing/property/get:
                                        Das war bis jetzt immer derselbe datensatz

                                        0a0d1020182070b424ba0103617070
                                        

                                        Das wird beim ein und ausschalten von der App auf /app/<userid>/HW52ZDH4SF5J6396/thing/property/set gepublished:
                                        AUS:

                                        0a 22 10 20 18 35 40 02 48 81 01 58 01 70 8f eb 2b ca 01 10 48 57 35 32 5a 44 48 34 53 46 35 4a 36 33 39 36
                                        

                                        AN:

                                        0a 28 0a 02 08 01 10 20 18 35 40 02 48 81 01 50 02 58 01 70 9f ac 1f ca 01 10 48 57 35 32 5a 44 48 34 53 46 35 4a 36 33 39 36
                                        

                                        Wie seit ihr auf die .proto files gekommen?
                                        habt ihr die im code der app gefunden?
                                        könnte man für die steckdosen auch eine .proto machen?

                                        Vielen Dank

                                        M Offline
                                        M Offline
                                        mattenausohz
                                        schrieb am zuletzt editiert von mattenausohz
                                        #175

                                        @felixcrafter83 Darf ich fragen wozu Du noch SmartPlugs brauchst wenn Du doch die "echte" Last live nutzen kannst?

                                        FelixCrafter83F 1 Antwort Letzte Antwort
                                        0
                                        • M mattenausohz

                                          @felixcrafter83 Darf ich fragen wozu Du noch SmartPlugs brauchst wenn Du doch die "echte" Last live nutzen kannst?

                                          FelixCrafter83F Offline
                                          FelixCrafter83F Offline
                                          FelixCrafter83
                                          schrieb am zuletzt editiert von FelixCrafter83
                                          #176

                                          @mattenausohz ich habe diesen smartplug bei einem Ecoflow gewinnspiel gewonnen.
                                          ich möchte mit dem etwas rumspielen, also für etwas anderes benutzen und ich habe ja noch kein powerstream.
                                          Da hast du recht, smart plugs braucht man mit den scripten natürlich nicht mehr, wenn man die leistung am stromzähler abgreift.
                                          Ich hätte mir sonst keinen gekauft.
                                          Habe nämlich auch schon ein smartmeter

                                          1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          594

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          Themen

                                          1.3m

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

                                          • Du hast noch kein Konto? Registrieren

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