Der Milquino ist ein Automat zur Zubereitung von Babyflaschen. Wahlweise gibt es dieses Gerät mit einer WLAN-Funktion und kann somit App gestützt genutzt werden.
Eine Nachfrage beim Hersteller ergab, dass es leider keine API gibt die man nutzen könnte, um den Milquino z.B. in Home-Assistant zu integrieren.
Da die Kommunikation offensichtlich über das lokale WLAN läuft und kein Cloud-Service dazwischen ist (was ich sehr begrüßenswert finde), müsste es doch möglich sein, die Kommunikation zwischen der App und dem Gerät mitzuhören, zu analysieren und entsprechend nutzen daraus zu ziehen.
Und genau darum soll es hier gehen. Wie kommuniziert die App mit dem Milquino und kann dies genutzt werden.
Disclaimer
Bevor es losgeht braucht es, so denke ich, einen kleinen Disclaimer. Soll mir ja schließlich niemand auf Idee kommen, hier würden illegale Aktivitäten durchgeführt.
Das Gerät wurde von mir weder geöffnet noch wurde die App de-kompiliert. Die gesamte Analyse basiert auf Mitschnitten in meinem eigenen Netzwerk.
Die ersten Informationen
Hat man den Milquino einmal in sein WLAN integriert wird man relativ schnell feststellen dass er eine „Webseite“ mitbringt. Ruft man die IP des Milquino auf, wird einem folgendes präsentiert:
Einmal mit Wireshark den Aufruf mitgeschnitten bekommt man die Info was verbaut wurde um einen kleinen Server im Milquino zu betreiben:
Wir wissen nun also, dass es sich um einen ESP8226 Chip auf einem NodeMCU Board handelt. Wir gehen einfach mal davon aus, dass keine Anstrengungen unternommen wurden, diese Info zu streuen obwohl ein anderes „Gerät“ genutzt wurde ;). Man mag sich nun fragen: In wie weit bringt mir diese Info nun etwas?
Nun ja, die Antwort ist so simpel wie eventuell überraschend. Sie bringt uns nichts, außer eventuell interessant zu sein.
Kommen wir nun aber zu den wirklich interessante Informationen. Schneidet man den Netzwerkverkehr zwischen App und Milquino mit, findet man heraus dass der Milquino auf Port 9990 mittels UDP angesprochen wird. Zum Glück für uns, ist der Datenverkehr nicht verschlüsselt, sondern Plaintext.
Machen wir es kurz
Das Ergebnis meiner Analyse sind, aktuell, die folgenden Funktionen.
- Anschalten
- Ausschalten
- Domainname erfragen
- Status erfragen
- Wassermenge konfigurieren
- Konfigurierte Wassermenge erfragen
- Anzahl der Pulverlöffel konfigurieren
- Konfigurierte Anzahl der Pulverlöffel erfragen
- Pulverdosierung per Löffel konfigurieren
- Konfigurierte Pulverdosierung per Löffel erfragen
- Fläschchen machen
Es gibt noch weitere Funktionen in der App, wie z.B. eine Info ob der Wassertank installiert ist oder ob entkalkt werden muss. Alles habe ich noch nicht analysiert.
Da die Kommunikation per UDP im Klartext stattfindet, können die Funktionen mittels netcat ganz einfach selber getestet werden, indem man die folgenden Strings per UDP an seinen Milquino auf Port 9990 sendet.
Die Payloads, die zu senden sind, um die oben genannten Funktionen zu nutzen lauten:
Function: on
Payload: C:1:72\r\n
Der Returnwert ist
E:C11:06
S:0A:22
S:0A:22 ist der Status dafür, dass die Maschine angeschaltet ist. Wofür der erste Wert steht, habe ich nicht rausgefunden.
Function: off
Payload: C:0:73\r\n
Der Returnwert ist
E:C01:07
S:02:51
S:02:51 ist der Status der anzeigt, dass die Maschine ausgeschaltet ist. Der Sinn des ersten Wertes entzieht sich aktuell meiner Kenntnis
Function: getDomainName
Payload: A:1:70\r\n
In meinem Fall ist der Returnwert zum Beispiel
:MILQUINO-9335445:192.168.178.149
Hier sieht man die IP des Gerätes in meinem Netzwerk sowie den Domainnamen. Ob diese Funktion tatsächlich genutzt wird, um den Domainnamen zu erfahren, ist nur eine Vermutung von mir.
Function: getStatus
Payload: C:5:76\r\n
Der Returnwert um anzuzeigen, dass die Maschine angeschaltet ist, ist
S:0A:22
Ist die Maschine ausgeschaltet kommt der Wert
S:02:51
zurück.
Function: setAmountOfWater
Der Milquino unterstützt in 30ml Schritten die Zubereitung von 60ml bis 240ml Babymilch. Entsprechend gibt es mehrere Payloads die zu senden sind, entsprechend der gewünschten Zubereitungsmenge Wasser.
Payload (60ml): W:2:65\r\n
Payload (90ml): W:3:64\r\n
Payload (120ml): W:4:63\r\n
Payload (150ml): W:5:62\r\n
Payload (180ml): W:6:61\r\n
Payload (210ml): W:7:60\r\n
Payload (240ml): W:8:6F\r\n
Der Returnwert ist immer der gesendete Payload. Möchte man also 120ml Wasser einstellen, kommt als Antwort „W:4:63“ zurück.
Function: getAmountOfWater
Payload: C:3:70\r\n
Dieser Payload führt dazu, dass man die aktuell konfigurierte Wassermenge zurückbekommt. Der Rückgabewert entspricht den Payload-Werten aus der Funktion „setAmountOfWater“.
Function: setNumberOfSpoons
Der Milquino unterstützt die Zubereitung von Babymilch mit maximal 7 Pulverlöffeln. Entsprechend, wie bereits bei der Funktion „setAmountOfWater“ gibt es wieder mehrere Payloads die gesendet werden können um die Konfiguration entsprechend durchzuführen.
Payload (2 Spoons): P:1:61\r\n
Payload (3 Spoons): P:2:62\r\n
Payload (4 Spoons): P:3:63\r\n
Payload (5 Spoons): P:4:64\r\n
Payload (6 Spoons): P:5:65\r\n
Payload (7 Spoons): P:6:66\r\n
Der Returnwert ist immer der gesendete Payload. Möchte man also 5 Pulverlöffel konfigurieren, sendet man den Payload „P:4:64\r\n“ und erhält als Antwort „P:4:64“
Function: getNumberOfSpoons
C:4:77\r\n
Die Funktion liefert den aktuell eingestellten Wert für die Menge zu verwendender Pulverlöffel zurück. Die Werte entsprechen denen aus der Funktion „getNumberOfSpoons“.
Function: setDosingPerSpoon
Der Milquino unterstützt die Dosierung eines Löffels im Bereich von 3,8g Pulver bis 5,6g Pulver in 0,2g Schritten. Entsprechend gibt es die folgenden Payloads:
Payload (3,8g): G:0:77
Payload (4g): G:1:76
Payload (4,2g): G:2:75
Payload (4,4g): G:3:74
Payload (4,6g): G:4:73
Payload (4,8g): G:5:72
Payload (5g): G:6:71
Payload (5,2g): G:7:70
Payload (5,4g): G:8:7F
Payload (5,6g): G:9:7E
Die Rückgabe ist auch hier immer der Wert, der gesendet wurde.
Function: getDosingPerSpoon
Payload: C:7:74
Der Rückgabewert ist die aktuell eingestellte Menge Pulver pro Löffel und die Werte sind in der Funktion „getDosingPerSpoon“ aufgeführt.
Function: makeMilk
Schlussendlich die Funktion, die die Maschine dazu veranlasst, eine Flasche Babymilch zu mischen.
Payload: C:2:71\r\n
Die Rückgabewerte dieser Funktion verstehe ich aktuell noch nicht, sind für mich zum jetzigen Zeitpunkt aber auch nicht von allzu großem Interesse.
Ein Beispiel
Wie kann man all diese Informationen nun nutzen, um den Milquino auch ohne App zu steuern? Man nutzt z.B. das Kommandozeilen-Tool netcat.
Möchte man so zum Beispiel den Status der Maschine erfahren, könnte man folgenden Befehl in einer Bash-Shell ausführen:
echo -ne "C:5:76\r\n" | nc -u 192.168.178.149 9990
Dieser Befehl sendet den String „C:5:76“ mit CRLF Zeilenterminierung durch das Tool netcat mittels UDP an die IP 192.168.178.149 auf Port 9990:
Wie bekomme ich „das“ jetzt in Home-Assistant?
Abschließend möchte ich ein Beispiel geben, wie man die Informationen nun nutzen kann um eine erste Home-Assistant Integration seinen Milquino zu bauen.
Dazu nutzen wir sowohl die command_line als auch die shell_command Integration von Home-Assistant.
Möchte man sich einen Button bauen um sowohl den aktuellen Status der Maschine als auch den An/Aus-Zustand zu steuern, wäre dies mit einem Binary-Sensor z.B. so machbar:
- platform: command_line name: milquino_status command: 'echo -ne "C:5:76\r\n" | nc -u -w 2 192.168.178.149 9990' scan_interval: 30 payload_on: "S:0A:22" payload_off: "S:02:51" command_timeout: 5 device_class: running
Um sich die aktuelle Konfiguration der Wassermenge sowie Löffelanzahl und Pulvermenge anzeigen zu lassen, könnte man einen Sensoren mit entsprechendem value_template nutzen:
- platform: command_line name: milquino_configuredAmountOfWater command: 'echo -ne "C:3:70\r\n" | nc -u -w 2 192.168.178.149 9990' scan_interval: 30 command_timeout: 5 unit_of_measurement: ml value_template: >- {% if value == 'W:2:65' %} 60 {% elif value == 'W:3:64' %} 90 {% elif value == 'W:4:63' %} 120 {% elif value == 'W:5:62' %} 150 {% elif value == 'W:6:61' %} 180 {% elif value == 'W:7:60' %} 210 {% elif value == 'W:8:6F' %} 240 {% else %} 0 {% endif %} - platform: command_line name: milquino_configuredNumberOfSpoons command: 'echo -ne "C:4:77\r\n" | nc -u -w 2 192.168.178.149 9990' scan_interval: 30 command_timeout: 5 unit_of_measurement: spoons value_template: >- {% if value == 'P:1:61' %} 2 {% elif value == 'P:2:62' %} 3 {% elif value == 'P:3:63' %} 4 {% elif value == 'P:4:64' %} 5 {% elif value == 'P:5:65' %} 6 {% elif value == 'P:6:66' %} 7 {% else %} 0 {% endif %} - platform: command_line name: milquino_dosingPerSpoon command: 'echo -ne "C:7:74\r\n" | nc -u -w 2 192.168.178.149 9990' scan_interval: 30 command_timeout: 5 unit_of_measurement: g value_template: >- {% if value == 'G:0:77' %} 3,8 {% elif value == 'G:1:76' %} 4 {% elif value == 'G:2:75' %} 4,2 {% elif value == 'G:3:74' %} 4,4 {% elif value == 'G:4:73' %} 4,6 {% elif value == 'G:5:72' %} 4,8 {% elif value == 'G:6:71' %} 5 {% elif value == 'G:7:70' %} 5,2 {% elif value == 'G:8:7F' %} 5,4 {% elif value == 'G:9:7E' %} 5,6 {% else %} 0 {% endif %}
Um eine Flasche dann letztendlich zuzubereiten könnte man sich einen „Button“ basteln, der einen Service aufruft, den man sich per shell_command baut:
milquino_make_milk: 'echo -ne "C:2:71\r\n" | nc -u -w 2 192.168.178.149 9990'
Das Endergebnis könnte nun so aussehen:
Abschließende Gedanken
So richtig zufriedenstellend, zumindest für mich, ist die Kommunikation mittels UDP an dieser Stelle nicht. Der Server im Milquino ist oft nicht so wirklich responsive. So kommt es mir persönlich einfach viel zu häufig vor, dass Abfragen an das Gerät gehen, die einfach nicht beantwortet werden. Das führt im Home-Assistant natürlich dazu, dass oft einfach keine Werte angezeigt werden.
Hätte man diesen Datenverlust eventuell vermeiden können indem man statt UDP auf TCP setzt? Vermutlich. Ändert aber nichts daran, dass aktuell einfach Datenpakete „verloren“ gehen. UDP ist nun mal Zustandslos und der Client wird es per Protokoll nicht erfahren ob das Paket ankam oder nicht.
Eventuell könnte man sich, um diese Problematik ein wenig zu mitgieren, einen eigenen Service in Home-Assistant programmieren oder eine komplette Proxy-Komponente bauen die dann auch direkt per REST angesprochen werden könnte.
Mal sehen was die Zeit noch so hergibt. Für einen ersten Start, könnte es ja jedoch reichen. Ein „Alexa – Schalte Milquino an“ ist so zumindest mal ohne große Probleme möglich. Nachts um 3 Uhr, wenn der/die kleine Hunger hat, eine Wohltat, ohne viel Aufwand an eine frische Flasche zu kommen 😉