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:
@@ -0,0 +1,356 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../core/constants/api_constants.dart';
|
||||
|
||||
/// Remote data source for GeViScope operations
|
||||
///
|
||||
/// This data source provides methods to interact with GeViScope Camera Server
|
||||
/// through the FastAPI backend, which wraps the GeViScope SDK.
|
||||
/// GeViScope handles video recording, PTZ control, and media channel management.
|
||||
class GeViScopeRemoteDataSource {
|
||||
final Dio _dio;
|
||||
|
||||
GeViScopeRemoteDataSource({required Dio dio}) : _dio = dio;
|
||||
|
||||
// ============================================================================
|
||||
// Connection Management
|
||||
// ============================================================================
|
||||
|
||||
/// Connect to GeViScope Camera Server
|
||||
///
|
||||
/// [address] - Server address (e.g., 'localhost' or IP address)
|
||||
/// [username] - Username for authentication (default: sysadmin)
|
||||
/// [password] - Password (default: masterkey)
|
||||
Future<Map<String, dynamic>> connect({
|
||||
required String address,
|
||||
required String username,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeConnect,
|
||||
data: {
|
||||
'address': address,
|
||||
'username': username,
|
||||
'password': password,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Connection failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect from GeViScope
|
||||
Future<Map<String, dynamic>> disconnect() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeDisconnect,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Disconnection failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get connection status
|
||||
Future<Map<String, dynamic>> getStatus() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
ApiConstants.geviScopeStatus,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Status check failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Media Channels
|
||||
// ============================================================================
|
||||
|
||||
/// Get list of media channels (cameras)
|
||||
Future<Map<String, dynamic>> getChannels() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
ApiConstants.geviScopeChannels,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Get channels failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh media channel list
|
||||
Future<Map<String, dynamic>> refreshChannels() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeChannelsRefresh,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Refresh channels failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Actions
|
||||
// ============================================================================
|
||||
|
||||
/// Send generic action to GeViScope
|
||||
///
|
||||
/// [action] - Action string (e.g., "CustomAction(1,\"Hello\")")
|
||||
Future<Map<String, dynamic>> sendAction(String action) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeAction,
|
||||
data: {
|
||||
'action': action,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Send action failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Send custom action
|
||||
///
|
||||
/// [typeId] - Action type ID
|
||||
/// [text] - Action text/parameters
|
||||
Future<Map<String, dynamic>> sendCustomAction({
|
||||
required int typeId,
|
||||
String text = '',
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCustomAction,
|
||||
queryParameters: {
|
||||
'type_id': typeId,
|
||||
'text': text,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('CustomAction failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Video Control
|
||||
// ============================================================================
|
||||
|
||||
/// CrossSwitch - Route video input to output
|
||||
///
|
||||
/// [videoInput] - Source camera/channel number
|
||||
/// [videoOutput] - Destination monitor/output number
|
||||
/// [switchMode] - Switch mode (0 = normal)
|
||||
Future<Map<String, dynamic>> crossSwitch({
|
||||
required int videoInput,
|
||||
required int videoOutput,
|
||||
int switchMode = 0,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCrossSwitch,
|
||||
queryParameters: {
|
||||
'video_input': videoInput,
|
||||
'video_output': videoOutput,
|
||||
'switch_mode': switchMode,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('CrossSwitch failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PTZ Camera Control
|
||||
// ============================================================================
|
||||
|
||||
/// Pan camera left or right
|
||||
///
|
||||
/// [camera] - Camera/PTZ head number
|
||||
/// [direction] - 'left' or 'right'
|
||||
/// [speed] - Movement speed (1-100)
|
||||
Future<Map<String, dynamic>> cameraPan({
|
||||
required int camera,
|
||||
required String direction,
|
||||
int speed = 50,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCameraPan,
|
||||
queryParameters: {
|
||||
'camera': camera,
|
||||
'direction': direction,
|
||||
'speed': speed,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Camera pan failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Tilt camera up or down
|
||||
///
|
||||
/// [camera] - Camera/PTZ head number
|
||||
/// [direction] - 'up' or 'down'
|
||||
/// [speed] - Movement speed (1-100)
|
||||
Future<Map<String, dynamic>> cameraTilt({
|
||||
required int camera,
|
||||
required String direction,
|
||||
int speed = 50,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCameraTilt,
|
||||
queryParameters: {
|
||||
'camera': camera,
|
||||
'direction': direction,
|
||||
'speed': speed,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Camera tilt failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Zoom camera in or out
|
||||
///
|
||||
/// [camera] - Camera/PTZ head number
|
||||
/// [direction] - 'in' or 'out'
|
||||
/// [speed] - Movement speed (1-100)
|
||||
Future<Map<String, dynamic>> cameraZoom({
|
||||
required int camera,
|
||||
required String direction,
|
||||
int speed = 50,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCameraZoom,
|
||||
queryParameters: {
|
||||
'camera': camera,
|
||||
'direction': direction,
|
||||
'speed': speed,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Camera zoom failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop all camera movement
|
||||
///
|
||||
/// [camera] - Camera/PTZ head number
|
||||
Future<Map<String, dynamic>> cameraStop({
|
||||
required int camera,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCameraStop,
|
||||
queryParameters: {
|
||||
'camera': camera,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Camera stop failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Go to camera preset position
|
||||
///
|
||||
/// [camera] - Camera/PTZ head number
|
||||
/// [preset] - Preset position number
|
||||
Future<Map<String, dynamic>> cameraPreset({
|
||||
required int camera,
|
||||
required int preset,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeCameraPreset,
|
||||
queryParameters: {
|
||||
'camera': camera,
|
||||
'preset': preset,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Camera preset failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Digital I/O
|
||||
// ============================================================================
|
||||
|
||||
/// Close digital output contact (activate relay)
|
||||
///
|
||||
/// [contactId] - Digital contact ID
|
||||
Future<Map<String, dynamic>> digitalIoClose({
|
||||
required int contactId,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeDigitalIoClose,
|
||||
queryParameters: {
|
||||
'contact_id': contactId,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Digital I/O close failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Open digital output contact (deactivate relay)
|
||||
///
|
||||
/// [contactId] - Digital contact ID
|
||||
Future<Map<String, dynamic>> digitalIoOpen({
|
||||
required int contactId,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeDigitalIoOpen,
|
||||
queryParameters: {
|
||||
'contact_id': contactId,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Digital I/O open failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Message Log
|
||||
// ============================================================================
|
||||
|
||||
/// Get received message log
|
||||
Future<Map<String, dynamic>> getMessages() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
ApiConstants.geviScopeMessages,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Get messages failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear message log
|
||||
Future<Map<String, dynamic>> clearMessages() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviScopeMessagesClear,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Clear messages failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../../core/constants/api_constants.dart';
|
||||
|
||||
/// Remote data source for GeViServer operations
|
||||
///
|
||||
/// This data source provides methods to interact with GeViServer through
|
||||
/// the FastAPI backend, which wraps GeViProcAPI.dll
|
||||
class GeViServerRemoteDataSource {
|
||||
final Dio _dio;
|
||||
|
||||
GeViServerRemoteDataSource({required Dio dio}) : _dio = dio;
|
||||
|
||||
// ============================================================================
|
||||
// Connection Management
|
||||
// ============================================================================
|
||||
|
||||
/// Connect to GeViServer
|
||||
///
|
||||
/// [address] - Server address (e.g., 'localhost' or IP address)
|
||||
/// [username] - Username for authentication
|
||||
/// [password] - Password (will be encrypted by backend)
|
||||
Future<Map<String, dynamic>> connect({
|
||||
required String address,
|
||||
required String username,
|
||||
required String password,
|
||||
String? username2,
|
||||
String? password2,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerConnect,
|
||||
data: {
|
||||
'address': address,
|
||||
'username': username,
|
||||
'password': password,
|
||||
if (username2 != null) 'username2': username2,
|
||||
if (password2 != null) 'password2': password2,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Connection failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect from GeViServer
|
||||
Future<Map<String, dynamic>> disconnect() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerDisconnect,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Disconnection failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get connection status
|
||||
Future<Map<String, dynamic>> getStatus() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
ApiConstants.geviServerStatus,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Status check failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Send ping to GeViServer
|
||||
Future<Map<String, dynamic>> sendPing() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerPing,
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Ping failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Message Sending
|
||||
// ============================================================================
|
||||
|
||||
/// Send generic action message to GeViServer
|
||||
///
|
||||
/// [message] - ASCII action message (e.g., "CrossSwitch(7,3,0)")
|
||||
Future<Map<String, dynamic>> sendMessage(String message) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerSendMessage,
|
||||
data: {
|
||||
'message': message,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('Send message failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Video Control
|
||||
// ============================================================================
|
||||
|
||||
/// Cross-switch video input to output
|
||||
///
|
||||
/// [videoInput] - Video input channel number
|
||||
/// [videoOutput] - Video output channel number
|
||||
/// [switchMode] - Switch mode (default: 0 for normal)
|
||||
Future<Map<String, dynamic>> crossSwitch({
|
||||
required int videoInput,
|
||||
required int videoOutput,
|
||||
int switchMode = 0,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerVideoCrossSwitch,
|
||||
queryParameters: {
|
||||
'video_input': videoInput,
|
||||
'video_output': videoOutput,
|
||||
'switch_mode': switchMode,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('CrossSwitch failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear video output
|
||||
///
|
||||
/// [videoOutput] - Video output channel number
|
||||
Future<Map<String, dynamic>> clearOutput({
|
||||
required int videoOutput,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerVideoClearOutput,
|
||||
queryParameters: {
|
||||
'video_output': videoOutput,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('ClearOutput failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Digital I/O Control
|
||||
// ============================================================================
|
||||
|
||||
/// Close digital output contact
|
||||
///
|
||||
/// [contactId] - Digital contact ID
|
||||
Future<Map<String, dynamic>> closeContact({
|
||||
required int contactId,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerDigitalIoCloseContact,
|
||||
queryParameters: {
|
||||
'contact_id': contactId,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('CloseContact failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Open digital output contact
|
||||
///
|
||||
/// [contactId] - Digital contact ID
|
||||
Future<Map<String, dynamic>> openContact({
|
||||
required int contactId,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerDigitalIoOpenContact,
|
||||
queryParameters: {
|
||||
'contact_id': contactId,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('OpenContact failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Timer Control
|
||||
// ============================================================================
|
||||
|
||||
/// Start timer
|
||||
///
|
||||
/// [timerId] - Timer ID (0 to use name)
|
||||
/// [timerName] - Timer name
|
||||
Future<Map<String, dynamic>> startTimer({
|
||||
int timerId = 0,
|
||||
String timerName = '',
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerTimerStart,
|
||||
queryParameters: {
|
||||
'timer_id': timerId,
|
||||
'timer_name': timerName,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('StartTimer failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop timer
|
||||
///
|
||||
/// [timerId] - Timer ID (0 to use name)
|
||||
/// [timerName] - Timer name
|
||||
Future<Map<String, dynamic>> stopTimer({
|
||||
int timerId = 0,
|
||||
String timerName = '',
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerTimerStop,
|
||||
queryParameters: {
|
||||
'timer_id': timerId,
|
||||
'timer_name': timerName,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('StopTimer failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Custom Actions
|
||||
// ============================================================================
|
||||
|
||||
/// Send custom action
|
||||
///
|
||||
/// [typeId] - Action type ID
|
||||
/// [text] - Action text/parameters
|
||||
Future<Map<String, dynamic>> sendCustomAction({
|
||||
required int typeId,
|
||||
String text = '',
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
ApiConstants.geviServerCustomAction,
|
||||
queryParameters: {
|
||||
'type_id': typeId,
|
||||
'text': text,
|
||||
},
|
||||
);
|
||||
return response.data as Map<String, dynamic>;
|
||||
} on DioException catch (e) {
|
||||
throw Exception('CustomAction failed: ${e.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user