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

View File

@@ -0,0 +1,43 @@
# Check database file header
$dbPath = "C:\Users\ADMINI~1\AppData\Local\Temp\3\GeViDB_shadow_copy.mdb"
if (-not (Test-Path $dbPath)) {
Write-Host "[ERROR] Database file not found at: $dbPath" -ForegroundColor Red
exit 1
}
Write-Host "Reading database file header..." -ForegroundColor Yellow
Write-Host "File: $dbPath" -ForegroundColor Gray
Write-Host ""
try {
$bytes = [System.IO.File]::ReadAllBytes($dbPath) | Select-Object -First 64
Write-Host "First 64 bytes (hex):" -ForegroundColor Cyan
$hex = ($bytes | ForEach-Object { $_.ToString("X2") }) -join " "
Write-Host $hex -ForegroundColor White
Write-Host ""
Write-Host "As ASCII:" -ForegroundColor Cyan
$ascii = [System.Text.Encoding]::ASCII.GetString($bytes)
Write-Host $ascii -ForegroundColor White
Write-Host ""
# Check for common database signatures
$signature = [System.Text.Encoding]::ASCII.GetString($bytes[0..15])
if ($signature -match "Standard Jet DB") {
Write-Host "[INFO] Detected: Microsoft Jet Database (Access 97-2003)" -ForegroundColor Green
} elseif ($signature -match "Standard ACE DB") {
Write-Host "[INFO] Detected: Microsoft ACE Database (Access 2007+)" -ForegroundColor Green
} elseif ($bytes[0] -eq 0x00 -and $bytes[1] -eq 0x01 -and $bytes[2] -eq 0x00 -and $bytes[3] -eq 0x00) {
Write-Host "[INFO] Detected: Encrypted or protected database" -ForegroundColor Yellow
} else {
Write-Host "[WARN] Unknown database format" -ForegroundColor Yellow
}
} catch {
Write-Host "[ERROR] Failed to read file: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Reflection;
using System.Linq;
class InspectGeViSDK
{
static void Main()
{
Console.WriteLine("===========================================");
Console.WriteLine("GeViSoft SDK .NET API Inspector");
Console.WriteLine("===========================================");
Console.WriteLine();
// Load the assembly
string dllPath = @"C:\GEVISOFT\GeViProcAPINET_4_0.dll";
Assembly assembly = Assembly.LoadFrom(dllPath);
Console.WriteLine($"Loaded: {assembly.FullName}");
Console.WriteLine();
// Get all public types
var types = assembly.GetTypes()
.Where(t => t.IsPublic)
.OrderBy(t => t.FullName);
Console.WriteLine($"Found {types.Count()} public types");
Console.WriteLine();
// Focus on GeViDatabase class
var geviDatabaseType = types.FirstOrDefault(t => t.Name == "GeViDatabase");
if (geviDatabaseType != null)
{
Console.WriteLine("===========================================");
Console.WriteLine("GeViDatabase Class Methods");
Console.WriteLine("===========================================");
Console.WriteLine();
var methods = geviDatabaseType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName) // Exclude property getters/setters
.OrderBy(m => m.Name);
foreach (var method in methods)
{
var parameters = method.GetParameters();
var paramString = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($"{method.ReturnType.Name} {method.Name}({paramString})");
}
Console.WriteLine();
Console.WriteLine($"Total methods: {methods.Count()}");
Console.WriteLine();
}
// Look for Setup-related types
Console.WriteLine("===========================================");
Console.WriteLine("Setup-Related Types");
Console.WriteLine("===========================================");
Console.WriteLine();
var setupTypes = types.Where(t =>
t.Name.Contains("Setup") ||
t.Name.Contains("Alarm") ||
t.Name.Contains("Config") ||
t.Name.Contains("Enumerate"));
foreach (var type in setupTypes)
{
Console.WriteLine($"- {type.FullName}");
if (type.IsClass && !type.IsAbstract)
{
var setupMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName)
.Take(5);
foreach (var method in setupMethods)
{
Console.WriteLine($" {method.Name}()");
}
}
}
Console.WriteLine();
Console.WriteLine("===========================================");
Console.WriteLine("Inspection Complete");
Console.WriteLine("===========================================");
}
}

View File

@@ -0,0 +1,313 @@
# Read locked GeViDB.mdb using Windows Volume Shadow Copy
# This allows reading the database even when GeViServer has it locked
param(
[string]$Action = "test"
)
$ErrorActionPreference = "Stop"
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Volume Shadow Copy Database Reader" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
$dbPath = "C:\GEVISOFT\DATABASE\GeViDB.mdb"
$drive = "C:"
function Create-ShadowCopy {
Write-Host "Creating shadow copy of $drive..." -ForegroundColor Yellow
# Create shadow copy using WMI
$class = [WMICLASS]"root\cimv2:Win32_ShadowCopy"
$result = $class.Create($drive + "\", "ClientAccessible")
if ($result.ReturnValue -ne 0) {
throw "Failed to create shadow copy. Return value: $($result.ReturnValue)"
}
$shadowID = $result.ShadowID
Write-Host " [OK] Shadow copy created: $shadowID" -ForegroundColor Green
# Get the shadow copy
$shadow = Get-WmiObject -Class Win32_ShadowCopy | Where-Object { $_.ID -eq $shadowID }
if (-not $shadow) {
throw "Failed to retrieve shadow copy"
}
$deviceObject = $shadow.DeviceObject
Write-Host " Device: $deviceObject" -ForegroundColor Gray
return @{
ID = $shadowID
DeviceObject = $deviceObject
Shadow = $shadow
}
}
function Remove-ShadowCopy {
param($ShadowInfo)
Write-Host "Removing shadow copy..." -ForegroundColor Yellow
try {
$ShadowInfo.Shadow.Delete()
Write-Host " [OK] Shadow copy removed" -ForegroundColor Green
} catch {
Write-Host " [WARN] Failed to remove shadow copy: $($_.Exception.Message)" -ForegroundColor Yellow
}
}
function Test-DatabaseAccess {
param($ShadowDevicePath)
Write-Host "Testing database access from shadow copy..." -ForegroundColor Yellow
# Create a symbolic link to access the shadow copy
$linkPath = "C:\ShadowLink"
# Remove existing link if present
if (Test-Path $linkPath) {
cmd /c "rmdir $linkPath"
}
Write-Host " Creating symbolic link to shadow copy..." -ForegroundColor Gray
$linkCmd = "mklink /d `"$linkPath`" `"$ShadowDevicePath\`""
$linkResult = cmd /c $linkCmd 2>&1
if (-not (Test-Path $linkPath)) {
throw "Failed to create symbolic link: $linkResult"
}
Write-Host " [OK] Symbolic link created" -ForegroundColor Green
try {
# List what's in the shadow link root
Write-Host " Listing shadow copy root..." -ForegroundColor Gray
$rootItems = Get-ChildItem $linkPath -ErrorAction SilentlyContinue | Select-Object -First 10 -ExpandProperty Name
if ($rootItems) {
foreach ($item in $rootItems) {
Write-Host " - $item" -ForegroundColor DarkGray
}
} else {
Write-Host " [WARN] No items found in shadow root" -ForegroundColor Yellow
}
# The relative path from the drive root
$relativePath = $dbPath.Substring(3) # Remove "C:\" to get "GEVISOFT\DATABASE\GeViDB.mdb"
$shadowDbPath = Join-Path $linkPath $relativePath
Write-Host " Shadow DB path: $shadowDbPath" -ForegroundColor Gray
# Check if GEVISOFT folder exists
$gevisoftPath = Join-Path $linkPath "GEVISOFT"
if (Test-Path $gevisoftPath) {
Write-Host " [OK] GEVISOFT folder found in shadow" -ForegroundColor Green
# Check DATABASE subfolder
$databasePath = Join-Path $gevisoftPath "DATABASE"
if (Test-Path $databasePath) {
Write-Host " [OK] DATABASE folder found" -ForegroundColor Green
# List database files - try different methods
Write-Host " Attempting to list database files..." -ForegroundColor Gray
try {
$dbFiles = Get-ChildItem $databasePath -Force -ErrorAction Stop
Write-Host " Found $($dbFiles.Count) files:" -ForegroundColor Gray
foreach ($file in $dbFiles) {
Write-Host " - $($file.Name) ($([math]::Round($file.Length / 1MB, 2)) MB)" -ForegroundColor DarkGray
}
} catch {
Write-Host " [WARN] Get-ChildItem failed: $($_.Exception.Message)" -ForegroundColor Yellow
# Try dir command instead
Write-Host " Trying dir command..." -ForegroundColor Gray
$dirOutput = cmd /c "dir `"$databasePath`" /b" 2>&1
Write-Host " Dir output: $dirOutput" -ForegroundColor DarkGray
}
} else {
Write-Host " [WARN] DATABASE folder not found at: $databasePath" -ForegroundColor Yellow
}
} else {
Write-Host " [WARN] GEVISOFT folder not found in shadow copy" -ForegroundColor Yellow
}
Write-Host ""
Write-Host " Preparing to copy database..." -ForegroundColor Yellow
# Copy from shadow to temp location for testing
$tempDb = "$env:TEMP\GeViDB_shadow_copy.mdb"
if (Test-Path $tempDb) {
Remove-Item $tempDb -Force
}
Write-Host " Copying database from shadow..." -ForegroundColor Gray
Write-Host " Target: $tempDb" -ForegroundColor Gray
# Try .NET file I/O which may handle shadow copies better
Write-Host " Attempting .NET File.Copy..." -ForegroundColor Yellow
try {
# Use .NET FileStream to read from shadow copy
Write-Host " Opening source file..." -ForegroundColor Gray
$sourceStream = [System.IO.File]::Open(
$shadowDbPath,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::ReadWrite
)
Write-Host " Source opened, size: $([math]::Round($sourceStream.Length / 1MB, 2)) MB" -ForegroundColor Green
# Create destination file
Write-Host " Creating destination file..." -ForegroundColor Gray
$destStream = [System.IO.File]::Create($tempDb)
# Copy data
Write-Host " Copying data..." -ForegroundColor Gray
$sourceStream.CopyTo($destStream)
# Close streams
$destStream.Close()
$sourceStream.Close()
Write-Host " [OK] .NET copy completed" -ForegroundColor Green
} catch {
Write-Host " [ERROR] .NET copy failed: $($_.Exception.Message)" -ForegroundColor Red
# Clean up if open
if ($null -ne $destStream) { $destStream.Dispose() }
if ($null -ne $sourceStream) { $sourceStream.Dispose() }
# Try direct .NET File.Copy as last resort
Write-Host " Trying System.IO.File.Copy..." -ForegroundColor Yellow
try {
[System.IO.File]::Copy($shadowDbPath, $tempDb, $true)
Write-Host " [OK] System.IO.File.Copy succeeded" -ForegroundColor Green
} catch {
Write-Host " [ERROR] All copy methods failed: $($_.Exception.Message)" -ForegroundColor Red
}
}
} finally {
# Clean up symbolic link
if (Test-Path $linkPath) {
Write-Host " Removing symbolic link..." -ForegroundColor Gray
cmd /c "rmdir $linkPath" 2>&1 | Out-Null
}
}
if (Test-Path $tempDb) {
$size = (Get-Item $tempDb).Length / 1MB
Write-Host " [OK] Database copied successfully ($([math]::Round($size, 2)) MB)" -ForegroundColor Green
# Try to read it
Write-Host ""
Write-Host "Testing database read..." -ForegroundColor Yellow
# Try different providers
$providers = @(
"Microsoft.Jet.OLEDB.4.0", # Access 2003 and earlier
"Microsoft.ACE.OLEDB.12.0", # Access 2007-2013
"Microsoft.ACE.OLEDB.16.0" # Access 2016+
)
$connected = $false
$conn = New-Object -ComObject ADODB.Connection
foreach ($provider in $providers) {
$connectionString = "Provider=$provider;Data Source=$tempDb;"
Write-Host " Trying provider: $provider" -ForegroundColor Gray
try {
$conn.Open($connectionString)
Write-Host " [OK] Connected with $provider" -ForegroundColor Green
$connected = $true
break
} catch {
Write-Host " [WARN] Failed with $provider : $($_.Exception.Message)" -ForegroundColor Yellow
if ($conn.State -ne 0) {
try { $conn.Close() } catch { }
}
}
}
if (-not $connected) {
throw "Failed to connect with any provider"
}
try {
# Test query
$rs = $conn.Execute("SELECT COUNT(*) FROM MSysObjects WHERE Type=1")
$tableCount = $rs.Fields.Item(0).Value
$rs.Close()
Write-Host " [OK] Database has $tableCount user tables" -ForegroundColor Green
$conn.Close()
return $tempDb
} catch {
Write-Host " [ERROR] Failed to query database: $($_.Exception.Message)" -ForegroundColor Red
if ($conn.State -ne 0) {
try { $conn.Close() } catch { }
}
throw
}
} else {
throw "Failed to copy database from shadow"
}
}
# Main execution
try {
# Check if running as administrator
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "[ERROR] This script requires Administrator privileges" -ForegroundColor Red
Write-Host "Please run PowerShell as Administrator and try again" -ForegroundColor Yellow
exit 1
}
Write-Host "[OK] Running as Administrator" -ForegroundColor Green
Write-Host ""
# Create shadow copy
$shadowInfo = Create-ShadowCopy
Write-Host ""
try {
# Test access
$tempDb = Test-DatabaseAccess -ShadowDevicePath $shadowInfo.DeviceObject
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "SUCCESS!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Database successfully copied from shadow to:" -ForegroundColor Green
Write-Host " $tempDb" -ForegroundColor White
Write-Host ""
Write-Host "You can now use this copy for read operations." -ForegroundColor Gray
} finally {
Write-Host ""
Remove-ShadowCopy -ShadowInfo $shadowInfo
}
} catch {
Write-Host ""
Write-Host "[ERROR] $($_.Exception.Message)" -ForegroundColor Red
Write-Host ""
Write-Host "Stack trace:" -ForegroundColor Gray
Write-Host $_.ScriptStackTrace -ForegroundColor Gray
exit 1
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan

View File

@@ -0,0 +1,53 @@
using System;
using System.Data.OleDb;
class TestDbAccess
{
static void Main()
{
string dbPath = @"C:\GEVISOFT\DATABASE\GeViDB.mdb";
// Try different connection strings
string[] connectionStrings = {
$"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath};",
$"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath};Mode=Read;",
$"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath};Mode=Share Deny None;",
$"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={dbPath};",
};
foreach (var connStr in connectionStrings)
{
try
{
Console.WriteLine($"\nTrying: {connStr.Substring(0, Math.Min(60, connStr.Length))}...");
using (var conn = new OleDbConnection(connStr))
{
conn.Open();
Console.WriteLine("[OK] Connected successfully!");
// Try to list tables
var schema = conn.GetSchema("Tables");
Console.WriteLine($"[OK] Found {schema.Rows.Count} tables");
// Try to query Alarms table
using (var cmd = new OleDbCommand("SELECT COUNT(*) FROM Alarms", conn))
{
var count = cmd.ExecuteScalar();
Console.WriteLine($"[OK] Alarms table has {count} records");
}
conn.Close();
Console.WriteLine("[OK] This connection string works!");
return;
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] {ex.Message}");
}
}
Console.WriteLine("\n[FAILED] None of the connection strings worked.");
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Data.OleDb" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,176 @@
# Build and Test Script for GeViSoft Action Mapping Implementation
# PowerShell script to build SDK Bridge and verify implementation
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "GeViSoft Action Mapping Build & Test" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
$ErrorActionPreference = "Continue"
$ProjectRoot = "C:\DEV\COPILOT\geutebruck-api"
# Step 1: Build SDK Bridge
Write-Host "Step 1: Building SDK Bridge..." -ForegroundColor Yellow
Write-Host ""
Set-Location "$ProjectRoot\src\sdk-bridge\GeViScopeBridge"
Write-Host " - Restoring NuGet packages..." -ForegroundColor Gray
dotnet restore
Write-Host " - Building in Release configuration..." -ForegroundColor Gray
dotnet build --configuration Release
if ($LASTEXITCODE -eq 0) {
Write-Host " ✓ SDK Bridge build successful" -ForegroundColor Green
} else {
Write-Host " ✗ SDK Bridge build failed" -ForegroundColor Red
exit 1
}
Write-Host ""
# Step 2: Build DiagnoseActionMapping tool
Write-Host "Step 2: Building Diagnostic Tool..." -ForegroundColor Yellow
Write-Host ""
Set-Location "$ProjectRoot\src\sdk-bridge\DiagnoseActionMapping"
Write-Host " - Restoring NuGet packages..." -ForegroundColor Gray
dotnet restore
Write-Host " - Building diagnostic tool..." -ForegroundColor Gray
dotnet build --configuration Release
if ($LASTEXITCODE -eq 0) {
Write-Host " ✓ Diagnostic tool build successful" -ForegroundColor Green
} else {
Write-Host " ✗ Diagnostic tool build failed" -ForegroundColor Red
exit 1
}
Write-Host ""
# Step 3: Verify Python dependencies
Write-Host "Step 3: Verifying Python Dependencies..." -ForegroundColor Yellow
Write-Host ""
Set-Location "$ProjectRoot\src\api"
Write-Host " - Checking Python version..." -ForegroundColor Gray
python --version
Write-Host " - Checking required packages..." -ForegroundColor Gray
$packages = @("fastapi", "sqlalchemy", "alembic", "pydantic", "structlog")
foreach ($package in $packages) {
$installed = python -c "import $package; print($package.__version__)" 2>$null
if ($installed) {
Write-Host "$package installed: $installed" -ForegroundColor Green
} else {
Write-Host "$package not found" -ForegroundColor Red
}
}
Write-Host ""
# Step 4: Verify file creation
Write-Host "Step 4: Verifying Implementation Files..." -ForegroundColor Yellow
Write-Host ""
$filesToCheck = @(
# SDK Bridge files
@{Path="$ProjectRoot\src\sdk-bridge\GeViScopeBridge\appsettings.json"; Type="Config"},
@{Path="$ProjectRoot\src\sdk-bridge\GeViScopeBridge\SDK\ActionMappingHandler.cs"; Type="SDK Handler"},
@{Path="$ProjectRoot\src\sdk-bridge\GeViScopeBridge\Services\ActionMappingService.cs"; Type="gRPC Service"},
@{Path="$ProjectRoot\src\sdk-bridge\Protos\actionmapping.proto"; Type="Proto Definition"},
# Python API files
@{Path="$ProjectRoot\src\api\models\action_mapping.py"; Type="Database Model"},
@{Path="$ProjectRoot\src\api\schemas\action_mapping.py"; Type="Pydantic Schema"},
@{Path="$ProjectRoot\src\api\services\action_mapping_service.py"; Type="Service Layer"},
@{Path="$ProjectRoot\src\api\routers\action_mappings.py"; Type="API Router"},
@{Path="$ProjectRoot\src\api\migrations\versions\20251210_action_mappings.py"; Type="Migration"},
# Tools & Docs
@{Path="$ProjectRoot\tools\test_action_mappings.py"; Type="Test Tool"},
@{Path="$ProjectRoot\docs\ACTION_MAPPING_IMPLEMENTATION.md"; Type="Documentation"}
)
$allFilesExist = $true
foreach ($file in $filesToCheck) {
if (Test-Path $file.Path) {
Write-Host "$($file.Type) exists" -ForegroundColor Green
} else {
Write-Host "$($file.Type) missing: $($file.Path)" -ForegroundColor Red
$allFilesExist = $false
}
}
if (-not $allFilesExist) {
Write-Host ""
Write-Host "✗ Some files are missing!" -ForegroundColor Red
exit 1
}
Write-Host ""
# Step 5: Test SDK Bridge Configuration
Write-Host "Step 5: Validating SDK Bridge Configuration..." -ForegroundColor Yellow
Write-Host ""
Set-Location "$ProjectRoot\src\sdk-bridge\GeViScopeBridge"
$config = Get-Content "appsettings.json" | ConvertFrom-Json
Write-Host " GeViScope Configuration:" -ForegroundColor Gray
Write-Host " Host: $($config.GeViScope.Host)" -ForegroundColor Gray
Write-Host " Username: $($config.GeViScope.Username)" -ForegroundColor Gray
Write-Host " GeViSoft Configuration:" -ForegroundColor Gray
Write-Host " Host: $($config.GeViSoft.Host)" -ForegroundColor Gray
Write-Host " Username: $($config.GeViSoft.Username)" -ForegroundColor Gray
Write-Host " gRPC Server:" -ForegroundColor Gray
Write-Host " Port: $($config.GrpcServer.Port)" -ForegroundColor Gray
Write-Host ""
# Step 6: Summary
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Build & Verification Complete" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Next Steps:" -ForegroundColor Yellow
Write-Host ""
Write-Host "1. Start GeViServer (GeViSoft)" -ForegroundColor White
Write-Host " - Ensure GeViServer is running on localhost" -ForegroundColor Gray
Write-Host ""
Write-Host "2. Run Database Migration:" -ForegroundColor White
Write-Host " cd $ProjectRoot\src\api" -ForegroundColor Gray
Write-Host " alembic upgrade head" -ForegroundColor Gray
Write-Host ""
Write-Host "3. Start SDK Bridge:" -ForegroundColor White
Write-Host " cd $ProjectRoot\src\sdk-bridge\GeViScopeBridge" -ForegroundColor Gray
Write-Host " dotnet run" -ForegroundColor Gray
Write-Host ""
Write-Host "4. Start Python API:" -ForegroundColor White
Write-Host " cd $ProjectRoot\src\api" -ForegroundColor Gray
Write-Host " python main.py" -ForegroundColor Gray
Write-Host ""
Write-Host "5. Test Implementation:" -ForegroundColor White
Write-Host " cd $ProjectRoot" -ForegroundColor Gray
Write-Host " python tools\test_action_mappings.py" -ForegroundColor Gray
Write-Host ""
Write-Host "6. Run Diagnostic Tool:" -ForegroundColor White
Write-Host " cd $ProjectRoot\src\sdk-bridge\DiagnoseActionMapping" -ForegroundColor Gray
Write-Host " dotnet run -- localhost sysadmin password" -ForegroundColor Gray
Write-Host ""
Write-Host "Documentation:" -ForegroundColor Yellow
Write-Host " $ProjectRoot\docs\ACTION_MAPPING_IMPLEMENTATION.md" -ForegroundColor Gray
Write-Host ""
Set-Location $ProjectRoot

View File

@@ -0,0 +1,24 @@
"""
Create database tables for action mappings
"""
import sys
import asyncio
sys.path.insert(0, r'C:\DEV\COPILOT\geutebruck-api\src\api')
from models import Base, engine
from models.action_mapping import ActionMapping, ActionMappingExecution
async def create_tables():
"""Create all tables"""
print("Creating database tables...")
async with engine.begin() as conn:
# Create all tables
await conn.run_sync(Base.metadata.create_all)
print("✅ Tables created successfully!")
print(" - action_mappings")
print(" - action_mapping_executions")
if __name__ == "__main__":
asyncio.run(create_tables())

View File

@@ -0,0 +1,51 @@
# Extract CHM file contents using hh.exe and ITS protocol
param(
[string]$ChmPath = "C:\GEVISOFT\Documentation\GeViSoft .NET SDK API Documentation.chm",
[string]$OutputDir = "C:\DEV\COPILOT\geutebruck-api\docs\chm-extracted"
)
$ErrorActionPreference = "Continue"
Write-Host "Extracting CHM file..." -ForegroundColor Cyan
Write-Host "Source: $ChmPath" -ForegroundColor Gray
Write-Host "Output: $OutputDir" -ForegroundColor Gray
Write-Host ""
# Create output directory
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
# Try method 1: hh.exe with -decompile
Write-Host "[1] Trying hh.exe -decompile..." -ForegroundColor Yellow
$result = Start-Process -FilePath "hh.exe" -ArgumentList "-decompile", $OutputDir, $ChmPath -Wait -PassThru -NoNewWindow
Write-Host " Exit code: $($result.ExitCode)" -ForegroundColor Gray
# Check if files were created
$files = Get-ChildItem -Path $OutputDir -Recurse -File
Write-Host " Files extracted: $($files.Count)" -ForegroundColor Gray
if ($files.Count -eq 0) {
Write-Host "[2] Trying expand.exe..." -ForegroundColor Yellow
expand.exe $ChmPath $OutputDir
$files = Get-ChildItem -Path $OutputDir -Recurse -File
Write-Host " Files extracted: $($files.Count)" -ForegroundColor Gray
}
if ($files.Count -gt 0) {
Write-Host ""
Write-Host "[OK] Successfully extracted CHM contents" -ForegroundColor Green
Write-Host ""
Write-Host "HTML files found:" -ForegroundColor Cyan
$htmlFiles = $files | Where-Object { $_.Extension -match '\.(html?|htm)$' } | Select-Object -First 10
foreach ($file in $htmlFiles) {
Write-Host " - $($file.Name)" -ForegroundColor White
}
} else {
Write-Host ""
Write-Host "[ERROR] Could not extract CHM file" -ForegroundColor Red
Write-Host ""
Write-Host "Alternative: You can manually extract using:" -ForegroundColor Yellow
Write-Host " 1. 7-Zip: Right-click CHM -> 7-Zip -> Extract" -ForegroundColor Gray
Write-Host " 2. CHM Decoder: http://www.manmrk.net/tutorials/CHM/" -ForegroundColor Gray
Write-Host " 3. View in browser: mk:@MSITStore:$ChmPath" -ForegroundColor Gray
}

View File

@@ -0,0 +1,126 @@
# Finding Geutebruck Server Credentials
The SDK Bridge is failing to connect with error: **`connectRemoteUnknownUser`**
This means the username/password combination is invalid.
## Servers Running ✅
- **GeViServer** (PID 5212) - GeViSoft server
- **GSCServer** (PID 10852) - GeViScope server
## Credentials Tried ❌
- Username: `sysadmin` / Password: `` (empty)
- Username: `sysadmin` / Password: `masterkey`
## Where to Find Correct Credentials
### Option 1: Check GeViSet Configuration
1. Open **GeViSet** (Geutebruck configuration tool)
2. Look for connection settings or user management
3. Check what username is configured for SDK access
4. Note the username and password
### Option 2: Check GeViScope Configuration Files
Look for configuration files in:
- `C:\GEVISOFT\`
- `C:\Program Files\Geutebruck\`
- `C:\Program Files (x86)\Geutebruck\`
Common config file names:
- `GeViScope.ini`
- `GSCServer.ini`
- `config.xml`
- `users.xml`
### Option 3: Check GeViAPI Test Client
If you have GeViAPI Test Client working:
1. Open the client
2. Check what credentials it's using to connect
3. Use the same credentials in `appsettings.json`
### Option 4: Try Common Default Users
Common Geutebruck default usernames:
- `administrator` / `admin`
- `admin` / `admin`
- `root` / `root`
- `geviscope` / `geviscope`
- `gevisoft` / `gevisoft`
### Option 5: Check Your Previous Working Configuration
From the conversation history, the system was working before. Check:
1. Previous `appsettings.json` backups
2. Your notes about what credentials worked
3. System documentation you may have
## How to Test Credentials
### Quick Test Script
Create a file `test_credentials.ps1`:
```powershell
# Test different credentials
$users = @("sysadmin", "admin", "administrator", "root", "geviscope")
$passwords = @("", "masterkey", "admin", "password", "geviscope")
foreach ($user in $users) {
foreach ($pass in $passwords) {
Write-Host "Testing: $user / $pass" -ForegroundColor Yellow
# Update appsettings.json
$config = Get-Content 'C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\appsettings.json' | ConvertFrom-Json
$config.GeViScope.Username = $user
$config.GeViScope.Password = $pass
$config | ConvertTo-Json | Set-Content 'C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\appsettings.json'
# Try to connect
$result = & 'C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\bin\Release\net8.0\GeViScopeBridge.exe' 2>&1
if ($result -match "Successfully connected") {
Write-Host "SUCCESS! Working credentials: $user / $pass" -ForegroundColor Green
break
}
Start-Sleep -Seconds 2
}
}
```
## Once You Find Working Credentials
### Update Configuration
Edit: `C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\appsettings.json`
```json
{
"GeViScope": {
"Host": "localhost",
"Username": "YOUR_WORKING_USERNAME",
"Password": "YOUR_WORKING_PASSWORD"
},
"GeViSoft": {
"Host": "localhost",
"Username": "YOUR_WORKING_USERNAME",
"Password": "YOUR_WORKING_PASSWORD"
},
"GrpcServer": {
"Port": 50051
}
}
```
### Then Start Services
```powershell
# Terminal 1: SDK Bridge
cd C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\bin\Release\net8.0
.\GeViScopeBridge.exe
# Terminal 2: Python API
cd C:\DEV\COPILOT\geutebruck-api\src\api
& 'C:\DEV\COPILOT\geutebruck-api\.venv\Scripts\python.exe' main.py
```
### Test the API
Open: http://localhost:8000/docs
---
**Everything else is ready - just need the right credentials!**

View File

@@ -0,0 +1,103 @@
# Inspect GeViSoft SDK Methods using Reflection
$ErrorActionPreference = "Stop"
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "GeViSoft SDK Method Inspector" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host ""
# Load the assembly
$dllPath = "C:\GEVISOFT\GeViProcAPINET_4_0.dll"
$assembly = [System.Reflection.Assembly]::LoadFrom($dllPath)
Write-Host "Loaded: $($assembly.FullName)" -ForegroundColor Gray
Write-Host ""
# Get all public types
$types = $assembly.GetTypes() | Where-Object { $_.IsPublic }
# Focus on GeViDatabase class
$geviDB = $types | Where-Object { $_.Name -eq "GeViDatabase" }
if ($geviDB) {
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "GeViDatabase Class Methods" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host ""
# Get methods related to Setup, Alarm, Config, Enumerate
Write-Host "Methods containing Setup/Alarm/Config/Enumerate:" -ForegroundColor Yellow
Write-Host ""
$relevantMethods = $geviDB.GetMethods() |
Where-Object { $_.Name -match 'Setup|Alarm|Config|Enumerate|GetSetup|SetSetup' } |
Sort-Object Name
foreach ($method in $relevantMethods) {
$params = $method.GetParameters()
$paramStrings = @()
foreach ($p in $params) {
$paramStrings += "$($p.ParameterType.Name) $($p.Name)"
}
$paramStr = $paramStrings -join ", "
Write-Host " $($method.ReturnType.Name) $($method.Name)($paramStr)" -ForegroundColor White
}
Write-Host ""
Write-Host "Total relevant methods: $($relevantMethods.Count)" -ForegroundColor Gray
Write-Host ""
# Also look for any Alarm-related methods
Write-Host "All methods with 'Alarm' in name:" -ForegroundColor Yellow
Write-Host ""
$alarmMethods = $geviDB.GetMethods() |
Where-Object { $_.Name -match 'Alarm' } |
Sort-Object Name
foreach ($method in $alarmMethods) {
$params = $method.GetParameters()
$paramStrings = @()
foreach ($p in $params) {
$paramStrings += "$($p.ParameterType.Name) $($p.Name)"
}
$paramStr = $paramStrings -join ", "
Write-Host " $($method.ReturnType.Name) $($method.Name)($paramStr)" -ForegroundColor White
}
Write-Host ""
Write-Host "Total alarm methods: $($alarmMethods.Count)" -ForegroundColor Gray
Write-Host ""
}
# Look for other relevant types
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "Other Setup/Alarm Related Types" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host ""
$setupTypes = $types | Where-Object {
$_.Name -match 'Setup|Alarm|Config|ActionMapping'
} | Sort-Object Name
foreach ($type in $setupTypes) {
Write-Host "- $($type.FullName)" -ForegroundColor White
if ($type.IsClass -and !$type.IsAbstract) {
$methods = $type.GetMethods() |
Where-Object { !$_.IsSpecialName } |
Select-Object -First 5
foreach ($m in $methods) {
Write-Host " $($m.Name)()" -ForegroundColor Gray
}
}
}
Write-Host ""
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "Inspection Complete" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan

View File

@@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
Test script for Action Mapping API endpoints
Tests CRUD operations and validates the implementation
"""
import requests
import json
from typing import Optional
import sys
class ActionMappingAPITester:
"""Test harness for Action Mapping API"""
def __init__(self, base_url: str = "http://localhost:8000", token: Optional[str] = None):
self.base_url = base_url
self.token = token
self.session = requests.Session()
if token:
self.session.headers.update({
"Authorization": f"Bearer {token}"
})
def login(self, username: str = "admin", password: str = "admin123"):
"""Login and get access token"""
print(f"\n=== Logging in as {username} ===")
response = self.session.post(
f"{self.base_url}/api/v1/auth/login",
json={"username": username, "password": password}
)
if response.status_code == 200:
data = response.json()
self.token = data["access_token"]
self.session.headers.update({
"Authorization": f"Bearer {self.token}"
})
print(f"✓ Login successful")
return True
else:
print(f"✗ Login failed: {response.status_code} - {response.text}")
return False
def list_action_mappings(self, enabled_only: bool = False):
"""Test: List all action mappings"""
print(f"\n=== Test: List Action Mappings (enabled_only={enabled_only}) ===")
params = {"enabled_only": enabled_only}
response = self.session.get(
f"{self.base_url}/api/v1/action-mappings",
params=params
)
print(f"Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"✓ Found {data['total_count']} action mappings")
print(f" Enabled: {data['enabled_count']}")
print(f" Disabled: {data['disabled_count']}")
for mapping in data['mappings']:
print(f"\n {mapping['name']} ({mapping['id']})")
print(f" Input: {mapping['input_action']}")
print(f" Outputs: {mapping['output_actions']}")
print(f" Enabled: {mapping['enabled']}")
print(f" Executions: {mapping['execution_count']}")
return data
else:
print(f"✗ Failed: {response.text}")
return None
def create_action_mapping(self, name: str, input_action: str, output_actions: list):
"""Test: Create new action mapping"""
print(f"\n=== Test: Create Action Mapping '{name}' ===")
mapping_data = {
"name": name,
"description": f"Test mapping: {name}",
"input_action": input_action,
"output_actions": output_actions,
"enabled": True
}
print(f"Creating:")
print(f" Input: {input_action}")
print(f" Outputs: {output_actions}")
response = self.session.post(
f"{self.base_url}/api/v1/action-mappings",
json=mapping_data
)
print(f"Status: {response.status_code}")
if response.status_code == 201:
data = response.json()
print(f"✓ Created action mapping")
print(f" ID: {data['id']}")
print(f" Name: {data['name']}")
return data
else:
print(f"✗ Failed: {response.text}")
return None
def get_action_mapping(self, mapping_id: str):
"""Test: Get specific action mapping"""
print(f"\n=== Test: Get Action Mapping {mapping_id} ===")
response = self.session.get(
f"{self.base_url}/api/v1/action-mappings/{mapping_id}"
)
print(f"Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"✓ Retrieved action mapping")
print(f" Name: {data['name']}")
print(f" Input: {data['input_action']}")
print(f" Outputs: {data['output_actions']}")
return data
else:
print(f"✗ Failed: {response.text}")
return None
def update_action_mapping(self, mapping_id: str, updates: dict):
"""Test: Update action mapping"""
print(f"\n=== Test: Update Action Mapping {mapping_id} ===")
print(f"Updates: {json.dumps(updates, indent=2)}")
response = self.session.put(
f"{self.base_url}/api/v1/action-mappings/{mapping_id}",
json=updates
)
print(f"Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"✓ Updated action mapping")
print(f" Name: {data['name']}")
print(f" Enabled: {data['enabled']}")
return data
else:
print(f"✗ Failed: {response.text}")
return None
def delete_action_mapping(self, mapping_id: str):
"""Test: Delete action mapping"""
print(f"\n=== Test: Delete Action Mapping {mapping_id} ===")
response = self.session.delete(
f"{self.base_url}/api/v1/action-mappings/{mapping_id}"
)
print(f"Status: {response.status_code}")
if response.status_code == 204:
print(f"✓ Deleted action mapping")
return True
else:
print(f"✗ Failed: {response.text}")
return False
def run_full_test_suite(self):
"""Run complete test suite"""
print("=" * 60)
print("Action Mapping API Test Suite")
print("=" * 60)
# Login
if not self.login():
print("\n✗ Cannot proceed without authentication")
return False
# Test 1: List initial state
initial_list = self.list_action_mappings()
# Test 2: Create action mapping
created = self.create_action_mapping(
name="Test Motion Detection Alert",
input_action="VMD_Start(101038)",
output_actions=[
"CrossSwitch(101038, 1, 0)",
"SendMail(security@example.com, Motion Detected)"
]
)
if not created:
print("\n✗ Cannot proceed - failed to create action mapping")
return False
mapping_id = created['id']
# Test 3: Get the created mapping
self.get_action_mapping(mapping_id)
# Test 4: Update the mapping
self.update_action_mapping(
mapping_id,
{
"enabled": False,
"description": "Updated test description"
}
)
# Test 5: List again to see the update
self.list_action_mappings()
# Test 6: Delete the mapping
self.delete_action_mapping(mapping_id)
# Test 7: Verify deletion
print(f"\n=== Test: Verify Deletion ===")
response = self.session.get(f"{self.base_url}/api/v1/action-mappings/{mapping_id}")
if response.status_code == 404:
print("✓ Mapping successfully deleted (404 Not Found)")
else:
print(f"✗ Unexpected response: {response.status_code}")
print("\n" + "=" * 60)
print("Test Suite Complete")
print("=" * 60)
return True
def main():
"""Main entry point"""
import argparse
parser = argparse.ArgumentParser(description="Test Action Mapping API")
parser.add_argument("--url", default="http://localhost:8000", help="API base URL")
parser.add_argument("--username", default="admin", help="Username for login")
parser.add_argument("--password", default="admin123", help="Password for login")
args = parser.parse_args()
tester = ActionMappingAPITester(base_url=args.url)
if not tester.login(args.username, args.password):
print("Login failed - check credentials and API availability")
sys.exit(1)
success = tester.run_full_test_suite()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,97 @@
# Automated Credential Testing Script
# Tests common Geutebruck username/password combinations
$ErrorActionPreference = "SilentlyContinue"
$users = @("sysadmin", "admin", "administrator", "root", "geviscope", "gevisoft")
$passwords = @("", "masterkey", "admin", "password", "geviscope", "gevisoft", "123456", "geutebruck")
$configPath = "C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\appsettings.json"
$exePath = "C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\bin\Release\net8.0\GeViScopeBridge.exe"
$logPath = "C:\DEV\COPILOT\geutebruck-api\tools\credential_test.log"
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Testing Geutebruck Server Credentials" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
$testCount = 0
$totalTests = $users.Count * $passwords.Count
foreach ($user in $users) {
foreach ($pass in $passwords) {
$testCount++
$passDisplay = if ($pass -eq "") { "(empty)" } else { $pass }
Write-Host "[$testCount/$totalTests] Testing: $user / $passDisplay" -ForegroundColor Yellow
# Update appsettings.json
$config = @{
GeViScope = @{
Host = "localhost"
Username = $user
Password = $pass
}
GeViSoft = @{
Host = "localhost"
Username = $user
Password = $pass
}
GrpcServer = @{
Port = 50051
}
Logging = @{
LogLevel = @{
Default = "Information"
Microsoft = "Warning"
Grpc = "Information"
}
}
}
$config | ConvertTo-Json -Depth 5 | Out-File $configPath -Encoding UTF8
# Run SDK Bridge with timeout
$proc = Start-Process -FilePath $exePath -PassThru -RedirectStandardOutput $logPath -RedirectStandardError $logPath -WindowStyle Hidden
# Wait up to 8 seconds for connection
Start-Sleep -Seconds 8
# Check output
$output = Get-Content $logPath -Raw -ErrorAction SilentlyContinue
# Kill the process
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
# Check for success
if ($output -match "Successfully connected to GeViServer") {
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host "SUCCESS! WORKING CREDENTIALS FOUND" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "Username: $user" -ForegroundColor Green
Write-Host "Password: $passDisplay" -ForegroundColor Green
Write-Host ""
Write-Host "Configuration has been updated in:" -ForegroundColor Green
Write-Host " $configPath" -ForegroundColor Green
Write-Host ""
Write-Host "You can now start the SDK Bridge:" -ForegroundColor Cyan
Write-Host " cd C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\bin\Release\net8.0" -ForegroundColor Cyan
Write-Host " .\GeViScopeBridge.exe" -ForegroundColor Cyan
exit 0
}
Start-Sleep -Milliseconds 500
}
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Red
Write-Host "No working credentials found" -ForegroundColor Red
Write-Host "========================================" -ForegroundColor Red
Write-Host ""
Write-Host "Please check:" -ForegroundColor Yellow
Write-Host " 1. GeViSet configuration" -ForegroundColor Yellow
Write-Host " 2. GeViScope/GeViSoft config files" -ForegroundColor Yellow
Write-Host " 3. GeViAPI Test Client settings" -ForegroundColor Yellow
Write-Host ""
Write-Host "See: tools/find_credentials.md for more options" -ForegroundColor Cyan