feat: Geutebruck GeViScope/GeViSoft Action Mapping System - MVP

This MVP release provides a complete full-stack solution for managing action mappings
in Geutebruck's GeViScope and GeViSoft video surveillance systems.

## Features

### Flutter Web Application (Port 8081)
- Modern, responsive UI for managing action mappings
- Action picker dialog with full parameter configuration
- Support for both GSC (GeViScope) and G-Core server actions
- Consistent UI for input and output actions with edit/delete capabilities
- Real-time action mapping creation, editing, and deletion
- Server categorization (GSC: prefix for GeViScope, G-Core: prefix for G-Core servers)

### FastAPI REST Backend (Port 8000)
- RESTful API for action mapping CRUD operations
- Action template service with comprehensive action catalog (247 actions)
- Server management (G-Core and GeViScope servers)
- Configuration tree reading and writing
- JWT authentication with role-based access control
- PostgreSQL database integration

### C# SDK Bridge (gRPC, Port 50051)
- Native integration with GeViSoft SDK (GeViProcAPINET_4_0.dll)
- Action mapping creation with correct binary format
- Support for GSC and G-Core action types
- Proper Camera parameter inclusion in action strings (fixes CrossSwitch bug)
- Action ID lookup table with server-specific action IDs
- Configuration reading/writing via SetupClient

## Bug Fixes
- **CrossSwitch Bug**: GSC and G-Core actions now correctly display camera/PTZ head parameters in GeViSet
- Action strings now include Camera parameter: `@ PanLeft (Comment: "", Camera: 101028)`
- Proper filter flags and VideoInput=0 for action mappings
- Correct action ID assignment (4198 for GSC, 9294 for G-Core PanLeft)

## Technical Stack
- **Frontend**: Flutter Web, Dart, Dio HTTP client
- **Backend**: Python FastAPI, PostgreSQL, Redis
- **SDK Bridge**: C# .NET 8.0, gRPC, GeViSoft SDK
- **Authentication**: JWT tokens
- **Configuration**: GeViSoft .set files (binary format)

## Credentials
- GeViSoft/GeViScope: username=sysadmin, password=masterkey
- Default admin: username=admin, password=admin123

## Deployment
All services run on localhost:
- Flutter Web: http://localhost:8081
- FastAPI: http://localhost:8000
- SDK Bridge gRPC: localhost:50051
- GeViServer: localhost (default port)

Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Administrator
2025-12-31 18:10:54 +01:00
commit 14893e62a5
4189 changed files with 1395076 additions and 0 deletions

206
Parse-GeViSet-Simple.ps1 Normal file
View File

@@ -0,0 +1,206 @@
# Simple GeViSet parser - reads and displays configuration structure
param(
[string]$FilePath = "C:\Users\Administrator\Desktop\GeViSoft.set",
[string]$OutputJson = ""
)
$data = [System.IO.File]::ReadAllBytes($FilePath)
$pos = 0
$config = [ordered]@{}
function Read-Byte {
if ($script:pos -ge $script:data.Length) { throw "EOF" }
$b = $script:data[$script:pos]
$script:pos++
return $b
}
function Read-UInt16 {
$bytes = $script:data[$script:pos..($script:pos+1)]
$script:pos += 2
return [BitConverter]::ToUInt16($bytes, 0)
}
function Read-UInt32 {
$bytes = $script:data[$script:pos..($script:pos+3)]
$script:pos += 4
return [BitConverter]::ToUInt32($bytes, 0)
}
function Read-UInt64 {
$bytes = $script:data[$script:pos..($script:pos+7)]
$script:pos += 8
return [BitConverter]::ToUInt64($bytes, 0)
}
function Read-String {
param([int]$lengthBytes)
if ($lengthBytes -eq 1) {
$length = Read-Byte
} else {
$length = Read-UInt16
}
if ($length -eq 0) { return "" }
$stringData = $script:data[$script:pos..($script:pos + $length - 1)]
$script:pos += $length
try {
return [System.Text.Encoding]::UTF8.GetString($stringData)
} catch {
return [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetString($stringData)
}
}
function Read-Value {
param([byte]$type)
switch ($type) {
0x01 { return [bool](Read-Byte) }
0x04 { return Read-UInt32 }
0x02 { return Read-UInt64 }
0x08 { return Read-String -lengthBytes 2 }
default { throw "Unknown type: 0x$($type.ToString('X2'))" }
}
}
function Read-Section {
param([string]$name, [int]$indent = 0)
$section = [ordered]@{}
$prefix = " " * $indent
# Read metadata (int32 + byte)
try {
$metaInt = Read-UInt32
$metaByte = Read-Byte
$section['_meta'] = @{value=$metaInt; flags=$metaByte}
Write-Host "$prefix [Metadata: value=$metaInt, flags=$metaByte]" -ForegroundColor DarkGray
} catch {
Write-Warning "Failed to read metadata for section '$name'"
return $section
}
$maxIterations = 10000
$iterations = 0
while ($script:pos -lt $script:data.Length -and $iterations -lt $maxIterations) {
$iterations++
try {
$typeMarker = $script:data[$script:pos]
} catch {
break
}
if ($typeMarker -eq 0x00) {
# Section
Read-Byte | Out-Null
$subName = Read-String -lengthBytes 1
if ([string]::IsNullOrEmpty($subName)) {
# End of section
if ($script:pos + 3 -lt $script:data.Length) {
$script:pos += 4
}
break
}
Write-Host "$prefix [$subName]" -ForegroundColor Cyan
$section[$subName] = Read-Section -name $subName -indent ($indent + 1)
} elseif ($typeMarker -eq 0x07) {
# Property
Read-Byte | Out-Null
$propName = Read-String -lengthBytes 1
if ([string]::IsNullOrEmpty($propName)) {
continue
}
try {
$valueType = Read-Byte
$value = Read-Value -type $valueType
$section[$propName] = $value
$valueStr = if ($value -is [string] -and $value.Length -gt 50) {
$value.Substring(0, 47) + "..."
} else {
$value
}
Write-Host "$prefix $propName = $valueStr" -ForegroundColor Green
} catch {
Write-Warning "$prefix Failed to read property: $propName"
break
}
} else {
# Unknown or end
break
}
}
if ($iterations -ge $maxIterations) {
Write-Warning "Max iterations reached in section '$name'"
}
return $section
}
Write-Host "=" * 80 -ForegroundColor Yellow
Write-Host "Parsing GeViSet Configuration File" -ForegroundColor Yellow
Write-Host "=" * 80 -ForegroundColor Yellow
Write-Host "File: $FilePath"
Write-Host "Size: $($data.Length) bytes"
Write-Host ""
$topLevelSections = 0
while ($pos -lt $data.Length) {
try {
$typeMarker = $data[$pos]
if ($typeMarker -eq 0x00) {
Read-Byte | Out-Null
$sectionName = Read-String -lengthBytes 1
if ([string]::IsNullOrEmpty($sectionName)) {
if ($pos -ge $data.Length - 10) { break }
continue
}
$topLevelSections++
Write-Host ""
Write-Host "[$sectionName]" -ForegroundColor Yellow
$config[$sectionName] = Read-Section -name $sectionName -indent 0
} else {
if ($pos -ge $data.Length - 10) { break }
$pos++
}
} catch {
if ($_.Exception.Message -ne "EOF") {
Write-Warning "Error at position $pos : $_"
}
break
}
}
Write-Host ""
Write-Host "=" * 80 -ForegroundColor Yellow
Write-Host "Parsing Complete" -ForegroundColor Green
Write-Host "Top-level sections: $topLevelSections" -ForegroundColor Green
Write-Host "=" * 80 -ForegroundColor Yellow
if ($OutputJson) {
Write-Host ""
Write-Host "Exporting to JSON: $OutputJson" -ForegroundColor Cyan
$config | ConvertTo-Json -Depth 100 | Set-Content -Path $OutputJson -Encoding UTF8
Write-Host "JSON export complete." -ForegroundColor Green
}
return $config