Close
0%
0%

oximite - rust espresso control

Help Pico W to brew a shot.

Similar projects worth following
Oximite is an open-source hardware and software stack that brings true pressure and flow profiling to standard pump-driven espresso machines. Driven by a single Raspberry Pi Pico W running bare-metal Asynchronous Rust, it features dual PID loops, zero-cross Triac phase control, and an embedded Wi-Fi web server. ⚠️ DISCLAIMER & SAFETY WARNING ⚠️ This project involves modifying appliances that use mains voltages (110V/230V), extreme heat, and water/steam under high pressure. If mishandled, these elements can cause severe injury, death, or significant property damage. All hardware designs, code, and documentation are provided "AS IS", without warranty of any kind, express or implied. The author(s) assume no responsibility or liability for any personal injury, property damage, or other losses that may occur from building, using, or attempting to replicate this project. You are solely responsible for your own safety and the safety of your equipment. Please exercise extreme caution

Why Build This? (My Motivation)

There are already some fantastic open-source espresso mods out there (like the Gaggiuino or GaggiMate), so why start from scratch? My motivation boils down to three main things: a different design philosophy, a deep dive into embedded Rust, and the pursuit of coffee science.

1. The UI Philosophy: No Screens, Just Coffee Most existing mods rely on touchscreens. My goal is absolute minimalism. In the morning, before I’ve had my coffee, I don't want to navigate menus on a tiny screen with my big fingers. I want tactile buttons and LEDs. I can just press a single button, and the machine executes my perfect default profile. For the heavy lifting (creating profiles, viewing live telemetry charts, tuning PID), the RP2040 hosts a built-in Web Server. I, or any guests, can just pull out a smartphone browser to control the machine—no app installation required.

2. The Tech Stack: Learning Async Rust The absolute main driver for this project is educational. I wanted to deeply learn embedded Rust. By utilizing the Embassy framework, I am getting hands-on experience with Async concepts on microcontrollers. It has been a revelation; designing the system as a collection of decoupled, asynchronous tasks makes the code incredibly clean, easy to modify, and safe.

3. Industrial Control & Hardware Hacking I wanted to refresh my knowledge of industrial control loops while learning the actual physical science of coffee extraction. To handle the vibratory pump and sensors, I am writing some assembly code to run in parallel on the RP2040’s PIO (Programmable I/O) hardware blocks. Offloading microsecond-accurate phase control and flow-meter pulse timing to the PIO leaves the main async Rust OS completely free to handle the logic, math and the web server.

Ultimately, this project is the perfect intersection of my desire to learn cutting-edge embedded software techniques, industrial hardware control, and how to pull a god-tier shot of espresso.

  • Temperature PID, Flow-based Feed-Forward, and First Shots!

    pchala06/15/2026 at 19:39 0 comments

    Hey everyone! I wanted to share an update on my open-source espresso machine build. All the electrical connections are finally done!

    Thermal PID & Triac Control
    I've successfully implemented the thermal PID control. To achieve clean sensor readings, I used a hardware RC filter with a 0.1µF capacitor, combined with a software Exponential Moving Average (EMA) filter on the ADC (using a ~20Hz cutoff for temperature, and a ~4Hz cutoff to aggressively attenuate the 50Hz vibration pump ripple for the pressure sensor).

    For driving the heater reliably, I wrote a Sigma-Delta modulator that distributes power across AC full-waves. A critical trick I used here was adding a ~2.5ms delay (a quarter of a half-wave) *after* the zero-cross detection. This ensures the system toggles the MOC3042 optocoupler when the voltage is high enough (>20V), avoiding accidental single half-wave latches that could stress the components.

    Surprisingly, the triac stays completely cold! I've seen other projects use huge 25A SSR blocks with massive heatsinks, whereas my 16A triac with a tiny heatsink handles it effortlessly.

    Pressure Tracking & First Shots
    Now I have both temperature and pressure tracking in place. Check out my sleek connection for the pressure sensor!

    I pulled my first shots, and to be honest, they were quite sour and salty. I blasted through a full espresso volume in just 9 seconds! 

    **The Good News:** One massive success from this test is that the temperature drop during the shot was incredibly minimal. This is achieved using a feed-forward control mechanism based on the water flow rate. By reading the flow meter, the system anticipates the cooling effect of the incoming cold water and adds a proportional power boost directly to the PID output, stabilizing the brew temperature *before* the sensor even registers a drop.

    Next Steps
    Without proper skills in dialing in a grinder and preparing a good coffee puck, I'm a bit stuck on the coffee side of things for now. I'll be learning that in parallel with the next major milestone: designing the custom PCB!

    Thanks for reading, and I'd love to hear your thoughts or tips on dialing in shots!

  • Mechanical Overhauls, Pylons, and the Thermal Secrets of the Gaggia Boiler

    pchala05/16/2026 at 20:01 0 comments

    The Overhaul
    Since the last update, a lot of hardware work has been happening on the bench. I picked up a **Gaggia New Baby 06** to serve as the new testbed for the Oximite system.
    The first order of business was a complete teardown and overhaul of the machine to ensure a clean baseline. Overall, the restoration was a huge success, with the only lingering mechanical issue being a stubborn Over Pressure Valve (OPV) that currently isn't working as it should. I'll need to circle back to that, but the machine is otherwise structurally sound.

    Making Room for Brains
    To actually test the custom electronics, I needed space inside the chassis. I ended up 3D printing a set of custom pylons to hold the main mounting plate. This simple mod effectively creates a lower "basement" compartment giving me plenty of clearance to securely install the prototype control boards and run wiring without interfering with the high-voltage or wet components.

    Data Logging the Factory Thermostats

    Before stripping out the original control loop, I wanted to understand exactly how the stock machine behaves. I installed an NTC thermistor and spent some time logging the thermal curves driven by the factory brewing and steam thermostats (which are an absolute mechanical beauty to look at).
    This data logging led to a fascinating realization about my software design, and raised some great engineering questions for the next phase.

    The "Burst Mode" Dilemma: To Boost or Not to Boost?
    In the current iteration of my software, I implemented a "burst mode"—a feature that artificially spikes the heating power the moment a shot begins to compensate for the influx of cold water into the boiler.
    However, looking at the thermal graphs of the stock machine, I'm starting to think this software boost might be unnecessary. The data shows the factory setup sitting at a rather hot mean temperature of ~101°C right before a shot. As the shot starts, the water rapidly loses heat to the group head and portafilter (a drop of about 7 degrees), bringing it perfectly down to the ~94°C "sweet spot".
    As the shot progresses, the thermal losses decrease at roughly the same rate as the boiler temperature drops. It looks like the Gaggia engineers relied entirely on this natural thermal balancing act to keep the output temperature surprisingly stable!

    Next Steps & Experiments
    To validate this theory, I need to run a controlled experiment:
    1. Simulate the exact water flow of a real espresso shot.
    2. Accurately log the *output* water temperature (hitting the puck) under two conditions: **with** my software burst mode enabled, and **without** it.
    3. If the boost is needed, determine exactly what percentage of power yields the flattest thermal line.

    Plot of brew mode temperature

    Steam mode

  • Hardware in the Loop: Pressure Sensors, Flow Limits, and a Phase-Lag Mystery

    pchala04/06/2026 at 13:57 1 comment

    The physical plumbing is finally coming together! I received the necessary Tee-connectors and an analog manometer, which allowed me to successfully plumb the pressure transducer into the hydraulic system.

    Here is what the test bench looks like right now:

    With the hardware in place, it was time to validate the software.

    1. Linearizing the Pump (The LUT)

    Vibratory pumps are notoriously non-linear. To make the PID controller's life easier, I previously built a Look-Up Table (LUT) to map the AC phase delay to actual expected pump output. I ran a quick manual power-output sweep to verify this, and the results are excellent. The LUT successfully forces the vibratory pump to behave linearly across its pressure range!

    2. Taming the Pressure (PID Control)

    With the pump behaving linearly, I closed the loop. After running some experiments with the p_kp and p_ki coefficients, I successfully brought the system under control. The machine can now rapidly hit and hold a target pressure without oscillating. (Note: Ignore the temperature line in the graph below—the NTC sensor is currently disconnected while I focus on hydraulics!)

    3. The "Shot Saver" (Dynamic Flow Limiting)

    One of the coolest software features I’ve implemented is a dynamic flow limit. In standard espresso, if your coffee puck cracks (channeling), the physical resistance disappears, the flow rate spikes, and the shot is ruined.

    I tested my safety clamp: I set a hard maximum flow limit in the active profile. As you can see in the telemetry, the exact moment the flow hits the limit, the system aggressively drops the pressure to clamp the flow rate, effectively "saving" the shot!

    4. Hardware Quirks: The 3ms DAC and a Phase Mystery

    While scrutinizing the high-speed telemetry on the oscilloscope, I noticed two fascinating quirks about the hardware.

    First, despite the pressure transducer having an "analog" voltage output, looking closely at the signal reveals a distinct digital stepping nature. It looks exactly like an internal DAC updating its output buffer every 3ms. It is fast enough for PID loop, but an interesting reminder of how modern "analog" sensors actually work internally.

    Second, I found a mystery: When plotting the pump's Triac trigger pulse against the pressure wave, I noticed that the absolute peak of the pressure wave happens right before the Triac trigger pulse fires. 

    At first glance, this seems impossible—how can the peak pressure arrive before the electrical signal that turns the pump on?

    I have my theories, but I want to hear from the community. Drop a comment below with your ideas on why this electromechanical phase-lag happens!

  • Triac Phase Control & PIO Magic: Taming the Pump and Measuring Flow

    pchala03/24/2026 at 12:35 0 comments

    It’s time to dive into the hardware and software that actually makes the pressure profiling possible. Controlling a vibratory water pump requires precise AC phase control. To do this, we need to detect the exact moment the AC waveform crosses zero volts, and then wait a calculated number of microseconds before firing a Triac.

    Here is the schematic I am using for the zero-cross detection and Triac firing circuit:


    And here is how the modified board turned out:

    Testing the Control Loop
    Before hooking the system up to high-pressure boiling water, I needed to verify the control loop. I set up a mock environment using a potentiometer to simulate the analog pressure transducer.

    As I twist the knob to create an artificial pressure "error," the PID controller instantly recalculates and adjusts the AC phase delay to compensate. Here is the bring-up video showing the dual-PIO system running flawlessly and maintaining stable control:


    The Secret Sauce: RP2040 Programmable I/O (PIO)

    Doing microsecond-accurate AC phase chopping on a main CPU is a nightmare—if the CPU gets distracted by Wi-Fi or UI tasks, the pump stutters. To solve this, I completely offloaded the timing to the RP2040's hardware PIO blocks.

    PIO Block 1: The Wave Measurer The first state machine measures the exact length of the AC half-wave in microseconds and pushes that data to the CPU to trigger the PID calculation.

            "wait 1 pin 0", // Wait for pin to go high
            "wait 0 pin 0", // Wait for pin to go low (detect falling edge of zero-cross)
            "mov x, !null", // Initialize X counter to 0xFFFFFFFF
            "low_loop:",
            "jmp pin, rising_edge", // If pin goes high, we found the next edge
            "jmp x--, low_loop",    // Decrement X and loop
            "rising_edge:",
            "mov isr, !x",  // ISR = NOT(X) = elapsed cycles
            "push noblock", // Push the period measurement to the RX FIFO
    

    PIO Block 2: The Triac Trigger
    The second state machine pulls the calculated microsecond delay from the PID controller, waits for the zero-cross, and fires the Triac at the perfect moment. 

            "pull block",   // Pull phase delay from TX FIFO (block if empty)
            "mov x, osr",   // Move delay value to X counter
            "wait 1 pin 0", // Wait for Zero-Cross signal high
            "wait 0 pin 0", // Wait for Zero-Cross signal low (start of half-wave)
            "lp:",
            "jmp x-- lp",       // Wait for 'X' microseconds
            "set pins, 1 [30]", // Trigger Triac (pulse high for ~30 cycles)
            "set pins, 0",      // Set Triac gate low
    

    PIO Block 3: High-Resolution Flow Measurement
    I dedicated another PIO block entirely to the Hall-effect flow meter. This PIO block precisely measures time between signal edges and pushes them to FIFO. This gives the MCU incredibly high-resolution instantaneous flow rate (ml/s) and total volume readings.

            // --- 1. MEASURE HIGH STATE ---
            "mov x, !null",
            "high_loop:",
            "jmp x-- next_high", // 1 cycle
            "next_high:",
            "jmp pin high_loop", // 1 cycle
            "mov isr, !x",       // Pin went LOW, invert X
            "push noblock",      // Push HIGH duration
            // --- 2. MEASURE LOW STATE ---
            "mov x, !null",
            "low_loop:",
            "jmp pin low_done", // 1 cycle: breaks out if pin goes HIGH
            "jmp x-- low_loop", // 1 cycle: decrements and loops if X != 0
            "jmp low_loop",     // catch the fall-through and loop back
            "low_done:",
            "mov isr, !x",
            "push noblock",

    Next step is to connect pressure transducer and adjust PID coefficients. Stay tuned!

  • Software Draft Complete & Tackling the Hardware Design

    pchala03/12/2026 at 15:22 0 comments

    Welcome to the first official update for oximite, my high-performance, asynchronous Rust controller for smart espresso machines (running on the Raspberry Pi Pico W).

    I’m thrilled to announce a major milestone: the initial software draft is largely complete!
    Even better, I’ve built out a full Hardware-In-the-Loop (HIL) simulation suite, and all of my Python-driven integration tests are passing successfully.
    With the core firmware architecture, PID loops, web interface, and state machines solidifying, it’s finally time to move towards the physical world: Hardware Design.

    First Up: AC Zero-Cross & TRIAC Pump Control

    The very first hardware hurdle I am tackling is precision control of the espresso machine's vibratory pump (like the classic Ulka pumps) alongside flow sensor calibration.

    To achieve accurate pressure profiling and flow control, I am designing a phase-angle TRIAC firing circuit synced to the 50/60Hz mains. But I am doing the zero-cross detection a bit differently to give me a distinct software advantage.
    Instead of a circuit that just blips at the exact zero-crossing point, my zero-cross detector is designed to output HIGH for one complete half-wave and LOW for the other.
    Why does this matter? Standard vibratory pumps require half-wave AC to function correctly, which is usually achieved by putting a diode in series with the pump. Because my zero-cross circuit lets the Pico W easily distinguish between the positive and negative half-cycles of the AC mains, I can write my firmware to trigger the TRIAC only on the correct half-waves, entirely skipping the others.

    As a side effect, doing this in software means oximite will be able to drive pumps that don't have an inline diode, or even pumps where the existing diode has fried and shorted out! I handle the half-wave rectification directly via smart TRIAC switching.

    Next Step: Flow Calibration

    Alongside the pump control, I am calibrating the Hall-effect flow meter.

    Stay tuned as the PCB routing begins! Let me know in the comments if you've ever dealt with fried pump diodes, or if you have any thoughts on the hardware approach. ☕⚙️

View all 5 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

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