diff --git a/app.py b/app.py index cf3378f..bb8096d 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,14 @@ from __future__ import annotations from contextlib import asynccontextmanager +from time import sleep from typing import Annotated 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 from db import ( MessageDB, - MessagePublic, + MessageUpdate, NetBriteBase, NetBriteDB, NetBritePublic, @@ -38,7 +39,6 @@ async def lifespan(_: FastAPI): app = FastAPI(lifespan=lifespan) - 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) -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: - load_empty(net_dev) - zones: dict[str, nb.Zone] = {} 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, ) messages[zone.name] = default_msg + # print(f"{zone.name}: {default_msg.text}") if zones: net_dev.zones(zones) + sleep(0.2) for zone, message in messages.items(): net_dev.message(message, zone) + sleep(0.2) def create_default_zone(session: Session, device_id: int) -> None: @@ -166,12 +159,24 @@ def get_devices(session: SessionDep): 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): db_dev = session.get(NetBriteDB, device_id) if not db_dev: 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) _ = db_dev.sqlmodel_update(dev_data) @@ -179,7 +184,7 @@ def edit_device(device_id: int, updated_device: NetBriteUpdate, session: Session session.commit() session.refresh(db_dev) - return db_dev # TODO: update device + return db_dev @app.delete("/api/devices/{device_id}") @@ -195,7 +200,10 @@ def delete_device(device_id: int, session: SessionDep): delete.append(zone) 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] for i in delete: @@ -212,7 +220,8 @@ def reconnect_device(device_id: int, session: SessionDep): raise HTTPException(404, "Device not found") 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]) return 200 except nb.NetbriteConnectionException as exc: @@ -230,12 +239,8 @@ def sync_device( if device_id not in active_devices: 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: - load_zones(db_dev.zones, active_devices[device_id]) + load_zones_id(session, device_id, active_devices[device_id]) except nb.NetbriteTransferException: del active_devices[device_id] raise HTTPException(500, "Failed to send zones. Device inactive now.") @@ -248,7 +253,6 @@ def sync_device( ) def restart_device( device_id: int, - session: SessionDep, ): if device_id not in active_devices: raise HTTPException(500, "Device not active, try reconnecting") @@ -257,23 +261,7 @@ def restart_device( active_devices[device_id].reboot() del active_devices[device_id] except nb.NetbriteTransferException: - raise HTTPException(500, "Failed to send zones. 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.") + raise HTTPException(500, "Failed to send reboot command. Device inactive now.") return 200 @@ -362,33 +350,46 @@ def edit_zone(zone_id: int, zone: ZoneUpdate, session: SessionDep): @app.post( - "/api/zone/{zone_id}/adhoc_message", + "/api/zone/{zone_id}/message", response_model=ZonePublic, 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) if not zone_db: raise HTTPException(404, "Zone not found") - device_id = zone_db.netbrite_id - if not device_id in active_devices: - raise HTTPException(500, "Device inactive") + if not zone_db.default_message: + db_message = MessageDB.model_validate(message) - session.add(zone_db) - session.commit() - session.refresh(zone_db) + session.add(db_message) + session.commit() + 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 @app.post( "/api/zone/{zone_id}/adhoc_message", - response_model=ZonePublic, 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) if not zone_db: @@ -399,13 +400,13 @@ def adhoc_message(zone_id: int, message: MessagePublic, session: SessionDep): raise HTTPException(500, "Device inactive") nb_message = nb.Message( - text=message.text, - activation_delay=message.activation_delay, - display_delay=message.display_delay, - display_repeat=message.display_repeat, - priority=message.priority, - sound_alarm=message.sound_alarm, - ttl=message.ttl, + text=message.text or "", + activation_delay=message.activation_delay or 0, + display_delay=message.display_delay or 0, + display_repeat=message.display_repeat or 0, + priority=message.priority or nb.Priorities.OVERRIDE, + sound_alarm=message.sound_alarm or False, + ttl=message.ttl or 0, ) try: diff --git a/db.py b/db.py index c70ec7d..9d46fb5 100644 --- a/db.py +++ b/db.py @@ -2,25 +2,29 @@ from sqlmodel import Field, Relationship, SQLModel from netbrite import Colors, Fonts, Priorities, ScrollSpeeds, Message +MAX_WIDTH = 120 +MAX_HEIGHT = 7 + # --- Message --- class MessageBase(SQLModel): + text: str = "" activation_delay: int = 0 display_delay: int = 0 display_repeat: int = 0 priority: Priorities = Priorities.OVERRIDE sound_alarm: bool = False - text: str = "" ttl: int = 0 -# class MessageUpdate(SQLModel): -# activation_delay: int | None = 0 -# display_delay: int | None = 0 -# display_repeat: int | None = 0 -# priority: Priorities | None = Priorities.OVERRIDE -# text: str | None = "" -# ttl: int | None = 0 +class MessageUpdate(SQLModel): + text: str | None = "" + activation_delay: int | None = 0 + display_delay: int | None = 0 + display_repeat: int | None = 0 + priority: Priorities | None = Priorities.OVERRIDE + sound_alarm: bool = False + ttl: int | None = 0 class MessageDB(MessageBase, table=True): @@ -63,8 +67,8 @@ class ZoneBase(SQLModel): name: str x: int = 0 y: int = 0 - width: int = 120 - height: int = 7 + width: int = MAX_WIDTH + height: int = MAX_HEIGHT scroll_speed: ScrollSpeeds = ScrollSpeeds.NORMAL pause_duration: int = 1000 volume: int = 4