Programming an IoT webserver

 

In this project I build a webserver with the ESP8266 as the interface between the TCP/IP protocol and the Atmega328 microcontroller. The interesting part is that both parts are very cheap and that makes it a practical way to connect home devices to the internet. It’s a bit basic now with only a few buttons to turn 4 led’s on or off via a webpage, but the led’s can be replaced with relay’s and a proper current driver for the relays. Then it will be possible to control all your home equipment remotely from any location in the world with an internet connection.

The hardware setup

In the schematic I removed the connection details of the lcd display to get the schematic on 1 page. In this blog you can find these connections. Keep in mind that the logic level converter and ESP8266 requires 3.3V instead of 5V.

Schematic esp8266 with the Atmega328p

 

The software

I preconfigured the esp8266 in my last blog with the settings that will not change like the ssid and WiFi password and the baudrate. All other settings are set in the esp8266_init() function in the esp8266 library. This library also contains a function to send data to the esp. The esp requires a noticiation of the amount of bytes that are going to be send and after an ok is received by the microcontroller the bytes can be send so just sending with the uart send function doesn’t work.

The basic working of the program is text getting send to the esp by an internet browser. The microcontroller reads it and checks it against some predefined text. If it matches then the display will show this and a GPIO gets turned on or off.

Because I only have 2Kb of data memory and a basic page with 8 buttons takes a lot of bytes, i used the PROGMEM macro to save the page in the program memory, because i have enough free space there.

The incoming UART data gets automatically saved via interrupts. To save some space I used a smaller buffer then the actual bytes that get send by an web browser. For this project only the first ca. 50 bytes send by the browser are interesting, because these bytes tells which button is pressed. So the rest of the bytes will not be used and a buffer overflow var is set. After the incoming message is handelled the buffer overflow can be reset by the flushBuffer function.

Download the full code here or have a look below.

esp8266.h

/*
 * esp8266.h
 *
 * Created: 8-2-2018 21:08:06
 *  Author: acer
 */ 


#ifndef ESP8266_H_
#define ESP8266_H_

#define GETRECEIVED 1
#define USERDEFINEDRECEIVED 2
#define BTN1RCVDON 3
#define BTN1RCVDOFF 4
#define BTN2RCVDON 5
#define BTN2RCVDOFF 6
#define BTN3RCVDON 7
#define BTN3RCVDOFF 8
#define BTN4RCVDON 9
#define BTN4RCVDOFF 10
#define NOTHINGSET 11



void esp8266_init(void);
bool esp8266_send(const char* text1);
uint8_t readInComing(int timeout, const char* text1);
void resetIncoming(void);
uint8_t checkIncomingFlag(void);
char* getIP(void);

extern unsigned long int msCounter;
struct RcvFlag rcvFlag1;

struct RcvFlag {
	bool getReceived;
	bool userDefinedReceived;
	bool btn1RcvdOn;
	bool btn1RcvdOff;
	bool btn2RcvdOn;
	bool btn2RcvdOff;
	bool btn3RcvdOn;
	bool btn3RcvdOff;
	bool btn4RcvdOn;
	bool btn4RcvdOff;
};


#endif /* ESP8266_H_ */

esp8266.c

/*
 * esp8266.c
 *
 * Created: 8-2-2018 21:07:55
 *  Author: acer
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed

#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include "timer4clocklib.h"
#include "i2cdisplay.h"
#include "uart.h"
#include "esp8266.h"

/****************************************************************************
	Bit and byte definitions
****************************************************************************/

char getReceived[] = "GET / HTTP/1.1";
char btn1RcvdOn[] = "GET /?p1=on HTTP/1.1";
char btn1RcvdOff[] = "GET /?p1=off HTTP/1.1";
char btn2RcvdOn[] = "GET /?p2=on HTTP/1.1";
char btn2RcvdOff[] = "GET /?p2=off HTTP/1.1";
char btn3RcvdOn[] = "GET /?p3=on HTTP/1.1";
char btn3RcvdOff[] = "GET /?p3=off HTTP/1.1";
char btn4RcvdOn[] = "GET /?p4=on HTTP/1.1";
char btn4RcvdOff[] = "GET /?p4=off HTTP/1.1";

/****************************************************************************
	esp8266_init(), initialize the esp8266
****************************************************************************/
void esp8266_init(){
	
	uint8_t p;
	
	// Check the status of the ESP8266 if not ok reset
	_delay_ms(500);
	serialWrite("AT", CRLF);
	p = readInComing(1000, "OK\r\n");
	if (p != 2){
		writeI2cDisplayLine("Reset needed!", 0, 1);
		_delay_ms(1000);
		serialWrite("AT+RST", CRLF);
		p = readInComing(2000, "OK\r\n");
		if (p == 2){
			writeI2cDisplayLine("Reset done OK!", 0, 1);
			_delay_ms(1000);
		}
	}
	else{
		writeI2cDisplayLine("ESP8266 conn. OK", 0, 1);
		_delay_ms(1000);
	}
	
	// reset all flags generated by incoming data from the ESP8266 and empty the buffer.
	resetIncoming();
	flushBuffer();
	
	// configure the server and activate the server on the ESP8266
	serialWrite("AT+CIPMUX=1", CRLF);
	p = readInComing(1000, "OK\r\n");
	if (p == 2){
		resetIncoming();
		flushBuffer();
		writeI2cDisplayLine("AT+CIPMUX=1 set", 0, 1);
		_delay_ms(1000);
		serialWrite("AT+CIPSERVER=1,80", CRLF); //\r\n
		p = readInComing(1000, "OK\r\n");
		if (p == 2){
			resetIncoming();
			writeI2cDisplayLine("AT+CIPSERVER set", 0, 1);
			//_delay_ms(1000);
		}
	}
	
	// init is done so clean up all flags again.
	resetIncoming();
}

/****************************************************************************
	esp8266_send, send uart data to the esp8266
****************************************************************************/
bool esp8266_send(const char* text1){
	
	int p;
	int textLength;
	char buf1[700] = {'\0'};
	char itoaBuf[3];

	// the PROGMEM memory for saving the data memory usage.
	if (text1 != NULL){
		strcpy_P(buf1, (char *) text1);
		textLength = strlen(buf1);
	}
	else{
		textLength = 0;
	}
	
	// the esp8266 requires the amount of bytes that will be send in chars.
	itoa(textLength+2, itoaBuf ,10);
	serialWrite("AT+CIPSEND=0,", NO_EOL);
	serialWrite(itoaBuf, CRLF);
	
	// the data is send wait on the esp8266 for a confirmation
	p = readInComing(1000, "OK\r\n");
	if (p == 2){
		resetIncoming();
		flushBuffer();
		serialWrite(buf1, CRLF);
		p = readInComing(500, "SEND OK\r\n");
		if (p == 2){
			resetIncoming();
			flushBuffer();
			return true;
		}
		else{
			return false;
		}
	}
	else{
		return false;
	}
		
}

/****************************************************************************
	checkInComing, check/read data received by the esp8266
****************************************************************************/
uint8_t readInComing(int timeOut, const char* text1){ // ipv int
	
	char buf1[30] = {'\0'};
	uint8_t flag;
	
	uint8_t p; //unsigned char p;
	
	uint8_t lengthGetRcvd;
	uint8_t lengthUsrDef;
	uint8_t lengthBtn1On, lengthBtn1Off, lengthBtn2On, lengthBtn2Off, lengthBtn3On, lengthBtn3Off, lengthBtn4On, lengthBtn4Off;
	
	
	uint8_t posGetRcvd = 0;
	uint8_t posUsrDef = 0;
	uint8_t posBtn1On = 0;
	uint8_t posBtn1Off = 0;
	uint8_t posBtn2On = 0;
	uint8_t posBtn2Off = 0;
	uint8_t posBtn3On = 0;
	uint8_t posBtn3Off = 0;
	uint8_t posBtn4On = 0;
	uint8_t posBtn4Off = 0;
	
	lengthGetRcvd = strlen(getReceived);
	lengthBtn1On = strlen(btn1RcvdOn);
	lengthBtn1Off = strlen(btn1RcvdOff);
	lengthBtn2On = strlen(btn2RcvdOn);
	lengthBtn2Off = strlen(btn2RcvdOff);
	lengthBtn3On = strlen(btn3RcvdOn);
	lengthBtn3Off = strlen(btn3RcvdOff);
	lengthBtn4On = strlen(btn4RcvdOn);
	lengthBtn4Off = strlen(btn4RcvdOff);
	
	
	
	if (text1 != NULL){
		strcpy(buf1, (char *) text1);
		lengthUsrDef = strlen(buf1);
	}
	else{
		lengthUsrDef = 0;
	}
	
	flag = checkIncomingFlag();			
	if (flag != NOTHINGSET){
		return flag;
	}

	
	if(timeOut != 0){ // user gives predefined time
		unsigned long stop = msCounter + timeOut;
		do{
			p = getChar();
			while (p != 0x0){
				
				posUsrDef = (p == buf1[posUsrDef]) ? posUsrDef + 1 : 0;
				if (lengthUsrDef > 0 && posUsrDef == lengthUsrDef){ // user defined
					rcvFlag1.userDefinedReceived = true;
					return USERDEFINEDRECEIVED;
				}
				p = getChar();
			}
		}while (msCounter < stop); } else if(timeOut == 0){ // if no time out set, used in the app to continuously check for incoming data p = getChar(); while (p != 0){ //writeI2cDisplay(p, DATAREGISTER); posGetRcvd = (p == getReceived[posGetRcvd]) ? posGetRcvd + 1 : 0; posUsrDef = (p == buf1[posUsrDef]) ? posUsrDef + 1 : 0; posBtn1On = (p == btn1RcvdOn[posBtn1On]) ? posBtn1On + 1 : 0; posBtn1Off = (p == btn1RcvdOff[posBtn1Off]) ? posBtn1Off + 1 : 0; posBtn2On = (p == btn2RcvdOn[posBtn2On]) ? posBtn2On + 1 : 0; posBtn2Off = (p == btn2RcvdOff[posBtn2Off]) ? posBtn2Off + 1 : 0; posBtn3On = (p == btn3RcvdOn[posBtn3On]) ? posBtn3On + 1 : 0; posBtn3Off = (p == btn3RcvdOff[posBtn3Off]) ? posBtn3Off + 1 : 0; posBtn4On = (p == btn4RcvdOn[posBtn4On]) ? posBtn4On + 1 : 0; posBtn4Off = (p == btn4RcvdOff[posBtn4Off]) ? posBtn4Off + 1 : 0; if (lengthGetRcvd > 0 && posGetRcvd == lengthGetRcvd){ // "GET / HTTP/1.1"
				rcvFlag1.getReceived = true;
				return 1;
			}
			if (lengthUsrDef > 0 && posUsrDef == lengthUsrDef){ // user defined
				rcvFlag1.userDefinedReceived = true;
				return 2;
			}
			if (lengthBtn1On > 0 && posBtn1On == lengthBtn1On){ // button 1 on
				rcvFlag1.btn1RcvdOn = true;
				return 3;
			}
			if (lengthBtn1Off > 0 && posBtn1Off == lengthBtn1Off){ // button 1 off
				rcvFlag1.btn1RcvdOff = true;
				return 4;
			}
			if (lengthBtn2On > 0 && posBtn2On == lengthBtn2On){ // button 2 on
				rcvFlag1.btn2RcvdOn = true;
				return 5;
			}
			if (lengthBtn2Off > 0 && posBtn2Off == lengthBtn2Off){ // button 2 off
				rcvFlag1.btn2RcvdOff = true;
				return 6;
			}
			if (lengthBtn3On > 0 && posBtn3On == lengthBtn3On){ // button 3 on
				rcvFlag1.btn3RcvdOn = true;
				return 7;
			}
			if (lengthBtn3Off > 0 && posBtn3Off == lengthBtn3Off){ // button 3 off
				rcvFlag1.btn3RcvdOff = true;
				return 8;
			}
			if (lengthBtn4On > 0 && posBtn4On == lengthBtn4On){ // button 4 on
				rcvFlag1.btn4RcvdOn = true;
				return 9;
			}
			if (lengthBtn4Off > 0 && posBtn4Off == lengthBtn4Off){ // button 4 off
				rcvFlag1.btn4RcvdOff = true;
				return 10;
			}
			
			p = getChar();
		}
	}
		
	return NOTHINGSET;
}

/****************************************************************************
	checkIncomingFlag, check the current status of what message is received at last
****************************************************************************/
uint8_t checkIncomingFlag(){
	
	
	if(rcvFlag1.getReceived == true){
		return GETRECEIVED;
	}
	else if(rcvFlag1.userDefinedReceived == true){
		return USERDEFINEDRECEIVED;
	}
	
	else if(rcvFlag1.btn1RcvdOn == true){
		return BTN1RCVDON;
	}
	
	else if(rcvFlag1.btn1RcvdOff == true){
		return BTN1RCVDOFF;
	}
	
	else if(rcvFlag1.btn2RcvdOn == true){
		return BTN2RCVDON;
	}
	
	else if(rcvFlag1.btn2RcvdOff == true){
		return BTN2RCVDOFF;
	}
	
	else if(rcvFlag1.btn3RcvdOn == true){
		return BTN3RCVDON;
	}
	
	else if(rcvFlag1.btn3RcvdOff == true){
		return BTN3RCVDOFF;
	}
	
	else if(rcvFlag1.btn4RcvdOn == true){
		return BTN4RCVDON;
	}
	
	else if(rcvFlag1.btn4RcvdOff == true){
		return BTN4RCVDOFF;
	}
	else{
		return NOTHINGSET;
	}
}

/****************************************************************************
	resetIncoming, reset all flags to prepare for the next expected data from the esp8266
****************************************************************************/

void resetIncoming(){
	
	
	rcvFlag1.getReceived = false;
	rcvFlag1.userDefinedReceived  = false;
	rcvFlag1.btn1RcvdOn = false;
	rcvFlag1.btn1RcvdOff = false;
	rcvFlag1.btn2RcvdOn = false;
	rcvFlag1.btn2RcvdOff = false;
	rcvFlag1.btn3RcvdOn = false;
	rcvFlag1.btn3RcvdOff = false;
	rcvFlag1.btn4RcvdOn = false;
	rcvFlag1.btn4RcvdOff = false;
}


/****************************************************************************
	getIP, just to know the IP as i forgot to make it static instead of dhcp...
****************************************************************************/

char* getIP(){
	
	uint8_t p;
	int posIpReceived = 0;
	char matchStaip[] = "STAIP,\"";
	int ipCharsCounter = 0;
	int staIpLength = strlen(matchStaip);
	static char tempBuffer[16];
	bool matched = false;
	
	serialWrite("AT+CIFSR", CRLF);
	_delay_ms(200); // wait for all the data from this cmd
	p = getChar();
	while (p != 0x0){
		
		if(p == matchStaip[posIpReceived]){
			posIpReceived++;
		}
		else{
			posIpReceived = 0;
		}
		
		if (staIpLength > 0 && posIpReceived == staIpLength){ // STAIP received
			matched = true;
		}
		
		if(matched == true){
			tempBuffer[ipCharsCounter] = p;
			ipCharsCounter++;
			if (ipCharsCounter > 16){
				ipCharsCounter = 0;
				matched = false;
				flushBuffer();
				return tempBuffer;
			}
			
		}
		p = getChar();
	}
	
	return NULL;
	
}

application1.c

/*
 * application1.c
 *
 * Created: 8-2-2018 15:07:22
 * Author: HdH
 * Description: webserver controlling 4 outputs.
 */

#define F_CPU 16000000UL // 16 MHz clock speed for delay.h
#define PIN_LED1 10
#define PIN_LED2 9
#define PIN_LED3 6
#define PIN_LED4 8

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "gpiolib.h"
#include "i2cdisplay.h"
#include "uart.h"
#include "esp8266.h"


void sendPage(void);

/****************************************************************************
	Bit and byte definitions
****************************************************************************/

const char htmlHeader[] PROGMEM = "HTTP/1.1 200 OK\r\nContent-Location: /buttons.html\r\nContent-Type: text/html\r\n";
const char htmlPart1[] PROGMEM = "<!DOCTYPE HTML><html><head><title>hdh</title></head><body>

<form method=\"GET\" >
"
"

 GPIO1 <input type=\"submit\" name=\"p1\" value=\"on\"><input type=\"submit\" name=\"p1\" value=\"off\">

"
"

 GPIO2 <input type=\"submit\" name=\"p2\" value=\"on\"><input type=\"submit\" name=\"p2\" value=\"off\">

"
"

 GPIO3 <input type=\"submit\" name=\"p3\" value=\"on\"><input type=\"submit\" name=\"p3\" value=\"off\">

"
"

 GPIO4 <input type=\"submit\" name=\"p4\" value=\"on\"><input type=\"submit\" name=\"p4\" value=\"off\">

"
"</form>


</body></html>\r\n";


extern int alarmCode;


void Application1(void){

uint8_t p;

	p = readInComing(0, NULL); // Checking incoming uart data
	switch (p){
		
		case GETRECEIVED:
			writeI2cDisplayLine("Get received",0,1);
			sendPage();
			break;
		case BTN1RCVDON:
			writeI2cDisplayLine("Button1 on pressed",0,1);
			writePin(PIN_LED1, HIGH);
			sendPage();
			break;
		case BTN1RCVDOFF:
			writeI2cDisplayLine("Button1 off pressed",0,1);
			writePin(PIN_LED1, LOW);
			sendPage();
			break;
		case BTN2RCVDON:
			writeI2cDisplayLine("Button2 on pressed",0,1);
			writePin(PIN_LED2, HIGH);
			sendPage();
			break;
		case BTN2RCVDOFF:
			writeI2cDisplayLine("Button2 off pressed",0,1);
			writePin(PIN_LED2, LOW);
			sendPage();
			break;
		case BTN3RCVDON:
			writeI2cDisplayLine("Button3 on pressed",0,1);
			writePin(PIN_LED3, HIGH);
			sendPage();
			break;
		case BTN3RCVDOFF:
			writeI2cDisplayLine("Button3 off pressed",0,1);
			writePin(PIN_LED3, LOW);
			sendPage();
			break;
		case BTN4RCVDON:
			writeI2cDisplayLine("Button4 on pressed",0,1);
			writePin(PIN_LED4, HIGH);
			sendPage();
			break;
		case BTN4RCVDOFF:
			writeI2cDisplayLine("Button4 off pressed",0,1);
			writePin(PIN_LED4, LOW);
			sendPage();
			break;
			
		default:
			break;
	}
}

void sendPage(){

	flushBuffer();
	resetIncoming();
	
	esp8266_send(htmlHeader);
	esp8266_send(htmlPart1);
	
	serialWrite("AT+CIPCLOSE=5", CRLF);
	
}

uart.h

/*
 * uart.h
 *
 * Created: 8-12-2017 13:36:22
 *  Author: acer
 */


#ifndef UART_H_
#define UART_H_

/****************************************************************************
	Bit and byte definitions
****************************************************************************/
#define CLOCKSPEED_CPU   16000000
#define BAUD    57600
#define BRC     ((CLOCKSPEED_CPU/16/BAUD) - 1)
#define TX_BUFFER_SIZE  40
#define RX_BUFFER_SIZE  300
#define CR 1
#define LF 2
#define CRLF 3
#define NO_EOL 4



/****************************************************************************
	Function definitions
****************************************************************************/

void UART_Init();
void serialWrite(char c[], uint8_t eol);
void UART_Transmit(unsigned char data );
uint8_t getChar(void);
void flushBuffer(void);


#endif /* UART_H_ */

uart.c

/*
 * uart.c
 *
 * Created: 8-12-2017 13:36:09
 *  Author: acer
 */ 

#define F_CPU 16000000UL // 16 MHz clock speed


#include <avr/io.h>
#include <stdlib.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>
#include <stdbool.h>
#include "uart.h"


/****************************************************************************
	Bit and byte definitions
****************************************************************************/
char rxBuffer[RX_BUFFER_SIZE];
uint8_t rxWritePos = 0;
uint8_t rxReadPos = 0;
bool bufferOverFlow = false;
long bufferOverFlowWaitTime;


/****************************************************************************
	USART_Init, see important info below
****************************************************************************/
void UART_Init(){

	UBRR0H = (unsigned char)(BRC >> 8); //baud rate register set to 9600 BAUD
	UBRR0L = (unsigned char) BRC;

	UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1 << RXCIE0);
	UCSR0C = (1 << UCSZ01) | (3<<UCSZ00);
}


/****************************************************************************
	serialWrite, send data with optional crlf
****************************************************************************/
void serialWrite(char c[], uint8_t eol)
{
	int i; //was uint8
	
	//_delay_us(100);
	for(i = 0; i < strlen(c); i++)// put the string you want to write in a buffer. { UART_Transmit(c[i]); //put the char's one by one in the buffer } if (eol == CR){ UART_Transmit(0xd); //CR \r } else if (eol == LF){ UART_Transmit(0xa); //LF \n } else if (eol == CRLF){ UART_Transmit(0xd); //CR \r UART_Transmit(0xa); //LF \n } else if (eol == NO_EOL){ return; } } /**************************************************************************** ISR(USART_RX_vect), Fills up a buffer, to keep the buffer not to big I used a buffer overflow var in case the buffer exceeds. Internet explorer sends almost 500 bytes. the buffer overflow gets a reset by the flushbuffer function. ****************************************************************************/ ISR(USART_RX_vect) { if(rxWritePos >= RX_BUFFER_SIZE - 1){
		bufferOverFlow = true;
	}
	if (bufferOverFlow == false){
		rxBuffer[rxWritePos+1] = 0x0;
		rxBuffer[rxWritePos] = UDR0;//
		rxWritePos++;
	}
}

/****************************************************************************
	USART_Transmit, 
****************************************************************************/
void UART_Transmit(unsigned char data ){
	
	while( !( UCSR0A & (1<<UDRE0)) ); // Wait for empty transmit buffer
	UDR0 = data; // Put data into buffer, sends the data
}


/****************************************************************************
	getChar, 
****************************************************************************/


uint8_t getChar(void)
{
	uint8_t ret = 0x0;  // '\0';
	
	_delay_us(500); // to prevent reading faster then the bytes are coming in
	if(rxWritePos != 0){
		ret = rxBuffer[rxReadPos];
		rxReadPos++;
		if(ret == 0){ // last byte read, start from the beginning
			flushBuffer();
		}
	}
	return ret;
}


void flushBuffer(void){
	
	for(int i=0; i<RX_BUFFER_SIZE; i++){
		rxBuffer[i] = 0;
	}
	rxWritePos = 0;
	rxReadPos = 0;
	bufferOverFlow = false;
}

 

Even a simple webpage uses relatively a lot of memory when using a microcontroller. To get a better user interface I will make an android application to control the microcontroller instead of a web browser in a later blog. So stay tuned and thanks for reading!