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:
164
parse_all_actions.py
Normal file
164
parse_all_actions.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Parse all action documentation HTML files to extract complete action catalog
|
||||
"""
|
||||
import re
|
||||
import json
|
||||
from pathlib import Path
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Base path for action documentation
|
||||
ACTION_DOCS_PATH = Path(r"C:\DEV\COPILOT\SOURCES\CODEX\GeViScope\GeViScopeSDK_HTML5\Content\ActionDoku")
|
||||
|
||||
# Category mapping from filename to display name
|
||||
CATEGORY_MAPPING = {
|
||||
"ATMActions.htm": "ATM",
|
||||
"AudioActions.htm": "Audio",
|
||||
"BackupActions.htm": "Backup",
|
||||
"CameraControlActions.htm": "Camera Control",
|
||||
"CashManagementActions.htm": "Cash Management",
|
||||
"DeviceActions.htm": "Device",
|
||||
"DigitalContactsActions.htm": "Digital Contacts",
|
||||
"LogisticActions.htm": "Logistics",
|
||||
"LPSActions.htm": "License Plate System",
|
||||
"POSActions.htm": "Point of Sale",
|
||||
"RemoteExportActions.htm": "Remote Export",
|
||||
"SkiDataActions.htm": "Ski Data",
|
||||
"SystemActions.htm": "System",
|
||||
"VideoActions.htm": "Video",
|
||||
"ViewerActions.htm": "Viewer",
|
||||
"ViewerNotificationActions.htm": "Viewer Notification",
|
||||
"Lenelactions.htm": "Lenel Access Control"
|
||||
}
|
||||
|
||||
def extract_action_name(action_name_str):
|
||||
"""
|
||||
Extract action name and parameters from string like:
|
||||
'CustomAction(Int, String)' -> ('CustomAction', ['Int', 'String'])
|
||||
"""
|
||||
match = re.match(r'([A-Za-z0-9_]+)\((.*?)\)', action_name_str)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
params_str = match.group(2)
|
||||
if params_str.strip():
|
||||
params = [p.strip() for p in params_str.split(',')]
|
||||
else:
|
||||
params = []
|
||||
return name, params
|
||||
return action_name_str, []
|
||||
|
||||
def parse_action_html(html_file, category):
|
||||
"""Parse a single HTML file and extract all actions"""
|
||||
actions = {}
|
||||
|
||||
try:
|
||||
with open(html_file, 'r', encoding='utf-8') as f:
|
||||
soup = BeautifulSoup(f.read(), 'html.parser')
|
||||
|
||||
# Find all h2 headers (each action starts with h2)
|
||||
for h2 in soup.find_all('h2'):
|
||||
# Get the next p tag which should contain "Action name:"
|
||||
action_name_p = h2.find_next('p')
|
||||
if not action_name_p:
|
||||
continue
|
||||
|
||||
action_name_text = action_name_p.get_text()
|
||||
if not action_name_text.startswith('Action name:'):
|
||||
continue
|
||||
|
||||
# Extract action name
|
||||
action_full_name = action_name_text.replace('Action name:', '').strip()
|
||||
action_name, param_types = extract_action_name(action_full_name)
|
||||
|
||||
# Get action category (logical, notification, command)
|
||||
category_p = action_name_p.find_next('p')
|
||||
action_category = "unknown"
|
||||
if category_p and 'Action category:' in category_p.get_text():
|
||||
action_category = category_p.get_text().replace('Action category:', '').strip()
|
||||
|
||||
# Get description
|
||||
desc_p = category_p.find_next('p') if category_p else h2.find_next('p', class_=None)
|
||||
description = desc_p.get_text().strip() if desc_p else ""
|
||||
|
||||
# Find parameter table
|
||||
param_table = None
|
||||
current = h2.find_next_sibling()
|
||||
while current and current.name != 'h2':
|
||||
if current.name == 'table':
|
||||
param_table = current
|
||||
break
|
||||
current = current.find_next_sibling()
|
||||
|
||||
# Extract parameters from table
|
||||
parameters = []
|
||||
if param_table:
|
||||
rows = param_table.find_all('tr')
|
||||
for row in rows[1:]: # Skip header row
|
||||
cols = row.find_all('td')
|
||||
if len(cols) >= 2:
|
||||
param_name = cols[0].get_text().strip()
|
||||
param_type = cols[1].get_text().strip()
|
||||
if param_name and param_type:
|
||||
parameters.append(param_name)
|
||||
|
||||
# Store action info
|
||||
actions[action_name] = {
|
||||
"parameters": parameters,
|
||||
"description": description,
|
||||
"category": category,
|
||||
"action_category": action_category,
|
||||
"required_caption": True, # Default, can be refined
|
||||
"supports_delay": True # Default, can be refined
|
||||
}
|
||||
|
||||
print(f" Found: {action_name} ({len(parameters)} params)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing {html_file}: {e}")
|
||||
|
||||
return actions
|
||||
|
||||
def main():
|
||||
"""Parse all action HTML files and create comprehensive action catalog"""
|
||||
all_actions = {}
|
||||
|
||||
print("Parsing action documentation files...")
|
||||
print("=" * 60)
|
||||
|
||||
for filename, category_name in CATEGORY_MAPPING.items():
|
||||
html_file = ACTION_DOCS_PATH / filename
|
||||
|
||||
if not html_file.exists():
|
||||
print(f"⚠ Skipping {filename} (not found)")
|
||||
continue
|
||||
|
||||
print(f"\n{category_name} ({filename})")
|
||||
print("-" * 60)
|
||||
|
||||
actions = parse_action_html(html_file, category_name)
|
||||
all_actions.update(actions)
|
||||
|
||||
print(f" Total: {len(actions)} actions")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"TOTAL ACTIONS FOUND: {len(all_actions)}")
|
||||
print("=" * 60)
|
||||
|
||||
# Save to JSON
|
||||
output_file = Path(r"C:\DEV\COPILOT\all_actions_catalog.json")
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(all_actions, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\nSaved to: {output_file}")
|
||||
|
||||
# Print category breakdown
|
||||
print("\nActions by category:")
|
||||
category_counts = {}
|
||||
for action_name, action_info in all_actions.items():
|
||||
cat = action_info['category']
|
||||
category_counts[cat] = category_counts.get(cat, 0) + 1
|
||||
|
||||
for cat, count in sorted(category_counts.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f" {cat}: {count} actions")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user