Lewis Freiberg
Published © MIT

Pay Per Coffee

A hacked Nespresso machine that requests cryptocurrencies in order to dispense coffee.

AdvancedWork in progress10 hours10,981
Pay Per Coffee

Things used in this project

Hardware components

ESP32 with E-ink
×1
NEC 5v Relay SMD
×1
240v to 5v Buck Converter
×1

Software apps and online services

IOTA Tangle
IOTA Tangle

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Nespresso Essenza Mini - Faceplate

This print replaces the faceplate on the Essenza Mini. It houses the ESP32 and the display.

Code

ESP32 Main code

C/C++
Use the Arduino IDE to load it into your ESP32 device. It contains the ability to write to a Waveshare E-ink screen, draw a QR code and POST via HTTPS.
///////////////////////////////////////////////////
////////////// IOTA Capsule Coffe Machine /////////
// Uses a ESP32 w/ Eink + IOTA + Nespresso machine
// Build files can be found at the following link
// 
// TO DO:
// - Wallet Implimentation
//
// License: MIT 

// ESP32 Internet Functions
#include <WiFiClientSecure.h>
WiFiClientSecure client;
#include <ArduinoJson.h>
#include <string.h>

// Let's Encrypt root cert
const char* le_root_ca= \
     "-----BEGIN CERTIFICATE-----\n" \
     "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \
     "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
     "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \
     "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \
     "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \
     "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \
     "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \
     "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \
     "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \
     "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \
     "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \
     "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \
     "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \
     "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \
     "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \
     "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \
     "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \
     "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \
     "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \
     "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \
     "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \
     "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \
     "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \
     "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \
     "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \
     "-----END CERTIFICATE-----\n";

// Screen Info
#include <GxEPD.h>
#include <GxGDE0213B72/GxGDE0213B72.h>      // 2.13" b/w
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

// define pins to match your wiring
#define PIN_CS    5
#define PIN_DC    17
#define PIN_RST   16
#define PIN_BUSY  4

GxIO_Class io(SPI, PIN_CS, PIN_DC, PIN_RST);
GxEPD_Class display(io, PIN_RST, PIN_BUSY);  

// Fonts for text
#include <Adafruit_GFX.h>    // Core graphics library
#include <Fonts/FreeSans9pt7b.h>

// QR code lib
#include "qrcode.h"

// Name PINs for Coffee Machine control
int emulateButton = 33;  // Connected to the touch button input
int readLed = 32;    // Connected to the LED powerline

// WiFi Credentials - MODIFY MEEEE
const char* ssid = "wifiName";
const char* password =  "password";
const char* server = "nodes.devnet.thetangle.org";  // Server URL
const char* address = "VJ9RKXDAXYXIZWXITZJSQFSUSRDQWXPPJDNKDKEEYGACPDIH9PKDCQVJVOCDWCFSGZTLYOHSYTVKCDUU9";
const char* addressChecksum = "VJ9RKXDAXYXIZWXITZJSQFSUSRDQWXPPJDNKDKEEYGACPDIH9PKDCQVJVOCDWCFSGZTLYOHSYTVKCDUU9OVLGVBV9B";
int paymentsAtAddress = 0;
int iterator = 0;

void setup() {
  /// Start Serial
  Serial.begin(115200);
  Serial.println();

  /// Setup pins for monitoring and control
  pinMode(readLed, INPUT);
  pinMode(emulateButton, OUTPUT);

  /// Setup Display
  Serial.println("Display setup...");
  display.init(115200); // enable diagnostic output on Serial
  display.setRotation(45);
  display.setFont(&FreeSans9pt7b);
  display.setTextColor(GxEPD_BLACK,GxEPD_WHITE); // Set text to plot foreground and background colours
  display.setTextSize(0.5);

  /// Start WiFi Routine
  display.setCursor(40, 60);
  display.println("Connecting to WiFi...");
  display.update();
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  // Set Cert
  client.setCACert(le_root_ca);
  Serial.println("Setup done");

}

void loop() {
    // Intialise loop by re/setting counters
    fetchTransactions();
    iterator = paymentsAtAddress;
    
    // Draw payment request.
    paymentRequest();

    // Check for payment
    while (paymentsAtAddress == iterator) {
        // Wait 5 seconds before checking
       delay(5000);
       fetchTransactions();
    }

  // Dispense coffee
  dispenseCoffee();
}

void paymentRequest(){
  // Clear screen
  display.fillRect(0, 0, GxEPD_HEIGHT, GxEPD_WIDTH, GxEPD_WHITE);

  // Start time
  uint32_t dt = millis();

  byte box_x = 5;
  byte box_y = 5;
  byte box_s = 3.5;
  byte init_x = box_x;
  
  // Create the QR code
  QRCode qrcode;
  uint8_t qrcodeData[qrcode_getBufferSize(5)];
  qrcode_initText(&qrcode, qrcodeData, 5, ECC_LOW, addressChecksum);
  
  // Delta time
  dt = millis() - dt;
  Serial.print("QR Code Generation Time: ");
  Serial.print(dt);
  
  for (uint8_t y = 0; y < qrcode.size; y++) {
    // Each horizontal module
    for (uint8_t x = 0; x < qrcode.size; x++) {
      if(qrcode_getModule(&qrcode, x, y)){
        display.fillRect(box_x, box_y, box_s, box_s, GxEPD_BLACK);
      } else {
      }
      box_x = box_x + box_s;
    }
    box_y = box_y + box_s;
    box_x = init_x;
  }
  
  // Add text
  display.setCursor(130, 40);
  display.println("Send 100i to");
  display.setCursor(130, 60);
  display.println("the address");
  display.setCursor(130, 80);
  display.println("for a coffee");

  // Update Display
  display.update();
}

void fetchTransactions() {
    // Make a HTTP request:
  char* data1="{\"command\":\"findTransactions\",\"addresses\":[\"";
  char* data2="\"]}";
  char payload[129] = "";
  strcat(payload, data1);
  strcat(payload, address);
  strcat(payload, data2);
  
  Serial.println("\nFetching Transactions...");
  if (!client.connect(server, 443))
    Serial.println("Connection failed!");
  else {
    client.println("POST / HTTP/1.1");
    client.println("Host: nodes.devnet.thetangle.org");
    client.println("X-IOTA-API-Version: 1");
    client.println("Content-Type: application/json");
    client.println("Content-Length: 129");
    client.println();
    client.println(payload);
    client.println("Connection: close");
    client.println();

    while (client.connected()) {
      String line = client.readStringUntil('\n');
      if (line == "\r") {
        // Headers Received
        break;
      }
    }
    // if there are incoming bytes available
    // from the server, read them and print them:
    while (client.available()) {
      // Parse JSON object
      DynamicJsonDocument doc(1024);
      DeserializationError error = deserializeJson(doc, client);
      if (error) {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
        return;
      }
      paymentsAtAddress = doc["hashes"].size();

      String output = "";
      serializeJson(doc, output);
      Serial.println(output);
      client.stop();
    }
  }
}


void dispenseCoffee(){

  // Display message to screen
  display.fillRect(0, 0, GxEPD_HEIGHT, GxEPD_WIDTH, GxEPD_WHITE);
  display.setCursor(50, 50);
  display.println("Payment Received.");
  display.update();
  delay(1000);

  // Check that boiler is hot
  if(digitalRead(readLed) == LOW){
      /// MANS NOT HOT
      display.fillRect(0, 0, GxEPD_HEIGHT, GxEPD_WIDTH, GxEPD_WHITE);
      display.setCursor(60, 50);
      display.println("Heating up water.");
      display.update();

      // Loop until it wakes up 
      while (digitalRead(readLed) == LOW) {
        // Eumlate someone booting the machine up from a cold state.
        digitalWrite(emulateButton, HIGH);
        Serial.println("Pushing HIGH");
        delay(450);
      }
      digitalWrite(emulateButton, LOW);

      // Wait for the boiler to heat up
      delay(15000); // Might fix this to detect when LED stops flashing and goes to 100% duty cycle
  }
  
  // Start dispensing coffee
  display.fillRect(0, 0, GxEPD_HEIGHT, GxEPD_WIDTH, GxEPD_WHITE);
  display.setCursor(50, 50);
  display.println("Dispensing Coffee.");
  display.update();

  // Emulate button press.
  digitalWrite(emulateButton, HIGH);
  delay(1000);
  digitalWrite(emulateButton, LOW);

  delay(5000);
  display.fillRect(0, 0, GxEPD_HEIGHT, GxEPD_WIDTH, GxEPD_WHITE);
  display.setCursor(60, 50);
  display.println("Enjoy your Coffee!");
  display.setCursor(30, 70);
  display.println("Please remove your pod.");
  display.update();
  delay(8000);

}

Credits

Lewis Freiberg

Lewis Freiberg

4 projects • 21 followers
Director of Ecosystem at IOTA. Tinkerer, developer and general fire hazard.

Comments