Servidor Web Raspberry Pi Pico com BME280 (Estação Meteorológica)

Tempo de leitura: 15 minutes

Neste tutorial, vamos criar um servidor web Raspberry Pi Pico usando um sensor BME280 e MicroPython. BME280 é usado para medir a temperatura em Celsius, a temperatura em Fahrenheit, umidade e pressão. Este servidor da web funcionará como uma estação meteorológica, pois mostrará leituras de temperatura, umidade e pressão na página da web usando o MicroPython.

Usaremos o firmware MicroPython para construir um servidor web Raspberry Pi Pico responsivo que pode ser acessado através de qualquer dispositivo que tenha um navegador web, e o dispositivo deve estar conectado à sua rede local. Isso significa que o dispositivo deve estar conectado à mesma rede à qual a placa está conectada.

Raspberry Pi Pico não suporta recursos Wi-Fi, portanto, temos que usar um módulo Wi-Fi separado para habilitar a conectividade Wi-Fi. Portanto, vamos fazer a interface e programar o módulo Wi-Fi ESP-01 com o Raspberry Pi Pico para habilitar os recursos Wi-Fi. Usaremos o Thonny IDE para programar o Raspberry Pi Pico com ESP-01 e BME280 em MircoPython. Usaremos comandos AT através da porta serial que é UART para configurar o módulo Wi-Fi ESP-01.

 

Pré-requisitos

Antes de começarmos esta lição, certifique-se de que você esteja familiarizado e tenha a versão mais recente do Python 3 em seu sistema, tenha configurado o MicoPython no Raspberry Pi Pico e tenha um Ambiente de Desenvolvimento Integrado (IDE) em execução no qual faremos a programação.

 

Introdução ao BME280

O sensor BME280 é usado para medir leituras de temperatura ambiente, pressão barométrica e umidade relativa. É usado principalmente em aplicativos da Web e móveis, onde o baixo consumo de energia é fundamental. Este sensor usa I2C ou SPI para comunicar dados com os microcontroladores. Embora existam várias versões diferentes do BME280 disponíveis no mercado, a que iremos estudar utiliza protocolo de comunicação I2C e SPI.

I2C significa Circuito Inter-Integrado e funciona segundo o princípio do sistema síncrono, multi-mestre e multi-escravo. Com o BME280 e o microcontrolador, o Raspberry Pi Pico atua como mestre, e o sensor BME280 como escravo por ser um dispositivo externo, atua como escravo. O Raspberry Pi Pico se comunica com o sensor BME280 através do protocolo I2C para fornecer leituras de temperatura, pressão barométrica e umidade relativa.

Diagrama de pinagem

A figura abaixo mostra o sensor BME280 e sua pinagem.

  • VCC: conectado com 3.3V
  • SCL: usado para gerar o sinal de clock
  • SDA: usado no envio e recebimento de dados

 

Interface Raspberry Pi Pico com BME280 e ESP-01

Esta seção mostra como conectar o Raspberry Pi Pico com o sensor BME280 e o ESP-01.

Vamos precisar dos seguintes componentes:

  • Raspberry PI Pico
  • Sensor BME280
  • Módulo ESP-01
  • Fios de conexão
  • Protoboard

 

Raspberry Pi Pico com BME280

A conexão do BME280 com o Raspberry Pi Pico é muito simples. Temos que conectar o terminal VCC com 3,3V, terra com o terra (terra comum), SCL do sensor com SCL da placa e SDA do sensor com o pino SDA da placa.

Pinos I2C Raspberry Pi Pico

Raspberry Pi Pico tem dois controladores I2C. Ambos os controladores I2C são acessíveis através dos pinos GPIO do Raspberry Pi Pico. A tabela a seguir mostra a conexão dos pinos GPIO com os dois controladores I2C. Cada conexão do controlador pode ser configurada através de vários pinos GPIO conforme mostrado na figura. Mas antes de usar um controlador I2C, você deve configurar no software quais pinos GPIO você deseja usar com um controlador I2C específico.

Controlador I2CPinos GPIO
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

As conexões entre os dois dispositivos que estamos usando podem ser vistas abaixo.

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

Usamos as mesmas conexões especificadas na tabela acima. No entanto, você também pode usar outras combinações de pinos SDA/SCL, mas lembre-se de alterá-las no script MicroPython.

Raspberry Pi Pico com ESP-01

O módulo ESP-01 é composto por 8 pinos. No entanto, usaremos 5 pinos para conectar com a placa Pi Pico. Estes incluem os pinos VCC, EN, GND, RX e TX. Os pinos RX e TX do módulo serão conectados aos pinos UART da placa Pi Pico. Vamos primeiro dar uma olhada nos pinos UART do Raspberry Pi Pi.

Pinos UART Raspberry Pi Pico

Raspberry Pi Pico contém dois periféricos UART idênticos com FIFOs 32×8 Tx e 32×12 Rx separados.

A tabela a seguir lista os pinos GPIO para ambos os periféricos UART que são expostos nas pinagens da placa de desenvolvimento Raspberry Pi Pico.

Pinos UARTPinos GPIO
UART0-TXGP0/GP12/GP16
UART0-RXGP1/GP13/GP17
UART1-TXGP4/GP8
UART1-RXGP5/GP9

Para este guia, usaremos os pinos UART0-TX e RX.

Siga o diagrama de conexão abaixo para conectar os dois dispositivos.

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

Diagrama de conexão Raspberry Pi Pico com BME280 e ESP-01

Usamos as mesmas conexões fornecidas nas duas tabelas acima. Todos os três dispositivos serão comumente aterrados e alimentados com o mesmo pino de 3,3 V do Raspberry Pi Pico.

O diagrama abaixo mostra o diagrama de conexão do Raspberry Pi Pico com BME280 e ESP-01.

Raspberry Pi Pico com diagrama de conexão BME280 e ESP-01
Raspberry Pi Pico com diagrama de conexão BME280 e ESP-01

Biblioteca MicroPython BME280

Teremos que instalar a biblioteca BME280 para MicroPython para continuar com nosso projeto.

Para fazer isso com sucesso, abra seu Thonny IDE com seu Raspberry Pi Pico conectado ao seu sistema. Vá para Tools > Manage Packages. Isso abrirá o Thonny Package Manager.

Procure por “bme280” na barra de pesquisa digitando seu nome e clicando no botão ‘Pesquisar no PyPI’. Instale esta biblioteca.

Após alguns momentos, esta biblioteca será instalada com sucesso. Agora estamos prontos para programar nosso Raspberry Pi Pico com sensor BME280 em MicroPython.

 

Servidor Web MicroPython Script Raspberry Pi Pico BME280 com 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...')

 

Como o Código Funciona?

Começaremos importando o módulo máquina e o módulo uos. Também importaremos a classe I2C e Pin do módulo da máquina. Isso ocorre porque temos que especificar o pino para comunicação I2C. Também importamos o módulo utime para que possamos adicionar um atraso de 10 segundos entre nossas leituras. Além disso, importe a biblioteca bme280 que instalamos anteriormente.

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

Em seguida, imprimiremos as informações sobre nosso sistema operacional atual no terminal Thonny shell. Vamos uos.uname() e imprimir a versão e lançamento do sistema operacional.

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

Inicializar a comunicação I2C

Em seguida, inicializaremos os pinos I2C GPIO para SCL e SDA, respectivamente. Usamos os pinos I2C1 SCL e I2C0 SDA.

Criamos um método I2C() que recebe quatro parâmetros. O primeiro parâmetro é o canal I2C que estamos usando. O segundo parâmetro especifica o pino I2C GPIO da placa que está conectada à linha SDA. O terceiro parâmetro especifica o pino I2C GPIO da placa que está conectada à linha SCL. O último parâmetro é a conexão de frequência.

Estamos configurando o SCL no pino 3 e o SDA no pino 2.

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

Inicializar a comunicação UART

Em seguida, criaremos um objeto uart usando UART() e especificaremos o canal UART como o primeiro parâmetro e a taxa de transmissão como o segundo parâmetro. Estamos usando UART0 neste caso com baud rate 115200 para a comunicação uart. O ESP8266 tem uma taxa de transmissão padrão de 115200, portanto, usaremos a mesma taxa de transmissão aqui para a comunicação UART do Raspberry Pi Pico para criar a sincronização. Além disso, também imprimiremos os detalhes do UART no terminal do shell.

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

Esta função Connect_WiFi() é usada para conectar o ESP8266 com 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()

A seguir, definiremos três funções. O primeiro é Rx_ESP_Data(). Isso lê os dados seriais que estão sendo recebidos. Esses dados são decodificados do formato UTF-8 e retornados.

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

A segunda função é Send_AT_Cmd(cmd, uart=uart0, timeout=3000). Leva em três parâmetros, o comando AT, o canal UART e o tempo de resposta. Esta função será utilizada para enviar um comando AT para o ESP8266 via uart0. O tempo de resposta é definido para 3 segundos.

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

A função Wait_ESP_Rsp(uart=uart0, timeout=3000) aguarda 3 segundos para obter a resposta do ESP8266. Após receber os dados do ESP8266 ele concatena os bytes recebidos e os imprime no terminal shell.

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)

Comandos AT

Agora vamos ver a série de comandos AT que enviaremos através do UART0 para o 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="A32-X23F","3X421X1a"\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: Este tipo de comando é usado para testar a função de inicialização do módulo WiFi. A resposta seria ok, contra este comando se tudo estiver ok.

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

AT+GMR: Este tipo de comando AT é usado para verificar a versão do comando AT e usamos a versão SDK do comando AT neste tipo de módulo WIFI.

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

AT+CIPSERVER=0: Isso configura o ESP8266 como servidor e define o modo como 0, o que significa excluir servidor (precisa seguir por reinicialização)

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

AT+RST: Este tipo de comando é usado para redefinir o módulo WiFi quando estiver em condições de funcionamento. A resposta seria ok, quando resetar o módulo.

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

AT+RESTORE: Este tipo de comando é usado para restaurar as configurações de fábrica significa, quando este comando é inserido, todos os parâmetros são redefinidos automaticamente para os padrões.

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

AT+CWMODE?: Este tipo de comando é usado para consultar o modo WiFi do ESP8266.

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

AT+CWMODE=1: Isso define o modo WiFi do ESP8266 neste caso no modo estação.

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

AT+CWJAP=”SSID”,”PASSWORD”\r\n’, timeout=TIME_ms: Isso conecta o ESP8266 com um AP cujo SSID e senha são fornecidos, O tempo limite aqui é o tempo de reconexão.

Connect_WiFi('AT+CWJAP="A31-x221","1uX73A1n"\r\n', timeout=5000) #Connect to AP

AT+CIFSR: Este comando obtém o endereço IP local.

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

AT+CIPMUX=1: Este comando é usado para habilitar múltiplas conexões (máximo 4)

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

AT+CIPSERVER=1:Este comando configura o ESP8266 como servidor.

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

loop while

Dentro do loop while, vamos primeiro chamar Rx_ESP_Data() que retorna os dados que o ESP8266 recebe. Isso é salvo na variável ‘res.’

Adicione um atraso de 2 segundos antes de prosseguir.

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

Em seguida, criamos um objeto do BME280 chamado bme e acessamos os valores de temperatura, pressão e umidade através dele. Essas leituras são salvas em suas variáveis correspondentes.

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

Vamos verificar se o buffer contém uma conexão IPD ou não. Se isso acontecer, responda com um handshake HTML.

Imprima as leituras do sensor BME280 junto com a resposta no terminal shell. Obtenha o ID de conexão e imprima-o também.

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)

Então, usando o objeto uart no método write(), enviaremos os bytes para o UART. Primeiro, estamos escrevendo o comando AT: AT+CIPSEND=’ID’, ‘LENGTH’ Isso definirá o comprimento dos dados que serão enviados. Em seguida, após um atraso de 1 segundo, escreveremos o corpo HTML que construirá a página da web na porta serial. Depois disso, fecharemos as múltiplas conexões enquanto estamos enviando o comando AT: AT+CIPCLOSE=’ID’. Em seguida, vamos redefinir o buffer e aguardar a conexão.

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...')

Criar página da Web (HTML+CSS)

Para construir a página da web, adicionaremos código HTML e, para estilizar, adicionaremos script CSS.

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)

Agora vamos passar por cada linha de código HTML para entender como ele constrói a página da web.

Neste documento HTML, usamos cartões, parágrafos, títulos e tags de título para criar uma página da web. Esta página da web exibe as leituras de temperatura, umidade e pressão do sensor BME280.

HTML é uma linguagem de marcação de hipertexto que é usada para construir páginas da web. Todos os navegadores da Web entendem essa linguagem e podem ler páginas da Web baseadas na linguagem HTML.

Em HTML, colocamos todo o conteúdo de uma página web entre as tags <html> e </html>. A tag <html> mostra o início de uma página da web e a </html> indica o final de uma página da web.

O código HTML inclui principalmente duas partes, como cabeça e corpo. A parte principal contém CSS, scripts, meta tags, links de recursos externos e códigos de estilo. Ele é colocado entre as tags <head> e </head>.

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')

Vamos começar com o título da página web. A tag <title> indicará o início do título e a tag </title> indicará o final. Entre essas tags, especificaremos “BME280 Web Server” que será exibido na barra de título do navegador.

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

Esta meta-tag http-equiv fornece atributos para o cabeçalho HTTP. O atributo http-equiv recebe muitos valores ou informações para simular a resposta do cabeçalho. Neste exemplo, usamos o atributo http-equiv para atualizar o conteúdo da página da Web após cada intervalo de tempo especificado. Os usuários não precisam atualizar a página da Web para obter os valores atualizados do sensor. Essa linha força a página HTML a se atualizar a cada 10 segundos. Além disso, essa metatag garantirá que nosso servidor da Web esteja disponível para todos os navegadores, por exemplo, smartphones, laptops, computadores 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')

Estilizando a página da Web com CSS

CSS é usado para dar estilos a uma página da web. Para adicionar arquivos CSS em tags head, usamos tags <style></style>. Este código CSS estiliza os cartões e a página da web, especificando as cores, fonte, tamanho da fonte, etc.

Este código CSS define o alinhamento do texto, o preenchimento, a margem e a largura das tags do corpo do documento HTML, juntamente com o tamanho da fonte, cor dos cartões, etc. Queremos exibir temperatura, pressão e umidade nos cartões e queremos que eles sejam exibidos no centro da página web.

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')

Corpo da página da Web HTML

A segunda parte mais importante de um documento HTML é o corpo que vai dentro das tags <body> e </body>. A parte do corpo inclui o conteúdo principal da página da web, como títulos, imagens, botões, ícones, tabelas, gráficos, etc. Por exemplo, neste servidor web baseado em Raspberry Pi Pico BME280 MicroPython, a parte do corpo inclui cabeçalho e três cartões exibir as leituras do sensor BME280.

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')

Incluiremos o cabeçalho da nossa página dentro das tags <h3></h3> e será “Raspberry Pi Pico BME280 WEB SERVER”.

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

A seguir, incluiremos as seguintes linhas de código para exibir textos e cartões para leituras de temperatura, pressão e umidade.

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')

Demonstração

Depois de copiar o código a seguir em um novo arquivo, clique no ícone ‘Salvar’ para salvar o código do programa em seu PC.

Depois de salvar o código, pressione o botão Executar para fazer o upload do código para sua placa. Antes de fazer o upload do código, certifique-se de que a placa correta esteja selecionada.

No terminal shell do seu IDE, você poderá visualizar o endereço IP depois que uma conexão bem-sucedida for estabelecida:

Agora, abra seu navegador da Web em seu laptop ou celular e digite o endereço IP que encontramos na última etapa. Assim que você digitar o endereço IP no seu navegador da Web e pressionar enter, o servidor da Web Raspberry Pi Pico receberá uma solicitação HTTP.

Você verá a página da web com os valores de temperatura mais recentes em seu navegador da web:

A página da Web visualizada de um telefone celular terá a seguinte aparência: