Files
geutebruck-api/src/sdk-bridge/GeViScopeBridge/Services/MonitorService.cs
Geutebruck API Developer 48fafae9d2 Phase 2 Complete: SDK Bridge Foundation (T011-T026)
Implemented complete C# gRPC service wrapping GeViScope SDK:

 gRPC Protocol Definitions (T011-T014):
- common.proto: Status, Error, Timestamp messages
- camera.proto: CameraService with ListCameras, GetCamera RPCs
- monitor.proto: MonitorService with ListMonitors, GetMonitor RPCs
- crossswitch.proto: CrossSwitchService with ExecuteCrossSwitch, ClearMonitor, GetRoutingState, HealthCheck RPCs

 SDK Wrapper Classes (T015-T021):
- GeViDatabaseWrapper.cs: Connection lifecycle with retry logic (3 attempts, exponential backoff)
- StateQueryHandler.cs: GetFirst/GetNext enumeration pattern for cameras/monitors
- ActionDispatcher.cs: CrossSwitch and ClearVideoOutput action execution
- ErrorTranslator.cs: SDK errors → gRPC status codes → HTTP status codes

 gRPC Service Implementations (T022-T026):
- CameraService.cs: List/get camera information from GeViServer
- MonitorService.cs: List/get monitor/viewer information from GeViServer
- CrossSwitchService.cs: Execute cross-switching, clear monitors, query routing state
- Program.cs: gRPC server with Serilog logging, dependency injection
- appsettings.json: GeViServer connection configuration

Key Features:
- Async/await pattern throughout
- Comprehensive error handling and logging
- In-memory routing state tracking
- Health check endpoint
- Connection retry with exponential backoff
- Proper resource disposal

Architecture:
FastAPI (Python) ←gRPC→ SDK Bridge (C# .NET 8.0) ←SDK→ GeViServer

Ready for Phase 3: Python API Foundation

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 08:38:20 +01:00

120 lines
4.2 KiB
C#

using Grpc.Core;
using GeViScopeBridge.Protos;
using GeViScopeBridge.SDK;
using GeViScopeBridge.Utils;
using Serilog;
namespace GeViScopeBridge.Services
{
/// <summary>
/// gRPC service for monitor (video output) operations
/// </summary>
public class MonitorServiceImplementation : MonitorService.MonitorServiceBase
{
private readonly StateQueryHandler _stateQuery;
private readonly ILogger _logger;
public MonitorServiceImplementation(StateQueryHandler stateQuery, ILogger logger)
{
_stateQuery = stateQuery ?? throw new ArgumentNullException(nameof(stateQuery));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// List all monitors/viewers (video outputs)
/// </summary>
public override async Task<ListMonitorsResponse> ListMonitors(
ListMonitorsRequest request,
ServerCallContext context)
{
try
{
_logger.Information("ListMonitors called");
var monitors = await _stateQuery.EnumerateMonitorsAsync();
var response = new ListMonitorsResponse
{
TotalCount = monitors.Count
};
foreach (var monitor in monitors)
{
response.Monitors.Add(new MonitorInfo
{
Id = monitor.Id,
Name = monitor.Name,
Description = monitor.Description,
IsActive = monitor.IsActive,
CurrentCameraId = monitor.CurrentCameraId,
Status = monitor.Status,
LastUpdated = new Timestamp
{
Seconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
Nanos = 0
}
});
}
_logger.Information("ListMonitors completed: {Count} monitors", monitors.Count);
return response;
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to list monitors");
throw ErrorTranslator.CreateRpcException(ex, "Failed to list monitors");
}
}
/// <summary>
/// Get detailed information about a specific monitor
/// </summary>
public override async Task<MonitorInfo> GetMonitor(
GetMonitorRequest request,
ServerCallContext context)
{
try
{
_logger.Information("GetMonitor called for monitor {MonitorId}", request.MonitorId);
// Enumerate all monitors and find the requested one
var monitors = await _stateQuery.EnumerateMonitorsAsync();
var monitor = monitors.FirstOrDefault(m => m.Id == request.MonitorId);
if (monitor == null)
{
throw new RpcException(new Status(StatusCode.NotFound,
$"Monitor with ID {request.MonitorId} not found"));
}
var response = new MonitorInfo
{
Id = monitor.Id,
Name = monitor.Name,
Description = monitor.Description,
IsActive = monitor.IsActive,
CurrentCameraId = monitor.CurrentCameraId,
Status = monitor.Status,
LastUpdated = new Timestamp
{
Seconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
Nanos = 0
}
};
_logger.Information("GetMonitor completed for monitor {MonitorId}", request.MonitorId);
return response;
}
catch (RpcException)
{
throw; // Re-throw RpcExceptions as-is
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to get monitor {MonitorId}", request.MonitorId);
throw ErrorTranslator.CreateRpcException(ex, "Failed to get monitor");
}
}
}
}