From f9f26946949f73668a798ca3d6607ca9c170efbe Mon Sep 17 00:00:00 2001
From: James Ide <ide@expo.dev>
Date: Fri, 22 Dec 2023 13:34:03 -0800
Subject: [PATCH] Remove the web server, support just MQTT

---
 .vscode/settings.json        |   3 +-
 requirements.txt             |   9 ++---
 src/boot.py                  |   2 +-
 src/code.py                  |  73 ++++++++---------------------------
 src/mqtt.py                  |   1 +
 src/tls.py                   |  48 -----------------------
 website/icon-192.avif        | Bin 4474 -> 0 bytes
 website/icon-512.avif        | Bin 11545 -> 0 bytes
 website/index.html           |  16 --------
 website/manifest.webmanifest |  21 ----------
 website/script.js            |  42 --------------------
 website/service-worker.js    |  23 -----------
 website/style.css            |   6 ---
 13 files changed, 24 insertions(+), 220 deletions(-)
 delete mode 100644 src/tls.py
 delete mode 100644 website/icon-192.avif
 delete mode 100644 website/icon-512.avif
 delete mode 100644 website/index.html
 delete mode 100644 website/manifest.webmanifest
 delete mode 100644 website/script.js
 delete mode 100644 website/service-worker.js
 delete mode 100644 website/style.css

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 000811b..5ffd970 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,6 +3,5 @@
     "reportMissingImports": "none",
     "reportMissingModuleSource": "none",
     "reportShadowedImports": "none"
-  },
-  "python.formatting.provider": "black"
+  }
 }
diff --git a/requirements.txt b/requirements.txt
index 1c3ed38..a6dfcce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
-adafruit_debouncer==2.0.5
-adafruit_logging==5.2.1
-adafruit_ticks==1.0.9
-adafruit_httpserver==2.3.0
-adafruit_minimqtt==7.2.2
+adafruit_debouncer==2.0.8
+adafruit_logging==5.2.5
+adafruit_ticks==1.0.13
+adafruit_minimqtt==7.5.6
diff --git a/src/boot.py b/src/boot.py
index 0287c12..ee5b061 100644
--- a/src/boot.py
+++ b/src/boot.py
@@ -2,4 +2,4 @@ import storage
 
 # 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)
+# storage.remount("/", readonly=False, disable_concurrent_write_protection=True)
diff --git a/src/code.py b/src/code.py
index d0e125a..646c674 100644
--- a/src/code.py
+++ b/src/code.py
@@ -1,6 +1,5 @@
 import binascii
 import os
-import ssl
 import time
 import traceback
 
@@ -14,15 +13,9 @@ 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:
@@ -52,59 +45,28 @@ def main(logger: adafruit_logging.Logger) -> None:
     pool = socketpool.SocketPool(wifi.radio)
 
     # Connect 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)
-        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
-
-    # 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"
+    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,
     )
-    ssl_pool = SSLServerSocketPool(pool, ssl_context)
+    mqtt.logger = logger
+    mqtt.connect()
+    logger.info("Connected to the MQTT broker")
 
-    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")
+    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")
 
     logger.info("Monitoring door sensor...")
     while True:
         switch.update()
-
-        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
+        mqtt.loop()
 
         if switch.rose or switch.fell:
             is_door_open = not switch.value
@@ -112,8 +74,7 @@ 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
-            if mqtt is not None:
-                publish_sensor_state_message(mqtt, mqtt_state_topic, is_door_open)
+            publish_sensor_state_message(mqtt, mqtt_state_topic, is_door_open)
 
 
 logger = create_logger()
diff --git a/src/mqtt.py b/src/mqtt.py
index b2ab815..6ae14fe 100644
--- a/src/mqtt.py
+++ b/src/mqtt.py
@@ -1,5 +1,6 @@
 import json
 
+
 def publish_homeassistant_discovery_message(
     mqtt, device_id: str, state_topic: str
 ) -> None:
diff --git a/src/tls.py b/src/tls.py
deleted file mode 100644
index c0298ae..0000000
--- a/src/tls.py
+++ /dev/null
@@ -1,48 +0,0 @@
-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)
diff --git a/website/icon-192.avif b/website/icon-192.avif
deleted file mode 100644
index 9bdcee6c84d1f5dcde9b393798fa046036900f46..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 4474
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

diff --git a/website/icon-512.avif b/website/icon-512.avif
deleted file mode 100644
index f82377c1e8c5437984d9c102b45f3a5fd26322e7..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 11545
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

diff --git a/website/index.html b/website/index.html
deleted file mode 100644
index cb5afa7..0000000
--- a/website/index.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!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/website/manifest.webmanifest b/website/manifest.webmanifest
deleted file mode 100644
index 02e946d..0000000
--- a/website/manifest.webmanifest
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "$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/website/script.js b/website/script.js
deleted file mode 100644
index 128a419..0000000
--- a/website/script.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const vapidPublicKey =
-  'BP1ZvYiUs2YDFtbJu2weWZdBS4DFA0kKw3pFK6iMVV7zue-fmg_gJM8lEshHkbJO5KkiO8wYh15Xn8y0BZNpRCs';
-
-// 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 subscriptionURL = getSensorSubscriptionURL();
-  if (!subscriptionURL) {
-    return;  
-  }
-
-  const serviceWorkerRegistration = await navigator.serviceWorker.ready;
-  const pushSubscription = await serviceWorkerRegistration.pushManager.subscribe({
-    userVisibleOnly: true,
-    applicationServerKey: vapidPublicKey,
-  });
-
-  await fetch(subscriptionURL, {
-    method: 'post',
-    headers: { 'content-type': 'application/json' },
-    body: JSON.stringify(pushSubscription.toJSON()),
-  });
-});
-
-
-function getSensorSubscriptionURL() {
-  const queryParameters = new URLSearchParams(location.search);
-  const subscriptionURLString = queryParameters.get('sensor_subscription_url');
-  if (subscriptionURLString) {
-    try {
-      return new URL(subscriptionURLString);
-    } catch (error) {
-      console.error(`Malformed sensor subscription URL: ${subscriptionURLString}`);
-    }
-  }
-  return null;
-}
diff --git a/website/service-worker.js b/website/service-worker.js
deleted file mode 100644
index f13431f..0000000
--- a/website/service-worker.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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();
-  event.waitUntil(
-    self.registration.showNotification(message.title, {
-      body: message.body,
-      icon: '/icon-192.avif',
-      tag: message.tag,
-      timestamp: message.timestamp ? message.timestamp * 1000 : undefined,
-    })
-  );
-});
-
-// TODO: handle changes to the push subscription, such as when the user disables push notifications
-// through the system settings or when the browser cycles the push subscription
-// self.addEventListener('pushsubscriptionchange', (event) => {});
diff --git a/website/style.css b/website/style.css
deleted file mode 100644
index c867eed..0000000
--- a/website/style.css
+++ /dev/null
@@ -1,6 +0,0 @@
-html {
-  background: #252526;
-  color: #fff;
-  font-family: 'Inter', sans-serif;
-}
-
-- 
GitLab