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:
394
Docs/plans/2026-02-03-flutter-ui-design.md
Normal file
394
Docs/plans/2026-02-03-flutter-ui-design.md
Normal 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/
|
||||
```
|
||||
235
Docs/plans/PHASE_0_INFRASTRUCTURE.md
Normal file
235
Docs/plans/PHASE_0_INFRASTRUCTURE.md
Normal 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
|
||||
382
Docs/plans/PHASE_1_FLUTTER_KEYBOARD.md
Normal file
382
Docs/plans/PHASE_1_FLUTTER_KEYBOARD.md
Normal 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)
|
||||
Reference in New Issue
Block a user