Web

Para criar um servidor de páginas Web no ESP32 será conveniente usar uma biblioteca para o efeito. A biblioteca micropython-aioweb que usaremos neste exemplo recorre ao paradigma da programação assíncrona já abordada anteriormente, bastando incluir o ficheiro web.py no nosso projeto.

Tal como consta do exemplo incluído na descrição da biblioteca, uma página web simples poderia ser servida pelo seguinte programa main.py:

import web
import uasyncio as asyncio

app = web.App(host='0.0.0.0', port=80)

# root route handler
@app.route('/')
async def handler(r, w):
    # write http headers
    w.write(b'HTTP/1.0 200 OK\r\n')
    w.write(b'Content-Type: text/html; charset=utf-8\r\n')
    w.write(b'\r\n')
    # write page body
    w.write(b'Hello world!')
    # drain stream buffer
    await w.drain()

# Start event loop and create server task
loop = asyncio.get_event_loop()
loop.create_task(app.serve())
loop.run_forever()

Quando o programa é executado é possível constatar em que IP o servidor se encontra (neste exemplo em 10.0.5.117):

Connecting to network...
................ Connected!
network config: ('10.0.5.117', '255.255.255.0', '10.0.5.1', '10.0.5.1')

Pelo que se em seguida acedermos a partir de um browser a esse IP obteremos a página Web que apresenta a mensagem “Hello world!”:

Contudo no nosso caso pretendemos uma página que interaja com determinados componentes ligados ao ESP32 (concretamente um LED e um botão), pelo que teremos que usar websockets para o efeito (funcionalidade incluída na biblioteca escolhida).

Para além do ficheiro web.py, o programa que apresentamos de seguida é basicamente formado por dois ficheiros:

  • main.py – script Python que cria o servidor e interage com o hardware
  • index.html – ficheiro HTML que descreve o conteúdo da página web

Os respetivos códigos são mostrados de seguida:

import network
import web
import uasyncio as asyncio
from machine import Pin

led = Pin(21, Pin.OUT)
led.value(False)
but = Pin(23, Pin.IN, Pin.PULL_UP)

async def checkbut():
    global WS_CLIENTS
    state = True
    while True:
        if but.value() != state:
            state = not state
            msg = 'Button ' + ('OFF' if state else 'ON')
            print(msg)
            for ws_client in WS_CLIENTS:
                try:
                    await ws_client.send(msg)
                except:
                    continue
        await asyncio.sleep_ms(5)
    
app = web.App(host='0.0.0.0', port=80)

# Store current WebSocket clients
WS_CLIENTS = set()

# root route handler
@app.route('/')
async def index_handler(r, w):
    f = open('index.html')
    w.write(f.read())
    f.close()
    await w.drain()

# /ws WebSocket route handler
@app.route('/ws')
async def ws_handler(r, w):
    global WS_CLIENTS
    # upgrade connection to WebSocket
    ws = await web.WebSocket.upgrade(r, w)
    r.closed = False
    # add current client to set
    WS_CLIENTS.add(ws)
    while ws.open:
        # handle ws events
        evt = await ws.recv()
        if evt is None or evt['type'] == 'close':
            ws.open = False
        elif evt['type'] == 'text':
            msg = evt['data']
            led.value(True if msg == "LED ON" else False)
    # remove current client from set
    WS_CLIENTS.discard(ws)

# Start event loop and create server task
loop = asyncio.get_event_loop()
loop.create_task(app.serve())
loop.create_task(checkbut())
loop.run_forever()

Não é intenção desta unidade curricular abordar em profundidade a temática da construção de páginas web nem muito menos abordar o conceito dos Websockets aqui usados. O código apresentado pode contudo constituir um ponto de partida para a análise destas temáticas.

A página Web servida pelo ESP32 pode ser acedida através dum Browser indicando o IP atribuído ao ESP32 (neste caso particular o 10.0.5.137). Nesta página não só é possível controlar o estado dum LED na placa como também receber o estado dum dos seus botões assim que ele muda de estado.

Neste último exemplo o ESP32 está a aceder a uma rede WiFi existente nas suas imediações mas como já foi referido na ausência dessa rede, ele próprio pode criar um Access Point. Para tal o script boot.py tem que ser alterado em conformidade:

import network

wlan = network.WLAN(network.AP_IF)
wlan.config(essid='ESP32-AP', password='12345678', authmode=network.AUTH_WPA_WPA2_PSK)
wlan.config(max_clients=5)
wlan.active(True)
print('network config:', wlan.ifconfig())

Neste caso é criada uma rede WiFi com o nome ESP32-AP cuja password de acesso é 12345678. Nessa rede, o ESP32-AP fica com o IP 192.168.4.1, pelo que deverá agora ser a esse IP que o browser deverá se ligar (desde que esteja a ser executado num dispositivo ligado a essa mesma rede).

De notar, que caso este servidor seja executado no simulador Wokwi, o nosso browser não consegue aceder nem a essa rede ESP32-AP nem à rede gerada pelo simulador (Wokwi-GUEST), pelo que nesta situação não nos é possível abrir a página Web.