Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d965d7
[WIP] Refactor
brandonhs May 9, 2026
f75ba7e
Move schemas to src/models
brandonhs May 11, 2026
9c06bf8
Switch settings and preferences managers to use threading.Lock
brandonhs May 11, 2026
84233b7
Suppress KeyboardInterrupt for cleaner shutdown
brandonhs May 13, 2026
56741fb
Update pylintrc
brandonhs May 13, 2026
9190ee1
Add colored logging
brandonhs May 13, 2026
5ca4fd4
Remove requirement to chmod entire repo to create release
brandonhs May 13, 2026
2a1b0d4
Remove requests from update_versioning script
brandonhs May 13, 2026
a9d83ea
Add support for booleans in models
brandonhs May 13, 2026
79f9108
Refactor entire camera stack:
brandonhs May 13, 2026
9abf5f3
Update frontend to include backend changes
brandonhs May 13, 2026
beaf40f
Remove console.log statement
brandonhs May 13, 2026
91cb701
Add check for control in case it does not exist
brandonhs May 13, 2026
917fd3e
Fix ruff diagnostic issues
brandonhs May 13, 2026
9735b66
Fix type errors
brandonhs May 13, 2026
c9776bb
Fix ruff formatting
brandonhs May 13, 2026
539f5ba
Add colorlog to requirements.txt
brandonhs May 14, 2026
d92b5d4
Fix issue with type checking action resulting in false import errors
brandonhs May 14, 2026
91afd55
Refactor recordings and add data dir for SVC Pro compatibility
brandonhs May 14, 2026
e56c671
[WIP] Asic control improvements and add VTS HTS
brandonhs May 14, 2026
6065a06
Move pyproject.toml for linting fixes
brandonhs May 15, 2026
2e44374
Fix formatting issues from changes with pyproject.toml
brandonhs May 15, 2026
1effbb6
Add debounce for ASIC interface
brandonhs May 15, 2026
da3a2eb
Change SerialPWMController to only log a command when it's connected
brandonhs May 15, 2026
0ca13dd
[WIP] Minor updates to SynchronizedStreamEngine to improve reliability
brandonhs May 15, 2026
96e2278
Update backend action to use caching and virtual environments
brandonhs May 15, 2026
6cddd2e
Significant improvements to streaming system, re-add saved
brandonhs May 16, 2026
0945ead
Fix cameras route by removing unnecessary async and legacy shd code
brandonhs May 18, 2026
ab9d27a
Improve shutdown routine to avoid threading issues and add
brandonhs May 18, 2026
440e82d
Add configurable delay to ASICInterface to remove delay for non shutter
brandonhs May 18, 2026
c5211d2
additional features for recordings tab
jaayzee May 19, 2026
d988927
ruff, i missed one
jaayzee May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ jobs:
build:
runs-on: ubuntu-22.04

defaults:
run:
working-directory: ./backend_py

steps:
- uses: actions/checkout@v4

Expand All @@ -31,21 +27,37 @@ jobs:
with:
python-version: "3.11"

- name: Restore cached virtualenv
id: cache-venv-restore
uses: actions/cache/restore@v5
with:
key: venv-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}
path: .venv

- name: Install dependencies
if: steps.cache-venv-restore.outputs.cache-hit != 'true'
run: | # TODO: switch to using proper dev dependencies
sudo apt-get install build-essential libdbus-glib-1-dev libdbus-1-dev libpython3-dev -y # For dbus-python
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install ruff bandit ty # dev dependencies
./create_venv.sh
source .venv/bin/activate
echo "$VIRTUAL_ENV/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $GITHUB_ENV
pip install ruff bandit ty

- name: Saved cached virtualenv
uses: actions/cache/save@v4
with:
key: ${{ steps.cache-primes-restore.outputs.cache-primary-key }}
path: .venv

- name: Ruff linting
run: ruff check --output-format=github .
run: ruff check --output-format=github backend_py

- name: Ruff formatting
run: ruff format --check .
run: ruff format --check backend_py

- name: Bandit security check (high severity)
run: bandit -r . -lll
run: bandit -r backend_py -lll

- name: Type checking (ty)
run: ty check
run: ty check backend_py
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,6 @@ release.tar.gz
**/server_preferences.json
.env
pi-gen
videos
/recordings/

!frontend/src/lib/
22 changes: 22 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.12
hooks:
# Run the linter.
- id: ruff-check
args: [--fix]
files: ^backend_py/
# Run the formatter.
- id: ruff-format
files: ^backend_py/
# ty
- repo: local
hooks:
- id: ty
name: ty check backend_py
entry: ty check backend_py
language: system
types: [python]
files: ^backend_py/
pass_filenames: false
1 change: 1 addition & 0 deletions backend_py/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ sdbus==0.14.0
sdbus-networkmanager==2.0.0
rtp==0.0.4
pyserial
colorlog==6.10.1
25 changes: 15 additions & 10 deletions backend_py/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
# two is hosted as a uvicorn server, which handles traffic

import asyncio
import contextlib
import logging
import signal
from contextlib import asynccontextmanager

import socketio
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from src import FeatureSupport, Server
from backend_py.src import FeatureSupport, Server

# TODO: narrow
ORIGINS = ["*"]

# Use AsyncServer
Expand All @@ -27,11 +28,8 @@
async def lifespan(app: FastAPI): # noqa: ANN201
await server.serve()
yield
print("Shutting down server...")
try:
server.shutdown()
except Exception as e:
print(f"Error during shutdown: {e}")

# Shutdown should go here, but it isn't always reached


# FastAPI application
Expand All @@ -54,6 +52,7 @@ async def lifespan(app: FastAPI): # noqa: ANN201
FeatureSupport(ttyd=True, wifi=True, serial=True),
sio,
app,
data_dir=".",
settings_path=".",
log_level=logging.DEBUG,
is_dev_mode=True,
Expand All @@ -68,7 +67,13 @@ async def lifespan(app: FastAPI): # noqa: ANN201

async def main() -> None:
config = uvicorn.Config(app, host="0.0.0.0", port=5000, log_level="warning")
server = uvicorn.Server(config)
await server.serve()
uvicorn_server = uvicorn.Server(config)
signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
await uvicorn_server.serve()
finally:
signal.signal(signal.SIGINT, signal.SIG_IGN)
server.shutdown()

asyncio.run(main())
with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
asyncio.run(main())
86 changes: 86 additions & 0 deletions backend_py/src/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from .cameras import (
AddFollowerPayload,
CameraModel,
ControlFlagsModel,
ControlModel,
ControlTypeEnum,
DeviceDescriptorModel,
DeviceInfoModel,
DeviceLeaderModel,
DeviceModel,
DeviceNicknameModel,
DeviceOptionsModel,
DeviceType,
FormatSizeModel,
FrameDropStats,
H264Mode,
IntervalModel,
MenuItemModel,
StreamEncodeTypeEnum,
StreamEndpointModel,
StreamFormatModel,
StreamInfoModel,
StreamModel,
StreamTypeEnum,
UVCControlModel,
V4LControlTypeEnum,
)
from .network import (
ConnectionProfileModel,
IPV4Address,
IPV4Configuration,
IPV4Method,
WiredDeviceModel,
)
from .preferences import SavedPreferencesModel
from .recordings import RecordingInfo
from .saved_cameras import (
SavedControlModel,
SavedDeviceModel,
SavedLeaderFollowerPairModel,
SavedStreamModel,
)

__all__ = [
# Network
"ConnectionProfileModel",
"IPV4Address",
"IPV4Configuration",
"IPV4Method",
"WiredDeviceModel",
# Cameras
"AddFollowerPayload",
"CameraModel",
"ControlFlagsModel",
"ControlModel",
"ControlTypeEnum",
"DeviceDescriptorModel",
"DeviceInfoModel",
"DeviceLeaderModel",
"DeviceModel",
"DeviceNicknameModel",
"DeviceOptionsModel",
"DeviceType",
"FormatSizeModel",
"FrameDropStats",
"H264Mode",
"IntervalModel",
"MenuItemModel",
"StreamEncodeTypeEnum",
"StreamEndpointModel",
"StreamFormatModel",
"StreamInfoModel",
"StreamModel",
"StreamTypeEnum",
"UVCControlModel",
"V4LControlTypeEnum",
# Preferences
"SavedPreferencesModel",
# Saved Cameras
"SavedControlModel",
"SavedDeviceModel",
"SavedLeaderFollowerPairModel",
"SavedStreamModel",
# Recordings
"RecordingInfo",
]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
pydantic_schemas.py
cameras.py

Defines Pydantic models and Enums for camera and device configs
Includes schemas for streams, controls, device info, and API request/response strutures
Expand Down Expand Up @@ -102,10 +102,11 @@ class Config:


class ControlFlagsModel(BaseModel):
# TODO: allow booleans, strings, etc.
default_value: float | int
max_value: float | int
min_value: float | int
step: float | int
max_value: float | int = 0
min_value: float | int = 0
step: float | int = 0
control_type: ControlTypeEnum = Field(...)
menu: list[MenuItemModel] = Field(default_factory=list)

Expand All @@ -117,7 +118,7 @@ class ControlModel(BaseModel):
flags: ControlFlagsModel
control_id: int
name: str
value: float | int
value: float | int | bool

class Config:
from_attributes = True
Expand Down Expand Up @@ -230,7 +231,7 @@ class Config:
class UVCControlModel(BaseModel):
bus_info: str
control_id: int
value: float | int
value: float | int | bool

class Config:
from_attributes = True
Expand Down
40 changes: 40 additions & 0 deletions backend_py/src/models/network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from enum import Enum

from pydantic import BaseModel
from sdbus_async.networkmanager import (
DeviceState,
)


class IPV4Method(Enum):
manual = "manual"
auto = "auto"
unknown = "unknown"


class IPV4Address(BaseModel):
address: str
prefix: int


class IPV4Configuration(BaseModel):
ip_addresses: list[IPV4Address] | None = None
gateway: str | None = None
method: IPV4Method = IPV4Method.unknown
dns: list[str] | None = None
never_default: bool | None = None


class WiredDeviceModel(BaseModel):
interface: str
state: DeviceState
is_active: bool
active_profile_id: str | None = None
active_ip_configuration: IPV4Configuration | None = None
available_profiles: list[str]


class ConnectionProfileModel(BaseModel):
id: str
path: str
ipv4_settings: IPV4Configuration
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""
pydantic_schemas.py
preferences.py
Defines Pydantic models for persistent server settings
Includes schemas for saved preferences, like default stream endpoints
"""

from pydantic import BaseModel

from ..cameras.pydantic_schemas import StreamEndpointModel
from .cameras import StreamEndpointModel


class SavedPreferencesModel(BaseModel):
Expand Down
10 changes: 10 additions & 0 deletions backend_py/src/models/recordings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pydantic import BaseModel


class RecordingInfo(BaseModel):
path: str
name: str
format: str
duration: str
size: str
created: str
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
saved_pydantic_schemas.py
saved_cameras.py

Defines Pydantic models and Enums for persisting device settings and configs
Includes schemas for serializing device states (streams, controls, nicknames) to JSON,
Expand All @@ -8,7 +8,7 @@

from pydantic import BaseModel

from .pydantic_schemas import (
from .cameras import (
DeviceType,
IntervalModel,
StreamEncodeTypeEnum,
Expand All @@ -20,7 +20,8 @@
class SavedControlModel(BaseModel):
control_id: int
name: str
value: int | float
# TODO: This is not enough to allow booleans and might actually cause issues
value: int | float | bool

class Config:
from_attributes = True
Expand Down
2 changes: 0 additions & 2 deletions backend_py/src/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .cameras import camera_router
from .lights import lights_router
from .logs import logs_router
from .network import network_router
from .preferences import preferences_router
Expand All @@ -9,7 +8,6 @@

__all__ = [
"camera_router",
"lights_router",
"logs_router",
"preferences_router",
"system_router",
Expand Down
Loading
Loading