MicroPython: BME280 Web Server with ESP32/ESP8266 (Weather Station)

In this tutorial, we will learn to design a web server using a BME280 sensor with ESP32 /ESP8266 development boards and MicroPython. BME280 is used to measure temperature in Celsius, the temperature in Fahrenheit, humidity, and Pressure. This web server will act as a weather station as it will show temperature, humidity, and pressure readings on the web page using MicroPython.

We will use MicroPython firmware to build a responsive EP32/ESP8266 web server that can be accessed through any device which has a web browser, and the device should be connected to your local area network. That means the device should be connected to the same network to which the ESP32/ESP8266 board is connected.

BME280 web server esp32 esp8266 micropython

Prerequisites

Before we start this lesson make sure you are familiar with and have the latest version of MicroPython firmware installed in your ESP boards and have a running Integrated Development Environment(IDE) in which we will be doing the programming. We will be using the same uPyCraft IDE as we have done previously when we learned how to blink and chase LEDs in microPython.

If you are using Thonny IDE, you can check this getting started guide:

We will cover the following content in this MicroPython tutorial: 

  • How to create a BM280 web server using ESP32/ESP8266 and MicroPython 
  • Display Temperature, Humidity, and Pressure readings on a web page with ESP32 and ESP8266 

We have similar guide to display BME280 sensor readings on MicroPython shell:

BME280 Introduction

The BME280 sensor is used to measure readings regarding ambient temperature, barometric pressure, and relative humidity. It is mostly used in web and mobile applications where low power consumption is key. This sensor uses I2C or SPI to communicate data with the micro-controllers. Although there are several different versions of BME280 available in the market, the one we will be studying uses I2C communication protocol and SPI.

I2C means Inter-Integrated Circuit and works on the principle of the synchronous, multi-master multi-slave system. With BME280 and the ESP boards, the ESP32/ESP8266 acts as a master, and the BME280 sensor as a slave because it is an external device, acts as a slave. The ESP development boards communicate with the BME280 sensor through the I2C protocol to temperature, barometric pressure, and relative humidity.

Pinout Diagram

The figure below shows the BME280 sensor and its pinout.

BME280 Pinout Diagram
         BME280 Pinout
  • VCC: connected with 3.3V
  • SCL: used to generate the clock signal
  • SDA: used in sending and receiving data

ESP32 and ESP8266 Schematic with BME280

The connection of BME280 with the ESP boards is very easy. We have to connect the VCC terminal with 3.3V, ground with the ground (common ground), SCL of the sensor with SCL of the module, and SDA of the sensor with the SDA pin of the ESP modules.

The I2C pin in ESP32 for SDA is GPIO21 and for SCL is GPIO22. Whereas, in ESP8266 the default I2C pins for SDA are GPIO4 and for SCL are GPIO5. The connections between the two devices can be seen below.

ESP32ESP8266BME280
VCC=3.3V3.3VVin
GPIO21(I2C SDA)GPIO4 (D2)SDA
GPIO22 (I2C SCL)GPIO5 (D1)SCL
GROUNDGROUNDGROUND

ESP32 I2C Pins

The I2C pins stated above are set in default. If we want to change the GPIO pins we have to set them in code. The diagrams below show the pinout for the ESP32 and Esp8266 respectively.

ESP32 I2C Pins
ESP32 I2C Pins

ESP8266 I2C Pins

The following figure shows the I2C pins of ESP8266 NodeMCU:

ESP8266 I2C Pins
ESP8266 I2C Pins

More information on ESP32 and ESP8266 pins is available here:

Components Required

We will need the following components to connect our ESP board with the BME280 sensor.

  1. ESP32/ESP8266
  2. BME280 Sensor
  3. Connecting Wires
  4. Breadboard

Follow the schematic diagrams below for both the ESP modules and connect them accordingly. If you are using ESP32 for this project, connect the ESP32 device with BME280 as shown in the schematic diagram below:

BME280 with ESP32 MicroPython

Similarly, you are using ESP8266 NodeMCU for this project, connect the ESP8266 device with BME280 as shown in the schematic diagram below:

BME280 with ESP8266 MicroPython

In some BME280 sensors as seen in the above connection diagram, the SCK terminal means the SCL pin and is connected with its respective GPIO pin on the ESP board. Likewise, the SDI terminal means the SDA pin and is connected with its respective GPIO pin on the board. Vin is connected with a 3.3V pin on the module and both the ESP board and the sensor is commonly grounded.

BME280 MicroPython Library

By default, MicroPython does not have an implementation of the BME280 library. But, MicroPyhon provides I2C API of ESP32 and ESP8266 which can be used to read the temperature, humidity, and pressure values from the BME280 sensor. Fortunately, there is one library available which is developed by Robert and can be downloaded from this link.

Hence, download the following library and upload it to ESP32/ESP8266 board with the name of BME280.py.

from machine import I2C
import time

# BME280 default address.
BME280_I2CADDR = 0x76

# Operating Modes
BME280_OSAMPLE_1 = 1
BME280_OSAMPLE_2 = 2
BME280_OSAMPLE_4 = 3
BME280_OSAMPLE_8 = 4
BME280_OSAMPLE_16 = 5

# BME280 Registers

BME280_REGISTER_DIG_T1 = 0x88  # Trimming parameter registers
BME280_REGISTER_DIG_T2 = 0x8A
BME280_REGISTER_DIG_T3 = 0x8C

BME280_REGISTER_DIG_P1 = 0x8E
BME280_REGISTER_DIG_P2 = 0x90
BME280_REGISTER_DIG_P3 = 0x92
BME280_REGISTER_DIG_P4 = 0x94
BME280_REGISTER_DIG_P5 = 0x96
BME280_REGISTER_DIG_P6 = 0x98
BME280_REGISTER_DIG_P7 = 0x9A
BME280_REGISTER_DIG_P8 = 0x9C
BME280_REGISTER_DIG_P9 = 0x9E

BME280_REGISTER_DIG_H1 = 0xA1
BME280_REGISTER_DIG_H2 = 0xE1
BME280_REGISTER_DIG_H3 = 0xE3
BME280_REGISTER_DIG_H4 = 0xE4
BME280_REGISTER_DIG_H5 = 0xE5
BME280_REGISTER_DIG_H6 = 0xE6
BME280_REGISTER_DIG_H7 = 0xE7

BME280_REGISTER_CHIPID = 0xD0
BME280_REGISTER_VERSION = 0xD1
BME280_REGISTER_SOFTRESET = 0xE0

BME280_REGISTER_CONTROL_HUM = 0xF2
BME280_REGISTER_CONTROL = 0xF4
BME280_REGISTER_CONFIG = 0xF5
BME280_REGISTER_PRESSURE_DATA = 0xF7
BME280_REGISTER_TEMP_DATA = 0xFA
BME280_REGISTER_HUMIDITY_DATA = 0xFD


class Device:
  """Class for communicating with an I2C device.

  Allows reading and writing 8-bit, 16-bit, and byte array values to
  registers on the device."""

  def __init__(self, address, i2c):
    """Create an instance of the I2C device at the specified address using
    the specified I2C interface object."""
    self._address = address
    self._i2c = i2c

  def writeRaw8(self, value):
    """Write an 8-bit value on the bus (without register)."""
    value = value & 0xFF
    self._i2c.writeto(self._address, value)

  def write8(self, register, value):
    """Write an 8-bit value to the specified register."""
    b=bytearray(1)
    b[0]=value & 0xFF
    self._i2c.writeto_mem(self._address, register, b)

  def write16(self, register, value):
    """Write a 16-bit value to the specified register."""
    value = value & 0xFFFF
    b=bytearray(2)
    b[0]= value & 0xFF
    b[1]= (value>>8) & 0xFF
    self.i2c.writeto_mem(self._address, register, value)

  def readRaw8(self):
    """Read an 8-bit value on the bus (without register)."""
    return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF

  def readU8(self, register):
    """Read an unsigned byte from the specified register."""
    return int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF

  def readS8(self, register):
    """Read a signed byte from the specified register."""
    result = self.readU8(register)
    if result > 127:
      result -= 256
    return result

  def readU16(self, register, little_endian=True):
    """Read an unsigned 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = int.from_bytes(
        self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF
    if not little_endian:
      result = ((result << 8) & 0xFF00) + (result >> 8)
    return result

  def readS16(self, register, little_endian=True):
    """Read a signed 16-bit value from the specified register, with the
    specified endianness (default little endian, or least significant byte
    first)."""
    result = self.readU16(register, little_endian)
    if result > 32767:
      result -= 65536
    return result

  def readU16LE(self, register):
    """Read an unsigned 16-bit value from the specified register, in little
    endian byte order."""
    return self.readU16(register, little_endian=True)

  def readU16BE(self, register):
    """Read an unsigned 16-bit value from the specified register, in big
    endian byte order."""
    return self.readU16(register, little_endian=False)

  def readS16LE(self, register):
    """Read a signed 16-bit value from the specified register, in little
    endian byte order."""
    return self.readS16(register, little_endian=True)

  def readS16BE(self, register):
    """Read a signed 16-bit value from the specified register, in big
    endian byte order."""
    return self.readS16(register, little_endian=False)


class BME280:
  def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,
               **kwargs):
    # Check that mode is valid.
    if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,
                    BME280_OSAMPLE_8, BME280_OSAMPLE_16]:
        raise ValueError(
            'Unexpected mode value {0}. Set mode to one of '
            'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '
            'BME280_ULTRAHIGHRES'.format(mode))
    self._mode = mode
    # Create I2C device.
    if i2c is None:
      raise ValueError('An I2C object is required.')
    self._device = Device(address, i2c)
    # Load calibration values.
    self._load_calibration()
    self._device.write8(BME280_REGISTER_CONTROL, 0x3F)
    self.t_fine = 0

  def _load_calibration(self):

    self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)
    self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)
    self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)

    self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)
    self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)
    self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)
    self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)
    self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)
    self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)
    self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)
    self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)
    self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)

    self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)
    self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)
    self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)
    self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)

    h4 = self._device.readS8(BME280_REGISTER_DIG_H4)
    h4 = (h4 << 24) >> 20
    self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F)

    h5 = self._device.readS8(BME280_REGISTER_DIG_H6)
    h5 = (h5 << 24) >> 20
    self.dig_H5 = h5 | (
        self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F)

  def read_raw_temp(self):
    """Reads the raw (uncompensated) temperature from the sensor."""
    meas = self._mode
    self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)
    meas = self._mode << 5 | self._mode << 2 | 1
    self._device.write8(BME280_REGISTER_CONTROL, meas)
    sleep_time = 1250 + 2300 * (1 << self._mode)

    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
    time.sleep_us(sleep_time)  # Wait the required time
    msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)
    lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_pressure(self):
    """Reads the raw (uncompensated) pressure level from the sensor."""
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)
    lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)
    xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)
    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
    return raw

  def read_raw_humidity(self):
    """Assumes that the temperature has already been read """
    """i.e. that enough delay has been provided"""
    msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)
    lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)
    raw = (msb << 8) | lsb
    return raw

  def read_temperature(self):
    """Get the compensated temperature in 0.01 of a degree celsius."""
    adc = self.read_raw_temp()
    var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11)
    var2 = ((
        (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) *
        self.dig_T3) >> 14
    self.t_fine = var1 + var2
    return (self.t_fine * 5 + 128) >> 8

  def read_pressure(self):
    """Gets the compensated pressure in Pascals."""
    adc = self.read_raw_pressure()
    var1 = self.t_fine - 128000
    var2 = var1 * var1 * self.dig_P6
    var2 = var2 + ((var1 * self.dig_P5) << 17)
    var2 = var2 + (self.dig_P4 << 35)
    var1 = (((var1 * var1 * self.dig_P3) >> 8) +
            ((var1 * self.dig_P2) >> 12))
    var1 = (((1 << 47) + var1) * self.dig_P1) >> 33
    if var1 == 0:
      return 0
    p = 1048576 - adc
    p = (((p << 31) - var2) * 3125) // var1
    var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
    var2 = (self.dig_P8 * p) >> 19
    return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)

  def read_humidity(self):
    adc = self.read_raw_humidity()
    # print 'Raw humidity = {0:d}'.format (adc)
    h = self.t_fine - 76800
    h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) +
         16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h *
                          self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) *
                          self.dig_H2 + 8192) >> 14))
    h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4)
    h = 0 if h < 0 else h
    h = 419430400 if h > 419430400 else h
    return h >> 12

  @property
  def temperature(self):
    "Return the temperature in degrees."
    t = self.read_temperature()
    ti = t // 100
    td = t - ti * 100
    return "{}.{:02d}C".format(ti, td)

  @property
  def pressure(self):
    "Return the temperature in hPa."
    p = self.read_pressure() // 256
    pi = p // 100
    pd = p - pi * 100
    return "{}.{:02d}hPa".format(pi, pd)

  @property
  def humidity(self):
    "Return the humidity in percent."
    h = self.read_humidity()
    hi = h // 1024
    hd = h * 100 // 1024 - hi * 100
    return "{}.{:02d}%".format(hi, hd)

Uploading BME280 Library with uPyCraft IDE

Now, we will look at how to install the BME280 library to be used in MicroPython. This step is required because MicroPython itself does not contain the sensor’s library. Follow the steps in order to successfully install the library in the IDE.

  • First, in the Tools section, click on the NEW FILE button and open a file.
uPyCraft IDE create new file
  • Then replicate the following BME280 library in that file. You can copy the library by clicking here.
  • Name the file BME280.py and save it by choosing your desired directory.
upycraft IDE save file
  • Now press the button DOWNLOAD AND RUN in the tools section.
upycraft IDE download and run code

You have now successfully uploaded the BME280 library o ESP32/ESP8266 using uPyCraft IDE. After that, we can use the above library functions to read temperature, pressure, and humidity from the BM280 sensor. You can use a similar procedure to upload files using Thonny IDE.

BME280 Web Server Code: Display Temperature, Humidity, and Pressure

In this part of the user guide, we will learn how to display readings obtained from a BME280 on a web server. Follow the steps in order to successfully display sensor readings on a local webserver.

  1. We will be required to make three files in our IDE.
  • BME280.py
  • boot.py
  • main.py

Out of these three, we have already uploaded the BME280.py file to ESP32/ESP8266. This was the file that contains the BME280 library.

  1. Similarly, we will also upload a boot.py file. This contains all the major configurations like the setting up of Pins, I2C GPIOs, and importing other relevant libraries.
  2. Finally, we will upload the main.py file which contains our final code to configure the web server which runs after boot.py.
BME280 web server micropython code

boot.py File

Copy the following code in your boot.py file and upload it your ESP board.

Note: Make sure to change the Wi-Fi name and password to the one you are currently using.

try:
  import usocket as socket
except:
  import socket
  
from time import sleep

from machine import Pin, I2C
import network

import esp
esp.osdebug(None)

import gc
gc.collect()

import BME280

# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'

station = network.WLAN(network.STA_IF)

station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
  pass

print('Connection successful')
print(station.ifconfig())

Importing MicroPython Libraries

In this code, we first import all the modules and from the modules, we import the necessary classes. After that, we define the I2C pins for ESP32 and ESP8266 board. We have set the default I2C GPIO pins for both. Next, we will connect to the network.

First, we will use sockets and the socket API of Python to develop our web server. Hence, we will import the socket library first.

try:
import usocket as socket
except:
import socket

Now, we will import the sleep class from the time module to include delays. We will also import I2C and Pin class from the machine module.

from time import sleep
from machine import Pin, I2C
As we have to connect our ESP32/ESP8266 board with Wi-Fi that is why we will also import the network library.
import network

Vendor OS debugging messages are turned off through the following lines.

import esp
esp.osdebug(None)

In these lines, we are calling the garbage collector. Object memories which are no longer used by the system are reclaimed through this.
import gc.

gc.collect()

Defining ESP32/ESP8266 GPIO Pins for BME280

Now, we initialize the I2C method by giving it three arguments. The first argument specifies the GPIO pin for SCL. This is given as GPIO22 for ESP32 and GPIO5 for ESP8266. The second parameter specifies the GPIO pin for the SDA. This is given as GPIO21 for ESP32 and GPIO4 for ESP8266. Keep in mind, these are the default I2C pins for SCL and SDA which we have used for both the ESP boards respectively. The third parameter specifies the maximum frequency for SCL to be used.

import BME280

# ESP32 - Pin assignment
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=10000)
# ESP8266 - Pin assignment
#i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000)

Connect to WiFi Network

Make sure you enter your Wi-Fi name in SSID and your wi-fi password as well so that your esp board connects with your local network.

ssid = 'Enter your SSID name'
password = 'Enter your SSID password here'

In these lines, we are setting our ESP32/ESP8266 development boards as a WI-FI station.

station = network.WLAN(network.STA_IF) #Connecting to the network

In order to activate our Wi-Fi station, we write the following line:

station.active(True)

These next lines, authenticate our Wi-Fi and password and only proceeds with the program code when the ESP board is properly connected.

station.connect(ssid, password)
while station.isconnected() == False:
pass

main.py

Now write the following code in a new file and save it as main.py. Make sure all your three files (BME280.py, boot.py, main.py) are uploded to ESP32/ESP8266 and you can see them under device option in uPyCraft IDE.

def web_page():
  bme = BME280.BME280(i2c=i2c)
  
  html = """<html>
<head>
  <title>BME280 Web Server</title>
  <meta http-equiv="refresh" content="10">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p {  font-size: 1.2rem;}
    body {  margin: 0;}
    .topnav { overflow: hidden; background-color: #0e7c7b; color: white; font-size: 1.7rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
    .reading { font-size: 2.8rem; }
    .card.temperature { color: #0e7c7b; }
    .card.humidity { color: #17bebb; }
    .card.pressure { color: #3fca6b; }
  </style>
</head>
<body>
  <div class="topnav">
    <h3>BME280 WEB SERVER</h3>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i>Temp. Celsius</h4><p><span class="reading">""" + str(bme.temperature) + """</p>
      </div>
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> Temp. Fahrenheit</h4><p><span class="reading">""" + str(round((bme.read_temperature()/100.0) * (9/5) + 32, 2))  + """F</p>
      </div>
      <div class="card pressure">
        <h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4><p><span class="reading">""" + str(bme.pressure) + """</p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading">""" + str(bme.humidity) + """</p>
      </div>
    </div>
  </div>
</body>

</html>"""
  return html

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
  try:
    if gc.mem_free() < 102000:
      gc.collect()
    conn, addr = s.accept()
    conn.settimeout(3.0)
    print('Got a connection from %s' % str(addr))
    request = conn.recv(1024)
    conn.settimeout(None)
    request = str(request)
    print('Content = %s' % request)
    response = web_page()
    conn.send('HTTP/1.1 200 OK\n')
    conn.send('Content-Type: text/html\n')
    conn.send('Connection: close\n\n')
    conn.sendall(response)
    conn.close()
  except OSError as e:
    conn.close()
    print('Connection closed')

Create Web Page to Display BM280 Sensor Readings

We start off our program code by defining a web_page() function. In order to build our web page, we have to create an HTML document that sets up the web page. This HTML variable is returned through the web_page function which we will discuss later.

def web_page():

Inside the web_page() function, we create an object of the BME280 method named bme and specify the I2C communication protocol to read data from a sensor. Because we can also use SPI protocol to read sensor readings.

bme = BME280.BME280(i2c=i2c)

HTML and CSS File

In this HTML document, we use cards, paragraphs, links, icons, headings and title tags to create a web page. This web page displays temperature, humidity and pressure readings of BME280 sensor. 

<html>
<head>
    <title>BME280 Web Server</title>
    <meta http-equiv="refresh" content="10">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
        integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="icon" href="data:,">
    <style>
        html {
            font-family: Arial;
            display: inline-block;
            text-align: center;
        }

        p {
            font-size: 1.2rem;
        }

        body {
            margin: 0;
        }

        .topnav {
            overflow: hidden;
            background-color: #0e7c7b;
            color: white;
            font-size: 1.7rem;
        }

        .content {
            padding: 20px;
        }

        .card {
            background-color: white;
            box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
        }

        .cards {
            max-width: 700px;
            margin: 0 auto;
            display: grid;
            grid-gap: 2rem;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
        }

        .reading {
            font-size: 2.8rem;
        }

        .card.temperature {
            color: #0e7c7b;
        }

        .card.humidity {
            color: #17bebb;
        }

        .card.pressure {
            color: #3fca6b;
        }
    </style>
</head>

<body>
    <div class="topnav">
        <h3>BME280 WEB SERVER</h3>
    </div>
    <div class="content">
        <div class="cards">
            <div class="card temperature">
                <h4><i class="fas fa-thermometer-half"></i>Temp. Celsius</h4>
                <p><span class="reading">""" + str(bme.temperature) + """</p>
            </div>
            <div class="card temperature">
                <h4><i class="fas fa-thermometer-half"></i> Temp. Fahrenheit</h4>
                <p><span class="reading">""" + str(round((bme.read_temperature()/100.0) * (9/5) + 32, 2)) + """F</p>
            </div>
            <div class="card pressure">
                <h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4>
                <p><span class="reading">""" + str(bme.pressure) + """</p>
            </div>
            <div class="card humidity">
                <h4><i class="fas fa-tint"></i> HUMIDITY</h4>
                <p><span class="reading">""" + str(bme.humidity) + """</p>
            </div>
        </div>
    </div>
</body>

</html>

This meta-tag http-equiv provides attributes to HTTP header. The http-equiv attribute takes many values or information to simulate header response. In this example, we use the http-equiv attribute to refresh the content of the web page after every specified time interval. Users aren’t required to refresh the web page to get updated sensor values. This line forces the HTML page to refresh itself after every 10 seconds.

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

We have explained rest o the HTML and CSS part of in our previously published articles. You can read these articles here:

 Web Server with MicroPython Socket API

In these lines of code, we create a socket object called ‘s’. This is formed by creating a socket and stating its type.

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

By using the bind() method, we will bind the socket with an address. We are passing two parameters. The first one is an empty string ‘ ’, which specifies our ESP32/ESP8266 board and the second parameter is the port which we have stated as ‘80’.

s.bind(('', 80))

Now we are creating a socket used for listening. We have passed ‘5’ as the parameter which means that 5 is the maximum number of queued connections.

s.listen(5)

Next, we use an infinite loop through which we will check when a user connects to the web server. In order to accept the connection, the accept() method is used. A new socket object is saved in the conn variable and it also saves the address there.

while True:
  try:
    if gc.mem_free() < 102000:
      gc.collect()
    conn, addr = s.accept()
    conn.settimeout(3.0)
print('Got a connection from %s' % str(addr))

By using send() and recv() methods, we exchange the data between the user and the web server. A request variable is created which saves it in the newly created socket.

request = conn.recv(1024)
conn.settimeout(None)
request = str(request)

Prints the whatever data is present in the request variable.

print('Content = %s' % request)

The variable ‘response’ contains the HTML text

response = web_page()

By using send() and sendall() methods, we will be sending the response to the socket user.

conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: text/html\n')
conn.send('Connection: close\n\n')
conn.sendall(response)

This line closes the connection with the socket which was created.

conn.close()
except OSError as e:
conn.close()
print('Connection closed')

In summary, we created a web server with ESP32/ESP8266 which could handle HTTP requests from a web client. Whenever the ESP32 or ESP8266 board received a request on its IP address, the program code creates a socket which in turn responds to the client request with an HTML page to display the current readings on the BME280 sensor regarding temperature, humidity, and pressure.

ESP32/ESP8266 BME280MicroPython Web Server Demo

To test the MicroPython BME280 web server with ESP32 and ESP8266, upload all the files to ESP32/ESP8266.

BME280 sensor interfacing with ESP32

After uploading MicroPython scripts, click on Enable/Reset button of ESP32:

 

ESP32 enable button

Sometimes later, your ESP board will make a connection with your WiFi router and shows a “successful connection” message and also prints the IP address on the MicroPython shell as follows: 

get bme280 web server ip address

Now, open your web browser either on your laptop or mobile and type the IP address which we have found in the last step. As soon as you type the IP address on your web browser and hit enter. The ESP32/ESP8266 web server will receive an HTTP request. The web page function will be called.

BME280 web server esp32 esp8266 micropython working

You will see the web page with the latest temperature values in your web browser:

BME280 web server esp32 esp8266 micropython demo

On Mobile, you will see web page like this:

BME280 display temperature pressure and humidity values on web page

More MicroPython tutorials:

Leave a Comment