From 717ce951a60dd05ec2ff76381ab94017c4848e1c Mon Sep 17 00:00:00 2001 From: ahtlon Date: Fri, 31 Jul 2026 00:36:11 +0200 Subject: [PATCH] [door] add doorcontroller with mock pins for testing --- app/main.py | 9 +++++- app/services/door.py | 73 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/app/main.py b/app/main.py index f8312e9..7910363 100644 --- a/app/main.py +++ b/app/main.py @@ -20,6 +20,7 @@ from app.controllers import ( ) from app.services.auth import create_first_user, token_router from app.services.database import create_db_and_tables, get_db_session +from app.services.door import DoorController, init_controller from app.services.scanner import BackgroundScanner from app.services.settings import verify_settings @@ -37,7 +38,13 @@ async def lifespan(app: FastAPI): verify_settings() create_db_and_tables() create_first_user(db=get_db_session()) - + init_controller( + DoorController( + lock_pin=os.getenv("LOCK_PIN", 17), + unlock_pin=os.getenv("UNLOCK_PIN", 18), + mock_factory=os.getenv("MOCK_GPIO", True), + ), + ) if not os.getenv("DISABLE_CARDS"): scanner.start() diff --git a/app/services/door.py b/app/services/door.py index c734ee9..fcc0d4d 100644 --- a/app/services/door.py +++ b/app/services/door.py @@ -1,6 +1,9 @@ import logging from datetime import date, datetime, timedelta +from time import sleep +from gpiozero import OutputDevice +from gpiozero.pins.mock import MockFactory from sqlalchemy import exc from sqlmodel import select @@ -9,25 +12,77 @@ from app.services.database import Session, add_and_refresh logger = logging.getLogger(__name__) -doorIsOpen = True -# I think this could also be gpio controlled + # See: https://github.com/technyon/nuki_hub#gpio-lock-control-optional +# TODO: add sensor pin +class DoorController: + def __init__( + self, + lock_pin: str = 17, # connected to 20 on the esp + unlock_pin: str = 18, # connected to 21 on the esp + mock_factory: bool = False, + ): + self._is_open: bool = False + self._lock_pin = lock_pin + self._unlock_pin = unlock_pin + + if mock_factory: + from gpiozero import Device + + Device.pin_factory = MockFactory() + + self._lock = OutputDevice(pin=lock_pin, active_high=False) + self._unlock = OutputDevice(pin=unlock_pin, active_high=False) + + logger.info( + "DoorController started. lock=%s unlock=%s factory=%s", + lock_pin, + unlock_pin, + type(self._lock.pin_factory).__name__, + ) + + def open(self): + self._unlock.on() + sleep(0.4) + self._unlock.off() + self._is_open = True + logger.info("Door unlocked!") + + def close(self): + self._lock.on() + sleep(0.4) + self._lock.off() + self._is_open = False + logger.info("Door locked!") + + def is_open(self): + return self._is_open + + +_contoller: DoorController | None = None + + +def init_controller(ctrl: DoorController): + global _contoller + _contoller = ctrl + + +def get_controller(): + if _contoller is None: + raise RuntimeError("DoorController not initialized.") + return _contoller def openDoor(): - global doorIsOpen - doorIsOpen = True - logger.info("Still needs gpio out") + get_controller().open() def closeDoor(): - global doorIsOpen - doorIsOpen = False - logger.info("Still needs gpio out") + get_controller().close() def isDoorOpen(): - return doorIsOpen + return get_controller().is_open() def decrementOneshot(db: Session, oneshot: OneShotAccess):