From 005ea763c61ccdc9e7d2b6b630454c3c280e0eb3 Mon Sep 17 00:00:00 2001 From: Louis31423142 Date: Fri, 5 Jun 2026 13:02:02 +0100 Subject: [PATCH 1/2] Add pico_w/wifi/access_point_wifi_provisioning/ --- README.md | 1 + pico_w/wifi/CMakeLists.txt | 1 + .../AP_provisioning.c | 355 ++++++++++++++++++ .../CMakeLists.txt | 25 ++ .../access_point_wifi_provisioning/README.md | 4 + .../content/index.shtml | 57 +++ .../dhcpserver/LICENSE | 21 ++ .../dhcpserver/dhcpserver.c | 309 +++++++++++++++ .../dhcpserver/dhcpserver.h | 49 +++ .../dnsserver/dnsserver.c | 235 ++++++++++++ .../dnsserver/dnsserver.h | 20 + .../access_point_wifi_provisioning/lwipopts.h | 101 +++++ 12 files changed, 1178 insertions(+) create mode 100644 pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c create mode 100644 pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt create mode 100644 pico_w/wifi/access_point_wifi_provisioning/README.md create mode 100644 pico_w/wifi/access_point_wifi_provisioning/content/index.shtml create mode 100644 pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE create mode 100644 pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c create mode 100644 pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h create mode 100644 pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c create mode 100644 pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h create mode 100644 pico_w/wifi/access_point_wifi_provisioning/lwipopts.h diff --git a/README.md b/README.md index eda6d39bb..4abfcfec9 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ These networking examples are only available if Wi-Fi is supported by the board. App|Description ---|--- [picow_access_point](pico_w/wifi/access_point) | Starts a WiFi access point, and fields DHCP requests. +[picow_access_point_wifi_provisioning](pico_w/wifi/access_point_wifi_provisioning) | Starts a WiFi access point, and allows WiFi credentials to be provisioned through a web page. [picow_blink](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip). [picow_blink_slow_clock](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip) with a slower system clock to show how to reconfigure communication with the WiFi chip at run time under those circumstances. [picow_blink_fast_clock](pico_w/wifi/blink) | Blinks the on-board LED (which is connected via the WiFi chip) with a faster system clock to show how to reconfigure communication with the WiFi chip at build time under those circumstances. diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index b096d2583..36d73f43f 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -4,6 +4,7 @@ set(WIFI_PASSWORD "${WIFI_PASSWORD}" CACHE INTERNAL "WiFi password for examples" add_subdirectory_exclude_platforms(blink) add_subdirectory_exclude_platforms(wifi_scan) add_subdirectory_exclude_platforms(access_point) +add_subdirectory_exclude_platforms(access_point_wifi_provisioning) if ("${WIFI_SSID}" STREQUAL "") message("Skipping some Pico W examples as WIFI_SSID is not defined") diff --git a/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c b/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c new file mode 100644 index 000000000..ec89a383a --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c @@ -0,0 +1,355 @@ +/** + * 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/init.h" +#include "lwip/apps/httpd.h" + +#include "dhcpserver.h" +#include "dnsserver.h" + +#include "pico/flash.h" +#include "hardware/flash.h" // for saving succesful credentials + +static absolute_time_t wifi_connected_time; +static bool led_on = false; + +// max lengths + 1 +static char ssid[33]; +static char password[64]; + +static int num_credentials; + +static bool connection_status = false; + +// how many sectors would you like to reserve +// each sector is 4096 bytes, so can hold 40 pairs of max length credentials +#define DESIRED_FLASH_SECTORS 1 +static char ssid_list[40 * DESIRED_FLASH_SECTORS][33]; +static char password_list[40 * DESIRED_FLASH_SECTORS][64]; + +// Define flash offset towards end of flash +#ifndef PICO_FLASH_BANK_TOTAL_SIZE +#define PICO_FLASH_BANK_TOTAL_SIZE (FLASH_SECTOR_SIZE * 2u) +#endif + +#ifndef PICO_FLASH_BANK_STORAGE_OFFSET +#if PICO_RP2350 && PICO_RP2350_A2_SUPPORTED +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE - PICO_FLASH_BANK_TOTAL_SIZE - FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS) +#else +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - PICO_FLASH_BANK_TOTAL_SIZE - FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS) +#endif +#endif + +static const uint8_t *flash_target_contents = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET); + +// Function prototypes +static void call_flash_range_erase(void *param); +static void call_flash_range_program(void *param); + +static void save_credentials(char ssid[], char password[]); +static void read_credentials(void); + +static void attempt_wifi_connection(void); + +static const char *credential_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); +static const char *connect_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); +static const char *connect_from_saved_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); +static const char *clear_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); + +static u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen +#if LWIP_HTTPD_SSI_MULTIPART + , uint16_t current_tag_part, uint16_t *next_tag_part +#endif +); + +static tCGI cgi_handlers[] = { + { "/credentials.cgi", credential_cgi_handler }, + { "/connect.cgi", connect_cgi_handler }, + { "/connect_from_saved.cgi", connect_from_saved_cgi_handler}, + {"/clear.cgi", clear_cgi_handler} +}; + +// Be aware of LWIP_HTTPD_MAX_TAG_NAME_LEN +static const char *ssi_tags[] = { + "wifilist", + "ssid", + "password" +}; + +int main() { + stdio_init_all(); + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + printf("intitialised\n"); + + // for testing, erase memory first + // might need to erase memory first time you use provisioning in case there are garbage values in flash + //int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + //hard_assert(rc == PICO_OK); + + // First, try to connect to network using saved credentials + read_credentials(); + + cyw43_arch_enable_sta_mode(); + for (int i = 0; i < num_credentials; i++) { + if (cyw43_arch_wifi_connect_timeout_ms(ssid_list[i], password_list[i], CYW43_AUTH_WPA2_AES_PSK, 5000)) { + printf("failed to connect with saved credentials %i \n", i); + } else { + printf("Connected.\n"); + connection_status = true; + break; + } + } + + // If this fails, enable access point + if (connection_status == false) { + cyw43_arch_disable_sta_mode(); + cyw43_arch_enable_ap_mode("picow_test", "12345678", CYW43_AUTH_WPA2_AES_PSK); + printf("\nReady, running server at %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list))); + + #if LWIP_IPV6 + #define IP(x) ((x).u_addr.ip4) + #else + #define IP(x) (x) + #endif + + ip4_addr_t mask; + ip4_addr_t gw; + IP(gw).addr = PP_HTONL(CYW43_DEFAULT_IP_AP_ADDRESS); + IP(mask).addr = PP_HTONL(CYW43_DEFAULT_IP_MASK); + + #undef IP + dhcp_server_t dhcp_server; + dhcp_server_init(&dhcp_server, &gw, &mask); + + dns_server_t dns_server; + dns_server_init(&dns_server, &gw); + + char hostname[sizeof(CYW43_HOST_NAME) + 4]; + memcpy(&hostname[0], CYW43_HOST_NAME, sizeof(CYW43_HOST_NAME) - 1); + hostname[sizeof(hostname) - 1] = '\0'; + netif_set_hostname(&cyw43_state.netif[CYW43_ITF_STA], hostname); + + // start http server + wifi_connected_time = get_absolute_time(); + + // setup http server + cyw43_arch_lwip_begin(); + httpd_init(); + http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers)); + http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); + cyw43_arch_lwip_end(); + } + + //wait for connection + while(connection_status == false) { + cyw43_arch_poll(); + cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000)); + } + + printf("Finished provisioning credentials. \n"); + cyw43_arch_deinit(); + return 0; +} + +// This function will be called when it's safe to call flash_range_erase +static void call_flash_range_erase(void *param) { + uint32_t offset = (uint32_t)param; + flash_range_erase(offset, FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS); +} + +// This function will be called when it's safe to call flash_range_program +static void call_flash_range_program(void *param) { + uint32_t offset = ((uintptr_t*)param)[0]; + const uint8_t *data = (const uint8_t *)((uintptr_t*)param)[1]; + flash_range_program(offset, data, FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS); +} + +// Functions for saving and reading credentials from flash +static void save_credentials(char ssid[], char password[]) { + // create empty 256 byte list + uint8_t flash_data[FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS] = {0}; + + uint ssid_len = strlen(ssid); + uint password_len = strlen(password); + uint credential_count; + + //first check how many credentials are already saved + if (flash_target_contents[1] != 255) { + credential_count = flash_target_contents[1]; + //incriment this count since we are about to add a credential + credential_count++; + flash_data[1] = credential_count; + } else { + // first (empty) save, so dont want to incriment + credential_count = 0; + } + + //now need to find how far through the flash to start writing, and also add previous stuff to flash data + uint write_start_location = 2; + if (credential_count != 0) { + uint count = 0; + while (count < 2 * credential_count - 2) { + flash_data[write_start_location] = flash_target_contents[write_start_location]; + if (flash_target_contents[write_start_location] == 0) { + count++; + } + write_start_location++; + } + } + + // no character has ascii value 0, so we can seperate our ssid and password with a single 0 + // first add ssid + for (uint i = 0; i < ssid_len; i++) { + int ascii = (int) ssid[i]; + //printf("%i\n", ascii); + flash_data[i + write_start_location] = ascii; + } + + //next add password + for (uint i = 0; i < password_len; i++) { + int ascii = (int) password[i]; + flash_data[i + ssid_len + write_start_location + 1] = ascii; + } + + // must always erase flash before write + int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + hard_assert(rc == PICO_OK); + + // write flash + uintptr_t params[] = { FLASH_TARGET_OFFSET, (uintptr_t)flash_data}; + rc = flash_safe_execute(call_flash_range_program, params, UINT32_MAX); + hard_assert(rc == PICO_OK); +} + +static void read_credentials(void) { + uint credential_count; + + // first check if the flash page begins with FF - this indicates the flash has not yet been written to + // so must initialise with empty write + if (flash_target_contents[0] == 255) { + save_credentials("", ""); + } + + //second byte saves credential count (allows 255 sets of credentials, should be enough) + credential_count = flash_target_contents[1]; + num_credentials = credential_count; + //initialise temporary ssid and password as 1 bigger than max to ensure null termination + char t_ssid_list[20][33] = {0}; + char t_password_list[20][64] = {0}; + + uint space_count = 0; + uint start_index = 1; + + for (uint i = 2; i < FLASH_SECTOR_SIZE * DESIRED_FLASH_SECTORS; i++) { + if (space_count >= 2*credential_count) { + break; + } else if (flash_target_contents[i] == 0) { + space_count++; + start_index = i; + printf("\n"); + //printf("space count %i\n", space_count); + } else if (flash_target_contents[i] != 0 && space_count % 2 == 0) { + // there is a char, and even space count. So we are reading a ssid + t_ssid_list[(int) space_count / 2][i - start_index - 1] = flash_target_contents[i]; + //printf("%c", flash_target_contents[i]); + } else if (flash_target_contents[i] != 0 && space_count % 2 == 1) { + // there is a char and odd space count, so reading password + t_password_list[(int) space_count / 2][i - start_index - 1] = flash_target_contents[i]; + //printf("%c", flash_target_contents[i]); + } + } + + // update global ssid and password lists + memset(ssid_list, 0, sizeof(ssid_list)); + memcpy(ssid_list, t_ssid_list, sizeof(t_ssid_list)); + + memset(password_list, 0, sizeof(password_list)); + memcpy(password_list, t_password_list, sizeof(t_password_list)); +} + +static void attempt_wifi_connection(void) { + cyw43_arch_disable_ap_mode(); + cyw43_arch_enable_sta_mode(); + + if (cyw43_arch_wifi_connect_timeout_ms(ssid, password, CYW43_AUTH_WPA2_AES_PSK, 15000)) { + panic("Failed to connect!"); + } else { + printf("Connected.\n"); + connection_status = true; + // success, so save credentials for future use + save_credentials(ssid, password); + } +} + +static const char *credential_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("credential_cgi_handler called\n"); + strncpy(ssid, pcValue[0], sizeof(ssid) - 1); + strncpy(password, pcValue[1], sizeof(password) - 1); + printf("SSID AND PASSWORD: %s %s \n", ssid, password); + return "/index.shtml"; +} + +static const char *connect_from_saved_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("load_from_saved_cgi_handler called\n"); + strncpy(ssid, ssid_list[atoi(pcValue[0])], sizeof(ssid) - 1); + strncpy(password, password_list[atoi(pcValue[0])], sizeof(password) - 1); + attempt_wifi_connection(); + return "/index.shtml"; +} + +static const char *connect_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("connect_cgi_handler called\n"); + attempt_wifi_connection(); + return "/index.shtml"; +} + +static const char *clear_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { + printf("clear_cgi_handler called\n"); + int rc = flash_safe_execute(call_flash_range_erase, (void*)FLASH_TARGET_OFFSET, UINT32_MAX); + hard_assert(rc == PICO_OK); + save_credentials("", ""); + read_credentials(); + return "/index.shtml"; +} + +// 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 +static u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen +#if LWIP_HTTPD_SSI_MULTIPART + , uint16_t current_tag_part, uint16_t *next_tag_part +#endif +) { + int printed = 0; + switch (iIndex) { + case 0: { // wifilist + for (int i = 0; i < num_credentials; i++) { + printed += snprintf(pcInsert + printed, iInsertLen - printed, + "
  • SSID: %s, Password: %s, Index: %i
  • ", + ssid_list[i], password_list[i], i); + } + break; + } + case 1: { // ssid + printed = snprintf(pcInsert, iInsertLen, ssid); + break; + } + case 2: { // password + printed = snprintf(pcInsert, iInsertLen, password); + break; + } + default: { // unknown tag + printed = 0; + break; + } + } + return printed; +} \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt b/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt new file mode 100644 index 000000000..c22c25a5d --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt @@ -0,0 +1,25 @@ +add_executable(AP_provisioning + AP_provisioning.c + dhcpserver/dhcpserver.c + dnsserver/dnsserver.c + ) +target_include_directories(AP_provisioning PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ${PICO_LWIP_CONTRIB_PATH}/apps/httpd + ${CMAKE_CURRENT_LIST_DIR}/dhcpserver + ${CMAKE_CURRENT_LIST_DIR}/dnsserver + ) +target_link_libraries(AP_provisioning PRIVATE + pico_cyw43_arch_lwip_poll + pico_lwip_http + ap_provisioning_content + pico_stdlib + hardware_flash + ) +pico_add_extra_outputs(AP_provisioning) + +pico_add_library(ap_provisioning_content NOFLAG) +pico_set_lwip_httpd_content(ap_provisioning_content INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/content/index.shtml + ) \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/README.md b/pico_w/wifi/access_point_wifi_provisioning/README.md new file mode 100644 index 000000000..b264909ff --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/README.md @@ -0,0 +1,4 @@ +### Access point wifi provisioning + +This example demonstrates provisioning wifi credentials using an access point. The pico saves succesful credentials in flash, which it first uses when attempting to join wifi. If this fails, the pico starts an access point which you can connect to and input credentials. The DNS server means that any address you enter when connected to the access point will take you to the provisioning web page. + diff --git a/pico_w/wifi/access_point_wifi_provisioning/content/index.shtml b/pico_w/wifi/access_point_wifi_provisioning/content/index.shtml new file mode 100644 index 000000000..5f552d417 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/content/index.shtml @@ -0,0 +1,57 @@ + + + + + + + +

    AP provisioning

    + +

    Enter new credentials, submit, then connect, or select from previously saved.

    + +
    + +
    +

    Current SSID:

    +

    Current password:

    + +
    +
    +
    +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    List of saved credentials:

    +
    +
    +
    + +
    +
    + + +
    + + + \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE new file mode 100644 index 000000000..8f9b52cbb --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2022 Damien P. George + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c new file mode 100644 index 000000000..2061d047e --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.c @@ -0,0 +1,309 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// For DHCP specs see: +// https://www.ietf.org/rfc/rfc2131.txt +// https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions + +#include +#include +#include + +#include "cyw43_config.h" +#include "dhcpserver.h" +#include "lwip/udp.h" + +#define DHCPDISCOVER (1) +#define DHCPOFFER (2) +#define DHCPREQUEST (3) +#define DHCPDECLINE (4) +#define DHCPACK (5) +#define DHCPNACK (6) +#define DHCPRELEASE (7) +#define DHCPINFORM (8) + +#define DHCP_OPT_PAD (0) +#define DHCP_OPT_SUBNET_MASK (1) +#define DHCP_OPT_ROUTER (3) +#define DHCP_OPT_DNS (6) +#define DHCP_OPT_HOST_NAME (12) +#define DHCP_OPT_REQUESTED_IP (50) +#define DHCP_OPT_IP_LEASE_TIME (51) +#define DHCP_OPT_MSG_TYPE (53) +#define DHCP_OPT_SERVER_ID (54) +#define DHCP_OPT_PARAM_REQUEST_LIST (55) +#define DHCP_OPT_MAX_MSG_SIZE (57) +#define DHCP_OPT_VENDOR_CLASS_ID (60) +#define DHCP_OPT_CLIENT_ID (61) +#define DHCP_OPT_END (255) + +#define PORT_DHCP_SERVER (67) +#define PORT_DHCP_CLIENT (68) + +#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds + +#define MAC_LEN (6) +#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +typedef struct { + uint8_t op; // message opcode + uint8_t htype; // hardware address type + uint8_t hlen; // hardware address length + uint8_t hops; + uint32_t xid; // transaction id, chosen by client + uint16_t secs; // client seconds elapsed + uint16_t flags; + uint8_t ciaddr[4]; // client IP address + uint8_t yiaddr[4]; // your IP address + uint8_t siaddr[4]; // next server IP address + uint8_t giaddr[4]; // relay agent IP address + uint8_t chaddr[16]; // client hardware address + uint8_t sname[64]; // server host name + uint8_t file[128]; // boot file name + uint8_t options[312]; // optional parameters, variable, starts with magic +} dhcp_msg_t; + +static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { + // family is AF_INET + // type is SOCK_DGRAM + + *udp = udp_new(); + if (*udp == NULL) { + return -ENOMEM; + } + + // Register callback + udp_recv(*udp, cb_udp_recv, (void *)cb_data); + + return 0; // success +} + +static void dhcp_socket_free(struct udp_pcb **udp) { + if (*udp != NULL) { + udp_remove(*udp); + *udp = NULL; + } +} + +static int dhcp_socket_bind(struct udp_pcb **udp, uint16_t port) { + // TODO convert lwIP errors to errno + return udp_bind(*udp, IP_ANY_TYPE, port); +} + +static int dhcp_socket_sendto(struct udp_pcb **udp, struct netif *nif, const void *buf, size_t len, uint32_t ip, uint16_t port) { + if (len > 0xffff) { + len = 0xffff; + } + + struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (p == NULL) { + return -ENOMEM; + } + + memcpy(p->payload, buf, len); + + ip_addr_t dest; + IP4_ADDR(ip_2_ip4(&dest), ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + err_t err; + if (nif != NULL) { + err = udp_sendto_if(*udp, p, &dest, port, nif); + } else { + err = udp_sendto(*udp, p, &dest, port); + } + + pbuf_free(p); + + if (err != ERR_OK) { + return err; + } + + return len; +} + +static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) { + for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) { + if (opt[i] == cmd) { + return &opt[i]; + } + i += 2 + opt[i + 1]; + } + return NULL; +} + +static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, const void *data) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = n; + memcpy(o, data, n); + *opt = o + n; +} + +static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = 1; + *o++ = val; + *opt = o; +} + +static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) { + uint8_t *o = *opt; + *o++ = cmd; + *o++ = 4; + *o++ = val >> 24; + *o++ = val >> 16; + *o++ = val >> 8; + *o++ = val; + *opt = o; +} + +static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { + dhcp_server_t *d = arg; + (void)upcb; + (void)src_addr; + (void)src_port; + + // This is around 548 bytes + dhcp_msg_t dhcp_msg; + + #define DHCP_MIN_SIZE (240 + 3) + if (p->tot_len < DHCP_MIN_SIZE) { + goto ignore_request; + } + + size_t len = pbuf_copy_partial(p, &dhcp_msg, sizeof(dhcp_msg), 0); + if (len < DHCP_MIN_SIZE) { + goto ignore_request; + } + + dhcp_msg.op = DHCPOFFER; + memcpy(&dhcp_msg.yiaddr, &ip4_addr_get_u32(ip_2_ip4(&d->ip)), 4); + + uint8_t *opt = (uint8_t *)&dhcp_msg.options; + opt += 4; // assume magic cookie: 99, 130, 83, 99 + + uint8_t *msgtype = opt_find(opt, DHCP_OPT_MSG_TYPE); + if (msgtype == NULL) { + // A DHCP package without MSG_TYPE? + goto ignore_request; + } + + switch (msgtype[2]) { + case DHCPDISCOVER: { + int yi = DHCPS_MAX_IP; + for (int i = 0; i < DHCPS_MAX_IP; ++i) { + if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { + // MAC match, use this IP address + yi = i; + break; + } + if (yi == DHCPS_MAX_IP) { + // Look for a free IP address + if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { + // IP available + yi = i; + } + uint32_t expiry = d->lease[i].expiry << 16 | 0xffff; + if ((int32_t)(expiry - cyw43_hal_ticks_ms()) < 0) { + // IP expired, reuse it + memset(d->lease[i].mac, 0, MAC_LEN); + yi = i; + } + } + } + if (yi == DHCPS_MAX_IP) { + // No more IP addresses left + goto ignore_request; + } + dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; + opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPOFFER); + break; + } + + case DHCPREQUEST: { + uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP); + if (o == NULL) { + // Should be NACK + goto ignore_request; + } + if (memcmp(o + 2, &ip4_addr_get_u32(ip_2_ip4(&d->ip)), 3) != 0) { + // Should be NACK + goto ignore_request; + } + uint8_t yi = o[5] - DHCPS_BASE_IP; + if (yi >= DHCPS_MAX_IP) { + // Should be NACK + goto ignore_request; + } + if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { + // MAC match, ok to use this IP address + } else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { + // IP unused, ok to use this IP address + memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN); + } else { + // IP already in use + // Should be NACK + goto ignore_request; + } + d->lease[yi].expiry = (cyw43_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16; + dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; + opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPACK); + printf("DHCPS: client connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x IP=%u.%u.%u.%u\n", + dhcp_msg.chaddr[0], dhcp_msg.chaddr[1], dhcp_msg.chaddr[2], dhcp_msg.chaddr[3], dhcp_msg.chaddr[4], dhcp_msg.chaddr[5], + dhcp_msg.yiaddr[0], dhcp_msg.yiaddr[1], dhcp_msg.yiaddr[2], dhcp_msg.yiaddr[3]); + break; + } + + default: + goto ignore_request; + } + + opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); + opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &ip4_addr_get_u32(ip_2_ip4(&d->nm))); + opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); // aka gateway; can have multiple addresses + opt_write_n(&opt, DHCP_OPT_DNS, 4, &ip4_addr_get_u32(ip_2_ip4(&d->ip))); // this server is the dns + opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); + *opt++ = DHCP_OPT_END; + struct netif *nif = ip_current_input_netif(); + dhcp_socket_sendto(&d->udp, nif, &dhcp_msg, opt - (uint8_t *)&dhcp_msg, 0xffffffff, PORT_DHCP_CLIENT); + +ignore_request: + pbuf_free(p); +} + +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { + ip_addr_copy(d->ip, *ip); + ip_addr_copy(d->nm, *nm); + memset(d->lease, 0, sizeof(d->lease)); + if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) { + return; + } + dhcp_socket_bind(&d->udp, PORT_DHCP_SERVER); +} + +void dhcp_server_deinit(dhcp_server_t *d) { + dhcp_socket_free(&d->udp); +} diff --git a/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h new file mode 100644 index 000000000..2349d2ea4 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dhcpserver/dhcpserver.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H +#define MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H + +#include "lwip/ip_addr.h" + +#define DHCPS_BASE_IP (16) +#define DHCPS_MAX_IP (8) + +typedef struct _dhcp_server_lease_t { + uint8_t mac[6]; + uint16_t expiry; +} dhcp_server_lease_t; + +typedef struct _dhcp_server_t { + ip_addr_t ip; + ip_addr_t nm; + dhcp_server_lease_t lease[DHCPS_MAX_IP]; + struct udp_pcb *udp; +} dhcp_server_t; + +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm); +void dhcp_server_deinit(dhcp_server_t *d); + +#endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H diff --git a/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c new file mode 100644 index 000000000..029870b56 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.c @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#include "dnsserver.h" +#include "lwip/udp.h" + +#define PORT_DNS_SERVER 53 +#define DUMP_DATA 0 + +#define DEBUG_printf(...) +#define ERROR_printf printf + +typedef struct dns_header_t_ { + uint16_t id; + uint16_t flags; + uint16_t question_count; + uint16_t answer_record_count; + uint16_t authority_record_count; + uint16_t additional_record_count; +} dns_header_t; + +#define MAX_DNS_MSG_SIZE 300 + +static int dns_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { + *udp = udp_new(); + if (*udp == NULL) { + return -ENOMEM; + } + udp_recv(*udp, cb_udp_recv, (void *)cb_data); + return ERR_OK; +} + +static void dns_socket_free(struct udp_pcb **udp) { + if (*udp != NULL) { + udp_remove(*udp); + *udp = NULL; + } +} + +static int dns_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) { + ip_addr_t addr; + IP4_ADDR(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + err_t err = udp_bind(*udp, &addr, port); + if (err != ERR_OK) { + ERROR_printf("dns failed to bind to port %u: %d", port, err); + assert(false); + } + return err; +} + +#if DUMP_DATA +static void dump_bytes(const uint8_t *bptr, uint32_t len) { + unsigned int i = 0; + + for (i = 0; i < len;) { + if ((i & 0x0f) == 0) { + printf("\n"); + } else if ((i & 0x07) == 0) { + printf(" "); + } + printf("%02x ", bptr[i++]); + } + printf("\n"); +} +#endif + +static int dns_socket_sendto(struct udp_pcb **udp, const void *buf, size_t len, const ip_addr_t *dest, uint16_t port) { + if (len > 0xffff) { + len = 0xffff; + } + + struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (p == NULL) { + ERROR_printf("DNS: Failed to send message out of memory\n"); + return -ENOMEM; + } + + memcpy(p->payload, buf, len); + err_t err = udp_sendto(*udp, p, dest, port); + + pbuf_free(p); + + if (err != ERR_OK) { + ERROR_printf("DNS: Failed to send message %d\n", err); + return err; + } + +#if DUMP_DATA + dump_bytes(buf, len); +#endif + return len; +} + +static void dns_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { + dns_server_t *d = arg; + DEBUG_printf("dns_server_process %u\n", p->tot_len); + + uint8_t dns_msg[MAX_DNS_MSG_SIZE]; + dns_header_t *dns_hdr = (dns_header_t*)dns_msg; + + size_t msg_len = pbuf_copy_partial(p, dns_msg, sizeof(dns_msg), 0); + if (msg_len < sizeof(dns_header_t)) { + goto ignore_request; + } + +#if DUMP_DATA + dump_bytes(dns_msg, msg_len); +#endif + + uint16_t flags = lwip_ntohs(dns_hdr->flags); + uint16_t question_count = lwip_ntohs(dns_hdr->question_count); + + DEBUG_printf("len %d\n", msg_len); + DEBUG_printf("dns flags 0x%x\n", flags); + DEBUG_printf("dns question count 0x%x\n", question_count); + + // flags from rfc1035 + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + // Check QR indicates a query + if (((flags >> 15) & 0x1) != 0) { + DEBUG_printf("Ignoring non-query\n"); + goto ignore_request; + } + + // Check for standard query + if (((flags >> 11) & 0xf) != 0) { + DEBUG_printf("Ignoring non-standard query\n"); + goto ignore_request; + } + + // Check question count + if (question_count < 1) { + DEBUG_printf("Invalid question count\n"); + goto ignore_request; + } + + // Print the question + DEBUG_printf("question: "); + const uint8_t *question_ptr_start = dns_msg + sizeof(dns_header_t); + const uint8_t *question_ptr_end = dns_msg + msg_len; + const uint8_t *question_ptr = question_ptr_start; + while(question_ptr < question_ptr_end) { + if (*question_ptr == 0) { + question_ptr++; + break; + } else { + if (question_ptr > question_ptr_start) { + DEBUG_printf("."); + } + int label_len = *question_ptr++; + if (label_len > 63) { + DEBUG_printf("Invalid label\n"); + goto ignore_request; + } + DEBUG_printf("%.*s", label_len, question_ptr); + question_ptr += label_len; + } + } + DEBUG_printf("\n"); + + // Check question length + if (question_ptr - question_ptr_start > 255) { + DEBUG_printf("Invalid question length\n"); + goto ignore_request; + } + + // Skip QNAME and QTYPE + question_ptr += 4; + + // Generate answer + uint8_t *answer_ptr = dns_msg + (question_ptr - dns_msg); + *answer_ptr++ = 0xc0; // pointer + *answer_ptr++ = question_ptr_start - dns_msg; // pointer to question + + *answer_ptr++ = 0; + *answer_ptr++ = 1; // host address + + *answer_ptr++ = 0; + *answer_ptr++ = 1; // Internet class + + *answer_ptr++ = 0; + *answer_ptr++ = 0; + *answer_ptr++ = 0; + *answer_ptr++ = 60; // ttl 60s + + *answer_ptr++ = 0; + *answer_ptr++ = 4; // length + memcpy(answer_ptr, &d->ip.addr, 4); // use our address + answer_ptr += 4; + + dns_hdr->flags = lwip_htons( + 0x1 << 15 | // QR = response + 0x1 << 10 | // AA = authoritative + 0x1 << 7); // RA = authenticated + dns_hdr->question_count = lwip_htons(1); + dns_hdr->answer_record_count = lwip_htons(1); + dns_hdr->authority_record_count = 0; + dns_hdr->additional_record_count = 0; + + // Send the reply + DEBUG_printf("Sending %d byte reply to %s:%d\n", answer_ptr - dns_msg, ipaddr_ntoa(src_addr), src_port); + dns_socket_sendto(&d->udp, &dns_msg, answer_ptr - dns_msg, src_addr, src_port); + +ignore_request: + pbuf_free(p); +} + +void dns_server_init(dns_server_t *d, ip_addr_t *ip) { + if (dns_socket_new_dgram(&d->udp, d, dns_server_process) != ERR_OK) { + DEBUG_printf("dns server failed to start\n"); + return; + } + if (dns_socket_bind(&d->udp, 0, PORT_DNS_SERVER) != ERR_OK) { + DEBUG_printf("dns server failed to bind\n"); + return; + } + ip_addr_copy(d->ip, *ip); + DEBUG_printf("dns server listening on port %d\n", PORT_DNS_SERVER); +} + +void dns_server_deinit(dns_server_t *d) { + dns_socket_free(&d->udp); +} diff --git a/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h new file mode 100644 index 000000000..d23534c03 --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/dnsserver/dnsserver.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _DNSSERVER_H_ +#define _DNSSERVER_H_ + +#include "lwip/ip_addr.h" + +typedef struct dns_server_t_ { + struct udp_pcb *udp; + ip_addr_t ip; +} dns_server_t; + +void dns_server_init(dns_server_t *d, ip_addr_t *ip); +void dns_server_deinit(dns_server_t *d); + +#endif diff --git a/pico_w/wifi/access_point_wifi_provisioning/lwipopts.h b/pico_w/wifi/access_point_wifi_provisioning/lwipopts.h new file mode 100644 index 000000000..98170b8de --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/lwipopts.h @@ -0,0 +1,101 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +// allow override in some examples +#ifndef NO_SYS +#define NO_SYS 1 +#endif +// allow override in some examples +#ifndef LWIP_SOCKET +#define LWIP_SOCKET 0 +#endif +#if PICO_CYW43_ARCH_POLL +#define MEM_LIBC_MALLOC 1 +#else +// MEM_LIBC_MALLOC is incompatible with non polling versions +#define MEM_LIBC_MALLOC 0 +#endif +#define MEM_ALIGNMENT 4 +#ifndef MEM_SIZE +#define MEM_SIZE 4000 +#endif +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +// 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 0 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 + +// Generated file containing html data +#define HTTPD_FSDATA_FILE "pico_fsdata.inc" + +#endif \ No newline at end of file From 6716136e81abe800223900fce2f4c9e45dfd1f55 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Thu, 11 Jun 2026 10:56:30 +0100 Subject: [PATCH 2/2] Some fixes Fix compile warning. Use the same password as the other access point example. Fix captive portal --- pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c | 3 +-- pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt | 2 +- pico_w/wifi/access_point_wifi_provisioning/content/404.html | 0 .../wifi/access_point_wifi_provisioning/content/404.response | 3 +++ 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 pico_w/wifi/access_point_wifi_provisioning/content/404.html create mode 100644 pico_w/wifi/access_point_wifi_provisioning/content/404.response diff --git a/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c b/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c index ec89a383a..158703c75 100644 --- a/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c +++ b/pico_w/wifi/access_point_wifi_provisioning/AP_provisioning.c @@ -18,7 +18,6 @@ #include "hardware/flash.h" // for saving succesful credentials static absolute_time_t wifi_connected_time; -static bool led_on = false; // max lengths + 1 static char ssid[33]; @@ -113,7 +112,7 @@ int main() { // If this fails, enable access point if (connection_status == false) { cyw43_arch_disable_sta_mode(); - cyw43_arch_enable_ap_mode("picow_test", "12345678", CYW43_AUTH_WPA2_AES_PSK); + cyw43_arch_enable_ap_mode("picow_test", "password", CYW43_AUTH_WPA2_AES_PSK); printf("\nReady, running server at %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list))); #if LWIP_IPV6 diff --git a/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt b/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt index c22c25a5d..cfb385bde 100644 --- a/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt +++ b/pico_w/wifi/access_point_wifi_provisioning/CMakeLists.txt @@ -21,5 +21,5 @@ pico_add_extra_outputs(AP_provisioning) pico_add_library(ap_provisioning_content NOFLAG) pico_set_lwip_httpd_content(ap_provisioning_content INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/content/index.shtml + ${CMAKE_CURRENT_LIST_DIR}/content/index.shtml ${CMAKE_CURRENT_LIST_DIR}/content/404.html ${CMAKE_CURRENT_LIST_DIR}/content/404.response ) \ No newline at end of file diff --git a/pico_w/wifi/access_point_wifi_provisioning/content/404.html b/pico_w/wifi/access_point_wifi_provisioning/content/404.html new file mode 100644 index 000000000..e69de29bb diff --git a/pico_w/wifi/access_point_wifi_provisioning/content/404.response b/pico_w/wifi/access_point_wifi_provisioning/content/404.response new file mode 100644 index 000000000..9f1133f2d --- /dev/null +++ b/pico_w/wifi/access_point_wifi_provisioning/content/404.response @@ -0,0 +1,3 @@ +HTTP/1.1 302 Redirect +Location: index.shtml +