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>
This commit is contained in:
klas
2026-02-12 14:57:38 +01:00
commit 40143734fc
125 changed files with 65073 additions and 0 deletions

View File

@@ -0,0 +1,394 @@
# Flutter COPILOT Keyboard UI Design
## Overview
This document defines the Flutter UI implementation based on the existing D6 application and the Klavesnice Business Analysis specification.
## Screen Structure
### 1. Main Screen (Basic View)
The main screen consists of three primary areas:
```
+--------------------------------------------------+
| Connection Status Bar |
+--------------------------------------------------+
| |
| Video Wall Grid |
| (Physical Monitors with Viewers) |
| |
+--------------------------------------------------+
| Bottom Toolbar |
+--------------------------------------------------+
```
### 2. Video Wall Grid
Based on D6 screenshots, the wall is divided into **5 sections**:
- Vrchní část (Top section) - monitors 210-234
- Pravá část (Right section)
- Levá část (Left section)
- Dolní část (Bottom section)
- Střední část (Middle section)
Each section contains:
- Section header with name
- Grid of physical monitors
- Each physical monitor can contain 1-4 viewers (quad view)
#### Monitor Display States
| State | Visual |
|-------|--------|
| Normal | Dark background, white text |
| Selected (touched) | Cyan/blue border (thick) |
| Active Alarm | Red background |
| Alarm + Selected | Red background + cyan border |
| Locked by current user | Lock icon visible |
| Locked by other user | Lock icon + disabled |
#### Viewer Number Display
- Each viewer shows its viewer number (e.g., 210, 211, 212, 213)
- Quad view: 4 viewers in one physical monitor with visible border around physical monitor
- Single view: One viewer fills the physical monitor
### 3. Bottom Toolbar (Button Strip)
Dynamic button strip that changes based on context:
#### Default State (No Selection)
```
[Search] [500] [501] [502] [HOME] [F1] [F2] [F3] [F4] [F5] [F6] [F7]
```
#### Monitor Selected (Camera View)
```
[←] [PREPOS] [PvZ] [ALARM] [LOCK/UNLOCK] [SEARCH]
```
Where:
- **←** Back to default
- **PREPOS** Open preposition list (only for PTZ cameras when unlocked)
- **PvZ** Enter playback mode (only if keyboard has permission)
- **ALARM** Open alarm history list
- **LOCK/UNLOCK** Toggle PTZ lock
- **SEARCH** Open camera search dialog
### 4. Camera Prefix Selection
Three prefix buttons for camera number input:
- **500** - GeViScope cameras (500001-500999)
- **501** - G-CORE cameras (501001-501999)
- **502** - GeViServer cameras (502001-502999)
Selected prefix is highlighted. User then types 3-digit camera number.
### 5. Camera Number Input
```
+----------------------------------+
| Input Field: [500] + [ ] |
| Current: 500201 |
+----------------------------------+
| [1] [2] [3] |
| [4] [5] [6] |
| [7] [8] [9] |
| [C] [0] [OK] |
+----------------------------------+
```
- Number input via touchscreen or physical USB keyboard
- C = Clear, OK = Confirm CrossSwitch
- ESC/Back = Cancel
---
## Secondary Screens
### 6. Search Screen
Opened via Search button when monitor is selected:
```
+--------------------------------------------------+
| Search Camera [X] |
+--------------------------------------------------+
| Camera Number: [________] |
| |
| [Keyboard toggle] |
+--------------------------------------------------+
| Search Results: |
| [500001 - Jindřišská, tramvaj] |
| [500002 - Václavské náměstí] |
| ... |
+--------------------------------------------------+
| [←] [OK] |
+--------------------------------------------------+
```
### 7. Preposition List Screen
```
+--------------------------------------------------+
| PREPOZICE - Kamera 500005 [X] |
+--------------------------------------------------+
| [2] Jindřišská, tramvajový ostrůvek |
| [10] Jindřišská, křižovatka [●] |
| [15] U Bulhara směr centrum |
| ... |
+--------------------------------------------------+
| [←] [+] [🗑] [✓] |
+--------------------------------------------------+
```
- Blue highlight on selected preposition
- [+] Add new preposition (disabled if AppServer unavailable)
- [🗑] Delete preposition (disabled if editable=0 or positions 1-9)
- [✓] Confirm/go to selected preposition
### 8. Add Preposition Screen
```
+--------------------------------------------------+
| Nová prepozice - Kamera 500005 [X] |
+--------------------------------------------------+
| Číslo prepozice: [__] (10-99 only) |
| |
| Název prepozice: [________________] |
| |
| [Keyboard] |
+--------------------------------------------------+
| [←] [ULOŽIT] |
+--------------------------------------------------+
```
Save button disabled until both fields filled.
### 9. Playback Mode (PvZ) Screen
Overlay controls on main view:
```
+--------------------------------------------------+
| PvZ: Kamera 500005 |
| Čas: 2026-02-03 14:35:22 |
+--------------------------------------------------+
| [|◄] [◄◄] [◄] [⏸] [►] [►►] [►|] [LIVE] |
+--------------------------------------------------+
| Speed: [-7 ... -1] [0] [+1 ... +7] |
+--------------------------------------------------+
```
Jog-shuttle speed table:
- Position -7 to -1: Reverse (slow to fast)
- Position 0: Pause
- Position +1 to +7: Forward (slow to fast)
### 10. Alarm List Screen
```
+--------------------------------------------------+
| Alarmy - Kamera 500005 [X] |
+--------------------------------------------------+
| Od: [2026-02-01] [📅] Do: [2026-02-03] [📅] |
+--------------------------------------------------+
| Začátek | Konec |
| 02-03 14:30:15 | 02-03 14:32:45 [●] |
| 02-03 12:15:30 | 02-03 12:18:22 |
| 02-02 23:45:00 | 02-02 23:47:15 |
+--------------------------------------------------+
| [←] [LIVE] [⏩] [▶⏩] [◄◄] [⏸] |
+--------------------------------------------------+
```
- [⏩] Jump to timestamp (paused)
- [▶⏩] Jump to timestamp and play
- [◄◄] Reverse playback
- [⏸] Stop playback
- [LIVE] Return to live stream
### 11. Function Button Config (F1-F7, HOME)
Each function button triggers predefined wall configuration:
- Stored on Application Server
- Can set camera or sequence per monitor
- Before CrossSwitch, check if sequence is running and stop it
### 12. Service Menu
Activated by holding Backspace for 3 seconds:
```
+----------------------------------+
| Servisní Menu [X] |
+----------------------------------+
| (1) Restartovat aplikaci |
| (2) Restartovat klávesnici |
| (3) Vypnout klávesnici |
+----------------------------------+
```
---
## Component Specifications
### Color Palette
| Element | Color | Hex |
|---------|-------|-----|
| Background | Dark blue-gray | #1a2332 |
| Monitor normal | Dark gray | #2d3748 |
| Monitor selected | Cyan border | #00d4ff |
| Monitor alarm | Red | #ff4444 |
| Button active | Blue | #3182ce |
| Button disabled | Gray | #4a5568 |
| Text primary | White | #ffffff |
| Text secondary | Gray | #a0aec0 |
| Preposition highlight | Blue | #2b6cb0 |
### Typography
- Monitor numbers: Monospace, bold, 16-20px
- Section headers: Sans-serif, semibold, 14px
- Button labels: Sans-serif, medium, 12-14px
- Input fields: Monospace, regular, 16px
### Touch Targets
- Minimum touch target: 44x44 pixels
- Monitor tiles: Variable (based on grid)
- Toolbar buttons: 48px height
- List items: 48px height minimum
---
## State Management (BLoC)
### Required BLoCs
1. **ConnectionBloc** - Server connection states
2. **WallBloc** - Video wall state, monitor selection
3. **AlarmBloc** - Active alarms, alarm history
4. **CameraBloc** - Camera input, CrossSwitch operations
5. **PTZBloc** - Lock state, telemetry controls
6. **PlaybackBloc** - PvZ mode, jog-shuttle
7. **PrepositionBloc** - Preposition list, add/delete
8. **FunctionButtonBloc** - Function button configurations
9. **SequenceBloc** - Sequence state per monitor
### Events & States Example (WallBloc)
```dart
// Events
abstract class WallEvent {}
class LoadWallConfig extends WallEvent {}
class SelectMonitor extends WallEvent { final int viewerId; }
class DeselectMonitor extends WallEvent {}
class CrossSwitchCamera extends WallEvent { final int cameraId; final int viewerId; }
// States
abstract class WallState {}
class WallLoading extends WallState {}
class WallLoaded extends WallState {
final List<WallSection> sections;
final int? selectedViewerId;
final int? selectedPhysicalMonitorId;
}
```
---
## Implementation Priority
### Phase 1: Core UI (MVP)
1. Main screen layout with wall grid
2. Monitor selection (touch)
3. Camera number input (prefix + digits)
4. CrossSwitch command
5. Connection status bar
### Phase 2: Alarms & Status
1. Alarm state display (red monitors)
2. Alarm blocking (prevent CrossSwitch on active alarm)
3. Monitor lock indicators
### Phase 3: PTZ & Playback
1. PTZ lock/unlock
2. Telemetry controls (pan/tilt/zoom)
3. Playback mode (PvZ)
4. Jog-shuttle controls
### Phase 4: Advanced Features
1. Preposition management
2. Alarm history list
3. Function buttons (F1-F7, HOME)
4. Sequences
5. Search functionality
### Phase 5: Polish
1. Service menu
2. CAMEA integration
3. Autonomous mode fallbacks
4. Error handling dialogs
---
## Autonomous Mode Behavior
When Application Server is unavailable:
| Feature | Available | Notes |
|---------|-----------|-------|
| CrossSwitch | ✓ | Direct to bridge |
| PTZ Lock | ✓ | Local lock only |
| CAMEA Reserve | ✓ | Direct to CAMEA |
| Preposition List | ✓ | Cached config |
| Add Preposition | ✗ | Requires AppServer |
| Delete Preposition | ✗ | Requires AppServer |
| Sequences | ✗ | Run by AppServer |
| Function Buttons | ✓ | Cached config |
| Alarm Management | ✗ | Run by GeViSoft |
---
## File Structure
```
lib/
├── presentation/
│ ├── screens/
│ │ ├── main_screen.dart
│ │ ├── search_screen.dart
│ │ ├── preposition_screen.dart
│ │ ├── alarm_list_screen.dart
│ │ ├── playback_overlay.dart
│ │ └── service_menu_dialog.dart
│ ├── widgets/
│ │ ├── wall_grid/
│ │ │ ├── wall_grid.dart
│ │ │ ├── wall_section.dart
│ │ │ ├── physical_monitor.dart
│ │ │ └── viewer_tile.dart
│ │ ├── toolbar/
│ │ │ ├── bottom_toolbar.dart
│ │ │ ├── prefix_buttons.dart
│ │ │ ├── function_buttons.dart
│ │ │ └── action_buttons.dart
│ │ ├── input/
│ │ │ ├── camera_input.dart
│ │ │ ├── numeric_keypad.dart
│ │ │ └── datetime_picker.dart
│ │ └── common/
│ │ ├── connection_status_bar.dart
│ │ └── confirmation_dialog.dart
│ └── blocs/
│ ├── wall/
│ ├── alarm/
│ ├── camera/
│ ├── ptz/
│ ├── playback/
│ ├── preposition/
│ └── function_button/
```

View File

@@ -0,0 +1,235 @@
# Phase 0: Infrastructure Implementation Plan
**Status:** In Progress
**Duration:** Week 1-2
**Goal:** Finalize bridges, add event subscriptions, test direct commands, document configurations
---
## Current State Assessment
### Bridge Readiness
| Bridge | Port | Status | PLC Events | ViewerConnectLive | PTZ | Playback |
|--------|------|--------|------------|-------------------|-----|----------|
| GeViScope | 7720 | ✅ Ready | ✅ | ✅ | ✅ | ✅ |
| G-Core | 7721 | ✅ Ready | ✅ | ✅ | ✅ | ✅ |
| GeViServer | 7710 | ⚠️ Minimal | ⚠️ Events only | ❌ | ❌ | ❌ |
**Decision:** GeViServer Bridge remains minimal per design ("Minimal GeViSoft usage").
---
## Phase 0 Tasks
### Task 1: Event Notification Forwarding ✅ COMPLETED
**Priority:** HIGH
**Effort:** 4-6 hours
**Status:** Done (2026-02-03)
The bridges have PLC subscriptions but notifications are only logged. For the new architecture, events must be forwarded to the Flutter app.
**Sub-tasks:**
1. [x] Add WebSocket endpoint to GeViScope Bridge for event streaming (`ws://localhost:7720/ws/events`)
2. [x] Add WebSocket endpoint to G-Core Bridge for event streaming (`ws://localhost:7721/ws/events`)
3. [x] Define event message format (JSON)
4. [x] Forward these events:
- `ViewerConnected(Viewer, Channel, PlayMode)`
- `ViewerCleared(Viewer)`
- `ViewerSelectionChanged(Viewer, Channel, PlayMode)`
- `EventStarted(EventID, TypeID, ForeignKey)`
- `EventStopped(EventID, TypeID)`
- `VCAlarmQueueNotification(Viewer, Notification, AlarmID, TypeID)`
- `DigitalInput(Contact, State)`
- `ConnectionLost` (bonus)
**Event Message Format:**
```json
{
"timestamp": "2026-02-03T10:30:00.123Z",
"server": "GeViScope-01",
"action": "ViewerConnected",
"params": {
"Viewer": 5,
"Channel": 101,
"PlayMode": 11
}
}
```
---
### Task 2: Alarm Query Endpoints ✅ COMPLETED (Event-Based)
**Priority:** HIGH
**Effort:** 3-4 hours
**Status:** Done (2026-02-03)
Add alarm query capability to bridges (critical for "never miss alarms" requirement).
**Finding:** GeViScope/G-Core SDKs don't have direct alarm query APIs like GeViSoft. They use event-based tracking.
**Implementation:**
- [x] Research GeViScope SDK - uses EventStarted/EventStopped notifications (no query API)
- [x] Research G-Core SDK - uses EventStarted/EventStopped notifications (no query API)
- [x] Implement `GET /alarms/active` - returns alarms tracked from events
- [x] Implement `GET /alarms` - returns all tracked alarms (active + stopped)
- [x] Track alarm state from EventStarted/EventStopped notifications
**Important:** For complete alarm state on startup (before any events received), the Flutter app should query GeViServer bridge which has `GeViSQ_GetFirstAlarm/GetNextAlarm` methods.
---
### Task 3: Monitor State Query ✅ COMPLETED (Event-Based)
**Priority:** MEDIUM
**Effort:** 2-3 hours
**Status:** Done (2026-02-03)
Add ability to query current monitor state on startup.
**Finding:** GeViScope/G-Core SDKs use ViewerConnected/ViewerCleared events for state changes. GetFirstVideoOutput is GeViSoft SDK only.
**Implementation:**
- [x] Research GeViScope SDK - uses ViewerConnected/ViewerCleared/ViewerSelectionChanged events
- [x] Research G-Core SDK - same event-based approach
- [x] Implement `GET /monitors` - returns all monitored states tracked from events
- [x] Implement `GET /monitors/{viewerId}` - returns specific monitor state
- [x] Track monitor state from ViewerConnected/ViewerCleared events
**Important:** For complete monitor state on startup (before any events received), the Flutter app should either:
1. Trigger a ViewerConnectLive to each known monitor (will fire ViewerConnected event)
2. Query GeViServer bridge if GeViSoft integration is acceptable
---
### Task 4: Server Configuration Documentation ✅ COMPLETED
**Priority:** MEDIUM
**Effort:** 2-3 hours
**Status:** Done (2026-02-03)
Document server configurations for deployment.
**Sub-tasks:**
1. [x] Create `servers.json` template with all server definitions
2. [x] Document camera ID ranges per server
3. [x] Document monitor ID mappings
4. [x] Create `crossswitch-rules.json` template
5. [x] Create `keyboards.json` template
**Files Created:**
- `C:\DEV\COPILOT_D6\config\servers.json.template`
- `C:\DEV\COPILOT_D6\config\crossswitch-rules.json.template`
- `C:\DEV\COPILOT_D6\config\keyboards.json.template`
---
### Task 5: Bridge Health Checks ✅ COMPLETED
**Priority:** MEDIUM
**Effort:** 1-2 hours
**Status:** Done (2026-02-03)
Enhance health check endpoints for production monitoring.
**Sub-tasks:**
1. [x] Add detailed status to `/status` endpoint:
- `is_connected` (bool)
- `address` (server address)
- `connection_duration_sec`
- `event_count` (since connection)
- `websocket_clients` (connected WS clients)
- `plc_active` (bool)
2. [x] Add `/health` endpoint for load balancer probes
3. [ ] Add connection auto-reconnect logic (deferred to Phase 1)
---
### Task 6: Integration Testing ⬜ IN PROGRESS
**Priority:** HIGH
**Effort:** 4-6 hours
Test bridges against actual servers.
**Sub-tasks:**
1. [ ] Test GeViScope Bridge against real GeViScope server
- [ ] Connection/disconnection
- [ ] ViewerConnectLive
- [ ] PTZ control
- [ ] Event reception via WebSocket
2. [ ] Test G-Core Bridge against real G-Core server
- [ ] Same tests as above
3. [x] Create test scripts (HTTP files)
- `C:\DEV\COPILOT_D6\tests\test-geviscope.http`
- `C:\DEV\COPILOT_D6\tests\test-gcore.http`
4. [ ] Document any SDK issues found
---
### Task 7: Logging Standardization ⬜
**Priority:** LOW
**Effort:** 2 hours
Standardize logging format for Kibana integration.
**Sub-tasks:**
1. [ ] Configure Serilog with JSON formatter
2. [ ] Add structured logging fields:
- keyboard_id
- server_id
- command
- duration_ms
- success
3. [ ] Configure log rotation
4. [ ] Test log output format
---
## File Structure After Phase 0
```
C:\DEV\COPILOT_D6\
├── docs\
│ ├── NEW_SYSTEM_DESIGN_SUMMARY.md
│ ├── IMPLEMENTATION_QUICK_REFERENCE.md
│ └── plans\
│ └── PHASE_0_INFRASTRUCTURE.md
├── config\
│ ├── servers.json.template ✅ Created
│ ├── crossswitch-rules.json.template ✅ Created
│ └── keyboards.json.template ✅ Created
└── tests\
├── test-geviscope.http ✅ Created
└── test-gcore.http ✅ Created
Bridges (existing, enhanced):
├── C:\DEV\COPILOT\geviscope-bridge\ ✅ WebSocket added
└── C:\DEV\COPILOT\gcore-bridge\ ✅ WebSocket added
```
---
## Success Criteria
- [x] Events are streamed via WebSocket from both bridges
- [x] Alarm state can be queried on demand (event-based tracking + `/alarms/active` endpoint)
- [x] Monitor state can be queried on demand (event-based tracking + `/monitors` endpoint)
- [x] Configuration templates are documented
- [ ] All bridge endpoints tested against real servers
- [ ] Logging format matches ELK requirements (Task 7 - pending, low priority)
---
## Dependencies
- Access to GeViScope server for testing
- Access to G-Core server for testing
- GeViScope SDK documentation (chunk files available)
- G-Core SDK documentation (chunk files available)
---
## Next Phase
After Phase 0, proceed to **Phase 1: Flutter Keyboard Core**
- Keyboard layout UI
- Direct command execution
- Server routing logic
- State notification subscription

View File

@@ -0,0 +1,382 @@
# Phase 1: Flutter Keyboard Core Implementation Plan
**Status:** In Progress
**Duration:** Week 3-5
**Goal:** Build the core Flutter keyboard app with direct command execution and state tracking
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Flutter Keyboard App │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ UI Layer │ │ BLoC Layer │ │ Data Layer │ │
│ │ (Screens) │◄─┤ (State) │◄─┤ (Services) │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌────────────────────────────────────────┼─────────────────┐ │
│ │ Service Layer │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌───┴───────┐ │ │
│ │ │BridgeService│ │ StateService│ │AlarmService│ │ │
│ │ │ (HTTP+WS) │ │ (Monitor+ │ │(Query+Track)│ │ │
│ │ └──────┬──────┘ │ Alarm) │ └─────┬─────┘ │ │
│ └─────────┼─────────┴─────────────┴────────┼──────────────┘ │
└────────────┼────────────────────────────────┼──────────────────┘
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ GeViScope/GCore│ │ GeViServer │
│ Bridges │ │ Bridge │
│ (7720/7721) │ │ (7710) │
└────────────────┘ └────────────────┘
```
---
## Phase 1 Tasks
### Task 1.1: Project Setup ✅ COMPLETED
**Priority:** HIGH
Create new Flutter project with proper structure.
**Sub-tasks:**
- [x] Create Flutter project: `copilot_keyboard`
- [x] Set up directory structure (clean architecture)
- [x] Add dependencies (BLoC, dio, web_socket_channel, etc.)
- [x] Configure for Windows desktop target
- [x] Create base config loading from `servers.json`
**Directory Structure:**
```
copilot_keyboard/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── config/
│ │ ├── app_config.dart
│ │ └── server_config.dart
│ ├── core/
│ │ ├── constants/
│ │ ├── errors/
│ │ └── utils/
│ ├── data/
│ │ ├── models/
│ │ ├── repositories/
│ │ └── services/
│ ├── domain/
│ │ ├── entities/
│ │ ├── repositories/
│ │ └── usecases/
│ └── presentation/
│ ├── blocs/
│ ├── screens/
│ └── widgets/
├── assets/
│ └── config/
├── test/
└── pubspec.yaml
```
---
### Task 1.2: Bridge Service ✅ COMPLETED
**Priority:** HIGH
Create service to communicate with all bridges.
**Sub-tasks:**
- [ ] Create `BridgeService` class
- [ ] Implement HTTP client for REST calls
- [ ] Implement WebSocket client for event streaming
- [ ] Add connection management (connect/disconnect/reconnect)
- [ ] Route commands to correct bridge based on camera/monitor ID
**Key Methods:**
```dart
class BridgeService {
// Connection
Future<void> connect(ServerConfig server);
Future<void> disconnect(String serverId);
// Commands (routed to correct bridge)
Future<void> viewerConnectLive(int viewer, int channel);
Future<void> viewerClear(int viewer);
Future<void> ptzPan(int camera, String direction, int speed);
Future<void> ptzTilt(int camera, String direction, int speed);
Future<void> ptzZoom(int camera, String direction, int speed);
Future<void> ptzStop(int camera);
Future<void> ptzPreset(int camera, int preset);
// Event stream
Stream<BridgeEvent> get eventStream;
}
```
---
### Task 1.3: State Service ✅ COMPLETED
**Priority:** HIGH
Track monitor and alarm state from events.
**Sub-tasks:**
- [ ] Create `StateService` class
- [ ] Subscribe to bridge WebSocket events
- [ ] Track monitor states (viewer → camera mapping)
- [ ] Track alarm states (active alarms)
- [ ] Provide state streams for UI
**Key Methods:**
```dart
class StateService {
// Monitor state
Stream<Map<int, MonitorState>> get monitorStates;
MonitorState? getMonitorState(int viewerId);
// Alarm state
Stream<List<AlarmState>> get activeAlarms;
bool isMonitorBlocked(int viewerId);
// Sync
Future<void> syncFromBridges();
}
```
---
### Task 1.4: Alarm Service (GeViServer Query) ✅ COMPLETED
**Priority:** HIGH
Query initial alarm state from GeViServer on startup.
**Sub-tasks:**
- [ ] Create `AlarmService` class
- [ ] Implement GeViServer bridge connection
- [ ] Query active alarms on startup using GetFirstAlarm/GetNextAlarm pattern
- [ ] Merge with event-based alarm tracking
- [ ] Periodic sync (every 30 seconds)
**Key Methods:**
```dart
class AlarmService {
Future<List<AlarmInfo>> queryAllAlarms();
Future<void> startPeriodicSync(Duration interval);
void stopPeriodicSync();
}
```
---
### Task 1.5: Keyboard Layout UI ✅ COMPLETED
**Priority:** HIGH
Build the main keyboard interface.
**Sub-tasks:**
- [ ] Create main keyboard screen layout
- [ ] Camera selection grid (numbered buttons)
- [ ] Monitor selection grid (numbered buttons)
- [ ] PTZ control panel (joystick or directional buttons)
- [ ] Preset buttons
- [ ] Status display (current camera on selected monitor)
- [ ] Alarm indicator panel
**UI Components:**
```
┌─────────────────────────────────────────────────────────────┐
│ COPILOT Keyboard [Status: Online] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ CAMERAS │ │ MONITORS │ │
│ │ [1] [2] [3] [4] [5] │ │ [1] [2] [3] [4] │ │
│ │ [6] [7] [8] [9] [10] │ │ [5] [6] [7] [8] │ │
│ │ ... │ │ [9!][10][11][12] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ PTZ CONTROL │ │ PRESETS │ │
│ │ [▲] │ │ [1] [2] [3] [4] │ │
│ │ [◄][●][►] │ │ [5] [6] [7] [8] │ │
│ │ [▼] │ │ │ │
│ │ [Z-] [Z+] │ │ │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ACTIVE ALARMS │ │
│ │ [!] Camera 5 - Motion Detected (10:30:15) │ │
│ │ [!] Camera 12 - Door Contact (10:28:42) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
### Task 1.6: BLoC Implementation ✅ COMPLETED
**Priority:** HIGH
Implement state management with BLoC pattern.
**BLoCs to Create:**
- [x] `ConnectionBloc` - Bridge connection state
- [x] `CameraBloc` - Camera selection and routing
- [x] `MonitorBloc` - Monitor state and selection
- [x] `PtzBloc` - PTZ control state
- [x] `AlarmBloc` - Alarm state and display
---
### Task 1.7: Server Routing Logic ✅ COMPLETED
**Priority:** HIGH
Route commands to correct bridge based on camera/monitor ranges.
**Sub-tasks:**
- [ ] Load server config from `servers.json`
- [ ] Implement camera-to-server mapping
- [ ] Implement monitor-to-server mapping
- [ ] Handle cross-server scenarios (camera on server A → monitor on server B)
**Routing Rules:**
```dart
class ServerRouter {
// Find which server owns a camera
ServerConfig? getServerForCamera(int cameraId);
// Find which server owns a monitor
ServerConfig? getServerForMonitor(int monitorId);
// Get bridge URL for a server
String getBridgeUrl(String serverId);
}
```
---
### Task 1.8: Error Handling ✅ COMPLETED
**Priority:** MEDIUM
Implement basic error handling and recovery.
**Sub-tasks:**
- [ ] Connection error handling with retry
- [ ] Command timeout handling
- [ ] Offline/degraded mode detection
- [ ] User-friendly error messages
- [ ] Logging for debugging
---
## Dependencies (pubspec.yaml)
```yaml
dependencies:
flutter:
sdk: flutter
# State Management
flutter_bloc: ^8.1.6
equatable: ^2.0.5
# Networking
dio: ^5.7.0
web_socket_channel: ^3.0.1
# Local Storage
shared_preferences: ^2.3.3
# Routing
go_router: ^14.6.2
# Dependency Injection
get_it: ^8.0.2
# Utilities
json_annotation: ^4.9.0
rxdart: ^0.28.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.13
json_serializable: ^6.8.0
bloc_test: ^9.1.7
mocktail: ^1.0.4
```
---
## Success Criteria
- [ ] App connects to all configured bridges on startup
- [ ] Camera button switches camera to selected monitor
- [ ] PTZ controls move the selected camera
- [ ] Monitor states update in real-time from WebSocket events
- [ ] Alarm states query from GeViServer on startup
- [ ] Alarms update in real-time from events
- [ ] Monitors with active alarms show visual indicator
- [ ] App works in degraded mode if bridges unavailable
---
## Files to Create
```
copilot_keyboard/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── injection_container.dart
│ ├── config/
│ │ ├── app_config.dart
│ │ └── server_config.dart
│ ├── core/
│ │ ├── constants/
│ │ │ └── api_constants.dart
│ │ ├── errors/
│ │ │ └── failures.dart
│ │ └── utils/
│ │ └── logger.dart
│ ├── data/
│ │ ├── models/
│ │ │ ├── monitor_state_model.dart
│ │ │ ├── alarm_state_model.dart
│ │ │ └── bridge_event_model.dart
│ │ └── services/
│ │ ├── bridge_service.dart
│ │ ├── state_service.dart
│ │ └── alarm_service.dart
│ ├── domain/
│ │ └── entities/
│ │ ├── monitor_state.dart
│ │ ├── alarm_state.dart
│ │ └── server_config.dart
│ └── presentation/
│ ├── blocs/
│ │ ├── connection/
│ │ ├── camera/
│ │ ├── monitor/
│ │ ├── ptz/
│ │ └── alarm/
│ ├── screens/
│ │ └── keyboard_screen.dart
│ └── widgets/
│ ├── camera_grid.dart
│ ├── monitor_grid.dart
│ ├── ptz_control.dart
│ ├── preset_buttons.dart
│ └── alarm_panel.dart
└── pubspec.yaml
```
---
## Next Phase Dependencies
Phase 1 creates the foundation for:
- **Phase 2:** Coordination layer (PRIMARY election, PTZ locks)
- **Phase 3:** Advanced features (sequences, CrossSwitch rules)