diff --git a/.vscode/settings.json b/.vscode/settings.json index 000811b4d0f9f2ff7dbaa56f142c5f4686c1de05..5ffd9705edf850875d404282d365f12a2397903e 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 1c3ed3898ed9fb6955fd8d9309854fa5facbe551..a6dfcce63a74ae9063879c9b5c6a5bd8fa3aa8c4 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 0287c1203cacdad2af47f8e1ea5f1b21011d7bc6..ee5b0612c2c2705a3cb422181936b223b6834fbd 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 d0e125a1eb5b236e86a7bed90059e2a442d5998b..646c674f2536f02c723adb7305b83cec7c665a55 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 b2ab815ec027e7a712d3261ddf168b8987b433de..6ae14fe4268eed0fe7afe2859eda1585503ee864 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 c0298ae4dea1a73b674c81e075c7c12ee86230d4..0000000000000000000000000000000000000000 --- 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 Binary files a/website/icon-192.avif and /dev/null differ diff --git a/website/icon-512.avif b/website/icon-512.avif deleted file mode 100644 index f82377c1e8c5437984d9c102b45f3a5fd26322e7..0000000000000000000000000000000000000000 Binary files a/website/icon-512.avif and /dev/null differ diff --git a/website/index.html b/website/index.html deleted file mode 100644 index cb5afa718881d0d3de9a23bc13111d688a24cf1c..0000000000000000000000000000000000000000 --- 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 02e946d81fca657b74e0859f9dc1136ce141e7ad..0000000000000000000000000000000000000000 --- 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 128a4195f7732007bd2639488e8a0de9ec8ae405..0000000000000000000000000000000000000000 --- 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 f13431f7f69d5b452311b7426b3db9e2c0f74bf6..0000000000000000000000000000000000000000 --- 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 c867eeda9336e865074622417f14592245726007..0000000000000000000000000000000000000000 --- a/website/style.css +++ /dev/null @@ -1,6 +0,0 @@ -html { - background: #252526; - color: #fff; - font-family: 'Inter', sans-serif; -} -