Building a cheap WiFi power switch

Building a cheap WiFi power switch

The first stop in my smart home series is to control power switches via WiFi.

To do that I researched what devices are available and found that they either cost a lot, like 49€ for a power switch from Fritz!, or always want to connect to either someone else’s server or a shady cloud.

So the choice was either between

  • Paying a lot
  • Trusting a 3rd party server/cloud and hope they don’t go down

The first option is something that I, as a student, can’t really afford on a bigger scale. Considering the second option I just remembered about a server outage(German article) of a smart home provider, so I don’t want to rely on a service like that.

Because of those reasons I went with option 3, do it yourself! :-)

SonOFF

To build a switch myself I had to find a microcontroller which I can program and which is compatible to switch a 230V connection.

When researching this I found a cheap (4.54€ as of writing) device called SonOFF, which is based on the ESP8266 microcontroller and contains a WiFi chip.

The SonOFF itself connects to an AWS cloud server to make it possible to control it via a smartphone app (called eWeLink). Since this violates my 2nd rule about 3rd party servers/clouds it should be possible to flash a new firmware on it.

Because it is using the ESP8266 it can be programmed with the Arduino SDK and we can therefore flash the original firmware, use our own API and, most importantly, stop the connection to the cloud.

aREST

To communicate with the SonOFF I’m using aREST, which is a framework for running a RESTful HTTP server on a microcontroller. They also support the ESP8266 and provide an example in their Github.

The framework supports five types of commands which can be used over HTTP:

We’ll be using a function to set power state of our SonOFF, but more to that later.

Shopping list

Name Price
SonOFF 4.54€
USB to TTL Converter (one time buy) 1.50€
Female jumper cable (one time buy) 1.28€
Male pin header (5 pins per SonOFF) 1.16€

To get started we need a USB to TTL Converter, to connect the SonOFF to the PC. I used the converter from the table above, and therefore needed 4 female jumper cables. My SonOFFs didn’t come with a pin header on the board, so I needed a total of 5 male pins per board.

The one time buys cost around 4€. The pins can be used for 80 switches. So with the initial cost of around 4€, the first 80 switches cost around 5€ per switch :-)

Connecting the SonOFF to the connector

To get started on how to wire the SonOFF to my PC I followed this guide, but I’ll write it up to follow along the components from above.

From top to bottom, where top is the side of the SonOFF’s button, the pins on the board are

  • 3.3V
  • TX (transmit)
  • RX (receive)
  • GND (ground)
  • GPIO 14

Pins on the SonOFF

The last pin is a spare pin, which could be used to connect another sensor.

The order on my USB to TTL converter is

  • 5V
  • VCC
  • 3V3
  • TXD
  • RXD
  • GND

Pins on the Converter

To connect the converter we have to connect the following pins:

Converter SonOFF
VCC 3.3V
TXD TX
RXD RX
GND GND

Setting up the IDE

To develop the new firmware we can use any existing IDE that supports building for Arduino.

There are plugins for VSCode and Visual Studio, but for this tutorial I’ll go with the official Arduino IDE.

First we have to download the Arduino IDE and install it.

The next step is to get the ESP8266 board into the IDE, for that we go to File -> Preferences and add the URL http://arduino.esp8266.com/versions/2.3.0/package_esp8266com_index.json to Additional Boards Manager URLs.

Add the ESP8266 to the board manager

Now that we have the boardlist we need the aREST library: Sketch -> Include Library -> Manage Libraries, search for aREST and click install. This will set the current Sketch up to be able to reference aREST and also put a #include into the sketch.

The last step to get the sketch ready is to download and set the ESP8266 board as a target. To do so go to Tools -> Board -> Boards Manager..., search for ESP8266 and install the package (my screenshot says it is already installed).

Install the ESP8266 package

After that we can specify our target board via Tools -> Board -> Generic ESP8266 Module.

If everything went well it should look like the following, with the aREST library included and the IDE stating Generic ESP8266 Module as a target in the bottom status bar:

Sketch with included library and set up target

The sketch contains two empty functions, setup() and loop(). The setup() function is run first after the board starts and the loop() function gets called over and over again, as the name suggests.

Programming

We finally get to the exciting stuff, programming! (the full sketch can be found here)

The first thing we need to do is include the headers we need, set up some configuration for each device and declare our global variables (note: we can also get an IP via DHCP but I used static IPs):

This includes the ip addresses, WiFi connection, #defines for states, the port and GPIO pins for the SonOFF button, LED and relay (power switch). We also create an instance of aREST and the WiFiServer of the ESP8266 board.

The SonOFF has 3 pins on the board itself (aside from the pins explained in Connecting the SonOFF to the connector), which are connected to the power relay (switches the power on and off), the LED (on or off) and the button. The most important pin here is the power pin, because we use it to control the power of the connected device. These pins can have a state of HIGH or LOW (constants come from the Arduino library). We can declare a pin as either OUTPUT or INPUT.

OUTPUT allows us to change the state of the pin within our code with the digitalWrite function. INPUT allows us to read the state of the pin within our code with the digitalRead function.

Pin# controls HIGH state LOW state
0 button not pressed pressed
12 power relay power on power off
13 LED LED on LED off
#include <ESP8266WiFi.h>
#include <aREST.h>

// Configuration for each SonOFF
#define DEVICE_NAME "My Printer"
IPAddress ip(192, 168, 0, 100);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
const char* ssid = "WLAN_SSID";
const char* password = "WLAN_PASSWORD";

#define LISTEN_PORT 80
#define STATE_ON 1
#define STATE_OFF 0
#define GPIO_LED 13
#define GPIO_RELAY 12
#define GPIO_BUTTON 0


// Global variables
aREST rest = aREST();
WiFiServer server(LISTEN_PORT);
int currentPowerState;          // remember our current state

// The function exposed to the API to turn the power on and off
int powerControl(String command);

void setup() {

}

void loop() {

}

In setup() we do different things to set up our serial connection (for println-debugging), configure our pins, set up the aREST API and connect to WiFi. The pins have to be set into OUTPUT or INPUT mode, which we’ll also do once in the setup().

void setup(void)
{
    Serial.begin(115200);
    Serial.println("Setting up pins");

    pinMode(GPIO_LED, OUTPUT);
    pinMode(GPIO_RELAY, OUTPUT);
    pinMode(GPIO_BUTTON, INPUT);

    // set the power relay initialy to off
    digitalWrite(GPIO_RELAY, LOW);
    currentPowerState = STATE_OFF;

    // declare our function exposed to the API
    rest.function("power", powerControl);

    rest.set_id("1");
    rest.set_name(DEVICE_NAME);

    // set up our static IP
    WiFi.config(ip, gateway, subnet);

    // Connect to WiFi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");

    // Start the WiFi server which listens to port 80
    server.begin();
    Serial.println("Server started");

    // Print our IP address
    Serial.println(WiFi.localIP());

    // We're ready, so flash a bit!
    digitalWrite(GPIO_LED, LOW);
    delay(1);
    digitalWrite(GPIO_LED, HIGH);
    delay(1);
    digitalWrite(GPIO_LED, LOW);
    delay(1);
    digitalWrite(GPIO_LED, HIGH);
}

The Serial.println is similar to System.out.println in Java, but prints into the serial monitor within our Arduino IDE.

Now we need to implement the loop function:

// Another global variable to not toggle the state when button is hold down
bool needsHandle = true;

void loop() {
    int newButtonState = digitalRead(GPIO_BUTTON);

    // Button pressed
    if (newButtonState == LOW && needsHandle) {
        needsHandle = false;
        Serial.println(newButtonState);
        toggleState();
    }

    // Button released
    if (newButtonState == HIGH && !needsHandle) {
        needsHandle = true;
    }

    WiFiClient client = server.available();
    if (!client) {
        return;
    }
    while (!client.available()) {
        delay(1);
    }
    rest.handle(client);
}

Now each loop of the microcontroller we check if the button on SonOFF is pressed. This way we can either switch the power by pressing the button or using our REST API.

To change the state via our REST API we need to write the powerControl function which we declared above the setup() function.

powerControl takes an int as parameter (stating the desired new state, provided as an URL parameter) and returns an int as an indicator of the new state:

int powerControl(String command) {
    if (command.length() == 0) {
        return currentPowerState;
    }

    // Sanitize input, aREST doesn't remove the =
    command.replace("=", "");

    int newState = command.toInt();
    if (newState == currentPowerState) {
        return currentPowerState;
    }

    if (newState == STATE_ON) {
        powerOn();
    }
    if (newState == STATE_OFF) {
        powerOff();
    }

    return currentPowerState;
}

That’s all we need for the API. aREST will handle the incoming call and redirect it to the powerControl function, taking the return value and response with JSON.

All we’re missing now to complete our sketch are the actual functions that control the power state of our SonOFF:

void powerOn() {
    digitalWrite(GPIO_LED, LOW);
    digitalWrite(GPIO_RELAY, HIGH);
    currentPowerState = STATE_ON;
}

void powerOff() {
    digitalWrite(GPIO_LED, HIGH);
    digitalWrite(GPIO_RELAY, LOW);
    currentPowerState = STATE_OFF;
}

void toggleState() {
    if (currentPowerState == STATE_ON) {
        powerOff();
    }
    else if (currentPowerState == STATE_OFF) {
        powerOn();
    }
}

Note: setting GPIO_LED to LOW will light the LED up

Flashing the firmware

WARNING: If you flash a new firmware onto the SonOFF you’ll lose the eWeLink feature of the SonOFF. Also if something fails, the device might be bricked.

Also: don’t plug your SonOFF into 230V while you’re flashing

Now with the connector plugged into the SonOFF board we have to hold down the SonOFF button, plug USB in, wait 1-2 seconds and release the button to enter the firmware flash mode of the SonOFF.

To compile and flash the sketch we first have to select the current COM port via Tools -> Port (in my case COM3), if you’re unsure you can check the ports before plugging in the converter and see what port was added.

Now open the serial monitor via Tools -> Serial Monitor and set its baudrate to 115200 in the bottom right.

We can now compile and upload the sketch (if the SonOFF is in flash mode) with the => arrow above the sketch, right beneath the menu bar.

The output in the IDE (not serial monitor) should say something like:

Sketch uses 238661 bytes (54%) of program storage space. Maximum is 434160 bytes.
Global variables use 34848 bytes (42%) of dynamic memory, leaving 47072 bytes for local variables. Maximum is 81920 bytes.
Uploading 242816 bytes from C:\Users\Markus\AppData\Local\Temp\arduino_build_8682/sketch_nov24a.ino.bin to flash at 0x00000000
................................................................................ [ 33% ]
................................................................................ [ 67% ]
..............................................................................   [ 100% ]

and when it is finished you should see some output in the serial monitor:

Output of the serial monitor after uploading the sketch

This indicates that the SonOFF is connected to the WiFi and the setup stage is completed. While plugged in we can now press the button and should see the LED light up. Pressing it again should turn the LED off.

Opening the IP in the browser (in my case 192.168.1.254) it should return JSON stating infos about the device and http://192.168.1.254/power infos about the current power state.

We can now call our function to turn the power on, also via browser: http://192.168.1.254/power?command=1

And also turn it off again via http://192.168.1.254/power?command=0

If everything works, we can plug out the USB and get ready to build the SonOFF into a cable :-)

Build the SonOFF into a cable

The last step is to get the SonOFF into an actual cable to power it on and off.

For this I took a “Kaltgerätestecker” which is connected to a printer.

Important: Don’t cut the cable. The ground cable has to remain connected and isn’t connected to the SonOFF

Strip the cable in the middle and leave enough space for the SonOFF.

Have a look at the case of your SonOFF, it indicates at which side the input and output should be.

Now we can connect the phase and neutral cable through the SonOFF and let the grounding go aside the SonOFF (see below).

With the cable set up I can now connect my printer and control it via my smartphone:

The video contains a small spoiler, because in the next post we’ll look at how to integrate the aREST powered SonOFFs to Home Assistant, so hang tight :-)

comments powered by Disqus