,

Decode Rolling Code 433Mhz Signals with Arduino

I’m posting this here because of my frustration in interpreting signals from a 433Mhz rolling code garage door opener based on the HCS301 chip with Arduino. To start with, I’d like to say that I don’t think it’s actually possible to decode the encrypted part of the transmission. But the 66 bit payload transmitted by rolling code remote controls actually contains unencrypted (fixed) data corresponding to the remotes serial number, battery status and button pressed, as can be seen on the picture bellow.

My idea was to turn on an external light when the garage door was opened, so reading the fixed data (especially the remote’s serial number) would be enough for my project.

I connected a standard 433Mhz receiver to my Arduino nano and tried using the usual libraries like rc-switch and VirtualWire to detect the signal from the remote but nothing was showing up. After a lot of researching I found this post in Russian and actually got it to work! Bellow is the working code for the Arduino Nano. Both the encrypted and unencrypted parts of the payload are shown in the serial monitor.

// HCS301 decoder //

#define HCS_RECIEVER_PIN  2    //connected to 433Mhz receiver
#define RELAY_PIN  3           //connected to relay

class HCS301 {
  public:
    unsigned BatteryLow : 1; 
    unsigned Repeat : 1;
    unsigned Btn1 : 1;
    unsigned Btn2 : 1; 
    unsigned Btn3 : 1; 
    unsigned Btn4 : 1;
    unsigned long SerialNum;
    unsigned long Encript;
    void print();
};

volatile boolean  HCS_Listening = true;   
byte              HCS_preamble_count = 0;
uint32_t          HCS_last_change = 0;
uint32_t          HCS_start_preamble = 0;
uint8_t           HCS_bit_counter;        
uint8_t           HCS_bit_array[66];     
uint32_t          HCS_Te = 400;                //Typical Te duration
uint32_t          HCS_Te2_3 = 600;             //HCS_TE * 3 / 2
boolean           relayOn = false;
unsigned long     millisStart = 0;
unsigned long     relayDuration = 5*60*1000;   //Duration of relay on

HCS301 hcs301;

void setup()
{
  Serial.begin(115200);
  pinMode(HCS_RECIEVER_PIN, INPUT);
  pinMode(RELAY_PIN, OUTPUT);
  attachInterrupt(0, HCS_interrupt, CHANGE);
  Serial.println("Setup OK");
}

void loop()
{
  long CurTime = millis();
  
  if(HCS_Listening == false) {
    //get message
    HCS301 msg;
    memcpy(&msg,&hcs301,sizeof(HCS301));

    //do something
    msg.print();
    if(msg.SerialNum == 4876246) {       //if remote serial matches
      millisStart = CurTime;
      relayOn = true;
    }
    
    //listen for another command
    HCS_Listening = true;
  }

  if((millis() - millisStart < relayDuration) && relayOn) {
     digitalWrite(RELAY_PIN, HIGH);
  }
  else {
     digitalWrite(RELAY_PIN, LOW);
     relayOn = false;
  }
}
 
//print data from HCS301
void HCS301::print(){
  
  String btn;
  if (Btn1 == 1) btn += "B1 ";
  if (Btn2 == 1) btn += "B2 ";
  if (Btn3 == 1) btn += "B3 ";
  if (Btn4 == 1) btn += "B4 ";

  String it2;
  it2 += "Encript=";
  it2 += Encript;
  it2 += " Serial=";
  it2 += SerialNum;
  it2 += " Button=";
  it2 += btn;
  it2 += " BatteryLow=";
  it2 += BatteryLow;
  it2 += " Rep=";
  it2 += Repeat;

  Serial.println(it2);
  
}

//new data
void HCS_interrupt() {

  if(HCS_Listening == false) {
    return;
  }

  uint32_t cur_timestamp = micros();
  uint8_t  cur_status = digitalRead(HCS_RECIEVER_PIN);
  uint32_t pulse_duration = cur_timestamp - HCS_last_change;
  HCS_last_change = cur_timestamp;

  // gets preamble
  if(HCS_preamble_count < 12) {
    if(cur_status == HIGH){
      if( ((pulse_duration > 150) && (pulse_duration < 500)) || HCS_preamble_count == 0) {
        if(HCS_preamble_count == 0){
          HCS_start_preamble = cur_timestamp;
        }
      } else {
        HCS_preamble_count = 0;
        goto exit; 
      }
    } else {
      if((pulse_duration > 300) && (pulse_duration < 600)) {
        HCS_preamble_count ++;
        if(HCS_preamble_count == 12) {
          HCS_Te = (cur_timestamp - HCS_start_preamble) / 23;
          HCS_Te2_3 = HCS_Te * 3 / 2;
          HCS_bit_counter = 0;
          goto exit; 
        }
      } else {
        HCS_preamble_count = 0;
        goto exit; 
      }
    }
  }
  
  // gets data
  if(HCS_preamble_count == 12) {
    if(cur_status == HIGH){
      if(((pulse_duration > 250) && (pulse_duration < 900)) || HCS_bit_counter == 0){
        // beginning of data pulse
      } else {
        // incorrect pause between pulses
        HCS_preamble_count = 0;
        goto exit; 
      }
    } else {
      // end of data pulse
      if((pulse_duration > 250) && (pulse_duration < 900)) {
        HCS_bit_array[65 - HCS_bit_counter] = (pulse_duration > HCS_Te2_3) ? 0 : 1;
        HCS_bit_counter++;  
        if(HCS_bit_counter == 66){
          // all bits captured
          HCS_Listening = false; 
          HCS_preamble_count = 0;

          hcs301.Repeat = HCS_bit_array[0];
          hcs301.BatteryLow = HCS_bit_array[1];
          hcs301.Btn1 = HCS_bit_array[2];
          hcs301.Btn2 = HCS_bit_array[3];
          hcs301.Btn3 = HCS_bit_array[4];
          hcs301.Btn4 = HCS_bit_array[5];

          hcs301.SerialNum = 0;
          for(int i = 6; i < 34;i++){
            hcs301.SerialNum = (hcs301.SerialNum << 1) + HCS_bit_array[i];
          };

          uint32_t Encript = 0;
          for(int i = 34; i < 66;i++){
             Encript = (Encript << 1) + HCS_bit_array[i];
          };
          hcs301.Encript = Encript;
        }
      } else {
        HCS_preamble_count = 0;
        goto exit; 
      }
    }
  }
  exit:;
}

I also added a PIR sensor and light sensor to my project to turn on the light when someone walked to the garage and ported everything to the ATTINY85. The main difference is the pin numbering and how the ATTINY85 works with interrupts (there is no attachInterrupt() function with this chip). Bellow is the working code.

// HCS301 decoder - ATTINY85 //

#include <avr/io.h>
#include <avr/interrupt.h> #define INTERRUPTPIN PCINT0 //connected to 433mhz receiver #define PCINT_VECTOR PCINT0_vect #define DATADIRECTIONPIN DDB0 #define PORTPIN PB0 #define READPIN PINB0 #define RELAY_PIN PB1 //connected to relay #define PIR PB2 //connected to PIR motion sensor #define LUX A2 //connected to light sensor class HCS301 { public: unsigned BatteryLow : 1; unsigned Repeat : 1; unsigned Btn1 : 1; unsigned Btn2 : 1; unsigned Btn3 : 1; unsigned Btn4 : 1; unsigned long SerialNum; unsigned long Encript; }; volatile boolean HCS_Listening = true; byte HCS_preamble_count = 0; uint32_t HCS_last_change = 0; uint32_t HCS_start_preamble = 0; uint8_t HCS_bit_counter; uint8_t HCS_bit_array[66]; uint32_t HCS_Te = 400; //typical Te duration uint32_t HCS_Te2_3 = 600; //HCS_TE * 3 / 2 boolean relayOn = false; unsigned long millisStart = 0; unsigned long relayDuration = 5*60*1000; //duration of relay on uint32_t lightThreshold = 400; //only turn light on at night HCS301 hcs301; void setup() { cli(); //disable interrupts during setup pinMode(RELAY_PIN, OUTPUT); pinMode(PIR, INPUT); pinMode(LUX, INPUT); digitalWrite(RELAY_PIN, LOW); //set the Relay to LOW PCMSK |= (1 << INTERRUPTPIN); //tell pin change mask to listen to pin GIMSK |= (1 << PCIE); //enable PCINT interrupt in the general interrupt mask DDRB &= ~(1 << DATADIRECTIONPIN); //set up as input PORTB |= (1 << PORTPIN); //disable pull-up. hook up pulldown resistor. - set to zero sei(); //last line of setup - enable interrupts after setup } void loop() { long CurTime = millis(); //check remote if(HCS_Listening == false) { //get message HCS301 msg; memcpy(&msg,&hcs301,sizeof(HCS301)); //do something if(msg.SerialNum == 4876246) { millisStart = CurTime; relayOn = true; } if(msg.SerialNum == 4529701) { millisStart = CurTime; relayOn = true; } if(msg.SerialNum == 1297291) { millisStart = CurTime; relayOn = true; } //listen for another command HCS_Listening = true; } //check PIR if(digitalRead(PIR) == HIGH) { if(analogRead(LUX) < lightThreshold) { millisStart = CurTime; relayOn = true; } } //turn relay on if((millis() - millisStart < relayDuration) && relayOn) { digitalWrite(RELAY_PIN, HIGH); } else { digitalWrite(RELAY_PIN, LOW); relayOn = false; } } //new data ISR(PCINT_VECTOR) { if(HCS_Listening == false) { return; } uint32_t cur_timestamp = micros(); uint8_t cur_status = digitalRead(PORTPIN); uint32_t pulse_duration = cur_timestamp - HCS_last_change; HCS_last_change = cur_timestamp; // gets preamble if(HCS_preamble_count < 12) { if(cur_status == HIGH){ if( ((pulse_duration > 150) && (pulse_duration < 500)) || HCS_preamble_count == 0) { if(HCS_preamble_count == 0){ HCS_start_preamble = cur_timestamp; } } else { HCS_preamble_count = 0; goto exit; } } else { if((pulse_duration > 300) && (pulse_duration < 600)) { HCS_preamble_count ++; if(HCS_preamble_count == 12) { HCS_Te = (cur_timestamp - HCS_start_preamble) / 23; HCS_Te2_3 = HCS_Te * 3 / 2; HCS_bit_counter = 0; goto exit; } } else { HCS_preamble_count = 0; goto exit; } } } // gets data if(HCS_preamble_count == 12) { if(cur_status == HIGH){ if(((pulse_duration > 250) && (pulse_duration < 900)) || HCS_bit_counter == 0){ // beginning of data pulse } else { // incorrect pause between pulses HCS_preamble_count = 0; goto exit; } } else { // end of data pulse if((pulse_duration > 250) && (pulse_duration < 900)) { HCS_bit_array[65 - HCS_bit_counter] = (pulse_duration > HCS_Te2_3) ? 0 : 1; HCS_bit_counter++; if(HCS_bit_counter == 66){ // all bits captured HCS_Listening = false; HCS_preamble_count = 0; hcs301.Repeat = HCS_bit_array[0]; hcs301.BatteryLow = HCS_bit_array[1]; hcs301.Btn1 = HCS_bit_array[2]; hcs301.Btn2 = HCS_bit_array[3]; hcs301.Btn3 = HCS_bit_array[4]; hcs301.Btn4 = HCS_bit_array[5]; hcs301.SerialNum = 0; for(int i = 6; i < 34;i++){ hcs301.SerialNum = (hcs301.SerialNum << 1) + HCS_bit_array[i]; }; uint32_t Encript = 0; for(int i = 34; i < 66;i++){ Encript = (Encript << 1) + HCS_bit_array[i]; }; hcs301.Encript = Encript; } } else { HCS_preamble_count = 0; goto exit; } } } exit:; }

Hope this helps anyone with a similar project!

2 responses to “Decode Rolling Code 433Mhz Signals with Arduino”

  1. Ricardo Avatar
    Ricardo

    I tried this on an ESP8266 but the led light kept flashing and I get a weird response in the serial monitor (below) is this code missing a library or it works only with some boards? Appreciate the help I am looking for a solutions for weeks now and cannot get it working 🙁

    ets Jan 8 2013,rst cause:2, boot mode:(3,6)

    load 0x4010f000, len 3456, room 16
    tail 0
    chksum 0x84
    csum 0x84
    va5432625
    ~ld
    ISR not in IRAM!

    User exception (panic/abort/assert)
    Abort called

    >>>stack>>>

    ctx: cont
    sp: 3ffffef0 end: 3fffffc0 offset: 0000
    3ffffef0: feefeffe feefeffe feefeffe 00000100
    3fffff00: 000000fe 00000000 00000000 00000000
    3fffff10: 00000000 00000000 00000000 00ff0000
    3fffff20: 5ffffe00 5ffffe00 3ffef154 00000000
    3fffff30: 00000003 00000000 3ffee3c4 402021de
    3fffff40: 40100462 402031bd ffffffff 402021f0
    3fffff50: feefeffe 00000001 3ffee3c4 40202705
    3fffff60: 00000000 00000001 3ffee3c4 40201558
    3fffff70: 00000000 feefeffe feefeffe 3ffee42c
    3fffff80: 3fffdad0 00000000 3ffee3c4 402027b4
    3fffff90: 3fffdad0 00000000 3ffee3c4 40201240
    3fffffa0: feefeffe feefeffe 3ffee3ec 40201e30
    3fffffb0: feefeffe feefeffe 3ffe84f0 40100d81
    <<<stack<<<

    1. manuel Avatar
      manuel

      The ESP8266 has a slightly different way of dealing with interrupts when running the Arduino IDE. Check this out on how to modify the code for the ESP8266: https://randomnerdtutorials.com/interrupts-timers-esp8266-arduino-ide-nodemcu/

Leave a Reply

Your email address will not be published. Required fields are marked *

*