(Pylontech) BatteryView

Aus TippvomTibb
Zur Navigation springen Zur Suche springen

Allgemeines

Es ist doch recht schwierig an Battery heranzukommen. Einen offizielen Download-Ort habe ich bei Pylontech noch nicht gefunden.

Merkwuerdigerweise kommen bei einer Recherche Download-Links bei Facebook, in irgendwelchen Foren nach Anmeldung oder per nicht mehr funktionierenden Dropbox-Links. Grrrrrrrr! Was soll das?

Version

Ich habe folgende Versionen bisher downloaden koennen.

  • 3.0.24
  • 3.0.29
  • 3.0.37

Es gibt scheinbar auch schon eine 3.0.40. Den Download-Link dazu suche ich noch.

Wer nicht fuendig wird, kann unten einen Kommentar hinterlassen. Dann poste ich einen (temporaeren) Link.

3.0.37

Meine ersten Versuche habe ich mit der Version 3.0.29 unternommen. Siehe dazu (Pylontech) Consolen-Kommunikation

Bei der 3.0.37 konnte ich bisher keine offensichtlichen Aenderungen feststellen.

Die 3.0.37 hat auch den Fehler, dass bei einem Device-Wechsel in der Devicelist darunter die Angaben zur Adresse, Firmware etc. nicht mitwechseln. Manchmal funktioniert ein Links-Klick gefolgt von einem Rechts-Klick.

Von Claude Code habe ich mir mal eine gepatche EXE anfertigen lassen. Ein Test steht noch aus.

Claude Code Analyse

Eine .NET-Framework-4.5.2-WinForms-Anwendung (kompiliert 13.10.2022), die zum Pylontech-Batteriemanagement-Ökosystem gehört – Software zur Überwachung/Konfiguration von Pylontech-Lithium-Batteriespeichern (z. B. US2000/US3000, verbreitet in Solar-/Off-Grid-Anlagen). Erkennbar an Namespaces wie Pylon.Battery, PylonSerialPort, BatManageSystem, BMUControlItem etc.

Funktionsumfang laut Klassenstruktur: - Kommunikation mit dem BMS über RS232/RS485 seriell (PylonSerialPort) und TCP-Socket (Pylon_Socket) inkl. Baudrate-Konfiguration - Live-Monitoring von Zellspannungen/-strömen (Cells_Window, VoltCurrCurve_Window, AllPwrsStatus_Window) - Verlaufsdaten/Event-Logs, Export nach Excel via NPOI-Bibliothek - Diagramme via ZedGraph - Firmware-Updates per XMODEM-Protokoll (XmodemFrame, Update_Window, Versionschecks) - Passwortgeschützter Bereich für Steuerparameter (LoginDialog, AdminPassWord) – typisch, um Techniker-Zugriff auf BMS-Steuerbefehle abzusichern - Mehrsprachigkeit, dockbare Fenster (WeifenLuo.WinFormsUI.Docking)

Auffälligkeiten

- Unsigniert – keine Authenticode-Signatur vorhanden. - Abhängigkeiten fehlen im Ordner: referenziert u. a. ZNetAdv.dll (native DLL), WeifenLuo.WinFormsUI.Docking, NPOI, ZedGraph, LabelGradient, Microsoft.Office.Interop.Excel – ohne diese DLLs daneben läuft die EXE nicht. - Icon-Pfade in den Ressourcen verweisen auf easyicon.net/ImageMagick-Build-Artefakte – harmlose Entwickler-Reste, keine Auffälligkeit. - Keine Hinweise auf Schadcode: einziger nativer Import ist mscoree.dll!_CorExeMain (Standard für .NET-EXEs), keine verdächtigen URLs/IPs, keine Packer-typische Entropie (.text-Sektion ~6,07 – normal für IL-Code).

Fazit: Legitime Industrie-/Konfigurationssoftware für Pylontech-Batteriespeicher, kein Malware-Verdacht, aber unsigniert und mit fehlenden Begleit-DLLs – ohne die restlichen Programmdateien nicht lauffähig.

Speziell zum DeviceList-Fehler

Ich habe treeView1_NodeMouseClick (DeviceList_Window) und die zugehörigen Event-Handler in MainFrame, ScanData_Window und VoltCurrCurve_Window dekompiliert. Der Fehler ist eindeutig lokalisierbar.

Root Cause

Beim Linksklick auf ein Gerät in der Device-List feuert DeviceList_Window.treeView1_NodeMouseClick() das Event RaiseNodeSelectEvent. Darauf reagieren mehrere Fenster unterschiedlich konsequent:

- ScanData_Window und VoltCurrCurve_Window (das "Scan"-Fenster) holen sich beim Event korrekt die bereits gecachten Pro-Modul-Daten (arrPwrsTable[e.nNodeIndex], ArrVoltList[num] usw.) und zeichnen neu → deshalb schaltet die Scan-Anzeige sichtbar um. - MainFrame.M_deviceListWindow_RaiseNodeSelectEvent() (BatteryView/MainFrame.cs:422‑433) macht im Parallel-Betrieb (mehrere Akkus mit Adressen = m_bms.RealParallel == true) nur: tlslbl_CurrentDev.Text = "Battery module: " + (e.nNodeIndex + 1);

 Es wird weder UpdateDevTreeView() aufgerufen, noch ein MsgFlag.DeviceInfo-Kommando an das BMS geschickt.

Die Info-Anzeige unter der Liste (treeView2 in DeviceList_Window) wird ausschließlich über UpdateDevTreeView(bms) befüllt, und die wird nur an vier Stellen aufgerufen: Initial-Connect, Reconnect, nach abgeschlossenem Shift2Dev und im !RealParallel-Zweig des Klick-Handlers selbst (der bei euch wegen RealParallel == true gar nicht greift). Ein reiner Linksklick löst nie eine info N-Abfrage (OnGetDeviceInfo in MsgBackgroundWorker.cs:1141) für das neu gewählte Gerät aus – die Werte in PwrCellInfo.ListDevInfoDataFieldInfo stammen daher immer noch vom zuletzt tatsächlich abgefragten Gerät.

Kurz: Auswahl-Event wird nur an die Fenster mit eigenem Pro-Modul-Cache "weitergereicht", die Geräte-Info-Baumansicht hat aber keinen solchen Cache und bekommt nie einen Refresh-Befehl.

Workaround (ohne Codeänderung)

Nach dem Linksklick zusätzlich Rechtsklick auf dieselbe Batterie → Kontextmenü "an Gerät wechseln" (Shift to device) wählen. Das löst tatsächlich MsgFlag.Shift2Dev aus, welches über PostConnectedComplete() UpdateDevTreeView() aufruft – die Info-Liste aktualisiert sich dann korrekt auf das gewählte Gerät.

Nachteil: Das ist ein "harter" Wechsel (Login/Info-Kommandos werden über den Bus neu gesendet, IsShifted wird zurückgesetzt), dauert spürbar länger und ist zum schnellen Durchklicken mehrerer Module unpraktisch. Der Menüpunkt ist zudem nur aktiv, wenn das gewählte Gerät ≠ LastDwellDevice ist.

Alternative: Trennen → neu Verbinden erzwingt ebenfalls einen kompletten Refresh (inkl. Infoliste), setzt aber die ganze Sitzung/den Scan zurück – nur sinnvoll, wenn ihr sowieso neu verbindet.

Echter Fix (Codepatch)

In MainFrame.M_deviceListWindow_RaiseNodeSelectEvent() müsste im RealParallel-Zweig zusätzlich ein Info-Refresh für das neu gewählte Gerät ausgelöst werden, z. B.:

private void M_deviceListWindow_RaiseNodeSelectEvent(object sender, NodeSelectedEventArgs e) {

   if (m_bms.RealParallel)
   {
       ((Control)this).Invoke((Delegate)(MethodInvoker)delegate
       {
           tlslbl_CurrentDev.Text = MultiLanguages.GetItemName("battery_module") + ": " + (e.nNodeIndex + 1);
       });
       m_bkgWorker.SendMessage(
           m_msg.CreateMessage(MsgFlag.DeviceInfo, m_bms, MultiLanguages.GetItemName("get_device_information")),
           burgency: false);
   }

} Das MsgFlag.DeviceInfo-Handling existiert bereits vollständig (sendet "info " + (DeviceIndex+1) und ruft danach in MainFrame UpdateDevTreeView() auf) – es wird nur nie vom Klick-Handler angestoßen. Das wäre der minimalinvasive Fix, sofern ihr Zugriff auf den Quellcode/eine neue Build-Pipeline habt. Ohne Quellcode bliebe nur der Shift2Dev-Workaround oder ein IL-Patch der EXE.

✻ Worked for 5m 31s · done 10:48

❯ wie funktioniert ein il-patch

Grundprinzip

Eine .NET-EXE (wie BatteryView.exe) enthält keinen nativen x86-Code, sondern CIL/MSIL (Common Intermediate Language) – eine stapelbasierte Zwischensprache, die erst zur Laufzeit vom JIT in Maschinencode übersetzt wird. Die Datei ist im Kern:

- Metadaten-Tabellen (TypeDef, MethodDef, Field, MemberRef …) – das haben wir vorhin schon mit dnfile ausgelesen - pro Methode ein IL-Codeblock: eine Folge von Opcodes wie ldarg.0, callvirt, brtrue.s, stfld usw.

Ein "IL-Patch" heißt: man ändert diesen Bytecode direkt in der kompilierten Datei, ohne das Originalprojekt zu haben oder neu zu kompilieren.

Die drei üblichen Wege

1. Text-Roundtrip (ildasm → editieren → ilasm) Disassemblieren der ganzen Assembly in eine .il-Textdatei, dort die Opcodes von Hand ändern, dann mit ilasm wieder zu einer EXE zusammenbauen. Sehr transparent, aber fehleranfällig: man muss Stack-Balance, Sprungziele (Labels) und Metadaten-Tokens von Hand konsistent halten. Für .NET Framework/Windows der klassische Weg; auf Linux/.NET-Core-Toolchain ist das inzwischen unbequemer (ildasm gibt's dort kaum noch offiziell).

2. Programmatisch mit einer Bibliothek (Mono.Cecil / dnlib) Ein kleines C#-Script lädt die Assembly, sucht sich per Name die Methode (MainFrame.M_deviceListWindow_RaiseNodeSelectEvent), und fügt in die MethodBody.Instructions-Liste neue Instruction-Objekte ein (z. B. Call, Ldfld, Newobj) – man baut die IL-Instruktionen also selbst per API zusammen, aber ohne rohen Text zu parsen. Danach module.Write() → neue EXE. Das ist der robusteste Weg für so einen chirurgischen Fix, weil man die Referenzen (Methoden, Felder) sauber aus der bestehenden Assembly importiert statt Text zu tippen.

3. GUI-Tool wie dnSpy (Windows) dnSpy dekompiliert die Methode zu lesbarem C#, man editiert diesen C#-Text direkt im Tool ("Edit Method (C#)"), und dnSpy kompiliert das intern per Roslyn zurück zu IL und schreibt es in die Methode. Das ist mit Abstand am einfachsten, weil man in C# statt in Opcodes denkt – erfordert aber Windows (bzw. Wine) und eine GUI, hier auf der Linux-Session nicht verfügbar.

Für unseren konkreten Fall

Der Fix ist ein Idealkandidat für Variante 2 (Cecil), weil er klein und additiv ist – wir fügen in M_deviceListWindow_RaiseNodeSelectEvent nur einen zusätzlichen Aufruf einer bereits vorhandenen Methode hinzu (m_bkgWorker.SendMessage(m_msg.CreateMessage(MsgFlag.DeviceInfo, ...))), keine neuen Typen/Felder, keine Signaturänderung. Ich habe hier auf der Session bereits .NET SDK + Decompiler installiert – ich könnte ein Cecil-Script schreiben, das:

1. eine Kopie von BatteryView.exe patcht (Original bleibt unangetastet), 2. die IL-Instruktionen für den zusätzlichen SendMessage-Aufruf in die Methode einfügt, 3. die gepatchte EXE speichert, die ihr dann testen könnt.

Der Bug existiert unverändert auch in der neueren BatteryView_V3.0.37.exe (Build 2024-04-12) – ist also bis heute nicht vom Hersteller behoben. Da das vermutlich die Version ist, die du tatsächlich nutzt, patche ich die gleich mit.

 Made 1 scratchpad edit +3 -3, read 1 file, ran 2 shell commands

Auch für V3.0.37 verifiziert. Vollständige Konsistenzprüfung (nur diese eine Methode geändert) und dann Kopie ins Projektverzeichnis.

 Ran 1 shell command

Nur diese eine Datei/Methode unterscheidet sich – wie beabsichtigt.

 Ran 1 shell command

Fertig

Beide EXEs sind gepatcht und liegen neben den Originalen:

- BatteryView.patched.exe (Basis: die ursprüngliche 2022er BatteryView.exe) - BatteryView_V3.0.37.patched.exe (Basis: die neuere BatteryView_V3.0.37.exe, die während der Session dazukam – der Bug steckt dort identisch drin, ist also bis heute vom Hersteller nicht behoben)

Was der Patch macht: In MainFrame.M_deviceListWindow_RaiseNodeSelectEvent wird jetzt bei jedem Linksklick auf eine Batterie (im RealParallel-Zweig) zusätzlich

PylonMessage msg = m_msg.CreateMessage(MsgFlag.DeviceInfo, m_bms, MultiLanguages.GetItemName("get_device_information")); m_bkgWorker.SendMessage(msg, burgency: false);

ausgelöst – exakt der Aufruf, der sonst nur bei Connect/Reconnect/Shift2Dev passiert und über OnGetDeviceInfo → "info N" → UpdateDevTreeView() die Infoanzeige unter der Device-List befüllt.

Wie ich es umgesetzt habe (Mono.Cecil, nicht Text-Roundtrip): 1. .NET SDK + ilspycmd lokal im Scratch-Verzeichnis installiert (kein Eingriff ins System). 2. Mit dnfile/ilspycmd die exakten IL-Instruktionen der Zielmethode und eines bereits vorhandenen, strukturell identischen Aufrufmusters (tlsmi_shift2dev_Click) verglichen, um Opcode-Reihenfolge (callvirt vs. call), Feld-/Methodensignaturen und den MsgFlag.DeviceInfo-Wert (14) zu verifizieren. 3. Ein kleines C#-Script mit Mono.Cecil geschrieben, das die Methode lädt, den Anker Control.Invoke(...) → pop findet und dort die neuen Instruktionen einfügt (neue lokale Variable vom Typ PylonMessage inklusive). 4. Verifikation: gepatchte Assembly komplett neu dekompiliert und mit dem Original verglichen – im ganzen Projekt unterscheiden sich nur die zwei neuen Zeilen in genau dieser einen Methode, sonst nichts.

Bitte vor dem Einsatz testen (echte Hardware, mehrere Pylontech-Module am Bus): Klick auf ein Modul in der Device-List sollte jetzt sofort ein info N-Kommando auslösen und die Infoanzeige darunter aktualisieren. Die Originale habe ich nicht verändert, falls ihr zurück wollt.