In meinem Haushalt hängt noch eine WLAN-RGB-Lampe – gekauft irgendwann um 2016, welche über eine Android App namens „InLit WIFI“ gesteuert werden kann. Die Lampe selbst tut bis heute klaglos ihren Dienst. Nur die App dazu ist tot: Sie lässt sich auf aktuellen Android-Versionen schlicht nicht mehr installieren bzw. ausführen. Anstatt die Lampe zu entsorgen, wollte ich sie zum Beispiel in Home Assistant einbinden. Das Problem: Es gibt kein offenes Protokoll, keine Dokumentation, nichts – nur eine alte APK und ein Stück Hardware, das brav im Heimnetz auf Befehle wartet. Also habe ich mir das Wochenende genommen und mich selbst rangesetzt.


Schritt 1: Das WLAN-Modul identifizieren

Zuerst hab ich die Lampe aufgeschraubt und mir das WLAN-Modul angeschaut: „HF-LPB100″ stand drauf, Softwareversion V1.0.04b. Ein kurzer Griff zur Suchmaschine meiner Wahl hat gezeigt: Das ist ein serielles WLAN-Bridge-Modul von Hi-Flying, wie es 2013/2014 in unzähligen No-Name-Smart-Home-Geräten verbaut wurde. Kein ESP8266, also auch kein Tasmota oder ESPHome zum Neuflashen – das Modul leitet ankommende TCP/UDP-Bytes einfach roh an die serielle Schnittstelle weiter, wo ein eigener Mikrocontroller in der Lampe sie interpretiert. Die eigentliche „Sprache“ der Lampe steckt also nicht im Modul selbst, sondern tief im Code der alten App.


Schritt 2: Die APK auseinandernehmen

Die APK lässt sich mit einem einfachen unzip entpacken, aber die eigentliche Logik steckt kompiliert in der classes.dex. Dafür muss man zum Glück kein eigenes Tool schreiben: Mit jadx (bzw. jadx-gui) lässt sich eine APK direkt zurück in einigermaßen lesbaren Java-Quellcode übersetzen. Ich hab mich für jadx entschieden und mich Klasse für Klasse durchgeklickt. Mühsam, weil der Code stark obfuskiert war (Klassen- und Methodennamen bestehen nur aus einzelnen Buchstaben), aber machbar. Aus dem Manifest ging schon mal hervor, dass die App rein über WLAN kommuniziert – keine Bluetooth-Permission drin. Im Code selbst fanden sich zwei Kernbausteine:

  • Eine UDP-Discovery auf Port 988: Die App schickt den ASCII-Text HLK als Broadcast raus, das Gerät antwortet mit einem ADM(...)-formatierten Text, aus dem sich Hardware- und Software-ID auslesen lassen.
  • Eine TCP-Verbindung zur Lampen-IP, über die die eigentlichen Steuerbefehle als rohe Bytes geschickt werden.

Schritt 3: Der erste Verbindungsversuch

Aus dem disassemblierten Code sah es so aus, als würde die App standardmäßig Port 8080 für die TCP-Steuerverbindung nutzen. Also fix ein kleines PHP-Testskript gebaut, das rohe Bytes an IP:8080 schickt – und prompt eine erste Verbindung über das WLAN-Modul zum Mikrocontroller aufgebaut.


Schritt 4: Das Paketformat rekonstruieren

Immerhin ließ sich nun schonmal eine Verbindung aufbauen. Nur: Erste Testpakete mit Rot auf Vollausschlag haben die Lampe nicht zum Leuchten gebracht. Des Rätsels Lösung fand sich beim genaueren Lesen der Bulb-Klasse im disassemblierten Code – praktischerweise war da eine Debug-Ausgabe drin, die die internen Feldnamen direkt verraten hat. Daraus ergab sich ein kompaktes 8-Byte-Paket:

Byte 0: 0x73   ('s')  Start
Byte 1: Blau         0–255
Byte 2: Grün          0–255
Byte 3: Rot           0–255
Byte 4: Weiß / Bri    0–255
Byte 5: Mod           Modus
Byte 6: Ctl           Steuer-/Schaltbyte
Byte 7: 0x65   ('e')  Ende

Mein erster Testbefehl hatte schlicht das Weiß/Helligkeits-Byte auf 0 stehen – die Lampe hat brav „schwarz“ angezeigt, technisch also alles richtig gemacht, nur eben nichts zu sehen.


Schritt 5: Ein/Aus wollte partout nicht mitspielen

Die Farbe hat danach zuverlässig funktioniert, aber das Schaltbyte (Ctl) blieb hartnäckig. Aus dem Code allein ließ sich nur ableiten, dass ein einzelnes Bit für An/Aus zuständig ist – in der Praxis hat ein einzelnes Bit-Paket aber nichts bewirkt. Also hab ich einen Netzwerk-Mitschnitt gemacht (mit PCAPdroid direkt auf dem Smartphone) und die Original-App währenddessen benutzt. Und siehe da: Die App schickt beim Umschalten nicht ein einziges Paket, sondern zwei kurz hintereinander – ein „vorbereitendes“ und ein „finales“, mit rund 100 ms Abstand:

Aus: 0x82 → 0x92
An:  0x83 → 0x93

Das war der fehlende Puzzlestein. Sobald mein Testskript beide Pakete in der richtigen Reihenfolge geschickt hat, hat die Lampe zuverlässig ein- und ausgeschaltet.


Bonus-Fund: der Sleep-Timer

Beim Stöbern in der App ist mir noch die „Sleep“-Funktion aufgefallen (Lampe dimmt sich in X Minuten selbst aus). Auch die per Mitschnitt bestätigt – eigenes, simples Format:

0x73 0x73 <Minuten/5> 0x00 0x00 0x00 0xA0 0x65

Fünf Byte-Header, ein Byte für die Minutenstufe in 5er-Schritten, fertig.


Vom Rätsel zum Werkzeug

Aus alledem ist inzwischen ein kleines PHP-Testwerkzeug geworden: ein Farbrad direkt im Browser, ein Helligkeitsregler, ein eigener Weiß-Modus und die Sleep-Timer-Buttons – alles sendet live per AJAX die passenden Bytes an die Lampen-IP, ganz ohne „Senden“-Knopf. Ich hab das Layout so gebaut, dass es sich als Progressive Web App auf dem Homescreen installieren lässt und sich IP sowie Port dauerhaft im Browser merkt.


Fazit und nächste Schritte

Mit Discovery-, Steuer- und Sleep-Protokoll komplett rekonstruiert, steht der eigentlichen Home-Assistant-Anbindung nichts mehr im Weg: Ein kleiner MQTT-Bridge-Dienst, der auf einem Topic lauscht und die passenden Bytes an die Lampe schickt, macht daraus ganz regulär eine light-Entity in Home Assistant – inklusive Farbe, Helligkeit und Ein/Aus, ganz ohne die alte App.

Die Lampe hängt seit über zehn Jahren an der Decke. Manchmal muss man eben nicht neu kaufen, sondern nur genau genug hinschauen und ein bisschen hartnäckig sein, um herauszufinden, was die alte Hardware eigentlich die ganze Zeit schon konnte.

Falls ihr selbst eine alte „verwaiste“ Smart-Home-Lampe rumliegen habt und Fragen zum Vorgehen habt – gerne in die Kommentare damit!