garagegpioraspberry-pihome-assistant

Garage Gate in Home Assistant: Tapping the Remote via GPIO

/Published/Updated

The gate was the last holdout. Everything else in the house eventually landed in Home Assistant — lights, shutters, energy, the car. Not the driveway. And the reason was almost insultingly simple: the only interface to the gate is a handheld remote. No cloud service, no app, no terminal block I can reach without opening up the drive unit. A Belfox handset on 868 MHz, a battery, two buttons. That’s the whole API.

The obvious move is: fine, I’ll record the RF and replay it. I genuinely tried. For weeks. It did not work. What does work — and what now operates all three of my gates — is far less glamorous: the Raspberry Pi electrically presses the button inside the original remote.

Why the RF route failed for me

Some context so you don’t repeat it. My handset is a Belfox 7834-E-M, and it is not rolling code — unlike my SOMFY shutters. So capture-and-replay is theoretically on the table. That’s exactly why I went so deep: fixed code, my own gate, a drawer full of SDR toys. I was gonna get medieval on that thing.

What I burned time on:

  • RTL-SDR captures at 868.300 MHz. I got raw IQ material, clean recordings of several button presses, the lot.
  • OpenMQTTGateway with a CC1101 on an ESP32. Broker up, device reporting cleanly, frequency swept from 868.20 to 868.40 MHz — and not one usable receive event for that remote came through.
  • rpitx on the Pi. This was the heaviest lift: rebuilding librpitx because the legacy -lbcm_host linker dependency no longer resolves on current Pi OS, then compiling sendook and sendiq myself. After that I pushed candidate timings out over GPIO 4 — that’s the pin rpitx keys the carrier on, physical pin 7. The Pi dutifully reported Message successfuly transmitted.

The gate? Never moved. Not once.

That’s the most maddening possible state: the transmit path demonstrably works, and the receiver simply doesn’t care. Somewhere between timing, modulation, preamble length and repeat count something is off — and without a working receive path you have no reference to debug against. You’re guessing. I guessed for weeks — with growing ambition and a hit rate of exactly zero.

The pivot: don’t rebuild the signal, replace the finger

The thought that turned it around: I don’t need the RF. I need the button press. The handset already speaks the protocol perfectly — it’s paired, it’s on the right frequency, right encoding, right output power. The only missing component is a finger.

So: open the remote, solder two wires to the switch pads, and have the Pi close that contact for a few hundred milliseconds. That’s button emulation. The remote stays electrically intact and keeps running on its own battery.

That last point matters more than it sounds. Power the remote from the Pi and you suddenly share a ground between a 3.3 V computer and a coin-cell circuit whose ground reference you don’t actually know. That’s the textbook setup for ground loops, level problems, and in the worst case a dead GPIO pin.

The golden rule: 3.3 V, and nothing unknown near the Pi

This is the part I can’t write often enough:

Raspberry Pi GPIOs are 3.3 V pins. Not 5 V. Not “whatever”. An unknown signal from someone else’s PCB never goes straight onto a GPIO.

The same applies to the common 868 MHz modules like the QIACHIP RX480E-868, if you insist on that path: in transmit mode its D0–D3 are active-low inputs that should not be driven directly from Pi GPIO without a transistor, MOSFET or optocoupler. In receive mode you must level-shift down to 3.3 V before anything touches the Pi. Neither of those is optional garnish.

Transistor or optocoupler?

Both work in principle. I’d still steer you firmly to the optocoupler.

An NPN transistor (garden-variety BC547 or 2N2222, with a base resistor in the typical 1 kΩ ballpark) is cheaper and faster to build — but it assumes the remote’s ground and the Pi’s ground are tied together, and that you know which side of the switch sits at ground. On a third-party handset you often don’t know that for sure. Some buttons don’t switch to GND at all; they sit in a wake-up circuit.

The optocoupler — a PC817 is plenty — sidesteps the whole question with galvanic isolation: the Pi side and the remote side share no electrical connection whatsoever. The output is a genuinely dry contact. It no longer matters how the transmitter’s PCB is wired internally, and a mistake on one side can’t drag the other side down with it.

I keep a PC817 optocoupler assortment* around because these things come up constantly. Add a fine-tip soldering iron — the pads inside a handset are small — and some Dupont jumper wires* back to the Pi.

Treat those component values as standard, illustrative choices you must verify against your own hardware, not as a bill of materials I measured on your remote. Meter it before you solder.

The wiring, in principle

Pi side (optocoupler input):

  • GPIO → series resistor (around 330 Ω is typical for a PC817 at 3.3 V) → LED anode
  • LED cathode → Pi GND

Remote side (optocoupler output transistor):

  • Collector and emitter across the existing button, correct way round
  • And nothing else. No shared ground, no power from the Pi. The battery stays in.

If you get the polarity wrong on the remote side, nothing happens — no damage, just flip it. That’s the second argument for the optocoupler: it forgives you.

Crucially: a pulse, not a latched output

A gate remote only understands a press. Hold the contact closed permanently and, as far as the transmitter is concerned, someone is leaning on the button forever: it transmits continuously, the battery is dead within hours, and depending on mode the receiver sees nonsense.

So: a 300–500 ms pulse, then release.

In Python with gpiozero:

#!/usr/bin/env python3
"""A short pulse into the optocoupler = one button press on the remote."""
from gpiozero import DigitalOutputDevice
from time import sleep

# GPIO 4 (BCM) = physical pin 7
GATE = DigitalOutputDevice(4, active_high=True, initial_value=False)

def press_button(duration: float = 0.4) -> None:
    GATE.on()
    sleep(duration)   # 300-500 ms is a realistic button press
    GATE.off()

if __name__ == "__main__":
    press_button()

initial_value=False is not a detail. Without it the pin can be briefly undefined at boot — and a gate that opens whenever the Pi reboots is not a thing you want in your life.

In Home Assistant itself I drive this from an ESP node, because it lives closer to the gate. The ESPHome config for a clean pulse output:

switch:
  - platform: gpio
    pin: GPIO4
    id: gate_contact
    restore_mode: ALWAYS_OFF

button:
  - platform: template
    name: "Driveway gate pulse"
    id: gate_pulse
    on_press:
      - switch.turn_on: gate_contact
      - delay: 400ms
      - switch.turn_off: gate_contact

cover:
  - platform: template
    name: "Driveway gate"
    device_class: gate
    open_action:
      - button.press: gate_pulse
    close_action:
      - button.press: gate_pulse
    stop_action:
      - button.press: gate_pulse
    optimistic: true

restore_mode: ALWAYS_OFF is there for the same reason as above. And optimistic: true because the handset gives you no feedback at all. It’s a one-way transmitter. Home Assistant doesn’t know whether the gate is open — it only knows it sent a pulse. If you want real state, you need a reed switch or limit-switch sensor on the gate itself. That’s a separate project, but it’s worth doing.

The three options side by side

RF replay / interception GPIO button emulation Commercial gate module (Shelly-style)
Cost ~£10–40 (CC1101/RTL-SDR), Pi already there ~£2 per gate + a spare remote ~£25–50 per gate
Difficulty very high, open-ended medium, soldering required low
Reliability for me: 0 % high, stable for months high
Reversible yes, nothing touched yes, if you sacrifice a spare remote no, you’re inside the drive unit
Drive warranty intact intact often void
Needs motor access no no yes, terminal block on the motor

The underrated advantage of button emulation: you never touch the drive unit. No terminals on the motor, no ladder, no warranty question on the expensive part. You sacrifice a ~£20 handset instead — and you buy that as a second remote and pair it beforehand, so your original stays untouched.

Scaling to three gates

I have three gates. Two approaches make sense:

  1. One handset with several buttons — one optocoupler per button, one GPIO per optocoupler. Cheap and compact. Only works if a single remote genuinely covers all your gates.
  2. One handset per gate — each on its own battery, each galvanically isolated from the Pi. That’s my setup. Slightly more cable clutter, but zero interaction: no shared ground reference, no transmitter interfering with another, and if one dies the rest keep running.

Because each remote stays on its own battery, there is no ground loop between channels. That’s precisely what makes this solution so boringly stable. On batteries: set yourself a reminder. A handset that gets “pressed” by automation several times a day won’t last as long as one living in a coat pocket.

Safety — and this isn’t boilerplate

A powered gate is a crush hazard. It has enough force to seriously injure a child.

  • Never automate gate movement without line of sight. No “close the gate at 22:00” automation unless you’re standing there, or you have working photocells and/or a safety contact edge.
  • Existing safety mechanisms stay exactly as they are. Photocells, force cutoff, safety edge — none of it gets bypassed. Your hack sits upstream of the drive, not in the middle of it.
  • Opening the handset voids its warranty. Use the spare.
  • In some jurisdictions, unattended remote operation of a gate carries insurance and liability implications. If you want certainty, a quick call to your insurer before you fully automate is cheap.
  • On the RF question: transmitting on 868 MHz is regulated. Cloning a fixed-code remote that you own, for your own gate, is generally fine — blasting arbitrary signals into that band is not. One more reason I sleep better with button emulation: the only thing still transmitting is the type-approved original device.

Bottom line

If the handset is your only way into the gate, don’t start by rebuilding its RF protocol. I did that. I got rpitx compiled and running, I demonstrably transmitted, and the gate stayed shut.

Two euros of optocoupler, ten minutes of soldering and a 400 ms pulse did what weeks of reverse engineering couldn’t. A two-euro part beat the SDR, the CC1101 and a hand-compiled rpitx. Fair enough. It isn’t the elegant solution. It’s the one that has worked every single day for months.

I'm René, a CTO based near Linz, Austria. If your problem is bigger than a roller shutter — streaming, cloud, local LLMs, technical leadership — here's what I do for a living.