Close
0%
0%

Smart Coffee Machine Pump

DIY automatic water pump system for coffee machine with Raspberry Pi, HC-SR04 Ultrasonic Sensor and Cloud4RPi control panel

Similar projects worth following
In theory, every time you go to the coffee machine for your morning cup, there’s only a one-in-twenty chance you’ll have to fill the water tank. In practice, however, it seems that the machine somehow finds a way to always put this chore on you. The more you want coffee, the more likely you are to get the dreaded “fill the water tank” message. My colleagues feel the same way about this. Being the nerds that we are, we decided to implement the technology that would put an end to this.

Our Equipment

We have a SAECO Aulika Focus coffee machine. Up to this day, we used a hand pump to fill the machine’s water tank from a standard 5 Gallon (19L) water bottle.

Our Goals

  1. Use an electric pump driven by some kind of a controller or a microcomputer through a relay.
  2. Have a way to measure the water level in the coffee machine’s tank so our system knows when to refill it.
  3. Have means to control the system, preferably in real-time from a mobile device.
  4. Receive notifications (through Slack or a similar service) if anything goes wrong with the system.

The Assembled Coffee Machine Pump System:

Demo video

  • 1 × Raspberry pi 3 B+
  • 1 × HC-SR04 Ultrasonic Sensor
  • 1 × Solid State Relay Power Supplies / Uninterruptible Power Supplies (UPS)
  • 1 × Coffee machine SAECO

View project log

  • 1
    Choosing the Equipment

    The Pump

    A quick web search will show several electric pump models designed for your water bottle of choice. Such pumps are usually controlled by an ON/OFF switch (for example, Hot Frost A12 or SMixx ХL-D2). Here’s the pump we chose for our project:

    The Controller Device

    We tried several devices but settled on a Raspberry Pi due to the following advantages:

    • It has a GPIO that allows us to connect a proximity sensor
    • It supports Python

    We installed a fresh version of Raspbian Buster Lite and everything required to run Python 3.

    How We Toggle the Pump

    To control the power, we picked a medium power (12V/2A) solid state relay suited for alternating current.

    The relay connects the pump to the outlet and is controlled by the Raspberry Pi’s digital pin.

    How We Check the Water Level

    It was important for us to not alter the coffee machine’s construction, so we decided to use the HC-SR04 Ultrasonic proximity sensor to measure the water level.

    We 3d-printed a custom water tank cover with two holes for the sensor’s emitters.

    We easily found a GitHub library for the sensor.

    At this point all preparations were finished.

  • 2
    Designing the System

    System’s Logic

    The system is designed with the following simple logic in mind:

    • The system constantly monitors the distance between the sensor and the water surface.
    • Whenever a change in distance goes over a threshold value, the system sends information about its state to the cloud.
    • If the distance goes over the maximum allowed value (the tank is empty), the system activates the pump and turns it off once the distance is less than the minimum allowed value.
    • Whenever the system’s state changes (for example, the pump activates), it informs the cloud.

    In case of an error, a notification is sent to a Slack channel.

    When the coffee machine is idle, the system pings the cloud service with diagnostic data once every minute. Additionally, it sends its state to the cloud every 5 minutes.

    When the pump is active, the system sends data more frequently but no more than once every half a second.

    def send(cloud, variables, dist, error_code=0, force=False):
        pump_on = is_pump_on()
        percent = calc_water_level_percent(dist)
        variables['Distance']['value'] = dist
        variables['WaterLevel']['value'] = percent
        variables['PumpRelay']['value'] = pump_on
        variables['Status']['value'] = calc_status(error_code, percent, pump_on)
    
        current = time()
        global last_sending_time
        if force or current - last_sending_time > MIN_SEND_INTERVAL:
            readings = cloud.read_data()
            cloud.publish_data(readings)
            last_sending_time = current

    Working with the Pump

    We define the following constants as a base for pump operation logic.

    # GPIO Pins (BCM)
    GPIO_PUMP = 4
    GPIO_TRIGGER = 17
    GPIO_ECHO = 27
    # Pump
    START_PUMP = 1
    STOP_PUMP = 0
    PUMP_BOUNCE_TIME = 50   # milliseconds
    PUMP_STOP_TIMEOUT = 5   # secs

     IMPORTANT: If you are going to use Pin 4, do not forget to disable the 1-Wire raspi-config option to avoid conflicts.

    At the program’s startup, we register a callback and set the initial state to OFF.

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(GPIO_PUMP, GPIO.IN)
    
    GPIO.add_event_detect(GPIO_PUMP, GPIO.BOTH, callback=pump_relay_handle, bouncetime=PUMP_BOUNCE_TIME)
    
    toggle_pump(STOP_PUMP)

     Here’s the code for the function that toggles the pump:

    def toggle_pump(value):
        if pump_disabled:
            return        
        if is_pump_on() != value:
            log_debug("[x] %s" % ('START' if value else 'STOP'))
        GPIO.setup(GPIO_PUMP, GPIO.OUT)
        GPIO.output(GPIO_PUMP, value)  # Start/Stop pouring

    As defined in the startup code above, when the relay turns ON, the following callback is called:

    pump_on = False
    def pump_relay_handle(pin):
        global pump_on
        pump_on = GPIO.input(GPIO_PUMP)
        log_debug("Pump relay changed to %d" % pump_on)
    

    In the callback, we save the pump’s current state to a variable.

    In the application’s main loop, we can detect the moment when the pump toggles as shown below:

    def is_pump_on():
        global pump_on
        return pump_on
    if GPIO.event_detected(GPIO_PUMP):                
        is_pouring = is_pump_on()
        # ...
        log_debug('[!] Pump event detected:  %s' % ('On' if is_pouring else 'Off'))
        send(cloud, variables, distance, force=True)

     Measuring the Distance

    It’s quite easy to measure the distance towards the water surface using an ultrasonic proximity sensor. In our repository, we shared a couple of python scripts that allow you to test a sensor.

    In real applications, sensor readings can fluctuate because of the sensor’s bouncing effect and water oscillations. In some cases, readings can be completely missing.

    We implemented a BounceFilter class that accumulates N recent values, discards peaks and calculates the average of remaining measurements.

    The measurement process is implemented by the following asynchronous algorithm.

    # Keeps the last sensor measurements
    readings = BounceFilter(size=6, discard_count=1)
    
    reading_complete = threading.Event()
    
    def wait_for_distance():
        reading_complete.clear()
        thread = threading.Thread(target=read_distance)
        thread.start()
      
        if not reading_complete.wait(MAX_READING_TIMEOUT):
            log_info('Reading sensor timeout')
            return None
        return readings.avg()
    
    
    def read_distance():
        try:
            value = hcsr04.raw_distance(sample_size=5)
            rounded = value if value is None else round(value, 1)
            readings.add(rounded)        
        except Exception as err:
            log_error('Internal error: %s' % err)
        finally:
            reading_complete.set()

    You can find the filter’s full implementation in the sources.

    Handling Emergency Situations

    What if the sensor burned out, or fell off, or points to a wrong area? We needed a way to report such cases so that we can take manual action.

    If the sensor fails to provide distance readings, the system sends the changed status to the cloud and generates a corresponding notification.

    The logic is illustrated by the code below.

    distance = wait_for_distance() # Read the current water depth
    if distance is None:
        log_error('Distance error!')
        notify_in_background(calc_alert(SENSOR_ERROR))
        send(cloud, variables, distance, error_code=SENSOR_ERROR, force=True)
     
    

     We have an operational water level range that should be maintained when the sensor is in its place. We test if the current water level falls in this range:

    # Distance from the sensor to the water level 
    # based on the coffee-machine's water tank
    MIN_DISTANCE = 2  # cm
    MAX_DISTANCE = 8  # cm
    # Distance is out of expected range: do not start pouring
    if distance > MAX_DISTANCE * 2:
        log_error('Distance is out of range:  %.2f' % distance)
            continue

    We turn the pump off if it was active when an error occurred. 

    if is_pump_on() and prev_distance < STOP_PUMP_DISTANCE + DISTANCE_DELTA:
        log_error('[!] Emergency stop of the pump. No signal from a distance sensor')
        toggle_pump(STOP_PUMP)

    We also process the case when the bottle runs out of water. We check if the water level does not change when the pump runs. If so, the system waits for 5 seconds and then checks if the pump has turned off. If it has not, then the system implements emergency pump shutdown and sends an error notification. 

    PUMP_STOP_TIMEOUT = 5   # secs
    emergency_stop_time = None
    def set_emergency_stop_time(now, is_pouring):
        global emergency_stop_time
        emergency_stop_time = now + PUMP_STOP_TIMEOUT if \
            is_pouring else None
    def check_water_source_empty(now):
        return emergency_stop_time and now > emergency_stop_time
    # --------- main loop -----------
        if GPIO.event_detected(GPIO_PUMP):                
            is_pouring = is_pump_on()
            set_emergency_stop_time(now, is_pouring)
            # ...       
        global pump_disabled
        if check_water_source_empty(now):
             log_error('[!] Emergency stop of the pump. \
                    Water source is empty')                
            toggle_pump(STOP_PUMP)
            pump_disabled = True

    Below is an example of a message log generated during an emergency stop.

  • 3
    Running the System 24/7

    The code on the device is debugged and runs without problems. We launched it as a service, so it restarts if the Raspberry Pi is rebooted. For convenience, we created a Makefile that helps with deployment, running the service and viewing logs.

    PHONY: install run start stop status log deploy
    
    MAIN_FILE:= coffee-pump/main.py
    SERVICE_INSTALL_SCRIPT:= service_install.sh
    SERVICE_NAME:= coffee-pump.service
    
    install:
        chmod +x $(SERVICE_INSTALL_SCRIPT)
        sudo ./$(SERVICE_INSTALL_SCRIPT) $(MAIN_FILE)
    
    run:
        sudo python3 $(MAIN_FILE)
    
    start:
        sudo systemctl start $(SERVICE_NAME)
    
    status:
        sudo systemctl status $(SERVICE_NAME)
    
    stop:
        sudo systemctl stop $(SERVICE_NAME)
    
    log:
        sudo journalctl -u coffee-pump --since today
    
    deploy:
        rsync -av coffee-pump sensor-setup Makefile *.sh pi@XX.XX.XXX.XXX:~/

    You can find this file and all the required scripts in our repository.

View all 4 instructions

Enjoy this project?

Share

Discussions

Drew Pilcher wrote 12/05/2019 at 09:16 point

The control side seems a little over-complicated. Why not just use an arduino? But the idea is great, and it works! Nice job. I didn't know food grade pumps like this existed, good to know!

  Are you sure? yes | no

Cloud4RPi wrote 6 days ago point

Thank you. This is basically a fun but very useful project. We used the Raspberry Pi but any device may be suitable for the same approach with an ultrasonic sensor.

  Are you sure? yes | no

Mike Szczys wrote 12/03/2019 at 17:27 point

I have a Keurig that is magically always out of water and never tells you until you've pushed the button and walked away, only to return and find the "add water" light on and an empty coffee mug.

This is a brilliant solution. I have thought of plumbing a water line but I don't really want the city water pressure behind a device I built. Using the 5-gallon jug gets around that concern.

  Are you sure? yes | no

Cloud4RPi wrote 6 days ago point

Many thanks! We hope you can complete it soon.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates