Michael Silverman documenting innovation at work

21Jan/142

Interfacing with remote controlled outlets

sched

In my apartment all of my lights are controlled via an outlet. These outlets are not connected to a light switch. As a first step towards convenience I purchased an EtekCity wireless outlet and switch. Every Friday these lights are put on a timer until Saturday night. That meant removing the existing outlet adapter and putting on one of the lovely cheap timers for the day. The timers only have a 24 hour cycle so it did not make sense to leave them on for the rest of the week. Within the timer there is also an audible mechanical clicking. There had to be a better solution.

Of course! I already have a wireless light switch. Why not somehow interface with it? A quick search on a hackaday lead me to others who have done similar, such as this and this. It was possible and not too hard.

Now, my goal was to do this easily and at minimum cost. I already had a Raspberry Pi lying around, doing nothing. Great, that will work as my web interface and the GPIO will send the proper digital sequence to the RF transmitter. That was easy. Now of course, there are initial questions that need to be answered:

1. Hardware - How will I send the RF data?

2. Software - What RF data will I be sending?

To answer the first question, I took apart one of the remote controls. Inside I found a simple circuit. There are a bunch of diodes, what looks like a microcontroller and an antenna. The antenna is marked "NY R433A". A Google search turns up with this datasheet. I see from this the antenna runs at 433.920MHz, a fact that could have been gleamed from the product description. More importantly, the form factor matches that of my controller and I have pinout description. Using my Salae Logic Analyzer I find that the waveforms are being sent to pin 1. I repeatedly hit "On" and "Off" for various switches, hoping to find a common pattern within them. Below is a set of waveforms.
1off 1on
As a layer of abstraction, I am calling a "period" or bit as 775uS. I am calling the shorter pulses (those with a 25% duty cycle), a zero. The ones with a longer duty pulse, a one. Whether or not this is actually true does not matter as I am reproducing the original waveform now with zeros and ones.

From these waveforms, the first 16 bits do not change. The last 11 appear to be unique. Further examination shows that the last bit is always zero, and that the "Off" sequence is the exact same waveform as "On" with a complement of the [N-5] to [N-1] bits.

"On": 0100000101010101001111000

"Off": 0100000101010101001100110

I have answered the second question. With this information I am ready to recreate the waveform in Python on the Pi and check with my logic analyzer. Before you do this, don't do this. It is a waste of time. The accuracy of Python is within a few mS. We are sending a signal at a rate lower than 1mS. This cannot be done with the Pi, in any language. Fortunately I had an Arduino Nano laying around. While a huge overkill of processing power, this should have no problem sending the waveforms. As a plus, it is easily interfaced to the Pi using USB as a serial port. Below is the code, which simply creates a buffer of the input and sends it to the output GPIO pin. The newline character is used as a placemarker for the end of sequence. If there is a transmission issue or otherwise the counter will reset to zero when it sees a newline character.

/* Arduino Nano Code */
#define SEQ_LEN 26

int outputPin = 2;
char lightSeq[SEQ_LEN];
char seqReady = 0;
char serialCount;

void setup() {
   initGPIO();
   Serial.begin(9600);
}

void loop() {
  if (seqReady) {
    for (char i = 0; i < 5; i++) {
        writeSequence();
        delay(5);
    }
    seqReady = 0;
    serialCount = 0;
  }
}

void initGPIO() {
   pinMode(outputPin, OUTPUT);
}

void writeOne() {
    digitalWrite(outputPin, HIGH);
    delayMicroseconds(580);
    digitalWrite(outputPin, LOW);
    delayMicroseconds(195);
}
void writeZero() {
    digitalWrite(outputPin, HIGH);
    delayMicroseconds(195);
    digitalWrite(outputPin, LOW);
    delayMicroseconds(580);
}

void writeSequence() {
  for (char i = 0; i < SEQ_LEN-1; i++) {     if (lightSeq[i] == '1') writeOne();     else writeZero();   } } void serialEvent() {   while (Serial.available()) {       if (seqReady == 0) {         lightSeq[serialCount] = (char)Serial.read();         serialCount++;         if (serialCount >= SEQ_LEN) seqReady = 1;
        if (lightSeq[serialCount-1] == '\n') serialCount = 0;
      }
  }
}

Now that the waveforms matched those of the remote control, I needed to send them to the antenna. Rather than making my own circuit, a search on Sparkfun lead me to an easy to use transmitter. This was purchased because it operated at the proper frequency, used the same antenna, and the circuity looks identical to that of my remote. That would mean they are driven the same way. Having the same antenna module was the key component. As an antenna, I cut a piece of 6.5" wire as recommended in the comments of the product.

Using the 5V power from the arduino, I connected the "Data In" pin on the antenna to the GPIO pin and the light was turning on and off. Questions answered! With this information, I no longer needed to view waveforms. When I want to link a new outlet to the computer, I change a bit between [N-11] and [N-6] and I have the "on"sequence, and flip for the "Off" sequence. It may be that the bit field that can be manipulated is larger but I have not had the time to determine this.

Now I need a website.

I installed Apache and MySQL on the Pi. Google has many tutorials on how to do this if you don't know how.

Then I created a website. I wanted to be able to go to a website, select a time I want the light on and that is it. The nice sliders on travel websites was an inspiration for how this can be done easily. After viewing the source of one of the websites I found that it was jQuery UI (the free open source javascript library). Thanks to the blog Marc Neuwirth, I had a javascript function that selects 24 hour periods in any way possible. Remember, I want to select when the light will be "On".

<?php
$mysqli = new mysqli("localhost", "root", "root", "lights");
$res = $mysqli->query("SELECT * FROM schedule");

$time = array();
for ($row_no = $res->num_rows - 1; $row_no >= 0; $row_no--) {
    $res->data_seek($row_no);
    $row = $res->fetch_assoc();
    $time[$row['id']]['time1'] = $row['time1'];
    $time[$row['id']]['time2'] = $row['time2'];
    $time[$row['id']]['checked'] = ($row['active'] == 1)?"checked":"";
}

//Save POST data into database
if (key_exists('vals', $_POST)) {
    for ($i = 0; $i < count($_POST['vals']); $i++) {
        $v1 = strval($_POST['vals'][$i][0]);
        $v2 = strval($_POST['vals'][$i][1]);
        $v3 = strval($_POST['vals'][$i][2]);

        $mysqli->query("UPDATE schedule SET time1=$v1, time2=$v2, active=$v3 WHERE id=$i");
        echo "UPDATE schedule SET time1=$v1, time2=$v2, active=$v3 WHERE id=$i";
    }
}
?>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body>
        <link rel="stylesheet" href="css/ui-lightness/jquery-ui-1.10.3.custom.min.css" />
        <script src="js/jquery-1.9.1.js"></script>
        <script src="js/jquery-ui-1.10.3.custom.min.js"></script>
        <script src="js/jquery.ui.touch-punch.min.js"></script>
        <script>
              function getTime(hours, minutes) {
              var time = null;
              minutes = minutes + "";
              if (hours < 12) {
                  time = "AM";
              } else {
                  time = "PM";
              }
              if (hours == 0) {
                  hours = 12;
              }
              if (hours > 12) {
                  hours = hours - 12;
              }
              if (minutes.length == 1) {
                  minutes = "0" + minutes;
              }
              return hours + ":" + minutes + " " + time;
          }

          function slideTime(event, ui) {
            var times;
            if (ui.values != null) times = getFormattedTime(ui.values[0], ui.values[1]);
            else times = getFormattedTime($(this).slider('values', 0), $(this).slider('values', 1));
            $("#time-" + this.id).val(times[0] + ' - ' + times[1]);
            $("#scheduleSubmit").css("background-color","white");
            $("#savedStatus").text("");
          }

          function getFormattedTime(val0, val1) {
                minutes0 = parseInt(val0 % 60, 10),
                hours0 = parseInt(val0 / 60 % 24, 10),
                minutes1 = parseInt(val1 % 60, 10),
                hours1 = parseInt(val1 / 60 % 24, 10);
            startTime = getTime(hours0, minutes0);
            endTime = getTime(hours1, minutes1);
            return [startTime, endTime];
          }

          $(function () {
            $("#slider-range").slider({
                range: true,
                min: 0,
                max: 2880,
                values: [<?php echo $time[0]['time1'] ?>, <?php echo $time[0]['time2'] ?>],
                step: 15,
                slide: slideTime,
                create: slideTime
            });
            $("#slider-range-2").slider({
                range: true,
                min: 0,
                max: 2880,
                values: [<?php echo $time[1]['time1'] ?>, <?php echo $time[1]['time2'] ?>],
                step: 15,
                slide: slideTime,
                create: slideTime
            });
            $("#slider-range-3").slider({
                range: true,
                min: 0,
                max: 2880,
                values: [<?php echo $time[2]['time1'] ?>, <?php echo $time[2]['time2'] ?>],
                step: 15,
                slide: slideTime,
                create: slideTime
            });
            $("#scheduleSubmit").on('click', function (e) {
                e.preventDefault();
                $("#scheduleSubmit").css("background-color","green");
                saveTime();
                $.post("index.php",{"vals[0][0]":$("#slider-range").slider("values", 0),
                                   "vals[0][1]":$("#slider-range").slider("values", 1),
                                   "vals[0][2]":$("#time-enabled").prop("checked"),
                                   "vals[1][0]":$("#slider-range-2").slider("values", 0),
                                   "vals[1][1]":$("#slider-range-2").slider("values", 1),
                                   "vals[1][2]":$("#time-enabled-2").prop("checked"),
                                   "vals[2][0]":$("#slider-range-3").slider("values", 0),
                                   "vals[2][1]":$("#slider-range-3").slider("values", 1),
                                   "vals[2][2]":$("#time-enabled-3").prop("checked")}
                       ,saveTime());
            });
            $("#enall").on('click', function (e) {
                e.preventDefault();
                $("#time-enabled").prop('checked', true);
                $("#time-enabled-2").prop('checked', true);
                $("#time-enabled-3").prop('checked', true);
            });
            $("#disall").on('click', function (e) {
                e.preventDefault();
                $("#time-enabled").prop('checked', false);
                $("#time-enabled-2").prop('checked', false);
                $("#time-enabled-3").prop('checked', false);
            });
          });
          function saveTime() {
              $("#savedStatus").text("Saved.")
          }
        </script>
        <div style="width:280px; padding:5%;">
            <input type="submit" value="Enable All" id="enall" class="ui-button ui-state-default ui-corner-all" />
            <input type="submit" value="Disable All" id="disall" class="ui-button ui-state-default ui-corner-all" />
            <p>
                <input id="time-enabled" type="checkbox" <?php echo $time[0]['checked'] ?>>
                <b><u>Living Room:</u></b><br>
                <label for="timeLabel">Time On:</label>
                <input type="text" id="time-slider-range" class="time" style="border:0; color:#33CC33; font-weight:bold;">
            </p>
            <div id="slider-range"></div>

            <p>
                <input id="time-enabled-2" type="checkbox" <?php echo $time[1]['checked'] ?>>
                <b><u>Office:</u></b><br>
                <label for="timeLabel2">Time On:</label>
                <input type="text" id="time-slider-range-2" class="time" style="border:0; color:#33CC33; font-weight:bold;">
            </p>
            <div id="slider-range-2"></div>

            <p>
                <input id="time-enabled-3" type="checkbox" <?php echo $time[2]['checked'] ?>>
                <b><u>Bedroom:</u></b><br>
                <label for="timeLabel3">Time On:</label>
                <input type="text" id="time-slider-range-3" class="time" style="border:0; color:#33CC33; font-weight:bold;">
            </p>
            <div id="slider-range-3"></div>

            <br>
            <br>
            <input type="submit" name="scheduleSubmit" value="Save" id="scheduleSubmit" class="ui-button ui-state-default ui-corner-all" />
            <span id="savedStatus"></span>
        </div>
    </body>
</html>

sched

The code is relatively self explanatory if you know javascript. I am reading the saved data from MySQL (using root as user/pass, I know- it's fine), and displaying it properly in the web page. When the "save" button is hit, it will POST the new data in the webpage. The data is then saved and read into MySQL.
If you are using the slider on a mobile device, you need to use additional javascript for it to work, I found jquery.ui.touch-punch.min.js to work great.

Now that the data is stored into a MySQL database it needs to be parsed and executed. For this, I used a cron job which is triggered every 15 minutes (the granularity of the slider) and Python.

The cron job can be set by typing "crontab -e". For what to set, you can use this tool. For a 15 minute interval execution, I added the following line:

0,15,30,45 * * * * python /home/pi/lightSchedule.py

import serial
import time
import MySQLdb
from datetime import datetime

onArray = ("0100000101010101001100110", "0100000101010101110000110", "0100000101010101010000110")
offArray = ("0100000101010101001111000", "0100000101010101110011000", "0100000101010101010011000");

db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="lights")
cur = db.cursor()
cur.execute("SELECT * FROM schedule LIMIT 3")
queryData = cur.fetchall()

mins = int((datetime.now() - datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds()/60)

nano = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE)
time.sleep(3)

#0 = time1
#1 = time2
#2 = id
#3 = state
#4 = i
#5 = active

i = 0
for row in queryData:
    time1 = row[0]
    time2 = row[1]
    state = row[3]
    active = row[5]

    if (active == "1"):
        if (time2 > 1440):
            localMins = mins + 1440
        else:
            localMins = mins

        if (localMins > time1):
            if (localMins < time2):
                send = onArray[i]
            else:
                send = offArray[i]
        else:
            send = offArray[i]

        for x in range(0,3):
            nano.write(send + "\n")
            time.sleep(1)
    i = i + 1

A few things to note about Python/Linux. For the serial port to be accessed by a user other than root, it need to have permission. The easiest and most reckless (but under these circumstances acceptable) way to do this, is chmod 777 . This will give any user all permission to the file or in this case, resource. This is not sticky and must be done on each reboot or unplug/plug of the device.

As a result of this, a web page light switch was created. I already had the infrastructure, why not.

<html>
    <head>
       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body>
        <?php
        if (key_exists('cmd', $_POST)) {
            exec('python /var/www/cgi-bin/sendSeq.py ' . $_POST['cmd'] . ' 2>&1 | tee file');
        }
        ?>
        <script src="js/jquery-1.9.1.js"></script>
        <script src="js/jquery.mobile-1.4.0.min.js"></script>
        <link rel="stylesheet" href="css/jquery.mobile-1.4.0.min.css" />
        <script>
        $(function () {
            $(".switch").on('click', function (e) {
                event.preventDefault();
                $.post("switch.php",{"cmd":this.id});
            });
        });
        </script>
        <div style="padding:10%">
            <div class="ui-grid-a">
                <h4>Living Room:</h4>
                <div class="ui-block-a"><button class="switch" type=submit" data-theme="a" id="0100000101010101001100110">ON</button></div>
                <div class="ui-block-b"><button class="switch" type=submit" data-theme="a" id="0100000101010101001111000">OFF</button></div>
                <br>
            </div>
            <div class="ui-grid-a">
                <h4>Office:</h4>
                <div class="ui-block-a"><button class="switch" type=submit" data-theme="a" id="0100000101010101110000110">ON</button></div>
                <div class="ui-block-b"><button class="switch" type=submit" data-theme="a" id="0100000101010101110011000">OFF</button></div>
                <br>
            </div>
            <div class="ui-grid-a">
                <h4>Bedroom:</h4>
                <div class="ui-block-a"><button class="switch" type=submit" data-theme="a" id="0100000101010101010000110">ON</button></div>
                <div class="ui-block-b"><button class="switch" type=submit" data-theme="a" id="0100000101010101010011000">OFF</button></div>
                <br>
            </div>
        </div>
    </body>
</html>

switch

Within this code the "id" is actually the bit sequence being sent to the antenna. This is useful for easy changing and manipulation. This is sent to a file called SendSeq.py

#!/usr/bin/python
import serial
import sys
import time

nano = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE)
time.sleep(2)
nano.write(sys.argv[1] + '\n')

The two second sleep is required by the Arduino Nano as a wake-up of sorts. It makes me want to switch to another timing mechanism because this is overkill and it could be instant, but that would be more time spent. It works perfect as is aside from this.

This can probably be extended to other outlets as it seems many brands operate on the same frequency. It is just different waveforms being sent.

This was a quick write up/project, and aside from not explaining all the code I probably missed some steps along the way. I hope it provides insight for anyone working on a similar project. Any questions, make a comment or email me through the form.

Click here to download all source files.

Comments (2) Trackbacks (0)
  1. Great article Mike! I just wanted to say thanks for the breakdown on this device and the protocol it uses. A few moments ago I got an arduino to send the correct sequence to my Etekcity remote outlet turning it on. To find the unique digits for my unit I invested in a RTL type software defined radio and basically did what Sammy Kamkar did in his Digital Ding Dong Ditch article: http://samy.pl/dingdong/

    In case anyone is curious the sequence that got my remote to turn on “outlet 1” as designated by the included remote was:
    0100 0000 0101 0101 0011 0011 0

    One most likely could derive the correct sequence from the data sheet for the encoder chip on the device which I found browsing the internet as well as looking at the state of soldered jumpers on the board of the remote (and presumably a similar setup with a decoder in the outlet/receiver). I was not well versed enough in such things to get that working but this is my first attempt at any kind of fiddling with 1’s and 0’s at this level. Guessing most people who can fenangle this kind of thing would have a spectrum analyzer handy or could spare the $20 for a RTL-SDR dongle to make things easy (especially when debugging).

    Thanks Again Mike, these devices are seemingly 100% reliable and also as cheap as peanuts. Hoping I can figure out the ESP8266(new fangled cheap wifi chip) to get a wifi to 430.92mhz bridge going and have a wifi-enabled remote outlet setup for less than $10/outlet.

  2. Thanks for the article.

    Another way to sniff the signal: http://forum.arduino.cc/index.php/topic,22105.0.html

    And also I’m going to try the cheap ESP8266 to see if I can make this whole setup work with Arduino Micro.


Leave a comment

No trackbacks yet.