Internet Of Thinks (IoT) is almost everywhere. A useful application that many people have wished to have is an easy way to remotely control lights no mater where they are. In the following pages we will describe how we built a complete platform for remotely controlling lights for less than 10$.
Hardware
Our platform is based on ESP8266 – a low cost System On Chip (SoC) module equipped with WiFi interface, plenty of GPIOs, SPI interface and more. The following picture displays the module pinout:

ESP-3 ESP8266 SoC
ESP-3 module provides seven general purpose input-output pins, and one UART channel. In our application we will use the UART channel for programming the module with our code and also we will use three of the I/O pins to control our lights. Special handling is needed for pins that serving multiple functionalities. In example, the following table describes the boot mode selection depending on the level that GPIO pins have during the power-on:
GPIO 15 | GPIO 0 | GPIO 2 | Mode | Description |
---|---|---|---|---|
L | L | H | UART | Download code from UART |
L | H | H | Flash | Boot from SPI Flash |
H | X | X | SD | Boot from SD-card |
So, we have to ensure – by placing the appropriates pull-up/down resistors – the correct levels during start-up. In our case we will use the SPI Flash for storing our code. Please note that we have also to ensure that we will be able to switch UART mode in order to be able to re-program the module with new software. The schematic diagram that following depicts our module interconnection:

Software
The custom ESP software initialize the WiFi interface and creates a TCP socket that the application can send commands to. Following the main part of the code that executes the ESP8266 for our application:
#include "ets_sys.h"
#include "osapi.h"
#include "gpio.h"
#include "driver/uart.h"
#include "os_type.h"
#include "user_config.h"
#include "user_interface.h"
#include "espconn.h"
#include "osapi.h"
#include "mem.h"
#define user_procTaskPrio 0
#define user_procTaskQueueLen 1
LOCAL char *precvbuffer;
os_event_t user_procTaskQueue[user_procTaskQueueLen];
static void loop(os_event_t *events);
char sta_mac[6] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xab};
static int count = 0;
static bool wifiStatus = false;
static unsigned char lights = 0;
void user_rf_pre_init(void){
}
LOCAL void ICACHE_FLASH_ATTR
webserver_recv(void *arg, char *pusrdata, unsigned short length)
{
char *pParseBuffer = NULL;
bool parse_flag = false;
struct espconn *ptrespconn = arg;
os_printf("len:%u\n",length);
if(length == 5){
os_printf("%2X %2X %2X %2X %2X\n",
pusrdata[0], pusrdata[1], pusrdata[2],
pusrdata[3], pusrdata[4]);
if (pusrdata[0] == 0x19 && pusrdata[1] == 0x78 &&
pusrdata[4] == (pusrdata[0] + pusrdata[1] + pusrdata[2] +
pusrdata[3])){
os_printf("DATA Ok\n");
switch (pusrdata[2]){
case (0x00):
break;
case (0x10):
lights = lights & ~0x01;
break;
case (0x20):
lights = lights & ~0x02;
break;
case (0x30):
lights = lights & ~0x04;
break;
case (0x11):
lights = lights | 0x01;
break;
case (0x21):
lights = lights | 0x02;
break;
case (0x31):
lights = lights | 0x04;
break;
case (0xF0):
lights = 0;
break;
case (0xF1):
lights = 7;
break;
default:
os_printf("Wrong command\n");
break;
}
switch (lights){
case (0):
gpio_output_set(0, BIT12|BIT13|BIT14, BIT12|BIT13|BIT14, 0);
break;
case (1):
gpio_output_set(BIT12, BIT13|BIT14, BIT13|BIT14, BIT12);
break;
case (2):
gpio_output_set(BIT13, BIT12|BIT14, BIT12|BIT14, BIT13);
break;
case (3):
gpio_output_set(BIT12|BIT13, BIT14, BIT14, BIT12|BIT13);
break;
case (4):
gpio_output_set(BIT14, BIT12|BIT13, BIT12|BIT13, BIT14);
break;
case (5):
gpio_output_set(BIT12|BIT14, BIT13, BIT13, BIT12|BIT14);
break;
case (6):
gpio_output_set(BIT13|BIT14, BIT12, BIT12, BIT13|BIT14);
break;
case (7):
gpio_output_set(BIT12|BIT13|BIT14, 0, 0, BIT12|BIT13|BIT14);
break;
default:
break;
}
espconn_sent(ptrespconn, &lights, 1);
}
}
}
LOCAL ICACHE_FLASH_ATTR
void webserver_recon(void *arg, sint8 err)
{
struct espconn *pesp_conn = arg;
os_printf("webserver's %d.%d.%d.%d:%d err %d reconnect\n", pesp_conn->proto.tcp->remote_ip[0],
pesp_conn->proto.tcp->remote_ip[1],pesp_conn->proto.tcp->remote_ip[2],
pesp_conn->proto.tcp->remote_ip[3],pesp_conn->proto.tcp->remote_port, err);
}
LOCAL ICACHE_FLASH_ATTR
void webserver_discon(void *arg)
{
struct espconn *pesp_conn = arg;
os_printf("webserver's %d.%d.%d.%d:%d disconnect\n", pesp_conn->proto.tcp->remote_ip[0],
pesp_conn->proto.tcp->remote_ip[1],pesp_conn->proto.tcp->remote_ip[2],
pesp_conn->proto.tcp->remote_ip[3],pesp_conn->proto.tcp->remote_port);
}
LOCAL void ICACHE_FLASH_ATTR
server_listen(void *arg)
{
struct espconn *pesp_conn = arg;
espconn_regist_recvcb(pesp_conn, webserver_recv);
espconn_regist_reconcb(pesp_conn, webserver_recon);
espconn_regist_disconcb(pesp_conn, webserver_discon);
}
void ICACHE_FLASH_ATTR
user_server_init(uint32 port)
{
LOCAL struct espconn esp_conn;
LOCAL esp_tcp esptcp;
esp_conn.type = ESPCONN_TCP;
esp_conn.state = ESPCONN_NONE;
esp_conn.proto.tcp = &esptcp;
esp_conn.proto.tcp->local_port = port;
espconn_regist_connectcb(&esp_conn, server_listen);
espconn_accept(&esp_conn);
}
//Main code function
static void ICACHE_FLASH_ATTR
loop(os_event_t *events)
{
int i;
gpio16_output_set(0);
if (wifi_station_connect()){
os_printf("Not Connected to WiFi\n\r");
wifiStatus = true;
}
// for(i=0;i<1000;i++)
os_delay_us(1000);
// system_os_post(user_procTaskPrio, 0, 0 );
}
//Init function
void ICACHE_FLASH_ATTR
user_init()
{
char ssid[32] = "MY_SSID";
char password[64] = "MY_PASS";
struct station_config stationConf;
os_printf("Init a\n\r");
uart_init(BIT_RATE_115200, BIT_RATE_115200);
gpio16_output_conf();
gpio16_output_set(0);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14);
gpio_output_set(0, BIT12|BIT13|BIT14, BIT12|BIT13|BIT14, 0);
//Set station mode
//wifi_set_macaddr(uint8 if_index, uint8 *macaddr)
wifi_set_opmode_current( STATION_MODE );
os_memcpy(&stationConf.ssid, ssid, 32);
os_memcpy(&stationConf.password, password, 64);
stationConf.bssid_set = 0;
wifi_station_set_config_current(&stationConf);
// wifi_status_led_install (16, uint32 gpio_name, FUNC_GPIO16)
os_printf("Init Ok! %d\n\r", wifi_station_get_connect_status());
wifi_station_set_auto_connect(1);
wifi_station_connect();
wifi_station_dhcpc_start();
user_server_init(8888);
//Start os task
system_os_task(loop, user_procTaskPrio,user_procTaskQueue, user_procTaskQueueLen);
// system_os_post(user_procTaskPrio, 0, 0 );
}
The full ESP project is also available under the following directory:
The module is now programmed and ready for the field.

IoT Module
Applications
Now we have to prepare the client application. The application is running under android platform and is able to control the tree outputs. The main layout of our application is the following:

Android application layout
The application is a simple android layout communicating with our module using a TCP socket and sending command either to switch-off or to switch-on a specific module output. The main code of the application is the following:
package com.ioko.iot;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.StrictMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class MainActivity extends Activity {
Context m;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
m = this;
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
Button A_OFF = (Button)findViewById(R.id.buttonAoff);
Button A_ON = (Button)findViewById(R.id.buttonAon);
Button B_OFF = (Button)findViewById(R.id.buttonBoff);
Button B_ON = (Button)findViewById(R.id.buttonBon);
Button C_OFF = (Button)findViewById(R.id.buttonCoff);
Button C_ON = (Button)findViewById(R.id.buttonCon);
Button EXIT = (Button)findViewById(R.id.buttonExit);
A_OFF.setOnClickListener(buttonHandler);
A_ON.setOnClickListener(buttonHandler);
B_OFF.setOnClickListener(buttonHandler);
B_ON.setOnClickListener(buttonHandler);
C_OFF.setOnClickListener(buttonHandler);
C_ON.setOnClickListener(buttonHandler);
EXIT.setOnClickListener(buttonHandler);
}
View.OnClickListener buttonHandler = new View.OnClickListener() {
public void onClick(View v) {
byte data = 0;
switch(v.getId()){
case (R.id.buttonAoff):
data = 0x10; // Light A off
break;
case (R.id.buttonAon):
data = 0x11; // Light A on
break;
case (R.id.buttonBoff):
data = 0x20; // Light B off
break;
case (R.id.buttonBon):
data = 0x21; // Light B on
break;
case (R.id.buttonCoff):
data = 0x30; // Light C off
break;
case (R.id.buttonCon):
data = 0x31; // Light C on
break;
case (R.id.buttonExit):
finish();
break;
}
byte[] a = new byte[5];
a[0] = 0x19; //Preamp word A
a[1] = 0x78; //Preamp word B
a[2] = data;
a[3] = 0x00;
a[4] = (byte)(a[0] + a[1] + a[2] + a[3]); //Checksum
Socket s;
OutputStream o;
InputStream i;
try {
s = new Socket("MyHost.com", 8888);
o = s.getOutputStream();
o.write(a,0,5);
i = s.getInputStream();
i.read(a,0,1);
} catch (IOException e) {
e.printStackTrace();
}
}
};
}
Android application source code can be found under the following github project:
Finally, we have to install our module. The following picture displays the module full functional, installed and controlling the garden light:

Module installed
Additional information
How to configure ESP tool-chain under Ubuntu.
> sudo apt-get install git autoconf build-essential gperf bison flex texinfo libtool libncurses5-dev wget gawk libc6-dev-amd64 python-serial libexpat-dev libtool-bin
> sudo mkdir /opt/Espressif
> sudo chown $USER /opt/Espressif/
> cd /opt/Espressif
> git clone -b lx106 git://github.com/johnkok/crosstool-NG.git
> cd crosstool-NG
> ./bootstrap && ./configure --prefix=`pwd` && make && make install
> ./ct-ng xtensa-lx106-elf
> ./ct-ng build
> PATH=/opt/Espressif/crosstool-NG/builds/xtensa-lx106-elf/bin:$PATH
Hint: you can append the user’s .profile file with the following line in order to avoid sending the PATH each time you are using the tool-chain:
> export PATH=/opt/Espressif/crosstool-NG/builds/xtensa-lx106-elf/bin:$PATH
Test your setup
Download the SDK from the following link and extract it under /opt/Espressif folder:
https://github.com/johnkok/esp8266-wiki/blob/master/sdk/esp_iot_sdk_v1.2.0_15_07_03.zip
Try to build a sample project by executing the gen_misc.sh script file.
Program ESP board
In order to program the module through the UART interface, you will need a 3,3V TTL RS232 adapter (either from COM port or a USB adapter). Then you need to X-connect the RxD and TxD of the module with the adapter. The module is equipped with a build-in loader capable to program the flash from the UART port. A prerequisite is to put the module in UART Boot mode during start-up (by pulling down the GPIO-0 signal or pressing the SW1 switch of our scematic diagram).
Download the ESP tools from the following link:
https://github.com/johnkok/esp8266-wiki/blob/master/deb/esptool_0.0.2-1_i386.deb
Install the packet by executing the following command:
> dpkg -i esptool_0.0.2-1_i386.deb
> mkdir /opt/Espressif/esptool
> cd /opt/Espressif/esptool
> git clone https://github.com/johnkok/esptool esptool-py
> sudo ln -s /opt/Espressif/esptool-py/esptool.py /opt/Espressif/crosstool-NG/builds/xtensa-lx106-elf/bin/
Now we are ready to program the module! you have to make your project using the module port in order to automatically program the module with the new software:
> make ESPPORT=/dev/ttyUSB0 flash
Useful Resources:
- ESP8266 Getting Started Guide
- ESP8266 System Description
- ESP8266 Technical Reference
- ESP8266 Programming Guide