Files
COPILOT/Docs/legacy-architecture/data-flows.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

288 lines
9.1 KiB
Markdown

---
title: "Data Flows"
description: "CrossSwitch, Alarms, Sequences, Playback, and Camera Lock workflows"
---
# Data Flows
## 1. CrossSwitch (Switch Camera to Monitor)
The primary operation — routing a camera feed to a physical monitor output.
```mermaid
sequenceDiagram
participant Op as Operator
participant VM as SegmentViewModel
participant CenD as CentralServerDriver
participant Srv as Camera Server
Op->>VM: Enter camera number (digits + Enter)
VM->>VM: Validate camera exists via MediaChannelService
alt Camera exists
alt Playback active on this viewer
VM->>VM: playbackStateService.SetPlaybackState(false)
end
alt Sequence active on this viewer
VM->>VM: hub.Stop(viewerId)
end
VM->>CenD: CrossSwitch(viewerId, cameraNumber)
CenD->>Srv: SDK CrossSwitch command
Note over Srv: Server switches video output<br/>and fires ViewerConnectionEvent
else Camera not found
VM->>VM: ShowError("MediaChannel does not exist", 2s)
end
```
### CrossSwitch Triggers
1. **Numeric entry** — Type camera number + Enter
2. **Plus/Minus keys** — Cycle to next/previous camera in sorted order
3. **Function buttons** — F1-F7 mapped to preconfigured CrossSwitch actions
4. **Home key** — When not in PTZ mode or playback, executes function button "Home"
### Camera Number Editing
```
State: Not Editing
├─ Press digit → BeginEdit(), append digit, start cancel timer
State: Editing (showing entered digits)
├─ Press digit → append digit, restart cancel timer
├─ Press Backspace → remove last digit, restart cancel timer
├─ Press Enter → validate, CrossSwitch if valid, EndEdit()
└─ Cancel timer fires → CancelEdit(), revert to original camera
```
Camera number can be up to 6 digits. Configurable prefixes (e.g., "500", "501", "502") are prepended automatically based on the selected prefix.
## 2. Camera Lock Workflow
Camera locks coordinate PTZ access across multiple keyboards via the AppServer SignalR hub.
```mermaid
sequenceDiagram
participant K1 as Keyboard 1 (Low priority)
participant Hub as AppServer Hub
participant DB as SQLite DB
participant K2 as Keyboard 2 (High priority)
K1->>Hub: TryLockCamera(camId, "K1", Low)
Hub->>DB: Check existing lock
DB-->>Hub: No lock exists
Hub->>DB: Create lock(camId, "K1", Low, expires=5min)
Hub-->>K1: LockResult(Acquired=true)
K1->>K1: IsCameraTelemetryActive = true
Note over K1: Operator controls PTZ...<br/>Each action calls ResetCameraLockExpiration()
K2->>Hub: TryLockCamera(camId, "K2", High)
Hub->>DB: Check existing lock → held by K1
Hub-->>K2: LockResult(Acquired=false, Owner="K1")
K2->>Hub: RequestCameraLock(camId, "K2", High)
Hub->>K1: CameraLockNotify(ConfirmTakeOver)
K1->>K1: Show dialog "K2 requests lock"
alt K1 confirms takeover
K1->>Hub: CameraLockConfirmTakeOver(confirm=true)
Hub->>K1: CameraLockNotify(Unlocked)
Hub->>K2: CameraLockNotify(Confirmed)
K2->>Hub: TryLockCamera(camId, "K2", High)
Hub-->>K2: LockResult(Acquired=true)
else K1 denies
K1->>Hub: CameraLockConfirmTakeOver(confirm=false)
Hub->>K2: CameraLockNotify(Rejected)
end
```
### Lock Priority Levels
Configured per keyboard in `appsettings-copilot.json`:
- `Low` — Standard operator
- `High` — Supervisor (can request takeover)
### Lock Expiration
- **Timeout:** 5 minutes (`LockExpirationConfig.LockExpirationTimeout`)
- **Warning:** 1 minute before expiration (`NotificationBeforeExpiration`)
- **Reset:** Every PTZ action or explicit reset call extends the timer
## 3. Alarm System
```mermaid
sequenceDiagram
participant Worker as CameraAlarmsUpdateWorker
participant CAS as CameraAlarmService
participant Hub as CopilotHub
participant AS as AppServer
participant Camea as Camea API
Note over Worker: Background worker (periodic)
Worker->>CAS: UpdateCameraToAlarmsMapping()
CAS->>Hub: GetCamerasWithAlarms()
Hub->>AS: Query
AS->>Camea: GET /api/alarms
Camea-->>AS: List of cameras with active alarms
AS-->>Hub: HashSet<int> cameraIds
Hub-->>CAS: Store in camerasWithAlarms
Note over CAS: UI can now check HasCameraAlarms(cameraId)
Note over Worker: Also on SegmentViewModel...
participant VM as SegmentViewModel
VM->>CAS: GetAlarmForCamera(camId, from, to)
CAS->>Hub: GetAlarmsForCamera(camId, from, to)
Hub->>AS: Query alarm history
AS-->>Hub: List<CameraAlarm>
Hub-->>CAS: Alarms (UTC → LocalTime)
CAS-->>VM: Sorted by StartTime desc
```
### Alarm-Related Behavior
- Screens with active alarms **block** camera number editing (CrossSwitch disabled)
- Alarm history page uses configurable regex to extract camera ID from alarm names: `^\D*(?<CameraId>\d{6})\D*$`
- Default search interval: 30 days
## 4. Sequence Management
Sequences automatically cycle cameras on a monitor at configurable intervals.
```mermaid
sequenceDiagram
participant Op as Operator
participant VM as SegmentViewModel
participant SeqS as SequenceService
participant Hub as SignalR Hub
participant AS as AppServer
Op->>VM: Press Sequence key
VM->>VM: Navigate to SequenceCategoriesPage
Op->>VM: Select category
VM->>SeqS: GetSequences(categoryId)
SeqS->>Hub: GetSequences(categoryId)
Hub-->>SeqS: List<SequenceMessage>
Op->>VM: Select sequence
VM->>SeqS: Start(viewerId, sequenceId)
SeqS->>Hub: Start(viewerId, sequenceId)
Note over AS: AppServer runs sequence timer
AS->>Hub: ViewerSequenceStateChanged
Hub->>SeqS: OnChanged(viewerSequenceState)
SeqS->>VM: Update UI (sequence indicator)
Note over AS: Timer fires every N seconds
AS->>AS: CrossSwitch next camera in list
```
### Sequence Configuration
```json
{
"Id": 1,
"CategoryId": 1,
"Name": "Test-A",
"Interval": 1, // seconds between switches
"MediaChannels": [500100, 500101, 500102, 500103]
}
```
Sequences are **run on the AppServer**, not on the keyboard. The keyboard only starts/stops them.
## 5. Playback Control
```mermaid
sequenceDiagram
participant Op as Operator
participant VM as SegmentViewModel
participant VC as IViewerController
participant Srv as Camera Server
Note over Op: Playback enters via Jog/Shuttle<br/>or Playback button
Op->>VM: Jog right (arrow key)
VM->>VM: IsPlaybackActive = true
VM->>VC: StepForwardAndStop()
VC->>Srv: Step forward one frame
Op->>VM: Shuttle right position 3
VM->>VM: TryGetShuttleSpeed(speeds, out speed, out forward)
Note over VM: position 3 → speeds[2] = 5
VM->>VC: FastForward(5)
VC->>Srv: Play at 5x speed
Op->>VM: Shuttle center
VM->>VC: Stop()
Op->>VM: Press Home key
VM->>VC: PlayLive()
VM->>VM: IsPlaybackActive = false
```
### Playback Controls
| Input | Action | SDK Method |
|-------|--------|------------|
| Jog Left | Step back 1 frame | `StepBackwardAndStop()` |
| Jog Right | Step forward 1 frame | `StepForwardAndStop()` |
| Shuttle Left 1-7 | Rewind at speed | `FastBackward(speed)` |
| Shuttle Right 1-7 | Fast forward at speed | `FastForward(speed)` |
| Shuttle Center | Pause | `Stop()` |
| Home | Return to live | `PlayLive()` |
## 6. Function Buttons
F1-F7 and Home keys execute preconfigured action lists per wall:
```mermaid
graph TD
Key["F1-F7 / Home Key Press"] --> FBS["FunctionButtonsService"]
FBS --> Config["Load from appsettings-function-buttons.json"]
Config --> Actions["Get actions for wallId + buttonKey"]
Actions --> ForEach["For each action:"]
ForEach -->|CrossSwitch| CS["CentralServerDriver.CrossSwitch(viewerId, sourceId)<br/>+ Minimize viewer"]
ForEach -->|SequenceStart| SS["SequenceService.Start(viewerId, sourceId)"]
```
Example: Pressing F1 on Wall 2 executes:
```json
{ "ViewerId": 12322, "ActionType": "CrossSwitch", "SourceId": 500123 }
```
This switches camera 500123 to viewer 12322.
## 7. Configuration Sync
Configuration files are managed centrally on the AppServer and synced to keyboards:
```mermaid
sequenceDiagram
participant AS as AppServer
participant Hub as SignalR Hub
participant CfgS as ConfigurationService
participant Disk as Local JSON Files
Note over CfgS: On startup
CfgS->>Hub: GetConfigurationFile(filename) for each manager
Hub-->>CfgS: ConfigurationFile (content + hash)
CfgS->>Disk: Compare hash, write if changed
Note over AS: Admin changes config via Blazor UI
AS->>Hub: ConfigurationFileChanged event
Hub->>CfgS: OnConfigurationFileChanged(file)
CfgS->>Disk: Write updated content
Note over CfgS: Also triggered when AppServer<br/>becomes available after disconnect
```
Configuration files synced:
- `appsettings-copilot.json` — Keyboard-specific settings
- `appsettings-camera-servers.json` — Camera server connections
- `appsettings-monitor-wall.json` — Monitor wall topology
- `appsettings-function-buttons.json` — Function button actions
- `appsettings-prepositions.json` — Camera prepositions
- `appsettings-sequences.json` — Sequence definitions
- `appsettings-sequence-categories.json` — Sequence categories