projects / zigbrew

zigbrew, or how my Sage Barista Express joined the smart home

Two Zigbee dry-contact relays across the POWER button and the grinder switch, driven by Home Assistant. It turns itself on when I get in the shower and grinds a dose when I walk into the kitchen. Here's the whole build, traps included.

Sage BES870Sonoff MINI-ZBDZigbee2MQTTHome Assistant230 V inside September 2026 · 15 min read
why

The Barista Express heats its thermoblock in a couple of minutes. Everything the coffee actually touches takes twenty.

Group head, portafilter, basket, the metal around them: all of it has to soak up heat before the first shot tastes right. Sage's own manual says a cold portafilter and basket lower the extraction temperature enough to spoil it. I've tested that claim by impatience, many times. It holds.

So the machine needs to come on about twenty minutes before I want coffee, and I don't want to think about it. kopiro did the power button with a Tuya relay, building on a Home Assistant community thread. I did the same with Sonoff relays, then kept going and did the grinder.

how it works

fn press_the_button()

Nothing switches the machine's mains. A relay just presses the button for me, electrically. The relay's contact sits across the same two wires the front-panel button uses, closes for half a second, and lets go. The board can't tell the difference.

Home Assistantswitch.turn_on"zigbrew_power" Zigbee2MQTTMQTT in, Zigbee outcoordinator, ch 24 MINI-ZBD relayinching 0.5 s POWER button wiresblack pos 1 · white pos 7button still in parallel Sage main board"someone pressedPOWER" MQTT Zigbee dry contact low voltage POWER ring standby
relay open · machine in standby
The contact closes for 0.5 s and opens by itself. It is the relay that guarantees that, not an automation, so a broken automation can never hold the button down.

Two properties of the relay make this safe. It must be momentary, so it can never hold the button. And it must be a dry contact, an isolated switch that puts no voltage of its own into the machine. That second one turns out to matter more than I expected.

what you need

const parts

The Sonoff MINI-ZBD on calipers, terminals annotated
The MINI-ZBD. Left column NO / COM / NC is the dry contact. Right column N, L for mains in. S1, S2, DC+ and DC- stay empty. 41 × 41 × 22 mm.
ItemNotes
Sage Barista Express BES870or the Breville twin. Other Sage models differ inside.
Sonoff MINI-ZBD, one per button100-240 V AC in, isolated NO/COM/NC out. Zigbee2MQTT supports it natively, inching included.
WAGO 221 lever connectors5-way for the mains tap if you're feeding two relays.
0.75 mm² flexA metre of ordinary three-core does every lead.
Soldering iron, heat shrinkFor the button wires. WAGOs don't fit there.
Multimeter with a beeperContinuity is the whole job.
Cable ties, tape, a markerLabel everything. Every wire in the loom is the same colour.
// safety

Part of this touches 230 V. Unplug at the wall before opening anything, and again before every step that touches wiring. The board's low-voltage side is not isolated from the mains, so never probe it plugged in. If that sentence worries you, this isn't the project for you.

step 1

open the_machine

Water tank off, every screw on the back, then the one you'll miss: under a round plastic cap in the tank recess, just right of the water inlet. Prise the cap off with a fingernail. The rear panel then lifts a few millimetres and tilts out from the bottom. The cup tray on top lifts off separately, with the bean-hopper cradle on its underside.

Rear of the machine with the tank off, the hidden screw cap marked
The cap on the right of the inlet valve. kopiro lost half an hour to this one. I lost ten minutes because he'd warned me.
Rear view with the panel off, ten parts annotated
Rear view, panel off. Left half is hot and wet: thermoblock, solenoids, pump, heatsink. Right half is the grinder motor over the main-board box, and the mains entry at the bottom. Everything I added lives on the right.
step 2

read the_board

Four corner screws on the white box. The board is a BV1871A1, and two things about it shape everything after.

Main board component side, seven callouts
Component side. The two white headers on the right edge carry the front-panel loom. Bottom left is the power supply, and it is a capacitive dropper.

The front-panel wiring plugs in. A loom of identical off-white wires lands on two headers next to the microcontroller. One serves the POWER side of the panel, the other the shot buttons. That's where the probing happens.

The supply is transformerless. Yellow X2 capacitors, chokes, a diode chain, no transformer. So the board's "ground" sits at mains potential while the machine is plugged in. It's why the relay must be a dry contact, why you only probe with the plug out, and why I dropped the tempting idea of running the relay from the board's 12 V rail. There is one, the heater relay coil uses it, but it's a live 12 V.

step 3

find the_two_wires()

You need the two wires the POWER button joins when pressed. Every wire in the loom is the same colour except one, so this is done by position, and the neat way removes every confusing reading the board would otherwise give you.

POWER-side plug, unplugged from the board, numbered from the keyed end 1 continuity open probe A probe B POWER button on the panel
probe A on 1, sweep B along the rest, button held
The pair that beeps only while the button is held is the one. On mine: the single black wire at position 1, and white at position 7, on the top header.
Unplug the machine. Pull the POWER-side plug off its header.

With the plug off, the wires only see the panel: buttons and LED rings. A pressed button is a clean short. Nothing else looks like one.

Number the positions on a strip of tape.

The crimp tails are exposed where each wire enters the housing, so a normal probe tip reaches them. No need for anything thinner.

Probe A on 1, hold POWER, sweep probe B. Then A on 2, and so on.

Tape the button down so both hands are free. LED lines show a one-way forward drop whether or not a button is pressed; ignore those.

Write both positions down, and photograph the tape.

The forum thread found "first and fourth" on a different unit. Treat any such number as a hint to check first, never as an answer.

The front-panel plug held unplugged, all wires off-white
The plug in hand. Not a ribbon cable, a loom of identical wires crimped into one housing, which is why numbering is the only way.
Behind the panel carrier, the white ON wire tagged and the black wire beside it
Found and tagged, at the spot behind the panel carrier where the loom has slack. That's where the joints go.
step 4

power the_relays()

kopiro spliced into the mains. On the BES870 it's easier: where the cord's brown and blue meet the internal wiring, just below the control box, each core ends in an insulated bullet connector. Pull it apart, cut the crimps off, and a WAGO takes both halves plus the new lead. Nothing else is cut, and the bullets can be re-crimped if the mod ever comes out.

mains cord machine internal wiring bullet bullet as found: push-fit crimps, one per core WAGO 221-415WAGO 221-415 relay 1POWER LN relay 2GRIND LN one colour per block: three browns, three blues. Earth untouched on its stud.
Brown to L, blue to N on each relay. Route the leads up the gap between the two halves of the machine, alongside the loom, and tie them to it.
WireViz harness drawing for the mains tap: cord, two WAGOs, machine wiring, relay L and N
← scroll sideways →open full size ↗
The same step as a harness drawing, generated from a short WireViz description. Every wire, colour and terminal, nothing implied. Scrolls sideways.
Two WAGO blocks in place of the bullets, leads running to a relay on the bench
The tap as built, relay on the bench for the first power-up. Top block all blue, bottom block all brown. The one mistake that goes bang is mixing them, so check twice.
step 5

configure before_you_wire()

This order matters. Out of the box the relay is a normal on/off switch, and some modules restore to "on" after a power cut. Either would hold the button down forever. So with L and N connected and nothing on COM or NO, plug in with your hands clear, pair in Zigbee2MQTT, and set four things.

Zigbee2MQTTValueWhy
power_on_behavioroffcomes up open after a power cut
inching_controlONmomentary
inching_time0.5 sthe minimum, plenty for a press
inching_modeOFFturn on, then auto-off
zigbee2mqtt/zigbrew_power · pulse test
# three ON commands, four seconds apart

Toggle it three times from Z2M. Each time the relay clicks, reports ON, then reports OFF within a second with no further command. That log is the moment I stopped worrying about the design. Unplug again before the next step.

step 6

tee into_the_button()

Each of the two wires is cut once and rejoined with a third lead branching to the relay. The real button stays in circuit, in parallel with the relay contact, so it works whatever the relay does.

front panelPOWER button main boardtop headerpos 1 · pos 7 joint joint black, pos 1 white "ON", pos 7 COMNO relay 1 contact, in parallel with the button not NC. NC would hold the button down.
Solder, not WAGOs: there's no room behind the panel carrier. Heat shrink on first, cut, strip 10 mm on all three ends, twist along the length, solder, shrink, tie to the loom.
WireViz harness drawing for the button breakout: panel wires, two joints, board plug, relay COM and NO
← scroll sideways →open full size ↗
The breakout as a harness drawing. Positions 1 and 7 on the top plug, joints in the loom, third leads to COM and NO.
Three black wire ends twisted and soldered into one joint
The black joint before its heat shrink: panel side, board side, and the new COM lead. Tug each wire before covering it.
The relay with all four leads on: L, N, COM, NO
All four leads on. White to NO, black to COM, NC empty, brown to L, blue to N. Resting on the grinder for the photo only.
// it worked

Loom plugged back on, machine plugged in, one tap in Home Assistant: the Sage woke up as if I'd pressed the button. The physical button still worked. Phase one done. Then I noticed something on the board.

bonus round

also the_grinder()

Three colour-keyed two-pin connectors on the board are silkscreened Steam, Hot water and Port filter. I'd assumed they drove the solenoids. A meter across the blue Port filter one while pressing the portafilter into the grinder cradle: continuity. They're switch inputs, and blue is the grinder trigger.

The cradle microswitch on the grinder housing with its blue pair
The other end of the blue pair: a microswitch on the grinder housing, lever facing the chute. The portafilter presses it. The second relay's NO and COM went across those two wires.
A relay held in the top compartment beside the grinder gear
Where both relays live: the top compartment between the panel carrier and the grinder gear, on the cool side of the divider, tied so nothing can drift into the teeth.

Both wires are blue, and it doesn't matter which is which: a switch contact has no polarity. Relay two was configured identically to relay one, and a 0.5 s pulse runs a normal dose. Two things I learned the hard way:

// standby

The grinder is ignored while the machine is in standby. POWER first, always. An automation that grinds before it wakes the machine does nothing, silently.

// hopper interlock

With the cup tray off, the bean hopper isn't locked, the interlock on the tray's underside is open, and the grinder won't run for the relay or the real switch. The machine tells you with SINGLE and DOUBLE flashing alternately. Not a fault: "lock the hopper". Cost me twenty minutes.

Front panel with the single and double lights flashing
What "hopper not locked" looks like. If you're testing with the top off, that's why nothing grinds.
home assistant

automation morning_coffee

My house already knows when I've had a shower: a current sensor on the shower pump sets a marcus_showered flag that's cleared each night. And the kitchen has occupancy sensors that run the lights. So the coffee hangs off those, not a fixed time.

shower detectedpump > 1 A for 30 s05:00-11:30 · awake · home press POWERrelay 1 pulsesset: coffee armed first kitchen arrivaloccupancy on, armed,POWER pressed > 60 s ago disarm, then GRINDflag off first,so nothing can grind twice fail-safe disarmarmed 2 h unused, or 03:00 once a morning, by construction: the shower flag only rises once a day, and the armed flag is consumed by the first grind.
Three automations and one helper. The grind one is below; the other two are a state trigger and a timeout.
automations.yaml · the grind
- id: zigbrew_kitchen_grinds
  alias: "zigbrew - Kitchen arrival runs the grinder"
  triggers:
    - trigger: state
      entity_id: [binary_sensor.kitchen_occupancy_a, binary_sensor.kitchen_occupancy_b]
      to: "on"
  conditions:
    - condition: state
      entity_id: input_boolean.zigbrew_coffee_armed
      state: "on"
    - condition: template
      value_template: >-
        {{ (now() - states.input_boolean.zigbrew_coffee_armed.last_changed).total_seconds() > 60 }}
  actions:
    - action: input_boolean.turn_off        # disarm first
      target: { entity_id: input_boolean.zigbrew_coffee_armed }
    - action: switch.turn_on                # then one press
      target: { entity_id: switch.zigbrew_grind }
  mode: single
before you start

// what_id_tell_you

the hidden screw

Under the cap in the tank recess, right of the inlet. Look there first.

tray off = hopper unlocked

No grinder, and flashing filter-size lights. Not a fault.

tank off = pump primes dry

Every power-up. Keep test runs to seconds, or put the tank back before testing.

WAGOs won't fit up top

Solder the button breakouts. WAGOs are fine at the bottom for the mains tap.

don't power the relay from the board

The 12 V is there, but it's referenced to mains.

HA can't see if the machine is on

POWER toggles. If it were already on when the shower fired, the press would turn it off. A power-monitoring plug fixes this; the heater draw is unmistakable. That's the next change.

the grind lands where the cradle points

Portafilter in the cradle at night.

configure before you connect

Inching and power-on-off first, with COM and NO empty. Then wire the button. Never the other way round.

reference

export full_harness

Everything from the mains inlet to the front panel in one drawing, as built for the POWER relay. The grinder relay is the same pattern on the blue pair. It is generated from a short WireViz YAML file, so if your machine's positions differ, it is two lines to change and re-render.

Complete WireViz harness for phase one: mains inlet, WAGO splices, relay, front-panel loom breakouts
← scroll sideways →open full size ↗
Phase 1 harness. Left to right: mains inlet, WAGO splices, machine wiring and relay power, then the main board plug, the two loom breakouts, and the front panel.
what's next

A power-monitoring plug for machine state. Then the shot buttons, which are the same trick on the other header. Tamping still needs a human, so that one will be a voice command rather than an automation.

Thanks to kopiro for going first, and to the Home Assistant community thread that started it.