Join AeonLabs Discord Server here.

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.
Functionalities available:
- Control water temperature;
- Low water detection in the water tank;
- Order a cup of coffee (with the possibility to control the quantity of coffee in a cup);
- RGB LED;
- Magnetic Buzzer;
- This PCB can be powered using 220V AC or regular 5V DC.
- Control of the coffee machine is made using a 220V relay or alternatively any other 3.3V switch.
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
Available Commands
/start
: to view available commands on the Coffee Machine I have at my home/coffee
: to "buy" Miguel a cup of Coffee/tea
: to "buy" Miguel a cup of Tea/cappuccino
: to buy a cup of Cappuccino/decaf
: to buy a cup of decaf. coffee/accept
[short/normal/long]
:: to accept an offer made by another person/status
: to view the current status of the Philips Senseo Coffee Machine
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.
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
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
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