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

9.1 KiB

title, description
title description
Data Flows 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.

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.

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

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
  • 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.

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

{
    "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

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:

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:

{ "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:

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