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); } }