diff --git a/README.md b/README.md index f94a5bac6..82ad5c976 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,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 @@ -144,6 +145,8 @@ App|Description [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. [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. +[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 diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index 8288eb7aa..cad451c2e 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -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") diff --git a/pico_w/wifi/freertos/CMakeLists.txt b/pico_w/wifi/freertos/CMakeLists.txt index ebdbaa31f..5c12af6bc 100644 --- a/pico_w/wifi/freertos/CMakeLists.txt +++ b/pico_w/wifi/freertos/CMakeLists.txt @@ -6,4 +6,5 @@ else() add_subdirectory(iperf) add_subdirectory(ping) add_subdirectory(ntp_client_socket) + add_subdirectory(httpd) endif() diff --git a/pico_w/wifi/freertos/httpd/CMakeLists.txt b/pico_w/wifi/freertos/httpd/CMakeLists.txt new file mode 100644 index 000000000..35dd4eb35 --- /dev/null +++ b/pico_w/wifi/freertos/httpd/CMakeLists.txt @@ -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_set_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 + ) diff --git a/pico_w/wifi/freertos/httpd/FreeRTOSConfig.h b/pico_w/wifi/freertos/httpd/FreeRTOSConfig.h new file mode 100644 index 000000000..831e53500 --- /dev/null +++ b/pico_w/wifi/freertos/httpd/FreeRTOSConfig.h @@ -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 diff --git a/pico_w/wifi/freertos/httpd/content/404.html b/pico_w/wifi/freertos/httpd/content/404.html new file mode 100644 index 000000000..36fe1cca0 --- /dev/null +++ b/pico_w/wifi/freertos/httpd/content/404.html @@ -0,0 +1,10 @@ + +
+Sorry, the page you are requesting was not found on this server.
+ + diff --git a/pico_w/wifi/freertos/httpd/content/index.shtml b/pico_w/wifi/freertos/httpd/content/index.shtml new file mode 100644 index 000000000..4b8b58928 --- /dev/null +++ b/pico_w/wifi/freertos/httpd/content/index.shtml @@ -0,0 +1,12 @@ + + +Uptime is seconds
+ +Led is click here to toggle the led + + diff --git a/pico_w/wifi/freertos/httpd/content/test.shtml b/pico_w/wifi/freertos/httpd/content/test.shtml new file mode 100644 index 000000000..95d163bd5 --- /dev/null +++ b/pico_w/wifi/freertos/httpd/content/test.shtml @@ -0,0 +1,11 @@ + +
+Test result:
+ + + diff --git a/pico_w/wifi/freertos/httpd/lwipopts.h b/pico_w/wifi/freertos/httpd/lwipopts.h new file mode 100644 index 000000000..442bc2dc8 --- /dev/null +++ b/pico_w/wifi/freertos/httpd/lwipopts.h @@ -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 diff --git a/pico_w/wifi/freertos/httpd/pico_freertos_httpd.c b/pico_w/wifi/freertos/httpd/pico_freertos_httpd.c new file mode 100644 index 000000000..0a262e5de --- /dev/null +++ b/pico_w/wifi/freertos/httpd/pico_freertos_httpd.c @@ -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, "Sorry, the page you are requesting was not found on this server.
+ + diff --git a/pico_w/wifi/httpd/content/img/rpi.png b/pico_w/wifi/httpd/content/img/rpi.png new file mode 100755 index 000000000..753319c89 Binary files /dev/null and b/pico_w/wifi/httpd/content/img/rpi.png differ diff --git a/pico_w/wifi/httpd/content/index.shtml b/pico_w/wifi/httpd/content/index.shtml new file mode 100644 index 000000000..7952f5925 --- /dev/null +++ b/pico_w/wifi/httpd/content/index.shtml @@ -0,0 +1,18 @@ + + +
Uptime is seconds
+ ++
+ + + diff --git a/pico_w/wifi/httpd/content/ledfail.shtml b/pico_w/wifi/httpd/content/ledfail.shtml new file mode 100644 index 000000000..534b84349 --- /dev/null +++ b/pico_w/wifi/httpd/content/ledfail.shtml @@ -0,0 +1,10 @@ + + +Failed to update led
+ + + diff --git a/pico_w/wifi/httpd/content/ledpass.shtml b/pico_w/wifi/httpd/content/ledpass.shtml new file mode 100644 index 000000000..0731d4485 --- /dev/null +++ b/pico_w/wifi/httpd/content/ledpass.shtml @@ -0,0 +1,10 @@ + + +Success. Led is now
+ + + diff --git a/pico_w/wifi/httpd/content/test.shtml b/pico_w/wifi/httpd/content/test.shtml new file mode 100644 index 000000000..35d50e45f --- /dev/null +++ b/pico_w/wifi/httpd/content/test.shtml @@ -0,0 +1,11 @@ + + +Test result:
+ + + diff --git a/pico_w/wifi/httpd/lwipopts.h b/pico_w/wifi/httpd/lwipopts.h new file mode 100644 index 000000000..2227e866d --- /dev/null +++ b/pico_w/wifi/httpd/lwipopts.h @@ -0,0 +1,28 @@ +#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 some httpd features +#define LWIP_HTTPD_CGI 1 +#define LWIP_HTTPD_SSI 1 +#define LWIP_HTTPD_SSI_MULTIPART 1 +#define LWIP_HTTPD_SUPPORT_POST 1 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 + +// Generated file containing html data +#define HTTPD_FSDATA_FILE "pico_fsdata.inc" + +#endif diff --git a/pico_w/wifi/httpd/pico_httpd.c b/pico_w/wifi/httpd/pico_httpd.c new file mode 100644 index 000000000..b5c2db46c --- /dev/null +++ b/pico_w/wifi/httpd/pico_httpd.c @@ -0,0 +1,244 @@ +/** + * 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" + +void httpd_init(void); + +static absolute_time_t wifi_connected_time; +static bool led_on = false; + +#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 (iNumParams > 0) { + if (strcmp(pcParam[0], "test") == 0) { + return "/test.shtml"; + } + } + 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; + } + case 4: { // "ledinv" + printed = snprintf(pcInsert, iInsertLen, "%s", led_on ? "OFF" : "ON"); + break; + } +#if LWIP_HTTPD_SSI_MULTIPART + case 5: { /* "table" */ + printed = snprintf(pcInsert, iInsertLen, "