ESP8266 – Light Control

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:

iot_esp

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:

iot

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:

https://github.com/johnkok/IoT_LightControl/tree/master/esp

The module is now programmed and ready for the field.

iot1

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:

Screenshot (19 May 2016 11-45 pm)

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:

https://github.com/johnkok/IoT_LightControl/tree/master/android/app

Finally, we have to install our module. The following picture displays the module full functional, installed and controlling the garden light:

iot2

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:

Leave a Reply