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 receivedMessages = new List(); List mediaChannels = new List(); 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; } }