api: implement operating locks

This commit is contained in:
lilly 2026-05-14 15:47:59 +02:00
commit 60c4770280
Signed by: lilly
SSH key fingerprint: SHA256:y9T5GFw2A20WVklhetIxG1+kcg/Ce0shnQmbu1LQ37g
3 changed files with 45 additions and 12 deletions

View file

@ -240,9 +240,37 @@ async def list_locks(
@app.patch(
"/api/locks/{name}",
"/api/locks/{lock_id}",
tags=["locks"],
responses={status.HTTP_401_UNAUTHORIZED: {"model": models.HttpProblemDetail}},
responses={
status.HTTP_401_UNAUTHORIZED: {"model": models.HttpProblemDetail},
status.HTTP_404_NOT_FOUND: {"model": models.HttpProblemDetail},
},
)
async def operate_lock(name: str):
pass
async def operate_lock(req: Request, lock_id: str, requested_op: models.LockOperation, ccujack: deps.CCUJackClient, _current_user: deps.CurrentUser) -> None:
# TODO: Validate that the user is authorized
# find appropriate lock from ccujack
for i_lock, lock_channels in ccujack.locks:
if i_lock.identifier == lock_id:
for i_channel, channel_params in lock_channels:
if i_channel.type == "DOOR_LOCK_STATE_TRANSMITTER":
for i_param in channel_params:
if i_param.id == "LOCK_TARGET_LEVEL":
addr = f"{i_lock.address}/{i_channel.index}/{i_param.id}"
# match readable request parameter to ccujack value
match requested_op.desired_state:
case "closed":
ccujack_value = 0
case "open":
ccujack_value = 1
# write to ccujack
await ccujack.set_param_value(addr, ccujack_value)
return
else:
raise exceptions.HttpProblemException(
models.HttpProblemDetail.new_lock_not_found(lock_id, req.url)
)

View file

@ -83,13 +83,14 @@ class CCUJackClient:
]
async def query_param_value(self, address):
async def query_param_value(self, address: str):
logger.debug("Querying parameter value from '%s'", address)
async with self.http.get(f"/device/{address}/~pv") as resp:
return CCUValue.model_validate(await resp.json())
# async def toggle_lock(self, lock_id, new_state):
# pass
async def set_param_value(self, address: str, value: Any):
logger.debug("Writing parameter value '%s' to '%s'", value, address)
await self.http.put(f"/device/{address}/~pv", json={"v": value})
async def _inspect_ccu_device(self, device_ref: CCURef) -> Tuple[CCUDeviceInfo, List[Tuple[CCUChannelInfo, List[CCUParamInfo]]]]:
logger.debug("Inspecting device '%s' (%s)", device_ref.href, device_ref.title)

View file

@ -13,7 +13,7 @@ class HttpProblemType(Enum):
"""
UNAUTHORIZED = "type:noc@hamburg.ccc.de,2026:UNAUTHORIZED"
DOOR_NOT_FOUND = "type:noc@hamburg.ccc.de,2026:DOOR_NOT_FOUND"
LOCK_NOT_FOUND = "type:noc@hamburg.ccc.de,2026:LOCK_NOT_FOUND"
class HttpProblemDetail(BaseModel):
@ -38,12 +38,12 @@ class HttpProblemDetail(BaseModel):
)
@classmethod
def new_door_not_found(cls, requested_door: str, request_uri: str | URL) -> Self:
def new_lock_not_found(cls, requested_lock: str, request_uri: str | URL) -> Self:
return cls(
type=HttpProblemType.DOOR_NOT_FOUND,
type=HttpProblemType.LOCK_NOT_FOUND,
status=status.HTTP_404_NOT_FOUND,
title="Door not found",
detail=f"You tried to interact with door {requested_door!r} that is not known to dooris",
title="Lock not found",
detail=f"You tried to interact with lock {requested_lock!r} that is not known to dooris",
instance=str(request_uri),
)
@ -73,3 +73,7 @@ class Lock(BaseModel):
name: str
id: str
status: LockStatus
class LockOperation(BaseModel):
desired_state: Literal["open", "closed"]