diff --git a/XP.Hardware.Detector/Config/SimulatedDetectorConfig.cs b/XP.Hardware.Detector/Config/SimulatedDetectorConfig.cs
new file mode 100644
index 0000000..8650451
--- /dev/null
+++ b/XP.Hardware.Detector/Config/SimulatedDetectorConfig.cs
@@ -0,0 +1,30 @@
+using XP.Hardware.Detector.Abstractions.Enums;
+
+namespace XP.Hardware.Detector.Config
+{
+ ///
+ /// 软件模拟探测器配置 | Simulated detector configuration
+ ///
+ public class SimulatedDetectorConfig : DetectorConfig
+ {
+ public SimulatedDetectorConfig()
+ {
+ Type = DetectorType.Simulated;
+ }
+
+ ///
+ /// 合成帧宽度(像素)| Synthetic frame width (pixels)
+ ///
+ public int Width { get; set; } = 256;
+
+ ///
+ /// 合成帧高度(像素)| Synthetic frame height (pixels)
+ ///
+ public int Height { get; set; } = 256;
+
+ ///
+ /// 模拟帧率(帧/秒)| Simulated frame rate (fps)
+ ///
+ public double FrameRateFps { get; set; } = 5.0;
+ }
+}
diff --git a/XP.Hardware.Detector/Implementations/SimulatedDetector.cs b/XP.Hardware.Detector/Implementations/SimulatedDetector.cs
new file mode 100644
index 0000000..3c27c52
--- /dev/null
+++ b/XP.Hardware.Detector/Implementations/SimulatedDetector.cs
@@ -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
+{
+ ///
+ /// 软件模拟探测器 | Software simulated detector
+ /// 无需真实硬件,定时发布合成的 16-bit 灰度帧,用于集成链路验证。
+ /// 帧内容:渐变灰度 + 帧号水印,便于肉眼确认帧序列。
+ ///
+ 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 InitializeInternalAsync(CancellationToken cancellationToken)
+ {
+ _logger?.Info("[SimulatedDetector] 初始化完成(无硬件操作)");
+ return Task.FromResult(DetectorResult.Success("模拟探测器初始化成功"));
+ }
+
+ protected override Task 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 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 AcquireSingleFrameInternalAsync(CancellationToken cancellationToken)
+ {
+ PublishSyntheticFrame();
+ _logger?.Info("[SimulatedDetector] 单帧采集完成,帧号 {N}", _frameNumber);
+ return Task.FromResult(DetectorResult.Success("单帧采集成功"));
+ }
+
+ protected override Task DarkCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
+ => Task.FromResult(DetectorResult.Success("模拟暗场校正完成"));
+
+ protected override Task GainCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
+ => Task.FromResult(DetectorResult.Success("模拟亮场校正完成"));
+
+ protected override Task AutoCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
+ => Task.FromResult(DetectorResult.Success("模拟自动校正完成"));
+
+ protected override Task 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] 采集循环已退出");
+ }
+
+ ///
+ /// 生成并发布一帧合成图像。
+ /// 内容:水平渐变灰度(0–65535),每行叠加帧号偏移,形成滚动条纹效果。
+ ///
+ 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);
+ }
+ }
+}
diff --git a/XplorePlane/ViewModels/Setting/SettingsViewModel.cs b/XplorePlane/ViewModels/Setting/SettingsViewModel.cs
index 001609c..5b5ceeb 100644
--- a/XplorePlane/ViewModels/Setting/SettingsViewModel.cs
+++ b/XplorePlane/ViewModels/Setting/SettingsViewModel.cs
@@ -19,10 +19,15 @@ namespace XplorePlane.ViewModels.Setting
{
_logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger));
+ _logger.Info("SettingsViewModel 构造函数被调用 | SettingsViewModel constructor called");
+
SaveCommand = new DelegateCommand(ExecuteSave);
CancelCommand = new DelegateCommand(ExecuteCancel);
ResetToDefaultCommand = new DelegateCommand(ExecuteResetToDefault);
+ _logger.Debug("Commands initialized: SaveCommand={SaveCommand}, CancelCommand={CancelCommand}, ResetToDefaultCommand={ResetToDefaultCommand}",
+ SaveCommand != null, CancelCommand != null, ResetToDefaultCommand != null);
+
LoadSettings();
}
@@ -261,8 +266,11 @@ namespace XplorePlane.ViewModels.Setting
{
try
{
+ _logger.Info("Loading settings from App.config");
+
// 语言配置
Language = GetAppSetting("Language", "ZhCN");
+ _logger.Debug("Loaded Language: {Language}", Language);
// Serilog日志配置
SerilogLogPath = GetAppSetting("Serilog:LogPath", "D:\\XplorePlane\\Logs");
@@ -317,9 +325,13 @@ namespace XplorePlane.ViewModels.Setting
private void ExecuteSave()
{
+ _logger.Info("ExecuteSave 方法被调用 | ExecuteSave method called");
+
try
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
+
+ _logger.Info("Saving configuration to: {ConfigFilePath}", config.FilePath);
// 语言配置
SetAppSetting(config, "Language", Language);
@@ -369,13 +381,13 @@ namespace XplorePlane.ViewModels.Setting
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
- _logger.Info("Settings saved successfully");
- MessageBox.Show("配置已保存,部分设置需要重启应用程序后生效。", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
+ _logger.Info("Settings saved successfully to: {ConfigFilePath}", config.FilePath);
+ MessageBox.Show($"配置已保存到:\n{config.FilePath}\n\n部分设置需要重启应用程序后生效。", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
_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);
}
}