Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DirectControl - Remote Control for Electrical Loads via WiFi

0.00/5 (No votes)
7 Aug 2018 1  
Using ESP8266 module to remotely control high power electrical appliances

Introduction

This is an introductory article which shows how high-power loads can be controlled easily using affordable electronics, like popular ESP8266 wireless module.

Background

The ESP8266 is a Wi-Fi module with full TCP/IP stack and microcontroller programming capability. The ESP8266 module can work either in the Access Point mode or in the client (Station) mode, or in both modes at once (mixed mode), although the latter is unstable. It also has 1MB of flash memory.

Module Block Diagram

Mode Detailed Technical Specifications

  • 802.11 b / g / n wireless standards
  • STA / AP modes support
  • TCP / IP protocol stack, One socket
  • Supports standard TCP / UDP Server and Client
  • Supports serial port baud rate configuration: 1200/2400/4800/9600/19200/38400/57600/74800/115200 bps
  • Supports serial data bits: 5/6/7/8 bits
  • Supports serial parity: none
  • Supports serial stop bits: 1/2 bit
  • ESP8266 GPIO Pinout 0/2/4/5/9/10/12/13/14/15/16 / ADC / EN / * UART TX / UART RX
  • WiFi operation current: continuous transmission operation: ≈70mA (200mA MAX), idle mode: <200uA
  • Serial WiFi transmission rate: 110-460800bps

There are many flavours of ESP8266 module on the market. I was using ESP8266MOD, because it has plenty of GPIO wired outside of the chip, external antenna connector and rigid metal casing:

The Schematics

If you are familiar with the basics of electronics, you will find that constructing this schematic is very easy.

You will need:

  • ESP8266MOD
  • 3.3V Power Supply
  • Fortek SSR40-DA-H Solid-state Relay
  • A bunch of header connectors and resistors
  • USB-UART 3,3V Level Adapter
Disclaimer: This application uses hazardous voltages from mains line. Please take care! Debug when disconnected from mains! Make sure everything is insulated well!

Preparing Development Environment

As a development kit, I have chosen Unofficial Development Kit for Espressif ESP8266.
Instructions for installing and configuring the Unofficial Development Kit for Espressif ESP8266:

  1. Download the Unofficial Development Kit for Espressif ESP8266 (148Mb) and install.
  2. Download and install the Java Runtime x86 or x64 (jre-8uXXX-windows-xxx.exe)
  3. Download and install Eclipse Neon x86 or Eclipse Neon x86_64 for the development of C ++. Extract the archive to the root of drive C.
  4. Download and install MinGW. Run mingw-get-setup.exe, the installation process to select without GUI, i.e., uncheck "... also install support for the graphical user interface".
  5. Download scripts to automate the installation MinGW packages.
  6. Run the install-mingw-package.bat file. Download pre-loaded packages for MinGW files ensures that they will be installed, sometimes the server where MinGW packages are no longer available and the required packages are not installed.
  7. Start the Eclipse Neon from the directory c:\eclipse\eclipse.exe
  8. In Eclipse, select File -> Import -> General -> Existing Project into Workspace, in the line Select root directory, select project root directory and import it.
  9. You will need to replace the file c:\Espressif\examples\ESP8266\common_cpp.mk with this file: common_cpp.zip. This is to ensure correct C99 builds.

Using the Code

The following code is used to start an Access Point:

void ICACHE_FLASH_ATTR WifiConnectorInit()
{
	WifiEnterWorkMode();
	captdnsInit();
	WebInit(); // htttpdInit
}
void ICACHE_FLASH_ATTR WifiEnterWorkMode()
{
	wifi_set_opmode(SOFTAP_MODE);
		struct softap_config apconfig;
		if(wifi_softap_get_config(&apconfig))
		{
			wifi_softap_dhcps_stop();
			os_memset(apconfig.ssid, 0, sizeof(apconfig.ssid));
			os_memset(apconfig.password, 0, sizeof(apconfig.password));
			os_sprintf((char*)apconfig.ssid, "iqhub%ld", (long int)56); //our ssid name
			apconfig.ssid_len = os_strlen((char*)apconfig.ssid);
			apconfig.authmode = AUTH_OPEN;
			apconfig.ssid_hidden = 0;
			apconfig.channel = 9;
			apconfig.max_connection = 4;
			apconfig.beacon_interval = 100;
			if(!wifi_softap_set_config(&apconfig))
				os_printf("%s", "ESP8266 not set AP config!\r\n");
			struct ip_info ipinfo;
			wifi_get_ip_info(SOFTAP_IF, &ipinfo);
			IP4_ADDR(&ipinfo.ip, 192, 168, 4, 1);
			IP4_ADDR(&ipinfo.gw, 192, 168, 4, 1);
			IP4_ADDR(&ipinfo.netmask, 255, 255, 255, 0);
			wifi_set_ip_info(SOFTAP_IF, &ipinfo);
			wifi_softap_dhcps_start();
		}
		wifi_set_broadcast_if(3); //1:station; 2:soft-AP, 3:station+soft-APs
}

For this project, our URL query set will look like this:

HttpdBuiltInUrl builtInUrls[]={
	{"*", cgiRedirectApClientToHostname, "esp.nonet"},
	{"/", cgiRedirect, "/index.html"},
    {"/index.html", cgiIndex, NULL},
	{"/onButton", cgiOnButton, "/index.html"},
	{"/offButton", cgiOffButton, "/index.html"},
    {NULL, NULL, NULL}
};

void ICACHE_FLASH_ATTR WebInit() {
	httpdInit(builtInUrls, 80);
}

CGI scripts are programs running on web server that generates web pages dynamically due to client request.
To answer some of this client requests, we need to implement several custom CGI scripts:

static char * g_web_index = { //... }; // Our web page as byte array,
				       // contains some format notations 
				       // such as "%s" to insert curr led state
static char * g_web_index_formated = NULL; //  Buffer for formatted web pages 
typedef struct {
    int arrayPos;
    char buff[128];
} LongPageSendState; 

bool g_ledState = false;

int ICACHE_FLASH_ATTR cgiIndex(HttpdConnData *connData) {

	LongPageSendState *state= (LongPageSendState *)connData->cgiData;
    int len;
    //If the browser unexpectedly closes the connection, the CGI will be called
    //with connData->conn=NULL. We can use this to clean up any data. It's pretty relevant
    //here because otherwise we may leak memory when the browser aborts the connection.
    if (connData->conn==NULL) {
        //Connection aborted. Clean up.
        if (state!=NULL) os_free(state);
        if(g_web_index_formated) {
        	os_free(g_web_index_formated);
        	g_web_index_formated = NULL;
        }
        return HTTPD_CGI_DONE;
    }
    if (state == NULL) {
        //This is the first call to the CGI for this webbrowser request.
        //Allocate a state structure.
        state= (LongPageSendState *)os_malloc(sizeof(LongPageSendState));
        g_web_index_formated = (char *)os_malloc(sizeof(g_web_index) + 10);
        //Save the ptr in connData so we get it passed the next time as well.
        connData->cgiData=state;
        //Set initial pointer to start of string
        state->arrayPos= 0;
        state->buff[0] = 0;
        //We need to send the headers before sending any data. Do that now.
        httpdStartResponse(connData, 200);
        httpdHeader(connData, "Content-Type", "text/html");
        httpdEndHeaders(connData);
    }
    //Figure out length of string to send. We will never send more than 128 bytes in this example.
    os_sprintf(g_web_index_formated, g_web_index,  g_ledState ? "ON" : "OFF");
    len = os_strlen(g_web_index_formated) - state->arrayPos; //Get remaining length
    if (len>128) len=128; //Never send more than 128 bytes
    if (len==0) {
    	os_free(state);
    	os_free(g_web_index_formated);
    	g_web_index_formated = NULL;
    	return HTTPD_CGI_DONE;
    }
    //substr
    for(int i = 0; i < len; i++){
    	state->buff[i] = g_web_index_formated[i + state->arrayPos];
    }
    //Send that amount of data
    httpdSend(connData, state->buff, len);
    //Adjust stringPos to first byte we haven't sent yet
    state->arrayPos+=len;
    //See if we need to send more
    if (len == 128) {
        //we have more to send; let the webserver call this function again.
        return HTTPD_CGI_MORE;
    } else {
        //We're done. Clean up here as well: if the CGI function returns HTTPD_CGI_DONE, it will
        //not be called again.
        os_free(state);
        os_free(g_web_index_formated);
        g_web_index_formated = NULL;
        return HTTPD_CGI_DONE;
    }
}

int ICACHE_FLASH_ATTR cgiOnButton(HttpdConnData *connData){
	g_ledState = true;
	ioLed(1);
	return cgiRedirect(connData); // Redirect to /index.html
}
int ICACHE_FLASH_ATTR cgiOffButton(HttpdConnData *connData){
	g_ledState = false;
	ioLed(0);
	return cgiRedirect(connData); // Redirect to /index.html
}

And our web page HTML code will look like this:

<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:">
<style>html { text-align: center;} </style></head>
<body><h1>ESP8266 Web Server</h1>
<p>GPIO 5 - State %s</p>     
<p><a href="onButton"><button class="button">ON</button></a></p>
<p><a href="offButton"><button class="button">OFF</button></a></p>
</body></html>

Converting Web to Binary Format

To convert HTML page to C++ code, I have used a utility that receives HTML file path as command line argument.
Usage:

BinToCArray.exe index.html

And the output looks like this:

The source code of this utility is pretty simple:

int main(int argc, char** argv) {
	assert(argc == 2);
	char* fn = argv[1];
	FILE* f = fopen(fn, "r");
	fseek(f, 0, SEEK_END); 
	long size = ftell(f); 
	fseek(f, 0, SEEK_SET); 

	printf("char a[%ld] = {\n", size );
	unsigned long n = 0;
	while (!feof(f)) {
		unsigned char c;
		if (fread(&c, 1, 1, f) == 0) break;
		printf("0x%.2X,", (int)c); //Print in hex
		++n;
		if (n % 10 == 0) printf("\n");
	}
	fclose(f);
	printf("};\n");
}

By doing this, we can store our web pages in ESP8266 RAM. As an alternative, I suggest you write it into the header file and include it into the module where it is used.

How to Build and Flash

  1. In Eclipse, select File -> Import -> General -> Existing Project into Workspace, in the line Select root directory, select project root directory and import it.
  2. To build project, select Project -> Build Project on Menu Bar.

  3. To flash the ESP8266, go to project folder on Make Target view and build "flash" target.

  4. You will need USB-3.3V UART adapter, there's plenty on the market available for a few bucks.
  5. Before flashing, make sure GPIO0 is tied to ground and reset the chip (power cycle), disconnect from ground after flashing.

How to Use

If flashed correctly, the module will bring up access point called iqhub56, connect to it using any mobile phone and open web address: http://192.168.4.1. You will see small webpage where you will be able to control pin output. Here is a small video showing how this device works:

History

  • 6th August, 2018 - First version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here