From 46fb1e62edd03442ad6adfb3e8beb42239fa7dc8 Mon Sep 17 00:00:00 2001
From: James Ide <ide@expo.dev>
Date: Sun, 5 Mar 2023 13:35:55 -0800
Subject: [PATCH] Add small site with a service worker, make MQTT optional, use
 HTTPS (will abandon)

- A small website registers a service worker and subscribes to push notifications
- MQTT is now optional, set `MQTT_ENABLED = 1` in settings.toml to opt in
- Configured the web server to use HTTPS. However, even with a small 1024-bit RSA certificate, performance is too slow (~10 seconds per response) and the service worker doesn't even load. Going to deploy this to the cloud instead eventually, and have the Pico just receive push subscriptions and send push notifications.
---
 .prettierrc                            |   6 ++
 assets/icon-1024.avif                  | Bin 0 -> 15296 bytes
 requirements.txt                       |   3 +-
 scripts/certificates                   |  11 ++++
 src/boot.py                            |   9 +--
 src/certificates/certificate-chain.pem |  12 ++++
 src/certificates/key.pem               |  16 +++++
 src/code.py                            |  79 ++++++++++++++++++-------
 src/public_html/icon-192.avif          | Bin 0 -> 4474 bytes
 src/public_html/icon-512.avif          | Bin 0 -> 11545 bytes
 src/public_html/index.html             |  16 +++++
 src/public_html/manifest.webmanifest   |  21 +++++++
 src/public_html/script.js              |  16 +++++
 src/public_html/service-worker.js      |  22 +++++++
 src/public_html/style.css              |   6 ++
 src/tls.py                             |  48 +++++++++++++++
 16 files changed, 238 insertions(+), 27 deletions(-)
 create mode 100644 .prettierrc
 create mode 100644 assets/icon-1024.avif
 create mode 100755 scripts/certificates
 create mode 100644 src/certificates/certificate-chain.pem
 create mode 100644 src/certificates/key.pem
 create mode 100644 src/public_html/icon-192.avif
 create mode 100644 src/public_html/icon-512.avif
 create mode 100644 src/public_html/index.html
 create mode 100644 src/public_html/manifest.webmanifest
 create mode 100644 src/public_html/script.js
 create mode 100644 src/public_html/service-worker.js
 create mode 100644 src/public_html/style.css
 create mode 100644 src/tls.py

diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..ee33faf
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+  "printWidth": 100,
+  "tabWidth": 2,
+  "singleQuote": true,
+  "bracketSameLine": true
+}
diff --git a/assets/icon-1024.avif b/assets/icon-1024.avif
new file mode 100644
index 0000000000000000000000000000000000000000..31a20b087a5e66cf844b99736cc4ef986d7e85a9
GIT binary patch
literal 15296
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

literal 0
HcmV?d00001

diff --git a/requirements.txt b/requirements.txt
index 3b1c8ac..1c3ed38 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
 adafruit_debouncer==2.0.5
-adafruit_ticks==1.0.9
 adafruit_logging==5.2.1
+adafruit_ticks==1.0.9
+adafruit_httpserver==2.3.0
 adafruit_minimqtt==7.2.2
diff --git a/scripts/certificates b/scripts/certificates
new file mode 100755
index 0000000..467785d
--- /dev/null
+++ b/scripts/certificates
@@ -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 36525 -nodes \
+  -subj '/CN=garagesensor.local' \
+  -out "$script_directory/../src/certificates/certificate-chain.pem" \
+  -keyout "$script_directory/../src/certificates/key.pem"
diff --git a/src/boot.py b/src/boot.py
index ca15b5a..0287c12 100644
--- a/src/boot.py
+++ b/src/boot.py
@@ -1,8 +1,5 @@
-import os
-
 import storage
 
-if os.getenv("LOG_FILE"):
-  # Allow CircuitPython to write to its flash storage
-  # https://learn.adafruit.com/circuitpython-essentials/circuitpython-storage
-  storage.remount("/", readonly=False, disable_concurrent_write_protection=True)
+# Allow CircuitPython to write to its flash storage
+# https://learn.adafruit.com/circuitpython-essentials/circuitpython-storage
+storage.remount("/", readonly=False, disable_concurrent_write_protection=True)
diff --git a/src/certificates/certificate-chain.pem b/src/certificates/certificate-chain.pem
new file mode 100644
index 0000000..c8f077c
--- /dev/null
+++ b/src/certificates/certificate-chain.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIBszCCARwCCQCj+re8WZ4+aDANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJn
+YXJhZ2VzZW5zb3IubG9jYWwwIBcNMjMwMzA1MTk0ODIxWhgPMjEyMzAzMDYxOTQ4
+MjFaMB0xGzAZBgNVBAMMEmdhcmFnZXNlbnNvci5sb2NhbDCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAxSb4Ao/A6wxE/a4w9YOkHY+mzmnO9naqE/DDasJYQO5+
+MgLnpDgOcStQojfTJHHu8V2o0TqSadYZuY75c6A1oji0kG5CcpYOyhtpKdKjvEQ1
+3WPt+vBOCU3mmL1W7pS1RiNy3f/g3xC7qPEvW6nZZonafCO5f9edupNsBfbgSDUC
+AwEAATANBgkqhkiG9w0BAQsFAAOBgQAP51g4jkm2YQ3ADmNesKewHK2vSw248PS3
+SNAaxaj0n+6PEUZO/COOD5Of6yHZVDa4DAxKdGUdL/YIBkf8mceLfzNFbW8Le+t5
+OQ/YNFMXLobDf7HRkVzIfVXAwtoJqUY/DSEI0cMfpHIcNC4ThYgdreiANn2f1WDK
+YDe8O9X5UA==
+-----END CERTIFICATE-----
diff --git a/src/certificates/key.pem b/src/certificates/key.pem
new file mode 100644
index 0000000..e88c972
--- /dev/null
+++ b/src/certificates/key.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMUm+AKPwOsMRP2u
+MPWDpB2Pps5pzvZ2qhPww2rCWEDufjIC56Q4DnErUKI30yRx7vFdqNE6kmnWGbmO
++XOgNaI4tJBuQnKWDsobaSnSo7xENd1j7frwTglN5pi9Vu6UtUYjct3/4N8Qu6jx
+L1up2WaJ2nwjuX/XnbqTbAX24Eg1AgMBAAECgYAiN0cnuqcyo+h9VnPsyDH9Z2b9
+v+NJZwLRfyGLL7t9WWbRayuklo37Ghdeb+3XD2b2wNiBp3ato5jHWYb1iEKGXPGg
+biF4gYAasjB3HiauOeG3UNLOM27+yI04K6XQZSV3s6CrM+C1ILWGHU0S7LQxjeBA
+hsMdH18jVjUP1RS6QQJBAOI8pJ4x5fCp7KbnBY6lwBMn5Q2KxAqQHqCL6brrgeqh
+Bzp1fR/OnzsXcn6Cb5BMs4cMWxeV3DyfBTFCwO51wu0CQQDfFssAl84TcRrNyriq
+fuZBth5z9auGmS38FdbrDqXPzgDa2MM4e4/LUYBvF7FQtneiekHXKis5fJlxoytT
+fQlpAkAwSKcNiDK9+VYjjNy3xBJJRFNzX3FVm8qdkx7QIOE6VSG4zUhmGHANaYSr
+EWWEE4qhQPbUAszdN0cha1DH0+RFAkAKz6f212R9PLX30yMv4AZ4mMLRC87MLxAz
+bzuDGKqgb3NLJ8YOLq7BQ6nduGA3cSBLF3GpY7nEh21IPIgU+7JBAkBjyMupNQHW
+HHll7Fa4YtBfEXK2sIB/wvdtroT05s2YM1F2XUwLfLyepSSniyLMVnd+gOZwr4c/
+KRbIBmqaBrh9
+-----END PRIVATE KEY-----
diff --git a/src/code.py b/src/code.py
index 7eeb815..d0e125a 100644
--- a/src/code.py
+++ b/src/code.py
@@ -1,5 +1,6 @@
 import binascii
 import os
+import ssl
 import time
 import traceback
 
@@ -13,9 +14,15 @@ import wifi
 import adafruit_debouncer
 import adafruit_logging
 import adafruit_minimqtt.adafruit_minimqtt as adafruit_minimqtt
+from adafruit_httpserver.methods import HTTPMethod
+from adafruit_httpserver.mime_type import MIMEType
+from adafruit_httpserver.server import HTTPServer
+from adafruit_httpserver.request import HTTPRequest
+from adafruit_httpserver.response import HTTPResponse
 
 from logging import create_logger
 from mqtt import publish_homeassistant_discovery_message, publish_sensor_state_message
+from tls import SSLServerSocketPool
 
 
 def main(logger: adafruit_logging.Logger) -> None:
@@ -27,6 +34,9 @@ def main(logger: adafruit_logging.Logger) -> None:
     switch_io.switch_to_input()
     switch = adafruit_debouncer.Debouncer(switch_io)
 
+    # The switch is open when the door is closed
+    led.value = not switch.value
+
     # Connect to the Wi-Fi network
     logger.info("Connecting to the local Wi-Fi network...")
     wifi.radio.hostname = os.getenv("WIFI_HOSTNAME")
@@ -42,31 +52,59 @@ def main(logger: adafruit_logging.Logger) -> None:
     pool = socketpool.SocketPool(wifi.radio)
 
     # Connect to the MQTT broker
-    logger.info("Connecting to the MQTT broker...")
-    mqtt = adafruit_minimqtt.MQTT(
-        broker=os.getenv("MQTT_HOSTNAME"),
-        username=os.getenv("MQTT_USERNAME"),
-        password=os.getenv("MQTT_PASSWORD"),
-        is_ssl=False,
-        socket_pool=pool,
-    )
-    mqtt.logger = logger
-    mqtt.connect()
-    logger.info("Connected to the MQTT broker")
+    if os.getenv("MQTT_ENABLED"):
+        logger.info("Connecting to the MQTT broker...")
+        mqtt = adafruit_minimqtt.MQTT(
+            broker=os.getenv("MQTT_HOSTNAME"),
+            username=os.getenv("MQTT_USERNAME"),
+            password=os.getenv("MQTT_PASSWORD"),
+            is_ssl=False,
+            socket_pool=pool,
+        )
+        mqtt.logger = logger
+        mqtt.connect()
+        logger.info("Connected to the MQTT broker")
 
-    mqtt_device_id = binascii.hexlify(microcontroller.cpu.uid).decode("utf-8")
-    mqtt_state_topic = f"door/{mqtt_device_id}/state"
-    publish_homeassistant_discovery_message(mqtt, mqtt_device_id, mqtt_state_topic)
+        mqtt_device_id = binascii.hexlify(microcontroller.cpu.uid).decode("utf-8")
+        mqtt_state_topic = f"door/{mqtt_device_id}/state"
+        publish_homeassistant_discovery_message(mqtt, mqtt_device_id, mqtt_state_topic)
+        publish_sensor_state_message(mqtt, mqtt_state_topic, not switch.value)
+        logger.info(
+            "Advertised Home Assistant discovery message and current door state"
+        )
+    else:
+        mqtt = None
 
-    # The switch is open when the door is closed
-    led.value = not switch.value
-    publish_sensor_state_message(mqtt, mqtt_state_topic, not switch.value)
-    logger.info("Advertised Home Assistant discovery message and current door state")
+    # Listen to HTTP requests and serve static files
+    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"
+    )
+    ssl_pool = SSLServerSocketPool(pool, ssl_context)
+
+    server = HTTPServer(ssl_pool)
+    host = str(wifi.radio.ipv4_address)
+    server.start(host, port=443, root_path="public_html")
+    logger.info(f"Listening to HTTP requests at https://{host}")
+    logger.info(f"Serving the website from https://{wifi.radio.hostname}.local")
 
     logger.info("Monitoring door sensor...")
     while True:
         switch.update()
-        mqtt.loop()
+
+        if mqtt is not None:
+            mqtt.loop()
+
+        try:
+            server.poll()
+        except OSError as error:
+            if error.strerror.startswith("MBEDTLS_ERR_"):
+                logger.info("TLS library error %s with code %d", error.strerror, error.errno)
+            else:
+                raise
 
         if switch.rose or switch.fell:
             is_door_open = not switch.value
@@ -74,7 +112,8 @@ def main(logger: adafruit_logging.Logger) -> None:
                 "Detected the door open" if is_door_open else "Detected the door closed"
             )
             led.value = is_door_open
-            publish_sensor_state_message(mqtt, mqtt_state_topic, is_door_open)
+            if mqtt is not None:
+                publish_sensor_state_message(mqtt, mqtt_state_topic, is_door_open)
 
 
 logger = create_logger()
diff --git a/src/public_html/icon-192.avif b/src/public_html/icon-192.avif
new file mode 100644
index 0000000000000000000000000000000000000000..9bdcee6c84d1f5dcde9b393798fa046036900f46
GIT binary patch
literal 4474
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

literal 0
HcmV?d00001

diff --git a/src/public_html/icon-512.avif b/src/public_html/icon-512.avif
new file mode 100644
index 0000000000000000000000000000000000000000..f82377c1e8c5437984d9c102b45f3a5fd26322e7
GIT binary patch
literal 11545
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

literal 0
HcmV?d00001

diff --git a/src/public_html/index.html b/src/public_html/index.html
new file mode 100644
index 0000000..cb5afa7
--- /dev/null
+++ b/src/public_html/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en-US">
+  <head>
+    <title>Garage Door Sensor</title>
+    <meta name="viewport" content="width=device-width" />
+    <link rel="manifest" href="/manifest.webmanifest" />
+    <link rel="icon" type="image/avif" href="/icon-192.avif" />
+    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter" />
+    <link rel="stylesheet" href="/style.css" />
+    <script type="module" src="/script.js"></script>
+  </head>
+  <body>
+    <h1>My Garage</h1>
+    <button id="subscribe-button">Get Notified</button>
+  </body>
+</html>
diff --git a/src/public_html/manifest.webmanifest b/src/public_html/manifest.webmanifest
new file mode 100644
index 0000000..02e946d
--- /dev/null
+++ b/src/public_html/manifest.webmanifest
@@ -0,0 +1,21 @@
+{
+  "$schema": "https://json.schemastore.org/web-manifest-combined.json",
+  "name": "Garage Door Sensor",
+  "short_name": "Garage",
+  "start_url": "/",
+  "display": "fullscreen",
+  "background_color": "#252526",
+  "description": "Check on whether your garage door is open",
+  "icons": [
+    {
+      "src": "icon-192.avif",
+      "sizes": "192x192",
+      "type": "image/avif"
+    },
+    {
+      "src": "icon-512.avif",
+      "sizes": "512x512",
+      "type": "image/avif"
+    }
+  ]
+}
diff --git a/src/public_html/script.js b/src/public_html/script.js
new file mode 100644
index 0000000..c9528ff
--- /dev/null
+++ b/src/public_html/script.js
@@ -0,0 +1,16 @@
+// Keep the service worker up to date
+navigator.serviceWorker.register('/service-worker.js', { type: 'module' }).then((registration) => {
+  registration.update();
+});
+
+// Subscribe to notifications when the user wishes to do so
+const subscribeButton = document.getElementById('subscribe-button');
+subscribeButton.addEventListener('click', async () => {
+  const serviceWorkerRegistration = await navigator.serviceWorker.ready;
+  const pushSubscription = await serviceWorkerRegistration.pushManager.subscribe({
+    userVisibleOnly: true,
+    applicationServerKey:
+      'BP1ZvYiUs2YDFtbJu2weWZdBS4DFA0kKw3pFK6iMVV7zue-fmg_gJM8lEshHkbJO5KkiO8wYh15Xn8y0BZNpRCs',
+  });
+  console.log(pushSubscription.toJSON());
+});
diff --git a/src/public_html/service-worker.js b/src/public_html/service-worker.js
new file mode 100644
index 0000000..b1e2819
--- /dev/null
+++ b/src/public_html/service-worker.js
@@ -0,0 +1,22 @@
+// Keep the service worker up to date by letting the most recent script become the active service
+// worker even if another is registered. This is acceptable since there is no API contract that
+// could break between the web page and this service worker in the event a newer version of the
+// service worker becomes active while the user is viewing an older version of the web page.
+self.addEventListener('install', (event) => {
+  event.waitUntil(self.skipWaiting());
+});
+
+self.addEventListener('push', (event) => {
+  const message = event.data.json();
+  console.log(message);
+  event.waitUntil(
+    self.registration.showNotification(message.title, {
+      body: message.body,
+      icon: '/icon-192.avif',
+      tag: message.tag,
+      timestamp: message.timestamp ? message.timestamp * 1000 : undefined,
+    })
+  );
+});
+
+self.addEventListener('pushsubscriptionchange', (event) => {});
diff --git a/src/public_html/style.css b/src/public_html/style.css
new file mode 100644
index 0000000..c867eed
--- /dev/null
+++ b/src/public_html/style.css
@@ -0,0 +1,6 @@
+html {
+  background: #252526;
+  color: #fff;
+  font-family: 'Inter', sans-serif;
+}
+
diff --git a/src/tls.py b/src/tls.py
new file mode 100644
index 0000000..c0298ae
--- /dev/null
+++ b/src/tls.py
@@ -0,0 +1,48 @@
+import ssl
+
+import socketpool
+
+
+class SSLServerSocketPool:
+    def __init__(self, pool: socketpool.SocketPool, ssl_context: ssl.SSLContext):
+        self._pool = pool
+        self._ssl_context = ssl_context
+
+    @property
+    def AF_INET(self) -> int:
+        return self._pool.AF_INET
+
+    @property
+    def AF_INET6(self) -> int:
+        return self._pool.AF_INET6
+
+    @property
+    def SOCK_STREAM(self) -> int:
+        return self._pool.SOCK_STREAM
+
+    @property
+    def SOCK_DGRAM(self) -> int:
+        return self._pool.SOCK_DGRAM
+
+    @property
+    def SOCK_RAW(self) -> int:
+        return self._pool.SOCK_RAW
+
+    @property
+    def EAI_NONAME(self) -> int:
+        return self._pool.EAI_NONAME
+
+    @property
+    def TCP_NODELAY(self) -> int:
+        return self._pool.TCP_NODELAY
+
+    @property
+    def IPPROTO_TCP(self) -> int:
+        return self._pool.IPPROTO_TCP
+
+    def socket(self, family: int = None, type: int = None) -> ssl.SSLSocket:
+        socket = self._pool.socket(family, type)
+        return self._ssl_context.wrap_socket(socket, server_side=True)
+
+    def getaddrinfo(self, *args, **kwargs):
+        return self._pool.getaddrinfo(*args, **kwargs)
-- 
GitLab