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