Files
COPILOT/Docs/legacy-architecture/migration-guide.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

10 KiB

Migration Guide: WPF → Flutter (No AppServer)

This guide maps every component of the legacy WPF COPILOT D6 system to its Flutter equivalent. The key architectural change: there is no centralized AppServer — the PRIMARY keyboard handles coordination.

Architecture Transformation

graph TB
    subgraph "LEGACY Architecture"
        direction TB
        L_HW["Hardware Keyboard"] --> L_App["Copilot.App.exe<br/>(WPF .NET 7)"]
        L_App -->|"Native SDK DLL<br/>(in-process)"| L_Cam["Camera Servers"]
        L_App -->|"SignalR<br/>(HTTPS/WSS)"| L_AS["AppServer<br/>(separate machine)"]
        L_AS --> L_DB["SQLite DB"]
    end

    subgraph "NEW Architecture"
        direction TB
        N_HW["Hardware Keyboard"] --> N_App["Flutter App<br/>(Web/Desktop)"]
        N_App -->|"REST HTTP<br/>(localhost bridges)"| N_Bridge["C# Bridges<br/>(.NET 8)"]
        N_Bridge -->|"Native SDK"| N_Cam["Camera Servers"]
        N_App -->|"WebSocket<br/>(:8090)"| N_Primary["PRIMARY Keyboard<br/>(coordination)"]
    end

What Replaces the AppServer?

The legacy AppServer was a centralized ASP.NET Core server providing:

AppServer Feature New Approach Where It Runs
Camera locks (SignalR hub) WebSocket hub + in-memory state PRIMARY keyboard
Sequence execution Sequence runner service PRIMARY keyboard
Config file sync Local JSON files + PRIMARY broadcast Each keyboard
Alarm history (Camea API) Bridge alarm endpoints + periodic sync Each keyboard queries bridges
Viewer state tracking Bridge WebSocket events Each keyboard tracks locally
Auto-update Out of scope (Phase 4+)
Admin UI (Blazor) Not needed initially

PRIMARY/STANDBY Model

sequenceDiagram
    participant K1 as Keyboard 1 (PRIMARY)
    participant K2 as Keyboard 2 (STANDBY)
    participant B as C# Bridges
    participant C as Camera Servers

    Note over K1,K2: Normal operation
    K1->>B: Direct commands (CrossSwitch, PTZ)
    K2->>B: Direct commands (CrossSwitch, PTZ)
    K1->>K2: Heartbeat every 2s via WebSocket
    K1->>K2: State sync (locks, sequences)

    Note over K1,K2: PRIMARY failure
    K2->>K2: No heartbeat for 6s
    K2->>K2: Self-promote to PRIMARY
    K2->>B: Continue direct commands
    Note over K2: Lock/sequence state rebuilt from bridges

Key principle: Direct commands (CrossSwitch, PTZ) always work — they go straight to bridges. Only coordination features (locks, sequences) need the PRIMARY.

Component Mapping: Legacy → Flutter

Assembly-Level Mapping

Legacy Assembly Flutter Equivalent Status
Copilot.App.dll (WPF UI + ViewModels) copilot_keyboard/ Flutter app Partially built
Copilot.Device.dll (Serial + HID) Web Serial API / flutter_libserialport Not started
Copilot.Drivers.GeViScope.dll GeViScope Bridge (:7720) REST API Complete
Copilot.Drivers.GCore.dll G-Core Bridge (:7721) REST API Complete
Copilot.Drivers.GeviSoft.dll GeViServer Bridge (:7710) REST API Minimal
Copilot.Drivers.Common.dll Bridge REST API contracts Complete
Copilot.Common.dll config/, domain/entities/ Partially built
Copilot.Common.Services.dll data/services/ (BridgeService, StateService) Partially built
Copilot.AppServer.Client.dll CoordinationService (WebSocket to PRIMARY) Not started
Copilot.AppServer.dll PRIMARY keyboard coordination logic Not started
Copilot.AppServer.Database.dll In-memory state on PRIMARY Not started

Class-Level Mapping

Legacy Class Flutter Class BLoC Notes
SegmentViewModel (1323 lines) Split into multiple BLoCs WallBloc + PtzBloc + CameraBloc Core logic, needs decomposition
MainWindow (217 lines) MainScreen + KeyboardService Input routing
CameraControllerService (65 lines) BridgeService Already mapped to REST
CameraLockService (61 lines) LockService (new) LockBloc (new) Via PRIMARY WebSocket
SequenceService (77 lines) SequenceService (new) SequenceBloc (new) Via PRIMARY WebSocket
ConfigurationService (100 lines) AppConfig + JSON files Local files, no sync
FunctionButtonsService (78 lines) FunctionButtonService (new) WallBloc Config-driven
PrepositionService (114 lines) PrepositionService (new) PrepositionBloc (new) Via bridge REST
CameraAlarmService (47 lines) AlarmService (exists) AlarmBloc (exists) Already implemented
PlaybackStateService (37 lines) PlaybackService (new) PlaybackBloc (new) Via bridge REST
CopilotDevice KeyboardService (new) Web Serial + HID APIs
NavigationService GoRouter Already in pubspec

What's Already Built vs. What's Missing

Already Built in Flutter

Feature Files Status
CrossSwitch (camera → monitor) WallBloc, BridgeService Working
PTZ control (Pan/Tilt/Zoom) PtzBloc, BridgeService Working
Camera number input (numpad) WallBloc, MainScreen Working
Camera prefix selection (500/501/502) WallBloc Working
Monitor state tracking MonitorBloc, StateService Working
Alarm monitoring AlarmBloc, AlarmService Working
Connection management ConnectionBloc, BridgeService Working
Wall grid UI (5 sections) WallOverview, SectionView Working
WebSocket event streaming BridgeService Working
Bridge health checks BridgeService Working
DI container (GetIt) injection_container.dart Working

Missing — Must Be Built

Feature Priority Complexity Legacy Reference
Camera lock system Critical High CameraLockService, SegmentViewModel:631-676
PRIMARY/STANDBY coordination Critical High Replaces AppServer
Hardware keyboard (Serial) Critical Medium CopilotDevice, SerialPortDataProvider
Hardware joystick (HID) Critical Medium JoystickHidDataProvider
Function buttons (F1-F7) High Low FunctionButtonsService
Prepositions (saved positions) High Medium PrepositionService
Camera number edit timeout High Low SegmentViewModel:829-847
Playback mode (jog/shuttle) Medium Medium PlaybackStateService, viewer controller
Sequences (camera cycling) Medium High SequenceService
Keyboard emulation (dev mode) Medium Low MainWindow:60-104
Service menu Low Low Long-press Backspace 3s
Config hot-reload Low Medium ConfigurationService
CrossSwitch rules engine Medium Medium crossswitch-rules.json
Alarm history view Low Low CameraAlarmService.GetAlarmForCamera

Implementation Phases

Phase 2A: Camera Lock System (Critical)

The lock system is the most complex missing piece. In the legacy app:

  1. User selects a PTZ camera → triggers lock attempt
  2. If lock acquired → PTZ telemetry enabled
  3. If locked by another keyboard → dialog: "Request takeover?"
  4. Lock expires after 5 minutes unless reset by PTZ movement
  5. 1-minute warning before expiration
  6. Priority levels: Low (default), High (override)

New implementation:

  • PRIMARY keyboard manages lock state in memory
  • Keyboards send lock requests via WebSocket to PRIMARY
  • PRIMARY broadcasts lock state changes to all keyboards
  • In degraded mode (no PRIMARY): local-only locks (no coordination)

Phase 2B: PRIMARY/STANDBY Coordination

WebSocket Hub on PRIMARY (:8090)
├── /ws/heartbeat     → 2-second heartbeat
├── /ws/locks         → Lock state sync
├── /ws/sequences     → Sequence state sync
└── /ws/state         → General state broadcast

Phase 3: Hardware Input

Serial port protocol (from CopilotDevice):

  • Prefix p = key press, r = key release
  • Prefix j = jog value, s = shuttle value
  • Prefix v = version, h = heartbeat

HID joystick:

  • VendorId: 10959 (0x2ACF), ProductId: 257 (0x0101)
  • 3 axes: X (pan), Y (tilt), Z (zoom)
  • Value range: -255 to +255

Phase 4: Sequences & Playback

  • Sequences run on PRIMARY keyboard (not AppServer)
  • Playback via bridge viewer control endpoints
  • Jog/shuttle wheel maps to playback speed (-7 to +7)

Bridge API Reference (Quick)

Both bridges expose identical endpoints:

POST /viewer/connect-live   {Viewer, Channel}         → CrossSwitch
POST /viewer/clear          {Viewer}                   → Clear monitor
POST /camera/pan            {Camera, Direction, Speed}  → PTZ pan
POST /camera/tilt           {Camera, Direction, Speed}  → PTZ tilt
POST /camera/zoom           {Camera, Direction, Speed}  → PTZ zoom
POST /camera/stop           {Camera}                    → Stop PTZ
POST /camera/preset         {Camera, Preset}            → Go to preset
GET  /monitors                                          → All monitor states
GET  /alarms/active                                     → Active alarms
GET  /health                                            → Bridge health
WS   /ws/events                                         → Real-time events

Configuration Files

File Purpose Legacy Equivalent
servers.json Server connections, bridge URLs, ID ranges CameraServersConfiguration
keyboards.json Keyboard identity, PRIMARY/STANDBY role CopilotAppConfiguration
wall-config.json Monitor wall layout (sections, monitors, viewers) MonitorWallConfiguration
function-buttons.json F1-F7 actions per wall FunctionButtonsConfiguration
prepositions.json Camera preset names PrepositionsConfiguration
crossswitch-rules.json Routing rules for CrossSwitch New (was in AppServer logic)

Risk Mitigation

Risk Mitigation
Joystick latency via HTTP bridge Measure. Legacy: ~1ms (in-process). New: ~5-20ms (HTTP). Acceptable for PTZ.
Lock coordination without AppServer PRIMARY WebSocket hub. Degraded mode = local-only locks.
Sequence failover mid-execution PRIMARY tracks sequence state. STANDBY can resume from last known position.
Config drift between keyboards PRIMARY broadcasts config changes. Startup sync on connect.
Browser HID/Serial support Use Web HID API + Web Serial API. Desktop fallback via flutter_libserialport.