一、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>
沒有留言:
張貼留言