AdSense

Saturday, 17 August 2013

"Home Automation" with Arduino and 433 MHz - The shutters, part 1

(Deutsche Version) As I have shown in my last post, we can successfully communicate with the power outlets so turning on and off the power for the shutters is no problem. The tricky part is communicating with the shutter itself. We only now that communication is done wireless on the 433MHz band but we don't have any information about the used protocol.

Reverse Engineering

To get a rough overview about the used protocol, you have to read the signals sent by the remote control. This could be easily done with a logic analyzer. Unfortunately I don't own such a device. So I googled a little bit and found a way to use the Arduino as a simple logic analyzer. I will describe this in a separate post. Now I just connected the data pin of my 433MHz receiver with the logic analyzer Arduino and I was able to read the signals of the remote control.
When I pushed the “up”-button, I tracked this signal:

“Down” resulted in this signal:

And “stop”:

Transmission is started with some kind of sync bit. The remote control sends a HIGH signal for about 4500µs and a LOW signal for about 1500µs. After that, the data is transmitted. The pattern is similar to the tri-state-system of the power outlets. We have the known states 0, 1 and F and additionally an forth state:
 _ _ _       _
|         | _ |   | _

You could say we have a “quad-state-system”, so I called the forth state Q. The “short” signal has a length of 250µs, the “long” signal has a length of 750µs. Theoretically we could see the signal as a “normal” binary signal: a HIGH signal would be 1, a LOW signal 0. But in my opinion, this would be pretty confusing because we would get pretty long chains of 0s and 1s. Here are the signals in the quad-state-system: Up = 0F0F0100QQ0F100F 0F0F Down = 0F0F0100QQ0F100F 0101 Stop = 0F0F0100QQ0F100F FFFF You can see that the first 16 bits are equivalent, only the last 4 bits differ. I think the first 16 bits are some kind of address, the last 4 bits are the command.

Read the remote control

To read the signal of the remote control, I wrote a program that waits for the signal and prints them in the quad-state-system.

int logfile[40];
int i = 0;
float lastTime = 0;
boolean capturing = false;
boolean checking = false;
boolean dataIncoming = false;

void setup() {
  Serial.begin(9600);
  Serial.println("Ready...");
  pinMode(2, INPUT);
  attachInterrupt(0, handleInterrupt, CHANGE);
}

void loop() {
}

First some variables are declared. Among them is a “logfile” which is used to store the read code. There isn't much to say about the setup() function: the serial connections is started and the interrupt on pin 2 (the receiver module is connected to this pin) is activated here. The interrupt is activated every time the signal at pin 2 is changed. There is even less to say about the loop() function since it's empty. The work is done in the interrupt function:

void handleInterrupt() {

  if (!capturing) {  //if we are not capturing
    if (!checking) {  //if we don't wait for the "start" signal
      if (digitalRead(2) == HIGH) {  //if change from LOW to (now) HIGH
        lastTime = micros();
        checking = true;
      }
    }

    else {    //if we check for the "start" signal
      if ((micros() - lastTime > 4000) && (digitalRead(2) == LOW)) {    //if HIGH was longer than 4000µs and we are LOW now
        checking = false;
        capturing = true;
        lastTime = micros();
      }

      else {
        //that wasn't the "start" signal
        checking = false;
      }
    }
  }

  else {  //we are capturing
    if (!dataIncoming) {  //we haven't received data yet
      if ((micros() - lastTime > 1000) && digitalRead(2) == HIGH) {  //that was the long LOW part before transmission of data
        dataIncoming = true; //now we receive data  
        lastTime = micros();
      }
    }

    else {  //now we receive data
      //if rising flank (now HIGH)
      if (digitalRead(2) == HIGH) {
        //store the time
        lastTime = micros();
      }  

      //if falling flank (now LOW) 
      else if (digitalRead(2) == LOW) {
        //=> check how long we were HIGH
        if (micros() - lastTime > 500) {
          //long
          logfile[i] = 1;
        }

        else {
          //short
          logfile[i] = 0;
        }

        if (i < 39) {
          //as long as we haven't received all bits
          i++;
        }

        else {
          //now we are done
          noInterrupts();  //turn interrupts off
          Serial.println("Empfangene Daten:");
          //print as "quad-bit"
          for (i = 0; i <= 38; i = i + 2) {
            if ((logfile[i] == 0) && (logfile[i+1] == 0))
              Serial.print("0");

            else if ((logfile[i] == 0) && (logfile[i+1] == 1))
              Serial.print("F");

            else if ((logfile[i] == 1) && (logfile[i+1] == 0))
              Serial.print("Q");

            else if ((logfile[i] == 1) && (logfile[i+1] == 1))
              Serial.print("1");
          }
          Serial.println();
          i = 0;
          dataIncoming = false;
          capturing = false;
          interrupts();  //turn interrupts on
          return;  //and begin again
        }
      }

    }
  }
}

I hope the source code is self-explaining. If you have any questions, feel free to ask them in the comments. If you run the program on the Arduino and push a button on the remote control you should see the corresponding code in the serial monitor. I noticed two things I didn't notice when I just analyzed the signal using the logic analyer: 1. every signal is sent four times. I think that is done to prevent errors in transmission 2. the signal for “up” and “down” consists of two command: “up” sends four times the already know command "0F0F0100QQ0F100F 0F0F" followed by the command "0F0F0100QQ0F100F 0F1Q". “down” is four times "0F0F0100QQ0F100F 0101" followed by "0F0F0100QQ0F100F 0110".

4 comments:

  1. Hello,

    I'm trying to run this code on a LOLIN D32 and a RXB6 but I cannot get any output when I press the remote button.

    I'm checking your code and I saw this on setup():
    attachInterrupt(0, handleInterrupt, CHANGE);

    Shouldn't the 0 be a 2 there as the first argument should be the PIN? Thank you

    ReplyDelete
    Replies
    1. Just for the future (as asnwering this question 4 years later is of course silly). attachInterrupt() takes an interrupt number as first argument. For D2 pin the number is 0, so the code is ok. It can be written with additional method to avoid such puzzlements: attachInterrupt(digitalPinToInterrupt(D2), handleInterrupt, CHANGE);

      Delete
    2. proper line for this example: attachInterrupt(digitalPinToInterrupt(2), handleInterrupt, CHANGE);

      Delete