From 0497d1bbcfbf0c8f1c62353d60c711f8e364815a Mon Sep 17 00:00:00 2001 From: ahtlon Date: Wed, 5 Aug 2026 00:21:39 +0200 Subject: [PATCH 1/5] seperate dev/normal deps --- pyproject.toml | 12 ++-- uv.lock | 146 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 146 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 15bc111..b37500f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,15 +11,19 @@ dependencies = [ "python-desfire", "pyjwt[crypto]>=2.12.1", "pwdlib[argon2]>=0.3.0", - "pytest>=9.0.3", "requests>=2.33.1", - "pytest-cov>=7.1.0", "setuptools>=82.0.1", "pyscard>=2.3.1", "alembic>=1.18.5", - "ruff>=0.16.0", "pydantic-settings>=2.13.1", - 'lgpio>=0.2.2.0' # ; sys_platform == "linux" and platform_machine == "aarch64"', + 'lgpio>=0.2.2.0; sys_platform == "linux" and platform_machine == "aarch64"', +] +[dependency-groups] +dev = [ + "ruff>=0.16.0", + "pytest>=9.0.3", + "pytest-cov>=7.1.0", + "jedi-language-server>=0.47.0", ] [tool.uv.sources] diff --git a/uv.lock b/uv.lock index 1f3154c..43deeda 100644 --- a/uv.lock +++ b/uv.lock @@ -93,6 +93,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, ] +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + [[package]] name = "build" version = "1.4.3" @@ -125,6 +134,19 @@ filecache = [ { name = "filelock" }, ] +[[package]] +name = "cattrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" }, +] + [[package]] name = "certifi" version = "2026.2.25" @@ -419,6 +441,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] +[[package]] +name = "docstring-to-markdown" +version = "0.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/d8/8abe80d62c5dce1075578031bcfde07e735bcf0afe2886dd48b470162ab4/docstring_to_markdown-0.17.tar.gz", hash = "sha256:df72a112294c7492487c9da2451cae0faeee06e86008245c188c5761c9590ca3", size = 32260, upload-time = "2025-05-02T15:09:07.932Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7b/af3d0da15bed3a8665419bb3a630585756920f4ad67abfdfef26240ebcc0/docstring_to_markdown-0.17-py3-none-any.whl", hash = "sha256:fd7d5094aa83943bf5f9e1a13701866b7c452eac19765380dead666e36d3711c", size = 23479, upload-time = "2025-05-02T15:09:06.676Z" }, +] + [[package]] name = "dulwich" version = "1.1.0" @@ -624,40 +659,50 @@ source = { virtual = "." } dependencies = [ { name = "alembic" }, { name = "fastapi", extra = ["standard"] }, - { name = "lgpio" }, + { name = "lgpio", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, { name = "poetry" }, { name = "pwdlib", extra = ["argon2"] }, { name = "pydantic-settings" }, { name = "pyjwt", extra = ["crypto"] }, { name = "pyscard" }, - { name = "pytest" }, - { name = "pytest-cov" }, { name = "python-desfire" }, { name = "requests" }, - { name = "ruff" }, { name = "setuptools" }, { name = "sqlmodel" }, ] +[package.dev-dependencies] +dev = [ + { name = "jedi-language-server" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + [package.metadata] requires-dist = [ { name = "alembic", specifier = ">=1.18.5" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.135.3" }, - { name = "lgpio", specifier = ">=0.2.2.0" }, + { name = "lgpio", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'", specifier = ">=0.2.2.0" }, { name = "poetry", specifier = ">=2.3.4" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.3.0" }, { name = "pydantic-settings", specifier = ">=2.13.1" }, { name = "pyjwt", extras = ["crypto"], specifier = ">=2.12.1" }, { name = "pyscard", specifier = ">=2.3.1" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "python-desfire", git = "https://github.com/waza-ari/python-desfire" }, { name = "requests", specifier = ">=2.33.1" }, - { name = "ruff", specifier = ">=0.16.0" }, { name = "setuptools", specifier = ">=82.0.1" }, { name = "sqlmodel", specifier = ">=0.0.38" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "jedi-language-server", specifier = ">=0.47.0" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "ruff", specifier = ">=0.16.0" }, +] + [[package]] name = "greenlet" version = "3.4.0" @@ -757,6 +802,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -808,6 +865,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "jedi-language-server" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cattrs" }, + { name = "docstring-to-markdown" }, + { name = "jedi" }, + { name = "lsprotocol" }, + { name = "pygls" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/00/0cc9fc9996f79f1935a39236f300e6b2399aed34b8170248c5108e38e652/jedi_language_server-0.47.0.tar.gz", hash = "sha256:43d9b89d07b504470544b80d76739449856a2be6565571cb362ee0681f6f0b86", size = 91714, upload-time = "2026-05-31T14:02:43.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e1/ad4afa632b4ea58f422391d11e86b5100b5578879af6bf04bf5aaf4beede/jedi_language_server-0.47.0-py3-none-any.whl", hash = "sha256:0e00d5af41c1a966c9ec73221e81b1af3b8c0243d29477e5441c661d5b61ec3e", size = 33844, upload-time = "2026-05-31T14:02:42.161Z" }, +] + [[package]] name = "jeepney" version = "0.9.0" @@ -852,6 +937,19 @@ version = "0.2.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/56/33/26ec2e8049eaa2f077bf23a12dc61ca559fbfa7bea0516bf263d657ae275/lgpio-0.2.2.0.tar.gz", hash = "sha256:11372e653b200f76a0b3ef8a23a0735c85ec678a9f8550b9893151ed0f863fff", size = 90087, upload-time = "2024-03-29T21:59:55.901Z" } +[[package]] +name = "lsprotocol" +version = "2025.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/26/67b84e6ec1402f0e6764ef3d2a0aaf9a79522cc1d37738f4e5bb0b21521a/lsprotocol-2025.0.0.tar.gz", hash = "sha256:e879da2b9301e82cfc3e60d805630487ac2f7ab17492f4f5ba5aaba94fe56c29", size = 74896, upload-time = "2025-06-17T21:30:18.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f0/92f2d609d6642b5f30cb50a885d2bf1483301c69d5786286500d15651ef2/lsprotocol-2025.0.0-py3-none-any.whl", hash = "sha256:f9d78f25221f2a60eaa4a96d3b4ffae011b107537facee61d3da3313880995c7", size = 76250, upload-time = "2025-06-17T21:30:19.455Z" }, +] + [[package]] name = "mako" version = "1.3.12" @@ -990,6 +1088,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + [[package]] name = "pbs-installer" version = "2026.4.7" @@ -1231,6 +1338,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, ] +[[package]] +name = "pygls" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, + { name = "lsprotocol" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/2e/7bbe061d175c0baddde8fc9edb908a4c31ba5d9165b8c68e3439c3a9f138/pygls-2.1.1.tar.gz", hash = "sha256:1da03ba9053201bb337dcdd8d121df70feb2a91e1a0dcc74de5da79755b1a201", size = 55091, upload-time = "2026-03-25T11:19:10.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/1a/208293b6c350f5abea6941d5606080d4a492644052504f5312e5de30a902/pygls-2.1.1-py3-none-any.whl", hash = "sha256:510a6dea2476177230c7d851125e5948efdf3fdb9ebfd8543fc434972f8faed4", size = 68975, upload-time = "2026-03-25T11:19:11.374Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -1924,6 +2045,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/19/4b4e3e2ea5fa213ff4220e84450628fecde042b0961e7b4e6d845e555ade/xattr-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1e6c216927b16fd4b72df655d5124b69b2a406cb3132b5231179021182f0f0d1", size = 19023, upload-time = "2025-10-13T22:16:34.395Z" }, ] +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +] + [[package]] name = "zstandard" version = "0.25.0" -- 2.51.2 From bbb41e41ab6d70d76324e263465d274385db8921 Mon Sep 17 00:00:00 2001 From: ahtlon Date: Wed, 5 Aug 2026 00:24:22 +0200 Subject: [PATCH 2/5] Add active check --- app/services/door.py | 2 ++ app/services/scanner.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/door.py b/app/services/door.py index c3fb0ee..78fb80a 100644 --- a/app/services/door.py +++ b/app/services/door.py @@ -103,6 +103,8 @@ def checkAccess(key: str, db: Session): card = db.exec(select(Card).where(Card.key == key)).one() for auth in card.group.accessauths: logger.info(f"checking auth: {auth.name}") + if not auth.is_active: + return False if auth.type == "timetable": for timetable in auth.timetables: logger.info(f" checking timetable {timetable.id}") diff --git a/app/services/scanner.py b/app/services/scanner.py index 1b5b23b..13ac085 100644 --- a/app/services/scanner.py +++ b/app/services/scanner.py @@ -18,7 +18,6 @@ from desfire.enums import ( DESFireKeyType, ) from desfire.schemas import FilePermissions, FileSettings, KeySettings -from dotenv import load_dotenv from fastapi import HTTPException, status from smartcard.CardRequest import CardRequest from smartcard.CardType import AnyCardType @@ -30,7 +29,6 @@ from app.services.settings import settings logger = logging.getLogger(__name__) # ENV vars -load_dotenv() MIFARE_APP_MASTER_KEY = settings.mifare_app_master_key MIFARE_ACL_READ_BASE_KEY = settings.mifare_acl_read_base_key MIFARE_ACL_WRITE_BASE_KEY = settings.mifare_acl_write_base_key -- 2.51.2 From f635149d1feae93438a4b60302dcd553e0f4f027 Mon Sep 17 00:00:00 2001 From: ahtlon Date: Wed, 5 Aug 2026 00:52:46 +0200 Subject: [PATCH 3/5] Check for disabled card and add tests --- app/services/door.py | 11 ++++-- test/test_services/test_door.py | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/app/services/door.py b/app/services/door.py index 78fb80a..61de4ab 100644 --- a/app/services/door.py +++ b/app/services/door.py @@ -2,7 +2,6 @@ import logging from datetime import date, datetime, timedelta from time import sleep -import lgpio from sqlalchemy import exc from sqlmodel import select @@ -28,6 +27,7 @@ class DoorController: self._chip = None if not mock_factory: + import lgpio self._chip = lgpio.gpiochip_open(0) lgpio.gpio_claim_output(self._chip, unlock_pin, 1) lgpio.gpio_claim_output(self._chip, lock_pin, 1) @@ -42,7 +42,7 @@ class DoorController: def open(self): if self._mock: self._is_open = True - logger.info("Dor unlocked.[MOCK]") + logger.info("Door unlocked.[MOCK]") return lgpio.gpio_write(self._chip, self._unlock_pin, 0) @@ -52,6 +52,11 @@ class DoorController: logger.info("Door unlocked!") def close(self): + if self._mock: + self._is_open = False + logger.info("Door locked.[MOCK]") + return + lgpio.gpio_write(self._chip, self._lock_pin, 0) sleep(0.4) lgpio.gpio_write(self._chip, self._lock_pin, 1) @@ -101,6 +106,8 @@ def checkAccess(key: str, db: Session): current_weekday = datetime.weekday(date.today()) current_time = datetime.now() card = db.exec(select(Card).where(Card.key == key)).one() + if not card.enabled: + return False for auth in card.group.accessauths: logger.info(f"checking auth: {auth.name}") if not auth.is_active: diff --git a/test/test_services/test_door.py b/test/test_services/test_door.py index 855a185..3fcc356 100644 --- a/test/test_services/test_door.py +++ b/test/test_services/test_door.py @@ -112,3 +112,65 @@ def test_check_access_invalid_card(db_session): # Should raise exception for non-existent card with pytest.raises(Exception): checkAccess("non-existent-key", db_session) + +def test_check_access_with_inactive_aa(db_session): + # Setup: create card with valid access + group = GroupDB(name="Test Group") + db_session.add(group) + db_session.commit() + + card = Card( + key="test-key-123", + group_id=group.id, + enabled=True, + name="test_card", + card_serial="00:00:00:00:00:00:00", + ) + db_session.add(card) + + timetable = Timetable( + weekday=datetime.datetime.weekday(datetime.date.today()), + starttime=datetime.datetime.now().time(), + duration=120, # 2 hours + ) + db_session.add(timetable) + + aa = AccessAuthorizationDB(name="Test AA", is_active=False, type="timetable") + db_session.add(aa) + aa.timetables = [timetable] + group.accessauths = [aa] + + db_session.commit() + + assert checkAccess("test-key-123", db_session) == False + +def test_check_access_with_inactive_card(db_session): + # Setup: create card with valid access + group = GroupDB(name="Test Group") + db_session.add(group) + db_session.commit() + + card = Card( + key="test-key-123", + group_id=group.id, + enabled=False, + name="test_card", + card_serial="00:00:00:00:00:00:00", + ) + db_session.add(card) + + timetable = Timetable( + weekday=datetime.datetime.weekday(datetime.date.today()), + starttime=datetime.datetime.now().time(), + duration=120, # 2 hours + ) + db_session.add(timetable) + + aa = AccessAuthorizationDB(name="Test AA", is_active=True, type="timetable") + db_session.add(aa) + aa.timetables = [timetable] + group.accessauths = [aa] + + db_session.commit() + + assert checkAccess("test-key-123", db_session) == False \ No newline at end of file -- 2.51.2 From 4b797997d46c16d9a2e4b821d0c4c72f36ee1ee9 Mon Sep 17 00:00:00 2001 From: ahtlon Date: Wed, 5 Aug 2026 02:24:05 +0200 Subject: [PATCH 4/5] Switch lgpio for mqtt --- pyproject.toml | 2 +- uv.lock | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b37500f..321e0b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "pyscard>=2.3.1", "alembic>=1.18.5", "pydantic-settings>=2.13.1", - 'lgpio>=0.2.2.0; sys_platform == "linux" and platform_machine == "aarch64"', + "paho-mqtt>=2.1.0", ] [dependency-groups] dev = [ diff --git a/uv.lock b/uv.lock index 43deeda..8a76127 100644 --- a/uv.lock +++ b/uv.lock @@ -660,6 +660,7 @@ dependencies = [ { name = "alembic" }, { name = "fastapi", extra = ["standard"] }, { name = "lgpio", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "paho-mqtt" }, { name = "poetry" }, { name = "pwdlib", extra = ["argon2"] }, { name = "pydantic-settings" }, @@ -684,6 +685,7 @@ requires-dist = [ { name = "alembic", specifier = ">=1.18.5" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.135.3" }, { name = "lgpio", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'", specifier = ">=0.2.2.0" }, + { name = "paho-mqtt", specifier = ">=2.1.0" }, { name = "poetry", specifier = ">=2.3.4" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.3.0" }, { name = "pydantic-settings", specifier = ">=2.13.1" }, @@ -1088,6 +1090,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, +] + [[package]] name = "parso" version = "0.8.7" -- 2.51.2 From 0bcc83871d11d6583fdb57d1a5505d41238603c3 Mon Sep 17 00:00:00 2001 From: ahtlon Date: Wed, 5 Aug 2026 03:22:55 +0200 Subject: [PATCH 5/5] [Door] switch gpio out to mqtt --- app/main.py | 6 +++--- app/services/door.py | 31 +++++++++++++------------------ app/services/settings.py | 6 +++--- module.nix | 8 ++++---- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/app/main.py b/app/main.py index 210c59b..81fe0a4 100644 --- a/app/main.py +++ b/app/main.py @@ -32,9 +32,9 @@ async def lifespan(app: FastAPI): create_first_user(db=get_db_session()) init_controller( DoorController( - lock_pin=settings.lock_pin, - unlock_pin=settings.unlock_pin, - mock_factory=settings.mock_gpio, + mqtt_host=settings.mqtt_host, + mqtt_port=settings.mqtt_port, + mock_factory=settings.mock, ), ) if not settings.disable_cards: diff --git a/app/services/door.py b/app/services/door.py index 61de4ab..453cc4e 100644 --- a/app/services/door.py +++ b/app/services/door.py @@ -16,26 +16,25 @@ logger = logging.getLogger(__name__) class DoorController: def __init__( self, - lock_pin: int = 17, # connected to 20 on the esp - unlock_pin: int = 18, # connected to 21 on the esp + mqtt_host: str = "localhost", + mqtt_port: int = 1883, mock_factory: bool = False, + ): self._is_open: bool = False - self._lock_pin = lock_pin - self._unlock_pin = unlock_pin + self._mqtt = None + self._mqtt_topic = "nukihub/lock/action" self._mock = mock_factory - self._chip = None if not mock_factory: - import lgpio - self._chip = lgpio.gpiochip_open(0) - lgpio.gpio_claim_output(self._chip, unlock_pin, 1) - lgpio.gpio_claim_output(self._chip, lock_pin, 1) + import paho.mqtt.client as mqtt + self._mqtt = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) + self._mqtt.connect(mqtt_host, mqtt_port) + self._mqtt.loop_start() + logger.info("Mqtt client connect to %s:%s", mqtt_host, mqtt_port) logger.info( - "DoorController started. lock=%s unlock=%s mock=%s", - lock_pin, - unlock_pin, + "DoorController started. topic=%s mock=%s", self._mqtt_topic, mock_factory, ) @@ -45,9 +44,7 @@ class DoorController: logger.info("Door unlocked.[MOCK]") return - lgpio.gpio_write(self._chip, self._unlock_pin, 0) - sleep(0.4) - lgpio.gpio_write(self._chip, self._unlock_pin, 1) + self._mqtt.publish(self._mqtt_topic, "unlock") self._is_open = True logger.info("Door unlocked!") @@ -57,9 +54,7 @@ class DoorController: logger.info("Door locked.[MOCK]") return - lgpio.gpio_write(self._chip, self._lock_pin, 0) - sleep(0.4) - lgpio.gpio_write(self._chip, self._lock_pin, 1) + self._mqtt.publish(self._mqtt_topic, "lock") self._is_open = False logger.info("Door locked!") diff --git a/app/services/settings.py b/app/services/settings.py index e22c766..4a0d7d3 100644 --- a/app/services/settings.py +++ b/app/services/settings.py @@ -11,9 +11,9 @@ class Settings(BaseSettings): secret_key: str sqlalchemy_database_url: str = "sqlite:///./gatekeeper.db" - lock_pin: int = 17 - unlock_pin: int = 18 - mock_gpio: bool = True + mqtt_host: str = "localhost" + mqtt_port: int = 1883 + mock: bool = True alembic_config: str = "./alembic.ini" disable_cards: bool = False diff --git a/module.nix b/module.nix index aa37337..59e0880 100644 --- a/module.nix +++ b/module.nix @@ -15,10 +15,10 @@ in description = "Where to save the database."; default = "/var/lib/gatekeeper"; }; - mockGpio = lib.mkOption { + mock = lib.mkOption { type = lib.types.str; - default = "True"; - description = "Mock GPIO pins. Has to be a string!"; + default = "False"; + description = "Mock the doorcontroller. Has to be a string!"; }; }; }; @@ -67,7 +67,7 @@ in environment = { SQLALCHEMY_DATABASE_URL = "sqlite:///${cfg.db}/gatekeeper.db"; ALEMBIC_CONFIG = "${self}/alembic.ini"; - MOCK_GPIO = cfg.mockGpio; + MOCK = cfg.mock; }; }; }; -- 2.51.2