diff --git a/.gitignore b/.gitignore index da7fbc2..d0a5a31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .direnv __pycache__/ +devices.db diff --git a/app.py b/app.py new file mode 100644 index 0000000..2ed7fe0 --- /dev/null +++ b/app.py @@ -0,0 +1,115 @@ +from contextlib import asynccontextmanager +from nt import device_encoding +from typing import Annotated +from fastapi import Depends, FastAPI, HTTPException +from sqlmodel import SQLModel, Session, col, create_engine, select +from netbrite import NetBrite, NetbriteConnectionException, Zone +from db import ( + APIAddNetBriteDevice, + APIGetNetBriteDevice, + APIGetZone, + APIMessages, + BaseNetBriteDevice, + BaseZone, +) + +sqlite_file_name = "devices.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + print("creating tables") + create_db_and_tables() + yield + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI(lifespan=lifespan) +devices: list[NetBrite] = [] + + +@app.post("/api/device") +def create_device(device: APIAddNetBriteDevice, session: SessionDep): + statement = select(BaseNetBriteDevice).where( + col(BaseNetBriteDevice.address) == device.address + ) + result = session.exec(statement) + if result.first() != None: + raise HTTPException(400, "Device is already added") + + try: + netbrite_device = NetBrite(device.address, device.port) + devices.append(netbrite_device) + except NetbriteConnectionException as exc: + raise HTTPException(400, "Failed to connect to device") + + dbdevice = BaseNetBriteDevice(address=device.address, port=device.port) + session.add(dbdevice) + session.commit() + session.refresh(dbdevice) + + return device + + +@app.get("/api/devices") +def get_devices(session: SessionDep): + db_devices: dict[int, BaseNetBriteDevice] = {} + + statement = select(BaseZone, BaseNetBriteDevice).join(BaseNetBriteDevice) + results = session.exec(statement) + + for zone, device in results: + if device.id == None: + continue + if not device.id in db_devices: + connected = devices.inde + get_device = APIGetNetBriteDevice(device., zones={}) + db_devices[device.id] = device + + db_devices[device.id].zones_list + + + for device in devices: + + zones: dict[str, APIGetZone] = {} + for index, zone in device.zones_list.items(): + x, y, xend, yend = zone.rect + width = xend - x + height = yend - y + + zones[index] = APIGetZone( + x=x, + y=y, + width=width, + height=height, + scroll_speed=zone.scroll_speed, + pause_duration=zone.pause_duration, + volume=zone.volume, + default_font=zone.default_font, + default_color=zone.default_color, + default_message=zone.initial_text, + ) + + return devices + + +@app.get("/api/devices/{device_index}") +def get_device(device_index: int): + if device_index > len(devices) - 1: + raise HTTPException(400, "Device doesn't exist") + + return devices[device_index] diff --git a/db.py b/db.py new file mode 100644 index 0000000..f4524d4 --- /dev/null +++ b/db.py @@ -0,0 +1,65 @@ +from sqlmodel import Field, Session, SQLModel, create_engine, select + +from netbrite import Colors, Fonts, Message, Priorities, ScrollSpeeds + + +class BaseZone(SQLModel, table=True, echo=True): + id: int | None = Field(default=None, primary_key=True) + + x: int + y: int + width: int + height: int + scroll_speed: ScrollSpeeds + pause_duration: int + volume: int + default_font: Fonts + default_color: Colors + + default_message: int | None = Field(foreign_key="apimessages.id") + netbrite_device_id: int = Field(foreign_key="apinetbritedevice.id") + + +class APIGetZone(SQLModel, table=True, echo=True): + id: int | None = Field(default=None, primary_key=True) + + x: int + y: int + width: int + height: int + scroll_speed: ScrollSpeeds + pause_duration: int + volume: int + default_font: Fonts + default_color: Colors + + default_message: Message | None + + +class APIMessages(SQLModel, table=True, echo=True): + id: int | None = Field(default=None, primary_key=True) + + activation_delay: int + display_delay: int + display_repeat: int + priority: Priorities + text: str + ttl: int + + +class BaseNetBriteDevice(SQLModel, table=True, echo=True): + id: int | None = Field(default=None, primary_key=True) + address: str = Field(unique=True) + port: int = 700 + + +class APIAddNetBriteDevice(SQLModel, table=False): + address: str + port: int = 700 + + +class APIGetNetBriteDevice(SQLModel, table=False): + address: str + port: int = 700 + connected: bool + zones: dict[str, APIGetZone] diff --git a/flake.nix b/flake.nix index 53bf9e0..ac1634a 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ nativeBuildInputs = [ pkgs.entr pkgs.fastapi-cli - (pkgs.python3.withPackages (x: [x.crc x.fastapi])) + (pkgs.python3.withPackages (x: [x.crc x.fastapi x.sqlmodel x.sqlalchemy])) ]; }; }; diff --git a/netbrite.py b/netbrite.py index ba97efb..1a39dbb 100644 --- a/netbrite.py +++ b/netbrite.py @@ -10,6 +10,14 @@ import re DEFAULT_PORT = 700 +class NetbriteConnectionException(Exception): + pass + + +class NetbriteTransferException(Exception): + pass + + class Colors(Enum): RED = 0x01 GREEN = 0x02 @@ -101,7 +109,7 @@ class Message: (rb"\{right\}", b"\x10\x28"), (rb"\{pause\}", b"\x10\x05"), (rb"\{erase\}", b"\x10\x03"), - (rb"\{serial\}", b"\x10\x09"), + (rb"\{serialnum\}", b"\x10\x09"), (rb"\{bell\}", b"\x10\x05"), (rb"\{red\}", b"\x10\x0c" + pack("B", Colors.RED.value)), (rb"\{green\}", b"\x10\x0c" + pack("B", Colors.GREEN.value)), @@ -183,16 +191,26 @@ class NetBrite: try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.settimeout(5000) + self.sock.settimeout(2) self.connect() except OSError as e: - raise ConnectionError(f"Error while opening network socket. {e}") + raise NetbriteConnectionException( + f"Error while opening network socket. {e}" + ) def connect(self): - self.sock.connect((self.address, self.port)) + try: + self.sock.connect((self.address, self.port)) + except OSError as e: + raise NetbriteConnectionException( + f"Error while opening network socket. {e}" + ) def tx(self, pkt: bytes): - _ = self.sock.send(pkt) + try: + _ = self.sock.send(pkt) + except OSError as e: + raise NetbriteTransferException(f"Error while opening network socket. {e}") def message(self, msg: Message, zoneName: str): z = self.zones_list.get(zoneName)