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