Garage Gate in Home Assistant: Tapping the Remote via GPIO
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
librpitxbecause the legacy-lbcm_hostlinker dependency no longer resolves on current Pi OS, then compilingsendookandsendiqmyself. 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 reportedMessage 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:
- 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.
- 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.