# COPILOT Implementation Quick Reference ## Architecture at a Glance ``` ┌─────────────────────────────────────────────────────────────────┐ │ KEYBOARD (LattePanda Sigma) │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Flutter App (UI + PRIMARY Logic if elected) │ │ │ │ • Camera/Monitor selection │ │ │ │ • PTZ controls │ │ │ │ • Alarm display │ │ │ │ • Sequence management (PRIMARY only) │ │ │ └─────────────────────────┬──────────────────────────────────┘ │ │ │ localhost HTTP │ │ ┌─────────────────────────┼──────────────────────────────────┐ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │GeViScope│ │ G-Core │ │GeViSrvr │ C# Bridges (.NET 8) │ │ │ │ │ :7720 │ │ :7721 │ │ :7710 │ │ │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ └───────┼───────────┼───────────┼────────────────────────────┘ │ └──────────┼───────────┼───────────┼──────────────────────────────┘ │ │ │ ▼ ▼ ▼ GeViScope G-Core GeViServer Servers Servers (PTZ only) ``` --- ## Key Commands ### ViewerConnectLive (Switch Camera to Monitor) ```http POST http://localhost:7720/api/crossswitch Content-Type: application/json { "camera_id": 101, "monitor_id": 5, "mode": 0 } ``` ### PTZ Control ```http POST http://localhost:7720/api/ptz/move Content-Type: application/json { "camera_id": 101, "pan": 50, "tilt": 30, "zoom": 0 } ``` ### Query Active Alarms ```http GET http://localhost:7720/api/alarms/active ``` ### Query Monitor State ```http GET http://localhost:7720/api/monitors ``` --- ## State Queries (SDK) | Query | Purpose | Answer Type | |-------|---------|-------------| | `GeViSQ_GetFirstAlarm(activeOnly, enabledOnly)` | First active alarm | `GeViSA_AlarmInfo` | | `GeViSQ_GetNextAlarm(...)` | Next alarm in list | `GeViSA_AlarmInfo` | | `GeViSQ_GetFirstVideoOutput(activeOnly, enabledOnly)` | First monitor | `GeViSA_VideoOutputInfo` | | `GeViSQ_GetNextVideoOutput(...)` | Next monitor | `GeViSA_VideoOutputInfo` | | `GeViSQ_GetFirstVideoInput(activeOnly, enabledOnly)` | First camera | `GeViSA_VideoInputInfo` | | `GeViSQ_GetNextVideoInput(...)` | Next camera | `GeViSA_VideoInputInfo` | --- ## Event Notifications (Subscribe via PLC) | Event | When Fired | Key Fields | |-------|------------|------------| | `EventStarted` | Alarm triggered | EventID, TypeID, ForeignKey | | `EventStopped` | Alarm cleared | EventID, TypeID | | `ViewerConnected` | Camera switched to monitor | Viewer, Channel, PlayMode | | `ViewerCleared` | Monitor cleared | Viewer | | `ViewerSelectionChanged` | Monitor content changed | Viewer, Channel, PlayMode | | `VCAlarmQueueNotification` | Alarm queue change | Viewer, Notification, AlarmID | | `DigitalInput` | External contact change | Contact, State | --- ## Alarm States (PlcViewerAlarmState) | Value | Name | Description | Monitor Blocked? | |-------|------|-------------|------------------| | 0 | `vasNewAlarm` | New alarm added | YES | | 1 | `vasPresented` | Currently displayed | YES | | 2 | `vasStacked` | In queue, not displayed | Depends | | 3 | `vasConfirmed` | Acknowledged | NO | --- ## PTZ Lock Flow ``` 1. Keyboard → PRIMARY: RequestLock(cameraId, priority) 2. PRIMARY checks: - Camera locked? → Compare priority - High > Low priority wins - Same priority → First wins 3. PRIMARY → Keyboard: LockGranted/LockDenied 4. If granted: Keyboard sends PTZ directly to server 5. Lock expires after 5 minutes 6. Warning sent at 4 minutes (1 min before expiry) ``` --- ## Failover Timeline ``` T+0s PRIMARY stops sending heartbeats T+2s STANDBY misses 1st heartbeat T+4s STANDBY misses 2nd heartbeat T+6s STANDBY misses 3rd heartbeat → Declares itself PRIMARY T+6s New PRIMARY broadcasts role change T+6s Keyboards reconnect to new PRIMARY ``` --- ## Degraded Mode (No PRIMARY/STANDBY) | Feature | Works? | Notes | |---------|--------|-------| | ViewerConnectLive | ✅ | Direct to server | | PTZ Control | ✅ | Direct to server | | PTZ Locking | ❌ | No coordinator | | Sequences | ❌ | Runs on PRIMARY | | State Sync | ❌ | No broadcaster | | Alarms | ⚠️ | Local only per keyboard | --- ## Playback Commands ``` // Seek to timestamp ViewerPlayFromTime(viewer, channel, "play forward", "2024/01/15 14:30:00,000 GMT+01:00") // Set playback speed (2x) ViewerSetPlayMode(viewer, "play forward", 2.0) // Jump back 60 seconds ViewerJumpByTime(viewer, channel, "play forward", -60) // Export snapshot ViewerExportPicture(viewer, "C:\\Snapshots\\frame.bmp") ``` --- ## Port Reference | Port | Service | |------|---------| | 7700-7703 | GeViServer (native) | | 7710 | GeViServer Bridge (REST) | | 7720 | GeViScope Bridge (REST) | | 7721 | G-Core Bridge (REST) | | 8090 | PRIMARY WebSocket | | 50051 | gRPC (if used) | --- ## Startup Sequence ``` 1. Start bridges (GeViScope, G-Core, GeViServer) 2. Wait for bridge health checks to pass 3. Query current alarm state from all servers 4. Query current monitor state from all servers 5. Subscribe to event notifications 6. Connect to PRIMARY (or start election if none) 7. Sync shared state from PRIMARY 8. Start periodic alarm sync (every 30s) 9. Ready to accept user commands ``` --- ## Error Handling | Error | Action | |-------|--------| | Bridge unreachable | Retry 3x, then show offline status | | Command timeout | Retry 1x, then report failure | | PRIMARY unreachable | Continue in degraded mode | | Alarm query fails | Use cached state, retry on next sync | | Lock request timeout | Assume denied, inform user | --- ## Logging Format ```json { "timestamp": "2024-01-15T14:30:00.123Z", "level": "INFO", "keyboard_id": "KB-001", "user": "operator1", "event": "command_executed", "command": "ViewerConnectLive", "params": { "camera_id": 101, "monitor_id": 5 }, "duration_ms": 45, "success": true } ```