paint-brush
Reverse Engineer von LED-Leuchten mit Python, um Ihren Computermonitor in einen Umgebungsmonitor umzuwandelnvon@reaminated
2,409 Lesungen
2,409 Lesungen

Reverse Engineer von LED-Leuchten mit Python, um Ihren Computermonitor in einen Umgebungsmonitor umzuwandeln

von Reaminated12m2023/03/31
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

Wandeln Sie Ihren normalen Monitor in einen Umgebungsmonitor um, dessen Beleuchtung sich an die aktuell auf dem Bildschirm angezeigten Farben anpasst. In diesem Tutorial erfahren Sie, wie Sie Ihr Film- und Spielerlebnis mithilfe von Python- und RGB-Lichtern verbessern können.
featured image - Reverse Engineer von LED-Leuchten mit Python, um Ihren Computermonitor in einen Umgebungsmonitor umzuwandeln
Reaminated HackerNoon profile picture
0-item

Einführung

Für diejenigen, die mit Ambient-TVs nicht vertraut sind: Es ist eine Möglichkeit, den Sprung vom Rand des Fernsehbildschirms und seiner unmittelbaren Umgebung abzuschwächen, um ein noch intensiveres Erlebnis zu ermöglichen. Ich hatte ein paar LED-Leuchten herumliegen und beschloss, zu prüfen, ob es möglich wäre, die Leuchten per Code zu steuern und so meinen Computerbildschirm in einen Umgebungsmonitor zu verwandeln. Obwohl ich es für meinen Monitor verwenden wollte, kann es überall und mit allen Farben verwendet werden, die Sie senden können, einschließlich anderer Funktionen Ihrer Lichter, wie z. B. Audioreaktionen oder zufällige Muster. Ich wollte diesen Beitrag schon seit einiger Zeit schreiben, da ich ihn auf einem früheren Monitor verwendet habe, aber ich kam nie dazu, ihn zu meinem neuen Monitor hinzuzufügen, also habe ich ihn im Laufe der Zeit für jeden dokumentiert, der ihn finden könnte es nützlich. Also nichts wie los! (Bitte beachten Sie, dass es sich bei LED-Leuchten wahrscheinlich um Bluetooth Low Energy (BLE) handelt, sodass Ihr Computer BLE unterstützen muss, um mit ihnen interagieren zu können.) Der vollständige Code ist auf GitHub .

Hohe Stufen

  • Erfahren Sie, welche Befehle der Bluetooth-Empfänger der LED-Leuchte akzeptiert
  • Senden Sie Befehle über das Bluetooth meines Computers an die LED-Leuchten
  • Ermitteln Sie die dominante Farbe des aktuellen Bildschirms
  • Senden Sie die dominierende Farbe an die LED-Leuchten

Voraussetzungen

  • Bluetooth-unterstützte RGB-LED-Leuchten und zugehörige App (ich verwende Android, iOS würde wahrscheinlich einen alternativen Ansatz als den hier beschriebenen erfordern, aber es sollte möglich sein, Wireshark direkt zur Überwachung des Bluetooth-Verkehrs zu verwenden). Ich habe diese Lichter an der Rückseite meines Monitors angebracht
  • Wireshark
  • SDK-Tools von Android (insbesondere adb.exe)
  • Entwicklertools (ich verwende Python 3.10, obwohl alle 3.x-Versionen funktionieren sollten, aber die Prinzipien sollten unabhängig von der von Ihnen bevorzugten Sprache dieselben sein)
  • Ein Gerät zum Senden von BLE-Befehlen (z. B. ein Laptop, der BLE unterstützt)


Bluetooth-Daten abrufen

Der erste Schritt besteht darin, sicherzustellen, dass die mit den Lichtern gelieferte App wie erwartet funktioniert. Dies kann einfach getestet werden, indem Sie die Original-App der Leuchte ausführen und sicherstellen, dass die Leuchten je nach den Ein-/Aus-/Beleuchtungstasten, die Sie in Ihrer App drücken, entsprechend reagieren. Wir tun dies, weil wir in Kürze drücken und die spezifischen Codes erkennen werden, die an den Bluetooth-Empfänger der Lichter gesendet werden.


Es gibt zwei Ansätze, die ich wählen könnte. Eine bestand darin, die JAR-Datei der App zu dekompilieren und die gesendeten Codes zu finden, aber ich wollte mehr über das Bluetooth-Protokoll erfahren, also entschied ich mich dafür, alle Bluetooth-Aktivitäten auf meinem Android zu protokollieren und sie von dort zu extrahieren. Hier ist wie:


  1. Aktivieren Sie die Entwickleroptionen auf Ihrem Android-Gerät.


  2. Aktivieren Sie das Bluetooth-HCI-Snoop-Protokoll (HCI steht für Host-Controller-Schnittstelle). Sie finden diese Option unter „Einstellungen“ > „System“ > „Entwickler“ oder suchen Sie in den Einstellungen danach, wie im Bild unten dargestellt.


Aktivieren von HCI-Snoop-Protokollen


  1. Wir müssen nun bestimmte Aktionen ausführen, damit wir erkennen können, was jede Aktion an den Bluetooth-Empfänger der Leuchte sendet. Ich werde die Reihenfolge einfach auf Ein/Rot/Grün/Blau/Aus beschränken, aber wenn Ihre Lichter andere Funktionen unterstützen, können Sie auch damit herumspielen.


  2. Führen Sie die App aus und drücken Sie Ein, Rot, Grün, Blau und Aus. Es kann auch nützlich sein, die ungefähre Zeit im Auge zu behalten, um die Filterung zu erleichtern, wenn auf Ihrem Gerät viel Bluetooth-Aktivität herrscht.


Aufzeichnen von zu sendenden Befehlen


  1. Schalten Sie Bluetooth aus, damit wir keinen Lärm mehr hören. In den folgenden Schritten analysieren wir die Bluetooth-Befehle und können, da wir die Reihenfolge kennen, in der wir gedrückt haben, herausfinden, welche Werte welchem Tastendruck entsprechen.


  2. Wir müssen jetzt auf die Bluetooth-Protokolle auf dem Telefon zugreifen. Es gibt mehrere Möglichkeiten, dies zu tun, aber ich werde einen Fehlerbericht erstellen und exportieren. Aktivieren Sie dazu das USB-Debugging in den Einstellungen des Telefons, verbinden Sie das Telefon mit dem Computer und verwenden Sie das Befehlszeilentool adb.exe.


     adb bugreport led_bluetooth_report


  3. Dadurch wird im lokalen Verzeichnis Ihres Computers eine ZIP-Datei mit dem Dateinamen „ led_bluetooth_report.zip “ generiert. Sie können bei Bedarf einen Pfad angeben (z. B. „C:\MyPath\led_bluetooth_report“).


  4. In dieser ZIP-Datei befinden sich die Protokolle, die wir benötigen. Dies kann von Gerät zu Gerät unterschiedlich sein (bitte kommentieren Sie, wenn Sie es an anderer Stelle auf Ihrem Gerät gefunden haben). Auf meinem Google Pixel-Telefon befand es sich in FS\data\misc\bluetooth\logs\btsnoop_hci.log


  5. Jetzt haben wir die Protokolldateien, analysieren wir sie! Dazu habe ich mich für Wireshark entschieden. Starten Sie also Wireshark, gehen Sie zu Datei...Öffnen... und wählen Sie die Protokolldatei btsnoop_hci aus.


Auch wenn es entmutigend aussehen mag, machen wir es uns leicht, das Gesuchte zu finden, indem wir BTL2CAP nach 0x0004 filtern, dem Attributprotokoll im Wireshark-Quellcode . Das Attributprotokoll definiert die Art und Weise, wie zwei BLE-Geräte miteinander kommunizieren. Das ist es also, was wir brauchen, um herauszufinden, wie die App mit den Lichtern kommuniziert. Sie können die Protokolle in Wireshark filtern, indem Sie btl2cap.cid == 0x0004 in die Leiste „ Anzeigefilter anwenden “ oben eingeben und die Eingabetaste drücken


Filtern Sie die Protokolle, um es einfacher zu machen


Jetzt haben wir das Protokoll gefiltert; Es sollte die Suche nach den Befehlen erleichtern. Wir können uns die Zeitstempel ansehen (Gehen Sie zu Ansicht…Zeitanzeigeformat…Tageszeit, um die Zeit umzuwandeln, wenn sie das falsche Format hat). Wir möchten uns die Protokolle „Gesendete Schreibbefehle“ ansehen, da wir dort einen Wert an die Lichter gesendet haben. Gehen Sie davon aus, dass Ihre letzte Zeit ganz unten steht. Scrollen Sie dann nach unten zu den letzten fünf Ereignissen. Diese sollten in dieser Reihenfolge „Ein“, „Rot“, „Grün“, „Blau“ und „Aus“ sein, wobei „Aus“ an letzter Stelle stehen sollte.


BS_ADDR und Werte – Die Informationen, die wir brauchen!


Notieren Sie sich das Ziel BD_ADDR, da wir es in Kürze benötigen, und setzen Sie Ihren Sherlock-Holmes-Hut auf, denn hier müssen wir das Muster entschlüsseln, wie die Farben und Ein-/Aus-Befehle in der Nachricht codiert werden. Dies variiert je nach Leuchtenhersteller, aber hier ist die Liste der Werte, die ich für mein Gerät erhalten habe:


  • Am: 7e0404f00001ff00ef
  • Rot: 7e070503ff000010ef
  • Grün: 7e07050300ff0010ef
  • Blau: 7e0705030000ff10ef
  • Aus: 7e0404000000ff00ef


Dies sind eindeutig hexadezimale Werte, und wenn Sie genau hinschauen, werden Sie feststellen, dass es einige feste Muster gibt. Lassen Sie uns die Muster aufteilen, da dies die Dinge viel klarer machen sollte.


  • Am: 7e0404 f00001 ff00ef
  • Rot: 7e070503 ff0000 10ef
  • Grün: 7e070503 00ff00 10ef
  • Blau: 7e070503 0000ff 10ef
  • Aus: 7e0404 000000 ff00ef


Wer mit den Hexadezimalwerten von reinem Rot, Grün und Blau vertraut ist, weiß, dass die Werte jeweils #FF000, #00FF00 und #0000FF sind, was genau das ist, was wir oben sehen können. Das bedeutet, dass wir jetzt das Format kennen, um die Farben nach unseren Wünschen zu ändern! (oder zumindest das, wozu die Lichter selbst fähig sind). Wir können auch sehen, dass On und Off ein anderes Format als die Farben haben und einander ähnlich sind, wobei On f00001 und Off 00000 hat.

Das ist es! Wir haben jetzt genügend Informationen, um mit der Codierung und Interaktion mit den Lichtern zu beginnen.

Anschluss an LED-Leuchten

Es gibt drei wichtige Dinge, die wir brauchen:


  • Die Adresse des Geräts (dies ist das Ziel BD_ADDR von oben)

  • Die an das Gerät zu sendenden Werte (die oben erhaltenen Hexadezimalwerte)

  • Das Merkmal, das wir ändern möchten. Ein Bluetooth LE-Merkmal ist eine Datenstruktur, die im Wesentlichen Daten definiert, die zwischen einem Host und Client-Bluetooth-Geräten gesendet werden können. Wir müssen das Merkmal (eine 16-Bit- oder 128-Bit-UUID) finden, das sich auf die Lichter bezieht. Hier finden Sie einige häufig verwendete zugewiesene Nummern. Sofern das Gerät diesen jedoch nicht entspricht, verwenden sie möglicherweise eine benutzerdefinierte UUID. Da meine Lichter nicht in der Liste der zugewiesenen Nummern enthalten sind, suchen wir sie über den Code.


Ich verwende Python 3.10 und Bleak 0.20.1 . Stellen Sie sicher, dass Bluetooth auf Ihrem Computer aktiviert ist (keine Kopplung mit dem Gerät erforderlich, wir stellen über einen Code eine Verbindung her).


 # Function to create a BleakClient and connect it to the address of the light's Bluetooth reciever async def init_client(address: str) -> BleakClient: client = BleakClient(address) print("Connecting") await client.connect() print(f"Connected to {address}") return client # Function we can call to make sure we disconnect properly otherwise there could be caching and other issues if you disconnect and reconnect quickly async def disconnect_client(client: Optional[BleakClient] = None) -> None: if client is not None : print("Disconnecting") if characteristic_uuid is not None: print(f"charUUID: {characteristic_uuid}") await toggle_off(client, characteristic_uuid) await client.disconnect() print("Client Disconnected") print("Exited") # Get the characteristic UUID of the lights. You don't need to run this every time async def get_characteristics(client: BleakClient) -> None: # Get all the services the device (lights in this case) services = await client.get_services() # Iterate the services. Each service will have characteristics for service in services: # Iterate and subsequently print the characteristic UUID for characteristic in service.characteristics: print(f"Characteristic: {characteristic.uuid}") print("Please test these characteristics to identify the correct one") await disconnect_client(client)


Ich habe den Code kommentiert, daher sollte er selbsterklärend sein, aber im Wesentlichen stellen wir eine Verbindung zu den Lichtern her und finden alle darin enthaltenen Eigenschaften. Meine Ausgabe war:


Merkmal: 00002a00-0000-1000-8000-00805f9b34fb Merkmal: 00002a01-0000-1000-8000-00805f9b34fb Merkmal: 0000fff3-0000-1000-8000-00805f9b34fb Merkmal: 0000fff4-0000-1000-8000-00805f9b34fb


Eine kurze Google-Suche nach den ersten beiden UUIDs zeigt, dass sich dies auf den Namen und das Erscheinungsbild des Dienstes bezieht, was für uns irrelevant ist. Die dritte und vierte scheinen jedoch am besten geeignet zu sein, wobei laut dieser Seite die dritte ( 0000fff3-0000-1000-8000-00805f9b34fb ) die Schreibeigenschaft ist. Hervorragend, wir haben jetzt die Charakteristik, die wir für dieses bestimmte Gerät benötigen, um mit einem Wert (der hexadezimalen Farbe) zu schreiben.

LED-Leuchten steuern

Endlich haben wir alle Teile, die wir brauchen. In dieser Phase können Sie Ihrer Kreativität freien Lauf lassen und entscheiden, welche Farbeingabe Sie verwenden möchten. Sie könnten die Lichter beispielsweise mit einer Handelsmarkt-API verbinden, um die Farben entsprechend der Entwicklung Ihres Portfolios zu ändern. In diesem Fall möchten wir unsere Monitore auf die Umgebungstemperatur aufmerksam machen, also müssen wir die dominante Farbe des Bildschirms ermitteln und diese weiterleiten.


Es gibt viele Möglichkeiten, dies zu tun. Experimentieren Sie also ruhig mit den Algorithmen Ihrer Wahl. Einer der einfachsten Ansätze bestünde darin, alle Fühlen Sie sich frei, alle Ergebnisse zu kommentieren, die Sie teilen möchten!

In diesem Blogbeitrag werde ich es einfach halten und die Methode get_dominant_color der fast_colorthief- Bibliothek verwenden.


 ''' Instead of taking the whole screensize into account, I'm going to take a 640x480 resolution from the middle. This should make it faster but you can toy around depending on what works for you. You may, for example, want to take the outer edge colours instead so it the ambience blends to the outer edges and not the main screen colour ''' screen_width, screen_height = ImageGrab.grab().size #get the overall resolution size region_width = 640 region_height = 480 region_left = (screen_width - region_width) // 2 region_top = (screen_height - region_height) // 2 screen_region = (region_left, region_top, region_left + region_width, region_top + region_height) screenshot_memory = io.BytesIO(b"") # Method to get the dominant colour on screen. You can change this method to return whatever colour you like def get_dominant_colour() -> str: # Take a screenshot of the region specified earlier screenshot = ImageGrab.grab(screen_region) ''' The fast_colorthief library doesn't work directly with PIL images but we can use an in memory buffer (BytesIO) to store the picture This saves us writing then reading from the disk which is costly ''' # Save screenshot region to in-memory bytes buffer (instead of to disk) # Seeking and truncating fo performance rather than using "with" and creating/closing BytesIO object screenshot_memory.seek(0) screenshot_memory.truncate(0) screenshot.save(screenshot_memory, "PNG") # Get the dominant colour dominant_color = fast_colorthief.get_dominant_color(screenshot_memory, quality=1) # Return the colour in the form of hex (without the # prefix as our Bluetooth device doesn't use it) return '{:02x}{:02x}{:02x}'.format(*dominant_color)


Der Code ist kommentiert, daher sollte hoffentlich klar sein, was passiert, aber wir nehmen einen kleineren Bereich des Bildschirms von der Mitte und erhalten dann die dominante Farbe aus diesem Bereich. Der Grund, warum ich eine kleinere Region nehme, ist die Leistung. Es müssten weniger Pixel analysiert werden.


Wir sind fast da! Wir wissen jetzt, was und wohin wir es senden sollen. Lassen Sie uns den letzten großen Teil dieser Herausforderung abschließen, nämlich das tatsächliche Versenden. Glücklicherweise ist dies mit der Bleak-Bibliothek recht einfach.


 async def send_colour_to_device(client: BleakClient, uuid: str, value: str) -> None: #write to the characteristic we found, in the format that was obtained from the Bluetooth logs await client.write_gatt_char(uuid, bytes.fromhex(f"7e070503{value}10ef")) async def toggle_on(client: BleakClient, uuid: str) -> None: await client.write_gatt_char(uuid, bytes.fromhex(ON_HEX)) print("Turned on") async def toggle_off(client: BleakClient, uuid: str) -> None: await client.write_gatt_char(uuid, bytes.fromhex(OFF_HEX)) print("Turned off")


Wie wir aus den Protokollen herausgefunden haben, hat jede Farbe eine feste Vorlage, sodass wir F-Strings verwenden können, um den gemeinsamen Teil fest zu codieren, und einfach eine Hexadezimalzahl einer Farbe für den Wert in der Mitte übergeben können. Dies kann aus unserer Schleife aufgerufen werden. „Ein“ und „Aus“ hatten eindeutige Hexadezimalzahlen, daher habe ich einzelne Funktionen erstellt und einen konstanten Wert übergeben, der die entsprechende Hexadezimalzahl enthielt.


 while True: # send the dominant colour to the device await send_colour_to_device(client, characteristic_uuid, get_dominant_colour()) # allow a small amount of time before update time.sleep(0.1)


Und da haben wir es; Unsere Bluetooth-LED-Leuchten werden jetzt durch die Farben auf dem Bildschirm gesteuert, wodurch unser eigener Umgebungsmonitor entsteht.


Sie können den vollständigen Code auf GitHub sehen, der eine kleine Menge Infrastrukturcode enthält, der nicht spezifisch für diesen Beitrag war. Ich habe versucht, den Code so zu kommentieren, dass er selbsterklärend ist, aber Sie können jederzeit Fragen stellen oder Vorschläge machen.


Hoffentlich gibt Ihnen dies eine Vorstellung davon, wie Sie mit Ihren LED-Leuchten kreativ werden können.

Zukünftige Verbesserungen

  • Mehrfarbig – Meine Lichtstreifen können jeweils nur eine Farbe haben, aber ich habe ein anderes Set, das vier Quadranten haben kann, jeder mit seinen eigenen Farben. Dies bedeutet, dass es möglich sein könnte, Abschnitte des Monitors an vier Abschnitte auf dem Bildschirm anzupassen, um eine noch genauere Atmosphäreneinstellung zu erzielen. Diese Lichter laufen über WLAN statt über Bluetooth und könnten ein zukünftiges Projekt sein.
  • Helligkeit – um es einfach zu halten, habe ich nur nach den Farbwechsel- und Ein-/Aus-Befehlen gesucht. Dies kann jedoch leicht verbessert werden, indem die Helligkeitssteuerungsbefehle erkannt und in den Farbalgorithmus übernommen werden.
  • Leistung – Da wir erreichen möchten, dass sich die Lichter in Echtzeit ändern, ist die Leistung von entscheidender Bedeutung. Es gibt einige komplexe Algorithmen, um zu erkennen, welche Farbe als die dominanteste gilt, insbesondere wenn sie von Menschen wahrgenommen wird (was zu einer ganzen Welt von Farbkonvertierungen führt). Da dies jedoch recht schnell ablaufen muss, muss ein Gleichgewicht zwischen Leistung und Genauigkeit bestehen. Eine zukünftige Verbesserung könnte darin bestehen, zu versuchen, direkt auf die Grafikkarte zuzugreifen, um aus dem Puffer zu lesen, anstatt die Pixel auf dem Bildschirm direkt zu analysieren. Wenn dies möglich ist, würden Sie auch die Zeit verkürzen, die vom Grafikpuffer bis zum Bildschirm benötigt wird, was die Reaktion der Lichter optimieren könnte.


Fühlen Sie sich frei, unten einen Kommentar abzugeben, wenn Sie Feedback oder Fragen haben.


Auch hier veröffentlicht