playing arround with the ESP8266, I set up a small web-server on an chipkit UNO32.
Why UNO32. So it's more powerfull than the Arduino (80MHz, more RAM etc) at nearly the same price.
However, the development suit for the UNO32 based on the Arduino stuff.
I decided to implement a Finite State Machine (FSM).
The program has three parts:
Block #01: Reading from the ESP via serial.
Block #02: Check for key words and set the state.
Block #03: The FMS
My ESP comes with the firmware 0018000902-AI03.
There are some differences to the 0018000902 (e.g. AT+RST has response "[System ready .....]", AI03 sends "ready" at the end).
So, if you have 0018000902 you have tho change the keywords in #02.
To have a first use case, I connected a Ultrasonic ranging module HC-SR04 to measure the distance to an object.
There are a lot of descriptions of the modul available, so I will not explain it here. Just search for HC-SR04.
The hardware connections are as follows:
UNO32 ESP
pin0/RX TX
pin1/TX RX
GND GND
3,3V 3,3V
pin3 CH_PD
UNO32 RS232
pin6/soft RX Terminal/TX
pin7/soft TX Terminal/RX
UNO32 HC-SR04
pin12 HC-SR04 Trigger
pin13 HC-SR04 Echo
GND HC-SR04 GND
+5V HC-SR04 Vcc
// *** ESP8266 with UNO32 and ultrasonic ranging modul - small web server
// For ESP Firmware 0018000902-AI03
// Server with state machine due to AT commands
// (c) THI 2015-02-01
// Program has three blocks:
// #01 receiving data from ESP serial
// #02 evaluating data comming from ESP and setting states
// #03 FSM to step through the commands you want to send and wait for right response (#01 will be still executed)
// and send feedback e.g. to "GET / "
// Known bugs ore incomplete stuff:
// (1) +CIPCLOSE will not be executed correctly with 0018000902-AI03, sometimes ESP reboots,
// 00180001902 seems to be more stable. But I didn't explore this further.
// (2) Handling for multible connctions not propperly implemented.
// (3) Buffer overflow is not handled.
// (4) Getting a request from a browser (e.g. /favicon.ico) is not handled.
// At the moment, connection will be closed without sending 404 or something else.
#include <SoftwareSerial.h>
// Hardware settings for ESP8266
#define rxPin 6
#define txPin 7
SoftwareSerial dbgTerminal(rxPin, txPin); // RX, TX
HardwareSerial & espSerial = Serial;
// set pin numbers:
// the number of the LED pin
const int ledPin = 43;
const int ESP8266_CHPD = 3;
// End of hardware settings
// Settings for SR04
// Digital 12 -> HC-SR04 Trigger
// Digital 13 -> HC-SR04 Echo
// GND -> HC-SR04 GND
// +5V -> HC-SR04 Vcc
int pingPin = 12; // Digital 13 -> HC-SR04 Trigger
int inPin = 13; // Digital 12 -> HC-SR04 Echo
// timeout couter
long lmillis;
long loldmillis;
int iticks = 0;
// Variables will change:
int ledState = HIGH; // ledState used to set the LED
// vars for loop
int bLen = 0;
int idatastop = 0;
char c;
char charVal[2];
// definition of states for #01 and #02
#define LINENOTCOMPLETE 0
#define LINEAVAILABLE 1
#define LINEPROCESSED 2
byte readstate = LINENOTCOMPLETE;
int iLinkCounter = 0;
// States for state machine #02
enum R_State {R_Data, R_OK, R_DATAOK, R_Link, R_Unlink, R_nochange, R_busy, R_ERROR, R_ready, R_none, R_processed, R_linkisnot};
enum W_State {W_WaitForDataOK, W_none};
// current state of state machine #03
// starting with 0
// Initial state can not be set here -> using the struct outside of a function fails (-> namespace)
int oldbState = 255;
typedef struct {
int bState; // Status for switch/case #03
int oldbState;
R_State rState; // Receive status für #02
W_State wState; // WaitState in case of data received, now wait for appropriate OK in #02 and #03
int ch_id; // current channel id of data received
} Statusstruct;
// Stack for multible connections / messages received
Statusstruct S_Stack[3];
int pPointerS = 0;
// my network parameters
String NetworkSSID = "yourSSID";
String NetworkPASS = "yourPASS";
#define BUFFER_SIZE 512
// buffer for AT communication
char buffer[BUFFER_SIZE];
// buffer for received data via +IPD
char databuffer[BUFFER_SIZE];
void setup() {
pinMode(rxPin, INPUT);
pinMode(txPin, OUTPUT);
pinMode(ledPin, OUTPUT);
pinMode(ESP8266_CHPD, OUTPUT);
dbgTerminal.begin(19200); // Serial monitor
espSerial.begin(9600); // ESP8266
// Chip wake up
digitalWrite(ESP8266_CHPD, HIGH); // set CH_PD HIGH
delay(200);
//while (!dbgTerminal) {
// ; // wait for serial port to connect. Needed for Leonardo only
//}
dbgTerminal.println("ESP8266 Server.");
//hardReset();
//delay(2000);
clearSerialBuffer();
// Initialize status stack adn first state
S_Stack[0].bState = 0;
S_Stack[0].oldbState = 255;
S_Stack[0].rState = R_none; // Receive status für #02
S_Stack[0].wState = W_none; // WaitState in case of data received, now wait for appropriate OK in #02 and #03
S_Stack[0].ch_id = 0; // current channel id of data received
// digitalWrite(ledPin,ledState);
}
// main loop
void loop() {
int ch_id, packet_len;
char *pb;
// #01: read from ESP
if (espSerial.available() >0 ) {
c = espSerial.read();
buffer[bLen] = c;
// sprintf(charVal, "%02X", buffer[bLen]);
// dbgTerminal.print(charVal);
bLen++;
// Read until 0x0A is received
// todo: avoid buffer overflow
if ((c == 0x0A))
{
buffer[bLen] = 0;
dbgTerminal.print(buffer);
idatastop = bLen;
bLen = 0;
readstate = LINEAVAILABLE;
}
}
// #02: check for keyword received
if (readstate == LINEAVAILABLE) {
char* cchecked;
int imessLen, ireadLen;
int idatawrite = 0;
if(cchecked = strstr(buffer, "+IPD,")) {
// "+IPD," -----------------------------------------------------
// request: +IPD,ch,len:data
sscanf(cchecked+5, "%d,%d", &ch_id, &packet_len);
// S_Stack[pPointerS].ch_id = ch_id;
// read rest of message
// calculate length of rest
cchecked = strstr(buffer, ":");
cchecked++;
imessLen = strlen(cchecked);
ireadLen = packet_len - imessLen;
// copy data into data buffer
while (idatawrite < imessLen) {
databuffer[idatawrite] = cchecked[idatawrite];
idatawrite++;
}
// read rest of data received
// todo: avoid buffer overflow
while (ireadLen > 0) {
if (espSerial.available() >0 ) {
c = espSerial.read();
databuffer[idatawrite] = c;
ireadLen--;
idatawrite++;
}
}
databuffer[idatawrite] = 0;
dbgTerminal.println("--- start of data received ---");
// dbgTerminal.print(buffer);
dbgTerminal.print(databuffer);
dbgTerminal.println("--- end of data received ---");
// Increment status stack if we are not waiting for data (state 170)
// this means that the ESP got one more connection
if ( S_Stack[pPointerS].bState != 170) {
pPointerS++;
S_Stack[pPointerS].oldbState = S_Stack[pPointerS-1].bState;
S_Stack[pPointerS].rState = R_Data;
S_Stack[pPointerS].wState = W_none;
S_Stack[pPointerS].ch_id = ch_id;
S_Stack[pPointerS].bState = 170;
} else {
S_Stack[pPointerS].rState = R_Data;
S_Stack[pPointerS].ch_id = ch_id;
}
dbgTerminal.print("SPointer: ");
dbgTerminal.println(pPointerS);
} else if ((cchecked = strstr(buffer, "OK")) && (strlen(buffer) == 4)) {
// "OK" ------------------------------------------------------
if (S_Stack[pPointerS].wState == W_WaitForDataOK)
S_Stack[pPointerS].rState = R_DATAOK; // marks if OK comes from received data via session
else
S_Stack[pPointerS].rState = R_OK;
} else if ((cchecked = strstr(buffer, "SEND OK")) && (strlen(buffer) == 9)) {
// "OK" ------------------------------------------------------
S_Stack[pPointerS].rState = R_OK;
} else if ((cchecked = strstr(buffer, "link is not")) && (strlen(buffer) == 13)) {
// "OK" ------------------------------------------------------
S_Stack[pPointerS].rState = R_linkisnot;
} else if (cchecked = strstr(buffer, "Link")) {
// "Link" ------------------------------------------------------
S_Stack[pPointerS].rState = R_Link;
iLinkCounter++;
} else if (cchecked = strstr(buffer, "Unlink")) {
// "Unlink" ------------------------------------------------------
S_Stack[pPointerS].rState = R_Unlink;
iLinkCounter--;
} else if (cchecked = strstr(buffer, "ERROR")) {
// "ERROR" ------------------------------------------------------
S_Stack[pPointerS].rState = R_ERROR;
} else if (cchecked = strstr(buffer, "ready")) {
// "ready" ------------------------------------------------------
S_Stack[pPointerS].rState = R_ready;
// S_Stack[pPointerS].bState = 1;
} else if (cchecked = strstr(buffer, "busy")) {
// "busy" ------------------------------------------------------
S_Stack[pPointerS].rState = R_busy;
} else if (cchecked = strstr(buffer, "no change")) {
// "no change" ------------------------------------------------------
S_Stack[pPointerS].rState = R_nochange;
} else {
S_Stack[pPointerS].rState = R_none;
}
readstate = LINEPROCESSED;
};
// #03: state machine
lmillis = millis();
if (loldmillis > lmillis) { // overflow
loldmillis = lmillis;
}
if (lmillis > loldmillis + 1000) {
iticks++;
loldmillis = lmillis;
digitalWrite(ledPin, ledState); // show lifesign
ledState = !ledState;
}
if (oldbState != S_Stack[pPointerS].bState) {
dbgTerminal.print("new state: ");
dbgTerminal.println((int) S_Stack[pPointerS].bState);
oldbState = S_Stack[pPointerS].bState;
// prepare timeout
iticks = 0;;
}
switch (S_Stack[pPointerS].bState) {
// panic
case 0:
espSerial.println("AT+RST");
S_Stack[pPointerS].bState = 1;
S_Stack[pPointerS].rState = R_processed;
break;
// wait for response
case 1:
if (S_Stack[pPointerS].rState == R_ready) {
dbgTerminal.println("Reset done");
S_Stack[pPointerS].bState = 10;
S_Stack[pPointerS].rState = R_none;
} // todo: case of error e.g. goto state 0
break;
// show version info
case 10:
espSerial.println("AT+GMR");
S_Stack[pPointerS].bState = 11;
S_Stack[pPointerS].rState = R_processed;
break;
// wait for response
case 11:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 12;
S_Stack[pPointerS].rState = R_none;
} // todo: case of error e.g. goto state 0
break;
// allow multible connections, must be set before start of server
case 12:
espSerial.println("AT+CIPMUX=1");
S_Stack[pPointerS].bState = 13;
S_Stack[pPointerS].rState = R_processed;
break;
// wait for response
case 13:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 14;
S_Stack[pPointerS].rState = R_none;
} // todo: case of error e.g. goto state 0
break;
// activate client mode, muist be set before start of server
case 14:
espSerial.println("AT+CWMODE=1");
S_Stack[pPointerS].bState = 15;
S_Stack[pPointerS].rState = R_processed;
break;
// wait for response
case 15:
if ((S_Stack[pPointerS].rState == R_OK) || (S_Stack[pPointerS].rState == R_nochange)){
S_Stack[pPointerS].bState = 90;
S_Stack[pPointerS].rState = R_none;
} // todo: case of error e.g. goto state 0
break;
// connect to router
case 90:
if (S_Stack[pPointerS].rState == R_none) {
String cmd = "AT+CWJAP=\"";
cmd += NetworkSSID;
cmd += "\",\"";
cmd += NetworkPASS;
cmd += "\"";
// cmd = "AT+CWJAP";
espSerial.println(cmd);
S_Stack[pPointerS].bState = 91;
S_Stack[pPointerS].rState = R_processed;
}
break;
// wait for response
case 91:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 100;
S_Stack[pPointerS].rState = R_processed;
} // todo: case of error e.g. goto state 0
break;
// send AT - just for fun
case 100:
if (S_Stack[pPointerS].rState == R_processed) {
espSerial.println("AT");
S_Stack[pPointerS].bState = 101;
S_Stack[pPointerS].rState = R_processed;
}
break;
// wait for response
case 101:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 110;
S_Stack[pPointerS].rState = R_processed;
} // todo: case of error e.g. goto state 0
break;
// ask for baud rate
case 110:
if ((S_Stack[pPointerS].rState == R_processed) || (S_Stack[pPointerS].rState == R_none)) {
espSerial.println("AT+CIOBAUD?");
S_Stack[pPointerS].bState = 111;
S_Stack[pPointerS].rState = R_processed;
}
break;
// wait for response
case 111:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 140;
S_Stack[pPointerS].rState = R_processed;
} // todo: case of error e.g. goto state 0
break;
// not used
case 120:
break;
// wait for response
case 121:
break;
// not used
case 130:
break;
// wait for response
case 131:
break;
case 140:
// start server
if ((S_Stack[pPointerS].rState == R_processed) || (S_Stack[pPointerS].rState == R_none)) {
espSerial.println("AT+CIPSERVER=1,8888");
S_Stack[pPointerS].bState = 141;
S_Stack[pPointerS].rState = R_processed;
}
break;
// wait for response
case 141:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 150;
S_Stack[pPointerS].rState = R_processed;
} // todo: case of error e.g. goto state 0
break;
case 150:
// set timeout for server
if ((S_Stack[pPointerS].rState == R_processed) || (S_Stack[pPointerS].rState == R_none)) {
espSerial.println("AT+CIPSTO=15");
S_Stack[pPointerS].bState = 151;
S_Stack[pPointerS].rState = R_processed;
}
break;
// wait for response
case 151:
if (S_Stack[pPointerS].rState == R_OK) {
S_Stack[pPointerS].bState = 160;
S_Stack[pPointerS].rState = R_processed;
} // todo: case of error e.g. goto state 0
break;
case 160:
// request ip address
if ((S_Stack[pPointerS].rState == R_processed) || (S_Stack[pPointerS].rState == R_none)) {
espSerial.println("AT+CIFSR");
S_Stack[pPointerS].bState = 161;
S_Stack[pPointerS].rState = R_processed;
}
break;
// wait for response
case 161:
if ((S_Stack[pPointerS].rState == R_OK)|| (iticks > 10)) {
S_Stack[pPointerS].bState = 170;
S_Stack[pPointerS].rState = R_processed;
dbgTerminal.println("Server online");
} // todo: case of error e.g. goto state 0
break;
case 170:
// server ready - waiting for requests
if (S_Stack[pPointerS].rState == R_Data) {
// read data from request
// todo: request is complete if empty line has been received
if (strstr(databuffer,"GET / ")) {
// change state if "GET / " request has been detected
S_Stack[pPointerS].bState = 171;
} else {
// request is not valid - wait for end of request - then close session
S_Stack[pPointerS].bState = 175;
}
S_Stack[pPointerS].rState = R_processed;
S_Stack[pPointerS].wState = W_WaitForDataOK;
}
break;
// wait for response
case 171:
if ((S_Stack[pPointerS].rState == R_DATAOK) || (iticks > 10)) {
S_Stack[pPointerS].bState = 172;
S_Stack[pPointerS].rState = R_processed;
S_Stack[pPointerS].wState = W_none;
// if we have received an intermediate data packet recover status stack
if (pPointerS > 0)
pPointerS--;
} // todo: case of error e.g. goto state 0
break;
case 172:
// send data
if (S_Stack[pPointerS].rState == R_processed) {
String Header, Content;
gethomepage(&Header, &Content);
// todo: request is complete if empty line has been received
espSerial.print("AT+CIPSEND=");
espSerial.print(S_Stack[pPointerS].ch_id);
espSerial.print(",");
espSerial.println(Header.length()+Content.length());
if (espSerial.find(">")) {
// todo: if we send more bytes than buffer length, then we have to read from ESP
// because data sent will be echoed to buffer via serial
espSerial.print(Header);
espSerial.print(Content);
S_Stack[pPointerS].bState = 173;
S_Stack[pPointerS].rState = R_processed;
}
}
break;
// wait for response
case 173:
if ((S_Stack[pPointerS].rState == R_OK) || (iticks > 10)){
S_Stack[pPointerS].bState = 170;
S_Stack[pPointerS].rState = R_processed;
} // todo: case of error e.g. goto state 0
break;
// wait for response but then close session because request will not be handled
case 175:
if ((S_Stack[pPointerS].rState == R_DATAOK) || (iticks > 10)){
S_Stack[pPointerS].bState = 180;
S_Stack[pPointerS].rState = R_processed;
S_Stack[pPointerS].wState = W_none;
// if (pPointerS > 0)
// pPointerS--;
} // todo: case of error e.g. goto state 0
break;
// close session
// todo: until now, not used, because ESP crashes if our response contain "Connection: close"
// and we want to close the session. Firefox closes then automatically.
case 180:
if (S_Stack[pPointerS].rState == R_processed) {
S_Stack[pPointerS].bState = 181;
S_Stack[pPointerS].rState = R_processed;
espSerial.print("AT+CIPCLOSE="); // close all - but it is still unstable
// espSerial.println("AT+CIPSTATUS");
espSerial.println(S_Stack[pPointerS].ch_id);
// dbgTerminal.println("Command finished");
} // todo: case of error e.g. goto state 0
break;
// wait for response
case 181:
if ((S_Stack[pPointerS].rState == R_OK) || (S_Stack[pPointerS].rState == R_linkisnot) || (iticks > 10)) {
S_Stack[pPointerS].bState = 170;
S_Stack[pPointerS].rState = R_processed;
// if we have received an intermediate data packet recover status stack
if (pPointerS > 0)
pPointerS--;
} // todo: case of error e.g. goto state 0
break;
default:
;
} // end of switch #03
} // end of loop()
// create response for valid request
void gethomepage(String* Header, String* Content) {
char outputString[25]; // for result of supersonic modul
readdistance(outputString);
*Header = "HTTP/1.1 200 OK\r\n";
*Header += "Content-Type: text/html\r\n";
*Header += "Connection: close\r\n";
//Header += "Refresh: 5\r\n";
*Content = "<html>\r\n<body>\r\n";
*Content += "Distance:\r\n";
// *Content += String(ledState);
*Content += outputString;
*Content += "\r\n</body>\r\n</html>\r\n";
*Header += "Content-Length: ";
*Header += (int)((*Content).length());
*Header += "\r\n\r\n";
}
// create response for not valid request
void geterrorresponse(String* Header, String* Content) {
*Header = "HTTP/1.1 404 Not Found\r\n";
*Header += "Content-Type: text/html\r\n";
*Header += "Connection: close\r\n";
//Header += "Refresh: 5\r\n";
*Content = "<html>\r\n<body>\r\n";
*Content += "\r\n</body>\r\n</html>\r\n";
*Header += "Content-Length: ";
*Header += (int)((*Content).length());
*Header += "\r\n\r\n";
}
// hardware reset of ESP
boolean hardReset() {
String tmpData;
digitalWrite(ESP8266_CHPD,LOW);
delay(100);
digitalWrite(ESP8266_CHPD,HIGH);
delay(1000);
while ( espSerial.available() > 0 ) {
char c = espSerial.read();
tmpData +=c;
espSerial.write(c);
if ( tmpData.indexOf("ready") > -1 ) {
clearBuffer();
return 1;
}
}
}
// clear serial from ESP
void clearSerialBuffer(void) {
while ( espSerial.available() > 0 ) {
espSerial.read();
}
}
// clear serial buffer
void clearBuffer(void) {
for (int i =0;i<BUFFER_SIZE;i++ ) {
buffer[i]=0;
}
}
void readdistance(char sResult[]) {
// establish variables for duration of the ping,
// and the distance result in inches and centimeters:
long duration;
long inches, cm;
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(10);
digitalWrite(pingPin, LOW);
// The same pin is used to read the signal from the PING))): a HIGH
// pulse whose duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
pinMode(inPin, INPUT);
duration = pulseIn(inPin, HIGH);
// convert the time into a distance
inches = microsecondsToInches(duration);
cm = microsecondsToCentimeters(duration);
// Serial.println(cm, DEC);
sprintf(sResult,"%04i cm",cm);
}
long microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
return microseconds / 74 / 2;
}
long microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}
Attention:
Before using HardwareSerial.find of the UNO32 you have to patch
the pic32 library code pic32/cores/pic32/Stream.cpp as follows:
// find returns true if the target string is found
bool Stream::find(char *target)
{
// THI return findUntil(target, NULL);
return findUntil(target, strlen(target), NULL, 0);
}
May be next steps are
- reconnect if connection to network has been interrupted
- FSM driven by a table
- handle other HTTP-requests (e.g. 404)
So, have fun playing with it and feel free to make changes.