From b1b624a7b3115e917b138785103fda84716f68ad Mon Sep 17 00:00:00 2001 From: lilly Date: Tue, 19 May 2026 14:57:28 +0200 Subject: [PATCH 1/2] api: simplify logging setup --- api/src/dooris_api/__init__.py | 7 +++++++ api/src/dooris_api/app.py | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/api/src/dooris_api/__init__.py b/api/src/dooris_api/__init__.py index 8ee8809..139ee17 100644 --- a/api/src/dooris_api/__init__.py +++ b/api/src/dooris_api/__init__.py @@ -1,4 +1,6 @@ import os +import sys +import logging from contextvars import ContextVar from argparse import ArgumentParser, Namespace @@ -72,6 +74,10 @@ def main(): ) args = argp.parse_args() + # setup logging + logging.basicConfig(level=logging.DEBUG, format="[%(levelname)s] %(filename)s: %(message)s") + + # setup app app_config.set(args) import uvicorn from dooris_api.app import app @@ -80,6 +86,7 @@ def main(): from fastapi.staticfiles import StaticFiles app.mount("/", StaticFiles(directory=args.serve_static, html=True), name="static") + # start webserver config = uvicorn.Config(app, port=8000, log_level="debug") server = uvicorn.Server(config) server.run() diff --git a/api/src/dooris_api/app.py b/api/src/dooris_api/app.py index edb338f..7e2a948 100644 --- a/api/src/dooris_api/app.py +++ b/api/src/dooris_api/app.py @@ -22,12 +22,6 @@ logger = logging.getLogger(__name__) async def lifespan(app: FastAPI): app_cfg = app_config.get() - root_logger = logging.getLogger("") - root_logger.setLevel(logging.INFO) - root_logger.addHandler(logging.StreamHandler(sys.stderr)) - app_logger = logging.getLogger("dooris_api") - app_logger.setLevel(logging.DEBUG) - app.extra["oidc_client"] = OpenidClient.from_issuer_url( url=app_cfg.openid_issuer, authentication_redirect_uri=f"{app_cfg.base_url}/auth/login-callback", From 2349e58924b010b01f6e33160053bd34027beab8 Mon Sep 17 00:00:00 2001 From: lilly Date: Tue, 19 May 2026 15:03:26 +0200 Subject: [PATCH 2/2] api: automatically rediscover locks from CCUJack closes CCCHH/dooris#5 --- api/src/dooris_api/app.py | 4 +--- api/src/dooris_api/ccujack.py | 21 ++++++++++++++++++++- api/src/dooris_api/mqtt_client.py | 4 +++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/api/src/dooris_api/app.py b/api/src/dooris_api/app.py index 7e2a948..54f97f1 100644 --- a/api/src/dooris_api/app.py +++ b/api/src/dooris_api/app.py @@ -1,7 +1,6 @@ from typing import Optional, List, AsyncIterable import logging import secrets -import sys from datetime import datetime, UTC from fastapi import FastAPI, Request, Response, status from fastapi.responses import RedirectResponse @@ -36,8 +35,7 @@ async def lifespan(app: FastAPI): auth=BasicAuth(app_cfg.ccujack_user, app_cfg.ccujack_password), mqtt_conn=app_cfg.ccujack_mqtt, ) - await app.extra["ccujack"].connect_mqtt() - await app.extra["ccujack"].find_locks() + await app.extra["ccujack"].start() yield diff --git a/api/src/dooris_api/ccujack.py b/api/src/dooris_api/ccujack.py index 1132e63..9edbcdb 100644 --- a/api/src/dooris_api/ccujack.py +++ b/api/src/dooris_api/ccujack.py @@ -72,6 +72,7 @@ class CCUJackClient: locks: LockData param_values: Dict[str, Any] task_process_messages: asyncio.Task + task_find_locks: asyncio.Task data_updated: asyncio.Event def __init__(self, base_uri: str, auth: BasicAuth, mqtt_conn: str): @@ -85,10 +86,16 @@ class CCUJackClient: self.locks = None self.param_values = dict() self.task_process_messages = None + self.task_find_locks = None self.data_updated = asyncio.Event() - async def connect_mqtt(self): + async def start(self): await self.mqtt.connect() + await self.find_locks() + + self.task_find_locks = asyncio.get_running_loop().create_task( + self.cron(), name="ccujack-cron" + ) self.task_process_messages = asyncio.get_running_loop().create_task( self.process_mqt_messages(), name="process-mqtt-messages" ) @@ -116,6 +123,7 @@ class CCUJackClient: # save the result new_locks = [i for i in device_infos if i[0].type == DEVICE_TYPE_LOCK] if new_locks != self.locks: + logger.info("Found new locks, updating state") self.locks = new_locks self.data_updated.set() self.data_updated.clear() @@ -148,6 +156,17 @@ class CCUJackClient: finally: self.data_updated.clear() + async def cron(self): + while True: + try: + + await asyncio.sleep(60 * 60) # 1 hour + logger.info("Running CCUJack cron") + await self.find_locks() + + except Exception as e: + logger.exception(f"Error in CCUJack cron task: {e}") + async def query_param_value(self, address: str) -> CCUValue: if address in self.param_values: return self.param_values[address] diff --git a/api/src/dooris_api/mqtt_client.py b/api/src/dooris_api/mqtt_client.py index 1548e66..03db2cc 100644 --- a/api/src/dooris_api/mqtt_client.py +++ b/api/src/dooris_api/mqtt_client.py @@ -3,7 +3,7 @@ # https://github.com/eclipse-paho/paho.mqtt.python/blob/master/examples/loop_asyncio.py # -from typing import Any, List, Set, Iterable +from typing import Any, Set, Iterable import logging import asyncio import socket @@ -132,6 +132,7 @@ class AsyncMqttClient: self.fut_subscribe = asyncio.get_running_loop().create_future() self.client.subscribe([(i, qos) for i in to_add]) await self.fut_subscribe + self.active_subscriptions.update(to_add) to_remove = self.active_subscriptions.difference(topics) if to_remove: @@ -139,6 +140,7 @@ class AsyncMqttClient: self.fut_unsubscribe = asyncio.get_running_loop().create_future() self.client.unsubscribe(list(to_remove)) await self.fut_unsubscribe + self.active_subscriptions.difference_update(to_remove) async def connect(self): server_host, server_port = self.connection_string.rsplit(":", maxsplit=1)