虚拟探测器设备
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
using XP.Hardware.Detector.Abstractions.Enums;
|
||||||
|
|
||||||
|
namespace XP.Hardware.Detector.Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 软件模拟探测器配置 | Simulated detector configuration
|
||||||
|
/// </summary>
|
||||||
|
public class SimulatedDetectorConfig : DetectorConfig
|
||||||
|
{
|
||||||
|
public SimulatedDetectorConfig()
|
||||||
|
{
|
||||||
|
Type = DetectorType.Simulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 合成帧宽度(像素)| Synthetic frame width (pixels)
|
||||||
|
/// </summary>
|
||||||
|
public int Width { get; set; } = 256;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 合成帧高度(像素)| Synthetic frame height (pixels)
|
||||||
|
/// </summary>
|
||||||
|
public int Height { get; set; } = 256;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟帧率(帧/秒)| Simulated frame rate (fps)
|
||||||
|
/// </summary>
|
||||||
|
public double FrameRateFps { get; set; } = 5.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Prism.Events;
|
||||||
|
using XP.Hardware.Detector.Abstractions;
|
||||||
|
using XP.Hardware.Detector.Abstractions.Enums;
|
||||||
|
using XP.Hardware.Detector.Config;
|
||||||
|
using XP.Common.Logging.Interfaces;
|
||||||
|
|
||||||
|
namespace XP.Hardware.Detector.Implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 软件模拟探测器 | Software simulated detector
|
||||||
|
/// 无需真实硬件,定时发布合成的 16-bit 灰度帧,用于集成链路验证。
|
||||||
|
/// 帧内容:渐变灰度 + 帧号水印,便于肉眼确认帧序列。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SimulatedDetector : AreaDetectorBase
|
||||||
|
{
|
||||||
|
private readonly SimulatedDetectorConfig _config;
|
||||||
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
|
private CancellationTokenSource _acquisitionCts;
|
||||||
|
private Task _acquisitionTask;
|
||||||
|
private int _frameNumber;
|
||||||
|
|
||||||
|
public override DetectorType Type => DetectorType.Simulated;
|
||||||
|
|
||||||
|
public SimulatedDetector(SimulatedDetectorConfig config, IEventAggregator eventAggregator, ILoggerService logger = null)
|
||||||
|
: base(eventAggregator)
|
||||||
|
{
|
||||||
|
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||||
|
_logger = logger?.ForModule("SimulatedDetector");
|
||||||
|
_logger?.Info("[SimulatedDetector] 实例已创建,分辨率 {W}x{H},帧率 {FPS} fps",
|
||||||
|
_config.Width, _config.Height, _config.FrameRateFps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 模板方法实现 ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> InitializeInternalAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger?.Info("[SimulatedDetector] 初始化完成(无硬件操作)");
|
||||||
|
return Task.FromResult(DetectorResult.Success("模拟探测器初始化成功"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> StartAcquisitionInternalAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_acquisitionCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
_acquisitionTask = Task.Run(() => AcquisitionLoopAsync(_acquisitionCts.Token), _acquisitionCts.Token);
|
||||||
|
_logger?.Info("[SimulatedDetector] 连续采集已启动");
|
||||||
|
return Task.FromResult(DetectorResult.Success("模拟采集已启动"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<DetectorResult> StopAcquisitionInternalAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_acquisitionCts?.Cancel();
|
||||||
|
if (_acquisitionTask != null)
|
||||||
|
{
|
||||||
|
try { await _acquisitionTask.ConfigureAwait(false); }
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
_acquisitionCts?.Dispose();
|
||||||
|
_acquisitionCts = null;
|
||||||
|
_acquisitionTask = null;
|
||||||
|
_logger?.Info("[SimulatedDetector] 连续采集已停止");
|
||||||
|
return DetectorResult.Success("模拟采集已停止");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> AcquireSingleFrameInternalAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
PublishSyntheticFrame();
|
||||||
|
_logger?.Info("[SimulatedDetector] 单帧采集完成,帧号 {N}", _frameNumber);
|
||||||
|
return Task.FromResult(DetectorResult.Success("单帧采集成功"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> DarkCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
|
||||||
|
=> Task.FromResult(DetectorResult.Success("模拟暗场校正完成"));
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> GainCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
|
||||||
|
=> Task.FromResult(DetectorResult.Success("模拟亮场校正完成"));
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> AutoCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
|
||||||
|
=> Task.FromResult(DetectorResult.Success("模拟自动校正完成"));
|
||||||
|
|
||||||
|
protected override Task<DetectorResult> BadPixelCorrectionInternalAsync(CancellationToken cancellationToken)
|
||||||
|
=> Task.FromResult(DetectorResult.Success("模拟坏像素校正完成"));
|
||||||
|
|
||||||
|
public override DetectorInfo GetInfo() => new DetectorInfo
|
||||||
|
{
|
||||||
|
Type = DetectorType.Simulated,
|
||||||
|
Model = "SimulatedDetector",
|
||||||
|
SerialNumber = "SIM-0001",
|
||||||
|
FirmwareVersion = "1.0.0",
|
||||||
|
MaxWidth = (uint)_config.Width,
|
||||||
|
MaxHeight = (uint)_config.Height,
|
||||||
|
PixelSize = 0.139,
|
||||||
|
BitDepth = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── 内部实现 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private async Task AcquisitionLoopAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
var intervalMs = (int)(1000.0 / Math.Max(1, _config.FrameRateFps));
|
||||||
|
_logger?.Info("[SimulatedDetector] 采集循环启动,间隔 {Interval} ms", intervalMs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
PublishSyntheticFrame();
|
||||||
|
await Task.Delay(intervalMs, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// 正常退出
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Error(ex, "[SimulatedDetector] 采集循环异常退出");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger?.Info("[SimulatedDetector] 采集循环已退出");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成并发布一帧合成图像。
|
||||||
|
/// 内容:水平渐变灰度(0–65535),每行叠加帧号偏移,形成滚动条纹效果。
|
||||||
|
/// </summary>
|
||||||
|
private void PublishSyntheticFrame()
|
||||||
|
{
|
||||||
|
int n = Interlocked.Increment(ref _frameNumber);
|
||||||
|
int w = _config.Width;
|
||||||
|
int h = _config.Height;
|
||||||
|
|
||||||
|
var pixels = new ushort[w * h];
|
||||||
|
int offset = (n * 64) % 65536; // 每帧偏移,产生滚动效果
|
||||||
|
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
// 水平渐变 + 帧偏移
|
||||||
|
int value = (int)((double)x / w * 65535) + offset;
|
||||||
|
pixels[y * w + x] = (ushort)(value & 0xFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = new ImageCapturedEventArgs
|
||||||
|
{
|
||||||
|
ImageData = pixels,
|
||||||
|
Width = (uint)w,
|
||||||
|
Height = (uint)h,
|
||||||
|
FrameNumber = n,
|
||||||
|
CaptureTime = DateTime.Now,
|
||||||
|
ExposureTime = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger?.Info("[SimulatedDetector] 发布合成帧 #{N},分辨率 {W}x{H}", n, w, h);
|
||||||
|
PublishImageCaptured(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_acquisitionCts?.Cancel();
|
||||||
|
_acquisitionCts?.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,10 +19,15 @@ namespace XplorePlane.ViewModels.Setting
|
|||||||
{
|
{
|
||||||
_logger = logger?.ForModule<SettingsViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger?.ForModule<SettingsViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
|
_logger.Info("SettingsViewModel 构造函数被调用 | SettingsViewModel constructor called");
|
||||||
|
|
||||||
SaveCommand = new DelegateCommand(ExecuteSave);
|
SaveCommand = new DelegateCommand(ExecuteSave);
|
||||||
CancelCommand = new DelegateCommand(ExecuteCancel);
|
CancelCommand = new DelegateCommand(ExecuteCancel);
|
||||||
ResetToDefaultCommand = new DelegateCommand(ExecuteResetToDefault);
|
ResetToDefaultCommand = new DelegateCommand(ExecuteResetToDefault);
|
||||||
|
|
||||||
|
_logger.Debug("Commands initialized: SaveCommand={SaveCommand}, CancelCommand={CancelCommand}, ResetToDefaultCommand={ResetToDefaultCommand}",
|
||||||
|
SaveCommand != null, CancelCommand != null, ResetToDefaultCommand != null);
|
||||||
|
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,8 +266,11 @@ namespace XplorePlane.ViewModels.Setting
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_logger.Info("Loading settings from App.config");
|
||||||
|
|
||||||
// 语言配置
|
// 语言配置
|
||||||
Language = GetAppSetting("Language", "ZhCN");
|
Language = GetAppSetting("Language", "ZhCN");
|
||||||
|
_logger.Debug("Loaded Language: {Language}", Language);
|
||||||
|
|
||||||
// Serilog日志配置
|
// Serilog日志配置
|
||||||
SerilogLogPath = GetAppSetting("Serilog:LogPath", "D:\\XplorePlane\\Logs");
|
SerilogLogPath = GetAppSetting("Serilog:LogPath", "D:\\XplorePlane\\Logs");
|
||||||
@@ -317,9 +325,13 @@ namespace XplorePlane.ViewModels.Setting
|
|||||||
|
|
||||||
private void ExecuteSave()
|
private void ExecuteSave()
|
||||||
{
|
{
|
||||||
|
_logger.Info("ExecuteSave 方法被调用 | ExecuteSave method called");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
||||||
|
|
||||||
|
_logger.Info("Saving configuration to: {ConfigFilePath}", config.FilePath);
|
||||||
|
|
||||||
// 语言配置
|
// 语言配置
|
||||||
SetAppSetting(config, "Language", Language);
|
SetAppSetting(config, "Language", Language);
|
||||||
@@ -369,13 +381,13 @@ namespace XplorePlane.ViewModels.Setting
|
|||||||
config.Save(ConfigurationSaveMode.Modified);
|
config.Save(ConfigurationSaveMode.Modified);
|
||||||
ConfigurationManager.RefreshSection("appSettings");
|
ConfigurationManager.RefreshSection("appSettings");
|
||||||
|
|
||||||
_logger.Info("Settings saved successfully");
|
_logger.Info("Settings saved successfully to: {ConfigFilePath}", config.FilePath);
|
||||||
MessageBox.Show("配置已保存,部分设置需要重启应用程序后生效。", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
MessageBox.Show($"配置已保存到:\n{config.FilePath}\n\n部分设置需要重启应用程序后生效。", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Failed to save settings");
|
_logger.Error(ex, "Failed to save settings");
|
||||||
MessageBox.Show($"保存配置失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
MessageBox.Show($"保存配置失败: {ex.Message}\n\n详细信息:\n{ex.StackTrace}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user