Door alarms are a common sight and have become more sophisticated over the years to increase security and reliability.Many of these new boards are either proprietary or, as with Zigbee, more expensive compared to traditional options. These old door alarms operate on 433MHz and use an amplitude-based, fixed code encoding called 1527, typically based on the EV1527 chip. Poor signal is common with these devices and the transceivers found online. If one wants to transmit these messages much further away, a forwarder is needed.
Enter LoRa
LoRa (Long RAnge) is a spread spectrum modulation technology designed for low power, long distance, and IoT communication. A basic LoRa setup using a wire antenna can easily reach a mile. Development boards are also relatively inexpensive at around $30.
The Problems
I have a shed at least 100 yards away and wanted to add a door alarm to it. The initial setup used two LoRa boards, one installed at the shed door with a reed switch, and another at the house with a buzzer connected. This shed has two double doors securing two sections. Another reed switch can be run to the other door but there is a second smaller shed on the property without power, necessitating a battery-powered wireless solution. Using more LoRa boards is overkill and more expensive than using purpose-built door sensors.
I can install the common 433MHz door switches (and a couple smoke detectors using the same 1527 encoding) and create a receiver at the house. This would cut out the LoRa boards entirely and only need a single receiver at the house. This sounded great until I implemented the system. It turns out the wireless range is was abysmal and cannot reach the house. See here
The Solution
I’ll use the LoRa transmitter in the shed as a forwarder to the house and just change the receiver’s firmware. The flow will look like this:
Hardware
- Boards
- Adafruit Feather RF96 M0
- This can be swapped for an ESP board with an RF96 chip but the pin numbers may differ. This ARM chip doesn’t have as large of a library compared to the ESP boards.
- Antenna: 8.2cm length of wire
- 433MHz: RXB6 Receiver
- Other receivers have terrible sensitivity or are packet-based. Do not use them.
- Antenna: 17.32cm length of wire
- Adafruit Feather RF96 M0
- Sensors:
- Generic 433MHz sensor supporting 1527
- Generic reed switch(optional)
- Cases(3D printed):
- Found on Thingiverse.
- Buzzer
- Any 3.3V buzzer large enough to hear across the house.
The RXB6 receiver.
Wiring
The RXB6 can be powered by 3.3V and, if 5V is used, the receive pin can be connected directly to an input pin on the microcontroller as it never exceeds 3.3V. In my case, I never saw it over 1.2V. The 912MHz antenna is attached to the small ant via on the top right of the Feather board. If using the two reed(magnetic) switch pins, you can place a low value resistor inline or enable the pullup resistor which is what the code below does.
The wiring can be determined by looking at the code or at the photos below.
Forwarder
Note: The 912MHz antenna can be pointed downward and the ground is expanded on the protoboard to accomodate all the devices. This isn’t needed if just using the RXB6
Receiver
Firmware
I used the Arduino IDE with the RCSwitch, SPI, and RadioHead libraries. They can be found in the Library Manager.
The Adafruit Feather M0 needs a board repository added: https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
This option can be found in the preferences menu under Additional Boards Manager URLs.
Flashing the forwarder
After wiring the forwarder, flash it with the code found at https://github.com/Daniel-McDonough/433mhz-to-lora-forwarder
or below, keep it connected to the computer, and open up the serial output. Activate the 433MHz devices you intend to use and write down the codes.
Flashing the Receiver
Get the receiver code from the same Github link above and add the 1527 codes to the receiver code as shown below. Replace the current list with your codes.
For the door codes: const long triggerCodes[] = {10000001, 10000002, 00000000, 00000001, 00000002, 00000003}
Keep 10000001
and 10000002
if using the two reed switch inputs.
For the smoke detectors or other alarms: const long smokeDetectorCodes[] = {20000000, 20000002};
Flash the same as the forwarder board and then test it by activating one of the sensors.
Troubleshooting
-
Check the logs from the forwarder in the serial monitor to make sure it’s receiving, processing, and forwarding the codes.
-
Make sure the antennas are fairly straight and in the same orientation.
-
Activate the sensor from a closer distance(~2 feet).
-
Distance the boards from one another.
-
Make sure your sensors use 1527 encoding.
Failures
The type of 433MHz board matters. The MX-RM-5V has terrible reception, even with a longer antenna. There is a mod out there to make them more sensitive but it isn’t worth the effort. I wasted a lot of time trying to increase the distance only to find a comparison chart showing just how bad all the boards are except the RXB6 superheterodyne receiver. After using this board, I discovered my forwarder setup was uncecessary when I activated a door alarm accidentally at the house and the door beeper went off…. With the RXB6 and a long antenna, the house is on the edge of the signal range. The current setup is likely more reliable since the LoRa protocol is more sophisticated and the two devices are well within the maximum distance.
Security Considerations
There’s an obvious security flaw in this implementation that can’t be mitigated without using a more secure method for the sensors. One can send the unique codes of the doors and trigger the door alarm repeatedly. The forwarder also blindly forwards any code it receives which may create a denial of service attack. A whitelist can be added to the forwarder but it would then require flashing both boards with the device codes. The LoRa communication can be secured and that may be something added later if more LoRa devices are added(ATV, tractor, camper trackers/alarms).
Future Projects
Connecting the Setup to HomeAssistant
Connecting the setup to HomeAssistant shouldn’t be too difficult since one can just push the codes to MQTT via WiFi. It’s possible to also track the close events since the door sensors send close codes but I’d need to determine where to place the logic. ESPHome has some support for 433MHz signals if I wanted to just capture the 1527 codes directly but that support isn’t well documented and seems specific to which receivers is supports.
Tracking Other Objects
Due to LoRa’s long distance, it’s possible to place boards in the ATVs and Tractor which can send messages when the vehicle is turned on or jostled. One can even send GPS coordinates. This can be used to great effect when integrated with HomeAssistant and configured for mobile app notifications.
Make a Repeater
This is can be done by attaching a 433MHz transmitter to the receiver board and adding functions to transmit the LoRa-received code. Useful for installed security systems that already use the same type of sensors.
The code is below but the Github repo is likely more up to date.
The forwarder code:
#include <RCSwitch.h>
#include <RH_RF95.h>
#include <SPI.h>
#define RXB6_RX_PIN 5 // RX pin for RXB6 - Adjust this pin as per your wiring
#define RF95_CS 8 // Chip select pin for RF96
#define RF95_RST 4 // Reset pin for RF96
#define RF95_INT 3
#define TRIGGER_PIN1 10 // Trigger pin 1
#define TRIGGER_PIN2 11 // Trigger pin 2
// Instantiate the RF95 (RF96) and RCSwitch objects
RH_RF95 rf95(RF95_CS, RF95_INT);
RCSwitch rcSwitch = RCSwitch();
unsigned long lastReceivedTime = 0; // Last received time for debounce
long lastReceivedCode = -1; // Last received code for debounce
const unsigned long debounceTime = 3000;
bool PIN1_RESET = false;
bool PIN2_RESET = false;
void setup() {
Serial.begin(9600);
// Initialize RCSwitch for receiving 1527 encoded messages
rcSwitch.enableReceive(digitalPinToInterrupt(RXB6_RX_PIN)); // Enable receiver on interrupt
// Manual reset of RF96 to make sure it's initialized
pinMode(RF95_RST, OUTPUT);
digitalWrite(RF95_RST, HIGH);
delay(100);
digitalWrite(RF95_RST, LOW);
delay(100);
digitalWrite(RF95_RST, HIGH);
delay(100);
// Initialize RF95 (RF96) module
if (!rf95.init()) {
Serial.println("RF96 initialization failed!");
}
// Set the frequency for the RF96 module
if (!rf95.setFrequency(915.0)) {
Serial.println("Setting RF96 frequency failed!");
} else {
Serial.println("RF96 frequency set to 915.0 MHz");
}
// Initialize trigger pins
pinMode(TRIGGER_PIN1, INPUT_PULLUP);
pinMode(TRIGGER_PIN2, INPUT_PULLUP);
}
void loop() {
// Check for external triggers
checkExternalTriggers();
// Check if RCSwitch has received a message
if (rcSwitch.available()) {
long receivedValue = rcSwitch.getReceivedValue();
// Check if the code is a duplicate and if it's within the debounce time
if (receivedValue == lastReceivedCode && millis() - lastReceivedTime < debounceTime) {
rcSwitch.resetAvailable();
return;
}
// Update the last received code and time
lastReceivedCode = receivedValue;
lastReceivedTime = millis();
// Process the received code
processReceivedCode(receivedValue);
rcSwitch.resetAvailable();
}
}
//Checks the two reed switches connected to the board and debounces them
//Reset logic added to stop
void checkExternalTriggers() {
if (digitalRead(TRIGGER_PIN1) == HIGH && !PIN1_RESET) {
//Send code for receiver to process
processReceivedCode(10000001);
//Debounce
delay(300);
PIN1_RESET = true;
}
if (digitalRead(TRIGGER_PIN1) == LOW && PIN1_RESET) {
delay(300);
Serial.println("1 closed");
PIN1_RESET = false;
}
if (digitalRead(TRIGGER_PIN2) == HIGH && !PIN2_RESET) {
//Send code for receiver to process
processReceivedCode(10000002);
delay(300);
PIN2_RESET = true;
}
if (digitalRead(TRIGGER_PIN2) == LOW && PIN2_RESET) {
delay(300);
Serial.println("2 closed");
PIN2_RESET = false;
}
}
void processReceivedCode(long receivedValue) {
if (receivedValue > 0) {
Serial.print("Received: ");
Serial.println(receivedValue, DEC);
// Prepare a buffer to send the message
uint8_t buf[sizeof(receivedValue)];
memcpy(buf, &receivedValue, sizeof(receivedValue));
// Send the message via RF96
if (rf95.send(buf, sizeof(buf))) {
rf95.waitPacketSent();
Serial.println("Message forwarded via RF96");
} else {
Serial.println("Error sending message via RF96");
}
} else {
Serial.println("Received unknown code");
}
}
The LoRa receiver code:
#include <RH_RF95.h>
#define RF95_CS 8 // Chip select pin for RF96
#define RF95_RST 4 // Reset pin for RF96
#define RF95_INT 3 // Interrupt pin for RF96
#define BUZZER_PIN 10 // Buzzer pin
#define LED_PIN 13
// Door codes
const long triggerCodes[] = {10000001, 10000002, 00000000, 00000001, 00000002, 00000003}; // Add your standard codes here
const int numTriggerCodes = sizeof(triggerCodes) / sizeof(triggerCodes[0]);
// Smoke detector codes
const long smokeDetectorCodes[] = {20000000, 20000002}; // Replace with actual smoke detector codes
const int numSmokeDetectorCodes = sizeof(smokeDetectorCodes) / sizeof(smokeDetectorCodes[0]);
RH_RF95 rf95(RF95_CS, RF95_INT);
void setup() {
pinMode(RF95_RST, OUTPUT);
digitalWrite(RF95_RST, HIGH);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
if (!rf95.init()) {
// If init fails, blink LED rapidly
while (1) {
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
}
if (!rf95.setFrequency(915.0)) {
// If frequency set fails, blink LED slowly
while (1) {
digitalWrite(LED_PIN, HIGH);
delay(1000);
digitalWrite(LED_PIN, LOW);
delay(1000);
}
}
// If setup is successful, turn on LED for 2 seconds
digitalWrite(LED_PIN, HIGH);
delay(2000);
digitalWrite(LED_PIN, LOW);
}
void loop() {
if (rf95.available()) {
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len)) {
if (len == sizeof(long)) {
long receivedCode;
memcpy(&receivedCode, buf, len);
if (processReceivedCode(receivedCode)) {
// Turn on LED to indicate code processed
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
}
}
}
}
}
//Determine which function to call based on door or smoke detector
bool processReceivedCode(long code) {
for (int i = 0; i < numSmokeDetectorCodes; i++) {
if (code == smokeDetectorCodes[i]) {
smokeDetectorAlert();
return true;
}
}
for (int i = 0; i < numTriggerCodes; i++) {
if (code == triggerCodes[i]) {
beep(3);
return true;
}
}
return false;
}
// Change the timings and count to your liking
void beep(int numOfBeeps) {
for (int i = 0; i < numOfBeeps; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(300);
digitalWrite(BUZZER_PIN, LOW);
if (i < numOfBeeps - 1) {
delay(300);
}
}
}
void smokeDetectorAlert() {
for (int i = 0; i < 10; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(1000);
digitalWrite(BUZZER_PIN, LOW);
delay(100);
for (int i = 0; i < 3; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(300);
digitalWrite(BUZZER_PIN, LOW);
delay(100);
}
}
}