虚拟探测器与实时切换按钮的绑定

This commit is contained in:
zhengxuan.zhang
2026-05-15 16:51:21 +08:00
parent 8e9ef312ad
commit e9d388beb2
10 changed files with 153 additions and 19 deletions
BIN
View File
Binary file not shown.
@@ -19,6 +19,11 @@ namespace XP.Hardware.Detector.Abstractions.Enums
/// <summary>
/// Hamamatsu 探测器 | Hamamatsu detector (预留)
/// </summary>
Hamamatsu = 2
Hamamatsu = 2,
/// <summary>
/// 软件模拟探测器,用于集成验证(无需硬件)| Software simulated detector for integration testing (no hardware required)
/// </summary>
Simulated = 99
}
}
+34 -8
View File
@@ -38,6 +38,7 @@ namespace XP.Hardware.Detector.Config
{
DetectorType.Varex => LoadVarexConfiguration(),
DetectorType.IRay => LoadIRayConfiguration(),
DetectorType.Simulated => LoadSimulatedConfiguration(),
_ => throw new NotSupportedException($"不支持的探测器类型:{detectorType} | Unsupported detector type: {detectorType}")
};
@@ -111,6 +112,27 @@ namespace XP.Hardware.Detector.Config
return config;
}
/// <summary>
/// 加载软件模拟探测器配置 | Load simulated detector configuration
/// </summary>
private static SimulatedDetectorConfig LoadSimulatedConfiguration()
{
var config = new SimulatedDetectorConfig();
if (int.TryParse(ConfigurationManager.AppSettings["Detector:Simulated:Width"], out var w) && w > 0)
config.Width = w;
if (int.TryParse(ConfigurationManager.AppSettings["Detector:Simulated:Height"], out var h) && h > 0)
config.Height = h;
if (double.TryParse(ConfigurationManager.AppSettings["Detector:Simulated:FrameRateFps"],
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var fps) && fps > 0)
config.FrameRateFps = fps;
return config;
}
/// <summary>
/// 加载 iRay 配置 | Load iRay configuration
/// </summary>
@@ -208,16 +230,20 @@ namespace XP.Hardware.Detector.Config
/// </summary>
private static DetectorResult ValidateConfiguration(DetectorConfig config)
{
// 验证 IP 地址 | Validate IP address
if (!IPAddress.TryParse(config.IP, out _))
// 模拟探测器不需要网络连接,跳过 IP/端口验证 | Simulated detector skips IP/port validation
if (config.Type != DetectorType.Simulated)
{
return DetectorResult.Failure($"无效的 IP 地址:{config.IP} | Invalid IP address: {config.IP}");
}
// 验证 IP 地址 | Validate IP address
if (!IPAddress.TryParse(config.IP, out _))
{
return DetectorResult.Failure($"无效的 IP 地址:{config.IP} | Invalid IP address: {config.IP}");
}
// 验证端口范围 | Validate port range
if (config.Port < 1 || config.Port > 65535)
{
return DetectorResult.Failure($"无效的端口号:{config.Port},必须在 1-65535 之间 | Invalid port: {config.Port}, must be between 1-65535");
// 验证端口范围 | Validate port range
if (config.Port < 1 || config.Port > 65535)
{
return DetectorResult.Failure($"无效的端口号:{config.Port},必须在 1-65535 之间 | Invalid port: {config.Port}, must be between 1-65535");
}
}
// 验证存储路径 | Validate save path
@@ -7,7 +7,6 @@ using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Config;
using XP.Hardware.Detector.Implementations;
using XP.Common.Logging.Interfaces;
namespace XP.Hardware.Detector.Factories
{
/// <summary>
@@ -54,6 +53,7 @@ namespace XP.Hardware.Detector.Factories
DetectorType.Varex => CreateVarexDetector(config),
//DetectorType.IRay => CreateIRayDetector(config),
DetectorType.Hamamatsu => throw new NotImplementedException("Hamamatsu 探测器尚未实现 | Hamamatsu detector not implemented yet"),
DetectorType.Simulated => CreateSimulatedDetector(config),
_ => throw new NotSupportedException($"不支持的探测器类型:{config.Type} | Unsupported detector type: {config.Type}")
};
@@ -113,7 +113,21 @@ namespace XP.Hardware.Detector.Factories
/// </summary>
public IEnumerable<DetectorType> GetSupportedTypes()
{
return new[] { DetectorType.Varex, DetectorType.IRay };
return new[] { DetectorType.Varex, DetectorType.IRay, DetectorType.Simulated };
}
/// <summary>
/// 创建软件模拟探测器 | Create simulated detector
/// </summary>
private IAreaDetector CreateSimulatedDetector(DetectorConfig config)
{
var simConfig = config as SimulatedDetectorConfig
?? new SimulatedDetectorConfig();
_logger?.Info("[DetectorFactory] 创建 SimulatedDetector,分辨率 {W}x{H},帧率 {FPS} fps",
simConfig.Width, simConfig.Height, simConfig.FrameRateFps);
return new SimulatedDetector(simConfig, _eventAggregator, _logger);
}
}
}
@@ -1705,7 +1705,10 @@
"Telerik.UI.for.Wpf.NetCore.Xaml": "2024.1.408"
},
"runtime": {
"XP.Common.dll": {}
"XP.Common.dll": {
"assemblyVersion": "1.4.16.1",
"fileVersion": "1.4.16.1"
}
},
"resources": {
"en-US/XP.Common.resources.dll": {
+6 -1
View File
@@ -57,13 +57,18 @@
<!-- 探测器配置 -->
<!-- 探测器类型 | Detector Type -->
<!-- 可选值: Varex, IRay, Hamamatsu | Available values: Varex, IRay, Hamamatsu -->
<!-- 可选值: Varex, IRay, Hamamatsu, Simulated | Available values: Varex, IRay, Hamamatsu, Simulated -->
<!-- 切换为 Simulated 可在无硬件环境下验证图像采集链路 | Switch to Simulated to verify image chain without hardware -->
<add key="Detector:Type" value="Varex" />
<!-- 通用配置 | Common Configuration -->
<add key="Detector:IP" value="192.168.1.200" />
<add key="Detector:Port" value="5000" />
<add key="Detector:SavePath" value="D:\XplorePlane\DetectorImages" />
<add key="Detector:AutoSave" value="true" />
<!-- Simulated 探测器专属配置 | Simulated Detector Specific Configuration -->
<add key="Detector:Simulated:Width" value="256" />
<add key="Detector:Simulated:Height" value="256" />
<add key="Detector:Simulated:FrameRateFps" value="5" />
<!-- Varex 探测器专属配置 | Varex Detector Specific Configuration -->
<!-- Binning 模式: Bin1x1, Bin2x2, Bin4x4 | Binning mode: Bin1x1, Bin2x2, Bin4x4 -->
<add key="Detector:Varex:BinningMode" value="Bin1x1" />
+43 -1
View File
@@ -27,6 +27,7 @@ using XP.Common.Logging.Implementations;
using XP.Common.Logging.Interfaces;
using XP.Common.Module;
using XP.Hardware.Detector.Module;
using XP.Hardware.Detector.Services;
using XP.Hardware.MotionControl.Module;
using XP.Hardware.PLC;
using XP.Hardware.RaySource.Module;
@@ -298,7 +299,7 @@ namespace XplorePlane
var shell = Container.Resolve<MainWindow>();
// 主窗口加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化
shell.Loaded += (s, e) =>
shell.Loaded += async (s, e) =>
{
// [DEV] 导航相机连接已屏蔽,开发阶段跳过以加快启动速度
// TryConnectCamera();
@@ -313,6 +314,9 @@ namespace XplorePlane
Log.Error(ex, "初始化主界面探测器帧流水线失败");
}
// 若配置为模拟探测器,自动初始化并启动采集(无需用户手动操作)
await TryAutoStartSimulatedDetectorAsync();
// [DEV] 相机状态通知已屏蔽
// try
// {
@@ -333,6 +337,44 @@ namespace XplorePlane
return shell;
}
/// <summary>
/// 若 App.config 中 Detector:Type = Simulated,自动初始化并启动连续采集。
/// 真实硬件类型不受影响。
/// </summary>
private async System.Threading.Tasks.Task TryAutoStartSimulatedDetectorAsync()
{
try
{
var typeStr = System.Configuration.ConfigurationManager.AppSettings["Detector:Type"];
if (!string.Equals(typeStr, "Simulated", StringComparison.OrdinalIgnoreCase))
return;
Log.Information("[SimulatedDetector] 检测到 Simulated 类型,开始自动初始化探测器...");
var detectorService = Container.Resolve<XP.Hardware.Detector.Services.IDetectorService>();
var initResult = await detectorService.InitializeAsync();
if (!initResult.IsSuccess)
{
Log.Error("SimulatedDetector 自动初始化失败:{Msg}", initResult.ErrorMessage);
return;
}
Log.Information("[SimulatedDetector] 初始化成功,启动连续采集...");
var startResult = await detectorService.StartAcquisitionAsync();
if (!startResult.IsSuccess)
{
Log.Error("SimulatedDetector 启动采集失败:{Msg}", startResult.ErrorMessage);
return;
}
Log.Information("[SimulatedDetector] 连续采集已启动,帧将以 5 fps 持续发布");
}
catch (Exception ex)
{
Log.Error(ex, "SimulatedDetector 自动启动异常");
}
}
/// <summary>
/// 在主线程上检索并连接导航相机。
/// pylon SDK 要求在主线程(STA)上操作,不能放到后台线程。
@@ -586,20 +586,49 @@ namespace XplorePlane.Services.Cnc
private BitmapSource TryGetSourceImage()
{
var viewportImage = _mainViewportService?.LatestManualImage as BitmapSource
?? _mainViewportService?.CurrentDisplayImage as BitmapSource;
if (viewportImage != null)
return viewportImage;
// ── 优先级 1MainViewportService 中的手动图像或当前显示图像 ──
var manualImage = _mainViewportService?.LatestManualImage as BitmapSource;
if (manualImage != null)
{
_logger.ForModule<CncExecutionService>().Info(
"[图像链路] TryGetSourceImage:从 MainViewportService.LatestManualImage 获取图像,尺寸 {W}x{H}",
manualImage.PixelWidth, manualImage.PixelHeight);
return manualImage;
}
var displayImage = _mainViewportService?.CurrentDisplayImage as BitmapSource;
if (displayImage != null)
{
_logger.ForModule<CncExecutionService>().Info(
"[图像链路] TryGetSourceImage:从 MainViewportService.CurrentDisplayImage 获取图像,尺寸 {W}x{H}",
displayImage.PixelWidth, displayImage.PixelHeight);
return displayImage;
}
// ── 优先级 2AppStateService 中的原始探测器帧 ──
var detectorFrame = _appStateService?.LatestDetectorFrame;
if (detectorFrame?.ImageData == null || detectorFrame.Width <= 0 || detectorFrame.Height <= 0)
{
_logger.ForModule<CncExecutionService>().Warn(
"[图像链路] TryGetSourceImage:所有图像源均为空(MainViewport 无图像,AppState 无探测器帧),返回 null");
return null;
}
_logger.ForModule<CncExecutionService>().Info(
"[图像链路] TryGetSourceImage:从 AppStateService.LatestDetectorFrame 获取原始帧,帧号 {N},分辨率 {W}x{H},采集时间 {T}",
detectorFrame.FrameNumber, detectorFrame.Width, detectorFrame.Height,
detectorFrame.CaptureTime.ToString("HH:mm:ss.fff"));
var bitmap = XP.Common.Converters.ImageConverter.ConvertGray16ToBitmapSource(
detectorFrame.ImageData,
(int)detectorFrame.Width,
(int)detectorFrame.Height);
bitmap.Freeze();
_logger.ForModule<CncExecutionService>().Info(
"[图像链路] TryGetSourceImageGray16→BitmapSource 转换完成,输出尺寸 {W}x{H}",
bitmap.PixelWidth, bitmap.PixelHeight);
return bitmap;
}
@@ -77,6 +77,12 @@ namespace XplorePlane.Services.MainViewport
try
{
_logger.Info(
"[图像链路] DetectorFramePipeline 收到帧 #{N},分辨率 {W}x{H},采集时间 {T},当前队列深度 AcquireQ={AQ} ProcessQ={PQ}",
args.FrameNumber, args.Width, args.Height,
args.CaptureTime.ToString("HH:mm:ss.fff"),
AcquireQueueCount, ProcessQueueCount);
var rawPixels = new ushort[args.ImageData.Length];
Array.Copy(args.ImageData, rawPixels, rawPixels.Length);
@@ -99,6 +105,9 @@ namespace XplorePlane.Services.MainViewport
{
EnqueueBounded(_processQueue, frame, ProcessQueueCapacity, ref _processQueueCount);
_processSignal.Release();
_logger.Info(
"[图像链路] 帧 #{N} 已入处理队列(每 {Every} 帧采样一次),累计接收 {Total} 帧",
args.FrameNumber, ProcessEveryNFrames, sequence);
}
}
catch (Exception ex)
+2 -1
View File
@@ -127,7 +127,8 @@
Margin="4,10,4,4"
HorizontalAlignment="Center"
ToolTip="主界面实时"
IsChecked="True"
IsChecked="{Binding IsMainViewportRealtimeEnabled, Mode=TwoWay}"
IsEnabled="{Binding IsMainViewportSwitchEnabled}"
SwitchWidth="44"
SwitchHeight="24" />
<TextBlock