Raspberry Pi Pico W - WiFi Manager

Setup WiFi per Raspberry Pi Pico W

In questo articolo ti mostrerò come ho implementato un WiFi Manager per il nuovo Raspberry Pi Pico W.

Ho scritto una libreria in Python cercando di rendere il suo utilizzo il più semplice possibile.

Tutto il codice è pubblicato sul repository giemma/RaspberryPiPicoW-WifiManager (github.com)

Cosa intendo per WiFi Manager?

Detto tra noi, francamente non so se il nome è abbastanza esplicativo.

Quando si realizza un prototipo che deve accedere ad una WiFi (che sia con un Esp8266 e "derivati", o un Raspberry Pi Pico W) tutti noi tendiamo a cablare le informazioni relative alla connessione alla rete WiFi direttamente nel codice. Tipicamente il nome della rete (SSID) e la relativa password. Semplicissimo, bellissimo, ma poco funzionale perchè in caso si spostamento fisico del prototipo e indiponibilità della rete prescelta, ovviamente, dovremo andare a ricompilare il codice e ricaricarlo sul microprocessore di turno.

Il metodo, o meglio "il flusso logico", che ho scelto per ovviare a questo inconveniente è il seguente:

  1. Sono già state salvate il nome della rete e la relativa password?
  2. Se "si" -> tento la connessione alla rete WiFi utilizzando le credenziali trovate
  3. Se "no" oppure "non sono riuscito a connettermi" -> avvio il Raspberry Pi Pico W come Access Point, creo un webserver ed espongo una pagina web che consentirà ad un browser di configurare la rete. Una volta salvate le credenziali, si riparte dal punto n°1

Nota: Tratterò WiFi di tipo WPA2 

Le pagine web disponibili sono principalmente 3:

  1. La "Home" che consente di selezionare la rete da una dropdown e indicare la relativa password di rete.
  2. Una pagina che consente di riavviare il Raspberry Pi Pico W
  3. La "Info" che consente di visualizzare informazioni  sia sul software che sull' hardware

La pagina "Home" contiene un form con una dropdown prepopolata con le reti WiFi rilevate e una casella di testo per l'immissione della password:

Dopo il submit del form viene presentata una pagina che consente di riavviare il Raspberry Pi Pico W.

La "Info" consente di visualizzare informazioni  sia sul software che sull' hardware del Raspberr Pi Pico W

Lo schema logico

Il flusso logico che il codice segue è il seguente

Il codice

L'obbiettivo è avere una classe in Python semplice da utilizzare.

Ho scritto una classe che di fatto espone solo il costruttore che accetta come parametro una istanza di "DisplayUtility".

DisplayUtility è una classe che contiene un paio di metodi utili per scrivere una o più linee centrate sia orizzontalmente che verticalmente su un display 240x240.

main.py
import os
import sys
import uos
import network
import machine
import socket
import time
import displayutility
import displaycolors as Colors
import wifimanager.wifimanager as wifimanager

led = machine.Pin("LED",machine.Pin.OUT)
reset_button = machine.Pin(16, machine.Pin.IN)

display = displayutility.DisplayUtility()
display.WriteLines(["Display: OK"])

WiFIManager = wifimanager.WiFiManager(display)

while True:
    logic_state = reset_button.value()
    if logic_state == True:     
      WiFIManager.ClearCredentials()
      display.WriteLines(["Reset","on","2 seconds"], color=Colors.BLACK, background=Colors.YELLOW)
      time.sle ep(2)
      machine.reset()
      
      
    time.sle ep(1)
    led.off()
    time.sle ep(1)
    led.on()
displayutility.py
import uos
import machine
import time
import st7789py as st7789
import vga2_8x8 as font1
import vga1_16x32 as font2
import displaycolors as Colors


class DisplayUtility():
    
    def __init__(self):
        spi1_sck=10
        spi1_mosi=11
        spi1_miso=8 
        st7789_res = 12
        st7789_dc  = 13
        disp_width = 240
        disp_height = 240
        CENTER_Y = int(disp_width/2)
        CENTER_X = int(disp_height/2)

        print(uos.uname())
        spi1 = machine.SPI(1, baudrate=40000000, polarity=1)
        print(spi1)
        self._display = st7789.ST7789(spi1, disp_width, disp_width,
                              reset=machine.Pin(st7789_res, machine.Pin.OUT),
                              dc=machine.Pin(st7789_dc, machine.Pin.OUT),
                              rotation=0)
        time.sle ep(1)
        
        
    
    def WriteLine(self, line):
        self._display.fill(Colors.BLACK)
        start_x=10
        start_y=10
        self._display.text(font2, line, start_x, start_y)

    def WriteLines(self, lines, color = Colors.WHITE, background = Colors.BLACK):
        self._display.fill(background)
        start_x = 10
        start_y = 10
        yspace = font2.HEIGHT + 2        
        
        start_y = int((240- (len(lines) * font2.HEIGHT) )/2)
        
        for line in lines:        
            start_x = int((240- (len(line) * font2.WIDTH) )/2)
            self._display.text(font2, line, start_x, start_y, color, background)
            start_y += yspace + 1

I colori utilizzati per il display sono definiti nel file "displaycolors.py":

displaycolors.py
BLACK = const(0x0000)
BLUE = const(0x001F)
RED = const(0xF800)
GREEN = const(0x07E0)
CYAN = const(0x07FF)
MAGENTA = const(0xF81F)
YELLOW = const(0xFFE0)
WHITE = const(0xFFFF)

Altra classe di utilità è quella che consente di fare il parsing dei valori del form.

Per capire bene come leggere i dati inviati in POST dal form HTML è necessario comprendere come e cosa viene inviato ovvero capire come è  fatta una richiesta HTTP.

Richieste HTTP GET e POST con Raspberry Pi Pico W

Non è mio obbiettivo dilungarmi sul protocollo HTTP e riporto giusto le nozioni basilari.

HTTP è un protocollo basato su TCP e la porta di default è la porta 80. 

Da un lato abbiamo chi richiede la risorsa e dall'altro chi la fornisce (il web server). Tipicamente il web server rimane in ascolto fino a quando non arriva una richiesta che poi tenterà di esaudire.

Nel codice che riporterò di seguito l'attesa della connessione è delegata alla seguente riga:

cl, addr = s.accept() 
print('client connected from', addr)            
request = cl.recv(1024)

Supponiamo di richiedere la pagina "/" da un qualsiasi browser.

Il web server, in questo caso il Raspberry Pi Pico W (vedi istruzione request = cl.recv(1024) ), riceverà la seguente stringa:

Nel caso di post la stringa ricevuta sarà del tipo:

Ho scritto dei metodi in Python che eseguono il parse della richiesta e restituiscono i valori dei campi inviati (sia in GET che in POST)

def GetParameterValue(string, parameterName):
    startpos= string.find(parameterName)+len(parameterName)
    if startpos < 0:
        return ''
    
    endpos=string.find('&',startpos+1)
    if endpos < 0:
        endpos=len(string)
        
    return string[startpos:endpos]

def GetRequestPageGet(requestString):
    if requestString == '':
        return '/'
        
    startpos= requestString.find('GET ')+4
    endpos=requestString.find(' ',startpos+1)
    return requestString[startpos:endpos] 

Gestione delle richieste HTTP GET e POST

La gestione delle richieste HTTP GET che POST è di fatto una serie di "if".

La prima condizione è verificare se si tratta di una richiesta GET o POST perchè nel caso di GET deve essere restituito un HTML.

Nel caso di post si tratta invece di un invio di dati e in base alla destinazione devono essere eseguite operazioni differenti.

Nel caso in oggetto abbiamo un solo POST e si tratta dell'invio del form che contiene il nome della rete prescelta e la relativa password di rete.

Per quanto riguarda le richieste HTTP GET ho scritto una piccola classe in Python che mi consente di avere dei template delle pagine HTML. In funzionamento è banale.

Ho creato dei file HTML con dei placeholders che poi verranno letti dal Raspberry e i placeholders sostituiti con i valori reali.

Caso eccezionale è il file css che è di fatto una risorsa statica (anche questo viene richiesto dal browser tramite GET).

htmmanager.py
import os
import machine


title='TITLE'

def GetCss():
    f = open('/www/style.css', 'r')
    content = f.read()
    f.close()
    return content

def _sub_read_template_file(filename):
    f = open('/www/' + filename +'.html', 'r')
    content = f.read()
    f.close()
    return content

    
class HTMLMANAGER():
    def __init__(self, mainTitle):
        self.title = mainTitle
    
   
    def GetIndexPage(self, infoTitle, networks):
        template = _sub_read_template_file('index')
        template = template.replace('%title%',infoTitle)
        template = template.replace('%options%',networks)
        return template
        
        
    def GetInfoPage(self, infoTitle, version, osversion, hardware):
        template = _sub_read_template_file('info')
        template = template.replace('%title%',infoTitle)
        template = template.replace('%version%', version)
        template = template.replace('%os-version%', osversion)
        template = template.replace('%hardware%', hardware)
        return template
       
    def GetPasswordOkPage(self, infoTitle):
        template = _sub_read_template_file('passwordok')
        template = template.replace('%title%',infoTitle)
        return template
    
    def GetRestartingPage(self, infoTitle):
        template = _sub_read_template_file('restarting')
        template = template.replace('%title%',infoTitle)
        return template
    
    def GetStoppedPage(self, infoTitle):
        template = _sub_read_template_file('stopped')
        template = template.replace('%title%',infoTitle)
        return template

Collegamento display ST7789 e Raspberry Pi Pico W

Di seguito lo schema dei collegamenti tra il Raspberry Pi Pico, il display St7789 (240x240) e la batteria da 3.7 volt.

Ho previsto un interruttore poichè, quando si connette il Raspberry al pc, la batteria dovrebbe essere scollegata.

Non ho provato a tenerla collegata perchè ho preferito non mettere a repentaglio il Raspberry e la porta usb del pc.

Buon divertimento!

Se hai voglia di lasciarmi un commento mi trovi su Linkedin.