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:
92
geutebruck-api/rebuild-flutter.ps1
Normal file
92
geutebruck-api/rebuild-flutter.ps1
Normal 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 ""
|
||||
@@ -56,3 +56,6 @@ structlog==24.1.0
|
||||
|
||||
# Date/Time
|
||||
python-dateutil==2.8.2
|
||||
|
||||
# Excel Processing
|
||||
openpyxl==3.1.5
|
||||
|
||||
@@ -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
|
||||
|
||||
132
geutebruck-api/src/api/routers/excel_import.py
Normal file
132
geutebruck-api/src/api/routers/excel_import.py
Normal 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)}")
|
||||
621
geutebruck-api/src/api/routers/geviscope.py
Normal file
621
geutebruck-api/src/api/routers/geviscope.py
Normal 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()
|
||||
626
geutebruck-api/src/api/routers/geviserver.py
Normal file
626
geutebruck-api/src/api/routers/geviserver.py
Normal 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
|
||||
}
|
||||
162
geutebruck-api/src/api/services/geviscope_service.py
Normal file
162
geutebruck-api/src/api/services/geviscope_service.py
Normal 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
|
||||
230
geutebruck-api/src/api/services/geviserver_service.py
Normal file
230
geutebruck-api/src/api/services/geviserver_service.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user