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!
Leave a Reply