API is now stable-ish

This commit is contained in:
Jurn Wubben 2025-08-28 11:23:25 +02:00
parent 2ae520f6b3
commit 0721edd431
2 changed files with 72 additions and 67 deletions

115
app.py
View file

@ -1,13 +1,14 @@
from __future__ import annotations from __future__ import annotations
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from time import sleep
from typing import Annotated from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException from fastapi import Depends, FastAPI, HTTPException
from sqlmodel import SQLModel, Session, create_engine, delete, select from sqlmodel import SQLModel, Session, create_engine, select
import netbrite as nb import netbrite as nb
from db import ( from db import (
MessageDB, MessageDB,
MessagePublic, MessageUpdate,
NetBriteBase, NetBriteBase,
NetBriteDB, NetBriteDB,
NetBritePublic, NetBritePublic,
@ -38,7 +39,6 @@ async def lifespan(_: FastAPI):
app = FastAPI(lifespan=lifespan) app = FastAPI(lifespan=lifespan)
active_devices: dict[int, nb.NetBrite] = {} active_devices: dict[int, nb.NetBrite] = {}
@ -65,17 +65,7 @@ def load_zones_id(session: Session, device_id: int, net_dev: nb.NetBrite):
load_zones(zones, net_dev) load_zones(zones, net_dev)
def load_empty(net_dev: nb.NetBrite):
message = nb.Message("clearing...")
zone = nb.Zone(0, 0, 150, 7)
net_dev.zones({"empty": zone})
net_dev.message(message, "empty")
def load_zones(zones_in: list[ZoneDB], net_dev: nb.NetBrite) -> None: def load_zones(zones_in: list[ZoneDB], net_dev: nb.NetBrite) -> None:
load_empty(net_dev)
zones: dict[str, nb.Zone] = {} zones: dict[str, nb.Zone] = {}
messages: dict[str, nb.Message] = {} messages: dict[str, nb.Message] = {}
@ -107,12 +97,15 @@ def load_zones(zones_in: list[ZoneDB], net_dev: nb.NetBrite) -> None:
initial_text=default_msg, initial_text=default_msg,
) )
messages[zone.name] = default_msg messages[zone.name] = default_msg
# print(f"{zone.name}: {default_msg.text}")
if zones: if zones:
net_dev.zones(zones) net_dev.zones(zones)
sleep(0.2)
for zone, message in messages.items(): for zone, message in messages.items():
net_dev.message(message, zone) net_dev.message(message, zone)
sleep(0.2)
def create_default_zone(session: Session, device_id: int) -> None: def create_default_zone(session: Session, device_id: int) -> None:
@ -166,12 +159,24 @@ def get_devices(session: SessionDep):
return devices return devices
@app.post("/api/devices/{device_id}", response_model=NetBritePublic) @app.post(
"/api/devices/{device_id}",
response_model=NetBritePublic,
description="**NOTE**: this **WILL** disconnect the device. You'll have to manually reconnect it.",
)
def edit_device(device_id: int, updated_device: NetBriteUpdate, session: SessionDep): def edit_device(device_id: int, updated_device: NetBriteUpdate, session: SessionDep):
db_dev = session.get(NetBriteDB, device_id) db_dev = session.get(NetBriteDB, device_id)
if not db_dev: if not db_dev:
raise HTTPException(404, "Device not found") raise HTTPException(404, "Device not found")
if device_id in active_devices:
try:
active_devices[device_id].sock.close()
except OSError:
print("Failed to close socket.")
del active_devices[device_id]
dev_data = updated_device.model_dump(exclude_unset=True) dev_data = updated_device.model_dump(exclude_unset=True)
_ = db_dev.sqlmodel_update(dev_data) _ = db_dev.sqlmodel_update(dev_data)
@ -179,7 +184,7 @@ def edit_device(device_id: int, updated_device: NetBriteUpdate, session: Session
session.commit() session.commit()
session.refresh(db_dev) session.refresh(db_dev)
return db_dev # TODO: update device return db_dev
@app.delete("/api/devices/{device_id}") @app.delete("/api/devices/{device_id}")
@ -195,7 +200,10 @@ def delete_device(device_id: int, session: SessionDep):
delete.append(zone) delete.append(zone)
if device_id in active_devices: if device_id in active_devices:
active_devices[device_id].sock.close() try:
active_devices[device_id].sock.close()
except OSError:
print("Failed to close socket.")
del active_devices[device_id] del active_devices[device_id]
for i in delete: for i in delete:
@ -212,7 +220,8 @@ def reconnect_device(device_id: int, session: SessionDep):
raise HTTPException(404, "Device not found") raise HTTPException(404, "Device not found")
try: try:
active_devices[device_id] = nb.NetBrite(db_dev.address, db_dev.port) new_netbrite = nb.NetBrite(db_dev.address, db_dev.port)
active_devices[device_id] = new_netbrite
load_zones_id(session, device_id, active_devices[device_id]) load_zones_id(session, device_id, active_devices[device_id])
return 200 return 200
except nb.NetbriteConnectionException as exc: except nb.NetbriteConnectionException as exc:
@ -230,12 +239,8 @@ def sync_device(
if device_id not in active_devices: if device_id not in active_devices:
raise HTTPException(500, "Device not active, try reconnecting") raise HTTPException(500, "Device not active, try reconnecting")
db_dev = session.get(NetBriteDB, device_id)
if not db_dev:
raise HTTPException(404, "Device not found")
try: try:
load_zones(db_dev.zones, active_devices[device_id]) load_zones_id(session, device_id, active_devices[device_id])
except nb.NetbriteTransferException: except nb.NetbriteTransferException:
del active_devices[device_id] del active_devices[device_id]
raise HTTPException(500, "Failed to send zones. Device inactive now.") raise HTTPException(500, "Failed to send zones. Device inactive now.")
@ -248,7 +253,6 @@ def sync_device(
) )
def restart_device( def restart_device(
device_id: int, device_id: int,
session: SessionDep,
): ):
if device_id not in active_devices: if device_id not in active_devices:
raise HTTPException(500, "Device not active, try reconnecting") raise HTTPException(500, "Device not active, try reconnecting")
@ -257,23 +261,7 @@ def restart_device(
active_devices[device_id].reboot() active_devices[device_id].reboot()
del active_devices[device_id] del active_devices[device_id]
except nb.NetbriteTransferException: except nb.NetbriteTransferException:
raise HTTPException(500, "Failed to send zones. Device inactive now.") raise HTTPException(500, "Failed to send reboot command. Device inactive now.")
return 200
@app.post(
"/api/devices/{device_id}/reload",
description="**NOTE**: Will remove all zones and reload them with **default** messages",
)
def reload_device(device_id: int, session: SessionDep):
if device_id not in active_devices:
raise HTTPException(500, "Device not active, try reconnecting")
try:
load_zones_id(session, device_id, active_devices[device_id])
except nb.NetbriteTransferException:
raise HTTPException(500, "Failed to send zones. Device inactive now.")
return 200 return 200
@ -362,33 +350,46 @@ def edit_zone(zone_id: int, zone: ZoneUpdate, session: SessionDep):
@app.post( @app.post(
"/api/zone/{zone_id}/adhoc_message", "/api/zone/{zone_id}/message",
response_model=ZonePublic, response_model=ZonePublic,
description="**NOTE**: this does not update the device.", description="**NOTE**: this does not update the device.",
) )
def edit_message(zone_id: int, message: MessagePublic, session: SessionDep): def edit_message(zone_id: int, message: MessageUpdate, session: SessionDep):
zone_db = session.get(ZoneDB, zone_id) zone_db = session.get(ZoneDB, zone_id)
if not zone_db: if not zone_db:
raise HTTPException(404, "Zone not found") raise HTTPException(404, "Zone not found")
device_id = zone_db.netbrite_id if not zone_db.default_message:
if not device_id in active_devices: db_message = MessageDB.model_validate(message)
raise HTTPException(500, "Device inactive")
session.add(zone_db) session.add(db_message)
session.commit() session.commit()
session.refresh(zone_db) session.refresh(db_message)
zone_db.default_message_id = db_message.id
session.add(zone_db)
session.commit()
session.refresh(zone_db)
else:
db_message = zone_db.default_message
data = message.model_dump(
exclude_unset=True,
)
_ = db_message.sqlmodel_update(data, update={"id": db_message.id})
session.add(db_message)
session.commit()
session.refresh(zone_db)
return zone_db return zone_db
@app.post( @app.post(
"/api/zone/{zone_id}/adhoc_message", "/api/zone/{zone_id}/adhoc_message",
response_model=ZonePublic,
description="**NOTE**: this updates the device temporarily. Edit the zone message for permanent changes.", description="**NOTE**: this updates the device temporarily. Edit the zone message for permanent changes.",
) )
def adhoc_message(zone_id: int, message: MessagePublic, session: SessionDep): def adhoc_message(zone_id: int, message: MessageUpdate, session: SessionDep):
zone_db = session.get(ZoneDB, zone_id) zone_db = session.get(ZoneDB, zone_id)
if not zone_db: if not zone_db:
@ -399,13 +400,13 @@ def adhoc_message(zone_id: int, message: MessagePublic, session: SessionDep):
raise HTTPException(500, "Device inactive") raise HTTPException(500, "Device inactive")
nb_message = nb.Message( nb_message = nb.Message(
text=message.text, text=message.text or "",
activation_delay=message.activation_delay, activation_delay=message.activation_delay or 0,
display_delay=message.display_delay, display_delay=message.display_delay or 0,
display_repeat=message.display_repeat, display_repeat=message.display_repeat or 0,
priority=message.priority, priority=message.priority or nb.Priorities.OVERRIDE,
sound_alarm=message.sound_alarm, sound_alarm=message.sound_alarm or False,
ttl=message.ttl, ttl=message.ttl or 0,
) )
try: try:

24
db.py
View file

@ -2,25 +2,29 @@
from sqlmodel import Field, Relationship, SQLModel from sqlmodel import Field, Relationship, SQLModel
from netbrite import Colors, Fonts, Priorities, ScrollSpeeds, Message from netbrite import Colors, Fonts, Priorities, ScrollSpeeds, Message
MAX_WIDTH = 120
MAX_HEIGHT = 7
# --- Message --- # --- Message ---
class MessageBase(SQLModel): class MessageBase(SQLModel):
text: str = ""
activation_delay: int = 0 activation_delay: int = 0
display_delay: int = 0 display_delay: int = 0
display_repeat: int = 0 display_repeat: int = 0
priority: Priorities = Priorities.OVERRIDE priority: Priorities = Priorities.OVERRIDE
sound_alarm: bool = False sound_alarm: bool = False
text: str = ""
ttl: int = 0 ttl: int = 0
# class MessageUpdate(SQLModel): class MessageUpdate(SQLModel):
# activation_delay: int | None = 0 text: str | None = ""
# display_delay: int | None = 0 activation_delay: int | None = 0
# display_repeat: int | None = 0 display_delay: int | None = 0
# priority: Priorities | None = Priorities.OVERRIDE display_repeat: int | None = 0
# text: str | None = "" priority: Priorities | None = Priorities.OVERRIDE
# ttl: int | None = 0 sound_alarm: bool = False
ttl: int | None = 0
class MessageDB(MessageBase, table=True): class MessageDB(MessageBase, table=True):
@ -63,8 +67,8 @@ class ZoneBase(SQLModel):
name: str name: str
x: int = 0 x: int = 0
y: int = 0 y: int = 0
width: int = 120 width: int = MAX_WIDTH
height: int = 7 height: int = MAX_HEIGHT
scroll_speed: ScrollSpeeds = ScrollSpeeds.NORMAL scroll_speed: ScrollSpeeds = ScrollSpeeds.NORMAL
pause_duration: int = 1000 pause_duration: int = 1000
volume: int = 4 volume: int = 4