HTML class Attribute


The HTML class attribute is used to specify a class for an HTML element.

Multiple HTML elements can share the same class.


Using The class Attribute

The class attribute is often used to point to a class name in a style sheet. It can also be used by a JavaScript to access and manipulate elements with the specific class name.

In the following example we have three <div> elements with a class attribute with the value of "city". All of the three <div> elements will be styled equally according to the .city style definition in the head section:

Example

<!DOCTYPE html>
<html>
<head>
<style>
.city {
  background-color: tomato;
  color: white;
  border: 2px solid black;
  margin: 20px;
  padding: 20px;
}
</style>
</head>
<body>

<div class="city">
  <h2>London</h2>
  <p>London is the capital of England.</p>
</div>

<div class="city">
  <h2>Paris</h2>
  <p>Paris is the capital of France.</p>
</div>

<div class="city">
  <h2>Tokyo</h2>
  <p>Tokyo is the capital of Japan.</p>
</div>

</body>
</html>

In the following example we have two <span> elements with a class attribute with the value of "note". Both <span> elements will be styled equally according to the .note style definition in the head section:

Example

<!DOCTYPE html>
<html>
<head>
<style>
.note {
  font-size: 120%;
  color: red;
}
</style>
</head>
<body>

<h1>My <span class="note">Important</span> Heading</h1>
<p>This is some <span class="note">important</span> text.</p>

</body>
</html>

Tip: The class attribute can be used on any HTML element.

Note: The class name is case sensitive!

Tip: You can learn much more about CSS in our .



The Syntax For Class

To create a class; write a period (.) character, followed by a class name. Then, define the CSS properties within curly braces {}:

Example

Create a class named "city":

<!DOCTYPE html>
<html>
<head>
<style>
.city {
  background-color: tomato;
  color: white;
  padding: 10px;
}
</style>
</head>
<body>

<h2 class="city">London</h2>
<p>London is the capital of England.</p>

<h2 class="city">Paris</h2>
<p>Paris is the capital of France.</p>

<h2 class="city">Tokyo</h2>
<p>Tokyo is the capital of Japan.</p>

</body>
</html>

Multiple Classes

HTML elements can belong to more than one class.

To define multiple classes, separate the class names with a space, e.g. <div class="city main">. The element will be styled according to all the classes specified.

In the following example, the first <h2> element belongs to both the city class and also to the main class, and will get the CSS styles from both of the classes: 

Example

<h2 class="city main">London</h2>
<h2 class="city">Paris</h2>
<h2 class="city">Tokyo</h2>

Different Elements Can Share Same Class

Different HTML elements can point to the same class name.

In the following example, both <h2> and <p> point to the "city" class and will share the same style:

Example

<h2 class="city">Paris</h2>
<p class="city">Paris is the capital of France</p>

Use of The class Attribute in JavaScript

The class name can also be used by JavaScript to perform certain tasks for specific elements.

JavaScript can access elements with a specific class name with the getElementsByClassName() method:

Example

Click on a button to hide all elements with the class name "city":

<script>
function myFunction() {
  var x = document.getElementsByClassName("city");
  for (var i = 0; i < x.length; i++) {
    x[i].style.display = "none";
  }
}
</script>

Don't worry if you don't understand the code in the example above.

You will learn more about JavaScript in our chapter, or you can study our .


Chapter Summary

  • The HTML class attribute specifies one or more class names for an element
  • Classes are used by CSS and JavaScript to select and access specific elements
  • The class attribute can be used on any HTML element
  • The class name is case sensitive
  • Different HTML elements can point to the same class name
  • JavaScript can access elements with a specific class name with the getElementsByClassName() method

In recent years, the Internet of Things (IoT) has become a hot topic in the tech industry. It has altered the way we work. Physical objects and the digital world are more intertwined than ever before.

With this in mind, in 2014, Espressif Systems released the incredibly tiny and inexpensive WiFi-enabled microcontroller – The ESP8266. It costs less than $3 and can monitor and control things from anywhere in the world, making it ideal for almost any IoT application.

Introducing ESP8266

The ESP8266 is a low-cost WiFi-enabled microcontroller, created and developed by Espressif Systems, a Shanghai-based Chinese company. The following features are the primary reasons why people like the ESP8266:

dualcore ico

Powerful Processor

The ESP8266 is equipped with a Tensilica Xtensa® 32-bit LX106 RISC microprocessor that operates at an adjustable clock frequency of 80 to 160 MHz and supports RTOS.

power ico

Ultra-Low Power Consumption

With its multiple power modes and dynamic power scaling, ESP8266 achieves ultra-low power consumption, making it ideal for use in mobile devices, wearable electronics and IoT applications.

wifi ico

Wi-Fi Capabilities

The ESP8266 incorporates an 802.11b/g/n HT40 Wi-Fi transceiver, allowing it to connect to a Wi-Fi network to access the internet (Station mode) or to create its own Wi-Fi wireless network (Soft access point mode) to which other devices can connect.

peripherals ico

Rich set of Peripherals

The ESP8266 includes a plethora of built-in peripherals such as ADCs, UART, SPI, I2C, I2S, PWM, and much more.

ways ico

Many Development Platforms

There are several development platforms available for programming the ESP8266. You can use the Arduino IDE, MicroPython, Espruino, Espressif SDK, or one of the platforms listed on WikiPedia.

lowcost ico

Low Cost

It is one of the cheap microcontrollers, with prices beginning at $3; this makes them accessible to a wide audience.

ESP8266 Development Boards

To get started with the ESP8266, you’ll need a good development board that includes all the extras. There are numerous varieties of development boards out there. While they all function similarly, some boards may be better suited to specific projects than others.

The following are just a few of the many different types of ESP8266 boards you can try out.

esp8266 nodemcu

ESP8266 NodeMCU

This is one of the most popular ESP8266 development boards. It has 4MB of flash memory, 11 GPIO pins, and one ADC pin with 10-bit resolution. This is what we use more often in our Wi-Fi and IoT projects. It is extremely versatile and perfect for beginners.

esp8266 wemos d1 mini

WeMos D1 Mini

Compared to the previous board, it has a smaller form factor and is more affordable, making it an excellent option for a more cost-efficient project. In addition, a variety of shields are available for the D1 mini board, allowing you to build small, simple setups with minimal wiring.

esp8266 esp01

ESP-01

This one is small and fits into any enclosure, making it ideal for finished projects. It has 1MB of flash memory and four GPIOs for controlling and connecting peripherals. If your projects require more peripherals, use one of the previous boards.

ESP8266 NodeMCU Hardware Overview

When it comes to ESP8266 development boards, the NodeMCU is by far the most widely used option. This is what we will use for our experiments. Don’t worry if you have a different board; the information on this page should still be useful.

Let’s take a deep dive into the hardware overview of the ESP8266 NodeMCU.

ESP-12E Module

This development board is equipped with the ESP-12E module, which contains the Tensilica Xtensa® 32-bit LX106 RISC that operates at an adjustable clock frequency of 80 to 160 MHz and supports RTOS.

ESP-12E Chip
  • Tensilica Xtensa® 32-bit LX106
  • 80 to 160 MHz Clock Freq.
  • 128kB internal RAM
  • 4MB external flash
  • 802.11b/g/n Wi-Fi transceiver
esp8266 nodemcu hardware specifications esp 12e chip

There’s also 128 KB RAM, and 4 MB of Flash memory (for program and data storage), which is sufficient to handle the long strings that make up web pages or JSON/XML data.

The ESP8266 incorporates an 802.11b/g/n HT40 Wi-Fi transceiver, allowing it to connect to a Wi-Fi network to access the internet (Station mode) or to create its own Wi-Fi wireless network (Soft access point mode) to which other devices can connect.

Power

Because the ESP8266’s operating voltage range is 2.5V to 3.6V, the board includes an LDO voltage regulator to keep the voltage stable at 3.3V. It can reliably provide up to 600 mA, which should be sufficient even when the ESP8266 is drawing its maximum current (80 mA) during RF transmissions.

Power Requirement
  • Operating Voltage: 2.5V to 3.6V
  • On-board 3.3V 600mA regulator
  • 80mA Operating Current
  • 20 µA during Sleep Mode
esp8266 nodemcu hardware specifications power supply

The output of the 3.3V regulator is broken out to the header pin labeled 3V3. This pin can be used to power external circuitry.

The ESP8266 NodeMCU is typically powered by the on-board MicroB USB connector. Alternatively, if you have a regulated 5V power supply, you can use the VIN pin to power the ESP8266 and its peripherals directly.

Peripherals and I/O

The ESP8266 NodeMCU has a total of 17 GPIO pins that are broken out to pin headers. These pins can be assigned a variety of peripheral duties, including:

  • 1 ADC channel – 1 channel of 10-bit precision SAR ADC
  • 2 UART interface – 2 UART interfaces with with support for flow control
  • 4 PWM outputs – 4 PWM pins to control things like motor speed or LED brightness.
  • SPI and I2C interface – Two SPI and one I2C interfaces for connecting various sensors and peripherals.
  • I2S interface – One I2S interface for adding sound to your project.
Multiplexed I/Os
  • 1 ADC channels
  • 2 UART interfaces
  • 4 PWM outputs
  • SPI, I2C & I2S interface
esp8266 nodemcu hardware specifications multiplexed gpio pins

Thanks to the ESP8266’s pin multiplexing feature, which allows multiple peripherals to share a single GPIO pin. For example, a single GPIO pin can perform functions such as I2C, I2S, UART, PWM, and IR Remote Control, etc.

On-board Switches & LED Indicator

There are two buttons on the ESP8266 NodeMCU. The Reset button, labeled RST, is used to reset the ESP8266 chip. The other button is the FLASH button, which is used to upgrade firmware.

Switches & Indicators
  • RST – Reset the ESP8266 chip
  • FLASH – Download new programs
  • Blue LED – User Programmable
esp8266 nodemcu hardware specifications reset flash buttons led indicators

The board also includes a user-programmable LED that is connected to the D0 pin.

Serial Communication

The board includes Silicon Labs‘ CP2102 USB-to-UART Bridge Controller, which converts USB signals to serial and allows you to program the ESP8266 chip.

Serial Communication
  • CP2102 USB-to-UART converter
  • 4.5 Mbps communication speed
  • Flow Control support
esp8266 nodemcu hardware specifications cp2102 usb to ttl converter

ESP8266 NodeMCU Pinout

The ESP8266 NodeMCU has 30 pins in total. For convenience, pins with similar functionality are grouped together. The pinout is as follows:

esp8266 pinout nodemcu

Power Pins There are four power pins, one VIN and three 3.3V. The VIN pin can be used to directly power the ESP8266 and its peripherals, if you have a regulated 5V power supply. The 3.3V pin is the output from the on-board voltage regulator; you can get up to 600mA from it.

GND is the ground pin.

GPIO Pins The ESP8266 NodeMCU has 17 GPIO pins that can be assigned different functions by programming the appropriate registers. Each GPIO pin can be configured with internal pull-up or pull-down, or set to high impedance. When configured as an input, it can also be set to edge-trigger or level-trigger to generate CPU interrupts.

ADC Channel The ESP8266 includes a 10-bit precision SAR ADC. ADC can be used to perform two measurements: testing the power supply voltage of the VDD3P3 pin and testing the input voltage of the TOUT pin. They cannot, however, be implemented concurrently.

SPI Pins ESP8266 has two SPIs (SPI, and HSPI) in slave and master modes. These SPIs also support the general-purpose SPI features listed below:

  • 4 timing modes of the SPI format transfer
  • Up to 80 MHz and the divided clocks of 80 MHz
  • Up to 64-Byte FIFO

I2C Pins The ESP8266 doesn’t have hardware I2C pins, but it can be done by “bitbanging.” It works quite well, and the ESP8266 is fast enough to match “Arduino level” speed. By default, GPIO4 (SDA) and GPIO5 (SCL) are used as I2C pins to make it easier for people using existing Arduino code, libraries, and sketches. However, you can use any other two GPIO pins as I2C pins by calling wire.begin(SDA, SCL) in the Arduino IDE.

UART Pins The ESP8266 has two UART interfaces, UART0 and UART2, that support asynchronous communication (RS232 and RS485) at up to 4.5 Mbps. UART0 (TXD0, RXD0, RST0, and CTS0 pins) is used for communication, whereas UART1 (TXD1 pin) only features a data transmit signal and is typically used for printing logs.

~ PWM Pins Pulse width modulation (PWM) output can be implemented programmatically on all GPIO pins from GPIO0 to GPIO15. On the ESP8266, the PWM signal has a 10-bit resolution, and the PWM frequency range is adjustable between 1000 μs and 10000 μs, i.e., between 100 Hz and 1 kHz.

SDIO Pins The ESP8266 has one slave SDIO (Secure Digital Input/Output Interface) for connecting SD cards. SDIO v1.1 (4-bit, 25 MHz) and SDIO v2.0 (4-bit, 50 MHz) are supported.

Control Pins are used to control the ESP8266. These pins are the EN pin, the RST pin, the FLASH pin, and the WAKE pin.

  • The EN (also known as CH_PD or Chip Power Down) pin is used to enable the ESP8266. When pulled HIGH, the chip is enabled; when pulled LOW, the chip operates at low power.
  • The RST pin is the reset pin and it is normally held high. It can be used to reset the ESP8266 system by momentarily pulling it low. It is equivalent to pressing the on-board RST button.
  • The FLASH pin is used by the ESP8266 to determine when to boot into the bootloader. If the pin is held low during power-up it will start bootloading! It is equivalent to pressing the on-board FLASH button.
  • The WAKE pin is used to wake the ESP8266 from deep sleep.

ESP8266 Development Platforms

There are several development platforms available for programming the ESP8266.

You can go with Espruino (JavaScript SDK and firmware closely emulating Node.js), Mongoose OS (an operating system for IoT devices that is recommended by Espressif Systems and Google Cloud IoT), MicroPython (implementation of Python 3 for microcontrollers), an SDK provided by Espressif, or one of the platforms listed on WiKiPedia.

Fortunately, the amazing ESP8266 community recently took the IDE selection a step further by creating an Arduino add-on. If you’re just getting started with the ESP8266, we recommend starting with this environment, which we’ll cover in this tutorial. For more information, visit the ESP8266 Arduino GitHub repository.

Installing the ESP8266 Core

The first step in installing the ESP8266 Arduino core is to have the latest Arduino IDE (Arduino 1.8.5 or higher) installed on your computer. If you haven’t already, we recommend that you do so right away.

Latest Arduino IDE

Then, launch the Arduino IDE and navigate to File > Preferences.

arduino ide preferences

Fill in the “Additional Board Manager URLs” field with the following.

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Then, click the “OK” button.

arduino ide esp8266 json url

Now navigate to Tools > Board > Boards Manager…

arduino ide boards manager

Filter your search by entering ‘esp8266‘. Look for ESP8266 by ESP8266 Community. Click on that entry, and then choose Install.

arduino ide esp8266 package

After installing, restart your Arduino IDE and navigate to Tools > Board to ensure you have ESP8266 boards available.

arduino ide boards manager esp8266 option

Now select your board in the Tools > Board menu (in our case, it’s the NodeMCU 1.0 (ESP-12E Module)). If you are unsure which board you have, select the Generic ESP8266 Module.

arduino ide boards manager esp8266 boards

Finally, connect the ESP8266 NodeMCU to your computer and select the Port (if the COM port does not appear in your Arduino IDE, you will need to install the CP210x USB to UART Bridge VCP Drivers):

arduino ide esp8266 port selection

That’s it! You can now begin writing code for your ESP8266 in the Arduino IDE.

You should make sure you always have the most recent version of the ESP8266 Arduino core installed.

Simply navigate to Tools > Board > Boards Manager, search for ESP8266, and verify the version you have installed. If a newer version is available, you should install it.

ESP8266 Example Sketches

The ESP8266 Arduino core includes several example sketches. To access the example sketches, navigate to File > Examples > ESP8266.

You will see a selection of example sketches. You can choose any of them to load the sketch into your IDE and start experimenting with it.

arduino ide esp8266 package examples

To ensure that everything is properly configured, we’ll upload the most basic sketch of all – Blink!

Navigate to File > Examples > ESP8266, and open the Blink sketch.

esp8266 blink sketch

This sketch uses the on-board LED that most ESP8266 development boards have. This LED is typically connected to digital pin D0, and its number may vary from board to board.

Warning:

Failing to select the appropriate board, COM port, and Upload speed may result in the espcomm_upload_mem error during the upload of new sketches.

void setup() {
	pinMode(D0, OUTPUT);
}

void loop() {
	digitalWrite(D0, HIGH);
	delay(500);
	digitalWrite(D0, LOW);
	delay(500);
}

When the code is uploaded, the LED will begin to blink. To get your ESP8266 to run the sketch, you may need to press the RST button.

ESP8266 NodeMCU Blink Sketch Working Arduino IDE

There are several development platforms available for programming the ESP8266. You can go with:

  • Arduino IDE – intended for those who are familiar with Arduino
  • Espruino – JavaScript SDK and firmware closely emulating Node.js
  • Mongoose OS – An operating system for IoT devices that is recommended by Espressif Systems and Google Cloud IoT
  • MicroPython – Implementation of Python 3 for microcontrollers
  • SDK provided by Espressif – Official SDK to take advantage of all ESP8266 features

When compared to other platforms, the Arduino IDE is the most user-friendly for beginners. While it may not be the ideal platform for working with the ESP8266, it is a program that most people are already familiar with, which makes getting started much easier.

Before you can use the Arduino IDE to program the ESP8266, you must first install the ESP8266 board (also known as the ESP8266 Arduino Core) via the Arduino Board Manager. This guide will walk you through the process of downloading, installing, and testing the ESP8266 Arduino Core.

What is a Core?

The cores are required to make new microcontrollers compatible with your Arduino IDE as well as existing sketches and libraries. Arduino develops the cores for the microcontrollers (Atmel AVR MCUs) used on their boards, but anyone can develop a core for their own boards as long as they follow the rules and requirements set by Arduino.

Some development boards require the installation of an additional core; therefore, Arduino developed the Boards Manager as a tool to add cores to the Arduino IDE.

For more information on how to use the Arduino IDE Boards Manager, check out their tutorial.

Step 1: Installing or Updating the Arduino IDE

The first step in installing the ESP8266 Arduino core is to have the latest version of the Arduino IDE installed on your computer. If you haven’t already, we recommend that you do so right away.

Latest Arduino IDE

Step 2: Installing the USB-to-Serial Bridge Driver

There are numerous ESP8266-based development boards available. Depending on the design, you may need to install additional drivers for your USB-to-serial converter before you are able to upload code to your ESP8266.

For example, the ESP8266 NodeMCU uses the CP2102 to convert USB signals to UART signals, whereas the WeMos D1 Mini uses the CH340G. The ESP-01, on the other hand, lacks an onboard USB-to-serial converter and requires a separate module.

cp2102 on esp8266 nodemcu
ch340g on wemos d1 mini

Make sure to inspect your board carefully to identify the USB-to-serial converter that is present. You’ll probably have either CP2102 or CH340 populated on the board.

If you’ve never installed drivers for these USB-to-serial converters on your computer before, you should do so right now.

CP210x USB to UART Bridge VCP Drivers

CH340 drivers

Step 3: Installing the ESP8266 Arduino Core

Launch the Arduino IDE and navigate to File > Preferences.

Arduino IDE Preferences

Fill in the “Additional Board Manager URLs” field with the following.

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Then, click the “OK” button.

Arduino IDE ESP8266 Json URL

Now navigate to Tools > Board > Boards Manager…

Arduino IDE Boards Manager

Filter your search by entering ‘esp8266‘. Look for ESP8266 by ESP8266 Community. Click on that entry, and then choose Install.

Arduino IDE ESP8266 Package

Step 4: Selecting the Board and Port

After installing the ESP8266 Arduino Core, restart your Arduino IDE and navigate to Tools > Board to ensure you have ESP8266 boards available.

Arduino IDE Boards Manager ESP8266 Option

Now select your board in the Tools > Board menu (in our case, it’s the NodeMCU 1.0 (ESP-12E Module)). If you are unsure which board you have, select the Generic ESP8266 Module.

Arduino IDE Boards Manager ESP8266 Boards

Finally, connect the ESP8266 NodeMCU to your computer and select the Port.

Arduino IDE ESP8266 Port Selection

That’s it! You can now begin writing code for your ESP8266 in the Arduino IDE.

You should make sure you always have the most recent version of the ESP8266 Arduino core installed.

Simply navigate to Tools > Board > Boards Manager, search for ESP8266, and verify the version you have installed. If a newer version is available, you should install it.

Step 5: Testing the Installation

Once you’ve finished the preceding steps, you are ready to test your first program with your ESP8266! Launch the Arduino IDE. If you disconnected your board, plug it back in.

The ESP8266 Arduino core includes several examples that demonstrate everything from scanning for nearby networks to building a web server. To access the example sketches, navigate to File > Examples > ESP8266.

You will see a selection of example sketches. You can choose any of them to load the sketch into your IDE and begin experimenting.

Arduino IDE ESP8266 Package Examples

Let’s upload the most basic sketch of all – Blink! Navigate to File > Examples > ESP8266, and open the Blink sketch.

ESP8266 Blink Sketch

This sketch uses the on-board LED that most ESP8266 development boards have. This LED is typically connected to digital pin D0, and its number may vary from board to board.

void setup() {
	pinMode(D0, OUTPUT);
}

void loop() {
	digitalWrite(D0, HIGH);
	delay(500);
	digitalWrite(D0, LOW);
	delay(500);
}

If everything worked, the on-board LED on your ESP8266 should now be blinking! To execute the sketch, you may need to press the RST button on your ESP8266.

ESP8266 NodeMCU Blink Sketch Working Arduino IDE

Congratulations! You have just programmed your first ESP8266!

ESP8266 Example: WiFi Scan

Let’s try to run another ESP8266 example sketch, which demonstrates how to use the ESP8266WiFi library to scan nearby WiFi networks and print the results.

You can find this example under File > Examples > ESP8266WiFi > WiFiScan.

ESP8266 WiFi Scan Sketch

Load the WiFiScan sketch from the example sketches into your Arduino IDE.

#include <ESP8266WiFi.h>

void setup() {
  Serial.begin(115200);
  Serial.println(F("\nESP8266 WiFi scan example"));

  // Set WiFi to station mode
  WiFi.mode(WIFI_STA);

  // Disconnect from an AP if it was previously connected
  WiFi.disconnect();
  delay(100);
}

void loop() {
  String ssid;
  int32_t rssi;
  uint8_t encryptionType;
  uint8_t* bssid;
  int32_t channel;
  bool hidden;
  int scanResult;

  Serial.println(F("Starting WiFi scan..."));

  scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true);

  if (scanResult == 0) {
    Serial.println(F("No networks found"));
  } else if (scanResult > 0) {
    Serial.printf(PSTR("%d networks found:\n"), scanResult);

    // Print unsorted scan results
    for (int8_t i = 0; i < scanResult; i++) {
      WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);

      Serial.printf(PSTR("  %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %c %s\n"),
                    i,
                    channel,
                    bssid[0], bssid[1], bssid[2],
                    bssid[3], bssid[4], bssid[5],
                    rssi,
                    (encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
                    hidden ? 'H' : 'V',
                    ssid.c_str());
      delay(0);
    }
  } else {
    Serial.printf(PSTR("WiFi scan error %d"), scanResult);
  }

  // Wait a bit before scanning again
  delay(5000);
}

Once you have uploaded the sketch, open the serial monitor at baud rate 115200 and press the RST button on the ESP8266. You should see the SSID, RSSI, WiFi channel, and encryption for each discovered network.

ESP8266 WiFi Scan Sketch Output

One of the nice things about the ESP8266 is that it has a fair amount of GPIO pins to work with. You won’t have to juggle or multiplex your IO pins. However, there are a few things to keep in mind, so please read the pinout carefully.

Note:

Please note that the following pinout reference is for the popular 30-pin ESP8266 NodeMCU development board.

esp8266 nodemcu dev board.webp

Not every ESP8266 development board exposes every pin, but each pin works exactly the same no matter which development board you use.

ESP8266 Peripherals and I/O

The ESP8266 NodeMCU has 17 GPIO pins in total, which are broken out to the pin headers on both sides of the development board. These pins can be assigned a variety of peripheral duties, including:

1 ADC channel1 channel of 10-bit precision SAR ADC
2 UART interfaces2 UART interfaces with with support for flow control
4 PWM outputs4 PWM pins to control things like motor speed or LED brightness
2 SPI and 1 I2C interfacesTwo SPI and one I2C interfaces for connecting various sensors and peripherals
I2S interfaceOne I2S interface for adding sound to your project

Thanks to the ESP8266’s pin multiplexing feature, which allows multiple peripherals to share a single GPIO pin. Meaning, a single GPIO pin can perform functions such as I2C, I2S, UART, and PWM, etc.

For extensive information about the ESP8266, please refer to the datasheet.

ESP8266 Pinout

The ESP8266 NodeMCU has 30 pins in total. For convenience, pins with similar functionality are grouped together. The pinout is as follows:

ESP8266 Pinout NodeMCU

Let’s take a closer look at the ESP8266 pins and their functions one by one.

ESP8266 GPIO Pins

The ESP8266 NodeMCU has 17 GPIO pins that can be assigned different functions by programming the appropriate registers. Each GPIO can be configured with internal pull-up or pull-down, or set to high impedance.

ESP8266 GPIO Pins

Which ESP8266 GPIOs are safe to use?

Although the ESP8266 has a lot of pins with various functions, some of them may not be suitable for your projects. The table below shows which pins are safe to use and which pins should be used with caution.

  • – Your top priority pins. They are perfectly safe to use.
  • – Pay close attention because their behavior, particularly during boot, can be unpredictable. Use them only when absolutely necessary.
  • – It is recommended that you avoid using these pins.
  Label    GPIO    Safe to use?  Reason
D0GPIO16HIGH at boot, used to wake up from deep sleep
D1GPIO5
D2GPIO4
D3GPIO0connected to FLASH button, boot fails if pulled LOW
D4GPIO2HIGH at boot, boot fails if pulled LOW
D5GPIO14
D6GPIO12
D7GPIO13
D8GPIO15Required for boot, boot fails if pulled HIGH
RXGPIO3Rx pin, used for flashing and debugging
TXGPIO1Tx pin, used for flashing and debugging
CLKGPIO6Connected to Flash memory
SDOGPIO7Connected to Flash memory
CMDGPIO11Connected to Flash memory
SD1GPIO8Connected to Flash memory
SD2GPIO9Connected to Flash memory
SD3GPIO10Connected to Flash memory
A0ADC0Analog input pin, cannot be configured as output

The image below shows which GPIO pins can be used safely.

ESP8266 GPIO Pins that are Safe to Use

ESP8266 ADC Pins

The ESP8266 is embedded with a 10-bit precision SAR ADC, which means it can detect 1024 (2^10) discrete analog levels. In other words, it will convert input voltages ranging from 0 to 3.3V (operating voltage) into integer values ranging from 0 to 1024. This results in a resolution of 3.3 volts / 1024 units, or 0.0032 volts (3.2 mV) per unit.

ESP8266 ADC Pins

The following two measurements can be implemented using ADC. However, they cannot be implemented at the same time.

  • Measure the power supply voltage of VDD3P3 (Pin3 and Pin4).
  • Measure the input voltage of A0.

ESP8266 SPI Pins

ESP8266 features two SPIs (SPI, and HSPI) in slave and master modes. These SPIs also support the general-purpose SPI features listed below:

  • 4 timing modes of the SPI format transfer
  • Up to 80 MHz and the divided clocks of 80 MHz
  • Up to 64-Byte FIFO
ESP8266 SPI Pins

It is possible to use SPI on any pins by ‘bitbanging’.

ESP8266 I2C Pins

The ESP8266 doesn’t have hardware I2C pins, but it can be done by ‘bitbanging’. It works quite well, and the ESP8266 is fast enough to match ‘Arduino level’ speed.

By default, GPIO4 (SDA) and GPIO5 (SCL) are used as I2C pins to make it easier for people using existing Arduino code, libraries, and sketches.

However, you can use any other two GPIO pins as I2C pins by calling wire.begin(SDA, SCL) in the Arduino IDE.

ESP8266 I2C Pins

ESP8266 UART Pins

The ESP8266 has two UART interfaces, UART0 and UART2, that support asynchronous communication (RS232 and RS485) at up to 4.5 Mbps.

  • UART0 (TXD0, RXD0, RST0, and CTS0 pins) is used for communication.
  • UART1 (TXD1 pin) only features a data transmit signal and is typically used for printing logs.
ESP8266 UART Pins

RXD0 and TXD0 are the serial control and bootloading pins. They are primarily used for communicating with the ESP module.

Therefore, you should use caution when using these since they are connected through to the USB-to-serial converter and will therefore receive USB traffic.

ESP8266 PWM Pins

All of the ESP8266’s GPIO pins, from GPIO0 to GPIO15, can be programmed to generate pulse width modulated (PWM) outputs.

ESP8266 PWM Pins

On the ESP8266, the PWM signal has a 10-bit resolution, and the PWM frequency range is adjustable between 1000 μs and 10000 μs, i.e., between 100 Hz and 1 kHz.

ESP8266 SDIO Pins

The ESP8266 has one slave SDIO (Secure Digital Input/Output Interface) for connecting SD cards. SDIO v1.1 (4-bit 25 MHz) and SDIO v2.0 (4-bit 50 MHz) are supported.

ESP8266 SDIO Pins

ESP8266 Power Pins

The VIN pin can be used to directly power the ESP8266 and its peripherals, if you have a regulated 5V power supply.

The 3V3 pin is the output from the on-board voltage regulator; you can get up to 600mA from it.

GND is the ground pin.

ESP8266 Power Pins

ESP8266 Interrupt Pins

All GPIOs can be configured as interrupts, except GPIO16.

ESP8266 Control Pins

The EN (a.k.a. CH_PD or Chip Power Down) pin is the enable pin for the ESP8266, pulled high by default. When pulled HIGH, the chip is enabled; when pulled LOW, the chip is disabled.

The RST pin is the reset pin for the ESP8266, pulled high by default. When pulled down to ground momentarily it will reset the ESP8266. It is equivalent to pressing the on-board RST button.

The FLASH pin is used by the ESP8266 to determine when to boot into the bootloader. If the pin is held low during power-up it will start bootloading! It is equivalent to pressing the on-board FLASH button.

The WAKE pin is used to wake the ESP8266 from deep sleep.

ESP8266 Control Pins

In recent years, the ESP8266 has risen to prominence in IoT and WiFi-related projects. It’s a low-cost WiFi module that, with a little extra effort, can be programmed to run a standalone web server. What a cool thing!

What exactly is a Web server and how does it work?

A web server is a place where web pages are stored, processed, and served to web clients. A web client is just a web browser that we use on our computers and phones. A web client and a web server communicate using a special protocol known as Hypertext Transfer Protocol (HTTP).

HTTP Web Server Client Illustration

In this protocol, a client starts a conversation by sending an HTTP request for a specific web page. The server then sends back the content of that web page or an error message if it can’t find it (like the famous 404 Error).

ESP8266 Operating Modes

One of the most useful features of the ESP8266 is its ability to not only connect to an existing WiFi network and act as a Web Server, but also to create its own network, allowing other devices to connect directly to it and access web pages.

This is possible because the ESP8266 can operate in three modes: Station (STA) mode, Soft Access Point (AP) mode, and both simultaneously.

Station (STA) Mode

In Station (STA) mode, the ESP8266 connects to an existing WiFi network (the one created by your wireless router).

ESP8266 NodeMCU Web Server Station STA Mode Demonstration

In STA mode, the ESP8266 obtains an IP address from the wireless router to which it is connected. With this IP address, it can set up a web server and serve web pages to all connected devices on the existing WiFi network.

Soft Access Point (AP) Mode

In Access Point (AP) mode, the ESP8266 sets up its own WiFi network and acts as a hub (just like a WiFi router) for one or more stations.

However, unlike a WiFi router, it does not have an interface to a wired network. So, this mode of operation is called Soft Access Point (soft-AP). Also, no more than five stations can connect to it at the same time.

ESP8266 NodeMCU Web Server Soft Access Point AP Mode Demonstration

In AP mode, the ESP8266 creates a new WiFi network and assigns it an SSID (the network’s name) and an IP address. With this IP address, it can serve web pages to all connected devices.

Wiring LEDs to an ESP8266

Now that we understand the fundamentals of how a web server works and the modes in which the ESP8266 can create one, it’s time to connect some LEDs to the ESP8266 that we want to control via WiFi.

Begin by placing the NodeMCU on your breadboard, making sure that each side of the board is on a different side of the breadboard. Next, connect two LEDs to digital GPIO D6 and D7 using a 220Ω current limiting resistor.

When you are finished, you should have something that looks like the image below.

Simple ESP8266 NodeMCU Web Server Wiring Fritzing Connections with LED
Wiring LEDs to ESP8266 NodeMCU

The Idea Behind Using an ESP8266 Web Server to Control Things

So you might be wondering, “How do I control things from a web server that only processes and serves web pages?”.

It’s extremely simple. We’re going to control things by visiting a specific URL.

When you enter a URL into a web browser, it sends an HTTP request (also known as a GET request) to a web server. It is the web server’s responsibility to handle this request.

Assume you entered a URL like http://192.168.1.1/ledon into a browser. The browser then sends an HTTP request to the ESP8266. When the ESP8266 receives this request, it recognizes that the user wishes to turn on the LED. As a result, it turns on the LED and sends a dynamic webpage to a browser that displays the LED’s status as “on.” Quite simple, right?

Configuring the ESP8266 Web Server in Access Point (AP) mode

Let’s get to the interesting stuff now!

This example, as the title suggests, shows how to configure the ESP8266 Web Server in Access Point (AP) mode and serve web pages to any connected client. To begin, connect your ESP8266 to your computer and run the sketch. Then we will look at it in more detail.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

/* Put your SSID & Password */
const char* ssid = "NodeMCU";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

ESP8266WebServer server(80);

uint8_t LED1pin = D7;
bool LED1status = LOW;

uint8_t LED2pin = D6;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);
  
  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);
  
  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO7 Status: OFF | GPIO6 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO7 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO7 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO6 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO6 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #1abc9c;}\n";
  ptr +=".button-on:active {background-color: #16a085;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP8266 Web Server</h1>\n";
  ptr +="<h3>Using Access Point(AP) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server in AP mode

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP8266. If everything is fine, it will show the “HTTP server started” message.

ESP8266 NodeMCU Web Server Access Point Mode Serial Monitor Output - Server Started

Now, get a phone, laptop, or other device that can connect to a WiFi network, and look for a network called “NodeMCU”. Connect to the network using the password 12345678.

ESP8266 NodeMCU Web Server Access Point Mode - Joining Server

After connecting to your NodeMCU AP network, open a browser and navigate to 192.168.1.1. The ESP8266 should return a web page displaying the current status of the LEDs and buttons. At the same time, you can check the serial monitor to see the status of the ESP8266’s GPIO pins.

ESP8266 NodeMCU Web Server Access Point Mode - Web Page
ESP8266 NodeMCU Web Server Access Point Mode Serial Monitor Output - Webpage Accessed

Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP8266 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.

ESP8266 NodeMCU Web Server Access Point Mode Web Page - LED Control
ESP8266 NodeMCU Web Server Access Point Mode Serial Monitor Output - LED Control

You can test the LED2 button to see if it works similarly.

Let’s take a closer look at the code to see how it works so you can modify it to suit your needs.

Detailed Code Explanation

The sketch begins by including the ESP8266WiFi.h library. This library contains ESP8266-specific methods that we use to connect to the network. Following that, we include the ESP8266WebServer.h library, which contains some methods that will assist us in configuring a server and handling incoming HTTP requests without having to worry about low-level implementation details.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

Because we are configuring the ESP8266 web server in Access Point (AP) mode, it will create its own WiFi network. So, we need to set the SSID, password, IP address, IP subnet mask, and IP gateway.

/* Put your SSID & Password */
const char* ssid = "NodeMCU";  // Enter SSID here
const char* password = "12345678";  //Enter Password here

/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

Following that, we create an object of the ESP8266WebServer library so that we can access its functions. The constructor of this object accepts as a parameter the port to which the server will be listening. Since HTTP uses port 80 by default, we’ll use this value. This allows us to connect to the server without specifying the port in the URL.

// declare an object of ESP8266WebServer library
ESP8266WebServer server(80);

Next, we declare the NodeMCU’s GPIO pins to which LEDs are connected, as well as their initial state.

uint8_t LED1pin = D7;
bool LED1status = LOW;

uint8_t LED2pin = D6;
bool LED2status = LOW;

Inside Setup() Function

In the setup function, we configure our HTTP server. First, we establish a serial connection for debugging purposes and configure the GPIO pins to behave as an OUTPUT.

Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);

Then, we configure a soft access point to create a Wi-Fi network by providing an SSID, password, IP address, IP subnet mask, and IP gateway.

WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);

To handle incoming HTTP requests, we must specify which code should be executed when a specific URL is accessed. For this, we use the .on() method. This method accepts two parameters: a relative URL path and the name of the function to be executed when that URL is visited.

The first line of the code snippet below, for example, indicates that when a server receives an HTTP request on the root (/) path, it will call the handle_OnConnect() function. It is important to note that the URL specified is a relative path.

Similarly, we must specify four more URLs to handle the two states of two LEDs.

server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);

We haven’t specified what the server should serve if the client requests a URL that isn’t specified with server.on(). It should give a 404 error (Page Not Found) as a response. To accomplish this, we use the server.onNotFound() method.

server.onNotFound(handle_NotFound);

Now, to start the server, we call the server object’s begin() method.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

Actual incoming HTTP requests are handled in the loop function. For this, we use the server object’s handleClient() method. We also change the state of LEDs based on the request.

void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

Now we must write the handle_OnConnect() function, which we previously attached to the root (/) URL with server.on. We begin this function by setting the status of both LEDs to LOW (initial state of LEDs) and printing it on the serial monitor.

We use the send method to respond to an HTTP request. Although the method can be called with a number of different arguments, the simplest form requires the HTTP response code, the content type, and the content.

The first parameter we pass to the send method is the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then we specify the content type as “text/html,” and finally we pass the SendHTML() custom function, which generates a dynamic HTML page with the LED status.

void handle_OnConnect() 
{
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO7 Status: OFF | GPIO6 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

Similarly, we write five more functions to handle LED ON/OFF requests and the 404 Error page.

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO7 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO7 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO6 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO6 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

Whenever the ESP8266 web server receives a request from a web client, the sendHTML() function generates a web page. It simply concatenates HTML code into a long string and returns to the server.send() function we discussed earlier. The function uses the status of the LEDs as a parameter to generate HTML content dynamically.

The first text you should always send is the <!DOCTYPE> declaration, which indicates that we’re sending HTML code.

String SendHTML(uint8_t led1stat,uint8_t led2stat){
String ptr = "<!DOCTYPE html> <html>\n";

The <meta> viewport element makes the web page responsive, ensuring that it looks good on all devices. The title tag determines the page’s title.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>LED Control</title>\n";

Styling the Web Page

Following that, we have some CSS to style the buttons and the overall appearance of the web page. We select the Helvetica font and define the content to be displayed as an inline-block, center-aligned.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

The code that follows then sets the color, font, and margin around the body, H1, H3, and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";

The buttons are also styled with properties such as color, size, margin, and so on. The :active selector changes the look of buttons while they are being clicked.

ptr +=".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #1abc9c;}\n";
ptr +=".button-on:active {background-color: #16a085;}\n";
ptr +=".button-off {background-color: #34495e;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";

Setting the Web Page Heading

Next, the heading of the web page is set. You can change this text to anything that works for your application.

ptr +="<h1>ESP8266 Web Server</h1>\n";
ptr +="<h3>Using Access Point(AP) Mode</h3>\n";

Displaying the Buttons and Corresponding State

The if statement is used to dynamically update the status of the buttons and LEDs.

if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

Configuring the ESP8266 Web Server in WiFi Station (STA) mode

Let’s move on to the next example, which shows how to configure the ESP8266 web server in Station (STA) mode and serve web pages to any connected client on the existing network.

Before you proceed with uploading the sketch, you must make some changes to ensure that it works for you. To connect ESP8266 to an existing network, you must modify the following two variables with your network credentials.

Change SSID & Password before trying STA mode web server sketch

Once you’re done, go ahead and try out the sketch.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

ESP8266WebServer server(80);

uint8_t LED1pin = D7;
bool LED1status = LOW;

uint8_t LED2pin = D6;
bool LED2status = LOW;

void setup() {
  Serial.begin(115200);
  delay(100);
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.on("/led1on", handle_led1on);
  server.on("/led1off", handle_led1off);
  server.on("/led2on", handle_led2on);
  server.on("/led2off", handle_led2off);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
  if(LED1status)
  {digitalWrite(LED1pin, HIGH);}
  else
  {digitalWrite(LED1pin, LOW);}
  
  if(LED2status)
  {digitalWrite(LED2pin, HIGH);}
  else
  {digitalWrite(LED2pin, LOW);}
}

void handle_OnConnect() {
  LED1status = LOW;
  LED2status = LOW;
  Serial.println("GPIO7 Status: OFF | GPIO6 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,LED2status)); 
}

void handle_led1on() {
  LED1status = HIGH;
  Serial.println("GPIO7 Status: ON");
  server.send(200, "text/html", SendHTML(true,LED2status)); 
}

void handle_led1off() {
  LED1status = LOW;
  Serial.println("GPIO7 Status: OFF");
  server.send(200, "text/html", SendHTML(false,LED2status)); 
}

void handle_led2on() {
  LED2status = HIGH;
  Serial.println("GPIO6 Status: ON");
  server.send(200, "text/html", SendHTML(LED1status,true)); 
}

void handle_led2off() {
  LED2status = LOW;
  Serial.println("GPIO6 Status: OFF");
  server.send(200, "text/html", SendHTML(LED1status,false)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(uint8_t led1stat,uint8_t led2stat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>LED Control</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #1abc9c;}\n";
  ptr +=".button-on:active {background-color: #16a085;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>ESP8266 Web Server</h1>\n";
    ptr +="<h3>Using Station(STA) Mode</h3>\n";
  
   if(led1stat)
  {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
  else
  {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}

  if(led2stat)
  {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
  else
  {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}

  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server in STA mode

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP8266. If everything is fine, it will display the dynamic IP address obtained from your router as well as the “HTTP server started” message.

ESP8266 NodeMCU Web Server Station Mode Serial Monitor Output - Server Started

Next, launch a browser and navigate to the IP address displayed on the serial monitor. The NodeMCU should serve a web page with the current status of the LEDs and two buttons for controlling them. At the same time, you can check the serial monitor to see the status of the NodeMCU’s GPIO pins.

ESP8266 NodeMCU Web Server Station Mode - Web Page
ESP8266 NodeMCU Web Server Station Mode Serial Monitor Output - Webpage Accessed

Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP8266 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.

ESP8266 NodeMCU Web Server Station Mode Web Page - LED Control
ESP8266 NodeMCU Web Server Station Mode Serial Monitor Output - LED Control

You can test the LED2 button to see if it works similarly.

Let’s take a closer look at the code to see how it works so you can modify it to suit your needs.

Code Explanation

The only difference between this code and the previous code is that we are not creating our own WiFi network but rather joining an existing network using the WiFi.begin() function.

//connect to your local wi-fi network
  WiFi.begin(ssid, password);

While the ESP8266 attempts to connect to the network, we can use the WiFi.status() function to check the connectivity status.

//check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }

For your information, this function returns the following statuses:

  • WL_CONNECTED: when connected to a Wi-Fi network
  • WL_NO_SHIELD: when no Wi-Fi shield is present
  • WL_IDLE_STATUS: a temporary status assigned when WiFi.begin() is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED)
  • WL_NO_SSID_AVAIL: when no SSID are available
  • WL_SCAN_COMPLETED: when the scan networks is completed
  • WL_CONNECT_FAILED: when the connection fails for all the attempts
  • WL_CONNECTION_LOST: when the connection is lost
  • WL_DISCONNECTED: when disconnected from a network

Once connected to the network, the WiFi.localIP() function is used to print the ESP8266’s IP address.

Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

The only difference between AP and STA mode is that one creates its own network while the other joins an existing one. So, the rest of the code for handling HTTP requests and serving web pages in STA mode is the same as explained above for AP mode. This includes the following:

  • Declaring NodeMCU’s GPIO pins to which LEDs are connected
  • Defining multiple server.on() methods to handle incoming HTTP requests
  • Defining server.onNotFound() method to handle HTTP 404 error
  • Creating custom functions that are executed when specific URL is hit
  • Creating HTML page
  • Styling the web page
  • Creating buttons and displaying their status

Often in a project you want the ESP8266 to perform its normal program while continuously monitoring for some kind of event. One widely adopted solution for this is the use of interrupts.

Interrupts can be classified into two types.

Hardware interrupts – These occur in response to an external event. For example, a GPIO interrupt (when a key is pressed).

Software Interrupts – These occur in response to a software instruction. For example, a simple timer interrupt or a watchdog timer interrupt (when the timer times out).

ESP8266 GPIO Interrupt

You can configure the ESP8266 to generate an interrupt when a GPIO pin changes its logic level.

All GPIO pins in an ESP8266 board can be configured to act as interrupt request inputs, except GPIO16.

esp8266 interrupt pins

Attaching an Interrupt to a GPIO Pin

In the Arduino IDE, we use a function called attachInterrupt() to set an interrupt on a pin by pin basis. The syntax looks like below.

attachInterrupt(GPIOPin, ISR, Mode);

This function accepts three arguments:

GPIOPin – sets the GPIO pin as the interrupt pin, which tells ESP8266 which pin to monitor.

ISR – is the name of the function that will be called each time the interrupt occurs.

Mode – defines when the interrupt should be triggered. Five constants are predefined as valid values:

LOWTriggers the interrupt whenever the pin is LOW
HIGHTriggers the interrupt whenever the pin is HIGH
CHANGETriggers the interrupt whenever the pin changes value, from HIGH to LOW or LOW to HIGH
FALLINGTriggers the interrupt when the pin goes from HIGH to LOW
RISINGTriggers the interrupt when the pin goes from LOW to HIGH

Interrupt Service Routine

The Interrupt Service Routine (ISR) is a function that is invoked every time an interrupt occurs on the GPIO pin.

Its syntax looks like below.

void ICACHE_RAM_ATTR ISR() {
    Statements;
}

ISRs in ESP8266 are special kinds of functions that have some unique rules that most other functions do not have.

  • An ISR cannot have any parameters, and they should not return anything.
  • ISRs should be as short and fast as possible as they block normal program execution.
  • They should have the ICACHE_RAM_ATTR attribute, according to the ESP8266 documentation.

What is ICACHE_RAM_ATTR?

When we flag a piece of code with the ICACHE_RAM_ATTR attribute, the compiled code is placed in the ESP8266’s Internal RAM (IRAM). Otherwise the code is kept in Flash. And Flash on ESP8266 is much slower than internal RAM.

If the code we want to run is an Interrupt Service Routine (ISR), we generally want to execute it as soon as possible. If we had to ‘wait’ for the ISR to load from Flash then things could go horribly wrong.

Hardware Hookup

Enough of the theory! Let’s look at a practical example.

Let’s connect a push button to GPIO#12 (D6) on the ESP8266. You do not need any pullup for this pin as we will be enabling internal pullup.

wiring push buttons to esp8266 for gpio interrupt

Example Code: Simple Interrupt

The following sketch demonstrates the use of interrupts and the correct way to write an interrupt service routine.

This program watches GPIO#12 (D6) for the FALLING edge. In other words, it looks for a voltage change going from logic HIGH to logic LOW that occurs when the button is pressed. When this happens the function isr is called. The code within this function counts the number of times the button has been pressed.

struct Button {
  const uint8_t PIN;
  uint32_t numberKeyPresses;
  bool pressed;
};

Button button1 = {D6, 0, false};

void ICACHE_RAM_ATTR isr() {
  button1.numberKeyPresses++;
  button1.pressed = true;
}

void setup() {
  Serial.begin(115200);
  pinMode(button1.PIN, INPUT_PULLUP);
  attachInterrupt(button1.PIN, isr, FALLING);
}

void loop() {
  if (button1.pressed) {
      Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
      button1.pressed = false;
  }
}

Once you have uploaded the sketch, open the serial monitor at baud rate 115200. On pressing the button you will get the following output.

esp8266 gpio interrupt output on serial monitor

Code Explanation

At the beginning of the sketch we create a structure called Button. This structure has three members – the pin number, the number of key presses, and the pressed state. FYI, a structure is a collection of variables of different types (but logically related to each other) under a single name.

struct Button {
  const uint8_t PIN;
  uint32_t numberKeyPresses;
  bool pressed;
};

We then create an instance of the Button structure and initialize the pin number to D6, the number of key presses to 0 and the default pressed state to false.

Button button1 = {D6, 0, false};

The following code is an interrupt service routine. As mentioned earlier, the ISR in ESP8266 must have the ICACHE_RAM_ATTR attribute.

In the ISR we simply increment the KeyPresses counter by 1 and set the button pressed state to True.

void ICACHE_RAM_ATTR isr() {
  button1.numberKeyPresses++;
  button1.pressed = true;
}

In the setup section of the code, we first initialize the serial communication with the PC and then we enable the internal pullup for the D6 GPIO pin.

Next we tell ESP8266 to monitor the D6 pin and to call the interrupt service routine isr when the pin goes from HIGH to LOW i.e. FALLING edge.

void setup() {
  Serial.begin(115200);
  pinMode(button1.PIN, INPUT_PULLUP);
  attachInterrupt(button1.PIN, isr, FALLING);
}

In the loop section of the code, we simply check if the button has been pressed and then print the number of times the key has been pressed so far and set the button pressed state to false so that we can continue to receive interrupts.

void loop() {
  if (button1.pressed) {
      Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
      button1.pressed = false;
  }
}

Managing Switch Bounce

A common problem with interrupts is that they often get triggered multiple times for the same event. If you look at the serial output of the above example, you will notice that even if you press the button only once, the counter is incremented several times.

esp8266 gpio interrupt bounce problem

To find out why this happens, you have to take a look at the signal. If you monitor the voltage of the pin on the signal analyzer while you press the button, you will get a signal like this:

switch bounce signal

You may feel like contact is made immediately, but in fact the mechanical parts within the button come into contact several times before they settle into a particular state. This causes multiple interrupts.

It is purely a mechanical phenomenon known as a switch bounce, like dropping a ball – it bounces several times before finally landing on the ground.

The time for the signal to stabilize is very fast and seems almost instantaneous to us, but for an ESP8266 this is a huge period of time. It can execute multiple instructions in that time period.

The process of eliminating switch bounce is called debouncing. There are two ways to achieve this.

  • Through hardware: by adding an appropriate RC filter to smooth the transition.
  • Through software: by temporarily ignoring further interrupts for a short period of time after the first interrupt is triggered.

Example Code: Debouncing an Interrupt

Here the above sketch is rewritten to demonstrate how to debounce an interrupt programmatically. In this sketch we allow the ISR to be executed only once on each button press, instead of executing it multiple times.

Changes to the sketch are highlighted in green.

struct Button {
    const uint8_t PIN;
    uint32_t numberKeyPresses;
    bool pressed;
};

Button button1 = {D6, 0, false};

//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;  
unsigned long last_button_time = 0; 

void ICACHE_RAM_ATTR isr() {
    button_time = millis();
if (button_time - last_button_time > 250)
{
        button1.numberKeyPresses++;
        button1.pressed = true;
       last_button_time = button_time;
}
}

void setup() {
    Serial.begin(115200);
    pinMode(button1.PIN, INPUT_PULLUP);
    attachInterrupt(button1.PIN, isr, FALLING);
}

void loop() {
    if (button1.pressed) {
        Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses);
        button1.pressed = false;
    }
}

Let’s look at the serial output again as you press the button. Note that the ISR is called only once for each button press.

esp8266 gpio interrupt debounce

Code Explanation:

This fix works because each time the ISR is executed, it compares the current time returned by the millis() function to the time the ISR was last called.

If it is within 250ms, ESP8266 ignores the interrupt and immediately goes back to what it was doing. If not, it executes the code within the if statement incrementing the counter and updating the last_button_time variable, so the function has a new value to compare against when it’s triggered in the future.

One of the best things about ESP8266 is that its firmware can be updated wirelessly. This kind of programming is called “Over-The-Air” (OTA).

What is OTA programming in ESP8266?

OTA programming lets you update/upload a new program to the ESP8266 over Wi-Fi without having to connect the ESP8266 to the computer via USB.

The OTA functionality comes in handy when there is no physical access to the ESP module. In addition, it reduces the time required to update each ESP module during maintenance.

One key advantage of OTA is that a single central location can send an update to multiple ESPs on the same network.

The only disadvantage is that you must include an OTA code with each sketch you upload in order to use OTA in the next update.

3 Simple Steps for Using OTA with the ESP8266

  1. Installing Python 2.7.x series: The first step is to install the Python 2.7.x series on your computer.
  2. Uploading Basic OTA Firmware Serially: Upload the sketch containing the OTA firmware serially. This is a required step in order to perform the subsequent updates over-the-air.
  3. Uploading New Sketch Over-The-Air: You can now upload new sketches to the ESP8266 from the Arduino IDE over-the-air.

Step 1: Install Python 2.7.x series

To use OTA functionality, you must first install Python 2.7.x, if it is not already installed on your machine.

Download Python 2.7.x for Windows (MSI installer) from the official Python website.

Download Python 2.7.x Series

Launch the installer and proceed through the installation wizard.

Install Python 2.7.x Series On PC

Make sure the “Add python.exe to Path” option is enabled in the Customize Python 2.7.X section.

Enable Add Python.exe to Path While Python Installation

Step 2: Upload Basic OTA Firmware Serially

Because the ESP8266 ‘s factory image lacks OTA Upgrade capability, you must first load the OTA firmware on the ESP8266 via serial interface.

It is required to first update the firmware in order to perform subsequent updates over-the-air.

The ESP8266 add-on for the Arduino IDE includes an OTA library as well as a BasicOTA example. Simply navigate to File > Examples > ArduinoOTA > BasicOTA.

Open BasicOTA Sketch In Arduino IDE

Before you begin uploading the sketch, you must modify the following two variables with your network credentials so that the ESP8266 can connect to an existing network.

const char* ssid = "..........";
const char* password = "..........";

When you’re finished, go ahead and upload the sketch.

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "..........";
const char* password = "..........";

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

Now, open the Serial Monitor at 115200 baud rate and press the RST button on the ESP8266. If everything is fine, you should see the dynamic IP address assigned by your router. Make a note of it.

Note Down IP Address Allotted to ESP8266 NodeMCU

Step 3: Upload New Sketch Over-The-Air

Now, let’s upload a new sketch over-the-air.

Remember that you must include the OTA code in every sketch you upload. Otherwise, you will lose OTA capability and will be unable to perform the next over-the-air upload. Therefore, it is recommended that you modify the preceding code to include your new code.

As an example, we will include a simple Blink sketch in the Basic OTA code. Remember to modify the SSID and password variables with your network credentials.

Changes to the code are highlighted in blue.

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "..........";
const char* password = "..........";

//variabls for blinking an LED with Millis
const int led = D0; // ESP8266 Pin to which onboard LED is connected
unsigned long previousMillis = 0;  // will store last time LED was updated
const long interval = 1000;  // interval at which to blink (milliseconds)
int ledState = LOW;  // ledState used to set the LED
void setup() {
pinMode(led, OUTPUT);
    
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();

//loop to blink without delay
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
  // save the last time you blinked the LED
  previousMillis = currentMillis;
  // if the LED is off turn it on and vice-versa:
  ledState = not(ledState);
  // set the LED with the ledState of the variable:
  digitalWrite(led,  ledState);
  }
}

Notice that we haven’t used the delay() function to make the LED blink. This is because the delay() function pauses the program. If the next OTA request is generated while the ESP8266 is paused waiting for the delay() to complete, your program will miss that request.

After copying the above sketch to your Arduino IDE, navigate to Tools > Port option. Look for something like: esp8266-xxxxxx at your_esp_ip_address. If you are unable to locate it, you may need to restart your IDE.

Select OTA Port in Arduino IDE

Choose the port and press the Upload button. The new sketch will be uploaded in a matter of seconds. The on-board LED should start blinking.

ESP8266 NodeMCU's Onboard LED Blinking

Every now and then, you’ll come across an idea where keeping time is a top priority. For example, consider a relay that must be activated at a specific time or a data logger that must store values at precise intervals.

The first thing that comes to mind is to use an RTC (Real Time Clock) chip. However, because these chips are not perfectly accurate, you must perform manual adjustments on a regular basis to keep them synchronized.

Instead, it is preferable to employ the Network Time Protocol (NTP). If your ESP8266 project has Internet access, you can obtain date and time (with a precision of a few milliseconds of UTC) for FREE. Also, you don’t need any additional hardware.

What is an NTP?

NTP is an abbreviation for Network Time Protocol. It is a standard Internet Protocol (IP) for synchronizing computer clocks over a network.

This protocol synchronizes all networked devices to Coordinated Universal Time (UTC) within a few milliseconds ( 50 milliseconds over the public Internet and under 5 milliseconds in a LAN environment).

Coordinated Universal Time (UTC) is a global time standard that is similar to GMT (Greenwich Mean Time). UTC does not change; it is the same all over the world.

The idea here is to use NTP to set the computer clocks to UTC and then apply any local time zone offset or daylight saving time offset. This allows us to synchronize our computer clocks regardless of location or time zone differences.

NTP Architecture

NTP employs a hierarchical architecture. Each level in the hierarchy is known as a stratum.

NTP Hierarchical Architecture With Stratums

At the very top are high-precision timekeeping devices, such as atomic clocks, GPS or radio clocks, known as stratum 0 hardware clocks.

Stratum 1 servers have a direct connection to a stratum 0 hardware clock and therefore provide the most accurate time.

Each stratum in the hierarchy synchronizes with the stratum above and acts as a server for computers in lower stratums.

How NTP Works?

NTP can operate in a number of ways. The most common configuration is to operate in client-server mode.

The fundamental operating principle is as follows:

  1. The client device, such as the ESP8266, connects to the NTP server via the User Datagram Protocol (UDP) on port 123.
  2. The client then sends a request packet to the NTP server.
  3. In response to this request, the NTP server sends a time stamp packet. A time stamp packet contains a variety of data, such as a UNIX timestamp, accuracy, delay, or timezone.
  4. A client can then extract the current date and time from it.
NTP Server Working - Request And Timestamp Packet Transfer

Preparing the Arduino IDE

You should have the ESP8266 add-on installed in your Arduino IDE before proceeding with this tutorial. If you haven’t installed it yet, follow the tutorial below.

Installing the NTP Client Library

The NTP Client Library simplifies the process of retrieving time and date from an NTP server. Follow the steps below to install this library in your Arduino IDE.

Navigate to Sketch > Include Library > Manage Libraries… Wait for the Library Manager to download the libraries index and update the list of installed libraries.

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by entering ‘ntpclient’. Look for NTPClient by Fabrice Weinberg. Click on that entry and then choose Install.

Installing NTP Client Library In Arduino IDE

Getting Date and Time from NTP Server

The sketch below will show you exactly how to get the date and time from the NTP Server.

#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

const char *ssid     = "YOUR_SSID";
const char *password = "YOUR_PASS";

const long utcOffsetInSeconds = 3600;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

void setup(){
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }

  timeClient.begin();
}

void loop() {
  timeClient.update();

  Serial.print(daysOfTheWeek[timeClient.getDay()]);
  Serial.print(", ");
  Serial.print(timeClient.getHours());
  Serial.print(":");
  Serial.print(timeClient.getMinutes());
  Serial.print(":");
  Serial.println(timeClient.getSeconds());
  //Serial.println(timeClient.getFormattedTime());

  delay(1000);
}

Before you start uploading the sketch, you’ll need to make a few changes to make sure it’ll work for you.

  • Modify the following two variables with your network credentials so that the ESP8266 can connect to an existing network.
    const char* ssid       = "YOUR_SSID";
    const char* password   = "YOUR_PASS";
  • Adjust the UTC offset for your timezone (in seconds). Refer to the list of UTC time offsets. Here are some examples for various time zones:
    • For UTC -5.00 : -5 * 60 * 60 : -18000
    • For UTC +1.00 : 1 * 60 * 60 : 3600
    • For UTC +0.00 : 0 * 60 * 60 : 0
    const long utcOffsetInSeconds = 3600;

After uploading the sketch, press the RST button on your NodeMCU. The serial monitor should display the date and time every second.

ESP32 Reads Date & Time From NTP Server Output On Serial monitor

Code Explanation

Let’s take a quick look at the code to see how it works. To begin, we include the libraries required for this project.

  • NTPClient.h is a time library that handles NTP server synchronization gracefully.
  • ESP8266WiFi.h is a library containing the ESP8266-specific WiFi methods we will use to connect to a network.
  • WiFiUdp.h library handles UDP protocol tasks such as opening a UDP port, sending and receiving UDP packets, and so on.
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

A few constants are defined, such as the SSID, WiFi password, and UTC offset. daysOfTheWeek 2D array is also defined.

const char *ssid     = "YOUR_SSID";
const char *password = "YOUR_PASS";
const long utcOffsetInSeconds = 3600;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

In addition, the address of the NTP Server is specified. pool.ntp.org is a great open NTP project for this kind of thing.

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

pool.ntp.org automatically selects time servers that are physically close to you. However, if you want to select a specific server, use one of the pool.ntp.org sub-zones.

AreaHostName
Worldwidepool.ntp.org
Asiaasia.pool.ntp.org
Europeeurope.pool.ntp.org
North Americanorth-america.pool.ntp.org
Oceaniaoceania.pool.ntp.org
South Americasouth-america.pool.ntp.org

In the setup section, we first establish serial communication with the PC and then connect to the WiFi network by calling the WiFi.begin() function.

Serial.begin(115200);

WiFi.begin(ssid, password);

while ( WiFi.status() != WL_CONNECTED ) {
  delay ( 500 );
  Serial.print ( "." );
}

Once the ESP8266 is connected to the network, we use the begin() function to initialize the NTP client.

timeClient.begin();

Now we simply call the update() function to obtain the current date and time. This function sends a request packet to an NTP server and parses the received time stamp packet into a readable format.

timeClient.update();

You can retrieve the current date and time by calling the NTP Client object’s methods.

Serial.print(daysOfTheWeek[timeClient.getDay()]);
Serial.print(", ");
Serial.print(timeClient.getHours());
Serial.print(":");
Serial.print(timeClient.getMinutes());
Serial.print(":");
Serial.println(timeClient.getSeconds());

Whether you call them individually addressable RGB LEDs, WS2812B, or NeoPixels, there’s no denying that they are extremely popular and a must-have for any glowy and blinky project.

Writing code to control addressable LEDs isn’t difficult, but what if you just want to add some ambient lighting to your living room or office and manage it all from your smartphone? Currently the best option, hands down, is WLED—A free, feature-rich, open-source mobile app that gives us complete command over a wide variety of RGB LEDs.

The WLED app makes controlling individually addressable LEDs much simpler, convenient, and—most importantly—fun. This app is just too cool not to try.

In this tutorial, we will learn how to install WLED on an ESP8266 board and use it to control a string of addressable LEDs.

Installing WLED on an ESP8266 Board

1. The first step is to download the ESPHome-Flasher tool. Open a browser to the ESPHome-Flasher github page and look for the installer for your operating system, then download it.

esphome flasher tool download page

2. You also need to download the WLED compiled binary. Navigate to the WLED github page and download the WLED_0.x.x_ESP8266.bin file.

wled esp8266 compiled binary download

3. Connect your ESP8266 board to your computer using a USB cable. Make sure the USB cable you’re using supports data transfer.

esp8266 connected to computer using a usb cable

4. Launch the ESPHome-Flasher tool.

esphome flasher tool

5. Select the COM port to which your ESP8266 is connected. If nothing appears in the serial port dropdown, you may need to update your USB-to-serial drivers, or you may not be using a USB cable that supports data transfer.

esphome flasher tool com port

6. Click the ‘Browse’ button and then select the WLED binary file that you just downloaded.

esphome flasher tool wled binary selected

7. Click ‘Flash ESP’ to flash the firmware to the board. It shouldn’t take more than a few minutes to complete.

esphome flasher tool flashing esp8266

8. Once it displays “Done! Flashing is complete!”, you can exit the program.

esphome flasher tool done flashing

9. Select the Network icon on the taskbar. You should notice a new wireless access point named WLED-AP.

wled ap network

10. Join that and then enter wled1234 when prompted for a password.

wled ap network joined

11. When you connect to the network, your default browser will launch automatically and load the WLED home page. If it doesn’t launch for some reason, open a new browser and navigate to http://4.3.2.1, which should take you to the same location.

wled access point home page

12. Select WIFI Settings. This will bring up the WIFI Setup section.

wled ap mode wifi settings

13. Change the Network Name and Network Password to the name and password for your WiFi network. Scroll down to mDNS address and set the address to your liking. I called mine http://mylights.local/ – from now on, this is what I’ll be typing into a web browser on my WiFi network to connect to my lights.

wled wifi settings updated

14. Click Save and Connect. Your ESP8266 will reboot and connect to your WiFi network. However, it is a good idea to reset your ESP8266 by pressing the RST button.

wled wifi settings save and connect

15. Finally, make sure that you reconnect to your home network.

16. Now navigate to your mDNS address (in my case it’s http://mylights.local/) to access the WLED User Interface (UI).

wled accessed through new address

A Quick Tour of the WLED UI

WLED’s UI is simple, but may appear overwhelming at first. The UI can be divided into five sections.

wled ui overview pc mode
WLED Web Interface
wled app ui overview
WLED App

1. Configuration: Here you can find buttons such as Power (to turn the lights on or off), Timer (to organize the lights to come on and off at certain points during the day), Sync (to sync multiple WLED devices across your network), Peek (to see an animation preview of your lights), Config (to configure the number of LEDs and the GPIO port), and a Brightness slider (to control the overall brightness).

2. Color Picker: This section allows you to change the color of the LEDs, whether they are static or animated. If you scroll down this section, you can access various color palettes for use in effects.

3. Effects/Animation: Here you’ll find a library of ready-made animations for your lights. While each effect has its own color scheme, you can easily customize it in the Color Picker section.

4. Segment: If you have a large array or matrix of LEDs, you can split them into segments and assign a different color, animation, or color scheme to each segment.

5. Presets: Here, you can create presets for your custom light show, as well as a playlist to cycle through the many available animations.

Configuring the WLED

Once the setup is complete it’s a good idea to head to the LED Preferences screen and configure LED lights.

1. Click on Config and select LED Preferences.

wled led preferences

2. Scroll down to Hardware Setup and select the type of led strip you have.

wled led type select

4. Set the “Length” to match the number of LEDs. We have a total of 12 LEDs, so I changed the length to 12.

wled esp8266 number of leds select

5. Make a note of the GPIO pin number. This pin will be used to send data to the LEDs. By default, GPIO2 (D4) is used.

wled esp8266 gpio pin select

6. Scroll down to the bottom and click Save.

wled save led preferences

7. Click Back to return to the main screen.

wled return to main page

Connecting a WS2812x Addressable LED Strip to an ESP8266

Once you’ve finished configuring the WLED, unplug the ESP8266 from the USB port. Let’s wire up an addressable LED strip to the ESP8266.

The wiring is relatively simple. There are only three wires to connect: two for power and one for data transmission.

Connect the Red wire (+5V/VCC) of the addressable LED strip to the ESP8266’s VIN pin and the White/Yellow wire (GND) to the ESP8266’s GND pin.

Finally, connect the Green wire (DIN) of the LED strip to the ESP8266’s GPIO2 (D4), via a 330 Ohm resistor. This in-line resistor is there to protect the data pin. A resistor between 220 and 470 Ohm should do nicely. Try to place the resistor as close to your addressable LEDs as possible.

If you have few LEDs you can plug the ESP8266 into your computer using a USB cable and power the strip directly through the board.

connecting ws2812b addressable led strip to esp8266 5v usb powered

If you have a larger project that requires more LEDs, USB power won’t be enough. Instead you should inject power into the strip from an external source. Keep in mind that each RGB LED consumes about 60mA (20mA per color channel), when the brightness is set to full. That means that for every 30 LEDs, your LED strip could be drawing up to 1.8 Amps.

connecting ws2812b addressable led strip to esp8266 5v external power

Once wiring is complete, LEDs should come to life and emit a soft yellow light. If this isn’t the case, double-check your wiring before proceeding.

From here everything can now be done via the WLED App.

Using the WLED Mobile App

1. Download the WLED app from the Google Play Store or the Apple App Store on your smartphone or tablet.

wled app on google play store

2. Open the app and click on the plus icon in the upper right corner to open up the discovery page.

wled app add devices

3. Hit Discover Lights. This will search through your WIFI for all connected boards running WLED software.

wled app discover lights

4. Once it says Found WLED! click the Tick icon in the upper-right corner. This will bring you back to the home page where you will have a list of all the WLED devices on your network.

wled app add discovered wled devices

5. Click on the newly discovered device to open the control panel.

wled app list of wled devices

6. Select a color using the color wheel. And voilà, you now have fully operational, remotely activated addressable LEDs!

wled app interface

If the color of the LEDs does not match the color you selected in the app, go to Config > LED Preferences and adjust the Color Order setting under Hardware Setup until it does.

wled color order setting

7. If you have multiple WLED devices, you may want to change the name displayed on the app to differentiate them. Go to Config > User Interface and name it whatever you want, then hit Save.

wled device name change

Changing Effects

WLED offers over 180 different effects. This is where the real fun begins.

1. Navigate to the Effects tab and select an effect. The LEDs will respond immediately.

wled app effect select

2. You can change the speed and intensity of the effect. The further you move the slider, the faster and more intense the effect.

wled app effect speed intensity

3. Each effect has its own color scheme, which you can easily change in the Color Picker section. This will keep the animation effect but replace the colors.

wled app effect color scheme

Tips and Recommendations

There are a few tips and recommendations to keep in mind when designing your setup with ESP8266:

  • ESP8266 can handle up to three strips at a time.
  • While most strip types have yet to be tested, the following are known to work: WS281x, SK6812 RGBW, PWM white
  • As soon as installation is complete, select the LEDs type, pin numbers, length, and color order of your LED strips in the LED settings page.
  • Framerate can be increased significantly by selecting the appropriate power supply for the setup and turning off the WLED brightness limiter setting.
  • It is highly recommended to use two LED pins, GPIO1 (TX) and GPIO2 (D4), since they support hardware driving.
  • When GPIO1 is used, serial debugging is disabled. If you don’t need it, you can use a strip on this pin.
  • GPIO3 is the third pin on the ESP8266 that allows hardware driving. However, it consumes 5 times the memory per LED as GPIO 1 and 2, so it should only be used for low LED counts (recommended <50).
  • It is possible to use any other pin, but it will use the bitbang method for that, which is not recommended for reliability. It is best to stick to GPIO 1, 2, and, if necessary, 3.
  • The performance is determined by how many LEDs you drive with the ESP8266 and how many ESP8266 output pins you use.
    • For excellent performance, it is recommended to use 512 LEDs/pin with 2 outputs for a total of 1024 LEDs.
    • For marginal performance, you can use 800 LEDs/pin with 2 outputs for a total of 1600 LEDs.
  • ESP8266 is capable of calculating approximately 15k LEDs per second (that’s 250LEDs @~60fps, 500 LEDs @~30fps, 1000 LEDs @~15fps)

Have you ever wished you could use your mobile, tablet, or PC to monitor the temperature and humidity in your home, walk-in cooler, or wine cellar at any time? Then this IoT project could be a great place to start!

This project employs the ESP8266 NodeMCU as the control device, which connects to an existing WiFi network and creates a Web Server. When a device connects to this web server, the ESP8266 will read the temperature and relative humidity from the DHT11/DHT22 sensor and send it to the device’s web browser with a nice interface. Excited? Let’s get started!

There are a few concepts you should be familiar with before continuing with this tutorial. If any of the tutorials below are unfamiliar to you, consider reading through them first:

Wiring a DHT11/DHT22 sensor to an ESP8266

Connecting a DHT11/DHT22 sensor to an ESP8266 is pretty easy. Begin by placing the ESP8266 on your breadboard, making sure that each side of the board is on a different side of the breadboard.

Place the sensor on your breadboard next to the ESP8266. Connect the sensor’s VCC pin to the ESP8266’s 3.3V pin and ground to ground. Connect the sensor’s Data pin to the ESP8266’s D8 pin. Finally, add a 10KΩ pull-up resistor between VCC and the data line to keep it HIGH for proper communication between the sensor and the ESP8266.

The image below shows the wiring.

Wiring Fritzing Connecting DHT11 Temperature Humidity Sensor with ESP8266 NodeMCU
Wiring DHT11 Temperature Humidity Sensor with ESP8266 NodeMCU
Wiring Fritzing Connecting DHT22 Temperature Humidity Sensor with ESP8266 NodeMCU
Wiring DHT22 Temperature Humidity Sensor with ESP8266 NodeMCU

Installing DHT Sensor Library

To begin reading sensor data, you will need to install the DHT sensor library. It is available from the Arduino library manager.

To install the library, navigate to Sketch > Include Library > Manage Libraries… Wait for the Library Manager to download the libraries index and update the list of installed libraries.

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by entering ‘DHT sensor’. Look for the DHT sensor library by Adafruit. Click on that entry and then choose Install.

Adafruit DHT library Installation

The DHT sensor library makes use of the Adafruit Sensor support backend. So, look for Adafruit Unified Sensor in the library manager and install it as well.

Adafruit Unified Sensor library Installation

Creating an ESP8266 Web Server using WiFi Station (STA) mode

Let’s move on to the interesting stuff now!

As the title implies, we will configure an ESP8266 web server in Station (STA) mode to serve web pages to any connected client on the existing network.

Before you begin uploading the sketch, you must replace the following two variables with your network credentials so that the ESP8266 can connect to an existing network.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

When you’re finished, try out the sketch, and then we’ll go over it in detail.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "DHT.h"

// Uncomment one of the lines below for whatever DHT sensor type you're using!
//#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

ESP8266WebServer server(80);

// DHT Sensor
uint8_t DHTPin = D8; 
               
// Initialize DHT sensor.
DHT dht(DHTPin, DHTTYPE);                

float Temperature;
float Humidity;
 
void setup() {
  Serial.begin(115200);
  delay(100);
  
  pinMode(DHTPin, INPUT);

  dht.begin();              

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");

}
void loop() {
  
  server.handleClient();
  
}

void handle_OnConnect() {

 Temperature = dht.readTemperature(); // Gets the values of the temperature
  Humidity = dht.readHumidity(); // Gets the values of the humidity 
  server.send(200, "text/html", SendHTML(Temperature,Humidity)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float Temperaturestat,float Humiditystat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>ESP8266 Weather Report</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<div id=\"webpage\">\n";
  ptr +="<h1>ESP8266 NodeMCU Weather Report</h1>\n";
  
  ptr +="<p>Temperature: ";
  ptr +=(int)Temperaturestat;
  ptr +="°C</p>";
  ptr +="<p>Humidity: ";
  ptr +=(int)Humiditystat;
  ptr +="%</p>";
  
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server

After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the NodeMCU. If everything is fine, it will display the dynamic IP address obtained from your router as well as the “HTTP server started” message.

ESP8266 NodeMCU Web Server Station Mode Serial Monitor Output - Server Started

Next, launch a browser and navigate to the IP address displayed on the serial monitor. The ESP8266 should serve a web page with the current temperature and relative humidity.

Display DHT11 DHT22 AM2302 Temperature Humidity on ESP8266 Web Server - Without CSS

Detailed Code Explanation

The sketch begins by including the ESP8266WiFi.h library. This library contains ESP8266-specific methods that we use to connect to the network. Following that, we include the ESP8266WebServer.h library, which contains some methods that will assist us in configuring a server and handling incoming HTTP requests without having to worry about low-level implementation details. Finally, we include the DHT.h library.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "DHT.h"

Next, we specify the type of DHT sensor we are using. Uncomment the appropriate line below!

//#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

Because we are configuring the ESP8266 web server in Station (STA) mode, it will join the existing WiFi network. So, we need to specify the SSID and password.

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

Following that, we create an object of the ESP8266WebServer library so that we can access its functions. The constructor of this object accepts as a parameter the port to which the server will be listening. Since HTTP uses port 80 by default, we’ll use this value. This allows us to connect to the server without specifying the port in the URL.

// declare an object of WebServer library
ESP8266WebServer server(80);

Next, we define the GPIO pin number on the ESP8266 NodeMCU to which our sensor’s Data pin is connected and create a DHT object. So, we can access DHT library-specific functions.

// DHT Sensor
uint8_t DHTPin = D8;
 
// Initialize DHT sensor.
DHT dht(DHTPin, DHTTYPE);

There are two float variables set up: Temperature and Humidity.

float Temperature;
float Humidity;

Inside Setup() Function

In the setup function, we configure our web server. First, we establish a serial connection for debugging purposes and configure the GPIO pin to behave as an INPUT. We also initialize the DHT object.

Serial.begin(115200);
delay(100);
  
pinMode(DHTPin, INPUT);

dht.begin();

Then, we use the WiFi.begin() function to connect to the WiFi network. The function accepts SSID (Network Name) and password as parameters.

Serial.println("Connecting to ");
Serial.println(ssid);

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP8266 attempts to connect to the network, we can use the WiFi.status() function to check the connectivity status.

//check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }

Once connected to the network, the WiFi.localIP() function is used to print the IP address assigned to the ESP8266.

Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

To handle incoming HTTP requests, we must specify which code should be executed when a specific URL is accessed. For this, we use the .on() method. This method accepts two parameters: a relative URL path and the name of the function to be executed when that URL is visited.

The code below indicates that when a server receives an HTTP request on the root (/) path, it will call the handle_OnConnect() function. It is important to note that the URL specified is a relative path.

server.on("/", handle_OnConnect);

We haven’t specified what the server should serve if the client requests a URL that isn’t specified with server.on() . It should give a 404 error (Page Not Found) as a response. To accomplish this, we use the server.onNotFound() method.

server.onNotFound(handle_NotFound);

Now, to start the server, we call the server object’s begin() method.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

Actual incoming HTTP requests are handled in the loop function. For this, we use the server object’s handleClient() method.

server.handleClient();

Now we must write the handle_OnConnect() function, which we previously attached to the root (/) URL with server.on. We begin this function by reading temperature and humidity values from the sensor.

We use the send method to respond to an HTTP request. Although the method can be called with a number of different arguments, the simplest form requires the HTTP response code, the content type, and the content.

The first parameter we pass to the send method is the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then we specify the content type as “text/html,” and finally we pass the SendHTML() custom function, which generates a dynamic HTML page with the temperature and humidity readings.

void handle_OnConnect() {

 Temperature = dht.readTemperature(); // Gets the values of the temperature
  Humidity = dht.readHumidity(); // Gets the values of the humidity 
  server.send(200, "text/html", SendHTML(Temperature,Humidity)); 
}

Similarly, we write a function to handle the 404 Error page.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

Whenever the ESP8266 web server receives a request from a web client, the sendHTML() function generates a web page. It simply concatenates HTML code into a long string and returns to the server.send() function we discussed earlier. The function uses temperature and humidity readings as parameters to generate HTML content dynamically.

The first text you should always send is the <!DOCTYPE> declaration, which indicates that we’re sending HTML code.

String SendHTML(float Temperaturestat,float Humiditystat){
String ptr = "<!DOCTYPE html> <html>\n";

The <meta> viewport element makes the web page responsive, ensuring that it looks good on all devices. The title tag determines the page’s title.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>ESP8266 Weather Report</title>\n";

Styling the Web Page

Following that, we have some CSS to style the overall appearance of the web page. We select the Helvetica font and define the content to be displayed as an inline-block, center-aligned.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

The code that follows then sets the color, font, and margin around the body, H1, and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Setting the Web Page Heading

Next, the heading of the web page is set. You can change this text to anything that works for your application.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP8266 Weather Report</h1>\n";

Displaying Temperature and Humidity on Web Page

We used the paragraph tag to display temperature and humidity values. Note that these values ​​are converted to integers using type casting.

ptr +="<p>Temperature: ";
ptr +=(int)Temperaturestat;
ptr +="°C</p>";
ptr +="<p>Humidity: ";
ptr +=(int)Humiditystat;
ptr +="%</p>";

ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Styling Web Page to Look More Attractive

Programmers have a tendency to overlook aesthetics, but with a little effort, our webpage can appear more attractive. The screenshot below will give you an idea of what we’re going to do.

Display DHT11 DHT22 AM2302 Temperature Humidity on ESP8266 Web Server - Professional Look

Isn’t it incredible? Let’s add some style to our previous HTML page now. To begin, replace the SendHTML() function in the sketch above with the code below. Try out the new sketch, and then we’ll go over it in detail.

String SendHTML(float TempCstat,float TempFstat,float Humiditystat){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,600\" rel=\"stylesheet\">\n";
  ptr +="<title>ESP8266 Weather Report</title>\n";
  ptr +="<style>html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #333333;}\n";
  ptr +="body{margin-top: 50px;}\n";
  ptr +="h1 {margin: 50px auto 30px;}\n";
  ptr +=".side-by-side{display: inline-block;vertical-align: middle;position: relative;}\n";
  ptr +=".humidity-icon{background-color: #3498db;width: 30px;height: 30px;border-radius: 50%;line-height: 36px;}\n";
  ptr +=".humidity-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
  ptr +=".humidity{font-weight: 300;font-size: 60px;color: #3498db;}\n";
  ptr +=".temperature-icon{background-color: #f39c12;width: 30px;height: 30px;border-radius: 50%;line-height: 40px;}\n";
  ptr +=".temperature-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
  ptr +=".temperature{font-weight: 300;font-size: 60px;color: #f39c12;}\n";
  ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;right: -20px;top: 15px;}\n";
  ptr +=".data{padding: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  
   ptr +="<div id=\"webpage\">\n";
   
   ptr +="<h1>ESP8266 Weather Report</h1>\n";
   ptr +="<div class=\"data\">\n";
   ptr +="<div class=\"side-by-side temperature-icon\">\n";
   ptr +="<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n";
   ptr +="width=\"9.915px\" height=\"22px\" viewBox=\"0 0 9.915 22\" enable-background=\"new 0 0 9.915 22\" xml:space=\"preserve\">\n";
   ptr +="<path fill=\"#FFFFFF\" d=\"M3.498,0.53c0.377-0.331,0.877-0.501,1.374-0.527C5.697-0.04,6.522,0.421,6.924,1.142\n";
   ptr +="c0.237,0.399,0.315,0.871,0.311,1.33C7.229,5.856,7.245,9.24,7.227,12.625c1.019,0.539,1.855,1.424,2.301,2.491\n";
   ptr +="c0.491,1.163,0.518,2.514,0.062,3.693c-0.414,1.102-1.24,2.038-2.276,2.594c-1.056,0.583-2.331,0.743-3.501,0.463\n";
   ptr +="c-1.417-0.323-2.659-1.314-3.3-2.617C0.014,18.26-0.115,17.104,0.1,16.022c0.296-1.443,1.274-2.717,2.58-3.394\n";
   ptr +="c0.013-3.44,0-6.881,0.007-10.322C2.674,1.634,2.974,0.955,3.498,0.53z\"/>\n";
   ptr +="</svg>\n";
   ptr +="</div>\n";
   ptr +="<div class=\"side-by-side temperature-text\">Temperature</div>\n";
   ptr +="<div class=\"side-by-side temperature\">";
   ptr +=(int)TempCstat;
   ptr +="<span class=\"superscript\">°C</span></div>\n";
   ptr +="</div>\n";
   ptr +="<div class=\"data\">\n";
   ptr +="<div class=\"side-by-side humidity-icon\">\n";
   ptr +="<svg version=\"1.1\" id=\"Layer_2\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\"; width=\"12px\" height=\"17.955px\" viewBox=\"0 0 13 17.955\" enable-background=\"new 0 0 13 17.955\" xml:space=\"preserve\">\n";
   ptr +="<path fill=\"#FFFFFF\" d=\"M1.819,6.217C3.139,4.064,6.5,0,6.5,0s3.363,4.064,4.681,6.217c1.793,2.926,2.133,5.05,1.571,7.057\n";
   ptr +="c-0.438,1.574-2.264,4.681-6.252,4.681c-3.988,0-5.813-3.107-6.252-4.681C-0.313,11.267,0.026,9.143,1.819,6.217\"></path>\n";
   ptr +="</svg>\n";
   ptr +="</div>\n";
   ptr +="<div class=\"side-by-side humidity-text\">Humidity</div>\n";
   ptr +="<div class=\"side-by-side humidity\">";
   ptr +=(int)Humiditystat;
   ptr +="<span class=\"superscript\">%</span></div>\n";
   ptr +="</div>\n";

  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Code Explanation

We already know that the <!DOCTYPE> declaration informs the browser that we are sending HTML code, and the <meta> viewport element makes the web page responsive. The only difference here is that we’ll be using Google Fonts. Google offers hundreds of free web fonts for commercial and personal use.

For our website, we will use the Google-commissioned Open Sans web font. The Google font is embedded in your HTML document’s head using the <link> tag.

For our page, we’ve chosen font weights of 300 (Light), 400 (Regular), and 600 (Bold). You can choose as many as you want, but keep in mind that excessive font weights slow down page load time.

You can also add italic style by simply appending an i character to the end of the font weight, for example, 400i will embed regular-italic-style-font.

It is important to note that google fonts are loaded dynamically; you will not be able to see the Google font unless you have an active internet connection on the device from which you are accessing this page.

String SendHTML(float TempCstat,float TempFstat,float Humiditystat){
String ptr = "<!DOCTYPE html> <html>\n";
ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,600\" rel=\"stylesheet\">\n";

Next, we will apply the Open Sans font to the entire HTML document. We must also specify sans-serif as our fallback font. If the first specified font fails to load, the browser will attempt to load the fallback font.

ptr +="<title>ESP8266 Weather Report</title>\n";
ptr +="<style>html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #333333;}\n";
ptr +="body{margin-top: 50px;}\n";
ptr +="h1 {margin: 50px auto 30px;}\n";

Next, we apply CSS for Humidity & Temperature – icons, titles, and values. All three of these elements are inline and vertically aligned. The icon background is made circular using a border radius of 50% and 30px height and width.

ptr +=".side-by-side{display: inline-block;vertical-align: middle;position: relative;}\n";
ptr +=".humidity-icon{background-color: #3498db;width: 30px;height: 30px;border-radius: 50%;line-height: 36px;}\n";
ptr +=".humidity-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
ptr +=".humidity{font-weight: 300;font-size: 60px;color: #3498db;}\n";
ptr +=".temperature-icon{background-color: #f39c12;width: 30px;height: 30px;border-radius: 50%;line-height: 40px;}\n";
ptr +=".temperature-text{font-weight: 600;padding-left: 15px;font-size: 19px;width: 160px;text-align: left;}\n";
ptr +=".temperature{font-weight: 300;font-size: 60px;color: #f39c12;}\n";
ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;right: -20px;top: 15px;}\n";
ptr +=".data{padding: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Following that, we display temperature readings with this nice little icon.

The temperature icon is a Scalable Vector Graphics (SVG) image defined in the <svg> tag. Creating an SVG requires no special programming skills. You can create graphics for your page using Google SVG Editor. Following the icon, we display the actual temperature reading from the sensor.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP8266 NodeMCU Weather Report</h1>\n";
ptr +="<div class=\"data\">\n";

ptr +="<div class=\"side-by-side temperature-icon\">\n";
ptr +="<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n";
ptr +="width=\"9.915px\" height=\"22px\" viewBox=\"0 0 9.915 22\" enable-background=\"new 0 0 9.915 22\" xml:space=\"preserve\">\n";
ptr +="<path fill=\"#FFFFFF\" d=\"M3.498,0.53c0.377-0.331,0.877-0.501,1.374-0.527C5.697-0.04,6.522,0.421,6.924,1.142\n";
ptr +="c0.237,0.399,0.315,0.871,0.311,1.33C7.229,5.856,7.245,9.24,7.227,12.625c1.019,0.539,1.855,1.424,2.301,2.491\n";
ptr +="c0.491,1.163,0.518,2.514,0.062,3.693c-0.414,1.102-1.24,2.038-2.276,2.594c-1.056,0.583-2.331,0.743-3.501,0.463\n";
ptr +="c-1.417-0.323-2.659-1.314-3.3-2.617C0.014,18.26-0.115,17.104,0.1,16.022c0.296-1.443,1.274-2.717,2.58-3.394\n";
ptr +="c0.013-3.44,0-6.881,0.007-10.322C2.674,1.634,2.974,0.955,3.498,0.53z\"/>\n";
ptr +="</svg>\n";
ptr +="</div>\n";

ptr +="<div class=\"side-by-side temperature-text\">Temperature</div>\n";
ptr +="<div class=\"side-by-side temperature\">";
ptr +=(int)TempCstat;
ptr +="<span class=\"superscript\">°C</span></div>\n";
ptr +="</div>\n";

With this icon, we display humidity readings. It’s another SVG.

After printing the humidity values, we close all open tags such as body and html.

ptr +="<div class=\"data\">\n";
ptr +="<div class=\"side-by-side humidity-icon\">\n";
ptr +="<svg version=\"1.1\" id=\"Layer_2\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\"; width=\"12px\" height=\"17.955px\" viewBox=\"0 0 13 17.955\" enable-background=\"new 0 0 13 17.955\" xml:space=\"preserve\">\n";
ptr +="<path fill=\"#FFFFFF\" d=\"M1.819,6.217C3.139,4.064,6.5,0,6.5,0s3.363,4.064,4.681,6.217c1.793,2.926,2.133,5.05,1.571,7.057\n";
ptr +="c-0.438,1.574-2.264,4.681-6.252,4.681c-3.988,0-5.813-3.107-6.252-4.681C-0.313,11.267,0.026,9.143,1.819,6.217\"></path>\n";
ptr +="</svg>\n";
ptr +="</div>\n";
ptr +="<div class=\"side-by-side humidity-text\">Humidity</div>\n";
ptr +="<div class=\"side-by-side humidity\">";
ptr +=(int)Humiditystat;
ptr +="<span class=\"superscript\">%</span></div>\n";
ptr +="</div>\n";

ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Improvement 1 – Auto Page Refresh

One of the improvements you can make is to automatically refresh the page in order to update the sensor value.

You can instruct the browser to automatically reload the page at a specified interval by inserting a single <meta> tag into your HTML document.

<meta http-equiv="refresh" content="2" >

Insert this code into your document’s <head> tag; this meta tag will instruct the browser to refresh every two seconds.

Improvement 2 – Dynamically load Sensor Data with AJAX

Refreshing a web page is impractical if the web page is large. Asynchronous Javascript And Xml (AJAX) is a better method because it allows us to request data from the server asynchronously (in the background) without refreshing the page.

The XMLHttpRequest object within JavaScript is commonly used to execute AJAX on webpages. It sends a silent GET request to the server and updates the page element. AJAX is not a new technology, nor is it a different programming language; rather, it is simply an existing technology that is used in a different way. It allows you to:

  • Request data from a server after the page has loaded
  • Receive data from a server after the page has loaded
  • Send data to a server in the background

This is the AJAX script we’ll be using. Place this script before closing the <head> tag.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,200);\n";
ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";
ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.getElementById(\"webpage\").innerHTML =this.responseText}\n";
ptr +="};\n";
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";
ptr +="</script>\n";

Code Explanation

Because the AJAX script is just javascript, it must be enclosed in the <script> tag. We’ll use the javascript setInterval() function to make this function call itself repeatedly. It requires two parameters: the function to be executed and the time interval (in milliseconds).

ptr +="<script>\n";
ptr +="setInterval(loadDoc,200);\n";

A loadDoc() function is the most important part of this script. In this function, an object of XMLHttpRequest() is created. This object is used to query a web server for data.

ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";

The xhttp.onreadystatechange() method is called whenever the readyState changes. The readyState property represents the XMLHttpRequest’s status. It has one of the values listed below.

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

The status property holds the XMLHttpRequest object’s status. It has one of the values listed below.

  • 200: “OK”
  • 403: “Forbidden”
  • 404: “Page not found”

When readyState is 4 and status is 200, the response is complete, and the content of the element with id ‘webpage’ (div containing temperature and humidity values) is updated.

ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.getElementById(\"webpage\").innerHTML =this.responseText}\n";
ptr +="};\n";

The open() and send() functions are then used to initiate the HTTP request.

ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";

Want to display sensor readings in your ESP8266 projects without resorting to serial output? Then an I2C LCD display might be a better choice for you! It consumes only two GPIO pins which can also be shared with other I2C devices.

Hardware Overview

A typical I2C LCD display consists of an HD44780 based character LCD display and an I2C LCD adapter. Let us get to know them one by one.

Character LCD Display

True to their name, these LCDs are ideal for displaying only text/characters. A 16×2 character LCD, for example, has an LED backlight and can display 32 ASCII characters in two rows of 16 characters each.

character lcd internal pixel grid structure

If you look closely you can see tiny rectangles for each character on the display and the pixels that make up a character. Each of these rectangles is a grid of 5×8 pixels.

I2C LCD Adapter

At the heart of the adapter is an 8-bit I/O expander chip – PCF8574. This chip converts the I2C data from an ESP8266 into the parallel data required for an LCD display.

pcf8574 chip on i2c lcd

The board also comes with a small trimpot to make fine adjustments to the display’s contrast.

i2c lcd adapter hardware overview

In addition, there is a jumper on the board that supplies power to the backlight. To control the intensity of the backlight, you can remove the jumper and apply external voltage to the header pin that is marked ‘LED’.

I2C Address of LCD

If you are using multiple devices on the same I2C bus, you may need to set a different I2C address for the LCD adapter so that it does not conflict with another I2C device.

To do this, the adapter has three solder jumpers (A0, A1 and A2) or solder pads.

i2c address selection jumpers on i2c lcd

Each of these is used to hardcode the address. If a jumper is shorted with a blob of solder, it sets the address.

An important point here is that several companies manufacture the same PCF8574 chip, Texas Instruments and NXP Semiconductors, to name a few. And the I2C address of your LCD depends on the chip manufacturer.

If your LCD has Texas Instruments’ PCF8574 chip:

According to the Texas Instruments’ datasheet, the three address selection bits (A0, A1 and A2) are placed at the end of the 7-bit I2C address register.

texas instruments pcf8574 i2c address register

Since there are 3 address inputs, which can take 2 states, either HIGH/LOW, we can therefore create 8 (23) different combinations (addresses).

By default, all 3 address inputs are pulled HIGH using onboard pullups, giving the PCF8574 a default I2C address of 0100111Binary or 0x27Hex.

By shorting the solder jumpers, the address inputs are pulled LOW. If you were to short all three jumpers, the address would be 0x20. The range of all possible addresses spans from 0x20 to 0x27. Please see the illustration below.

i2c lcd address selection jumper table for ti

If your LCD has NXP’s PCF8574 chip:

According to the NXP Semiconductors’ datasheet, the three address selection bits (A0, A1 and A2) are also placed at the end of the 7-bit I2C address register. But the other bits in the address register are different.

nxp semiconductors pcf8574 i2c address register

Since there are 3 address inputs, which can take 2 states, either HIGH/LOW, we can therefore create 8 (23) different combinations (addresses).

By default, all 3 address inputs are pulled HIGH using onboard pullups, giving the PCF8574 a default I2C address of 0111111Binary or 0x3FHex.

By shorting the solder jumpers, the address inputs are pulled LOW. If you were to short all three jumpers, the address would be 0x38. The range of all possible addresses spans from 0x38 to 0x3F. Please see the illustration below.

i2c lcd address selection jumper table for nxp

So your LCD probably has a default I2C address 0x27Hex or 0x3FHex. However it is recommended that you find out the actual I2C address of the LCD before using it.

Luckily there’s an easy way to do this. We will see that later in the tutorial.

I2C LCD display Pinout

An I2C LCD has only 4 pins that connect it to the outside world. The connections are as follows:

i2c lcd display pinout

GND is a ground pin. Connect it to the ground of the ESP8266.

VCC supplies power to the module and LCD. Connect it to the ESP8266’s VIN pin or an external 5V power supply.

SDA is the I2C data pin. Connect it to the ESP8266’s I2C data pin.

SCL is the I2C clock pin. Connect it to the ESP8266’s I2C clock pin.

Wiring an I2C LCD Display to an ESP8266

Connecting I2C LCD to ESP8266 is very easy as you only need to connect 4 pins. Start by connecting the VCC pin to the VIN on the ESP8266 and GND to ground.

Now we are left with the pins which are used for I2C communication. We are going to use the default I2C pins (GPIO#4 and GPIO#5) of the ESP8266. Connect the SDA pin to the ESP8266’s D2 (GPIO#4) and the SCL pin to the ESP8266’s D1 (GPIO#5).

The following table lists the pin connections:

I2C LCDESP8266
VCCVIN
GNDGND
SCLD1
SDAD2

The following diagram shows you how to wire everything up.

wiring i2c lcd display to esp8266

Adjusting The LCD Contrast

After wiring up the LCD you’ll need to adjust the contrast of the display. On the I2C module you will find a potentiometer that you can rotate with a small screwdriver.

Plug in the ESP8266’s USB connector to power the LCD. You will see the backlight lit up. Now as you turn the knob on the potentiometer, you will start to see the first row of rectangles. If that happens, Congratulations! Your LCD is working fine.

testing esp8266 i2c lcd contrast by turning potentiometer

Once this is done, we can start programming the LCD.

Library Installation

To drive an I2C LCD you must first install a library called LiquidCrystal_I2C.

To install the library navigate to Sketch > Include Libraries > Manage Libraries… Wait for Library Manager to download the library index and update the list of installed libraries.

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by typing ‘liquidcrystal‘. There should be some entries. Look for the LiquidCrystal I2C library by Marco Schwartz. Click on that entry, and then select Install.

liquidcrystal_i2c library installation

Determining the I2C Address

The I2C address of your LCD depends on the manufacturer, as mentioned earlier. If your LCD has a Texas Instruments’ PCF8574 chip, its default I2C address is 0x27Hex. If your LCD has NXP Semiconductors’ PCF8574 chip, its default I2C address is 0x3FHex.

So your LCD probably has I2C address 0x27Hex or 0x3FHex. However it is recommended that you find out the actual I2C address of the LCD before using it. Luckily there’s an easy way to do this. Below is a simple I2C scanner sketch that scans your I2C bus and returns the address of each I2C device it finds.

#include <Wire.h>

void setup() {
  Serial.begin (115200);

  // Leonardo: wait for serial port to connect
  while (!Serial) 
    {
    }

  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}

After uploading the code, open the serial monitor at a baud rate of 115200 and press the EN button on the ESP8266. You will see the I2C address of your I2C LCD display.

i2c address scanner output

Please note this address. You will need it in later examples.

Basic Example Code – Hello World!

The following test sketch will print ‘Hello World!’ on the first line of the LCD and ‘LCD Tutorial’ on the second line.

But, before you proceed to upload the sketch, you need to make a small change to make it work for you. You must pass the I2C address of your LCD and the dimensions of the display to the constructor of the LiquidCrystal_I2C class. If you are using a 16×2 character LCD, pass the 16 and 2; If you’re using a 20×4 LCD, pass 20 and 4. You got the point!

// enter the I2C address and the dimensions of your LCD here
LiquidCrystal_I2C lcd(0x3F, 16, 2);

Once you are done go ahead and give the sketch a try.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F,16,2);  // set the LCD address to 0x3F for a 16 chars and 2 line display

void setup() {
  lcd.init();
  lcd.clear();         
  lcd.backlight();      // Make sure backlight is on
  
  // Print a message on both lines of the LCD.
  lcd.setCursor(2,0);   //Set cursor to character 2 on line 0
  lcd.print("Hello world!");
  
  lcd.setCursor(2,1);   //Move cursor to character 2 on line 1
  lcd.print("LCD Tutorial");
}

void loop() {
}

If all goes well, you should see something like this on the display.

Interfacing 16x2 character LCD with Arduino Hello world Program output

Code Explanation:

The sketch begins by including the LiquidCrystal_I2C library.

#include <LiquidCrystal_I2C.h>

First of all an object of LiquidCrystal_I2C class is created. This object takes three parameters LiquidCrystal_I2C(address, columns, rows). This is where you need to enter the address you found earlier, and the dimensions of the display.

LiquidCrystal_I2C lcd(0x3F,16,2);

Once you have declared a LiquidCrystal_I2C object, you can access the object methods specific to the LCD.

In ‘setup’ we call three functions. The first function is init(). It initializes the LCD object. The second function is clear(). This clears the LCD screen and moves the cursor to the top left corner. And third, the backlight() function turns on the LCD backlight.

lcd.init();
lcd.clear();         
lcd.backlight();

After that we set the cursor position to the third column of the first row by calling the function lcd.setCursor(2, 0). The cursor position specifies the location where you want the new text to be displayed on the LCD. The upper left corner is assumed to be col=0, row=0.

lcd.setCursor(2,0);

Next, the string ‘Hello World!’ is printed by calling the print() function.

lcd.print("Hello world!");

Similarly the next two lines of code set the cursor position to the third column of the second row, and print ‘LCD Tutorial’ on the LCD.

lcd.setCursor(2,1);
lcd.print("LCD Tutorial");

Other useful functions of the Library

There are some useful functions you can use with LiquidCrystal_I2C objects. Some of them are listed below:

  • lcd.home() function is used to position the cursor in the upper-left of the LCD without clearing the display.
  • lcd.blink() function displays a blinking block of 5×8 pixels at the position at which the next character is to be written.
  • lcd.cursor() displays an underscore (line) at the position at which the next character is to be written.
  • lcd.noBlink() function turns off the blinking LCD cursor.
  • lcd.noCursor() hides the LCD cursor.
  • lcd.scrollDisplayRight() function scrolls the contents of the display one space to the right. If you want the text to scroll continuously, you have to use this function inside a for loop.
  • lcd.scrollDisplayLeft() function scrolls the contents of the display one space to the left. Similar to above function, use this inside a for loop for continuous scrolling.

Create and Display Custom Characters

If you find the characters on the display dull and boring, you can create your own custom characters (glyphs) and symbols for your LCD. They are extremely useful when you want to display a character that is not part of the standard ASCII character set.

As discussed earlier in this tutorial a character is made up of a 5×8 pixel matrix, so you need to define your custom character within that matrix. You can use the createChar() function to define a character.

To use createChar() you first set up an array of 8 bytes. Each byte in the array represents a row of characters in a 5×8 matrix. Whereas, 0 and 1 in a byte indicate which pixel in the row should be ON and which should be OFF.

All these user defined characters are stored in the CGRAM of the LCD.

CGROM and CGRAM

All Hitachi HD44780 controller based LCDs have two types of memory – CGROM and CGRAM (Character Generator ROM and RAM).

CGROM is non-volatile memory and cannot be modified whereas CGRAM is volatile memory and can be modified at any time.

CGROM is used to store all permanent fonts that are displayed using their ASCII codes. For example, if we send 0x41 to the LCD, the letter ‘A’ will be printed on the display.

CGRAM is another memory used to store user defined characters. This RAM is limited to 64 bytes. For a 5×8 pixel based LCD, only 8 user-defined characters can be stored in CGRAM. And for 5×10 pixel based LCD only 4 user-defined characters can be stored.

Custom Character Generator

Creating custom characters has never been easier! We have created a small application called Custom Character Generator. Can you see the blue grid below? You can click on any 5×8 pixel to set/clear that particular pixel. And as you click, the code for the character is generated next to the grid. This code can be used directly in your ESP8266 sketch.

byte Character[8] =
{
0b00000, 0b00000, 0b01010, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000
};

Your imagination is limitless. The only limitation is that the LiquidCrystal_I2C library only supports eight custom characters. But don’t be discouraged, look at the bright side, at least we have eight characters.

ESP8266 Example Code

The following sketch shows how you can create custom characters and print them on an LCD.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F, 16, 2);  // set the LCD address to 0x3F for a 16 chars and 2 line display

// make some custom characters:
byte Heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

byte Bell[8] = {
0b00100,
0b01110,
0b01110,
0b01110,
0b11111,
0b00000,
0b00100,
0b00000
};


byte Alien[8] = {
0b11111,
0b10101,
0b11111,
0b11111,
0b01110,
0b01010,
0b11011,
0b00000
};

byte Check[8] = {
0b00000,
0b00001,
0b00011,
0b10110,
0b11100,
0b01000,
0b00000,
0b00000
};

byte Speaker[8] = {
0b00001,
0b00011,
0b01111,
0b01111,
0b01111,
0b00011,
0b00001,
0b00000
};


byte Sound[8] = {
0b00001,
0b00011,
0b00101,
0b01001,
0b01001,
0b01011,
0b11011,
0b11000
};


byte Skull[8] = {
0b00000,
0b01110,
0b10101,
0b11011,
0b01110,
0b01110,
0b00000,
0b00000
};

byte Lock[8] = {
0b01110,
0b10001,
0b10001,
0b11111,
0b11011,
0b11011,
0b11111,
0b00000
};

void setup() 
{
  lcd.init();
  // Make sure backlight is on       
  lcd.backlight();

  // create a new characters
  lcd.createChar(0, Heart);
  lcd.createChar(1, Bell);
  lcd.createChar(2, Alien);
  lcd.createChar(3, Check);
  lcd.createChar(4, Speaker);
  lcd.createChar(5, Sound);
  lcd.createChar(6, Skull);
  lcd.createChar(7, Lock);

  // Clears the LCD screen
  lcd.clear();

  // Print a message to the lcd.
  lcd.print("Custom Character");
}

// Print All the custom characters
void loop() 
{ 
  lcd.setCursor(0, 1);
  lcd.write(0);

  lcd.setCursor(2, 1);
  lcd.write(1);

  lcd.setCursor(4, 1);
  lcd.write(2);

  lcd.setCursor(6, 1);
  lcd.write(3);

  lcd.setCursor(8, 1);
  lcd.write(4);

  lcd.setCursor(10, 1);
  lcd.write(5);

  lcd.setCursor(12, 1);
  lcd.write(6);

  lcd.setCursor(14, 1);
  lcd.write(7);
}

You will see the following output on the LCD:

Interfacing 16x2 LCD with Arduino Custom Character Generation Program output

Code Explanation:

After the library is included and the LCD object is created, custom character arrays are defined. The array consists of 8 bytes, each byte representing a row of a 5×8 LED matrix. In this sketch, eight custom characters have been created.

Let’s examine the Heart[8] array as an example. You can see how the bits (0s and 1s) are forming a heart shape. 0 turns the pixel off and 1 turns the pixel on.

byte Heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

In setup, a custom character is created using the createChar() function. This function takes two parameters. The first parameter is a number between 0 and 7 to reserve one of the 8 supported custom characters. The second is the name of the array.

lcd.createChar(0, Heart);

Next in the loop, to display the custom character we simply use the write() function and pass it the number of the character we reserved earlier.

lcd.setCursor(0, 1);
lcd.write(0);

Do you want to spice up your ESP8266 IoT projects with some graphics? Or perhaps you want to display the IP address of your ESP8266 without resorting to the serial output. These incredibly cool OLED (Organic Light-Emitting Diode) displays may be just what you need! They’re extremely light, almost paper-thin, theoretically flexible, and produce a brighter, crisper image.

OLED Display Module Overview

OLED displays are available in a range of sizes (such as 128×64, 128×32) and colors (such as white, blue, and dual-color OLEDs). Some OLED displays have an I2C interface, while others have an SPI interface.

In this tutorial, we’ll be using a 0.96-inch 128×64 I2C OLED display. Don’t worry if your module is a different size or color; the information on this page is still useful.

128x64 Blue I2C OLED Display

At the heart of the module is a powerful single-chip CMOS OLED driver controller – SSD1306, which handles all RAM buffering, requiring very little work from your ESP8266. Also, the SSD1306 controller’s operating voltage ranges from 1.65V to 3.3V, making it ideal for interfacing with 3.3V microcontrollers such as the ESP8266.

An OLED display, unlike a character LCD display, does not require a backlight because it generates its own light. This explains the display’s high contrast, extremely wide viewing angle, and ability to display deep black levels.

The absence of a backlight reduces power consumption significantly. A typical OLED display uses about 20mA on average, though this varies depending on how much of the display is lit.

Because of their high contrast, these OLED displays are highly readable and can display impressively detailed graphics.

OLED Memory Map

In order to control the display, it is crucial to understand the memory map of the OLED display.

Regardless of the size of the OLED display, the SSD1306 driver includes a 1KB Graphic Display Data RAM (GDDRAM) that stores the bit pattern to be displayed on the screen. This 1 KB memory area is divided into 8 pages (from 0 to 7). Each page has 128 columns/segments (block 0 to 127). And, each column can store 8 bits of data (from 0 to 7). That certainly proves that we have:

8 pages x 128 segments x 8 bits of data = 8192 bits = 1024 bytes = 1KB memory

The entire 1K memory, including pages, segments, and data, is highlighted below.

1KB 128x64 OLED Display RAM Memory Map

Each bit represents a single OLED pixel on the screen that can be turned ON or OFF programmatically.

As previously stated, regardless of the size of the OLED module, each module contains 1KB of RAM. The 128×64 OLED module displays the entire contents of 1KB of RAM (all 8 pages), whereas the 128×32 OLED module displays only half of the RAM contents (the first 4 pages).

Wiring an OLED display module to an ESP8266 NodeMCU

Let’s hook the OLED display to the ESP8266 NodeMCU.

Connections are straightforward. Begin by connecting the VCC pin to the NodeMCU’s 3.3V output and the GND pin to ground.

Finally, connect the SCL and SDA pins to the ESP8266’s I2C pins D1 and D2, respectively.

The following table lists the pin connections:

OLED DisplayESP8266
VCC3.3V
GNDGND
SCLD1
SDAD2

The diagram below shows how to connect everything.

Fritzing Wiring OLED Display with ESP8266 NodeMCU

Library Installation

The SSD1306 controller of the OLED display has flexible but complex drivers. To use the SSD1306 controller, extensive knowledge of memory addressing is required. Fortunately, the Adafruit SSD1306 library was written to hide the complexities of the SSD1306 controller, allowing us to control the display with simple commands.

To install the library, navigate to Sketch > Include Library > Manage Libraries… Wait for the Library Manager to download the library index and update the list of installed libraries.

Manage Libraries

Filter your search by typing ‘adafruit ssd1306’. There should be a few entries. Look for Adafruit SSD1306 by Adafruit. Click on that entry and then choose Install.

Installing Adafruit SSD1306 Monochrome OLED Display Library

This is a hardware-specific library that handles lower-level functions. To display graphics primitives such as points, lines, circles, and rectangles, it must be paired with the Adafruit GFX Library. Install this library as well.

Installing Adafruit GFX Graphics Core Library

Internally, the Adafruit SSD1306 library makes use of the Adafruit Bus IO Library. So, look for Adafruit BusIO in the library manager and install it as well.

Adafruit BusIO Library Installation

ESP8266 Example Code 1 – Displaying Text

Here’s a simple sketch that will do the following:

  • Display simple text
  • Display inverted text
  • Display numbers
  • Display numbers with base (Hex, Dec)
  • Display ASCII symbols
  • Scroll text horizontally and vertically
  • Scroll part of the display

This sketch will provide you with a thorough understanding of how to use the OLED display and can serve as the foundation for more practical experiments and projects. Try out the sketch, and then we’ll go over it in detail.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using I2C
#define OLED_RESET     -1 // Reset pin
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup()
{
  Serial.begin(9600);
  
  // initialize the OLED object
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Clear the buffer.
  display.clearDisplay();

  // Display Text
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,28);
  display.println("Hello world!");
  display.display();
  delay(2000);
  display.clearDisplay();

  // Display Inverted Text
  display.setTextColor(BLACK, WHITE); // 'inverted' text
  display.setCursor(0,28);
  display.println("Hello world!");
  display.display();
  delay(2000);
  display.clearDisplay();

  // Changing Font Size
  display.setTextColor(WHITE);
  display.setCursor(0,24);
  display.setTextSize(2);
  display.println("Hello!");
  display.display();
  delay(2000);
  display.clearDisplay();

  // Display Numbers
  display.setTextSize(1);
  display.setCursor(0,28);
  display.println(123456789);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Specifying Base For Numbers
  display.setCursor(0,28);
  display.print("0x"); display.print(0xFF, HEX); 
  display.print("(HEX) = ");
  display.print(0xFF, DEC);
  display.println("(DEC)"); 
  display.display();
  delay(2000);
  display.clearDisplay();

  // Display ASCII Characters
  display.setCursor(0,24);
  display.setTextSize(2);
  display.write(3);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Scroll full screen
  display.setCursor(0,0);
  display.setTextSize(1);
  display.println("Full");
  display.println("screen");
  display.println("scrolling!");
  display.display();
  display.startscrollright(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);    
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  display.clearDisplay();

  // Scroll part of the screen
  display.setCursor(0,0);
  display.setTextSize(1);
  display.println("Scroll");
  display.println("some part");
  display.println("of the screen.");
  display.display();
  display.startscrollright(0x00, 0x00);
}

void loop() {
}

This is what the output looks like.

OLED Display Example 1 Output

Code Explanation:

The sketch begins with the inclusion of four libraries: SPI.h, Wire.h, Adafruit_GFX.h, and Adafruit_SSD1306.h. Although the SPI.h library is not required for I2C OLED displays, we must include it to compile our program.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

The next step is to create an object of Adafruit_SSD1306.h. The Adafruit_SSD1306 constructor accepts 3 arguments: screen width, screen height, and the ESP8266 pin number to which the display’s reset pin is connected. So, a couple of constants are defined.

Also, since the OLED display we are using doesn’t have a RESET pin, we send -1 to the constructor to indicate that none of the ESP8266 pins are used to reset the display.

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using I2C
#define OLED_RESET     -1 // Reset pin
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

In the setup function, we initialize the OLED object using the begin() function. This function accepts two parameters. The first parameter, SSD1306_SWITCHCAPVCC, turns on the internal charge pump circuitry, and the second parameter sets the OLED display’s I2C address. Most OLED display modules of this type have an I2C address of 0x3C, but some have 0x3D.

After that, we clear the buffer before printing our first message to the screen.

// initialize the OLED object
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
	Serial.println(F("SSD1306 allocation failed"));
	for(;;); // Don't proceed, loop forever
}

// Clear the buffer.
display.clearDisplay();

Displaying simple Text (Hello World)

Displaying Text On OLED Dsiplay Module
// Display Text
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,28);
display.println("Hello world!");
display.display();
delay(2000);

To display text on the screen, we must first set the font size. This can be accomplished by calling setTextSize() and passing a font size (starting from 1) as a parameter.

Next, we set the font color by calling the function setTextColor(). Pass WHITE for a dark background and BLACK for a bright one.

Before printing the message, we must first set the cursor position by calling the setCursor(X,Y) function. Pixels on the screen are referenced by their horizontal (X) and vertical (Y) coordinates. The origin (0,0) is located in the upper left corner, with positive X increasing to the right and positive Y increasing downward.

To print the message on the screen, we can use the print(" ") or println(" ") functions, similar to how we print data on the serial monitor. Keep in mind that println() will move the cursor to the next line.

The final step is to use the display() command to instruct the library to bulk transfer the screen buffer to the SSD1306 controller’s internal memory and display the contents on the OLED screen.

Displaying Inverted Text

Displaying Inverted Text On OLED Dsiplay Module
// Display Inverted Text
display.clearDisplay();
display.setTextColor(BLACK, WHITE); // 'inverted' text
display.setCursor(0,28);
display.println("Hello world!");
display.display();
delay(2000);

To display inverted text, we use the setTextColor(FontColor, BackgroundColor) function. If you’ve been paying attention, you’ll notice that we previously passed only one parameter to this function, but now we’re passing two. This is possible due to function overloading.

In this case, using setTextColor(BLACK, WHITE) results in black text on a filled background.

Scaling Font Size

Changing Font Size On OLED Dsiplay Module
// Changing Font Size
display.clearDisplay();
display.setTextColor(WHITE);
display.setCursor(0,24);
display.setTextSize(2);
display.println("Hello!");
display.display();
delay(2000);

Earlier in this tutorial, we used the setTextSize() function to change the font size, passing 1 as a parameter. You can scale the font by passing any non-negative integer to this function.

Characters are rendered in a 7:10 ratio. In other words, passing font size 1 renders the text at 7×10 pixels per character, passing font size 2 renders the text at 14×20 pixels per character, and so on.

Displaying Numbers

Displaying Numbers On OLED Dsiplay Module
// Display Numbers
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0,28);
display.println(123456789);
display.display();
delay(2000);

The print() or println() functions can be used to display numbers on the OLED display. Because an overloaded implementation of these functions accepts 32-bit unsigned int values, you can only display numbers ranging from 0 to 4,294,967,295.

Specifying Base For Numbers

Displaying HEX, Decimal, OCT, Binary On OLED Dsiplay Module
// Specifying Base For Numbers
display.clearDisplay();
display.setCursor(0,28);
display.print("0x"); display.print(0xFF, HEX); 
display.print("(HEX) = ");
display.print(0xFF, DEC);
display.println("(DEC)"); 
display.display();
delay(2000);

The print() and println() functions have an optional second parameter that specifies the base (format); valid values are BIN (binary, or base 2), OCT (octal, or base 8), DEC (decimal, or base 10) and HEX (hexadecimal, or base 16). For floating-point numbers, this parameter specifies the number of decimal places to use. For instance:

  • print(78, BIN) prints “1001110”
  • print(78, OCT) prints “116”
  • print(78, DEC) prints “78”
  • print(78, HEX) prints “4E”
  • println(1.23456, 0) prints “1”
  • println(1.23456, 2) prints “1.23”
  • println(1.23456, 4) prints “1.2346”

Displaying ASCII Symbols

Displaying ASCII Symbols On OLED Dsiplay Module
// Display ASCII Characters
display.clearDisplay();
display.setCursor(0,24);
display.setTextSize(2);
display.write(3);
display.display();
delay(2000);

The print() and println() functions send data to the display as human-readable ASCII text, whereas the write() function sends binary data to the display. This function can thus be used to display ASCII symbols. For example, sending 3 displays a heart symbol.

Full Screen Scrolling

Full-Screen-Scrolling-On-OLED-Dsiplay-Module
// Scroll full screen
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.println("Full");
display.println("screen");
display.println("scrolling!");
display.display();
display.startscrollright(0x00, 0x07);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrollleft(0x00, 0x07);
delay(2000);
display.stopscroll();
delay(1000);    
display.startscrolldiagright(0x00, 0x07);
delay(2000);
display.startscrolldiagleft(0x00, 0x07);
delay(2000);
display.stopscroll();

You can scroll the display horizontally by calling the functions startscrollright() and startscrollleft(), and diagonally by calling the functions startscrolldiagright() and startscrolldiagleft(). All of these functions take two parameters: start page and stop page. Refer to the OLED Memory Map section for an explanation of the pages. Because the display has eight pages from 0 to 7, you can scroll the entire screen by scrolling all the pages, i.e. passing parameters 0x00 and 0x07.

The stopscroll() function can be used to stop the display from scrolling.

Scrolling Specific Part

Scrolling Part Of The Screen On OLED Dsiplay Module
// Scroll part of the screen
display.setCursor(0,0);
display.setTextSize(1);
display.println("Scroll");
display.println("some part");
display.println("of the screen.");
display.display();
display.startscrollright(0x00, 0x00);

Sometimes, we don’t want to scroll the whole display, but just a part of it. You can accomplish this by passing the appropriate start and stop page information to the scrolling functions.

Passing 0x00 for both parameters will only scroll the first page of the display.

ESP8266 Example Code 2 – Basic Drawings

Here’s a simple sketch that demonstrates how to draw basic shapes like rectangles, circles, and triangles. Try out the sketch, and then we’ll go over it in detail.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using I2C
#define OLED_RESET     -1 // Reset pin
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup()
{
  Serial.begin(9600);
  
  // initialize the OLED object
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Clear the buffer.
  display.clearDisplay();

  // Draw Rectangle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Rectangle");
  display.drawRect(0, 15, 60, 40, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Filled Rectangle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Filled Rectangle");
  display.fillRect(0, 15, 60, 40, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Round Rectangle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Round Rectangle");
  display.drawRoundRect(0, 15, 60, 40, 8, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Filled Round Rectangle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Filled Round Rectangl");
  display.fillRoundRect(0, 15, 60, 40, 8, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Circle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Circle");
  display.drawCircle(20, 35, 20, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Filled Circle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Filled Circle");
  display.fillCircle(20, 35, 20, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Triangle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Triangle");
  display.drawTriangle(30, 15, 0, 60, 60, 60, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Draw Filled Triangle
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Filled Triangle");
  display.fillTriangle(30, 15, 0, 60, 60, 60, WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();
}

void loop() {
}

This is what the output looks like.

OLED Display Example 2 Output

Most of the code (setting up the display) is the same as the above code example, except for the following code snippets that draw basic shapes.

Drawing Rectangle

Drawing Rectangle On OLED Dsiplay Module
Drawing Filled Rectangle On OLED Dsiplay Module
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Rectangle");
display.drawRect(0, 15, 60, 40, WHITE);
display.display();
delay(2000);

display.clearDisplay();  
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Filled Rectangle");
display.fillRect(0, 15, 60, 40, WHITE);
display.display();
delay(2000);

The drawRect() function can be used to draw a rectangle on the screen. This function accepts five parameters: X and Y coordinates, width, height, and color. This function actually draws a hollow rectangle with a 1 pixel border. The fillRect() function can be used to draw a filled rectangle.

Drawing Round Rectangle

Drawing Round Rectangle On OLED Dsiplay Module
Drawing Filled Round Rectangle On OLED Dsiplay Module
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Round Rectangle");
display.drawRoundRect(0, 15, 60, 40, 8, WHITE);
display.display();
delay(2000);

display.clearDisplay();  
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Filled Round Rectangl");
display.fillRoundRect(0, 15, 60, 40, 8, WHITE);
display.display();
delay(2000);

The drawRoundRect() function can be used to draw a round rectangle on the screen. This function accepts the same parameters as drawRect(), with the exception of one additional parameter – the radius of corner rounding. This function actually draws a hollow round rectangle with a 1 pixel border. The fillRoundRect() function can be used to draw a filled round rectangle.

Drawing Circle

Drawing Circle On OLED Dsiplay Module
Drawing Filled Circle On OLED Dsiplay Module
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Circle");
display.drawCircle(20, 35, 20, WHITE);
display.display();
delay(2000);

display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Filled Circle");
display.fillCircle(20, 35, 20, WHITE);
display.display();
delay(2000);

The drawCircle() function can be used to draw a circle on the screen. This function accepts four parameters: the X coordinate of the center, the Y coordinate of the center, the radius, and the color. This function draws a hollow circle with a 1 pixel border. The fillCircle() function can be used to draw a filled circle.

Drawing Triangle

Drawing Triangle On OLED Dsiplay Module
Drawing Filled Triangle On OLED Dsiplay Module
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Triangle");
display.drawTriangle(30, 15, 0, 60, 60, 60, WHITE);
display.display();
delay(2000);

display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("Filled Triangle");
display.fillTriangle(30, 15, 0, 60, 60, 60, WHITE);
display.display();
delay(2000);

The drawTriangle() function can be used to draw a triangle on the screen. This function accepts seven parameters: three X and Y coordinates (x0, y0, x1, y1, x2 & y2) of triangle vertices and a color. (X0, y0) is the top vertex, (x1, y1) is the left vertex, and (x2, y2) is the right vertex.

This function draws a hollow triangle with a 1 pixel border. The fillTriangle() function can be used to draw a filled triangle.

ESP8266 Example Code 3 – Displaying Bitmap

Our last example shows how to display bitmap images on the OLED display. This comes in handy when displaying things like logos, sprites, infographics, or icons.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using I2C
#define OLED_RESET     -1 // Reset pin
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Bitmap of MarilynMonroe Image
const unsigned char MarilynMonroe [] PROGMEM = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xf0, 0x41, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xf1, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xfc, 0x02, 0x78, 0x7f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfe, 0x03, 0x7c, 0x1f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xfe, 0x01, 0xfe, 0x1f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xfd, 0xe0, 0x03, 0xff, 0xff, 0xfc, 0x00, 0xfe, 0x0f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xfe, 0x87, 0xe0, 0xff, 0xff, 0xfc, 0x00, 0x06, 0x07, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xf9, 0xff, 0xff, 0xfc, 0x00, 0x02, 0x07, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xc3, 0xc3, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xe0, 0x0c, 0x00, 0xe7, 0x81, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xe0, 0x02, 0x00, 0x02, 0x00, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1e, 0x3f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0x3f, 0xf8, 0x00, 0x18, 0x7f, 0x1f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xf8, 0x01, 0x80, 0x03, 0xfc, 0x3f, 0xfc, 0x00, 0x70, 0xfe, 0x1f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xf0, 0x43, 0xff, 0xff, 0xf8, 0x7f, 0xf8, 0x00, 0x00, 0x7e, 0x1f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xf0, 0xff, 0xfc, 0x00, 0x00, 0x7c, 0x3f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf1, 0xef, 0xf8, 0x00, 0x01, 0xfc, 0x3f, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xe4, 0xff, 0xff, 0xff, 0xf3, 0x80, 0xa0, 0x00, 0x07, 0xfc, 0xaf, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xec, 0x5f, 0xff, 0xff, 0xe7, 0xf0, 0x00, 0x00, 0x03, 0xfe, 0xdf, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xee, 0x7f, 0xff,     

Have you ever wanted to have sensors scattered all around your house and garden reporting their temperature regularly to a central server? Then, this IoT project might be the solid launching point for you!

This project uses ESP8266 NodeMCU as the control device that easily connects to existing WiFi network & creates a Web Server. When any connected device accesses this web server, ESP8266 reads in temperature from multiple DS18B20 Temperature sensors & sends it to the web browser of that device with a nice interface. Excited? Let’s get started!

Multiple DS18B20s On Single Bus

One of the biggest features of DS18B20 is that multiple DS18B20s can coexist on the same 1-Wire bus. As each DS18B20 has a unique 64-bit serial code burned in at the factory, it’s easier to differentiate them from one another.

This feature can be a huge advantage when you want to control many DS18B20s distributed over a large area. In this tutorial we are going to do the same.

Wiring Multiple DS18B20 Sensors to ESP8266 NodeMCU

Connecting DS18B20 sensors to ESP8266 NodeMCU is fairly simple.

DS18B20 Pinout

Start by connecting all the DS18B20s in parallel i.e. common all the VDD pins, GND pins & signal pins. Then connect VDD to the 3.3V out, GND to ground and connect signal pin to digital pin D2 on ESP8266 NodeMCU.

Next, you’ll need to add one 4.7k pull-up resistor for whole bus between the signal and power pin to keep the data transfer stable.

Wiring Multiple DS18B20 Temperature Sensors to ESP8266
Wiring Multiple DS18B20 Temperature Sensors to ESP8266

Preparing the Arduino IDE

There’s an add-on for the Arduino IDE that allows you to program the ESP8266 NodeMCU using the Arduino IDE. Follow below tutorial to prepare your Arduino IDE to work with the ESP8266, if you haven’t already.

Installing Library For DS18B20

The Dallas 1-Wire protocol is somewhat complex, and requires a bunch of code to parse out the communication. To hide away this unnecessary complexity we will install DallasTemperature.h library so that we can issue simple commands to get temperature readings from the sensor.

To install the library navigate to the Arduino IDE > Sketch > Include Library > Manage Libraries… Wait for Library Manager to download libraries index and update list of installed libraries.

Filter your search by typing ‘ds18b20’. There should be a couple entries. Look for DallasTemperature by Miles Burton. Click on that entry, and then select Install.

Installing Dallas Temperature Library In Arduino IDE

This Dallas Temperature library is a hardware-specific library which handles lower-level functions. It needs to be paired with One Wire Library to communicate with any one-wire device not just DS18B20. Install this library as well.

Installing OneWire Library In Arduino IDE

Finding Addresses Of DS18B20s On Bus

We know that each DS18B20 has a unique 64-bit address assigned to it to differentiate them from one another. First, we’ll find that address to label each sensor accordingly. The address can then be used to read each sensor individually.

The following sketch detects all the DS18B20s present on the bus and prints their one-wire address on the serial monitor.

You can wire just one sensor at a time to find its address (or successively add a new sensor) so that you’re able to identify each one by its address. Then, you can label each sensor.

#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port D2 on the ESP8266
#define ONE_WIRE_BUS D2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// variable to hold device addresses
DeviceAddress Thermometer;

int deviceCount = 0;

void setup(void)
{
  // start serial port
  Serial.begin(115200);

  // Start up the library
  sensors.begin();

  // locate devices on the bus
  Serial.println("Locating devices...");
  Serial.print("Found ");
  deviceCount = sensors.getDeviceCount();
  Serial.print(deviceCount, DEC);
  Serial.println(" devices.");
  Serial.println("");
  
  Serial.println("Printing addresses...");
  for (int i = 0;  i < deviceCount;  i++)
  {
    Serial.print("Sensor ");
    Serial.print(i+1);
    Serial.print(" : ");
    sensors.getAddress(Thermometer, i);
    printAddress(Thermometer);
  }
}

void loop(void)
{ }

void printAddress(DeviceAddress deviceAddress)
{ 
  for (uint8_t i = 0; i < 8; i++)
  {
    Serial.print("0x");
    if (deviceAddress[i] < 0x10) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
    if (i < 7) Serial.print(", ");
  }
  Serial.println("");
}

Now, open the serial monitor. You should get something as follows.

Finding One-Wire Address Of All DS18B20 On the Bus

Copy all the addresses as we need them in out next sketch.

Create ESP8266 Web Server In Station (STA) mode

Now, we are going to configure our ESP8266 into Station (STA) mode, and create a web server to serve up web pages to any connected client under existing network.

If you want to learn about creating a web server with ESP8266 NodeMCU in AP/STA mode, check this tutorial out.

Before you head for uploading the sketch, you need to make some changes to make it work for you.

  • You need to modify the following two variables with your network credentials, so that ESP8266 can establish a connection with existing network.
    const char* ssid = "YourNetworkName";  // Enter SSID here
    const char* password = "YourPassword";  //Enter Password here
  • Before serving up a web page ESP8266 reads the temperature from each DS18B20 by its address so, you need to change the addresses of DS18B20s with the one you’ve found in previous sketch.
    uint8_t sensor1[8] = { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC };
    uint8_t sensor2[8] = { 0x28, 0x61, 0x64, 0x12, 0x3C, 0x7C, 0x2F, 0x27 };
    uint8_t sensor3[8] = { 0x28, 0x61, 0x64, 0x12, 0x3F, 0xFD, 0x80, 0xC6 };

Once you are done, go ahead and try the sketch out.

#include <ESP8266WebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port D2 on the ESP8266
#define ONE_WIRE_BUS D2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

float tempSensor1, tempSensor2, tempSensor3;

uint8_t sensor1[8] = { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC  };
uint8_t sensor2[8] = { 0x28, 0x61, 0x64, 0x12, 0x3C, 0x7C, 0x2F, 0x27  };
uint8_t sensor3[8] = { 0x28, 0x61, 0x64, 0x12, 0x3F, 0xFD, 0x80, 0xC6  };

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

ESP8266WebServer server(80);             
 
void setup() {
  Serial.begin(115200);
  delay(100);
  
  sensors.begin();              

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
}
void loop() {
  server.handleClient();
}

void handle_OnConnect() {
  sensors.requestTemperatures();
  tempSensor1 = sensors.getTempC(sensor1); // Gets the values of the temperature
  tempSensor2 = sensors.getTempC(sensor2); // Gets the values of the temperature
  tempSensor3 = sensors.getTempC(sensor3); // Gets the values of the temperature
  server.send(200, "text/html", SendHTML(tempSensor1,tempSensor2,tempSensor3)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>ESP8266 Temperature Monitor</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<div id=\"webpage\">\n";
  ptr +="<h1>ESP8266 Temperature Monitor</h1>\n";
  ptr +="<p>Living Room: ";
  ptr +=tempSensor1;
  ptr +="&deg;C</p>";
  ptr +="<p>Bedroom: ";
  ptr +=tempSensor2;
  ptr +="&deg;C</p>";
  ptr +="<p>Kitchen: ";
  ptr +=tempSensor3;
  ptr +="&deg;C</p>";
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server

After uploading the sketch, open the Serial Monitor at a baud rate of 115200. And press the RST button on NodeMCU . If everything is OK, it will output the dynamic IP address obtained from your router and show HTTP server started message.

ESP8266 Station Mode Web Server IP Address On Serial Monitor

Next, load up a browser and point it to the IP address shown on the serial monitor. The ESP8266 should serve up a web page showing temperatures from all the DS18B20s.

Multiple DS18B20 Readings on ESP32 Web Server - Without CSS

Detailed Code Explanation

The sketch starts by including following libraries.

  • ESP8266WebServer.h library provides ESP8266 specific WiFi methods we are calling to connect to network. It also has some methods available that will help us setting up a server and handle incoming HTTP requests without needing to worry about low level implementation details.
  • DallasTemperature.h library is a hardware-specific library which handles lower-level functions. It needs to be paired with One Wire Library, in order to make it work.
  • OneWire.h library communicates with any one-wire device not just DS18B20.
#include <ESP8266WebServer.h>
#include <OneWire.h>
#include <DallasTemperature.h>

Next we create the instances needed for the temperature sensor and variables to store temperature readings. The temperature sensor is connected to GPIO D2.

// Data wire is plugged into port D2 on the ESP8266
#define ONE_WIRE_BUS D2

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

float tempSensor1, tempSensor2, tempSensor3;

Next, we enter the addresses that are found previously for each temperature sensor. In our case, we have the following.

uint8_t sensor1[8] = { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC  };
uint8_t sensor2[8] = { 0x28, 0x61, 0x64, 0x12, 0x3C, 0x7C, 0x2F, 0x27  };
uint8_t sensor3[8] = { 0x28, 0x61, 0x64, 0x12, 0x3F, 0xFD, 0x80, 0xC6  };

As we are configuring ESP8266 in Station (STA) mode, it will join existing WiFi network. Hence, we need to provide it with your network’s SSID & Password. Next we start web server at port 80.

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

ESP8266WebServer server(80);

Inside Setup() Function

Inside Setup() Function we configure our HTTP server before actually running it. First of all, we initialize serial communication with PC and initialize DallasTemperature object using begin() function. It initializes the bus and detects all the DS18B20s present on it. Each sensor is then assigned with an index and set bit resolution to 12-bit.

Serial.begin(115200);
delay(100);
  
sensors.begin(); 

Now, we need to join the WiFi network using WiFi.begin() function. The function takes SSID (Network Name) and password as a parameter.

Serial.println("Connecting to ");
Serial.println(ssid);

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP8266 tries to connect to the network, we can check the connectivity status with WiFi.status() function.

//check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }

Once the ESP8266 is connected to the network, the sketch prints the IP address assigned to ESP8266 by displaying WiFi.localIP() value on serial monitor.

Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

In order to handle incoming HTTP requests, we need to specify which code to execute when a URL is hit. To do so, we use on method. This method takes two parameters. First one is a URL path and second one is the name of function which we want to execute when that URL is hit.

The code below indicates that when a server receives an HTTP request on the root (/) path, it will trigger the handle_OnConnect function. Note that the URL specified is a relative path.

server.on("/", handle_OnConnect);

We haven’t specified what the server should do if the client requests any URL other than specified with server.on. It should respond with an HTTP status 404 (Not Found) and a message for the user. We put this in a function as well, and use server.onNotFound to tell it that it should execute it when it receives a request for a URL that wasn’t specified with server.on

server.onNotFound(handle_NotFound);

Now, to start our server, we call the begin method on the server object.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

To handle the actual incoming HTTP requests, we need to call the handleClient() method on the server object.

server.handleClient();

Next, we need to create a function we attached to root (/) URL with server.on Remember?

At the start of this function, we get the temperature reading from each sensor. In order to respond to the HTTP request, we use the send method. Although the method can be called with a different set of arguments, its simplest form consists of the HTTP response code, the content type and the content.

In our case, we are sending the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then, we are specifying the content type as “text/html“, and finally we are calling SendHTML() custom function which creates a dynamic HTML page containing temperature readings.

void handle_OnConnect() {
  sensors.requestTemperatures();
  tempSensor1 = sensors.getTempC(sensor1);
  tempSensor2 = sensors.getTempC(sensor2);
  tempSensor3 = sensors.getTempC(sensor3);
  server.send(200, "text/html", SendHTML(tempSensor1,tempSensor2,tempSensor3)); 
}

Likewise, we need to create a function to handle 404 Error page.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

SendHTML() function is responsible for generating a web page whenever the ESP8266 web server gets a request from a web client. It merely concatenates HTML code into a big string and returns to the server.send() function we discussed earlier. The function takes temperature readings as a parameter to dynamically generate the HTML content.

The first text you should always send is the <!DOCTYPE> declaration that indicates that we’re sending HTML code.

String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){
  String ptr = "<!DOCTYPE html> <html>\n";

Next, the <meta> viewport element makes the web page responsive in any web browser, while title tag sets the title of the page.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>ESP8266 Temperature Monitor</title>\n";

Styling the Web Page

Next, we have some CSS to style the web page appearance. We choose the Helvetica font, define the content to be displayed as an inline-block and aligned at the center.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

Following code then sets color, font and margin around the body, H1 and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Setting the Web Page Heading

Next, heading of the web page is set; you can change this text to anything that suits your application.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP8266 Temperature Monitor</h1>\n";

Displaying Temperature readings on Web Page

To dynamically display temperature readings, we put those values in paragraph tag. To display degree symbol, we use HTML entity &deg;

ptr +="<p>Living Room: ";
ptr +=tempSensor1;
ptr +="&deg;C</p>";
ptr +="<p>Bedroom: ";
ptr +=tempSensor2;
ptr +="&deg;C</p>";
ptr +="<p>Kitchen: ";
ptr +=tempSensor3;
ptr +="&deg;C</p>";
ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Styling Web Page to Look More Professional

Programmers like us are often intimidated by design – but a little effort can make your web page look more attractive and professional. Below screenshot will give you a basic idea of what we are going to do.

Multiple DS18B20 Readings on ESP32 Web Server - With CSS

Pretty amazing, Right? Without further ado, let’s apply some style to our previous HTML page. To start with, copy-paste below code to replace SendHTML() function from the sketch above.

String SendHTML(float tempSensor1,float tempSensor2,float tempSensor3){
  String ptr = "<!DOCTYPE html>";
  ptr +="<html>";
  ptr +="<head>";
  ptr +="<title>ESP8266 Temperature Monitor</title>";
  ptr +="<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  ptr +="<style>";
  ptr +="html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}";
  ptr +="body{margin-top: 50px;} ";
  ptr +="h1 {margin: 50px auto 30px;} ";
  ptr +=".side-by-side{display: table-cell;vertical-align: middle;position: relative;}";
  ptr +=".text{font-weight: 600;font-size: 19px;width: 200px;}";
  ptr +=".temperature{font-weight: 300;font-size: 50px;padding-right: 15px;}";
  ptr +=".living-room .temperature{color: #3B97D3;}";
  ptr +=".bedroom .temperature{color: #F29C1F;}";
  ptr +=".kitchen .temperature{color: #26B99A;}";
  ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;right: -5px;top: 15px;}";
  ptr +=".data{padding: 10px;}";
  ptr +=".container{display: table;margin: 0 auto;}";
  ptr +=".icon{width:82px}";
  ptr +="</style>";
  ptr +="</head>";
  ptr +="<body>";
  ptr +="<h1>ESP8266 Temperature Monitor</h1>";
  ptr +="<div class='container'>";
  ptr +="<div class='data living-room'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 65.178 45.699'height=45.699px id=Layer_1 version=1.1 viewBox='0 0 65.178 45.699'width=65.178px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><polygon fill=#3B97D3 points='8.969,44.261 8.969,16.469 7.469,16.469 7.469,44.261 1.469,44.261 1.469,45.699 14.906,45.699 ";
  ptr +="14.906,44.261 '/><polygon fill=#3B97D3 points='13.438,0 3,0 0,14.938 16.438,14.938 '/><polygon fill=#3B97D3 points='29.927,45.699 26.261,45.699 26.261,41.156 32.927,41.156 '/><polygon fill=#3B97D3 points='58.572,45.699 62.239,45.699 62.239,41.156 55.572,41.156 '/><path d='M61.521,17.344c-2.021,0-3.656,1.637-3.656,3.656v14.199H30.594V21c0-2.02-1.638-3.656-3.656-3.656";
  ptr +="c-2.02,0-3.657,1.636-3.657,3.656v14.938c0,2.021,1.637,3.655,3.656,3.655H61.52c2.02,0,3.655-1.637,3.655-3.655V21";
  ptr +="C65.177,18.98,63.54,17.344,61.521,17.344z'fill=#3B97D3 /><g><path d='M32.052,30.042c0,2.02,1.637,3.656,3.656,3.656h16.688c2.019,0,3.656-1.638,3.656-3.656v-3.844h-24";
  ptr +="L32.052,30.042L32.052,30.042z'fill=#3B97D3 /><path d='M52.396,6.781H35.709c-2.02,0-3.656,1.637-3.656,3.656v14.344h24V10.438";
  ptr +="C56.053,8.418,54.415,6.781,52.396,6.781z'fill=#3B97D3 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Living Room</div>";
  ptr +="<div class='side-by-side temperature'>";
  ptr +=(int)tempSensor1;
  ptr +="<span class='superscript'>&deg;C</span></div>";
  ptr +="</div>";
  ptr +="<div class='data bedroom'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 43.438 35.75'height=35.75px id=Layer_1 version=1.1 viewBox='0 0 43.438 35.75'width=43.438px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M25.489,14.909H17.95C13.007,14.908,0,15.245,0,20.188v3.688h43.438v-3.688";
  ptr +="C43.438,15.245,30.431,14.909,25.489,14.909z'fill=#F29C1F /><polygon fill=#F29C1F points='0,31.25 0,35.75 2.5,35.75 4.5,31.25 38.938,31.25 40.938,35.75 43.438,35.75 43.438,31.25 ";
  ptr +="43.438,25.375 0,25.375 	'/><path d='M13.584,11.694c-3.332,0-6.033,0.973-6.033,2.175c0,0.134,0.041,0.264,0.105,0.391";
  ptr +="c3.745-0.631,7.974-0.709,10.341-0.709h1.538C19.105,12.501,16.613,11.694,13.584,11.694z'fill=#F29C1F /><path d='M30.009,11.694c-3.03,0-5.522,0.807-5.951,1.856h1.425V13.55c2.389,0,6.674,0.081,10.444,0.728";
  ptr +="c0.069-0.132,0.114-0.268,0.114-0.408C36.041,12.668,33.34,11.694,30.009,11.694z'fill=#F29C1F /><path d='M6.042,14.088c0-2.224,3.376-4.025,7.542-4.025c3.825,0,6.976,1.519,7.468,3.488h1.488";
  ptr +="c0.49-1.97,3.644-3.489,7.469-3.489c4.166,0,7.542,1.801,7.542,4.025c0,0.17-0.029,0.337-0.067,0.502";
  ptr +="c1.08,0.247,2.088,0.549,2.945,0.926V3.481C40.429,1.559,38.871,0,36.948,0H6.49C4.568,0,3.009,1.559,3.009,3.481v12.054";
  ptr +="c0.895-0.398,1.956-0.713,3.095-0.968C6.069,14.41,6.042,14.251,6.042,14.088z'fill=#F29C1F /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Bedroom</div>";
  ptr +="<div class='side-by-side temperature'>";
  ptr +=(int)tempSensor2;
  ptr +="<span class='superscript'>&deg;C</span></div>";
  ptr +="</div>";
  ptr +="<div class='data kitchen'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 48 31.5'height=31.5px id=Layer_1 version=1.1 viewBox='0 0 48 31.5'width=48px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><circle cx=24.916 cy=15.75 fill=#26B99A r=15.75 /><path d='M14.917,15.75c0-5.522,4.478-10,10-10c2.92,0,5.541,1.26,7.369,3.257l1.088-1.031";
  ptr +="c-2.103-2.285-5.106-3.726-8.457-3.726c-6.351,0-11.5,5.149-11.5,11.5c0,3.127,1.252,5.958,3.277,8.031l1.088-1.031";
  ptr +="C16.011,20.945,14.917,18.477,14.917,15.75z'fill=#FFFFFF /><path d='M45.766,2.906c-1.232,0-2.232,1-2.232,2.234v11.203c0,0,2.76,0,3,0v12H48v-12V2.906";
  ptr +="C48,2.906,46.035,2.906,45.766,2.906z'fill=#26B99A /><path d='M6.005,2.917v5.184c0,0.975-0.638,1.792-1.516,2.083V2.917H3.021v7.267c-0.878-0.29-1.516-1.107-1.516-2.083";
  ptr +="V2.917H0v5.458c0,1.802,1.306,3.291,3.021,3.592v16.376H4.49v-16.38c1.695-0.318,2.979-1.8,2.979-3.588V2.917H6.005z'fill=#26B99A /></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Kitchen</div>";
  ptr +="<div class='side-by-side temperature'>";
  ptr +=(int)tempSensor3;
  ptr +="<span class='superscript'>&deg;C</span></div>";
  ptr +="</div>";
  ptr +="</div>";
  ptr +="</body>";
  ptr +="</html>";
  return ptr;
}

If you try to compare this function with the previous one, you’ll come to know that they are similar except these changes.

  • We have used Google commissioned Open Sans web font for our web page. Note that you cannot see Google font, without active internet connection on the device. Google fonts are loaded on the fly.
    ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  • The icons used to display temperature readings are actually a Scalable Vector Graphics (SVG) defined in <svg> tag. Creating SVG doesn’t require any special programming skills. You can use Google SVG Editor for creating graphics for your page. We have used these SVG icons.
    SVG Icons

Improvement to the Code – Auto Page Refresh

One of the improvements you can do with our code is refreshing the page automatically in order to update the sensor value.

With the addition of a single meta tag into your HTML document, you can instruct the browser to automatically reload the page at a provided interval.

<meta http-equiv="refresh" content="2" >

Place this code in the the <head> tag of your document, this meta tag will instruct the browser to refresh every two seconds. Pretty nifty!

Dynamically load Sensor Data with AJAX

Refreshing a web page isn’t too practical if you have a heavy web page. A better method is to use Asynchronous Javascript And Xml (AJAX) so that we can request data from the server asynchronously (in the background) without refreshing the page.

The XMLHttpRequest object within JavaScript is commonly used to execute AJAX on webpages. It performs the silent GET request on the server and updates the element on the page. AJAX is not a new technology, or different language, just existing technologies used in new ways. Besides this, AJAX also makes it possible to

  • Request data from a server after the page has loaded
  • Receive data from a server after the page has loaded
  • Send data to a server in the background

Here is the AJAX script that we’ll be using. Place this script just before you close </head> tag.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";
ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";
ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";
ptr +="</script>\n";

The script starts with <script> tag. As AJAX script is nothing but a javascript, we need to write it in <script> tag. In order for this function to be repeatedly called, we will be using the javascript setInterval() function. It takes two parameters – a function to be executed and time interval (in milliseconds) on how often to execute the function.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";

The heart of this script is a loadDoc() function. Inside this function, an XMLHttpRequest() object is created. This object is used to request data from a web server.

ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";

The xhttp.onreadystatechange() function is called every time the readyState changes. The readyState property holds the status of the XMLHttpRequest. It has one of the following values.

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

The status property holds the status of the XMLHttpRequest object. It has one of the following values.

  • 200: “OK”
  • 403: “Forbidden”
  • 404: “Page not found”

When readyState is 4 and status is 200, the response is ready. Now, the content of body (holding temperature readings) is updated.

ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";

The HTTP request is then initiated via the open() and send() functions.

ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";

Don’t let the smartphone weather apps or commercial weather stations(that feeds you with data from stations based miles away) ruin your outdoor plans. With this IoT project you can be your own weatherman!

This project uses ESP8266 NodeMCU as the control device that easily connects to existing WiFi network & creates a Web Server. When any connected device accesses this web server, ESP8266 reads in temperature, humidity, barometric pressure & altitude from BME280 & sends it to the web browser of that device with a nice interface. Excited? Let’s get started!

BME280 Temperature, Humidity and Pressure Sensor

First, let’s take a quick look at the BME280 module.

BME280 is the next-generation digital temperature, humidity and pressure sensor manufactured by Bosch. It’s a successor to sensors like BMP180, BMP085 or BMP183.

BME280 Temperature Humidity Pressure Altitude Sensor Specifications

The operating voltage of the BME280 module is from 3.3V to 5V – Perfect for interfacing with 3.3V microcontrollers like ESP8266.

The module features a simple two-wire I2C interface for communication. The default I2C address of the BME280 module is 0x76 and can be changed to 0x77 easily with this procedure.

Wiring BME280 Sensor to ESP8266 NodeMCU

Connections are fairly simple. Start by connecting VIN pin to the 3.3V output on the ESP8266 NodeMCU and connect GND to ground.

Next, Connect the SCL pin to the I2C clock D1 pin on your ESP8266 and connect the SDA pin to the I2C data D2 pin on your ESP8266.

The following diagram shows you how to wire everything.

Fritzing Wiring ESP8266 & BME280 Temperature Humidity Pressure Sensor
Wiring ESP8266 & BME280 Temperature Humidity Pressure Sensor

Preparing the Arduino IDE

There’s an add-on for the Arduino IDE that allows you to program the ESP8266 NodeMCU using the Arduino IDE. Follow below tutorial to prepare your Arduino IDE to work with the ESP8266, if you haven’t already.

Installing Library For BME280

Communicating with a BME280 module is a bunch of work. Fortunately, Adafruit BME280 Library was written to hide away all the complexities so that we can issue simple commands to read the temperature, relative humidity & barometric pressure data.

To install the library navigate to the Arduino IDE > Sketch > Include Library > Manage Libraries… Wait for Library Manager to download libraries index and update list of installed libraries.

Filter your search by typing ‘bme280’. There should be a couple entries. Look for Adafruit BME280 Library by Adafruit. Click on that entry, and then select Install.

Installing BME280 Library In Arduino IDE

The BME280 sensor library uses the Adafruit Sensor support backend. So, search the library manager for Adafruit Unified Sensor and install that too (you may have to scroll a bit)

Adafruit Unified Sensor Library Installation

Displaying Temperature, Humidity, Pressure & Altitude On ESP8266 Web Server

Now, we are going to configure our ESP8266 into Station (STA) mode, and create a web server to serve up web pages to any connected client under existing network.

If you want to learn about creating a web server with ESP8266 in AP/STA mode, check this tutorial out.

Before you head for uploading the sketch, you need to make one change to make it work for you. You need to modify the following two variables with your network credentials, so that ESP8266 can establish a connection with existing network.

const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

Once you are done, go ahead and try the sketch out.

#include <ESP8266WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;

float temperature, humidity, pressure, altitude;

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

ESP8266WebServer server(80);              
 
void setup() {
  Serial.begin(115200);
  delay(100);
  
  bme.begin(0x76);   

  Serial.println("Connecting to ");
  Serial.println(ssid);

  //connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");

}
void loop() {
  server.handleClient();
}

void handle_OnConnect() {
  temperature = bme.readTemperature();
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 100.0F;
  altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
  server.send(200, "text/html", SendHTML(temperature,humidity,pressure,altitude)); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float temperature,float humidity,float pressure,float altitude){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<title>ESP8266 Weather Station</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr +="</style>\n";
  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<div id=\"webpage\">\n";
  ptr +="<h1>ESP8266 Weather Station</h1>\n";
  ptr +="<p>Temperature: ";
  ptr +=temperature;
  ptr +="&deg;C</p>";
  ptr +="<p>Humidity: ";
  ptr +=humidity;
  ptr +="%</p>";
  ptr +="<p>Pressure: ";
  ptr +=pressure;
  ptr +="hPa</p>";
  ptr +="<p>Altitude: ";
  ptr +=altitude;
  ptr +="m</p>";
  ptr +="</div>\n";
  ptr +="</body>\n";
  ptr +="</html>\n";
  return ptr;
}

Accessing the Web Server

After uploading the sketch, open the Serial Monitor at a baud rate of 115200. And press the EN button on NodeMCU. If everything is OK, it will output the dynamic IP address obtained from your router and show HTTP server started message.

ESP8266 Station Mode Web Server IP Address On Serial Monitor

Next, load up a browser and point it to the IP address shown on the serial monitor. The ESP8266 should serve up a web page showing temperature, humidity, pressure and altitude from BME280.

BME280 Readings on ESP8266 Web Server

Detailed Code Explanation

The sketch starts by including following libraries.

  • ESP8266WebServer.h library provides ESP8266 specific WiFi methods we are calling to connect to network. It also has some methods available that will help us setting up a server and handle incoming HTTP requests without needing to worry about low level implementation details.
  • Wire.h library communicates with any I2C device not just BME280.
  • Adafruit_BME280.h & Adafruit_Sensor.h libraries are hardware-specific libraries which handles lower-level functions.
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Next we create an object of the sensor and variables to store temperature, humidity, pressure and altitude.

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;

float temperature, humidity, pressure, altitude;

As we are configuring ESP8266 in Station (STA) mode, it will join existing WiFi network. Hence, we need to provide it with your network’s SSID & Password. Next we start web server at port 80.

/*Put your SSID & Password*/
const char* ssid = "YourNetworkName";  // Enter SSID here
const char* password = "YourPassword";  //Enter Password here

ESP8266WebServer server(80);

Inside Setup() Function

Inside Setup() Function we configure our HTTP server before actually running it.

First of all, we initialize serial communication with PC and initialize BME object using begin() function. It initializes I2C interface with given I2C Address(0x76) and checks if the chip ID is correct. It then resets the chip using soft-reset & waits for the sensor for calibration after wake-up.

Serial.begin(115200);
delay(100);

bme.begin(0x76);

Now, we need to join the WiFi network using WiFi.begin() function. The function takes SSID (Network Name) and password as a parameter.

Serial.println("Connecting to ");
Serial.println(ssid);

//connect to your local wi-fi network
WiFi.begin(ssid, password);

While the ESP8266 tries to connect to the network, we can check the connectivity status with WiFi.status() function.

//check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }

Once the ESP8266 is connected to the network, the sketch prints the IP address assigned to ESP8266 by displaying WiFi.localIP() value on serial monitor.

Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

In order to handle incoming HTTP requests, we need to specify which code to execute when a URL is hit. To do so, we use on method. This method takes two parameters. First one is a URL path and second one is the name of function which we want to execute when that URL is hit.

The code below indicates that when a server receives an HTTP request on the root (/) path, it will trigger the handle_OnConnect function. Note that the URL specified is a relative path.

server.on("/", handle_OnConnect);

We haven’t specified what the server should do if the client requests any URL other than specified with server.on. It should respond with an HTTP status 404 (Not Found) and a message for the user. We put this in a function as well, and use server.onNotFound to tell it that it should execute it when it receives a request for a URL that wasn’t specified with server.on

server.onNotFound(handle_NotFound);

Now, to start our server, we call the begin method on the server object.

server.begin();
Serial.println("HTTP server started");

Inside Loop() Function

To handle the actual incoming HTTP requests, we need to call the handleClient() method on the server object.

server.handleClient();

Next, we need to create a function we attached to root (/) URL with server.on Remember?

At the start of this function, we get the temperature, humidity, pressure & altitude readings from the sensor. In order to respond to the HTTP request, we use the send method. Although the method can be called with a different set of arguments, its simplest form consists of the HTTP response code, the content type and the content.

In our case, we are sending the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then, we are specifying the content type as “text/html“, and finally we are calling SendHTML() custom function which creates a dynamic HTML page containing temperature, humidity, pressure & altitude readings.

void handle_OnConnect() {
  temperature = bme.readTemperature();
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 100.0F;
  altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
  server.send(200, "text/html", SendHTML(temperature,humidity,pressure,altitude)); 
}

Likewise, we need to create a function to handle 404 Error page.

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

Displaying the HTML Web Page

SendHTML() function is responsible for generating a web page whenever the ESP8266 web server gets a request from a web client. It merely concatenates HTML code into a big string and returns to the server.send() function we discussed earlier. The function takes temperature, humidity, pressure & altitude readings as a parameter to dynamically generate the HTML content.

The first text you should always send is the <!DOCTYPE> declaration that indicates that we’re sending HTML code.

String SendHTML(float temperature,float humidity,float pressure,float altitude){
  String ptr = "<!DOCTYPE html> <html>\n";

Next, the <meta> viewport element makes the web page responsive in any web browser, while title tag sets the title of the page.

ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>ESP8266 Weather Station</title>\n";

Styling the Web Page

Next, we have some CSS to style the web page appearance. We choose the Helvetica font, define the content to be displayed as an inline-block and aligned at the center.

ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";

Following code sets color, font and margin around the body, H1 and p tags.

ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
ptr +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";

Setting the Web Page Heading

Next, heading of the web page is set; you can change this text to anything that suits your application.

ptr +="<div id=\"webpage\">\n";
ptr +="<h1>ESP8266 Weather Station</h1>\n";

Displaying Readings on Web Page

To dynamically display temperature, humidity, pressure & altitude readings, we put those values in paragraph tag. To display degree symbol, we use HTML entity &deg;

ptr +="<p>Temperature: ";
ptr +=temperature;
ptr +="&deg;C</p>";
ptr +="<p>Humidity: ";
ptr +=humidity;
ptr +="%</p>";
ptr +="<p>Pressure: ";
ptr +=pressure;
ptr +="hPa</p>";
ptr +="<p>Altitude: ";
ptr +=altitude;
ptr +="m</p>";
ptr +="</div>\n";
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}

Styling Web Page to Look More Professional

Programmers like us are often intimidated by design – but a little effort can make your web page look more attractive and professional. Below screenshot will give you a basic idea of what we are going to do.

BME280 Readings on ESP8266 Web Server - Professional Look

Pretty amazing, Right? Without further ado, let’s apply some style to our previous HTML page. To start with, copy-paste below code to replace SendHTML() function from the sketch above.

String SendHTML(float temperature,float humidity,float pressure,float altitude){
  String ptr = "<!DOCTYPE html>";
  ptr +="<html>";
  ptr +="<head>";
  ptr +="<title>ESP8266 Weather Station</title>";
  ptr +="<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  ptr +="<style>";
  ptr +="html { font-family: 'Open Sans', sans-serif; display: block; margin: 0px auto; text-align: center;color: #444444;}";
  ptr +="body{margin: 0px;} ";
  ptr +="h1 {margin: 50px auto 30px;} ";
  ptr +=".side-by-side{display: table-cell;vertical-align: middle;position: relative;}";
  ptr +=".text{font-weight: 600;font-size: 19px;width: 200px;}";
  ptr +=".reading{font-weight: 300;font-size: 50px;padding-right: 25px;}";
  ptr +=".temperature .reading{color: #F29C1F;}";
  ptr +=".humidity .reading{color: #3B97D3;}";
  ptr +=".pressure .reading{color: #26B99A;}";
  ptr +=".altitude .reading{color: #955BA5;}";
  ptr +=".superscript{font-size: 17px;font-weight: 600;position: absolute;top: 10px;}";
  ptr +=".data{padding: 10px;}";
  ptr +=".container{display: table;margin: 0 auto;}";
  ptr +=".icon{width:65px}";
  ptr +="</style>";
  ptr +="</head>";
  ptr +="<body>";
  ptr +="<h1>ESP8266 Weather Station</h1>";
  ptr +="<div class='container'>";
  ptr +="<div class='data temperature'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 19.438 54.003'height=54.003px id=Layer_1 version=1.1 viewBox='0 0 19.438 54.003'width=19.438px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M11.976,8.82v-2h4.084V6.063C16.06,2.715,13.345,0,9.996,0H9.313C5.965,0,3.252,2.715,3.252,6.063v30.982";
  ptr +="C1.261,38.825,0,41.403,0,44.286c0,5.367,4.351,9.718,9.719,9.718c5.368,0,9.719-4.351,9.719-9.718";
  ptr +="c0-2.943-1.312-5.574-3.378-7.355V18.436h-3.914v-2h3.914v-2.808h-4.084v-2h4.084V8.82H11.976z M15.302,44.833";
  ptr +="c0,3.083-2.5,5.583-5.583,5.583s-5.583-2.5-5.583-5.583c0-2.279,1.368-4.236,3.326-5.104V24.257C7.462,23.01,8.472,22,9.719,22";
  ptr +="s2.257,1.01,2.257,2.257V39.73C13.934,40.597,15.302,42.554,15.302,44.833z'fill=#F29C21 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Temperature</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)temperature;
  ptr +="<span class='superscript'>&deg;C</span></div>";
  ptr +="</div>";
  ptr +="<div class='data humidity'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 29.235 40.64'height=40.64px id=Layer_1 version=1.1 viewBox='0 0 29.235 40.64'width=29.235px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><path d='M14.618,0C14.618,0,0,17.95,0,26.022C0,34.096,6.544,40.64,14.618,40.64s14.617-6.544,14.617-14.617";
  ptr +="C29.235,17.95,14.618,0,14.618,0z M13.667,37.135c-5.604,0-10.162-4.56-10.162-10.162c0-0.787,0.638-1.426,1.426-1.426";
  ptr +="c0.787,0,1.425,0.639,1.425,1.426c0,4.031,3.28,7.312,7.311,7.312c0.787,0,1.425,0.638,1.425,1.425";
  ptr +="C15.093,36.497,14.455,37.135,13.667,37.135z'fill=#3C97D3 /></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Humidity</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)humidity;
  ptr +="<span class='superscript'>%</span></div>";
  ptr +="</div>";
  ptr +="<div class='data pressure'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 40.542 40.541'height=40.541px id=Layer_1 version=1.1 viewBox='0 0 40.542 40.541'width=40.542px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M34.313,20.271c0-0.552,0.447-1,1-1h5.178c-0.236-4.841-2.163-9.228-5.214-12.593l-3.425,3.424";
  ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293c-0.391-0.391-0.391-1.023,0-1.414l3.425-3.424";
  ptr +="c-3.375-3.059-7.776-4.987-12.634-5.215c0.015,0.067,0.041,0.13,0.041,0.202v4.687c0,0.552-0.447,1-1,1s-1-0.448-1-1V0.25";
  ptr +="c0-0.071,0.026-0.134,0.041-0.202C14.39,0.279,9.936,2.256,6.544,5.385l3.576,3.577c0.391,0.391,0.391,1.024,0,1.414";
  ptr +="c-0.195,0.195-0.451,0.293-0.707,0.293s-0.512-0.098-0.707-0.293L5.142,6.812c-2.98,3.348-4.858,7.682-5.092,12.459h4.804";
  ptr +="c0.552,0,1,0.448,1,1s-0.448,1-1,1H0.05c0.525,10.728,9.362,19.271,20.22,19.271c10.857,0,19.696-8.543,20.22-19.271h-5.178";
  ptr +="C34.76,21.271,34.313,20.823,34.313,20.271z M23.084,22.037c-0.559,1.561-2.274,2.372-3.833,1.814";
  ptr +="c-1.561-0.557-2.373-2.272-1.815-3.833c0.372-1.041,1.263-1.737,2.277-1.928L25.2,7.202L22.497,19.05";
  ptr +="C23.196,19.843,23.464,20.973,23.084,22.037z'fill=#26B999 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Pressure</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)pressure;
  ptr +="<span class='superscript'>hPa</span></div>";
  ptr +="</div>";
  ptr +="<div class='data altitude'>";
  ptr +="<div class='side-by-side icon'>";
  ptr +="<svg enable-background='new 0 0 58.422 40.639'height=40.639px id=Layer_1 version=1.1 viewBox='0 0 58.422 40.639'width=58.422px x=0px xml:space=preserve xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink y=0px><g><path d='M58.203,37.754l0.007-0.004L42.09,9.935l-0.001,0.001c-0.356-0.543-0.969-0.902-1.667-0.902";
  ptr +="c-0.655,0-1.231,0.32-1.595,0.808l-0.011-0.007l-0.039,0.067c-0.021,0.03-0.035,0.063-0.054,0.094L22.78,37.692l0.008,0.004";
  ptr +="c-0.149,0.28-0.242,0.594-0.242,0.934c0,1.102,0.894,1.995,1.994,1.995v0.015h31.888c1.101,0,1.994-0.893,1.994-1.994";
  ptr +="C58.422,38.323,58.339,38.024,58.203,37.754z'fill=#955BA5 /><path d='M19.704,38.674l-0.013-0.004l13.544-23.522L25.13,1.156l-0.002,0.001C24.671,0.459,23.885,0,22.985,0";
  ptr +="c-0.84,0-1.582,0.41-2.051,1.038l-0.016-0.01L20.87,1.114c-0.025,0.039-0.046,0.082-0.068,0.124L0.299,36.851l0.013,0.004";
  ptr +="C0.117,37.215,0,37.62,0,38.059c0,1.412,1.147,2.565,2.565,2.565v0.015h16.989c-0.091-0.256-0.149-0.526-0.149-0.813";
  ptr +="C19.405,39.407,19.518,39.019,19.704,38.674z'fill=#955BA5 /></g></svg>";
  ptr +="</div>";
  ptr +="<div class='side-by-side text'>Altitude</div>";
  ptr +="<div class='side-by-side reading'>";
  ptr +=(int)altitude;
  ptr +="<span class='superscript'>m</span></div>";
  ptr +="</div>";
  ptr +="</div>";
  ptr +="</body>";
  ptr +="</html>";
  return ptr;
}

If you try to compare this function with the previous one, you’ll come to know that they are similar except these changes.

  • We have used Google commissioned Open Sans web font for our web page. Note that you cannot see Google font, without active internet connection on the device. Google fonts are loaded on the fly.
    ptr +="<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600' rel='stylesheet'>";
  • The icons used to display temperature, humidity, pressure & altitude readings are actually a Scalable Vector Graphics (SVG) defined in <svg> tag. Creating SVG doesn’t require any special programming skills. You can use Google SVG Editor for creating graphics for your page. We have used these SVG icons.
    SVG Icons

Improvement to the Code – Auto Page Refresh

One of the improvements you can do with our code is refreshing the page automatically in order to update the sensor value.

With the addition of a single meta tag into your HTML document, you can instruct the browser to automatically reload the page at a provided interval.

<meta http-equiv="refresh" content="2" >

Place this code in the the <head> tag of your document, this meta tag will instruct the browser to refresh every two seconds. Pretty nifty!

Dynamically load Sensor Data with AJAX

Refreshing a web page isn’t too practical if you have a heavy web page. A better method is to use Asynchronous Javascript And Xml (AJAX) so that we can request data from the server asynchronously (in the background) without refreshing the page.

The XMLHttpRequest object within JavaScript is commonly used to execute AJAX on webpages. It performs the silent GET request on the server and updates the element on the page. AJAX is not a new technology, or different language, just existing technologies used in new ways. Besides this, AJAX also makes it possible to

  • Request data from a server after the page has loaded
  • Receive data from a server after the page has loaded
  • Send data to a server in the background

Here is the AJAX script that we’ll be using. Place this script just before you close </head> tag.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";
ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";
ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";
ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";
ptr +="</script>\n";

The script starts with <script> tag. As AJAX script is nothing but a javascript, we need to write it in <script> tag. In order for this function to be repeatedly called, we will be using the javascript setInterval() function. It takes two parameters – a function to be executed and time interval (in milliseconds) on how often to execute the function.

ptr +="<script>\n";
ptr +="setInterval(loadDoc,1000);\n";

The heart of this script is a loadDoc() function. Inside this function, an XMLHttpRequest() object is created. This object is used to request data from a web server.

ptr +="function loadDoc() {\n";
ptr +="var xhttp = new XMLHttpRequest();\n";

The xhttp.onreadystatechange() function is called every time the readyState changes. The readyState property holds the status of the XMLHttpRequest. It has one of the following values.

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

The status property holds the status of the XMLHttpRequest object. It has one of the following values.

  • 200: “OK”
  • 403: “Forbidden”
  • 404: “Page not found”

When readyState is 4 and status is 200, the response is ready. Now, the content of body (holding temperature readings) is updated.

ptr +="xhttp.onreadystatechange = function() {\n";
ptr +="if (this.readyState == 4 && this.status == 200) {\n";
ptr +="document.body.innerHTML =this.responseText}\n";
ptr +="};\n";

The HTTP request is then initiated via the open() and send() functions.

ptr +="xhttp.open(\"GET\", \"/\", true);\n";
ptr +="xhttp.send();\n";
ptr +="}\n";

Login
ADS CODE