Skip to content

Add wifi_settings_connect library #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/rp2_common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
pico_add_subdirectory(usb_device_msc)
pico_add_subdirectory(wifi_settings_connect)
47 changes: 47 additions & 0 deletions src/rp2_common/wifi_settings_connect/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# 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)
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
)

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()
43 changes: 43 additions & 0 deletions src/rp2_common/wifi_settings_connect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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 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
[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.
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).

## 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.
127 changes: 127 additions & 0 deletions src/rp2_common/wifi_settings_connect/doc/INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# 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/wifi_settings_connect.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.
- `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.
`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.
162 changes: 162 additions & 0 deletions src/rp2_common/wifi_settings_connect/doc/SETTINGS_FILE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# 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/pico-sdk-tools/releases).
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<N>` - SSID name for hotspot N (a number from 1 to 100)
- `pass<N>` - 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<N>` - 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/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
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
```
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
`-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.
Comment on lines +110 to +131
Copy link
Contributor

@lurch lurch May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easy to imagine this "settings-file reader" code being useful for multiple projects beyond your wifi-settings-connect library. I wonder if it might be worth splitting it out into a library of its own? (If you have the energy & time, of course 🙂 )

EDIT: Haha, I've just read your later "This can be a useful way to store additional configuration data for your Pico application." line 😁

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have certainly found it useful for more than WiFi settings, but I have been thinking that a filesystem module would be a better solution, being much more general, and also avoiding the problem of having to find unused addresses (as in raspberrypi/pico-sdk#1378 ). Is there a plan to officially adopt a filesystem as part of the SDK? In the hope of enabling this library to use a filesystem in the future, I have turned wifi_settings_get_value_for_key into a weak symbol so it can be reimplemented in some other way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a plan to officially adopt a filesystem as part of the SDK?

See https://github.com/raspberrypi/pico-sdk/labels/filesystem


# WiFi settings

- `ssid<N+1>` is only checked if `ssid<N>` is present.
- The number reflects the priority. Lower numbers take priority over higher
numbers when more than one SSID is found.
- If `pass<N>` is not specified then wifi\_settings\_connect will assume
an open WiFi hotspot.
- If both `bssid<N>` and `ssid<N>` 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<N>` 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.
Loading