In this post we will continue and hopefully end writing a basic ESP8266 (based on ESP-12) web interface. If you haven’t read first part of this post, I suggest doing it here. If you have, then let’s proceed with the second part.
So, what we are left to do is to basically write all server-side code which is a firmware for ESP8266.
In the first part, I have talked how to create HTML files and write some code. That code was mainly HTML with some CSS and JavaScript. As I have mentioned, JavaScript part will not work until we write the firmware and host HTML code on the ESP.
In this part, we are going to write ESP8266 firmware with C++ using some Arduino libraries. I usually write code with Visual Studio Code and the firmware for the ESP is going to be written with VS Code and PlatformIO. How to set up it on your PC you can read here.
After writing and uploading the firmware to the ESP, the project is going to be finished with fully working WEB interface which could be reached through your web browser.
Hardware Connection
The connection from the ESP board to the sensor is simple:
The ESP development board connect to the SI7006 sensor with four wire. Two of them are power connection (+3.3V and GND). Other two are I2C data lines. GPIO5 (D1) connects to sensor’s SCL line and GPIO4 (D2) – to SDA line. Both, SDA and SCL, are pulled up with 4.7 kΩ resistors. Note, that some boards have different pin markings (other than D1 and D2).
PlatformIO Project’s Files (Structure)
When you create a new PlatformIO project, you automagically get default file structure. Most important folders are ‘include’, ‘lib’ and ‘source’. Also, you will notice folder ‘HTML sketches’, where all HTML files created in Part 1 are.
The folders
Folder ‘include’ contains header files used for saving HTML content to the ESP’s flash memory. Such approach was chosen to keep the HTML code as clean as possible with easy access to it. Because HTML files need to be converted to Strings, every header file defines those String which are written to the flash. When you access a web-page on the ESP it uses those Strings as a response to your web request, instead of using plain HTML files. It might be possible to use HTML files that we created in the first part, but then you would need some kind of file structure on ESP flash with a possibility to access individual files. As I said, it is simpler just to convert HTML content to a String and return it when ESP receives web page request from a client.
Folder ‘lib’ contains custom libraries. In this case it will have OTA and Si7006 library files. About library files I will talk later in this post.
Folder ‘src’ contains main.cpp file which is our main program code.
The .ini file
Also, you should keep in mind that there is ‘platformio.ini’ file in the root of project’s folder. In this file you will find some settings from which most important are the upload_port. This line sets up the port which is used for firmware uploading to the ESP. It can be set up to COMx, where x is you COM port’s number, or it can be an IP address of your ESP8266 device. You can also delete upload_port setting, then PlatformIO will try to automatically find used COM port for ESP firmware’s upload.
Libraries and additional files
Sensor’s Si7006 library
This library is used for communication with a Si7006 chip. It is a custom written library which mainly defines most of the Si7006 registers which are used for setting up the IC or getting information from it.
OTA library
Folder ‘OTA’ contains files which are modification of the default Arduino Ota library. It was mode in such way that it would be easy to access needed functions from within main code without having a lot of OTA code laying around in main.cpp file.
Included html header files
Folder ‘include’ contains files which end with extension ‘.h’. These files are rewritten ESP8266 basic web interface’s .html files. So, for example, ‘clear.h’ is equivalent of ‘clear.html’. You will notice that the ‘.h’ files are a bit different, as they are written in C++ which contains html content as a String variable located on the internal flash memory. Note, that contents from ‘form.html’ are written to ‘setup.h’ file (different naming). Also, there is an additional file called ‘setting.h’ which does not have its .html equivalent but is really just a text box like reset.h or reboot.h files.
Main Code
The file ‘main.cpp’ contains main program code. There are several distinct parts of it: global variables, helper functions, a setup and a loop function.
Variables
After some include lines and function prototypes, you will find these variables:
const char* ssid = "MC_S1"; //AP SSID
const char* passphrase = "12345678"; //AP Password
String wifiScanResult = "";
double currentTemperature = 0;
double currentRH = 0;
int measureInterval = 20 ; //*100 ms;
int timer1 = 0;
ESP8266WebServer server(80);
Ssid
is a String defining SSID name when ESP acts as Access Point and passphrase
is a password for that Access Point. String wifiScanResult
holds all available SSID’s which ESP can be connected to. Variables currentTemperature
and currentRH
holds temperature and relative humidity readings respectively. Integer measureInterval
sets how frequent temperature and humidity measurements should be (2 seconds between reading in this example). The variable timer1
is used as a simple incremental timer which is reset when it is equal to measureInterval
value. Finally, variable server
is an object needed for a web server functionality.
Additional functions
There are some helper functions in the ‘main.cpp’ file. Firstly, there are initial startup functions – used to connect to a known Wi-Fi or create ESP’s own AP.
bool testWifi(void) {
int c = 0;
while ( c < 20 ) {
if (WiFi.status() == WL_CONNECTED) { return true; }
delay(500);
c++;
}
return false;
}
Function testWifi()
is used to check if the ESP is connected to a router. The Wi-Fi check can take up to 10 seconds to return the result.
Function setupAP()
is used for setting up ESP’s own Access Point:
void setupAP(void) {
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
int n = WiFi.scanNetworks();
String SSISstr = "\"SSID\":[";
String RSSIstr = "\"RSSI\":[";
String Protected = "\"Protected\":[";
for (int i = 0; i < n; ++i)
{
if(i != 0) {
RSSIstr += ", ";
SSISstr += ", ";
Protected += ", ";
}
RSSIstr += (WiFi.RSSI(i));
SSISstr += "\""+ WiFi.SSID(i) + "\"";
Protected += (WiFi.encryptionType(i) == ENC_TYPE_NONE)?"0":"1";
}
RSSIstr += "]";
SSISstr += "]";
Protected += "]";
wifiScanResult = "{ " + RSSIstr +", "+ SSISstr + ", " + Protected +" }";
delay(100);
WiFi.softAP(ssid, passphrase, 6, 0);
launchWeb(1);
}
Firstly, this function sets the ESP into a station mode, disconnects from all Wi-Fi’s and then checks for available SSIDs. Then it creates three strings with all SSID names (SSISstr), signal strengths (RSSIstr) and if Wi-Fi is protected or not (Protected). Finally, it creates JSON string with all received data (SSISstr, RSSIstr, Protected), starts its own AP and launches web server trough launchWeb(1)
. Function launchWeb
is used for creating needed web-server with particular URLs and starting it:
void launchWeb(int webtype) {
createWebServer(webtype);
server.begin();
}
Function createWebServer sets accessible URLs on the web-server:
void createWebServer(int webtype)
{
if ( webtype == 1 ) {
server.on("/", handleSetup);
server.on("/wifissids", handleWifiSsids);
server.on("/direct", handleRoot);
server.on("/reset", handleReset);
server.on("/reboot", handleReboot);
server.on("/clearmem", handleClearMem);
server.on("/setting", handleSetting);
server.on("/data", handleData);
} else if (webtype == 0) {
server.on("/", handleRoot);
server.on("/reset", handleReset);
server.on("/clearmem", handleClearMem);
server.on("/reboot", handleReboot);
server.on("/data", handleData);
}
}
Here the variable webtype
sets whether ESP works in AP mode with main setup page or it works in normal mode with root page being general sensor’s information (temperature and humidity). Line server.on("/", handleSetup)
sets root directory (“/”) and wen you go to device’s IP address, server runs handleSetup()
function. Line server.on("/wifissids", handleWifiSsids)
sets IPaddress/wifissids URL and when you visit it, server runs handleWifiSsids()
function. Other lines works similarly, each sets up which function needs to be ran when you visit a particular URL.
Function init_first_boot()
reads from eeprom (actually emulated on flash memory) saved Wi-Fi SSID and password. Then, if the ESP successfully connects to the saved Wi-Fi, it starts a web server in normal mode. If the ESP cannot connect to the router, then it starts its own AP by calling function setupAP()
:
void init_first_boot()
{
EEPROM.begin(512);
delay(10);
String esid;
for (int i = 0; i < 32; ++i)
{
esid += char(EEPROM.read(i));
}
String epass = "";
for (int i = 32; i < 96; ++i)
{
epass += char(EEPROM.read(i));
}
if ( esid.length() > 1 ) {
WiFi.mode(WIFI_STA);
delay(200);
WiFi.begin(esid.c_str(), epass.c_str());
if (testWifi()) {
launchWeb(0);
return;
}
}
if (!testWifi()) {
setupAP();
}
}
Web routines
For each URL there is a function, which does something when a client visits that URL. In main.cpp you will find part where all these functions or routines are located.
Function handleSetting()
runs when a client visits IP/setting page:
void handleSetting(){
String qsid = server.arg("ssid");
String qpass = server.arg("pass");
if (qsid.length() > 0 && qpass.length() > 0) {
for (int i = 0; i < 96; ++i) { EEPROM.write(i, 0); }
for (int i = 0; i < qsid.length(); ++i){
EEPROM.write(i, qsid[i]);
}
for (int i = 0; i < qpass.length(); ++i){
EEPROM.write(32+i, qpass[i]);
}
EEPROM.commit();
String s = SETTING_page;
server.send(200, "text/html", s);
} else {
server.send(404, "application/json", "{\"Error\":\"404 not found\"}");
}
delay(2000);
pinMode(16, OUTPUT);
digitalWrite(16, LOW);
}
This function writes SSID and password to the eeprom (flash), returns to the client a web content (‘SETTING_page’ which is located in ‘setting.h’ file). Then it delays for two seconds and restarts an ESP8266 by pulling low 16th pin. It is also possible to do a software reset, but in my experience it doesn’t always work as intended. So, if you have 16th pin connected to Chip’s EN pin, you can always do a reset by pulling it low.
Other ‘handle’ functions are similar to the handleSetting()
function. Each of them runs when a client connects to the particular URL and sends back data – either html string or JSON data. Let’s have a look to one example, when a function returns JSON data:
void handleData(){
server.send(200, "text/json", "{\"temp\":" + String(currentTemperature,1) + ",\"rh\":" + String(currentRH,1) + "}");
}
The function handleData()
runs when someone visits ‘ESP-IP-ADDRESS/data’ URL. Although it is possible for a user to see JSON data in the web browser, this URL is used for other purpose. When a user opens root directory (goes to ESP’s IP address) and sees temperature and RH readings, every two seconds that opened page accesses temperature and RH data trough IP/data URL. So, the main page is always updated in the background without any need to refresh the page.
Setup
As this project uses Arduino framework, it has default functions Setup()
and Loop()
.
The setup function which runs once when ESP boots up looks like this:
void setup()
{
Serial.begin(9600);
init_first_boot();
OtaSetup();
delay(500);
Wire.begin(4,5); // (sda,scl)
Wire.setClock(100000);
server.begin();
currentRH = TemperatureHumiditySensor.measureRelativeHumidity();
currentTemperature = TemperatureHumiditySensor.readTemperature();
}
The line Serial.begin(9600)
starts a Serial connection, although it is not used a lot (if at all). Then it runs init_first_boot()
which starts either AP or Normal connection to a router. OtaSetup()
sets up Arduino OTA server for receiving firmware trough Wi-Fi connection – it is useful feature as you don’t need to connect a cable every time when you want to update the firmware. Wire.begin
and Wire.setClock
sets up I2C connection to the Si7006 sensor.
The last two lines just reads current temperature and relative humidity values to the variables.
Loop
Function loop()
runs constantly (unless an interrupt occurs):
void loop()
{
OtaHandleRequests();
server.handleClient();
delay(100);
timer1++;
if(timer1 >= measureInterval)
{
timer1 = 0;
currentRH = TemperatureHumiditySensor.measureRelativeHumidity();
currentTemperature = TemperatureHumiditySensor.readTemperature();
}
}
OtaHandleRequests(
, as the name suggests, handles over the air firmware updates. The function server.handleClient()
handles client connections and responds to web requests. If
statement checks if timer1 has reached measureInterval
value and if it has, then ESP reads temperature and humidity values from the sensor.
Summary
To sum up, after writing the and building the code (or just downloading and building it), we have fully working web interface for a temperature-humidity sensor. Although in this example Si7006 sensor was used, but it is possible to run this project with other sensors (with simple code modifications). This interface is a good base for building more complex devices based on ESP8266 IC.
Note: if you are using PlatformIO with ESP8266 platform version 2.2.2, this project although builds successfully, but behaves somehow erratically (it fails to load web interface on clients request). I have tested it with ESP8266 platform version 1.8.0 and it works as intended, so I suggest installing this version or just keep in mind that there might be some problems with other versions.
Links
You can find all the files at my GitHub Repository.
Post’s first part: here