Files
COPILOT/Docs/legacy-architecture/appserver.md
klas 40143734fc Initial commit: COPILOT D6 Flutter keyboard controller
Flutter web app replacing legacy WPF CCTV surveillance keyboard controller.
Includes wall overview, section view with monitor grid, camera input,
PTZ control, alarm/lock/sequence BLoCs, and legacy-matching UI styling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:57:38 +01:00

5.2 KiB

title, description
title description
AppServer ASP.NET Core coordination server - SignalR hub, REST API, database, admin UI

AppServer (Copilot.AppServer)

The AppServer is a centralized coordination service that runs as an ASP.NET Core application (Windows Service capable). It does NOT handle video — only coordination between keyboards.

Architecture

graph TB
    subgraph "Copilot.AppServer.exe"
        subgraph "SignalR Hub"
            CamLock["Camera Lock<br/>Management"]
            SeqMgr["Sequence<br/>Scheduler"]
            CfgSync["Configuration<br/>Sync"]
            ViewState["Viewer State<br/>Broadcasting"]
            AlarmQ["Alarm Query<br/>Proxy"]
        end

        subgraph "REST API (v1)"
            Updates["GET /api/v1/updates/{name}<br/>Auto-update manifest"]
            CfgAPI["Configuration endpoints"]
        end

        subgraph "Blazor Admin UI"
            AdminWWW["Web-based admin panel<br/>Configuration management"]
        end

        subgraph "Database"
            SQLite["SQLite (copilot.db)<br/>• Camera locks<br/>• Lock history<br/>• Alarm cache"]
        end

        subgraph "External Integrations"
            CameaClient["Camea API Client<br/>http://localhost:8081<br/>Alarm data source"]
        end
    end

    K1["Keyboard 1"] -->|WSS| CamLock
    K2["Keyboard 2"] -->|WSS| SeqMgr
    K3["Keyboard 3"] -->|WSS| CfgSync
    CamLock --> SQLite
    AlarmQ --> CameaClient
    AdminWWW --> CfgSync

SignalR Hub Interface

The hub exposes methods grouped by function:

Camera Locks

TryLockCamera(cameraId, copilotName, priority) → CameraLockResult
UnlockCamera(cameraId, copilotName)
RequestCameraLock(cameraId, copilotName, priority)
CameraLockConfirmTakeOver(cameraId, copilotName, confirm)
ResetCameraLockExpiration(cameraId, copilotName)
GetLockedCameraIds(copilotName) → IEnumerable<int>

Camera Lock Notifications (Server → Client)

CameraLockNotify(notification) where notification has:
  - NotificationType: Acquired | TakenOver | ConfirmTakeOver | Confirmed | Rejected | ExpireSoon | Unlocked
  - CameraId: int
  - CopilotName: string

Sequences

Start(viewerId, sequenceId)
Stop(viewerId)
GetSequences(categoryId) → IEnumerable<SequenceMessage>
GetSequenceCategories() → IEnumerable<SequenceCategoryMessage>
GetRunningSequences() → IEnumerable<ViewerSequenceState>

Sequence Notifications (Server → Client)

ViewerSequenceStateChanged(ViewerSequenceState)

Configuration

GetConfigurationFile(filename) → ConfigurationFile

Configuration Notifications (Server → Client)

ConfigurationFileChanged(ConfigurationFile)

Alarms

GetCamerasWithAlarms() → HashSet<int>
GetAlarmsForCamera(cameraId, from, to) → IReadOnlyList<CameraAlarm>

Database Schema (SQLite)

Managed via Entity Framework Core with code-first migrations:

erDiagram
    CameraLock {
        int CameraId PK
        string OwnerName
        string Priority
        datetime ExpiresAt
        datetime CreatedAt
    }

    CameraLockHistory {
        int Id PK
        int CameraId
        string OwnerName
        string Action
        datetime Timestamp
    }

    AlarmCache {
        int Id PK
        int CameraId
        int AlarmTypeId
        string Name
        datetime StartTime
        datetime EndTime
    }

Lock Expiration System

graph TD
    Lock["Lock Created<br/>(ExpiresAt = now + 5min)"] --> Timer["Expiration Timer"]
    Timer -->|"T+4min<br/>(1min before expiry)"| Warn["Send ExpireSoon<br/>notification"]
    Warn --> Reset{"PTZ Action?"}
    Reset -->|Yes| ExtendLock["Reset ExpiresAt = now + 5min"]
    ExtendLock --> Timer
    Reset -->|No timeout| Expire["Send Unlocked<br/>notification"]
    Expire --> Remove["Remove lock from DB"]

Auto-Update System

The AppServer serves firmware and application updates:

GET /api/v1/updates/{copilotName}
→ Returns JSON array of available updates:
[
    {
        "Version": "1.0.706",
        "Url": "https://copilot.test.d6.colsys.cz/updates/Copilot-1.0.706.zip",
        "Changelog": "https://...",
        "Mandatory": { "Value": true, "MinVersion": "1.0.700" },
        "CheckSum": { "Value": "abc123...", "HashingAlgorithm": "SHA256" }
    }
]

The WPF app uses AutoUpdater.NET to check on startup and apply updates.

Deployment

Copilot.AppServer.exe
├── appsettings.json           (main config)
├── configs/
│   ├── appsettings-copilot.json
│   ├── appsettings-camera-servers.json
│   ├── appsettings-monitor-wall.json
│   ├── appsettings-function-buttons.json
│   ├── appsettings-prepositions.json
│   ├── appsettings-sequences.json
│   └── appsettings-sequence-categories.json
├── copilot.db                 (SQLite database)
├── wwwroot/                   (Blazor admin UI assets)
├── logs/
│   ├── copilot-appserver-YYYYMMDD.log
│   └── buffer/                (Elasticsearch buffer)
└── web.config                 (IIS hosting, if used)

Runs as:

  • Windows Service (Microsoft.Extensions.Hosting.WindowsServices)
  • Or standalone Kestrel server on HTTPS port 443
  • Uses machine certificate store for TLS