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:
68
config/crossswitch-rules.json.template
Normal file
68
config/crossswitch-rules.json.template
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "./crossswitch-rules.schema.json",
|
||||
"version": "1.0",
|
||||
"description": "CrossSwitch rules for logical camera/monitor mapping",
|
||||
|
||||
"rules": [
|
||||
{
|
||||
"id": "rule-001",
|
||||
"name": "Direct Camera to Monitor",
|
||||
"description": "Default rule - map camera directly to monitor on same server",
|
||||
"enabled": true,
|
||||
"priority": 100,
|
||||
"conditions": {
|
||||
"cameraRange": [1, 200],
|
||||
"monitorRange": [1, 32]
|
||||
},
|
||||
"action": {
|
||||
"type": "viewerConnectLive",
|
||||
"routing": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rule-002",
|
||||
"name": "Alarm Monitor Override",
|
||||
"description": "Prevent switching on monitors with active alarms",
|
||||
"enabled": true,
|
||||
"priority": 10,
|
||||
"conditions": {
|
||||
"monitorHasAlarm": true,
|
||||
"alarmStates": ["vasNewAlarm", "vasPresented"]
|
||||
},
|
||||
"action": {
|
||||
"type": "deny",
|
||||
"reason": "Monitor has active alarm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rule-003",
|
||||
"name": "Cross-Server Camera to Monitor",
|
||||
"description": "Allow camera from one server to display on monitor of another",
|
||||
"enabled": true,
|
||||
"priority": 50,
|
||||
"conditions": {
|
||||
"crossServer": true
|
||||
},
|
||||
"action": {
|
||||
"type": "viewerConnectLive",
|
||||
"routing": "monitorServer",
|
||||
"note": "Command goes to monitor's server, not camera's server"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"defaults": {
|
||||
"playMode": "live",
|
||||
"switchTimeout": 5000,
|
||||
"verifySwitch": true,
|
||||
"verifyTimeout": 500
|
||||
},
|
||||
|
||||
"alarmMonitorReservation": {
|
||||
"enabled": true,
|
||||
"description": "When alarm fires, reserve monitor and switch to alarm camera",
|
||||
"reservationStates": ["vasNewAlarm", "vasPresented"],
|
||||
"autoRelease": false,
|
||||
"releaseOnConfirm": true
|
||||
}
|
||||
}
|
||||
69
config/keyboards.json.template
Normal file
69
config/keyboards.json.template
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "./keyboards.schema.json",
|
||||
"version": "1.0",
|
||||
"description": "Keyboard configuration for PRIMARY/STANDBY election",
|
||||
|
||||
"keyboards": [
|
||||
{
|
||||
"id": "KB-001",
|
||||
"name": "Control Room 1",
|
||||
"location": "Main Control Room",
|
||||
"priority": 100,
|
||||
"canBePrimary": true,
|
||||
"canBeStandby": true,
|
||||
"network": {
|
||||
"address": "192.168.1.101",
|
||||
"coordinationPort": 8090
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "KB-002",
|
||||
"name": "Control Room 2",
|
||||
"location": "Main Control Room",
|
||||
"priority": 90,
|
||||
"canBePrimary": true,
|
||||
"canBeStandby": true,
|
||||
"network": {
|
||||
"address": "192.168.1.102",
|
||||
"coordinationPort": 8090
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "KB-003",
|
||||
"name": "Security Station",
|
||||
"location": "Security Office",
|
||||
"priority": 50,
|
||||
"canBePrimary": false,
|
||||
"canBeStandby": true,
|
||||
"network": {
|
||||
"address": "192.168.1.103",
|
||||
"coordinationPort": 8090
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"election": {
|
||||
"heartbeatIntervalMs": 2000,
|
||||
"heartbeatTimeoutMs": 6000,
|
||||
"electionTimeoutMs": 10000,
|
||||
"minKeyboardsForQuorum": 1
|
||||
},
|
||||
|
||||
"coordination": {
|
||||
"primaryWebSocketPort": 8090,
|
||||
"stateSync": {
|
||||
"enabled": true,
|
||||
"intervalMs": 1000
|
||||
}
|
||||
},
|
||||
|
||||
"degradedMode": {
|
||||
"description": "Features available when no PRIMARY/STANDBY is online",
|
||||
"viewerConnectLive": true,
|
||||
"ptzControl": true,
|
||||
"ptzLocking": false,
|
||||
"sequences": false,
|
||||
"sharedState": false,
|
||||
"alarmDisplay": "local"
|
||||
}
|
||||
}
|
||||
104
config/servers.json.template
Normal file
104
config/servers.json.template
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"$schema": "./servers.schema.json",
|
||||
"version": "1.0",
|
||||
"description": "Server configuration for COPILOT keyboard system",
|
||||
|
||||
"servers": [
|
||||
{
|
||||
"id": "geviscope-01",
|
||||
"name": "GeViScope Server 1",
|
||||
"type": "geviscope",
|
||||
"enabled": true,
|
||||
"connection": {
|
||||
"address": "192.168.1.10",
|
||||
"port": 7700,
|
||||
"username": "operator",
|
||||
"password": "${GEVISCOPE_01_PASSWORD}"
|
||||
},
|
||||
"bridge": {
|
||||
"url": "http://localhost:7720",
|
||||
"websocket": "ws://localhost:7720/ws/events"
|
||||
},
|
||||
"resources": {
|
||||
"cameraRange": { "start": 1, "end": 100 },
|
||||
"monitorRange": { "start": 1, "end": 16 }
|
||||
},
|
||||
"features": {
|
||||
"ptz": true,
|
||||
"playback": true,
|
||||
"alarms": true,
|
||||
"digitalIO": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gcore-01",
|
||||
"name": "G-Core Server 1",
|
||||
"type": "gcore",
|
||||
"enabled": true,
|
||||
"connection": {
|
||||
"address": "192.168.1.20",
|
||||
"port": 7700,
|
||||
"username": "operator",
|
||||
"password": "${GCORE_01_PASSWORD}"
|
||||
},
|
||||
"bridge": {
|
||||
"url": "http://localhost:7721",
|
||||
"websocket": "ws://localhost:7721/ws/events"
|
||||
},
|
||||
"resources": {
|
||||
"cameraRange": { "start": 101, "end": 200 },
|
||||
"monitorRange": { "start": 17, "end": 32 }
|
||||
},
|
||||
"features": {
|
||||
"ptz": true,
|
||||
"playback": true,
|
||||
"alarms": true,
|
||||
"digitalIO": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "geviserver-01",
|
||||
"name": "GeViServer (PTZ Fallback)",
|
||||
"type": "geviserver",
|
||||
"enabled": false,
|
||||
"connection": {
|
||||
"address": "192.168.1.5",
|
||||
"port": 7700,
|
||||
"username": "operator",
|
||||
"password": "${GEVISERVER_01_PASSWORD}"
|
||||
},
|
||||
"bridge": {
|
||||
"url": "http://localhost:7710"
|
||||
},
|
||||
"resources": {
|
||||
"cameraRange": { "start": 1, "end": 200 },
|
||||
"monitorRange": { "start": 1, "end": 32 }
|
||||
},
|
||||
"features": {
|
||||
"ptz": true,
|
||||
"playback": false,
|
||||
"alarms": false,
|
||||
"digitalIO": false
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"routing": {
|
||||
"description": "Rules for routing commands to correct server based on camera/monitor ID",
|
||||
"cameraToServer": [
|
||||
{ "range": [1, 100], "serverId": "geviscope-01" },
|
||||
{ "range": [101, 200], "serverId": "gcore-01" }
|
||||
],
|
||||
"monitorToServer": [
|
||||
{ "range": [1, 16], "serverId": "geviscope-01" },
|
||||
{ "range": [17, 32], "serverId": "gcore-01" }
|
||||
]
|
||||
},
|
||||
|
||||
"healthCheck": {
|
||||
"enabled": true,
|
||||
"intervalMs": 30000,
|
||||
"timeoutMs": 5000,
|
||||
"unhealthyThreshold": 3
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user