I toyed a bit with the Paradox security protocol last year, tapping into the keypad’s interface in an attempt to decode the messages being exchanged between the base station and the keypads. I had some success: I was able to see the keys being pressed, but what was more important to me was the state of the alarm system and each room’s PIR + each door/window’s reed switch. That didn’t work out very well and I quickly abandoned the project.
Until today. I decided to revisit my work and inspect the options at the base station Spectra SP7000. I noticed a 4 pin port labeled ‘Serial’ and hooked my oscilloscope to identify what’s on there. Looked like the port provides ~13V and TX/RX lines at 5V. I hooked my FTDI cable set to 9600 Baud, 1 start bit, 8 data bits, 1 stop bit and no parity and it started spitting out chunks of 37 bytes. I then remembered that someone sent me a Paradox protocol documentation over the email following my last year’s attempt to hack in, but it seemed quite different from what I was getting at the keypad level and I just ignored it. Looking at it again today showed me exactly what I was looking for: the 37 bytes protocol:
I am interested in the event group, event sub-group and the command byte.
Event codes 00 and 01 are zone closed and zone opened respectively and the event sub-group specifies the zone. There are other event codes as well, but I am only interested in these.
I decided to use a JeeNode v6 to tap into the serial port and transmit the event to my home automation system. The JeeNode runs on 3.3V so I had to create a voltage divider (used 10K:10K, one of the resistors isn’t clearly visible on the picture below) to bring down the 5V to more Jee-friendly levels:
The MCP1702 on the JeeNode will handle the ~13V that the serial port is providing (it is at the limit indeed). Some more pictures of the setup:
Finally here is my code:
#include <JeeLib.h> // https://github.com/jcw/jeelib
#define myNodeID 3 // RF12 node ID in the range 1-30
#define network 210 // RF12 Network group
#define freq RF12_868MHZ // Frequency of RFM12B module
#define RETRY_PERIOD 250 // How soon to retry (in ms) if ACK didn't come in
#define RETRY_LIMIT 5 // Maximum number of times to retry
#define ACK_TIME 25 // Number of milliseconds to wait for an ack
char inData[38]; // Allocate some space for the string
byte index = 0; // Index into array; where to store the character
#define LED 6
typedef struct {
byte armstatus;
byte event;
byte sub_event;
byte dummy;
} Payload;
Payload paradox;
void setup() {
pinMode(LED,OUTPUT);
blink(100);
delay(1000);
rf12_initialize(myNodeID,freq,network); // Initialize RFM12 with settings defined above
Serial.begin(9600);
Serial.flush(); // Clean up the serial buffer in case previous junk is there
Serial.println("Paradox serial monitor is up");
blink(1000);
serial_flush_buffer();
}
void readSerial() {
while (Serial.available()<37 ) {} // wait for a serial packet
{
index=0;
while(index < 37) // Paradox packet is 37 bytes
{
inData[index++]=Serial.read();
}
inData[++index]=0x00; // Make it print-friendly
}
}
void loop()
{
readSerial();
Serial.println(inData);
if((inData[0] & 0xF0)==0xE0){ // Does it look like a valid packet?
paradox.armstatus=inData[0];
paradox.event=inData[7];
paradox.sub_event=inData[8];
rfwrite(); // Send data via RF
blink(50);
}
else //re-align buffer
{
blink(200);
serial_flush_buffer();
}
}
void serial_flush_buffer()
{
while (Serial.read() >= 0)
; // do nothing
}
void blink(int duration) {
digitalWrite(LED,HIGH);
delay(duration);
digitalWrite(LED,LOW);
}
// Wait a few milliseconds for proper ACK
static byte waitForAck() {
MilliTimer ackTimer;
while (!ackTimer.poll(ACK_TIME)) {
if (rf12_recvDone() && rf12_crc == 0 &&
rf12_hdr == (RF12_HDR_DST | RF12_HDR_CTL | myNodeID))
return 1;
}
return 0;
}
//--------------------------------------------------------------------------------------------------
// Send payload data via RF
//-------------------------------------------------------------------------------------------------
static void rfwrite(){
for (byte i = 0; i <= RETRY_LIMIT; ++i) { // tx and wait for ack up to RETRY_LIMIT times
while (!rf12_canSend())
rf12_recvDone();
rf12_sendStart(RF12_HDR_ACK, ¶dox, sizeof paradox);
rf12_sendWait(0); // Wait for RF to finish sending
byte acked = waitForAck(); // Wait for ACK
if (acked) {
return;
} // Return if ACK received
delay(RETRY_PERIOD); // If no ack received wait and try again
}
}
The code is running for few hours already and all is as expected.
I have a Raspberry Pi with RFM2Pi board running a read-only Raspbian that does my home automation, it captures the wireless packets using a node-red flow. With this new data available, my smart house will be able to know where its inhabitants are (PIR) and take various decisions based on that. I can also use the window/door state information (Reed switches) for smarter climate control, i.e. turn off the thermostat in a room that has the window open.






I see you never use special register after rf_initialize for less baudrate and other. I use the 433 MHz Modules, but i have only connect for some meters (3-4) and after that, no connect. If you use some tricks or if your connect better cuz you use the 868 MHz modules. At your photo on top of every website i see you build the antenna as a spiral, if this better to connect the RFM Modules or just for fun? 🙂
Great work!
I’m looking to build a similar system but with an ethernet interface on arduino.
Could you possibly send me the Paradox protocol documentation doc you have?
Have you perhaps gotten any further with this project?