Compare commits
1 Commits
master
...
change_tt_
| Author | SHA1 | Date | |
|---|---|---|---|
|
d7af294e8b
|
40
README.md
40
README.md
@@ -1,39 +1,17 @@
|
|||||||
## Gatekeeper - Door access system
|
## Gatekeeper - Door access system
|
||||||
#### Status: WIP - getting there o.o
|
Status: WIP - not prod ready
|
||||||
|
|
||||||
Start prod server `nix run`<br>
|
Dev with `nix develop`
|
||||||
Start dev server `nix run .#dev` or `nix run .#dev -- {args}`<br>
|
sync python deps with `uv sync`
|
||||||
Interactive dev with `nix develop`, then sync deps with `uv sync`<br>
|
start dev server `uv run fastapi dev`
|
||||||
Swagger UI @ http://127.0.0.1:8000/api/v1/docs<br>
|
Swagger UI @ http://127.0.0.1:8000/docs
|
||||||
OpenApi @ http://127.0.0.1:8000/api/v1/openapi.json<br>
|
start prod server `uv run fastapi run`
|
||||||
|
|
||||||
#### Config:
|
You need to set services.pcscd.enable = true; for the smartcard reader to work
|
||||||
|
|
||||||
Nixos config for the card reader:
|
Issues:
|
||||||
```
|
|
||||||
services.pcscd = {
|
|
||||||
enable = true;
|
|
||||||
plugins = [ pkgs.acsccid ]; #<-- Needed for the ACE1552U
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This application is configured using env vars. You can use `cp .env.example .env` and then edit `.env` to change the keys to something else.
|
|
||||||
|
|
||||||
|
|
||||||
### A note on keycard scanners
|
|
||||||
I have tried two scanners while developing this project, the TWN4 MultiTech 2 from elatec and the ACR1552U from acs.<br>
|
|
||||||
##### TWN4
|
|
||||||
From the factory the TWN4 comes with firmware that emulates a HID and just inputs the serial of any scanned card. To be usable with pcscd it had to be flashed with the `TWN4_xPx520_S1SC161_Multi_CCID_1Slot_Standard.bix` firmware found in the `TWN4DevPack520` which can be downloaded under https://www.elatec-rfid.com/int/elatec-software (They send you a download link via email).
|
|
||||||
This software seems to not work under wine. I had to setup a win10 vm and pass the usb to be able to flash.<br>
|
|
||||||
Range: In my testing the TWN4 has a range of about 22mm when interfacing with our Mifare DESFire v2 cards. This is not enough for our idea of mounting the scanner on the inside of a glass window.
|
|
||||||
|
|
||||||
#### ACR1552U
|
|
||||||
The ACR1552U comes preloaded with PC/SC firmware. You need to install the acsccid package with your package-manager of choice (Nixos user see above) for the scanner to work but after that I had no problems with the hardware interfacing.<br>
|
|
||||||
Range: The range of the ACR1552U was much better at over 60mm (almost 70mm if you take of the face plate)
|
|
||||||
|
|
||||||
|
|
||||||
#### Issues:
|
|
||||||
- documentation missing
|
- documentation missing
|
||||||
|
- `nix run` currently broken
|
||||||
- raspberry pi image not working
|
- raspberry pi image not working
|
||||||
- no door state
|
- no door state
|
||||||
- no door operations
|
- no door operations
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from sqlmodel import Field, Relationship, Session, SQLModel
|
from sqlmodel import Field, Relationship, Session, SQLModel
|
||||||
from typing import List
|
from typing import List, Literal, Union
|
||||||
from datetime import time
|
from datetime import datetime, time
|
||||||
|
from pydantic import model_validator
|
||||||
|
|
||||||
class Base(SQLModel):
|
class Base(SQLModel):
|
||||||
pass
|
pass
|
||||||
@@ -60,19 +61,49 @@ class GroupResponse(GroupBase):
|
|||||||
#### AccessAuthorization
|
#### AccessAuthorization
|
||||||
class AccessAuthorizationBase(Base):
|
class AccessAuthorizationBase(Base):
|
||||||
name: str = Field(index=True)
|
name: str = Field(index=True)
|
||||||
|
type: Literal["timetable", "oneshot", "somefuturespec"]
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
|
||||||
class AccessAuthorizationDB(AccessAuthorizationBase, table=True):
|
class AccessAuthorizationDB(AccessAuthorizationBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
type: str
|
||||||
groups: List["GroupDB"] = Relationship(back_populates="accessauths", link_model=AaGroupLink)
|
groups: List["GroupDB"] = Relationship(back_populates="accessauths", link_model=AaGroupLink)
|
||||||
timetables: List["Timetable"] = Relationship(back_populates="accessauth", cascade_delete=True)
|
timetables: List["Timetable"] = Relationship(back_populates="accessauth", cascade_delete=True)
|
||||||
|
oneshot: "OneShotAccess" = Relationship(back_populates="accessauth", cascade_delete=True)
|
||||||
|
|
||||||
|
class OneShotAccessBase(Base):
|
||||||
|
uses: int = 1
|
||||||
|
ends_at: datetime
|
||||||
|
|
||||||
|
class OneShotAccess(OneShotAccessBase, table=True):
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
accessauth_id: int = Field(default=None, foreign_key="accessauthorizationdb.id")
|
||||||
|
accessauth: AccessAuthorizationDB = Relationship(back_populates="oneshot")
|
||||||
|
|
||||||
class AccessAuthorizationCreate(AccessAuthorizationBase):
|
class AccessAuthorizationCreate(AccessAuthorizationBase):
|
||||||
timetables: List["TimetableCreate"]
|
timetables: List["TimetableCreate"] = []
|
||||||
|
oneshot: OneShotAccessBase | None = None
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def check_type(self):
|
||||||
|
if self.type == "timetable":
|
||||||
|
if not self.timetables:
|
||||||
|
raise ValueError("timetable auths require at least one timetable object")
|
||||||
|
if self.oneshot is not None:
|
||||||
|
raise ValueError("timetable auths are not allowed oneshot objects")
|
||||||
|
elif self.type == "oneshot":
|
||||||
|
if not self.oneshot:
|
||||||
|
raise ValueError("oneshot auths require a oneshot object")
|
||||||
|
if self.timetables:
|
||||||
|
raise ValueError("oneshot auths are not allowed timetable objects")
|
||||||
|
elif self.type == "somefuturespec":
|
||||||
|
raise ValueError("somefuturespec is not jet implemented. Please do not use")
|
||||||
|
return self
|
||||||
|
|
||||||
class AccessAuthorizationResponse(AccessAuthorizationBase):
|
class AccessAuthorizationResponse(AccessAuthorizationBase):
|
||||||
id: int
|
id: int
|
||||||
timetables: List["Timetable"]
|
timetables: List["Timetable"] = []
|
||||||
|
oneshot: OneShotAccessBase | None = None
|
||||||
groups: List["GroupDB"]
|
groups: List["GroupDB"]
|
||||||
|
|
||||||
class AccessAuthorizationUpdate(Base):
|
class AccessAuthorizationUpdate(Base):
|
||||||
|
|||||||
18
flake.nix
18
flake.nix
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
self,
|
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
pyproject-nix,
|
pyproject-nix,
|
||||||
uv2nix,
|
uv2nix,
|
||||||
@@ -106,22 +105,5 @@
|
|||||||
packages = forAllSystems (system: {
|
packages = forAllSystems (system: {
|
||||||
default = pythonSets.${system}.mkVirtualEnv "gatekeeper" workspace.deps.default;
|
default = pythonSets.${system}.mkVirtualEnv "gatekeeper" workspace.deps.default;
|
||||||
});
|
});
|
||||||
|
|
||||||
apps = forAllSystems (system: {
|
|
||||||
default = {
|
|
||||||
type = "app";
|
|
||||||
program = toString (nixpkgs.legacyPackages.${system}.writeShellScript "gatekeeper" ''
|
|
||||||
export LD_LIBRARY_PATH="${lib.getLib nixpkgs.legacyPackages.${system}.pcsclite}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
|
||||||
exec ${self.packages.${system}.default}/bin/fastapi run ${self}/app/main.py "$@";
|
|
||||||
'');
|
|
||||||
};
|
|
||||||
dev = {
|
|
||||||
type = "app";
|
|
||||||
program = toString (nixpkgs.legacyPackages.${system}.writeShellScript "gatekeeper" ''
|
|
||||||
export LD_LIBRARY_PATH="${lib.getLib nixpkgs.legacyPackages.${system}.pcsclite}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
|
||||||
exec ${self.packages.${system}.default}/bin/fastapi run ${self}/app/main.py "$@";
|
|
||||||
'');
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user