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>
This commit is contained in:
287
Docs/legacy-architecture/data-flows.md
Normal file
287
Docs/legacy-architecture/data-flows.md
Normal file
@@ -0,0 +1,287 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user