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>
187 lines
5.7 KiB
Python
187 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare two .set files to find differences when adding a mapping"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def compare_binary_files(file1_path, file2_path):
|
|
"""Compare two binary files and show differences"""
|
|
|
|
# Read both files
|
|
with open(file1_path, 'rb') as f1:
|
|
data1 = f1.read()
|
|
|
|
with open(file2_path, 'rb') as f2:
|
|
data2 = f2.read()
|
|
|
|
print(f"File 1 (original): {len(data1):,} bytes")
|
|
print(f"File 2 (added): {len(data2):,} bytes")
|
|
print(f"Size difference: {len(data2) - len(data1):,} bytes")
|
|
print("=" * 80)
|
|
|
|
# Find first difference
|
|
min_len = min(len(data1), len(data2))
|
|
first_diff = None
|
|
|
|
for i in range(min_len):
|
|
if data1[i] != data2[i]:
|
|
first_diff = i
|
|
break
|
|
|
|
if first_diff is not None:
|
|
print(f"\nFirst difference at offset: {first_diff} (0x{first_diff:X})")
|
|
|
|
# Show context around first difference
|
|
start = max(0, first_diff - 50)
|
|
end = min(min_len, first_diff + 200)
|
|
|
|
print(f"\nOriginal file bytes [{start}:{end}]:")
|
|
print_hex_dump(data1[start:end], start)
|
|
|
|
print(f"\nAdded mapping file bytes [{start}:{end}]:")
|
|
print_hex_dump(data2[start:end], start)
|
|
|
|
else:
|
|
print("\nFiles are identical up to the shorter length")
|
|
if len(data1) != len(data2):
|
|
print(f"Extra data at end of {'file1' if len(data1) > len(data2) else 'file2'}")
|
|
|
|
# Find all difference regions
|
|
print("\n" + "=" * 80)
|
|
print("FINDING ALL DIFFERENCE REGIONS:")
|
|
print("=" * 80)
|
|
|
|
diff_regions = []
|
|
in_diff = False
|
|
diff_start = None
|
|
|
|
for i in range(min_len):
|
|
if data1[i] != data2[i]:
|
|
if not in_diff:
|
|
diff_start = i
|
|
in_diff = True
|
|
else:
|
|
if in_diff:
|
|
diff_regions.append((diff_start, i))
|
|
in_diff = False
|
|
|
|
if in_diff:
|
|
diff_regions.append((diff_start, min_len))
|
|
|
|
print(f"\nFound {len(diff_regions)} different regions:")
|
|
|
|
for idx, (start, end) in enumerate(diff_regions[:10], 1): # Show first 10
|
|
length = end - start
|
|
print(f"\nRegion {idx}: offset {start} (0x{start:X}), length {length} bytes")
|
|
|
|
# Show bytes
|
|
region_start = max(0, start - 20)
|
|
region_end = min(min_len, end + 20)
|
|
|
|
print(f" Original [{region_start}:{region_end}]:")
|
|
print_hex_dump(data1[region_start:region_end], region_start, highlight_start=start, highlight_end=end)
|
|
|
|
print(f" Added [{region_start}:{region_end}]:")
|
|
print_hex_dump(data2[region_start:region_end], region_start, highlight_start=start, highlight_end=end)
|
|
|
|
# Count "ules" markers in each file
|
|
print("\n" + "=" * 80)
|
|
print("ACTION MAPPING MARKERS:")
|
|
print("=" * 80)
|
|
|
|
ules_count1 = count_markers(data1, b'ules')
|
|
ules_count2 = count_markers(data2, b'ules')
|
|
|
|
print(f"'ules' markers in original: {ules_count1}")
|
|
print(f"'ules' markers in added: {ules_count2}")
|
|
print(f"Difference: {ules_count2 - ules_count1}")
|
|
|
|
# Find positions of ules markers
|
|
print("\nPositions of 'ules' markers:")
|
|
print("Original file:")
|
|
find_and_print_markers(data1, b'ules')
|
|
|
|
print("\nAdded mapping file:")
|
|
find_and_print_markers(data2, b'ules')
|
|
|
|
def print_hex_dump(data, offset=0, highlight_start=None, highlight_end=None):
|
|
"""Print hex dump of data with optional highlighting"""
|
|
for i in range(0, len(data), 16):
|
|
chunk = data[i:i+16]
|
|
addr = offset + i
|
|
|
|
# Hex part
|
|
hex_parts = []
|
|
for j, byte in enumerate(chunk):
|
|
byte_offset = addr + j
|
|
if highlight_start and highlight_end and highlight_start <= byte_offset < highlight_end:
|
|
hex_parts.append(f"\033[91m{byte:02X}\033[0m") # Red highlight
|
|
else:
|
|
hex_parts.append(f"{byte:02X}")
|
|
|
|
hex_str = " ".join(hex_parts)
|
|
|
|
# ASCII part
|
|
ascii_parts = []
|
|
for j, byte in enumerate(chunk):
|
|
byte_offset = addr + j
|
|
if 32 <= byte <= 126:
|
|
char = chr(byte)
|
|
else:
|
|
char = "."
|
|
|
|
if highlight_start and highlight_end and highlight_start <= byte_offset < highlight_end:
|
|
ascii_parts.append(f"\033[91m{char}\033[0m")
|
|
else:
|
|
ascii_parts.append(char)
|
|
|
|
ascii_str = "".join(ascii_parts)
|
|
|
|
print(f" {addr:06X}: {hex_str:<48} {ascii_str}")
|
|
|
|
def count_markers(data, marker):
|
|
"""Count occurrences of marker in data"""
|
|
count = 0
|
|
pos = 0
|
|
while True:
|
|
pos = data.find(marker, pos)
|
|
if pos == -1:
|
|
break
|
|
count += 1
|
|
pos += len(marker)
|
|
return count
|
|
|
|
def find_and_print_markers(data, marker):
|
|
"""Find and print all marker positions"""
|
|
positions = []
|
|
pos = 0
|
|
while True:
|
|
pos = data.find(marker, pos)
|
|
if pos == -1:
|
|
break
|
|
positions.append(pos)
|
|
pos += len(marker)
|
|
|
|
for i, pos in enumerate(positions[:20], 1): # Show first 20
|
|
# Check if it's preceded by 0x05 (marker byte)
|
|
if pos > 0 and data[pos-1] == 0x05:
|
|
print(f" {i:2d}. Offset {pos:6d} (0x{pos:05X}) - marker type 0x05")
|
|
else:
|
|
print(f" {i:2d}. Offset {pos:6d} (0x{pos:05X})")
|
|
|
|
if len(positions) > 20:
|
|
print(f" ... and {len(positions) - 20} more")
|
|
|
|
if __name__ == '__main__':
|
|
original = 'TestMKS_original.set'
|
|
added = 'TestMKS_added_mapping.set'
|
|
|
|
if not Path(original).exists():
|
|
print(f"Error: {original} not found")
|
|
sys.exit(1)
|
|
|
|
if not Path(added).exists():
|
|
print(f"Error: {added} not found")
|
|
sys.exit(1)
|
|
|
|
compare_binary_files(original, added)
|