From 9acc549734307b4163f024f39d16759a1cdf493f Mon Sep 17 00:00:00 2001 From: Jack Whitham Date: Sun, 27 Apr 2025 21:24:12 +0100 Subject: [PATCH 1/5] Add wifi_settings_connect library The library provides a way to store WiFi hotspot details in Flash and connect automatically, avoiding the need to specify build-time flags such as WIFI_SSID and WIFI_PASSWORD. --- README.md | 1 + src/rp2_common/CMakeLists.txt | 3 +- .../wifi_settings_connect/CMakeLists.txt | 40 ++ .../wifi_settings_connect/README.md | 22 + .../wifi_settings_configuration.h | 68 +++ .../wifi_settings/wifi_settings_connect.h | 73 +++ .../wifi_settings_connect_internal.h | 63 ++ .../wifi_settings/wifi_settings_flash_range.h | 97 +++ .../wifi_settings_flash_storage.h | 29 + .../wifi_settings/wifi_settings_hostname.h | 27 + .../wifi_settings_connect.c | 565 ++++++++++++++++++ .../wifi_settings_flash_range.c | 224 +++++++ .../wifi_settings_flash_storage.c | 128 ++++ .../wifi_settings_hostname.c | 46 ++ 14 files changed, 1385 insertions(+), 1 deletion(-) create mode 100644 src/rp2_common/wifi_settings_connect/CMakeLists.txt create mode 100644 src/rp2_common/wifi_settings_connect/README.md create mode 100644 src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h create mode 100644 src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h create mode 100644 src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h create mode 100644 src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h create mode 100644 src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h create mode 100644 src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_hostname.h create mode 100644 src/rp2_common/wifi_settings_connect/wifi_settings_connect.c create mode 100644 src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c create mode 100644 src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c create mode 100644 src/rp2_common/wifi_settings_connect/wifi_settings_hostname.c diff --git a/README.md b/README.md index 998a8f4..56826cc 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Library|Description [platypus](src/common/platypus)| Decoder for a custom image compression format suitable for dithered images (good for RGB555) and suitable for decoding on RP2040 at scanline speeds ... i.e you can easily decode a 320x240 image 60x per second to avoid storing the uncompressed image for scanout video. It gets about 50% compression (but is designed only for 4x4 fixed dithered RGB555 images, so is somewhat specific!). TODO add the encoder here :-) [usb_device](src/rp2_common/usb_device), [usb_common](src/rp2_common/usb_common)| The custom and somewhat minimal USB device stack used in the bootrom. We now use TinyUSB in the Pico SDK but kept here for posterity [usb_device_msc](src/rp2_common/usb_device_msc)| USB Mass Storage Class implementation using _usb_device_ +[wifi_settings_connect](src/rp2_common/wifi_settings_connect)| Library to manage WiFi connections. It provides Flash storage for WiFi passwords and hotspot names, and a background async\_context service to automatically connect to them. You can add Pico Extras to your project similarly to the SDK (copying [external/pico_extras_import.cmake](external/pico_extras_import.cmake) into your project) having set the `PICO_EXTRAS_PATH` variable in your environment or via cmake variable. diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index c780ab7..9a33cc2 100644 --- a/src/rp2_common/CMakeLists.txt +++ b/src/rp2_common/CMakeLists.txt @@ -10,4 +10,5 @@ pico_add_subdirectory(pico_sd_card) pico_add_subdirectory(pico_scanvideo_dpi) pico_add_subdirectory(usb_common) pico_add_subdirectory(usb_device) -pico_add_subdirectory(usb_device_msc) \ No newline at end of file +pico_add_subdirectory(usb_device_msc) +pico_add_subdirectory(wifi_settings_connect) diff --git a/src/rp2_common/wifi_settings_connect/CMakeLists.txt b/src/rp2_common/wifi_settings_connect/CMakeLists.txt new file mode 100644 index 0000000..b3eca52 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright (c) 2025 Jack Whitham +# +# SPDX-License-Identifier: BSD-3-Clause +# +# wifi_settings_connect +# +# Library to manage WiFi connections. It provides Flash storage +# for WiFi passwords and hotspot names, and a background async_context +# service to automatically connect to them. +# + +if (NOT PICO_CYW43_SUPPORTED) + message("wifi_settings_connect: WiFi hardware is required: run cmake -DPICO_BOARD=pico_w or -DPICO_BOARD=pico2_w") +elseif (NOT TARGET pico_cyw43_arch) + message("wifi_settings_connect: WiFi driver pico_cyw43_arch is not present") +elseif (NOT TARGET pico_lwip_core) + message("wifi_settings_connect: IP layer pico_lwip_core is not present") +else() + message("wifi_settings_connect: library is available.") + add_library(wifi_settings_connect INTERFACE) + + target_include_directories(wifi_settings_connect INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/include + ) + + target_sources(wifi_settings_connect INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/wifi_settings_connect.c + ${CMAKE_CURRENT_LIST_DIR}/wifi_settings_flash_storage.c + ${CMAKE_CURRENT_LIST_DIR}/wifi_settings_flash_range.c + ${CMAKE_CURRENT_LIST_DIR}/wifi_settings_hostname.c + ) + + target_link_libraries(wifi_settings_connect INTERFACE + pico_async_context_base + pico_stdlib + pico_cyw43_arch + pico_lwip_core + ) +endif() diff --git a/src/rp2_common/wifi_settings_connect/README.md b/src/rp2_common/wifi_settings_connect/README.md new file mode 100644 index 0000000..ba800d0 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/README.md @@ -0,0 +1,22 @@ +# wifi\_settings\_connect + +This is a library to manage WiFi connections. It provides Flash storage +for WiFi passwords and hotspot names, and a background async\_context +service to automatically connect to them. You can store details for +up to 16 hotspots and update them using `picotool` or a setup +application. This avoids any need to +specify build-time flags such as `WIFI_SSID` and `WIFI_PASSWORD`. + +The Flash storage location for hotspot details is specified in +`include/wifi_settings/wifi_settings_configuration.h`. It is at +`0x101ff000` (for Pico W) and `0x103fe000` (for Pico 2 W). To add your +WiFi details at this location, please [see these +instructions](https://github.com/jwhitham/pico-wifi-settings/blob/master/doc/SETTINGS_FILE.md). +You can edit the settings as a text file and transfer it with `picotool`, +or install a [setup application](https://github.com/jwhitham/pico-wifi-settings/blob/master/doc/SETUP_APP.md) +to add or update WiFi details. + +This wifi\_settings\_connect library is a subset of a larger +library, [wifi\_settings](https://github.com/jwhitham/pico-wifi-settings/), +which adds remote update functions for both the WiFi settings +and (optionally) your Pico application too. diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h new file mode 100644 index 0000000..068feb2 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This header file contains default values for timeouts, + * addresses and limits within the pico-wifi-settings library. + */ + +#ifndef WIFI_SETTINGS_CONFIGURATION_H +#define WIFI_SETTINGS_CONFIGURATION_H + + +#if defined(FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE) +// Flash address of wifi-settings file already defined +#elif PICO_RP2040 +// Flash address of wifi-settings file on Pico 1 W +#define FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE 0x001ff000 +// Note: Flash addresses have 0 = start of Flash. +// Note: The CPU's address for the wifi-settings file is 0x101ff000 on Pico 1. + +#elif PICO_RP2350 +// Flash address of wifi-settings file on Pico 2 W +#define FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE 0x003fe000 +// Note: Flash addresses have 0 = start of Flash. +// Note: avoid final sector due to RP2350-E10 bug +// Note: The CPU's address for the wifi-settings file is 0x103fe000 on Pico 2 +// assuming that there is no translation (e.g. partitioning). + +#else +#error "Unknown Pico model - please set FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE appropriately" +#endif + +// Size of wifi-settings file (bytes, must be a whole number of Flash sectors) +#ifndef WIFI_SETTINGS_FILE_SIZE +#define WIFI_SETTINGS_FILE_SIZE 0x1000 +#endif + +// Minimum time between initialisation and the first scan (milliseconds). +#ifndef INITIAL_SETUP_TIME_MS +#define INITIAL_SETUP_TIME_MS 1000 +#endif + +// Maximum time allowed between calling cyw43_wifi_join and getting an IP address (milliseconds). +// If this timeout expires, wifi_settings will try a different hotspot or rescan. The attempt to +// join a hotspot can fail sooner than this, e.g. if the password is incorrect or the hotspot vanishes. +#ifndef CONNECT_TIMEOUT_TIME_MS +#define CONNECT_TIMEOUT_TIME_MS 30000 +#endif + +// Minimum time between scans (milliseconds). If a scan fails to find any known hotspot, +// wifi_settings will always wait at least this long before retry. +#ifndef REPEAT_SCAN_TIME_MS +#define REPEAT_SCAN_TIME_MS 3000 +#endif + +// Minimum time between calls to the periodic function, wifi_settings_periodic_callback, +// which will initiate scans and connections if necessary (milliseconds). +#ifndef PERIODIC_TIME_MS +#define PERIODIC_TIME_MS 1000 +#endif + +// Maximum number of SSIDs that can be supported. +#ifndef NUM_SSIDS +#define NUM_SSIDS 16 +#endif + +#endif diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h new file mode 100644 index 0000000..e22b508 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This header file declares functions used to initialise and connect to WiFi + * with pico-wifi-settings. + * + */ + +#ifndef _WIFI_SETTINGS_CONNECT_H_ +#define _WIFI_SETTINGS_CONNECT_H_ + +#include + +// These settings are fixed by WPA-PSK standards +#define WIFI_SSID_SIZE 33 // including '\0' character +#define WIFI_BSSID_SIZE 6 // size of a MAC address +#define WIFI_PASSWORD_SIZE 65 // including '\0' character + +/// @brief Initialise wifi_settings module +/// @return 0 on success, or an error code from cyw43_arch_init +int wifi_settings_init(); + +/// @brief Deinitialise wifi_settings module +void wifi_settings_deinit(); + +/// @brief Connect to WiFi if possible, using the settings in Flash. +/// The actual connection may take some time to be established, and +/// may not be possible. Call wifi_settings_is_connected() to see if +/// the connection is ready. +void wifi_settings_connect(); + +/// @brief Disconnect from WiFi immediately. +void wifi_settings_disconnect(); + +/// @brief Determine if connection is ready. +/// @return true if ready +bool wifi_settings_is_connected(); + +/// @brief Determine if the WiFi settings are empty - if the +/// file is empty, wifi_settings will be unable to connect. See README.md +/// for instructions on how to provide settings. +/// @return true if empty (no known SSIDs or BSSIDs) +bool wifi_settings_has_no_wifi_details(); + +/// @brief Get a report on the current connection status +/// @param[inout] text Text buffer for the report +/// @param[in] text_size Available space in the buffer (bytes) +/// @return Return code from snprintf when formatting +int wifi_settings_get_connect_status_text(char* text, int text_size); + +/// @brief Get a report on the network hardware (cyw43) status (e.g. signal strength) +/// @param[inout] text Text buffer for the report +/// @param[in] text_size Available space in the buffer (bytes) +/// @return Return code from snprintf when formatting +int wifi_settings_get_hw_status_text(char* text, int text_size); + +/// @brief Get a report on the IP stack status (e.g. IP address) +/// @param[inout] text Text buffer for the report +/// @param[in] text_size Available space in the buffer (bytes) +/// @return Return code from snprintf when formatting +int wifi_settings_get_ip_status_text(char* text, int text_size); + +/// @brief Get the status of a connection attempt to +/// an SSID as a static string, e.g. SUCCESS, NOT_FOUND. "" is returned +/// if the SSID index is not known. +/// @param[in] ssid_index Index matching the ssid number. +/// @return Static string containing the SSID status. +const char* wifi_settings_get_ssid_status(int ssid_index); + + +#endif diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h new file mode 100644 index 0000000..ee14577 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This header file is intended to be internal and should not be included directly by applications. + * + */ + +#ifndef WIFI_SETTINGS_CONNECT_INTERNAL_H +#define WIFI_SETTINGS_CONNECT_INTERNAL_H + +#ifndef WIFI_SETTINGS_CONNECT_C +#error "This is an internal header intended only for use by wifi_settings_connect.c and its unit tests" +#else + +#include "wifi_settings_configuration.h" + +#include "pico/async_context.h" +#include "pico/stdlib.h" +#include "pico/time.h" +#include "pico/cyw43_arch.h" + +enum wifi_connect_state_t { + UNINITIALISED = 0, // cyw43 hardware was not started + INITIALISATION_ERROR, // initialisation failed (see hw_error_code) + STORAGE_EMPTY_ERROR, // no WiFi details are known + DISCONNECTED, // call wifi_settings_connect() to connect + TRY_TO_CONNECT, // connection process begun + SCANNING, // scan running + CONNECTING, // connection running + CONNECTED_IP, // connection is ready for use +}; + +enum ssid_scan_info_t { + NOT_FOUND = 0, // this SSID was not found + FOUND, // this SSID was found by the most recent scan + ATTEMPT, // we attempted to connect to this SSID + FAILED, // ... but it failed with an error + TIMEOUT, // ... but it failed with a timeout + BADAUTH, // ... but the password is wrong + SUCCESS, // ... and it worked + LOST, // we connected to this SSID but the connection dropped +}; + +#define IPV4_ADDRESS_SIZE 16 // "xxx.xxx.xxx.xxx\0" +#define KEY_SIZE 10 // e.g. "bssid0" + +struct wifi_state_t { + enum wifi_connect_state_t cstate; + enum ssid_scan_info_t ssid_scan_info[NUM_SSIDS + 1]; + struct netif* netif; + cyw43_t* cyw43; + uint selected_ssid_index; + int hw_error_code; + absolute_time_t connect_timeout_time; + absolute_time_t scan_holdoff_time; + async_context_t* context; + async_at_time_worker_t periodic_worker; +}; + +#endif +#endif diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h new file mode 100644 index 0000000..8bbc03f --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This header file declares functions used to check and translate address ranges. + * + */ + +#ifndef _WIFI_SETTINGS_FLASH_RANGE_H_ +#define _WIFI_SETTINGS_FLASH_RANGE_H_ + +#include "hardware/flash.h" + +#include +#include + +/// @brief Represents a range of Flash memory addresses +typedef struct wifi_settings_flash_range_t { + uint32_t start_address; + uint32_t size; +} wifi_settings_flash_range_t; + +/// @brief Represents a range of logical memory addresses +typedef struct wifi_settings_logical_range_t { + void* start_address; + uint32_t size; +} wifi_settings_logical_range_t; + +/// @brief Detect if a Flash memory range entirely fits within another +/// @param[in] inner Inner Flash memory range +/// @param[in] outer Outer Flash memory range +/// @return true if inner is entirely within outer +bool wifi_settings_range_is_contained( + const wifi_settings_flash_range_t* inner, + const wifi_settings_flash_range_t* outer); + +/// @brief Detect if a Flash memory range intersects with another +/// @param[in] fr1 First range +/// @param[in] fr2 Second range +/// @return true if fr1 and fr2 overlap by one or more bytes +bool wifi_settings_range_has_overlap( + const wifi_settings_flash_range_t* fr1, + const wifi_settings_flash_range_t* fr2); + +/// @brief Determine the range of addresses that are in Flash +/// @param[out] r Flash memory range +void wifi_settings_range_get_all( + wifi_settings_flash_range_t* r); + +/// @brief Determine the range of addresses that are reusable +/// (Reusable -> not occupied by the current program, not +/// occupied by the wifi-settings file, and within the current partition, if any.) +/// @param[out] r Flash memory range +void wifi_settings_range_get_reusable( + wifi_settings_flash_range_t* r); + +/// @brief Determine the range of addresses used by the wifi-settings file +/// @param[out] r Flash memory range +void wifi_settings_range_get_wifi_settings_file( + wifi_settings_flash_range_t* r); + +/// @brief Determine the range of addresses used by the current program +/// @param[out] r Flash memory range +void wifi_settings_range_get_program( + wifi_settings_flash_range_t* r); + +/// @brief Determine the range of addresses used by the current partition +/// @param[out] r Flash memory range +void wifi_settings_range_get_partition( + wifi_settings_flash_range_t* r); + +/// @brief Translate Flash range to logical range +/// @param[in] fr Flash memory range +/// @param[out] lr Logical memory range +void wifi_settings_range_translate_to_logical( + const wifi_settings_flash_range_t* fr, + wifi_settings_logical_range_t* lr); + +/// @brief Align Flash range to sector boundary and size: +/// no effect if they are already aligned +/// @param[inout] fr Flash memory range (possibly unaligned) +void wifi_settings_range_align_to_sector( + wifi_settings_flash_range_t* fr); + +/// @brief Translate logical range to Flash range if possible. +/// Not possible if the logical range is outside of an accessible area of Flash: +/// (1) ROM/RAM/other non-Flash addresses, (2) outside of the current partition, +/// (3) outside of both XIP_BASE and XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE regions. +/// @param[in] lr Logical memory range +/// @param[out] fr Flash memory range +/// @return true if translation was possible +bool wifi_settings_range_translate_to_flash( + const wifi_settings_logical_range_t* lr, + wifi_settings_flash_range_t* fr); + +#endif diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h new file mode 100644 index 0000000..3f31354 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This header file declares a function used to access the WiFi settings + * and other key/value data in Flash. + * + */ + +#ifndef _WIFI_SETTINGS_FLASH_STORAGE_H_ +#define _WIFI_SETTINGS_FLASH_STORAGE_H_ + +#include "pico/stdlib.h" +#include +#include + +/// @brief Scan the settings file in Flash for a particular key. +/// If found, copy up to *value_size characters to value. +/// Note: value will not be '\0' terminated. +/// @param[in] key Key to be found ('\0' terminated) +/// @param[out] value Value for key (if found) - not '\0' terminated +/// @param[inout] value_size Size of the value +/// @return true if key found +bool wifi_settings_get_value_for_key( + const char* key, + char* value, uint* value_size); + +#endif diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_hostname.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_hostname.h new file mode 100644 index 0000000..1531d92 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_hostname.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Hostname for wifi-settings. The hostname can be specified in + * the WiFi settings file as "name=". + */ + +#ifndef _WIFI_SETTINGS_HOSTNAME_H_ +#define _WIFI_SETTINGS_HOSTNAME_H_ + +#define MAX_HOSTNAME_SIZE 64 +#define BOARD_ID_SIZE 8 + +/// @brief Return a pointer to the hostname (which is a global variable) +/// @return pointer to the hostname +const char* wifi_settings_get_hostname(); + +/// @brief Return a pointer to the board ID in hex format (this is a global variable) +/// @return pointer to the board ID in hex format +const char* wifi_settings_get_board_id_hex(); + +/// @brief Load the hostname from the wifi-settings file +void wifi_settings_set_hostname(); + +#endif diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c b/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c new file mode 100644 index 0000000..41f8554 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c @@ -0,0 +1,565 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This pico-wifi-settings module manages the WiFi connection + * by calling cyw43 and LwIP functions. + * + */ + + +#define WIFI_SETTINGS_CONNECT_C +#include "wifi_settings/wifi_settings_configuration.h" +#include "wifi_settings/wifi_settings_connect.h" +#include "wifi_settings/wifi_settings_connect_internal.h" +#include "wifi_settings/wifi_settings_flash_storage.h" +#include "wifi_settings/wifi_settings_hostname.h" + +#ifdef ENABLE_REMOTE_UPDATE +#include "wifi_settings/wifi_settings_remote.h" +#endif + +#include "pico/error.h" + +#include +#include +#include + + +struct wifi_state_t g_wifi_state; + +enum ssid_type_t { + NONE = 0, + BSSID, + SSID, +}; + +static enum ssid_type_t fetch_ssid(uint ssid_index, char* ssid, uint8_t* bssid); + + +bool wifi_settings_has_no_wifi_details() { + char ssid[WIFI_SSID_SIZE]; + uint8_t bssid[WIFI_BSSID_SIZE]; + return fetch_ssid(1, ssid, bssid) == NONE; +} + +int wifi_settings_get_connect_status_text(char* text, int text_size) { + char ssid[WIFI_SSID_SIZE]; + uint8_t bssid[WIFI_BSSID_SIZE]; + enum ssid_type_t ssid_type; + + switch(g_wifi_state.cstate) { + case TRY_TO_CONNECT: + return snprintf(text, text_size, "WiFi did not find any known hotspot yet"); + case SCANNING: + return snprintf(text, text_size, "WiFi is scanning for hotspots"); + case CONNECTING: + ssid_type = fetch_ssid(g_wifi_state.selected_ssid_index, ssid, bssid); + return snprintf(text, text_size, + "WiFi is connecting to %sssid%u=%s", + (ssid_type == BSSID) ? "b" : "", + g_wifi_state.selected_ssid_index, + ssid); + case CONNECTED_IP: + ssid_type = fetch_ssid(g_wifi_state.selected_ssid_index, ssid, bssid); + return snprintf(text, text_size, + "WiFi is connected to %sssid%u=%s", + (ssid_type == BSSID) ? "b" : "", + g_wifi_state.selected_ssid_index, + ssid); + case DISCONNECTED: + return snprintf(text, text_size, "WiFi is disconnected"); + case UNINITIALISED: + return snprintf(text, text_size, "WiFi uninitialised"); + case INITIALISATION_ERROR: + return snprintf(text, text_size, "WiFi init error: %d", + g_wifi_state.hw_error_code); + case STORAGE_EMPTY_ERROR: + return snprintf(text, text_size, + "No WiFi details have been stored - unable to connect"); + default: + break; + } + return snprintf(text, text_size, "WiFi status is unknown (%d)", (int) g_wifi_state.cstate); +} + +int wifi_settings_get_hw_status_text(char* text, int text_size) { + if (!g_wifi_state.cyw43) { + text[0] = '\0'; + return 0; + } + + const char* hw_status_text = "?"; + switch (cyw43_wifi_link_status(g_wifi_state.cyw43, CYW43_ITF_STA)) { + case CYW43_LINK_DOWN: hw_status_text = "DOWN"; break; + case CYW43_LINK_JOIN: hw_status_text = "JOIN"; break; + case CYW43_LINK_NOIP: hw_status_text = "NOIP"; break; + case CYW43_LINK_UP: hw_status_text = "UP"; break; + case CYW43_LINK_FAIL: hw_status_text = "FAIL"; break; + case CYW43_LINK_NONET: hw_status_text = "NONET"; break; + case CYW43_LINK_BADAUTH: hw_status_text = "BADAUTH"; break; + default: break; + } + int32_t rssi = -1; + cyw43_wifi_get_rssi(g_wifi_state.cyw43, &rssi); + return snprintf(text, text_size, + "cyw43_wifi_link_status = CYW43_LINK_%s scan_active = %s rssi = %d", + hw_status_text, + cyw43_wifi_scan_active(g_wifi_state.cyw43) ? "True" : "False", + (int) rssi); +} + +int wifi_settings_get_ip_status_text(char* text, int text_size) { + if ((!g_wifi_state.netif) + || (!netif_is_link_up(g_wifi_state.netif))) { + text[0] = '\0'; + return 0; + } + char addr_buf1[IPV4_ADDRESS_SIZE]; + char addr_buf2[IPV4_ADDRESS_SIZE]; + char addr_buf3[IPV4_ADDRESS_SIZE]; + return snprintf(text, text_size, + "IPv4 address = %s netmask = %s gateway = %s", + ip4addr_ntoa_r(netif_ip4_addr(g_wifi_state.netif), addr_buf1, IPV4_ADDRESS_SIZE), + ip4addr_ntoa_r(netif_ip4_netmask(g_wifi_state.netif), addr_buf2, IPV4_ADDRESS_SIZE), + ip4addr_ntoa_r(netif_ip4_gw(g_wifi_state.netif), addr_buf3, IPV4_ADDRESS_SIZE)); +} + +const char* wifi_settings_get_ssid_status(int ssid_index) { + if ((ssid_index >= 1) && (ssid_index <= NUM_SSIDS)) { + switch (g_wifi_state.ssid_scan_info[ssid_index]) { + case NOT_FOUND: return "NOT FOUND"; break; + case FOUND: return "FOUND"; break; + case ATTEMPT: return "ATTEMPT"; break; + case SUCCESS: return "SUCCESS"; break; + case FAILED: return "FAILED"; break; + case TIMEOUT: return "TIMEOUT"; break; + case BADAUTH: return "BADAUTH"; break; + case LOST: return "LOST"; break; + default: break; + } + } + return ""; +} + +static bool wifi_is_connected() { + if (g_wifi_state.netif) { + return netif_is_link_up(g_wifi_state.netif); + } else { + return false; + } +} + +static bool convert_string_to_bssid(const char* text, uint text_size, uint8_t* bssid) { + // A BSSID is specified in the file as bssid1=01:23:45:67:89:ab + // note 1 - ':' separators + // note 2 - exactly 17 bytes + memset(bssid, 0, WIFI_BSSID_SIZE); + if (text_size != ((WIFI_BSSID_SIZE * 3) - 1)) { + // Malformed BSSID - not exactly 17 bytes + return false; + } + for (uint i = 0; i < (WIFI_BSSID_SIZE - 1); i++) { + if (text[(i * 3) + 2] != ':') { + // Malformed BSSID - not ':' separator + return false; + } + } + for (uint i = 0; i < WIFI_BSSID_SIZE; i++) { + char copy[3]; + char* check = NULL; + copy[0] = text[(i * 3) + 0]; + copy[1] = text[(i * 3) + 1]; + copy[2] = '\0'; + bssid[i] = (uint8_t) strtol(copy, &check, 16); + if (check != ©[2]) { + // Malformed BSSID - not a hex number + return false; + } + } + // valid BSSID + return true; +} + +static enum ssid_type_t fetch_ssid(uint ssid_index, char* ssid, uint8_t* bssid) { + // Generate search key + char key[KEY_SIZE]; + snprintf(key, sizeof(key), "bssid%u", ssid_index); + + uint ssid_size = WIFI_SSID_SIZE; + memset(bssid, 0, WIFI_BSSID_SIZE); + + // A BSSID is specified in the file as bssid1=01:23:45:67:89:ab + if (wifi_settings_get_value_for_key(key, ssid, &ssid_size)) { + ssid[ssid_size] = '\0'; + if (convert_string_to_bssid(ssid, ssid_size, bssid)) { + return BSSID; + } + } + + // An SSID is specified in the file as ssid1=MyHotspotName + // and must match exactly; SSIDs cannot contain characters recognised + // as end of line or end of file (\r \n \xff \x00 \x1a) + if (wifi_settings_get_value_for_key(&key[1], ssid, &ssid_size)) { + ssid[ssid_size] = '\0'; + return SSID; + } + // Undefined SSID and BSSID + ssid[0] = '?'; + ssid[1] = '\0'; + return NONE; +} + +static int wifi_scan_callback(void* unused, const cyw43_ev_scan_result_t* scan_result) { + // Is this SSID known? Iterate through the file to see if there is a record of it. + for (uint ssid_index = 1; ssid_index <= NUM_SSIDS; ssid_index++) { + // Skip SSIDs that we already saw + if (g_wifi_state.ssid_scan_info[ssid_index] != NOT_FOUND) { + continue; + } + + // Check file entries for bssid and ssid + char ssid[WIFI_SSID_SIZE]; + uint8_t bssid[WIFI_BSSID_SIZE]; + enum ssid_type_t ssid_type = fetch_ssid(ssid_index, ssid, bssid); + switch (ssid_type) { + case BSSID: + if (memcmp(bssid, scan_result->bssid, WIFI_BSSID_SIZE) == 0) { + // BSSID match + g_wifi_state.ssid_scan_info[ssid_index] = FOUND; + } + break; + case SSID: + if ((strlen(ssid) == (uint) scan_result->ssid_len) + && (memcmp(scan_result->ssid, ssid, (uint) scan_result->ssid_len) == 0)) { + // SSID match + g_wifi_state.ssid_scan_info[ssid_index] = FOUND; + } + break; + case NONE: + // ssid doesn't exist, so ssid, ssid etc. won't be checked + return 0; + } + } + // No more entries to try + return 0; +} + +static void ensure_disconnected() { + cyw43_wifi_leave(g_wifi_state.cyw43, CYW43_ITF_STA); + g_wifi_state.netif = NULL; +} + +static void begin_connecting() { + // This function is called after a scan, to begin connecting to a new hotspot. + // It looks at the results of the scan and previous connections, via ssid_scan_info. + ensure_disconnected(); + + // Which hotspot to connect to? + g_wifi_state.selected_ssid_index = 0; + for (uint ssid_index = 1; ssid_index <= NUM_SSIDS; ssid_index++) { + if (g_wifi_state.ssid_scan_info[ssid_index] == FOUND) { + g_wifi_state.selected_ssid_index = ssid_index; + break; + } + } + + if (g_wifi_state.selected_ssid_index == 0) { + // There are no available hotspots to connect to, either because the scan + // didn't find anything, or everything is FAILED, TIMEOUT, BADAUTH or LOST. + // In this case we should scan again. + g_wifi_state.cstate = TRY_TO_CONNECT; + return; + } + + // Begin connecting + g_wifi_state.ssid_scan_info[g_wifi_state.selected_ssid_index] = ATTEMPT; + g_wifi_state.connect_timeout_time = make_timeout_time_ms(CONNECT_TIMEOUT_TIME_MS); + g_wifi_state.cstate = CONNECTING; + + // Get the password + char key[KEY_SIZE]; + snprintf(key, sizeof(key), "pass%u", g_wifi_state.selected_ssid_index); + char password[WIFI_PASSWORD_SIZE]; + uint password_size = sizeof(password) - 1; + uint32_t auth_type = CYW43_AUTH_WPA2_AES_PSK; + if (!wifi_settings_get_value_for_key(key, password, &password_size)) { + // No password specified (open WiFi) + password_size = 0; + auth_type = CYW43_AUTH_OPEN; + } + password[password_size] = '\0'; + + // Get the BSSID or SSID + char ssid[WIFI_SSID_SIZE]; + uint8_t bssid[WIFI_BSSID_SIZE]; + enum ssid_type_t ssid_type = fetch_ssid(g_wifi_state.selected_ssid_index, ssid, bssid); + if (ssid_type == NONE) { + // No valid SSID or BSSID - this could happen if the storage was updated + // between scanning and connecting. Force a rescan + g_wifi_state.selected_ssid_index = 0; + g_wifi_state.cstate = TRY_TO_CONNECT; + return; + } + + // Begin connection + if (ssid_type == BSSID) { + g_wifi_state.hw_error_code = cyw43_wifi_join(g_wifi_state.cyw43, + 0, // size_t ssid_len + NULL, // const uint8_t *ssid + password_size, // size_t key_len + (const uint8_t *) password, // const uint8_t *key + auth_type, // uint32_t auth_type + bssid, // const uint8_t *bssid + CYW43_CHANNEL_NONE); // uint32_t channel + } else { + g_wifi_state.hw_error_code = cyw43_wifi_join(g_wifi_state.cyw43, + strlen(ssid), // size_t ssid_len + (const uint8_t *) ssid, // const uint8_t *ssid + password_size, // size_t key_len + (const uint8_t *) password, // const uint8_t *key + auth_type, // uint32_t auth_type + NULL, // const uint8_t *bssid + CYW43_CHANNEL_NONE); // uint32_t channel + } +} + +static void give_up_connecting(enum ssid_scan_info_t info) { + // Mark the selected SSID as bad in some way (e.g. BADAUTH, TIMEOUT) + // so that it won't be tried again. Go back to the SCANNING state. + g_wifi_state.ssid_scan_info[g_wifi_state.selected_ssid_index] = info; + g_wifi_state.cstate = SCANNING; +} + +static bool has_valid_address() { + char address_buf[IPV4_ADDRESS_SIZE]; + if (g_wifi_state.netif) { + const char* address = ip4addr_ntoa_r(netif_ip4_addr(g_wifi_state.netif), + address_buf, IPV4_ADDRESS_SIZE); + if ((address[0] != '\0') && (strcmp("0.0.0.0", address) != 0)) { + return true; + } + } + return false; +} + +static void begin_new_scan() { + // Begin a scan. We will reset everything we know about hotspots first. + for (uint ssid_index = 1; ssid_index <= NUM_SSIDS; ssid_index++) { + g_wifi_state.ssid_scan_info[ssid_index] = NOT_FOUND; + } + // Start the scan + cyw43_wifi_scan_options_t opts; + memset(&opts, 0, sizeof(opts)); + g_wifi_state.hw_error_code = cyw43_wifi_scan(g_wifi_state.cyw43, &opts, NULL, wifi_scan_callback); + g_wifi_state.cstate = SCANNING; + g_wifi_state.scan_holdoff_time = make_timeout_time_ms(REPEAT_SCAN_TIME_MS); +} + +static void wifi_settings_periodic_callback(async_context_t* unused1, async_at_time_worker_t* unused2) { + switch (g_wifi_state.cstate) { + case TRY_TO_CONNECT: + // In this state, we are not connected, and we are waiting for a holdoff time + // before beginning a scan for available hotspots. If a scan is already running + // (e.g. due to disconnecting during a scan) we wait for it to finish. + ensure_disconnected(); + if (wifi_settings_has_no_wifi_details()) { + // This is reached if the storage file contains no SSIDs. + g_wifi_state.cstate = STORAGE_EMPTY_ERROR; + } else if (time_reached(g_wifi_state.scan_holdoff_time) && !cyw43_wifi_scan_active(g_wifi_state.cyw43)) { + begin_new_scan(); + } + break; + case SCANNING: + // In this state, we are waiting for a hotspot scan to complete. + // If it already completed, and we have some results, we can go directly to CONNECTING. + if (!cyw43_wifi_scan_active(g_wifi_state.cyw43)) { + begin_connecting(); + } + break; + case CONNECTING: + // In this state, we are joining a WiFi hotspot, having found at least one + // possibility during the scan. + switch (cyw43_wifi_link_status(g_wifi_state.cyw43, CYW43_ITF_STA)) { + case CYW43_LINK_DOWN: + case CYW43_LINK_FAIL: + case CYW43_LINK_NONET: + // Connection failed - this hotspot must have disappeared + give_up_connecting(FAILED); + break; + case CYW43_LINK_BADAUTH: + // Connection failed because the password is incorrect + give_up_connecting(BADAUTH); + break; + case CYW43_LINK_JOIN: + case CYW43_LINK_NOIP: + case CYW43_LINK_UP: + // Connection still in progress or completed + g_wifi_state.netif = netif_default; + if (wifi_is_connected() && has_valid_address()) { + // Successful + g_wifi_state.ssid_scan_info[g_wifi_state.selected_ssid_index] = SUCCESS; + g_wifi_state.cstate = CONNECTED_IP; + } else if (time_reached(g_wifi_state.connect_timeout_time)) { + // Connection failed with a timeout + give_up_connecting(TIMEOUT); + } + break; + default: + // Fallback -> connection failure + give_up_connecting(FAILED); + break; + } + break; + case CONNECTED_IP: + // In this state we should be connected, but the connection could drop at any time + if (!wifi_is_connected() || !has_valid_address()) { + // Connection lost + give_up_connecting(LOST); + // It may be some time since the last scan, so scan again + g_wifi_state.cstate = TRY_TO_CONNECT; + } + break; + case STORAGE_EMPTY_ERROR: + // This state is reached if the storage file contains no SSIDs. + // Wait for the file to be updated. + if (!wifi_settings_has_no_wifi_details()) { + g_wifi_state.cstate = TRY_TO_CONNECT; + } + break; + case INITIALISATION_ERROR: + case UNINITIALISED: + case DISCONNECTED: + // nothing to do + break; + default: + break; + } + // trigger again after the period + g_wifi_state.periodic_worker.next_time = + delayed_by_ms(g_wifi_state.periodic_worker.next_time, + PERIODIC_TIME_MS); + async_context_add_at_time_worker( + g_wifi_state.context, + &g_wifi_state.periodic_worker); +} + +int wifi_settings_init() { + if (g_wifi_state.cstate != UNINITIALISED) { + return PICO_ERROR_INVALID_STATE; + } + + // Start with globals in known state + memset(&g_wifi_state, 0, sizeof(g_wifi_state)); + g_wifi_state.cstate = UNINITIALISED; + g_wifi_state.cyw43 = &cyw43_state; // from Pico SDK, lib/cyw43-driver (MAC layer) + + // Which country should be used? + // You can put "country=" in the WiFi settings file to set a different value. + // The code is a two-byte ISO-3166-1 country code + // such as AU (Australia), SE (Sweden) or GB (United Kingdom). + char value[2]; + uint value_size = sizeof(value); + uint32_t country = PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE; // worldwide default + + if ((wifi_settings_get_value_for_key("country", value, &value_size)) + && (value_size == 2)) { + country = CYW43_COUNTRY(value[0], value[1], 0); + } + + // Set the hostname from wifi-settings "name=" or use unique board id + wifi_settings_set_hostname(); + + // Hardware init + g_wifi_state.hw_error_code = cyw43_arch_init_with_country(country); + if (g_wifi_state.hw_error_code) { + g_wifi_state.cstate = INITIALISATION_ERROR; + return g_wifi_state.hw_error_code; + } + + // After initialisation, any call to LWIP requires this lock (callback functions + // are always holding it already, but this function is not a callback) + cyw43_arch_lwip_begin(); + + // Set up to connect to an access point + cyw43_arch_enable_sta_mode(); + + // State initialised + g_wifi_state.connect_timeout_time = make_timeout_time_ms(CONNECT_TIMEOUT_TIME_MS); + g_wifi_state.scan_holdoff_time = make_timeout_time_ms(INITIAL_SETUP_TIME_MS); + g_wifi_state.cstate = DISCONNECTED; + + // Use cyw43 async context + g_wifi_state.context = cyw43_arch_async_context(); + + // Start periodic worker + g_wifi_state.periodic_worker.next_time = g_wifi_state.scan_holdoff_time; + g_wifi_state.periodic_worker.do_work = wifi_settings_periodic_callback; + async_context_add_at_time_worker( + g_wifi_state.context, + &g_wifi_state.periodic_worker); + +#ifdef ENABLE_REMOTE_UPDATE + // Start remote access service + g_wifi_state.hw_error_code = wifi_settings_remote_init(); +#endif + // set lwip hostname (overriding the default set by cyw43_cb_tcpip_init) + netif_set_hostname(netif_default, wifi_settings_get_hostname()); + + // Ready to run LWIP functions + cyw43_arch_lwip_end(); + + return g_wifi_state.hw_error_code; +} + +void wifi_settings_deinit() { + if (g_wifi_state.cstate == UNINITIALISED) { + return; + } + ensure_disconnected(); + if (g_wifi_state.context) { + // stop periodic task + async_context_remove_at_time_worker( + g_wifi_state.context, + &g_wifi_state.periodic_worker); + } + g_wifi_state.context = NULL; + cyw43_arch_deinit(); + g_wifi_state.cstate = UNINITIALISED; + g_wifi_state.selected_ssid_index = 0; +} + +void wifi_settings_connect() { + if (g_wifi_state.cstate == DISCONNECTED) { + // Try to connect when periodic worker is next called + cyw43_arch_lwip_begin(); + if (g_wifi_state.cstate == DISCONNECTED) { + g_wifi_state.cstate = TRY_TO_CONNECT; + } + cyw43_arch_lwip_end(); + } +} + +void wifi_settings_disconnect() { + // Immediate disconnect + if ((g_wifi_state.cstate != UNINITIALISED) + && (g_wifi_state.cstate != INITIALISATION_ERROR)) { + cyw43_arch_lwip_begin(); + ensure_disconnected(); + g_wifi_state.cstate = DISCONNECTED; + g_wifi_state.selected_ssid_index = 0; + cyw43_arch_lwip_end(); + } +} + +bool wifi_settings_is_connected() { + bool rc = false; + if (g_wifi_state.cstate == CONNECTED_IP) { + // wifi_is_connected calls LWIP functions, so the lock is needed + cyw43_arch_lwip_begin(); + rc = wifi_is_connected(); + cyw43_arch_lwip_end(); + } + return rc; +} diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c new file mode 100644 index 0000000..c3c557a --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This file defines functions used to check and translate address ranges. + * + */ + +#include "wifi_settings/wifi_settings_configuration.h" +#include "wifi_settings/wifi_settings_flash_range.h" + +#include "hardware/regs/addressmap.h" +#ifdef XIP_QMI_BASE +#include "hardware/structs/qmi.h" +#endif + +// Calculate end address for a range +static uint32_t get_end_address(const wifi_settings_flash_range_t* r) { + return r->start_address + r->size; +} + +// A valid range contains at least 1 byte, and the end address is greater than +// the start address. (So, a valid range never wraps over UINT_MAX.) +static uint32_t is_valid(const wifi_settings_flash_range_t* r) { + return get_end_address(r) > r->start_address; +} + +// Determine if inner is within outer (or exactly the same as outer) +bool wifi_settings_range_is_contained( + const wifi_settings_flash_range_t* inner, + const wifi_settings_flash_range_t* outer) { + + if ((!is_valid(inner)) || (!is_valid(outer))) { + // At least one of the ranges is not valid (must contain at least 1 byte + // and not wrap over UINT_MAX.) + return false; + } + if (inner->start_address < outer->start_address) { + // inner starts before outer + return false; + } + if (get_end_address(inner) > get_end_address(outer)) { + // inner ends after outer + return false; + } + // inner is no larger than outer + return true; +} + +// Detect if a Flash memory range intersects with another +bool wifi_settings_range_has_overlap( + const wifi_settings_flash_range_t* fr1, + const wifi_settings_flash_range_t* fr2) +{ + if ((!is_valid(fr1)) || (!is_valid(fr2))) { + // Invalid ranges are considered non-overlapping + return false; + } + if (fr1->start_address >= get_end_address(fr2)) { + return false; // gap: fr1 is after fr2 + } + if (fr2->start_address >= get_end_address(fr1)) { + return false; // gap: fr2 is after fr1 + } + // no gap: fr1 and fr2 must overlap + return true; +} + +// Determine the range of addresses that are in Flash +void wifi_settings_range_get_all(wifi_settings_flash_range_t* r) { + r->start_address = 0; + r->size = PICO_FLASH_SIZE_BYTES; +} + + +extern char __flash_binary_end; + +// Determine the range of Flash addresses used by the current program +void wifi_settings_range_get_program(wifi_settings_flash_range_t* r) { + // Initial setup: assume the program is not in Flash + r->start_address = 0; + r->size = 0; + + // If the program is in Flash, determine the size + const uintptr_t end_of_program = ((uintptr_t) &__flash_binary_end); + if ((end_of_program > XIP_BASE) + && (end_of_program <= (XIP_BASE + PICO_FLASH_SIZE_BYTES))) { + // get start of partition + wifi_settings_range_get_partition(r); + // get program size + r->size = end_of_program - XIP_BASE; + } +} + +// Determine the range of Flash addresses for the current partition +void wifi_settings_range_get_partition(wifi_settings_flash_range_t* r) { +#ifdef XIP_QMI_BASE + // Partition base and size is in the ATRANS0 register + const uint32_t atrans0 = *((io_ro_32*)(XIP_QMI_BASE + QMI_ATRANS0_OFFSET)); + const uint32_t base = atrans0 & 0xfff; + const uint32_t size = (atrans0 >> 16) & 0x7ff; + r->start_address = base * FLASH_SECTOR_SIZE; + r->size = size * FLASH_SECTOR_SIZE; +#else + // Partition is the whole of Flash + r->start_address = 0; + r->size = PICO_FLASH_SIZE_BYTES; +#endif +} + +// Determine the range of addresses used by the wifi-settings file +void wifi_settings_range_get_wifi_settings_file(wifi_settings_flash_range_t* r) { + r->start_address = FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE; + r->size = WIFI_SETTINGS_FILE_SIZE; + wifi_settings_range_align_to_sector(r); +} + +// Determine the range of addresses that are reusable +// They are between the end of the program and either the start of the +// wifi-settings file, or the end of the partition, whichever comes first +void wifi_settings_range_get_reusable(wifi_settings_flash_range_t* r) { + wifi_settings_flash_range_t program_range; + wifi_settings_flash_range_t partition_range; + wifi_settings_flash_range_t wifi_settings_file_range; + + wifi_settings_range_get_program(&program_range); + wifi_settings_range_get_partition(&partition_range); + wifi_settings_range_get_wifi_settings_file(&wifi_settings_file_range); + + // round up the program size + wifi_settings_range_align_to_sector(&program_range); + + const uint32_t end_of_partition = get_end_address(&partition_range); + const uint32_t start_of_settings_file = wifi_settings_file_range.start_address; + const uint32_t end_of_reusable_space = + (end_of_partition < start_of_settings_file) ? end_of_partition : start_of_settings_file; + + r->start_address = get_end_address(&program_range); + if (end_of_reusable_space <= r->start_address) { + // There is no reusable space + r->start_address = 0; + r->size = 0; + } else { + r->size = end_of_reusable_space - r->start_address; + } +} + +// Translate Flash range to logical range +void wifi_settings_range_translate_to_logical( + const wifi_settings_flash_range_t* fr, + wifi_settings_logical_range_t* lr) { + +#ifdef XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE + // if the Flash memory might use address translation, use a non-translated address (used for Pico 2) + lr->start_address = (void*) ((uintptr_t) (fr->start_address + XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE)); +#else + // XIP_BASE represents flash address 0 + lr->start_address = (void*) ((uintptr_t) (fr->start_address + XIP_BASE)); +#endif + lr->size = fr->size; +} + +void wifi_settings_range_align_to_sector(wifi_settings_flash_range_t* fr) { + // Start address is rounded down (no effect if already aligned) + fr->start_address &= ~(FLASH_SECTOR_SIZE - 1); + // Size is rounded up (no effect if already aligned) + if ((fr->size & (FLASH_SECTOR_SIZE - 1)) != 0) { + fr->size |= FLASH_SECTOR_SIZE - 1; + fr->size++; + } +} + +// Translate logical range to Flash range, if possible +bool wifi_settings_range_translate_to_flash( + const wifi_settings_logical_range_t* lr, + wifi_settings_flash_range_t* fr) { + + const uintptr_t start_address = (uintptr_t) lr->start_address; + const uintptr_t end_address = start_address + lr->size; + + fr->start_address = 0; + fr->size = lr->size; + + if (end_address < start_address) { + return false; // Range is too large (overflow) + } + + if ((start_address >= XIP_BASE) +#ifdef XIP_NOALLOC_BASE + && (end_address < XIP_NOALLOC_BASE) // Pico 1: end of main region of Flash +#endif +#ifdef XIP_END + && (end_address < XIP_END) // Pico 2: end of main region of Flash +#endif + && (end_address < SRAM_BASE)) { // Fallback: end of all Flash addresses + + // Address is within the part of Flash that can use address translation; + // apply address translation based on the partition size if relevant. + wifi_settings_flash_range_t pr; + wifi_settings_range_get_partition(&pr); + fr->start_address = start_address + pr.start_address - XIP_BASE; + // If the resulting range is still contained in the partition, then the + // translation was successful + return wifi_settings_range_is_contained(fr, &pr); + } + +#ifdef XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE + if ((start_address >= XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE) + && (end_address < SRAM_BASE)) { + // Pico 2: Address is within the untranslated region of Flash + wifi_settings_flash_range_t ar; + wifi_settings_range_get_all(&ar); + fr->start_address = start_address - XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE; + // If the resulting range is still contained in Flash, then the + // translation was successful + return wifi_settings_range_is_contained(fr, &ar); + } +#endif + + // Not in Flash, or not in an area of Flash that can be supported + return false; +} + diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c new file mode 100644 index 0000000..a683504 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This pico-wifi-settings module reads WiFi settings + * information in Flash. + * + */ + +#include "wifi_settings/wifi_settings_flash_storage.h" +#include "wifi_settings/wifi_settings_flash_range.h" + +#include + + +bool wifi_settings_get_value_for_key( + const char* key, char* value, uint* value_size) { + + wifi_settings_flash_range_t fr; + wifi_settings_logical_range_t lr; + + wifi_settings_range_get_wifi_settings_file(&fr); + wifi_settings_range_translate_to_logical(&fr, &lr); + + const char* file = (const char*) lr.start_address; + const uint file_size = lr.size; + + enum parse_state_t { + NEW_LINE, + KEY, + SEPARATOR, + VALUE, + WAIT_FOR_NEW_LINE, + } parse_state = NEW_LINE; + uint value_index = 0; + uint key_index = 0; + + if (key[0] == '\0') { + // Invalid key - must contain at least 1 character + return false; + } + + for (uint file_index = 0; + (file_index < file_size) + && (file[file_index] != '\0') + && (file[file_index] != '\x1a') // CPM EOF character + && (file[file_index] != '\xff'); // Flash padding character + file_index++) { + + if ((file[file_index] == '\n') || (file[file_index] == '\r')) { + // End of line reached (Unix or DOS line endings) + if (parse_state == VALUE) { + // This is the end of the value + *value_size = value_index; + return true; + } else { + // Reset the parsing state + parse_state = NEW_LINE; + continue; + } + } + + switch (parse_state) { + case NEW_LINE: + // At the beginning of a new line - ignore whitespace before the key + key_index = 0; + if (key[key_index] == file[file_index]) { + // Matched the first character in the key + key_index++; + if (key[key_index] == '\0') { + // There is only one character in the key + parse_state = SEPARATOR; + } else { + // Match the other characters in the key + parse_state = KEY; + } + } else { + // Non-matching character: a different key, + // a comment - wait for the next newline + parse_state = WAIT_FOR_NEW_LINE; + } + break; + case KEY: + if (key[key_index] == file[file_index]) { + // Still matching the key + key_index++; + if (key[key_index] == '\0') { + // There are no more characters in the key + parse_state = SEPARATOR; + } + } else { + // Non-matching character in the key + parse_state = WAIT_FOR_NEW_LINE; + } + break; + case SEPARATOR: + if (file[file_index] == '=') { + // Key is recognised - copy the value + parse_state = VALUE; + } else { + // Key is not immediately followed by '=': not valid + parse_state = WAIT_FOR_NEW_LINE; + } + break; + case VALUE: + if (value_index >= *value_size) { + // Unable to copy more value characters - value is complete + return true; + } else { + value[value_index] = file[file_index]; + value_index++; + } + break; + case WAIT_FOR_NEW_LINE: + // Do nothing in this state, as the state will be reset when + // the next newline is seen + break; + } + } + if (parse_state == VALUE) { + // Reached end of file while parsing the value - value is complete + *value_size = value_index; + return true; + } + // Key was not found + return false; +} diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_hostname.c b/src/rp2_common/wifi_settings_connect/wifi_settings_hostname.c new file mode 100644 index 0000000..e0b559a --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_hostname.c @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2025 Jack Whitham + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Hostname for wifi-settings. The hostname can be specified in + * the WiFi settings file as "name=". + */ + +#include "wifi_settings/wifi_settings_hostname.h" +#include "wifi_settings/wifi_settings_flash_storage.h" + +#include "pico/unique_id.h" +#include +#include + +static char g_hostname[MAX_HOSTNAME_SIZE]; +static char g_board_id_hex[(BOARD_ID_SIZE * 2) + 1]; + + +const char* wifi_settings_get_hostname() { + return g_hostname; +} + +const char* wifi_settings_get_board_id_hex() { + return g_board_id_hex; +} + +void wifi_settings_set_hostname() { + // Convert board id to uppercase hex + pico_unique_board_id_t id; + pico_get_unique_board_id(&id); + for (int i = 0; (i < PICO_UNIQUE_BOARD_ID_SIZE_BYTES) && (i < BOARD_ID_SIZE); i++) { + snprintf(&g_board_id_hex[i * 2], 3, "%02X", id.id[i]); + } + + // Load host name from the settings file (if set) + uint name_size = sizeof(g_hostname) - 1; + if ((wifi_settings_get_value_for_key("name", g_hostname, &name_size)) && (name_size > 0)) { + // name= is valid + g_hostname[name_size] = '\0'; + } else { + // host name fallback: PicoW- + snprintf(g_hostname, sizeof(g_hostname), "PicoW-%s", g_board_id_hex); + } +} From 3984658765d1f8eb0d08a733b22e3ac269b5bfa4 Mon Sep 17 00:00:00 2001 From: Jack Whitham Date: Tue, 6 May 2025 19:37:31 +0100 Subject: [PATCH 2/5] Add changes from pico-wifi-settings v0.2.0 --- .../wifi_settings_connect/CMakeLists.txt | 7 ++ .../wifi_settings_configuration.h | 86 +++++++++++++------ .../wifi_settings_connect_internal.h | 7 +- .../wifi_settings/wifi_settings_flash_range.h | 7 +- .../wifi_settings_connect.c | 11 ++- .../wifi_settings_flash_range.c | 9 +- 6 files changed, 87 insertions(+), 40 deletions(-) diff --git a/src/rp2_common/wifi_settings_connect/CMakeLists.txt b/src/rp2_common/wifi_settings_connect/CMakeLists.txt index b3eca52..91f7817 100644 --- a/src/rp2_common/wifi_settings_connect/CMakeLists.txt +++ b/src/rp2_common/wifi_settings_connect/CMakeLists.txt @@ -19,6 +19,13 @@ elseif (NOT TARGET pico_lwip_core) else() message("wifi_settings_connect: library is available.") add_library(wifi_settings_connect INTERFACE) + set(WIFI_SETTINGS_VERSION_STRING "0.2.0c") + set(WIFI_SETTINGS_PROJECT_URL "https://github.com/jwhitham/pico-wifi-settings") + + target_compile_definitions(wifi_settings_connect INTERFACE + WIFI_SETTINGS_VERSION_STRING="${WIFI_SETTINGS_VERSION_STRING}" + WIFI_SETTINGS_PROJECT_URL="${WIFI_SETTINGS_PROJECT_URL}" + ) target_include_directories(wifi_settings_connect INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h index 068feb2..2265803 100644 --- a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h @@ -10,30 +10,45 @@ #ifndef WIFI_SETTINGS_CONFIGURATION_H #define WIFI_SETTINGS_CONFIGURATION_H +#include "hardware/flash.h" -#if defined(FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE) -// Flash address of wifi-settings file already defined -#elif PICO_RP2040 -// Flash address of wifi-settings file on Pico 1 W -#define FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE 0x001ff000 -// Note: Flash addresses have 0 = start of Flash. -// Note: The CPU's address for the wifi-settings file is 0x101ff000 on Pico 1. - -#elif PICO_RP2350 -// Flash address of wifi-settings file on Pico 2 W -#define FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE 0x003fe000 -// Note: Flash addresses have 0 = start of Flash. -// Note: avoid final sector due to RP2350-E10 bug -// Note: The CPU's address for the wifi-settings file is 0x103fe000 on Pico 2 -// assuming that there is no translation (e.g. partitioning). +#if defined(WIFI_SETTINGS_FILE_ADDRESS) && (WIFI_SETTINGS_FILE_ADDRESS == 0) +// If WIFI_SETTINGS_FILE_ADDRESS == 0, then use the default start location +#undef WIFI_SETTINGS_FILE_ADDRESS +#endif -#else -#error "Unknown Pico model - please set FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE appropriately" +#if !defined(WIFI_SETTINGS_FILE_ADDRESS) +// The default start location of the wifi-settings file is 16kb before the end of Flash: +#define WIFI_SETTINGS_FILE_ADDRESS (PICO_FLASH_SIZE_BYTES - 0x4000) +// +// The CPU's address for this Flash location is: +// 0x101fc000 on Pico 1 W +// 0x103fc000 on Pico 2 W +// and may have other values on other RP2040/RP2350 boards. +// +// This location is chosen because the final three 4kb sectors of Flash are +// already assigned a function by the Pico SDK. The Bluetooth library uses +// two 4kb sectors for storage of devices that have been paired by Bluetooth. +// The final 4kb sector is used for a workaround for the RP2350-E10 bug - this +// sector may be erased when copying a UF2 file to a Pico 2 via drag-and-drop. +// Therefore, these three sectors are avoided. +// +// If you wish to store the wifi-settings file at a specific address you can +// do so by setting -DWIFI_SETTINGS_FILE_ADDRESS=0x.... when running +// cmake. This should be an address relative to the start of Flash, and +// should be a multiple of WIFI_SETTINGS_FILE_SIZE. +// +// Versions of pico-wifi-settings before 0.2.0 used 0x1ff000 for Pico 1 and +// 0x3fe000 for Pico 2. #endif -// Size of wifi-settings file (bytes, must be a whole number of Flash sectors) +// Size of wifi-settings file in bytes +// This must be a whole number of Flash sectors. +// The setup app, documentation and examples all assume 0x1000 bytes, and that +// is the recommended value, but any positive multiple of the Flash sector size +// can be used. #ifndef WIFI_SETTINGS_FILE_SIZE -#define WIFI_SETTINGS_FILE_SIZE 0x1000 +#define WIFI_SETTINGS_FILE_SIZE (FLASH_SECTOR_SIZE) // (0x1000 bytes) #endif // Minimum time between initialisation and the first scan (milliseconds). @@ -41,28 +56,43 @@ #define INITIAL_SETUP_TIME_MS 1000 #endif -// Maximum time allowed between calling cyw43_wifi_join and getting an IP address (milliseconds). -// If this timeout expires, wifi_settings will try a different hotspot or rescan. The attempt to -// join a hotspot can fail sooner than this, e.g. if the password is incorrect or the hotspot vanishes. +// Maximum time allowed between calling cyw43_wifi_join and getting an +// IP address (milliseconds). If this timeout expires, wifi_settings will +// try a different hotspot or rescan. The attempt to join a hotspot can fail +// sooner than this, e.g. if the password is incorrect or the hotspot vanishes. #ifndef CONNECT_TIMEOUT_TIME_MS #define CONNECT_TIMEOUT_TIME_MS 30000 #endif -// Minimum time between scans (milliseconds). If a scan fails to find any known hotspot, -// wifi_settings will always wait at least this long before retry. +// Minimum time between scans (milliseconds). If a scan fails to find any +// known hotspot, wifi_settings will always wait at least this long before +// retry. #ifndef REPEAT_SCAN_TIME_MS #define REPEAT_SCAN_TIME_MS 3000 #endif -// Minimum time between calls to the periodic function, wifi_settings_periodic_callback, +// Minimum time between calls to the periodic function, +// wifi_settings_periodic_callback, // which will initiate scans and connections if necessary (milliseconds). #ifndef PERIODIC_TIME_MS #define PERIODIC_TIME_MS 1000 #endif -// Maximum number of SSIDs that can be supported. -#ifndef NUM_SSIDS -#define NUM_SSIDS 16 +// Maximum number of SSIDs that can be supported. This determines the size +// of the g_wifi_state.ssid_scan_info array. You can set this maximum +// to larger values if you wish, at the cost of some additional memory +// usage (1 byte per SSID), but the setup app assumes this maximum. +#ifndef MAX_NUM_SSIDS +#define MAX_NUM_SSIDS 100 +#endif + +// Validation for wifi-settings file address and size +#ifdef static_assert +static_assert((WIFI_SETTINGS_FILE_ADDRESS + WIFI_SETTINGS_FILE_SIZE) <= PICO_FLASH_SIZE_BYTES); +static_assert(WIFI_SETTINGS_FILE_ADDRESS > 0); +static_assert(WIFI_SETTINGS_FILE_SIZE > 0); +static_assert((WIFI_SETTINGS_FILE_SIZE % FLASH_SECTOR_SIZE) == 0); +static_assert((WIFI_SETTINGS_FILE_ADDRESS % WIFI_SETTINGS_FILE_SIZE) == 0); #endif #endif diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h index ee14577..52be942 100644 --- a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect_internal.h @@ -20,8 +20,9 @@ #include "pico/stdlib.h" #include "pico/time.h" #include "pico/cyw43_arch.h" +#include "pico/platform.h" -enum wifi_connect_state_t { +enum __packed wifi_connect_state_t { UNINITIALISED = 0, // cyw43 hardware was not started INITIALISATION_ERROR, // initialisation failed (see hw_error_code) STORAGE_EMPTY_ERROR, // no WiFi details are known @@ -32,7 +33,7 @@ enum wifi_connect_state_t { CONNECTED_IP, // connection is ready for use }; -enum ssid_scan_info_t { +enum __packed ssid_scan_info_t { NOT_FOUND = 0, // this SSID was not found FOUND, // this SSID was found by the most recent scan ATTEMPT, // we attempted to connect to this SSID @@ -48,7 +49,7 @@ enum ssid_scan_info_t { struct wifi_state_t { enum wifi_connect_state_t cstate; - enum ssid_scan_info_t ssid_scan_info[NUM_SSIDS + 1]; + enum ssid_scan_info_t ssid_scan_info[MAX_NUM_SSIDS + 1]; struct netif* netif; cyw43_t* cyw43; uint selected_ssid_index; diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h index 8bbc03f..8df1412 100644 --- a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_range.h @@ -10,8 +10,6 @@ #ifndef _WIFI_SETTINGS_FLASH_RANGE_H_ #define _WIFI_SETTINGS_FLASH_RANGE_H_ -#include "hardware/flash.h" - #include #include @@ -57,6 +55,11 @@ void wifi_settings_range_get_reusable( /// @brief Determine the range of addresses used by the wifi-settings file /// @param[out] r Flash memory range +/// @details This function has a weak symbol, allowing it to be reimplemented +/// by applications in order to place the file at any Flash location, +/// including a location that is determined dynamically. A different +/// static location can also be set at build time with +/// -DWIFI_SETTINGS_FILE_ADDRESS=0x... void wifi_settings_range_get_wifi_settings_file( wifi_settings_flash_range_t* r); diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c b/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c index 41f8554..2ac09a4 100644 --- a/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c @@ -20,6 +20,7 @@ #include "wifi_settings/wifi_settings_remote.h" #endif +#include "pico/binary_info.h" #include "pico/error.h" #include @@ -127,7 +128,7 @@ int wifi_settings_get_ip_status_text(char* text, int text_size) { } const char* wifi_settings_get_ssid_status(int ssid_index) { - if ((ssid_index >= 1) && (ssid_index <= NUM_SSIDS)) { + if ((ssid_index >= 1) && (ssid_index <= MAX_NUM_SSIDS)) { switch (g_wifi_state.ssid_scan_info[ssid_index]) { case NOT_FOUND: return "NOT FOUND"; break; case FOUND: return "FOUND"; break; @@ -213,7 +214,7 @@ static enum ssid_type_t fetch_ssid(uint ssid_index, char* ssid, uint8_t* bssid) static int wifi_scan_callback(void* unused, const cyw43_ev_scan_result_t* scan_result) { // Is this SSID known? Iterate through the file to see if there is a record of it. - for (uint ssid_index = 1; ssid_index <= NUM_SSIDS; ssid_index++) { + for (uint ssid_index = 1; ssid_index <= MAX_NUM_SSIDS; ssid_index++) { // Skip SSIDs that we already saw if (g_wifi_state.ssid_scan_info[ssid_index] != NOT_FOUND) { continue; @@ -258,7 +259,7 @@ static void begin_connecting() { // Which hotspot to connect to? g_wifi_state.selected_ssid_index = 0; - for (uint ssid_index = 1; ssid_index <= NUM_SSIDS; ssid_index++) { + for (uint ssid_index = 1; ssid_index <= MAX_NUM_SSIDS; ssid_index++) { if (g_wifi_state.ssid_scan_info[ssid_index] == FOUND) { g_wifi_state.selected_ssid_index = ssid_index; break; @@ -346,7 +347,7 @@ static bool has_valid_address() { static void begin_new_scan() { // Begin a scan. We will reset everything we know about hotspots first. - for (uint ssid_index = 1; ssid_index <= NUM_SSIDS; ssid_index++) { + for (uint ssid_index = 1; ssid_index <= MAX_NUM_SSIDS; ssid_index++) { g_wifi_state.ssid_scan_info[ssid_index] = NOT_FOUND; } // Start the scan @@ -449,6 +450,8 @@ int wifi_settings_init() { if (g_wifi_state.cstate != UNINITIALISED) { return PICO_ERROR_INVALID_STATE; } + // Put wifi-settings library version into the binary info + bi_decl_if_func_used(bi_program_feature("pico-wifi-settings v" WIFI_SETTINGS_VERSION_STRING)); // Start with globals in known state memset(&g_wifi_state, 0, sizeof(g_wifi_state)); diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c index c3c557a..24bdeb2 100644 --- a/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_range.c @@ -10,6 +10,7 @@ #include "wifi_settings/wifi_settings_configuration.h" #include "wifi_settings/wifi_settings_flash_range.h" +#include "pico/platform.h" #include "hardware/regs/addressmap.h" #ifdef XIP_QMI_BASE #include "hardware/structs/qmi.h" @@ -110,10 +111,12 @@ void wifi_settings_range_get_partition(wifi_settings_flash_range_t* r) { } // Determine the range of addresses used by the wifi-settings file -void wifi_settings_range_get_wifi_settings_file(wifi_settings_flash_range_t* r) { - r->start_address = FLASH_ADDRESS_OF_WIFI_SETTINGS_FILE; +// This function can be reimplemented in order to set the file location dynamically; +// this default version uses values from wifi_settings_configuration.h which are +// guaranteed to be valid because of static assertions in the header +__weak void wifi_settings_range_get_wifi_settings_file(wifi_settings_flash_range_t* r) { + r->start_address = WIFI_SETTINGS_FILE_ADDRESS; r->size = WIFI_SETTINGS_FILE_SIZE; - wifi_settings_range_align_to_sector(r); } // Determine the range of addresses that are reusable From cdde8e526dba8d76ce8bb2783e4cec8dfc1b4221 Mon Sep 17 00:00:00 2001 From: Jack Whitham Date: Tue, 6 May 2025 23:53:58 +0100 Subject: [PATCH 3/5] Add self-contained documentation for wifi_settings_connect --- .../wifi_settings_connect/README.md | 47 +++-- .../wifi_settings_connect/doc/INTEGRATION.md | 120 +++++++++++++ .../doc/SETTINGS_FILE.md | 160 ++++++++++++++++++ 3 files changed, 313 insertions(+), 14 deletions(-) create mode 100644 src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md create mode 100644 src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md diff --git a/src/rp2_common/wifi_settings_connect/README.md b/src/rp2_common/wifi_settings_connect/README.md index ba800d0..d694b2d 100644 --- a/src/rp2_common/wifi_settings_connect/README.md +++ b/src/rp2_common/wifi_settings_connect/README.md @@ -3,20 +3,39 @@ This is a library to manage WiFi connections. It provides Flash storage for WiFi passwords and hotspot names, and a background async\_context service to automatically connect to them. You can store details for -up to 16 hotspots and update them using `picotool` or a setup +up to 100 hotspots and update them using `picotool` or a setup application. This avoids any need to specify build-time flags such as `WIFI_SSID` and `WIFI_PASSWORD`. -The Flash storage location for hotspot details is specified in -`include/wifi_settings/wifi_settings_configuration.h`. It is at -`0x101ff000` (for Pico W) and `0x103fe000` (for Pico 2 W). To add your -WiFi details at this location, please [see these -instructions](https://github.com/jwhitham/pico-wifi-settings/blob/master/doc/SETTINGS_FILE.md). -You can edit the settings as a text file and transfer it with `picotool`, -or install a [setup application](https://github.com/jwhitham/pico-wifi-settings/blob/master/doc/SETUP_APP.md) -to add or update WiFi details. - -This wifi\_settings\_connect library is a subset of a larger -library, [wifi\_settings](https://github.com/jwhitham/pico-wifi-settings/), -which adds remote update functions for both the WiFi settings -and (optionally) your Pico application too. +WiFi hotspot details are stored in a Flash sector that isn't normally used by programs, +normally located near the end of Flash memory. This +[wifi-settings file](doc/SETTINGS_FILE.md) is a simple text file which +can be updated by USB or by installing a setup app from the +[pico-wifi-settings home page](https://github.com/jwhitham/pico-wifi-settings) +on Github. + +## Requirements + + - Raspberry Pi Pico W or Pico 2 W hardware + - a "bare metal" C/C++ application for Pico W (not FreeRTOS) + using the `cyw43` driver and `lwip` network stack + which are provided with the [Pico SDK](https://github.com/raspberrypi/pico-sdk/). + - between 2kb and 13kb of code space depending on options used + - WiFi network(s) with a DHCP server and WPA authentication + +## How to use it + +First, you need to configure the WiFi settings file +in Flash. See the [wifi-settings file documentation](doc/SETTINGS_FILE.md). + +Next, you need to modify your application to use wifi\_settings\_connect. +There is an [integration guide which explains what you need to do +to add wifi\_settings\_connect to your application](doc/INTEGRATION.md). + +## Enabling remote updates + +wifi\_settings\_connect is a subset of a larger library (wifi\_settings) which +also has support for remote updates of WiFi settings and over-the-air (OTA) +firmware updates. Visit the +[pico-wifi-settings home page](https://github.com/jwhitham/pico-wifi-settings) +for more information. diff --git a/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md b/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md new file mode 100644 index 0000000..49bbe5c --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md @@ -0,0 +1,120 @@ +# Integrating wifi\_settings\_connect into your own Pico application + +You can integrate wifi\_settings\_connect into your own Pico application +with just a few lines of code: +``` + #include "wifi_settings.h" // << add this + int main() { + stdio_init_all(); + if (wifi_settings_init() != 0) { // << and add this + panic(...); + } + wifi_settings_connect(); // << and add this + // and that's it... + } +``` +The following steps go through the process in more detail for +a [CMake](https://cmake.org) project stored in Git, +similar to all of the official Pico projects. + +You may find it useful to look at [the example app for wifi\_settings\_connect in +the pico-playground repository](https://github.com/raspberrypi/pico-playground/tree/master/wifi_settings_connect/example). + +## Modify CMakeLists.txt to use the library + +The `target_link_libraries` rule for your project should be extended to +add `wifi_settings_connect`. +``` + target_link_libraries(your_app + wifi_settings_connect + pico_stdlib + ) +``` +If your project does not include the `pico-extras` repository, then this +must also be added. +``` + include(pico_extras_import.cmake) +``` +You can copy the `pico_extras_import.cmake` file from [the root of +the pico-playground repository](https://github.com/raspberrypi/pico-playground). + +### Additional configuration for LwIP and mbedtls + +If your project has not previously used WiFi you will also need +to add one of the WiFi driver targets to `target_link_libraries`, e.g. +`pico_cyw43_arch_lwip_background` or `pico_cyw43_arch_lwip_poll`. + +You will also need `lwipopts.h` in the project directory (this configures +LwIP). You can copy an example from +[here](https://github.com/raspberrypi/pico-playground/tree/master/wifi_settings_connect/example). + +## Include the header file + +Your main C/C++ source file (containing `main()`) should be modified to include +`wifi_settings/wifi_settings_connect.h`: +``` + #include "wifi_settings/wifi_settings_connect.h" +``` + +## Modify your main function + +Your `main()` function should be modified to call `wifi_settings_init()` once on startup. + + - This must *replace* any call to `cyw43` initialisation functions, because + these are called from `wifi_settings_init()` (with the correct country code). + - The call should be after `stdio_init_all()`. + - If the call returns a non-zero value, an error has occurred. You do not have + to handle this error; it is still safe to call other `wifi_settings` functions, + but they will not work and will return error codes where appropriate. + +Your application should also call `wifi_settings_connect()` when it wishes to connect +to WiFi. This can be called immediately after `wifi_settings_init()` or at any later +time. `wifi_settings_connect()` does not block, as the connection takes +place in the background. + +All other modifications are optional. You can now rebuild your application +and it will include the wifi\_settings\_connect features. + +## CMake command line + +When running `cmake`, you need to provide the location of the `pico-extras` +repository as well as the `pico-sdk` repository. This is typically done +with `-DPICO_EXTRAS_PATH`, e.g.: +``` + cmake -DPICO_BOARD=pico_w \ + -DPICO_SDK_PATH=/home/user/pico-sdk \ + -DPICO_EXTRAS_PATH=/home/user/pico-extras \ + .. +``` + +# Optional modifications + +Your application can call `wifi_settings_is_connected()` at any time +to determine if the WiFi connection is available or not. + +Your application can call various status functions at any time +to get a text report on the connection status. This can be useful for debugging. +Each function should be passed a `char[]` buffer for the output, along with the +size of the buffer. + + - `wifi_settings_get_connect_status_text()` produces a line of + text showing the connection status, e.g. `WiFi is connected to ssid1=MyHomeWiFi`. + - `wifi_settings_get_hw_status_text()` produces a line of + text describing the status of the `cyw43` hardware driver; this will be empty + if the hardware is not initialised. + - `wifi_settings_get_ip_status_text()` produces a line of + text describing the status of the `lwip` network stack e.g. IP address; this will be empty + if unconnected. + +There is also a function to report the current connection state +`wifi_settings_get_ssid_status()` returns +a pointer to a static string, indicating the status of a connection attempt to +an SSID, e.g. `SUCCESS`, `NOT FOUND`. + +Your application can call `wifi_settings_disconnect()` to force disconnect, +or `wifi_settings_deinit()` to deinitialise the driver, but this is never necessary +and these steps can be left out. They exist to allow the application to shut down WiFi, +e.g. to save power, or in order to control the WiFi hardware directly for some other +purpose. For example, the +[setup app](https://github.com/jwhitham/pico-wifi-settings/tree/master/doc/SETUP_APP.md) +uses this feature to perform its own WiFi scan. diff --git a/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md b/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md new file mode 100644 index 0000000..11eb832 --- /dev/null +++ b/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md @@ -0,0 +1,160 @@ +# Creating and updating a WiFi settings file + +wifi\_settings\_connect stores WiFi hotspot names and passwords +in a Flash sector that isn't normally used by programs. This +is called the "WiFi settings file". It is similar to a file on a disk, +except that it is always at the same location, and the size is +limited to 4096 bytes. + +The file can be updated over USB by using [picotool](https://github.com/raspberrypi/picotool). +It is a text file which can be edited with any text editor. +Here is an example of typical contents: +``` + ssid1=MyHomeWiFi + pass1=mypassword1234 + ssid2=MyPhoneHotspot + pass2=secretpassword + country=GB +``` +wifi\_settings\_connect will automatically scan for hotspots and connect to +hotspots matching the SSID names and passwords in the file. + +- On the [pico-wifi-settings home page](https://github.com/jwhitham/pico-wifi-settings) + you can also find a setup app which runs on your Pico and automates much of the + setup process. The pico-wifi-settings library is a superset of + wifi\_settings\_connect. It includes a remote update feature that allows + a new wifi-settings file to be installed via WiFi. + +# Creating the file on a computer + +Use any text editor to create a text file similar to the example above. + +Each line in the file should contain a key and a value, separated by `=`, +with no spaces around `=`, or at the beginning or end of each line. + +The file must have at least `ssid1` or `bssid1`, otherwise there will +be no connection attempts, and wifi\_settings\_connect will stay in the +STORAGE\_EMPTY\_ERROR state. + +You can also use the following: + + - `ssid` - SSID name for hotspot N (a number from 1 to 100) + - `pass` - Password for hotspot N + - `country` - Your two-letter country code from [ISO-3166-1](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) + - `bssid` - The BSSID ID for hotspot N + - `name` - The hostname of the Pico (sent to DHCP servers) + +# Copying the WiFi settings file by USB + +You can use [picotool](https://github.com/raspberrypi/picotool) to copy the +file from your PC to your Pico via USB. + +picotool is part of the Pico SDK. +You need to build picotool with USB support for your OS (or download a pre-built copy). + +To use picotool, +boot the Pico in bootloader mode by holding down the BOOTSEL button while plugging it +into USB. In bootloader mode, you can upload files with picotool. +The default address is 16kb before the final address in Flash: + + - On Pico W, use `0x101fc000` as the address. + - On Pico 2 W, use `0x103fc000` as the address. + +You must also rename your WiFi settings file so that it ends with `.bin` as +picotool is not able to upload files unless they are `.bin`, `.elf` or `.uf2`. + +Here is a sample upload command for Pico W (RP2040): +``` + picotool load -o 0x101fc000 mywifisettings.bin +``` +and the equivalent for Pico 2 W (RP2350): +``` + picotool load -o 0x103fc000 mywifisettings.bin +``` + +## Location of the wifi-settings file + +The default location of the file (16kb before the final address in Flash) +has been chosen because the final three 4kb Flash sectors are already assigned +a function by the Pico SDK. The Bluetooth library uses two 4kb sectors for storage of +devices that have been paired by Bluetooth. The final 4kb sector is used for a workaround +for the RP2350-E10 bug - this sector may be erased when copying a UF2 file to a Pico 2 +via drag-and-drop. Therefore, these three sectors are avoided. + +If you wish to store the wifi-settings file at a specific address you can +do so by setting `-DWIFI_SETTINGS_FILE_ADDRESS=0x....` when running `cmake`. +The value `0x...` should be an address relative to the start of Flash, so Flash address +`0x1fc000` corresponds to absolute address `0x101fc000`. + +## Backing up a WiFi settings file + +picotool can be used to download WiFi settings files from a Pico W: +``` + picotool save -r 0x101fc000 0x101fd000 backup.bin +``` +and Pico 2 W (RP2350): +``` + picotool save -r 0x103fc000 0x103fd000 backup.bin +``` +Characters after the end of the file will be copied (usually either 0x00 or 0xff). +These can be safely deleted using your text editor. The backup is restored by +using `picotool load` as described in "Copying the WiFi settings file by USB". + +These examples use the default location for the wifi-settings file. If you +are using a custom location, e.g. building with +`-DWIFI_SETTINGS_FILE_ADDRESS=0x...`, then +you would need to substitute the actual address. + +# File format details + +The file format is very simple so that it can be read by a simple algorithm +that doesn't require much code space. The parser ignores any line that it +doesn't understand, and skips any keys that are not known. Here are the rules: + + - The key and the value should be separated only by an `=` character, e.g. `ssid1=HomeWiFi`. + - Lines that don't match the form `key=value` are completely ignored; + you can add text, comments etc. in order to help you manage your configuration. + - On a line that does match `key=value`, whitespace is NOT ignored. + Be careful to avoid adding extra spaces around `=`. + A space before `=` will be part of the key, and a space after `=` will be part of the value. + - Unix and Windows line endings are supported. + - The maximum size of the file is 4096 bytes. + - Values can contain any printable UTF-8 character. + - Keys can also contain any printable UTF-8 character except for '='. + - There is no maximum size for a key or a value (except for the file size). + - Values can be zero length. + - Keys must be at least 1 byte. + - If a key appears more than once in the file, the first value is used. + - The end of the file is the first byte with value 0x00, 0xff or 0x1a, or the 4097th byte, + whichever comes first. + +# WiFi settings + + - `ssid` is only checked if `ssid` is present. + - The number reflects the priority. Lower numbers take priority over higher + numbers when more than one SSID is found. + - If `pass` is not specified then wifi\_settings\_connect will assume + an open WiFi hotspot. + - If both `bssid` and `ssid` are specified, then the BSSID is used + and the SSID is ignored. + - If you don't specify a country, the default worldwide settings are used, which might work + slightly less well (e.g. fewer WiFi channels are supported). + - `bssid` should be specified as + a `:`-separated lower-case MAC address, e.g. `01:23:45:67:89:ab`. BSSIDs are + not normally required and should only be used if you have a special requirement + e.g. a "hidden" hotspot without an SSID name. + +# Custom keys and values + +The WiFi settings file can have keys which are not used by the wifi\_settings\_connect library. +Your application can obtain their values using the `wifi_settings_get_value_for_key()` function. +This can be a useful way to store additional configuration data for your Pico application. +For example you might use it to store encryption keys, server addresses, user names +or any other setting that you may wish to update without rebuilding your application. + +`wifi_settings_get_value_for_key` uses a linear search, starting at the beginning +of the file. This search does not backtrack and is fast because of the simplistic +nature of the file format. However, in algorithmic terms, this is not the best way +to implement or search a key/value store, and if you need frequent access to keys/values, +you may wish to implement something better (e.g. use a hash table to implement a dictionary) +or just load the values when your application starts up and then store them elsewhere. From c6a5a2122ec26bc6e0f5d2f8d61cd36de0ab52c8 Mon Sep 17 00:00:00 2001 From: Jack Whitham Date: Wed, 7 May 2025 00:49:50 +0100 Subject: [PATCH 4/5] Correct documentation error. --- src/rp2_common/wifi_settings_connect/README.md | 1 + src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rp2_common/wifi_settings_connect/README.md b/src/rp2_common/wifi_settings_connect/README.md index d694b2d..5530466 100644 --- a/src/rp2_common/wifi_settings_connect/README.md +++ b/src/rp2_common/wifi_settings_connect/README.md @@ -29,6 +29,7 @@ First, you need to configure the WiFi settings file in Flash. See the [wifi-settings file documentation](doc/SETTINGS_FILE.md). Next, you need to modify your application to use wifi\_settings\_connect. +This involves adding a few lines of C code. There is an [integration guide which explains what you need to do to add wifi\_settings\_connect to your application](doc/INTEGRATION.md). diff --git a/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md b/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md index 49bbe5c..d267b37 100644 --- a/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md +++ b/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md @@ -3,13 +3,13 @@ You can integrate wifi\_settings\_connect into your own Pico application with just a few lines of code: ``` - #include "wifi_settings.h" // << add this + #include "wifi_settings/wifi_settings_connect.h" // << add this int main() { stdio_init_all(); - if (wifi_settings_init() != 0) { // << and add this + if (wifi_settings_init() != 0) { // << and add this panic(...); } - wifi_settings_connect(); // << and add this + wifi_settings_connect(); // << and add this // and that's it... } ``` From 06daea436bcea58b314e6142a507488bc12a2201 Mon Sep 17 00:00:00 2001 From: Jack Whitham Date: Wed, 7 May 2025 23:15:30 +0100 Subject: [PATCH 5/5] Update documentation and add status API functions --- .../wifi_settings_connect/README.md | 9 +++--- .../wifi_settings_connect/doc/INTEGRATION.md | 11 +++++-- .../doc/SETTINGS_FILE.md | 22 +++++++------ .../wifi_settings_configuration.h | 2 +- .../wifi_settings/wifi_settings_connect.h | 12 +++++++ .../wifi_settings_flash_storage.h | 2 ++ .../wifi_settings_connect.c | 31 +++++++++++++++++++ .../wifi_settings_flash_storage.c | 6 +++- 8 files changed, 77 insertions(+), 18 deletions(-) diff --git a/src/rp2_common/wifi_settings_connect/README.md b/src/rp2_common/wifi_settings_connect/README.md index 5530466..f6e956b 100644 --- a/src/rp2_common/wifi_settings_connect/README.md +++ b/src/rp2_common/wifi_settings_connect/README.md @@ -3,12 +3,13 @@ This is a library to manage WiFi connections. It provides Flash storage for WiFi passwords and hotspot names, and a background async\_context service to automatically connect to them. You can store details for -up to 100 hotspots and update them using `picotool` or a setup -application. This avoids any need to -specify build-time flags such as `WIFI_SSID` and `WIFI_PASSWORD`. +up to 100 hotspots and update them using +[picotool](https://github.com/raspberrypi/pico-sdk-tools/releases). +or a [setup application](https://github.com/jwhitham/pico-wifi-settings/releases/). +This avoids any need to specify build-time flags such as `WIFI_SSID` and `WIFI_PASSWORD`. WiFi hotspot details are stored in a Flash sector that isn't normally used by programs, -normally located near the end of Flash memory. This +normally located near the end of Flash memory. This [wifi-settings file](doc/SETTINGS_FILE.md) is a simple text file which can be updated by USB or by installing a setup app from the [pico-wifi-settings home page](https://github.com/jwhitham/pico-wifi-settings) diff --git a/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md b/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md index d267b37..7d42264 100644 --- a/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md +++ b/src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md @@ -105,8 +105,15 @@ size of the buffer. - `wifi_settings_get_ip_status_text()` produces a line of text describing the status of the `lwip` network stack e.g. IP address; this will be empty if unconnected. + - `wifi_settings_get_ip` produces the IP address by itself; this will be empty + if unconnected. + - `wifi_settings_get_ssid` produces the current SSID by itself; this will be empty + if unconnected. If connected using a BSSID, this will be reported as + a `:`-separated lower-case MAC address, e.g. `01:23:45:67:89:ab`. If the wifi-settings + file has been updated since the connection was made, then the result may be `?`, + as the SSID is found by searching the wifi-settings file. -There is also a function to report the current connection state +There is also a function to report the current connection state. `wifi_settings_get_ssid_status()` returns a pointer to a static string, indicating the status of a connection attempt to an SSID, e.g. `SUCCESS`, `NOT FOUND`. @@ -115,6 +122,6 @@ Your application can call `wifi_settings_disconnect()` to force disconnect, or `wifi_settings_deinit()` to deinitialise the driver, but this is never necessary and these steps can be left out. They exist to allow the application to shut down WiFi, e.g. to save power, or in order to control the WiFi hardware directly for some other -purpose. For example, the +purpose. For example, the [setup app](https://github.com/jwhitham/pico-wifi-settings/tree/master/doc/SETUP_APP.md) uses this feature to perform its own WiFi scan. diff --git a/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md b/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md index 11eb832..de178f0 100644 --- a/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md +++ b/src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md @@ -6,7 +6,7 @@ is called the "WiFi settings file". It is similar to a file on a disk, except that it is always at the same location, and the size is limited to 4096 bytes. -The file can be updated over USB by using [picotool](https://github.com/raspberrypi/picotool). +The file can be updated over USB by using [picotool](https://github.com/raspberrypi/pico-sdk-tools/releases). It is a text file which can be edited with any text editor. Here is an example of typical contents: ``` @@ -46,11 +46,9 @@ You can also use the following: # Copying the WiFi settings file by USB -You can use [picotool](https://github.com/raspberrypi/picotool) to copy the -file from your PC to your Pico via USB. - -picotool is part of the Pico SDK. -You need to build picotool with USB support for your OS (or download a pre-built copy). +You can use +[picotool](https://github.com/raspberrypi/pico-sdk-tools/releases). +to copy the file from your computer to your Pico via USB. To use picotool, boot the Pico in bootloader mode by holding down the BOOTSEL button while plugging it @@ -60,7 +58,7 @@ The default address is 16kb before the final address in Flash: - On Pico W, use `0x101fc000` as the address. - On Pico 2 W, use `0x103fc000` as the address. -You must also rename your WiFi settings file so that it ends with `.bin` as +You must also rename your WiFi settings file so that it ends with `.bin` as picotool is not able to upload files unless they are `.bin`, `.elf` or `.uf2`. Here is a sample upload command for Pico W (RP2040): @@ -96,9 +94,13 @@ and Pico 2 W (RP2350): ``` picotool save -r 0x103fc000 0x103fd000 backup.bin ``` -Characters after the end of the file will be copied (usually either 0x00 or 0xff). -These can be safely deleted using your text editor. The backup is restored by -using `picotool load` as described in "Copying the WiFi settings file by USB". +Bytes after the end of the file will also be copied (usually either 0x00 or 0xff). +These can be safely deleted. Some text editors will allow you to delete them, +but if you have any difficulty, you can also remove them with a shell command such as: +``` + LC_ALL=C sed -i 's/[\x00\xFF]//g' backup.bin +``` +The backup is restored using `picotool load` as described in "Copying the WiFi settings file by USB". These examples use the default location for the wifi-settings file. If you are using a custom location, e.g. building with diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h index 2265803..0e4fcb9 100644 --- a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_configuration.h @@ -48,7 +48,7 @@ // is the recommended value, but any positive multiple of the Flash sector size // can be used. #ifndef WIFI_SETTINGS_FILE_SIZE -#define WIFI_SETTINGS_FILE_SIZE (FLASH_SECTOR_SIZE) // (0x1000 bytes) +#define WIFI_SETTINGS_FILE_SIZE (1 * FLASH_SECTOR_SIZE) // (0x1000 bytes) #endif // Minimum time between initialisation and the first scan (milliseconds). diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h index e22b508..c228967 100644 --- a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_connect.h @@ -62,6 +62,18 @@ int wifi_settings_get_hw_status_text(char* text, int text_size); /// @return Return code from snprintf when formatting int wifi_settings_get_ip_status_text(char* text, int text_size); +/// @brief Get the IP address by itself +/// @param[inout] text Text buffer for address +/// @param[in] text_size Available space in the buffer (bytes) +/// @return Return code from snprintf when formatting +int wifi_settings_get_ip(char* text, int text_size); + +/// @brief Get the current SSID by itself +/// @param[inout] text Text buffer for SSID +/// @param[in] text_size Available space in the buffer (bytes) +/// @return Return code from snprintf when formatting +int wifi_settings_get_ssid(char* text, int text_size); + /// @brief Get the status of a connection attempt to /// an SSID as a static string, e.g. SUCCESS, NOT_FOUND. "" is returned /// if the SSID index is not known. diff --git a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h index 3f31354..c378c05 100644 --- a/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h +++ b/src/rp2_common/wifi_settings_connect/include/wifi_settings/wifi_settings_flash_storage.h @@ -22,6 +22,8 @@ /// @param[out] value Value for key (if found) - not '\0' terminated /// @param[inout] value_size Size of the value /// @return true if key found +/// @details This function has a weak symbol, allowing it to be reimplemented +/// by applications in order to load settings from some other storage bool wifi_settings_get_value_for_key( const char* key, char* value, uint* value_size); diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c b/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c index 2ac09a4..78bac79 100644 --- a/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_connect.c @@ -127,6 +127,37 @@ int wifi_settings_get_ip_status_text(char* text, int text_size) { ip4addr_ntoa_r(netif_ip4_gw(g_wifi_state.netif), addr_buf3, IPV4_ADDRESS_SIZE)); } +int wifi_settings_get_ip(char* text, int text_size) { + if ((!g_wifi_state.netif) + || (!netif_is_link_up(g_wifi_state.netif))) { + // Not connected - return empty string + text[0] = '\0'; + return 0; + } + char addr_buf[IPV4_ADDRESS_SIZE]; + return snprintf(text, text_size, + "%s", + ip4addr_ntoa_r(netif_ip4_addr(g_wifi_state.netif), addr_buf, IPV4_ADDRESS_SIZE)); +} + +int wifi_settings_get_ssid(char* text, int text_size) { + char ssid[WIFI_SSID_SIZE]; + uint8_t bssid[WIFI_BSSID_SIZE]; + + switch(g_wifi_state.cstate) { + case CONNECTING: + case CONNECTED_IP: + (void) fetch_ssid(g_wifi_state.selected_ssid_index, ssid, bssid); + // The text buffer will contain '?' if the SSID is unknown (e.g. if + // the wifi-settings file was updated to remove the SSID while connected). + return snprintf(text, text_size, "%s", ssid); + default: + // Not connected - return empty string + text[0] = '\0'; + return 0; + } +} + const char* wifi_settings_get_ssid_status(int ssid_index) { if ((ssid_index >= 1) && (ssid_index <= MAX_NUM_SSIDS)) { switch (g_wifi_state.ssid_scan_info[ssid_index]) { diff --git a/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c index a683504..93b2a0e 100644 --- a/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c +++ b/src/rp2_common/wifi_settings_connect/wifi_settings_flash_storage.c @@ -11,10 +11,14 @@ #include "wifi_settings/wifi_settings_flash_storage.h" #include "wifi_settings/wifi_settings_flash_range.h" +#include "pico/platform.h" + #include -bool wifi_settings_get_value_for_key( +// Scan the settings file in Flash for a particular key. +// This function can be reimplemented in order to load settings from some other storage +__weak bool wifi_settings_get_value_for_key( const char* key, char* value, uint* value_size) { wifi_settings_flash_range_t fr;