Switch gpio to mqtt out #26

Merged
ahtlon merged 5 commits from dev into master 2026-08-07 19:14:56 +02:00
8 changed files with 252 additions and 43 deletions
+3 -3
View File
@@ -32,9 +32,9 @@ async def lifespan(app: FastAPI):
create_first_user(db=get_db_session()) create_first_user(db=get_db_session())
init_controller( init_controller(
DoorController( DoorController(
lock_pin=settings.lock_pin, mqtt_host=settings.mqtt_host,
unlock_pin=settings.unlock_pin, mqtt_port=settings.mqtt_port,
mock_factory=settings.mock_gpio, mock_factory=settings.mock,
), ),
) )
if not settings.disable_cards: if not settings.disable_cards:
+23 -19
View File
@@ -2,7 +2,6 @@ import logging
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from time import sleep from time import sleep
import lgpio
from sqlalchemy import exc from sqlalchemy import exc
from sqlmodel import select from sqlmodel import select
@@ -17,44 +16,45 @@ logger = logging.getLogger(__name__)
class DoorController: class DoorController:
def __init__( def __init__(
self, self,
lock_pin: int = 17, # connected to 20 on the esp mqtt_host: str = "localhost",
unlock_pin: int = 18, # connected to 21 on the esp mqtt_port: int = 1883,
mock_factory: bool = False, mock_factory: bool = False,
): ):
self._is_open: bool = False self._is_open: bool = False
self._lock_pin = lock_pin self._mqtt = None
self._unlock_pin = unlock_pin self._mqtt_topic = "nukihub/lock/action"
self._mock = mock_factory self._mock = mock_factory
self._chip = None
if not mock_factory: if not mock_factory:
self._chip = lgpio.gpiochip_open(0) import paho.mqtt.client as mqtt
lgpio.gpio_claim_output(self._chip, unlock_pin, 1) self._mqtt = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
lgpio.gpio_claim_output(self._chip, lock_pin, 1) 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( logger.info(
"DoorController started. lock=%s unlock=%s mock=%s", "DoorController started. topic=%s mock=%s", self._mqtt_topic,
lock_pin,
unlock_pin,
mock_factory, mock_factory,
) )
def open(self): def open(self):
if self._mock: if self._mock:
self._is_open = True self._is_open = True
logger.info("Dor unlocked.[MOCK]") logger.info("Door unlocked.[MOCK]")
return return
lgpio.gpio_write(self._chip, self._unlock_pin, 0) self._mqtt.publish(self._mqtt_topic, "unlock")
sleep(0.4)
lgpio.gpio_write(self._chip, self._unlock_pin, 1)
self._is_open = True self._is_open = True
logger.info("Door unlocked!") logger.info("Door unlocked!")
def close(self): def close(self):
lgpio.gpio_write(self._chip, self._lock_pin, 0) if self._mock:
sleep(0.4) self._is_open = False
lgpio.gpio_write(self._chip, self._lock_pin, 1) logger.info("Door locked.[MOCK]")
return
self._mqtt.publish(self._mqtt_topic, "lock")
self._is_open = False self._is_open = False
logger.info("Door locked!") logger.info("Door locked!")
@@ -101,8 +101,12 @@ def checkAccess(key: str, db: Session):
current_weekday = datetime.weekday(date.today()) current_weekday = datetime.weekday(date.today())
current_time = datetime.now() current_time = datetime.now()
card = db.exec(select(Card).where(Card.key == key)).one() card = db.exec(select(Card).where(Card.key == key)).one()
if not card.enabled:
return False
for auth in card.group.accessauths: for auth in card.group.accessauths:
logger.info(f"checking auth: {auth.name}") logger.info(f"checking auth: {auth.name}")
if not auth.is_active:
return False
if auth.type == "timetable": if auth.type == "timetable":
for timetable in auth.timetables: for timetable in auth.timetables:
logger.info(f" checking timetable {timetable.id}") logger.info(f" checking timetable {timetable.id}")
-2
View File
@@ -18,7 +18,6 @@ from desfire.enums import (
DESFireKeyType, DESFireKeyType,
) )
from desfire.schemas import FilePermissions, FileSettings, KeySettings from desfire.schemas import FilePermissions, FileSettings, KeySettings
from dotenv import load_dotenv
from fastapi import HTTPException, status from fastapi import HTTPException, status
from smartcard.CardRequest import CardRequest from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType from smartcard.CardType import AnyCardType
@@ -30,7 +29,6 @@ from app.services.settings import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# ENV vars # ENV vars
load_dotenv()
MIFARE_APP_MASTER_KEY = settings.mifare_app_master_key MIFARE_APP_MASTER_KEY = settings.mifare_app_master_key
MIFARE_ACL_READ_BASE_KEY = settings.mifare_acl_read_base_key MIFARE_ACL_READ_BASE_KEY = settings.mifare_acl_read_base_key
MIFARE_ACL_WRITE_BASE_KEY = settings.mifare_acl_write_base_key MIFARE_ACL_WRITE_BASE_KEY = settings.mifare_acl_write_base_key
+3 -3
View File
@@ -11,9 +11,9 @@ class Settings(BaseSettings):
secret_key: str secret_key: str
sqlalchemy_database_url: str = "sqlite:///./gatekeeper.db" sqlalchemy_database_url: str = "sqlite:///./gatekeeper.db"
lock_pin: int = 17 mqtt_host: str = "localhost"
unlock_pin: int = 18 mqtt_port: int = 1883
mock_gpio: bool = True mock: bool = True
alembic_config: str = "./alembic.ini" alembic_config: str = "./alembic.ini"
disable_cards: bool = False disable_cards: bool = False
+4 -4
View File
@@ -15,10 +15,10 @@ in
description = "Where to save the database."; description = "Where to save the database.";
default = "/var/lib/gatekeeper"; default = "/var/lib/gatekeeper";
}; };
mockGpio = lib.mkOption { mock = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "True"; default = "False";
description = "Mock GPIO pins. Has to be a string!"; description = "Mock the doorcontroller. Has to be a string!";
}; };
}; };
}; };
@@ -67,7 +67,7 @@ in
environment = { environment = {
SQLALCHEMY_DATABASE_URL = "sqlite:///${cfg.db}/gatekeeper.db"; SQLALCHEMY_DATABASE_URL = "sqlite:///${cfg.db}/gatekeeper.db";
ALEMBIC_CONFIG = "${self}/alembic.ini"; ALEMBIC_CONFIG = "${self}/alembic.ini";
MOCK_GPIO = cfg.mockGpio; MOCK = cfg.mock;
}; };
}; };
}; };
+8 -4
View File
@@ -11,15 +11,19 @@ dependencies = [
"python-desfire", "python-desfire",
"pyjwt[crypto]>=2.12.1", "pyjwt[crypto]>=2.12.1",
"pwdlib[argon2]>=0.3.0", "pwdlib[argon2]>=0.3.0",
"pytest>=9.0.3",
"requests>=2.33.1", "requests>=2.33.1",
"pytest-cov>=7.1.0",
"setuptools>=82.0.1", "setuptools>=82.0.1",
"pyscard>=2.3.1", "pyscard>=2.3.1",
"alembic>=1.18.5", "alembic>=1.18.5",
"ruff>=0.16.0",
"pydantic-settings>=2.13.1", "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 = [
"ruff>=0.16.0",
"pytest>=9.0.3",
"pytest-cov>=7.1.0",
"jedi-language-server>=0.47.0",
] ]
[tool.uv.sources] [tool.uv.sources]
+62
View File
@@ -112,3 +112,65 @@ def test_check_access_invalid_card(db_session):
# Should raise exception for non-existent card # Should raise exception for non-existent card
with pytest.raises(Exception): with pytest.raises(Exception):
checkAccess("non-existent-key", db_session) 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
Generated
+149 -8
View File
@@ -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" }, { 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]] [[package]]
name = "build" name = "build"
version = "1.4.3" version = "1.4.3"
@@ -125,6 +134,19 @@ filecache = [
{ name = "filelock" }, { 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]] [[package]]
name = "certifi" name = "certifi"
version = "2026.2.25" 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" }, { 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]] [[package]]
name = "dulwich" name = "dulwich"
version = "1.1.0" version = "1.1.0"
@@ -624,40 +659,52 @@ source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "alembic" }, { name = "alembic" },
{ name = "fastapi", extra = ["standard"] }, { name = "fastapi", extra = ["standard"] },
{ name = "lgpio" }, { name = "lgpio", marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" },
{ name = "paho-mqtt" },
{ name = "poetry" }, { name = "poetry" },
{ name = "pwdlib", extra = ["argon2"] }, { name = "pwdlib", extra = ["argon2"] },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] }, { name = "pyjwt", extra = ["crypto"] },
{ name = "pyscard" }, { name = "pyscard" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "python-desfire" }, { name = "python-desfire" },
{ name = "requests" }, { name = "requests" },
{ name = "ruff" },
{ name = "setuptools" }, { name = "setuptools" },
{ name = "sqlmodel" }, { name = "sqlmodel" },
] ]
[package.dev-dependencies]
dev = [
{ name = "jedi-language-server" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "ruff" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "alembic", specifier = ">=1.18.5" }, { name = "alembic", specifier = ">=1.18.5" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.135.3" }, { 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 = "paho-mqtt", specifier = ">=2.1.0" },
{ name = "poetry", specifier = ">=2.3.4" }, { name = "poetry", specifier = ">=2.3.4" },
{ name = "pwdlib", extras = ["argon2"], specifier = ">=0.3.0" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.3.0" },
{ name = "pydantic-settings", specifier = ">=2.13.1" }, { name = "pydantic-settings", specifier = ">=2.13.1" },
{ name = "pyjwt", extras = ["crypto"], specifier = ">=2.12.1" }, { name = "pyjwt", extras = ["crypto"], specifier = ">=2.12.1" },
{ name = "pyscard", specifier = ">=2.3.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 = "python-desfire", git = "https://github.com/waza-ari/python-desfire" },
{ name = "requests", specifier = ">=2.33.1" }, { name = "requests", specifier = ">=2.33.1" },
{ name = "ruff", specifier = ">=0.16.0" },
{ name = "setuptools", specifier = ">=82.0.1" }, { name = "setuptools", specifier = ">=82.0.1" },
{ name = "sqlmodel", specifier = ">=0.0.38" }, { 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]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.4.0" version = "3.4.0"
@@ -757,6 +804,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" }, { 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]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "2.3.0" version = "2.3.0"
@@ -808,6 +867,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" }, { 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]] [[package]]
name = "jeepney" name = "jeepney"
version = "0.9.0" version = "0.9.0"
@@ -852,6 +939,19 @@ version = "0.2.2.0"
source = { registry = "https://pypi.org/simple" } 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" } 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]] [[package]]
name = "mako" name = "mako"
version = "1.3.12" version = "1.3.12"
@@ -990,6 +1090,24 @@ 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" }, { 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"
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]] [[package]]
name = "pbs-installer" name = "pbs-installer"
version = "2026.4.7" version = "2026.4.7"
@@ -1231,6 +1349,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" }, { 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]] [[package]]
name = "pygments" name = "pygments"
version = "2.20.0" version = "2.20.0"
@@ -1924,6 +2056,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" }, { 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]] [[package]]
name = "zstandard" name = "zstandard"
version = "0.25.0" version = "0.25.0"