MultivanPi Teil 7: Der steinige Weg zum Vevor Protokoll (Reverse-Engineering)

Der MultivanPi wächst unaufhaltsam weiter. Nachdem die grundlegende Energie- und Klimaüberwachung im Dashboard reibungslos läuft, stand nun der nächste Meilenstein an, der mich fast eine ganze Woche lang täglich mehrere Stunden Arbeit gekostet hat: Die direkte Integration der Kompressorkühlbox. Wer ein Modell von Vevor oder Alpicool besitzt, kennt die Steuerung per Smartphone-App. Doch das ist mir zu umständlich, ich möchte  die Kühlbox vom Fahrer-/Beifahrersitz aus mit meinem MultivanPi steuern.
Der Hersteller bietet keine offene API an. Es folgte eine technische Odyssee, die ich hier detailliert für Dich dokumentiere, falls Du ein ähnliches Projekt planst.

Die ersten Versuche: Blindflug im Python-Dschungel

Die naive Herangehensweise an solche Projekte ist bei mir meistens von großem Optimismus geprägt. Die Idee war simpel: Ich scanne die Bluetooth-Schnittstellen mit dem Raspberry, finde die richtigen UUIDs für das Senden (TX) und Empfangen (RX) und schicke einfach ein paar Steuerbefehle. In den ersten Tagen entstanden unzählige kleine Python-Scripte. Ich  schrieb Code, um die Services auszulesen, Code, um blinde Hex-Werte an die Box zu feuern, und Code, der verzweifelt auf irgendeine Antwort der Kühlbox wartete.

22 unterschiedliche Python-Scripte sind dabei in der letzten Woche entstanden, wie oft ich die einzelnen Scripte geändert habe, habe ich nicht mitgezählt.
Ich wollte die Funktionalität unbedingt isoliert funktional haben, bevor ich die Kühlbox in die MultivanPi-Software integriere, um dort nicht unnötig Fehler oder Abhängigkeiten einzubauen.

Das Ergebnis? Ernüchternd. Die Kühlbox blieb stumm. Manchmal entlockte ihr ein gesendeter String ein kurzes Piepen, aber die Temperatur oder der Modus änderten sich nicht. Ohne die genaue Grammatik der Kühlbox zu kennen, war es, als würde ich versuchen, ein Schloss mit tausenden falschen Schlüsseln im Dunkeln zu öffnen. Es wurde schnell klar: Raten bringt mich hier nicht weiter. Ich muss der originalen App beim Sprechen zuhören.

Reverse-Engineering mit dem Google Pixel 8

Um das Protokoll wirklich zu verstehen, musste ich schwerere Geschütze auffahren. Das Mittel der Wahl war mein Google Pixel 8 Pro. In den versteckten Entwickleroptionen von Android lässt sich das sogenannte „Bluetooth HCI Snoop Log“ aktivieren. Diese Funktion zeichnet jedes Bluetooth-Paket auf, das das Smartphone sendet oder empfängt.

Der Ablauf war nun systematisch: Aufzeichnung starten, die originale Vevor-App öffnen, die Kühlbox einschalten, die Temperatur exakt um ein Grad erhöhen und dann wieder verringern, den Modus von ECO auf MAX wechseln, den Batterieschutz von L über M auf H wechseln, Die Kühlbox ausschalten und die Aufzeichnung wieder beenden. Die entstandene Log-Datei habe ich  anschließend auf den PC übertragen und in der Netzwerk-Analysesoftware Wireshark analysiert.

Die entscheidenden BLE-Schnittstellen der Kühlbox:
Sende-Charakteristik (TX): 00001237-0000-1000-8000-00805f9b34fb
Empfangs-Charakteristik (RX): 00001236-0000-1000-8000-00805f9b34fb

Das Protokoll im Detail: So spricht die Kühlbox

Die Analyse in Wireshark offenbarte, warum meine ersten Python-Scripte scheitern mussten. Die Kühlbox sendet ihren Status nicht von alleine. Sie ist extrem passiv und muss durch einen permanenten „Herzschlag“ am Leben gehalten werden.

1. Der Wake-Up Ping (Get Status)

Damit die Box überhaupt Daten liefert, muss ihr regelmäßig die Frage nach dem Befinden gestellt werden. Dieser kurze Befehl ist immer gleich und lautet im Hexadezimalsystem: FE FE 03 01 02 00. Erst wenn die Box diesen Ping auf der TX-UUID empfängt, antwortet sie mit einem langen String auf der RX-UUID.

2. Das Status-Paket lesen

Die Antwort der Box beginnt immer mit dem Start-Header FE FE. Der für mich spannende Teil spielt sich in den Bytes ab der vierten Position ab (ich beginne bei 0 zu zählen):

  • Byte 4 (Lock): 0x00 = Display entsperrt, 0x01 = Gesperrt (Kindersicherung)
  • Byte 5 (Power): 0x00 = Aus, 0x01 = An
  • Byte 6 (Modus): 0x00 = MAX, 0x01 = ECO
  • Byte 7 (Batterieschutz): 0x00 = Low, 0x01 = Medium, 0x02 = High
  • Byte 8 (Soll-Temperatur): Eingestellter Wert in Grad Celsius.
  • Byte 18 (Ist-Temperatur): Aktuelle Temperatur im Innenraum.
  • Byte 20 & 21 (Spannung): Die aktuelle Bordnetzspannung (z. B. 13.4V).

3. Die Checksummen- und Echo-Falle

Wer nun denkt, er schickt einfach Byte 6 als 0x01, um auf ECO zu schalten, wird enttäuscht. Die Box verlangt bei Änderungen immer den kompletten 20-Byte-String inklusive aller aktuellen Ist-Zustände. Und hier hat der Hersteller eine mathematische Hürde eingebaut:

Die Checksummen-Falle: Neun von zehn Bluetooth-Geräten berechnen ihre Prüfsumme am Ende des Pakets mit einer XOR-Verknüpfung. Meine ersten Python-Scripte taten das auch. Die Kühlbox nutzt jedoch eine schlichte Addition. Die korrekte Formel für das letzte Byte lautet: (Summe aller Payload-Bytes - 8) & 0xFF. Ein Bit daneben, und der Befehl landet völlig stumm im Mülleimer.

Die Echo-Falle (Piepen ohne Wirkung): Bei der Programmierung des Web-Dashboards stieß ich auf ein faszinierendes Phänomen: Beim Tippen auf dem MultivanPi piepte die Kühlbox zwar zur Bestätigung, änderte aber weder Temperatur noch Modus. Die Ursache lag in der Logik meines Skripts: Wenn (durch einen Tippfehler) im Code (z.B. falsche API-Variablen) die Ist-Werte im Hintergrund nicht überschrieben werden, sendet das Skript exakt den aktuellen Zustand wieder an die Box zurück. Die Box empfängt das Paket, verifiziert die Checksumme mit einem freudigen Piepen, stellt dann aber fest: „Moment, ich bin ja schon auf MAX und 5 Grad“ und unternimmt nichts. Das Piepen ohne Änderung ist also der ultimative Beweis, dass der eigene Code keine neuen Werte ins Payload-Paket geschrieben hat.
Die vielen, vielen Scripte und Versuche haben ihren Tribut gefordert 😉

Das Vevor / Alpicool Protokoll in der Übersicht

1. Die originalen Hex-Strings aus dem Bluetooth-Log
Bevor ich das Protokoll in seine Einzelteile zerlege, zeige ich hier die rohen 20-Byte-Strings, die ich beim Reverse-Engineering direkt vom Smartphone mitgeschnitten habe. Durch den Vergleich dieser Zeilen lässt sich das Protokoll Stück für Stück entschlüsseln:

Aktion / Funktion Aufgezeichneter Hex-String (20 Bytes)
Unlock (Entsperren) fefe110200010100050cec0200000000fd00040d
Lock (Sperren) fefe110201010100050cec0200000000fd00040e
Power ON fefe110200010100070cec0200000000fd00040f
Power OFF fefe110201000100070cec0200000000fd00040f
MAX Modus fefe110201010000070cec0200000000fd00040f
ECO Modus fefe110201010100070cec0200000000fd000410
Battery Protection L fefe110201010100070cec0200000000fd000410
Battery Protection M fefe110201010101070cec0200000000fd000411
Battery Protection H fefe110201010102070cec0200000000fd000412

2. Das RX-Statuspaket (Empfang von der Kühlbox)
Die Box antwortet auf einen Ping meist mit einem 24-Byte langen String. Hier sind die wichtigsten Positionen, um den Status aus diesen Daten auszulesen (der Byte-Index beginnt bei 0):

Byte-Index Bedeutung Hex-Werte / Interpretation
0 & 1 Start-Header FE FE
4 Kindersicherung 0x00 = Entsperrt, 0x01 = Gesperrt
5 Power (Ein/Aus) 0x00 = Aus, 0x01 = Ein
6 Modus (Eco/Max) 0x00 = MAX, 0x01 = ECO
7 Batterieschutz 0x00 = Low, 0x01 = Medium, 0x02 = High
8 Soll-Temperatur Ganzzahl in °C (bei Minusgraden: Wert – 256)
18 Ist-Temperatur Aktuelle Innentemperatur in °C
20 & 21 Spannung Zusammengesetzt (z.B. Byte 20 = 12, Byte 21 = 5 ➔ 12.5V)

3. Die TX-Steuerbefehle (Senden an die Kühlbox)
Es gibt zwei Möglichkeiten, Werte an die Box zu übermitteln: Die klassische Spiegelung des kompletten 20-Byte-Payloads (wie oben im Log zu sehen) oder kompakte, gezielte 8-Byte-Befehle.

Variante A: Der 20-Byte-String (Kompletter Payload)
[FE, FE, 11, 02, Lock, Power, Mode, Battery, TempSoll, 0c, ec, 02, 00, 00, 00, 00, fd, 00, 04, Checksum]
Wichtig: Die Checksumme (letztes Byte) berechnet sich durch die Addition aller Payload-Bytes (Byte 2 bis 18) minus 8.

Variante B: Die kompakten 8-Byte-Befehle
Aufbau: [FE, FE, 05, 01, Funktion, Wert, 00, Checksum]
Wichtig: Die Checksumme berechnet sich hier durch die simple Addition von (05 + 01 + Funktion + Wert + 00).

Steuerung Funktions-Byte Werte-Byte
Power (Ein/Aus) 0x01 0x00 (Aus), 0x01 (Ein)
Modus (Eco/Max) 0x02 0x00 (Max), 0x01 (Eco)
Batterieschutz 0x03 0x00 (Low), 0x01 (Med), 0x02 (High)
Kindersicherung 0x04 0x00 (Entsperrt), 0x01 (Gesperrt)
Soll-Temperatur 0x05 Gewünschte Temperatur als Hex-Wert

Der nächste Gegner: Die MultivanPi Systemintegration

Nach Tagen des Testens lief mein isoliertes Python-Script endlich fehlerfrei. Ich konnte die Box ein- und ausschalten und die Temperatur ändern. Zeit für die Siegesfeier? Von wegen. Die nächste Hürde wartete bei der Integration in das MultivanPi-Projekt.

Das MultivanPi-Backend ist ein asynchrones Multifunktionstool, das gleichzeitig Victron-Geräte scannt, Sensoren ausliest und einen Webserver betreibt. Als ich die Kühlbox-Routinen dort einfügte, brach das Chaos aus. Es kam zu unschönen Wettlaufsituationen. Das Skript versuchte, den Modus zu ändern, bevor der Hintergrund-Ping den echten aktuellen Status der Box geladen hatte. Das Resultat: Das Dashboard zeigte „MAX“, die Box war physisch auf „ECO“, und ein Klick im Dashboard sendete aus Versehen den Befehl „Bleib auf MAX“. Das ständige, ergebnislose Piepen der Box trieb mich fast in den Wahnsinn.

Ich habe deshalb eine asynchrone Warteschlange (asyncio.Queue) gebaut. Eingehende API-Befehle vom Web-Dashboard werden nun ordentlich aufgereiht. Nach jedem gesendeten Befehl legt das Skript zudem eine künstliche Pause (ENtprellen) von 2,5 Sekunden ein, in der es eingehende Statusmeldungen der Box ignoriert. Warum? Weil der physische Kompressor eine gewisse Trägheit besitzt. Er braucht ein bis zwei Sekunden, um dem Bluetooth-Modul intern zurückzumelden: „Verstanden, ich drossle jetzt die Drehzahl für den ECO-Modus.“

Zusätzliche Funktion zum Schutz der Aufbaubatterie: Watchdog

Der Watchdog ist ein eigener Task namens fridge_watchdog_task. Dieser wird in der main()-Funktion zusammen mit der Kühlbox-Routine gestartet.

  • Er wacht alle 180 Sekunden (3 Minuten) auf.
  • Er holt sich den auto_off_soc Wert aus der config.json.
  • Er prüft, ob der SmartShunt überhaupt live Daten liefert, damit er nicht bei einem kurzen Bluetooth-Abbruch blind abschaltet.
  • Wenn der aktuelle Batteriestand kleiner oder gleich dem eingestellten Wert ist UND die Kühlbox gerade läuft, schiebt er den Power-Off-Befehl (build_fridge_cmd(0x01, 0x00)) in die asynchrone Befehls-Warteschlange.

Innerhalb von maximal 3 Minuten sollte die Box piepen, automatisch herunterfahren und am Dashboard das Klimamodul rot umranden.

Ziel erreicht!

Heute, fast eine Woche nach der ersten Implementierung des Status-Auslesens der Kühlbox, laufen die Prozesse perfekt ineinander. Das Frontend zeigt interaktive Icons für die Modis, die Ist-Temperatur und die Versorgungsspannung fließen in Echtzeit auf den MultivanPi. Es war ein hartes Stück Arbeit, aber das Gefühl, ein vollständig geschlossenes, selbst programmiertes System ohne fremde Cloud-Abhängigkeit zu betreiben, ist jede investierte Stunde wert.