Wir zerlegen das Redmond G200S-Kesselprotokoll und verbinden es mit dem HomeAssistant

Eintrag


Es gibt bereits einen Artikel über die Gicktime, der sich mit dem Parsen des Protokolls des Redmond SkyKettle-Kessels befasst. Dort wurde jedoch über das Modell RK-M171S gesprochen, hier wird über ein funktionaleres G200S gesprochen. In diesem Modell hat sich das Interaktionsprotokoll geändert, wodurch der Ansatz des Autors des vorherigen Artikels nicht mehr funktioniert und zusätzliche Funktionen des Nachtlichts und die Anzeige der aktuellen Farbtemperatur erschienen sind.

In diesem Artikel werde ich die Ergebnisse einer Protokollanalyse mit Python-Codebeispielen vorstellen (wenn jemand sein Modul / seine Anwendung zur Steuerung der Teekanne entwickeln möchte). Am Ende des Artikels befindet sich außerdem ein Link zu einem vorgefertigten Modul zum Anschließen einer Teekanne an HomeAssistant (dies ist meine erste Erfahrung beim Schreiben in Python nach einem Online-Kurs, sodass dieses Modul verbessert werden kann und muss).

Jeder, der interessiert ist, willkommen bei Katze.

Probleme und Aufgaben


Diese Teekanne hat ein großes Minus (mit Ausnahme der vom Autor des ersten Artikels angegebenen): Sobald die Teekanne vom Stand entfernt wird, wird die aktuelle Zeit zurückgesetzt und der Zeitplan kann daher nicht zum Kochen der Teekanne verwendet werden. Nach den Vorstellungen der Autoren dieser Kreation müssen Sie jedes Mal, wenn Sie den Wasserkocher wieder auf den Stand bringen, die proprietäre Anwendung starten und den Wasserkocher mit einem Smartphone synchronisieren. Anstatt Routineaufgaben zu erleichtern, schult uns die „intelligente“ Technologie, zusätzliche Aktionen auszuführen. Das änderte sich jedoch, als HomeAssistant im Haus erschien. Dann habe ich beschlossen, das Protokoll zu verstehen.

Die Werkzeuge


Ich habe ehrlich gesagt versucht, die ursprüngliche Anwendung zu dekompilieren und zu analysieren, bin aber gescheitert. Die Werkzeuge, die ich benutzte, erlaubten mir nicht, die Logik des Kessels zu verstehen. Alle Prozeduren und Funktionen wurden durch "Kurven" ohne Namen (nach Typ a, b, c usw.) erhalten. Vielleicht habe ich nicht genug Erfahrung und Können. Am Ende bin ich den gleichen Weg gegangen wie der Autor des vorherigen Artikels. Der einzige signifikante Unterschied besteht darin, dass ich den interaktiven Modus des Dienstprogramms gatttool verwendet habe. Der Vorteil ist, dass dieser Modus alle Arten von "Rassen" eliminiert, über die der Autor des ersten Artikels geschrieben hat.

Da HomeAssistant in Python geschrieben ist, werden wir alle weiteren Befehle darauf schreiben. Um den interaktiven Gatttool-Betriebsmodus in Python zu verwenden, hilft uns die pexpect-Bibliothek, mit der Sie die Essenz von Anwendungen von Drittanbietern hervorbringen und deren Ausgabe überwachen können (bekanntermaßen verbogen).

Übe


Ich werde den ersten Artikel über die allgemeine Beschreibung des Austauschprotokolls erneut an den Autor des Artikels senden, sodass wir ohne weiteres mit den Steuerbefehlen fortfahren werden.

  1. Installation und Trennung

    Stellen Sie eine Verbindung her:

    child = pexpect.spawn("gatttool -I -t random -b " + mac, ignore_sighup=False) child.expect(r'\[LE\]>', timeout=3) child.sendline("connect") child.expect(r'Connection successful.*\[LE\]>', timeout=3) 

    Hier ist Mac die Mohnadresse der Teekanne.

    Wir unterbrechen die Verbindung:

     child.sendline("exit") 
  2. Benachrichtigungen abonnieren

    Nachdem wir die Verbindung hergestellt haben, müssen wir uns zunächst anmelden, um Benachrichtigungen vom Wasserkocher zu erhalten. Ohne dies nimmt die Teekanne die Befehle wahr, kann uns jedoch nur den Text „Erfolgreich“ beantworten.

     child.sendline("char-write-cmd 0x000c 0100") child.expect(r'\[LE\]>') 
  3. Login

     child.sendline("char-write-req 0x000e 55" + iter + "ff" + key + "aa") child.expect("value: ") child.expect("\r\n") connectedStr = child.before[0:].decode("utf-8") answer = connectedStr.split()[3] # parse: 00 - no 01 - yes child.expect(r'\[LE\]>') 

    Im Folgenden ist iter eine ganzzahlige iterative Hex-Variable von 0 bis 64 (von 0 bis 100 im Dezimalsystem). Nach jedem Befehl (sowohl erfolgreich als auch erfolglos) sollte diese Variable um 1 erhöht werden. Wenn sie 64 erreicht, wird sie erneut auf 0 zurückgesetzt. Schlüssel - Hex 8-Byte-Autorisierungsschlüssel (zum Beispiel: ffffffffffffffff).

    Antwortbeispiel:
    Wert: 55 00 ff 01 aa
    Das vierte Byte (01) bedeutet, dass der Wasserkocher Sie autorisiert hat, andernfalls lautet die Antwort 00.
  4. Etwas Straßenmagie
    Nach der Autorisierung wird immer eine "magische" Anfrage gesendet, deren Wesen mir nicht klar ist. Es gibt eine Theorie, dass es notwendig ist, den verbundenen Zustand zu "halten". Wenn Sie es nicht senden, erfolgt die Trennung angeblich innerhalb einer Sekunde, und Sie müssen von vorne beginnen. Wenn Sie es senden, erhöht sich das Zeitlimit erheblich und erreicht bis zu etwa ein Dutzend Sekunden. Bestätige dies zuverlässig, konnte ich nicht.

     child.sendline("char-write-req 0x000e 55" + iter + "01aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    Antwortbeispiel:
    Wert: 55 01 01 02 1d aa

    In all meinen Experimenten war die Antwort immer diese.

    UPD: In den Kommentaren wurde vorgeschlagen, dass es überhaupt nicht magisch sei, sondern lediglich die Softwareversion anzufordern. Dementsprechend ist diese Version in der Antwort enthalten. Somit kann diese Anforderung im Allgemeinen als unnötig entfernt werden.
  5. Synchronisieren
    Ein Befehl, der die Zeit in einer Teekanne mit einer Serveruhr synchronisiert. Sie hat noch einen Effekt. Im Wasserkocher ist es möglich, die aktuelle Temperatur im Leerlauf anzuzeigen, indem eine LED einer bestimmten Farbe blinkt. Diese Funktion funktioniert nur nach der Synchronisation. Eine Beschreibung der Funktion selbst finden Sie in Abschnitt 11.

     child.sendline("char-write-req 0x000e 55" + iter + "6e" + timeNow + tmz + "0000aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    Hier ist tmz die Zeitzone im umgekehrten Hex-Format (übersetzen Sie beispielsweise die Zeitzone +3 in Sekunden, dann in das Hex-Format und erhalten Sie hex (3 * 60 * 60) = 2a30, paarweise aufgeteilt und geben Sie 302a in umgekehrter Reihenfolge aus). Ich weiß nicht, was ich mit negativen Zeitzonen machen soll, ich habe es nicht getestet, aber es besteht der Verdacht, dass das nächste tmz-Byte dafür verantwortlich ist. Hier ist timeNow die aktuelle Unixtime- Zeit im umgekehrten Hex-Format. Der Algorithmus ist der gleiche: Wir erhalten die aktuelle Zeit in Sekunden, übersetzen sie in HEX, teilen sie in Paare auf und geben sie in umgekehrter Reihenfolge hintereinander aus.

    Antwortbeispiel:
    Wert: 55 02 6e 00 aa
    In all meinen Experimenten war die Antwort immer diese.
  6. Statistiken
    Der Wasserkocher hat einen Meter Stromverbrauch, Gesamtbetriebszeit und Anzahl der Starts. Wenn jemand diese Daten nicht benötigt, können Sie diesen Artikel sicher überspringen.

     child.sendline("char-write-req 0x000e 55" + iter + "4700aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") Watts = hexToDec(str(statusStr.split()[11] + statusStr.split()[10] + statusStr.split()[9])) alltime = round(self._Watts/2200, 1) child.expect(r'\[LE\]>') child.sendline("char-write-req 0x000e 55" + iter + "5000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") times = hexToDec(str(statusStr.split()[7] + statusStr.split()[6])) child.expect(r'\[LE\]>') 

    Watt - gibt die verbrauchte Energie in Wh * h zu jeder Zeit zurück - die Betriebsstunden des Kessels, Zeiten - die Anzahl der Starts des Kessels. hexToDec - eine Funktion zum Konvertieren in das Dezimalformat.
  7. Lesen Sie den aktuellen Betriebsmodus

     child.sendline("char-write-req 0x000e 55" + iter + "06aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split() status = str(answer[11]) temp = hexToDec(str(answer[8])) mode = str(answer[3]) 

    Antwortbeispiel:
    Wert: 55 04 06 00 00 00 00 01 2a 1e 00 00 00 00 00 80 00 00 aa
    Das vierte Byte ist der Betriebsmodus (Modus): 00 - Kochen, 01 - Erhitzen auf Temperatur, 03 - Nachtlicht. Das sechste Byte ist die Hex-Temperatur, auf die im Heizmodus erwärmt werden muss, im Siedemodus 00. Das neunte Byte ist hex die aktuelle Wassertemperatur (2a = 42 Grad Celsius). Das zwölfte Byte ist der Zustand der Teekanne: 00 - aus, 02 - ein. Das siebzehnte Byte ist die Dauer des Kessels nach Erreichen der gewünschten Temperatur, standardmäßig 80 in hex (anscheinend sind dies relative Einheiten, sicherlich keine Sekunden).
  8. Aktuelle Betriebsart aufzeichnen

     child.sendline("char-write-req 0x000e 55" + iter + "05" + mode + "00" + temp + "00000000000000000000" + howMuchBoil + "0000aa") child.expect("value: ") child.expect("\r\n") statusStr = child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    Parametermodus: 00 - Kochen, 01 - Erhitzen auf Temperatur, 03 - Nachtlicht. Der Temp-Parameter ist die Hex-Temperatur, auf die im „Heizmodus“ erwärmt werden muss, im Siedemodus ist es 00. Der HowMuchBoil-Parameter ist die Dauer des Kessels nach Erreichen der gewünschten Temperatur, der Standardwert ist 80 in Hex (anscheinend sind dies einige relative Einheiten sicherlich nicht Sekunden).

    Antwortbeispiel:
    Wert: 55 05 05 01 aa
    Das vierte Byte der Antwort zeigt den Erfolg der Einstellungen an: 01 - erfolgreich, 00 - nicht erfolgreich.
  9. Führen Sie den aktuellen Betriebsmodus aus

     child.sendline("char-write-req 0x000e 55" + iter + "03aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    Antwortbeispiel:
    Wert: 55 06 03 01 aa
    Das vierte Byte der Antwort zeigt den Erfolg der Aufnahme an: 01 - erfolgreich, 00 - nicht erfolgreich.
  10. Stoppen Sie den aktuellen Betriebsmodus

     child.sendline("char-write-req 0x000e 55" + iter + "04aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") answer = statusStr.split()[3] child.expect(r'\[LE\]>') 

    Antwortbeispiel:
    Wert: 55 07 04 01 aa
    Das vierte Byte der Antwort zeigt den Erfolg des Herunterfahrens an: 01 - erfolgreich, 00 - nicht erfolgreich.
  11. Zeigen Sie die aktuelle Temperatur in Farbe im Leerlauf an

     child.sendline("char-write-req 0x000e 55" + iter + "37c8c8" + onoff + "aa") # 00 - off, 01 - on child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    Der Ein-Aus-Parameter ist entweder 01, um die Funktion zu aktivieren, oder 00, um die Funktion zu deaktivieren.

    Antwortbeispiel:
    Wert: 55 08 37 00 aa
    In all meinen Experimenten war die Antwort immer diese.
  12. Nehmen Sie eine Farbpalette mit verschiedenen Betriebsarten auf
    Eine Korrespondenzpalette zwischen der Farbe der LED und der Temperatur wird im Anzeigemodus der aktuellen Temperatur und im Heiz- und Siedemodus sowie der Farbpalette im Nachtlichtmodus eingestellt.

     child.sendline("char-write-req 0x000e 55" + iter + "32" + boilOrLight + scale_from + rand + rgb1 + scale_mid + rand + rgb_mid + scale_to + rand + rgb2 + "aa") child.expect("value: ") child.expect("\r\n") child.expect(r'\[LE\]>') 

    Der Parameter boilOrLight ist 00, wenn wir den Anzeigemodus der aktuellen Temperatur einstellen, oder 01, wenn wir den Nachtmodus einstellen. Der Parameter scale_from gibt den Beginn des Farbwechselbereichs an und ist im Nachtlichtmodus gleich 00 und im aktuellen Temperaturanzeigemodus gleich 28 (28 ist 40 im Dezimalformat und ab dieser Temperatur beginnt ein sanfter Farbwechsel). Der Parameter scale_mid liegt in der Mitte des Bereichs und beträgt 32 im Nachtlichtmodus und 46 im aktuellen Temperaturanzeigemodus. Der Parameter scale_to gibt das Ende des Farbbereichs an und ist in beiden Modi 64. Der Parameter rgb1 ist die hexadezimale Farbe am Anfang der Palette. Der Parameter rgb_mid ist die hexadezimale Farbe der Mitte der Palette (ich berechne sie als die Mitte zwischen dem linken und rechten Ende, aber theoretisch können Sie jede Farbe angeben, dies wirkt sich nur auf die Schönheit und Glätte der Farbänderung aus). Der Parameter rgb2 ist die hexadezimale Farbe am Ende der Palette. Der Rand-Parameter ist ein bestimmter Parameter, dessen Wert ich nicht genau verstanden habe, vielleicht irgendwie in Bezug auf die Helligkeit der Farbe (Beispiele für Werte: e5, cc).

    Antwortbeispiel:
    Wert: 55 09 32 00 aa
    In all meinen Experimenten war die Antwort immer diese.
  13. Lesen Sie die Farbpalette der verschiedenen Betriebsarten

     child.sendline("char-write-req 0x000e 55" + iter + "33" + boilOrLight + "aa") child.expect("value: ") child.expect("\r\n") statusStr = self.child.before[0:].decode("utf-8") child.expect(r'\[LE\]>') 

    Der Parameter boilOrLight kann 00 sein - wenn wir den Anzeigemodus der aktuellen Temperatur einstellen, oder 01 - wenn wir den Nachtmodus einstellen.

    Antwortbeispiel:
    Wert: 55 10 33 01 00 7f 00 00 ff 32 7f 00 ff 00 64 7f ff 00 00 aa
    Hier ist das sechste, elfte und sechzehnte Byte (7f) der Rand-Parameter aus Punkt 12. Das fünfte Byte ist scale_from, das zehnte Byte ist scale_mid, das fünfzehnte Byte ist scale_to. Siebte + achte + neunte Bytes sind rgb_from. Das zwölfte + dreizehnte + vierzehnte Byte ist rgb_mid. Siebzehntes + achtzehntes + neunzehntes Byte - rgb_to.

Fazit


Wenn gatttool keine Verbindung zur Teekanne herstellen möchte (dies ist möglich, wenn Sie zum ersten Mal eine Verbindung zu unbekannten Geräten herstellen), versuchen Sie, mit os nach der Teekanne zu suchen, bevor Sie das Modul anschließen:

 sudo hciconfig device reset sudo timeout 1 hcitool lescan 


Geräte-ID Ihres Bluetooth-Geräts (z. B. hci0). Stellen Sie sicher, dass die Mohnadresse Ihres Wasserkochers in der Liste der gefundenen Geräte enthalten ist. Danach:

 sudo hcitool lewladd mac sudo hcitool lerladd mac 


mac - die Mohnadresse Ihrer Teekanne

UPD6 : Das Wasserkochermodul wurde erheblich verbessert:
1. Übertragen Sie das Modul von der Plattform in den Integrationsmodus
2. Nach dem Hinzufügen haben Sie automatisch 3 Elemente: einen Warmwasserbereiter (aktuelle Temperatur, Zieltemperatur, Kochen und Heizen), einen Sensor (Synchronisationszeit, Energieverbrauch, Betriebsstunden, Anzahl der Starts) und Licht (kann als Nachtlampe verwendet werden und eine beliebige Farbe auswählen Hintergrundbeleuchtung)
3. Das Modul ist jetzt auf GitHub verfügbar.
4. Das Modul unterstützt die Installation über HACS
5. Konfigurationsbeispiel:
 r4s_kettler: device: 'hci0' mac: 'FF:FF:FF:FF:FF:FF' password: 'ffffffffffffffff' 


Screenshots der neuen Version
Bild
Bild
Bild
Bild


UPD7 : Irrelevante Informationen gelöscht

Source: https://habr.com/ru/post/de412583/


All Articles