api: automatically rediscover locks from CCUJack
All checks were successful
Build Container / Build Container (push) Successful in 1m35s

closes CCCHH/dooris#5
This commit is contained in:
lilly 2026-05-19 15:03:26 +02:00
commit 2349e58924
Signed by: lilly
SSH key fingerprint: SHA256:y9T5GFw2A20WVklhetIxG1+kcg/Ce0shnQmbu1LQ37g
3 changed files with 24 additions and 5 deletions

View file

@ -1,7 +1,6 @@
from typing import Optional, List, AsyncIterable from typing import Optional, List, AsyncIterable
import logging import logging
import secrets import secrets
import sys
from datetime import datetime, UTC from datetime import datetime, UTC
from fastapi import FastAPI, Request, Response, status from fastapi import FastAPI, Request, Response, status
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
@ -36,8 +35,7 @@ async def lifespan(app: FastAPI):
auth=BasicAuth(app_cfg.ccujack_user, app_cfg.ccujack_password), auth=BasicAuth(app_cfg.ccujack_user, app_cfg.ccujack_password),
mqtt_conn=app_cfg.ccujack_mqtt, mqtt_conn=app_cfg.ccujack_mqtt,
) )
await app.extra["ccujack"].connect_mqtt() await app.extra["ccujack"].start()
await app.extra["ccujack"].find_locks()
yield yield

View file

@ -72,6 +72,7 @@ class CCUJackClient:
locks: LockData locks: LockData
param_values: Dict[str, Any] param_values: Dict[str, Any]
task_process_messages: asyncio.Task task_process_messages: asyncio.Task
task_find_locks: asyncio.Task
data_updated: asyncio.Event data_updated: asyncio.Event
def __init__(self, base_uri: str, auth: BasicAuth, mqtt_conn: str): def __init__(self, base_uri: str, auth: BasicAuth, mqtt_conn: str):
@ -85,10 +86,16 @@ class CCUJackClient:
self.locks = None self.locks = None
self.param_values = dict() self.param_values = dict()
self.task_process_messages = None self.task_process_messages = None
self.task_find_locks = None
self.data_updated = asyncio.Event() self.data_updated = asyncio.Event()
async def connect_mqtt(self): async def start(self):
await self.mqtt.connect() 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.task_process_messages = asyncio.get_running_loop().create_task(
self.process_mqt_messages(), name="process-mqtt-messages" self.process_mqt_messages(), name="process-mqtt-messages"
) )
@ -116,6 +123,7 @@ class CCUJackClient:
# save the result # save the result
new_locks = [i for i in device_infos if i[0].type == DEVICE_TYPE_LOCK] new_locks = [i for i in device_infos if i[0].type == DEVICE_TYPE_LOCK]
if new_locks != self.locks: if new_locks != self.locks:
logger.info("Found new locks, updating state")
self.locks = new_locks self.locks = new_locks
self.data_updated.set() self.data_updated.set()
self.data_updated.clear() self.data_updated.clear()
@ -148,6 +156,17 @@ class CCUJackClient:
finally: finally:
self.data_updated.clear() 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: async def query_param_value(self, address: str) -> CCUValue:
if address in self.param_values: if address in self.param_values:
return self.param_values[address] return self.param_values[address]

View file

@ -3,7 +3,7 @@
# https://github.com/eclipse-paho/paho.mqtt.python/blob/master/examples/loop_asyncio.py # 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 logging
import asyncio import asyncio
import socket import socket
@ -132,6 +132,7 @@ class AsyncMqttClient:
self.fut_subscribe = asyncio.get_running_loop().create_future() self.fut_subscribe = asyncio.get_running_loop().create_future()
self.client.subscribe([(i, qos) for i in to_add]) self.client.subscribe([(i, qos) for i in to_add])
await self.fut_subscribe await self.fut_subscribe
self.active_subscriptions.update(to_add)
to_remove = self.active_subscriptions.difference(topics) to_remove = self.active_subscriptions.difference(topics)
if to_remove: if to_remove:
@ -139,6 +140,7 @@ class AsyncMqttClient:
self.fut_unsubscribe = asyncio.get_running_loop().create_future() self.fut_unsubscribe = asyncio.get_running_loop().create_future()
self.client.unsubscribe(list(to_remove)) self.client.unsubscribe(list(to_remove))
await self.fut_unsubscribe await self.fut_unsubscribe
self.active_subscriptions.difference_update(to_remove)
async def connect(self): async def connect(self):
server_host, server_port = self.connection_string.rsplit(":", maxsplit=1) server_host, server_port = self.connection_string.rsplit(":", maxsplit=1)