I will try to delve into the essence of this topic, but I do not understand how to insert it into ESPHome, so I do not know the C++ programming language and I am not a programmer, but an amateur and more often copy ready-made versions than I write myself
Ive changed the lambda up a bit to hopefully help discover more of the status info and hopefully simplify things.
ive removed alot of the info you tracked down, just to test if the lambda part works with the bit checks for the water container and pallet. it should spit out the part of the message without the header, checksum and stop bit into B0 meesage and b5 message
if you put the machine in maintenance mode and test the different components in isolation it should help decode the messages.
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, ':');
if (bytes[0]==0xAA && bytes[1]==0xAA && bytes[2]==0xAA){ // header
if (bytes[3]==0xB0) { // status message
id(b0message).publish_state("B0 %x %x %x %x %x %x %x %x %x", bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12]);
if (bytes[9] & 0b1000000) ? id(idPallet).publish_state("Removed") : id(idPallet).publish_state("Installed");
if (bytes[9] & 0b0100000) ? id(idWater).publish_state("Removed") : id(idWater).publish_state("Installed");
}
if (bytes[3]==0xB5) { // status2 message
id(b5message).publish_state("B5 %x %x %x %x %x %x %x %x", bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11]);
}
}
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
- platform: template
id: b0message
name: "B0 message"
update_interval: 10s
- platform: template
id: b5message
name: "B5 message"
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
#####################################################################################
############################ 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]
I copied your example and there were no errors during the check, but when I started sending the updated code to esp, the following errors occurred during compilation
hmm thats my miss understanding of the text sensor needing static strings… lets remove that for a minute and try get the bit checks part working
try this, sorry i’m travelling at the moment so cant test myself
- lambda: |-
UARTDebug::log_hex(direction, bytes, ':');
if (bytes[0]==0xAA && bytes[1]==0xAA && bytes[2]==0xAA){ // header
if (bytes[3]==0xB0) { // status message
if (bytes[9] & (1 << 7)) ? id(idPallet).publish_state("Removed") : id(idPallet).publish_state("Installed");
if (bytes[9] & (1 << 6)) ? id(idWater).publish_state("Removed") : id(idWater).publish_state("Installed");
}
}
I can’t compile, an error occurs during compilation. It will be right if you can check the correctness of the code yourself during compilation, and then publish the working code here
I implemented switching the coffee machine on and off by means of a relay. It works great. I took a single-channel 5V relay (KY-019), you can use the relay that I suggested above, but it hasn’t arrived yet, and it arrived much earlier than AXICOM IM01 3vdc SMD
I glued the relay itself to the back of the control panel housing, made a divorce for the servo, for esp and for the relay.
I also implemented a water drain to drain water when flushing the coffee machine past the cup, and then you can pour coffee into the cup.
Watch the video how the water drain works and how the full process works, from turning on to making coffee
Connection diagram
You can download the part for printing on a 3d printer here
Important. I designed the hole for the gear for the SG90 Micro Servo. It comes in very tight, but it holds tight and does not jump off. There is no need to expand the hole, just make a good effort when pressing the servo into the hole and the servo will start to enter.
You can set the desired position of the servo to adjust the water discharge. I have set the following values
Code for turning the coffee machine on and off, as well as servo control
substitutions:
board_name: Coffee Philips 5400
node_name: coffee-philips-5400
esphome:
name: ${node_name}
friendly_name: coffee-philips-5400
comment: ESP32 Coffee Philips 5400
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
services:
- service: control_servo
variables:
level: float
then:
- servo.write:
id: servo_control
level: !lambda 'return level / 100.0;'
- sensor.template.publish:
id: servo_sensor
state: !lambda 'return (float)level;'
#####################################################################################
################################## External component ################################
external_components:
- source: github://TillFleisch/ESPHome-Philips-Smart-Coffee@main
#####################################################################################
######################################### UART ######################################
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
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(idGrainTray).publish_state("There are grains");
id(idStatus).publish_state("Choose a drink");
id(idCoffeeGroundsContainer).publish_state("Empty");
}
//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("The grains have run 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 "Statuses"
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0C && bytes[7]==0x01) { id(idStatus).publish_state("Enjoy"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x0C && bytes[7]==0x02) { id(idStatus).publish_state("Something (07 0C 02)"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x01 && bytes[7]==0x00) { id(idStatus).publish_state("Something (07 01 00)"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x05 && bytes[7]==0x00) { id(idStatus).publish_state("Off"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0E) { id(idStatus).publish_state("Water heating"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x0D) { id(idStatus).publish_state("Grinding the grains"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x10) { id(idStatus).publish_state("Pour the milk"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x11) { id(idStatus).publish_state("Pour the coffee"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x12) { id(idStatus).publish_state("Pre-dosing of water"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x13) { id(idStatus).publish_state("Creating steam for milk"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x14) { id(idStatus).publish_state("The brewing unit to the brewing position"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x07 && bytes[7]==0x15) { id(idStatus).publish_state("Enjoy"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x0E) { id(idStatus).publish_state("Heating"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x02) { id(idStatus).publish_state("Flushing"); }
else if (bytes[0]==0xAA && bytes[3]==0xB0 && bytes[5]==0x07 && bytes[6]==0x08 && bytes[7]==0x14) { id(idStatus).publish_state("Something (07 08 14)"); }
//AA:AA:AA:B5 "Error Code"
if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x00) { id(idErrorCode).publish_state("00"); }
else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x0B) { id(idErrorCode).publish_state("0B"); }
else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xE6) { id(idErrorCode).publish_state("E6"); }
else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0x80) { id(idErrorCode).publish_state("80"); }
else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xCB) { id(idErrorCode).publish_state("CB"); }
else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 &&& bytes[10]==0x00 & bytes[11]==0xFF) { id(idErrorCode).publish_state("FF"); }
else if (bytes[0]==0xAA && bytes[3]==0xB5 && bytes[5]==0x06 && bytes[10]==0x00 && bytes[11]==0xA0) { id(idErrorCode).publish_state("A0"); }
if (bytes[0]==0xAA) { id(idPowerStatus).publish_state(true); }
#####################################################################################
############################## Global variables #####################################
globals:
- id: saved_position
type: int
initial_value: '0'
restore_value: true
#####################################################################################
############################### Output Platform #####################################
output:
- platform: ledc
id: pwm_output
pin: GPIO27
frequency: 50 Hz
#####################################################################################
################################ Configuration ######################################
servo:
- id: servo_control
output: pwm_output
auto_detach_time: 5s
transition_length: 5s
restore: true
#####################################################################################
################################## Connected component ##############################
philips_series_2200:
display_uart: uart_display
mainboard_uart: uart_mainboard
power_pin: GPIO12
id: philip
#####################################################################################
##################################### Text Sensor ###################################
text_sensor:
- platform: wifi_info
ip_address:
name: IP
- platform: template
name: "Water"
icon: mdi:cup-water
id: idWater
update_interval: 60s
- platform: template
name: "Coffee Grounds Container"
icon: mdi:train-car-centerbeam-full
id: idCoffeeGroundsContainer
update_interval: 60s
- platform: template
name: "Coffee Pallet"
icon: mdi:spirit-level
id: idPallet
update_interval: 60s
- platform: template
name: "Error Code"
icon: mdi:alert-circle-outline
id: idErrorCode
update_interval: 60s
- platform: template
name: "Grain Tray"
icon: mdi:grain
id: idGrainTray
update_interval: 60s
- platform: template
name: "Status"
icon: mdi:coffee-to-go
id: idStatus
update_interval: 60s
#####################################################################################
################################### Binary sensor ###################################
binary_sensor:
- platform: template
name: "PowerStatus"
id: idPowerStatus
lambda: |-
if (id(idPowerStatus).state) {
return true;
} else {
return false;
}
#####################################################################################
################################## Sensor ###########################################
sensor:
#WiFi signal strength sensor
- platform: wifi_signal
name: RSSI WiFi
icon: mdi:wifi
update_interval: 60s
#Servomotor position sensor
- platform: template
name: Servo Sensor Position
id: servo_sensor
icon: mdi:valve
on_value:
then:
- script.execute: script_servo
- script.execute: record_servo_position
#####################################################################################
#################################### Switch #########################################
switch:
- platform: gpio
pin: GPIO14
name: "Relay Switch"
id: idRelaySwitch
internal: True #Скрыть - true \показать - false
restore_mode: ALWAYS_OFF
#Выключатель сервопривода с начальной и конечной позицией
- platform: template
id: servo_control_switch
name: Valve open/closed
icon: mdi:coffee-maker-outline
restore_state: false
turn_on_action:
- sensor.template.publish:
id: servo_sensor
state: !lambda 'return id(valve_open).state;'
- switch.template.publish:
id: servo_control_switch
state: true
- servo.write:
id: servo_control
level: !lambda 'return (id(valve_open).state/100);'
turn_off_action:
- sensor.template.publish:
id: servo_sensor
state: !lambda 'return id(valve_closed).state;'
- switch.template.publish:
id: servo_control_switch
state: false
- servo.write:
id: servo_control
level: !lambda 'return (id(valve_closed).state/100);'
#####################################################################################
###################################### Number #######################################
#Slider for servo control
number:
- platform: template
name: Valve Position
min_value: -100
max_value: 100
update_interval: 1s
mode: slider #slider/box
lambda: 'return id(servo_sensor).state;'
step: 1
set_action:
then:
- servo.write:
id: servo_control
level: !lambda 'return x / 100.0;'
- sensor.template.publish:
id: servo_sensor
state: !lambda 'return x;'
#Specify the initial position of the servo
- platform: template
name: Valve open
id: valve_open
min_value: -100
max_value: 100
mode: box #slider/box
step: 1
optimistic: true
restore_value: true
#Specify the final position of the servo
- platform: template
name: Valve closed
id: valve_closed
min_value: -100
max_value: 100
mode: box #slider/box
step: 1
optimistic: true
restore_value: true
#####################################################################################
####################################### Script ######################################
script:
- id: script_servo
then:
- if:
condition:
- lambda: 'return id(servo_sensor).state == id(valve_open).state;'
then:
- sensor.template.publish:
id: servo_sensor
state: !lambda 'return (id(valve_open).state/100);'
- switch.turn_on: servo_control_switch
- if:
condition:
and:
- lambda: 'return id(servo_sensor).state == id(valve_closed).state;'
then:
- sensor.template.publish:
id: servo_sensor
state: !lambda 'return id(valve_closed).state;'
- switch.turn_off: servo_control_switch
- id: record_servo_position
then:
- globals.set:
id: saved_position
value: !lambda 'return id(servo_sensor).state;'
#####################################################################################
####################################### Button ######################################
button:
#Restart
- platform: restart
name: Restart
icon: mdi:restart
#Turn on the coffee machine
- platform: template
name: "Power ON"
icon: mdi:power
on_press:
- switch.turn_on: idRelaySwitch
- delay: 1s
- switch.turn_off: idRelaySwitch
#Turn off the coffee machine
- platform: template
name: "Power OFF"
icon: mdi:power
on_press:
- switch.turn_on: idRelaySwitch
- delay: 2s
- switch.turn_off: idRelaySwitch
#####################################################################################
####################################### Time #######################################
time:
- platform: sntp
id: sntp_time
timezone: Europe/Moscow
on_time:
- seconds: 59
then:
- lambda: |-
id(idPowerStatus).publish_state(false);
I’ll post another option. It is possible to implement switching on and off via the PC817C optocoupler, but with a relay it is better. If you implement the connection via an optocoupler, then by de-energizing and turning on the ESP, we give an impulse to the coffee machines and the coffee machine will turn on, and in the case of a relay, this will not happen. You can safely de-energize and plug in the coffee machine and the coffee machine does not turn on, also the coffee machine does not turn on if there is a short-term power outage in the house. Any capacitor will do, 10/20/47pf.
If you use this option, it works. For reference, I used ChatGPT, so there may be errors in the code from the programmer’s point of view
- 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
sequence:
- lambda: |-
UARTDebug::log_hex(direction, bytes, ':');
if (bytes[0]==0xAA && bytes[1]==0xAA && bytes[2]==0xAA){ // header
if (bytes[3]==0xB0) { // status message
std::string b0_message = "B0 ";
for (int i = 4; i <= 12; i++) {
char hex_str[3];
snprintf(hex_str, sizeof(hex_str), "%02X", bytes[i]);
b0_message += hex_str;
b0_message += " ";
}
id(b0message).publish_state(b0_message);
}
if (bytes[3]==0xB5) { // status2 message
std::string b5_message = "B5 ";
for (int i = 4; i <= 11; i++) {
char hex_str[3];
snprintf(hex_str, sizeof(hex_str), "%02X", bytes[i]);
b5_message += hex_str;
b5_message += " ";
}
id(b5message).publish_state(b5_message);
}
if (bytes[15]==0x90) { // status message
std::string message90 = "90 ";
for (int i = 18; i <= 27; i++) {
char hex_str[3];
snprintf(hex_str, sizeof(hex_str), "%02X", bytes[i]);
message90 += hex_str;
message90 += " ";
}
id(id90message).publish_state(message90);
}
}
and it works that way too
if (bytes[0]==0xAA && bytes[1]==0xAA && bytes[2]==0xAA){ // header
if (bytes[3]==0xB0) { // status message
std::string b0_message = "B0 " + std::to_string(bytes[4]) + " " + std::to_string(bytes[5]) + " " + std::to_string(bytes[6]) + " " +
std::to_string(bytes[7]) + " " + std::to_string(bytes[8]) + " " + std::to_string(bytes[9]) + " " +
std::to_string(bytes[10]) + " " + std::to_string(bytes[11]) + " " + std::to_string(bytes[12]);
id(b0message).publish_state(b0_message);
}
if (bytes[3]==0xB5) { // status2 message
std::string b5_message = "B5 " + std::to_string(bytes[4]) + " " + std::to_string(bytes[5]) + " " +
std::to_string(bytes[6]) + " " + std::to_string(bytes[7]) + " " +
std::to_string(bytes[8]) + " " + std::to_string(bytes[9]) + " " +
std::to_string(bytes[10]) + " " + std::to_string(bytes[11]);
id(b5message).publish_state(b5_message);
}
if (bytes[15]==0x90) { // status message
std::string message90 = "90 " + std::to_string(bytes[18]) + " " + std::to_string(bytes[19]) + " " +
std::to_string(bytes[20]) + " " + std::to_string(bytes[21]) + " " +
std::to_string(bytes[22]) + " " + std::to_string(bytes[23]) + " " +
std::to_string(bytes[24]) + " " + std::to_string(bytes[25]) + " " +
std::to_string(bytes[26]) + " " + std::to_string(bytes[27]);
id(id90message).publish_state(message90);
}
}
I rewrote the ESPHome-Philips-Smart-Coffee component for Philips 5400, removed all sensors, switches, buttons and everything that does not apply to Philips 5400, left only the UART. The Philips 5400 component can be downloaded here. Why did I take a component from ESPHome-Philips-Smart-Coffee? The thing is that if you use another uart_mitm component, then the coffee machine does not work correctly, it can turn off at any time, standby mode does not work normally, and if you use a component from ESPHome-Philips-Smart-Coffee, then the coffee machine works correctly. The coffee machine will not turn off exactly as long as the standby mode, which we set through the settings menu of the coffee machine, does not work, or until we turn it off ourselves. The component must be placed in the \esphome\components folder\
To operate the component for Philips 5400, you need to insert the code
external_components:
- source:
type: local
path: components
components: [philips_series_5400]
philips_series_5400:
display_uart: uart_display
mainboard_uart: uart_mainboard
id: philip
I created sensors, and thanks to the sensors, I came to some understanding of how to run coffee recipes many times. It turns out that the counter is working and it is impossible to jump over the counter, the coffee recipe will not work. Here’s what I saw
The code for the operation of sensors message 91 and 93
In order for the Message 90 sensor to work, you need to enable debugging in both directions for uart_mainboard. It is not recommended to do this, because because of this, the changed coffee preparation parameters are not saved in the profile and interferes with the normal operation of the coffee machine, so I turned off BOTH and specified RX
- 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
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: BOTH
dummy_receiver: false
sequence:
- lambda: |-
UARTDebug::log_hex(direction, bytes, ':');
//Coffee drinks
//Message 93. Teaches bytes 0 through 11. We display in this form [0xAA, 0xAA, 0xAA, 0x93, 0x0F, 0x01, 0x01, 0xBC, 0xD7, 0xF1, 0xEB, 0x55]
if (bytes[0] == 0xAA && bytes[2] == 0xAA && bytes[3] == 0x93) {
std::string message93 = "[";
for (int i = 0; i <= 11; i++) {
char hex_str[6];
snprintf(hex_str, sizeof(hex_str), "0x%02X", bytes[i]);
message93 += hex_str;
if (i != 11) {
message93 += ", ";
}
}
message93 += "]";
id(id93message).publish_state(message93);
}
//Message 90. Accounting for bytes from 0 to 20. We display it in this form [0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x00, 0x03, 0x00, 0x02, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xC5, 0x47, 0xCB, 0x8E, 0x55]
if (bytes[0] == 0xAA && bytes[2] == 0xAA && bytes[3] == 0x90) {
std::string message90 = "[";
for (int i = 0; i <= 20; i++) {
char hex_str[6];
snprintf(hex_str, sizeof(hex_str), "0x%02X", bytes[i]);
message90 += hex_str;
if (i != 20) {
message90 += ", ";
}
}
message90 += "]";
id(id90message).publish_state(message90);
}
//Message 91.Accounting for bytes from 0 to 11. We display it in this form [0xAA, 0xAA, 0xAA, 0x91, 0x12, 0x01, 0x08, 0xB0, 0x97, 0xDA, 0x2C, 0x55]
if (bytes[0] == 0xAA && bytes[2] == 0xAA && bytes[3] == 0x91) {
std::string message91 = "[";
for (int i = 0; i <= 11; i++) {
char hex_str[6];
snprintf(hex_str, sizeof(hex_str), "0x%02X", bytes[i]);
message91 += hex_str;
if (i != 11) {
message91 += ", ";
}
}
message91 += "]";
id(id91message).publish_state(message91);
}
text_sensor:
- platform: template
name: "Message1 93"
id: id93message
update_interval: 60s
- platform: template
name: "Message2 90"
id: id90message
update_interval: 60s
- platform: template
name: "Message3 91"
id: id91message
update_interval: 60s
To understand how the counter works, you can watch the video
Looking at the Message 90 and 93 sensors, I noticed the following.
This is what bytes look like when the coffee machine offers to choose a drink and before switching off
Turn off the coffee machine
The coffee machine is turned off
The coffee machine was completely de-energized and plugged into an outlet, after a pause of 5 seconds
The coffee machine was turned on, the heating and flushing stage began
The coffee machine has passed the washing stage and offers to choose a drink
I realized that immediately after the coffee machine turns on and offers to choose a drink, it is impossible to send a command to prepare a recipe, since the washing stage will start. To prevent this from happening, you need to wait until it happens like this
When making coffee drinks, the counter is triggered and I had an idea how to run the recipe, but I can’t write the code correctly. ChatGPT is trying to help me, but it doesn’t work well and it offers a bunch of options, and most of the options are wrong, compilation goes wrong. ChatGPT simply offers codes by brute force and there is zero sense from this.
The point is that we extract bytes from the Message 91 sensor and insert it as a variable to send a command, thus simulating the operation of the counter
For the test, I created a button and it works correctly. This is not sending a cooking command, but just checking to see how bytes are being sent. I extract bytes from the Message 91 sensor and send the Message Send button 91 to the sensor and the sensor displays exactly the same as in Message3 91
[0xAA, 0xAA, 0xAA, 0x91, 0x08, 0x01, 0x08, 0x16, 0xB1, 0x6B, 0x3D, 0x55]
- platform: template
name: "Message Button 91"
icon: mdi:coffee
on_press:
- text_sensor.template.publish:
id: idMessageButton
state: !lambda 'return id(id91message).state;'
To send the command, I first tried this option, but it doesn’t work, because when compiling I get an error
button
- platform: template
name: "Coffee"
icon: mdi:coffee
on_press:
- uart.write:
id: uart_mainboard
data: [0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x00, 0x03, 0x00, 0x02, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xC5, 0x47, 0xCB, 0x8E, 0x55]
- uart.write:
id: uart_mainboard
data: return 'id(id91message).state;'
And I got this code from ChatGPT, the compilation was successful, but it doesn’t work and I don’t see how bytes are being sent
button
- platform: template
name: "Coffee"
icon: mdi:coffee
on_press:
- uart.write:
id: uart_mainboard
data: [0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x0A, 0x00, 0x03, 0x00, 0x02, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xC5, 0x47, 0xCB, 0x8E, 0x55]
- uart.write:
id: uart_mainboard
data: !lambda |-
std::string message = id(id91message).state.c_str();
std::vector<uint8_t> bytes;
for (std::size_t i = 0; i < message.size(); i += 4) {
std::string byte_str = message.substr(i, 4);
uint8_t byte = std::stoi(byte_str, nullptr, 16);
bytes.push_back(byte);
}
return bytes;
Table of analysis of the protocol AA AA AA 90 which is responsible for accounting. The last 4 bytes are the checksum. If you change one byte, the recipe will not work.
Thanks to one person, you can now make coffee many times. We wrote a component to control the coffee machine. But I must say right away, the code is not perfect, but it works. You can use the code as it is or modify it and bring it to mind. A pass-through counter has been implemented in the code and now you can prepare coffee remotely as many times as you want
The project ESP-Philips-5400-Coffee-Machine is still in Russian, I will add a page in English soon.
The project itself ESP-Philips-5400-Coffee-Machine
In the config you need to add
external_components:
- source:
type: git
url: https://github.com/DivanX10/ESP-Philips-5400-Coffee-Machine
philips_series_5400:
display_uart: uart_display
mainboard_uart: uart_mainboard
id: philip
The full code can be found here
I have one of these coffee machines and this project intrigues me. Amazing work reverse engineering the machine!
What is the use case for this, though? What functionality does this give over just the buttons?
It all depends on you.
- The coffee machine can be turned on and off remotely
- There are sensors showing:
- Availability of water
- Availability of grain
- Is the pallet inserted
- Is the coffee grounds container empty
- Coffee preparation statuses
- Power status, whether the coffee machine is turned on or not
- You can send your recipes for making coffee based on the recipe table, or you can make coffee and the recipe will be displayed in the coffee preparation sensor and it can be used
The coffee machine makes me coffee as I wake up, it’s damn cool, it’s what I dreamed of. For my philips 5400 coffee machine, the REDMOND SkyCoffee M1519S coffee maker worked like this. It turned on when we woke up, but with the advent of the coffee machine I wanted to do the same and I did it.
Video of how the coffee machine turns on and makes me coffee
The: make coffee as you wake up is nice, but doesn’t it still need to do the first cleaning after being off for a long time? Or does this make is so that you can skip that step?
Do you mean when the coffee machine turns on and does the heating and rinsing and drains the water into the cup?
To do this, I designed a water outlet. When the coffee machine is turned on, it starts the washing stage and water flows through it into the cup if it is standing or is poured into the tray if there is no cup. The water drain removes water from the cup, preventing water from entering the cup during washing and the cup remains dry, all the washing water goes past the cup
That is very clever
News
The component for controlling the Philips 5400 coffee machine is completely rewritten and ready. I added an English version to the coffee machine project here. Any improvements and improvements in the code are welcome. Fork and finish the code at your discretion. Share a link to your projects with revision
This is how the control panel of the coffee machine and the panel of parameters for making coffee drinks looks like
Is the relay specifically if you want control over on/off? What is the use-case for using the optocoupler instead?
Optocoupler is probably a good option. Why am I writing probably? Because I do not know how the optocoupler will behave if connected to a pin without pulses when power is supplied. As I later found out, the esp does not have all pins free (without pulses when current is applied). When I connected the optocoupler to the esp, I didn’t think about it and when I plugged the coffee machine into the socket, the coffee machine turned on every time. This does not happen with the relay. The relay can be connected to any pin on the ESP and the coffee machine will not turn on when plugged into the socket. When I remembered about it, by this time I had everything assembled on the relay and had no desire to check. But, in the case of an optocoupler, you can turn on the coffee machine from the button on the control panel and remotely, but it does not work with the relay, only switching on with esp.
Can I make this work with the 3200 series somehow?
The 4000/5000 series coffee machine project is not suitable for the 3200 series coffee machine, since they have different protocols. The 3200 coffee machine has no LCD screen and buttons, so you need to look at projects for 1000-3000 series coffee machines. The links are in the header