Skip to content

Instantly share code, notes, and snippets.

@farminf
Last active March 16, 2024 19:04
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save farminf/94f681eaca2760212f457ac59da99f23 to your computer and use it in GitHub Desktop.
Save farminf/94f681eaca2760212f457ac59da99f23 to your computer and use it in GitHub Desktop.
Hacking Nespresso Expert Machine to brew coffee using custom applications via bluetooth

Nespresso Expert machine Hack (Bluetooth)

Nespresso Expert machine has the Bluetooth ability which officially can be used only by Nespresso mobile app and it does not offer any API for 3rd party applications and services. Moreover, the Bluetooth services and characteristics are not documented and easy to use by the other Bluetooth libraries plus there is no documentation for the Bluetooth packets payload that need to be sent or received.

However, after searching a lot and sniffing the packets for a couple of days, I've been able to hack the machine and write the small nodejs application using noble and express to control and monitor the machine with Rest API exposed by express through Bluetooth connection. As I did this application for my ex-company and they are still using it for their demo I cannot share the code but I'm going to explain how it works.

Thanks to this repo: https://github.com/fsalomon/nespresso-expert-ble and also this nice medium post https://medium.com/@urish/reverse-engineering-a-bluetooth-lightbulb-56580fcb7546 that basically helped me to understand how I need to sniff the packets.

Sniffing

first I have installed the Nespresso app on my mobile and I connected to the machine and used the application. Then, using the post I mentioned above, I sniffed the packets and opened them in the Wireshark to analyze them.

Authentication

I notice that everytime on the app we start communicating with the machine (brew or monitor or just oppening machine tab) it starts the conversation with a packet that has "8" at the start of its value and has 16 characters (mine was 85c55bc324a4170b) and it is writting on the 4th characteristic of the service 06aa1910f22a11e39daa0002a5d5c51b. note: only one mobile can connect to the expert machine and everytime that new mobile connects (which needs the device reset) the authentication packet will change.

I have used noble library for ble communication. I mention some hints ;) Authentication will be done by writing to 4th characteristic of the first service Buffer.from("85c55bc324a4170b", "hex"). after writing when you read the 5th characteristic, the data should be "2" which means authenticaton was successful.

Communication

I don't know why, and dont ask me please but before sending the brew command you need to write this Buffer.from("01100800000200c8000000", "hex") on the 4th charac of the second service ("06aa1920f22a11e39daa0002a5d5c51b") and then inside the callback, you write this Buffer.from("03050704000000000101", "hex") which is low temp espresso. Now you machine should start brewing... yayyyy

below I leave all of my notes in case it can be useful

Brewing Types

Here are my observations:

  • 0305070400000000 00 00 medium ristretto
  • 0305070400000000 01 01 low espresso
  • 0305070400000000 02 02 high lungo
  • 0305070400000000 01 04 low hot water
  • 0305070400000000 01 05 low americano
  • 0305070400000000 01 07 low probably cleaning mode (all lights turning on together)
  • 03060102 would stop the brewing (not always)

Machine Alarms (Errors)

On the second service, on 0th charac read normally is like 0:64 1:9 2:13 3:64 4:128 5:0 6:255 7:255.

  • 0: 64 is ok, 65: no water
  • 1: 64 with 132 is brewing or busy , 64 with 2 ready , 64 with 66/67/7x :full disposal or no disposal on second service, on 4th charac read normally is like: 0:129 1:16 2:1 3:32 4:0 5:0 6:0 7:0 8:0 9:0 10:0 11:0 12:0 13:0 14:0 15:0 16:0 17:0 18:0 19:0

For understanding slide error, read should be done after command of brew

  • 1: 195 slider error, 129 ok, 131 busy brewing
@n2k
Copy link

n2k commented Apr 7, 2021

Since btsnooz.py would always give me a pcap file with malformed/too short packets I took a look at the Nespresso android app and it was fairly easy to extract the pairing key code stuff, so I thought I'd share it here if someone else stumbles on this gist :)

This is how it looks in Wireshark with malformed packets,
Screenshot 2021-04-07 at 13 04 17

To get the current pairing code you'll need a rooted android device so you can access the Nespresso app database file. it will also work with an android device that has TWRP recovery mode, so you can mount the filesystem in recovery mode and get the database file. If you don't have access to a rooted android device you can factory reset the machine, generate a new pairing key and use that for pairing.

To get the current pairing key from a rooted android device:

#> adb root
#> adb shell
#android-device> sqlite3 /data/data/com.nespresso.activities/databases/nespresso_app_fusion.db 'SELECT pairing_key FROM MyMachines;' 

92d51b90e2bea6cd77f3fd7107907e2c

The key needs to be transformed to a format that the Nespresso machine accepts, this can be done with this java program,

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

public final class PairingKey {
  private final ByteBuffer data = ByteBuffer.allocate(8);

  public PairingKey(String str) {
    getBufferFromByteArray(prepareHashForPairing(str));
  }

  public static PairingKey from(String str) {
    if (str != null && str.length() == 32) {
      return new PairingKey(str);
    }
    throw new InvalidParameterException("Pairing Key hash has to be valid");
  }

  private void getBufferFromByteArray(byte[] bArr) {
    this.data.put((byte) (((bArr[0] & 240) >> 4) | 128));
    this.data.put((byte) (((bArr[0] & 15) << 4) | ((bArr[1] & 240) >> 4)));
    this.data.put((byte) (((bArr[1] & 15) << 4) | ((bArr[2] & 240) >> 4)));
    this.data.put((byte) (((bArr[2] & 15) << 4) | ((bArr[3] & 240) >> 4)));
    this.data.put((byte) (((bArr[3] & 15) << 4) | ((bArr[4] & 240) >> 4)));
    this.data.put((byte) (((bArr[4] & 15) << 4) | ((bArr[5] & 240) >> 4)));
    this.data.put((byte) (((bArr[5] & 15) << 4) | ((bArr[6] & 240) >> 4)));
    this.data.put((byte) (((bArr[7] & 240) >> 4) | ((bArr[6] & 15) << 4)));
    this.data.rewind();
  }

  private static byte[] prepareHashForPairing(String str) {
    String substring = (str + "0").substring(0, 16);
    int length = substring.length();
    byte[] bArr = new byte[(length / 2)];
    for (int i = 0; i < length; i += 2) {
      bArr[i / 2] = (byte) ((Character.digit(substring.charAt(i), 16) << 4) + Character.digit(substring.charAt(i + 1), 16));
    }
    return bArr;
  }

  private static String bytesToHex(byte[] bArr) {
    char[] cArr = new char[(bArr.length * 2)];
    for (int i = 0; i < bArr.length; i++) {
      int b =  bArr[i] & 255;
      int i2 = i * 2;
      char[] cArr2 = "0123456789ABCDEF".toCharArray();
      cArr[i2] = cArr2[b >>> 4];
      cArr[i2 + 1] = cArr2[b & 15];
    }
    return new String(cArr);
  }

  public ByteBuffer getData() {
    return this.data;
  }

  public String getHex() {
    return bytesToHex(this.data.array());
  }

  public static String generatePairingKey() {
    String uuid = UUID.randomUUID().toString();
    try {
      MessageDigest instance = MessageDigest.getInstance("SHA-1");
      instance.reset();
      instance.update(uuid.getBytes("UTF-8"));
      return new BigInteger(1, instance.digest()).toString(16).substring(0, 32);
    } catch (UnsupportedEncodingException | NoSuchAlgorithmException unused) {
      throw new InvalidParameterException("Problem occurred when trying to generate Hash");
    }
  }

  public static void main(String... args) {
    // adb root && adb shell "sqlite3 /data/data/com.nespresso.activities/databases/nespresso_app_fusion.db 'SELECT pairing_key FROM MyMachines;'"
    String currentPairingKey = (args.length > 0) ? args[0]:"92d51b90e2bea6cd77f3fd7107907e2c";

    String newKey = PairingKey.generatePairingKey();

    System.out.printf("\nNew pairing key: %s", newKey);
    System.out.printf("\nNew pairing key hex: %s\n", PairingKey.from(newKey).getHex());

    System.out.printf("\nCurrent key: %s", currentPairingKey);
    System.out.printf("\nCurrent key hex: %s\n", PairingKey.from(currentPairingKey).getHex());
  }
}

This java program can also generate a new pairing key, just save it as pairing_key.java and you can run it with

#> java pairing_key.java 92d51b90e2bea6cd77f3fd7107907e2c

New pairing key: d1e357a7c12f17615126e88445ef9c1b
New pairing key hex: 8D1E357A7C12F176

Current key: 92d51b90e2bea6cd77f3fd7107907e2c
Current key hex: 892D51B90E2BEA6C

Now good luck with hacking on your Nespresso machine :)

@bulldog5046
Copy link

8D1E357A7C12F176

Thank you for this.

I wondered if you happened to explore the pairing process any further? I'm without an Android device to sniff the BT packets from and have made progress but seem to be missing a piece of the puzzle.

I can pair with a new auth code by setting the txLevel to Low as per the decompiled source

device.char_write('06aa3a61-f22a-11e3-9daa-0002a5d5c51b', binascii.unhexlify('01'), wait_for_response=True)

And then sending the new AuthKey. This will let me read all the sensors and send a brew command. However, i can only send the brew command once before the machine has to be power cycled to work again.

Feels like there is a missing step as the N logo on the machine doesn't illuminate which makes me think the process isn't complete. Do you know of any further command to tell the machine to save the pairing? or any logs from when you looked at this previously that may help track down the issue?

With the vendor app now completely useless it would be good to establish a method for pairing the devices without relying on legacy app versions.

Thanks

@bulldog5046
Copy link

Turns out it wasn't a pairing issue. Although the N still doesn't light up.

I implemented the error handling for cmd_response and it was because the lid wasn't being cycled between attempts, which is why it worked after a power cycle.

Thanks anyway for your previous efforts. Been helpful getting this working.

@n2k
Copy link

n2k commented Nov 6, 2023

Unfortunately I don't have this machine anymore, I suggest you try to find the Android app .apk file online, unzip it an run the code through any of the online Java or android decompilers

@spilz87
Copy link

spilz87 commented Dec 9, 2023

Hello
Thanks for your work

I’m in the same situation than @bulldog5046
To be more precise, Nespresso forced to update the app and I can not use the old one anymore :(

just to know if I understand well :

  • if I reset my Nespresso machine the old machine, the pairing will be canceled
  • can I paire my diy system through ble by using a random or a defined (like 8D1E357A7C12F176) pairing key ?
  • if it has to be defined, I didn’t get how to generate it.

Thanks in advance for your help
Have a good day

@bulldog5046
Copy link

bulldog5046 commented Dec 9, 2023

Hello Thanks for your work

I’m in the same situation than @bulldog5046 To be more precise, Nespresso forced to update the app and I can not use the old one anymore :(

just to know if I understand well :

  • if I reset my Nespresso machine the old machine, the pairing will be canceled
  • can I paire my diy system through ble by using a random or a defined (like 8D1E357A7C12F176) pairing key ?
  • if it has to be defined, I didn’t get how to generate it.

Thanks in advance for your help Have a good day

See my Git repo for detailed process:

https://github.com/bulldog5046/ha_nespresso_integration/blob/main/nespresso.py#L217

But in short:

  • Yes
  • Either/or
  • It's just a hex key derived from a random source. There's nothing special about it other than the length

@spilz87
Copy link

spilz87 commented Dec 9, 2023

@bulldog5046
Thanks for your fast reply 😁

I factory reset the machine
I sent the 01 value
I sent the random key (good length)
I got the 02 value

so if I get it well, the authentification is ok, isn’t it ?
if yes, I will work on the next steps later :D

thanks again

@realfox81
Copy link

Ist there a way to hack the Firmware to use make every time big cup on button press without a cup inserted. Dolce Gusto pods seems to work fine, bit the Barcode Ring is needed. https://youtu.be/509bpMVuOO4?si=0NtsxGFbFIctIIyn

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment