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

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

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. Node-Red
  5. ich brauche etwas Nachhilfe mit Node Red und RS232

NEWS

  • Monatsrückblick Juli / August 2026 ist online!
    BluefoxB
    Bluefox
    10
    1
    1.0k

  • Der neue Monatsrückblick für Mai und Juni 2026 ist online!
    BluefoxB
    Bluefox
    8
    1
    1.4k

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    11
    1
    3.9k

ich brauche etwas Nachhilfe mit Node Red und RS232

Geplant Angeheftet Gesperrt Verschoben Node-Red
77 Beiträge 6 Kommentatoren 1.2k Aufrufe 4 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • A andibr

    @OliverIO
    Ich fürchte es ist alles viel einfacher als das wir alle denken. Mit dieser Ergänzung:
    Bildschirmfoto vom 2026-09-05 14-26-15.png

    habe ich jetzt nämlich ein File bekommen, dass mitte im String einfach aufhört.
    meinfile.txt

    ganz am Schluss hat es einen Antwortblock, der einfach mittendrin abbricht. Damit ist es natürlich logisch, das die erzeugung der Daten für die weiterverarbeitung nicht funktioniert.

    Aus meiner Sicht müsste man in dem Fall zusätzlich zuerst noch überprüfen ob der "Datensatz" quasi auch vollständig ist und sonst den ganzen verwerfen. Nur was geschieht wenn er plötzlich alle verwirft und die Daten nicht mehr aktualisiert werden und keiner das bemerkt? Da müsste man ja noch irgend eine Fehlebehandlung reinbauen?

    Ich frage mich aber immer noch woran es liegt, das er plötzlich abbricht, kann es sein, dass da irgend ein Interrupt im Prozessor die Komunikation unterbricht? Das wäre ja auch etwas doof, schliesslich ist das ja die Konfigurationsschnittstelle.

    Nachtrag

    Kann man da eine Abfrage machen ob am Schluss der Text "Comman completed successfully" steht und erst dann die Daten aufbereiten?

    OliverIOO Offline
    OliverIOO Offline
    OliverIO
    schrieb zuletzt editiert von
    #64

    @andibr

    Das Geheimnis liegt bei

    Command completed successfully

    Erst wenn das kommt, hast du alle Daten erhalten.
    Eine andere Alternative wäre, jede Zeile hat eine definierte Länge.
    Wenn eine Zeile mal nicht die exakte Länge hat, dann wird diese Zeile verworfen.
    Interessant wäre jetzt, gibt es immer wieder mal Kommunikationsunterbrechungen und wird beim nächsten anfordern normal fortgesetzt? Oder bricht der Flow dann komplett ab?
    Wenn die serielle Schnittstelle immer wieder mal was verschluckt, aber beim nächsten Mal normal weitermacht, könnte man das glaube ich für verschmerzen.
    Alternativ könnte man, wenn man so etwas erkennt, die Daten sofort erneut anfordern, muss man aber aufpassen, dass man nicht in einen unendlich Zyklus, der sich aufschaukelt gerät.

    Meine Adapter und Widgets
    TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
    Links im Profil

    1 Antwort Letzte Antwort
    0
    • A Offline
      A Offline
      andibr
      schrieb zuletzt editiert von andibr
      #65

      Genau das habe ich auch gerade entdeckt.
      Jetzt müsste man eigentlich doch im ersten Function Node in Zeile 7 ein Filter einbauen der im String die Anwesenheit vom "Command....." überwacht?
      Nur da bin ich jetzt überfordert.

      Wie geschrieben habe ich das Gefühl, das geschieht nicht wilkürlich, sondern das BMS bricht den Stream ab, weil etwas anderes wichtigeres gemacht werden muss. Denn der Datenverlust ist an verschiedenen Stellen im ganzen String zu finden.

      Nachtrag
      Mit hilfe von irgend einer KI habe ich nun ein Filter gebastelt:

      // Prüft, ob der Suchbegriff in der msg.payload existiert
      if (msg.payload.includes("completed")) {
          msg.found = true;
          msg.payload = "Text wurde gefunden!";
      } else {
          msg.found = false;
          return null;
      }
      

      Nur zweifle ich stark, das der funktioniert, denn eigentlich dürfte jetzt doch keine Zelldaten mehr rauskommen, der derpyload sollte doch jetzt "Text wurde gefunden sein"?
      Es kommen aber nach wievor die einzelnen Zelldaten hinten raus.

      OliverIOO 3 Antworten Letzte Antwort
      0
      • A andibr

        Genau das habe ich auch gerade entdeckt.
        Jetzt müsste man eigentlich doch im ersten Function Node in Zeile 7 ein Filter einbauen der im String die Anwesenheit vom "Command....." überwacht?
        Nur da bin ich jetzt überfordert.

        Wie geschrieben habe ich das Gefühl, das geschieht nicht wilkürlich, sondern das BMS bricht den Stream ab, weil etwas anderes wichtigeres gemacht werden muss. Denn der Datenverlust ist an verschiedenen Stellen im ganzen String zu finden.

        Nachtrag
        Mit hilfe von irgend einer KI habe ich nun ein Filter gebastelt:

        // Prüft, ob der Suchbegriff in der msg.payload existiert
        if (msg.payload.includes("completed")) {
            msg.found = true;
            msg.payload = "Text wurde gefunden!";
        } else {
            msg.found = false;
            return null;
        }
        

        Nur zweifle ich stark, das der funktioniert, denn eigentlich dürfte jetzt doch keine Zelldaten mehr rauskommen, der derpyload sollte doch jetzt "Text wurde gefunden sein"?
        Es kommen aber nach wievor die einzelnen Zelldaten hinten raus.

        OliverIOO Offline
        OliverIOO Offline
        OliverIO
        schrieb zuletzt editiert von
        #66

        @andibr

        dann hast du die KI falsch angewendet.
        Bei KI immer lösungsoffen formulieren, sonst interpretiert sie eine gewisse Vorgabe als Regel und hält sich daran.

        Hier mal mein Prompt

        ich habe eine function node in node red diese erhält input der serial node aus einer rs232 kommunikation leider bricht die kommunikation immer wieder mal ab, so das einzelne zeilen aus dem input nicht korrekt verarbeitet werden können. das ist die function node

        const text = msg.payload;
        
        if (typeof text !== "string") {
            node.error("msg.payload ist kein String", msg);
            return null;
        }
        
        // In einzelne Zeilen zerlegen
        const lines = text
            .split(/\r?\n/)
            .map(line => line.trim())
            .filter(line => line !== "");
        
        // Nur Zeilen berücksichtigen, die mit einer Batterienummer beginnen
        const dataLines = lines.filter(line => /^\d+\s/.test(line));
        
        const messages = [];
        
        for (const line of dataLines) {
        
            // Spalten sind durch mindestens 2 Leerzeichen getrennt
            const columns = line.split(/\s{2,}/);
        
            if (columns.length < 9) {
                node.warn("Zeile konnte nicht verarbeitet werden: " + line);
                continue;
            }
        
            const battery = {
                battery: Number(columns[0]),
                volt: Number(columns[1]),
                tempr: Number(columns[2]),
                baseState: columns[3],
                voltState: columns[4],
                currState: columns[5],
                tempState: columns[6],
                coulomb: Number(columns[7].replace("%", "")),
                capacity: Number(columns[8].replace(/[^\d]/g, ""))
            };
        
            messages.push({
                ...msg,
                payload: battery
            });
        }
        
        // Alle Nachrichten nacheinander über denselben Ausgang senden
        return [messages];
        

        das ist der message input.

        <<hier habe ich die komplette datei eingefügt.>>

        leider weiß ich nicht, ob durch die serial node noch transformationen durchgeführt wurden. kannst du mir die function node so erweitern, das prüfungen der datenqualität vorgenommen werden, das nachrichtenende ordentlich erkannt wird. ggfs unvollständige daten ausgefiltert werden. fehler und warnungen dazu als nachrichten ausgegeben werden, so das man bei weiteren problemen diese dann besser analysiert werden können. passe nur die function node an. keine weiteren nodes einführen. bzw. als hinweis für den anwender mitgeben.

        Meine Adapter und Widgets
        TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
        Links im Profil

        1 Antwort Letzte Antwort
        0
        • A andibr

          Genau das habe ich auch gerade entdeckt.
          Jetzt müsste man eigentlich doch im ersten Function Node in Zeile 7 ein Filter einbauen der im String die Anwesenheit vom "Command....." überwacht?
          Nur da bin ich jetzt überfordert.

          Wie geschrieben habe ich das Gefühl, das geschieht nicht wilkürlich, sondern das BMS bricht den Stream ab, weil etwas anderes wichtigeres gemacht werden muss. Denn der Datenverlust ist an verschiedenen Stellen im ganzen String zu finden.

          Nachtrag
          Mit hilfe von irgend einer KI habe ich nun ein Filter gebastelt:

          // Prüft, ob der Suchbegriff in der msg.payload existiert
          if (msg.payload.includes("completed")) {
              msg.found = true;
              msg.payload = "Text wurde gefunden!";
          } else {
              msg.found = false;
              return null;
          }
          

          Nur zweifle ich stark, das der funktioniert, denn eigentlich dürfte jetzt doch keine Zelldaten mehr rauskommen, der derpyload sollte doch jetzt "Text wurde gefunden sein"?
          Es kommen aber nach wievor die einzelnen Zelldaten hinten raus.

          OliverIOO Offline
          OliverIOO Offline
          OliverIO
          schrieb zuletzt editiert von
          #67

          @andibr

          hier die Antowrt der KI

          Ja. Im Mitschnitt ist das Muster ziemlich eindeutig: Ein sauberer Block beginnt mit der Kopfzeile Battery Volt Tempr ..., enthält Batterien 0 bis 15 und endet mit Command completed successfully. Später sieht man tatsächlich einen Abbruch mitten in Batterie 12, worauf direkt ein neuer Header folgt. Genau so einen Block sollte man komplett verwerfen statt teilweise weiterzuverarbeiten.

          Ich würde außerdem nicht mehr davon ausgehen, dass ein msg.payload exakt einen vollständigen Datensatz enthält. Die Function puffert deshalb Fragmente über mehrere Serial-Nachrichten hinweg und gibt Batteriedaten erst nach einem sauber erkannten Blockende und vollständiger Prüfung aller 16 Batterien aus.

          /*
           * Robuster Parser für RS232-Batteriedaten
           *
           * Eigenschaften:
           * - verarbeitet fragmentierte Serial-Daten über mehrere msg.payload hinweg
           * - erkennt Beginn eines Datensatzes anhand der Headerzeile
           * - erkennt Ende anhand "Command completed successfully"
           * - erwartet Batterien 0..15 genau einmal
           * - verwirft unvollständige Datensätze komplett
           * - prüft Zahlen, Prozentwert, Capacity und grobe Wertebereiche
           * - erzeugt Diagnosemeldungen für Warnungen/Fehler
           * - schreibt Warnungen zusätzlich in die Node-RED Debug-/Log-Ausgabe
           *
           * EIN Ausgang:
           *   msg.messageType === "data"     -> gültige Batteriedaten
           *   msg.messageType === "warning"  -> Diagnose/Warnung
           *   msg.messageType === "error"    -> Diagnose/Fehler
           */
          
          
          // ------------------------------------------------------------
          // Konfiguration
          // ------------------------------------------------------------
          
          const EXPECTED_BATTERIES = 16;
          
          // Maximale Zeit, die ein begonnener Datensatz offen bleiben darf.
          // Die Prüfung erfolgt beim nächsten eintreffenden msg.
          const BLOCK_TIMEOUT_MS = 30000;
          
          // Schutz gegen unbegrenzt anwachsende Fragmentpuffer
          const MAX_FRAGMENT_LENGTH = 4096;
          
          // Schutz gegen völlig entartete Datensätze
          const MAX_BLOCK_LINES = 100;
          
          // Sehr großzügige Plausibilitätsgrenzen.
          // Diese können später an die tatsächliche Anlage angepasst werden.
          const LIMITS = {
              voltMin: 0,
              voltMax: 10000,
          
              temprMin: -100000,
              temprMax: 200000,
          
              coulombMin: 0,
              coulombMax: 100,
          
              capacityMin: 0,
              capacityMax: 1000000
          };
          
          
          // ------------------------------------------------------------
          // Eingang prüfen
          // ------------------------------------------------------------
          
          const text = msg.payload;
          
          if (typeof text !== "string") {
          
              const diagnostic = createDiagnostic(
                  "error",
                  "INVALID_PAYLOAD_TYPE",
                  "msg.payload ist kein String",
                  {
                      receivedType: typeof text
                  }
              );
          
              node.error(diagnostic, msg);
          
              return [[diagnostic]];
          }
          
          
          // ------------------------------------------------------------
          // Zustand aus Node-Context holen
          // ------------------------------------------------------------
          
          let fragment = context.get("serialFragment") || "";
          let block = context.get("batteryBlock") || null;
          
          
          // ------------------------------------------------------------
          // Ausgaben dieser Function-Ausführung
          // ------------------------------------------------------------
          
          const output = [];
          
          
          // ------------------------------------------------------------
          // Diagnosefunktion
          // ------------------------------------------------------------
          
          function createDiagnostic(severity, code, message, details) {
          
              return {
                  ...msg,
          
                  messageType: severity,
          
                  payload: {
                      severity: severity,
                      code: code,
                      message: message,
                      timestamp: new Date().toISOString(),
                      details: details || {}
                  }
              };
          }
          
          
          function diagnostic(severity, code, message, details) {
          
              const diag = createDiagnostic(
                  severity,
                  code,
                  message,
                  details
              );
          
              output.push(diag);
          
              // Zusätzlich in Node-RED sichtbar machen
              if (severity === "error") {
                  node.error(
                      code + ": " + message +
                      (details ? " | " + JSON.stringify(details) : "")
                  );
              } else {
                  node.warn(
                      code + ": " + message +
                      (details ? " | " + JSON.stringify(details) : "")
                  );
              }
          }
          
          
          // ------------------------------------------------------------
          // Hilfsfunktionen
          // ------------------------------------------------------------
          
          function isHeader(line) {
          
              return /^Battery\s+Volt\s+Tempr\s+Base\s+State/i.test(line);
          }
          
          
          function isEndMarker(line) {
          
              return /^Command completed successfully\s*$/i.test(line);
          }
          
          
          function isPromptOrNoise(line) {
          
              return (
                  line === "" ||
                  line === "$$" ||
                  line === "@" ||
                  /^PYTES>/i.test(line)
              );
          }
          
          
          // ------------------------------------------------------------
          // Batteriezeile parsen
          // ------------------------------------------------------------
          
          function parseBatteryLine(line) {
          
              /*
               * Beispiel:
               *
               * 0        3462     28000    Charge       Normal
               * Normal   Normal   97%      95980 mAH
               *
               * Absichtlich nicht nur split(/\s{2,}/), damit kleinere
               * Änderungen der Abstände nicht sofort zum Fehler führen.
               */
          
              const match = line.match(
                  /^(\d+)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+(?:\.\d+)?)%\s+(\d+)\s*mAH\s*$/i
              );
          
              if (!match) {
                  return {
                      valid: false,
                      reason: "FORMAT_ERROR"
                  };
              }
          
              const battery = {
                  battery: Number(match[1]),
                  volt: Number(match[2]),
                  tempr: Number(match[3]),
                  baseState: match[4],
                  voltState: match[5],
                  currState: match[6],
                  tempState: match[7],
                  coulomb: Number(match[8]),
                  capacity: Number(match[9])
              };
          
          
              // --------------------------------------------------------
              // Datentypen prüfen
              // --------------------------------------------------------
          
              const numericValues = [
                  battery.battery,
                  battery.volt,
                  battery.tempr,
                  battery.coulomb,
                  battery.capacity
              ];
          
              if (numericValues.some(v => !Number.isFinite(v))) {
          
                  return {
                      valid: false,
                      reason: "INVALID_NUMBER",
                      battery: battery
                  };
              }
          
          
              // --------------------------------------------------------
              // Harte Plausibilitätsprüfungen
              // --------------------------------------------------------
          
              if (
                  battery.battery < 0 ||
                  battery.battery >= EXPECTED_BATTERIES
              ) {
                  return {
                      valid: false,
                      reason: "INVALID_BATTERY_NUMBER",
                      battery: battery
                  };
              }
          
          
              if (
                  battery.coulomb < LIMITS.coulombMin ||
                  battery.coulomb > LIMITS.coulombMax
              ) {
                  return {
                      valid: false,
                      reason: "INVALID_COULOMB_VALUE",
                      battery: battery
                  };
              }
          
          
              if (
                  battery.capacity < LIMITS.capacityMin ||
                  battery.capacity > LIMITS.capacityMax
              ) {
                  return {
                      valid: false,
                      reason: "INVALID_CAPACITY_VALUE",
                      battery: battery
                  };
              }
          
          
              if (
                  battery.volt < LIMITS.voltMin ||
                  battery.volt > LIMITS.voltMax
              ) {
                  return {
                      valid: false,
                      reason: "INVALID_VOLTAGE_VALUE",
                      battery: battery
                  };
              }
          
          
              if (
                  battery.tempr < LIMITS.temprMin ||
                  battery.tempr > LIMITS.temprMax
              ) {
                  return {
                      valid: false,
                      reason: "INVALID_TEMPERATURE_VALUE",
                      battery: battery
                  };
              }
          
          
              return {
                  valid: true,
                  battery: battery
              };
          }
          
          
          // ------------------------------------------------------------
          // Angefangenen Block verwerfen
          // ------------------------------------------------------------
          
          function discardBlock(reason, additionalDetails) {
          
              if (!block) {
                  return;
              }
          
              const receivedBatteries = Object.keys(block.batteries)
                  .map(Number)
                  .sort((a, b) => a - b);
          
              diagnostic(
                  "warning",
                  "BLOCK_DISCARDED",
                  "Unvollständiger oder beschädigter Batteriedatensatz wurde verworfen.",
                  {
                      reason: reason,
                      receivedBatteryCount: receivedBatteries.length,
                      receivedBatteries: receivedBatteries,
                      invalidLines: block.invalidLines,
                      startedAt: new Date(block.startedAt).toISOString(),
                      ...additionalDetails
                  }
              );
          
              block = null;
          }
          
          
          // ------------------------------------------------------------
          // Fertigen Block validieren
          // ------------------------------------------------------------
          
          function finishBlock() {
          
              if (!block) {
          
                  diagnostic(
                      "warning",
                      "END_WITHOUT_HEADER",
                      "Blockende erkannt, obwohl kein aktiver Datensatz vorhanden war."
                  );
          
                  return;
              }
          
          
              const batteryNumbers = Object.keys(block.batteries)
                  .map(Number)
                  .sort((a, b) => a - b);
          
          
              // Erwartete Batterien erzeugen
              const expected = [];
          
              for (let i = 0; i < EXPECTED_BATTERIES; i++) {
                  expected.push(i);
              }
          
          
              const missing = expected.filter(
                  batteryNumber => !batteryNumbers.includes(batteryNumber)
              );
          
          
              // --------------------------------------------------------
              // Datensatz unvollständig
              // --------------------------------------------------------
          
              if (
                  batteryNumbers.length !== EXPECTED_BATTERIES ||
                  missing.length > 0 ||
                  block.invalidLines.length > 0
              ) {
          
                  diagnostic(
                      "warning",
                      "INCOMPLETE_DATASET",
                      "Datensatz wurde vollständig empfangen beendet, enthält aber nicht alle erwarteten gültigen Batteriedaten.",
                      {
                          expectedBatteryCount: EXPECTED_BATTERIES,
                          receivedBatteryCount: batteryNumbers.length,
                          receivedBatteries: batteryNumbers,
                          missingBatteries: missing,
                          invalidLines: block.invalidLines,
                          startedAt: new Date(block.startedAt).toISOString(),
                          durationMs: Date.now() - block.startedAt
                      }
                  );
          
                  block = null;
          
                  return;
              }
          
          
              // --------------------------------------------------------
              // Vollständigen Datensatz ausgeben
              // --------------------------------------------------------
          
              const batchId =
                  Date.now().toString(36) +
                  "-" +
                  Math.random().toString(36).substring(2, 8);
          
          
              for (let i = 0; i < EXPECTED_BATTERIES; i++) {
          
                  output.push({
                      ...msg,
          
                      messageType: "data",
          
                      batchId: batchId,
          
                      dataQuality: {
                          valid: true,
                          completeDataset: true,
                          batteryCount: EXPECTED_BATTERIES
                      },
          
                      payload: block.batteries[i]
                  });
              }
          
          
              // optional im Node-Status sichtbar
              node.status({
                  fill: "green",
                  shape: "dot",
                  text: "16 Batterien OK"
              });
          
          
              block = null;
          }
          
          
          // ------------------------------------------------------------
          // Einzelne komplette Textzeile verarbeiten
          // ------------------------------------------------------------
          
          function processLine(rawLine) {
          
              const line = rawLine.trim();
          
          
              // --------------------------------------------------------
              // Header
              // --------------------------------------------------------
          
              if (isHeader(line)) {
          
                  // Es läuft bereits ein Block:
                  // der vorherige wurde offensichtlich nicht sauber beendet.
                  if (block) {
          
                      discardBlock(
                          "NEW_HEADER_BEFORE_END",
                          {
                              newHeader: line
                          }
                      );
                  }
          
          
                  block = {
                      startedAt: Date.now(),
                      batteries: {},
                      invalidLines: [],
                      lineCount: 0
                  };
          
          
                  node.status({
                      fill: "yellow",
                      shape: "ring",
                      text: "Datensatz empfangen..."
                  });
          
                  return;
              }
          
          
              // --------------------------------------------------------
              // Blockende
              // --------------------------------------------------------
          
              if (isEndMarker(line)) {
          
                  finishBlock();
          
                  return;
              }
          
          
              // --------------------------------------------------------
              // bekannte Prompt-/Steuerzeilen ignorieren
              // --------------------------------------------------------
          
              if (isPromptOrNoise(line)) {
                  return;
              }
          
          
              // --------------------------------------------------------
              // Außerhalb eines Blocks
              // --------------------------------------------------------
          
              if (!block) {
          
                  // Batteriezeile außerhalb eines Headers ist verdächtig
                  if (/^\d+\s/.test(line)) {
          
                      diagnostic(
                          "warning",
                          "DATA_OUTSIDE_BLOCK",
                          "Batteriezeile außerhalb eines gültigen Datensatzes erkannt und verworfen.",
                          {
                              rawLine: line
                          }
                      );
                  }
          
                  return;
              }
          
          
              block.lineCount++;
          
          
              // --------------------------------------------------------
              // Sicherheitslimit
              // --------------------------------------------------------
          
              if (block.lineCount > MAX_BLOCK_LINES) {
          
                  discardBlock(
                      "TOO_MANY_LINES",
                      {
                          lineCount: block.lineCount
                      }
                  );
          
                  return;
              }
          
          
              // --------------------------------------------------------
              // Nur Zeilen, die mit Batterie-ID anfangen, interessieren
              // --------------------------------------------------------
          
              if (!/^\d+\s/.test(line)) {
          
                  diagnostic(
                      "warning",
                      "UNEXPECTED_LINE",
                      "Unerwartete Zeile innerhalb eines Batteriedatensatzes.",
                      {
                          rawLine: line
                      }
                  );
          
                  return;
              }
          
          
              // --------------------------------------------------------
              // Batteriezeile parsen
              // --------------------------------------------------------
          
              const parsed = parseBatteryLine(line);
          
          
              if (!parsed.valid) {
          
                  block.invalidLines.push({
                      line: line,
                      reason: parsed.reason
                  });
          
          
                  diagnostic(
                      "warning",
                      "INVALID_BATTERY_LINE",
                      "Batteriezeile konnte nicht sicher verarbeitet werden.",
                      {
                          reason: parsed.reason,
                          rawLine: line,
                          parsedData: parsed.battery || null
                      }
                  );
          
                  return;
              }
          
          
              const batteryNumber = parsed.battery.battery;
          
          
              // --------------------------------------------------------
              // Doppelte Batterie-ID
              // --------------------------------------------------------
          
              if (block.batteries[batteryNumber] !== undefined) {
          
                  block.invalidLines.push({
                      line: line,
                      reason: "DUPLICATE_BATTERY"
                  });
          
          
                  diagnostic(
                      "warning",
                      "DUPLICATE_BATTERY",
                      "Batterienummer wurde innerhalb desselben Datensatzes mehrfach empfangen.",
                      {
                          battery: batteryNumber,
                          rawLine: line
                      }
                  );
          
                  return;
              }
          
          
              block.batteries[batteryNumber] = parsed.battery;
          }
          
          
          // ------------------------------------------------------------
          // Timeout des vorherigen Blocks prüfen
          // ------------------------------------------------------------
          
          if (
              block &&
              Date.now() - block.startedAt > BLOCK_TIMEOUT_MS
          ) {
          
              discardBlock(
                  "BLOCK_TIMEOUT",
                  {
                      timeoutMs: BLOCK_TIMEOUT_MS
                  }
              );
          }
          
          
          // ------------------------------------------------------------
          // Fragmentierung der seriellen Schnittstelle behandeln
          // ------------------------------------------------------------
          
          fragment += text;
          
          
          // Schutz gegen kaputte / niemals abgeschlossene Eingabe
          if (fragment.length > MAX_FRAGMENT_LENGTH) {
          
              diagnostic(
                  "error",
                  "SERIAL_FRAGMENT_OVERFLOW",
                  "Der serielle Fragmentpuffer wurde zu groß und wurde zurückgesetzt.",
                  {
                      fragmentLength: fragment.length,
                      maxFragmentLength: MAX_FRAGMENT_LENGTH,
                      fragmentPreview: fragment.substring(0, 500)
                  }
              );
          
              fragment = "";
          
              discardBlock(
                  "SERIAL_FRAGMENT_OVERFLOW"
              );
          }
          
          
          // ------------------------------------------------------------
          // Zeilenenden erkennen
          //
          // Unterstützt:
          //   \r\n
          //   \n
          //   \r
          // ------------------------------------------------------------
          
          const parts = fragment.split(/\r\n|\n|\r/);
          
          
          // Das letzte Element könnte eine noch unvollständige Zeile sein
          fragment = parts.pop() || "";
          
          
          // Alle sicher abgeschlossenen Zeilen verarbeiten
          for (const line of parts) {
              processLine(line);
          }
          
          
          // ------------------------------------------------------------
          // Sonderfall:
          // Serial-Node liefert möglicherweise bereits jeweils EINE komplette
          // Zeile, entfernt aber selbst das Zeilenende.
          //
          // Wenn das aktuelle Fragment eindeutig wie eine vollständige
          // relevante Zeile aussieht, kann es sofort verarbeitet werden.
          // ------------------------------------------------------------
          
          const trimmedFragment = fragment.trim();
          
          if (trimmedFragment !== "") {
          
              const completeBatteryLine =
                  parseBatteryLine(trimmedFragment).valid;
          
              const obviousCompleteLine =
                  isHeader(trimmedFragment) ||
                  isEndMarker(trimmedFragment) ||
                  isPromptOrNoise(trimmedFragment) ||
                  completeBatteryLine;
          
              if (obviousCompleteLine) {
          
                  processLine(trimmedFragment);
          
                  fragment = "";
              }
          }
          
          
          // ------------------------------------------------------------
          // Zustand speichern
          // ------------------------------------------------------------
          
          context.set("serialFragment", fragment);
          context.set("batteryBlock", block);
          
          
          // ------------------------------------------------------------
          // Node-Status bei Fehlern/Warnungen
          // ------------------------------------------------------------
          
          if (output.some(m => m.messageType === "error")) {
          
              node.status({
                  fill: "red",
                  shape: "dot",
                  text: "RS232 Fehler"
              });
          
          } else if (output.some(m => m.messageType === "warning")) {
          
              node.status({
                  fill: "yellow",
                  shape: "dot",
                  text: "RS232 Warnung"
              });
          }
          
          
          // ------------------------------------------------------------
          // Alle Nachrichten nacheinander über denselben Ausgang senden
          // ------------------------------------------------------------
          
          return [output];
          

          Was sich gegenüber deiner bisherigen Function ändert

          Der wichtigste Unterschied ist, dass die Function nicht mehr jede zufällig empfangene Batteriezeile sofort weitergibt. Sie wartet auf einen kompletten Datensatz:

          Battery ... → Batterie 0 → 1 → … → 15 → Command completed successfully

          Erst wenn dieses Muster vollständig und plausibel ist, werden die 16 Nachrichten ausgegeben. Im Mitschnitt ist beispielsweise ein Block bei Batterie 12 mitten in der Zeile abgebrochen. Mit deiner bisherigen Function könnten die davor stehenden Batterien 0–11 bereits als scheinbar gültige Messwerte verarbeitet werden. Mit der neuen Version wird der gesamte Block verworfen.

          Die Function erkennt außerdem einen besonders wichtigen Fehlerfall: Kommt ein neuer Battery ...-Header, während der vorherige Datensatz noch nicht durch Command completed successfully beendet wurde, wird eine Warnung BLOCK_DISCARDED / NEW_HEADER_BEFORE_END erzeugt. Das dürfte genau der typische Fehler bei deinen Kommunikationsabbrüchen sein.

          Bei einem gültigen Block sehen die normalen Nachrichten weiterhin fast genauso aus wie bisher:

          msg.messageType = "data";
          
          msg.payload = {
              battery: 0,
              volt: 3462,
              tempr: 28000,
              baseState: "Charge",
              voltState: "Normal",
              currState: "Normal",
              tempState: "Normal",
              coulomb: 97,
              capacity: 95980
          };
          

          Zusätzlich bekommen alle 16 Nachrichten eines Blocks dieselbe batchId. Damit kannst du später erkennen, welche Messwerte garantiert aus derselben RS232-Abfrage stammen.

          Eine Diagnosemeldung sieht beispielsweise so aus:

          msg.messageType = "warning";
          
          msg.payload = {
              severity: "warning",
              code: "INCOMPLETE_DATASET",
              message: "Datensatz wurde vollständig empfangen beendet, enthält aber nicht alle erwarteten gültigen Batteriedaten.",
              timestamp: "...",
              details: {
                  expectedBatteryCount: 16,
                  receivedBatteryCount: 12,
                  receivedBatteries: [0,1,2,3,4,5,6,7,8,9,10,11],
                  missingBatteries: [12,13,14,15]
              }
          };
          

          Damit sind unter anderem NEW_HEADER_BEFORE_END, BLOCK_TIMEOUT, INVALID_BATTERY_LINE, DUPLICATE_BATTERY, DATA_OUTSIDE_BLOCK und SERIAL_FRAGMENT_OVERFLOW unterscheidbar. Gleichzeitig werden diese Informationen über node.warn() beziehungsweise node.error() im Node-RED-Debug/Log sichtbar.

          Ein Punkt ist für dein bestehendes Flow wichtig: Weil du ausdrücklich nur diese eine Function Node ändern möchtest, laufen Daten und Diagnosen über denselben Ausgang. Nachgelagerte Verarbeitung, die blind davon ausgeht, dass jede Nachricht eine Batterie enthält, sollte deshalb msg.messageType === "data" beachten. Falls du deinen bestehenden Flow überhaupt nicht anfassen möchtest, kann ich die Variante auch so ändern, dass nur gültige Batteriedaten am Ausgang erscheinen und sämtliche Diagnoseinformationen ausschließlich über node.warn()/node.error() protokolliert werden. Das wäre für einen bestehenden Produktiv-Flow wahrscheinlich die kompatibelste Variante.

          Meine Adapter und Widgets
          TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
          Links im Profil

          1 Antwort Letzte Antwort
          0
          • A andibr

            Genau das habe ich auch gerade entdeckt.
            Jetzt müsste man eigentlich doch im ersten Function Node in Zeile 7 ein Filter einbauen der im String die Anwesenheit vom "Command....." überwacht?
            Nur da bin ich jetzt überfordert.

            Wie geschrieben habe ich das Gefühl, das geschieht nicht wilkürlich, sondern das BMS bricht den Stream ab, weil etwas anderes wichtigeres gemacht werden muss. Denn der Datenverlust ist an verschiedenen Stellen im ganzen String zu finden.

            Nachtrag
            Mit hilfe von irgend einer KI habe ich nun ein Filter gebastelt:

            // Prüft, ob der Suchbegriff in der msg.payload existiert
            if (msg.payload.includes("completed")) {
                msg.found = true;
                msg.payload = "Text wurde gefunden!";
            } else {
                msg.found = false;
                return null;
            }
            

            Nur zweifle ich stark, das der funktioniert, denn eigentlich dürfte jetzt doch keine Zelldaten mehr rauskommen, der derpyload sollte doch jetzt "Text wurde gefunden sein"?
            Es kommen aber nach wievor die einzelnen Zelldaten hinten raus.

            OliverIOO Offline
            OliverIOO Offline
            OliverIO
            schrieb zuletzt editiert von OliverIO
            #68

            @andibr

            In der Nachfolge node "Nachrichten erzeugen"

            solltest du noch

            if (msg.messageType !== "data") {
                return null;
            } 
            

            am Anfang einfügen.
            f6ade35b-d8c7-43da-b197-f61115dbb10e-image.jpeg

            Meine Adapter und Widgets
            TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
            Links im Profil

            A 1 Antwort Letzte Antwort
            0
            • OliverIOO OliverIO

              @andibr

              In der Nachfolge node "Nachrichten erzeugen"

              solltest du noch

              if (msg.messageType !== "data") {
                  return null;
              } 
              

              am Anfang einfügen.
              f6ade35b-d8c7-43da-b197-f61115dbb10e-image.jpeg

              A Offline
              A Offline
              andibr
              schrieb zuletzt editiert von
              #69

              @OliverIO
              Huch, jetzt muss ganz viel auch noch kapieren, danke ich versuche das der Reihe nach zu verstehen:

              Als Versuch habe ich in Zeile 7 einfach mal das eingefügt:

              if (msg.payload.includes("completed")){
                  msg.found = true;
                  return msg;
              }   else {
                  msg.found = false;
                  node.error("String nicht komplett erhalten",msg);
                  return null;
              }
              
              

              Ob das nun richtig ist weiss ich nicht so ganz.

              OliverIOO 1 Antwort Letzte Antwort
              0
              • A andibr

                @OliverIO
                Huch, jetzt muss ganz viel auch noch kapieren, danke ich versuche das der Reihe nach zu verstehen:

                Als Versuch habe ich in Zeile 7 einfach mal das eingefügt:

                if (msg.payload.includes("completed")){
                    msg.found = true;
                    return msg;
                }   else {
                    msg.found = false;
                    node.error("String nicht komplett erhalten",msg);
                    return null;
                }
                
                

                Ob das nun richtig ist weiss ich nicht so ganz.

                OliverIOO Offline
                OliverIOO Offline
                OliverIO
                schrieb zuletzt editiert von
                #70

                @andibr

                probiere den code oben aus.
                Einmal neuer code mit viel diagnostik, warnungen und fehlermeldungen um zukünftige probleme besser analysieren zu können.

                leider steht bei der serial node nicht soviel beschreibung dabei.

                evtl mal was zur theorie.
                eine rs232 schnittstelle knn theoretisch kontinuierlich etwas senden.
                muss sie aber nicht. das rs232 protokoll prüft grob auf datenfehler, falls mal auf der leitung ein knacken war, könnte ein bit acuh falsch verstanden werden. das ist aber extrem simple.
                dann gehe ich davon aus, das die serial node einen puffer hat, den sie kontinuierlich vollschreibt. die node weiß ja nichts von irgendwelchen Endebedingungen insbesondere dann, wenn die verbindung nicht bei jeder nachricht von einem gerät geschlossen und wieder geöffnet wird.

                also irgendwann ist der puffer voll, oder man kann in der node was einstellen.
                leider konnte ich die node über die oberfläche nicht installieren um selbst nachzuschauen. auf betriebssystemebene zu installieren war mir jetzt aber zu aufwändig.

                also puffer wird beschrieben, irgendwann ist der voll, dann erzeugt diese eine msg.
                daher kann in einer msg nur ein teil einer nachricht drin stecken und der rest kommt dann beim nächsten mal.

                daher hat die KI den code der function umgeschrieben und baut selbst einen puffer auf. sobald sie eine vollständige nachricht erkennt (...completed...) erzeugt sie die msgs die dann durch die nachfolge nodes verarbeitet werden können.
                Ausserdem erzeugt sie weitere nachrichten um warnungen und fehlermeldungen ebenfalls als nachricht auszugeben. zusätzlich gibt sie dies aber auch über dein iobroker log aus.

                Meine Adapter und Widgets
                TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                Links im Profil

                A 1 Antwort Letzte Antwort
                0
                • OliverIOO OliverIO

                  @andibr

                  probiere den code oben aus.
                  Einmal neuer code mit viel diagnostik, warnungen und fehlermeldungen um zukünftige probleme besser analysieren zu können.

                  leider steht bei der serial node nicht soviel beschreibung dabei.

                  evtl mal was zur theorie.
                  eine rs232 schnittstelle knn theoretisch kontinuierlich etwas senden.
                  muss sie aber nicht. das rs232 protokoll prüft grob auf datenfehler, falls mal auf der leitung ein knacken war, könnte ein bit acuh falsch verstanden werden. das ist aber extrem simple.
                  dann gehe ich davon aus, das die serial node einen puffer hat, den sie kontinuierlich vollschreibt. die node weiß ja nichts von irgendwelchen Endebedingungen insbesondere dann, wenn die verbindung nicht bei jeder nachricht von einem gerät geschlossen und wieder geöffnet wird.

                  also irgendwann ist der puffer voll, oder man kann in der node was einstellen.
                  leider konnte ich die node über die oberfläche nicht installieren um selbst nachzuschauen. auf betriebssystemebene zu installieren war mir jetzt aber zu aufwändig.

                  also puffer wird beschrieben, irgendwann ist der voll, dann erzeugt diese eine msg.
                  daher kann in einer msg nur ein teil einer nachricht drin stecken und der rest kommt dann beim nächsten mal.

                  daher hat die KI den code der function umgeschrieben und baut selbst einen puffer auf. sobald sie eine vollständige nachricht erkennt (...completed...) erzeugt sie die msgs die dann durch die nachfolge nodes verarbeitet werden können.
                  Ausserdem erzeugt sie weitere nachrichten um warnungen und fehlermeldungen ebenfalls als nachricht auszugeben. zusätzlich gibt sie dies aber auch über dein iobroker log aus.

                  A Offline
                  A Offline
                  andibr
                  schrieb zuletzt editiert von
                  #71

                  @OliverIO
                  Also du meinst den Block mit dem R232 Parser?

                  Ich habe noch folgendes ausprobiert:

                  if (msg.payload.includes("completed")){
                      msg.found = true;
                  //    return msg;
                  }   else {
                      msg.found = false;
                      node.error("String nicht komplett erhalten",msg);
                      return null;
                  }
                  
                  const text = msg.payload;
                  

                  So kommen zumindest die Zellendaten wieder durch, das mit dem "return msg" hat irgendwie nicht funktioniert, da konnte er den "const text...." scheinbar gar nicht mehr ausführen, den der wurde gar im Code.

                  OliverIOO 1 Antwort Letzte Antwort
                  0
                  • A andibr

                    @OliverIO
                    Also du meinst den Block mit dem R232 Parser?

                    Ich habe noch folgendes ausprobiert:

                    if (msg.payload.includes("completed")){
                        msg.found = true;
                    //    return msg;
                    }   else {
                        msg.found = false;
                        node.error("String nicht komplett erhalten",msg);
                        return null;
                    }
                    
                    const text = msg.payload;
                    

                    So kommen zumindest die Zellendaten wieder durch, das mit dem "return msg" hat irgendwie nicht funktioniert, da konnte er den "const text...." scheinbar gar nicht mehr ausführen, den der wurde gar im Code.

                    OliverIOO Offline
                    OliverIOO Offline
                    OliverIO
                    schrieb zuletzt editiert von
                    #72

                    @andibr

                    ok ich sehe du willst es alleine probieren

                    Meine Adapter und Widgets
                    TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                    Links im Profil

                    1 Antwort Letzte Antwort
                    0
                    • A Offline
                      A Offline
                      andibr
                      schrieb zuletzt editiert von andibr
                      #73

                      nee, ich habe einfach probiert Schlussendlich möchte ich auch noch was lernen dabei.

                      Bildschirmfoto vom 2026-09-05 16-15-21.png

                      Aktuell kommt beim debug 1 etwas raus, aber beim debug 4 gar nichts mehr

                      OliverIOO 1 Antwort Letzte Antwort
                      0
                      • A andibr

                        nee, ich habe einfach probiert Schlussendlich möchte ich auch noch was lernen dabei.

                        Bildschirmfoto vom 2026-09-05 16-15-21.png

                        Aktuell kommt beim debug 1 etwas raus, aber beim debug 4 gar nichts mehr

                        OliverIOO Offline
                        OliverIOO Offline
                        OliverIO
                        schrieb zuletzt editiert von OliverIO
                        #74

                        @andibr

                        ich habe dir oben geschrieben warum das nicht richtig funktionieren wird.

                        verstehe, das ist schon aufwändig jemanden etwas zu erklären.
                        wenn man aber an der eigenen Lösung weiter rumdoktern will, alles gut.
                        dann wende ich mich anderen dingen zu.

                        wenn etwas unklar ist, dann stelle konstruktive fragen

                        Meine Adapter und Widgets
                        TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                        Links im Profil

                        1 Antwort Letzte Antwort
                        0
                        • A Offline
                          A Offline
                          andibr
                          schrieb zuletzt editiert von andibr
                          #75

                          ich verstehe jetzt gar nichts mehr?
                          Was habe ich jetzt falsch gemacht?

                          1 Antwort Letzte Antwort
                          0
                          • P Offline
                            P Offline
                            peterfido
                            schrieb zuletzt editiert von
                            #76

                            Ist wohl wie bei mir. Die Lösungsvorschläge werden nicht angenommen / ignoriert. Da sinkt die Motivation zu helfen. Mein Beispiel lief bei mir und hat sich wieder gefangen, nachdem ich da kurz die Verbindung unterbrochen hatte. Zeiten / Intervalle verlängern sowie die korrekte Schnittstelle eingeben und testen ist das, was ich erwartet hatte. Ohne die Batterien zu haben, können wir nur theoretisch helfen.

                            Gruß

                            Peterfido

                            1 Antwort Letzte Antwort
                            0
                            • A Offline
                              A Offline
                              andibr
                              schrieb zuletzt editiert von andibr
                              #77

                              scheinbar ist es hier nicht gewollt das man was lernen kann, ich weiss im Moment zwar nicht welchen Lösungsvorschlag ich nicht umgesetzt haben sollte (ausser den mit PC bei der Batterie). Woran es ja aber sichtlich nicht wirklich liegen kann.

                              PS: es ist für mich das erste mal, dass ich mit Node-Red da was versuche zu basteln, aber das muss ich scheinbar sehr schnell wieder vergessen, weil ich scheinbar etwas zu unlogisch oder gar zu dumm dazu bin. Na ja, dann ist es halt so.

                              Mit diesem parser function Node kommt einfach mal gar nichts mehr durch, nur noch solche Meldungen:

                              5.9.2026, 16:41:57node: debug 1msg.payload : Object
                              object
                              severity: "warning"
                              code: "BLOCK_DISCARDED"
                              message: "Unvollständiger oder beschädigter Batteriedatensatz wurde verworfen."
                              timestamp: "2026-09-05T14:41:57.110Z"
                              details: object
                              reason: "BLOCK_TIMEOUT"
                              receivedBatteryCount: 10
                              receivedBatteries: array[10]
                              invalidLines: array[0]
                              startedAt: "2026-09-05T14:38:17.544Z"
                              timeoutMs: 30000
                              

                              Ist zwar schön das ich jetzt weiss das der Datensatz nicht komplett ist, aber das weiss ich auch sonst. -> das ganze KI gedöns ist halt wirklich nur gedöns und keine echte Hilfe

                              1 Antwort Letzte Antwort
                              0

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

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

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

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


                              Support us

                              ioBroker
                              Community Adapters
                              Donate

                              201

                              Online

                              33.1k

                              Benutzende

                              83.7k

                              Themen

                              1.3m

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

                              • Du hast noch kein Konto? Registrieren

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