AdapterRequests icon indicating copy to clipboard operation
AdapterRequests copied to clipboard

DeLonghi Coffee Link Adapter

Open chrvidal opened this issue 2 years ago • 24 comments

What kind of device or service you would like to see an adapter for?

De'Longhi Primo Donna Soul über Coffe Link App. https://play.google.com/store/apps/details?id=it.delonghi&hl=de&gl=DE

Is an official API including documentation is available? Nein, noch nichts gefunden

Are other libraries for an integration available? Nein

Is this device already integrated in other Smart Home systems? Nein

Is this device already integrated in homebridge? Might the ham adapter together with the homebridge plugin be sufficient? Nein

Additional context Kleiner Thread zum Adapterwunsch. https://forum.iobroker.net/topic/45069/de-longhi-kaffeemaschine-mit-iobroker-starten?_=1631533653272

chrvidal avatar Sep 13 '21 12:09 chrvidal

Zusatzinfo: (Zumindest meine Maschine) kann per BLE angesteuert werden. Erste Entwicklungen habe ich bereits, aber die Zeit... 😅

AlCalzone avatar Sep 13 '21 12:09 AlCalzone

push

zonkAK avatar Jan 21 '22 08:01 zonkAK

@zonkAK Where you want to push it to?

Apollon77 avatar Jan 21 '22 08:01 Apollon77

into the capable hands of a developer (and coffee fancier) who wants to tell his/her Alexa to make the Delonghi brew some coffee while crawling out of bed :-) Sorry that for myself I cannot contribute anything besides maybe testing later on :-|

zonkAK avatar Jan 21 '22 09:01 zonkAK

I'll have more time in ~3 months, planning to work on this again then.

AlCalzone avatar Jan 21 '22 09:01 AlCalzone

Sounds great :-) Thank you!

zonkAK avatar Jan 21 '22 09:01 zonkAK

Hi coffee friends (like @AlCalzone and @ldittmar81),

as a new owner of a "DeLonghi Primadonna Soul" I wish to make my initial contribution to a possible DeLonghi coffee machine adapter for iobroker.

Situation:

  • De'Longhi did not yet publish any public information about an API.
  • An Android (and iPhone) app "De'Longhi COFFEE LINK" is available used to control selected coffee machines via an API over WLAN/HTTPS - e.g. the machine "Primadonna Soul".
  • The app uses "certificate pinning" which makes it a bit more complicated to analyze its behavior (HTTPS traffic).

Approach: Analyze the behavior of "De'Longhi COFFEE LINK" android app to learn how the undocumented API of De'Longhi works in order to support development of a possible iobroker adapter.

Proposed steps and initial hints to have more insights into the existing API:

  • Read and understand instructions "Intercepting HTTPS from a 3rd party app on non-rooted device": https://httptoolkit.tech/blog/intercepting-android-https/#intercepting-https-from-a-3rd-party-app-on-non-rooted-devices

  • Download and install "fiddler" for debugging/intercepting https connections (Windows/Mac/Linux): https://www.telerik.com/fiddler/fiddler-classic

    • Enable https decoding via "Tools/Options/HTTPS/"
      • "Capture HTTPS CONNECTs = enable"
      • "Decrypt HTTPS traffic = enable"
      • Export "fiddler root certificate" via "Tools/Options/HTTPS/Export root certificate to desktop" (will create "FiddlerRoot.cer" file)
    • Note the local/private IP of your system where you installed "fiddler"
  • Copy exported "FiddlerRoot.cer" file to your mobile phone (Android).

  • Install/trust "FiddlerRoot.cer" at your phone by open "settings" and search for "CA certificate", follow screen instructions to add it

  • Download and install "apk-mitm" (tool to remove apps' "certificate pinning") at your system where the Android App will be processed by it (Linux): https://github.com/shroudedcode/apk-mitm#apk-mitm Make sure Node.js (14+) and Java (8+) are already installed beforehand

  • Download android app "De'Longhi COFFEE LINK" via https://apkpure.com/de/de-longhi-coffee-link/it.delonghi/ and place it on the system where you can process it (Linux)

  • Process DeLonghi-App with "apk-mitm" to remove "CA Certificate" pinning from it. See https://github.com/shroudedcode/apk-mitm#apk-mitm

  • Remove any previously installed DeLonghi-App from your mobile phone (Android)

  • Upload "processed DeLonghi-App" to your mobile phone (Android) Install it by allowing installation from "unsecure sources"

  • Modify your existing (and active) WLAN configuration at your mobile phone and set/enable a "Proxy".

    • Set "Proxy IP" to the local/private IP of your system where you installed "fiddler"
    • Set "Proxy port" to "8888" ("Fiddler's default")
  • Now start and use the "processed DeLonghi-App" at your phone while monitoring its traffic/activity at "fiddler" At "fiddler" you will see all activities initiated from the DeLonghi-App, like accesses to the DeLonghi cloud

    • POST https://delonghibe.reply.it/api/getAddictionalCommonData.sr {"countryCode":"DE","locale":"de"} Response: {"result":{"code":0,"message":"OK"},"termsConditionsLink":"http://app.delonghi.com/coffee/de-de/terms-and-conditions.html","marketingConsensText":"Indem Sie diese Zustimmung erteilen, ermächtigen Sie De'Longhi Deutschland GmbH: <br> a. Ihnen Werbemitteilungen zu senden, die ...... De'Longhi Cookie Policy<\/a>","privacyPolicyLink":"https://www.delonghi.com/de-de/datenschutzerklarung","gygyaLocale":"de"}
    • GET https://ads-eu.aylanetworks.com/apiv1/dsns/AC000W026718269/lan.json Response: {"lanip":{"lanip_key_id":53272,"lanip_key":"QiXrFDvxtdCX9KLBuje0wU4BFx2SDA==","keep_alive":30,"auto_sync":1,"status":"enable"}}
    • GET https://ads-eu.aylanetworks.com/apiv1/dsns/AC000W02*****69/data/BS1IMG.json Response (containing my individualized photo for the coffee type as png): {"datum":{"created_at":"2022-04-25T11:12:59Z","from_template":false,"key":"BS1IMG","updated_at":"2022-04-25T11:15:03Z","value":"/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX......A\nAti7xT6o0AKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//Z\n","dsn":"AC000W02*****69"}}
    • GET https://ads-eu.aylanetworks.com/apiv1/devices.json Response:
[
  {
    "device": {
      "product_name": "AC000W02*****69",
      "model": "AY008ESP1",
      "dsn": "AC000W02*****69",
      "oem_model": "DL-millcore",
      "sw_version": "ADA 1.5.3 esp-idf-v3.3.1 2020-04-13 00:25:55 2cfd564",
      "template_id": 5651,
      "mac": "34865****84",
      "unique_hardware_id": null,
      "lan_ip": "192.168.178.xxx",
      "connected_at": "2022-04-25T10:49:47Z",
      "key": 1772503,
      "lan_enabled": true,
      "connection_priority": [
        "LAN"
      ],
      "has_properties": true,
      "product_class": null,
      "connection_status": "Offline",
      "lat": "50.1zzz",
      "lng": "8.9zzz",
      "locality": "67486",
      "device_type": "Wifi",
      "dealer": null
    }
  }
]
  • GET https://ads-eu.aylanetworks.com/apiv1/dsns/AC000W02*****69/properties.json Response:
[
  {
    "property": {
      "type": "Property",
      "name": "d001_rec_espresso",
      "base_type": "string",
      "read_only": false,
      "direction": "output",
      "scope": "user",
      "data_updated_at": "2022-04-25T10:50:30Z",
      "key": 131130551,
      "device_key": 1772503,
      "product_name": "AC000W02*****69",
      "track_only_changes": false,
      "display_name": "001_rec_espresso",
      "host_sw_version": false,
      "time_series": false,
      "derived": false,
      "app_type": null,
      "recipe": null,
      "value": "0CWw8AEIAAABGAABAQEAFAAoALQbAAEEAgAEBQQAAAAZAAEBuZY=",
      "generated_from": "AYLA::device",
      "generated_at": 1650883000,
      "denied_roles": [],
      "ack_enabled": false,
      "retention_days": 30
    }
  },
...(additional 310 entries here!)...
  {
    "property": {
      "type": "Property",
      "name": "software_version",
      "base_type": "string",
      "read_only": false,
      "direction": "output",
      "scope": "user",
      "data_updated_at": "2022-04-25T10:50:29Z",
      "key": 131130859,
      "device_key": 1772503,
      "product_name": "AC000W02*****69",
      "track_only_changes": true,
      "display_name": "software_version",
      "host_sw_version": false,
      "time_series": false,
      "derived": false,
      "app_type": null,
      "recipe": null,
      "value": "Millcore_demo 2.0.0 Jun 18 2020 15:41:06",
      "generated_from": "AYLA::device",
      "generated_at": null,
      "denied_roles": [],
      "ack_enabled": false,
      "retention_days": 30
    }
  }
]
  • Additionally there is direct traffic from the DeLonghi-App to the local IP of your coffee machine like

    • POST http://192.168.178.xxx/local_reg.json (with local IP of coffee machine) {"local_reg":{"ip":"192.168.178.yyy","notify":0,"port":10275,"uri":"/local_lan"}} (with local IP of your mobile phone)
  • Additionally the app at your phone locally acts as a server and opens a local port (as announced above: 10275) and receives direct traffic from the local coffee machine like

    • POST http://192.168.178.yyy:10275/local_lan/key_exchange.json {"key_exchange":{"ver":1,"random_1":"8vZsmt7ABmlGFsBc","time_1":841334571651047,"proto":1,"key_id":57452}}
    • POST http://192.168.178.yyy:10275/local_lan/property/datapoint.json {"enc":"uCqGzuDhT5o/E5qKB3BFgcfff2GWxszq/I7AbET+lK8q1Wl8d9ypi8ElVLU7H9O+3R95C52AZ/Kn59zNV7/bvK39PU83dkVpA1NJnglSu6s=","sign":"EZf2hHPp2QHgPgDGI7aLrozUuaoasc4PYg00HJT080g="}
    • GET http://192.168.178.yyy:10275/local_lan/commands.json
    • ...
    • All this kind of traffic can be catched by tracing your local network, e.g. FritzBox via its capture option and analyzing the output via WireShark as the traffic itself is not secured. But the content seems to be encrypted...
  • Last but not least the coffee machine accesses parts of the DeLonghi cloud like

    • DL-millcore-229b963f-device.aylanetworks.com
    • ...
    • Problem: I did not get aware of this traffic's content, because the coffee machine uses HTTPS to connect to the cloud service and I could not yet get to be a "man-in-the-middle".

I hope my input helps a bit to find a skilled iobroker enthusiast with enough patience and passion to push things towards an adapter. Looking forwards to your feedback, additions and corrections!

th4git avatar Apr 27 '22 16:04 th4git

@th4git I currently don't have the capacity to continue with this project. Also, my machine is communicating with the App via Bluetooth, not WiFi, so my previous work would be of little help for the HTTP API. Maybe the commands are encoded similarly, if anyone needs the ones I've figured out (from decompiling the App), let me know.

AlCalzone avatar Apr 28 '22 06:04 AlCalzone

Is there anyone who wants to take care of this now?

Bully85 avatar Nov 28 '22 11:11 Bully85

Some updates of this ? Already in HA integrated: https://github.com/Arbuzov/home_assistant_delonghi_primadonna

Ilovegym66 avatar Aug 26 '23 15:08 Ilovegym66

I believe it's not, it's by error.

https://github.com/Arbuzov/home_assistant_delonghi_primadonna/issues/59

shaarkys avatar Sep 18 '23 13:09 shaarkys

Hello, did someone manage to communicate with some wifi only delonghi coffeelink enabled applicance ? Here are some API docs https://docs.aylanetworks.com/reference/getting_started auth_token determination is the key to success...

Philmo67 avatar Feb 01 '24 22:02 Philmo67

Hello, I was able to analyze a bit the traffic between the app and the Delonghi Cloud, thanks to @th4git explanations. Here is what I identified so far.

Authenticating

the authentication to the API is a 2 steps process:

  1. You have to connect to Delonghi's IDP Gigya In my case, this was done while accessing this URL: https://fidm.eu1.gigya.com/oidc/op/v1.0/3_e5qn7USZK-QtsIso1wCelqUKAK_IVEsYshRIssQ-X-k55haiZXmKWDHDRul2e5Y2/authorize?client_id=1S8q1WJEs-emOB43Z0-66WnL&response_type=code&redirect_uri=https://google.it&scope=openid%20email%20profile%20UID%20coffee&nonce=1707250274134

  2. Using the code from the IDP, you transform it to a temporary token by sending it to this URL: https://fidm.eu1.gigya.com/oidc/op/v1.0/3_e5qn7USZK-QtsIso1wCelqUKAK_IVEsYshRIssQ-X-k55haiZXmKWDHDRul2e5Y2/token

  3. You can finally use this token to sign-in to Ayla using this URL: https://user-field-eu.aylanetworks.com/api/v1/token_sign_in This will get you a refresh_token that could be saved and used to generate the access_token required to send all requests to https://ads-eu.aylanetworks.com/apiv1

Extracting data

My main purpose would be to monitor the status of the coffee machine. It seems that most of the information is in the JSON obtained at this URL: https://ads-eu.aylanetworks.com/apiv1/dsns/AC000W02*****69/properties.json

The thing is that most of the information about the status is aggregated in the 'd302_monitor' entry. Some other values are stored as integer and are easier to retrieve ('d700_tot_bev_b' for example seems to contain the amount of beverages made so far...)

Looking into the APK helped me a bit decyphering the data from 'd302_monitor', but it's still a work in progress.

Just a short addition here, in case it can help someone to go further.

duckwc avatar Feb 07 '24 20:02 duckwc

Very nice ! Did you see any chance to directly talk to the coffee machine, through its local IP ? Mine seems to have an open http port.

Philmo67 avatar Feb 07 '24 23:02 Philmo67

Very nice ! Did you see any chance to directly talk to the coffee machine, through its local IP ? Mine seems to have an open http port.

As a mater of fact, there is some important local trafic, which seems to include all actions sent to the machine. I was able to capture a bit of it, but everything is encrypted using some RSA keys shared at some point.

The traffic is based on Ayla solution, so I think that understanding the Ayla protocol will be necessary to move further: https://content.aylanetworks.com/Archive/Ayla-Mobile-SDK-Developers-Guide.pdf

To be followed

duckwc avatar Feb 09 '24 09:02 duckwc

Well, another update on my debug. I was finally able to start a local LAN discussion with the machine.

Here is basically the process to do so:

  1. Getting the LAN key shared between the app and the machine. It is saved on the Ayla cloud and can be retrieved using the command https://ads-eu.aylanetworks.com/apiv1/devices/AC00xxxxxxx15/connection_config.json

  2. Establishing a LAN session starts by exchanging keys required to secure it.

  • Sending the command http://xxx/local_reg.json to the machine will trigger this exchange.
  • The machine will contact the described service on /local_lan/key_exchange.json endpoint and send values random_1 and time_1, waiting in return a random_2 and time_2 value.
  1. The exchanged values are used to generate several keys:
  • appCryptoKey (app AES key)
  • appSignKey (app HMAC signing key)
  • appIvSeed (1st IV for AES)
  • devCryptoKey (machine AES key)
  • devSignKey (machine HMAC signing key)
  • devIvSeed (1st IV for AES)
  1. If the keys are correctly received by the machine, next time it receives a call on http://xxx/local_reg.json, it'll interrogate the app on /local_lan/commands.json. My guess is that commands are stacked on the app and sent to the machine that way.
  • Message looks like this

{"seq_no":2,"data":{"properties":[{"property":"base_type":"string","dsn":"AC00xxxxxxx15","name":"device_connected","value":"1707570437"}}]}}

  • The message is then encrypted using AES/CBC with the appCryptoKey (and appIvSeed for the 1st message), signed, and send back to the machine, looking like this:

{"enc":"vjpe0AE/GkckjJCYhO3pLzh/arNEAHoIpRT1otoIODWQy3JrYcYMtD/N59FkYf3q/KJWpY3iKJGoku63T/gEE5VQjPN9TWsjKRCX7toHlL7aPV3puSglnh9p/748riKWquCckQp1RHl3J9/+VZZlHNbWRLdibW4EB8dhUKd5ylvFO5kYRy8XMhBhY1zKTIG6","sign":"kJfMtwuxiJ7WNTd0mJjhVoLgfpihWpVVSZGbF9ev72A="}

This is how far I have been able to push for now. It looks also like updates to the cloud are also transiting through the app, or are at least initiated by the app. I spent a lot of time figuring out the way the encryption was working.

I should now be able to debug further and send commands to the machine...

To be continued...

duckwc avatar Feb 11 '24 23:02 duckwc

Ahaa, getting closer to the Graal...

So, I found that the commands.json call could return 3 types of messages to command the machine:

{"seq_no":x,"data":{"cmds":[{"cmd":{"cmd_id":2,"data":"","method":"GET","resource":"property.json?name=d302_monitor","uri":"/local_lan/property/datapoint.json"}}]}}

This asks the machine to return the value of the requested property (d302_monitor here)

{"seq_no":x,"data":{"properties":[{"property":{"base_type":"string","dsn":"AC000W030661915","name":"device_connected","value":"1707839356"}}]}}

I don't know yet the usage for this command

{"seq_no":x,"data":{"properties":[{"property":{"base_type":"string","dsn":"AC000W030661915","name":"data_request","value":"xxxxxxxxx\n"}}]}}

This asks the machine to exectute the command xxxxxxxxx.

It seems that these commands follow the same syntax as bluetooth signals. I was able to wake up the machine by sending 'DQeEDwIBVRJly498\n' string (0d 07 84 0f 02 01 55 12 65 cb 8f 7c) and the bluetooth code I found was [0x0d, 0x07, 0x84, 0x0f, 0x02, 0x01, 0x55, 0x12], so quite close with a short trailing...

I'll now need to wait a bit to get thirsty in order to try the different coffees and get the values. :)

duckwc avatar Feb 13 '24 21:02 duckwc

Nice progress @duckwc !

shaarkys avatar Feb 13 '24 21:02 shaarkys

Well here is a small python code that I wrote. webserver.txt It's basically a small HTTP Requests Handler that includes the following features:

  • Gets local refresh_key or asks the user to first identify to Delonghi's IDP to retrieve it
  • Uses the refresh key to get the LAN key used for encrypting the local traffic between the server and the coffee machine
  • Opens a web server locally (http://127.0.0.1:10280/xxx). The web server should be on the same network as the machine and handles communication with it. I also implemented a couple endpoints to control it:
    • http://127.0.0.1:10280/get_monitor triggers a request to get the content of d302_monitor and decodes alarm and switches flags from the returned value
    • http://127.0.0.1:10280/turn_on sends the signal to turn the machine on

I'm not a developper, so the code does the job, but is awful. Sorry about it...

If someone with better coding skill than me is interested to take over from here, that would be very nice.

duckwc avatar Feb 14 '24 12:02 duckwc

Well, it seems the turn on signal doesn't work anymore. I'll need to figure out the trailing I guess... The core of the exchange won't probably change a lot...

duckwc avatar Feb 14 '24 14:02 duckwc

If any testing is needed I’m happy to run some tests. Unfortunately I have an iOS device so tracing is a pain.

behold81 avatar Feb 14 '24 23:02 behold81

Wow! It's great that there are people who also want to be able to operate our coffee machine!

I really want to help, but I don't know how

oksibutch avatar Feb 15 '24 03:02 oksibutch

Well, it seems the turn on signal doesn't work anymore. I'll need to figure out the trailing I guess... The core of the exchange won't probably change a lot...

Little addition: the power_on signal bits are 0x0d07840f02015512, and the trailing 0x65cb8f7c seems to simply be the current timestamp.

duckwc avatar Feb 16 '24 13:02 duckwc

For those interested in testing and completing the tool, I created a small repository here: https://github.com/duckwc/ECAMpy It's ugly and messy, but functionnal. All you need to do is edit the SRV_IP in webserver.py and set your IP in the local network. You may need to send the commands several times before the machine gets it and executes it. Feel free to send me any comment about new findings or issues.

duckwc avatar Feb 25 '24 13:02 duckwc