Hintergrund: Als eines meiner Hobbys hatte ich ein „Smart Home“. Ich möchte schöne Geräte, aber ich möchte auch Freiheit und Privatsphäre. Deshalb beschäftige ich mich mit der Kreuzung von Xiaomi uzhik mit dem Hedgehog
Home Assistant .
Um eine angenehme Umgebung zu erhalten, müssen wir wissen, was zu Hause los ist. Kurz gesagt, Sensoren werden benötigt. Xiaomi hat viele verschiedene, aber am meisten hat mir das quadratische Thermometer auf elektronischer Tinte gefallen. Aber er ist überhaupt nicht schlau in dem Sinne, dass er überhaupt keine Schnittstellen außer der grafischen bereitstellt - weder WiFi noch BLE noch ZigBee. CR2032-Batterien halten jedoch mehrere Jahre. Es gibt auch eine Version mit Bluetooth, aber es ist etwas weniger elegant - eine Art dicker Pfannkuchen.
Zu Frühlingsbeginn wurde ein neuer Temperatur- / Feuchtigkeitssensor für elektronische Tinte mit BLE und sogar mit einer Uhr angekündigt. Ich brauche eigentlich keine Uhr, aber alles andere unterdrückte sofort alle rationalen Argumente und das Thermometer wurde auf Vorbestellung in einem der beliebten Online-Shops bestellt. Es ritt, es ritt und kam schließlich an.

Der Sensor wurde der MiHome-Anwendung ohne Probleme hinzugefügt (ich habe überall eine englischsprachige Oberfläche, mit der russischen Version von MiHome gab es angeblich Übersetzungsschwierigkeiten). Zeigt die aktuellen Werte und den Verlauf der Änderungen der Messwerte an.
Aber mit der Integration in den Home Assistant sind Schwierigkeiten aufgetreten. Die vorhandene Komponente für den Temperatursensor wollte in keiner Weise Daten vom Gerät übernehmen und beschwerte sich über das falsche Datenformat. Nun, es gibt nichts zu tun, wir nehmen eine Schaufel heraus und beginnen zu graben.
Der erste Gedanke war, sich mit dem BLE-Protokollgerät vertraut zu machen, aber nachdem die Größe der Dokumentation bewertet worden war, wurde beschlossen, auf die beliebte Poke-Methode umzusteigen.
Die erste Annäherung an die Shell
Öffnen Sie zum Starten das Terminal auf Ubuntu und führen Sie Bluetoothctl aus. Wir sehen folgendes:
[NEW] Controller 00:1A:7D:DA:71:13 fett [default] [NEW] Device 3F:59:C8:80:70:BE LYWSD02 [NEW] Device 4C:65:A8:DC:0D:AF MJ_HT_V1
MJ_HT_V1 ist ein alter Temperatursensor, LYWSD02 ist ein neuer. Der Unterschied im Modellbenennungsformat ist etwas alarmierend.
Dann müssen Sie irgendwie lesen, und welche Art von Daten im Allgemeinen von uns erhalten werden können. Er öffnete die Quellen der
Mitemp- Bibliothek, die im Home Assistant zum Empfangen von Daten vom alten Sensor verwendet wird. Dort fand ich heraus, dass die Blewrap-Bibliothek verwendet wird, die wiederum ein Wrapper für zwei Python-Bibliotheken für die Arbeit mit BLE ist. Ich brauche nicht so viele Schichten, wir werden
Bluepy verwenden . Es gibt Dokumentation, es ist nicht viel und nicht wenig, wir lesen und schreiben ein Skript, das alle Datenfelder auf dem Gerät durchläuft.
from bluepy import btle mac = '3F:59:C8:80:70:BE' p = btle.Peripheral(mac) for s in p.getServices(): print('Service:', s.uuid) for c in s.getCharacteristics(): print('\tCharacteristic:', c.uuid) print('\t\t', c.propertiesToString()) if c.supportsRead(): print('\t\t', c.read())
Im Allgemeinen ist alles einfach - ein BLE-Gerät bietet eine Reihe von Diensten, von denen jeder aus einer Reihe von Merkmalen besteht. Jedes Merkmal kann einer von 8 Typen sein. Für ein Merkmal können Sie mehrere Typen gleichzeitig angeben. Dienste und Funktionen werden auf zwei Arten identifiziert - eine Adresse in Form eines HEX-Werts und einer UUID. Ich bin mit der Arbeit mit UUID besser vertraut.
Also habe ich alle Spezifikationen für beide Sensoren berücksichtigt, sie angeschaut und festgestellt, dass wieder Geräte von völlig verschiedenen Herstellern unter der Marke Xiaomi verkauft werden. Unter den Werten des alten Sensors wurde "Cleargrass Inc" und im neuen "miaomiaoce.com" gefunden. Die Struktur der Dienste und Eigenschaften dieser beiden Sensoren ist ebenfalls völlig unterschiedlich, und die Liste der Eigenschaften des neuen Sensors ist doppelt so lang. Dann wurde klar, dass Sie Ihre eigene Bibliothek für die Integration mit dem Sensor schreiben müssen (nein, natürlich habe ich zuerst gegoogelt, vielleicht gibt es auf Anfrage etwas Nützliches LYWSD02, aber ich habe nichts Sinnvolles google gegeben).
Wie erhalten Sie die Daten?
Unter den verfügbaren Arten von Merkmalen gibt es neben READ auch WRITE und NOTIFY. SCHREIBEN - um Daten an das Gerät zu senden, und NOTIFY - um Daten zu empfangen. Gleichzeitig gibt es auch WRITE NOTIFY - das Gerät sendet erst nach dem Abonnieren Daten, indem es das gewünschte Byte mit dem Befehl WRITE sendet.
Versuche, etwas mit meinen Händen zu tun, brachten kein Ergebnis, die erste Linie der Verzweiflung war erreicht, aber in diesem Moment las ich Artikel über Kunsthandwerk auf Basis von Chips von Nordic Semiconductors und legte das Programm
nRF Connect auf mein Smartphone. Mit seiner Hilfe konnte ich alle vom Gerät bereitgestellten Dienste abonnieren, die Antwortprotokolle speichern und versuchen zu verstehen, was in ihnen steckt.

Diese dreifachen Pfeile aktivieren das Abonnement.
Die Besonderheit des alten Sensors war, dass die Daten zu Temperatur und Luftfeuchtigkeit in Form eines UTF-Strings vorliegen, während der neue alles in binärer Form zurückgibt.
Benachrichtigungen abonnieren
Um Daten vom Sensor zu empfangen, müssen Sie eine Abonnementanfrage senden. In der Mitemp-Bibliothek wurden dafür zwei Bytes für das Merkmal gesendet, aber es ist nicht klar, woher es stammt. Hier habe ich mir die Datenstruktur für den alten Sensor in nRF Connect angesehen und festgestellt, dass die gewünschte Adresse für das Merkmal mit Daten wie ein Deskriptor angegeben ist. Dann fing ich wieder an, die Dokumentation für bluepy zu lesen und stellte fest, dass die Deskriptoradresse leicht aus dem Merkmalobjekt erhalten werden kann. Es bleibt nur eine Klasse mit einer Rückrufmethode zu schreiben, die Daten von der Benachrichtigung empfängt.
class MyDelegate(btle.DefaultDelegate): def handleNotification(self, cHandle, data): print(data) mac_addr = '3F:59:C8:80:70:BE' p = btle.Peripheral(mac_addr) p.setDelegate(MyDelegate()) uuid = 'EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6'
Wir trennen die Körner von der Spreu
Glücklicherweise wurden nur drei Merkmale als WRITE NOTIFY markiert, während die Daten unterschiedliche Frequenzen und visuelle Merkmale aufwiesen.
Die erste Anfrage schickte sofort eine große Menge Daten und blieb dann hängen. In diesem Fall war das erste Byte eine monoton ansteigende Zahl. Dies scheint eine akkumulierte Geschichte von Durchschnittswerten zu sein.
Der zweite und der dritte wurden regelmäßig gesendet, aber bei genauerem Hinsehen stellte ich fest, dass sich einer von ihnen nicht ändert und in den Daten des zweiten nur ein Byte geändert wird. Nun, dann ist dies die aktuelle Zeit (ich erinnere Sie daran, dass dieses Thermometer eine Uhr hat. In jedem Gerät mit Selbstachtung sollte eine Uhr für ein Smart Home vorhanden sein).
Angenommen, das dritte Merkmal sind nützliche Daten zu Temperatur und Luftfeuchtigkeit. Um die Hypothese zu bestätigen, wurde ein physikalisches Experiment durchgeführt - er ging zum Sensor und atmete ihn grob ein. Die Datenwerte stiegen auf dem Display stark an und die Bytes änderten sich im Terminal. Hurra, die Daten sind irgendwo in der Nähe.
Datenanalyse
Normalerweise arbeite ich mit Textdaten (HTTP-Daten in Form von JSON / xml abrufen, in eine Datei oder in eine Datenbank einfügen), daher habe ich nicht wirklich verstanden, wie ich mit der Aufgabe umgehen soll. Aus diesem Grund habe ich versucht, die Daten auf verschiedene Arten zu transformieren, die aus Python erstellt werden können. Ich schrieb hier eine solche Konvertierungsfunktion und begann zu beobachten, wie dies mit den Daten auf dem Sensorbildschirm korreliert.
def parse(v): print([x for x in v]) print('{0:#x}'.format(int.from_bytes(data, byteorder='big'))) print('{0:#x}'.format(int.from_bytes(data, byteorder='little')))
In die Konsole strömten Linien mit unterschiedlichem Grad an Dunkelheit, aber das dritte Byte war immer eine Zahl, und diese Zahl stimmte mit dem Feuchtigkeitswert überein. Aus Gründen der Wiedergabetreue habe ich den Sensor noch einmal eingeatmet - und die Feuchtigkeitswerte auf dem Bildschirm und im dritten Byte haben sich geändert!
Dann schlug ich vor, dass die Temperatur in den ersten zwei Bytes gespeichert wird. Damit sich die Daten ändern können, habe ich den Sensor auf einen beheizten Handtuchhalter im Badezimmer übertragen. Aber egal wie ich versuchte, die Ergebnisse zu transformieren, die erforderlichen Zahlen funktionierten nicht.
Auf dem Weg zum Erfolg
In diesem Moment schaute ich noch einmal auf die
Beschreibung des Sensors und sah, dass sich darin ein Sensor von Swiss Sensirion befand. Es lohnt sich wahrscheinlich, damit zu beginnen, aber dies ist nicht unsere Methode. Auf der Swiss Sensirion-Website wurden eine Reihe von Sensoren und Datenblätter dafür gefunden. Im Datenblatt wurde unter anderem eine Formel gefunden, um über den I2C-Bus übertragene Bytes in eine Zahl umzuwandeln.
T[°C]=−45+175 cdot fracST216−1
Aber ... es stellte sich als sehr seltsame Werte heraus. So etwas wie -34,66, aber ich war deutlich wärmer. Aus Trauer und Trauer öffnete ich sogar den Sensor und überprüfte, ob der Sensor von Swiss Sensirion dort wahr war. Es stellte sich heraus, dass es wahr war, aber mit dem SHTC3-Index, und es brauchte eine etwas andere Formel dafür.
T[°C]=−45+175 cdot fracST216
Trotzdem ähnelten die Daten nach der Konvertierung nicht einmal den tatsächlichen. Hier war ich noch trauriger, öffnete den Quellcode der Bibliothek für Adfruits SHTC3 und begann zu versuchen, den Transformationscode von C ++ an Python anzupassen. Ich habe alles auf das Tablet gebracht - Rohdaten, konvertierte Struktur und Ergebnis.
def handleNotification(self, cHandle, data): temp = data[:2] humid = data[2] unpacked = struct.unpack('H', temp)[0] print(data, unpacked, -45 + 175 * unpacked / 2 ** 19, sep='\t')
Habe so etwas:
b',\n2' 2604 -44.130821228027344 b'-\n2' 2605 -44.1304874420166 b'+\n2' 2603 -44.131155014038086 b',\n2' 2604 -44.130821228027344
Ja ... es ist irgendwie kalt ... Aber warte, warte, was ist 2604? Das ist es, 26,0 Grad auf dem Bildschirm! Um die Hypothese zu bestätigen, brachte er den Sensor erneut zur Batterie und überprüfte, ob die Werte übereinstimmen.
Als Ergebnis erhalten wir den folgenden Datenkonvertierungscode:
def handleNotification(self, cHandle, data): humid_bytes = data[2] temp_bytes = data[:2] humidity = humid_bytes temperature = struct.unpack('H', temp_bytes)[0] / 100 print(temperature, humidity)
Nachwort
Die Verbindung zum Sensor und die Suche nach dem richtigen Transformationsalgorithmus dauerte einige Abende. Mehrmals wollte ich alles fallen lassen, aber gleichzeitig kamen neue Ideen und ich versuchte es weiter.
Jetzt werden die Daten an den Home Assistant übertragen. Anschließend müssen Sie den Integrationscode fertigstellen und möglicherweise von bluepy in düster umschreiben, da bleak async / await verwendet und besser für den von aiohttp geschriebenen Home Assistant geeignet ist.

Referenzen: