From 86009ef6ae1a4e1692ec037bb0d43065d16a83e7 Mon Sep 17 00:00:00 2001 From: James Ide Date: Sun, 26 Feb 2023 18:31:37 -0800 Subject: [PATCH] Add an example server (very slow) This implements a simple HTTP and HTTPS server pair that serve a static HTML file. The HTTP server uses adafruit_httpserver in a straightforward, basic way. The HTTPS server works by wrapping a socket pool object in an object that wraps all sockets in SSLSockets. There are throwaway, self-signed certificates for testing under src/certificates. However, HTTPS is very slow. Probably need an optimized AES implementation. --- .gitignore | 3 + .vscode/settings.json | 8 ++ README.md | 32 ++++++-- requirements.txt | 1 + scripts/deploy.sh | 31 ++++++++ scripts/generate-single-certificate.sh | 11 +++ scripts/repl.sh | 8 ++ src/certificates/certificate-chain.pem | 11 +++ src/certificates/key.pem | 16 ++++ src/code.py | 86 ++++++++++++++++++++++ src/lib/adafruit_httpserver/__init__.mpy | Bin 0 -> 150 bytes src/lib/adafruit_httpserver/headers.mpy | Bin 0 -> 891 bytes src/lib/adafruit_httpserver/methods.mpy | Bin 0 -> 190 bytes src/lib/adafruit_httpserver/mime_type.mpy | Bin 0 -> 2639 bytes src/lib/adafruit_httpserver/request.mpy | Bin 0 -> 1038 bytes src/lib/adafruit_httpserver/response.mpy | Bin 0 -> 1909 bytes src/lib/adafruit_httpserver/route.mpy | Bin 0 -> 338 bytes src/lib/adafruit_httpserver/server.mpy | Bin 0 -> 2243 bytes src/lib/adafruit_httpserver/status.mpy | Bin 0 -> 486 bytes src/public_html/index.html | 3 + 20 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 requirements.txt create mode 100755 scripts/deploy.sh create mode 100755 scripts/generate-single-certificate.sh create mode 100755 scripts/repl.sh create mode 100644 src/certificates/certificate-chain.pem create mode 100644 src/certificates/key.pem create mode 100644 src/code.py create mode 100755 src/lib/adafruit_httpserver/__init__.mpy create mode 100755 src/lib/adafruit_httpserver/headers.mpy create mode 100755 src/lib/adafruit_httpserver/methods.mpy create mode 100755 src/lib/adafruit_httpserver/mime_type.mpy create mode 100755 src/lib/adafruit_httpserver/request.mpy create mode 100755 src/lib/adafruit_httpserver/response.mpy create mode 100755 src/lib/adafruit_httpserver/route.mpy create mode 100755 src/lib/adafruit_httpserver/server.mpy create mode 100755 src/lib/adafruit_httpserver/status.mpy create mode 100644 src/public_html/index.html diff --git a/.gitignore b/.gitignore index a0090b4..be350b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ # macOS .DS_Store + +# Python +__pycache__/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..000811b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingImports": "none", + "reportMissingModuleSource": "none", + "reportShadowedImports": "none" + }, + "python.formatting.provider": "black" +} diff --git a/README.md b/README.md index 0c3b29e..5cd9ee6 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,39 @@ # CircuitPython HTTPS Web Server (for Raspberry Pi Pico W) -> Note: this example isn't complete! There is an issue with the TLS library, perhaps an incompatible certificate. Help investigating is appreciated! - This is an example of an HTTPS web server written in [CircuitPython](https://circuitpython.org/), intended to run on a [Raspberry Pi Pico W](https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html#raspberry-pi-pico-w-and-pico-wh). [Adafruit](https://www.adafruit.com/) (the makers of CircuitPython) and the CircuitPython documentation have [guides](https://learn.adafruit.com/pico-w-http-server-with-circuitpython/code-the-pico-w-http-server) on running an unsecured HTTP server but none on serving content over HTTPS. This example will show you how to run an HTTPS server from a Pico W. +Note: the server is very slow and takes about 6 seconds to respond with a tiny HTML file over HTTPS. In comparison, responding with the same file over HTTP takes 75 to 350 milliseconds. + +## Getting started + +1. Follow [Adafruit's guide to connecting your Pico W to Wi-Fi](https://learn.adafruit.com/pico-w-wifi-with-circuitpython/overview). Make sure you can run the the basic Wi-Fi test successfully. + +2. Clone this repository to your computer: `git clone https://github.com/ide/circuitpython-https-server.git` + +3. Copy the contents of the **src** directory to your **CIRCUITPY** volume. On macOS, you can run `scripts/deploy.sh` if you have [`rsync`](https://formulae.brew.sh/formula/rsync) and [`circup`](https://github.com/adafruit/circup) installed. Make sure your **settings.toml** file on your Pico still has your Wi-Fi credentials you configured when following Adafruit's Wi-Fi guide. + +4. Connect to your Pico W's serial console. See [Adafruit's guide](https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console) on how to do this. On macOS you can run `scripts/repl.sh`. Reload the code running in CircuitPython by entering Ctrl-C in the console. + +5. The program running on your Pico W will start a web server and print two URLs, one with the Pico W's local IP address (e.g. https://192.168.1.2) and one with its local mDNS hostname (configured to be https://picow.local). + +6. Run `curl --insecure https://picow.local` (or specify your Pico W's IP address). After about 10 seconds, you should see a small HTML response. + ## Why HTTPS for Pico W? (A better user experience for IoT web apps) In the context of a Pico W serving content to your local network, the main motivation for HTTPS is to enable [web browser features limited to secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts). These include [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), which are needed to implement websites that work offline or use push notifications, two common features you might want in an IoT application. -Imagine you're at home and you visit your Pico W's homepage from your web browser. You add the web app to your home screen and your phone presents the web app somewhat like a native app with a home screen icon and its own entry in the task switcher. The web app lets you subscribe to push notifications from your Pico that you'll receive even when you're away from home. And, the web app also loads in "offline" mode when you're away from home and can't connect to your Pico. This is what the user experience should be like for web-based IoT applications. +Imagine you're at home and you visit your Pico W's homepage from your web browser. You add the web app to your home screen and your phone presents the web app somewhat like a native app with a home screen icon and its own entry in the task switcher. The web app lets you subscribe to push notifications from your Pico that you'll receive even when you're away from home. And, the web app also loads in "offline" mode when you're away from home and can't connect to your Pico. This is what the user experience should be like for private, web-based IoT applications. -The secondary motivation for HTTPS is security. The threat model of your Pico W accessed from your local network is different from a web server accessed from the internet. Your Pico W is already protected by your router and only trusted devices with your Wi-Fi password or physical Ethernet connections can access it. However, defense in depth is a good security principle and HTTPS prevents even your trusted devices from sniffing or tampering with traffic to your Pico W. +The secondary motivation for HTTPS is security. The threat model of your Pico W accessed from your local network is different from that of a web server accessed from the internet. Your Pico W is already protected by your router and only trusted devices with your Wi-Fi password or physical Ethernet connections can access it. However, defense in depth is a good security principle and HTTPS prevents even your trusted devices from sniffing or tampering with traffic to your Pico W. ## Goals and non-goals -The main goal of this repository is to show how to set up a web server that serves content over HTTPS and runs with CircuitPython on a Raspberry Pi Pico W. It's intended for a small, private home network. It uses self-signed certificates and requires installing the CA certificate on client devices. - -There are also several non-goals of this repository, which help keep its scope small. The example server targets only the Pico W and not other boards that CircuitPython supports, though it might happen to work for them, too. C -4096-bit +The main goal of this repository is to show how to set up a web server that serves content over HTTPS and runs with CircuitPython on a Raspberry Pi Pico W. It's intended for a small, private home network. It uses self-signed certificates and requires installing the CA certificate on client devices. +There are also several non-goals of this repository, which help keep its scope small. The repository provides an example, not a Python package. If support for serving content over HTTPS with CircuitPython is actually important, it probably makes sense for Adafruit to steer developers towards a package they provide. The example server targets only the Pico W and not other boards that CircuitPython supports, though it might happen to work for them, too. + +## Things to be aware of + +This example uses a 1024-bit RSA certificate for performance. With a 1024-bit certificate, the server responds in about 6 seconds, while a 2048-bit certificate causes it to take about 9 seconds. However, 1024-bit certificates are considered cryptographically insecure. This said, the primary motivation of this project is to enable web browser APIs that require HTTPS on Pico W devices running in a private, local network that is already protected. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aa35842 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +adafruit_httpserver==2.3.0 diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..393f729 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_directory="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +project_directory="$script_directory/.." + +if ! command -v rsync &> /dev/null; then + echo 'rsync is not installed on this computer. rsync is used to copy the application code to your device. Install it using the package manager you use with your OS.' + exit 1 +fi + +if ! command -v circup &> /dev/null; then + echo 'circup is not installed on this computer. circup is used to install CircuitPython dependencies on your device. Install it by following the instructions in the circup repository at: https://github.com/adafruit/circup' + exit 1 +fi + +echo 'Detecting your CircuitPython device...' +device_path=$(python3 -c 'import circup; print(circup.find_device())') +if [ "$device_path" == 'None' ]; then + echo 'circup could not detect a connected CircuitPython device. Make sure your device is flashed with CircuitPython and connected to your computer with a USB data cable.' + exit 1 +fi +echo "Found device at $device_path" + +echo 'Copying application code...' +rsync --verbose --recursive --delete --checksum \ + --include '/._*' --exclude '/.*' \ + --exclude '/boot_out.txt' --exclude '/settings.toml' \ + "$project_directory/src/" "$device_path" +echo 'Finished copying application code' diff --git a/scripts/generate-single-certificate.sh b/scripts/generate-single-certificate.sh new file mode 100755 index 0000000..82ad336 --- /dev/null +++ b/scripts/generate-single-certificate.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_directory="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Derived from https://www.linode.com/docs/guides/create-a-self-signed-tls-certificate/ +openssl req -new -newkey rsa:1024 -x509 -sha256 -days 365 -nodes \ + -subj '/CN=picow.local' \ + -out "$script_directory/../src/certificates/certificate-chain.pem" \ + -keyout "$script_directory/../src/certificates/key.pem" diff --git a/scripts/repl.sh b/scripts/repl.sh new file mode 100755 index 0000000..54a68fa --- /dev/null +++ b/scripts/repl.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +device=$(ls -1 -t /dev/tty.usbmodem*) + +# Reattach to an existing session if one exists; otherwise create a new one +screen -D -R -S circuitpython "$device" 115200 diff --git a/src/certificates/certificate-chain.pem b/src/certificates/certificate-chain.pem new file mode 100644 index 0000000..4e55e7b --- /dev/null +++ b/src/certificates/certificate-chain.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAQwCCQCwlOkfTi8J9zANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtw +aWNvdy5sb2NhbDAeFw0yMzAyMjgwNTQyNTZaFw0yNDAyMjgwNTQyNTZaMBYxFDAS +BgNVBAMMC3BpY293LmxvY2FsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq +UQiuhGBzHCLaesEjk6e6Kx7iNAKneaPht0Q6TsvF+IgOpuQFNkvQTU5S7POKDl2c +UGkMT9Q1x4xRdM3u3qT3OmutyAtfL94RkK1qsblJwIH8xop5p7P+gwHTT4UOEicj +MOSYBmkI6YvO/QImGdEoezWBqxKjOzyYLnv4UrwtYQIDAQABMA0GCSqGSIb3DQEB +CwUAA4GBABuzaLw1tcFNYZ0ViWnii1j97otemOFkM31TW8Ohm3zRS6f/aQi+hujN +BYMWIhxlK2cr0zsN+mXCVZN5u2BY0Q/w/7cNMBPNcGG7dJCGoBJRKRbEoBb/AMaO +vjp0giQ8kFOtRcf0Gun5gPdDcmZayZwU1n/V1Q7UjwKXcqe0+eXu +-----END CERTIFICATE----- diff --git a/src/certificates/key.pem b/src/certificates/key.pem new file mode 100644 index 0000000..cee4c29 --- /dev/null +++ b/src/certificates/key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOpRCK6EYHMcItp6 +wSOTp7orHuI0Aqd5o+G3RDpOy8X4iA6m5AU2S9BNTlLs84oOXZxQaQxP1DXHjFF0 +ze7epPc6a63IC18v3hGQrWqxuUnAgfzGinmns/6DAdNPhQ4SJyMw5JgGaQjpi879 +AiYZ0Sh7NYGrEqM7PJgue/hSvC1hAgMBAAECgYEAvZ05s0/4ZO493iM8LDgOoP7I +DTEdfL1Yuw19LtoY2GmYYJL5LqaTj0sfuMd7BRs+8YG4oHfxOFv01u34v/Z38vRa +E0tzSMVz29CRF0LUcko7c8Q2TzeTVsk3tEb3Kuf3tfVh5bhhjSakhUIY7D+Gb6LT +GRIUg72tP9wjayTDsCkCQQD6oyJqxUhxuuiL5D+e0ssp582YruEJK4f5CzRsjmVV +COBbqzRkIMCxPMCJCB+DbiMPlu/6CuxGh6i8rczDlkdfAkEA71SAt9PFx3hLW0hu +OBR9w7BF6B2YTR3cG5+SQXOiF1feMUIaAn/dDr1OxqAQ/hQ/QKUk5JDwquy6phYT +9qWDPwJBAPMqGLccBkgI/ZrTbIILov5aHdcXO88ow7f0jf0QPfG9NebZ+G94c1rB +RU7taZ2a2jtCxjqCJG/dJ/E+cZ4Ei+MCQFu3O3i2/FU7wU0jDbIKEEQc2j1gkgwD +hGVFmovgn15ouuqPlV4d1/4dCAJQNxLXeYHxh5jb/o7SF5ksXswnk4sCQA7Jlj5M +8+7qQV9DrOieFGpXMqlc9J6u9L0Tev2P9uE7a7z61NvLIdskGDpiaYOmADob5nHo +Duv3lSMQg6ql7EE= +-----END PRIVATE KEY----- diff --git a/src/code.py b/src/code.py new file mode 100644 index 0000000..47f3341 --- /dev/null +++ b/src/code.py @@ -0,0 +1,86 @@ +import os +import ssl +import time +import traceback + +import socketpool +import supervisor +import wifi + +from adafruit_httpserver.server import HTTPServer + + +def main() -> None: + print("Connecting to the local Wi-Fi network...") + wifi.radio.hostname = "picow" + wifi.radio.connect( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") + ) + print("Connected to the local network") + print(f" IP address = {wifi.radio.ipv4_address}") + print(f" Router = {wifi.radio.ipv4_gateway}") + print(f" DNS server = {wifi.radio.ipv4_dns}") + + host = str(wifi.radio.ipv4_address) + pool = socketpool.SocketPool(wifi.radio) + http_server = HTTPServer(pool) + http_server.start(host, port=80, root_path="public_html") + + ssl_context = ssl.create_default_context() + # The Pico is the server and does not require a certificate from the client, so disable + # certificate validation by explicitly specifying no verification CAs + ssl_context.load_verify_locations(cadata="") + ssl_context.load_cert_chain( + "certificates/certificate-chain.pem", "certificates/key.pem" + ) + tls_pool = TLSServerSocketPool(pool, ssl_context) + https_server = HTTPServer(tls_pool) + https_server.start(host, port=443, root_path="public_html") + + print() + print("The web server is listening on:") + print(f" http://{host}") + print(f" https://{host}") + print(f" http://{wifi.radio.hostname}.local") + print(f" https://{wifi.radio.hostname}.local") + print() + + while True: + http_server.poll() + try: + https_server.poll() + except OSError as error: + if error.strerror.startswith("MBEDTLS_ERR_"): + print(f"TLS library error {error.strerror} with code {error.errno}") + else: + raise + + +class TLSServerSocketPool: + def __init__(self, pool, ssl_context): + self._pool = pool + self._ssl_context = ssl_context + + @property + def AF_INET(self): + return self._pool.AF_INET + + @property + def SOCK_STREAM(self): + return self._pool.SOCK_STREAM + + def socket(self, *args, **kwargs): + socket = self._pool.socket(*args, **kwargs) + return self._ssl_context.wrap_socket(socket, server_side=True) + + def getaddrinfo(self, *args, **kwargs): + return self._pool.getaddrinfo(*args, **kwargs) + + +try: + main() +except Exception as exception: + print("".join(traceback.format_exception(exception, limit=8))) + print("Reloading in 3 seconds...") + time.sleep(3) + supervisor.reload() diff --git a/src/lib/adafruit_httpserver/__init__.mpy b/src/lib/adafruit_httpserver/__init__.mpy new file mode 100755 index 0000000000000000000000000000000000000000..4a3525c0276d0f93f97b1c1e800196748d50837b GIT binary patch literal 150 zcmZ=}Ws+A&U=U(pw@XY(Oe-qQEQ!x3DJdvUEhlIWcGzh6N2yhwc z8S5E{iN(hQrHeE3^Wx)`8N>wQFcLwmSmJB=_Tjq>cb4z ecZAvEoLQ6%v@M{rBm-!QM@UFOFxXB#pe_K}&@x8= literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_httpserver/headers.mpy b/src/lib/adafruit_httpserver/headers.mpy new file mode 100755 index 0000000000000000000000000000000000000000..1e1681e6ebca05f1de515aa192bced5a668c623b GIT binary patch literal 891 zcmah`T~E_c7=GGPyD{iG+8jWdFfj207tsSQUJyeRb$rA)88bvoSGSH8VOzFhAV@qj z-jEpn0tx(q&0DYVe;Dum6`p>GAzs*%=A7hxzRq(V%i?(YlbIQTGG|xpYHQc^teWRF z+fM7f)0(b1cExG6?=<&IrP8(WrxoN(FfF#=mOZV%u-n{rro^!UZ@=l*H%*xc{=dF7 zh*v3G|GCh|)|UjlDNtx6S17#5g>thZ5%OgSpUv335dWgF@p-r8rrop?t`gsa4=D^2 z5KSVunx7I@XF!k&4gx_jIEVztl+UZQk6??+1ZVUL!3k9e7HNziN#g`2?I%dk1c6Qm z2n;$%Ml7q`sC%|sZ(9~s3C4J&WG8kiXh_Ly<{5fVM3%uh6?+D%ScZj^|mk3$YOf*(XWqX0CK1A8}}&+ z>_Hk`T`^D$5qXZyik&qdbQG)GJC0g$s`l=-7c!0&HWx9DLx!(@!BCXFQ4Ljv&8IPh zl7R@YmBj_%Ok%lz02~(pKF}eYNNgp2gGcB5lfMmRg`k&& zUr0h>&;M7$-h_rlfxYBj$slh$iZjcFcmo@gDv~lQ`bOb(*P?C)YcncLnXSVwpsUN_ zxX2%d^JJWsI5i=&4Ni?_tFQk|y$Kb%%q-r~W?(B{wp~f7FeLVt&);$9C`@fqF*Z|k_!oPbOtHnbN2W1b9Dyl I1lr6103XaL82|tP literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_httpserver/mime_type.mpy b/src/lib/adafruit_httpserver/mime_type.mpy new file mode 100755 index 0000000000000000000000000000000000000000..42644490dbabe60719d4ffc688f35d3fc8b7607d GIT binary patch literal 2639 zcmb7G%~IP&9G9UfY0?DYI1bH6q1{5764^5jlU!qLV@xnsu)r?U8CXjzvqaKrw2N`L zG`;uG&h!C#?x}r*zEYo{yV91 zQWQ;4RQ0isj@a7}Rl>K&@8&eCrs;aO(`{LtTc0TxUfWvX7w!5VGlkioZyqnNu82SJ zxw^Wt+=~CpQ%tQ2K9R1*DAq~T_?dGF&6$?ApxrBLWqr1+-Peoo%)z7zprWCZy{SENB4BkFe z$&IlqmD`<7y%@@XKG7=vG~X`I7u;20%vWttmioP>3GSu14;E8wsRbfq z6}{I^BWB41@P)E!4^`46JfkvT&)nw&cS*pigsJiLEIyv3v0aw%R(LD#^FJPpOo0IM>?;Z7esmQFby zf^U_8D?L$oe+PUg@nOdVYqBRV4i~iFYERb-<%e!W{3928=qP|bdTl=~(7P}UJ#0gU zNl={{aD>lE{N#InV4=+eiK_f!m(8a8j*bFg)vIs$+ z7PFG6F}lDZz4Ja-qYHV`rYCt@pT_yNvuYv_5~5)0doGE5$f&{Fk8R`-`^-lHs|cGg zA~vEF2V>u>C<%RZ)sasPF)K1UMK+_QtQdu7qcxGao@K{^Sxl~y$i&BXL@04t1=}Q` zx02TvdMI#UlyA`~oIr#3`2HsN?| zd<#$o2b4k2L(v*<9j#tfB9LMlcUHBl5=MyfhQ{kdD<5G%xdn9S2||oM{s?{11UjEp zm>-6474h-rGLM*> zAL7*D>wGI&6*?KmUC%9`P$2dj{wyKG^&r>2GEBx})Da)CV$R;Fi3Iynn*a~@YpX;SAC}Pyzt|GN&Ee|_Rrr%ZBR^q|NT`g+YipV= Jo<;F0{tNu&QQ-gp literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_httpserver/request.mpy b/src/lib/adafruit_httpserver/request.mpy new file mode 100755 index 0000000000000000000000000000000000000000..46ebd255567707c5b6ed96f5633b474b5266cfd5 GIT binary patch literal 1038 zcmYjQ-*4Mg6h7C9n~){LZXMVCA?lH>+d|Za5Cxb9L?vTci>ihu8$(QvW8acMVte*2 zQ=WPw`qBv@A^1a_W^3WG&C13z65?gs6aPT}0CpYq&-x*N~D z1(EBzsc*U4Euu9EaXjpPkKJV#-`~a_S#rAddOfv}%6?sPpnLi4wE#RL-R;REFx`=A{(~dW>IO3JItF8F?=-v zcSKlxSqjs;s|fD(1i4(TZj`ZZV%K8@=bK}5lwHli82@5SHqXhZVgUN#%jgD5{wURp zYF$<78)}g~G{!#6t=IQvzb$suhp~s#Ai}f^vJjGCwvtAh?|}@WC^)X|V3%}rD39c5 z!#2Ajnu>dv5bWkS77K}dF2w$CkJ)HJiq_~7?1}JeE*v15qq`o~JfgcqYqu;MAUs3n z&ycf&FuoJJ(m#=lAihc0FI~E^SiYI67eD+8`o1C=wq;?1uomz8%DmBTVT)+GX}Z|+ z2EHP@`UCAusX^sMDM}~d{S@0D`%?*8ds4m(zLa=WK-sXb#5Gk+s&7ftjbp|tg3Kl5-z?hP1BQG=O9>~ALO~mTb0Vm75-kJQN`c@ z_L9k@H~ShL)zB@=CTat#>$ZixnVL1~h~8*p^|icfYDpC4W~ixxKQ+^<8Ne26&YtIa z+F}u&)>7PQ|I}9^6$3FS(g-JFiCAyrU73&2PBD}IoTWu1*i4}UI`XGbrN6}0j+xQl zJ*_ZvxIaj9quO!lZhlW#oZ;z%VrA{>XX~k@D@!+Tu|CC3Y}h6iqT6Kag94Hy&uO(;eY`G>J_*wH$2I}ACCi{TPz@L?J_(S zxQ`B>my-1G|Eyv8L{_@q8)18$UJ3+(w{{X_@Eqnj~ literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_httpserver/response.mpy b/src/lib/adafruit_httpserver/response.mpy new file mode 100755 index 0000000000000000000000000000000000000000..3e8ff22d70dcd65b3fd666ad728b91213cf61b11 GIT binary patch literal 1909 zcmaJ?%}*Ow5PxrN2jke`d3RYHAjOF><{JlGM`@*q7(09<0SuAsd~zxN~JbZB@UIU9(t6P*v(`S9)(|-^_33 zH#2X@!L|55>+WtxH5W1qnZ?pdPM4Q-y{OUBDlPSus8&=JjrJDT=jP@DE#2X3@%D5$ ze7=ZA#t|cAqNwLoC6g!4crL3G*Ng&N;!3R)^R(06T-A+?u63;>@x)LNg1rq?z`Xs!PjWsvi+w z)n7Ha5l+I%EW)3L{c+s$q>+XEgMN5e?VQdUHcmfjyvh7u5<2+EiM*&mLK3k_sL@+1 zxF;^l{o!yjNr-8%?`-ec$xg@g0Fsb5ne61I2e8yAYgAc~4-gQ9EjcHl4I<{01-jO| zq!;qU-rINPbJS^@9r|^3xo*uAEq9w{MttiX=4hgyYk4ebmMbG+cB3P2D zF6P&DszD&mhwa}zV%U$o_3R_&3cl}`gTWvw8@|&#%2fDM5e`_a#A+CiE7R{H!`A}x zO-o+OHbpcVMX_U`+>Bt*Z5cjaR#kMW=oV$$us2Ioh62@~+MXM}6BaPKFlqR@<-?kl zGx-vvecc4711yBT@YZ?Yc})IdxVNRAX>)^P{J_T2~M4^=~3G*fPEUURT3U%2toodpA|?dJ(-YGAEu&Q+Ef#9 zNL3V?HT#61VUQZa$#|U^=hJ|lU=YMy)t#eUJ+l}^@?{Z)xxJTLgxtCN9w4z`Y(I~3 z`@6AU?`;vg$F;v}h#^BbEr^hw0}orGn<^$sg1(BHwv$;K1m1j@z&urM=t~Wn8$g*E z?J+w-^QKA}MO&n$o`iWAI#xSz_`?w|oC#GQ;s z{Q+A9Qy$HhNo_J>i+fmE#1@2|7{|&xhS))BD+xQK36tYIS_W#PpQ5xOdJVxVcsBv* zl;NvOW-*|3&Z`US9_{4R!SwOp%4q%rHvJ}>kDGscgnI|-+1IQlG}7G`xPAUo=%&9f zZ~+k29w4#Tt(5R<)W9?{`N8vZ|7MAA! z)tf*o>Ei#>^g`qKL`uFiF*6>QBjHHo0T$6l6hXGl*<^?&U^u-kqPv?>Jo|EJ|6cOibx2H0H8I4r zWt!qks;bMyjJ_nzuPg#_vX;9||0iP)KKTYW9%Qz zh+Gf?9B@^R;6DMCAvz4fAy`FvxDME?661h0m(_#w_TZYwHP3lmc`uLL$JtMz5O8uX zUIP&ZAVY@Li1*tKI}8v9`y~_$b)s_e=T~u#O=iIXz<{cN!4t*lDeLEEx|XHZJ!AF% zJ8!qEnfwZEd%M?5nJk^pWS1YNe&qc19`^g0P$&@ixU01bu@}`)c6hNs3up!T74z@RX3T+G3`-DHZY+l0v8}u=5Td?s*?w!{V eL^7L9?zKCvn}tx>{9$PGo;iD9awv+s%GKY^$R&{g literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_httpserver/route.mpy b/src/lib/adafruit_httpserver/route.mpy new file mode 100755 index 0000000000000000000000000000000000000000..9eb6da87e766b88edb831ed4485b7ba3f318d275 GIT binary patch literal 338 zcmX|6%Syvg6g(#}-d1Y3N$NE=iUAR95rr7UjZhFD8^K35y15DMmAH_m>8&6tB<=+N z!XHTe8-K!YFipkP49uLFIY*kZ?&dBvfgD8r=q?@KOCC#^j70h<(%p10mSQJ)3d1mg zoHoyb;No1!c+l^lqd!z^w5l?^2|S=pKGm^5+7rs}H6UW4^ED6=5rV(|WblfxqSL2qKk2*vt*fg>w@H--r5%$Pmx=MT_n7IdRVtPEM11zO+(dR3J{`1>M;6Ow z)mBTbwg}5E8#>i%dW#L7)0(=L^z9p?Yn`TcF*YLN?O)kq$4tF;iO?k8hhtd8AQ6hL zWLei+AQe1)2i{^n&@YA1D-ksLFgIkCmoT~jJLKn1&Mlr{g?#qxnKLW-V!p(3`O@O) zeD3VI5(Ews!_f5Yn9j4j&I${q((-9S8+sk0gxl`QbVIDOqN7|PZ*+<2APXDba-fZR zTC<~TCIK~T5N|!u3yN!?cd4Td8qQ*%lCpZct!s|HNLAW3!C=gESFS}Vg*;?M_r7ID z#DyBd8o=6^{9^J6BVlo+W1nD`?8 z)F?b-UTPKEY!kR6uo_7P9X%ZRvGvAy3`L%>o~&f16O)Mq;!x(m|B;OCXn3&qx*cV= zou`oRqEo3U)N;N?0^(M>#{+snStt1L7g{&iEs?tu6c^ zekx)mt$DZfPnBj4qb)n?v-t^OKrfs3 z@!`3bl*KdolCu_zXS1i2VreBmcRJ13(S*%oAy92<_19+zJ|T$UkO^D??UU$qwEs3&kxR+p8gMs=G% z%ArdTOvY*E&L%7hTQkhL-)=8GKR9a4y1U4tKJSs1+adA;V0lkZa>$~Rhx{1Y#vHQw z&<@nQF4X?tq22?iH^DCCf;-}XyLZUH8{9nrcN4(5VgLtQZpwJ;=j{7eHr=DUr56I$ zxWua~I|Pf>bPcYbDlMX2q>Xo_kGS)PBi4E)6F-u;)Hpc5m?CXy>J4c&KOJvKa(a2{ z($qvXbLzmU1889gyXjYJHPWGJG|PN>yLa*rRPG6aLq(5rHvboaoFofR_R{#yR+g~} zHEwqggu0Y{cRBQWpAUPj!4%N>IJVM^jPLdNfP?xgTZs2QxtaF$ozgo22HXiyIZWyE z1FpzSA@6ARV%Dv`jKATK`YHEFYn2%&d=91J5QIQpz%BQZ)#rWO zuIBBwx(B)AARh%_tXJ_TNuz;=sl?$(3 zSbAnq;)ks77Gvmh{scD|T@P~3jeqVOY-fgf!g`_b-J#$GhxvsX1NCf%vtC&odD$%s z^Mw^cyM_j&lvGXHJ({FdLvKr}P@AjK2wTtd!Ds!TVbFq@N&Dg z%Gj;T(sab}eJTle-`_hA#8Kju2Fmy!Z_{(cPj4~gwT>2Sf- zmafty^uk=&>RI<8xzM6V=A9fLb8}p98H+wPpK}MKI|64O9THNzYf`C7;8-2qY*H9j RlGbe#qgj(KtF11X`4iJjnV|px literal 0 HcmV?d00001 diff --git a/src/lib/adafruit_httpserver/status.mpy b/src/lib/adafruit_httpserver/status.mpy new file mode 100755 index 0000000000000000000000000000000000000000..0dc04dd8b0526cbb4e7d59a8d363ea48ddb106de GIT binary patch literal 486 zcmYk3?@QZ27{{OF3|&-evc|KttHu;T!5<~mz1XX0O|f(&Ha&fN4b4dfZ8W)#NmocG zdoku8Q}^Ha-*_#933uG_{XCzC`_WM3Epz&*Xh7~u7=_o#=!Vk)=l3Z~zOm#eM;a2YKp15h>3r&M%cQ&|w1u z))0o04H$G=!cewl3>6#URZ7_drNUb3RSnRUEZ=i>;AK|)(2pbbSFD_U=Yj+LMkw0O zLs`i-peh+aW(7rz&vIW9FnxE%-SK4Kw8j%_Zy3^8rhK5^Zx$ecpBsorC#&eSaS=M&oWFTFESP@k*3Dv8`~dBhF8 yldrVv23KxC-5~G-dR(h@`ZA;|r(t9U>|w-Gp02lsoF&8X*6c1m&~%d|PTm0Q7IvWk literal 0 HcmV?d00001 diff --git a/src/public_html/index.html b/src/public_html/index.html new file mode 100644 index 0000000..73fb978 --- /dev/null +++ b/src/public_html/index.html @@ -0,0 +1,3 @@ + +Hello world · Raspberry Pi Pico W +

Hello world from Raspberry Pi Pico W!