Raspberry Pi Pico Web Server with BME280 (Weather Station)

In this tutorial, we will create a Raspberry Pi Pico web server using a BME280 sensor 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.

Raspberry Pi Pico BME280 Web Server with ESP8266

We will use MicroPython firmware to build a responsive Raspberry Pi Pico 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 board is connected.

Raspberry Pi Pico does not support Wi-Fi capabilities hence we have to use a separate Wi-Fi module to enable Wi-Fi connectivity. Therefore, we will interface and program ESP-01 Wi-Fi module with Raspberry Pi Pico to enable Wi-Fi features. We will use Thonny IDE to program Raspberry Pi Pico with ESP-01 and BME280 in MircoPython. We will use AT commands through the serial port that is UART to configure the ESP-01 Wi-Fi module.

Prerequisites

Before we start this lesson make sure you are familiar with and have the latest version Python 3 in your system, have set up MicoPython in Raspberry Pi Pico, and have a running Integrated Development Environment(IDE) in which we will be doing the programming. We will be using the same Thonny IDE as we have done previously when we learned how to blink and chase LEDs in micro-python. If you have not followed our previous tutorial, you check here:

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

Recommended Readings:

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 microcontroller, the Raspberry Pi Pico acts as a master, and the BME280 sensor as a slave because it is an external device, acts as a slave. The Raspberry Pi Pico communicates with the BME280 sensor through the I2C protocol to give temperature, barometric pressure, and relative humidity readings.

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

Interfacing Raspberry Pi Pico with BME280 and ESP-01

Raspberry Pi Pico with BME280 and ESP-01

This section shows how to connect Raspberry Pi Pico with BME280 sensor and ESP-01.

We will require the following components:

  • Raspberry Pi Pico
  • BME280 Sensor
  • ESP-01 Module
  • Connecting Wires
  • Breadboard

Raspberry Pi Pico with BME280

The connection of BME280 with the Raspberry Pi Pico is very simple. We have to connect the VCC terminal with 3.3V, ground with the ground (common ground), SCL of the sensor with SCL of the board, and SDA of the sensor with the SDA pin of the board.

Raspberry Pi Pico I2C Pins

Raspberry Pi Pico has two I2C controllers. Both I2C controllers are accessible through GPIO pins of Raspberry Pi Pico. The following table shows the connection of GPIO pins with both I2C controllers. Each connection of the controller can be configured through multiple GPIO pins as shown in the figure. But before using an I2C controller, you should configure in software which GPIO pins you want to use with a specific I2C controller. 

I2C ControllerGPIO Pins
I2C0 – SDAGP0/GP4/GP8/GP12/GP16/GP20
I2C0 – SCLGP1/GP5/GP9/GP13/GP17/GP21
I2C1 – SDAGP2/GP6/GP10/GP14/GP18/GP26
I2C1 – SCLGP3/GP7/GP11/GP15/GP19/GP27

The connections between the two devices which we are using can be seen below.

BME280Raspberry Pi Pico
VCC3.3V
SDAGP2 (I2C1 SDA)
SCLGP3 (I2C1 SCL)
GNDGND

We have used the same connections as specified in the table above. However, you can use other combinations of SDA/SCL pins as well but remember to change them in the MicroPython script.

You may also like to read:

Raspberry Pi Pico with ESP-01

The ESP-01 module consists of 8 pins. However, we will use 5 pins to connect with the Pi Pico board. These include the VCC, EN, GND, RX, and TX pins. RX and TX pins of the module will be connected with the UART pins of the Pi Pico board. Let us first have a look at the Raspberry Pi Pi UART Pins.

Raspberry Pi Pico UART Pins

Raspberry Pi Pico contains two identical UART peripherals with separate 32×8 Tx and 32×12 Rx FIFOs.

Raspberry Pi Pico pinout diagram

The following table lists the GPIO pins for both UART peripherals which are exposed on Raspberry Pi Pico development board pinouts. 

UART PinsGPIO Pins
UART0-TXGP0/GP12/GP16
UART0-RXGP1/GP13/GP17
UART1-TXGP4/GP8
UART1-RXGP5/GP9

For this guide we will use UART0-TX and RX pins.

Follow the connection diagram below to connect the two devices.

Raspberry Pi PicoESP-01
3.3VVCC
3.3VEN
GNDGND
GP1 (UART0 RX)TX
GP0 (UART0 TX)RX

Connection Diagram Raspberry Pi Pico with BME280 and ESP-01

We have used the same connections as given in the two tables above. All three devices will be commonly grounded and will be powered with the same 3.3V pin of Raspberry Pi Pico.

The diagram below shows the connection diagram of Raspberry Pi Pico with BME280 and ESP-01.

Raspberry Pi Pico with BME280 and ESP-01 connection diagram
Raspberry Pi Pico with BME280 and ESP-01 connection diagram

You may like to read:

BME280 MicroPython Library

We will have to install the BME280 library for MicroPython to continue with our project.

To successfully do that, open your Thonny IDE with your Raspberry Pi Pico plugged in your system. Go to Tools > Manage Packages. This will open up the Thonny Package Manager.

Raspberry Pi Pico Installing ssd1306 OLED library MicoPython 1

Search for “bme280” in the search bar by typing its name and clicking the button ‘Search on PyPI.’ From the following search results click on the one highlighted below: micropython-bme280. Install this library.

Installing BME280 Micropython library Thonny

After a few moments this library will get successfully installed. Now we are ready to program our Raspberry Pi Pico with BME280 sensor in MicroPython.

MicroPython Script Raspberry Pi Pico BME280 Web Server with ESP-01

import uos
import machine
import utime
from machine import Pin, I2C        #importing relevant modules & classes
import bme280       #importing BME280 library

recv_buf="" # receive buffer global variable

print()
print("Machine: \t" + uos.uname()[4])
print("MicroPython: \t" + uos.uname()[3])

i2c=I2C(1,sda=Pin(2), scl=Pin(3), freq=400000)    #initializing the I2C method 

uart0 = machine.UART(0, baudrate=115200)
print(uart0)

def Rx_ESP_Data():
    recv=bytes()
    while uart0.any()>0:
        recv+=uart0.read(1)
    res=recv.decode('utf-8')
    return res
def Connect_WiFi(cmd, uart=uart0, timeout=3000):
    print("CMD: " + cmd)
    uart.write(cmd)
    utime.sleep(7.0)
    Wait_ESP_Rsp(uart, timeout)
    print()

def Send_AT_Cmd(cmd, uart=uart0, timeout=3000):
    print("CMD: " + cmd)
    uart.write(cmd)
    Wait_ESP_Rsp(uart, timeout)
    print()
    
def Wait_ESP_Rsp(uart=uart0, timeout=3000):
    prvMills = utime.ticks_ms()
    resp = b""
    while (utime.ticks_ms()-prvMills)<timeout:
        if uart.any():
            resp = b"".join([resp, uart.read(1)])
    print("resp:")
    try:
        print(resp.decode())
    except UnicodeError:
        print(resp)
    
Send_AT_Cmd('AT\r\n')          #Test AT startup
Send_AT_Cmd('AT+GMR\r\n')      #Check version information
Send_AT_Cmd('AT+CIPSERVER=0\r\n')      #Check version information
Send_AT_Cmd('AT+RST\r\n')      #Check version information
Send_AT_Cmd('AT+RESTORE\r\n')  #Restore Factory Default Settings
Send_AT_Cmd('AT+CWMODE?\r\n')  #Query the Wi-Fi mode
Send_AT_Cmd('AT+CWMODE=1\r\n') #Set the Wi-Fi mode = Station mode
Send_AT_Cmd('AT+CWMODE?\r\n')  #Query the Wi-Fi mode again
Connect_WiFi('AT+CWJAP="HUAWEI-u67E","4uF77R2n"\r\n', timeout=5000) #Connect to AP
Send_AT_Cmd('AT+CIFSR\r\n',timeout=5000)    #Obtain the Local IP Address
Send_AT_Cmd('AT+CIPMUX=1\r\n')    #Obtain the Local IP Address
utime.sleep(1.0)
Send_AT_Cmd('AT+CIPSERVER=1,80\r\n')    #Obtain the Local IP Address
utime.sleep(1.0)
print ('Starting connection to ESP8266...')
while True:
    res =""
    res=Rx_ESP_Data()
    utime.sleep(2.0)
    bme = bme280.BME280(i2c=i2c)        #BME280 object created
    temperature = bme.values[0]         #reading the value of temperature
    pressure = bme.values[1]            #reading the value of pressure
    humidity = bme.values[2]            #reading the value of humidity

    if '+IPD' in res: # if the buffer contains IPD(a connection), then respond with HTML handshake
        print('Temperature: ', temperature)    #printing BME280 values
        print('Humidity: ', humidity)
        print('Pressure: ', pressure)
        id_index = res.find('+IPD')
        print("resp:")
        print(res)
        connection_id =  res[id_index+5]
        print("connectionId:" + connection_id)
        print ('! Incoming connection - sending webpage')
        uart0.write('AT+CIPSEND='+connection_id+',1350'+'\r\n')  #Send a HTTP response then a webpage as bytes the 108 is the amount of bytes you are sending, change this if you change the data sent below
        utime.sleep(1.0)
        uart0.write('HTTP/1.1 200 OK'+'\r\n')
        uart0.write('Content-Type: text/html'+'\r\n')
        uart0.write('Connection: close'+'\r\n')
        uart0.write(''+'\r\n')
        uart0.write('<!DOCTYPE HTML>'+'\r\n')
        uart0.write('<html><head>'+'\r\n')
        uart0.write('<title>BME280 Web Server</title>'+'\r\n')
        uart0.write('<meta http-equiv=\"refresh\" content=\"10\">'+'\r\n')
        uart0.write('<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\'\r\n')
        uart0.write('<link rel=\"icon\" href=\"data:,\">'+'\r\n')
        uart0.write('<style>'+'\r\n')
        uart0.write('html {font-family: Arial; display: inline-block; text-align: center;}'+'\r\n')
        uart0.write('p {  font-size: 1.2rem;}'+'\r\n')
        uart0.write('body {  margin: 0;}'+'\r\n')
        uart0.write('.topnav { overflow: hidden; background-color: #5c055c; color: white; font-size: 1.7rem; }'+'\r\n')
        uart0.write('.content { padding: 20px; }'+'\r\n')
        uart0.write('.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }'+'\r\n')
        uart0.write('.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }'+'\r\n')
        uart0.write('.reading { font-size: 2.8rem; }'+'\r\n')
        uart0.write('.card.temperature { color: #0e7c7b; }'+'\r\n')
        uart0.write('.card.humidity { color: #17bebb; }'+'\r\n')
        uart0.write('.card.pressure { color: hsl(113, 61%, 29%); }'+'\r\n')
        uart0.write('.card.gas { color: #5c055c; }'+'\r\n')
        uart0.write('</style>'+'\r\n')
        uart0.write('</head>'+'\r\n')
        uart0.write('<body>'+'\r\n')
        uart0.write('<div class=\"topnav\">'+'\r\n')
        uart0.write('<h3>Raspberry Pi Pico BME280 WEB SERVER</h3>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"content\">'+'\r\n')
        uart0.write('<div class=\"cards\">'+'\r\n')
        uart0.write('<div class=\"card temperature\">'+'\r\n')
        uart0.write('<h4>Temp. Celsius</h4><p><span class=\"reading\">' + temperature + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card humidity\">'+'\r\n')
        uart0.write('<h4>Humidity</h4><p><span class=\"reading\">' + humidity + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card pressure\">'+'\r\n')
        uart0.write('<h4>PRESSURE</h4><p><span class=\"reading\">' + pressure +'</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('</div></div>'+'\r\n')
        uart0.write('</body></html>'+'\r\n')
        utime.sleep(4.0)
        Send_AT_Cmd('AT+CIPCLOSE='+ connection_id+'\r\n') # once file sent, close connection
        utime.sleep(4.0)
        recv_buf="" #reset buffer
        print ('Waiting For connection...')

How the Code Works?

We will start by importing the machine module and the uos module. We will also import I2C and Pin class from the machine module.  This is because we have to specify the pin for I2C communication. We also import the utime module so that we will be able to add a delay of 10 seconds in between our readings. Also, import the bme280 library which we previously installed.

import uos
import machine
import utime
from machine import Pin, I2C        #importing relevant modules & classes
import bme280       #importing BME280 library

Then, we will print the information about our current operating system in the Thonny shell terminal. We will uos.uname() and print the operating system version and release.

print()
print("Machine: \t" + uos.uname()[4])
print("MicroPython: \t" + uos.uname()[3])

Initialize I2C Communication

Next, we will initialize the I2C GPIO pins for SCL and SDA respectively. We have used the I2C1 SCL and I2C0 SDA pins.

We have created an I2C() method which takes in four parameters. The first parameter is the I2C channel that we are using. The second parameter specifies the I2C GPIO pin of the board which is connected to the SDA line. The third parameter specifies the I2C GPIO pin of the board which is connected to the SCL line. The last parameter is the frequency connection.

We are setting the SCL on pin 3 and the SDA on pin 2.

i2c=I2C(1,sda=Pin(2), scl=Pin(3), freq=400000)

Initialize UART Communication

Then we will create an uart object by using UART() and specify the UART channel as the first parameter and the baud rate as the second parameter. We are using UART0 in this case with baud rate 115200 for the uart communication. ESP8266 has a default baud rate of 115200 hence we will use the same baud rate here for Raspberry Pi Pico UART communication in order to create synchronization. Moreover we will also print the UART details in the shell terminal.

uart0 = machine.UART(0, baudrate=115200)
print(uart0)

This Connect_WiFi() function is used to connect ESP8266 with WiFi.

def Connect_WiFi(cmd, uart=uart0, timeout=3000):
    print("CMD: " + cmd)
    uart.write(cmd)
    utime.sleep(7.0)
    Wait_ESP_Rsp(uart, timeout)
    print()

Next, we will define three functions. The first one is Rx_ESP_Data(). This reads the serial data being received. This data is decoded from UTF-8 format and returned.

def Rx_ESP_Data():
    recv=bytes()
    while uart0.any()>0:
        recv+=uart0.read(1)
    res=recv.decode('utf-8')
    return res

The second function is Send_AT_Cmd(cmd, uart=uart0, timeout=3000). It takes in three parameters, the AT command, the UART channel and the response time. This function will be used to it send an AT command to ESP8266 via uart0. The response time is set to 3 seconds.


def Send_AT_Cmd(cmd, uart=uart0, timeout=3000):
    print("CMD: " + cmd)
    uart.write(cmd)
    Wait_ESP_Rsp(uart, timeout)
    print()
    

The Wait_ESP_Rsp(uart=uart0, timeout=3000) function waits for 3 seconds to get the response from ESP8266. After receiving the data from ESP8266 it concatenates the received bytes and prints them on the shell terminal.

def Wait_ESP_Rsp(uart=uart0, timeout=3000):
    prvMills = utime.ticks_ms()
    resp = b""
    while (utime.ticks_ms()-prvMills)<timeout:
        if uart.any():
            resp = b"".join([resp, uart.read(1)])
    print("resp:")
    try:
        print(resp.decode())
    except UnicodeError:
        print(resp)

AT Commands

Now let us look at the series of AT commands that we will send through UART0 to ESP8266.

Send_AT_Cmd('AT\r\n')          #Test AT startup
Send_AT_Cmd('AT+GMR\r\n')      #Check version information
Send_AT_Cmd('AT+CIPSERVER=0\r\n')      #Check version information
Send_AT_Cmd('AT+RST\r\n')      #Check version information
Send_AT_Cmd('AT+RESTORE\r\n')  #Restore Factory Default Settings
Send_AT_Cmd('AT+CWMODE?\r\n')  #Query the Wi-Fi mode
Send_AT_Cmd('AT+CWMODE=1\r\n') #Set the Wi-Fi mode = Station mode
Send_AT_Cmd('AT+CWMODE?\r\n')  #Query the Wi-Fi mode again
Connect_WiFi('AT+CWJAP="HUAWEI-u67E","4uF77R2n"\r\n', timeout=5000) #Connect to AP
Send_AT_Cmd('AT+CIFSR\r\n',timeout=5000)    #Obtain the Local IP Address
Send_AT_Cmd('AT+CIPMUX=1\r\n')    #Obtain the Local IP Address
utime.sleep(1.0)
Send_AT_Cmd('AT+CIPSERVER=1,80\r\n')    #Obtain the Local IP Address
utime.sleep(1.0)

AT: This type of command is used to test the startup function of WiFi module. The response would be ok, against this command if everything is ok.

Send_AT_Cmd('AT\r\n')          #Test AT startup

AT+GMR : This type of AT command is used to check the version of AT command and we used SDK version of AT command in this type of WIFI module.

Send_AT_Cmd('AT+GMR\r\n')      #Check version information

AT+CIPSERVER=0: This configures the ESP8266 as server and sets the mode as 0 which means delete server (need to follow by restart)

Send_AT_Cmd('AT+CIPSERVER=0\r\n')     

AT+RST: This type of command is used for reset the WiFi module when it is in working condition. The response would be ok, when reset the module.

Send_AT_Cmd('AT+RST\r\n')   

AT+RESTORE: This type of command is used to restore factory settings means, when this command is entered then all the parameters are reset automatically to default one’s.

Send_AT_Cmd('AT+RESTORE\r\n')  #Restore Factory Default Settings

AT+CWMODE? : This type of command is used to query the WiFi mode of ESP8266.

Send_AT_Cmd('AT+CWMODE?\r\n')  #Query the WiFi mode

AT+CWMODE=1 : This sets the WiFi mode of ESP8266 in this case in station mode.

Send_AT_Cmd('AT+CWMODE=1\r\n') #Set the WiFi mode = Station mode

AT+CWJAP=”SSID”,”PASSWORD”\r\n’, timeout=TIME_ms : This connects the ESP8266 with an AP whose SSID and password are given, The timeout here is the reconnection time.

Connect_WiFi('AT+CWJAP="HUAWEI-u67E","4uF77R2n"\r\n', timeout=5000) #Connect to AP

AT+CIFSR: This command obtains the local IP address.

Send_AT_Cmd('AT+CIFSR\r\n')

AT+CIPMUX=1:This command is used to enable multiple connections (maximum 4)

Send_AT_Cmd('AT+CIPMUX=1\r\n')

AT+CIPSERVER=1: This command configures ESP8266 as server.

Send_AT_Cmd('AT+CIPSERVER=1,80\r\n')

while loop

Inside the while loop, we will first call Rx_ESP_Data() which returns the data that the ESP8266 receives. This is saved in the variable ‘res.’

Add a delay of 2 seconds before proceeding further.

res =""
res=Rx_ESP_Data()
utime.sleep(2.0)

Next, we create an object of BME280 named bme and access the temperature, pressure and humidity values through it. These readings are saved in their corresponding variables.

bme = bme280.BME280(i2c=i2c)        #BME280 object created
temperature = bme.values[0]         #reading the value of temperature
pressure = bme.values[1]            #reading the value of pressure
humidity = bme.values[2]            #reading the value of humidity

We will check if the buffer contains an IPD connection or not. If it does then, respond with an HTML handshake.

Print the BME280 sensor readings along with the response in the shell terminal. Obtain the connection ID and print it as well.

if '+IPD' in res: # if the buffer contains IPD(a connection), then respond with HTML handshake
        print('Temperature: ', temperature)    #printing BME280 values
        print('Humidity: ', humidity)
        print('Pressure: ', pressure)
        id_index = res.find('+IPD')
        print("resp:")
        print(res)
        connection_id =  res[id_index+5]
        print("connectionId:" + connection_id)

Then by using the uart object on the write() method, we will send the bytes to the UART. First, we are writing the AT command: AT+CIPSEND=’ID’, ‘LENGTH’ This will set the length of the data that will be sent. Next after a delay of 1 second, we will write the HTML body that will build the web page to the serial port. After that, we will close the multiple connections as we are sending the AT command: AT+CIPCLOSE=’ID’. Then we will reset the buffer and wait for the connection.

print ('! Incoming connection - sending webpage')
        uart0.write('AT+CIPSEND='+connection_id+',1350'+'\r\n')  #Send a HTTP response then a webpage as bytes the 108 is the amount of bytes you are sending, change this if you change the data sent below
        utime.sleep(1.0)
        uart0.write('HTTP/1.1 200 OK'+'\r\n')
        uart0.write('Content-Type: text/html'+'\r\n')
        uart0.write('Connection: close'+'\r\n')
        uart0.write(''+'\r\n')
        uart0.write('<!DOCTYPE HTML>'+'\r\n')
        uart0.write('<html><head>'+'\r\n')
        uart0.write('<title>BME280 Web Server</title>'+'\r\n')
        uart0.write('<meta http-equiv=\"refresh\" content=\"10\">'+'\r\n')
        uart0.write('<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\'\r\n')
        uart0.write('<link rel=\"icon\" href=\"data:,\">'+'\r\n')
        uart0.write('<style>'+'\r\n')
        uart0.write('html {font-family: Arial; display: inline-block; text-align: center;}'+'\r\n')
        uart0.write('p {  font-size: 1.2rem;}'+'\r\n')
        uart0.write('body {  margin: 0;}'+'\r\n')
        uart0.write('.topnav { overflow: hidden; background-color: #5c055c; color: white; font-size: 1.7rem; }'+'\r\n')
        uart0.write('.content { padding: 20px; }'+'\r\n')
        uart0.write('.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }'+'\r\n')
        uart0.write('.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }'+'\r\n')
        uart0.write('.reading { font-size: 2.8rem; }'+'\r\n')
        uart0.write('.card.temperature { color: #0e7c7b; }'+'\r\n')
        uart0.write('.card.humidity { color: #17bebb; }'+'\r\n')
        uart0.write('.card.pressure { color: hsl(113, 61%, 29%); }'+'\r\n')
        uart0.write('.card.gas { color: #5c055c; }'+'\r\n')
        uart0.write('</style>'+'\r\n')
        uart0.write('</head>'+'\r\n')
        uart0.write('<body>'+'\r\n')
        uart0.write('<div class=\"topnav\">'+'\r\n')
        uart0.write('<h3>Raspberry Pi Pico BME280 WEB SERVER</h3>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"content\">'+'\r\n')
        uart0.write('<div class=\"cards\">'+'\r\n')
        uart0.write('<div class=\"card temperature\">'+'\r\n')
        uart0.write('<h4>Temp. Celsius</h4><p><span class=\"reading\">' + temperature + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card humidity\">'+'\r\n')
        uart0.write('<h4>Humidity</h4><p><span class=\"reading\">' + humidity + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card pressure\">'+'\r\n')
        uart0.write('<h4>PRESSURE</h4><p><span class=\"reading\">' + pressure +'</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('</div></div>'+'\r\n')
        uart0.write('</body></html>'+'\r\n')
        utime.sleep(4.0)
        Send_AT_Cmd('AT+CIPCLOSE='+ connection_id+'\r\n') # once file sent, close connection
        utime.sleep(4.0)
        recv_buf="" #reset buffer
        print ('Waiting For connection...')

Create Web page (HTML+CSS)

To build the web page, we will add HTML code and for styling we will add CSS script.

        uart0.write('HTTP/1.1 200 OK'+'\r\n')
        uart0.write('Content-Type: text/html'+'\r\n')
        uart0.write('Connection: close'+'\r\n')
        uart0.write(''+'\r\n')
        uart0.write('<!DOCTYPE HTML>'+'\r\n')
        uart0.write('<html><head>'+'\r\n')
        uart0.write('<title>BME280 Web Server</title>'+'\r\n')
        uart0.write('<meta http-equiv=\"refresh\" content=\"10\">'+'\r\n')
        uart0.write('<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\'\r\n')
        uart0.write('<link rel=\"icon\" href=\"data:,\">'+'\r\n')
        uart0.write('<style>'+'\r\n')
        uart0.write('html {font-family: Arial; display: inline-block; text-align: center;}'+'\r\n')
        uart0.write('p {  font-size: 1.2rem;}'+'\r\n')
        uart0.write('body {  margin: 0;}'+'\r\n')
        uart0.write('.topnav { overflow: hidden; background-color: #5c055c; color: white; font-size: 1.7rem; }'+'\r\n')
        uart0.write('.content { padding: 20px; }'+'\r\n')
        uart0.write('.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }'+'\r\n')
        uart0.write('.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }'+'\r\n')
        uart0.write('.reading { font-size: 2.8rem; }'+'\r\n')
        uart0.write('.card.temperature { color: #0e7c7b; }'+'\r\n')
        uart0.write('.card.humidity { color: #17bebb; }'+'\r\n')
        uart0.write('.card.pressure { color: hsl(113, 61%, 29%); }'+'\r\n')
        uart0.write('.card.gas { color: #5c055c; }'+'\r\n')
        uart0.write('</style>'+'\r\n')
        uart0.write('</head>'+'\r\n')
        uart0.write('<body>'+'\r\n')
        uart0.write('<div class=\"topnav\">'+'\r\n')
        uart0.write('<h3>Raspberry Pi Pico BME280 WEB SERVER</h3>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"content\">'+'\r\n')
        uart0.write('<div class=\"cards\">'+'\r\n')
        uart0.write('<div class=\"card temperature\">'+'\r\n')
        uart0.write('<h4>Temp. Celsius</h4><p><span class=\"reading\">' + temperature + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card humidity\">'+'\r\n')
        uart0.write('<h4>Humidity</h4><p><span class=\"reading\">' + humidity + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card pressure\">'+'\r\n')
        uart0.write('<h4>PRESSURE</h4><p><span class=\"reading\">' + pressure +'</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('</div></div>'+'\r\n')
        uart0.write('</body></html>'+'\r\n')
        utime.sleep(4.0)

Now let’s go through each line of HTML code to understand how it builds the web page. 

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

HTML is a hypertext markup language which is used to build web pages. All web browsers understand this language and can read web pages which are based on HTML language. 

In HTML, we place all the content of a web page between <html> and </html> tags. The <html> tag shows the beginning of a web page and the  </html> indicates the end of a web page. 

HTML code mainly includes two parts such as head and body. The head part contains CSS, scripts, meta tags, links of external resources and styling codes. It is placed between <head> and </head> tags. 

uart0.write('<!DOCTYPE HTML>'+'\r\n')
        uart0.write('<html><head>'+'\r\n')
        uart0.write('<title>BME280 Web Server</title>'+'\r\n')
        uart0.write('<meta http-equiv=\"refresh\" content=\"10\">'+'\r\n')
        uart0.write('<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\'\r\n')
        uart0.write('<link rel=\"icon\" href=\"data:,\">'+'\r\n')
        uart0.write('<style>'+'\r\n')
        uart0.write('html {font-family: Arial; display: inline-block; text-align: center;}'+'\r\n')
        uart0.write('p {  font-size: 1.2rem;}'+'\r\n')
        uart0.write('body {  margin: 0;}'+'\r\n')
        uart0.write('.topnav { overflow: hidden; background-color: #5c055c; color: white; font-size: 1.7rem; }'+'\r\n')
        uart0.write('.content { padding: 20px; }'+'\r\n')
        uart0.write('.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }'+'\r\n')
        uart0.write('.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }'+'\r\n')
        uart0.write('.reading { font-size: 2.8rem; }'+'\r\n')
        uart0.write('.card.temperature { color: #0e7c7b; }'+'\r\n')
        uart0.write('.card.humidity { color: #17bebb; }'+'\r\n')
        uart0.write('.card.pressure { color: hsl(113, 61%, 29%); }'+'\r\n')
        uart0.write('.card.gas { color: #5c055c; }'+'\r\n')
        uart0.write('</style>'+'\r\n')
        uart0.write('</head>'+'\r\n')

We will start with the title of the web page. The <title> tag will indicate the beginning of the title and the </title> tag will indicate the ending. In between these tags, we will specify “BME280 Web Server” which will be displayed in the browser’s title bar.

uart0.write('<title>BME280 Web Server</title>'+'\r\n')

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. Moreover, this meta tag will make sure our web server is available for all browsers e.g., smartphones, laptops, computers etc.

uart0.write('<meta http-equiv=\"refresh\" content=\"10\">'+'\r\n')
uart0.write('<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\'\r\n')
Styling Web Page with CSS

CSS is used to give styles to a web page. To add CSS files in head tags, we use <style></style> tags. This CSS code styles the cards and web page by specifying the colours, font, font size etc.

This CSS code sets text alignment, padding, margin and width of body tags of HTML document along with the font size, colour of the cards etc.  We want to display temperature, pressure and humidity on cards and want them to show at the centre location of the web page. 

uart0.write('<style>'+'\r\n')
        uart0.write('html {font-family: Arial; display: inline-block; text-align: center;}'+'\r\n')
        uart0.write('p {  font-size: 1.2rem;}'+'\r\n')
        uart0.write('body {  margin: 0;}'+'\r\n')
        uart0.write('.topnav { overflow: hidden; background-color: #5c055c; color: white; font-size: 1.7rem; }'+'\r\n')
        uart0.write('.content { padding: 20px; }'+'\r\n')
        uart0.write('.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }'+'\r\n')
        uart0.write('.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }'+'\r\n')
        uart0.write('.reading { font-size: 2.8rem; }'+'\r\n')
        uart0.write('.card.temperature { color: #0e7c7b; }'+'\r\n')
        uart0.write('.card.humidity { color: #17bebb; }'+'\r\n')
        uart0.write('.card.pressure { color: hsl(113, 61%, 29%); }'+'\r\n')
        uart0.write('.card.gas { color: #5c055c; }'+'\r\n')
        uart0.write('</style>'+'\r\n')
HTML Web Page Body

The second most important part of an HTML document is the body which goes inside the tags <body> and </body>. The body part includes the main content of the web page such as headings, images, buttons, icons, tables, charts, etc. For example, in this Raspberry Pi Pico BME280 MicroPython based web server, the body part includes heading and three cards to display the BME280 sensor readings. 

uart0.write('<body>'+'\r\n')
        uart0.write('<div class=\"topnav\">'+'\r\n')
        uart0.write('<h3>Raspberry Pi Pico BME280 WEB SERVER</h3>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"content\">'+'\r\n')
        uart0.write('<div class=\"cards\">'+'\r\n')
        uart0.write('<div class=\"card temperature\">'+'\r\n')
        uart0.write('<h4>Temp. Celsius</h4><p><span class=\"reading\">' + temperature + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card humidity\">'+'\r\n')
        uart0.write('<h4>Humidity</h4><p><span class=\"reading\">' + humidity + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card pressure\">'+'\r\n')
        uart0.write('<h4>PRESSURE</h4><p><span class=\"reading\">' + pressure +'</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('</div></div>'+'\r\n')
        uart0.write('</body></html>'+'\r\n')

We will include the heading of our webpage inside the <h3></h3> tags and it will be “Raspberry Pi Pico BME280 WEB SERVER”.

uart0.write('<h3>Raspberry Pi Pico BME280 WEB SERVER</h3>'+'\r\n')

Next, we will include the following lines of code to display texts and cards for temperature, pressure and humidity readings.

        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"content\">'+'\r\n')
        uart0.write('<div class=\"cards\">'+'\r\n')
        uart0.write('<div class=\"card temperature\">'+'\r\n')
        uart0.write('<h4>Temp. Celsius</h4><p><span class=\"reading\">' + temperature + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card humidity\">'+'\r\n')
        uart0.write('<h4>Humidity</h4><p><span class=\"reading\">' + humidity + '</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('<div class=\"card pressure\">'+'\r\n')
        uart0.write('<h4>PRESSURE</h4><p><span class=\"reading\">' + pressure +'</p>'+'\r\n')
        uart0.write('</div>'+'\r\n')
        uart0.write('</div></div>'+'\r\n')

Demonstration

After you have copied the following code onto a new file, click the ‘Save’ icon to save your program code on your PC.

After you have saved the code press the Run button to upload the code to your board. Before uploading code make sure the correct board is selected.

In the shell terminal of your IDE, you will be able to view the IP address after a successful connection gets established:

Raspberry Pi Pico BME280 Web Server shell terminal

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 Raspberry Pi Pico web server will receive an HTTP request.

Raspberry Pi Pico BME280 Web Server overview

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

Raspberry Pi Pico BME280 Web Server laptop demo

The web page viewed from a cell phone will look like this:

Raspberry Pi Pico BME280 Web Server mobile demo

Video Demo:

You may also like to read other Web Server projects with Raspberry Pi Pico:

You can check these related BME280 tutorials:

Leave a Comment