Skip to content

Commit 1b895e1

Browse files
Add lwip httpd examples (#458)
Fixes #266 Fixes #272
1 parent 9ff02de commit 1b895e1

19 files changed

+716
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ App|Description
131131
[picow_tls_verify](pico_w/wifi/tls_client) | Demonstrates how to make a HTTPS request using TLS with certificate verification.
132132
[picow_wifi_scan](pico_w/wifi/wifi_scan) | Scans for WiFi networks and prints the results.
133133
[picow_udp_beacon](pico_w/wifi/udp_beacon) | A simple UDP transmitter.
134+
[picow_httpd](pico_w/wifi/httpd) | Runs a LWIP HTTP server test app
134135

135136
#### FreeRTOS examples
136137

@@ -144,6 +145,8 @@ App|Description
144145
[picow_freertos_ping_nosys](pico_w/wifi/freertos/ping) | Runs the lwip-contrib/apps/ping test app under FreeRTOS in NO_SYS=1 mode.
145146
[picow_freertos_ping_sys](pico_w/wifi/freertos/ping) | Runs the lwip-contrib/apps/ping test app under FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode. The test app uses the lwIP _socket_ API in this case.
146147
[picow_freertos_ntp_client_socket](pico_w/wifi/freertos/ntp_client_socket) | Connects to an NTP server using the LwIP Socket API with FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode.
148+
[pico_freertos_httpd_nosys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=1 mode.
149+
[pico_freertos_httpd_sys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode.
147150

148151
### Pico W Bluetooth
149152

pico_w/wifi/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ else()
1616
add_subdirectory(tcp_server)
1717
add_subdirectory(freertos)
1818
add_subdirectory(udp_beacon)
19+
add_subdirectory(httpd)
1920

2021
if (NOT PICO_MBEDTLS_PATH)
2122
message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined")

pico_w/wifi/freertos/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ else()
66
add_subdirectory(iperf)
77
add_subdirectory(ping)
88
add_subdirectory(ntp_client_socket)
9+
add_subdirectory(httpd)
910
endif()
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR})
2+
3+
add_executable(pico_freertos_httpd_nosys
4+
pico_freertos_httpd.c
5+
)
6+
target_compile_definitions(pico_freertos_httpd_nosys PRIVATE
7+
WIFI_SSID=\"${WIFI_SSID}\"
8+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
9+
)
10+
target_include_directories(pico_freertos_httpd_nosys PRIVATE
11+
${CMAKE_CURRENT_LIST_DIR}
12+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common FreeRTOSConfig
13+
${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts
14+
${PICO_LWIP_CONTRIB_PATH}/apps/httpd
15+
)
16+
target_link_libraries(pico_freertos_httpd_nosys
17+
pico_cyw43_arch_lwip_threadsafe_background
18+
pico_lwip_http
19+
pico_lwip_mdns
20+
pico_stdlib
21+
FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap
22+
pico_freertos_httpd_content
23+
)
24+
pico_add_extra_outputs(pico_freertos_httpd_nosys)
25+
26+
add_executable(pico_freertos_httpd_sys
27+
pico_freertos_httpd.c
28+
)
29+
target_compile_definitions(pico_freertos_httpd_sys PRIVATE
30+
WIFI_SSID=\"${WIFI_SSID}\"
31+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
32+
NO_SYS=0 # don't want NO_SYS (generally this would be in your lwipopts.h)
33+
LWIP_SOCKET=1 # we need the socket API (generally this would be in your lwipopts.h)
34+
)
35+
target_include_directories(pico_freertos_httpd_sys PRIVATE
36+
${CMAKE_CURRENT_LIST_DIR}
37+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common FreeRTOSConfig
38+
${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts
39+
${PICO_LWIP_CONTRIB_PATH}/apps/httpd
40+
)
41+
target_link_libraries(pico_freertos_httpd_sys
42+
pico_cyw43_arch_lwip_sys_freertos
43+
pico_lwip_http
44+
pico_lwip_mdns
45+
pico_stdlib
46+
FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap
47+
pico_freertos_httpd_content
48+
)
49+
pico_add_extra_outputs(pico_freertos_httpd_sys)
50+
51+
pico_add_library(pico_freertos_httpd_content NOFLAG)
52+
pico_set_lwip_httpd_content(pico_freertos_httpd_content INTERFACE
53+
${CMAKE_CURRENT_LIST_DIR}/content/404.html
54+
${CMAKE_CURRENT_LIST_DIR}/content/index.shtml
55+
${CMAKE_CURRENT_LIST_DIR}/content/test.shtml
56+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#ifndef FREERTOS_CONFIG_H
2+
#define FREERTOS_CONFIG_H
3+
4+
// This example uses a common include to avoid repetition
5+
#include "FreeRTOSConfig_examples_common.h"
6+
7+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<head>
3+
<title>Pico httpd example</title>
4+
</head>
5+
<body>
6+
<h1>Pico httpd example</h1>
7+
<h2>404 - Page not found</h2>
8+
<p>Sorry, the page you are requesting was not found on this server. </p>
9+
</body>
10+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<html>
2+
<head>
3+
<title>Pico httpd example</title>
4+
</head>
5+
<body>
6+
<h1>Pico httpd example</h1>
7+
<p><!--#welcome--></p>
8+
<p>Uptime is <!--#uptime--> seconds</p>
9+
<p><a href="/?test">CGI test</a></p>
10+
<p>Led is <!--#ledstate--> click <a href="/?toggleled">here</a> to toggle the led
11+
</body>
12+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<html>
2+
<head>
3+
<title>Pico cgi test</title>
4+
</head>
5+
<body>
6+
<h1>Pico cgi test</h1>
7+
<p><table><!--#table--></table></p>
8+
<p>Test result: <!--#status--></p>
9+
<p><a href="/">Go back</a></p>
10+
</body>
11+
</html>

pico_w/wifi/freertos/httpd/lwipopts.h

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#ifndef _LWIPOPTS_H
2+
#define _LWIPOPTS_H
3+
4+
// Generally you would define your own explicit list of lwIP options
5+
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html)
6+
//
7+
// This example uses a common include to avoid repetition
8+
#include "lwipopts_examples_common.h"
9+
10+
// The following is needed to test mDns
11+
#define LWIP_MDNS_RESPONDER 1
12+
#define LWIP_IGMP 1
13+
#define LWIP_NUM_NETIF_CLIENT_DATA 1
14+
#define MDNS_RESP_USENETIF_EXTCALLBACK 1
15+
#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + 3)
16+
#define MEMP_NUM_TCP_PCB 12
17+
18+
// Enable cgi and ssi
19+
#define LWIP_HTTPD_CGI 1
20+
#define LWIP_HTTPD_SSI 1
21+
#define LWIP_HTTPD_SSI_MULTIPART 1
22+
23+
#if !NO_SYS
24+
#define TCPIP_THREAD_STACKSIZE 2048 // mDNS needs more stack
25+
#define DEFAULT_THREAD_STACKSIZE 1024
26+
#define DEFAULT_RAW_RECVMBOX_SIZE 8
27+
#define TCPIP_MBOX_SIZE 8
28+
#define LWIP_TIMEVAL_PRIVATE 0
29+
30+
// not necessary, can be done either way
31+
#define LWIP_TCPIP_CORE_LOCKING_INPUT 1
32+
#endif
33+
34+
#define HTTPD_FSDATA_FILE "pico_fsdata.inc"
35+
36+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/**
2+
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include "pico/cyw43_arch.h"
8+
#include "pico/stdlib.h"
9+
10+
#include "lwip/ip4_addr.h"
11+
#include "lwip/apps/mdns.h"
12+
#include "lwip/init.h"
13+
#include "lwip/apps/httpd.h"
14+
15+
#include "FreeRTOS.h"
16+
#include "task.h"
17+
18+
void httpd_init(void);
19+
20+
static absolute_time_t wifi_connected_time;
21+
static bool led_on = false;
22+
23+
#ifndef RUN_FREERTOS_ON_CORE
24+
#define RUN_FREERTOS_ON_CORE 0
25+
#endif
26+
27+
#define TEST_TASK_PRIORITY ( tskIDLE_PRIORITY + 1UL )
28+
29+
#if LWIP_MDNS_RESPONDER
30+
static void srv_txt(struct mdns_service *service, void *txt_userdata)
31+
{
32+
err_t res;
33+
LWIP_UNUSED_ARG(txt_userdata);
34+
35+
res = mdns_resp_add_service_txtitem(service, "path=/", 6);
36+
LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
37+
}
38+
#endif
39+
40+
// Return some characters from the ascii representation of the mac address
41+
// e.g. 112233445566
42+
// chr_off is index of character in mac to start
43+
// chr_len is length of result
44+
// chr_off=8 and chr_len=4 would return "5566"
45+
// Return number of characters put into destination
46+
static size_t get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest_in) {
47+
static const char hexchr[16] = "0123456789ABCDEF";
48+
uint8_t mac[6];
49+
char *dest = dest_in;
50+
assert(chr_off + chr_len <= (2 * sizeof(mac)));
51+
cyw43_hal_get_mac(idx, mac);
52+
for (; chr_len && (chr_off >> 1) < sizeof(mac); ++chr_off, --chr_len) {
53+
*dest++ = hexchr[mac[chr_off >> 1] >> (4 * (1 - (chr_off & 1))) & 0xf];
54+
}
55+
return dest - dest_in;
56+
}
57+
58+
static const char *cgi_handler_test(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
59+
if (!strcmp(pcParam[0], "test")) {
60+
return "/test.shtml";
61+
} else if (strcmp(pcParam[0], "toggleled") == 0) {
62+
led_on = !led_on;
63+
cyw43_gpio_set(&cyw43_state, 0, led_on);
64+
}
65+
return "/index.shtml";
66+
}
67+
68+
static tCGI cgi_handlers[] = {
69+
{ "/", cgi_handler_test },
70+
{ "/index.shtml", cgi_handler_test },
71+
};
72+
73+
// Note that the buffer size is limited by LWIP_HTTPD_MAX_TAG_INSERT_LEN, so use LWIP_HTTPD_SSI_MULTIPART to return larger amounts of data
74+
u16_t ssi_example_ssi_handler(int iIndex, char *pcInsert, int iInsertLen
75+
#if LWIP_HTTPD_SSI_MULTIPART
76+
, uint16_t current_tag_part, uint16_t *next_tag_part
77+
#endif
78+
) {
79+
size_t printed;
80+
switch (iIndex) {
81+
case 0: { /* "status" */
82+
printed = snprintf(pcInsert, iInsertLen, "Pass");
83+
break;
84+
}
85+
case 1: { /* "welcome" */
86+
printed = snprintf(pcInsert, iInsertLen, "Hello from Pico");
87+
break;
88+
}
89+
case 2: { /* "uptime" */
90+
uint64_t uptime_s = absolute_time_diff_us(wifi_connected_time, get_absolute_time()) / 1e6;
91+
printed = snprintf(pcInsert, iInsertLen, "%"PRIu64, uptime_s);
92+
break;
93+
}
94+
case 3: { // "ledstate"
95+
printed = snprintf(pcInsert, iInsertLen, "%s", led_on ? "ON" : "OFF");
96+
break;
97+
}
98+
#if LWIP_HTTPD_SSI_MULTIPART
99+
case 4: { /* "table" */
100+
printed = snprintf(pcInsert, iInsertLen, "<tr><td>This is table row number %d</td></tr>", current_tag_part + 1);
101+
// Leave "next_tag_part" unchanged to indicate that all data has been returned for this tag
102+
if (current_tag_part < 9) {
103+
*next_tag_part = current_tag_part + 1;
104+
}
105+
break;
106+
}
107+
#endif
108+
default: { /* unknown tag */
109+
printed = 0;
110+
break;
111+
}
112+
}
113+
return (u16_t)printed;
114+
}
115+
116+
// Be aware of LWIP_HTTPD_MAX_TAG_NAME_LEN
117+
static const char * ssi_tags[] = {
118+
"status",
119+
"welcome",
120+
"uptime",
121+
"ledstate",
122+
"table",
123+
};
124+
125+
void main_task(__unused void *params) {
126+
if (cyw43_arch_init()) {
127+
printf("failed to initialise\n");
128+
return;
129+
}
130+
131+
cyw43_arch_enable_sta_mode();
132+
133+
char hostname[sizeof(CYW43_HOST_NAME) + 4];
134+
memcpy(&hostname[0], CYW43_HOST_NAME, sizeof(CYW43_HOST_NAME) - 1);
135+
get_mac_ascii(CYW43_HAL_MAC_WLAN0, 8, 4, &hostname[sizeof(CYW43_HOST_NAME) - 1]);
136+
hostname[sizeof(hostname) - 1] = '\0';
137+
netif_set_hostname(&cyw43_state.netif[CYW43_ITF_STA], hostname);
138+
139+
printf("Connecting to WiFi...\n");
140+
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
141+
printf("failed to connect.\n");
142+
exit(1);
143+
} else {
144+
printf("Connected.\n");
145+
}
146+
147+
wifi_connected_time = get_absolute_time();
148+
149+
#if LWIP_MDNS_RESPONDER
150+
mdns_resp_init();
151+
printf("mdns host name %s.local\n", hostname);
152+
#if LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 2
153+
mdns_resp_add_netif(&cyw43_state.netif[CYW43_ITF_STA], hostname);
154+
mdns_resp_add_service(&cyw43_state.netif[CYW43_ITF_STA], "picow_freertos_httpd", "_http", DNSSD_PROTO_TCP, 80, srv_txt, NULL);
155+
#else
156+
mdns_resp_add_netif(&cyw43_state.netif[CYW43_ITF_STA], hostname, 60);
157+
mdns_resp_add_service(&cyw43_state.netif[CYW43_ITF_STA], "picow_freertos_httpd", "_http", DNSSD_PROTO_TCP, 80, 60, srv_txt, NULL);
158+
#endif
159+
#endif
160+
161+
printf("\nReady, running httpd at %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list)));
162+
httpd_init();
163+
164+
http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers));
165+
http_set_ssi_handler(ssi_example_ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));
166+
167+
while(true) {
168+
vTaskDelay(100);
169+
}
170+
171+
#if LWIP_MDNS_RESPONDER
172+
mdns_resp_remove_netif(&cyw43_state.netif[CYW43_ITF_STA]);
173+
#endif
174+
175+
cyw43_arch_deinit();
176+
}
177+
178+
void vLaunch( void) {
179+
TaskHandle_t task;
180+
xTaskCreate(main_task, "TestMainThread", configMINIMAL_STACK_SIZE, NULL, TEST_TASK_PRIORITY, &task);
181+
182+
#if NO_SYS && configUSE_CORE_AFFINITY && configNUM_CORES > 1
183+
// we must bind the main task to one core (well at least while the init is called)
184+
// (note we only do this in NO_SYS mode, because cyw43_arch_freertos
185+
// takes care of it otherwise)
186+
vTaskCoreAffinitySet(task, 1);
187+
#endif
188+
189+
/* Start the tasks and timer running. */
190+
vTaskStartScheduler();
191+
}
192+
193+
int main( void )
194+
{
195+
stdio_init_all();
196+
197+
/* Configure the hardware ready to run the demo. */
198+
const char *rtos_name;
199+
#if ( portSUPPORT_SMP == 1 )
200+
rtos_name = "FreeRTOS SMP";
201+
#else
202+
rtos_name = "FreeRTOS";
203+
#endif
204+
205+
#if ( portSUPPORT_SMP == 1 ) && ( configNUM_CORES == 2 )
206+
printf("Starting %s on both cores:\n", rtos_name);
207+
vLaunch();
208+
#elif ( RUN_FREE_RTOS_ON_CORE == 1 )
209+
printf("Starting %s on core 1:\n", rtos_name);
210+
multicore_launch_core1(vLaunch);
211+
while (true);
212+
#else
213+
printf("Starting %s on core 0:\n", rtos_name);
214+
vLaunch();
215+
#endif
216+
return 0;
217+
}

0 commit comments

Comments
 (0)