IoT Connected Power Sockets with Low Cost Commercial RF-controlled Relays


Overview

In this project, a commercially available low-cost one-way RF controlled outlet is reverse engineered to support being controlled by a cheap ($5) RF transmitter/receiver pair. A proof-of-concept bluetooth input device is used in the example to control the switch from a smartphone, but it can easily be configured with a web server to respond to HTTP requests.


Procedures

Step 1: Setup Arduino

Because of the lack of library support for these RF switches in the mBed community as opposed to the Arduino community, I have elected to use a barebones “Breadboard Arduino” in this setup, with an Arduino-bootloaded ATMega328P in this project as the driver for the bare RF transmitter and receivers and communicated with it through the UART Serial interface.

You can find more information about the breadboard Arduino here. Note: if you are powering the ATMega from the 5V-out of the mBed, you can ignore the setting up of the voltage regulator. You can also use a full sized Arduino instead of setting up a barebones ATMega microcontroller.

Step 2: Download the RCSwitch library

The RCSwitch library can be obtained directly from the Arduino Library Manager, but can also be downloaded here.

Step 3: Connect the Components

mBed

PinLocation
9Bluetooth UART RX
10Bluetooth UART TX
13ATMega328P RX (Pin 1, indexed from 0)
14ATMega328P TX (Pin 2)
5vOutVdd (All Components)
GndGnd (All Components)

Arduino (Bare ATMega328P)

PinLocation
2mBed TX (Pin 13)
3mBed RX (Pin 14)
4RF-5V Data
5fs1000a Data
VddVdd
GndGnd
916Mhz Oscillator, Cap to Gnd
1016Mhz Oscillator, Cap to Gnd

fs1000a

PinLocation
VddVdd
DataArduino Pin 5
GndGnd

RF-5V

PinLocation
VddVdd
DataArduino Pin 4
GndGnd

Step 4: Program the Arduino

433MHzDriver.ino

/*
 * Code for Arduino Driver for 433MHz RF Wireless Outlet Switch Relays. Takes input from serial in the form of TriState
 * bits and passes them to the RF transmitter and takes the incoming signal and pipes it out.
 * @author Percy Yeung
*/

#include <RCSwitch.h>

#define DEFAULT_PULSE_LENGTH 200 
#define SERIAL_TIMEOUT_MILLIS 30
#define DEBUG 0

RCSwitch mySwitch = RCSwitch();

static const char* bin2tristate(const char* bin);
static char * dec2binWzerofill(unsigned long Dec, unsigned int bitLength);

void output(unsigned long decimal, unsigned int length, unsigned int delay, unsigned int* raw, unsigned int protocol) {

  if (decimal == 0) {
    Serial.print("Unknown encoding.");
  } else {
    const char* b = dec2binWzerofill(decimal, length);
//    Serial.print("Decimal: ");
//    Serial.print(decimal);
//    Serial.print(" (");
//    Serial.print( length );
//    Serial.print("Bit) Binary: ");
//    Serial.print( b );
//    Serial.print(" Tri-State: ");
    Serial.print( bin2tristate( b) );
    Serial.print("-");
//    Serial.print(" PulseLength: ");
    Serial.print(delay);
//    Serial.print(" microseconds");
//    Serial.print(" Protocol: ");
//    Serial.println(protocol);
  }
  
  Serial.println();
//  Serial.println();
}

static const char* bin2tristate(const char* bin) {
  static char returnValue[50];
  int pos = 0;
  int pos2 = 0;
  while (bin[pos]!='\0' && bin[pos+1]!='\0') {
    if (bin[pos]=='0' && bin[pos+1]=='0') {
      returnValue[pos2] = '0';
    } else if (bin[pos]=='1' && bin[pos+1]=='1') {
      returnValue[pos2] = '1';
    } else if (bin[pos]=='0' && bin[pos+1]=='1') {
      returnValue[pos2] = 'F';
    } else {
      return "not applicable";
    }
    pos = pos+2;
    pos2++;
  }
  returnValue[pos2] = '\0';
  return returnValue;
}

static char * dec2binWzerofill(unsigned long Dec, unsigned int bitLength) {
  static char bin[64]; 
  unsigned int i=0;

  while (Dec > 0) {
    bin[32+i++] = ((Dec & 1) > 0) ? '1' : '0';
    Dec = Dec >> 1;
  }

  for (unsigned int j = 0; j< bitLength; j++) {
    if (j >= bitLength - i) {
      bin[j] = bin[ 31 + i - (j - (bitLength - i)) ];
    } else {
      bin[j] = '0';
    }
  }
  bin[bitLength] = '\0';
  
  return bin;
}

void setup() {
  Serial.begin(9600);
  mySwitch.enableReceive(0);  // Receiver on interrupt 0 => that is pin #2
  mySwitch.enableTransmit(3);// Receiver on pin 3
  mySwitch.setPulseLength(DEFAULT_PULSE_LENGTH);
  mySwitch.setRepeatTransmit(5);
}

void debug(String s) {
  if (DEBUG > 0) {
    Serial.print(s);
    Serial.print("\n");
  }
}

void loop() {
  if (Serial.available() > 0) {
    String commandToSend = "";
    char receivedChar = Serial.read();
    unsigned long refTime;
    while (receivedChar != '\n' && receivedChar != 0x00) {
//      Serial.write("Received: ");
//      Serial.print(receivedChar, HEX);
//      Serial.write("\n");
      if (receivedChar != -1){
        refTime = millis();
        commandToSend = commandToSend + receivedChar;
      } else {
        unsigned long nowTime = millis();
        if ((nowTime - refTime > SERIAL_TIMEOUT_MILLIS) || (nowTime - refTime < 0)) {
          break;
        }
      }
      receivedChar = Serial.read();
    }
    int plIndex = commandToSend.lastIndexOf("-");
    int pulseLength;
    bool pulseModified = false;
    debug("--raw cts");
    debug(commandToSend);
    if (plIndex != -1) {
      debug("--plIndex:");
      debug((String)plIndex);
      pulseLength = commandToSend.substring(plIndex + 1).toInt();
      commandToSend = commandToSend.substring(0, plIndex);
      mySwitch.setPulseLength(pulseLength);
      pulseModified = true;
    }
    char ctsArray[commandToSend.length()];
    debug(commandToSend);
    commandToSend.toUpperCase();
    commandToSend.toCharArray(ctsArray, commandToSend.length() + 1);
    debug(ctsArray);
    mySwitch.sendTriState(ctsArray);
    if (pulseModified) {
      mySwitch.setPulseLength(DEFAULT_PULSE_LENGTH);
    }
    
  } else if (mySwitch.available()) {
    output(mySwitch.getReceivedValue(), mySwitch.getReceivedBitlength(), mySwitch.getReceivedDelay(), mySwitch.getReceivedRawdata(),mySwitch.getReceivedProtocol());
    mySwitch.resetAvailable();
  }
}

Step 5: Program the mBed

#include "mbed.h"
#include "rtos.h"
#include "stdio.h"
#include <string>

#define SERIAL_TIMEOUT 30
#define DEBUG 1

Serial pc(USBTX, USBRX);
Serial blue(p9, p10);
Serial rf(p13, p14, 9600);

DigitalOut led1(LED1);
DigitalOut led2(LED2);

Timer timer;

bool dev1power = false;
bool dev1changed = false;

Mutex io_mutex;

void debug(std::string s) {
    if (DEBUG > 0) {
        pc.printf(s.c_str());
        pc.printf("\n\r");
    }
}

void debug(char* s) {
    if (DEBUG > 0) {
        pc.printf(s);
        pc.printf("\n\r");
    }
}

void debug(char s) {
    if (DEBUG > 0) {
        pc.printf("0x%08x", s);
        pc.printf("\n\r");
    }
}

void switchOff() {
    debug("Switching Off!");
    rf.printf("FFFF0FFF0110");
}

void switchOn() {
    debug("Switching On!");
    rf.printf("FFFF0FFF0101");
}

void rfThread(void const *args) {
    while (true) {
        if (rf.readable()) {
            io_mutex.lock();
            char thisChar;
            std::string rfString = "";
            timer.reset();
            timer.start();
            while (timer.read_ms() < SERIAL_TIMEOUT) {
                if (rf.readable()) {
                    thisChar = rf.getc();
                    timer.reset();
                    rfString += thisChar;
                }
            }
            while (rf.readable()) {
                debug("rf");
                rf.getc();
            }
            timer.stop();
            debug(rfString);
            if (rfString.find("FFFF0FFF0110") != std::string::npos) {
                debug("External Switch Off Detected!");
                dev1power = false;
            } else if (rfString.find("FFFF0FFF0101") != std::string::npos) {
                debug("External Switch On Detected!");
                dev1power = true;
            }
            io_mutex.unlock();
        }
    }
}

void blueThread(void const *args) {
    while (true) {
        if (blue.readable()) {
            io_mutex.lock();
            char thisChar;
            std::string blueString = "";
            timer.reset();
            timer.start();
            while (timer.read_ms() < SERIAL_TIMEOUT) {
                if (blue.readable()) {
                    thisChar = blue.getc();
                    timer.reset();
                    blueString += thisChar;
                }
            }
            while (blue.readable()) {
                debug("blue");
                blue.getc();
            }
            timer.stop();
            if (blueString.find("!B11:") != std::string::npos) {
                dev1power = !dev1power;
                dev1changed = true;
                debug("Switch Flipped");
            } else if (blueString.find("on") != std::string::npos) {
                dev1power = true;
                dev1changed = true;
                blue.printf("Switching On!\n\r");
            }else if (blueString.find("off") != std::string::npos) {
                dev1power = false;
                dev1changed = true;
                blue.printf("Switching Off!\n\r");
            }
            debug(blueString);
            if (dev1changed) {
                dev1changed = false;
                if (dev1power) {
                    switchOn();
                } else {
                    switchOff();
                }
            }
            io_mutex.unlock();
        }
    }
}

int main() {
    Thread t1(rfThread);
    Thread t2(blueThread);
    while(true) {}
}

Step 6: Download the BLE app and Test!

Note: If you connect your terminal to the virtual serial port, you will see the decoded values you will need to customize this for your own setup.


Video


Please log in to post comments.