Files
geutebruck/geviscope-bridge/GeViScopeBridge/Program.cs
Administrator a92b909539 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>
2026-01-19 08:14:17 +01:00

753 lines
21 KiB
C#

using GEUTEBRUECK.GeViScope.Wrapper.DBI;
using GEUTEBRUECK.GeViScope.Wrapper.Actions;
using GEUTEBRUECK.GeViScope.Wrapper.Actions.SystemActions;
using GEUTEBRUECK.GeViScope.Wrapper.Actions.DigitalContactsActions;
using GEUTEBRUECK.GeViScope.Wrapper.Actions.ActionDispatcher;
using GEUTEBRUECK.GeViScope.Wrapper.MediaPlayer;
using Microsoft.OpenApi.Models;
using System.Collections;
var builder = WebApplication.CreateBuilder(args);
// Add Swagger services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "GeViScope Bridge API",
Version = "v1",
Description = "REST API bridge for Geutebruck GeViScope Camera Server SDK. Provides access to camera control, video routing, PTZ, and action/event handling."
});
});
// GeViScope connection state
GscServer? gscServer = null;
GscPLCWrapper? gscPLC = null;
GscActionDispatcher? actionDispatcher = null;
string? currentAddress = null;
string? currentUsername = null;
List<string> receivedMessages = new List<string>();
List<MediaChannelInfo> mediaChannels = new List<MediaChannelInfo>();
var app = builder.Build();
// Action dispatcher event handlers
void OnCustomAction(object? sender, GscAct_CustomActionEventArgs e)
{
var msg = $"[{DateTime.Now:HH:mm:ss}] CustomAction({e.aInt}, \"{e.aString}\")";
Console.WriteLine(msg);
receivedMessages.Add(msg);
}
void OnDigitalInput(object? sender, GscAct_DigitalInputEventArgs e)
{
var msg = $"[{DateTime.Now:HH:mm:ss}] DigitalInput(GlobalNo={e.aContact.GlobalNo})";
Console.WriteLine(msg);
receivedMessages.Add(msg);
}
// PLC callback handler
void OnPLCCallback(object? sender, PLCCallbackEventArgs e)
{
try
{
if (e.PlcNotification.GetNotificationType() == GscPlcNotificationType.plcnNewActionData)
{
var action = e.PlcNotification.GetAction();
if (action != null && actionDispatcher != null)
{
if (!actionDispatcher.Dispatch(action))
{
var msg = $"[{DateTime.Now:HH:mm:ss}] Action: {action}";
Console.WriteLine(msg);
receivedMessages.Add(msg);
}
}
}
else if (e.PlcNotification.GetNotificationType() == GscPlcNotificationType.plcnNewEventData)
{
var eventData = e.PlcNotification.GetEventData();
if (eventData != null)
{
var eventType = eventData.EventNotificationType switch
{
GscPlcEventNotificationType.plcenEventStarted => "started",
GscPlcEventNotificationType.plcenEventStopped => "stopped",
GscPlcEventNotificationType.plcenEventRetriggered => "retriggered",
_ => "unknown"
};
var msg = $"[{DateTime.Now:HH:mm:ss}] Event: {eventData.EventHeader.EventName} {eventData.EventHeader.EventID} {eventType}";
Console.WriteLine(msg);
receivedMessages.Add(msg);
}
}
else if (e.PlcNotification.GetNotificationType() == GscPlcNotificationType.plcnPushCallbackLost)
{
var msg = $"[{DateTime.Now:HH:mm:ss}] Connection lost!";
Console.WriteLine(msg);
receivedMessages.Add(msg);
}
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PLC Callback error: {ex.Message}");
}
}
// Helper function to create PLC and register callbacks
void CreatePLC()
{
if (gscServer == null) return;
gscPLC = gscServer.CreatePLC();
gscPLC.PLCCallback += OnPLCCallback;
gscPLC.OpenPushCallback();
actionDispatcher = new GscActionDispatcher();
actionDispatcher.OnCustomAction += OnCustomAction;
actionDispatcher.OnDigitalInput += OnDigitalInput;
gscPLC.SubscribeActionsAll();
gscPLC.SubscribeEventsAll();
}
// Helper function to destroy PLC
void DestroyPLC(bool connectionLost = false)
{
if (gscPLC != null)
{
if (!connectionLost)
{
gscPLC.UnsubscribeAll();
gscPLC.CloseCallback();
}
if (actionDispatcher != null)
{
actionDispatcher.Dispose();
actionDispatcher = null;
}
gscPLC.Dispose();
gscPLC = null;
}
}
// Helper function to load media channels
void LoadMediaChannels()
{
mediaChannels.Clear();
if (gscServer == null)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] LoadMediaChannels: gscServer is null");
return;
}
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] LoadMediaChannels: Starting channel query...");
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Server connected: {gscServer.IsConnected}");
try
{
var channelList = new ArrayList();
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Calling MediaPlayerHelperFunctions.QueryMediaChannelList...");
MediaPlayerHelperFunctions.QueryMediaChannelList(gscServer, out channelList);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] QueryMediaChannelList returned, channelList is null: {channelList == null}");
if (channelList != null)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Found {channelList.Count} total channels from server");
foreach (GscMediaChannelData channel in channelList)
{
// Include ALL channels (both active and inactive)
mediaChannels.Add(new MediaChannelInfo
{
ChannelID = channel.ChannelID,
GlobalNumber = channel.GlobalNumber,
Name = channel.Name,
Description = channel.Desc,
IsActive = channel.IsActive
});
Console.WriteLine($" - Channel {channel.ChannelID}: {channel.Name} (Active: {channel.IsActive})");
}
}
else
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] channelList is NULL!");
}
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Error loading media channels: {ex.Message}");
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Stack trace: {ex.StackTrace}");
}
}
// ============================================================================
// API ENDPOINTS
// ============================================================================
// Connection endpoint
app.MapPost("/connect", (ConnectRequest request) =>
{
try
{
// Disconnect existing connection
if (gscServer != null)
{
DestroyPLC();
gscServer.Disconnect(5000);
gscServer.Dispose();
gscServer = null;
}
// Create new connection
gscServer = new GscServer();
// Encode password
var encodedPassword = DBIHelperFunctions.EncodePassword(request.Password);
// Set connection parameters
using var connectParams = new GscServerConnectParams(request.Address, request.Username, encodedPassword);
gscServer.SetConnectParams(connectParams);
// Connect
var result = gscServer.Connect();
if (result == GscServerConnectResult.connectOk)
{
currentAddress = request.Address;
currentUsername = request.Username;
// Create PLC for actions/events
CreatePLC();
// Load media channels
LoadMediaChannels();
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Connected to GeViScope at {request.Address}");
return Results.Ok(new
{
success = true,
message = "Connected to GeViScope",
address = request.Address,
username = request.Username,
channelCount = mediaChannels.Count,
connected_at = DateTime.UtcNow
});
}
else
{
return Results.BadRequest(new
{
success = false,
error = "Connection failed",
message = result.ToString()
});
}
}
catch (Exception ex)
{
return Results.BadRequest(new
{
success = false,
error = "Connection error",
message = ex.Message,
stack_trace = ex.StackTrace
});
}
});
// Disconnect endpoint
app.MapPost("/disconnect", () =>
{
try
{
DestroyPLC();
if (gscServer != null)
{
gscServer.Disconnect(5000);
gscServer.Dispose();
gscServer = null;
}
currentAddress = null;
currentUsername = null;
mediaChannels.Clear();
return Results.Ok(new
{
success = true,
message = "Disconnected successfully"
});
}
catch (Exception ex)
{
return Results.BadRequest(new
{
success = false,
error = "Disconnect error",
message = ex.Message
});
}
});
// Status endpoint
app.MapGet("/status", () =>
{
return Results.Ok(new
{
is_connected = gscServer != null,
address = currentAddress,
username = currentUsername,
channel_count = mediaChannels.Count
});
});
// Get media channels
app.MapGet("/channels", () =>
{
if (gscServer == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
return Results.Ok(new
{
count = mediaChannels.Count,
channels = mediaChannels
});
});
// Refresh media channels
app.MapPost("/channels/refresh", () =>
{
if (gscServer == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
LoadMediaChannels();
return Results.Ok(new
{
count = mediaChannels.Count,
channels = mediaChannels
});
});
// Send action (generic)
app.MapPost("/action", (ActionRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Sending action: {request.Action}");
var action = GscAction.Decode(request.Action);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {request.Action}";
receivedMessages.Add(logMsg);
return Results.Ok(new
{
success = true,
message = "Action sent",
action = request.Action
});
}
else
{
return Results.BadRequest(new
{
success = false,
error = "Invalid action",
message = "Could not decode action string"
});
}
}
catch (Exception ex)
{
return Results.BadRequest(new
{
success = false,
error = "Action error",
message = ex.Message
});
}
});
// Send CustomAction
app.MapPost("/custom-action", (CustomActionRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var action = new GscAct_CustomAction(request.TypeId, request.Text ?? "");
gscPLC.SendAction(action);
action.Dispose();
var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: CustomAction({request.TypeId}, \"{request.Text}\")";
receivedMessages.Add(logMsg);
return Results.Ok(new
{
success = true,
message = "CustomAction sent",
type_id = request.TypeId,
text = request.Text
});
}
catch (Exception ex)
{
return Results.BadRequest(new
{
success = false,
error = "CustomAction error",
message = ex.Message
});
}
});
// CrossSwitch - video routing
app.MapPost("/crossswitch", (CrossSwitchRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var actionStr = $"CrossSwitch({request.VideoInput}, {request.VideoOutput}, {request.SwitchMode})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
receivedMessages.Add(logMsg);
return Results.Ok(new
{
success = true,
message = "CrossSwitch sent",
video_input = request.VideoInput,
video_output = request.VideoOutput,
switch_mode = request.SwitchMode
});
}
else
{
return Results.BadRequest(new { success = false, error = "Failed to create CrossSwitch action" });
}
}
catch (Exception ex)
{
return Results.BadRequest(new
{
success = false,
error = "CrossSwitch error",
message = ex.Message
});
}
});
// PTZ Camera Control - Pan
app.MapPost("/camera/pan", (CameraPanRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var direction = request.Direction.ToLower() == "left" ? "Left" : "Right";
var actionStr = $"CameraPan{direction}({request.Camera}, {request.Speed})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = $"Camera pan {direction}",
camera = request.Camera,
speed = request.Speed
});
}
return Results.BadRequest(new { error = "Failed to create pan action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// PTZ Camera Control - Tilt
app.MapPost("/camera/tilt", (CameraTiltRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var direction = request.Direction.ToLower() == "up" ? "Up" : "Down";
var actionStr = $"CameraTilt{direction}({request.Camera}, {request.Speed})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = $"Camera tilt {direction}",
camera = request.Camera,
speed = request.Speed
});
}
return Results.BadRequest(new { error = "Failed to create tilt action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// PTZ Camera Control - Zoom
app.MapPost("/camera/zoom", (CameraZoomRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var direction = request.Direction.ToLower() == "in" ? "In" : "Out";
var actionStr = $"CameraZoom{direction}({request.Camera}, {request.Speed})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = $"Camera zoom {direction}",
camera = request.Camera,
speed = request.Speed
});
}
return Results.BadRequest(new { error = "Failed to create zoom action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// PTZ Camera Control - Stop all movement
app.MapPost("/camera/stop", (CameraStopRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var actionStr = $"CameraStopAll({request.Camera})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = "Camera stopped",
camera = request.Camera
});
}
return Results.BadRequest(new { error = "Failed to create stop action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// PTZ Camera Control - Go to preset
app.MapPost("/camera/preset", (CameraPresetRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var actionStr = $"CameraGotoPreset({request.Camera}, {request.Preset})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = $"Camera going to preset {request.Preset}",
camera = request.Camera,
preset = request.Preset
});
}
return Results.BadRequest(new { error = "Failed to create preset action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// Digital Output - Close contact
app.MapPost("/digital-io/close", (DigitalContactRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var actionStr = $"CloseDigitalOutput({request.ContactId})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = "Digital output closed",
contact_id = request.ContactId
});
}
return Results.BadRequest(new { error = "Failed to create action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// Digital Output - Open contact
app.MapPost("/digital-io/open", (DigitalContactRequest request) =>
{
try
{
if (gscServer == null || gscPLC == null)
{
return Results.BadRequest(new { error = "Not connected" });
}
var actionStr = $"OpenDigitalOutput({request.ContactId})";
var action = GscAction.Decode(actionStr);
if (action != null)
{
gscPLC.SendAction(action);
action.Dispose();
return Results.Ok(new
{
success = true,
message = "Digital output opened",
contact_id = request.ContactId
});
}
return Results.BadRequest(new { error = "Failed to create action" });
}
catch (Exception ex)
{
return Results.BadRequest(new { error = ex.Message });
}
});
// Get message log
app.MapGet("/messages", () =>
{
return Results.Ok(new
{
count = receivedMessages.Count,
messages = receivedMessages.TakeLast(100).ToList()
});
});
// Clear message log
app.MapPost("/messages/clear", () =>
{
receivedMessages.Clear();
return Results.Ok(new { message = "Message log cleared" });
});
Console.WriteLine("========================================");
Console.WriteLine("GeViScope Bridge starting on port 7720");
Console.WriteLine("========================================");
app.Run("http://localhost:7720");
// Request/Response Models
record ConnectRequest(string Address, string Username, string Password);
record ActionRequest(string Action);
record CustomActionRequest(int TypeId, string? Text);
record CrossSwitchRequest(int VideoInput, int VideoOutput, int SwitchMode = 0);
record CameraPanRequest(int Camera, string Direction, int Speed = 50);
record CameraTiltRequest(int Camera, string Direction, int Speed = 50);
record CameraZoomRequest(int Camera, string Direction, int Speed = 50);
record CameraStopRequest(int Camera);
record CameraPresetRequest(int Camera, int Preset);
record DigitalContactRequest(int ContactId);
class MediaChannelInfo
{
public long ChannelID { get; set; }
public long GlobalNumber { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public bool IsActive { get; set; }
}