--- title: "Legacy → Flutter Migration Comparison" description: "Side-by-side comparison of legacy WPF architecture vs new Flutter system" --- # Legacy → Flutter Migration Comparison ## Architecture Comparison ```mermaid graph LR subgraph "Legacy (WPF)" L_HW["Hardware Keyboard
Serial + HID"] --> L_App["Copilot.App.exe
(WPF .NET 7)"] L_App -->|"Native SDK
(TCP direct)"| L_Srv["Camera Servers
GeViScope / G-Core"] L_App -->|"SignalR
(HTTPS/WSS)"| L_AS["AppServer
(ASP.NET Core)"] L_AS --> L_DB["SQLite"] end subgraph "New (Flutter)" N_HW["Hardware Keyboard
Serial + HID"] --> N_App["Flutter App
(Web/Desktop)"] N_App -->|"REST HTTP
(localhost)"| N_Bridge["C# Bridges
(.NET 8)"] N_Bridge -->|"Native SDK
(TCP)"| N_Srv["Camera Servers
GeViScope / G-Core"] N_App -->|"WebSocket
(PRIMARY)"| N_Coord["PRIMARY Keyboard
(Coordination)"] end ``` ## Component Mapping | Legacy (WPF) | New (Flutter) | Notes | |---------------|---------------|-------| | `Copilot.App.exe` | `copilot_keyboard` Flutter app | Full rewrite | | `Copilot.Device.dll` | Web HID / Serial API | Browser APIs or native plugins | | `Copilot.Drivers.GeViScope.dll` | GeViScope Bridge (:7720) | SDK stays in C#, exposed via REST | | `Copilot.Drivers.GCore.dll` | G-Core Bridge (:7721) | SDK stays in C#, exposed via REST | | `Copilot.Drivers.GeviSoft.dll` | GeViServer Bridge (:7710) | SDK stays in C#, exposed via REST | | `Copilot.Drivers.Common.dll` | Bridge REST API contracts | Interfaces become HTTP endpoints | | `Copilot.AppServer.exe` | PRIMARY keyboard + WebSocket hub | **No separate server** — runs on keyboard | | `Copilot.AppServer.Database.dll` | In-memory state on PRIMARY | No SQLite needed | | `Copilot.Common.Services.dll` | `BridgeService` + `StateService` | Dart services | | SignalR Hub | WebSocket hub on PRIMARY | Simpler protocol | | `IMovementController` | `POST /api/ptz/{action}` on bridge | REST instead of direct SDK | | `ICameraServerDriver` | Bridge handles connection | App doesn't touch SDK | | `ICentralServerDriver.CrossSwitch` | `POST /api/viewer/connect-live` | Via bridge REST | ## Key Architectural Differences ### 1. SDK Access Path **Legacy:** App → Native SDK DLL → Camera Server (direct TCP) ``` Copilot.App → GeViScopeMovementController → GscPLCWrapper → TCP → Server ``` **New:** App → HTTP → C# Bridge → Native SDK → Camera Server ``` Flutter App → HTTP POST /api/ptz/pan → Bridge (.NET 8) → SDK → Server ``` **Why:** Flutter (especially web) cannot load native .NET DLLs. The C# bridges wrap the same SDKs behind a REST API. ### 2. Coordination Model **Legacy:** Centralized AppServer (single point of coordination) - All keyboards connect to one AppServer via SignalR - AppServer manages locks, sequences, config, alarms - AppServer failure = loss of coordination features **New:** Distributed PRIMARY/STANDBY model - Any keyboard can be PRIMARY (runs coordination logic) - STANDBY monitors PRIMARY via heartbeat, auto-promotes after 6s - No separate server hardware needed - Critical operations (CrossSwitch, PTZ) work without PRIMARY ### 3. Configuration Management **Legacy:** AppServer stores config → syncs to keyboards via SignalR **New:** `servers.json` + `keyboards.json` + `crossswitch-rules.json` loaded from local files + PRIMARY sync ### 4. Alarm System **Legacy:** AppServer → Camea API → SignalR → Keyboards **New:** Each bridge can query alarms directly + periodic sync via PRIMARY ## Feature Parity Matrix | Feature | Legacy Status | New Status | Priority | |---------|--------------|------------|----------| | CrossSwitch (camera → monitor) | Complete | Phase 1 | Critical | | PTZ via joystick (Pan/Tilt/Zoom) | Complete | Phase 1 | Critical | | Camera number entry (numpad) | Complete | Phase 1 | Critical | | Camera lock (PTZ coordination) | Complete | Phase 2 | High | | Prepositions (saved positions) | Complete | Phase 1 | High | | Sequences (camera cycling) | Complete | Phase 3 | Medium | | Function buttons (F1-F7) | Complete | Phase 1 | High | | Playback (jog/shuttle) | Complete | Phase 3 | Medium | | Alarm display | Complete | Phase 3 | Medium | | Alarm history | Complete | Phase 3 | Low | | Monitor wall segments | Complete | Phase 1 | High | | Config sync from server | Complete | Phase 2 | Medium | | Auto-update (firmware + app) | Complete | Phase 4 | Low | | Service menu | Complete | Phase 4 | Low | | Keyboard emulation (dev mode) | Complete | N/A (browser) | N/A | ## PTZ Speed Values: Compatible The legacy app sends speed values **0-255** to the SDK. The new bridges should use the **same range** to maintain identical PTZ behavior. The zoom-proportional speed feature is provided by the camera/server infrastructure, not the app. ``` Legacy: Joystick HID (-255..+255) → PanRight(speed) → SDK → Server New: Joystick HID (-255..+255) → POST /api/ptz/pan {speed} → Bridge → SDK → Server ``` Same speed values = same camera behavior. ## Risk Areas 1. **Joystick latency** — Legacy sends joystick events directly via in-process SDK call (~1ms). New path adds HTTP overhead (~5-20ms). Monitor for responsiveness. 2. **Lock coordination** — Legacy uses SignalR (battle-tested). New uses custom WebSocket protocol. Needs thorough testing. 3. **Sequence execution** — Legacy runs on AppServer (always-on). New runs on PRIMARY keyboard (could failover mid-sequence). 4. **Alarm reliability** — Legacy has Camea API integration on AppServer. New needs bridge-level alarm subscription.