Skip to content

Commit

Permalink
Add lwip httpd examples
Browse files Browse the repository at this point in the history
Fixes #266
Fixes #272
  • Loading branch information
peterharperuk committed Jan 5, 2024
1 parent 96f4abe commit 2ee441b
Show file tree
Hide file tree
Showing 19 changed files with 716 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ App|Description
[picow_tls_verify](pico_w/wifi/tls_client) | Demonstrates how to make a HTTPS request using TLS with certificate verification.
[picow_wifi_scan](pico_w/wifi/wifi_scan) | Scans for WiFi networks and prints the results.
[picow_udp_beacon](pico_w/wifi/udp_beacon) | A simple UDP transmitter.
[picow_httpd](pico_w/wifi/httpd) | Runs a LWIP HTTP server test app

#### FreeRTOS examples

Expand All @@ -142,6 +143,8 @@ App|Description
[picow_freertos_iperf_server_sys](pico_w/wifi/freertos/iperf) | Runs an "iperf" server for WiFi speed testing under FreeRTOS in NO_SYS=0 (i.e. full FreeRTOS integration) mode. The LED is blinked in another task
[picow_freertos_ping_nosys](pico_w/wifi/freertos/ping) | Runs the lwip-contrib/apps/ping test app under FreeRTOS in NO_SYS=1 mode.
[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.
[pico_freertos_httpd_nosys](pico_w/wifi/freertos/httpd) | Runs a LWIP HTTP server test app under FreeRTOS in NO_SYS=1 mode.
[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.

### Pico W Bluetooth

Expand Down
1 change: 1 addition & 0 deletions pico_w/wifi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ else()
add_subdirectory(tcp_server)
add_subdirectory(freertos)
add_subdirectory(udp_beacon)
add_subdirectory(httpd)

if (NOT PICO_MBEDTLS_PATH)
message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined")
Expand Down
1 change: 1 addition & 0 deletions pico_w/wifi/freertos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ else()

add_subdirectory(iperf)
add_subdirectory(ping)
add_subdirectory(httpd)
endif()
56 changes: 56 additions & 0 deletions pico_w/wifi/freertos/httpd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR})

add_executable(pico_freertos_httpd_nosys
pico_freertos_httpd.c
)
target_compile_definitions(pico_freertos_httpd_nosys PRIVATE
WIFI_SSID=\"${WIFI_SSID}\"
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
)
target_include_directories(pico_freertos_httpd_nosys PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # for our common FreeRTOSConfig
${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts
${PICO_LWIP_CONTRIB_PATH}/apps/httpd
)
target_link_libraries(pico_freertos_httpd_nosys
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_http
pico_lwip_mdns
pico_stdlib
FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap
pico_freertos_httpd_content
)
pico_add_extra_outputs(pico_freertos_httpd_nosys)

add_executable(pico_freertos_httpd_sys
pico_freertos_httpd.c
)
target_compile_definitions(pico_freertos_httpd_sys PRIVATE
WIFI_SSID=\"${WIFI_SSID}\"
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
NO_SYS=0 # don't want NO_SYS (generally this would be in your lwipopts.h)
LWIP_SOCKET=1 # we need the socket API (generally this would be in your lwipopts.h)
)
target_include_directories(pico_freertos_httpd_sys PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # for our common FreeRTOSConfig
${CMAKE_CURRENT_LIST_DIR}/../.. # for our common lwipopts
${PICO_LWIP_CONTRIB_PATH}/apps/httpd
)
target_link_libraries(pico_freertos_httpd_sys
pico_cyw43_arch_lwip_sys_freertos
pico_lwip_http
pico_lwip_mdns
pico_stdlib
FreeRTOS-Kernel-Heap4 # FreeRTOS kernel and dynamic heap
pico_freertos_httpd_content
)
pico_add_extra_outputs(pico_freertos_httpd_sys)

pico_add_library(pico_freertos_httpd_content NOFLAG)
pico_lwip_httpd_content(pico_freertos_httpd_content INTERFACE
${CMAKE_CURRENT_LIST_DIR}/content/404.html
${CMAKE_CURRENT_LIST_DIR}/content/index.shtml
${CMAKE_CURRENT_LIST_DIR}/content/test.shtml
)
7 changes: 7 additions & 0 deletions pico_w/wifi/freertos/httpd/FreeRTOSConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

// This example uses a common include to avoid repetition
#include "FreeRTOSConfig_examples_common.h"

#endif
10 changes: 10 additions & 0 deletions pico_w/wifi/freertos/httpd/content/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>Pico httpd example</title>
</head>
<body>
<h1>Pico httpd example</h1>
<h2>404 - Page not found</h2>
<p>Sorry, the page you are requesting was not found on this server. </p>
</body>
</html>
12 changes: 12 additions & 0 deletions pico_w/wifi/freertos/httpd/content/index.shtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<head>
<title>Pico httpd example</title>
</head>
<body>
<h1>Pico httpd example</h1>
<p><!--#welcome--></p>
<p>Uptime is <!--#uptime--> seconds</p>
<p><a href="/?test">CGI test</a></p>
<p>Led is <!--#ledstate--> click <a href="/?toggleled">here</a> to toggle the led
</body>
</html>
11 changes: 11 additions & 0 deletions pico_w/wifi/freertos/httpd/content/test.shtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head>
<title>Pico cgi test</title>
</head>
<body>
<h1>Pico cgi test</h1>
<p><table><!--#table--></table></p>
<p>Test result: <!--#status--></p>
<p><a href="/">Go back</a></p>
</body>
</html>
36 changes: 36 additions & 0 deletions pico_w/wifi/freertos/httpd/lwipopts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef _LWIPOPTS_H
#define _LWIPOPTS_H

// Generally you would define your own explicit list of lwIP options
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html)
//
// This example uses a common include to avoid repetition
#include "lwipopts_examples_common.h"

// The following is needed to test mDns
#define LWIP_MDNS_RESPONDER 1
#define LWIP_IGMP 1
#define LWIP_NUM_NETIF_CLIENT_DATA 1
#define MDNS_RESP_USENETIF_EXTCALLBACK 1
#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + 3)
#define MEMP_NUM_TCP_PCB 12

// Enable cgi and ssi
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_SSI_MULTIPART 1

#if !NO_SYS
#define TCPIP_THREAD_STACKSIZE 2048 // mDNS needs more stack
#define DEFAULT_THREAD_STACKSIZE 1024
#define DEFAULT_RAW_RECVMBOX_SIZE 8
#define TCPIP_MBOX_SIZE 8
#define LWIP_TIMEVAL_PRIVATE 0

// not necessary, can be done either way
#define LWIP_TCPIP_CORE_LOCKING_INPUT 1
#endif

#define HTTPD_FSDATA_FILE "pico_fsdata.inc"

#endif
217 changes: 217 additions & 0 deletions pico_w/wifi/freertos/httpd/pico_freertos_httpd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"

#include "lwip/ip4_addr.h"
#include "lwip/apps/mdns.h"
#include "lwip/init.h"
#include "lwip/apps/httpd.h"

#include "FreeRTOS.h"
#include "task.h"

void httpd_init(void);

static absolute_time_t wifi_connected_time;
static bool led_on = false;

#ifndef RUN_FREERTOS_ON_CORE
#define RUN_FREERTOS_ON_CORE 0
#endif

#define TEST_TASK_PRIORITY ( tskIDLE_PRIORITY + 1UL )

#if LWIP_MDNS_RESPONDER
static void srv_txt(struct mdns_service *service, void *txt_userdata)
{
err_t res;
LWIP_UNUSED_ARG(txt_userdata);

res = mdns_resp_add_service_txtitem(service, "path=/", 6);
LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
}
#endif

// Return some characters from the ascii representation of the mac address
// e.g. 112233445566
// chr_off is index of character in mac to start
// chr_len is length of result
// chr_off=8 and chr_len=4 would return "5566"
// Return number of characters put into destination
static size_t get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest_in) {
static const char hexchr[16] = "0123456789ABCDEF";
uint8_t mac[6];
char *dest = dest_in;
assert(chr_off + chr_len <= (2 * sizeof(mac)));
cyw43_hal_get_mac(idx, mac);
for (; chr_len && (chr_off >> 1) < sizeof(mac); ++chr_off, --chr_len) {
*dest++ = hexchr[mac[chr_off >> 1] >> (4 * (1 - (chr_off & 1))) & 0xf];
}
return dest - dest_in;
}

static const char *cgi_handler_test(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
if (!strcmp(pcParam[0], "test")) {
return "/test.shtml";
} else if (strcmp(pcParam[0], "toggleled") == 0) {
led_on = !led_on;
cyw43_gpio_set(&cyw43_state, 0, led_on);
}
return "/index.shtml";
}

static tCGI cgi_handlers[] = {
{ "/", cgi_handler_test },
{ "/index.shtml", cgi_handler_test },
};

// 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
u16_t ssi_example_ssi_handler(int iIndex, char *pcInsert, int iInsertLen
#if LWIP_HTTPD_SSI_MULTIPART
, uint16_t current_tag_part, uint16_t *next_tag_part
#endif
) {
size_t printed;
switch (iIndex) {
case 0: { /* "status" */
printed = snprintf(pcInsert, iInsertLen, "Pass");
break;
}
case 1: { /* "welcome" */
printed = snprintf(pcInsert, iInsertLen, "Hello from Pico");
break;
}
case 2: { /* "uptime" */
uint64_t uptime_s = absolute_time_diff_us(wifi_connected_time, get_absolute_time()) / 1e6;
printed = snprintf(pcInsert, iInsertLen, "%"PRIu64, uptime_s);
break;
}
case 3: { // "ledstate"
printed = snprintf(pcInsert, iInsertLen, "%s", led_on ? "ON" : "OFF");
break;
}
#if LWIP_HTTPD_SSI_MULTIPART
case 4: { /* "table" */
printed = snprintf(pcInsert, iInsertLen, "<tr><td>This is table row number %d</td></tr>", current_tag_part + 1);
// Leave "next_tag_part" unchanged to indicate that all data has been returned for this tag
if (current_tag_part < 9) {
*next_tag_part = current_tag_part + 1;
}
break;
}
#endif
default: { /* unknown tag */
printed = 0;
break;
}
}
return (u16_t)printed;
}

// Be aware of LWIP_HTTPD_MAX_TAG_NAME_LEN
static const char * ssi_tags[] = {
"status",
"welcome",
"uptime",
"ledstate",
"table",
};

void main_task(__unused void *params) {
if (cyw43_arch_init()) {
printf("failed to initialise\n");
return;
}

cyw43_arch_enable_sta_mode();

char hostname[sizeof(CYW43_HOST_NAME) + 4];
memcpy(&hostname[0], CYW43_HOST_NAME, sizeof(CYW43_HOST_NAME) - 1);
get_mac_ascii(CYW43_HAL_MAC_WLAN0, 8, 4, &hostname[sizeof(CYW43_HOST_NAME) - 1]);
hostname[sizeof(hostname) - 1] = '\0';
netif_set_hostname(&cyw43_state.netif[CYW43_ITF_STA], hostname);

printf("Connecting to WiFi...\n");
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
printf("failed to connect.\n");
exit(1);
} else {
printf("Connected.\n");
}

wifi_connected_time = get_absolute_time();

#if LWIP_MDNS_RESPONDER
mdns_resp_init();
printf("mdns host name %s.local\n", hostname);
#if LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 2
mdns_resp_add_netif(&cyw43_state.netif[CYW43_ITF_STA], hostname);
mdns_resp_add_service(&cyw43_state.netif[CYW43_ITF_STA], "picow_freertos_httpd", "_http", DNSSD_PROTO_TCP, 80, srv_txt, NULL);
#else
mdns_resp_add_netif(&cyw43_state.netif[CYW43_ITF_STA], hostname, 60);
mdns_resp_add_service(&cyw43_state.netif[CYW43_ITF_STA], "picow_freertos_httpd", "_http", DNSSD_PROTO_TCP, 80, 60, srv_txt, NULL);
#endif
#endif

printf("\nReady, running httpd at %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list)));
httpd_init();

http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers));
http_set_ssi_handler(ssi_example_ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));

while(true) {
vTaskDelay(100);
}

#if LWIP_MDNS_RESPONDER
mdns_resp_remove_netif(&cyw43_state.netif[CYW43_ITF_STA]);
#endif

cyw43_arch_deinit();
}

void vLaunch( void) {
TaskHandle_t task;
xTaskCreate(main_task, "TestMainThread", configMINIMAL_STACK_SIZE, NULL, TEST_TASK_PRIORITY, &task);

#if NO_SYS && configUSE_CORE_AFFINITY && configNUM_CORES > 1
// we must bind the main task to one core (well at least while the init is called)
// (note we only do this in NO_SYS mode, because cyw43_arch_freertos
// takes care of it otherwise)
vTaskCoreAffinitySet(task, 1);
#endif

/* Start the tasks and timer running. */
vTaskStartScheduler();
}

int main( void )
{
stdio_init_all();

/* Configure the hardware ready to run the demo. */
const char *rtos_name;
#if ( portSUPPORT_SMP == 1 )
rtos_name = "FreeRTOS SMP";
#else
rtos_name = "FreeRTOS";
#endif

#if ( portSUPPORT_SMP == 1 ) && ( configNUM_CORES == 2 )
printf("Starting %s on both cores:\n", rtos_name);
vLaunch();
#elif ( RUN_FREE_RTOS_ON_CORE == 1 )
printf("Starting %s on core 1:\n", rtos_name);
multicore_launch_core1(vLaunch);
while (true);
#else
printf("Starting %s on core 0:\n", rtos_name);
vLaunch();
#endif
return 0;
}
Loading

0 comments on commit 2ee441b

Please sign in to comment.