The Raspberry Pi board is an amazing platform. Through the creativity and experimentation of many people, endless possibilities await. One of the coolest features, to me, is the fact that the Pi can transmit radio signals using one of its 40 GPIO pins. I have tried this with a Pi 2B, 3B, and 3B+. I am unsure if it works on the newer Pi 4 family of boards. This project was partially completed a while ago, so I will try to go into the details that I can… but some of it is a little foggy.
The signal is very dirty, whereas it creates some harmonics that could cause harmful interference if not properly filtered. Thankfully, the output power is well under 100mW, which makes it perfect for small experiments. I connected a small length of wire (nowhere in tune) to GPIO 18 of my Pi to create a small antenna.
I wanted to devise a system to use the Pi to open and close my overhead garage doors. Garage door opener remotes are simply radio transmitters, broadcasting a small encoded signal that the opener receives and then triggers the action to open, close, or stop the door.
The first step in the process was to determine the frequency that my garage door openers operated on. The easy way to do this was to simply google the device. All commercial radio controlled devices are registered with the FCC, and you can simply find the frequency by searching for the FCC ID. Mine was found to be operating at 390.000Mhz.
I used a RTL-SDR dongle to record the signal of my garage opener. Now I am not going to go into detail on this process since an awesome write up can be found here. If you are a nerd like me, and have not played around with RTL-SDR dongles, you need to. They are a ton of a fun for numerous reasons.
So once we have a recording, we use a software called RPITX, a software package that allows you to transmit radio frequency signals using the General Purpose Input/Output (GPIO) pins on a Raspberry Pi computer. I wrote a simple bash script to run the command to send the signal. I named the file “A-Garagedoor.sh” and placed it in my /var/www/html, which is the web directory for my apache server running on my pi.
#!/bin/bash
sudo /home/pi/rpitx/sendiq -s 250000 -f 390.0000e6 -t u8 -i /home/pi/rpitx/garagedoor/A.iq
Lets break down the command:
sudois used to run the command as superuser./home/pi/rpitx/sendiqis the path to thesendiqprogram, which is used to transmit the signal.-s 250000sets the sample rate to 250,000 samples per second.-f 390.0000e6sets the frequency of the transmitted signal to 390.0000 MHz.-t u8sets the type of the input file to be 8-bit unsigned integer.-i /home/pi/rpitx/garagedoor/A.iqis the path to the input file that contains the signal to be transmitted (that we previously recorded).
Obviously we can use the second line of our little bash script to send the signal whenever we want. The remote for the garage door opener does not differentiate from an open or close signal – well at least mine does not, so it will cycle every time the button is actuated.
To run the script we would execute:
pi@raspberrypi: $ A-Garagedoor.sh
Obviously the correct permissions need to be set for it to execute correctly. So next was a small web site for me to access in my LAN (from my phone when connected to WiFi or VPN) to open and close the door.

<?php
if (isset($_POST['DoorA'])) {
shell_exec("./A-Garagedoor.sh");
echo("Garage door A triggered.");
}
if (isset($_POST['DoorB'])) {
shell_exec("./B-Garagedoor.sh");
echo("Garage door B triggered.");
}
?>
<html>
<head>
<title>Garage Door Status</title>
https://code.jquery.com/jquery-3.5.1.min.js
</head>
<body>
<center>
<p id="doora" style="color:blue"></p>
</center>
<form action="garage.php" method="POST">
<input type="submit"
style="height:50%;
width:100%;
font-size:300%;
background-color:green"
name="DoorA"
value="Open/Close Door A">
</form>
<center>
<p id="doorb" style="color:blue"></p>
</center>
<input type="submit"
style="height:50%;
width:100%;
font-size:300%;
background-color:green"
name="DoorB"
value="Open/Close Door B">
</form>
<BR>
<BR>
<center>
<a href="/index.php">BACK TO DASHBOARD</a>
</center>
<script>
function update_garage_door_status() {
$.getJSON("garage_doors_status.php", function(result) {
aGarageDoor = result['stateOfDoorA']
bGarageDoor = result['stateOfDoorB']
if(aGarageDoor == '1') {
document.getElementById("doora").innerHTML = "Garage Door A Is <b>OPEN</b>";
}
if(aGarageDoor == '0') {
document.getElementById("doora").innerHTML = "Garage Door A Is <b>Closed</b>";
}
if(bGarageDoor == "1") {
document.getElementById("doorb").innerHTML = "Garage Door B Is <b>OPEN</b>";
}
if(bGarageDoor == '0') {
document.getElementById("doorb").innerHTML = "Garage Door B Is <b>Closed</b>";
}
});
}
update_garage_door_status();
setInterval(function() {update_garage_door_status();}, 10000);
</script>
</html>
Now there is a lot going on there for a little blog post. I do have extra scripting there for identifying the position of the garage door using JSON (JavaScript Object Notation), which I am not going to get into for this post. The main points to focus on are:
<?php
if (isset($_POST['DoorA'])) {
shell_exec("./A-Garagedoor.sh");
echo("Garage door A triggered.");
}
This is a PHP code snippet that runs a shell script named “A-Garagedoor.sh” when a POST request is made to the URL with the name “DoorA”. The “isset” function checks if the “DoorA” value is set in the $_POST array, and if it is, the “shell_exec” function runs the shell script. The script will execute the commands within it and return the output. The “echo” function then outputs the string “Garage door A triggered.” to the browser.
The next area to focus on is this:
<form action="garage.php" method="POST">
<input type="submit"
style="height:50%;
width:100%;
font-size:300%;
background-color:green"
name="DoorA"
value="Open/Close Door A">
</form>
This creates a button. The button is set to submit data to a file called “garage.php” using the HTTP POST method. The submit button has the following properties:
style="height:50%; width:100%; font-size:300%; background-color:green"sets the button’s height to 50% of the containing element, width to 100% of the containing element, font size to 300% of the default size, and background color to green.name="DoorA"sets the name attribute of the button to “DoorA”. This is used to identify the button when the form is submitted and the data is processed.value="Open/Close Door A"sets the text displayed on the button to “Open/Close Door A”.
When this form is submitted, the data “DoorA=Open/Close Door A” will be sent to the “garage.php” file for processing. It would then send the shell command mentioned before.
To sum up where we are so far, using RPITX software you can transmit RF signals directly from the GPIO 18on a Raspberry Pi. It does this by modulating the signals on the GPIO pins to produce a radio frequency signal. A simple website, which was accessible in my LAN, was used to actuate the script that I wrote, which in turn used RPITX to send the signal to the door opener.
The next phase of this project involved using an ESP8266 to make a remote opener that I would mount in my kitchen. As I mentioned before, I do have a way of sensing if my garage door is open or closed. Using a reed switch mounted to the door and the frame of garage, another ESP8266 is then able to determine if the door is shut or not. This ESP displays that status in JSON, which the Pi uses to display if the door is open or closed.
To sum up where we are so far, using RPITX software, you can transmit RF signals directly from the GPIO 18on a Raspberry Pi. It does this by modulating the signals on the GPIO pins to produce a radio frequency signal. A simple website, which was accessible in my LAN was used to actuate the script that I wrote, which in turn used RPITX to send the signal to the door opener.
The final part of this project involved using an ESP8266 to make a remote opener that I would mount in my kitchen. As I mentioned before, I do have a way of sensing if my garage door is open or closed. Using a reed switch mounted to the door and the frame of garage another ESP8266 is then able to determine if the door is shut or not. This ESP displays that status in JSON, which the Pi uses to display if the door is open or closed. Now I will mention phase one was done about two years ago, and phase two was done last week – yeah a little bit of a delay.
I wanted the remote opener to be located in my kitchen (my house is not connected to the garage) and serve two purposes:
- Open / Close Door A using a momentary push button switch
- Display the status of Door A using two LED bulbs (Blue = door closed / Red = door not closed)

I used LEDs with inline resistors, that are rated at 12VDC input. Using them with the ESP allows them to be dimmer as the ESPs GPIO pins are only giving out 3.3VDC. I connected red to GPIO 15, blue to GPIO 13 and one leg of the momentary button to GPIO 12.
Time to write a sketch:
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
unsigned long previousMillis = 0;
const unsigned long interval = 15000; // 15 seconds
const char* ssid = "xxxxxxxxxx";
const char* password = "xxxxxxxxxx";
const char* host = "IP ADDRESS OF PI";
const int httpPort = 80;
const int buttonPin = 12;
void setup() {
pinMode(13, OUTPUT);
pinMode(15, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(115200);
delay(250);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// Fetch the JSON string from the specified IP address
String jsonString = fetchJSON("192.168.86.134/garage_doors_status.php");
// Parse the JSON string
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, jsonString);
if (error) {
}
// Extract the state of door A and B
int stateOfDoorA = doc["stateOfDoorA"];
int stateOfDoorB = doc["stateOfDoorB"];
// Control the pins based on the state of door A
if (stateOfDoorA == 1) {
digitalWrite(15, HIGH);
digitalWrite(13, LOW);
} else {
digitalWrite(15, LOW);
digitalWrite(13, HIGH);
}
}
if (digitalRead(buttonPin) == LOW) {
Serial.println("Button Pressed!");
HTTPClient http;
http.begin("http://" + String(host) + "/garage.php");
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
int httpCode = http.POST("DoorA=1");
if (httpCode > 0) {
String response = http.getString();
Serial.println("HTTP Response: " + response);
}
else {
Serial.println("Error sending POST request");
}
http.end();
delay(1000);
}
}
String fetchJSON(const char *host) {
HTTPClient http;
// Make a GET request to the specified host
http.begin("http://" + String(host));
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
// Handle the error
return "";
}
// Return the response body
return http.getString();
}
Once the sketch was pushed to the ESP8266, it was time to dry run it and see if it was functioning as designed. And to my delight it was in fact working as intended. This sketch is set to check the status of the garage door (ESP8266 (opener) – RaspberryPi – ESP8266 (garage door sensor) every 15 seconds.

I then designed a small box that allowed access to plug in a micro-USB cable on the bottom and had a hole for the push button switch and both LEDs. The box is built much like a small shoe box, as it allows the cover where the LEDs and switch are mounted to be removed with ease. I attached the completed apparatus to our charging station in the kitchen and began using it. The only real issue of note that I have had, which was known before I made the remote opener, is that the reed switch sometimes provides a false open reading. This is due to the snow and ice that does build up at the bottom of my garage door, which with spring around the corner, should clear up.