feat: GeViScope SDK integration with C# Bridge and Flutter app

- Add GeViScope Bridge (C# .NET 8.0) on port 7720
  - Full SDK wrapper for camera control, PTZ, actions/events
  - 17 REST API endpoints for GeViScope server interaction
  - Support for MCS (Media Channel Simulator) with 16 test channels
  - Real-time action/event streaming via PLC callbacks

- Add GeViServer Bridge (C# .NET 8.0) on port 7710
  - Integration with GeViSoft orchestration layer
  - Input/output control and event management

- Update Python API with new routers
  - /api/geviscope/* - Proxy to GeViScope Bridge
  - /api/geviserver/* - Proxy to GeViServer Bridge
  - /api/excel/* - Excel import functionality

- Add Flutter app GeViScope integration
  - GeViScopeRemoteDataSource with 17 API methods
  - GeViScopeBloc for state management
  - GeViScopeScreen with PTZ controls
  - App drawer navigation to GeViScope

- Add SDK documentation (extracted from PDFs)
  - GeViScope SDK docs (7 parts + action reference)
  - GeViSoft SDK docs (12 chunks)

- Add .mcp.json for Claude Code MCP server config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Administrator
2026-01-19 08:14:17 +01:00
parent c9e83e4277
commit a92b909539
76 changed files with 62101 additions and 176 deletions

View File

@@ -0,0 +1,92 @@
# Rebuild Flutter Web App and Restart Server
$ErrorActionPreference = "Stop"
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Rebuilding Flutter Web App" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# Kill Flutter web server on port 8081
Write-Host "[1/3] Stopping Flutter web server..." -ForegroundColor Yellow
$flutterPid = Get-NetTCPConnection -LocalPort 8081 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty OwningProcess
if ($flutterPid) {
Stop-Process -Id $flutterPid -Force
Start-Sleep -Seconds 2
Write-Host " [OK] Flutter web server stopped" -ForegroundColor Green
} else {
Write-Host " [SKIP] No Flutter web server running" -ForegroundColor Gray
}
# Rebuild Flutter web app
Write-Host "[2/3] Rebuilding Flutter web app..." -ForegroundColor Yellow
cd C:\DEV\COPILOT\geutebruck_app
# Refresh PATH to find flutter
$env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
# Find flutter executable
$flutterExe = Get-Command flutter -ErrorAction SilentlyContinue
if (-not $flutterExe) {
Write-Host " [ERROR] Flutter not found in PATH!" -ForegroundColor Red
Write-Host " Looking for Flutter in common locations..." -ForegroundColor Yellow
# Common Flutter locations on Windows
$possiblePaths = @(
"$env:USERPROFILE\flutter\bin\flutter.bat",
"C:\flutter\bin\flutter.bat",
"C:\src\flutter\bin\flutter.bat"
)
foreach ($path in $possiblePaths) {
if (Test-Path $path) {
Write-Host " [FOUND] Flutter at: $path" -ForegroundColor Green
$flutterExe = $path
break
}
}
if (-not $flutterExe) {
Write-Host " [ERROR] Could not find Flutter! Please install Flutter or add it to PATH." -ForegroundColor Red
exit 1
}
}
Write-Host " Using Flutter: $($flutterExe.Source)" -ForegroundColor Cyan
# Run flutter build
& $flutterExe build web --release
if ($LASTEXITCODE -eq 0) {
Write-Host " [OK] Flutter web app built successfully" -ForegroundColor Green
} else {
Write-Host " [ERROR] Flutter build failed with exit code $LASTEXITCODE" -ForegroundColor Red
exit 1
}
# Start Flutter web server
Write-Host "[3/3] Starting Flutter web server..." -ForegroundColor Yellow
Start-Process -FilePath "python" `
-ArgumentList "-m", "http.server", "8081", "--bind", "0.0.0.0" `
-WorkingDirectory "C:\DEV\COPILOT\geutebruck_app\build\web" `
-WindowStyle Hidden
Start-Sleep -Seconds 3
$newPid = Get-NetTCPConnection -LocalPort 8081 -State Listen -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty OwningProcess
if ($newPid) {
Write-Host " [OK] Flutter web server started (PID: $newPid)" -ForegroundColor Green
} else {
Write-Host " [ERROR] Failed to start Flutter web server" -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host "Rebuild Complete!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "Flutter Web: http://localhost:8081" -ForegroundColor Cyan
Write-Host "Please refresh your browser (Ctrl+Shift+R) to see changes" -ForegroundColor Yellow
Write-Host ""

View File

@@ -56,3 +56,6 @@ structlog==24.1.0
# Date/Time
python-dateutil==2.8.2
# Excel Processing
openpyxl==3.1.5

View File

@@ -313,12 +313,16 @@ async def root():
}
# Register routers
from routers import auth, cameras, monitors, crossswitch, configuration
from routers import auth, cameras, monitors, crossswitch, configuration, excel_import, geviserver, geviscope
app.include_router(auth.router)
app.include_router(cameras.router)
app.include_router(monitors.router)
app.include_router(crossswitch.router)
app.include_router(configuration.router) # Includes action mappings & servers
app.include_router(excel_import.router)
app.include_router(geviserver.router, prefix="/api/v1")
app.include_router(geviscope.router, prefix="/api/v1")
if __name__ == "__main__":
import uvicorn

View File

@@ -0,0 +1,132 @@
from fastapi import APIRouter, UploadFile, File, HTTPException
from typing import List, Dict, Any
import openpyxl
from io import BytesIO
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/excel", tags=["Excel Import"])
@router.post("/import-servers")
async def import_servers_from_excel(file: UploadFile = File(...)) -> Dict[str, Any]:
"""
Import servers from an Excel file.
Expected Excel format:
- Row 2: Headers (Hostname, Typ, IP server, Username, Password)
- Row 3+: Data
- Column B: Hostname/Alias
- Column C: Type (GeViScope or G-Core)
- Column D: IP Server/Host
- Column E: Username
- Column F: Password
Returns:
List of parsed server objects
"""
try:
# Validate file type
if not file.filename.endswith('.xlsx'):
raise HTTPException(status_code=400, detail="File must be an Excel file (.xlsx)")
# Read file content
contents = await file.read()
logger.info(f"Received Excel file: {file.filename}, size: {len(contents)} bytes")
# Load workbook
try:
workbook = openpyxl.load_workbook(BytesIO(contents))
sheet = workbook.active
except Exception as e:
logger.error(f"Failed to load Excel file: {e}")
raise HTTPException(status_code=400, detail=f"Failed to parse Excel file: {str(e)}")
logger.info(f"Processing sheet: {sheet.title}, max row: {sheet.max_row}")
# Find header row (row with "Hostname")
header_row = None
for i in range(1, min(10, sheet.max_row + 1)): # Check first 10 rows
cell_value = sheet.cell(row=i, column=2).value # Column B
if cell_value and 'hostname' in str(cell_value).lower():
header_row = i
logger.info(f"Found header row at: {i}")
break
if not header_row:
raise HTTPException(
status_code=400,
detail="Could not find header row with 'Hostname' in column B"
)
# Parse server data
servers = []
success_count = 0
skip_count = 0
for row_idx in range(header_row + 1, sheet.max_row + 1):
try:
# Column B: Alias/Hostname
alias = sheet.cell(row=row_idx, column=2).value
# Column C: Type
type_str = sheet.cell(row=row_idx, column=3).value
# Column D: Host/IP
host = sheet.cell(row=row_idx, column=4).value
# Column E: Username
user = sheet.cell(row=row_idx, column=5).value
# Column F: Password
password = sheet.cell(row=row_idx, column=6).value
# Skip rows with empty alias or host
if not alias or not host:
skip_count += 1
continue
# Clean string values
alias = str(alias).strip()
host = str(host).strip()
user = str(user).strip() if user else 'sysadmin'
password = str(password).strip() if password else ''
# Determine server type
server_type = 'gcore'
if type_str and 'geviscope' in str(type_str).lower():
server_type = 'geviscope'
server = {
'alias': alias,
'host': host,
'user': user,
'password': password,
'type': server_type,
'enabled': True,
'deactivateEcho': False,
'deactivateLiveCheck': False,
}
servers.append(server)
success_count += 1
logger.info(f"Row {row_idx}: Imported {server_type} server '{alias}' @ {host}")
except Exception as e:
logger.warning(f"Row {row_idx}: Error parsing - {e}")
skip_count += 1
logger.info(f"Import complete: {success_count} imported, {skip_count} skipped")
return {
'success': True,
'total_imported': success_count,
'total_skipped': skip_count,
'servers': servers
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Unexpected error during Excel import: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")

View File

@@ -0,0 +1,621 @@
"""
GeViScope API Router
Provides REST API endpoints for interacting with GeViScope Camera Server SDK.
GeViScope is the DVR/camera recording system that handles video recording,
PTZ camera control, and media channel management.
"""
from fastapi import APIRouter, HTTPException, Depends, Query
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
import logging
from services.geviscope_service import get_geviscope_service, GeViScopeService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/geviscope", tags=["GeViScope"])
# ============================================================================
# Request/Response Models
# ============================================================================
class ConnectRequest(BaseModel):
"""Request model for connecting to GeViScope"""
address: str = Field(..., description="Server address (e.g., 'localhost' or IP)")
username: str = Field(..., description="Username for authentication")
password: str = Field(..., description="Password")
class Config:
json_schema_extra = {
"example": {
"address": "localhost",
"username": "sysadmin",
"password": "masterkey"
}
}
class ActionRequest(BaseModel):
"""Request model for sending generic action"""
action: str = Field(..., description="Action string in GeViScope format")
class Config:
json_schema_extra = {
"example": {
"action": "CustomAction(1,\"Hello\")"
}
}
class CustomActionRequest(BaseModel):
"""Request model for custom action"""
type_id: int = Field(..., description="Action type ID", ge=1)
text: str = Field("", description="Action text/parameters")
class Config:
json_schema_extra = {
"example": {
"type_id": 1,
"text": "Hello from API"
}
}
class CrossSwitchRequest(BaseModel):
"""Request model for video crossswitch"""
video_input: int = Field(..., description="Video input channel", ge=1)
video_output: int = Field(..., description="Video output channel", ge=1)
switch_mode: int = Field(0, description="Switch mode (0=normal)", ge=0)
class Config:
json_schema_extra = {
"example": {
"video_input": 1,
"video_output": 1,
"switch_mode": 0
}
}
class MediaChannelInfo(BaseModel):
"""Media channel information"""
channelID: int
globalNumber: int
name: str
description: str
isActive: bool
class StandardResponse(BaseModel):
"""Standard response model"""
success: bool
message: str
# ============================================================================
# Connection Management Endpoints
# ============================================================================
@router.post("/connect", response_model=Dict[str, Any])
async def connect_to_server(
request: ConnectRequest,
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Connect to GeViScope Camera Server
Establishes a connection to GeViScope DVR/camera server.
Default credentials are typically: sysadmin / masterkey
**Example Request:**
```json
{
"address": "localhost",
"username": "sysadmin",
"password": "masterkey"
}
```
**Success Response:**
```json
{
"success": true,
"message": "Connected to GeViScope",
"address": "localhost",
"username": "sysadmin",
"channelCount": 16
}
```
"""
logger.info(f"API: Connecting to GeViScope at {request.address}")
result = service.connect(
address=request.address,
username=request.username,
password=request.password
)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Connection failed"))
)
return result
@router.post("/disconnect", response_model=StandardResponse)
async def disconnect_from_server(
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Disconnect from GeViScope
Closes the current connection to GeViScope server.
"""
logger.info("API: Disconnecting from GeViScope")
result = service.disconnect()
return result
@router.get("/status", response_model=Dict[str, Any])
async def get_connection_status(
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Get connection status
Returns current GeViScope connection status and channel count.
**Response:**
```json
{
"is_connected": true,
"address": "localhost",
"username": "sysadmin",
"channel_count": 16
}
```
"""
return service.get_status()
# ============================================================================
# Media Channel Endpoints
# ============================================================================
@router.get("/channels", response_model=Dict[str, Any])
async def get_media_channels(
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Get media channels (cameras)
Returns list of active media channels (cameras) configured on the server.
**Response:**
```json
{
"count": 16,
"channels": [
{
"channelID": 1,
"globalNumber": 1,
"name": "Camera 1",
"description": "Front entrance",
"isActive": true
}
]
}
```
"""
result = service.get_channels()
if "error" in result:
raise HTTPException(status_code=400, detail=result.get("error"))
return result
@router.post("/channels/refresh", response_model=Dict[str, Any])
async def refresh_media_channels(
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Refresh media channel list
Re-queries the server for available media channels.
"""
return service.refresh_channels()
# ============================================================================
# Action Endpoints
# ============================================================================
@router.post("/action", response_model=Dict[str, Any])
async def send_action(
request: ActionRequest,
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Send generic action
Sends any action string to GeViScope server.
**Example Actions:**
- `CustomAction(1,"Hello")` - Send custom action
- `CrossSwitch(1,2,0)` - Route video
- `CameraStopAll(1)` - Stop camera movement
- `CameraGotoPreset(1,5)` - Go to preset position
**Request:**
```json
{
"action": "CustomAction(1,\"Test message\")"
}
```
"""
logger.info(f"API: Sending action: {request.action}")
result = service.send_action(request.action)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Action failed"))
)
return result
@router.post("/custom-action", response_model=Dict[str, Any])
async def send_custom_action(
type_id: int = Query(..., description="Action type ID", ge=1),
text: str = Query("", description="Action text/parameters"),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Send custom action
Sends a CustomAction message to GeViScope.
**Parameters:**
- `type_id`: Action type identifier
- `text`: Optional text parameter
**Example:**
```
POST /geviscope/custom-action?type_id=1&text=Hello
```
"""
logger.info(f"API: Sending CustomAction({type_id}, \"{text}\")")
result = service.send_custom_action(type_id, text)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "CustomAction failed"))
)
return result
# ============================================================================
# Video Control Endpoints
# ============================================================================
@router.post("/video/crossswitch", response_model=Dict[str, Any])
async def crossswitch_video(
video_input: int = Query(..., description="Video input channel", ge=1),
video_output: int = Query(..., description="Video output channel", ge=1),
switch_mode: int = Query(0, description="Switch mode (0=normal)", ge=0),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
CrossSwitch - Route video input to output
Routes a video input channel to a video output channel for display.
**Parameters:**
- `video_input`: Source camera/channel number
- `video_output`: Destination monitor/output number
- `switch_mode`: 0 = normal switch
**Example:**
```
POST /geviscope/video/crossswitch?video_input=1&video_output=2&switch_mode=0
```
"""
logger.info(f"API: CrossSwitch({video_input}, {video_output}, {switch_mode})")
result = service.crossswitch(video_input, video_output, switch_mode)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "CrossSwitch failed"))
)
return result
# ============================================================================
# PTZ Camera Control Endpoints
# ============================================================================
@router.post("/camera/pan", response_model=Dict[str, Any])
async def camera_pan(
camera: int = Query(..., description="Camera/PTZ head number", ge=1),
direction: str = Query(..., description="Direction: 'left' or 'right'"),
speed: int = Query(50, description="Pan speed (1-100)", ge=1, le=100),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Pan camera left or right
Sends PTZ pan command to the specified camera.
**Parameters:**
- `camera`: Camera/PTZ head number
- `direction`: 'left' or 'right'
- `speed`: Movement speed (1-100, default 50)
**Example:**
```
POST /geviscope/camera/pan?camera=1&direction=left&speed=50
```
"""
if direction.lower() not in ["left", "right"]:
raise HTTPException(status_code=400, detail="Direction must be 'left' or 'right'")
logger.info(f"API: Camera pan {direction} (camera={camera}, speed={speed})")
result = service.camera_pan(camera, direction, speed)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Pan failed"))
)
return result
@router.post("/camera/tilt", response_model=Dict[str, Any])
async def camera_tilt(
camera: int = Query(..., description="Camera/PTZ head number", ge=1),
direction: str = Query(..., description="Direction: 'up' or 'down'"),
speed: int = Query(50, description="Tilt speed (1-100)", ge=1, le=100),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Tilt camera up or down
Sends PTZ tilt command to the specified camera.
**Parameters:**
- `camera`: Camera/PTZ head number
- `direction`: 'up' or 'down'
- `speed`: Movement speed (1-100, default 50)
**Example:**
```
POST /geviscope/camera/tilt?camera=1&direction=up&speed=50
```
"""
if direction.lower() not in ["up", "down"]:
raise HTTPException(status_code=400, detail="Direction must be 'up' or 'down'")
logger.info(f"API: Camera tilt {direction} (camera={camera}, speed={speed})")
result = service.camera_tilt(camera, direction, speed)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Tilt failed"))
)
return result
@router.post("/camera/zoom", response_model=Dict[str, Any])
async def camera_zoom(
camera: int = Query(..., description="Camera/PTZ head number", ge=1),
direction: str = Query(..., description="Direction: 'in' or 'out'"),
speed: int = Query(50, description="Zoom speed (1-100)", ge=1, le=100),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Zoom camera in or out
Sends PTZ zoom command to the specified camera.
**Parameters:**
- `camera`: Camera/PTZ head number
- `direction`: 'in' or 'out'
- `speed`: Movement speed (1-100, default 50)
**Example:**
```
POST /geviscope/camera/zoom?camera=1&direction=in&speed=30
```
"""
if direction.lower() not in ["in", "out"]:
raise HTTPException(status_code=400, detail="Direction must be 'in' or 'out'")
logger.info(f"API: Camera zoom {direction} (camera={camera}, speed={speed})")
result = service.camera_zoom(camera, direction, speed)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Zoom failed"))
)
return result
@router.post("/camera/stop", response_model=Dict[str, Any])
async def camera_stop(
camera: int = Query(..., description="Camera/PTZ head number", ge=1),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Stop all camera movement
Stops all PTZ movement on the specified camera.
**Parameters:**
- `camera`: Camera/PTZ head number
**Example:**
```
POST /geviscope/camera/stop?camera=1
```
"""
logger.info(f"API: Camera stop (camera={camera})")
result = service.camera_stop(camera)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Stop failed"))
)
return result
@router.post("/camera/preset", response_model=Dict[str, Any])
async def camera_goto_preset(
camera: int = Query(..., description="Camera/PTZ head number", ge=1),
preset: int = Query(..., description="Preset position number", ge=1),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Go to camera preset position
Moves camera to a pre-configured preset position.
**Parameters:**
- `camera`: Camera/PTZ head number
- `preset`: Preset position number (configured in GeViScope)
**Example:**
```
POST /geviscope/camera/preset?camera=1&preset=5
```
"""
logger.info(f"API: Camera goto preset {preset} (camera={camera})")
result = service.camera_preset(camera, preset)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Preset failed"))
)
return result
# ============================================================================
# Digital I/O Endpoints
# ============================================================================
@router.post("/digital-io/close", response_model=Dict[str, Any])
async def close_digital_output(
contact_id: int = Query(..., description="Digital contact ID", ge=1),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Close digital output contact
Closes (activates) a digital output relay.
**Parameters:**
- `contact_id`: Digital output contact ID
**Example:**
```
POST /geviscope/digital-io/close?contact_id=1
```
"""
logger.info(f"API: Close digital output {contact_id}")
result = service.digital_io_close(contact_id)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Close failed"))
)
return result
@router.post("/digital-io/open", response_model=Dict[str, Any])
async def open_digital_output(
contact_id: int = Query(..., description="Digital contact ID", ge=1),
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Open digital output contact
Opens (deactivates) a digital output relay.
**Parameters:**
- `contact_id`: Digital output contact ID
**Example:**
```
POST /geviscope/digital-io/open?contact_id=1
```
"""
logger.info(f"API: Open digital output {contact_id}")
result = service.digital_io_open(contact_id)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", result.get("error", "Open failed"))
)
return result
# ============================================================================
# Message Log Endpoints
# ============================================================================
@router.get("/messages", response_model=Dict[str, Any])
async def get_message_log(
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Get received message log
Returns recent action/event messages received from GeViScope.
**Response:**
```json
{
"count": 5,
"messages": [
"[12:30:45] CustomAction(1, \"Test\")",
"[12:30:50] DigitalInput(GlobalNo=1)"
]
}
```
"""
return service.get_messages()
@router.post("/messages/clear", response_model=Dict[str, Any])
async def clear_message_log(
service: GeViScopeService = Depends(get_geviscope_service)
) -> Dict[str, Any]:
"""
Clear message log
Clears all messages from the log.
"""
return service.clear_messages()

View File

@@ -0,0 +1,626 @@
"""
GeViServer API Router
Provides REST API endpoints for interacting with GeViServer through GeViProcAPI.dll
"""
from fastapi import APIRouter, HTTPException, Depends, Query
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
import logging
from services.geviserver_service import get_geviserver_service, GeViServerService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/geviserver", tags=["GeViServer"])
# ============================================================================
# Request/Response Models
# ============================================================================
class ConnectRequest(BaseModel):
"""Request model for connecting to GeViServer"""
address: str = Field(..., description="Server address (e.g., 'localhost' or IP)")
username: str = Field(..., description="Username for authentication")
password: str = Field(..., description="Password (will be encrypted)")
username2: Optional[str] = Field(None, description="Optional second username")
password2: Optional[str] = Field(None, description="Optional second password")
class Config:
json_schema_extra = {
"example": {
"address": "localhost",
"username": "admin",
"password": "admin"
}
}
class SendMessageRequest(BaseModel):
"""Request model for sending action messages"""
message: str = Field(..., description="Action message in ASCII format")
class Config:
json_schema_extra = {
"example": {
"message": "CrossSwitch(7,3,0)"
}
}
class ConnectionStatusResponse(BaseModel):
"""Response model for connection status"""
is_connected: bool
address: Optional[str] = None
username: Optional[str] = None
connected_at: Optional[str] = None
class StandardResponse(BaseModel):
"""Standard response model"""
success: bool
message: str
# ============================================================================
# Connection Management Endpoints
# ============================================================================
@router.post("/connect", response_model=Dict[str, Any])
async def connect_to_server(
request: ConnectRequest,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Connect to GeViServer
This endpoint establishes a connection to GeViServer using the provided credentials.
The password will be encrypted before sending.
**Example Request:**
```json
{
"address": "localhost",
"username": "admin",
"password": "admin"
}
```
**Success Response:**
```json
{
"success": true,
"message": "Connected to GeViServer",
"address": "localhost",
"username": "admin",
"connected_at": "2026-01-12T10:30:00"
}
```
**Error Response:**
```json
{
"success": false,
"message": "Connection failed: connectRemoteUnknownUser",
"error": "Invalid username or password"
}
```
"""
logger.info(f"API: Connecting to GeViServer at {request.address}")
result = service.connect(
address=request.address,
username=request.username,
password=request.password,
username2=request.username2,
password2=request.password2
)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "Connection failed")
)
return result
@router.post("/disconnect", response_model=StandardResponse)
async def disconnect_from_server(
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Disconnect from GeViServer
Closes the current connection to GeViServer and frees resources.
**Success Response:**
```json
{
"success": true,
"message": "Disconnected successfully"
}
```
"""
logger.info("API: Disconnecting from GeViServer")
result = service.disconnect()
return result
@router.get("/status", response_model=Dict[str, Any])
async def get_connection_status(
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Get current connection status
Returns information about the current GeViServer connection.
**Response:**
```json
{
"is_connected": true,
"address": "localhost",
"username": "admin"
}
```
"""
try:
result = service.get_status()
return result
except Exception as e:
logger.error(f"Failed to get status: {e}")
raise HTTPException(status_code=500, detail={
"error": "Internal Server Error",
"message": str(e)
})
@router.post("/ping", response_model=StandardResponse)
async def send_ping(
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Send ping to GeViServer
Tests the connection to GeViServer by sending a ping.
**Success Response:**
```json
{
"success": true,
"message": "Ping successful"
}
```
**Error Response:**
```json
{
"success": false,
"message": "Ping failed: Connection lost"
}
```
"""
result = service.send_ping()
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "Ping failed")
)
return result
# ============================================================================
# Message Sending Endpoints
# ============================================================================
@router.post("/send-message", response_model=Dict[str, Any])
async def send_message(
request: SendMessageRequest,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Send action message to GeViServer
Sends a generic action message to GeViServer. The message should be in ASCII format
following the GeViSoft action syntax.
**Example Request:**
```json
{
"message": "CrossSwitch(7,3,0)"
}
```
**Common Actions:**
- `CrossSwitch(input, output, mode)` - Route video
- `ClearOutput(output)` - Clear video output
- `CloseContact(contactID)` - Close digital output
- `OpenContact(contactID)` - Open digital output
- `StartTimer(timerID, name)` - Start timer
- `StopTimer(timerID, name)` - Stop timer
**Success Response:**
```json
{
"success": true,
"message": "Message sent successfully",
"sent_message": "CrossSwitch(7,3,0)"
}
```
"""
logger.info(f"API: Sending message: {request.message}")
result = service.send_message(request.message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "Send failed")
)
return result
# ============================================================================
# Video Control Endpoints
# ============================================================================
@router.post("/video/crossswitch")
async def crossswitch_video(
video_input: int = Query(..., description="Video input channel number", ge=1),
video_output: int = Query(..., description="Video output channel number", ge=1),
switch_mode: int = Query(0, description="Switch mode (0=normal)", ge=0),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Cross-switch video input to output
Routes a video input channel to a video output channel.
**Parameters:**
- `video_input`: Video input channel number (e.g., 7)
- `video_output`: Video output channel number (e.g., 3)
- `switch_mode`: Switch mode (default: 0 for normal switching)
**Example:**
```
POST /api/v1/geviserver/video/crossswitch?video_input=7&video_output=3&switch_mode=0
```
**Success Response:**
```json
{
"success": true,
"message": "Routed video input 7 to output 3",
"video_input": 7,
"video_output": 3,
"switch_mode": 0
}
```
"""
message = f"CrossSwitch({video_input},{video_output},{switch_mode})"
logger.info(f"API: Cross-switching video: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "CrossSwitch failed")
)
return {
"success": True,
"message": f"Routed video input {video_input} to output {video_output}",
"video_input": video_input,
"video_output": video_output,
"switch_mode": switch_mode
}
@router.post("/video/clear-output")
async def clear_video_output(
video_output: int = Query(..., description="Video output channel number", ge=1),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Clear video output
Clears the specified video output channel (stops displaying video).
**Parameters:**
- `video_output`: Video output channel number to clear
**Example:**
```
POST /api/v1/geviserver/video/clear-output?video_output=3
```
**Success Response:**
```json
{
"success": true,
"message": "Cleared video output 3",
"video_output": 3
}
```
"""
message = f"ClearOutput({video_output})"
logger.info(f"API: Clearing output: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "ClearOutput failed")
)
return {
"success": True,
"message": f"Cleared video output {video_output}",
"video_output": video_output
}
# ============================================================================
# Digital I/O Endpoints
# ============================================================================
@router.post("/digital-io/close-contact")
async def close_digital_contact(
contact_id: int = Query(..., description="Digital contact ID", ge=1),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Close digital output contact
Closes (activates) a digital output contact.
**Parameters:**
- `contact_id`: Digital contact ID to close
**Example:**
```
POST /api/v1/geviserver/digital-io/close-contact?contact_id=1
```
**Success Response:**
```json
{
"success": true,
"message": "Closed digital contact 1",
"contact_id": 1
}
```
"""
message = f"CloseContact({contact_id})"
logger.info(f"API: Closing contact: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "CloseContact failed")
)
return {
"success": True,
"message": f"Closed digital contact {contact_id}",
"contact_id": contact_id
}
@router.post("/digital-io/open-contact")
async def open_digital_contact(
contact_id: int = Query(..., description="Digital contact ID", ge=1),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Open digital output contact
Opens (deactivates) a digital output contact.
**Parameters:**
- `contact_id`: Digital contact ID to open
**Example:**
```
POST /api/v1/geviserver/digital-io/open-contact?contact_id=1
```
**Success Response:**
```json
{
"success": true,
"message": "Opened digital contact 1",
"contact_id": 1
}
```
"""
message = f"OpenContact({contact_id})"
logger.info(f"API: Opening contact: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "OpenContact failed")
)
return {
"success": True,
"message": f"Opened digital contact {contact_id}",
"contact_id": contact_id
}
# ============================================================================
# Timer Control Endpoints
# ============================================================================
@router.post("/timer/start")
async def start_timer(
timer_id: int = Query(0, description="Timer ID (0 to use name)", ge=0),
timer_name: str = Query("", description="Timer name"),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Start a timer
Starts a configured timer in GeViServer.
**Parameters:**
- `timer_id`: Timer ID (use 0 if addressing by name)
- `timer_name`: Timer name (if addressing by name)
**Example:**
```
POST /api/v1/geviserver/timer/start?timer_id=1&timer_name=BeaconTimer
```
**Success Response:**
```json
{
"success": true,
"message": "Started timer",
"timer_id": 1,
"timer_name": "BeaconTimer"
}
```
"""
message = f'StartTimer({timer_id},"{timer_name}")'
logger.info(f"API: Starting timer: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "StartTimer failed")
)
return {
"success": True,
"message": "Started timer",
"timer_id": timer_id,
"timer_name": timer_name
}
@router.post("/timer/stop")
async def stop_timer(
timer_id: int = Query(0, description="Timer ID (0 to use name)", ge=0),
timer_name: str = Query("", description="Timer name"),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Stop a timer
Stops a running timer in GeViServer.
**Parameters:**
- `timer_id`: Timer ID (use 0 if addressing by name)
- `timer_name`: Timer name (if addressing by name)
**Example:**
```
POST /api/v1/geviserver/timer/stop?timer_id=1&timer_name=BeaconTimer
```
**Success Response:**
```json
{
"success": true,
"message": "Stopped timer",
"timer_id": 1,
"timer_name": "BeaconTimer"
}
```
"""
message = f'StopTimer({timer_id},"{timer_name}")'
logger.info(f"API: Stopping timer: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "StopTimer failed")
)
return {
"success": True,
"message": "Stopped timer",
"timer_id": timer_id,
"timer_name": timer_name
}
# ============================================================================
# Custom Action Endpoints
# ============================================================================
@router.post("/custom-action")
async def send_custom_action(
type_id: int = Query(..., description="Action type ID", ge=1),
text: str = Query("", description="Action text/parameters"),
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Send custom action
Sends a custom action message to GeViServer.
**Parameters:**
- `type_id`: Action type ID
- `text`: Action text or parameters
**Example:**
```
POST /api/v1/geviserver/custom-action?type_id=123&text=HelloGeViSoft
```
**Success Response:**
```json
{
"success": true,
"message": "Custom action sent",
"type_id": 123,
"text": "HelloGeViSoft"
}
```
"""
message = f'CustomAction({type_id},"{text}")'
logger.info(f"API: Sending custom action: {message}")
result = service.send_message(message)
if not result.get("success"):
raise HTTPException(
status_code=400,
detail=result.get("message", "CustomAction failed")
)
return {
"success": True,
"message": "Custom action sent",
"type_id": type_id,
"text": text
}

View File

@@ -0,0 +1,162 @@
"""
GeViScope Service
Provides communication with the GeViScope Bridge (C# service on port 7720)
for camera server SDK functionality.
"""
import requests
import logging
from typing import Dict, Any, Optional
logger = logging.getLogger(__name__)
# GeViScope Bridge URL (C# service)
GEVISCOPE_BRIDGE_URL = "http://localhost:7720"
class GeViScopeService:
"""Service for communicating with GeViScope through the C# Bridge"""
def __init__(self, bridge_url: str = GEVISCOPE_BRIDGE_URL):
self.bridge_url = bridge_url
self.timeout = 30
def _make_request(self, method: str, endpoint: str, json_data: Dict = None) -> Dict[str, Any]:
"""Make HTTP request to GeViScope Bridge"""
url = f"{self.bridge_url}{endpoint}"
try:
if method.upper() == "GET":
response = requests.get(url, timeout=self.timeout)
elif method.upper() == "POST":
response = requests.post(url, json=json_data, timeout=self.timeout)
else:
return {"success": False, "error": f"Unsupported method: {method}"}
return response.json()
except requests.exceptions.ConnectionError:
logger.error(f"GeViScope Bridge not available at {self.bridge_url}")
return {
"success": False,
"error": "GeViScope Bridge not available",
"message": "The GeViScope Bridge service is not running. Start it with start-services.ps1"
}
except requests.exceptions.Timeout:
logger.error(f"GeViScope Bridge request timed out")
return {"success": False, "error": "Request timed out"}
except Exception as e:
logger.error(f"GeViScope Bridge request failed: {e}")
return {"success": False, "error": str(e)}
# Connection Management
def connect(self, address: str, username: str, password: str) -> Dict[str, Any]:
"""Connect to GeViScope server"""
return self._make_request("POST", "/connect", {
"Address": address,
"Username": username,
"Password": password
})
def disconnect(self) -> Dict[str, Any]:
"""Disconnect from GeViScope server"""
return self._make_request("POST", "/disconnect")
def get_status(self) -> Dict[str, Any]:
"""Get connection status"""
return self._make_request("GET", "/status")
# Media Channels
def get_channels(self) -> Dict[str, Any]:
"""Get list of media channels (cameras)"""
return self._make_request("GET", "/channels")
def refresh_channels(self) -> Dict[str, Any]:
"""Refresh media channel list"""
return self._make_request("POST", "/channels/refresh")
# Actions
def send_action(self, action: str) -> Dict[str, Any]:
"""Send generic action"""
return self._make_request("POST", "/action", {"Action": action})
def send_custom_action(self, type_id: int, text: str = "") -> Dict[str, Any]:
"""Send custom action"""
return self._make_request("POST", "/custom-action", {
"TypeId": type_id,
"Text": text
})
# Video Control
def crossswitch(self, video_input: int, video_output: int, switch_mode: int = 0) -> Dict[str, Any]:
"""Route video input to output"""
return self._make_request("POST", "/crossswitch", {
"VideoInput": video_input,
"VideoOutput": video_output,
"SwitchMode": switch_mode
})
# PTZ Camera Control
def camera_pan(self, camera: int, direction: str, speed: int = 50) -> Dict[str, Any]:
"""Pan camera left or right"""
return self._make_request("POST", "/camera/pan", {
"Camera": camera,
"Direction": direction,
"Speed": speed
})
def camera_tilt(self, camera: int, direction: str, speed: int = 50) -> Dict[str, Any]:
"""Tilt camera up or down"""
return self._make_request("POST", "/camera/tilt", {
"Camera": camera,
"Direction": direction,
"Speed": speed
})
def camera_zoom(self, camera: int, direction: str, speed: int = 50) -> Dict[str, Any]:
"""Zoom camera in or out"""
return self._make_request("POST", "/camera/zoom", {
"Camera": camera,
"Direction": direction,
"Speed": speed
})
def camera_stop(self, camera: int) -> Dict[str, Any]:
"""Stop all camera movement"""
return self._make_request("POST", "/camera/stop", {"Camera": camera})
def camera_preset(self, camera: int, preset: int) -> Dict[str, Any]:
"""Go to camera preset position"""
return self._make_request("POST", "/camera/preset", {
"Camera": camera,
"Preset": preset
})
# Digital I/O
def digital_io_close(self, contact_id: int) -> Dict[str, Any]:
"""Close digital output"""
return self._make_request("POST", "/digital-io/close", {"ContactId": contact_id})
def digital_io_open(self, contact_id: int) -> Dict[str, Any]:
"""Open digital output"""
return self._make_request("POST", "/digital-io/open", {"ContactId": contact_id})
# Message Log
def get_messages(self) -> Dict[str, Any]:
"""Get received message log"""
return self._make_request("GET", "/messages")
def clear_messages(self) -> Dict[str, Any]:
"""Clear message log"""
return self._make_request("POST", "/messages/clear")
# Singleton instance
_geviscope_service: Optional[GeViScopeService] = None
def get_geviscope_service() -> GeViScopeService:
"""Get or create GeViScope service singleton"""
global _geviscope_service
if _geviscope_service is None:
_geviscope_service = GeViScopeService()
return _geviscope_service

View File

@@ -0,0 +1,230 @@
"""
GeViServer Service - Python wrapper for GeViServer C# Bridge
This service proxies requests to the C# bridge service that handles
the 32-bit GeViProcAPI.dll communication with GeViServer.
"""
import requests
import logging
from typing import Optional, Dict, Any
from dataclasses import dataclass
from datetime import datetime
logger = logging.getLogger(__name__)
@dataclass
class GeViServerConnectionInfo:
"""Information about current connection"""
address: str
username: str
is_connected: bool
connected_at: Optional[datetime] = None
class GeViServerService:
"""
Service to interact with GeViServer via C# Bridge
This service communicates with a C# bridge service running on localhost:7710
which handles the 32-bit GeViProcAPI.dll operations.
"""
def __init__(self, bridge_url: str = "http://localhost:7710"):
"""
Initialize the GeViServer service
Args:
bridge_url: URL of the C# bridge service
"""
self.bridge_url = bridge_url
self.connection_info: Optional[GeViServerConnectionInfo] = None
self._check_bridge_availability()
def _check_bridge_availability(self):
"""Check if C# bridge is available"""
try:
response = requests.get(f"{self.bridge_url}/status", timeout=2)
logger.info(f"C# Bridge is available at {self.bridge_url}")
except requests.exceptions.RequestException as e:
logger.warning(f"C# Bridge not available at {self.bridge_url}: {e}")
logger.warning("GeViServer operations will fail until bridge is started")
def connect(
self,
address: str,
username: str,
password: str,
username2: Optional[str] = None,
password2: Optional[str] = None
) -> Dict[str, Any]:
"""
Connect to GeViServer
Args:
address: Server address (e.g., 'localhost' or IP address)
username: Username for authentication
password: Password (will be encrypted by bridge)
username2: Optional second username
password2: Optional second password
Returns:
Dict with connection result
"""
try:
logger.info(f"Connecting to GeViServer at {address} via C# bridge")
payload = {
"address": address,
"username": username,
"password": password
}
response = requests.post(
f"{self.bridge_url}/connect",
json=payload,
timeout=30
)
if response.status_code == 200:
result = response.json()
self.connection_info = GeViServerConnectionInfo(
address=address,
username=username,
is_connected=True,
connected_at=datetime.utcnow()
)
logger.info(f"Successfully connected to GeViServer at {address}")
return result
else:
error_data = response.json()
logger.error(f"Connection failed: {error_data}")
raise Exception(f"Connection failed: {error_data.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to connect to C# bridge: {e}")
raise Exception(f"C# Bridge communication error: {str(e)}")
def disconnect(self) -> Dict[str, Any]:
"""
Disconnect from GeViServer
Returns:
Dict with disconnection result
"""
try:
logger.info("Disconnecting from GeViServer via C# bridge")
response = requests.post(
f"{self.bridge_url}/disconnect",
timeout=10
)
if response.status_code == 200:
self.connection_info = None
logger.info("Successfully disconnected from GeViServer")
return response.json()
else:
error_data = response.json()
logger.error(f"Disconnection failed: {error_data}")
raise Exception(f"Disconnection failed: {error_data.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to disconnect via C# bridge: {e}")
raise Exception(f"C# Bridge communication error: {str(e)}")
def get_status(self) -> Dict[str, Any]:
"""
Get connection status
Returns:
Dict with connection status
"""
try:
response = requests.get(
f"{self.bridge_url}/status",
timeout=5
)
if response.status_code == 200:
return response.json()
else:
raise Exception("Failed to get status")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to get status from C# bridge: {e}")
raise Exception(f"C# Bridge communication error: {str(e)}")
def send_ping(self) -> Dict[str, Any]:
"""
Send ping to GeViServer
Returns:
Dict with ping result
"""
try:
logger.debug("Sending ping to GeViServer via C# bridge")
response = requests.post(
f"{self.bridge_url}/ping",
timeout=10
)
if response.status_code == 200:
return response.json()
else:
error_data = response.json()
raise Exception(f"Ping failed: {error_data.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to ping via C# bridge: {e}")
raise Exception(f"C# Bridge communication error: {str(e)}")
def send_message(self, message: str) -> Dict[str, Any]:
"""
Send action message to GeViServer
Args:
message: ASCII action message (e.g., "CrossSwitch(7,3,0)")
Returns:
Dict with send result
"""
try:
logger.info(f"Sending message to GeViServer: {message}")
response = requests.post(
f"{self.bridge_url}/send-message",
json={"message": message},
timeout=30
)
if response.status_code == 200:
logger.info(f"Message sent successfully: {message}")
return response.json()
else:
error_data = response.json()
logger.error(f"Failed to send message: {error_data}")
raise Exception(f"Send message failed: {error_data.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
logger.error(f"Failed to send message via C# bridge: {e}")
raise Exception(f"C# Bridge communication error: {str(e)}")
# Singleton instance
_service_instance: Optional[GeViServerService] = None
def get_geviserver_service() -> GeViServerService:
"""
Get singleton GeViServerService instance
Returns:
GeViServerService instance
"""
global _service_instance
if _service_instance is None:
_service_instance = GeViServerService()
return _service_instance

View File

@@ -1,5 +1,5 @@
# Start Geutebruck API Services
# This script starts GeViServer, SDK Bridge, Python API, and Flutter Web App
# This script starts GeViServer, C# Bridge, GeViScope Bridge, SDK Bridge, Python API, and Flutter Web App
$ErrorActionPreference = "Stop"
@@ -10,6 +10,10 @@ Write-Host ""
# Paths
$geviServerExe = "C:\GEVISOFT\GeViServer.exe"
$geviServerBridgePath = "C:\DEV\COPILOT\geviserver-bridge\GeViServerBridge\bin\Debug\net8.0"
$geviServerBridgeExe = "$geviServerBridgePath\GeViServerBridge.exe"
$geviScopeBridgePath = "C:\DEV\COPILOT\geviscope-bridge\GeViScopeBridge\bin\Debug\net8.0\win-x86"
$geviScopeBridgeExe = "$geviScopeBridgePath\GeViScopeBridge.exe"
$sdkBridgePath = "C:\DEV\COPILOT\geutebruck-api\src\sdk-bridge\GeViScopeBridge\bin\Release\net8.0"
$sdkBridgeExe = "$sdkBridgePath\GeViScopeBridge.exe"
$apiPath = "C:\DEV\COPILOT\geutebruck-api\src\api"
@@ -39,6 +43,8 @@ function Wait-ForPort {
# Check if already running
$geviServerRunning = Get-Process -Name "GeViServer" -ErrorAction SilentlyContinue
$geviServerBridgeRunning = Get-Process -Name "GeViServerBridge" -ErrorAction SilentlyContinue
$geviScopeBridgeRunning = Get-NetTCPConnection -LocalPort 7720 -State Listen -ErrorAction SilentlyContinue
$sdkBridgeRunning = Get-Process -Name "GeViScopeBridge" -ErrorAction SilentlyContinue
$uvicornRunning = Get-Process -Name "uvicorn" -ErrorAction SilentlyContinue
$flutterRunning = Get-NetTCPConnection -LocalPort 8081 -State Listen -ErrorAction SilentlyContinue
@@ -47,7 +53,7 @@ $flutterRunning = Get-NetTCPConnection -LocalPort 8081 -State Listen -ErrorActio
if ($geviServerRunning) {
Write-Host "[SKIP] GeViServer is already running (PID: $($geviServerRunning.Id))" -ForegroundColor Yellow
} else {
Write-Host "[1/4] Starting GeViServer..." -ForegroundColor Green
Write-Host "[1/6] Starting GeViServer..." -ForegroundColor Green
Start-Process -FilePath $geviServerExe -ArgumentList "console" -WorkingDirectory "C:\GEVISOFT" -WindowStyle Hidden
# Wait for GeViServer to start listening on port 7700
@@ -63,23 +69,71 @@ if ($geviServerRunning) {
}
}
# Start SDK Bridge
# Start C# GeViServer Bridge (handles 32-bit DLL communication)
if ($geviServerBridgeRunning) {
Write-Host "[SKIP] C# GeViServer Bridge is already running (PID: $($geviServerBridgeRunning.Id))" -ForegroundColor Yellow
} else {
Write-Host "[2/6] Starting C# GeViServer Bridge..." -ForegroundColor Green
Start-Process -FilePath $geviServerBridgeExe `
-ArgumentList "--urls", "http://localhost:7710" `
-WorkingDirectory $geviServerBridgePath `
-WindowStyle Hidden
# Wait for C# Bridge to start listening on port 7710
Write-Host " Waiting for C# Bridge to initialize" -NoNewline -ForegroundColor Gray
if (Wait-ForPort -Port 7710 -TimeoutSeconds 20) {
Write-Host ""
$process = Get-Process -Name "GeViServerBridge" -ErrorAction SilentlyContinue
Write-Host " [OK] C# Bridge started (PID: $($process.Id))" -ForegroundColor Green
} else {
Write-Host ""
Write-Host " [ERROR] C# Bridge failed to start listening on port 7710" -ForegroundColor Red
exit 1
}
}
# Start GeViScope Bridge (camera server SDK)
if ($geviScopeBridgeRunning) {
$geviScopeProcess = Get-NetTCPConnection -LocalPort 7720 -State Listen -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty OwningProcess
Write-Host "[SKIP] GeViScope Bridge is already running (PID: $geviScopeProcess)" -ForegroundColor Yellow
} else {
Write-Host "[3/6] Starting GeViScope Bridge..." -ForegroundColor Green
Start-Process -FilePath $geviScopeBridgeExe -WorkingDirectory $geviScopeBridgePath -WindowStyle Hidden
# Wait for GeViScope Bridge to start listening on port 7720
Write-Host " Waiting for GeViScope Bridge to initialize" -NoNewline -ForegroundColor Gray
if (Wait-ForPort -Port 7720 -TimeoutSeconds 20) {
Write-Host ""
$geviScopeProcess = Get-NetTCPConnection -LocalPort 7720 -State Listen -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty OwningProcess
Write-Host " [OK] GeViScope Bridge started (PID: $geviScopeProcess)" -ForegroundColor Green
} else {
Write-Host ""
Write-Host " [WARN] GeViScope Bridge failed to start on port 7720 (optional)" -ForegroundColor Yellow
}
}
# Start SDK Bridge (gRPC)
if ($sdkBridgeRunning) {
Write-Host "[SKIP] SDK Bridge is already running (PID: $($sdkBridgeRunning.Id))" -ForegroundColor Yellow
} else {
Write-Host "[2/4] Starting SDK Bridge..." -ForegroundColor Green
Start-Process -FilePath $sdkBridgeExe -WorkingDirectory $sdkBridgePath -WindowStyle Hidden
Write-Host "[4/6] Starting SDK Bridge..." -ForegroundColor Green
if (Test-Path $sdkBridgeExe) {
Start-Process -FilePath $sdkBridgeExe -WorkingDirectory $sdkBridgePath -WindowStyle Hidden
# Wait for SDK Bridge to start listening on port 50051
Write-Host " Waiting for SDK Bridge to connect" -NoNewline -ForegroundColor Gray
if (Wait-ForPort -Port 50051 -TimeoutSeconds 30) {
Write-Host ""
$process = Get-Process -Name "GeViScopeBridge" -ErrorAction SilentlyContinue
Write-Host " [OK] SDK Bridge started (PID: $($process.Id))" -ForegroundColor Green
# Wait for SDK Bridge to start listening on port 50051
Write-Host " Waiting for SDK Bridge to connect" -NoNewline -ForegroundColor Gray
if (Wait-ForPort -Port 50051 -TimeoutSeconds 30) {
Write-Host ""
$process = Get-Process -Name "GeViScopeBridge" -ErrorAction SilentlyContinue
Write-Host " [OK] SDK Bridge started (PID: $($process.Id))" -ForegroundColor Green
} else {
Write-Host ""
Write-Host " [WARN] SDK Bridge failed to start on port 50051 (optional)" -ForegroundColor Yellow
}
} else {
Write-Host ""
Write-Host " [ERROR] SDK Bridge failed to start listening on port 50051" -ForegroundColor Red
exit 1
Write-Host " [SKIP] SDK Bridge executable not found (optional)" -ForegroundColor Yellow
}
}
@@ -87,9 +141,11 @@ if ($sdkBridgeRunning) {
if ($uvicornRunning) {
Write-Host "[SKIP] Python API is already running (PID: $($uvicornRunning.Id))" -ForegroundColor Yellow
} else {
Write-Host "[3/4] Starting Python API..." -ForegroundColor Green
Write-Host "[5/6] Starting Python API..." -ForegroundColor Green
# Clean Python cache to ensure fresh code load
Get-ChildItem -Path $apiPath -Recurse -Directory -Filter __pycache__ -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
Start-Process -FilePath $uvicorn `
-ArgumentList "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload" `
-ArgumentList "main:app", "--host", "0.0.0.0", "--port", "8000" `
-WorkingDirectory $apiPath `
-WindowStyle Hidden
@@ -112,7 +168,7 @@ if ($flutterRunning) {
Select-Object -First 1 -ExpandProperty OwningProcess
Write-Host "[SKIP] Flutter Web is already running (PID: $flutterProcess)" -ForegroundColor Yellow
} else {
Write-Host "[4/4] Starting Flutter Web Server..." -ForegroundColor Green
Write-Host "[6/6] Starting Flutter Web Server..." -ForegroundColor Green
Start-Process -FilePath "python" `
-ArgumentList "-m", "http.server", "8081", "--bind", "0.0.0.0" `
-WorkingDirectory $flutterWebPath `
@@ -137,11 +193,15 @@ Write-Host "========================================" -ForegroundColor Green
Write-Host "Services Started Successfully!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "GeViServer: Running on ports 7700-7703" -ForegroundColor Cyan
Write-Host "SDK Bridge: Running on port 50051 (gRPC)" -ForegroundColor Cyan
Write-Host "Python API: http://localhost:8000" -ForegroundColor Cyan
Write-Host "Swagger UI: http://localhost:8000/docs" -ForegroundColor Cyan
Write-Host "Flutter Web: http://localhost:8081" -ForegroundColor Cyan
Write-Host "GeViServer: Running on ports 7700-7703" -ForegroundColor Cyan
Write-Host "C# Bridge: http://localhost:7710 (GeViServer 32-bit adapter)" -ForegroundColor Cyan
Write-Host "GeViScope Bridge: http://localhost:7720 (Camera Server SDK)" -ForegroundColor Cyan
Write-Host "SDK Bridge: Running on port 50051 (gRPC)" -ForegroundColor Cyan
Write-Host "Python API: http://localhost:8000" -ForegroundColor Cyan
Write-Host "Swagger UI: http://localhost:8000/docs" -ForegroundColor Cyan
Write-Host "GeViServer API: http://localhost:8000/docs#/GeViServer" -ForegroundColor Green
Write-Host "GeViScope API: http://localhost:7720 (Direct access)" -ForegroundColor Green
Write-Host "Flutter Web: http://localhost:8081" -ForegroundColor Cyan
Write-Host ""
Write-Host "To check status, run: .\status-services.ps1" -ForegroundColor Yellow
Write-Host "To stop services, run: .\stop-services.ps1" -ForegroundColor Yellow

View File

@@ -10,10 +10,21 @@ Write-Host ""
# Check GeViServer
$geviServer = Get-Process -Name "GeViServer"
if ($geviServer) {
Write-Host "[OK] GeViServer: RUNNING (PID: $($geviServer.Id))" -ForegroundColor Green
Write-Host " Ports: 7700-7703" -ForegroundColor Gray
Write-Host "[OK] GeViServer: RUNNING (PID: $($geviServer.Id))" -ForegroundColor Green
Write-Host " Ports: 7700-7703" -ForegroundColor Gray
} else {
Write-Host "[--] GeViServer: STOPPED" -ForegroundColor Red
Write-Host "[--] GeViServer: STOPPED" -ForegroundColor Red
}
Write-Host ""
# Check C# GeViServer Bridge
$geviServerBridge = Get-Process -Name "GeViServerBridge"
if ($geviServerBridge) {
Write-Host "[OK] C# Bridge: RUNNING (PID: $($geviServerBridge.Id))" -ForegroundColor Green
Write-Host " Port: 7710 (GeViServer 32-bit adapter)" -ForegroundColor Gray
} else {
Write-Host "[--] C# Bridge: STOPPED" -ForegroundColor Red
}
Write-Host ""
@@ -21,10 +32,10 @@ Write-Host ""
# Check SDK Bridge
$sdkBridge = Get-Process -Name "GeViScopeBridge"
if ($sdkBridge) {
Write-Host "[OK] SDK Bridge: RUNNING (PID: $($sdkBridge.Id))" -ForegroundColor Green
Write-Host " Port: 50051 (gRPC)" -ForegroundColor Gray
Write-Host "[OK] SDK Bridge: RUNNING (PID: $($sdkBridge.Id))" -ForegroundColor Green
Write-Host " Port: 50051 (gRPC)" -ForegroundColor Gray
} else {
Write-Host "[--] SDK Bridge: STOPPED" -ForegroundColor Red
Write-Host "[--] SDK Bridge: STOPPED" -ForegroundColor Red
}
Write-Host ""
@@ -42,16 +53,42 @@ if ($uvicorn) {
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
# Test C# Bridge endpoint
if ($geviServerBridge) {
Write-Host ""
Write-Host "Testing C# Bridge health..." -ForegroundColor Yellow
try {
$response = Invoke-WebRequest -Uri "http://localhost:7710/status" -Method GET -TimeoutSec 5 -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "[OK] C# Bridge is responding" -ForegroundColor Green
}
} catch {
Write-Host "[--] C# Bridge is not responding: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Test API endpoint
if ($uvicorn) {
Write-Host ""
Write-Host "Testing API health..." -ForegroundColor Yellow
Write-Host "Testing Python API health..." -ForegroundColor Yellow
try {
$response = Invoke-WebRequest -Uri "http://localhost:8000/health" -Method GET -TimeoutSec 5 -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "[OK] API is responding" -ForegroundColor Green
Write-Host "[OK] Python API is responding" -ForegroundColor Green
}
} catch {
Write-Host "[--] API is not responding: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "[--] Python API is not responding: $($_.Exception.Message)" -ForegroundColor Red
}
# Test GeViServer API endpoint
Write-Host ""
Write-Host "Testing GeViServer API..." -ForegroundColor Yellow
try {
$response = Invoke-WebRequest -Uri "http://localhost:8000/api/v1/geviserver/status" -Method GET -TimeoutSec 5 -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "[OK] GeViServer API is responding" -ForegroundColor Green
}
} catch {
Write-Host "[--] GeViServer API is not responding: $($_.Exception.Message)" -ForegroundColor Red
}
}

View File

@@ -24,49 +24,59 @@ Write-Host ""
$flutterPort = Get-NetTCPConnection -LocalPort 8081 -State Listen -ErrorAction SilentlyContinue
if ($flutterPort) {
$flutterPid = $flutterPort | Select-Object -First 1 -ExpandProperty OwningProcess
Write-Host "[1/4] Stopping Flutter Web (PID: $flutterPid)..." -ForegroundColor Yellow
Write-Host "[1/5] Stopping Flutter Web (PID: $flutterPid)..." -ForegroundColor Yellow
Stop-Process -Id $flutterPid -Force
Write-Host " [OK] Flutter Web stopped" -ForegroundColor Green
} else {
Write-Host "[1/4] Flutter Web is not running" -ForegroundColor Gray
Write-Host "[1/5] Flutter Web is not running" -ForegroundColor Gray
}
# Stop Python API
$uvicorn = Get-Process -Name "uvicorn"
if ($uvicorn) {
Write-Host "[2/4] Stopping Python API (PID: $($uvicorn.Id))..." -ForegroundColor Yellow
Write-Host "[2/5] Stopping Python API (PID: $($uvicorn.Id))..." -ForegroundColor Yellow
Stop-Process -Name "uvicorn" -Force
Write-Host " [OK] Python API stopped" -ForegroundColor Green
} else {
Write-Host "[2/4] Python API is not running" -ForegroundColor Gray
Write-Host "[2/5] Python API is not running" -ForegroundColor Gray
}
# Stop SDK Bridge
$sdkBridge = Get-Process -Name "GeViScopeBridge"
if ($sdkBridge) {
Write-Host "[3/4] Stopping SDK Bridge (PID: $($sdkBridge.Id))..." -ForegroundColor Yellow
Write-Host "[3/5] Stopping SDK Bridge (PID: $($sdkBridge.Id))..." -ForegroundColor Yellow
Stop-Process -Name "GeViScopeBridge" -Force
Write-Host " [OK] SDK Bridge stopped" -ForegroundColor Green
} else {
Write-Host "[3/4] SDK Bridge is not running" -ForegroundColor Gray
Write-Host "[3/5] SDK Bridge is not running" -ForegroundColor Gray
}
# Stop C# GeViServer Bridge
$geviServerBridge = Get-Process -Name "GeViServerBridge"
if ($geviServerBridge) {
Write-Host "[4/5] Stopping C# GeViServer Bridge (PID: $($geviServerBridge.Id))..." -ForegroundColor Yellow
Stop-Process -Name "GeViServerBridge" -Force
Write-Host " [OK] C# Bridge stopped" -ForegroundColor Green
} else {
Write-Host "[4/5] C# GeViServer Bridge is not running" -ForegroundColor Gray
}
# Stop GeViServer (unless -KeepGeViServer flag is set)
if (-not $KeepGeViServer) {
$geviServer = Get-Process -Name "GeViServer"
if ($geviServer) {
Write-Host "[4/4] Stopping GeViServer (PID: $($geviServer.Id))..." -ForegroundColor Yellow
Write-Host "[5/5] Stopping GeViServer (PID: $($geviServer.Id))..." -ForegroundColor Yellow
Stop-Process -Name "GeViServer" -Force
Write-Host " [OK] GeViServer stopped" -ForegroundColor Green
} else {
Write-Host "[4/4] GeViServer is not running" -ForegroundColor Gray
Write-Host "[5/5] GeViServer is not running" -ForegroundColor Gray
}
} else {
$geviServer = Get-Process -Name "GeViServer"
if ($geviServer) {
Write-Host "[4/4] Keeping GeViServer running (PID: $($geviServer.Id))" -ForegroundColor Green
Write-Host "[5/5] Keeping GeViServer running (PID: $($geviServer.Id))" -ForegroundColor Green
} else {
Write-Host "[4/4] GeViServer is not running (cannot keep)" -ForegroundColor Yellow
Write-Host "[5/5] GeViServer is not running (cannot keep)" -ForegroundColor Yellow
}
}