448 lines
16 KiB
C#
448 lines
16 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||
using OrpaonVision.SiteApp.Runtime.Services;
|
||
using System.Collections.Concurrent;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 海康相机服务模拟实现。
|
||
/// </summary>
|
||
public sealed class MockHikCameraService : IHikCameraService, IDisposable
|
||
{
|
||
private readonly ILogger<MockHikCameraService> _logger;
|
||
private readonly HikCameraOptions _options;
|
||
private readonly object _lockObject = new();
|
||
private HikCameraDevice? _currentDevice;
|
||
private bool _isConnected;
|
||
private bool _isGrabbing;
|
||
private Timer? _simulationTimer;
|
||
private readonly ConcurrentQueue<byte[]> _frameQueue = new();
|
||
private int _frameCounter;
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public MockHikCameraService(ILogger<MockHikCameraService> logger, IOptions<HikCameraOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
_logger.LogInformation("海康相机服务已初始化(模拟模式)");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<IReadOnlyList<HikCameraDevice>> EnumerateDevices()
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("正在枚举海康相机设备(模拟模式)...");
|
||
|
||
// 模拟设备枚举
|
||
var devices = new List<HikCameraDevice>
|
||
{
|
||
new()
|
||
{
|
||
SerialNumber = "MOCK_HK_001",
|
||
DeviceName = "模拟海康相机1",
|
||
IpAddress = "192.168.1.100",
|
||
SubnetMask = "255.255.255.0",
|
||
DefaultGateway = "192.168.1.1",
|
||
MacAddress = "00:11:22:33:44:55",
|
||
FirmwareVersion = "V2.1.0",
|
||
IsConnected = false
|
||
},
|
||
new()
|
||
{
|
||
SerialNumber = "MOCK_HK_002",
|
||
DeviceName = "模拟海康相机2",
|
||
IpAddress = "192.168.1.101",
|
||
SubnetMask = "255.255.255.0",
|
||
DefaultGateway = "192.168.1.1",
|
||
MacAddress = "00:11:22:33:44:66",
|
||
FirmwareVersion = "V2.1.0",
|
||
IsConnected = false
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("发现 {Count} 个海康相机设备(模拟)", devices.Count);
|
||
return Result<IReadOnlyList<HikCameraDevice>>.Success(devices, message: "设备枚举成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "枚举海康相机设备失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_ENUM_FAILED", "枚举相机设备失败。", traceId);
|
||
return Result<IReadOnlyList<HikCameraDevice>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, [.. result.Errors]);
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result ConnectToDevice(HikCameraDevice device)
|
||
{
|
||
if (device == null)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_DEVICE_NULL", "相机设备不能为空。");
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_ALREADY_CONNECTED", "相机已连接,请先断开。");
|
||
}
|
||
|
||
_logger.LogInformation("正在连接相机设备: {SerialNumber}(模拟)", device.SerialNumber);
|
||
|
||
// 模拟连接过程
|
||
Thread.Sleep(500);
|
||
|
||
_currentDevice = new HikCameraDevice
|
||
{
|
||
SerialNumber = device.SerialNumber,
|
||
DeviceName = device.DeviceName,
|
||
IpAddress = device.IpAddress,
|
||
SubnetMask = device.SubnetMask,
|
||
DefaultGateway = device.DefaultGateway,
|
||
MacAddress = device.MacAddress,
|
||
FirmwareVersion = device.FirmwareVersion,
|
||
IsConnected = true,
|
||
ConnectedAt = DateTime.UtcNow
|
||
};
|
||
_isConnected = true;
|
||
|
||
_logger.LogInformation("相机连接成功: {SerialNumber}(模拟)", device.SerialNumber);
|
||
OnConnectionStateChanged?.Invoke(true, $"已连接到 {device.DeviceName}(模拟)");
|
||
|
||
return Result.Success(message: $"相机 {device.DeviceName} 连接成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "连接相机失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_CONNECT_FAILED", "连接相机失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result ConnectToDevice(string serialNumberOrIp)
|
||
{
|
||
var devicesResult = EnumerateDevices();
|
||
if (!devicesResult.Succeeded)
|
||
{
|
||
return Result.Fail(devicesResult.Code, devicesResult.Message, devicesResult.Errors.ToArray());
|
||
}
|
||
|
||
var targetDevice = devicesResult.Data.FirstOrDefault(d =>
|
||
d.SerialNumber.Equals(serialNumberOrIp, StringComparison.OrdinalIgnoreCase) ||
|
||
d.IpAddress.Equals(serialNumberOrIp, StringComparison.OrdinalIgnoreCase));
|
||
|
||
if (targetDevice == null)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_FOUND", $"未找到序列号或IP为 {serialNumberOrIp} 的相机设备。");
|
||
}
|
||
|
||
return ConnectToDevice(targetDevice);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result Disconnect()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Success(message: "相机未连接。");
|
||
}
|
||
|
||
_logger.LogInformation("正在断开相机连接(模拟)...");
|
||
|
||
// 停止采集
|
||
if (_isGrabbing)
|
||
{
|
||
StopGrabbing();
|
||
}
|
||
|
||
// 模拟断开过程
|
||
Thread.Sleep(100);
|
||
|
||
var deviceName = _currentDevice?.DeviceName ?? "未知设备";
|
||
_currentDevice = null;
|
||
_isConnected = false;
|
||
|
||
_logger.LogInformation("相机断开连接成功(模拟)");
|
||
OnConnectionStateChanged?.Invoke(false, $"已断开与 {deviceName} 的连接(模拟)");
|
||
|
||
return Result.Success(message: "相机断开连接成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "断开相机连接失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_DISCONNECT_FAILED", "断开相机连接失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public bool IsConnected => _isConnected;
|
||
|
||
/// <inheritdoc />
|
||
public HikCameraDevice? CurrentDevice => _currentDevice;
|
||
|
||
/// <inheritdoc />
|
||
public Result SetCameraParameters(int width, int height, string pixelFormat, int triggerMode = 0)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_CONNECTED", "相机未连接。");
|
||
}
|
||
|
||
_logger.LogInformation("正在设置相机参数: {Width}x{Height}, {PixelFormat}, TriggerMode: {TriggerMode}(模拟)",
|
||
width, height, pixelFormat, triggerMode);
|
||
|
||
// 模拟参数设置
|
||
Thread.Sleep(100);
|
||
|
||
_logger.LogInformation("相机参数设置成功(模拟)");
|
||
return Result.Success(message: "相机参数设置成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "设置相机参数失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_SET_PARAMS_FAILED", "设置相机参数失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result StartGrabbing()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_CONNECTED", "相机未连接。");
|
||
}
|
||
|
||
if (_isGrabbing)
|
||
{
|
||
return Result.Success(message: "相机已在采集中。");
|
||
}
|
||
|
||
_logger.LogInformation("正在开始图像采集(模拟)...");
|
||
|
||
// 模拟开始采集
|
||
Thread.Sleep(100);
|
||
|
||
_isGrabbing = true;
|
||
_frameQueue.Clear();
|
||
|
||
// 启动模拟定时器生成图像帧
|
||
_simulationTimer = new Timer(GenerateSimulationFrame, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(33)); // 约30fps
|
||
|
||
_logger.LogInformation("图像采集已启动(模拟)");
|
||
return Result.Success(message: "图像采集启动成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "启动图像采集失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_START_GRAB_FAILED", "启动图像采集失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result StopGrabbing()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isGrabbing)
|
||
{
|
||
return Result.Success(message: "相机未在采集状态。");
|
||
}
|
||
|
||
_logger.LogInformation("正在停止图像采集(模拟)...");
|
||
|
||
// 停止模拟定时器
|
||
_simulationTimer?.Dispose();
|
||
_simulationTimer = null;
|
||
|
||
// 模拟停止采集
|
||
Thread.Sleep(50);
|
||
|
||
_isGrabbing = false;
|
||
_frameQueue.Clear();
|
||
|
||
_logger.LogInformation("图像采集已停止(模拟)");
|
||
return Result.Success(message: "图像采集停止成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "停止图像采集失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_STOP_GRAB_FAILED", "停止图像采集失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result TriggerSoftware()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_CONNECTED", "相机未连接。");
|
||
}
|
||
|
||
if (!_isGrabbing)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_GRABBING", "相机未在采集状态。");
|
||
}
|
||
|
||
// 模拟软触发
|
||
_logger.LogDebug("执行软件触发(模拟)");
|
||
|
||
return Result.Success(message: "软件触发执行成功(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "软件触发失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_TRIGGER_FAILED", "软件触发失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<byte[]> GetLatestFrame()
|
||
{
|
||
try
|
||
{
|
||
if (!_isGrabbing)
|
||
{
|
||
return Result<byte[]>.Fail("HIK_CAMERA_NOT_GRABBING", "相机未在采集状态。");
|
||
}
|
||
|
||
if (_frameQueue.TryDequeue(out var frame))
|
||
{
|
||
return Result<byte[]>.Success(frame, message: "获取图像帧成功(模拟)。");
|
||
}
|
||
|
||
return Result<byte[]>.Fail("HIK_CAMERA_NO_FRAME", "暂无可用图像帧(模拟)。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取图像帧失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_GET_FRAME_FAILED", "获取图像帧失败。", traceId);
|
||
return Result<byte[]>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<CameraFrameDto> GetLatestFrameWithTimestamp()
|
||
{
|
||
var frameResult = GetLatestFrame();
|
||
if (!frameResult.Succeeded)
|
||
{
|
||
return Result<CameraFrameDto>.Fail(frameResult.Code, frameResult.Message, frameResult.Errors.ToArray());
|
||
}
|
||
|
||
return Result<CameraFrameDto>.Success(new CameraFrameDto
|
||
{
|
||
Timestamp = DateTime.UtcNow,
|
||
ImageData = frameResult.Data,
|
||
Width = _options.ImageWidth,
|
||
Height = _options.ImageHeight,
|
||
PixelFormat = _options.PixelFormat
|
||
}, message: "获取图像帧成功(模拟)。");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public event Action<byte[], DateTime>? OnImageReceived;
|
||
|
||
/// <inheritdoc />
|
||
public event Action<bool, string>? OnConnectionStateChanged;
|
||
|
||
/// <summary>
|
||
/// 生成模拟图像帧。
|
||
/// </summary>
|
||
private void GenerateSimulationFrame(object? state)
|
||
{
|
||
try
|
||
{
|
||
// 生成模拟图像数据(BGR格式)
|
||
var width = _options.ImageWidth;
|
||
var height = _options.ImageHeight;
|
||
var frameSize = width * height * 3; // BGR8Packed
|
||
var frameData = new byte[frameSize];
|
||
|
||
// 填充模拟图像数据(简单的渐变图案)
|
||
var random = new Random(_frameCounter++);
|
||
for (int i = 0; i < frameSize; i += 3)
|
||
{
|
||
frameData[i] = (byte)(i % 256); // B
|
||
frameData[i + 1] = (byte)((i / 3) % 256); // G
|
||
frameData[i + 2] = (byte)random.Next(256); // R
|
||
}
|
||
|
||
var timestamp = DateTime.UtcNow;
|
||
|
||
// 添加到队列
|
||
if (_frameQueue.Count < 10) // 限制队列大小防止内存溢出
|
||
{
|
||
_frameQueue.Enqueue(frameData);
|
||
}
|
||
|
||
// 触发事件
|
||
OnImageReceived?.Invoke(frameData, timestamp);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "生成模拟图像帧失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源。
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
Disconnect();
|
||
_simulationTimer?.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "释放海康相机服务资源时发生异常");
|
||
}
|
||
}
|
||
}
|