Philips Luftreiniger AC2889/10

Begonnen von mbagent007, 12 Mai 2017, 13:56:26

Vorheriges Thema - Nächstes Thema

mbagent007

Hallo zusammen!

Ich habe kürzlich den App gesteuerten Luftreiniger von Philips erworben. Wer mehr dazu wissen möchte, ich habe bei Amazon eine Rezension verfasst (3 Sterne).

Da man im smarten Zuhause natürlich nicht unbedingt noch eine App nutzen möchte, liegt es nahe, diesen in FHEM zu integrieren. Leider ist in dieser Hinsicht nichts dokumentiert von Philips und wird laut Support auch nicht offengelegt.

Da ich nicht ganz unwissend bin, was Technik angeht, habe ich schonmal ein paar Datenpakete mitgeschnitten und bin in der Lage, alle Funktionen die ich mit der App steuern kann theoretisch auch aus FHEM heraus auszulösen. Jedoch sind die Befehle wohl verschlüsselt und damit habe ich leider zu wenig Erfahrung diese zu knacken. Vielleicht gibts hier jemand, der mir sagen kann, wo ich da am besten ansetzen kann.

Mit folgendem Paket kann man die Funktionen steuern:
Hypertext Transfer Protocol
    PUT /di/v1/products/1/air HTTP/1.1\r\n
    Host: 10.0.0.33\r\n
    Content-Type: application/json\r\n
    Connection: keep-alive\r\n
    Accept: */*\r\n
    User-Agent: Air%20Matters/6.1.3.4216 CFNetwork/811.4.18 Darwin/16.5.0\r\n
    Content-Length: 44\r\n
    Accept-Language: de-de\r\n
    Accept-Encoding: gzip, deflate\r\n
    \r\n
    [Full request URI: http://10.0.0.33/di/v1/products/1/air]
    [HTTP request 1/1]
    [Response in frame: 4371]
    File Data: 44 bytes
JavaScript Object Notation: application/json
Line-based text data: application/json
    E2UHA8+GIVpKwBplLP5aslTPGZ3dTZao0x7R1/qKk4w=


0%:   dkdtWRzS9zASkTyFj4PQQUM5+9k1m7MYCCc3pvjWD4A=
25%:  /i/b81QDzVAXujeDVWO9gFhDePs6wcpWxqD+UZYOVbg=
50%:  27aob26BniJRPxF4nqmbVQj9u5DveNB11ueDN76HY+A=
75%:  bs2tiwHZDvMAMyP9IBgKyjiv0ve9znS2nH710lAo5cA=
100%: UeGFvgOaVErZHbjYzmsVvaitn88RSah3NFZbK062tqg=

Aus: JCNm86gljYj5/IhBPzU3FA==
An:  bAA2XMNBt//kWCqYUhnTwA==

Allergiker: ENv8XWpJwZ23Ku4UwEOkZg==
Allgemein:  btoDupWXnvlZcMcL7ke9CA==
Bakterien:  yq/bPyHAHiehBYDBgTHY4A==

Still: ksMM7Q9yfLUZ1ZfgsMsW4diTb8FdnSlbyVZC4SBLSYs=
G1:    8bceO31o9sI7dVrwTO5iWHWHu3qB2GqPmizQYWKyM8s=
G2:    1euDrAvk+Quzu2Pnm/UgzhGpUtqAh7O17zyvn8hiP0M=
G3:    qlEqI9QToLYyjwwfBIF03C4yCk2nAMuokD1E3suliD8=
Turbo: 7e7S36Wv0hZ/6BvLdKBVdxk5ecskuzf5x8syQzMBQQQ=


Wenn ich den gleichen Befehl aus der App wiederhole, ist der Datenstring jedes mal anders, vermutlich ein Timestamp o.ä.?
Die hier abgefangenen Codes funktionieren eine Zeit lang, danach leider nicht mehr...

Bei der Ersteinrichtung in der App werden auch keine Codes o.ä. ausgetauscht so wie ich das mitbekomme. Das einzige was noch vorhanden ist, ist die UPnP Description:
<?xml version="1.0"?>
<root
xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>1</minor>
</specVersion>
<device>
<deviceType>urn:philips-com:device:DiProduct:1</deviceType>
<friendlyName>AirPurifier</friendlyName>
<manufacturer>Royal Philips Electronics</manufacturer>
<modelName>AirPurifier</modelName>
<modelNumber>AC2889</modelNumber>
<UDN>uuid:12345678-1234-1234-1234-e8c1d70003b5</UDN>
<cppId>e8c1d7fffe0003b5</cppId>
</device>
</root>


Eine AES Entschlüsselung habe ich schon probiert mit der ID vom Gerät, kam jedoch nichts brauchbares bei raus (sofern ich alles richtig gemacht habe). Herauskommen sollte ja vermutlich ein JSON String.

Ansonsten kann ich z.B. auch noch Daten auslesen die sich hinter den entsprechenden URLs verbergen. Unter anderem ist da z.B. die Firmware dabei, es kommt auch dort jedesmal ein anderer Code heraus. Wenn man dort genügend Daten erzeugt (die ja zumindest teilweise immer die gleichen Werte enthalten), kann man damit eventuell was anfangen?

EDIT:
Die Sache hat mich doch noch nicht losgelassen... und dann zufällig was gesehen. Ich weiß nicht aus welchem Grund genau die Schlüssel ausgetauscht wurden, bzw. was der Auslöser dafür war.


[JSON] App --> Gerät
{
  "diffie": "1CCC15297E5ACA94BAE88CEBBF3882ACEFD4096D30C598686AEB2D2926012F1EF9C168D4AB31D976793279DE80BB23D1A4A4259683BFAC8AFCE48C62BB62732AA0BD93B30311FBE0062327CBF84CFDEC4722E5F748621E4AE444C63C21CB730F063F892BF8188F93325348CCA8FCF102A36F49870C9BB4780BC6BFB143E10528"
}

[JSON] Gerät --> App
{
  "hellman": "7525b98b3e7194ecf5923bd24cdc6855728e0798755bdf35a7fcb1f993a968538ca9b15e09a82d7e83e68e028861fa28d376b83c3c6536648f1789b371794b4e355afc0d642ceaf6a8f9debf39d5c10986703e1e04168851412f39a4406378c547f564b3e66a2ee68ac5ad980d3160c5d546364d277734277f30ee344161be17",
  "key": "99c4cf898c5f61cea5687a756fda734f0fe052b0598a5e94c2ed7ab08cbc3ecd"
}

Das ist der erste Datensatz direkt danach:
c5A/moRVs6Hvu0r5rgNassENvkYmXNBuuj8fwYD3awrMM5eZCnAswuRH7XzkcYcuksLOUkCb6iugILLxJ8Z+9Qxt1abQVaiUyB3y6BevOTWWMLb/62Hpu3X07+SNcc2vwEmxei0OAZC1RGr+3vnPHz72RcER6qsaZ+Elv49HIcY=




Wer kann hier weiterhelfen?

Vielen Dank schonmal :)

Beste Grüße
Markus

Loredo

So ganz spontan sehe ich ein paar Verwandtschaften mit den Philips Android TVs.
Ich würde sagen, dass alle Strings, die mit = oder == enden, schonmal Base64 codiert sind und man diese entsprechend einfach umwandeln kann.
Ansonsten sieht es so aus, als wenn das Gerät wie die Android TVs auch HTTP Digest Auth verwendeten und anfänglich dafür ein Pairing stattfindet, bei dem ein zufällig generierter Username und Passwort verifiziert werden.


Vielleicht sind das ja ein paar Denkanstöße. Viel mehr kann ich leider nicht liefern, weil ich selbst keinen solchen TV habe und die Unterstützung im PHTV Modul bisher auch nicht vollständig funktioniert.
Hat meine Arbeit dir geholfen? ⟹ https://paypal.me/pools/c/8gDLrIWrG9

Maintainer:
FHEM-Docker Image, https://github.com/fhem, Astro(Co-Maintainer), ENIGMA2, GEOFANCY, GUEST, HP1000, Installer, LaMetric2, MSG, msgConfig, npmjs, PET, PHTV, Pushover, RESIDENTS, ROOMMATE, search, THINKINGCLEANER

mbagent007

Das mit Base64 war mir schon klar, habe es lediglich vergessen zu erwähnen, weil es mir zu selbstverständlich war ::)
Aber trotzdem danke natürlich für den Hinweis!

Den Tipp mit Android TVs und PHTV Modul schaue ich mir mal an, selber habe ich jedoch auch nichts dergleichen hier stehen :-\

peterk_de

Du schreibst bei der Ersteinrichtung kein Schlüsselaustausch, später Schlüsselerneuerung mit DH ... so wie es aussieht, ist deine einzige Chance dann die App oder die Gerätefirmware zu dekompilieren, um den initialen, offenbar statischen, AES-Key zu finden. Ansonsten sieht das nämlich sehr ordentlich aus und es wird blödestesnfalls nicht mit Timestamps, sondern zufälligen Nonces gearbeitet. Ich schätze die Chancen das zu knacken als minimal ein!

LG Peter
FHEM auf Ubuntu-VM / 2xNUC Proxmox Cluster
UI: HomeKit, TabletUI, Grafana
IOdevs: 2xHueBridge, RaspiMatic-CCU, CUL868, 2xHarmonyHub, 6xRaspi-Roomnode mit CO2, VOC und lepresenced
Devices: 107xHomematic(IP), 96xPhilips Hue, 17xTECHEM, 12xBTLE, 8xSONOS, 2xHomeConnect, 1xShelly 3em, 1xNanoleaf ...

rgerganov

I have reversed engineered the mobile app and created an open source tool for controlling Philips air purifiers: https://github.com/rgerganov/py-air-control
Cheers :)

GhostInTheBottle

Getestet und es funktioniert. Offenbar also doch kein unlösbares Problem. Ganz so simpel, also einfach nur dem Link folgen reicht nicht. Da muss noch eine Crypt Komponente nachinstalliert werden. Jedenfalls bei einem Test unter MacOS war das erforderlich. Ich werde mir das allerdings noch unter Linux ansehen. Bei mir geht es um den AC1214/10 - funktioniert einwandfrei.

basty2

#6
Hallo zusammen,
habe heute meinen 2729 erhalten. Dank rgerganov, ist dies nun in fhem einbindbar.
Habe keine direkte Einbindungsmöglichkeit gefunden, daher habe ich mir selbst etwas gebastelt, auch wenn das sehr hands-on geschah. Vielleicht interessiert es jemanden (IP ist 192.168.0.90):
dummy: philips_humidifier
ergänzung 99_myUtils (optional mit readings, mir hilft es erstmal im debug modus)

sub phili(){
my $a = qx (airctrl 192.168.0.90);
fhem ("setreading philips_humidifier readings $a");

my $power = (split('\\[',(split("Power:",$a))[1]))[0];
fhem ("setreading philips_humidifier power $power");
my $pm25 = (split('\\[',(split("PM25:",$a))[1]))[0];
fhem ("setreading philips_humidifier pm25 $pm25");
my $humidity = (split('\\[',(split("Humidity:",$a))[1]))[0];
fhem ("setreading philips_humidifier humidity $humidity ");
my $target_humidity = (split('\\[',(split("humidity:",$a))[1]))[0];
fhem ("setreading philips_humidifier target_humidity $target_humidity");
my $allergen_index = (split('\\[',(split("Allergen index:",$a))[1]))[0];
fhem ("setreading philips_humidifier allergen_index $allergen_index");
my $temperature = (split('\\[',(split("Temperature:",$a))[1]))[0];
fhem ("setreading philips_humidifier temperature $temperature");
my $function = (split('\\[',(split("Function:",$a))[1]))[0];
fhem ("setreading philips_humidifier function $function");
my $mode = (split('\\[',(split("Mode:",$a))[1]))[0];
fhem ("setreading philips_humidifier mode $mode");
my $fan_speed = (split('\\[',(split("speed:",$a))[1]))[0];
fhem ("setreading philips_humidifier fan_speed $fan_speed");
my $light_brightness = (split('\\[',(split("brightness:",$a))[1]))[0];
fhem ("setreading philips_humidifier light_brightness $light_brightness");
my $buttons_light = (split('\\[',(split("light:",$a))[1]))[0];
fhem ("setreading philips_humidifier buttons_light $buttons_light");
my $used_index = (split('\\[',(split("Used index:",$a))[1]))[0];
fhem ("setreading philips_humidifier used_index $used_index");
my $water_level = (split('\\[',(split("level:",$a))[1]))[0];
fhem ("setreading philips_humidifier water_level $water_level");
my $child_lock = (split(" ",(split("lock:",$a))[1]))[0];
fhem ("setreading philips_humidifier child_lock $child_lock ");
}

zudem ein at zur Aktualisierung aller 5 Minuten (änderbar): +*00:05:00 {phili()}
und jetzt bin ich dabei, die Befehle einzubinden
Geht bestimmt einfacher...
Grüße

eddy242

#7
Hallo zusammen,

vor ein paar Tagen haben wir uns so ein Teil zugelegt und das weiter oben zitierte Python-Script funktionierte zu meinem Entsetzen nicht mehr. Philips hat anscheinend die API geändert. Die Community hat allerdings reagiert und eine Branch des Codes angelegt, was bei mir funktioniert, lesend und schreibend. https://github.com/rgerganov/py-air-control/issues/35#issuecomment-626071625. Es wäre vielleicht wünschenswert, wenn In- und Output via JSON kämen aber dem geschenkten Gaul... Meine Perl-Kenntnisse reichen nicht aus, um den Python-Code in Perl zu konvertieren. Also bin ich Deinem Vorschlag basty2 gefolgt und habe den Output geparse'd. Nun gehe ich auch an die Kommandos und ein etwas gescheiteres UI im DOIF. Wie man auch sehen kann, werde ich das ganze noch ein bischen Aufhübschen und Verallgemeiner. Hier schon mal der Read-Code:
UPDATE: Nun in schön (meine Definition) sowie auch zum Steuern der Kommandos. Ich habe noch ein Presence-Device angelegt um den Purifier regelmäßig auf Präsenz zu testen, das airctrl-tool ist etwas sperrig, wenn das Gerät nicht im LAN erreibar ist (aka Stecker raus). Das müsste man (ich?) nochmal sauber abfangen. Besser wäre natürlich ein etwas strukturierter Output des Tools aber wie gesagt, dem geschenkten Gaul...


defmod DOIF_PhilipsAirPurifierBernhard DOIF init {\
$_ReadingsNamesALL = "Name,Type,ModelID,SWVersion,\
FanSpeed,Power,ChildLock,Brightness,\
Buttonlight,dt,dtrs,mode,PM2.5,AirQualityIndex,\
AirQualityThreshold,DisplayValue,err,fltt1,fltt2,\
FilterVorfilter,FilterHEPA,FilterAC,ota,\
Runtime,WifiVersion,ProductId,DeviceId,\
StatusType,ConnectType";;\
\
$_AirctrlNamesALL = "name,type,modelid,swversion,\
om,pwr,cl,aqil,\
uil,dt,dtrs,mode,pm25,iaql,\
aqit,ddp,err,fltt1,fltt2,\
fltsts0,fltsts1,fltsts2,ota,\
Runtime,WifiVersion,ProductId,DeviceId,\
StatusType,ConnectType";;\
\
@{$_ReadingsNames} = split /,/, $_ReadingsNamesALL;;\
@{$_AirctrlNames} = split /,/, $_AirctrlNamesALL;;\
$_basicCommand = "airctrl --ipaddr ".AttrVal("$SELF", "IPAddress", "192.168.178.172")." --version 1.0.7";;\
$_PresenceDevice = AttrVal("$SELF", "PresenceDevice", "prsnc_PhilipsAirPurifierBernhard");;\
\
$_ReadingsUpdated = 0;;\
$_LastCommandSuccess = 0;;\
$_IsDevicePresent = 0;;\
}\
\
{[$SELF:desiredFanspeed];;\
  ProcessAirctrlCommand($_basicCommand." --mode M --om ".[$SELF:desiredFanspeed]);;\
}\
  \
{[$SELF:desiredLight];;\
  ProcessAirctrlCommand($_basicCommand." --aqil ".[$SELF:desiredLight]);;\
}\
  \
{[$SELF:desiredPower];;\
  ProcessAirctrlCommand($_basicCommand." --pwr ".([$SELF:desiredPower] eq "on"?"1":"0"));;\
}\
  \
{[$SELF:desiredButtonLight];;\
  ProcessAirctrlCommand($_basicCommand." --uil ".([$SELF:desiredButtonLight] eq "on"?"1":"0"));;\
}\
  \
{[$SELF:desiredDisplayType];;\
  ProcessAirctrlCommand($_basicCommand." --ddp ".([$SELF:desiredDisplayType] eq "PM25"?"1":"0"));;\
}\
\
{[$SELF:desiredMode];;\
  ProcessAirctrlCommand($_basicCommand." --mode ".([$SELF:desiredMode] eq "General"?"G":([$SELF:desiredMode] eq "Bacteria"?"B":"A")));;\
}\
  \
{[+:15];;\
  $_IsDevicePresent = (ReadingsVal("$_PresenceDevice","state","gone") eq "present");;\
  my $status = "Error: Device not present - please check";;\
  if ($_IsDevicePresent) {\
my $a = qx ($_basicCommand);;\
ProcessAirctrlOutput($a);;\
$status = "$_ReadingsUpdated Readings updated at ".::TimeNow();;\
  }\
  set_State($status);;\
}\
  \
subs  {\
  sub ProcessAirctrlCommand {\
my ($command) = @_;;\
my $status = "Error: Device not present - please check";;\
\
$_IsDevicePresent = (ReadingsVal("$_PresenceDevice","state","gone") eq "present");;\
if ($_IsDevicePresent) {\
  my $a = qx ($command);;\
  $_LastCommandSuccess = ($a =~ /{"status":"success"}/g) >=1;;\
  ProcessAirctrlOutput($a);;\
  $status = "Command: $command Result: ".($_LastCommandSuccess?"success":"error");;\
}\
    set_State($status);;\
  }\
  \
  sub ProcessAirctrlOutput {\
my ($rueckgabe) = @_;;\
$_ReadingsUpdated = 0;;\
set_Reading_Begin;;\
foreach (@{$_ReadingsNames}) {\
  set_Reading_Update("rdg_".@{$_ReadingsNames}[$_ReadingsUpdated], ($rueckgabe =~ /\[@{$_AirctrlNames}[$_ReadingsUpdated]\]: (.*)/gm)[0]);;\
  $_ReadingsUpdated++;;\
}\
set_Reading_End(1);;\
  } \
}\

attr DOIF_PhilipsAirPurifierBernhard userattr IPAddress PresenceDevice
attr DOIF_PhilipsAirPurifierBernhard DbLogExclude .*
attr DOIF_PhilipsAirPurifierBernhard DbLogInclude rdg_AirQualityIndex,rdg_FanSpeed,rdg_FilterAC,rdg_FilterHEPA,rdg_FilterVorfilter,rdg_PM2.5
attr DOIF_PhilipsAirPurifierBernhard IPAddress 192.168.178.172
attr DOIF_PhilipsAirPurifierBernhard PresenceDevice prsnc_PhilipsAirPurifierBernhard
attr DOIF_PhilipsAirPurifierBernhard alias Abfrage Philips Air Purifier AC 2889 Bernhard
attr DOIF_PhilipsAirPurifierBernhard event-on-change-reading rdg_.*,desired.*
attr DOIF_PhilipsAirPurifierBernhard event-on-update-reading rdg_.*,desired.*
attr DOIF_PhilipsAirPurifierBernhard readingList desiredPower desiredFanspeed desiredMode desiredLight desiredButtonLight desiredDisplayType
attr DOIF_PhilipsAirPurifierBernhard setList desiredPower:on,off desiredFanspeed:1,2,3,s,t desiredMode:General,Allergen,Bacteria desiredLight:0,25,50,75,100 desiredButtonLight:on,off desiredDisplayType:PM25,AllergenIndex
attr DOIF_PhilipsAirPurifierBernhard weekdays So,Mo,Di,Mi,Do,Fr,Sa,WE,WT

Ronn

Hallo zusammen,

ich habe ebenfalls vor de 2889 in FHEM einzubinden. Ich habe die IP und das Presence Device angepasst.

Aber ich bekommen folgendes Error:


condition c08: Use of uninitialized value $rueckgabe in pattern match (m//)


Hab ich was übersehen? 29 Readings werden erfolgreich abgerufen. Aber irgendwie nicht angezeigt.

Weiß jemand Rat?

Vielen Dank + Grüße

Dracolein

Hi zusammen, gibts hier ggf Updates zum Stand der Dinge?
Bei uns steht ein Philips 4000i, den in fhem einzubinden wäre cool
Raspberry Pi 4 mit FHEM; FTUI Dashboard auf Asus 15,6" VT168H Touchscreen; ZigBee mit ConBee2 USB-Stick; div. Shelly 2.5; integr. Gaszähler mit ESP8266 & ESPEasy;