ESP smart сoffee machine Philips 5400 Series

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
image

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);