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