ESP smart сoffee machine Philips 5400 Series

The good thing is, you got to the point of using a lambda to introduce C code into your ESPHome. Now it’s just a matter of defining the proper mask bit variables and moving the code out of the 5400_sensor.h. For a good example of bit manipulation, review Samuel Seib’s Wiegand component in the ESPHome GitHub

I’m not a programmer and I don’t know esphome well, I use mostly ready-made options. But since I really want to integrate my coffee machine into the Home Assistant, I put all my efforts to what I can. Now I decided to switch to byte search and found the following. I checked and it works. But I can’t find the commands to control. Later I will describe how I found these bytes. If someone undertakes to write code, I will be only glad

And also took aim at these bytes. I think it reflects what kind of drink we are preparing. In order for me to understand this, I will prepare different drinks and compare

Here is my code for ESPHome
substitutions:
  board_name: ESP32 Smart Coffee Philips
  node_name: esp32-smart-coffee-philips

esphome:
  name: ${node_name}
  friendly_name: esp32-smart-coffee-philips
  comment: ESP32 Smart Coffee Philips

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: on
  reboot_timeout: 10min
  ap:
    ssid: ESP SmartCoffeePhilips
    password: !secret ap_esp_password

captive_portal:

web_server:
  port: 80

logger:
  level: DEBUG
  baud_rate: 0

ota:
  password: "esphome"

api:
  encryption:
    key: !secret api_key


external_components:
  - source: github://TillFleisch/ESPHome-Philips-Smart-Coffee@main


uart:
 - id: uart_mainboard
   rx_pin: GPIO3
   tx_pin: GPIO1
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: TX
     dummy_receiver: false


 - id: uart_display
   rx_pin: GPIO16
   tx_pin: GPIO17
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: TX
     dummy_receiver: false
     sequence: 
      - lambda: |-
          UARTDebug::log_hex(direction, bytes, ':');
          //AA:AA:AA:B0
          if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x06 && bytes[9]==0x00) { 
            id(idWater).publish_state("There is water");
            id(idCoffeeGroundsContainer).publish_state("Inserted");
            id(idMakingCoffee).publish_state("Choose a drink");
            }
          //AA:AA:AA:B0 "Took out the container with water"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x0E && bytes[9]==0x40) { id(idWater).publish_state("There is no water"); }
          //AA:AA:AA:B0 "Removed the pallet"  
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x0E && bytes[9]==0x80) { id(idCoffeeGroundsContainer).publish_state("Retrieved"); }

          //AA:AA:AA:B0 "The pallet and the container with water were taken out"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x0E && bytes[9]==0xC0) { 
            id(idWater).publish_state("There is no water"); 
            id(idCoffeeGroundsContainer).publish_state("Retrieved");
            }

          //AA:AA:AA:B0 "Making coffee"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0C && bytes[7]==0x01) { id(idMakingCoffee).publish_state("Enjoy (01)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0E) { id(idMakingCoffee).publish_state("Pause (0E)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0D) { id(idMakingCoffee).publish_state("Grinding grains (0D)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x10) { id(idMakingCoffee).publish_state("Pour milk (10)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x11) { id(idMakingCoffee).publish_state("Pour coffee (11)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x12) { id(idMakingCoffee).publish_state("12"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x13) { id(idMakingCoffee).publish_state("Creating pressure for milk (13)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x14) { id(idMakingCoffee).publish_state("14"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x15) { id(idMakingCoffee).publish_state("15"); }
          
          //AA:AA:AA:B5 "Status 1"
          if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x00) { id(idStatusUnknown1).publish_state("00"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x0B) { id(idStatusUnknown1).publish_state("0B"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xE6) { id(idStatusUnknown1).publish_state("E6"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x80) { id(idStatusUnknown1).publish_state("80"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xCB) { id(idStatusUnknown1).publish_state("CB"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 &&& bytes[10]==0x00 & bytes[11]==0xFF) { id(idStatusUnknown1).publish_state("FF"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xA0) { id(idStatusUnknown1).publish_state("A0"); }
          
          //AA:AA:AA:B5 "Status 2"
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00) { id(idStatusUnknown2).publish_state("00"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x01) { id(idStatusUnknown2).publish_state("01"); }



philips_series_2200:
  display_uart: uart_display
  mainboard_uart: uart_mainboard
  power_pin: GPIO12
  id: philip


text_sensor:
  - platform: template
    id: idWater
    name: "Water"
    update_interval: 10s

  - platform: template
    id: idCoffeeGroundsContainer
    name: "Coffee Grounds Container"
    update_interval: 10s

  - platform: template
    id: idUnknown
    name: "Unknown"
    update_interval: 10s

  - platform: template
    id: idStatusCoffee
    name: "Status Coffee"
    update_interval: 10s

  - platform: template
    id: idMakingCoffee
    name: "Stages of coffee preparation"
    update_interval: 10s


button:
  - platform: restart
    name: Restart
    icon: mdi:restart

  - platform: template
    name: "Turn off" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0xFE, 0x00, 0x00, 0xC8, 0x87, 0x1B, 0x40, 0x55] #AA:AA:AA:FE:00:00:C8:87:1B:40:55

I will share my experience of how I found bytes. this can help others who want to join this project and bring it to the end.

In order to find the necessary bytes, you do not need to press the same buttons many times and time the time, because even if you fix the time of pressing, the execution in the logs will be as we fixed.

The first thing to do is install ESPHome on Windows and write all the data to a log file.

Copy the configuration file from ESPHome to Home Assistant on Windows to the ESPHome folder and run the log entry with the command, specifying our configuration file and the name of the text file for the logs

esphome logs esp32-smart-coffee-philips.yaml > esp32_philips5400_protocol.txt

Next, I copied the text file into Word and specified the key bytes, these are the address and functions

Function Description

AA:AA:AA:93 - display of the selected program on the LCD
AA:AA:AA:90 - a set of recipes
AA:AA:AA:91 - starting the preparation of a drink
AA:AA:AA:B0 - sensors of water container, tray, coffee grounds, coffee preparation statuses
AA:AA:AA:B5 - counters for making coffee and milk drinks
AA:AA:AA:BA - apparently service information, firmware version, date
AA:AA:AA:BB - rarely comes across
AA:AA:AA:FF - data from the control panel
AA:AA:AA:FE - turning off the coffee machine


If we see differences, we insert them into a notepad and compare them, and we see how the bytes change. This is the anomaly, this is our bytes

We look in word, where the countdown started at 07:07:10 96:00:00 / 96:00:02 / 96:00:05 / 96:00:06

This can be used in the sensor if there is a desire to track the status of the preparation of milk foam from 0 to 100%

For example, we are looking for bytes for a container with water and a pallet



Hooray :partying_face:, I found how to operate a coffee machine. To make coffee, we look in the logs for the address and functions that are listed below, this is the command to start making coffee. Start recording in the log, select the parameters we need (strength, amount of water, number of cups) to make coffee and run, then we find the command in the logs. Why is that? Because then we run what we set in the settings, so you can create a lot of different scripts with different parameters.

AA AA AA 93 
AA AA AA 90 
AA AA AA 91

The commands must go strictly in this order, although they may be scattered in the logs, but in ESPHome we specify in this sequence

AA AA AA 93 07 01 01 04 86 E2 E5 55 
AA AA AA 90 08 0A 00 02 00 02 03 00 1E 00 00 00 98 80 81 70 55
AA AA AA 91 09 01 03 A9 02 7B AB 55
This is how the commands in the logs look like



Example of a button to run a command from a log in ESPHome

button:
  - platform: template
    name: "Espresso X1" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x07, 0x01, 0x01, 0x04, 0x86, 0xE2, 0xE5, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x08, 0x0A, 0x00, 0x02, 0x00, 0x02, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x98, 0x80, 0x81, 0x70, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x09, 0x01, 0x03, 0xA9, 0x02, 0x7B, 0xAB, 0x55]

What is implemented in the code?

  1. Status of the water container
  2. Pallet status (coffee grounds container)
  3. Coffee grounds sensor
  4. Coffee preparation statuses
  5. Sensor for the presence of coffee beans
  6. One recipe for making Espresso for 30 ml for the dough, then you can create buttons with recipes yourself
  7. Turning off the coffee machine
Full and working code
substitutions:
  board_name: ESP32 Smart Coffee Philips
  node_name: esp32-smart-coffee-philips

esphome:
  name: ${node_name}
  friendly_name: esp32-smart-coffee-philips
  comment: ESP32 Smart Coffee Philips

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: on
  reboot_timeout: 10min
  ap:
    ssid: ESP SmartCoffeePhilips
    password: !secret ap_esp_password

captive_portal:

web_server:
  port: 80

logger:
  level: DEBUG
  baud_rate: 0

ota:
  password: "esphome"

api:
  encryption:
    key: !secret api_key


external_components:
  - source: github://TillFleisch/ESPHome-Philips-Smart-Coffee@main


uart:
 - id: uart_mainboard
   rx_pin: GPIO3
   tx_pin: GPIO1
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: TX
     dummy_receiver: false


 - id: uart_display
   rx_pin: GPIO16
   tx_pin: GPIO17
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: TX
     dummy_receiver: false
     sequence: 
      - lambda: |-
          UARTDebug::log_hex(direction, bytes, ':');
          //AA:AA:AA:B0
          if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x06 && bytes[9]==0x00) { 
            id(idWater).publish_state("There is water");
            id(idPallet).publish_state("Inserted");
            id(idMakingCoffee).publish_state("Choose a drink");
            id(idCoffeeGroundsContainer).publish_state("Empty");
            id(idGrainTray).publish_state("There are coffee beans");
            }

          //AA:AA:AA:B0 "Availability of coffee beans"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x05) { id(idGrainTray).publish_state("Coffee beans are out"); }

          //AA:AA:AA:B0 "Took out the container with water"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x0E && bytes[9]==0x40) { id(idWater).publish_state("There is no water"); }

          //AA:AA:AA:B0 "Removed the pallet"  
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x0E && bytes[9]==0x80) { id(idPallet).publish_state("Retrieved"); }

          //AA:AA:AA:B0 "The container with coffee grounds is filled"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0E && bytes[9]==0x00) { id(idCoffeeGroundsContainer).publish_state("Oporozhe. container for coffee grounds"); }


          //AA:AA:AA:B0 "The pallet and the container with water were taken out"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[6]==0x0E && bytes[9]==0xC0) { 
            id(idWater).publish_state("There is no water"); 
            id(idPallet).publish_state("Retrieved");
            }

          //AA:AA:AA:B0 "Making coffee"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0C && bytes[7]==0x01) { id(idMakingCoffee).publish_state("Enjoy (01)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0E) { id(idMakingCoffee).publish_state("Pause (0E)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0D) { id(idMakingCoffee).publish_state("Grinding grains (0D)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x10) { id(idMakingCoffee).publish_state("Pour milk (10)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x11) { id(idMakingCoffee).publish_state("Pour coffee (11)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x12) { id(idMakingCoffee).publish_state("12"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x13) { id(idMakingCoffee).publish_state("Creating pressure for milk (13)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x14) { id(idMakingCoffee).publish_state("14"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x15) { id(idMakingCoffee).publish_state("15"); }
          
          //AA:AA:AA:B5 "Status 1"
          if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x00) { id(idStatusUnknown1).publish_state("00"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x0B) { id(idStatusUnknown1).publish_state("0B"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xE6) { id(idStatusUnknown1).publish_state("E6"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x80) { id(idStatusUnknown1).publish_state("80"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xCB) { id(idStatusUnknown1).publish_state("CB"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 &&& bytes[10]==0x00 & bytes[11]==0xFF) { id(idStatusUnknown1).publish_state("FF"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xA0) { id(idStatusUnknown1).publish_state("A0"); }
          
          //AA:AA:AA:B5 "Status 2"
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00) { id(idStatusUnknown2).publish_state("00"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x01) { id(idStatusUnknown2).publish_state("01"); }



philips_series_2200:
  display_uart: uart_display
  mainboard_uart: uart_mainboard
  power_pin: GPIO12
  id: philip


text_sensor:
  - platform: template
    id: idWater
    name: "Water"
    update_interval: 10s

  - platform: template
    id: idCoffeeGroundsContainer
    name: "Coffee Grounds Container"
    update_interval: 10s

  - platform: template
    id: idPallet
    name: "Coffee Pallet"
    update_interval: 10s

  - platform: template
    id: idUnknown
    name: "Unknown"
    update_interval: 10s

  - platform: template
    id: idStatusCoffee
    name: "Status Coffee"
    update_interval: 10s

  - platform: template
    id: idMakingCoffee
    name: "Stages of coffee preparation"
    update_interval: 10s

  - platform: template
    id: idGrainTray
    name: "Grain Tray"
    update_interval: 10s


button:
  - platform: restart
    name: Restart
    icon: mdi:restart

  - platform: template
    name: "Turn off" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0xFE, 0x00, 0x00, 0xC8, 0x87, 0x1B, 0x40, 0x55] #AA:AA:AA:FE:00:00:C8:87:1B:40:55
2 Likes

Extracted recipes for making coffee drinks. Not all work and the program does not always run correctly. Probably because it is necessary to substitute only certain bytes, and not all where there are generated and managed bytes, and probably because I insert the generated bytes, this confuses the program, since the generated bytes go as a counter

In the screenshot, I highlighted bytes that indicate which drink needs to be made, strong, with or without milk

Recipes for making coffee drinks

Drink: Espresso. Grain: Minimum. Coffee: 30 ml. Serving: 1

AA:AA:AA:93:07:01:01:04:86:E2:E5:55
AA:AA:AA:90:08:0A:00:02:00:02:03:00:1E:00:00:00:98:80:81:70:55
AA:AA:AA:91:09:01:03:A9:02:7B:AB:55


Drink: Espresso X1. Grain: Medium. Coffee: 40 ml. Serving: 1

AA:AA:AA:93:25:01:01:8A:14:2B:DE:55
AA:AA:AA:90:26:0A:00:01:00:02:03:00:28:00:00:00:C6:13:4C:6D:55
AA:AA:AA:91:27:01:03:43:69:A8:99:55


Drink: Espresso X1. Grain: Maximum. Coffee: 40 ml. Serving: 1

AA:AA:AA:93:05:01:01:6A:52:66:E6:55
AA:AA:AA:90:06:0A:00:02:00:01:01:00:28:00:00:00:6D:4F:D0:30:55
AA:AA:AA:91:07:01:03:A3:2F:E5:A1:55


Drink: Espresso X2. Grain: Maximum. Coffee: 40 ml. Serving: 2
AA:AA:AA:93:19:01:01:7E:08:5A:F3:55
AA:AA:AA:90:1A:0A:00:02:01:01:01:00:28:00:00:00:8E:EF:68:F5:55
AA:AA:AA:91:1B:01:03:B7:75:D9:B4:55


Drink: Espresso X1. Grain: Extra Shot. Coffee: 90 ml. Serving: 1

AA:AA:AA:93:09:01:01:0E:AB:7C:EF:55
AA:AA:AA:90:0A:0A:00:02:02:01:01:00:5A:00:00:00:86:6D:C9:B6:55
AA:AA:AA:91:0B:01:03:C7:D6:FF:A8:55


Drink: Coffee X1. Grain: Maximum. Coffee: 150 ml. Serving: 1

AA:AA:AA:93:15:01:01:1A:F1:40:FA:55
AA:AA:AA:90:16:0A:00:02:00:02:02:00:96:00:00:00:0B:F3:19:76:55
AA:AA:AA:91:17:01:03:D3:8C:C3:BD:55


Drink: Americano X1. Grain: Maximum. Coffee: 100 ml. Serving: 1

AA:AA:AA:93:21:01:01:56:BC:22:D9:55
AA:AA:AA:90:22:0A:01:02:00:02:02:00:28:00:3C:00:18:D5:AA:00:55
AA:AA:AA:91:23:01:03:9F:C1:A1:9E:55


Drink: Cappuccino. Grain: Maximum. Coffee: 80 ml. Milk: 150 ml

AA:AA:AA:93:1D:01:01:A2:A0:53:F4:55
AA:AA:AA:90:1E:0A:03:02:00:02:03:02:50:00:96:00:75:22:83:5E:55
AA:AA:AA:91:1F:01:03:6B:DD:D0:B3:55


Drink: Latte Macchiato. Grain: Maximum. Coffee: 40 ml. Milk: 200 ml

AA:AA:AA:93:11:01:01:C6:59:49:FD:55
AA:AA:AA:90:12:0A:03:02:00:02:02:02:28:00:C8:00:F8:44:66:1A:55
AA:AA:AA:91:13:01:03:0F:24:CA:BA:55


Drink: Coffee with milk. Grain: Maximum. Coffee: 150 ml. Milk: 90 ml

AA:AA:AA:93:0F:01:01:BC:D7:F1:EB:55
AA:AA:AA:90:10:0A:02:02:00:02:01:02:96:00:5A:00:C6:12:D1:21:55
AA:AA:AA:91:11:01:03:61:F0:4E:B9:55

AA:AA:AA:93:0D:01:01:D2:03:75:E8:55
AA:AA:AA:90:0E:0A:02:02:00:02:01:02:96:00:5A:00:24:2C:F8:C4:55
AA:AA:AA:91:0F:01:03:1B:7E:F6:AF:55

To run any recipe, create a button

An example of launching a Cappuccino drink. Grain: Maximum. Coffee: 80 ml. Milk: 150 ml

button:
#Drink: Cappuccino. Grain: Maximum. Coffee: 80 ml. Milk: 150 ml
  - platform: template
    name: "Cappuccino. Grain: Maximum. Coffee: 80 ml. Milk: 150 ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x07, 0x01, 0x01, 0x04, 0x86, 0xE2, 0xE5, 0x55] 
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x08, 0x0A, 0x03, 0x02, 0x00, 0x02, 0x03, 0x02, 0x50, 0x00, 0x96, 0x00, 0x69, 0x6A, 0xA6, 0xBD, 0x55] 
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x09, 0x01, 0x03, 0xA9, 0x02, 0x7B, 0xAB, 0x55] 

An example of launching an Espresso drink. Grain: Extra Shot. Coffee: 90 ml. Serving: 1

button:
#Drink: Espresso. Grain: Extra Shot. Coffee: 90 ml. Serving: 1
  - platform: template
    name: "Espresso X1 Extra Shot 90ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x09, 0x01, 0x01, 0x0E, 0xAB, 0x7C, 0xEF, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x0A, 0x0A, 0x00, 0x02, 0x02, 0x01, 0x01, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x86, 0x6D, 0xC9, 0xB6, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x0B, 0x01, 0x03, 0xC7, 0xD6, 0xFF, 0xA8, 0x55]
2 Likes

I partially deciphered the protocol of coffee drinks, if in the future I understand what the remaining bytes mean, I will definitely update the information. If you understand what it could be, write. I didn’t quite understand what the 7, 8, 9 and 10 bytes mean. I assume that some of them are responsible for the fortress, grain or ground.

Using a calculator with the selected programmer mode, you can enter HEX and get DEC and find out the volume of coffee and milk


I found a specific oddity in launching the found recipes via ESPHome. If bytes are found in PulseView and pulled out through export to a file, then this will not work.

It’s just that the bytes are clearly visible in PulseView and there is an understanding of what the command should look like, but unfortunately I couldn’t get the recipes to run

In order for the recipes to run, it is necessary to make changes to the coffee parameters and save them to the profile, then it will be possible to catch a working team and it will work.

Step-by-step instructions

  1. Make sure that direction: RX is enabled in the configuration, it is not recommended to use BOTH or TX. If you turn on BOTH or TX, then when you change the coffee settings on the control panel, the changes will not be saved and what is in the ESP logs will start and this also prevents you from getting data.
An example of how to configure debugging to catch recipes for further launch
uart:
 - id: uart_display
   rx_pin: GPIO16
   tx_pin: GPIO17
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: RX
     dummy_receiver: false


 - id: uart_mainboard
   rx_pin: GPIO3
   tx_pin: GPIO1
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: RX
     dummy_receiver: false
  1. We turn on the coffee machine, as it turns off after we fill the firmware on the esp

  2. Enable logging to a file on your computer. This is very important and it must be started before the preparation of the drink is started. Read more about this here.

  3. Select the desired program. For example, choose a cappuccino, then change the strength, volume of coffee and milk by pressing OK until a picture appears on the screen with a message that you need to install a jug of milk and press start. It is important that the settings are saved in the profile, you need to change something

  4. After clicking on start, if the settings have been changed, a message will appear on the screen that the changes will be saved to such and such a profile, for example in green. Which profile, it doesn’t matter, we just need the recipe to be saved to the profile, and at this moment the data will be captured and written to a log file.

  5. We stop writing to the log

  6. Open a text file with logs and look for 3 lines with bytes, look for AA at once:AA:AA:90, these are our coffee drink recipes

AA:AA:AA:93 - display of the selected program on the LCD
AA:AA:AA:90 - a set of recipes
AA:AA:AA:91 - starting the preparation of a drink

Look carefully, they may be hidden among other bytes. I’ve highlighted how I found them and show how it might look

  1. Let’s say we found the necessary bytes. Below is an example of how I pull out the necessary bytes and create a button with a coffee drink recipe

For convenience, I will take out the code separately and it may be updated from time to time if new sensors and commands for control are found.

What is implemented in the code?

  1. Status of the water container
  2. Pallet status (coffee grounds container)
  3. Coffee grounds sensor
  4. Coffee preparation statuses
  5. Sensor for the presence of coffee beans
  6. Turning off the coffee machine
  7. System sensors showing: switching on, flushing, warming up, turning off
Full and working code
substitutions:
  board_name: ESP32 Smart Coffee Philips
  node_name: esp32-smart-coffee-philips

esphome:
  name: ${node_name}
  friendly_name: esp32-smart-coffee-philips
  comment: ESP32 Smart Coffee Philips

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: on
  reboot_timeout: 10min
  ap:
    ssid: ESP SmartCoffeePhilips
    password: !secret ap_esp_password

captive_portal:

web_server:
  port: 80

logger:
  level: DEBUG
  baud_rate: 0

ota:
  password: "esphome"

api:
  encryption:
    key: !secret api_key


external_components:
  - source: github://TillFleisch/ESPHome-Philips-Smart-Coffee@main


uart:
 - id: uart_mainboard
   rx_pin: GPIO3
   tx_pin: GPIO1
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: RX
     dummy_receiver: false


 - id: uart_display
   rx_pin: GPIO16
   tx_pin: GPIO17
   baud_rate: 115200
   stop_bits: 1
   data_bits: 8
   parity: NONE
   rx_buffer_size: 256
   debug:
     direction: RX
     dummy_receiver: false
     sequence: 
      - lambda: |-
          UARTDebug::log_hex(direction, bytes, ':');
          //AA:AA:AA:B0
          if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x06 && bytes[9]==0x00) { 
            id(idWater).publish_state("There is water");
            id(idPallet).publish_state("Inserted");
            id(idMakingCoffee).publish_state("Not preparing");
            id(idCoffeeGroundsContainer).publish_state("Empty");
            id(idGrainTray).publish_state("There are coffee beans");
            id(idSystemStatus).publish_state("Choose a drink");
            }
          //AA:AA:AA:B0 "Availability of coffee beans"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x05) { id(idGrainTray).publish_state("Coffee beans are out"); }

          //AA:AA:AA:B0 "Took out the container with water"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0E && bytes[9]==0x40) { id(idWater).publish_state("There is no water"); }

          //AA:AA:AA:B0 "Removed the pallet"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0E && bytes[9]==0x80) { id(idPallet).publish_state("Retrieved"); }

          //AA:AA:AA:B0 "The container with coffee grounds is filled"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0E && bytes[9]==0x00) { id(idCoffeeGroundsContainer).publish_state("Oporozhe. container for coffee grounds"); }

          //AA:AA:AA:B0 "The pallet and the container with water were taken out"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0E && bytes[9]==0xC0) { 
            id(idWater).publish_state("There is no water"); 
            id(idPallet).publish_state("Retrieved");
            }
          
          //AA:AA:AA:B0 "System statuses"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0C && bytes[7]==0x01) { id(idMakingCoffee).publish_state("Enjoy"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0C && bytes[7]==0x02) { id(idMakingCoffee).publish_state("Something (07 0C 02)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0E) { id(idMakingCoffee).publish_state("Water heating"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0D) { id(idMakingCoffee).publish_state("Grinding the grains"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x10) { id(idMakingCoffee).publish_state("Pour the milk"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x11) { id(idMakingCoffee).publish_state("Pour the coffee"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x12) { id(idMakingCoffee).publish_state("12"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x13) { id(idMakingCoffee).publish_state("Creating steam for milk"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x14) { id(idMakingCoffee).publish_state("14"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x15) { id(idMakingCoffee).publish_state("15"); }

          //AA:AA:AA:B0 "Flushing and other"
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x01 && bytes[7]==0x00) { id(idSystemStatus).publish_state("Something (07 01 00)"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x02 && bytes[7]==0x00) { id(idSystemStatus).publish_state("Enabled"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x05 && bytes[7]==0x00) { id(idSystemStatus).publish_state("Off"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x0E) { id(idSystemStatus).publish_state("Heating"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x02) { id(idSystemStatus).publish_state("Flushing"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x14) { id(idSystemStatus).publish_state("Something (07 08 14)"); }

          //AA:AA:AA:B5 "Status 1"
          if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x00) { id(idStatusUnknown1).publish_state("00"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x0B) { id(idStatusUnknown1).publish_state("0B"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xE6) { id(idStatusUnknown1).publish_state("E6"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x80) { id(idStatusUnknown1).publish_state("80"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xCB) { id(idStatusUnknown1).publish_state("CB"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 &&& bytes[10]==0x00 & bytes[11]==0xFF) { id(idStatusUnknown1).publish_state("FF"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xA0) { id(idStatusUnknown1).publish_state("A0"); }
          
          //AA:AA:AA:B5 "Status 2"
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00) { id(idStatusUnknown2).publish_state("00"); }
          else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x01) { id(idStatusUnknown2).publish_state("01"); }


philips_series_5400:
  display_uart: uart_display
  mainboard_uart: uart_mainboard
  power_pin: GPIO12
  id: philip



#####################################################################################
################################### Text Sensor ################################
text_sensor:
  - platform: wifi_info
    ip_address:
      name: ${board_name} IP

  - platform: template
    name: "Water"
    id: idWater
    update_interval: 60s

  - platform: template
    name: "Coffee Grounds Container"
    id: idCoffeeGroundsContainer
    update_interval: 60s

  - platform: template
    name: "Coffee Pallet"
    id: idPallet
    update_interval: 60s

  - platform: template
    name: "Status 1"
    id: idStatusUnknown1
    update_interval: 60s

  - platform: template
    name: "Status 2"
    id: idStatusUnknown2
    update_interval: 60s

  - platform: template
    name: "Making Coffee"
    id: idMakingCoffee
    update_interval: 10s

  - platform: template
    name: "Grain Tray"
    id: idGrainTray
    update_interval: 60s

  - platform: template
    id: idSystemStatus
    name: "System Status"
    update_interval: 60s


button:
  - platform: restart
    name: Restart
    icon: mdi:restart

  - platform: template
    name: "Turn off" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0xFE, 0x00, 0x00, 0xC8, 0x87, 0x1B, 0x40, 0x55] #AA:AA:AA:FE:00:00:C8:87:1B:40:55


#####################################################################################
############################ Coffee Recipes #############################

#Drink: Coffee X1. Grain: Maximum. Coffee: 150 ml. Serving: 1
  - platform: template
    name: "Coffee Max 150ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x05, 0x01, 0x01, 0x6A, 0x52, 0x66, 0xE6, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x78, 0x00, 0x00, 0x00, 0xFC, 0x28, 0xA1, 0x4C, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x07, 0x01, 0x03, 0xA3, 0x2F, 0xE5, 0xA1, 0x55]


#Drink: Cappuccino. Grain: Minimum (2 out of 5 level scale). Coffee: 20 ml. Milk 100 ml.
  - platform: template
    name: "Cappuccino Min 20/100ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x05, 0x01, 0x01, 0x6A, 0x52, 0x66, 0xE6, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x03, 0x01, 0x00, 0x02, 0x03, 0x02, 0x14, 0x00, 0x64, 0x00, 0xE7, 0xC9, 0xE6, 0x5F, 0x55]
      - uart.write:    
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x07, 0x01, 0x03, 0xA3, 0x2F, 0xE5, 0xA1, 0x55]


#Drink: Cappuccino. Grain: Maximum. Coffee: 60 ml. Milk: 150 ml
  - platform: template
    name: "Cappuccino Max 60/150ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x05, 0x01, 0x01, 0x6A, 0x52, 0x66, 0xE6, 0x55]
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x03, 0x02, 0x00, 0x02, 0x03, 0x02, 0x3C, 0x00, 0x96, 0x00, 0xCC, 0xF1, 0x67, 0x25, 0x55]
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x07, 0x01, 0x03, 0xA3, 0x2F, 0xE5, 0xA1, 0x55]


2 Likes

There are unresolved issues and I need help

  1. The coffee machine cannot be turned on remotely or via the command AA:AA:AA:FE:00:00:C8:87:1B:40:55, neither via transistor. I drew and tracked where the tracks lead from the coffee machine power button and rang them. The track from the power button goes to the resistor R220, then goes to the K73 chip, and then goes to the chip, the third leg. These are 100% correct tracks and they ring, but I don’t know where you can solder so that you can send a signal or GND or something else from ESP so that you can turn on the coffee machine.
Photos of the board and the rendered tracks




The fact that on the K73 board it turned out to be a small signal Schottky diode and the documentation for it itself

K73 BAS70TW 10PCS SOT-363 SCHOTTKY BARRIER DIODE ARRAYS CHIP IC


image


If you do a solder to the contacts as a similar person did here, will it turn on and off the coffee machine or not?

  1. The recipe can be run only once, then you need to restart the coffee machine. I can’t understand why this is happening. We turn on the coffee machine, do not run programs from the control panel, but run them from esphome. The process of making a coffee drink starts, and if you start it again, it no longer works. For this to work, you need to turn off and turn on the coffee machine. It seems that this happens because when sending a command to prepare a coffee drink, the counter is knocked off and the protection is turned on in the coffee machine and the commands are ignored further. How can I send a recipe in such a way that it can be sent many times without knocking down the coffee machine counter? for example, to send certain bytes, those that we know, and not all that we know and random bytes

I have been watching this thread quietly for a while now, and its awesome to see the reverse engineering involved.

i was playing with a device that interfaces with daiken heatpumps etc, and i see quite a few similarities

one thing i noticed in one of your tables above, bytes 16 thru 19 are likely CRC (the last 4 before the stop byte). im wondering if the checksum algorithm could be reversed using a tool like https://github.com/colinoflynn/crcbeagle
later if you want app control of levels or change settings you will need to calculate the correct CRC for the message. i found confirming things like this means you can tick off a load of bytes, just like finding the start and end bytes.

i learned so much by reading about the daiken protocol from here, Arnold has done a fantastic job documenting things.
https://github.com/Arnold-n/P1P2Serial/tree/main/LogicalFormat

one thing i would suggest is for any bytes you dont know convert them to decimal and send to HA so you can graph/trend. you may need to combine adjacent bytes, they may be 16bit for volumes or temperatures.

looking again through your information above, i think one you know the header and crc/stop bit there is no need to log them.

can you create a program that passes the data between the end of the header and before the crc?
also it would be wise to have a timestamp and a direction. and one message packet per line
something like
millis,send,program,byte1,byte2,byte3
millis,rec,program,byte1,byte2,byte3
millis,send,program,byte1,byte2,byte3

if you send and characters between the packets (after 55 and before AAAAAA) as
millis,send,error,bytes until next header

this makes it easy to cut and paste data into excel. i found filtering by columns allowed me to notice trends. for example filter by program and compare the bytes

you may gather more info about neighboring cells by increasing volumes above what the first byte allows, eg if you can set 260ml milk volume you may find byte 14 is 04 and byte 15 is 01.

I tried CRC Beagle and it doesn’t fit, it just doesn’t work correctly and reports some nonsense

I created a file philips5400.py and pointed out as an example from the author of the CRC Beagle project

This is an example from the author

I did this following the example of the author

from crcbeagle import crcbeagle

crcb = crcbeagle.CRCBeagle()

#Example 1 - CRC16 with non-standard xor output

crcb.search([[AA, AA, AA, 90, 08, 0A, 00, 02, 00, 02, 03, 00, 1E, 00, 00, 00, 98, 80, 81, 70, 55],
[AA,AA,AA,90,26,0A,00,01,00,02,03,00,28,00,00,00,C6,13,4C,6D,55],
[AA,AA,AA,90,06,0A,00,02,00,01,01,00,28,00,00,00,6D,4F,D0,30,55],
[AA,AA,AA,90,1A,0A,00,02,01,01,01,00,28,00,00,00,8E,EF,68,F5,55],
[AA,AA,AA,90,0A,0A,00,02,02,01,01,00,5A,00,00,00,86,6D,C9,B6,55],
[AA,AA,AA,90,0D,0A,00,02,02,02,03,00,46,00,00,00,B8,D7,BE,A4,55],
[AA,AA,AA,90,06,0A,00,01,00,02,03,00,37,00,00,00,F7,8F,07,7D,55]],
)

crcb = crcbeagle.CRCBeagle()

#Example 2 - linear checksum when you think it might be a CRC8

crcb.search([[0xAA, 0xAA, 0xAA, 0x90, 0x08, 0x0A, 0x00, 0x02, 0x00, 0x02, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x98, 0x80, 0x81, 0x70, 0x55],
[0xAA, 0xAA, 0xAA, 0x90, 0x26, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x03, 0x00, 0x28, 0x00, 0x00, 0x00, 0xC6, 0x13, 0x4C, 0x6D, 0x55],
[0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x6D, 0x4F, 0xD0, 0x30, 0x55],
[0xAA, 0xAA, 0xAA, 0x90, 0x1A, 0x0A, 0x00, 0x02, 0x01, 0x01, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8E, 0xEF, 0x68, 0xF5, 0x55],
[0xAA, 0xAA, 0xAA, 0x90, 0x0A, 0x0A, 0x00, 0x02, 0x02, 0x01, 0x01, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x86, 0x6D, 0xC9, 0xB6, 0x55],
[0xAA, 0xAA, 0xAA, 0x90, 0x0D, 0x0A, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00, 0x46, 0x00, 0x00, 0x00, 0xB8, 0xD7, 0xBE, 0xA4, 0x55],
[0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x03, 0x00, 0x37, 0x00, 0x00, 0x00, 0xF7, 0x8F, 0x07, 0x7D, 0x55]],

)

Launching philips5400.py and I get errors where Syntax Error is written: leading zeros in decimal integer literals are not allowed; use an 0o prefix for octal integers. What kind of nonsense is this?

also it would be wise to have a timestamp and a direction. and one message packet per line
something like
millis,send,program,byte1,byte2,byte3
millis,rec,program,byte1,byte2,byte3
millis,send,program,byte1,byte2,byte3

If you look at the data from the logic analyzer, there are no delays, but there is one long command AA AA AA 93 0D 01 01 D2 03 75 E8 55 AA AA AA 90 0E 0A 02 02 00 02 01 02 96 00 5A 00 24 2C F8 C4 55 AA AA AA 91 0F 01 03 1B 7E F6 AF 55. You can take the files from the logic analyzer here and see what the bytes for recipes look like personally



I send this command in such a way that we get one long command at the output. If you send with delays, even with 1 ms, then the command to run recipes will not work.

button:
  - platform: template
    name: "Coffee with milk Max 120/120ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x93, 0x0D, 0x01, 0x01, 0xD2, 0x03, 0x75, 0xE8, 0x55]
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x90, 0x0E, 0x0A, 0x02, 0x02, 0x00, 0x02, 0x01, 0x02, 0x96, 0x00, 0x5A, 0x00, 0x24, 0x2C, 0xF8, 0xC4, 0x55]
      - uart.write:
          id: uart_mainboard
          data: [0xAA, 0xAA, 0xAA, 0x91, 0x0F, 0x01, 0x03, 0x1B, 0x7E, 0xF6, 0xAF, 0x55]

I would like to try to send those bytes that have logical values, and do not send the rest that change, i.e. indicate somehow that we are sending such and such a byte, but esphome does not allow this, reports an error. I do not know how this can be done

button:
  - platform: template
    name: "Coffee with milk Max 120/120ml" 
    on_press:
      - uart.write:
          id: uart_mainboard
          data: [bytes[0]==0xAA && bytes[3]==0x93]
      - uart.write:
          id: uart_mainboard
          data: [bytes[0]==0xAA && bytes[3]==0x90 && bytes[5]==0x01 && bytes[6]==0x02 && bytes[10]==0x01 && bytes[11]==0x02 && bytes[12]==0x5A && bytes[14]==0x5A]
      - uart.write:
          id: uart_mainboard
          data: [bytes[0]==0xAA && bytes[3]==0x91]

ERROR Error while reading config: Invalid YAML syntax:

while parsing a flow sequence
in “/config/esphome/coffee-philips-5400.yaml”, line 413, column 17:
data: [bytes[0]==0xAA && bytes[3]==0x93]
^
expected ‘,’ or ‘]’, but got ‘[’
in “/config/esphome/coffee-philips-5400.yaml”, line 413, column 23:
data: [bytes[0]==0xAA && bytes[3]==0x93]

I’m not in front of my computer, it’s a little hard to see on my phone, I think with the crc you need to remove the header and the crc and stop bit. You pass 2 arrays,

Crcb.search ( [[data1],[data2],[data3]],[[crc1],[crc2],[crc3]])

chris.huitema

The author does not have 0 in bytes at all, and I often have 0 in bytes, so his project is not suitable. You can try it yourself when you have time




If you look closely, I see in its bytes addresses 165 16 2 7 it’s like AA AA AA 93.

the warnings are the leading 0 in a decimal, so 0 is OK, but 00 is not.
the second error is because there is no 0x in front of the byte indicating that its a hex number, hence why it throws an error when it gets a letter
I dont have a computer i can test this on till the weekend, travelling light… see if this fixes the errors, then we can play with the bytes its checking

from crcbeagle import crcbeagle

crcb = crcbeagle.CRCBeagle()
crcb.search([[0x90, 0x08, 0x0A, 0x00, 0x02, 0x00, 0x02, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00],
[0x90, 0x26, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x03, 0x00, 0x28, 0x00, 0x00, 0x00],
[0x90, 0x06, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00],
[0x90, 0x1A, 0x0A, 0x00, 0x02, 0x01, 0x01, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00],
[0x90, 0x0A, 0x0A, 0x00, 0x02, 0x02, 0x01, 0x01, 0x00, 0x5A, 0x00, 0x00, 0x00],
[0x90, 0x0D, 0x0A, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00, 0x46, 0x00, 0x00, 0x00],
[0x90, 0x06, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x03, 0x00, 0x37, 0x00, 0x00, 0x00]],
[[0x98, 0x80, 0x81, 0x70],
[0xC6, 0x13, 0x4C, 0x6D],
[0x6D, 0x4F, 0xD0, 0x30],
[0x8E, 0xEF, 0x68, 0xF5],
[0x86, 0x6D, 0xC9, 0xB6],
[0xB8, 0xD7, 0xBE, 0xA4],
[0xF7, 0x8F, 0x07, 0x7D]]
)

Actually i found an online tool
https://crc-reveng.septs.app

i threw in a few to check initially, then added a few more and then the power on command for good measure, this was the result eack time

so this confirms the CRC is the last 4 bytes before the stop bit

width=32 poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0xffffffff check=0xcbf43926 residue=0xdebb20e3 name="CRC-32/ISO-HDLC"

a quick check to generate a checksum

it would be wise to validate the checksum for each message you read, to make sure its not corrupt. this may be too much for the processor… i dont know.

the positive of this is you now know what the bytes are. and if you want to change a parameter you know how to calculate the CRC for the message…

1 Like

@DivanX10 can you put some hex dumps for both the motherboard and display in the drive the other data is in for me to analyze. looks like the ones you put up are just for the display to the motherboard. I cant install software on the PC i have access to…

i can see from the ones i looked at there is a counter that increments, there is also some data corruption as some checksums dont match, not many but more than one in the file i looked at.

i notice one of your tables above with drinks counter, but t believe that’s the checksum.

but to really see what’s happening i need to look at both sides of the conversation… every command from the display will likely have a response from the motherboard, but it could be that the motherboard polls the display, if the display wants something it will respond only when its polled.

ideally if you could get the data from power on all the way to making a coffee. i think there would be some status info coming from the motherboard to the display

edit: i just found some of your replies on github, and the service manual you linked. i would start by putting the unit in service mode and manually test the functions, this way you can decode large amounts of the data quickly and in an isolated manner. i found it best to record a video of each thing i did while logging the data. if i stated the video at the same time as the logging i could correlate the info.
now you know the start AAAAAA and stop 55 and last 4 bytes before the stop you should be able to group the packets and analyse quickly.

also can you connect the wakeup to a pin on the ESP, and add a gpio binary sensor. i think there may be a state change on this for a millisecond when you power on. i dont think it will work with pull ups as there is already micro controllers on the line. i think the display pulls it low normally and it goes high to power on. that’s why when you disconnect the line it turns on (pullup on the mainboard)

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO13
      mode:
        input: true
    name: wakeup
  1. I don’t understand CRC very well. Did I understand correctly that if we were to send command bytes with CRC, the coffee machine would work correctly? What do I need to find a working CRC and send it along with the command bytes to the coffee machine? How, for example, to send such bytes to the coffee machine? It would be nice to show one example of how it will look like

  2. I store all the data here and you can view the data taken from the logic analyzer, from ESP. There are logs of switching on, off, idle, and making coffee drinks. The data from the logic analyzer can be opened in the Pulse View program. I recommend using the latest version 0.4.2

All the analytics are here, I also post diagrams, service documentation and software
image

  1. About WakeUP, an interesting thought. Did I understand you correctly that you need to connect the 4 wire to the GPIO13 pin on the esp? 4 wire is the same wire that has noises and it is referred to as WakeUP

And by the way, in the STMicroelectronics STM32L4R5VIT6 datasheet, on pages 26 and 27, it is written about power


As I understand from the description of the documentation, it will not work to turn on the coffee machine programmatically by sending bytes. In order to be able to turn on the coffee machine remotely, the voltage must be higher than 1.7 V, and at the time of turning off the coffee machine, the voltage is lower than 1.7V, which does not allow it to be controlled remotely

  1. As for turning the coffee machine on and off, I have an idea and I want to check it first on the TTP223B + ESP touch sensor and will conduct tests. Only after a successful test, I will be able to repeat it on the coffee machine. The thing is that I rang the control board, namely the external contours (yellow lines) and did not find a track from the external contours of the touch buttons leading to the resistor or just GND, it’s just a closed loop and there is a point (indicated by a red arrow). If you remove the protective layer on the board where the yellow lines are anywhere and ring the multimeter between the track under the protective layer and the hat (red arrow), the multimeter will beep. I concluded that this is shielding, protection from interference. What is my idea? I want to apply voltage to the resistor R110 and to the first leg of the Schottky diode K73 and then the coffee machine will think that they are pressing the touch power button. But before that, I want to check the signals on the TTP223B + ESP touch sensor through an oscilloscope and a logic analyzer, because their principle of operation is identical, after which I will repeat this on the coffee machine and be able to see what voltage is applied to the resistor R110 and what voltage is applied to the Schottky diode K73 after the resistor R110 and maybe there is no voltage applied, it’s just an impulse. We need to look.

TTP223B Touch Sensor

I drew all the tracks of the touch button shielding (yellow lines) and the track from the power button (red line) that leads to the resistor R110, then to the first leg of the Schottky diode R73 and then goes to the processor for 3 pins

Here I show with a red line how the track from the power button goes. The track goes to the resistor R110, then to the first leg of the Schottky diode R73 and then goes to the processor for 3 pins

The processor of the STM32L4R5VIT6 coffee machine consists of 25 legs and this is LQFP100. The path leading to the 3rd leg, according to the alphabet, is 53 pins

We look at what 53 pin means and see that this pin has I/O, it is an input/output pin

Decoding

This is our Schottky diode K73, to which I was going to signal the first leg with the esp to wake up

CRC is an error checking feature for the packet
<header><data><CRC><stop>
the header is so you can find the start, the data is the info you are sending, the CRC validates the data is valid, the stop bit is so you can find the end.

I know of CRC but i’ve never had to implement anything before, there are a few examples if you search for CRC32 c++, for an ESP32 i would imagine you will need to generate a table and store it static because of the limited memory, this may give you some idea of how to do it https://gist.github.com/timepp/1f678e200d9e0f2a043a9ec6b3690635

basically it validates every 1 and 0 in the data section. it does this by the processor sending the packet generating a 4byte code (CRC32) using a specific function (polynomial) and sending that after the data. the receiving processor will look at the received data and generate its own 4byte code (CRC32) using the same function (polynomial), it compares the CRC it generated with the one in the packet it received, if they are the same its good and the data is likely valid. if the CRC does not match then there was an error in the received data or received crc and it gets thrown away.

there appears to be a CRC calculation unit built into the STM32

For the wakeup, my guess is it will be a very small pulse that wakes up the processor on the motherboard, too short to measure with a multimeter, but you may catch it with a digital oscilloscope if you have the trigger set correctly. the easiest way to detect will be to connect wire 4 to GPIO13 on the esp and add the binary sensor code above. you will be able to see in your event log if you get one when you power on the machine.

this pulse probably wakes the processor and gets the uart ready to read the turn on command, so you will probably need both the line 4 and the commands.

As for the touch buttons, they will be capacitive sensors, section 3.24 of the datasheet details how it works. i haven’t seen any way to spoof button presses besides a servo with a foam pad… which is not a nice solution.

lets start with detecting the wakeup pulse first and see what we find

3 Likes

I connected the 4 wire to GPIO14 and used this binary sensor, but it didn’t show anything, and there is no information in the logs, there is no message that the sensor saw something

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO14
      mode:
        input: true
    name: "Sensor WakeUP"
    on_press:
      then:
        - logger.log: "WakeUP signal detected"

Next, I decided to use the APS sensor, which also did not see anything, which is quite expected, since taking readings through an oscilloscope I saw noise and voltage up to 300 mv. After breaking the 4 wire, the coffee machine turns on quietly.

sensor:
  - platform: adc
    pin: GPIO39
    id: isVoltage
    name: "Voltage"
    update_interval: 500ms
    accuracy_decimals: 7
    attenuation: auto

[17:00:13][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:00:15][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:00:16][D][sensor:094]: ‘Voltage’: Sending state 0.08400 V with 2 decimals of accuracy
[17:00:17][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:00:18][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:00:19][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:00:20][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:00:36][D][sensor:094]: ‘Voltage’: Sending state 0.90200 V with 2 decimals of accuracy
[17:00:37][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 2 decimals of accuracy
[17:04:59][D][sensor:094]: ‘Voltage’: Sending state 0.07500 V with 7 decimals of accuracy

Measuring 4 wires through a logic analyzer




It’s not a fact that the 4 wire is a Wake UP, since the scheme was drawn by an amateur radio enthusiast for a coffee machine of another series, not for the 5400.

As a result, I soldered the wire to the resistor R110 and connected it to an 8-channel zigbee relay for testing. It works well, I can turn it on and off, but the on and off button itself does not work while the wire is in the relay. If I disconnect the wiring from the relay, the button on the control panel works. To make it work, you need to take GND, you can from ESP, you can from a coffee machine, no difference, connect a capacitor from 10 to 47 pf to GND and connect it all to a relay that will short and open GND.



After a successful experiment, I ordered such a relay. The module is based on the AXICOM IM01 3vdc SMD electromechanical relay from TE. The control voltage is 3 Volts. Plug-in load up to 2A, 250V AC. It can be used in various projects, including on Arduino, where it is required to switch the voltage, for example, to include a load of 220 volts.

1 Like

Ive found a similar project that uses uart and reads the frames, this should give you some idea of how to read the data, and build the frames
https://github.com/joshbenner/esphome-daikin-s21/blob/71b210be27e1ca6f5e7f202f390cc89f8275bfac/components/daikin_s21/s21.cpp

if you scroll down to line 194, you will see how they decode. ive added comments to the relevant parts below

bool DaikinS21::read_frame(std::vector<uint8_t> &payload) {
  uint8_t byte;
  std::vector<uint8_t> bytes;
  uint32_t start = millis();
  bool reading = false;
  while (true) {
    if (millis() - start > S21_RESPONSE_TIMEOUT) {
      ESP_LOGW(TAG, "Timeout waiting for frame");
      return false;
    }
    while (this->rx_uart->available()) {
      this->rx_uart->read_byte(&byte);
      if (byte == ACK) {
        ESP_LOGW(TAG, "Unexpected ACK waiting to read start of frame");
        continue;
      } else if (!reading && byte != STX) {
        ESP_LOGW(TAG, "Unexpected byte waiting to read start of frame: %x",
                 byte);
        continue;
      } else if (byte == STX) {  // this is the start of frame byte, would need to be adapted to suit the 3x AA bytes in the coffee machine frame
        reading = true;
        continue;
      }
      if (byte == ETX) { //ETX is the stop byte / end of frame byte
        reading = false;  // once we get stop byte it sets this false so it can exit the loop later
        uint8_t frame_csum = bytes[bytes.size() - 1];  // read the last byte as its the checksum, would need to be adapted to suit the 4x bytes in the coffee machine checksum
        bytes.pop_back();  // removes the checksum byte from all the bytes received, once again, will need to adjust for 4 bytes not 1
        uint8_t calc_csum = s21_checksum(bytes); // create its own checksum based on the bytes array
        if (calc_csum != frame_csum) { // compares received checksum against own calcultaed one
          ESP_LOGW(TAG, "Checksum mismatch: %x (frame) != %x (calc from %s)",
                   frame_csum, calc_csum,
                   hex_repr(&bytes[0], bytes.size()).c_str());
          return false;
        }
        break;
      }
      bytes.push_back(byte); // add the received byte to the bytes array
    }
    if (bytes.size() && !reading) // once we have some bytes and reading the frame has finished we leave the while loop
      break;
    yield();  
  }
  payload.assign(bytes.begin(), bytes.end()); // this sets the received bytes (without header, crc and end byte) in the payload address.
  return true;
}