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>
191 lines
5.2 KiB
Markdown
191 lines
5.2 KiB
Markdown
---
|
|
title: "AppServer"
|
|
description: "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
|
|
|
|
```mermaid
|
|
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:
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|