The Problem

Nowadays there are countless coffee machines however with limited to no smart functionality. With this new way of doing work remotely buying a cup of coffee for a colleague, friend or family is not possible.

The idea

This Home Automation Smart Coffee Machine Add-on was designed and prototyped by me using KiCad. Is of easy installation on any home or office coffee machine, and enables any vintage coffee machine connectivity to the internet (or a personal network).

The PCB uses an ESP32 S3 packed with Bluetooth, BLE, and WiFi compatible with major software vendors such as Apple Home, Google Home, Matter/Zigbee, Home Assistant, and many others.

The custom firmware coded includes the functionality of someone sending for a cup of coffee (or other) request from the Telegram Messenger App. In return, it sends a receipt as proof a coffee was indeed brewed on the coffee machine.

Join the WhatsApp group here.

image.png


Philips Senseo Coffee Machine

Functionalities available:

OEM Firmware code

The OEM version of the firmware code can be found in the folder firmware code. It has by default OTA updates, meaning the smart coffee machine add-on device automatically updates itself when newer updated versions are made available here.

This code uses my own ESP32 C++ class libraries to expedite the development of code for ESP32 microcontrollers. The repository is located here for anyone to use.

This smart device add-on Is able to connect to a WIFI network for handling message requests from the Telegram Messaging App. Below is the full code for the smart_cofee_machine.ino file . The remainder of the code can be found on my repository.

#define uS_TO_S_FACTOR 1000000
//----------------------------------------------------------------------------------------
// Components Testing  **************************************
bool SCAN_I2C_BUS = true;
bool TEST_FINGERPRINT_ID_IC = false;
//----------------------------------------------------------------------------------
#include <math.h>
#include <cmath>
#include "SPI.h"
#include <semphr.h>
#include "esp32-hal-psram.h"
// #include "rom/cache.h"
extern "C"
    {
    #include <esp_himem.h>
    #include <esp_spiram.h>   
    }

// custom includes **********************************
#include "nvs_flash.h"  //preferences lib
// External sensor moeasurements
#include "telegram.h"
TELEGRAM_CLASS* telegram = new TELEGRAM_CLASS();
// custom functions
#include "src/m_file_functions.h"
// Interface class ******************************
#include "src/interface_class.h"
INTERFACE_CLASS* interface = new INTERFACE_CLASS();
#define DEVICE_NAME "Smart Coffee Machine"
// GBRL commands  ***************************
#include "src/gbrl.h"
GBRL gbrl = GBRL();
// Onboard sensors  *******************************
#include "src/onboard_sensors.h"
ONBOARD_SENSORS* onBoardSensors = new ONBOARD_SENSORS();
// unique figerprint data ID
#include "src/m_atsha204.h"
// serial comm
#include <hardwareserial.h>
HardwareSerial UARTserial(0);
#include "src/mserial.h"
mSerial* mserial = new mSerial(true, &UARTserial);
// File class
#include <esp_partition.h>
#include "FS.h"
#include <littlefs.h>
#include "src/m_file_class.h"
FILE_CLASS* drive = new FILE_CLASS(mserial);
// WIFI Class
#include <esp32ping.h>
#include "src/m_wifi.h"
M_WIFI_CLASS* mWifi = new M_WIFI_CLASS();
// Certificates
#include "src/cert/github_cert.h"
// Coffee Machine
#include "coffee_machine.h"
COFFEE_MACHINE_CLASS* coffeeMachine = new COFFEE_MACHINE_CLASS();

/********************************************************************/
#include <bledevice.h>
#include <bleserver.h>
#include <bleutils.h>
#include <ble2902.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
BLECharacteristic *pCharacteristicTX, *pCharacteristicRX;
BLEServer *pServer;
BLEService *pService;
bool BLE_advertise_Started = false;
bool newValueToSend = false;
String $BLE_CMD = "";
bool newBLESerialCommandArrived = false;

SemaphoreHandle_t MemLockSemaphoreBLE_RX = xSemaphoreCreateMutex();
float txValue = 0;
String valueReceived = "";

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
    mWifi->setBLEconnectivityStatus (true);
    mserial->printStr("BLE connection init ", mserial->DEBUG_BOTH_USB_UART);
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_BLUE;
    interface->onBoardLED->statusLED(100, 1);
    String dataStr = "Connected to the Smart Concrete Maturity device (" + String(interface->firmware_version) + ")" + String(char(10)) + String(char(13)) + "Type $? or $help to see a list of available commands" + String(char(10));
    dataStr += String(interface->rtc.getDateTime(true)) + String(char(10)) + String(char(10));
    if (mWifi->getNumberWIFIconfigured() == 0 ) {
        dataStr += "no WiFi Networks Configured" + String(char(10)) + String(char(10));
    }
    //interface->sendBLEstring(dataStr, mserial->DEBUG_TO_BLE);
    }

void onDisconnect(BLEServer* pServer) {
    mWifi->setBLEconnectivityStatus (false);
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_BLUE;
    interface->onBoardLED->statusLED(100, 0.5);
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_RED;
    interface->onBoardLED->statusLED(100, 0.5);
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_BLUE;
    interface->onBoardLED->statusLED(100, 0.5);
    pServer->getAdvertising()->start();
    }
};

class pCharacteristicTX_Callbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
    String txValue = String(pCharacteristic->getValue().c_str());
    txValue.trim();
    mserial->printStrln("Transmitted TX Value: " + String(txValue.c_str()) , mserial->DEBUG_BOTH_USB_UART);
    if (txValue.length() == 0) {
        mserial->printStr("Transmitted TX Value: empty ", mserial->DEBUG_BOTH_USB_UART);
    }
    }

void onRead(BLECharacteristic *pCharacteristic) {
    mserial->printStr("TX onRead...", mserial->DEBUG_BOTH_USB_UART);
    //pCharacteristic->setValue("OK");
    }
};

class pCharacteristicRX_Callbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
    delay(10);
    String rxValue = String(pCharacteristic->getValue().c_str());
    rxValue.trim();
    mserial->printStrln("Received RX Value: " + String(rxValue.c_str()), mserial->DEBUG_BOTH_USB_UART );
    if (rxValue.length() == 0) {
        mserial->printStr("Received RX Value: empty " , mserial->DEBUG_BOTH_USB_UART);
    }
    $BLE_CMD = rxValue;
    mWifi->setBLEconnectivityStatus(true);
    xSemaphoreTake(MemLockSemaphoreBLE_RX, portMAX_DELAY);
        newBLESerialCommandArrived = true; // this needs to be the last line
    xSemaphoreGive(MemLockSemaphoreBLE_RX);
    delay(50);
}

void onRead(BLECharacteristic *pCharacteristic) {
    mserial->printStr("RX onRead..." , mserial->DEBUG_BOTH_USB_UART);
    //pCharacteristic->setValue("OK");
    }
};

// ********************************************************
// *************************  == SETUP == *****************
// ********************************************************
static uint8_t taskCoreZero = 0;
static uint8_t taskCoreOne  = 1;
long int prevMeasurementMillis;

void setup() {
    ESP_ERROR_CHECK(nvs_flash_erase());
    nvs_flash_init();
    // Firmware Build Version / revision ______________________________
    interface->firmware_version = "1.0.0";
    // Serial Communication Init ______________________________
    interface->UARTserial = &UARTserial;
    mserial->DEBUG_TO = mserial->DEBUG_TO_UART;
    mserial->DEBUG_EN = true;
    mserial->DEBUG_TYPE = mserial->DEBUG_TYPE_VERBOSE; // DEBUG_TYPE_INFO;
    mserial->start(115200);
    // .............................................................................
    // .......................... START OF IO & PIN CONFIGURATIO   .................
    // ........................................................................
    // I2C IOs  __________________________
    interface->I2C_SDA_IO_PIN = 9;
    interface->I2C_SCL_IO_PIN = 8;
    // Power Saving ____________________________________
    interface->LIGHT_SLEEP_EN = false;
    // ________________ Onboard LED  _____________
    interface->onBoardLED = new ONBOARD_LED_CLASS();
    interface->onBoardLED->LED_RED = 36;
    interface->onBoardLED->LED_BLUE = 34;
    interface->onBoardLED->LED_GREEN = 35;
    interface->onBoardLED->LED_RED_CH = 8;
    interface->onBoardLED->LED_BLUE_CH = 6;
    interface->onBoardLED->LED_GREEN_CH = 7;
    // ___________ MCU freq ____________________
    interface-> SAMPLING_FREQUENCY = 240;
    interface-> WIFI_FREQUENCY = 80; // min WIFI MCU Freq is 80-240
    interface->MIN_MCU_FREQUENCY = 10;
    interface-> SERIAL_DEFAULT_SPEED = 115200;
    // _____________________ coffee machine ________________________
    /* for IO assignment edit the coffee machine class constructor */
    coffeeMachine->coffeeMachineBrand = "Philips Senseo";
    // _____________________ TELEGRAM _____________________________
    telegram->OWNER_CHAT_ID = "1435561519";
    // Initialize Telegram BOT
    telegram->BOTtoken = "5813926838:AAFwC1cV_QghdZiVUP8lAwbg9mNvkWc27jA";  // your Bot Token (Get from Botfather)
    // ..........................................................................
    // .......................... END OF IO & PIN CONFIGURATION..................
    // ..........................................................................
    interface->onBoardLED->init();
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_RED;
    interface->onBoardLED->statusLED(100, 0);
    //init storage drive ___________________________
    drive->partition_info();
    if (drive->init(LittleFS, "storage", 2, mserial,   interface->onBoardLED ) == false)
    while (1);

    //init interface ___________________________
    interface->init(mserial, true); // debug EN ON
    interface->settings_defaults();
    if ( !interface->loadSettings() ) {
        interface->onBoardLED->led[0] = interface->onBoardLED->LED_RED;
        interface->onBoardLED->led[0] = interface->onBoardLED->LED_GREEN;
        interface->onBoardLED->statusLED(100, 2);
    }
    // init onboard sensors ___________________________
    onBoardSensors->init(interface, mserial);
    if (SCAN_I2C_BUS) {
        onBoardSensors->I2Cscanner();
    }
    if (TEST_FINGERPRINT_ID_IC) {
        mserial->printStrln("Testing the Unique FingerPrind ID for Sensor Data Measurements");
        mserial->printStr("This Smart Device  Serial Number is : ");
        mserial->printStrln(CryptoICserialNumber(interface));
        mserial->printStrln("Testing Random Genenator: " + CryptoGetRandom(interface));
        mserial->printStrln("");
        mserial->printStrln("Testing Sensor Data Validation hashing");
        mserial->printStrln( macChallengeDataAuthenticity(interface, "TEST IC"));
        mserial->printStrln("");
    }
    mserial->printStrln("\nMicrocontroller specifications:");
    interface->CURRENT_CLOCK_FREQUENCY = getCpuFrequencyMhz();
    mserial->printStr("Internal Clock Freq = ");
    mserial->printStr(String(interface->CURRENT_CLOCK_FREQUENCY));
    mserial->printStrln(" MHz");
    interface->Freq = getXtalFrequencyMhz();
    mserial->printStr("XTAL Freq = ");
    mserial->printStr(String(interface->Freq));
    mserial->printStrln(" MHz");
    interface->Freq = getApbFrequency();
    mserial->printStr("APB Freq = ");
    mserial->printStr(String(interface->Freq / 1000000));
    mserial->printStrln(" MHz");
    interface->setMCUclockFrequency( interface->WIFI_FREQUENCY);
    mserial->printStrln("setting Boot MCU Freq to " + String(getCpuFrequencyMhz()) +"MHz");
    mserial->printStrln("");
    // init BLE
    BLE_init();
    //init wifi
    mWifi->init(interface, drive, interface->onBoardLED);
    mWifi->OTA_FIRMWARE_SERVER_URL = "https://github.com/aeonSolutions/AeonLabs-Home-Automation-Smart-Coffee-MAchine-Addon/releases/download/openFirmware/firmware.bin";
    mWifi->add_wifi_network("TheScientist", "angelaalmeidasantossilva");
    mWifi->ALWAYS_ON_WIFI=true;
    mWifi->WIFIscanNetworks();
    mWifi->start(10000,5);
    // check for firmwate update
    mWifi->startFirmwareUpdate();
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_RED;
    interface->onBoardLED->statusLED(100, 0);
    // initialize the coffee machine
    coffeeMachine->init(interface);
    // initialize Telegram
    telegram->init(interface, mWifi, coffeeMachine);
    //Init GBRL
    gbrl.init(interface, mWifi);
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_BLUE;
    interface->onBoardLED->statusLED(100, 0);
    interface->$espunixtimePrev = millis();
    interface->$espunixtimeStartMeasure = millis();
    mWifi->$espunixtimeDeviceDisconnected = millis();
    prevMeasurementMillis = millis();
    mserial->printStr("\nStarting MCU cores... ");

    MemLockSemaphoreBLE_RX = xSemaphoreCreateMutex();
    mserial->printStrln("done. ");
    mserial->printStrln("Free memory: " + addThousandSeparators( std::string( String(e   sp_get_free_heap_size() ).c_str() ) ) + " bytes");
    mserial->printStrln("=========================================================");
    mserial->printStrln("Setup is completed. You may start using the " + String(DEVICE_NAME) );
    mserial->printStrln("Type $? for a List of commands.");
    mserial->printStrln("=======================================================\n");
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_GREEN;
    interface->onBoardLED->statusLED(100, 1);
    }

// ********END SETUP *********************************************************

void GBRLcommands(String command, uint8_t sendTo) {
    if (gbrl.commands(command, sendTo) == false) {
        if ( onBoardSensors->gbrl_commands(command, sendTo ) == false) {
            if (mWifi->gbrl_commands(command, sendTo ) == false) {
                if ( command.indexOf("$") > -1) {
                    interface->sendBLEstring("$ CMD ERROR \r\n", sendTo);
                } else {
                    // interface->sendBLEstring("$ CMD UNK \r\n", sendTo);
                }
            }
        }
    }
}

// ************************************************************
void BLE_init() {
    // Create the BLE Device
    BLEDevice::init(String("LDAD " + interface->config.DEVICE_BLE_NAME).c_str());  // max 29 chars
    // Create the BLE Server
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());
    // Create the BLE Service
    pService = pServer->createService(SERVICE_UUID);
    // Create a BLE Characteristic
    pCharacteristicTX = pService->createCharacteristic(
    CHARACTERISTIC_UUID_TX,
    BLECharacteristic::PROPERTY_NOTIFY
    );
    pCharacteristicTX->addDescriptor(new BLE2902());
    pCharacteristicRX = pService->createCharacteristic(
    CHARACTERISTIC_UUID_RX,
    BLECharacteristic::PROPERTY_READ   |
    BLECharacteristic::PROPERTY_WRITE  |
    BLECharacteristic::PROPERTY_NOTIFY |
    BLECharacteristic::PROPERTY_INDICATE
    );
    pCharacteristicTX->setCallbacks(new pCharacteristicTX_Callbacks());
    pCharacteristicRX->setCallbacks(new pCharacteristicRX_Callbacks());
    interface->init_BLE(pCharacteristicTX);
    // Start the service
    pService->start();
    // Start advertising
    pServer->getAdvertising()->start();
    }

// *********************************************************************************
//******************************* ==  LOOP == *************************************
    unsigned long lastMillisWIFI = 0;
    int waitTimeWIFI = 0;
    String dataStr = "";
    long int eTime;
    long int statusTime = millis();
    long int beacon = millis();
    // adc_power_release()
    unsigned int cycle = 0;
    uint8_t updateCycle = 0;
    // ********************* == Core 1 : Data Measurements Acquisition == ***********

void loop2 (void* pvParameters) {
    //measurements->runExternalMeasurements();
    delay(2000);
}

//************************** == Core 2: Connectivity WIFI & BLE == ******************
void loop(){
    if (millis() - beacon > 60000) {
        beacon = millis();
        mserial->printStrln("(" + String(beacon) + ") Free memory: " + addThousandSeparators( std::string( String(esp_get_free_heap_size() ).c_str() ) ) + " bytes\n", mSerial::DEBUG_TYPE_VERBOSE, mSerial::DEBUG_ALL_USB_UART_BLE);
    }
    if ( (millis() - statusTime > 10000)) { //10 sec
        statusTime = millis();
        interface->onBoardLED->led[1] = interface->onBoardLED->LED_GREEN;
        interface->onBoardLED->statusLED(100, 0.04);
    } else if  (millis() - statusTime > 10000) {
        statusTime = millis();
        interface->onBoardLED->led[1] = interface->onBoardLED->LED_RED;
        interface->onBoardLED->statusLED(100, 0.04);
    }

    // .............................................................................
    // disconnected for at least 3min
    // change MCU freq to min
    if ( mWifi->ALWAYS_ON_WIFI == false && mWifi->getBLEconnectivityStatus() == false && ( millis() - mWifi->$espunixtimeDeviceDisconnected > 180000) && interface->CURRENT_CLOCK_FREQUENCY >= interface->WIFI_FREQUENCY) {
    mserial->printStrln("setting min MCU freq.");
    btStop();
    //BLEDevice::deinit(); // crashes the device
    WiFi.disconnect(true);
    delay(100);
    WiFi.mode(WIFI_MODE_NULL);
    interface->setMCUclockFrequency(interface->MIN_MCU_FREQUENCY);
    mWifi->setBLEconnectivityStatus(false);
    interface->onBoardLED->led[0] = interface->onBoardLED->LED_RED;
    interface->onBoardLED->led[1] = interface->onBoardLED->LED_GREEN;
    interface->onBoardLED->statusLED(100, 2);
    }
    // ..............................................................................
    // Ligth Sleeep
    eTime = millis() - prevMeasurementMillis;
    if ( mWifi->getBLEconnectivityStatus() == false && interface->LIGHT_SLEEP_EN) {
        mserial->printStr("Entering light sleep....");
        interface->onBoardLED->turnOffAllStatusLED();
        //esp_sleep_enable_timer_wakeup( ( (measurements->config.MEASUREMENT_INTERVAL - eTime) / 1000)  * uS_TO_S_FACTOR);
        delay(100);
        esp_light_sleep_start();
        mserial->printStrln("wake up done."); 
    }
    prevMeasurementMillis = millis();
    // .....................................................................
    // Telegram
    telegram->runTelegramBot();
    // .............................................................................
    if (mserial->readSerialData()){
        GBRLcommands(mserial->serialDataReceived, mserial->DEBUG_TO_USB);
    }
    // ..............................................................................
    if (mserial->readUARTserialData()){
        GBRLcommands(mserial->serialUartDataReceived, mserial->DEBUG_TO_UART);
    }
    // ..............................................................................
    if (newBLESerialCommandArrived){
        xSemaphoreTake(MemLockSemaphoreBLE_RX, portMAX_DELAY);
        newBLESerialCommandArrived=false; // this needs to be the last line
        xSemaphoreGive(MemLockSemaphoreBLE_RX);
        GBRLcommands($BLE_CMD, mserial->DEBUG_TO_BLE);
    }
    //................................................................................
    // OTA Firmware
    if ( mWifi->forceFirmwareUpdate == true )
        mWifi->startFirmwareUpdate();
    // ---------------------------------------------------------------------------
    if (millis() - lastMillisWIFI > 60000) {
        xSemaphoreTake(interface->MemLockSemaphoreCore2, portMAX_DELAY);
        waitTimeWIFI++;
        lastMillisWIFI = millis();
        xSemaphoreGive(interface->MemLockSemaphoreCore2);
    }
}
// -----------------------------------------------------------------

Try it right now on Telegram

Is now #official. Anyone can "buy me a coffee" on Telegram. Just start a conversation with MiguelCoffeeMachineBot here and send a message /start to view available commands. (office hours only , CET)

Example of a cup of coffee request made on Telegram

Example of a cup of coffee request made on Telegram

Available Commands

Try it out is free and is #FUN. No money is asked! Great for a meeting or a conversation online. No excuses not to have a cup of coffee with colleagues while working remotely. ( online during office hours, CET )

Testing the firmware code

Brewing the very first coffee request on the Philips #senseo coffee machine. How #cool 😎 😍 is that? And testing the cup/mug presence sensor. I've used Arduino Studio 1.8 and also MS Visual Code for coding the firmware. For debugging, I used the coolTerm Windows app for UART 2 USB serial communication.

Brewing the very first coffee request on the Philips #senseo coffee machine

Mod Installation Overview

To make this work it was necessary to install a magnetic sensor behind the water tank and also a DS18b20 temperature sensor on the white boiler. The water pump is activated using the already-existent Philips PCB electronics.

back of rhe Philips senseo coffee machine

back of rhe Philips senseo coffee machine

Below is possible to see in greater detail the installation of a cup/mug sensor. I utilized the VL6180x sensor which also allows measurement while filling the cup. Short, medium, and full cup options are possible with this smart add-on.

Detail of the cup/mug sensor

Detail of the cup/mug sensor

Data Usage Statistics

I've created a Dashboard on Adafruit IO with some simple usage statistics check it out here.

Compatibility

  • Apple Home
  • Google Home
  • Home Assistant
  • Matter / Zigbee