Budeme měřit teplotu a chceme, aby byly výsledky měření přístupné na síti a mohli bychom si je prohlédnout v libovolném webovém prohlížeči. Popíši naprogramování jednoduchého web serveru, který poběží na Picu W s wifi kartou (na obyčejném Raspberry Pi Pico to nebude fungovat, protože se s ním neumíme připojit do sítě). Používat budeme nešifrovaný protokol HTTP na IPv4 a nebudeme vystavovat naše zařízení do Internetu, protože je těžce nezabezpečené a libovolný darebák ho může shodit.
Jaké problémy musíme vyřešit
-
připojit Pico W k nějakému WiFi AP
-
nastavit radiové parametry připojení
-
nastavit IP adresu zařízení Pico W a pokud si necháme přidělit IP pomocí DHCP, tak nějakým způsobem ji zjistit
-
-
naprogramovat a rozchodit webserver
Http server je naprogramován v lwIP (lightweight IP) a je součástí Pico SDK, takže se tím nemusíme zabývat, protože to je dost těžké. Ovladač wifi karty cyw43 je součástí Pico SDK, protokoly ethernet, IP, ARP, DHCP, DNS jsou součástí lwIP a nemusíme se tím zabývat, jenom je musíme umět použít, což tak těžké nebude.
Nastavení radiových parametrů
Nastavit ESSID a heslo k Wifi je nejlepší udělat přímo ve zdrojovém kódu.
// WIFI udaje pro pripojeni
const char WIFI_SSID[] = "chorche3"; (1)
const char WIFI_PASSWORD[] = "xxxxxxxxx"; (2)
int main() {
cyw43_arch_init();
// wifina bude jako klient
cyw43_arch_enable_sta_mode(); (3)
// nastavím hostname
printf("Nastavuji hostname na %s\n", MY_HOSTNAME);
struct netif *n = &cyw43_state.netif[CYW43_ITF_STA];
cyw43_arch_lwip_begin();
netif_set_hostname(n,MY_HOSTNAME);
netif_set_up(n);
cyw43_arch_lwip_end();
// Zkusim se ve smycce pripojit k wifine
while(cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000) != 0){ (4)
printf("Pokousim se pripojit...\n");
}
| 1 | ESSID |
| 2 | Heslo |
| 3 | Wifi na Picu přepneme do módu station. |
| 4 | Zkoušíme se připojit k AP. |
Problém zjištění IP adresy Pica
Pokud si necháme pro Pico W přidělit IP adresu od DHCP serveru, tak ji musíme nějakým způsobem zjistit. Lze to udělat několika způsoby:
Výpis IP adresy do debugovacího výstupu
// Zkusim se ve smycce pripojit k wifine
while(cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000) != 0){
printf("Pokousim se pripojit...\n");
}
// Jsem pripojen
printf("Pripojen k %s: %s IP adresa: %s\n", WIFI_SSID, MY_HOSTNAME, ip4addr_ntoa(netif_ip4_addr(netif_list)));
Na debuggovacím port se potom objeví výpis IP adresy
Connected. Use C-a C-q to exit.
Pripojen k chorche4: jirkovopico1 IP adresa: 10.0.0.38
Http server nahozen.
SSI Handler nahozen.
CGI Handler nahozen.
Výpis IP adresy na pomocný displej
Tohle je mnohem lepší, protože nemusíme mít debugovací konzoli, IP adresu máme na displeji.
Použil jsem kód z předchozího projektu s OLED displejem
Displej je malinko jiný, ale funguje úplně stejně jako ten v předchozím projektu.
Drobná změna je v tom, že má 64 řádků a proto je potřeba upravit #define DISPLAY_HEIGHT 64.
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define I2C_ADDRESS 0x3C
#define I2C_FREQ 400000
#define SLEEPTIME 55
void setup_gpios(void) { (1)
i2c_init(i2c_default, I2C_FREQ);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
}
int main() {
char buf[128];
ssd1306_t disp;
stdio_init_all();
setup_gpios(); (2)
disp.external_vcc = false;
ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, i2c_default);
ssd1306_clear(&disp);
cyw43_arch_init();
// wifina bude jako klient
cyw43_arch_enable_sta_mode();
// nastavím hostname
printf("Nastavuji hostname na %s\n", MY_HOSTNAME);
struct netif *n = &cyw43_state.netif[CYW43_ITF_STA];
cyw43_arch_lwip_begin();
netif_set_hostname(n,MY_HOSTNAME);
netif_set_up(n);
cyw43_arch_lwip_end();
snprintf(buf,128,"Hostname:%s", MY_HOSTNAME);
ssd1306_draw_string_with_font(&disp, 0, 30, 1, font_spleen_8x5, buf);
ssd1306_show(&disp);
// Zkusim se ve smycce pripojit k wifine
while(cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000) != 0){
printf("Pokousim se pripojit...\n");
}
// Jsem pripojen
snprintf(buf,20,ip4addr_ntoa(netif_ip4_addr(netif_list))); (3)
printf("Pripojen k %s: %s IP adresa: %s\n", WIFI_SSID, MY_HOSTNAME, buf );
ssd1306_draw_string_with_font(&disp, 0, 0, 1, font_spleen_16x8a, buf); (4)
ssd1306_draw_string_with_font(&disp, 0, 16, 1, font_spleen_8x5, WIFI_SSID ); (5)
ssd1306_show(&disp); (6)
// ....
| 1 | Funkce pro nastavení GPIO pinů k displeji |
| 2 | Nastavení displeje |
| 3 | Uložím si IP adresu do bufferu. |
| 4 | Pošlu na displej IP adresu (horní část displeje je žlutá). |
| 5 | Pošlu na displej ESSID APčka menším fontem (spodní část displeje je modrá). |
| 6 | Aktivuji na displeji výpisy. |

Zjištění IP adresy z DHCP serveru
Při tomto způsobu musíme mít přístup k DHCP serveru, někdy ho máme a někdy (třeba ve škole) ne.

Pico W se ve výpisu nejmenuje jirkovopico1, ale PicoW, což je defaultní jméno Pica, pokud ho nenastavíme.
Buďto mám chybu v kódu, nebo si DHCP pamatuje předchozí pokusy, kdy se testovací Pico jmenovalo skutečně PicoW.
Pro DHCP server je důležitá hlavně MAC adresa 28:cd:c1:0c:33:1e, kterou ovšem taky předem nevíme.
Někdy může být hledání správné IP adresy Pica docela zábava.
Webserver

Web server (httpd) funguje tak, že přijímá požadavky HTTP protokolu a na základě požadavku URI vrací příslušný soubor, který se nachází ve struktuře webu.
Například požadavek je /index.html, server se podívá do svého souborového systému (/ znamená kořen) a vyhledá soubor se jménem index.html a ten odešle klientovi.
Pokud soubor nenalezne, tak pošle chybu, obvykle 404.
Náš server je velmi jednoduchý, nemáme k dispozici filesystém a proto bude nás httpd server odpovídat jenom na požadavky /index.shtml (zobraz úvodní stránku), /led.cgi?led=0 (vypni LED) a /led.cgi?led=1 (zapni LED).
To že nemáme na Picu k dispozici filesytém obejdeme tak, že stránka index.html bude přímo v céčkovém kódu serveru httpd.
Toto zakódování provádí Pythoní skript makefsdata.py, který převede html stránku /html_files/index.shtml do céčkového kódu.
<!DOCTYPE html> (1)
<html>
<head> (2)
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> (3)
<meta name="author" content="Mgr. Ing. Jiří Chráska">
<title>Měření teploty a ovládání LED</title> (4)
<style> (5)
* {margin 0; padding: 0; font-size: 1em; font-family: 'Segoe UI', Tahoma, sans-serif;}
body {background-color: #dcdcdc;}
h1 {color: #00008b ; font-family: verdana; font-size: 120%;}
h2 {color: #00008b; font-family: verdana; font-size: 100%;}
button {margin: 5px; padding: 10px;}
</style>
</head>
<body> <h1>Jednoduchý webserver Pico W</h1> (6)
<br>
<h2>Technologie SSI:</h2>
<p>Napětí: <!--#volt--> V</p>
<p>Teplota: <!--#temp--> °C</p>
<p>LED je: <!--#led--></p>
<br>
<h2>Technologie CGI:</h2>
<div><a href="/led.cgi?led=1"><button>Zapnout LED</button></a></div>
<div><a href="/led.cgi?led=0"><button>Vypnout LED</button></a></div>
<br>
<br>
<div><a href="/index.shtml"><button>Aktualizovat měření</button</a></div>
</body>
</html>
| 1 | Typ dokumentu |
| 2 | Hlavička dokumentu (je ukončena </head>). |
| 3 | Kódování dokumentu UTF-8. To aby internetový prohlížeč věděl, jak má zobrazovat písmena. |
| 4 | Titulek dokumentu, prohlížeč ho zobrazuje v horní liště. |
| 5 | Kaskádový styl, informace pro prohlížeč, jak má dokument zobrazit. |
| 6 | Vlastní obsah dokumentu (je ukončeno </body>) |
To by bylo ale málo, takto lze distribuovat pouze statické html stránky.
Do stránky index.html potřebujeme nějak propašovat naměřené hodnoty teploty a napětí a k tomu použijeme technologii Server Side Includes.
Jak funguje technologie Server Side Include
Server Side Includes (zkláceně SSI — vkládání na straně serveru), funguje tak, že httpd server se dívá do html stránky před jejím odesláním klientovi a hledá vzory typu <!--#proměnná-->.
Pokud nalezne, tak místo tohoto vzoru <!--#proměnná--> do stránky vloží hodnotu, která se v této proměnné nachází.
Pokud nastane nějaká chyba, tak se nic neděje, protože internetový prohlížeč interpretuje html značku <!--blablabla--> jako komentář a nezobrazuje ji.
<h2>Technologie SSI:</h2>
<p>Napětí: <!--#volt--> V</p> (1)
<p>Teplota: <!--#temp--> °C</p> (2)
<p>LED je: <!--#led--></p> (3)
| 1 | Na tomto řádku najde server proměnnou se jménem volt a místo toho vloží třeba 0.72461. |
| 2 | Na tomto řádku najde server proměnnou se jménem temp a místo toho vloží třeba 22.865. |
| 3 | Na tomto řádku najde server proměnnou se jménem led a místo toho vloží zapnuto nebo vypnuto. |
<h2>Technologie SSI:</h2>
<p>Napětí: 0.72461 V</p>
<p>Teplota: 22.865 °C</p>
<p>LED je: zapnuto</p>
Vložení hodnoty do proměnné zajistí server tak, že zavolá ovladač (handler), předá mu jméno proměnné a práce handleru je odpovědět serveru hodnotou.
// SSI tags - položky, které umí handler zpracovat
const char * ssi_tags[] = {"volt","temp","led"};
// Inicializace SSI handleru
void ssi_init() {
// Inicializace analogově digitálního převodníku. Budeme ho potřebovat na zjištění naměřené teploty.
adc_init();
adc_set_temp_sensor_enabled(true);
adc_select_input(4);
// web serveru předáme adresu funkce handleru ssi_handler,
// seznam položek v poli, které handler umí -- ssi_tags
// a v třetím parametru velikost pole ssi_tags
http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));
}
u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { (1)
size_t printed;
switch (iIndex) {
case 0: // volty (2)
{
const float voltage = adc_read() * REF_VOLT / (1 << 12);
printed = snprintf(pcInsert, iInsertLen, "%f", voltage);
}
break;
case 1: // teplota (3)
{
const float voltage = adc_read() * REF_VOLT / (1 << 12);
const float tempC = 27.0f - (voltage - 0.706f) / 0.001721f;
printed = snprintf(pcInsert, iInsertLen, "%f", tempC); (5)
}
break;
case 2: // led (4)
{
bool led_status = cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN);
if(led_status == true){
printed = snprintf(pcInsert, iInsertLen, "zapnuto");
}
else{
printed = snprintf(pcInsert, iInsertLen, "vypnuto");
}
}
break;
default:
printed = 0;
break;
}
return (u16_t)printed; (6)
}
| 1 | Httpd server předává v parametru iIndex index položky z pole ssi_tags[], podle toho co našel.
*pcInsert je parametr předaný odkazem, ssi_handler do něho vloží odpověď, která ovšem nesmí být větší než iIntsertLen bajtů. |
| 2 | Tady zjisťujeme hodnotu napětí ve voltech. |
| 3 | Tady měříme teplotu. |
| 4 | Tady zjišťujeme, v jakém stavu se nachází LED. |
| 5 | Funkce snprintf() nebude kopírovat více bajtů než je hodnota iInsertLen, aby nám to nepřeteklo. |
| 6 | Návratová hodnota je skutečný počet předaných bajtů httpd serveru. |
Jak funguje CGI
Common Gateway Interface (zkráceně CGI) je protokol pro propojení externích aplikací s webovým serverem. To serveru umožňuje delegovat požadavek od klienta na externí aplikaci, která dle požadavku vrátí výstup. Taková aplikace typicky zpracuje nějaký skript ve webové stránce a webovému serveru navrátí statickou stránku, která je následně poslána klientovi jako výstup jeho požadavku.
Rozhraní Common Gateway Interface bylo v prostředí internetu přítomno již od počátku 90. let a ve své době představovalo jediný způsob dynamického zpracování obsahu. Postupně vznikla efektivnější řešení (FastCGI, integrace skriptovacích jazyků jako modulu WWW serveru) a CGI bylo vytlačeno do ústraní.
V našrm případě bude CGI velmi jednoduché, budeme mít jednu stránku /led.cgi, která bude přijímat dva parametry len=0 a led=1.
Otazník ? v URL slouží k oddělení jména stránky od parametrů.
Proto pokud obdrží httpd server URL http://adresa_serveru/led.cgi?led=1 bude vědět, že má zpracovat stránku led.cgi s parametrem led=1 a tudíž rozsvítil LED,
led je jméno parametru, = je oddělovač a hodnota parametru je 1.
Implementace bude opět velmi jednoduchá
#include "lwip/apps/httpd.h"
#include "pico/cyw43_arch.h"
// CGI handler which is run when a request for /led.cgi is detected
const char * cgi_led_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) (3)
{
// Check if an request for LED has been made (/led.cgi?led=x)
if (strcmp(pcParam[0] , "led") == 0){
// Look at the argument to check if LED is to be turned on (x=1) or off (x=0)
if(strcmp(pcValue[0], "0") == 0)
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); (4)
else if(strcmp(pcValue[0], "1") == 0)
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); (5)
}
// Send the index page back to the user
return "/index.shtml";
}
// tCGI Struct
// Fill this with all of the CGI requests and their respective handlers
static const tCGI cgi_handlers[] = { (1)
{
// Html request for "/led.cgi" triggers cgi_handler
"/led.cgi", cgi_led_handler
},
};
void cgi_init(void) (2)
{
http_set_cgi_handlers(cgi_handlers, 1);
}
| 1 | Inicializace cgi handleru, httpd serveru se předává pole se všemi cgi handlery a počet položek pole (v našem případě 1). |
| 2 | Pole pro obsluhu jednotlivých stránek, máme jenom jednu /led.cgi a obsluhuje ji cgi_led_handler. |
| 3 | Implementace cgi_led_handleru, iIndex je index do pole cgi_handlers[] (nepoužíváme ho), v iNumParams je počet parametrů, pcParam je pole se všemu parametry a v pcValue se předávají hodnoty parametrů. Rozlišení, co je parametr a co je hodnota parametru provádí httpd server. |
| 4 | Tady je malinko jiná funkce na rozsvícení a zhasnutí LED, pracujeme s Pico W |
| 5 | To samé jako bod 4. |
Vlastní httpd server je naprogramován v Pico-SDK v rámci lwIP a nemusíme se tím naštěstí zabývat, protože to je hodně těžké.
A to je všechno. Mělo by to fungovat.
Sestavení
Zde jse seznam souborů nutných k sestavení projektu.
CMakeLists.txt je sestavovací předpis pro Cmake, lwipopts.h je konfigurace TCP/IP stacku lwIP. Do toho raději nešahejte.
cmake_minimum_required(VERSION 3.13)
set(PROGRAM_NAME pico_w_webserver)
set(PICO_BOARD pico_w)
if(DEFINED HOSTNAME)
add_compile_definitions(CYW43_HOST_NAME=\"${HOSTNAME}\")
endif()
include(pico_sdk_import.cmake)
project(pico_w_webserver)
pico_sdk_init()
message("Running makefsdata python script")
execute_process(COMMAND
python3 makefsdata.py
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
add_executable(${PROGRAM_NAME}
main.c
)
target_include_directories(${PROGRAM_NAME} PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(${PROGRAM_NAME}
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_http
pico_stdlib
hardware_adc
)
pico_enable_stdio_usb(${PROGRAM_NAME} TRUE)
pico_enable_stdio_uart(${PROGRAM_NAME} FALSE)
pico_add_extra_outputs(${PROGRAM_NAME})
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="author" content="Mgr. Ing. Jiří Chráska">
<title>Měření teploty a ovládání LED</title>
<style>
* {margin 0; padding: 0; font-size: 1em; font-family: 'Segoe UI', Tahoma, sans-serif;}
body {background-color: #dcdcdc;}
h1 {color: #00008b ; font-family: verdana; font-size: 120%;}
h2 {color: #00008b; font-family: verdana; font-size: 100%;}
button {margin: 5px; padding: 10px;}
</style>
</head>
<body> <h1>Jednoduchý webserver Pico W</h1>
<br>
<h2>Technologie SSI:</h2>
<p>Napětí: <!--#volt--> V</p>
<p>Teplota: <!--#temp--> °C</p>
<p>LED je: <!--#led--></p>
<br>
<h2>Technologie CGI:</h2>
<div><a href="/led.cgi?led=1"><button>Zapnout LED</button></a></div>
<div><a href="/led.cgi?led=0"><button>Vypnout LED</button></a></div>
<br>
<br>
<div><a href="/index.shtml"><button>Aktualizovat měření</button></a></div>
</body>
</html>
/* Web server na Pico W
* (c) Jirka Chráska 2024, <jirka@lixis.cz>
* BSD-licence
*/
#include "lwip/apps/httpd.h"
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/netif.h"
#include "lwipopts.h"
#include "ssi.h"
#include "cgi.h"
// hostname
const char MY_HOSTNAME[] = "jirkovopico1";
// WIFI udaje pro pripojeni
const char WIFI_SSID[] = "essidwifi";
const char WIFI_PASSWORD[] = "heslowifi";
int main() {
stdio_init_all();
// pauza na nahození seriove linky
sleep_ms(20000);
cyw43_arch_init();
// wifina bude jako klient
cyw43_arch_enable_sta_mode();
// nastavím hostname
printf("Nastavuji hostname na %s\n", MY_HOSTNAME);
struct netif *n = &cyw43_state.netif[CYW43_ITF_STA];
cyw43_arch_lwip_begin();
netif_set_hostname(n,MY_HOSTNAME);
netif_set_up(n);
cyw43_arch_lwip_end();
// Zkusim se ve smycce pripojit k wifine
while(cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000) != 0){
printf("Pokousim se pripojit...\n");
}
// Jsem pripojen
printf("Pripojen k %s: %s IP adresa: %s\n", WIFI_SSID, MY_HOSTNAME, ip4addr_ntoa(netif_ip4_addr(netif_list)));
// Initializace web serveru
httpd_init();
printf("Http server nahozen.\n");
// Konfigurace SSI a CGI handleru
ssi_init();
printf("SSI Handler nahozen.\n");
cgi_init();
printf("CGI Handler nahozen.\n");
// Nekonecna smycka
while(1);
}
#include "lwip/apps/httpd.h"
#include "pico/cyw43_arch.h"
#include "hardware/adc.h"
#define REF_VOLT 3.3f
// SSI tags - tag length limited to 8 bytes by default
const char * ssi_tags[] = {"volt","temp","led"};
u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) {
size_t printed;
switch (iIndex) {
case 0: // volty
{
const float voltage = adc_read() * REF_VOLT / (1 << 12);
printed = snprintf(pcInsert, iInsertLen, "%f", voltage);
}
break;
case 1: // teplota
{
const float voltage = adc_read() * REF_VOLT / (1 << 12);
const float tempC = 27.0f - (voltage - 0.706f) / 0.001721f;
printed = snprintf(pcInsert, iInsertLen, "%f", tempC);
}
break;
case 2: // led
{
bool led_status = cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN);
if(led_status == true){
printed = snprintf(pcInsert, iInsertLen, "zapnuto");
}
else{
printed = snprintf(pcInsert, iInsertLen, "vypnuto");
}
}
break;
default:
printed = 0;
break;
}
return (u16_t)printed;
}
// Initialise the SSI handler
void ssi_init() {
// Initialise ADC (internal pin)
adc_init();
adc_set_temp_sensor_enabled(true);
adc_select_input(4);
http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));
}
#include "lwip/apps/httpd.h"
#include "pico/cyw43_arch.h"
// CGI handler which is run when a request for /led.cgi is detected
const char * cgi_led_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
// Check if an request for LED has been made (/led.cgi?led=x)
if (strcmp(pcParam[0] , "led") == 0){
// Look at the argument to check if LED is to be turned on (x=1) or off (x=0)
if(strcmp(pcValue[0], "0") == 0)
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0);
else if(strcmp(pcValue[0], "1") == 0)
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);
}
// Send the index page back to the user
return "/index.shtml";
}
// tCGI Struct
// Fill this with all of the CGI requests and their respective handlers
static const tCGI cgi_handlers[] = {
{
// Html request for "/led.cgi" triggers cgi_handler
"/led.cgi", cgi_led_handler
},
};
void cgi_init(void)
{
http_set_cgi_handlers(cgi_handlers, 1);
}
Pythoní skript, který předělá html soubory v adresáři html_files do céčkového kódu. (Na Picu nemáme filesystém).
#!/usr/bin/python3
# This script is by @rspeir on GitHub:
# https://github.com/krzmaz/pico-w-webserver-example/pull/1/files/4b3e78351dd236f213da9bebbb20df690d470476#diff-e675c4a367e382db6f9ba61833a58c62029d8c71c3156a9f238b612b69de279d
# Renamed output to avoid linking incorrect file
import os
import binascii
#Create file to write output into
output = open('htmldata.c', 'w')
#Traverse directory, generate list of files
files = list()
os.chdir('./html_files')
for(dirpath, dirnames, filenames) in os.walk('.'):
files += [os.path.join(dirpath, file) for file in filenames]
filenames = list()
varnames = list()
#Generate appropriate HTTP headers
for file in files:
if '404' in file:
header = "HTTP/1.0 404 File not found\r\n"
else:
header = "HTTP/1.0 200 OK\r\n"
header += "Server: lwIP/pre-0.6 (http://www.sics.se/~adam/lwip/)\r\n"
if '.html' in file:
header += "Content-type: text/html\r\n"
elif '.shtml' in file:
header += "Content-type: text/html\r\n"
elif '.jpg' in file:
header += "Content-type: image/jpeg\r\n"
elif '.gif' in file:
header += "Content-type: image/gif\r\n"
elif '.png' in file:
header += "Content-type: image/png\r\n"
elif '.class' in file:
header += "Content-type: application/octet-stream\r\n"
elif '.js' in file:
header += "Content-type: text/javascript\r\n"
elif '.css' in file:
header += "Content-type: text/css\r\n"
elif '.svg' in file:
header += "Content-type: image/svg+xml\r\n"
else:
header += "Content-type: text/plain\r\n"
header += "\r\n"
fvar = file[1:] #remove leading dot in filename
fvar = fvar.replace('/', '_') #replace *nix path separator with underscore
fvar = fvar.replace('\\', '_') #replace DOS path separator with underscore
fvar = fvar.replace('.', '_') #replace file extension dot with underscore
output.write("static const unsigned char data{}[] = {{\n".format(fvar))
output.write("\t/* {} */\n\t".format(file))
#first set of hex data encodes the filename
b = bytes(file[1:].replace('\\', '/'), 'utf-8') #change DOS path separator to forward slash
for byte in binascii.hexlify(b, b' ', 1).split():
output.write("0x{}, ".format(byte.decode()))
output.write("0,\n\t")
#second set of hex data is the HTTP header/mime type we generated above
b = bytes(header, 'utf-8')
count = 0
for byte in binascii.hexlify(b, b' ', 1).split():
output.write("0x{}, ".format(byte.decode()))
count = count + 1
if(count == 10):
output.write("\n\t")
count = 0
output.write("\n\t")
#finally, dump raw hex data from files
with open(file, 'rb') as f:
count = 0
while(byte := f.read(1)):
byte = binascii.hexlify(byte)
output.write("0x{}, ".format(byte.decode()))
count = count + 1
if(count == 10):
output.write("\n\t")
count = 0
output.write("};\n\n")
filenames.append(file[1:])
varnames.append(fvar)
for i in range(len(filenames)):
prevfile = "NULL"
if(i > 0):
prevfile = "file" + varnames[i-1]
output.write("const struct fsdata_file file{0}[] = {{{{ {1}, data{2}, ".format(varnames[i], prevfile, varnames[i]))
output.write("data{} + {}, ".format(varnames[i], len(filenames[i]) + 1))
output.write("sizeof(data{}) - {}, ".format(varnames[i], len(filenames[i]) + 1))
output.write("FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT}};\n")
output.write("\n#define FS_ROOT file{}\n".format(varnames[-1]))
output.write("#define FS_NUMFILES {}\n".format(len(filenames)))
// Common settings used in most of the pico_w examples
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)]
// allow override in some examples
#ifndef NO_SYS
#define NO_SYS 1
#endif
// allow override in some examples
#ifndef LWIP_SOCKET
#define LWIP_SOCKET 0
#endif
#if PICO_CYW43_ARCH_POLL
#define MEM_LIBC_MALLOC 1
#else
// MEM_LIBC_MALLOC is incompatible with non polling versions
#define MEM_LIBC_MALLOC 0
#endif
#define MEM_ALIGNMENT 4
#define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_ARP_QUEUE 10
#define PBUF_POOL_SIZE 24
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_RAW 1
#define TCP_WND (8 * TCP_MSS)
#define TCP_MSS 1460
#define TCP_SND_BUF (8 * TCP_MSS)
#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETCONN 0
#define MEM_STATS 0
#define SYS_STATS 0
#define MEMP_STATS 0
#define LINK_STATS 0
// #define ETH_PAD_SIZE 2
#define LWIP_CHKSUM_ALGORITHM 3
#define LWIP_DHCP 1
#define LWIP_IPV4 1
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_DNS 1
#define LWIP_TCP_KEEPALIVE 1
#define LWIP_NETIF_TX_SINGLE_PBUF 1
#define DHCP_DOES_ARP_CHECK 0
#define LWIP_DHCP_DOES_ACD_CHECK 0
#ifndef NDEBUG
#define LWIP_DEBUG 1
#define LWIP_STATS 1
#define LWIP_STATS_DISPLAY 1
#endif
#define ETHARP_DEBUG LWIP_DBG_OFF
#define NETIF_DEBUG LWIP_DBG_OFF
#define PBUF_DEBUG LWIP_DBG_OFF
#define API_LIB_DEBUG LWIP_DBG_OFF
#define API_MSG_DEBUG LWIP_DBG_OFF
#define SOCKETS_DEBUG LWIP_DBG_OFF
#define ICMP_DEBUG LWIP_DBG_OFF
#define INET_DEBUG LWIP_DBG_OFF
#define IP_DEBUG LWIP_DBG_OFF
#define IP_REASS_DEBUG LWIP_DBG_OFF
#define RAW_DEBUG LWIP_DBG_OFF
#define MEM_DEBUG LWIP_DBG_OFF
#define MEMP_DEBUG LWIP_DBG_OFF
#define SYS_DEBUG LWIP_DBG_OFF
#define TCP_DEBUG LWIP_DBG_OFF
#define TCP_INPUT_DEBUG LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF
#define TCP_RTO_DEBUG LWIP_DBG_OFF
#define TCP_CWND_DEBUG LWIP_DBG_OFF
#define TCP_WND_DEBUG LWIP_DBG_OFF
#define TCP_FR_DEBUG LWIP_DBG_OFF
#define TCP_QLEN_DEBUG LWIP_DBG_OFF
#define TCP_RST_DEBUG LWIP_DBG_OFF
#define UDP_DEBUG LWIP_DBG_OFF
#define TCPIP_DEBUG LWIP_DBG_OFF
#define PPP_DEBUG LWIP_DBG_OFF
#define SLIP_DEBUG LWIP_DBG_OFF
#define DHCP_DEBUG LWIP_DBG_OFF
// This section enables HTTPD server with SSI, SGI
// and tells server which converted HTML files to use
#define LWIP_HTTPD 1
#define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI_INCLUDE_TAG 0
#define HTTPD_FSDATA_FILE "htmldata.c"
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
Všechno zabalené ke stažení projekt-web-server.tar.gz
Doplnění o displej
Pro snadné zjištění IP adresy jsem k zařízení dopnil OLED displej pro zobrazovaní parametrů spojení a lehký debugging.

Na OpenBSD DHCP serveru se hostname zobrazuje správně.
dizzy# cat /var/db/dhcpd.leases
lease 192.168.120.249 {
starts 6 2024/02/10 11:18:56 UTC;
ends 6 2024/02/10 12:18:56 UTC;
hardware ethernet 28:cd:c1:0c:33:1e;
client-hostname "jirkovopico1";
}
Projekt s OLED displejem ke stažení projekt-web-server2.tar.gz
Zdroje a odkazy
Poznámky
IPv6
/home-jirka/pico/pico-sdk/lib/btstack/3rd-party/lwip/core/src/include/lwip/ip6_addr.h /home-jirka/pico/pico-sdk/lib/btstack/3rd-party/lwip/core/src/include/lwip/netif.h netif_ip6_addr()