- 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>
32 KiB
P0 Implementation - Backend API Approach (RECOMMENDED)
This is the CORRECT approach for your architecture!
You already have:
- ✅ FastAPI backend at
http://100.81.138.77:8000/api/v1 - ✅ Flutter app making REST calls
- ✅ Swagger UI documentation
- ✅ Authentication with Bearer tokens
DON'T create a native Windows plugin! Instead, add GeViServer integration to your existing backend.
Current Architecture (What You Have)
┌─────────────────────────────────────────┐
│ Flutter App (Dart) │
│ ┌───────────────────────────────────┐ │
│ │ ActionMappingBloc │ │
│ │ ServerBloc │ │
│ └──────────────┬────────────────────┘ │
│ │ │
│ ┌──────────────▼────────────────────┐ │
│ │ DioClient (HTTP) │ │
│ │ http://100.81.138.77:8000 │ │
│ └──────────────┬────────────────────┘ │
└─────────────────┼────────────────────────┘
│ REST API
┌─────────────────▼────────────────────────┐
│ FastAPI Backend (Python) │
│ ┌───────────────────────────────────┐ │
│ │ /api/v1/configuration/... │ │
│ │ /api/v1/auth/... │ │
│ │ /api/v1/excel/... │ │
│ └───────────────────────────────────┘ │
│ │
│ Database: PostgreSQL/SQLite │
└───────────────────────────────────────────┘
Target Architecture (What You Need)
┌─────────────────────────────────────────┐
│ Flutter App (Dart) │
│ ┌───────────────────────────────────┐ │
│ │ GeViServerConnectionBloc │ │ ← New BLoC
│ │ VideoControlBloc │ │ ← New BLoC
│ └──────────────┬────────────────────┘ │
│ │ │
│ ┌──────────────▼────────────────────┐ │
│ │ DioClient (HTTP) │ │
│ │ Existing HTTP client │ │
│ └──────────────┬────────────────────┘ │
└─────────────────┼────────────────────────┘
│ REST API (new endpoints)
┌─────────────────▼────────────────────────┐
│ FastAPI Backend (Python) │
│ ┌───────────────────────────────────┐ │
│ │ NEW: /api/v1/geviserver/... │ │ ← Add these!
│ │ - POST /connect │ │
│ │ - POST /disconnect │ │
│ │ - POST /send-action │ │
│ │ - GET /video-inputs │ │
│ │ - POST /crossswitch │ │
│ │ - POST /digital-io/close │ │
│ │ - GET /connection-status │ │
│ └──────────────┬────────────────────┘ │
│ │ │
│ ┌──────────────▼────────────────────┐ │
│ │ GeViServer Service (Python) │ │ ← New service
│ │ - Wrapper around GeViProcAPI │ │
│ │ - Connection pooling │ │
│ │ - Message construction │ │
│ └──────────────┬────────────────────┘ │
│ │ │
│ ┌──────────────▼────────────────────┐ │
│ │ Python → GeViProcAPI.dll │ │ ← Integration
│ │ (ctypes or subprocess) │ │
│ └──────────────┬────────────────────┘ │
└─────────────────┼─────────────────────────┘
│ Native calls
┌─────────────────▼─────────────────────────┐
│ GeViServer (Windows Service) │
│ C:\GEVISOFT\geviserver.exe │
└───────────────────────────────────────────┘
Implementation Steps
Step 1: Add GeViServer Integration to Backend
Location: geutebruck-api/src/api/routers/geviserver.py
1.1 Install Python Dependencies
cd C:\DEV\COPILOT\geutebruck-api
pip install ctypes # Already included in Python
1.2 Create GeViServer Service
File: geutebruck-api/src/services/geviserver_service.py
import ctypes
import logging
from typing import Optional, Dict, Any
from ctypes import POINTER, c_char_p, c_int, c_bool, c_void_p, CFUNCTYPE, Structure
from dataclasses import dataclass
from enum import Enum
logger = logging.getLogger(__name__)
class TServerNotification(Enum):
NFServer_SetupModified = 0
NFServer_Disconnected = 10
NFServer_GoingShutdown = 11
NFServer_NewMessage = 12
class TConnectResult(Enum):
connectOk = 0
connectWarningInvalidHeaders = 1
connectAborted = 100
connectGenericError = 101
connectRemoteUnknownUser = 302
connectRemoteConnectionLimitExceeded = 303
@dataclass
class GeViServerConnectionInfo:
address: str
username: str
is_connected: bool
connected_at: Optional[str] = None
class GeViServerService:
"""Service to interact with GeViServer via GeViProcAPI.dll"""
def __init__(self, dll_path: str = "C:/GEVISOFT/GeViProcAPI.dll"):
self.dll_path = dll_path
self.dll = None
self.database_handle = None
self._load_dll()
def _load_dll(self):
"""Load GeViProcAPI.dll and define function signatures"""
try:
self.dll = ctypes.WinDLL(self.dll_path)
# Define function signatures
# GeViAPI_InterfaceVersion
self.dll.GeViAPI_InterfaceVersion.restype = c_int
self.dll.GeViAPI_InterfaceVersion.argtypes = []
# GeViAPI_EncodeString
self.dll.GeViAPI_EncodeString.restype = c_bool
self.dll.GeViAPI_EncodeString.argtypes = [c_char_p, c_char_p, c_int]
# GeViAPI_Database_Create
self.dll.GeViAPI_Database_Create.restype = c_bool
self.dll.GeViAPI_Database_Create.argtypes = [
POINTER(c_void_p), # Database handle (out)
c_char_p, # Aliasname
c_char_p, # Address
c_char_p, # Username
c_char_p, # Password
c_char_p, # Username2
c_char_p, # Password2
]
# GeViAPI_Database_Connect
self.dll.GeViAPI_Database_Connect.restype = c_bool
self.dll.GeViAPI_Database_Connect.argtypes = [
c_void_p, # Database handle
POINTER(c_int), # ConnectResult (out)
c_void_p, # Callback (optional)
c_void_p, # Instance (optional)
]
# GeViAPI_Database_Connected
self.dll.GeViAPI_Database_Connected.restype = c_bool
self.dll.GeViAPI_Database_Connected.argtypes = [
c_void_p, # Database handle
POINTER(c_bool), # IsConnected (out)
]
# GeViAPI_Database_Disconnect
self.dll.GeViAPI_Database_Disconnect.restype = c_bool
self.dll.GeViAPI_Database_Disconnect.argtypes = [c_void_p]
# GeViAPI_Database_Destroy
self.dll.GeViAPI_Database_Destroy.restype = c_bool
self.dll.GeViAPI_Database_Destroy.argtypes = [c_void_p]
# GeViAPI_Database_SendPing
self.dll.GeViAPI_Database_SendPing.restype = c_bool
self.dll.GeViAPI_Database_SendPing.argtypes = [c_void_p]
# GeViAPI_Database_SendMessage
self.dll.GeViAPI_Database_SendMessage.restype = c_bool
self.dll.GeViAPI_Database_SendMessage.argtypes = [
c_void_p, # Database handle
c_char_p, # Message buffer
c_int, # Buffer size
]
# GeViAPI_GetLastError
self.dll.GeViAPI_GetLastError.restype = c_bool
self.dll.GeViAPI_GetLastError.argtypes = [
POINTER(c_int), # ExceptionClass (out)
POINTER(c_int), # ErrorNo (out)
POINTER(c_int), # ErrorClass (out)
POINTER(c_char_p), # ErrorMessage (out)
]
logger.info(f"Loaded GeViProcAPI.dll version: {self.dll.GeViAPI_InterfaceVersion()}")
except Exception as e:
logger.error(f"Failed to load GeViProcAPI.dll: {e}")
raise
def _encode_password(self, password: str) -> bytes:
"""Encode password using GeViAPI_EncodeString"""
encoded = ctypes.create_string_buffer(33) # 32 bytes + null
result = self.dll.GeViAPI_EncodeString(
encoded,
password.encode('utf-8'),
33
)
if not result:
raise Exception("Failed to encode password")
return encoded.value
def _get_last_error(self) -> str:
"""Get last error from GeViProcAPI"""
exception_class = c_int()
error_no = c_int()
error_class = c_int()
error_message = c_char_p()
result = self.dll.GeViAPI_GetLastError(
ctypes.byref(exception_class),
ctypes.byref(error_no),
ctypes.byref(error_class),
ctypes.byref(error_message)
)
if result and error_message:
return error_message.value.decode('utf-8', errors='ignore')
return "Unknown error"
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 "192.168.1.100")
username: Username for authentication
password: Password (will be encrypted)
username2: Optional second username
password2: Optional second password
Returns:
Dict with success status and message
"""
try:
# Disconnect if already connected
if self.database_handle:
self.disconnect()
# Encode passwords
encoded_password = self._encode_password(password)
encoded_password2 = self._encode_password(password2) if password2 else None
# Create database handle
db_handle = c_void_p()
result = self.dll.GeViAPI_Database_Create(
ctypes.byref(db_handle),
b"PythonAPI",
address.encode('utf-8'),
username.encode('utf-8'),
encoded_password,
username2.encode('utf-8') if username2 else None,
encoded_password2 if encoded_password2 else None
)
if not result or not db_handle.value:
error_msg = self._get_last_error()
logger.error(f"Failed to create database: {error_msg}")
return {
"success": False,
"message": f"Database creation failed: {error_msg}"
}
self.database_handle = db_handle
# Connect
connect_result = c_int()
result = self.dll.GeViAPI_Database_Connect(
self.database_handle,
ctypes.byref(connect_result),
None, # Progress callback
None # Instance
)
if not result or connect_result.value != TConnectResult.connectOk.value:
error_msg = self._get_last_error()
connect_result_name = TConnectResult(connect_result.value).name if connect_result.value in [e.value for e in TConnectResult] else f"Unknown({connect_result.value})"
logger.error(f"Connection failed: {connect_result_name}, {error_msg}")
# Clean up
self.dll.GeViAPI_Database_Destroy(self.database_handle)
self.database_handle = None
return {
"success": False,
"message": f"Connection failed: {connect_result_name}",
"error": error_msg
}
logger.info(f"Successfully connected to GeViServer at {address}")
return {
"success": True,
"message": "Connected to GeViServer",
"address": address,
"username": username
}
except Exception as e:
logger.error(f"Exception during connection: {e}")
return {
"success": False,
"message": f"Exception: {str(e)}"
}
def disconnect(self) -> Dict[str, Any]:
"""Disconnect from GeViServer"""
try:
if not self.database_handle:
return {
"success": True,
"message": "Already disconnected"
}
# Disconnect
self.dll.GeViAPI_Database_Disconnect(self.database_handle)
# Destroy handle
self.dll.GeViAPI_Database_Destroy(self.database_handle)
self.database_handle = None
logger.info("Disconnected from GeViServer")
return {
"success": True,
"message": "Disconnected successfully"
}
except Exception as e:
logger.error(f"Exception during disconnection: {e}")
return {
"success": False,
"message": f"Disconnection error: {str(e)}"
}
def is_connected(self) -> bool:
"""Check if connected to GeViServer"""
if not self.database_handle:
return False
connected = c_bool()
result = self.dll.GeViAPI_Database_Connected(
self.database_handle,
ctypes.byref(connected)
)
return result and connected.value
def send_ping(self) -> Dict[str, Any]:
"""Send ping to GeViServer"""
try:
if not self.database_handle:
return {
"success": False,
"message": "Not connected"
}
result = self.dll.GeViAPI_Database_SendPing(self.database_handle)
if not result:
error_msg = self._get_last_error()
logger.error(f"Ping failed: {error_msg}")
return {
"success": False,
"message": f"Ping failed: {error_msg}"
}
return {
"success": True,
"message": "Ping successful"
}
except Exception as e:
logger.error(f"Exception during ping: {e}")
return {
"success": False,
"message": f"Ping error: {str(e)}"
}
def send_message(self, message: str) -> Dict[str, Any]:
"""
Send action message to GeViServer
Args:
message: ASCII message string (e.g., "CrossSwitch(7,3,0)")
Returns:
Dict with success status
"""
try:
if not self.database_handle:
return {
"success": False,
"message": "Not connected"
}
message_bytes = message.encode('utf-8')
result = self.dll.GeViAPI_Database_SendMessage(
self.database_handle,
message_bytes,
len(message_bytes)
)
if not result:
error_msg = self._get_last_error()
logger.error(f"Send message failed: {error_msg}")
return {
"success": False,
"message": f"Send failed: {error_msg}"
}
logger.info(f"Sent message: {message}")
return {
"success": True,
"message": "Message sent successfully",
"sent_message": message
}
except Exception as e:
logger.error(f"Exception sending message: {e}")
return {
"success": False,
"message": f"Send error: {str(e)}"
}
def __del__(self):
"""Cleanup on destruction"""
if self.database_handle:
try:
self.disconnect()
except:
pass
# Global instance (singleton pattern)
_geviserver_service: Optional[GeViServerService] = None
def get_geviserver_service() -> GeViServerService:
"""Get or create GeViServer service instance"""
global _geviserver_service
if _geviserver_service is None:
_geviserver_service = GeViServerService()
return _geviserver_service
1.3 Create API Router
File: geutebruck-api/src/api/routers/geviserver.py
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from typing import Optional, Dict, Any
import logging
from src.services.geviserver_service import get_geviserver_service, GeViServerService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/geviserver", tags=["GeViServer"])
# Request/Response Models
class ConnectRequest(BaseModel):
address: str
username: str
password: str
username2: Optional[str] = None
password2: Optional[str] = None
class SendMessageRequest(BaseModel):
message: str
class ConnectionStatusResponse(BaseModel):
is_connected: bool
address: Optional[str] = None
username: Optional[str] = None
# Endpoints
@router.post("/connect")
async def connect_to_server(
request: ConnectRequest,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Connect to GeViServer
Example:
```json
{
"address": "localhost",
"username": "admin",
"password": "admin"
}
```
"""
logger.info(f"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")
async def disconnect_from_server(
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""Disconnect from GeViServer"""
logger.info("Disconnecting from GeViServer")
result = service.disconnect()
return result
@router.get("/status")
async def get_connection_status(
service: GeViServerService = Depends(get_geviserver_service)
) -> ConnectionStatusResponse:
"""Get current connection status"""
is_connected = service.is_connected()
return ConnectionStatusResponse(
is_connected=is_connected,
address=None, # Could store this in service
username=None
)
@router.post("/ping")
async def send_ping(
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""Send ping to GeViServer"""
result = service.send_ping()
if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("message", "Ping failed"))
return result
@router.post("/send-message")
async def send_message(
request: SendMessageRequest,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Send action message to GeViServer
Example:
```json
{
"message": "CrossSwitch(7,3,0)"
}
```
"""
logger.info(f"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,
video_output: int,
switch_mode: int = 0,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""
Cross-switch video input to output
Args:
video_input: Video input channel number
video_output: Video output channel number
switch_mode: Switch mode (0=normal, 1=...)
"""
message = f"CrossSwitch({video_input},{video_output},{switch_mode})"
logger.info(f"Cross-switching: {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
}
@router.post("/video/clear-output")
async def clear_video_output(
video_output: int,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""Clear video output"""
message = f"ClearOutput({video_output})"
logger.info(f"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,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""Close digital output contact"""
message = f"CloseContact({contact_id})"
logger.info(f"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,
service: GeViServerService = Depends(get_geviserver_service)
) -> Dict[str, Any]:
"""Open digital output contact"""
message = f"OpenContact({contact_id})"
logger.info(f"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
}
1.4 Register Router
File: geutebruck-api/src/api/main.py
Add this to your main.py:
from src.api.routers import geviserver
# ... existing imports and app setup ...
# Register GeViServer router
app.include_router(geviserver.router, prefix="/api/v1")
Step 2: Update Flutter App to Use API
Your Flutter app already has everything it needs - just add new endpoints!
2.1 Update API Constants
File: geutebruck_app/lib/core/constants/api_constants.dart
class ApiConstants {
// ... existing constants ...
// GeViServer endpoints
static const String geviServerConnect = '/geviserver/connect';
static const String geviServerDisconnect = '/geviserver/disconnect';
static const String geviServerStatus = '/geviserver/status';
static const String geviServerPing = '/geviserver/ping';
static const String geviServerSendMessage = '/geviserver/send-message';
// Video control
static const String videoCrossSwitch = '/geviserver/video/crossswitch';
static const String videoClearOutput = '/geviserver/video/clear-output';
// Digital I/O
static const String digitalIoCloseContact = '/geviserver/digital-io/close-contact';
static const String digitalIoOpenContact = '/geviserver/digital-io/open-contact';
}
2.2 Create Remote Data Source
File: geutebruck_app/lib/data/data_sources/remote/geviserver_remote_data_source.dart
import 'package:dio/dio.dart';
import '../../../core/network/dio_client.dart';
import '../../../core/constants/api_constants.dart';
class GeViServerRemoteDataSource {
final DioClient _dioClient;
GeViServerRemoteDataSource({required DioClient dioClient})
: _dioClient = dioClient;
/// Connect to GeViServer
Future<Map<String, dynamic>> connect({
required String address,
required String username,
required String password,
}) async {
try {
final response = await _dioClient.post(
ApiConstants.geviServerConnect,
data: {
'address': address,
'username': username,
'password': password,
},
);
return response.data;
} on DioException catch (e) {
throw Exception('Connection failed: ${e.message}');
}
}
/// Disconnect from GeViServer
Future<Map<String, dynamic>> disconnect() async {
try {
final response = await _dioClient.post(
ApiConstants.geviServerDisconnect,
);
return response.data;
} on DioException catch (e) {
throw Exception('Disconnection failed: ${e.message}');
}
}
/// Get connection status
Future<Map<String, dynamic>> getStatus() async {
try {
final response = await _dioClient.get(
ApiConstants.geviServerStatus,
);
return response.data;
} on DioException catch (e) {
throw Exception('Status check failed: ${e.message}');
}
}
/// Send ping
Future<Map<String, dynamic>> sendPing() async {
try {
final response = await _dioClient.post(
ApiConstants.geviServerPing,
);
return response.data;
} on DioException catch (e) {
throw Exception('Ping failed: ${e.message}');
}
}
/// Cross-switch video
Future<Map<String, dynamic>> crossSwitch({
required int videoInput,
required int videoOutput,
int switchMode = 0,
}) async {
try {
final response = await _dioClient.post(
ApiConstants.videoCrossSwitch,
queryParameters: {
'video_input': videoInput,
'video_output': videoOutput,
'switch_mode': switchMode,
},
);
return response.data;
} on DioException catch (e) {
throw Exception('CrossSwitch failed: ${e.message}');
}
}
/// Clear video output
Future<Map<String, dynamic>> clearOutput({
required int videoOutput,
}) async {
try {
final response = await _dioClient.post(
ApiConstants.videoClearOutput,
queryParameters: {
'video_output': videoOutput,
},
);
return response.data;
} on DioException catch (e) {
throw Exception('ClearOutput failed: ${e.message}');
}
}
/// Close digital contact
Future<Map<String, dynamic>> closeContact({
required int contactId,
}) async {
try {
final response = await _dioClient.post(
ApiConstants.digitalIoCloseContact,
queryParameters: {
'contact_id': contactId,
},
);
return response.data;
} on DioException catch (e) {
throw Exception('CloseContact failed: ${e.message}');
}
}
/// Open digital contact
Future<Map<String, dynamic>> openContact({
required int contactId,
}) async {
try {
final response = await _dioClient.post(
ApiConstants.digitalIoOpenContact,
queryParameters: {
'contact_id': contactId,
},
);
return response.data;
} on DioException catch (e) {
throw Exception('OpenContact failed: ${e.message}');
}
}
}
Step 3: Test the Integration
3.1 Start GeViServer
cd C:\GEVISOFT
geviserver.exe console
3.2 Start Backend API
cd C:\DEV\COPILOT\geutebruck-api
python -m uvicorn src.api.main:app --reload --host 0.0.0.0 --port 8000
3.3 Test with Swagger UI
Open: http://localhost:8000/docs
Test Sequence:
- POST
/api/v1/geviserver/connectwith:{ "address": "localhost", "username": "admin", "password": "admin" } - GET
/api/v1/geviserver/status- should showis_connected: true - POST
/api/v1/geviserver/ping- should return success - POST
/api/v1/geviserver/video/crossswitch?video_input=7&video_output=3 - POST
/api/v1/geviserver/disconnect
3.4 Test from Flutter App
Your existing Flutter app can now call these endpoints using DioClient!
Architecture Benefits
✅ Why This is Better
-
No Native Code in Flutter
- Flutter stays platform-independent
- Easier to test and maintain
- Can run on mobile later (just API calls)
-
Backend Handles Complexity
- Connection pooling
- Error handling
- Password encryption
- Message construction
-
Swagger Documentation
- All endpoints documented automatically
- Easy for other clients to integrate
- Built-in testing UI
-
Centralized Logic
- Multiple Flutter clients can connect
- Web dashboard can use same API
- Mobile app can use same API
-
Existing Architecture
- Uses your existing DioClient
- Uses your existing authentication
- Uses your existing error handling
Next Steps
- ✅ Implement backend endpoints (Step 1)
- ✅ Add Flutter data source (Step 2)
- ✅ Test with Swagger (Step 3)
- Add BLoC for connection state
- Add UI screens for video control
- Execute action mappings
Summary
DON'T create a native Windows plugin!
DO extend your existing FastAPI backend with GeViServer integration.
Your architecture becomes:
Flutter (Dart) → HTTP → FastAPI (Python) → ctypes → GeViProcAPI.dll → GeViServer
This is the CORRECT and RECOMMENDED approach! 🎯