DIY Home Automation in the IoT era with UDOO and Polymer

February 14, 2015 Comments ↓

Today is a very happy day for my control freakness. I managed to complete a DIY project I had in mind for a while and today I am going to share it with you, in full details.

This post is full of technical content (and source code) and covers many layers, which space from real-time bit banging on TRIACs to material design. Using remote lighting as a case study, let's go for a full dive into all the layers involved in a modern IoT application, based on a PIC micro, a UDOO board, Android and Polymer components.

A short teaser video first []:

This article is going to describe all the steps involved in the creation of a web-based light dimmer:

  • starting from very low level electronics: TRIACs, SSRs and dimmers
  • going up to the micro-controller firmware to handle real-time smoothing of the mains waveform
  • going to up to a simple RESTful embedded web-server on Linux/Android
  • going up to a modern and nice looking HTML UI based on Polymer

'cause I am an extreme DIYer, and buying gadgets is for wimps.

Buzzwords for this article (people tend to like them a lot, I'm too lazy to wire them up properly): connected revolution, internet-of-things, web-of-things, things-of-things.

Overall architecture

The lighting controller consists of the following:

architectural diagram

Safety warning and disclaimer

Part of this project involved a DIY board meant to be connected to the mains supply.

Never ever put your hands, try to fix, program or debug a board while it is connected to the mains, especially if you think you know what you are doing. Even if your board is supposed to be opto-isolated, you might have mis-connected the opto-isolators or created accidental short-circuits which invalidated the AC / DC galvanic isolation. In all these cases, take some extra patience and always physically unplug the board from the mains before doing any maintenance.

No religion has confirmed yet whether there are good compilers for micro-controllers in heaven. Likewise, micro-controllers might be difficult to operate in hell, due to the extreme temperature range (XTALs might drift a lot there causing PLLs to never lock).

Furthermore, the schematic here should be considered as a prototypical concept. Therefore I decline any responsibility for any damages / fires / earthquakes caused by that.

Light dimmers: theory of operation

Let's start from ground zero and cover the electronic principles behind AC light dimmers. How does a light dimmer work?

The principle it's extremely easy to understand but a bit hard to turn into practice (will see later why). The figure below should give a very good idea about the theory: imagine a switch (a very fast one) which gates the connection between the AC mains and the lamp. The higher proportion of the sine-wave will reach the lamp, the brighter the light will be.

light dimming principle

The first problem is: how do we actually switch the mains in a timely fashion? In most countries, the mains sine-wave has a frequency of 50 Hz, which means that each half cycle (0 V, +/- peak, 0 V) happens at a frequency of 100 Hz, hence with a period of 10 ms.

A mechanical relay won't be able to keep up with such timings (without wearing out in few hours). The building block used in these cases is the TRIAC. In the very essence, a TRIAC is similar to a transistor, but is able to drive alternate current loads.

Fantastic! So, all we need to do is to connect a TRIAC to a micro-controller? NO. It is not that simple. TRIACs have a couple of gotchas.

TRIACs are not isolated
and you should never have any galvanic path between AC and DC logic. TRIAC should be always driven with a opto-isolated driver (the MOC3021 is a popular choice). TRIAC drivers, however, require a number of extra components to work properly, which add extra complexity and points of failure to the circuit.

Solid State Relays (SSRs) are more DIY-friendly alternatives to TRIACs
In the very essence a SSR is an integrated circuit which embeds a TRIAC, an opto-coupled driver and the annex analog driving circuitry.

TRIACs and SSRs turn off only at zero cross
This is the detail that makes the driving logic a bit complicated. A TRIAC cannot be turned off at any random point (better: it can but it won't have the desired effect). The reason lies in its silicon design. See the wikipedia page for more details. A dimmer, therefore, needs to keep track with the mains sine-wave with pretty strict timing margins, within the sub millisecond range. Which is the perfect job for a micro-controller.

There are three important factors to remember when choosing a SSR:

  1. The driving voltage (typ. 5v or 12v): this should be chosen accordingly to the DC logic of the board. Usually SSRs cannot be driven directly from a PIC and require a buffer chip.
  2. Maximum Output Load (typ 1-2 A): this determines the maximum power that the SSR will be able to drive. Remember to oversize the SSR to avoid heat dissipation issues. For instance a 1 AMP SSR is theoretically able to drive a load of 1 A x 220 V = ~220 W. in practice it should not be used with loads higher than 100-150 W.
  3. The trigger type: zero-cross or random fire. Zero-cross SSRs are essentially on/off switches, which are able to turn on and off only on a zero-cross point. Their main application is driving very large loads (typically ovens / thermostats). Random-fire SSRs behave like standard TRIACs. They can be turned on at any point of the sine-wave but turn off only upon zero-crossing points.

Use only random-fire SSRs for light dimmers
It is obvious at this point that random-fire SSRs are what we are looking for here. Look carefully to the data-sheet before buying them, in order to avoid sad surprises and end up with a dimmer which just doesn't work.

Why not just PWM
You might wonder why we can't just use pulse-width modulation (PWM) like we do for dimming LEDs and other DC loads, and use something better than a TRIAC.
Technically speaking it is doable, but it has a number of disadvantages:

  • It requires a IC which is able to switch at arbitary points, like an IGBT. IGBTs are more expensive and much harder to operate (require a high gate voltage).
  • Even excluding the technical difficulties, high frequency switching of large AC loads will end up creating a lot of electromagnetic interference. In concrete words, a PWM-based AC dimmer is very likely to drop down significantly the quality of your WiFi (and cause many other problems).

Schematic of the dimmer:

Having explained the theory underneath, the schematic follows straightforwardly:

light dimmer schematic

The choice has fallen on the super-popular Microchip's PIC16F88. If you know PICs you will know why. If you don't know PICs, just trust me :) The 16F88 is super cheap (few EURs), can be found in virtually any electronics / DIY shop and has all the peripherals required for the job (ADC, timer, internal oscillator).

Why not an Arduino?
Absolutely reasonable objection. There is no reason why you should not use an Arduino here if you are familiar with them. Personally, I have used PICs since I have memory, and these days there are more PICs than pairs of socks in my bedroom.

UART input
The communication between the UDOO board and the PIC is handled via UART (read: serial port). UART is easy to use. Both the PIC16F88 and the UDOO board have UARTs and their voltage level is compatible (at least in the UDOO -> PIC dir.).

Mains tracking input stage
The mains AC waveform gets through a SFH620A opto-coupler to the analog-to-digital input RA1 (AN1) of the PIC. Note that:

  • The opto-isolator has a Math.abs() effect. The micro-controller will see only a train of positive half-sinusoids (as from the output of a rectifier bridge).
  • The pull up configuration has the further effect of inverting the phase of the wave. In other words, the PIC will read a value close to digital 0 when the mains are at the maximum peak point (either positive or negative) and a value close to 0xff on the zero-crossing points.

SSR output buffers
The PIC output drivers cannot source enough current to directly drive a SSR. For this reason the four SSR outputs are buffered using a ULN2003. The ULN2003 is a very popular darlington array chip consisting in 7 buffers, able to drive up to 500 mA loads.

SSR chips
My choice for the SSRs has fallen on the Panasonic AQG22212. It's reasonably cheap (~4 EUR each) and can drive loads up to 2 A (~440 W).

Firmware for the PIC16F88

The source code for the firmware is written in C and is available here:

I am not going to comment the code for the firmware, as most of it is PIC-specific. If you have used PICs before, you will find the source code very easy to follow. Otherwise, it will look as a nonsense assignment of registers without a meaning.

What can be more useful, instead, is a flow-chart which explains the overall logic. This should make it easier to rewrite the firmware on any other micro-controller.

CH[0] brightness
[Not supported by viewer]
CH[0] brightness
[Not supported by viewer]
CH[0] brightness
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[T=0] brightness value (0-31)
[T=1] smoothing value (0-31)
[Not supported by viewer]
Channel (0-3)
[Not supported by viewer]
 0: brightness
 1: smoothing
[Not supported by viewer]
CH[0] phase
[Not supported by viewer]
[Not supported by viewer]
SSR Output 0
[Not supported by viewer]
SSR Output 3
[Not supported by viewer]
[Not supported by viewer]
Reset TMR
[Not supported by viewer]
TMR >= phase[0]
[Not supported by viewer]
Wait for zero cross
[Not supported by viewer]
Set SSR[0]
[Not supported by viewer]
[Not supported by viewer]
TMR >= phase[3]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
Set SSR[3]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
Reset all SSRs
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
[Not supported by viewer]
TMR (Timer)
[Not supported by viewer]
CYCLE_TIME = 1/50 MHz / 2 = 10000 us
phase[X]= (31 - brightness[x]) * 384 us
[Not supported by viewer]

I am adding just few guidelines, which apply generally when writing embedded C software and are vital for a dimmer. With a cycle time of 10ms and a desired dimming resolution of 64 steps, the computation budget is about 156 uS. With a clock frequency of 1 MHz, it gets down to ~156 (assembly) instructions.

  • Keep Interrupt service routine extremely short.
  • Forget about multiplications and divisions. Micro-controllers often lack a hardware multiplier. Perform fancy math them on the host side (on the UDOO).
  • Avoid long / complex math and shard it (e.g., one channel per cycle).

Embedded REST web server on UDOO

Let's now switch to the UDOO side. The UART can be accessed in hardware using the pin TX0 (refer to the Pinout Diagram ) which corresponds to the PIN 1 of standard Arduino shields. From the software side, the UART is visible as a standard TTY, /dev/ttymxc3

I choose to develop the web-service in C. It makes it possible to reuse the same code whether you are running either Android or Linux (the UDOO board supports both).

The web-service should be run as part of initrc on Android or daemontools on Linux. See this CL as an example. Try not to run it as root: there is no need for that and is frightening from a security viewpoint. Just chown the serial TTY to an unpriviledged user using udev rules).

Accessing the /dev/ttymxc3 serial port

The way to go to access the serial, in C / Linux, is using libtermios:

#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>

static uint8_t dimmer_values[4];  // Brightness for each channel (0-255).


static int update_dimmers(void) {
  uint8_t i;
  int fd = open(tty, O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd < 0) {
    perror("Cannot open tty");
    return -1;

  struct termios tio;
  if(tcgetattr(fd, &tio) < 0) {
    return -1;

  tio.c_cflag &= ~(IGNBRK | BRKINT | ICRNL |
                    INLCR | PARMRK | INPCK | ISTRIP | IXON);
  tio.c_oflag = 0;
  tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
  tio.c_cflag &= ~(CSIZE | PARENB);
  tio.c_cflag |= CS8;

  if(cfsetispeed(&tio, B2400) < 0 || cfsetospeed(&tio, B2400) < 0) {
    return -1;

  if(tcsetattr(fd, TCSAFLUSH, &tio) < 0) {
    return -1;

  // Update the four dimmer channels.
  uint8_t buf[sizeof(dimmer_values)];
  for (i = 0; i < sizeof(dimmer_values); ++i) {
    const uint8_t value = 0x3F - ((dimmer_values[i] >> 2) & 0x3F);
    buf[i] = value | (i << 6);
  write(fd, buf, sizeof(buf));
  return 0;

What we are going to do now, is writing a web-service which raises the abstraction level and encapsulates the UART wire protocol with the PIC, exposing that through a RESTful web service. The API is going to be super simple:

Get the current state of a dimmer

GET /dimmer/0 HTTP/1.1
HOST: udoo:8080

HTTP/1.1 200 OK
Content-Type: text/plain

255  # The light is fully on

Set the brightness of a dimmer

POST /dimmer/0 HTTP/1.1
HOST: udoo:8080
Content-Length: 2

10  # Set the light almost off

(The actual web-service I wrote has two extra endpoints for some digital I/O lines. I am not going to comment them in this post as they are absolutely trivial and similar to the dimmer ones).

To implement the web service I used mongoose. It is an open-source, lightweight and very easy to use (one .c file) web-server.

Essentially mongoose allows to register a custom callback function to handle HTTP requests. What we need to do is just match the request path in the callback and invoke the right REST handler. I created a minimal layer of abstraction to make it more readable as follows (see the home_automation.c for the full code):

// Prototypes of the REST handlers, one per request method and path.
static bool REST_dimmer_get(const char* path, struct mg_connection *conn);
static bool REST_dimmer_post(const char* path, struct mg_connection *conn);

struct rest_endpoint_t {
  const char* uri;
  const char* method;
  bool (*handler)(const char*, struct mg_connection*);

// List of registered REST handlers.
const struct rest_endpoint_t endpoints[] = {
  {"/dimmer/", "GET",  REST_dimmer_get},
  {"/dimmer/", "POST", REST_dimmer_post},

// Invoked upon GET /dimmer/N.
// Returns the brightness of the N-th dimmer (0: off, 255: max brightness).
static bool REST_dimmer_get(const char* path, struct mg_connection *conn) {
  const uint8_t channel = atoi(path);
  if (channel >= sizeof(dimmer_values))
    return false;
  mg_printf_data(conn, "%d", dimmer_values[channel]);
  return true;

// Invoked upon POST /dimmer/N
// Sets the brightness of the N-th dimmer (0: off, 255: max brightness).
static bool REST_dimmer_post(const char* path, struct mg_connection *conn) {
  const uint8_t channel = atoi(path);
  if (channel >= sizeof(dimmer_values))
    return false;
  char val[4];
  strncpy(val, conn->content, MIN(conn->content_len, sizeof(val) - 1));
  dimmer_values[channel] = atoi(val);
  mg_printf_data(conn, "OK\n");
  return true;

// Actual mongoose HTTP handler. Checks whether the request method and path
// match any registered endpoint.
static int http_handler(struct mg_connection *conn, enum mg_event ev) {
  uint32_t i;
  switch (ev) {
    case MG_REQUEST:
      for (i = 0; i < ARRAY_SIZE(endpoints); ++i) {
        const struct rest_endpoint_t* ep = &endpoints[i];
        const size_t uri_len = strlen(ep->uri);
        if (strcmp(conn->request_method, ep->method) == 0 &&
            strncmp(conn->uri, ep->uri, uri_len) == 0) {
          // Invoke the corresponding REST_* function.
          if (ep->handler(&conn->uri[uri_len], conn))
            return MG_TRUE;
      return MG_FALSE;
    case MG_AUTH:
      return MG_TRUE;  // No HTTP auth, assume LAN is trusted.
      return MG_FALSE;

// Finally, the web-service entry-point.
int main() {
  struct mg_server *server;
  server = mg_create_server(NULL, http_handler);
  mg_set_option(server, "listening_port", "8080");

  for (;;) {
    // Blocks at most for some (60) ms, so we get a chance to re-trigger the
    // watchdog on the PIC if there is no REST request.
    mg_poll_server(server, 60);

Web based UI in Polymer

In this project the UI has been realized as a HTML web app based on polymer.

Why? Why not a native app? Mainly for three reasons:

  • I want to control the lights of my flat using both my smartphone and my laptop. I am a lazy engineer and I don't want to write the same code twice.
  • It is 2015 and the concept of installing an app is so démodé. It sounds like loading tapes into a drive (or should we download that using z-modem?).
  • I am a browser engineer, I spend way too much time writing native code and I like to switch on the other side and enjoy the beauty of the things I contribute to in my spare time.

Ok, HTML/JS cool. But what the heck is this polymer?
If you don't know the answer you should definitely check-out

Polymer-based UI Essentially Polymer is the way to go to create web-apps based on actual reusable components. Besides the architectural encapsulation itself, the nice thing about Polymer is that components act as actual boundaries for JS and CSS scopes.
In other words, Polymer is the fantastic world where you can add a new piece of UI without fearing that the style of the entire page will be screwed up.
Essentially it's the way to go for civilised modularity and forget about awkward hacks (i.e. get back to the state where !important is a word that you use only with the people you care about, not a CSS annotation to make an element actually coloured).
Furthermore, it has a lot of other nice properties (dynamic bindings similar to Angular JS, native browser support in Chrome).

The other nice thing about Polymer is that, beyond the framework itself, the project has recently made available the Paper Elements: a toolkit of material design UI components. This allows to quickly make a first-class UI which looks like any other native mobile app.

Last but not least, Polymer comes with a layout editor, Polymer Designer which allows to design the UI and take advantage of Paper Elements in a graphical environment, without having to know too much about polymer's syntax.

The architecture of the HTML UI for this project is decoupled in two levels:

The <home-automation-dimmer> component
The responsibility of this component is to handle the interaction with one channel of the dimmer. The component exposes three property to its clients, which are:

  • value: the actual brightness of the dimmer (read/write).
  • channel: the channel ID of the dimmer (0, 1, 2 or 3).
  • color: the color of the UI element.

<polymer-element name="home-automation-dimmer" attributes="value channel color">

Concretely this component consists of a <paper-slider> UI element and two <core-ajax> components, one for the read (GET), the other for writes (POST).

<paper-slider value="{{value}}" max="255" id="dimmer"></paper-slider>

<!-- For the GET requests -->
<core-ajax auto url="{{ajaxurl}}/dimmer/{{channel}}" handleAs="text" response="{{value}}" loading="{{loading}}">

<!-- For POST updates -->
<core-ajax auto="{{!loading}}" url="{{ajaxurl}}/dimmer/{{channel}}" method="POST" handleAs="text" body="{{value}}">

The three components are bound together by the value property. When the component is loaded, the first AJAX component will issue a request to the dimmer/{{channel}} enpoint, binding its reponse to value. When the response is ready, the property is changed. The change of value causes an automatic update of the <paper-slider>, which will reflect the value read from the web-service.

Similarly, when the paper slider is changed, tapping on that, the value property will be propagated all the way through the second AJAX component, which will issue a HTTP POST request, setting the dimmer to the desired value.

The <home-automation> UI
The responsibility of the <home-automation> is to simply instantiate and group together the four <home-automation-dimmer> elements, one for each channel of the dimmer. Furthermore, it will define the CSS style-sheet to give a proper shape and colour to the elements, as follows:

  <polymer-element name="home-automation">

         ... (see complete example in the source code)

        <core-toolbar id="core_toolbar">
           <core-icon-button icon="menu" id="core_icon_button" on-tap="{{reset_all}}">
           <div id="div" flex>Home Automation</div>

        <core-card id="dimmers" class="card" layout vertical>
          <home-automation-dimmer channel="0" id="dimmer0" color="gold">
          <home-automation-dimmer channel="1" id="dimmer1" color="green">
          <home-automation-dimmer channel="2" id="dimmer2" color="red">
          <home-automation-dimmer channel="3" id="dimmer3" color="navy">

The full sources of the UI are available here:

Have fun!