一、HTTP server:
lwIP的http server(HTTPD)支援簡易SSI(server-side-include)與CGI功能,另外有支援POST功能。
- SSI ; 相對應的API:
http_set_ssi_handler():Set the SSI handler function.
tSSIHandler: 處理SSI tag的callback function,可使用multi part處理較長的內容。
const char ** tags: SSI tags。 - CGI: 相對應的API:
http_set_cgi_handlers():Set an array of CGI filenames/handler functions。
tCGI StructURL):指定URL與相對應的function。 - POST: 相對應的API:
httpd_post_begin():
httpd_post_receive_data():每收到一個pbuf就呼叫一次。
httpd_post_data_recved():
httpd_post_finished():資料收完或connection close。 - 啟用HTTPD:
httpd_init() - 在lwipots.h加入下列:
#define LWIP_HTTPD 1
#define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI_MULTIPART 1
#define LWIP_HTTPD_SUPPORT_POST 1
#define LWIP_HTTPD_SSI_INCLUDE_TAG 0
#define HTTPD_FSDATA_FILE "_fsdata.c" //網頁檔案存放的flash memory image檔。 - 在pico-sdk/lib/lwip/src/apps/http/makefsdata的perl檔案makefsdata用來產生fsdata.c
- 修改makefsdata perl檔案,約在24行處,將
if($file =~ /\.html) {
改成
if($file =~ /\.html$/ or $file =~ /\.shtml$/ or $file =~ /\.htm$/ or $file =~ /\.shtm$/) {
讓.shtml副檔名的檔案,加入Content-type: text/html\r\n檔頭,成為網頁檔案。 - 在CMakeLists.txt加入下列執行產生fsdata.c
二、WiFi scan:
須先啟用為STA或AP mode才可執行wifi scan功能。
相對應API:
cyw43_arch_enable_sta_mode() or cyw43_arch_enable_ap_mode()
- cyw43_wifi_scan(&cyw43_state, &scan_options, aps, scan_result):啟用wifi scan並且呼叫scan_result callback function取得scan到的資料。
- cyw43_wifi_scan_active(&cyw43_state):查看wifi scan是否以完成。
其他進一步解說,請觀看「成果影片」連結。
三、成果影片
四、程式碼:
- cSJON: https://github.com/DaveGamble/cJSON
- dhcpserver: pico_examples/pico_w/wifi/access_point/dhcp_server
- makefsdata: pico-sdk/lib/lwip/src/apps/http/makefsdata
- ap_http_server.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "lwip/apps/httpd.h" #include "hardware/flash.h" #include "hardware/watchdog.h" #include "ap_http_server.h" #include "wifi_scan.h" #include "dhcpserver.h" SCAN_APS_T *aps; dhcp_server_t dhcp_server; #define WIFI_AP_SSID "PicoW" #define WIFI_AP_PASSWORD "picowpwd#" /* ==== cgi begin ======*/ const char * cgi_handler_wifi_refresh(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { scan_aps(aps, 20000); return "/index.shtml"; } static const tCGI cgi_handlers[] = { { //* Html request for "/wifi_refresh.cgi" will start cgi_handler_wifi_refresh "/wifi_refresh.cgi", cgi_handler_wifi_refresh }, }; /* ==== cgi end ======*/ /*===== post begin =====*/ typedef struct HTTP_POST_SERVER_T_ { void *current_connection; //void *valid_connection; char ssid[WIFI_PASS_BUFSIZE]; char pass[WIFI_PASS_BUFSIZE]; bool post_recv; } HTTP_POST_SERVER_T; HTTP_POST_SERVER_T *server=NULL; char* urldecode(char* str) { char tmpstr[WIFI_PASS_BUFSIZE]; int j=0; int i=0; char tmpval[5]; while(i < strlen(str)) { if (str[i] == '%') { sprintf(tmpval, "%s%c%c", "0x",str[i+1], str[i+2]); tmpstr[j] = strtol(tmpval, NULL, 16); i+=3; } else { tmpstr[j]=str[i]; i++; } j++; } tmpstr[j]='\0'; strcpy(str, tmpstr); return str; } void save_to_flash() { char flash_buff[256]; flash_range_erase(FLASH_OFFSET, 4096); // one sector memset(flash_buff, 0, 256); sprintf(flash_buff, "{\"ssid\":\"%s\",\"pass\":\"%s\"}", urldecode(server->ssid), urldecode(server->pass)); flash_range_program(FLASH_OFFSET, flash_buff, 256); // one page watchdog_enable(5000, false); // after save data to flash, reboot in 5 seconds } err_t httpd_post_begin(void *connection, const char *uri, const char *http_request, u16_t http_request_len, int content_len, char *response_uri, u16_t response_uri_len, u8_t *post_auto_wnd) { LWIP_UNUSED_ARG(connection); LWIP_UNUSED_ARG(http_request); LWIP_UNUSED_ARG(http_request_len); LWIP_UNUSED_ARG(content_len); LWIP_UNUSED_ARG(post_auto_wnd); server->post_recv=false; if (!memcmp(uri, "/wifi_conn.shtml", 17)) { if (server->current_connection != connection) { server->current_connection = connection; //server->valid_connection = NULL; snprintf(response_uri, response_uri_len, "/index.shtml"); // default : return main page /* e.g. for large uploads to slow flash over a fast connection, you should manually update the rx window. That way, a sender can only send a full tcp window at a time. If this is required, set 'post_aut_wnd' to 0. We do not need to throttle upload speed here, so: */ *post_auto_wnd = 1; return ERR_OK; } } return ERR_VAL; } err_t httpd_post_receive_data(void *connection, struct pbuf *p) { err_t ret; LWIP_ASSERT("NULL pbuf", p != NULL); if (server->current_connection == connection) { u16_t token_ssid = pbuf_memfind(p, "ssid=", 5, 0); u16_t token_pass = pbuf_memfind(p, "pass=", 5, 0); if ((token_ssid != 0xFFFF) && (token_pass != 0xFFFF)) { u16_t value_ssid = token_ssid + 5; u16_t value_pass = token_pass + 5; u16_t len_ssid = 0; u16_t len_pass = 0; u16_t tmp; /* find ssid len */ tmp = pbuf_memfind(p, "&", 1, value_ssid); if (tmp != 0xFFFF) { len_ssid = tmp - value_ssid; } else { len_ssid = p->tot_len - value_ssid; } /* find pass len */ tmp = pbuf_memfind(p, "&", 1, value_pass); if (tmp != 0xFFFF) { len_pass = tmp - value_pass; } else { len_pass = p->tot_len - value_pass; } if ((len_ssid > 0) && (len_ssid < WIFI_PASS_BUFSIZE) && (len_pass > 0) && (len_pass < WIFI_PASS_BUFSIZE) ) { char* tmpstr= (char*)pbuf_get_contiguous(p, &server->ssid, sizeof(server->ssid), len_ssid, value_ssid); tmpstr[len_ssid]=0; strcpy(server->ssid, tmpstr); tmpstr = (char*)pbuf_get_contiguous(p, &server->pass, sizeof(server->pass), len_pass, value_pass); tmpstr[len_pass]=0; strcpy(server->pass, tmpstr); server->post_recv=true; } } //server->valid_connection = connection; ret = ERR_OK; } else { ret = ERR_VAL; } /* this function must ALWAYS free the pbuf it is passed or it will leak memory */ pbuf_free(p); return ret; } void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len) { if (server->current_connection == connection) { //if (server->valid_connection == connection) { if (server->post_recv) { //save ssid & pass to fresh memory save_to_flash(); snprintf(response_uri, response_uri_len, "/wifi_conn.shtml"); } //} server->current_connection = NULL; //server->valid_connection = NULL; } } /*===== post end =====*/ /* === ssi begin =====*/ const char* __not_in_flash("httpd") ssi_tags[] = { "scanwifi", "ssid", }; /* for scan wifi, multipart: every part for one scaned AP*/ u16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen, u16_t current_tag_part, u16_t *next_tag_part) { size_t printed; static char buff[500]; char keyimg[]="<img src='img/key.png'>"; char checked[8]="checked"; int rssi=1; switch (iIndex) { case 0: // for scanwifi in index.shtml if (aps) { if (current_tag_part < aps->len) { if (((aps->AP)+current_tag_part)->rssi > -90) rssi=2; if (((aps->AP)+current_tag_part)->rssi > -80) rssi=3; if (((aps->AP)+current_tag_part)->rssi > -70) rssi=4; if (((aps->AP)+current_tag_part)->auth_mode == CYW43_AUTH_OPEN) strcpy(keyimg,""); if (current_tag_part == 0) strcpy(checked, "checked"); else strcpy(checked, ""); sprintf(buff, "<tr>" "<td>" "<input type='radio' name='ssid' %s value='%s'>%s" "</td>" "<td>" "<img src='img/wifi%d.png'>" "</td>" "<td>%s</td></tr>", checked, ((aps->AP)+current_tag_part)->ssid,((aps->AP)+current_tag_part)->ssid, rssi, keyimg); *next_tag_part=current_tag_part+1; printed = snprintf(pcInsert, iInsertLen, buff); } else { printed = snprintf(pcInsert, iInsertLen, ""); } } break; case 1: // for ssid in wifi_conn.shtml printed = snprintf(pcInsert, iInsertLen,server->ssid); break; default: printed = snprintf(pcInsert, iInsertLen, ""); } return printed; } /* === ssi end =====*/ void ap_http_server_start() { aps = (SCAN_APS_T*)calloc(1, sizeof(SCAN_APS_T)); if (!aps) { printf("cannot alloc scan ap memory\n"); return; } server = (HTTP_POST_SERVER_T*) calloc(1, sizeof(HTTP_POST_SERVER_T)); if (!server) { printf("cannot alloc server object\n"); return; } if (cyw43_arch_init()) { printf("http server cyw43_arch init error\n"); return; } printf("Starting AP Mode: local IP:192.168.4.1\n"); cyw43_arch_enable_ap_mode(WIFI_AP_SSID, WIFI_AP_PASSWORD, CYW43_AUTH_WPA2_AES_PSK); /* start dhcp server*/ ip_addr_t gw, mask; IP4_ADDR(ip_2_ip4(&gw), 192, 168, 4, 1); IP4_ADDR(ip_2_ip4(&mask), 255, 255, 255, 0); dhcp_server_init(&dhcp_server, &gw, &mask); scan_aps(aps, 2000); //check tag length size_t i; for (i = 0; i < LWIP_ARRAYSIZE(ssi_tags); i++) { LWIP_ASSERT("tag too long for LWIP_HTTPD_MAX_TAG_NAME_LEN", strlen(ssi_tags[i]) <= LWIP_HTTPD_MAX_TAG_NAME_LEN); } http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers)); httpd_init(); } void ap_http_server_stop() { dhcp_server_deinit(&dhcp_server); free(aps); cyw43_arch_deinit(); }
- ap_http_server.h
#ifndef __HTTP_SERVER_H_ #define __HTTP_SERVER_H_ #define FLASH_OFFSET 0x180000 //1.5M #define WIFI_PASS_BUFSIZE 91 void ap_http_server_start(); void ap_http_server_stop(); char* urldecode(char* str); #endif
- wifi_scan.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "string.h" #include "wifi_scan.h" static int scan_result(void *env, const cyw43_ev_scan_result_t *result) { SCAN_APS_T *res_APs = (SCAN_APS_T*) env; if (result) { for (int i =0; i < res_APs->len; i++) { if (strcmp(result->ssid, ((res_APs->AP)+i)->ssid)==0) { if (result->rssi > ((res_APs->AP)+i)->rssi) *((res_APs->AP)+i) = *result; return 0; } } cyw43_ev_scan_result_t *tmp = (cyw43_ev_scan_result_t*) realloc(res_APs->AP, sizeof(cyw43_ev_scan_result_t)*(res_APs->len+1)); if (tmp) { res_APs->len += 1; res_APs->AP = tmp; *(res_APs->AP+res_APs->len-1) = *result; } } return res_APs->len; } bool scan_aps(SCAN_APS_T* aps, uint32_t timeout) { // timeout: ms //cyw43_arch_enable_sta_mode() or cyw43_arch_enable_ap_mode() must be called before this function bool ret = false; aps = realloc(aps,0); cyw43_wifi_scan_options_t scan_options = {0}; int err = cyw43_wifi_scan(&cyw43_state, &scan_options, aps, scan_result); if (err == 0) { printf("\nPerforming wifi scan\n"); absolute_time_t scan_timeout = make_timeout_time_ms(timeout); // timeout while(absolute_time_diff_us(get_absolute_time(), scan_timeout) > 0) { if (!cyw43_wifi_scan_active(&cyw43_state)) { //print out all scaned APs for debug for (int i =0; i < aps->len; i++) { printf("%d.. ssid: %-32s rssi: %4d chan: %3d mac: %02x:%02x:%02x:%02x:%02x:%02x sec: %u\n", i, (aps->AP+i)->ssid, (aps->AP+i)->rssi, (aps->AP+i)->channel, (aps->AP+i)->bssid[0], (aps->AP+i)->bssid[1], (aps->AP+i)->bssid[2], (aps->AP+i)->bssid[3], (aps->AP+i)->bssid[4], (aps->AP+i)->bssid[5], (aps->AP+i)->auth_mode); } ret = true; break; } cyw43_arch_poll(); sleep_ms(1); } ret=false; } else { printf("Failed to start scan: %d\n", err); ret = false; } return ret; }
- wifi_scan.h
#ifndef __WIFI_SCAN_H_ #define _WIFI_SCAN_H_ #include "pico/stdlib.h" typedef struct SCAN_APS_T_ { uint16_t len; cyw43_ev_scan_result_t *AP; } SCAN_APS_T; bool scan_aps(SCAN_APS_T* aps, uint32_t timeout); #endif
CMakeLists.txt
# perl makefsdata find_package(Perl) if(NOT PERL_FOUND) message(FATAL_ERROR "Perl is needed for generating the fsdata.c file") endif() set(MAKE_FS_DATA_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/mkfsdata/makefsdata) if (EXISTS ${MAKE_FS_DATA_SCRIPT}) message("Find makefsdata script") message("Running makefsdata script") execute_process(COMMAND perl ${MAKE_FS_DATA_SCRIPT} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ECHO_OUTPUT_VARIABLE ECHO_ERROR_VARIABLE ) file(RENAME fsdata.c _fsdata.c) endif() # Generated Cmake Pico project file cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) # Initialise pico_sdk from installed location # (note this can come from environment, CMake cache etc) set(PICO_SDK_PATH "/home/duser/pico/pico-sdk") set(PICO_BOARD pico_w CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) if (PICO_SDK_VERSION_STRING VERSION_LESS "1.5.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.5.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() project(picow_wifimanager C CXX ASM) # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 add_executable(picow_wifimanager picow_wifimanager.c wifi_scan/wifi_scan.c ap_http_server/ap_http_server.c cJSON/cJSON.c dhcpserver/dhcpserver.c ) pico_set_program_name(picow_wifimanager "picow_wifimanager") pico_set_program_version(picow_wifimanager "0.1") pico_enable_stdio_uart(picow_wifimanager 1) pico_enable_stdio_usb(picow_wifimanager 0) # Add the standard library to the build target_link_libraries(picow_wifimanager pico_stdlib) # Add the standard include files to the build target_include_directories(picow_wifimanager PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ${CMAKE_CURRENT_LIST_DIR}/dhcpserver ${CMAKE_CURRENT_LIST_DIR}/cJSON ${CMAKE_CURRENT_LIST_DIR}/ap_http_server ${CMAKE_CURRENT_LIST_DIR}/wifi_scan ) # Add any user requested libraries target_link_libraries(picow_wifimanager pico_cyw43_arch_lwip_poll pico_lwip_http hardware_flash hardware_watchdog ) pico_add_extra_outputs(picow_wifimanager)
- picow_wifimanager.c
#include <stdio.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "lwip/apps/httpd.h" #include "ap_http_server.h" #include "cJSON.h" bool connect_to_wifi_ssid() { char flash_buff[256]; memset(flash_buff,0,256); snprintf(flash_buff, 256, "%s",(uint8_t*)(XIP_BASE+FLASH_OFFSET)); if (!flash_buff) return false; cJSON *ssid_pass = cJSON_CreateObject(); char *ssid; char *pass; ssid_pass = cJSON_Parse(flash_buff); if (ssid_pass) { ssid = cJSON_GetStringValue(cJSON_GetObjectItem(ssid_pass, "ssid")); pass = cJSON_GetStringValue(cJSON_GetObjectItem(ssid_pass, "pass")); if (cyw43_arch_init()) { printf("cyw43_arch init error\n"); return false; } cyw43_arch_enable_sta_mode(); printf("\n\n==========================\n" "Connecting to WiFi:%s\n" "==============================\n", ssid); if (cyw43_arch_wifi_connect_timeout_ms(ssid, pass, CYW43_AUTH_WPA2_AES_PSK, 10000)) { printf("wifi sta connect error ssid:%s\n", ssid); cyw43_arch_deinit(); return false; } } else { return false; } cJSON_Delete(ssid_pass); ip_addr_t addr = cyw43_state.netif->ip_addr; printf("connect successfully. get IP: %s\n", ipaddr_ntoa(&addr)); return true; } int main() { stdio_init_all(); bool ap_mode=false; if (!connect_to_wifi_ssid()) { ap_mode=true; ap_http_server_start(); } while(1) { static absolute_time_t led_time; static int led_on = true; if (absolute_time_diff_us(get_absolute_time(), led_time) < 0) { if (ap_mode) { led_on = !led_on; } cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on); led_time = make_timeout_time_ms(1000); } cyw43_arch_poll(); sleep_ms(1); } ap_http_server_stop(); return 0; }
- index.shtml
<!DOCTYPE html> <html> <head> <title>Pico W WiFi Manager</title> <style> h1 { font-size: 70px;} table {width:600px; margin:auto} img {height:35px;} td {padding:8px;font-size:40px;} p {font-size: 50px;} tr:hover {background-color: coral;} input[type="radio"] { height:40px; width:40px; } </style> </head> <body> <h1>Pico W WiFi Manager</h1> <p>Please select a SSID:</p> <form method="post" action="/wifi_conn.shtml"> <table> <!--#scanwifi--> </table> <p align="center"> Password:<input style="height:40px;width:300px;font-size: 35px;" type="password" name="pass" required maxlength="30"> </p> <p align="center"> <input style="height:70px;width:250px;font-size: 40px;" type="submit" name="button" value="Save">     <input style="height:70px;width:250px;font-size: 40px;" type="button" name="refresh" value="Refresh" onclick="window.location.href='wifi_refresh.cgi'"> </p> </form> </body> </html>
- wifi_conn.shtml
<!DOCTYPE html> <html> <head> <title> Pico W WiFi Manager </title> </head> <body> <p style="font-size: 45px;">Connecting to ssid: <!--#ssid--> </p> <p style="font-size: 35px;color:red;">The device will reboot in 5 seconds ... </p> </body> </html>
- 404.shmtl
<html> <head><title>lwIP - A Lightweight TCP/IP Stack</title></head> <body bgcolor="white" text="black"> <table width="100%"> <tr valign="top"><td width="80"> </td><td width="500"> <h1>lwIP - A Lightweight TCP/IP Stack</h1> <h2>404 - Page not found</h2> <p> Sorry, the page you are requesting was not found on this server. </p> </td><td> </td></tr> </table> </body> </html>
沒有留言:
張貼留言