将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
namespace XP.Hardware.RaySource.Abstractions.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源状态枚举 | X-Ray Source Status Enum
|
||||
/// </summary>
|
||||
public enum RaySourceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源不可用(未初始化)| Unavailable (Not Initialized)
|
||||
/// </summary>
|
||||
Unavailable = -1,
|
||||
|
||||
/// <summary>
|
||||
/// 射线源已关闭 | Closed
|
||||
/// </summary>
|
||||
Closed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 射线源已开启 | Opened
|
||||
/// </summary>
|
||||
Opened = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误触发事件 | Error Occurred Event
|
||||
/// 携带错误信息字符串 | Carries error message string
|
||||
/// </summary>
|
||||
public class ErrorOccurredEvent : PubSubEvent<string>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作结果数据模型 | Operation Result Data Model
|
||||
/// 携带操作名称和成功状态 | Carries operation name and success status
|
||||
/// </summary>
|
||||
public class OperationResultData
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作名称 | Operation name
|
||||
/// </summary>
|
||||
public string OperationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作是否成功 | Operation success status
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加消息 | Additional message
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
public OperationResultData(string operationName, bool isSuccess, string message = null)
|
||||
{
|
||||
OperationName = operationName;
|
||||
IsSuccess = isSuccess;
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作结果事件 | Operation Result Event
|
||||
/// 用于通知操作执行结果 | Used to notify operation execution result
|
||||
/// </summary>
|
||||
public class OperationResultEvent : PubSubEvent<OperationResultData>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Events;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源状态变更事件 | X-Ray Source Status Changed Event
|
||||
/// 携带 RaySourceStatus 枚举值,表示三态状态变更 | Carries RaySourceStatus enum for tri-state status changes
|
||||
/// </summary>
|
||||
public class RaySourceStatusChangedEvent : PubSubEvent<RaySourceStatus>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Prism.Events;
|
||||
using System;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统状态数据模型 | System Status Data Model
|
||||
/// 携带全量系统状态信息 | Carries complete system status information
|
||||
/// </summary>
|
||||
public class SystemStatusData
|
||||
{
|
||||
/// <summary>
|
||||
/// 设定电压值(kV)| Set voltage value (kV)
|
||||
/// </summary>
|
||||
public float SetVoltage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际电压值(kV)| Actual voltage value (kV)
|
||||
/// </summary>
|
||||
public float ActualVoltage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设定电流值(μA)| Set current value (μA)
|
||||
/// </summary>
|
||||
public float SetCurrent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际电流值(μA)| Actual current value (μA)
|
||||
/// </summary>
|
||||
public float ActualCurrent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 射线开启状态 | X-ray on status
|
||||
/// </summary>
|
||||
public bool IsXRayOn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 暖机状态描述 | Warm-up status description
|
||||
/// </summary>
|
||||
public string WarmUpStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 真空状态描述 | Vacuum status description
|
||||
/// </summary>
|
||||
public string VacuumStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动状态描述 | Startup status description
|
||||
/// </summary>
|
||||
public string StartUpStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自动定心状态描述 | Auto-center status description
|
||||
/// </summary>
|
||||
public string AutoCenterStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝调整状态描述 | Filament adjust status description
|
||||
/// </summary>
|
||||
public string FilamentAdjustStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连锁状态 | Interlock status
|
||||
/// </summary>
|
||||
public bool IsInterlockActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 看门狗状态 | Watchdog status
|
||||
/// </summary>
|
||||
public string WatchdogStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功率模式 | Power mode
|
||||
/// </summary>
|
||||
public string PowerMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TXI状态 | TXI status
|
||||
/// </summary>
|
||||
public string TxiStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间戳 | Timestamp
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态更新事件 | Status Updated Event
|
||||
/// 用于通知UI层系统状态变化 | Used to notify UI layer of system status changes
|
||||
/// </summary>
|
||||
public class StatusUpdatedEvent : PubSubEvent<SystemStatusData>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// PVI 变量连接状态变更事件 | PVI variables connection state changed event
|
||||
/// Payload: true = 已连接, false = 已断开 | true = connected, false = disconnected
|
||||
/// </summary>
|
||||
public class VariablesConnectedEvent : PubSubEvent<bool>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源工厂接口 | X-Ray Source Factory Interface
|
||||
/// 定义创建射线源实例的工厂方法 | Defines factory methods for creating X-ray source instances
|
||||
/// </summary>
|
||||
public interface IRaySourceFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建指定类型的射线源实例 | Create X-ray source instance of specified type
|
||||
/// </summary>
|
||||
/// <param name="sourceType">射线源类型名称 | X-ray source type name</param>
|
||||
/// <returns>射线源实例 | X-ray source instance</returns>
|
||||
IXRaySource CreateRaySource(string sourceType);
|
||||
|
||||
/// <summary>
|
||||
/// 获取支持的射线源类型列表 | Get list of supported X-ray source types
|
||||
/// </summary>
|
||||
/// <returns>类型名称列表 | List of type names</returns>
|
||||
IEnumerable<string> GetSupportedSourceTypes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源接口 | X-Ray Source Strategy
|
||||
/// 定义所有射线源必须实现的核心操作 | Defines core operations required for all X-ray sources
|
||||
/// </summary>
|
||||
public interface IXRaySource : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否已连接 | Is connected
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化射线源 | Initialize X-ray source
|
||||
/// </summary>
|
||||
XRayResult Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 连接 PVI 变量并启动实时状态通讯 | Connect PVI variables and start real-time status communication
|
||||
/// 对应硬件操作流程中的"开启实时状态通讯"步骤 | Corresponds to "start real-time status communication" step in hardware operation flow
|
||||
/// </summary>
|
||||
XRayResult ConnectVariables();
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线 | Turn on X-ray
|
||||
/// </summary>
|
||||
XRayResult TurnOn();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线 | Turn off X-ray
|
||||
/// </summary>
|
||||
XRayResult TurnOff();
|
||||
|
||||
/// <summary>
|
||||
/// 完全关闭设备(释放资源)| Fully shut down device (release resources)
|
||||
/// </summary>
|
||||
XRayResult CloseOff();
|
||||
|
||||
/// <summary>
|
||||
/// 设置电压(单位:kV)| Set voltage (unit: kV)
|
||||
/// </summary>
|
||||
/// <param name="voltage">目标电压值 | Target voltage value</param>
|
||||
XRayResult SetVoltage(float voltage);
|
||||
|
||||
/// <summary>
|
||||
/// 设置电流(单位:μA)| Set current (unit: μA)
|
||||
/// </summary>
|
||||
/// <param name="current">目标电流值 | Target current value</param>
|
||||
XRayResult SetCurrent(float current);
|
||||
|
||||
/// <summary>
|
||||
/// 设置焦点(部分设备支持)| Set focus (supported by some devices)
|
||||
/// </summary>
|
||||
/// <param name="focus">焦点参数 | Focus parameter</param>
|
||||
XRayResult SetFocus(float focus);
|
||||
|
||||
/// <summary>
|
||||
/// 读取实际电压值 | Read actual voltage value
|
||||
/// </summary>
|
||||
XRayResult ReadVoltage();
|
||||
|
||||
/// <summary>
|
||||
/// 读取实际电流值 | Read actual current value
|
||||
/// </summary>
|
||||
XRayResult ReadCurrent();
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统状态 | Read system status
|
||||
/// </summary>
|
||||
XRayResult ReadSystemStatus();
|
||||
|
||||
/// <summary>
|
||||
/// 检查错误状态 | Check error status
|
||||
/// </summary>
|
||||
XRayResult CheckErrors();
|
||||
|
||||
/// <summary>
|
||||
/// TXI 开启 | TXI On
|
||||
/// </summary>
|
||||
XRayResult TxiOn();
|
||||
|
||||
/// <summary>
|
||||
/// TXI 关闭 | TXI Off
|
||||
/// </summary>
|
||||
XRayResult TxiOff();
|
||||
|
||||
/// <summary>
|
||||
/// 暖机设置 | Warm-up setting
|
||||
/// </summary>
|
||||
XRayResult WarmUp();
|
||||
|
||||
/// <summary>
|
||||
/// 训机设置 | Training setting
|
||||
/// </summary>
|
||||
XRayResult Training();
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准 | Filament calibration
|
||||
/// </summary>
|
||||
XRayResult FilamentCalibration();
|
||||
|
||||
/// <summary>
|
||||
/// 全部电压自动定心 | Auto-center all voltages
|
||||
/// </summary>
|
||||
XRayResult AutoCenter();
|
||||
|
||||
/// <summary>
|
||||
/// 设置功率模式 | Set power mode
|
||||
/// </summary>
|
||||
/// <param name="mode">功率模式值:1=Micro Focus,2=High Power</param>
|
||||
XRayResult SetPowerMode(int mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源操作结果封装类 | X-Ray Source Operation Result Wrapper
|
||||
/// 用于统一返回操作状态、错误信息和数据 | Unified return of operation status, error messages, and data
|
||||
/// </summary>
|
||||
public class XRayResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作是否成功 | Operation success status
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误消息(成功时为null)| Error message (null when successful)
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作返回的数据 | Operation return data
|
||||
/// </summary>
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建成功结果 | Create success result
|
||||
/// </summary>
|
||||
/// <param name="data">返回数据 | Return data</param>
|
||||
/// <returns>成功结果对象 | Success result object</returns>
|
||||
public static XRayResult Ok(object data = null) =>
|
||||
new XRayResult { Success = true, Data = data };
|
||||
|
||||
/// <summary>
|
||||
/// 创建错误结果 | Create error result
|
||||
/// </summary>
|
||||
/// <param name="message">错误消息 | Error message</param>
|
||||
/// <returns>错误结果对象 | Error result object</returns>
|
||||
public static XRayResult Error(string message) =>
|
||||
new XRayResult { Success = false, ErrorMessage = message };
|
||||
|
||||
/// <summary>
|
||||
/// 获取浮点数据(带类型安全)| Get float data (type-safe)
|
||||
/// </summary>
|
||||
public float GetFloat() => Data != null ? Convert.ToSingle(Data) : 0f;
|
||||
|
||||
/// <summary>
|
||||
/// 获取整数数据(带类型安全)| Get integer data (type-safe)
|
||||
/// </summary>
|
||||
public int GetInt() => Data != null ? Convert.ToInt32(Data) : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Hardware.RaySource.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源抽象基类 | Abstract Base Class for X-Ray Sources
|
||||
/// 提供策略接口的基础实现框架 | Provides foundational implementation framework for strategy interface
|
||||
/// </summary>
|
||||
public abstract class XRaySourceBase : IXRaySource
|
||||
{
|
||||
protected bool _isDisposed = false;
|
||||
protected bool _isInitialized = false;
|
||||
protected bool _isConnected = false;
|
||||
|
||||
/// <summary>
|
||||
/// 射线源名称(用于日志)| X-ray source name (for logging)
|
||||
/// </summary>
|
||||
public virtual string SourceName => "Generic X-Ray Source";
|
||||
|
||||
/// <summary>
|
||||
/// 是否已连接 | Is connected
|
||||
/// </summary>
|
||||
public virtual bool IsConnected => _isConnected && _isInitialized;
|
||||
|
||||
#region IXRaySource Implementation
|
||||
public abstract XRayResult Initialize();
|
||||
public abstract XRayResult ConnectVariables();
|
||||
public abstract XRayResult TurnOn();
|
||||
public abstract XRayResult TurnOff();
|
||||
public abstract XRayResult CloseOff();
|
||||
public abstract XRayResult SetVoltage(float voltage);
|
||||
public abstract XRayResult SetCurrent(float current);
|
||||
public abstract XRayResult SetFocus(float focus);
|
||||
public abstract XRayResult ReadVoltage();
|
||||
public abstract XRayResult ReadCurrent();
|
||||
public abstract XRayResult ReadSystemStatus();
|
||||
public abstract XRayResult CheckErrors();
|
||||
public abstract XRayResult TxiOn();
|
||||
public abstract XRayResult TxiOff();
|
||||
public abstract XRayResult WarmUp();
|
||||
public abstract XRayResult Training();
|
||||
public abstract XRayResult FilamentCalibration();
|
||||
public abstract XRayResult AutoCenter();
|
||||
public abstract XRayResult SetPowerMode(int mode);
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
/// <summary>
|
||||
/// 释放资源 | Release resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 受保护的释放方法 | Protected dispose method
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否由用户代码调用 | Whether called from user code</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 释放托管资源 | Release managed resources
|
||||
try
|
||||
{
|
||||
// 尝试安全关闭 | Attempt safe shutdown
|
||||
if (_isInitialized)
|
||||
{
|
||||
CloseOff();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[射线源关闭异常 | X-Ray Source Close Exception] {SourceName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数(作为安全网)| Finalizer (as safety net)
|
||||
/// </summary>
|
||||
~XRaySourceBase()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
namespace XP.Hardware.RaySource.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置加载器 | Configuration Loader
|
||||
/// 从App.config读取配置,适配.NET 8 ConfigurationManager | Load configuration from App.config, adapted for .NET 8 ConfigurationManager
|
||||
/// </summary>
|
||||
public class ConfigLoader
|
||||
{
|
||||
private const string CONFIG_SECTION = "RaySource";
|
||||
|
||||
/// <summary>
|
||||
/// 加载射线源配置 | Load X-ray source configuration
|
||||
/// </summary>
|
||||
/// <param name="configFilePath">配置文件路径(可选,默认使用App.config)| Configuration file path (optional, defaults to App.config)</param>
|
||||
/// <param name="logger">日志服务(可选)| Logger service (optional)</param>
|
||||
/// <returns>配置对象 | Configuration object</returns>
|
||||
public static RaySourceConfig LoadConfig(string configFilePath = null, ILoggerService logger = null)
|
||||
{
|
||||
var config = new RaySourceConfig();
|
||||
|
||||
try
|
||||
{
|
||||
// 如果指定了配置文件路径,使用自定义配置文件
|
||||
// If a configuration file path is specified, use custom configuration file
|
||||
if (!string.IsNullOrEmpty(configFilePath) && File.Exists(configFilePath))
|
||||
{
|
||||
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = configFilePath };
|
||||
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
|
||||
LoadFromConfiguration(config, configuration.AppSettings.Settings, logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用默认App.config | Use default App.config
|
||||
LoadFromAppSettings(config, logger);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[配置加载警告 | Config Load Warning] 使用默认配置: {ex.Message}");
|
||||
// 加载失败时返回默认配置 | Return default configuration on load failure
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从AppSettings加载配置 | Load configuration from AppSettings
|
||||
/// </summary>
|
||||
private static void LoadFromAppSettings(RaySourceConfig config, ILoggerService logger = null)
|
||||
{
|
||||
// PVI通讯参数 | PVI Communication Parameters
|
||||
config.SourceType = GetAppSetting(CONFIG_SECTION + ":" + "SourceType", config.SourceType);
|
||||
config.PlcIpAddress = GetAppSetting(CONFIG_SECTION + ":" + "PlcIpAddress", config.PlcIpAddress);
|
||||
config.PlcPort = GetAppSetting(CONFIG_SECTION + ":" + "PlcPort", config.PlcPort);
|
||||
config.StationNumber = GetAppSetting(CONFIG_SECTION + ":" + "StationNumber", config.StationNumber);
|
||||
config.PortNumber = GetAppSetting(CONFIG_SECTION + ":" + "PortNumber", config.PortNumber);
|
||||
config.CpuName = GetAppSetting(CONFIG_SECTION + ":" + "CpuName", config.CpuName);
|
||||
config.ConnectionTimeout = GetAppSetting(CONFIG_SECTION + ":" + "ConnectionTimeout", config.ConnectionTimeout);
|
||||
|
||||
// 硬件参数范围 | Hardware Parameter Ranges
|
||||
config.MinVoltage = GetAppSetting(CONFIG_SECTION + ":" + "MinVoltage", config.MinVoltage);
|
||||
config.MaxVoltage = GetAppSetting(CONFIG_SECTION + ":" + "MaxVoltage", config.MaxVoltage);
|
||||
config.MinCurrent = GetAppSetting(CONFIG_SECTION + ":" + "MinCurrent", config.MinCurrent);
|
||||
config.MaxCurrent = GetAppSetting(CONFIG_SECTION + ":" + "MaxCurrent", config.MaxCurrent);
|
||||
|
||||
// 外部程序配置 | External Program Configuration
|
||||
config.AdvanceExePath = GetAppSetting(CONFIG_SECTION + ":" + "AdvanceExePath", config.AdvanceExePath);
|
||||
|
||||
// 操作超时配置 | Operation Timeout Configuration
|
||||
config.InitializationTimeout = GetAppSetting(CONFIG_SECTION + ":" + "InitializationTimeout", config.InitializationTimeout);
|
||||
config.WarmUpTimeout = GetAppSetting(CONFIG_SECTION + ":" + "WarmUpTimeout", config.WarmUpTimeout);
|
||||
config.StartUpTimeout = GetAppSetting(CONFIG_SECTION + ":" + "StartUpTimeout", config.StartUpTimeout);
|
||||
config.AutoCenterTimeout = GetAppSetting(CONFIG_SECTION + ":" + "AutoCenterTimeout", config.AutoCenterTimeout);
|
||||
config.FilamentAdjustTimeout = GetAppSetting(CONFIG_SECTION + ":" + "FilamentAdjustTimeout", config.FilamentAdjustTimeout);
|
||||
config.GeneralOperationTimeout = GetAppSetting(CONFIG_SECTION + ":" + "GeneralOperationTimeout", config.GeneralOperationTimeout);
|
||||
|
||||
// 灯丝寿命配置 | Filament Lifetime Configuration
|
||||
config.SerialNumber = GetAppSetting(CONFIG_SECTION + ":" + "SerialNumber", config.SerialNumber);
|
||||
config.TotalLifeThreshold = GetAppSetting(CONFIG_SECTION + ":" + "TotalLifeThreshold", config.TotalLifeThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Configuration对象加载配置 | Load configuration from Configuration object
|
||||
/// </summary>
|
||||
private static void LoadFromConfiguration(RaySourceConfig config, KeyValueConfigurationCollection settings, ILoggerService logger = null)
|
||||
{
|
||||
config.SourceType = GetSetting(settings, CONFIG_SECTION + ":" + "SourceType", config.SourceType);
|
||||
config.PlcIpAddress = GetSetting(settings, CONFIG_SECTION + ":" + "PlcIpAddress", config.PlcIpAddress);
|
||||
config.PlcPort = GetSetting(settings, CONFIG_SECTION + ":" + "PlcPort", config.PlcPort);
|
||||
config.StationNumber = GetSetting(settings, CONFIG_SECTION + ":" + "StationNumber", config.StationNumber);
|
||||
config.PortNumber = GetSetting(settings, CONFIG_SECTION + ":" + "PortNumber", config.PortNumber);
|
||||
config.CpuName = GetSetting(settings, CONFIG_SECTION + ":" + "CpuName", config.CpuName);
|
||||
config.ConnectionTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "ConnectionTimeout", config.ConnectionTimeout);
|
||||
|
||||
config.MinVoltage = GetSetting(settings, CONFIG_SECTION + ":" + "MinVoltage", config.MinVoltage);
|
||||
config.MaxVoltage = GetSetting(settings, CONFIG_SECTION + ":" + "MaxVoltage", config.MaxVoltage);
|
||||
config.MinCurrent = GetSetting(settings, CONFIG_SECTION + ":" + "MinCurrent", config.MinCurrent);
|
||||
config.MaxCurrent = GetSetting(settings, CONFIG_SECTION + ":" + "MaxCurrent", config.MaxCurrent);
|
||||
|
||||
// 外部程序配置 | External Program Configuration
|
||||
config.AdvanceExePath = GetSetting(settings, CONFIG_SECTION + ":" + "AdvanceExePath", config.AdvanceExePath);
|
||||
|
||||
config.InitializationTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "InitializationTimeout", config.InitializationTimeout);
|
||||
config.WarmUpTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "WarmUpTimeout", config.WarmUpTimeout);
|
||||
config.StartUpTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "StartUpTimeout", config.StartUpTimeout);
|
||||
config.AutoCenterTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "AutoCenterTimeout", config.AutoCenterTimeout);
|
||||
config.FilamentAdjustTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "FilamentAdjustTimeout", config.FilamentAdjustTimeout);
|
||||
config.GeneralOperationTimeout = GetSetting(settings, CONFIG_SECTION + ":" + "GeneralOperationTimeout", config.GeneralOperationTimeout);
|
||||
|
||||
// 灯丝寿命配置 | Filament Lifetime Configuration
|
||||
config.SerialNumber = GetSetting(settings, CONFIG_SECTION + ":" + "SerialNumber", config.SerialNumber);
|
||||
config.TotalLifeThreshold = GetSetting(settings, CONFIG_SECTION + ":" + "TotalLifeThreshold", config.TotalLifeThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置到AppSettings | Save configuration to AppSettings
|
||||
/// </summary>
|
||||
public static void SaveConfigToAppSettings(RaySourceConfig config)
|
||||
{
|
||||
// 获取程序默认配置文件路径(对应 ConfigurationManager.AppSettings 的文件)
|
||||
Assembly entryAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
|
||||
string assemblyPath = entryAssembly.Location;
|
||||
string defaultConfigPath = Path.ChangeExtension(assemblyPath, ".config");
|
||||
// 复用原有保存逻辑
|
||||
SaveConfig(config, defaultConfigPath);
|
||||
// 刷新 ConfigurationManager,让 AppSettings 立即读取最新值
|
||||
ConfigurationManager.RefreshSection("appSettings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置到文件 | Save configuration to file
|
||||
/// </summary>
|
||||
public static void SaveConfig(RaySourceConfig config, string configFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = configFilePath };
|
||||
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
|
||||
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "SourceType", config.SourceType);
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "PlcIpAddress", config.PlcIpAddress);
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "PlcPort", config.PlcPort.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "StationNumber", config.StationNumber.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "PortNumber", config.PortNumber.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "CpuName", config.CpuName);
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "ConnectionTimeout", config.ConnectionTimeout.ToString());
|
||||
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "MinVoltage", config.MinVoltage.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "MaxVoltage", config.MaxVoltage.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "MinCurrent", config.MinCurrent.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "MaxCurrent", config.MaxCurrent.ToString());
|
||||
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "AdvanceExePath", config.AdvanceExePath);
|
||||
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "InitializationTimeout", config.InitializationTimeout.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "WarmUpTimeout", config.WarmUpTimeout.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "StartUpTimeout", config.StartUpTimeout.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "AutoCenterTimeout", config.AutoCenterTimeout.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "FilamentAdjustTimeout", config.FilamentAdjustTimeout.ToString());
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "GeneralOperationTimeout", config.GeneralOperationTimeout.ToString());
|
||||
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "SerialNumber", config.SerialNumber);
|
||||
SetSetting(configuration.AppSettings.Settings, CONFIG_SECTION + ":" + "TotalLifeThreshold", config.TotalLifeThreshold.ToString());
|
||||
|
||||
configuration.Save(ConfigurationSaveMode.Modified);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"保存配置失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region 辅助方法 | Helper Methods
|
||||
|
||||
private static string GetAppSetting(string key, string defaultValue)
|
||||
{
|
||||
return ConfigurationManager.AppSettings[key] ?? defaultValue;
|
||||
}
|
||||
|
||||
private static int GetAppSetting(string key, int defaultValue)
|
||||
{
|
||||
var value = ConfigurationManager.AppSettings[key];
|
||||
return int.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
private static float GetAppSetting(string key, float defaultValue)
|
||||
{
|
||||
var value = ConfigurationManager.AppSettings[key];
|
||||
return float.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
private static bool GetAppSetting(string key, bool defaultValue)
|
||||
{
|
||||
var value = ConfigurationManager.AppSettings[key];
|
||||
return bool.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
private static string GetSetting(KeyValueConfigurationCollection settings, string key, string defaultValue)
|
||||
{
|
||||
return settings[key]?.Value ?? defaultValue;
|
||||
}
|
||||
|
||||
private static int GetSetting(KeyValueConfigurationCollection settings, string key, int defaultValue)
|
||||
{
|
||||
var value = settings[key]?.Value;
|
||||
return int.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
private static float GetSetting(KeyValueConfigurationCollection settings, string key, float defaultValue)
|
||||
{
|
||||
var value = settings[key]?.Value;
|
||||
return float.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
private static bool GetSetting(KeyValueConfigurationCollection settings, string key, bool defaultValue)
|
||||
{
|
||||
var value = settings[key]?.Value;
|
||||
return bool.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
private static void SetSetting(KeyValueConfigurationCollection settings, string key, string value)
|
||||
{
|
||||
if (settings[key] == null)
|
||||
settings.Add(key, value);
|
||||
else
|
||||
settings[key].Value = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Hardware.RaySource.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源配置实体类 | X-Ray Source Configuration Entity
|
||||
/// 包含PVI通讯参数、硬件参数和操作超时配置 | Contains PVI communication parameters, hardware parameters, and operation timeout configuration
|
||||
/// </summary>
|
||||
public class RaySourceConfig
|
||||
{
|
||||
#region PVI通讯参数 | PVI Communication Parameters
|
||||
|
||||
/// <summary>
|
||||
/// 射线源类型 | Ray source type
|
||||
/// 可选值: Comet225 | Available values: Comet225
|
||||
/// </summary>
|
||||
public string SourceType { get; set; } = "Comet225";
|
||||
|
||||
/// <summary>
|
||||
/// PLC IP地址 | PLC IP Address
|
||||
/// </summary>
|
||||
public string PlcIpAddress { get; set; } = "192.168.12.10";
|
||||
|
||||
/// <summary>
|
||||
/// PLC端口号 | PLC Port Number
|
||||
/// </summary>
|
||||
public int PlcPort { get; set; } = 11159;
|
||||
|
||||
/// <summary>
|
||||
/// 源站号 | Source Station Number
|
||||
/// </summary>
|
||||
public int StationNumber { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 源端口号 | Source Port Number
|
||||
/// </summary>
|
||||
public int PortNumber { get; set; } = 11;
|
||||
|
||||
/// <summary>
|
||||
/// CPU名称 | CPU Name
|
||||
/// </summary>
|
||||
public string CpuName { get; set; } = "cpu";
|
||||
|
||||
/// <summary>
|
||||
/// 连接超时时间(毫秒)| Connection timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int ConnectionTimeout { get; set; } = 5000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 硬件参数范围 | Hardware Parameter Ranges
|
||||
|
||||
/// <summary>
|
||||
/// 最小电压(kV)| Minimum voltage (kV)
|
||||
/// </summary>
|
||||
public float MinVoltage { get; set; } = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// 最大电压(kV)| Maximum voltage (kV)
|
||||
/// </summary>
|
||||
public float MaxVoltage { get; set; } = 225f;
|
||||
|
||||
/// <summary>
|
||||
/// 最小电流(μA)| Minimum current (μA)
|
||||
/// </summary>
|
||||
public float MinCurrent { get; set; } = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// 最大电流(μA)| Maximum current (μA)
|
||||
/// </summary>
|
||||
public float MaxCurrent { get; set; } = 1000f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 外部程序配置 | External Program Configuration
|
||||
|
||||
/// <summary>
|
||||
/// 高级设置外部程序路径 | Advance settings external program path
|
||||
/// </summary>
|
||||
public string AdvanceExePath { get; set; } = @"C:\Program Files (x86)\Feinfocus\FXEControl_3.1.1.65\FXEControl.exe";
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程可执行文件路径(可选)
|
||||
/// 为空时默认在主程序输出目录下查找 XP.Hardware.RaySource.Comet.Host.exe
|
||||
/// </summary>
|
||||
public string HostExePath { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 操作超时配置 | Operation Timeout Configuration
|
||||
|
||||
/// <summary>
|
||||
/// 初始化超时时间(毫秒)| Initialization timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int InitializationTimeout { get; set; } = 30000;
|
||||
|
||||
/// <summary>
|
||||
/// 暖机超时时间(毫秒)| Warm-up timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int WarmUpTimeout { get; set; } = 300000; // 5分钟
|
||||
|
||||
/// <summary>
|
||||
/// 启动超时时间(毫秒)| Startup timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int StartUpTimeout { get; set; } = 180000; // 3分钟
|
||||
|
||||
/// <summary>
|
||||
/// 自动定心超时时间(毫秒)| Auto-center timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int AutoCenterTimeout { get; set; } = 120000; // 2分钟
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝调整超时时间(毫秒)| Filament adjust timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int FilamentAdjustTimeout { get; set; } = 120000; // 2分钟
|
||||
|
||||
/// <summary>
|
||||
/// 一般操作超时时间(毫秒)| General operation timeout (milliseconds)
|
||||
/// </summary>
|
||||
public int GeneralOperationTimeout { get; set; } = 10000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 灯丝寿命配置 | Filament Lifetime Configuration
|
||||
|
||||
/// <summary>
|
||||
/// 射线源序列号 | Ray source serial number
|
||||
/// 用于唯一标识设备,灯丝寿命管理依赖此字段匹配设备记录
|
||||
/// Used to uniquely identify the device, filament lifetime management relies on this field to match device records
|
||||
/// </summary>
|
||||
public string SerialNumber { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝总寿命阈值(小时)| Filament total lifetime threshold (hours)
|
||||
/// 默认值为 1000 小时,超过阈值时系统将发出预警
|
||||
/// Default value is 1000 hours, system will issue a warning when threshold is exceeded
|
||||
/// </summary>
|
||||
public int TotalLifeThreshold { get; set; } = 1000;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 验证配置参数有效性 | Validate configuration parameters
|
||||
/// </summary>
|
||||
/// <returns>验证结果 | Validation result</returns>
|
||||
public (bool IsValid, string ErrorMessage) Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(PlcIpAddress))
|
||||
return (false, "PLC IP地址不能为空");
|
||||
|
||||
if (PlcPort <= 0 || PlcPort > 65535)
|
||||
return (false, "PLC端口号必须在1-65535之间");
|
||||
|
||||
if (MinVoltage >= MaxVoltage)
|
||||
return (false, "最小电压必须小于最大电压");
|
||||
|
||||
if (MinCurrent >= MaxCurrent)
|
||||
return (false, "最小电流必须小于最大电流");
|
||||
|
||||
if (ConnectionTimeout <= 0)
|
||||
return (false, "连接超时时间必须大于0");
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace XP.Hardware.RaySource.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 灯丝寿命百分比转颜色转换器 | Filament lifetime percentage to color converter
|
||||
/// 根据寿命使用百分比返回对应的颜色画刷:
|
||||
/// 低于 80% → 绿色 (#FF4CAF50)
|
||||
/// 80%-89% → 黄色 (#FFFFC107)
|
||||
/// 90% 及以上 → 红色 (#FFE53935)
|
||||
/// </summary>
|
||||
public class FilamentLifetimeColorConverter : IValueConverter
|
||||
{
|
||||
// 预定义颜色画刷(避免重复创建)| Pre-defined color brushes (avoid repeated creation)
|
||||
private static readonly SolidColorBrush GreenBrush = new(Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50));
|
||||
private static readonly SolidColorBrush YellowBrush = new(Color.FromArgb(0xFF, 0xFF, 0xC1, 0x07));
|
||||
private static readonly SolidColorBrush RedBrush = new(Color.FromArgb(0xFF, 0xE5, 0x39, 0x35));
|
||||
|
||||
static FilamentLifetimeColorConverter()
|
||||
{
|
||||
// 冻结画刷以提升性能 | Freeze brushes for better performance
|
||||
GreenBrush.Freeze();
|
||||
YellowBrush.Freeze();
|
||||
RedBrush.Freeze();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将寿命百分比(0-100)转换为对应颜色画刷 | Convert lifetime percentage (0-100) to corresponding color brush
|
||||
/// </summary>
|
||||
/// <param name="value">寿命百分比值(double,0-100)| Lifetime percentage value (double, 0-100)</param>
|
||||
/// <param name="targetType">目标类型 | Target type</param>
|
||||
/// <param name="parameter">转换参数(未使用)| Converter parameter (unused)</param>
|
||||
/// <param name="culture">区域信息 | Culture info</param>
|
||||
/// <returns>对应颜色的 SolidColorBrush | SolidColorBrush of corresponding color</returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not double percentage)
|
||||
{
|
||||
return GreenBrush;
|
||||
}
|
||||
|
||||
if (percentage >= 90)
|
||||
{
|
||||
return RedBrush;
|
||||
}
|
||||
|
||||
if (percentage >= 80)
|
||||
{
|
||||
return YellowBrush;
|
||||
}
|
||||
|
||||
return GreenBrush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反向转换(不支持,单向绑定)| Reverse conversion (not supported, one-way binding)
|
||||
/// </summary>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
|
||||
namespace XP.Hardware.RaySource.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源状态转背景色转换器(径向渐变)
|
||||
/// </summary>
|
||||
public class RaySourceStatusToColorConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态转颜色(不可用=灰色,关闭=绿色,开启=红色)- 中心渐变效果
|
||||
/// </summary>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is RaySourceStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
RaySourceStatus.Unavailable => CreateRadialGradient(
|
||||
Color.FromRgb(224, 224, 224), // 中心浅灰
|
||||
Color.FromRgb(189, 189, 189)), // 边缘深灰
|
||||
RaySourceStatus.Closed => CreateRadialGradient(
|
||||
Color.FromRgb(139, 195, 74), // 中心浅绿 #8BC34A
|
||||
Color.FromRgb(76, 175, 80)), // 边缘深绿 #4CAF50
|
||||
RaySourceStatus.Opened => CreateRadialGradient(
|
||||
Color.FromRgb(255, 138, 128), // 中心浅红 #FF8A80
|
||||
Color.FromRgb(244, 67, 54)), // 边缘深红 #F44336
|
||||
_ => CreateRadialGradient(
|
||||
Color.FromRgb(224, 224, 224), // 中心浅灰
|
||||
Color.FromRgb(189, 189, 189)) // 边缘深灰
|
||||
};
|
||||
}
|
||||
return CreateRadialGradient(
|
||||
Color.FromRgb(224, 224, 224),
|
||||
Color.FromRgb(189, 189, 189));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建径向渐变画刷
|
||||
/// </summary>
|
||||
private RadialGradientBrush CreateRadialGradient(Color centerColor, Color edgeColor)
|
||||
{
|
||||
var brush = new RadialGradientBrush();
|
||||
brush.GradientOrigin = new System.Windows.Point(0.5, 0.5);
|
||||
brush.Center = new System.Windows.Point(0.5, 0.5);
|
||||
brush.RadiusX = 0.8;
|
||||
brush.RadiusY = 0.8;
|
||||
brush.GradientStops.Add(new GradientStop(centerColor, 0.0));
|
||||
brush.GradientStops.Add(new GradientStop(edgeColor, 1.0));
|
||||
return brush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反向转换(无需实现)
|
||||
/// </summary>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态转边框色转换器
|
||||
/// </summary>
|
||||
public class RaySourceStatusToBorderColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is RaySourceStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
RaySourceStatus.Unavailable => new SolidColorBrush(Color.FromRgb(158, 158, 158)), // 深灰 #9E9E9E
|
||||
RaySourceStatus.Closed => new SolidColorBrush(Color.FromRgb(46, 125, 50)), // 深绿 #2E7D32
|
||||
RaySourceStatus.Opened => new SolidColorBrush(Color.FromRgb(198, 40, 40)), // 深红 #C62828
|
||||
_ => new SolidColorBrush(Color.FromRgb(153, 153, 153))
|
||||
};
|
||||
}
|
||||
return new SolidColorBrush(Color.FromRgb(153, 153, 153));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态转滑块启用状态转换器
|
||||
/// </summary>
|
||||
public class RaySourceStatusToSliderEnabledConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is RaySourceStatus status)
|
||||
{
|
||||
return status == RaySourceStatus.Opened; // 开启时滑块可用,关闭时禁用
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<!-- 射线源类型 | Ray Source Type -->
|
||||
<add key="RaySource:SourceType" value="Comet225" />
|
||||
|
||||
<!-- PVI 通讯参数 | PVI Communication Parameters -->
|
||||
<add key="RaySource:PlcIpAddress" value="192.168.12.10" />
|
||||
<add key="RaySource:PlcPort" value="11159" />
|
||||
<add key="RaySource:StationNumber" value="1" />
|
||||
<add key="RaySource:PortNumber" value="11" />
|
||||
<add key="RaySource:CpuName" value="cpu" />
|
||||
<add key="RaySource:ConnectionTimeout" value="5000" />
|
||||
|
||||
<!-- 硬件参数范围 | Hardware Parameter Ranges -->
|
||||
<add key="RaySource:MinVoltage" value="20" />
|
||||
<add key="RaySource:MaxVoltage" value="225" />
|
||||
<add key="RaySource:MinCurrent" value="10" />
|
||||
<add key="RaySource:MaxCurrent" value="1000" />
|
||||
|
||||
<!-- 外部程序配置 | External Program Configuration -->
|
||||
<add key="RaySource:AdvanceExePath" value="C:\Program Files (x86)\Feinfocus\FXEControl_3.1.1.65\FXEControl.exe" />
|
||||
<add key="RaySource:HostExePath" value="" />
|
||||
|
||||
<!-- 操作超时配置 | Operation Timeout Configuration -->
|
||||
<add key="RaySource:InitializationTimeout" value="30000" />
|
||||
<add key="RaySource:WarmUpTimeout" value="300000" />
|
||||
<add key="RaySource:StartUpTimeout" value="180000" />
|
||||
<add key="RaySource:AutoCenterTimeout" value="120000" />
|
||||
<add key="RaySource:FilamentAdjustTimeout" value="120000" />
|
||||
<add key="RaySource:GeneralOperationTimeout" value="10000" />
|
||||
|
||||
<!-- 灯丝寿命管理配置 | Filament Lifetime Management Configuration -->
|
||||
<add key="RaySource:SerialNumber" value="SN08602861" />
|
||||
<add key="RaySource:TotalLifeThreshold" value="1000" />
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,680 @@
|
||||
# X 射线源模块使用指南 | X-Ray Source Module Usage Guide
|
||||
|
||||
## 1. 模块概述 | Module Overview
|
||||
|
||||
XP.Hardware.RaySource 是 XplorePlane X 射线检测系统的核心硬件控制模块,负责与工业 X 射线源设备进行通讯和控制。该模块采用策略模式设计,通过 Named Pipe IPC 进程隔离架构与 .NET Framework 4.8 Host 子进程通信,支持多种 X 射线源型号的统一管理。
|
||||
|
||||
### 核心特性 | Key Features
|
||||
|
||||
- 支持多种 X 射线源型号(当前支持 Comet 225kV)
|
||||
- 基于 Named Pipe IPC 的进程隔离架构(.NET 8 ↔ .NET Framework 4.8)
|
||||
- 完整的设备生命周期管理(初始化、连接变量、暖机、训机、灯丝校准、自动定心、开关射线)
|
||||
- 电压电流精确控制(20-225kV / 10-1000μA)
|
||||
- TXI 开关控制、功率模式切换(Micro Focus / High Power)
|
||||
- 实时状态监控和错误检测
|
||||
- 灯丝寿命管理(使用时长记录、累计统计、预警提醒)
|
||||
- 基于 Prism 事件聚合器的松耦合通讯
|
||||
- 安全机制:紧急关闭优先级、参数范围验证、双重检查锁定
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块注册 | Module Registration
|
||||
|
||||
X 射线源模块已通过 Prism 的模块化系统注册,在应用启动时自动加载。
|
||||
|
||||
### 在 App.xaml.cs 中注册
|
||||
|
||||
```csharp
|
||||
using Prism.Modularity;
|
||||
using XP.Hardware.RaySource.Module;
|
||||
|
||||
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
|
||||
{
|
||||
// 注册 X 射线源模块 | Register X-Ray Source module
|
||||
moduleCatalog.AddModule<RaySourceModule>();
|
||||
|
||||
base.ConfigureModuleCatalog(moduleCatalog);
|
||||
}
|
||||
```
|
||||
|
||||
### 模块自动注册的服务
|
||||
|
||||
| 服务 | 类型 | 生命周期 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `RaySourceConfig` | 实例 | 单例 | 配置对象(从 App.config 加载) |
|
||||
| `IRaySourceFactory` → `RaySourceFactory` | 接口→实现 | 单例 | 射线源工厂 |
|
||||
| `IRaySourceService` → `RaySourceService` | 接口→实现 | 单例 | 射线源业务服务 |
|
||||
| `IFilamentLifetimeService` → `FilamentLifetimeService` | 接口→实现 | 单例 | 灯丝寿命管理服务 |
|
||||
|
||||
### 模块初始化行为
|
||||
|
||||
模块 `OnInitialized` 时会自动执行:
|
||||
1. 初始化灯丝寿命服务(建表、异常恢复、重算累计寿命)
|
||||
2. 检查灯丝寿命预警(≥90% 弹出模态对话框)
|
||||
|
||||
---
|
||||
|
||||
## 3. 配置文件设置 | Configuration File Setup
|
||||
|
||||
在 `App.config` 中添加 X 射线源配置:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<!-- 射线源类型 | Ray Source Type -->
|
||||
<add key="RaySource:SourceType" value="Comet225" />
|
||||
|
||||
<!-- PVI 通讯参数 | PVI Communication Parameters -->
|
||||
<add key="RaySource:PlcIpAddress" value="192.168.12.10" />
|
||||
<add key="RaySource:PlcPort" value="11159" />
|
||||
<add key="RaySource:StationNumber" value="1" />
|
||||
<add key="RaySource:PortNumber" value="11" />
|
||||
<add key="RaySource:CpuName" value="cpu" />
|
||||
<add key="RaySource:ConnectionTimeout" value="5000" />
|
||||
|
||||
<!-- 硬件参数范围 | Hardware Parameter Ranges -->
|
||||
<add key="RaySource:MinVoltage" value="20" />
|
||||
<add key="RaySource:MaxVoltage" value="225" />
|
||||
<add key="RaySource:MinCurrent" value="10" />
|
||||
<add key="RaySource:MaxCurrent" value="1000" />
|
||||
|
||||
<!-- 操作超时配置 | Operation Timeout Configuration -->
|
||||
<add key="RaySource:InitializationTimeout" value="30000" />
|
||||
<add key="RaySource:WarmUpTimeout" value="300000" />
|
||||
<add key="RaySource:StartUpTimeout" value="180000" />
|
||||
<add key="RaySource:AutoCenterTimeout" value="120000" />
|
||||
<add key="RaySource:FilamentAdjustTimeout" value="120000" />
|
||||
<add key="RaySource:GeneralOperationTimeout" value="10000" />
|
||||
|
||||
<!-- 灯丝寿命管理配置 | Filament Lifetime Management Configuration -->
|
||||
<add key="RaySource:SerialNumber" value="SN08602861" />
|
||||
<add key="RaySource:TotalLifeThreshold" value="1000" />
|
||||
</appSettings>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 在 ViewModel 中使用 | Usage in ViewModel
|
||||
|
||||
### 4.1 注入服务
|
||||
|
||||
```csharp
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
|
||||
namespace YourNamespace.ViewModels
|
||||
{
|
||||
public class YourViewModel : BindableBase
|
||||
{
|
||||
private readonly IRaySourceService _raySourceService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
public YourViewModel(
|
||||
IRaySourceService raySourceService,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_raySourceService = raySourceService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger.ForModule("YourViewModel");
|
||||
|
||||
// 订阅事件 | Subscribe to events
|
||||
SubscribeEvents();
|
||||
}
|
||||
|
||||
private void SubscribeEvents()
|
||||
{
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>()
|
||||
.Subscribe(OnRaySourceStatusChanged, ThreadOption.UIThread);
|
||||
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>()
|
||||
.Subscribe(OnStatusUpdated, ThreadOption.UIThread);
|
||||
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>()
|
||||
.Subscribe(OnErrorOccurred, ThreadOption.UIThread);
|
||||
|
||||
_eventAggregator.GetEvent<OperationResultEvent>()
|
||||
.Subscribe(OnOperationResult, ThreadOption.UIThread);
|
||||
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>()
|
||||
.Subscribe(OnVariablesConnected, ThreadOption.UIThread);
|
||||
}
|
||||
|
||||
private void OnRaySourceStatusChanged(RaySourceStatus newStatus)
|
||||
{
|
||||
_logger.Info("射线源状态变化: {Status}", newStatus);
|
||||
}
|
||||
|
||||
private void OnStatusUpdated(SystemStatusData data)
|
||||
{
|
||||
_logger.Debug("状态更新: 电压={Voltage}kV, 电流={Current}μA",
|
||||
data.ActualVoltage, data.ActualCurrent);
|
||||
}
|
||||
|
||||
private void OnErrorOccurred(string errorMessage)
|
||||
{
|
||||
_logger.Error(null, "错误发生: {Error}", errorMessage);
|
||||
}
|
||||
|
||||
private void OnOperationResult(OperationResultData data)
|
||||
{
|
||||
_logger.Info("操作 {Op} {Result}: {Msg}",
|
||||
data.OperationName, data.IsSuccess ? "成功" : "失败", data.Message);
|
||||
}
|
||||
|
||||
private void OnVariablesConnected(bool isConnected)
|
||||
{
|
||||
_logger.Info("变量连接状态: {Connected}", isConnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 初始化射线源
|
||||
|
||||
```csharp
|
||||
// 方式一:分步初始化 | Step-by-step initialization
|
||||
public void InitializeRaySource()
|
||||
{
|
||||
// 步骤1:初始化(启动 Host 进程,建立 IPC 连接)
|
||||
XRayResult result = _raySourceService.Initialize();
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Error(null, "初始化失败: {Error}", result.ErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// 步骤2:连接变量(创建和激活 PVI 变量)
|
||||
XRayResult connectResult = _raySourceService.ConnectVariables();
|
||||
if (!connectResult.Success)
|
||||
{
|
||||
_logger.Error(null, "连接变量失败: {Error}", connectResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// 方式二:异步一键初始化 | Async one-step initialization
|
||||
public async Task AutoInitializeAsync()
|
||||
{
|
||||
XRayResult result = await _raySourceService.InitializeAndConnectAsync();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("自动初始化完成");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 开启/关闭射线
|
||||
|
||||
```csharp
|
||||
// 开启射线 | Turn on X-ray
|
||||
public void TurnOnXRay()
|
||||
{
|
||||
if (!_raySourceService.IsInitialized || !_raySourceService.IsConnected)
|
||||
{
|
||||
_logger.Warn("射线源未就绪");
|
||||
return;
|
||||
}
|
||||
|
||||
XRayResult result = _raySourceService.TurnOn();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("射线已开启");
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭射线 | Turn off X-ray
|
||||
public void TurnOffXRay()
|
||||
{
|
||||
XRayResult result = _raySourceService.TurnOff();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("射线已关闭");
|
||||
}
|
||||
}
|
||||
|
||||
// 紧急关闭(最高优先级,任何状态下可执行)| Emergency shutdown
|
||||
public void EmergencyShutdown()
|
||||
{
|
||||
_raySourceService.EmergencyShutdown();
|
||||
}
|
||||
|
||||
// 断开连接(保留实例以便重连)| Disconnect
|
||||
public void Disconnect()
|
||||
{
|
||||
_raySourceService.Disconnect();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 5. 电压电流控制 | Voltage and Current Control
|
||||
|
||||
```csharp
|
||||
// 设置电压(范围由配置文件定义,默认 20-225kV)
|
||||
XRayResult result = _raySourceService.SetVoltage(100f);
|
||||
|
||||
// 设置电流(范围由配置文件定义,默认 10-1000μA)
|
||||
XRayResult result = _raySourceService.SetCurrent(500f);
|
||||
|
||||
// 读取实际电压
|
||||
XRayResult voltageResult = _raySourceService.ReadVoltage();
|
||||
if (voltageResult.Success)
|
||||
{
|
||||
float voltage = voltageResult.GetFloat();
|
||||
}
|
||||
|
||||
// 读取实际电流
|
||||
XRayResult currentResult = _raySourceService.ReadCurrent();
|
||||
if (currentResult.Success)
|
||||
{
|
||||
float current = currentResult.GetFloat();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 设备操作 | Device Operations
|
||||
|
||||
```csharp
|
||||
// TXI 开启/关闭
|
||||
_raySourceService.TxiOn();
|
||||
_raySourceService.TxiOff();
|
||||
|
||||
// 暖机
|
||||
_raySourceService.WarmUp();
|
||||
|
||||
// 训机
|
||||
_raySourceService.Training();
|
||||
|
||||
// 灯丝校准
|
||||
_raySourceService.FilamentCalibration();
|
||||
|
||||
// 全部电压自动定心
|
||||
_raySourceService.AutoCenter();
|
||||
|
||||
// 设置功率模式(1=Micro Focus,2=High Power)
|
||||
_raySourceService.SetPowerMode(1); // 微焦点模式
|
||||
_raySourceService.SetPowerMode(2); // 高功率模式
|
||||
```
|
||||
|
||||
注意:TXI、暖机、训机、灯丝校准、自动定心、功率模式切换等操作需要射线源已连接变量(`IsConnected == true`)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 状态监控和错误处理 | Status Monitoring and Error Handling
|
||||
|
||||
### 7.1 读取系统状态
|
||||
|
||||
```csharp
|
||||
XRayResult result = _raySourceService.ReadSystemStatus();
|
||||
if (result.Success && result.Data is SystemStatusData statusData)
|
||||
{
|
||||
// statusData 包含全量状态信息:
|
||||
// - SetVoltage / ActualVoltage(设定/实际电压)
|
||||
// - SetCurrent / ActualCurrent(设定/实际电流)
|
||||
// - IsXRayOn(射线开启状态)
|
||||
// - WarmUpStatus / VacuumStatus / StartUpStatus(暖机/真空/启动状态)
|
||||
// - AutoCenterStatus / FilamentAdjustStatus(自动定心/灯丝调整状态)
|
||||
// - IsInterlockActive(连锁状态)
|
||||
// - WatchdogStatus / PowerMode / TxiStatus(看门狗/功率模式/TXI状态)
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 检查和清除错误
|
||||
|
||||
```csharp
|
||||
// 检查错误
|
||||
XRayResult errorResult = _raySourceService.CheckErrors();
|
||||
|
||||
// 清除错误
|
||||
XRayResult clearResult = _raySourceService.ClearErrors();
|
||||
```
|
||||
|
||||
### 7.3 状态属性
|
||||
|
||||
```csharp
|
||||
bool isInitialized = _raySourceService.IsInitialized; // 是否已初始化
|
||||
bool isConnected = _raySourceService.IsConnected; // PVI 变量是否已连接
|
||||
bool isXRayOn = _raySourceService.IsXRayOn; // 射线是否开启
|
||||
RaySourceStatus status = _raySourceService.CurrentStatus; // 当前状态(三态)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Prism 事件通讯 | Prism Event Communication
|
||||
|
||||
模块使用 Prism 事件聚合器实现跨模块通讯,支持以下事件:
|
||||
|
||||
### 8.1 射线源状态变化事件
|
||||
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>()
|
||||
.Subscribe(OnRaySourceStatusChanged, ThreadOption.UIThread);
|
||||
|
||||
private void OnRaySourceStatusChanged(RaySourceStatus newStatus)
|
||||
{
|
||||
// newStatus: Unavailable(-1) / Closed(0) / Opened(1)
|
||||
switch (newStatus)
|
||||
{
|
||||
case RaySourceStatus.Unavailable:
|
||||
// 射线源不可用(未初始化或连接丢失)
|
||||
break;
|
||||
case RaySourceStatus.Closed:
|
||||
// 射线源已连接,射线关闭
|
||||
break;
|
||||
case RaySourceStatus.Opened:
|
||||
// 射线源已连接,射线开启
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 系统状态更新事件
|
||||
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>()
|
||||
.Subscribe(OnStatusUpdated, ThreadOption.UIThread);
|
||||
|
||||
private void OnStatusUpdated(SystemStatusData data)
|
||||
{
|
||||
// 全量状态数据,由 Host 进程定期推送
|
||||
float actualVoltage = data.ActualVoltage;
|
||||
float actualCurrent = data.ActualCurrent;
|
||||
bool isXRayOn = data.IsXRayOn;
|
||||
string warmUpStatus = data.WarmUpStatus;
|
||||
// ... 更多字段见 SystemStatusData 定义
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 错误事件
|
||||
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>()
|
||||
.Subscribe(OnErrorOccurred, ThreadOption.UIThread);
|
||||
|
||||
private void OnErrorOccurred(string errorMessage)
|
||||
{
|
||||
_logger.Error(null, "射线源错误: {Error}", errorMessage);
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 操作结果事件
|
||||
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<OperationResultEvent>()
|
||||
.Subscribe(OnOperationResult, ThreadOption.UIThread);
|
||||
|
||||
private void OnOperationResult(OperationResultData data)
|
||||
{
|
||||
// data.OperationName - 操作名称
|
||||
// data.IsSuccess - 是否成功
|
||||
// data.Message - 结果消息
|
||||
}
|
||||
```
|
||||
|
||||
### 8.5 PVI 变量连接状态事件
|
||||
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>()
|
||||
.Subscribe(OnVariablesConnected, ThreadOption.UIThread);
|
||||
|
||||
private void OnVariablesConnected(bool isConnected)
|
||||
{
|
||||
// true = PVI 变量已连接,射线源准备就绪
|
||||
// false = PVI 变量已断开
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 灯丝寿命管理 | Filament Lifetime Management
|
||||
|
||||
### 9.1 自动联动
|
||||
|
||||
灯丝寿命管理与射线源开关自动联动:
|
||||
- 射线源开启(TurnOn)→ 自动开始记录灯丝使用时长
|
||||
- 射线源关闭(TurnOff / Disconnect / EmergencyShutdown)→ 自动停止记录并累加统计
|
||||
- 异常断联 → 下次初始化时自动恢复未关闭的记录
|
||||
|
||||
### 9.2 手动查询
|
||||
|
||||
```csharp
|
||||
// 获取实时累计使用秒数(含当前运行时长)
|
||||
double totalSeconds = _filamentLifetimeService.GetCurrentTotalLifeSeconds();
|
||||
|
||||
// 获取寿命百分比(0-100)
|
||||
double percentage = _filamentLifetimeService.GetLifetimePercentage();
|
||||
|
||||
// 获取阈值秒数
|
||||
double thresholdSeconds = _filamentLifetimeService.GetThresholdSeconds();
|
||||
|
||||
// 检查是否需要预警(≥90%)
|
||||
bool shouldWarn = _filamentLifetimeService.ShouldShowLifetimeWarning();
|
||||
```
|
||||
|
||||
### 9.3 数据持久化
|
||||
|
||||
灯丝寿命数据通过 SQLite 持久化,包含两张表:
|
||||
- `RaySourceFilamentLifetimeStatistics`:累计统计表(按序列号唯一)
|
||||
- `RaySourceFilamentUsageLogs`:使用流水表(每次开关记录一条)
|
||||
|
||||
---
|
||||
|
||||
## 10. 在应用退出时释放资源 | Release Resources on Application Exit
|
||||
|
||||
```csharp
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
Log.Information("XplorePlane 主应用退出");
|
||||
|
||||
// 释放射线源资源 | Release X-ray source resources
|
||||
try
|
||||
{
|
||||
var raySourceService = Container.Resolve<IRaySourceService>();
|
||||
raySourceService?.Dispose();
|
||||
Log.Information("射线源资源已成功释放");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "射线源资源释放失败,忽略该错误继续退出");
|
||||
}
|
||||
|
||||
Log.CloseAndFlush();
|
||||
base.OnExit(e);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 安全机制 | Safety Mechanisms
|
||||
|
||||
### 11.1 参数范围验证
|
||||
|
||||
```csharp
|
||||
// 服务层自动校验参数范围,超出范围返回错误结果
|
||||
XRayResult result = _raySourceService.SetVoltage(300f); // 超出 225kV 上限
|
||||
// result.Success == false
|
||||
// result.ErrorMessage == "电压参数超出范围:20-225kV"
|
||||
```
|
||||
|
||||
### 11.2 紧急关闭优先级
|
||||
|
||||
紧急关闭具有最高优先级,可以在任何状态下执行:
|
||||
|
||||
```csharp
|
||||
// 紧急关闭:依次关闭射线 → 完全关闭设备 → 状态重置为 Unavailable
|
||||
_raySourceService.EmergencyShutdown();
|
||||
```
|
||||
|
||||
### 11.3 业务规则校验
|
||||
|
||||
- 未初始化禁止操作(所有操作前检查 `IsInitialized`)
|
||||
- 防重复开启/关闭(双重检查锁定模式)
|
||||
- 异常断联自动检测(状态变为 Unavailable 时重置标志位)
|
||||
|
||||
### 11.4 实时调节支持
|
||||
|
||||
连接射线源后,无论射线是否开启,都可以实时调节电压和电流。
|
||||
|
||||
---
|
||||
|
||||
## 12. 异常处理 | Exception Handling
|
||||
|
||||
### 12.1 XRayResult 结果处理
|
||||
|
||||
所有操作返回 `XRayResult` 对象:
|
||||
|
||||
```csharp
|
||||
XRayResult result = _raySourceService.TurnOn();
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
var data = result.Data; // 获取返回数据
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = result.ErrorMessage; // 获取错误消息
|
||||
_logger.Error(null, "操作失败: {Error}", error);
|
||||
}
|
||||
```
|
||||
|
||||
### 12.2 IPC 通信异常
|
||||
|
||||
当 Host 进程崩溃或管道断开时:
|
||||
- `CometIpcClient` 设置 `_isPipeConnected = false`,阻止后续命令发送
|
||||
- 返回 `null` 响应,服务层转换为错误结果
|
||||
- 推送 `Disconnected` 状态,触发 UI 更新
|
||||
|
||||
---
|
||||
|
||||
## 13. 日志记录 | Logging
|
||||
|
||||
模块使用 `ILoggerService` 进行结构化日志记录:
|
||||
|
||||
```csharp
|
||||
// 各层日志模块名称
|
||||
_logger = logger.ForModule("RaySource.Service"); // 服务层
|
||||
_logger = logger.ForModule("RaySource.Factory"); // 工厂层
|
||||
_logger = logger.ForModule<Comet225RaySource>(); // 适配器层
|
||||
_logger = logger.ForModule<CometIpcClient>(); // IPC 客户端
|
||||
_logger = logger.ForModule<CometHostManager>(); // Host 管理器
|
||||
_logger = logger.ForModule("RaySource.ViewModel"); // 操作视图模型
|
||||
_logger = logger.ForModule("RaySource.ConfigVM"); // 配置视图模型
|
||||
_logger = logger.ForModule<FilamentLifetimeService>(); // 灯丝寿命服务
|
||||
```
|
||||
|
||||
### 日志级别
|
||||
|
||||
- **Debug**: 详细调试信息(IPC 消息收发、电压电流读取等)
|
||||
- **Info**: 一般信息(初始化成功、射线开启、状态变更等)
|
||||
- **Warn**: 警告信息(参数超出范围、重复操作、异常恢复等)
|
||||
- **Error**: 错误信息(操作失败、通讯异常、IPC 超时等)
|
||||
- **Fatal**: 致命错误(系统崩溃等)
|
||||
|
||||
---
|
||||
|
||||
## 14. 故障排查 | Troubleshooting
|
||||
|
||||
### 14.1 初始化失败
|
||||
|
||||
**可能原因**:
|
||||
- Host 可执行文件未找到(检查 `{主程序目录}/Host/XP.Hardware.RaySource.Comet.Host.exe`)
|
||||
- PLC IP 地址或端口配置错误
|
||||
- PLC 未启动或网络不通
|
||||
- Named Pipe 连接超时
|
||||
- 残留的 Host 进程未清理
|
||||
|
||||
**解决方案**:
|
||||
1. 确认 Host 可执行文件和 BR.AN.PviServices.dll 已正确部署
|
||||
2. 检查 `App.config` 中的 PLC 连接参数
|
||||
3. 使用 `ping` 命令测试 PLC 网络连接
|
||||
4. 查看日志中 Host 进程 PID 和管道连接状态
|
||||
5. 手动终止残留的 `XP.Hardware.RaySource.Comet.Host.exe` 进程
|
||||
|
||||
### 14.2 射线无法开启
|
||||
|
||||
**可能原因**:
|
||||
- 射线源未初始化或变量未连接
|
||||
- 设备处于错误状态
|
||||
- 互锁信号未满足
|
||||
|
||||
**解决方案**:
|
||||
1. 确认 `IsInitialized == true` 且 `IsConnected == true`
|
||||
2. 调用 `CheckErrors()` 检查错误状态
|
||||
3. 检查设备互锁信号
|
||||
|
||||
### 14.3 电压电流设置失败
|
||||
|
||||
**可能原因**:
|
||||
- 参数超出配置范围
|
||||
- 射线源未初始化
|
||||
- IPC 管道已断开
|
||||
|
||||
**解决方案**:
|
||||
1. 验证参数在有效范围内
|
||||
2. 检查 `IsInitialized` 状态
|
||||
3. 查看日志中 IPC 通信状态
|
||||
|
||||
### 14.4 Host 进程异常退出
|
||||
|
||||
**可能原因**:
|
||||
- BR.AN.PviServices.dll 缺失或版本不匹配
|
||||
- PVI Service 未正确安装
|
||||
- Host 进程内存异常
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 Host 目录下 DLL 文件完整性
|
||||
2. 确认 B&R Automation Studio 和 PVI Services 已安装
|
||||
3. 查看 Host 进程 stderr 日志输出
|
||||
|
||||
### 14.5 灯丝寿命数据异常
|
||||
|
||||
**可能原因**:
|
||||
- SerialNumber 配置为空
|
||||
- SQLite 数据库文件损坏
|
||||
- 异常断电导致未关闭记录
|
||||
|
||||
**解决方案**:
|
||||
1. 确认 `App.config` 中 `RaySource:SerialNumber` 已配置
|
||||
2. 检查数据库文件完整性
|
||||
3. 服务初始化时会自动恢复未关闭记录(标记为异常中断,DurationSeconds=0)
|
||||
|
||||
---
|
||||
|
||||
## 15. 注意事项 | Notes
|
||||
|
||||
1. **线程安全**: `IRaySourceService` 是单例,内部使用 `lock` 保护关键操作
|
||||
2. **同步 API**: 所有 `IXRaySource` 和 `IRaySourceService` 方法为同步方法,ViewModel 中通过 `Task.Run` 包装避免阻塞 UI
|
||||
3. **InitializeAndConnectAsync**: 唯一的异步便捷方法,内部通过 `Task.Run` 包装同步调用
|
||||
4. **实时调节**: 连接后可随时调节电压电流,无论射线是否开启
|
||||
5. **资源释放**: 应用退出时务必调用 `Dispose()` 释放资源(会触发紧急关闭和 Host 进程终止)
|
||||
6. **事件订阅**: 记得在 ViewModel 销毁时取消事件订阅
|
||||
7. **配置验证**: 启动前自动验证配置参数的有效性
|
||||
8. **日志记录**: 所有关键操作都有详细的结构化日志记录
|
||||
9. **错误处理**: 始终检查 `XRayResult.Success` 并处理错误
|
||||
10. **Host 进程**: CometHostManager 会自动清理残留进程并管理 Host 生命周期
|
||||
|
||||
---
|
||||
|
||||
## 16. 文档索引 | Documentation Index
|
||||
|
||||
- **[GUIDENCE.md](./GUIDENCE.md)** - 本文档,详细使用指南
|
||||
- **[README.md](./README.md)** - 项目概述和快速参考
|
||||
- **[ProjectStructure.md](./ProjectStructure.md)** - 项目结构详解和调用链路
|
||||
- **[README_RaySourceOperateView.md](./README_RaySourceOperateView.md)** - 操作视图使用说明
|
||||
- **[App.config.example](./App.config.example)** - 配置文件示例
|
||||
|
||||
---
|
||||
|
||||
**最后更新 | Last Updated**: 2026-03-26
|
||||
@@ -0,0 +1,310 @@
|
||||
# XP.Hardware.RaySource 项目结构文档
|
||||
|
||||
## 1. 项目概览
|
||||
|
||||
| 项目 | 框架 | 类型 | 命名空间 | 程序集名称 |
|
||||
|------|------|------|----------|-----------|
|
||||
| XP.Hardware.RaySource | .NET 8.0 (net8.0-windows7.0) | WPF 类库 | `XP.Hardware.RaySource.*` | XP.Hardware.RaySource |
|
||||
| XP.Hardware.RaySource.Comet.Host | .NET Framework 4.8 | WinForms 控制台 | `XP.Hardware.RaySource.Comet.Host` | XP.Hardware.RaySource.Comet.Host |
|
||||
| XP.Hardware.RaySource.Comet.Messages | netstandard2.0 | 类库 | `XP.Hardware.RaySource.Comet.Messages` | XP.Hardware.RaySource.Comet.Messages |
|
||||
|
||||
## 2. 版本信息
|
||||
|
||||
### XP.Hardware.RaySource
|
||||
|
||||
- 目标框架:`net8.0-windows7.0`
|
||||
- SDK 风格项目
|
||||
- NuGet 依赖:
|
||||
- `Prism.Wpf` 9.0.537
|
||||
- `System.Configuration.ConfigurationManager` 8.0.0
|
||||
- `Telerik.UI.for.Wpf.NetCore.Xaml` 2024.1.408
|
||||
- 项目引用:
|
||||
- `XP.Common`(日志、数据库、多语言基础设施)
|
||||
- `XP.Hardware.RaySource.Comet.Messages`(IPC 消息定义)
|
||||
|
||||
### XP.Hardware.RaySource.Comet.Host
|
||||
|
||||
- 目标框架:`.NET Framework 4.8`
|
||||
- WinForms 进程(因依赖 B&R PVI Services COM 组件,无法迁移到 .NET 8)
|
||||
- 外部依赖:
|
||||
- `BR.AN.PviServices.dll`(Version 1.1.0.0,贝加莱 PVI 通讯库)
|
||||
- `Newtonsoft.Json`(JSON 序列化)
|
||||
- 项目引用:
|
||||
- `XP.Hardware.RaySource.Comet.Messages`(IPC 消息定义)
|
||||
|
||||
### XP.Hardware.RaySource.Comet.Messages
|
||||
|
||||
- 目标框架:`netstandard2.0`(跨框架共享)
|
||||
- 定义 IPC 命令和响应消息类型
|
||||
- 被 RaySource(.NET 8)和 Comet.Host(.NET Framework 4.8)共同引用
|
||||
|
||||
## 3. 引用关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ XP.Hardware.RaySource (.NET 8.0 WPF 类库) │
|
||||
│ │
|
||||
│ ProjectReference ──► XP.Common │
|
||||
│ ProjectReference ──► XP.Hardware.RaySource.Comet.Messages │
|
||||
│ │
|
||||
│ NuGet: Prism.Wpf, Telerik, ConfigurationManager │
|
||||
└────────────────────┬────────────────────────────────────────┘
|
||||
│ Named Pipe IPC(进程间通信)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ XP.Hardware.RaySource.Comet.Host (.NET Framework 4.8) │
|
||||
│ │
|
||||
│ ProjectReference ──► XP.Hardware.RaySource.Comet.Messages │
|
||||
│ 外部引用 ──► BR.AN.PviServices.dll(贝加莱 PVI 通讯库) │
|
||||
│ NuGet: Newtonsoft.Json │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ XP.Hardware.RaySource.Comet.Messages (netstandard2.0) │
|
||||
│ │
|
||||
│ 共享消息库,定义 IPC 命令和响应类型 │
|
||||
│ 被 RaySource 和 Comet.Host 共同引用 │
|
||||
│ NuGet: Newtonsoft.Json │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 进程隔离架构说明
|
||||
|
||||
RaySource(.NET 8)通过 Named Pipe 与 Comet.Host(.NET Framework 4.8)进行进程间通信。之所以采用这种架构,是因为 `BR.AN.PviServices.dll`(贝加莱 PVI 通讯库)仅支持 .NET Framework,无法直接在 .NET 8 中使用。因此将 PVI 通讯层隔离到独立的 .NET Framework 4.8 Host 子进程中,通过 Named Pipe 双管道模式实现跨框架通信。
|
||||
|
||||
`Comet.Messages` 作为 `netstandard2.0` 共享库,定义了两端通信的命令和响应消息格式,确保序列化/反序列化的一致性。
|
||||
|
||||
## 4. XP.Hardware.RaySource 项目结构
|
||||
|
||||
```
|
||||
XP.Hardware.RaySource/
|
||||
├── Abstractions/ # 抽象层(接口、基类、枚举、事件)
|
||||
│ ├── Enums/
|
||||
│ │ └── RaySourceStatus.cs # 射线源状态枚举(Unavailable=-1 / Closed=0 / Opened=1)
|
||||
│ ├── Events/
|
||||
│ │ ├── ErrorOccurredEvent.cs # 错误触发事件(PubSubEvent<string>)
|
||||
│ │ ├── OperationResultEvent.cs # 操作结果事件(PubSubEvent<OperationResultData>)
|
||||
│ │ ├── RaySourceStatusChangedEvent.cs # 射线源状态变更事件(PubSubEvent<RaySourceStatus>)
|
||||
│ │ ├── StatusUpdatedEvent.cs # 系统状态更新事件(PubSubEvent<SystemStatusData>)
|
||||
│ │ └── VariablesConnectedEvent.cs # PVI 变量连接状态事件(PubSubEvent<bool>)
|
||||
│ ├── IRaySourceFactory.cs # 射线源工厂接口
|
||||
│ ├── IXRaySource.cs # 射线源策略接口(核心接口,同步方法)
|
||||
│ ├── XRayResult.cs # 操作结果封装类(Ok/Error 静态工厂方法)
|
||||
│ └── XRaySourceBase.cs # 射线源抽象基类(含 Dispose 模式)
|
||||
├── Config/
|
||||
│ ├── ConfigLoader.cs # 配置加载器(支持加载和保存到 App.config)
|
||||
│ └── RaySourceConfig.cs # 射线源配置实体类(含验证方法)
|
||||
├── Converters/
|
||||
│ ├── RaySourceOperateConverter.cs # WPF 值转换器(状态→径向渐变色/边框色/启用状态)
|
||||
│ └── FilamentLifetimeColorConverter.cs # 灯丝寿命百分比→颜色(绿/黄/红)
|
||||
├── Factories/
|
||||
│ └── RaySourceFactory.cs # 射线源工厂实现(创建 CometHostManager + CometIpcClient + Comet225RaySource)
|
||||
├── Implementations/
|
||||
│ ├── Comet225RaySource.cs # Comet 225kV 射线源适配器(委托 IPC 客户端和 Host 管理器)
|
||||
│ ├── CometHostManager.cs # Host 进程生命周期管理器(启动/监控/关闭子进程)
|
||||
│ └── CometIpcClient.cs # Named Pipe IPC 客户端(双管道通信,消息收发)
|
||||
├── Module/
|
||||
│ └── RaySourceModule.cs # Prism 模块入口(DI 注册 + 灯丝寿命初始化 + 预警检查)
|
||||
├── Services/
|
||||
│ ├── IRaySourceService.cs # 射线源业务服务接口(含 InitializeAndConnectAsync)
|
||||
│ ├── RaySourceService.cs # 射线源业务服务实现(单例,业务规则校验 + 灯丝寿命联动)
|
||||
│ ├── IFilamentLifetimeService.cs # 灯丝寿命管理服务接口
|
||||
│ └── FilamentLifetimeService.cs # 灯丝寿命管理服务实现(SQLite 持久化)
|
||||
├── ViewModels/
|
||||
│ ├── RaySourceConfigViewModel.cs # 配置视图模型(初始化/连接/断开/设备状态监控/灯丝寿命进度条)
|
||||
│ └── RaySourceOperateViewModel.cs # 操作视图模型(开关/电压电流调节/自动初始化)
|
||||
├── Views/
|
||||
│ ├── RaySourceConfigView.xaml(.cs) # 配置视图(设备状态面板 + 功能按钮 + 灯丝寿命进度条)
|
||||
│ ├── RaySourceConfigWindow.xaml(.cs) # 配置窗口(包裹 ConfigView 的独立窗口)
|
||||
│ └── RaySourceOperateView.xaml(.cs) # 操作视图(腰圆状态指示器 + 开关按钮 + 电压电流滑块)
|
||||
└── Documents/ # 文档目录
|
||||
```
|
||||
|
||||
|
||||
## 5. 调用链路详解
|
||||
|
||||
### 5.1 整体架构(适配器模式 + IPC 进程隔离 + 事件驱动)
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐ ┌────────────────┐ ┌──────────────┐
|
||||
│ ViewModel │───►│ RaySourceService │───►│ Comet225RaySource │───►│ CometIpcClient │───►│ Host 进程 │
|
||||
│ (UI 层) │ │ (业务服务层) │ │ (适配器层) │ │ (IPC 通信层) │ │ (PVI 通讯层) │
|
||||
└──────────────┘ └──────────────────┘ └───────────────────┘ └────────────────┘ └──────────────┘
|
||||
▲ ▲ │ │
|
||||
│ │ │ │
|
||||
└────────── Prism EventAggregator ◄──────────────────────────────────┘◄─── Named Pipe ──────┘
|
||||
(推送消息路由为 Prism 事件)
|
||||
```
|
||||
|
||||
### 5.2 调用流程
|
||||
|
||||
#### 初始化流程
|
||||
|
||||
```
|
||||
1. RaySourceModule.RegisterTypes()
|
||||
→ 注册 RaySourceConfig(从 App.config 加载)
|
||||
→ 注册 IRaySourceFactory → RaySourceFactory(单例)
|
||||
→ 注册 IRaySourceService → RaySourceService(单例)
|
||||
→ 注册 IFilamentLifetimeService → FilamentLifetimeService(单例)
|
||||
|
||||
2. RaySourceModule.OnInitialized()
|
||||
→ FilamentLifetimeService.Initialize()(建表、异常恢复、重算累计寿命)
|
||||
→ 检查灯丝寿命预警(≥90% 弹出对话框)
|
||||
|
||||
3. ViewModel 调用 IRaySourceService.Initialize()
|
||||
→ RaySourceService.Initialize()
|
||||
→ 验证配置参数
|
||||
→ IRaySourceFactory.CreateRaySource("Comet225")
|
||||
→ new CometHostManager(logger, config)
|
||||
→ new CometIpcClient(config, eventAggregator, logger)
|
||||
→ new Comet225RaySource(hostManager, ipcClient, logger)
|
||||
→ Comet225RaySource.Initialize()
|
||||
→ CometHostManager.EnsureRunning() // 启动 Host 子进程
|
||||
→ 清理残留进程 → 启动新进程 → 等待 Named Pipe 就绪
|
||||
→ CometIpcClient.Initialize()
|
||||
→ Connect() // 连接双管道
|
||||
→ SendCommand(InitializeCommand) // 发送初始化命令
|
||||
→ Host 端执行 PVI Service/CPU 连接
|
||||
→ 等待 ServiceConnected 推送确认
|
||||
```
|
||||
|
||||
#### 变量连接流程
|
||||
|
||||
```
|
||||
4. ViewModel 调用 IRaySourceService.ConnectVariables()
|
||||
→ RaySourceService.ConnectVariables()
|
||||
→ Comet225RaySource.ConnectVariables()
|
||||
→ CometIpcClient.ConnectVariables()
|
||||
→ SendCommand(ConnectVariablesCommand)
|
||||
→ Host 端执行:CreateVariables → ActivateVariables → BindEventHandlers
|
||||
→ Host 推送 VariablesConnected → RaySourceConnected
|
||||
→ CometIpcClient 路由推送为 Prism 事件
|
||||
→ VariablesConnectedEvent(true)
|
||||
→ RaySourceStatusChangedEvent(Closed)
|
||||
```
|
||||
|
||||
#### 操作流程(以开启射线为例)
|
||||
|
||||
```
|
||||
5. ViewModel.ExecuteTurnOn()
|
||||
→ RaySourceService.TurnOn() // 业务规则校验(状态检查、防重复、双重检查锁定)
|
||||
→ Comet225RaySource.TurnOn() // 适配器层
|
||||
→ CometIpcClient.TurnOn() // IPC 通信层
|
||||
→ SendCommand(TurnOnCommand) // 序列化为 JSON,写入命令管道
|
||||
→ Host 端执行 PVI 变量写入
|
||||
→ Host 返回响应 → CometIpcClient 接收
|
||||
→ 更新状态为 Opened
|
||||
→ 发布 RaySourceStatusChangedEvent
|
||||
→ FilamentLifetimeService.StartFilamentUsage() // 记录灯丝使用开始
|
||||
```
|
||||
|
||||
#### 状态反馈流程(事件驱动)
|
||||
|
||||
```
|
||||
6. Host 端 PVI 变量值变化
|
||||
→ Host 构造推送消息(StatusChanged / XRayStateChanged / ErrorOccurred 等)
|
||||
→ 写入响应管道
|
||||
→ CometIpcClient.ReceiveLoop() 接收
|
||||
→ 识别为推送消息(IsPush=true)
|
||||
→ HandlePushMessage() 路由到对应 Prism 事件
|
||||
→ StatusUpdatedEvent(全量状态数据)
|
||||
→ RaySourceStatusChangedEvent(三态变更)
|
||||
→ ErrorOccurredEvent(错误信息)
|
||||
→ VariablesConnectedEvent(连接状态)
|
||||
→ ViewModel 通过事件订阅更新 UI
|
||||
```
|
||||
|
||||
### 5.3 IPC 通信机制
|
||||
|
||||
#### Named Pipe 双管道模式
|
||||
|
||||
| 管道名称 | 方向 | 用途 |
|
||||
|----------|------|------|
|
||||
| `XP.Hardware.RaySource.Comet.Cmd` | 客户端 → Host | 命令管道(写入命令 JSON) |
|
||||
| `XP.Hardware.RaySource.Comet.Rsp` | Host → 客户端 | 响应管道(接收响应和推送) |
|
||||
|
||||
#### 消息类型
|
||||
|
||||
- **命令消息**:客户端发送,Host 处理后返回响应(请求-响应模式)
|
||||
- **推送消息**:Host 主动推送状态变化(`IsPush=true`),包括:
|
||||
- `StatusChanged`:全量状态数据(电压/电流/各子系统状态)
|
||||
- `XRayStateChanged`:射线开关状态变化
|
||||
- `ErrorOccurred`:错误信息
|
||||
- `ConnectionStateChanged`:连接状态变化(ServiceConnected / VariablesConnected / RaySourceConnected / Disconnected)
|
||||
- `Log`:Host 端日志转发(按级别映射到主进程 ILoggerService)
|
||||
|
||||
#### 超时机制
|
||||
|
||||
- 命令超时:35000ms(大于 Host 端 PVI 连接超时 30s)
|
||||
- 管道连接超时:使用 `RaySourceConfig.ConnectionTimeout`(默认 5000ms)
|
||||
- Host 进程关闭超时:5000ms(超时则强制终止)
|
||||
|
||||
### 5.4 日志桥接机制
|
||||
|
||||
Host 进程(.NET Framework 4.8)通过 Named Pipe 推送日志消息到主进程。`CometIpcClient` 接收 `LogResponse` 推送后,根据 `Level` 字段映射到 `ILoggerService` 对应方法:
|
||||
|
||||
```
|
||||
Host 端日志 → LogResponse(JSON)→ Named Pipe → CometIpcClient.HandleLogPush()
|
||||
→ Level="Debug" → _logger.Debug(message, args)
|
||||
→ Level="Info" → _logger.Info(message, args)
|
||||
→ Level="Warn" → _logger.Warn(message, args)
|
||||
→ Level="Error" → _logger.Error(null, message, args)
|
||||
→ Level="Fatal" → _logger.Fatal(null, message, args)
|
||||
```
|
||||
|
||||
## 6. 灯丝寿命管理架构
|
||||
|
||||
### 6.1 数据库表结构
|
||||
|
||||
#### RaySourceFilamentLifetimeStatistics(累计统计表)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| Id | INTEGER PK | 自增主键 |
|
||||
| SourceType | TEXT | 射线源类型 |
|
||||
| SerialNumber | TEXT | 序列号(唯一索引) |
|
||||
| TotalLifeSeconds | REAL | 累计使用秒数 |
|
||||
| LastUpdateTime | TEXT | 最后更新时间(ISO 8601) |
|
||||
|
||||
#### RaySourceFilamentUsageLogs(使用流水表)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| Id | INTEGER PK | 自增主键 |
|
||||
| SourceType | TEXT | 射线源类型 |
|
||||
| SerialNumber | TEXT | 序列号 |
|
||||
| StartTime | TEXT | 开始时间(ISO 8601) |
|
||||
| EndTime | TEXT | 结束时间(可为 NULL) |
|
||||
| DurationSeconds | REAL | 持续秒数 |
|
||||
| Status | INTEGER | 0=正常,1=异常中断 |
|
||||
|
||||
### 6.2 灯丝寿命联动
|
||||
|
||||
- 射线源开启(TurnOn)→ `StartFilamentUsage()`:插入流水记录
|
||||
- 射线源关闭(TurnOff)→ `StopFilamentUsage()`:事务更新流水记录 + 累加统计表
|
||||
- 断开连接 / 紧急关闭 → `StopFilamentUsage()`:同上
|
||||
- 异常断联 → 下次初始化时 `RecoverUnclosedRecords()`:将未关闭记录标记为异常中断
|
||||
|
||||
### 6.3 预警机制
|
||||
|
||||
- 模块初始化时检查灯丝寿命百分比
|
||||
- 百分比 ≥ 90% 时弹出模态预警对话框
|
||||
- ConfigView 中显示灯丝寿命进度条(60 秒定时刷新)
|
||||
- 进度条颜色:< 80% 绿色,80%-89% 黄色,≥ 90% 红色
|
||||
|
||||
## 7. 设计模式总结
|
||||
|
||||
| 模式 | 应用位置 | 说明 |
|
||||
|------|---------|------|
|
||||
| 策略模式 | `IXRaySource` → `Comet225RaySource` | 支持多种射线源实现的可扩展架构 |
|
||||
| 工厂模式 | `IRaySourceFactory` → `RaySourceFactory` | 根据配置类型创建对应射线源实例 |
|
||||
| 适配器模式 | `Comet225RaySource` | 将 IPC 通信适配为 IXRaySource 接口 |
|
||||
| 进程隔离 | `CometHostManager` + `CometIpcClient` | 通过 Named Pipe 实现 .NET 8 与 .NET Framework 4.8 跨框架通信 |
|
||||
| 事件驱动 | Prism EventAggregator + IPC 推送 | 解耦 PVI 通讯层与 UI 层的状态同步 |
|
||||
| 依赖注入 | Prism DI 容器 | 通过 RaySourceModule 注册所有服务 |
|
||||
| 单例模式 | RaySourceService、RaySourceFactory、FilamentLifetimeService | 全局唯一的服务和工厂实例 |
|
||||
| 双重检查锁定 | RaySourceService.TurnOn/TurnOff | 防止并发重复操作 |
|
||||
|
||||
---
|
||||
|
||||
**最后更新 | Last Updated**: 2026-03-26
|
||||
@@ -0,0 +1,413 @@
|
||||
# XP.Hardware.RaySource
|
||||
|
||||
工业 X 射线源控制模块 | Industrial X-Ray Source Control Module
|
||||
|
||||
---
|
||||
|
||||
## 项目概述 | Project Overview
|
||||
|
||||
XP.Hardware.RaySource 是 XplorePlane X 射线检测系统的核心硬件控制模块,负责与工业 X 射线源设备进行通讯和控制。该模块采用策略模式和工厂模式设计,支持多种 X 射线源型号的统一管理,提供完整的设备生命周期控制和安全机制。
|
||||
|
||||
### 主要特性 | Key Features
|
||||
|
||||
- 支持多种 X 射线源型号(当前支持 Comet 225kV)
|
||||
- 基于 Named Pipe IPC 的进程隔离架构(.NET 8 主进程 ↔ .NET Framework 4.8 Host 进程)
|
||||
- 完整的设备生命周期管理(初始化、连接变量、暖机、训机、灯丝校准、自动定心、开关射线)
|
||||
- 精确的电压电流控制(20-225kV / 10-1000μA)
|
||||
- 实时状态监控和错误检测
|
||||
- 基于 Prism 事件聚合器的松耦合跨模块通讯
|
||||
- 安全机制:紧急关闭优先级、参数范围验证
|
||||
- 灯丝寿命管理:使用时长记录、累计统计、预警提醒
|
||||
- TXI 开关控制、功率模式切换(Micro Focus / High Power)
|
||||
- 完整的日志记录和异常处理
|
||||
|
||||
---
|
||||
|
||||
## 框架架构 | Architecture
|
||||
|
||||
```
|
||||
XP.Hardware.RaySource/
|
||||
├── Abstractions/ # 抽象层 | Abstraction Layer
|
||||
│ ├── IXRaySource.cs # 策略接口(同步方法)
|
||||
│ ├── XRaySourceBase.cs # 抽象基类
|
||||
│ ├── XRayResult.cs # 结果封装类
|
||||
│ ├── IRaySourceFactory.cs # 工厂接口
|
||||
│ ├── Enums/ # 枚举定义
|
||||
│ │ └── RaySourceStatus.cs # 三态枚举(Unavailable/Closed/Opened)
|
||||
│ └── Events/ # Prism 事件定义
|
||||
│ ├── StatusUpdatedEvent.cs # 系统状态更新事件
|
||||
│ ├── ErrorOccurredEvent.cs # 错误触发事件
|
||||
│ ├── OperationResultEvent.cs # 操作结果事件
|
||||
│ ├── RaySourceStatusChangedEvent.cs # 射线源状态变更事件
|
||||
│ └── VariablesConnectedEvent.cs # PVI 变量连接状态事件
|
||||
├── Implementations/ # 实现层 | Implementation Layer
|
||||
│ ├── Comet225RaySource.cs # Comet 225kV 适配器(委托 IPC 客户端)
|
||||
│ ├── CometHostManager.cs # Host 进程生命周期管理器
|
||||
│ └── CometIpcClient.cs # Named Pipe IPC 客户端
|
||||
├── Factories/ # 工厂层 | Factory Layer
|
||||
│ └── RaySourceFactory.cs # 射线源工厂
|
||||
├── Services/ # 业务服务层 | Service Layer
|
||||
│ ├── IRaySourceService.cs # 服务接口
|
||||
│ ├── RaySourceService.cs # 服务实现(单例)
|
||||
│ ├── IFilamentLifetimeService.cs # 灯丝寿命服务接口
|
||||
│ └── FilamentLifetimeService.cs # 灯丝寿命服务实现
|
||||
├── Config/ # 配置层 | Configuration Layer
|
||||
│ ├── RaySourceConfig.cs # 配置实体
|
||||
│ └── ConfigLoader.cs # 配置加载器(支持保存)
|
||||
├── Module/ # Prism 模块 | Prism Module
|
||||
│ └── RaySourceModule.cs # 模块注册
|
||||
├── ViewModels/ # 视图模型 | View Models
|
||||
│ ├── RaySourceConfigViewModel.cs # 配置视图模型(初始化/连接/断开/设备状态监控/灯丝寿命)
|
||||
│ └── RaySourceOperateViewModel.cs # 操作视图模型(开关/电压电流调节)
|
||||
├── Views/ # WPF 视图 | WPF Views
|
||||
│ ├── RaySourceConfigView.xaml # 配置视图(设备状态面板)
|
||||
│ ├── RaySourceConfigWindow.xaml # 配置窗口(包裹 ConfigView)
|
||||
│ └── RaySourceOperateView.xaml # 操作视图(开关/滑块)
|
||||
├── Converters/ # WPF 值转换器 | WPF Value Converters
|
||||
│ ├── RaySourceOperateConverter.cs # 状态→颜色/边框色/启用状态
|
||||
│ └── FilamentLifetimeColorConverter.cs # 灯丝寿命百分比→颜色
|
||||
└── Documents/ # 文档 | Documentation
|
||||
├── README.md # 项目说明(本文档)
|
||||
├── ProjectStructure.md # 项目结构详解
|
||||
├── GUIDENCE.md # 使用指南
|
||||
├── README_RaySourceOperateView.md # 操作视图说明
|
||||
└── App.config.example # 配置文件示例
|
||||
```
|
||||
|
||||
|
||||
### 设计模式 | Design Patterns
|
||||
|
||||
- **策略模式**:`IXRaySource` 接口定义统一操作,支持多种设备实现
|
||||
- **工厂模式**:`IRaySourceFactory` 根据设备类型动态创建实例
|
||||
- **适配器模式**:`Comet225RaySource` 将 IPC 通信适配为 `IXRaySource` 接口
|
||||
- **进程隔离**:通过 `CometHostManager` + `CometIpcClient` 实现 .NET 8 与 .NET Framework 4.8 的跨框架通信
|
||||
- **模板方法模式**:`XRaySourceBase` 提供基础实现框架
|
||||
- **单例模式**:`IRaySourceService`、`IFilamentLifetimeService` 作为全局单例
|
||||
- **依赖注入**:通过 Prism 容器管理服务生命周期
|
||||
- **事件聚合器**:使用 Prism `IEventAggregator` 实现松耦合通讯
|
||||
|
||||
### IPC 进程隔离架构 | IPC Process Isolation Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ .NET 8 主进程 (XP.Hardware.RaySource) │
|
||||
│ │
|
||||
│ ViewModel → RaySourceService → Comet225RaySource │
|
||||
│ │ │
|
||||
│ CometHostManager(管理 Host 进程生命周期)│
|
||||
│ CometIpcClient(Named Pipe 双管道通信) │
|
||||
│ │ │
|
||||
│ NamedPipe: Cmd(写入)/ Rsp(读取) │
|
||||
└──────────────────────────────┬───────────────────────────────────────┘
|
||||
│ Named Pipe IPC
|
||||
┌──────────────────────────────┴───────────────────────────────────────┐
|
||||
│ .NET Framework 4.8 Host 进程 (Comet.Host) │
|
||||
│ │
|
||||
│ CometPviClient ← BR.AN.PviServices.dll │
|
||||
│ (PVI 通讯层,直接操作 PLC 变量) │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心功能 | Core Features
|
||||
|
||||
### 1. 设备生命周期管理 | Device Lifecycle Management
|
||||
|
||||
```csharp
|
||||
// 初始化射线源(类型从配置文件读取)
|
||||
XRayResult result = _raySourceService.Initialize();
|
||||
|
||||
// 连接 PVI 变量并启动实时状态通讯
|
||||
XRayResult connectResult = _raySourceService.ConnectVariables();
|
||||
|
||||
// 异步执行初始化 + 连接变量的完整流程
|
||||
XRayResult autoResult = await _raySourceService.InitializeAndConnectAsync();
|
||||
|
||||
// 开启射线
|
||||
_raySourceService.TurnOn();
|
||||
|
||||
// 关闭射线
|
||||
_raySourceService.TurnOff();
|
||||
|
||||
// 紧急关闭(最高优先级)
|
||||
_raySourceService.EmergencyShutdown();
|
||||
|
||||
// 断开连接(保留实例以便重连)
|
||||
_raySourceService.Disconnect();
|
||||
```
|
||||
|
||||
### 2. 电压电流控制 | Voltage and Current Control
|
||||
|
||||
```csharp
|
||||
// 设置电压(20-225kV)
|
||||
_raySourceService.SetVoltage(100f);
|
||||
|
||||
// 设置电流(10-1000μA)
|
||||
_raySourceService.SetCurrent(500f);
|
||||
|
||||
// 读取实际电压
|
||||
XRayResult voltageResult = _raySourceService.ReadVoltage();
|
||||
float voltage = voltageResult.GetFloat();
|
||||
|
||||
// 读取实际电流
|
||||
XRayResult currentResult = _raySourceService.ReadCurrent();
|
||||
float current = currentResult.GetFloat();
|
||||
```
|
||||
|
||||
### 3. 设备操作 | Device Operations
|
||||
|
||||
```csharp
|
||||
// TXI 开启/关闭
|
||||
_raySourceService.TxiOn();
|
||||
_raySourceService.TxiOff();
|
||||
|
||||
// 暖机
|
||||
_raySourceService.WarmUp();
|
||||
|
||||
// 训机
|
||||
_raySourceService.Training();
|
||||
|
||||
// 灯丝校准
|
||||
_raySourceService.FilamentCalibration();
|
||||
|
||||
// 全部电压自动定心
|
||||
_raySourceService.AutoCenter();
|
||||
|
||||
// 设置功率模式(1=Micro Focus,2=High Power)
|
||||
_raySourceService.SetPowerMode(1);
|
||||
```
|
||||
|
||||
### 4. 状态监控 | Status Monitoring
|
||||
|
||||
```csharp
|
||||
// 读取系统状态
|
||||
_raySourceService.ReadSystemStatus();
|
||||
|
||||
// 检查错误
|
||||
_raySourceService.CheckErrors();
|
||||
|
||||
// 清除错误
|
||||
_raySourceService.ClearErrors();
|
||||
|
||||
// 检查状态属性
|
||||
bool isInitialized = _raySourceService.IsInitialized;
|
||||
bool isConnected = _raySourceService.IsConnected;
|
||||
bool isXRayOn = _raySourceService.IsXRayOn;
|
||||
RaySourceStatus status = _raySourceService.CurrentStatus;
|
||||
```
|
||||
|
||||
### 5. 灯丝寿命管理 | Filament Lifetime Management
|
||||
|
||||
```csharp
|
||||
// 初始化灯丝寿命服务(模块启动时自动调用)
|
||||
_filamentLifetimeService.Initialize();
|
||||
|
||||
// 获取累计使用秒数
|
||||
double totalSeconds = _filamentLifetimeService.GetCurrentTotalLifeSeconds();
|
||||
|
||||
// 获取寿命百分比
|
||||
double percentage = _filamentLifetimeService.GetLifetimePercentage();
|
||||
|
||||
// 检查是否需要预警(≥90%)
|
||||
bool shouldWarn = _filamentLifetimeService.ShouldShowLifetimeWarning();
|
||||
```
|
||||
|
||||
### 6. 事件通讯 | Event Communication
|
||||
|
||||
```csharp
|
||||
// 订阅射线源状态变化事件(三态:Unavailable/Closed/Opened)
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>()
|
||||
.Subscribe(OnRaySourceStatusChanged, ThreadOption.UIThread);
|
||||
|
||||
// 订阅系统状态更新事件(电压/电流/各子系统状态)
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>()
|
||||
.Subscribe(OnStatusUpdated, ThreadOption.UIThread);
|
||||
|
||||
// 订阅错误事件
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>()
|
||||
.Subscribe(OnErrorOccurred, ThreadOption.UIThread);
|
||||
|
||||
// 订阅操作结果事件
|
||||
_eventAggregator.GetEvent<OperationResultEvent>()
|
||||
.Subscribe(OnOperationResult, ThreadOption.UIThread);
|
||||
|
||||
// 订阅 PVI 变量连接状态事件
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>()
|
||||
.Subscribe(OnVariablesConnected, ThreadOption.UIThread);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技术要求 | Technical Requirements
|
||||
|
||||
### 运行环境 | Runtime Environment
|
||||
|
||||
- **.NET 8.0** (net8.0-windows7.0)
|
||||
- **Windows 操作系统**(WPF 依赖)
|
||||
- **Visual Studio 2022** 或更高版本
|
||||
|
||||
### 核心依赖 | Core Dependencies
|
||||
|
||||
| 依赖库 | 版本 | 用途 |
|
||||
|--------|------|------|
|
||||
| **Prism.Wpf** | 9.0.537 | MVVM 框架和依赖注入 |
|
||||
| **Telerik UI for WPF** | 2024.1.408 | UI 控件库 |
|
||||
| **System.Configuration.ConfigurationManager** | 8.0.0 | 配置文件管理 |
|
||||
| **XP.Common** | - | 日志、数据库、多语言基础设施 |
|
||||
| **XP.Hardware.RaySource.Comet.Messages** | - | IPC 消息定义(netstandard2.0 共享库) |
|
||||
|
||||
### 关联项目 | Related Projects
|
||||
|
||||
| 项目 | 框架 | 用途 |
|
||||
|------|------|------|
|
||||
| **XP.Hardware.RaySource.Comet.Host** | .NET Framework 4.8 | Host 子进程,运行 B&R PVI 通讯 |
|
||||
| **XP.Hardware.RaySource.Comet.Messages** | netstandard2.0 | IPC 命令/响应消息定义 |
|
||||
|
||||
---
|
||||
|
||||
## 快速开始 | Quick Start
|
||||
|
||||
### 1. 配置文件设置
|
||||
|
||||
参见 [App.config.example](./App.config.example)
|
||||
|
||||
### 2. 注册模块
|
||||
|
||||
在 `App.xaml.cs` 中:
|
||||
|
||||
```csharp
|
||||
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
|
||||
{
|
||||
moduleCatalog.AddModule<RaySourceModule>();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用服务
|
||||
|
||||
在 ViewModel 中注入并使用:
|
||||
|
||||
```csharp
|
||||
public class YourViewModel : BindableBase
|
||||
{
|
||||
private readonly IRaySourceService _raySourceService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public YourViewModel(
|
||||
IRaySourceService raySourceService,
|
||||
IEventAggregator eventAggregator)
|
||||
{
|
||||
_raySourceService = raySourceService;
|
||||
_eventAggregator = eventAggregator;
|
||||
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>()
|
||||
.Subscribe(OnStatusChanged, ThreadOption.UIThread);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
XRayResult result = _raySourceService.Initialize();
|
||||
if (result.Success)
|
||||
{
|
||||
_raySourceService.ConnectVariables();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置参数说明 | Configuration Parameters
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| SourceType | string | Comet225 | 射线源类型 |
|
||||
| PlcIpAddress | string | 192.168.12.10 | PLC IP 地址 |
|
||||
| PlcPort | int | 11159 | PLC 端口号 |
|
||||
| StationNumber | int | 1 | 源站号 |
|
||||
| PortNumber | int | 11 | 源端口号 |
|
||||
| CpuName | string | cpu | CPU 名称 |
|
||||
| ConnectionTimeout | int | 5000 | 连接超时(毫秒)|
|
||||
| MinVoltage | float | 20 | 最小电压(kV)|
|
||||
| MaxVoltage | float | 225 | 最大电压(kV)|
|
||||
| MinCurrent | float | 10 | 最小电流(μA)|
|
||||
| MaxCurrent | float | 1000 | 最大电流(μA)|
|
||||
| AdvanceExePath | string | FXEControl.exe 路径 | 高级设置外部程序路径 |
|
||||
| HostExePath | string | 空(自动查找) | Host 进程可执行文件路径 |
|
||||
| InitializationTimeout | int | 30000 | 初始化超时(毫秒)|
|
||||
| WarmUpTimeout | int | 300000 | 暖机超时(5分钟)|
|
||||
| StartUpTimeout | int | 180000 | 启动超时(3分钟)|
|
||||
| AutoCenterTimeout | int | 120000 | 自动定心超时(2分钟)|
|
||||
| FilamentAdjustTimeout | int | 120000 | 灯丝调整超时(2分钟)|
|
||||
| GeneralOperationTimeout | int | 10000 | 一般操作超时(毫秒)|
|
||||
| SerialNumber | string | 空 | 射线源序列号(灯丝寿命管理) |
|
||||
| TotalLifeThreshold | int | 1000 | 灯丝总寿命阈值(小时) |
|
||||
|
||||
---
|
||||
|
||||
## 安全机制 | Safety Mechanisms
|
||||
|
||||
### 1. 参数范围验证
|
||||
|
||||
所有参数在设置前都会进行范围验证,超出范围返回错误结果。
|
||||
|
||||
### 2. 紧急关闭优先级
|
||||
|
||||
紧急关闭具有最高优先级,可以在任何状态下执行,会依次关闭射线、完全关闭设备。
|
||||
|
||||
### 3. 业务规则校验
|
||||
|
||||
- 未初始化禁止操作
|
||||
- 防重复开启/关闭(双重检查锁定)
|
||||
- 异常断联自动检测和状态重置
|
||||
|
||||
### 4. 灯丝寿命预警
|
||||
|
||||
灯丝使用时长达到阈值 90% 时,系统自动弹出预警对话框。
|
||||
|
||||
---
|
||||
|
||||
## 文档索引 | Documentation Index
|
||||
|
||||
- **[README.md](./README.md)** - 本文档,项目概述和快速参考
|
||||
- **[ProjectStructure.md](./ProjectStructure.md)** - 项目结构详解和调用链路
|
||||
- **[GUIDENCE.md](./GUIDENCE.md)** - 详细使用指南,包含完整代码示例
|
||||
- **[README_RaySourceOperateView.md](./README_RaySourceOperateView.md)** - 操作视图使用说明
|
||||
- **[App.config.example](./App.config.example)** - 配置文件示例
|
||||
|
||||
---
|
||||
|
||||
## 故障排查 | Troubleshooting
|
||||
|
||||
### 初始化失败
|
||||
- 检查 Host 进程可执行文件是否存在(默认路径:`{主程序目录}/Host/XP.Hardware.RaySource.Comet.Host.exe`)
|
||||
- 检查 PLC IP 地址和端口配置
|
||||
- 确认 PLC 网络连接正常
|
||||
- 查看日志中 Named Pipe 连接状态
|
||||
|
||||
### 射线无法开启
|
||||
- 确认已成功初始化并连接变量(`IsInitialized && IsConnected`)
|
||||
- 检查设备错误状态
|
||||
- 验证互锁信号
|
||||
|
||||
### 电压电流设置失败
|
||||
- 验证参数在有效范围内
|
||||
- 确认射线源已初始化
|
||||
- 检查 IPC 管道连接状态
|
||||
|
||||
### Host 进程异常
|
||||
- 检查 Host 进程是否正常启动(查看日志中 PID 信息)
|
||||
- 确认 BR.AN.PviServices.dll 已正确部署到 Host 目录
|
||||
- 检查是否有残留的 Host 进程(CometHostManager 会自动清理)
|
||||
|
||||
详细故障排查请参考 [GUIDENCE.md](./GUIDENCE.md) 第 14 节。
|
||||
|
||||
---
|
||||
|
||||
## 许可证 | License
|
||||
|
||||
本模块是 XplorePlane 项目的一部分,遵循项目整体许可协议。
|
||||
|
||||
---
|
||||
|
||||
**最后更新 | Last Updated**: 2026-03-26
|
||||
@@ -0,0 +1,378 @@
|
||||
# RaySourceOperateView 使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
`RaySourceOperateView` 是射线源操作和监控面板,提供射线源开关控制、电压电流实时调节和状态监控功能。
|
||||
|
||||
## 架构设计
|
||||
|
||||
### MVVM 模式
|
||||
|
||||
- **View**: `RaySourceOperateView.xaml` - WPF 用户控件界面
|
||||
- **ViewModel**: `RaySourceOperateViewModel.cs` - 视图模型,处理业务逻辑
|
||||
- **Model**: `RaySourceStatus.cs` - 射线源状态枚举(Unavailable / Closed / Opened)
|
||||
|
||||
### 依赖注入
|
||||
|
||||
ViewModel 通过构造函数注入以下依赖:
|
||||
- `IRaySourceService` - 射线源业务服务
|
||||
- `IEventAggregator` - Prism 事件聚合器
|
||||
- `RaySourceConfig` - 射线源配置
|
||||
- `ILoggerService` - 日志服务
|
||||
- `ILocalizationService` - 多语言服务
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 状态监控
|
||||
|
||||
- **腰圆状态指示器**:
|
||||
- 灰色径向渐变 = 射线源不可用(Unavailable)
|
||||
- 绿色径向渐变 (#8BC34A → #4CAF50) = 射线源已关闭(Closed)
|
||||
- 红色径向渐变 (#FF8A80 → #F44336) = 射线源已开启(Opened)
|
||||
- **呼吸闪烁动画**:射线源开启时,状态指示器播放呼吸闪烁动画(透明度 1.0 → 0.6 循环,周期约 1.5 秒)
|
||||
- **实时状态文本**:通过 `ILocalizationService` 获取多语言状态文字
|
||||
|
||||
### 2. 射线源控制
|
||||
|
||||
- **开启射线源按钮**:
|
||||
- 仅在 Closed 状态、已初始化且变量已连接时可用
|
||||
- 调用 `IRaySourceService.TurnOn()`
|
||||
- 按钮文字通过多语言绑定:`{loc:Localization RaySource_TurnOnButton}`
|
||||
- **关闭射线源按钮**:
|
||||
- 仅在 Opened 状态、已初始化且变量已连接时可用
|
||||
- 调用 `IRaySourceService.TurnOff()`
|
||||
- 按钮文字通过多语言绑定:`{loc:Localization RaySource_TurnOffButton}`
|
||||
- **配置按钮**:
|
||||
- 打开 `RaySourceConfigWindow`(射线源配置窗口)
|
||||
- 窗口已存在时激活而非重复创建
|
||||
- **高级/设置按钮**:
|
||||
- 启动外部高级设置程序(`FXEControl.exe`)
|
||||
- 如果程序已运行则将窗口置前
|
||||
- 使用 `ProcessHelper.StartOrActivate()` 实现
|
||||
|
||||
### 3. 电压调节
|
||||
|
||||
- **滑块范围**:从配置文件读取(默认 20-225 kV)
|
||||
- **双向绑定**:滑块和数值输入框绑定同一属性 `VoltageValue`,使用 `Mode=TwoWay`
|
||||
- **延迟拖拽**:滑块使用 `IsDeferredDraggingEnabled="True"`,松手时才提交值
|
||||
- **手动提交**:通过 `ApplyVoltageCommand` 提交电压值到硬件(滑块松手/输入框失焦时触发)
|
||||
- **启用条件**:仅在服务已初始化且变量已连接时可调节(`IsSlidersEnabled`)
|
||||
- **范围显示**:滑块下方显示最小值和最大值
|
||||
- **实际值显示**:右上角显示当前实际电压值
|
||||
|
||||
### 4. 电流调节
|
||||
|
||||
- **滑块范围**:从配置文件读取(默认 10-1000 μA)
|
||||
- **双向绑定**:滑块和数值输入框绑定同一属性 `CurrentValue`,使用 `Mode=TwoWay`
|
||||
- **延迟拖拽**:滑块使用 `IsDeferredDraggingEnabled="True"`,松手时才提交值
|
||||
- **手动提交**:通过 `ApplyCurrentCommand` 提交电流值到硬件
|
||||
- **启用条件**:仅在服务已初始化且变量已连接时可调节(`IsSlidersEnabled`)
|
||||
- **范围显示**:滑块下方显示最小值和最大值
|
||||
- **实际值显示**:右上角显示当前实际电流值
|
||||
|
||||
### 5. 自动初始化
|
||||
|
||||
- ViewModel 提供 `AutoInitializeAsync()` 方法
|
||||
- 由 View 的 `Loaded` 事件触发,仅执行一次
|
||||
- 异步执行 `InitializeAndConnectAsync()`(初始化 + 连接变量)
|
||||
- 如果已初始化且变量已连接则跳过
|
||||
|
||||
## 关联视图:RaySourceConfigView
|
||||
|
||||
`RaySourceConfigView` 是射线源配置和设备状态监控面板,通过 `RaySourceConfigWindow` 包裹为独立窗口。
|
||||
|
||||
### ConfigView 功能
|
||||
|
||||
- **设备信息**:显示射线源类型和连接状态(颜色编码:灰色=未连接,橙色=已初始化,绿色=变量已连接)
|
||||
- **操作按钮**:初始化 / 连接变量 / 断开(三列等宽布局)
|
||||
- **设备状态面板**:暖机/真空/启动/自动定心/灯丝校准/射线开启/连锁/看门狗/TXI/功率模式
|
||||
- **TXI 控制**:TXI ON / TXI OFF 按钮
|
||||
- **功率模式切换**:High Power / Micro Focus 按钮
|
||||
- **功能设置按钮**:暖机设置 / 训机设置 / 灯丝校准 / 自动定心(四列等宽布局,带确认对话框和进度条窗口)
|
||||
- **灯丝寿命进度条**:显示灯丝使用百分比(60 秒定时刷新),颜色随百分比变化(绿/黄/红)
|
||||
|
||||
### ConfigView 依赖注入
|
||||
|
||||
- `IRaySourceService` - 射线源业务服务
|
||||
- `IEventAggregator` - Prism 事件聚合器
|
||||
- `RaySourceConfig` - 射线源配置
|
||||
- `ILoggerService` - 日志服务
|
||||
- `ILocalizationService` - 多语言服务
|
||||
- `IFilamentLifetimeService` - 灯丝寿命管理服务
|
||||
|
||||
## 多语言支持
|
||||
|
||||
### XAML 中使用
|
||||
|
||||
```xml
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
|
||||
|
||||
<TextBlock Text="{loc:Localization RaySource_VoltageLabel}"/>
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_TurnOnButton}"/>
|
||||
```
|
||||
|
||||
### ViewModel 中使用
|
||||
|
||||
```csharp
|
||||
_localizationService.GetString("RaySource_StatusClosed")
|
||||
```
|
||||
|
||||
### 资源键列表
|
||||
|
||||
#### OperateView 资源键
|
||||
|
||||
| 资源键 | 中文 | 英文 |
|
||||
|--------|------|------|
|
||||
| RaySource_VoltageLabel | 电压(kV) | Voltage (kV) |
|
||||
| RaySource_CurrentLabel | 电流(μA) | Current (μA) |
|
||||
| RaySource_ActualValueLabel | 当前值: {0} | Actual: {0} |
|
||||
| RaySource_TurnOnButton | 开启射线源 | Turn On X-Ray |
|
||||
| RaySource_TurnOffButton | 关闭射线源 | Turn Off X-Ray |
|
||||
| RaySource_AdvanceButton | 高级 | Advance |
|
||||
| RaySource_ConfigButton | 配置 | Config |
|
||||
| RaySource_StatusUnavailable | 射线源\n不可用 | X-Ray\nUnavailable |
|
||||
| RaySource_StatusClosed | 射线源\n已关闭 | X-Ray\nClosed |
|
||||
| RaySource_StatusOpened | 射线源\n已开启 | X-Ray\nOpened |
|
||||
|
||||
#### ConfigView 资源键
|
||||
|
||||
| 资源键 | 中文 | 英文 |
|
||||
|--------|------|------|
|
||||
| RaySource_SourceTypeLabel | 射线源类型 | Source Type |
|
||||
| RaySource_InitializeButton | 初始化 | Initialize |
|
||||
| RaySource_ConnectVariablesButton | 连接变量 | Connect Variables |
|
||||
| RaySource_DisconnectButton | 断开 | Disconnect |
|
||||
| RaySource_WarmUpLabel | 暖机 | Warm-up |
|
||||
| RaySource_VacuumLabel | 真空 | Vacuum |
|
||||
| RaySource_StartUpLabel | 启动 | Startup |
|
||||
| RaySource_AutoCenterLabel | 自动定心 | Auto-center |
|
||||
| RaySource_FilamentLabel | 灯丝校准 | Filament |
|
||||
| RaySource_XRayOnLabel | 射线 | X-Ray |
|
||||
| RaySource_InterlockLabel | 连锁 | Interlock |
|
||||
| RaySource_WatchdogLabel | 看门狗 | Watchdog |
|
||||
| RaySource_TxiStatusLabel | TXI | TXI |
|
||||
| RaySource_TxiOnButton | TXI ON | TXI ON |
|
||||
| RaySource_TxiOffButton | TXI OFF | TXI OFF |
|
||||
| RaySource_PowerModeLabel | 功率模式 | Power Mode |
|
||||
| RaySource_HighPowerButton | High Power | High Power |
|
||||
| RaySource_MicroFocusButton | Micro Focus | Micro Focus |
|
||||
| RaySource_WarmUpSettingButton | 暖机设置 | Warm-up |
|
||||
| RaySource_TrainingSettingButton | 训机设置 | Training |
|
||||
| RaySource_FilamentCalibrationButton | 灯丝校准 | Filament Cal. |
|
||||
| RaySource_AutoCenterSettingButton | 自动定心 | Auto-center |
|
||||
| RaySource_FilamentLifetimeLabel | 灯丝寿命 | Filament Life |
|
||||
| RaySource_ConfigWindowTitle | 射线源配置 | X-Ray Source Config |
|
||||
|
||||
> 注意:多语言仅在应用启动时加载,无需运行时热切换。
|
||||
|
||||
## 呼吸闪烁动画
|
||||
|
||||
射线源开启时,状态指示器播放呼吸闪烁动画,增强视觉警示效果。
|
||||
|
||||
### 动画定义
|
||||
|
||||
```xml
|
||||
<Storyboard x:Key="BreathingAnimation" RepeatBehavior="Forever">
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||
From="1.0" To="0.6" Duration="0:0:0.75"
|
||||
AutoReverse="True"/>
|
||||
</Storyboard>
|
||||
```
|
||||
|
||||
### 触发机制
|
||||
|
||||
通过 `DataTrigger` 监听 `RaySourceStatus` 属性:
|
||||
- 当状态变为 `Opened` 时,自动开始动画
|
||||
- 当状态离开 `Opened` 时,自动停止动画
|
||||
|
||||
## 事件订阅
|
||||
|
||||
### OperateViewModel 订阅事件
|
||||
|
||||
1. **RaySourceStatusChangedEvent**:射线源状态变更(三态)
|
||||
- 更新 `RaySourceStatus` 属性和按钮/滑块启用状态
|
||||
- 异常断联时重置变量连接状态
|
||||
|
||||
2. **StatusUpdatedEvent**:系统状态更新
|
||||
- 更新实际电压和电流值(仅在值真正变化时更新,避免无意义赋值)
|
||||
- 同步服务层权威状态
|
||||
|
||||
3. **ErrorOccurredEvent**:错误发生
|
||||
- 显示错误消息对话框
|
||||
|
||||
4. **OperationResultEvent**:操作结果
|
||||
- 操作失败时显示警告消息
|
||||
|
||||
5. **VariablesConnectedEvent**:变量连接状态变更
|
||||
- 更新 `IsVariablesConnected` 属性
|
||||
- 连接成功时主动读取最新设备状态
|
||||
|
||||
### ConfigViewModel 订阅事件
|
||||
|
||||
1. **RaySourceStatusChangedEvent**:刷新初始化状态和命令可执行状态
|
||||
2. **StatusUpdatedEvent**:刷新设备状态面板所有字段
|
||||
3. **VariablesConnectedEvent**:刷新连接状态和命令可执行状态
|
||||
|
||||
## 数据绑定
|
||||
|
||||
### OperateView 状态绑定
|
||||
|
||||
```xml
|
||||
<!-- 状态指示器背景色(径向渐变)-->
|
||||
Background="{Binding RaySourceStatus, Converter={StaticResource StatusToColorConverter}}"
|
||||
|
||||
<!-- 状态指示器边框色 -->
|
||||
BorderBrush="{Binding RaySourceStatus, Converter={StaticResource StatusToBorderColorConverter}}"
|
||||
|
||||
<!-- 状态文本(多语言)-->
|
||||
Text="{Binding StatusText}"
|
||||
```
|
||||
|
||||
### OperateView 命令绑定
|
||||
|
||||
```xml
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_TurnOnButton}" Command="{Binding TurnOnCommand}"/>
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_TurnOffButton}" Command="{Binding TurnOffCommand}"/>
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_AdvanceButton}" Command="{Binding SettingsCommand}"/>
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_ConfigButton}" Command="{Binding ConfigCommand}"/>
|
||||
```
|
||||
|
||||
### OperateView 滑块绑定
|
||||
|
||||
```xml
|
||||
<!-- 电压滑块(延迟拖拽模式)-->
|
||||
<telerik:RadSlider Minimum="{Binding VoltageMin}" Maximum="{Binding VoltageMax}"
|
||||
Value="{Binding VoltageValue, Mode=TwoWay}"
|
||||
IsDeferredDraggingEnabled="True"
|
||||
IsEnabled="{Binding IsSlidersEnabled}"/>
|
||||
|
||||
<!-- 电压数值输入框 -->
|
||||
<telerik:RadNumericUpDown Minimum="{Binding VoltageMin}" Maximum="{Binding VoltageMax}"
|
||||
Value="{Binding VoltageValue, Mode=TwoWay}"
|
||||
IsEnabled="{Binding IsSlidersEnabled}" NumberDecimalDigits="1"/>
|
||||
```
|
||||
|
||||
## 转换器
|
||||
|
||||
### 1. RaySourceStatusToColorConverter
|
||||
将 `RaySourceStatus` 枚举转换为径向渐变背景色:
|
||||
- `Unavailable` → 灰色渐变(#E0E0E0 → #BDBDBD)
|
||||
- `Closed` → 绿色渐变(#8BC34A → #4CAF50)
|
||||
- `Opened` → 红色渐变(#FF8A80 → #F44336)
|
||||
|
||||
### 2. RaySourceStatusToBorderColorConverter
|
||||
将 `RaySourceStatus` 枚举转换为边框色:
|
||||
- `Unavailable` → #9E9E9E
|
||||
- `Closed` → #2E7D32
|
||||
- `Opened` → #C62828
|
||||
|
||||
### 3. FilamentLifetimeColorConverter
|
||||
将灯丝寿命百分比转换为进度条颜色:
|
||||
- < 80% → 绿色 (#4CAF50)
|
||||
- 80%-89% → 黄色 (#FFC107)
|
||||
- ≥ 90% → 红色 (#E53935)
|
||||
|
||||
## 业务规则
|
||||
|
||||
### 安全规则
|
||||
|
||||
1. **未初始化禁止操作**:所有操作前检查 `IsInitialized` 和 `IsVariablesConnected`
|
||||
2. **参数范围校验**:电压和电流值必须在配置的范围内
|
||||
3. **操作失败回滚**:设置失败时恢复滑块到实际值(仅当实际值在有效范围内时)
|
||||
4. **连接丢失处理**:连接丢失时状态设为 `Unavailable`,重置变量连接状态,禁用所有操作
|
||||
5. **设备反馈保护**:`IsUpdatingFromDevice` 标志防止设备反馈更新时误触发写入
|
||||
|
||||
### 状态管理
|
||||
|
||||
- 状态变更通过事件驱动,确保 UI 与硬件状态同步
|
||||
- 使用 `RaisePropertyChanged` 通知 UI 更新
|
||||
- 命令的 `CanExecute` 自动监听状态变化
|
||||
- 三态管理:Unavailable → Closed → Opened
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 在独立窗口中使用
|
||||
|
||||
```csharp
|
||||
var raySourceView = _containerProvider.Resolve<RaySourceOperateView>();
|
||||
var window = new Window
|
||||
{
|
||||
Title = "射线源操作",
|
||||
Content = raySourceView,
|
||||
SizeToContent = SizeToContent.WidthAndHeight,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Owner = Application.Current.MainWindow
|
||||
};
|
||||
window.ShowDialog();
|
||||
```
|
||||
|
||||
### 在主窗口 Region 中加载
|
||||
|
||||
```xml
|
||||
<ContentControl prism:RegionManager.RegionName="RaySourceOperateRegion" />
|
||||
```
|
||||
|
||||
```csharp
|
||||
_regionManager.RequestNavigate("RaySourceOperateRegion", "RaySourceOperateView");
|
||||
```
|
||||
|
||||
## XAML 命名空间引用
|
||||
|
||||
```xml
|
||||
xmlns:local="clr-namespace:XP.Hardware.RaySource.Views"
|
||||
xmlns:converters="clr-namespace:XP.Hardware.RaySource.Converters"
|
||||
xmlns:enums="clr-namespace:XP.Hardware.RaySource.Abstractions.Enums"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:所有事件处理使用 `ThreadOption.UIThread` 确保在 UI 线程执行
|
||||
2. **资源释放**:ViewModel 实现 `IDisposable`,在 Dispose 时取消事件订阅、断开射线源连接、关闭配置窗口
|
||||
3. **同步操作**:硬件操作为同步方法,ViewModel 中直接调用(不阻塞 UI 因为操作通过 IPC 快速返回)
|
||||
4. **错误处理**:所有操作包含 try-catch,确保异常不会导致程序崩溃
|
||||
5. **多语言**:所有界面文字通过 `{loc:Localization}` 或 `ILocalizationService` 获取,无硬编码文字
|
||||
6. **动画性能**:呼吸闪烁动画使用 WPF Storyboard,性能开销极低
|
||||
7. **日志规范**:使用 `_logger.ForModule("RaySource.ViewModel")` 和结构化日志消息
|
||||
8. **自动初始化**:OperateView 加载时自动执行 `AutoInitializeAsync()`,仅执行一次
|
||||
9. **配置窗口**:通过 ConfigCommand 打开,单例模式(已存在时激活而非重复创建)
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 按钮不可用
|
||||
- 检查 `IsInitialized` 和 `IsVariablesConnected` 状态
|
||||
- 检查 `RaySourceStatus` 是否正确
|
||||
- 查看命令的 `CanExecute` 逻辑
|
||||
|
||||
### 滑块不可用
|
||||
- 确认服务已初始化且变量已连接(`IsSlidersEnabled`)
|
||||
|
||||
### 实际值不更新
|
||||
- 确认已订阅 `StatusUpdatedEvent`
|
||||
- 检查 Host 进程是否正常推送状态
|
||||
- 验证 `ThreadOption.UIThread` 设置
|
||||
|
||||
### 设置值不生效
|
||||
- 检查 `ApplyVoltageCommand` / `ApplyCurrentCommand` 是否正确触发
|
||||
- 查看业务规则校验逻辑
|
||||
- 确认 IPC 管道连接正常
|
||||
|
||||
### 多语言文字不显示
|
||||
- 确认 XP.Common 资源文件中已添加对应的资源键
|
||||
- 检查 XAML 中 `xmlns:loc` 命名空间引用是否正确
|
||||
|
||||
### 动画不播放
|
||||
- 确认 `RaySourceStatus` 已变为 `Opened`
|
||||
- 检查 `enums` 命名空间引用是否正确
|
||||
|
||||
### 状态显示为"不可用"
|
||||
- 检查射线源服务连接状态
|
||||
- 确认 Host 进程是否正常运行
|
||||
- 查看日志中是否有连接丢失的警告信息
|
||||
|
||||
---
|
||||
|
||||
**最后更新 | Last Updated**: 2026-03-26
|
||||
@@ -0,0 +1,116 @@
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Implementations;
|
||||
|
||||
namespace XP.Hardware.RaySource.Factories
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源工厂实现类 | X-Ray Source Factory Implementation
|
||||
/// 负责创建不同类型的射线源实例 | Responsible for creating different types of X-ray source instances
|
||||
/// </summary>
|
||||
public class RaySourceFactory : IRaySourceFactory
|
||||
{
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="config">配置对象 | Configuration object</param>
|
||||
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public RaySourceFactory(RaySourceConfig config, IEventAggregator eventAggregator, ILoggerService logger)
|
||||
{
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule("RaySource.Factory");
|
||||
|
||||
_logger.Info("射线源工厂已创建 | X-ray source factory created");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建指定类型的射线源实例 | Create X-ray source instance of specified type
|
||||
/// </summary>
|
||||
/// <param name="sourceType">射线源类型名称 | X-ray source type name</param>
|
||||
/// <returns>射线源实例 | X-ray source instance</returns>
|
||||
public IXRaySource CreateRaySource(string sourceType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceType))
|
||||
{
|
||||
_logger.Error(null, "射线源类型不能为空 | X-ray source type cannot be empty");
|
||||
throw new ArgumentException("射线源类型不能为空", nameof(sourceType));
|
||||
}
|
||||
|
||||
_logger.Info($"创建射线源实例,类型: {sourceType} | Creating X-ray source instance, type: {sourceType}");
|
||||
|
||||
try
|
||||
{
|
||||
var instance = sourceType.ToUpperInvariant() switch
|
||||
{
|
||||
"COMET225" => CreateComet225RaySource(),
|
||||
_ => throw new NotSupportedException($"不支持的射线源类型: {sourceType}")
|
||||
};
|
||||
|
||||
_logger.Info($"射线源实例创建成功: {sourceType} | X-ray source instance created successfully: {sourceType}");
|
||||
return instance;
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
_logger.Error(ex, $"不支持的射线源类型: {sourceType} | Unsupported X-ray source type: {sourceType}");
|
||||
throw;
|
||||
}
|
||||
catch (TypeLoadException ex)
|
||||
{
|
||||
_logger.Error(ex, "跨框架类型加载失败,请检查 BR.AN.PviServices.dll 是否正确部署:{Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
_logger.Error(ex, "跨框架程序集未找到,请检查 XP.Hardware.RaySource.Comet.dll 和 BR.AN.PviServices.dll 是否存在:{Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, $"创建射线源实例失败: {sourceType} | Failed to create X-ray source instance: {sourceType}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支持的射线源类型列表 | Get list of supported X-ray source types
|
||||
/// </summary>
|
||||
/// <returns>类型名称列表 | List of type names</returns>
|
||||
public IEnumerable<string> GetSupportedSourceTypes()
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
"Comet225"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 Comet225 射线源实例
|
||||
/// 通过 CometHostManager 管理 Host 进程生命周期,CometIpcClient 负责 IPC 通信
|
||||
/// </summary>
|
||||
private IXRaySource CreateComet225RaySource()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hostManager = new CometHostManager(_logger, _config);
|
||||
var ipcClient = new CometIpcClient(_config, _eventAggregator, _logger);
|
||||
return new Comet225RaySource(hostManager, ipcClient, _logger);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "创建 Comet225 射线源实例失败:{Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
|
||||
namespace XP.Hardware.RaySource.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Comet 225kV 射线源适配器
|
||||
/// 通过 CometIpcClient 和 CometHostManager 实现进程外隔离的 PVI 通信
|
||||
/// 所有操作委托给 CometIpcClient,Host 进程生命周期由 CometHostManager 管理
|
||||
/// </summary>
|
||||
public class Comet225RaySource : XRaySourceBase
|
||||
{
|
||||
#region 依赖注入字段
|
||||
|
||||
private readonly CometHostManager _hostManager;
|
||||
private readonly CometIpcClient _ipcClient;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,注入依赖
|
||||
/// </summary>
|
||||
/// <param name="hostManager">Host 进程生命周期管理器</param>
|
||||
/// <param name="ipcClient">IPC 客户端,负责与 Host 进程通信</param>
|
||||
/// <param name="loggerService">日志服务</param>
|
||||
public Comet225RaySource(
|
||||
CometHostManager hostManager,
|
||||
CometIpcClient ipcClient,
|
||||
ILoggerService loggerService)
|
||||
{
|
||||
_hostManager = hostManager ?? throw new ArgumentNullException(nameof(hostManager));
|
||||
_ipcClient = ipcClient ?? throw new ArgumentNullException(nameof(ipcClient));
|
||||
_logger = loggerService?.ForModule<Comet225RaySource>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
public override string SourceName => "Comet 225kV";
|
||||
|
||||
#endregion
|
||||
|
||||
#region IXRaySource 方法实现 - 委托给 CometIpcClient
|
||||
|
||||
/// <summary>
|
||||
/// 初始化射线源
|
||||
/// 先确保 Host 进程运行,再通过 IPC 客户端初始化 PVI 连接
|
||||
/// </summary>
|
||||
public override XRayResult Initialize()
|
||||
{
|
||||
_logger.Info("初始化 Comet225 射线源(IPC 模式)");
|
||||
try
|
||||
{
|
||||
// 先确保 Host 进程运行
|
||||
_hostManager.EnsureRunning();
|
||||
_logger.Info("Host 进程已就绪,开始 IPC 初始化");
|
||||
|
||||
// 再通过 IPC 客户端初始化
|
||||
var result = _ipcClient.Initialize();
|
||||
if (result.Success)
|
||||
{
|
||||
_isInitialized = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "初始化 Comet225 射线源异常:{Message}", ex.Message);
|
||||
return XRayResult.Error($"初始化异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建和连接 PVI 变量
|
||||
/// </summary>
|
||||
public override XRayResult ConnectVariables()
|
||||
{
|
||||
var result = _ipcClient.ConnectVariables();
|
||||
if (result.Success)
|
||||
{
|
||||
_isConnected = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线源高压
|
||||
/// </summary>
|
||||
public override XRayResult TurnOn()
|
||||
{
|
||||
return _ipcClient.TurnOn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线源高压
|
||||
/// </summary>
|
||||
public override XRayResult TurnOff()
|
||||
{
|
||||
return _ipcClient.TurnOff();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电压值
|
||||
/// </summary>
|
||||
public override XRayResult SetVoltage(float voltage)
|
||||
{
|
||||
return _ipcClient.SetVoltage(voltage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电流值
|
||||
/// </summary>
|
||||
public override XRayResult SetCurrent(float current)
|
||||
{
|
||||
return _ipcClient.SetCurrent(current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置焦点 - Comet 225kV 不支持
|
||||
/// </summary>
|
||||
public override XRayResult SetFocus(float focus)
|
||||
{
|
||||
return _ipcClient.SetFocus(focus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前电压反馈值
|
||||
/// </summary>
|
||||
public override XRayResult ReadVoltage()
|
||||
{
|
||||
return _ipcClient.ReadVoltage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前电流反馈值
|
||||
/// </summary>
|
||||
public override XRayResult ReadCurrent()
|
||||
{
|
||||
return _ipcClient.ReadCurrent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统状态
|
||||
/// </summary>
|
||||
public override XRayResult ReadSystemStatus()
|
||||
{
|
||||
return _ipcClient.ReadSystemStatus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查错误状态
|
||||
/// </summary>
|
||||
public override XRayResult CheckErrors()
|
||||
{
|
||||
return _ipcClient.CheckErrors();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 开启
|
||||
/// </summary>
|
||||
public override XRayResult TxiOn()
|
||||
{
|
||||
return _ipcClient.TxiOn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 关闭
|
||||
/// </summary>
|
||||
public override XRayResult TxiOff()
|
||||
{
|
||||
return _ipcClient.TxiOff();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暖机设置
|
||||
/// </summary>
|
||||
public override XRayResult WarmUp()
|
||||
{
|
||||
return _ipcClient.WarmUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 训机设置
|
||||
/// </summary>
|
||||
public override XRayResult Training()
|
||||
{
|
||||
return _ipcClient.Training();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准
|
||||
/// </summary>
|
||||
public override XRayResult FilamentCalibration()
|
||||
{
|
||||
return _ipcClient.FilamentCalibration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全部电压自动定心
|
||||
/// </summary>
|
||||
public override XRayResult AutoCenter()
|
||||
{
|
||||
return _ipcClient.AutoCenter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置功率模式
|
||||
/// </summary>
|
||||
public override XRayResult SetPowerMode(int mode)
|
||||
{
|
||||
return _ipcClient.SetPowerMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线源,释放 IPC 连接并关闭 Host 进程
|
||||
/// </summary>
|
||||
public override XRayResult CloseOff()
|
||||
{
|
||||
_logger.Info("执行 CloseOff 操作(IPC 模式)");
|
||||
try
|
||||
{
|
||||
// 先通过 IPC 客户端发送断开命令
|
||||
var result = _ipcClient.CloseOff();
|
||||
|
||||
// 再关闭 Host 进程
|
||||
_hostManager.Shutdown();
|
||||
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
_logger.Info("CloseOff 操作完成");
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("CloseOff 过程中发生异常:{Message}", ex.Message);
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
return XRayResult.Error($"CloseOff 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 资源释放
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源,清理 IPC 客户端和 Host 进程管理器
|
||||
/// </summary>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed && disposing)
|
||||
{
|
||||
_ipcClient?.Dispose();
|
||||
_hostManager?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
|
||||
namespace XP.Hardware.RaySource.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Host 进程生命周期管理器
|
||||
/// 负责启动、监控和关闭 .NET Framework 4.8 Host 子进程
|
||||
/// </summary>
|
||||
public class CometHostManager : IDisposable
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly RaySourceConfig _config;
|
||||
private Process _hostProcess;
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Host 可执行文件名
|
||||
/// </summary>
|
||||
private const string HostExeName = "XP.Hardware.RaySource.Comet.Host.exe";
|
||||
|
||||
/// <summary>
|
||||
/// 命令管道名称(客户端写 → Host 读)
|
||||
/// </summary>
|
||||
private const string CmdPipeName = "XP.Hardware.RaySource.Comet.Cmd";
|
||||
|
||||
/// <summary>
|
||||
/// 响应管道名称(Host 写 → 客户端读)
|
||||
/// </summary>
|
||||
private const string RspPipeName = "XP.Hardware.RaySource.Comet.Rsp";
|
||||
|
||||
/// <summary>
|
||||
/// 等待管道就绪的超时时间(毫秒)
|
||||
/// </summary>
|
||||
private const int PipeReadyTimeoutMs = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// 等待 Host 进程退出的超时时间(毫秒)
|
||||
/// </summary>
|
||||
private const int ShutdownTimeoutMs = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="loggerService">日志服务</param>
|
||||
/// <param name="config">射线源配置</param>
|
||||
public CometHostManager(ILoggerService loggerService, RaySourceConfig config)
|
||||
{
|
||||
_logger = loggerService?.ForModule<CometHostManager>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程是否正在运行
|
||||
/// </summary>
|
||||
public bool IsRunning => _hostProcess != null && !_hostProcess.HasExited;
|
||||
|
||||
/// <summary>
|
||||
/// 确保 Host 进程正在运行
|
||||
/// 优先检查是否已有同路径的 Host 进程在运行,有则直接关联;否则启动新进程
|
||||
/// </summary>
|
||||
public void EnsureRunning()
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
_logger.Debug("Host 进程已在运行,PID={Pid}", _hostProcess.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var hostExePath = GetHostExePath();
|
||||
|
||||
// 检查是否已有 Host 进程在运行(例如上次异常退出后残留的进程),杀掉后重新启动
|
||||
var hostProcessName = Path.GetFileNameWithoutExtension(HostExeName);
|
||||
var existingProcesses = Process.GetProcessesByName(hostProcessName);
|
||||
foreach (var proc in existingProcesses)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("发现残留的 Host 进程,正在终止,PID={Pid}", proc.Id);
|
||||
proc.Kill();
|
||||
proc.WaitForExit(1500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("终止残留 Host 进程失败,PID={Pid},{Message}", proc.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info("启动 Host 进程:{Path}", hostExePath);
|
||||
|
||||
if (!File.Exists(hostExePath))
|
||||
{
|
||||
throw new FileNotFoundException($"Host 可执行文件未找到:{hostExePath}", hostExePath);
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = hostExePath,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
_hostProcess = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
|
||||
_hostProcess.Exited += OnHostProcessExited;
|
||||
_hostProcess.ErrorDataReceived += OnHostErrorDataReceived;
|
||||
_hostProcess.Start();
|
||||
_hostProcess.BeginErrorReadLine();
|
||||
|
||||
_logger.Info("Host 进程已启动,PID={Pid}", _hostProcess.Id);
|
||||
|
||||
// 等待 NamedPipe 就绪
|
||||
WaitForPipeReady();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全关闭 Host 进程
|
||||
/// 等待进程退出,超时则强制终止(DisconnectCommand 已由 CometIpcClient 发送)
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
if (!IsRunning)
|
||||
{
|
||||
_logger.Debug("Host 进程未运行,无需关闭");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info("正在关闭 Host 进程,PID={Pid}", _hostProcess.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// 等待进程退出(DisconnectCommand 已由 CometIpcClient 发送)
|
||||
if (!_hostProcess.WaitForExit(ShutdownTimeoutMs))
|
||||
{
|
||||
_logger.Warn("Host 进程未在超时时间内退出,强制终止");
|
||||
_hostProcess.Kill();
|
||||
_hostProcess.WaitForExit(1000);
|
||||
}
|
||||
|
||||
_logger.Info("Host 进程已关闭");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("关闭 Host 进程时发生异常:{Message}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CleanupProcess();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Host 可执行文件路径
|
||||
/// 优先使用配置的路径,为空时默认在主程序输出目录下查找
|
||||
/// </summary>
|
||||
private string GetHostExePath()
|
||||
{
|
||||
// 优先使用配置的路径
|
||||
if (!string.IsNullOrWhiteSpace(_config.HostExePath))
|
||||
{
|
||||
return _config.HostExePath;
|
||||
}
|
||||
|
||||
// 默认在主程序输出目录的 Host 子目录下查找
|
||||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
return Path.Combine(baseDir, "Host", HostExeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待 NamedPipe 就绪
|
||||
/// 通过短暂延迟和进程存活检查确认 Host 进程已启动并创建了管道
|
||||
/// 使用临时 NamedPipeClientStream 尝试连接来检测管道是否就绪
|
||||
/// </summary>
|
||||
private void WaitForPipeReady()
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
while ((DateTime.UtcNow - startTime).TotalMilliseconds < PipeReadyTimeoutMs)
|
||||
{
|
||||
if (_hostProcess.HasExited)
|
||||
{
|
||||
throw new InvalidOperationException($"Host 进程启动后立即退出,退出码={_hostProcess.ExitCode}");
|
||||
}
|
||||
|
||||
// 短暂等待,让 Host 进程有时间创建管道
|
||||
Thread.Sleep(200);
|
||||
|
||||
// 进程仍在运行,认为管道即将就绪或已就绪
|
||||
if (!_hostProcess.HasExited)
|
||||
{
|
||||
_logger.Info("Host 进程正在运行,管道应已就绪");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 超时但进程仍在运行,假设管道已就绪
|
||||
if (!_hostProcess.HasExited)
|
||||
{
|
||||
_logger.Warn("等待管道就绪超时,但 Host 进程仍在运行,继续");
|
||||
return;
|
||||
}
|
||||
|
||||
throw new TimeoutException($"等待 Host 进程 NamedPipe 就绪超时({PipeReadyTimeoutMs}ms)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程退出事件处理
|
||||
/// </summary>
|
||||
private void OnHostProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
if (_hostProcess != null)
|
||||
{
|
||||
_logger.Warn("Host 进程已退出,退出码={ExitCode}", _hostProcess.ExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Host 进程 stderr 输出事件处理,转发到主进程日志
|
||||
/// </summary>
|
||||
private void OnHostErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
_logger.Debug("[Host stderr] {Message}", e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理进程资源
|
||||
/// </summary>
|
||||
private void CleanupProcess()
|
||||
{
|
||||
if (_hostProcess != null)
|
||||
{
|
||||
_hostProcess.Exited -= OnHostProcessExited;
|
||||
_hostProcess.ErrorDataReceived -= OnHostErrorDataReceived;
|
||||
_hostProcess.Dispose();
|
||||
_hostProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
Shutdown();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Telerik.Windows.Documents.Selection;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Comet.Messages;
|
||||
using XP.Hardware.RaySource.Comet.Messages.Commands;
|
||||
using XP.Hardware.RaySource.Comet.Messages.Responses;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
|
||||
namespace XP.Hardware.RaySource.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// IPC 客户端
|
||||
/// 通过 NamedPipe 与 Host 进程通信,实现 IXRaySource 接口
|
||||
/// 上层代码无需感知进程隔离的存在
|
||||
/// </summary>
|
||||
public class CometIpcClient : XRaySourceBase
|
||||
{
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
private NamedPipeClientStream _cmdPipe;
|
||||
private NamedPipeClientStream _rspPipe;
|
||||
private StreamReader _reader;
|
||||
private Thread _receiveThread;
|
||||
private volatile bool _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 管道连接状态标志位
|
||||
/// 用于在 Host 进程崩溃或管道断开后,阻止后续 SendCommand 尝试写入已断开的管道
|
||||
/// </summary>
|
||||
private volatile bool _isPipeConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 用于等待命令响应的同步机制
|
||||
/// </summary>
|
||||
private TaskCompletionSource<RaySourceResponse> _pendingResponse;
|
||||
private readonly object _sendLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 命令管道名称(客户端写 → Host 读)
|
||||
/// </summary>
|
||||
private const string CmdPipeName = "XP.Hardware.RaySource.Comet.Cmd";
|
||||
|
||||
/// <summary>
|
||||
/// 响应管道名称(Host 写 → 客户端读)
|
||||
/// </summary>
|
||||
private const string RspPipeName = "XP.Hardware.RaySource.Comet.Rsp";
|
||||
|
||||
/// <summary>
|
||||
/// 命令超时时间(毫秒)
|
||||
/// 需要大于 Host 端 PVI 连接超时时间(30s),因为 Initialize 命令会在 Host 端阻塞等待 PVI 回调链完成
|
||||
/// </summary>
|
||||
private const int CommandTimeoutMs = 35000;
|
||||
|
||||
public override string SourceName => "Comet 225kV (IPC)";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public CometIpcClient(
|
||||
RaySourceConfig config,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService loggerService)
|
||||
{
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = loggerService?.ForModule<CometIpcClient>() ?? throw new ArgumentNullException(nameof(loggerService));
|
||||
}
|
||||
|
||||
#region 管道连接管理
|
||||
|
||||
/// <summary>
|
||||
/// 连接到 Host 进程的 NamedPipe
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 命令管道:客户端写 → Host 读
|
||||
_cmdPipe = new NamedPipeClientStream(".", CmdPipeName, PipeDirection.Out);
|
||||
_cmdPipe.Connect(_config.ConnectionTimeout);
|
||||
|
||||
// 响应管道:Host 写 → 客户端读
|
||||
_rspPipe = new NamedPipeClientStream(".", RspPipeName, PipeDirection.In);
|
||||
_rspPipe.Connect(_config.ConnectionTimeout);
|
||||
|
||||
_reader = new StreamReader(_rspPipe);
|
||||
|
||||
// 启动后台消息接收线程
|
||||
_isRunning = true;
|
||||
_receiveThread = new Thread(ReceiveLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "CometIpcClient-Receiver"
|
||||
};
|
||||
_receiveThread.Start();
|
||||
|
||||
_isPipeConnected = true;
|
||||
_logger.Info("已连接到 Host 进程 NamedPipe(双管道模式)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "连接 Host 进程 NamedPipe 失败:{Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接写入一行文本到命令管道
|
||||
/// </summary>
|
||||
private void WriteLineToHost(string text)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(text + "\n");
|
||||
_cmdPipe.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 核心通信方法
|
||||
|
||||
/// <summary>
|
||||
/// 发送命令并等待响应
|
||||
/// </summary>
|
||||
private RaySourceResponse SendCommand(RaySourceCommand command)
|
||||
{
|
||||
// 检查管道连接状态,如果已断开则直接返回 null
|
||||
if (!_isPipeConnected)
|
||||
{
|
||||
_logger.Error(null, "管道未连接,无法发送命令:{CommandType}", command.CommandType);
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (_sendLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pendingResponse = new TaskCompletionSource<RaySourceResponse>();
|
||||
|
||||
var json = MessageSerializer.Serialize(command);
|
||||
_logger.Debug("发送命令:{CommandType},JSON 长度={Length}", command.CommandType, json.Length);
|
||||
WriteLineToHost(json);
|
||||
_logger.Debug("命令已写入管道,开始等待响应:{CommandType}", command.CommandType);
|
||||
|
||||
// 等待响应,带超时
|
||||
using (var cts = new CancellationTokenSource(CommandTimeoutMs))
|
||||
{
|
||||
cts.Token.Register(() => _pendingResponse?.TrySetCanceled());
|
||||
|
||||
try
|
||||
{
|
||||
_pendingResponse.Task.Wait();
|
||||
var result = _pendingResponse.Task.Result;
|
||||
_logger.Debug("收到命令响应:{CommandType},Success={Success}", command.CommandType, result?.Success);
|
||||
return result;
|
||||
}
|
||||
catch (AggregateException ae) when (ae.InnerException is TaskCanceledException)
|
||||
{
|
||||
_logger.Error(null, "命令超时({TimeoutMs}ms):{CommandType}", CommandTimeoutMs, command.CommandType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.Error(ex, "管道通信异常(Host 进程可能已崩溃):{Message}", ex.Message);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "发送命令异常:{Message}", ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 后台消息接收线程
|
||||
|
||||
/// <summary>
|
||||
/// 后台消息接收循环
|
||||
/// 持续读取管道消息,区分命令响应和主动推送
|
||||
/// </summary>
|
||||
private void ReceiveLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Debug("接收线程已启动");
|
||||
while (_isRunning)
|
||||
{
|
||||
var line = _reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
// 管道断开,设置标志位阻止后续 SendCommand 写入
|
||||
_isPipeConnected = false;
|
||||
_logger.Warn("Host 进程管道已断开");
|
||||
_pendingResponse?.TrySetResult(null);
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.Debug("收到管道消息,长度={Length}", line.Length);
|
||||
|
||||
var response = MessageSerializer.DeserializeResponse(line);
|
||||
if (response == null)
|
||||
{
|
||||
_logger.Warn("收到无法反序列化的消息,原始内容:{Raw}", line.Length > 200 ? line.Substring(0, 200) + "..." : line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (response.IsPush)
|
||||
{
|
||||
_logger.Debug("收到推送消息:{PushType}", response.PushType);
|
||||
// 主动推送消息,路由到对应的 Prism 事件
|
||||
HandlePushMessage(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("收到命令响应,Success={Success}", response.Success);
|
||||
// 命令响应,通知等待的 SendCommand 调用
|
||||
_pendingResponse?.TrySetResult(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_isPipeConnected = false;
|
||||
_logger.Warn("接收线程 IO 异常(管道可能已断开):{Message}", ex.Message);
|
||||
_pendingResponse?.TrySetResult(null);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 管道已释放,正常退出
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "接收线程异常:{Message}", ex.Message);
|
||||
_pendingResponse?.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 推送消息路由
|
||||
|
||||
/// <summary>
|
||||
/// 处理推送消息,根据 PushType 路由到对应的 Prism 事件
|
||||
/// </summary>
|
||||
private void HandlePushMessage(RaySourceResponse response)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (response.PushType)
|
||||
{
|
||||
case "StatusChanged":
|
||||
HandleStatusPush(response as StatusResponse);
|
||||
break;
|
||||
case "XRayStateChanged":
|
||||
HandleXRayStatePush(response as OperationResponse);
|
||||
break;
|
||||
case "ErrorOccurred":
|
||||
HandleErrorPush(response);
|
||||
break;
|
||||
case "ConnectionStateChanged":
|
||||
HandleConnectionStatePush(response as OperationResponse);
|
||||
break;
|
||||
case "Log":
|
||||
HandleLogPush(response as LogResponse);
|
||||
break;
|
||||
default:
|
||||
_logger.Warn("收到未知推送类型:{PushType}", response.PushType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "处理推送消息异常:{Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStatusPush(StatusResponse status)
|
||||
{
|
||||
if (status == null) return;
|
||||
|
||||
var statusData = new SystemStatusData
|
||||
{
|
||||
SetVoltage = status.SetVoltage,
|
||||
ActualVoltage = status.ActualVoltage,
|
||||
SetCurrent = status.SetCurrent,
|
||||
ActualCurrent = status.ActualCurrent,
|
||||
IsXRayOn = status.IsXRayOn,
|
||||
WarmUpStatus = status.WarmUpStatus,
|
||||
VacuumStatus = status.VacuumStatus,
|
||||
StartUpStatus = status.StartUpStatus,
|
||||
AutoCenterStatus = status.AutoCenterStatus,
|
||||
FilamentAdjustStatus = status.FilamentAdjustStatus,
|
||||
IsInterlockActive = status.IsInterlockActive,
|
||||
WatchdogStatus = status.WatchdogStatus,
|
||||
PowerMode = status.PowerMode,
|
||||
TxiStatus = status.TxiStatus
|
||||
};
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Publish(statusData);
|
||||
}
|
||||
|
||||
private void HandleXRayStatePush(OperationResponse response)
|
||||
{
|
||||
if (response?.Data == null) return;
|
||||
|
||||
var isXRayOn = Convert.ToBoolean(response.Data);
|
||||
var rayStatus = isXRayOn ? RaySourceStatus.Opened : RaySourceStatus.Closed;
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(rayStatus);
|
||||
}
|
||||
|
||||
private void HandleErrorPush(RaySourceResponse response)
|
||||
{
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(response.ErrorMessage);
|
||||
}
|
||||
|
||||
private void HandleConnectionStatePush(OperationResponse response)
|
||||
{
|
||||
if (response?.Data == null) return;
|
||||
|
||||
var stateStr = response.Data.ToString();
|
||||
if (stateStr == "RaySourceConnected")
|
||||
{
|
||||
_isConnected = true;
|
||||
_logger.Info("收到 RaySourceConnected 推送,射线源已完成全部连接流程,准备就绪");
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Publish(true);
|
||||
_logger.Info("射线源连接成功: {SourceName},状态更新为 Closed | X-ray source connectzed successfully: {SourceName}, status updated to Closed", SourceName);
|
||||
// 通知状态变更 | Notify status changed
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(RaySourceStatus.Closed);
|
||||
}
|
||||
else if (stateStr == "VariablesConnected")
|
||||
{
|
||||
_isInitialized = true;
|
||||
_logger.Info("收到 VariablesConnected 推送,PVI 变量已创建、激活并绑定");
|
||||
}
|
||||
else if (stateStr == "ServiceConnected")
|
||||
{
|
||||
_serviceConnectedEvent.Set();
|
||||
_logger.Info("收到 ServiceConnected 推送,PVI Service 和 CPU 已连接");
|
||||
}
|
||||
else if (stateStr == "Disconnected")
|
||||
{
|
||||
_isConnected = false;
|
||||
_isInitialized = false;
|
||||
_logger.Info("收到 Disconnected 推送,PVI 未连接或已断开");
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Publish(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 日志传递
|
||||
|
||||
/// <summary>
|
||||
/// 处理日志推送消息
|
||||
/// 根据 Level 字段映射到 ILoggerService 对应方法
|
||||
/// </summary>
|
||||
private void HandleLogPush(LogResponse logResponse)
|
||||
{
|
||||
if (logResponse == null) return;
|
||||
|
||||
var message = logResponse.Message ?? "";
|
||||
// 将 string[] 转换为 object[] 以匹配 ILoggerService 的 params object[] 签名
|
||||
var args = logResponse.Args != null ? (object[])logResponse.Args : Array.Empty<object>();
|
||||
|
||||
switch (logResponse.Level)
|
||||
{
|
||||
case "Debug":
|
||||
_logger.Debug(message, args);
|
||||
break;
|
||||
case "Info":
|
||||
_logger.Info(message, args);
|
||||
break;
|
||||
case "Warn":
|
||||
_logger.Warn(message, args);
|
||||
break;
|
||||
case "Error":
|
||||
_logger.Error(null, message, args);
|
||||
break;
|
||||
case "Fatal":
|
||||
_logger.Fatal(null, message, args);
|
||||
break;
|
||||
default:
|
||||
_logger.Debug(message, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IXRaySource 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 等待 ServiceConnected 推送的信号量
|
||||
/// Host 端 HandleInitialize 会等待 PVI 回调链完成后才返回响应
|
||||
/// 但作为双重保险,客户端也等待 ServiceConnected 推送到达
|
||||
/// </summary>
|
||||
private readonly ManualResetEventSlim _serviceConnectedEvent = new ManualResetEventSlim(false);
|
||||
|
||||
/// <summary>
|
||||
/// 等待 ServiceConnected 推送的超时时间(毫秒)
|
||||
/// 应略大于 Host 端的 PVI 连接超时时间,因为还有管道通信延迟
|
||||
/// </summary>
|
||||
private const int ServiceConnectedTimeoutMs = 35000;
|
||||
|
||||
public override XRayResult Initialize()
|
||||
{
|
||||
_logger.Info("初始化 Comet225 射线源(IPC 模式)");
|
||||
try
|
||||
{
|
||||
// 重置等待信号
|
||||
_serviceConnectedEvent.Reset();
|
||||
|
||||
// 连接管道
|
||||
Connect();
|
||||
|
||||
var command = new InitializeCommand
|
||||
{
|
||||
IpAddress = _config.PlcIpAddress,
|
||||
Port = _config.PlcPort,
|
||||
CpuName = _config.CpuName,
|
||||
SourcePort = _config.PortNumber,
|
||||
StationNumber = _config.StationNumber
|
||||
};
|
||||
|
||||
// Host 端 HandleInitialize 会等待 PVI 回调链完成后才返回响应
|
||||
var response = SendCommand(command);
|
||||
var result = ProcessResponse(response, "Initialize");
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 双重保险:等待 ServiceConnected 推送到达
|
||||
// Host 返回成功意味着 PVI 回调链已完成,推送应该很快到达
|
||||
if (!_serviceConnectedEvent.Wait(ServiceConnectedTimeoutMs))
|
||||
{
|
||||
_logger.Warn("Initialize 命令成功但未收到 ServiceConnected 推送,可能推送丢失");
|
||||
// Host 已确认成功,即使推送未到达也设置初始化标志
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Initialize 异常:{Message}", ex.Message);
|
||||
return XRayResult.Error($"Initialize 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override XRayResult ConnectVariables()
|
||||
{
|
||||
var response = SendCommand(new ConnectVariablesCommand());
|
||||
return ProcessResponse(response, "ConnectVariables");
|
||||
}
|
||||
|
||||
public override XRayResult TurnOn()
|
||||
{
|
||||
var response = SendCommand(new TurnOnCommand());
|
||||
return ProcessResponse(response, "TurnOn");
|
||||
}
|
||||
|
||||
public override XRayResult TurnOff()
|
||||
{
|
||||
var response = SendCommand(new TurnOffCommand());
|
||||
return ProcessResponse(response, "TurnOff");
|
||||
}
|
||||
|
||||
public override XRayResult SetVoltage(float voltage)
|
||||
{
|
||||
var response = SendCommand(new SetVoltageCommand { Voltage = voltage });
|
||||
return ProcessResponse(response, "SetVoltage");
|
||||
}
|
||||
|
||||
public override XRayResult SetCurrent(float current)
|
||||
{
|
||||
var response = SendCommand(new SetCurrentCommand { Current = current });
|
||||
return ProcessResponse(response, "SetCurrent");
|
||||
}
|
||||
|
||||
public override XRayResult SetFocus(float focus)
|
||||
{
|
||||
_logger.Info("SetFocus 被调用,Comet 225kV 射线源不支持焦点设置");
|
||||
return XRayResult.Ok("Comet 225kV 射线源不支持焦点设置");
|
||||
}
|
||||
|
||||
public override XRayResult ReadVoltage()
|
||||
{
|
||||
var response = SendCommand(new ReadVoltageCommand());
|
||||
if (response is OperationResponse opResp && opResp.Success)
|
||||
{
|
||||
return XRayResult.Ok(opResp.Data);
|
||||
}
|
||||
return ProcessResponse(response, "ReadVoltage");
|
||||
}
|
||||
|
||||
public override XRayResult ReadCurrent()
|
||||
{
|
||||
var response = SendCommand(new ReadCurrentCommand());
|
||||
if (response is OperationResponse opResp && opResp.Success)
|
||||
{
|
||||
return XRayResult.Ok(opResp.Data);
|
||||
}
|
||||
return ProcessResponse(response, "ReadCurrent");
|
||||
}
|
||||
|
||||
public override XRayResult ReadSystemStatus()
|
||||
{
|
||||
var response = SendCommand(new ReadSystemStatusCommand());
|
||||
if (response is StatusResponse statusResp && statusResp.Success)
|
||||
{
|
||||
var statusData = new SystemStatusData
|
||||
{
|
||||
SetVoltage = statusResp.SetVoltage,
|
||||
ActualVoltage = statusResp.ActualVoltage,
|
||||
SetCurrent = statusResp.SetCurrent,
|
||||
ActualCurrent = statusResp.ActualCurrent,
|
||||
IsXRayOn = statusResp.IsXRayOn,
|
||||
WarmUpStatus = statusResp.WarmUpStatus,
|
||||
VacuumStatus = statusResp.VacuumStatus,
|
||||
StartUpStatus = statusResp.StartUpStatus,
|
||||
AutoCenterStatus = statusResp.AutoCenterStatus,
|
||||
FilamentAdjustStatus = statusResp.FilamentAdjustStatus,
|
||||
IsInterlockActive = statusResp.IsInterlockActive,
|
||||
WatchdogStatus = statusResp.WatchdogStatus,
|
||||
PowerMode = statusResp.PowerMode,
|
||||
TxiStatus = statusResp.TxiStatus
|
||||
};
|
||||
return XRayResult.Ok(statusData);
|
||||
}
|
||||
return ProcessResponse(response, "ReadSystemStatus");
|
||||
}
|
||||
|
||||
public override XRayResult CheckErrors()
|
||||
{
|
||||
var response = SendCommand(new ReadErrorsCommand());
|
||||
if (response is ErrorDataResponse errorResp && errorResp.Success)
|
||||
{
|
||||
return XRayResult.Ok(errorResp);
|
||||
}
|
||||
return ProcessResponse(response, "CheckErrors");
|
||||
}
|
||||
|
||||
public override XRayResult TxiOn()
|
||||
{
|
||||
var response = SendCommand(new TxiOnCommand());
|
||||
return ProcessResponse(response, "TxiOn");
|
||||
}
|
||||
|
||||
public override XRayResult TxiOff()
|
||||
{
|
||||
var response = SendCommand(new TxiOffCommand());
|
||||
return ProcessResponse(response, "TxiOff");
|
||||
}
|
||||
|
||||
public override XRayResult WarmUp()
|
||||
{
|
||||
var response = SendCommand(new WarmUpCommand());
|
||||
return ProcessResponse(response, "WarmUp");
|
||||
}
|
||||
|
||||
public override XRayResult Training()
|
||||
{
|
||||
var response = SendCommand(new TrainingCommand());
|
||||
return ProcessResponse(response, "Training");
|
||||
}
|
||||
|
||||
public override XRayResult FilamentCalibration()
|
||||
{
|
||||
var response = SendCommand(new FilamentCalibrationCommand());
|
||||
return ProcessResponse(response, "FilamentCalibration");
|
||||
}
|
||||
|
||||
public override XRayResult AutoCenter()
|
||||
{
|
||||
var response = SendCommand(new AutoCenterCommand());
|
||||
return ProcessResponse(response, "AutoCenter");
|
||||
}
|
||||
|
||||
public override XRayResult SetPowerMode(int mode)
|
||||
{
|
||||
var response = SendCommand(new SetPowerModeCommand { Mode = mode });
|
||||
return ProcessResponse(response, "SetPowerMode");
|
||||
}
|
||||
|
||||
public override XRayResult CloseOff()
|
||||
{
|
||||
_logger.Info("执行 CloseOff 操作(IPC 模式)");
|
||||
try
|
||||
{
|
||||
var response = SendCommand(new DisconnectCommand());
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
return ProcessResponse(response, "CloseOff");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("CloseOff 过程中发生异常:{Message}", ex.Message);
|
||||
_isInitialized = false;
|
||||
_isConnected = false;
|
||||
return XRayResult.Error($"CloseOff 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 处理响应,将 Host 返回的响应转换为 XRayResult
|
||||
/// </summary>
|
||||
private XRayResult ProcessResponse(RaySourceResponse response, string operationName)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
var errorMsg = $"{operationName} 操作失败:未收到 Host 响应(管道可能已断开或超时)";
|
||||
_logger.Error(null, errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
_logger.Error(null, "{Operation} 操作失败:{ErrorMessage}", operationName, response.ErrorMessage);
|
||||
return XRayResult.Error(response.ErrorMessage);
|
||||
}
|
||||
|
||||
return XRayResult.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 资源释放
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed && disposing)
|
||||
{
|
||||
_isRunning = false;
|
||||
_isPipeConnected = false;
|
||||
|
||||
_serviceConnectedEvent?.Dispose();
|
||||
_reader?.Dispose();
|
||||
_cmdPipe?.Dispose();
|
||||
_rspPipe?.Dispose();
|
||||
|
||||
_reader = null;
|
||||
_cmdPipe = null;
|
||||
_rspPipe = null;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Modularity;
|
||||
using System.Resources;
|
||||
using XP.Common.Localization;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.Common.Logging.Implementations;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Factories;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
|
||||
namespace XP.Hardware.RaySource.Module
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源模块 | X-Ray Source Module
|
||||
/// Prism模块入口,注册服务/工厂到Unity容器 | Prism module entry, register services/factories to Unity container
|
||||
/// </summary>
|
||||
[Module(ModuleName = "RaySourceModule")]
|
||||
public class RaySourceModule : IModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块初始化 | Module initialization
|
||||
/// </summary>
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
// 注册模块级多语言资源到 Fallback Chain | Register module-level localization resources to Fallback Chain
|
||||
var localizationService = containerProvider.Resolve<ILocalizationService>();
|
||||
var resourceManager = new ResourceManager(
|
||||
"XP.Hardware.RaySource.Resources.Resources",
|
||||
typeof(RaySourceModule).Assembly);
|
||||
localizationService.RegisterResourceSource("XP.Hardware.RaySource", resourceManager);
|
||||
|
||||
// 初始化 LocalizationHelper,使其通过 ILocalizationService 获取字符串(支持 Fallback Chain)
|
||||
// Initialize LocalizationHelper to use ILocalizationService for string lookup (supports Fallback Chain)
|
||||
LocalizationHelper.Initialize(localizationService);
|
||||
|
||||
// 初始化灯丝寿命服务(建表、异常恢复)| Initialize filament lifetime service (create tables, recover anomalies)
|
||||
var filamentService = containerProvider.Resolve<IFilamentLifetimeService>();
|
||||
var initialized = filamentService.Initialize();
|
||||
|
||||
if (initialized && filamentService.ShouldShowLifetimeWarning())
|
||||
{
|
||||
// 获取预警数据 | Get warning data
|
||||
var totalSeconds = filamentService.GetCurrentTotalLifeSeconds();
|
||||
var thresholdSeconds = filamentService.GetThresholdSeconds();
|
||||
var percentage = filamentService.GetLifetimePercentage();
|
||||
|
||||
// 转换为小时,保留一位小数 | Convert to hours, keep one decimal place
|
||||
var totalHours = (totalSeconds / 3600.0).ToString("F1");
|
||||
var thresholdHours = (thresholdSeconds / 3600.0).ToString("F1");
|
||||
var percentageText = percentage.ToString("F1");
|
||||
|
||||
// 使用 LocalizationHelper 获取本地化文本(OnInitialized 中不方便注入)
|
||||
// Use LocalizationHelper for localized text (injection not convenient in OnInitialized)
|
||||
var title = LocalizationHelper.Get("RaySource_FilamentLifetimeWarningTitle");
|
||||
var message = LocalizationHelper.Get("RaySource_FilamentLifetimeWarningMessage",
|
||||
totalHours, thresholdHours, percentageText);
|
||||
|
||||
// 在 UI 线程弹出模态提醒窗口 | Show modal warning dialog on UI thread
|
||||
System.Windows.Application.Current?.Dispatcher?.Invoke(() =>
|
||||
{
|
||||
System.Windows.MessageBox.Show(
|
||||
message,
|
||||
title,
|
||||
System.Windows.MessageBoxButton.OK,
|
||||
System.Windows.MessageBoxImage.Warning);
|
||||
});
|
||||
}
|
||||
|
||||
System.Console.WriteLine("[RaySourceModule] 模块已初始化");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册类型到DI容器 | Register types to DI container
|
||||
/// </summary>
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
// 注册日志服务(单例)| Register logger service (singleton)
|
||||
if (!containerRegistry.IsRegistered<ILoggerService>())
|
||||
{
|
||||
containerRegistry.RegisterSingleton<ILoggerService, SerilogLoggerService>();
|
||||
System.Console.WriteLine("[RaySourceModule] 日志服务已注册 | Logger service registered");
|
||||
}
|
||||
|
||||
// 注册配置对象(单例)| Register configuration object (singleton)
|
||||
var config = ConfigLoader.LoadConfig();
|
||||
containerRegistry.RegisterInstance(config);
|
||||
|
||||
// 注册工厂层(单例)| Register factory layer (singleton)
|
||||
containerRegistry.RegisterSingleton<IRaySourceFactory, RaySourceFactory>();
|
||||
|
||||
// 注册服务层(单例)| Register service layer (singleton)
|
||||
containerRegistry.RegisterSingleton<IRaySourceService, RaySourceService>();
|
||||
|
||||
// 注册灯丝寿命服务(单例)| Register filament lifetime service (singleton)
|
||||
containerRegistry.RegisterSingleton<IFilamentLifetimeService, FilamentLifetimeService>();
|
||||
|
||||
System.Console.WriteLine("[RaySourceModule] 类型注册完成 | Type registration completed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Telerik.Windows.Controls.RadButton, Telerik.Windows.Controls, Version=2024.1.408.310, Culture=neutral, PublicKeyToken=5803cfa389c90ce7
|
||||
Telerik.Windows.Controls.RadPropertyGrid, Telerik.Windows.Controls.Data, Version=2024.1.408.310, Culture=neutral, PublicKeyToken=5803cfa389c90ce7
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
+885
@@ -0,0 +1,885 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace XP.Hardware.RaySource.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||
/// </summary>
|
||||
// 此类是由 StronglyTypedResourceBuilder
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XP.Hardware.RaySource.Resources.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 实际 {0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_ActualValueLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_ActualValueLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 高级 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_AdvanceButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_AdvanceButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 是否确认执行全部电压自动定心操作? 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_AutoCenter_Confirm {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_AutoCenter_Confirm", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 正在执行全部电压自动定心操作,请稍候... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_AutoCenter_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_AutoCenter_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 自动定心 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_AutoCenter_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_AutoCenter_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 自动定心: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_AutoCenterLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_AutoCenterLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 自动定心 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_AutoCenterSettingButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_AutoCenterSettingButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 配置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_ConfigButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_ConfigButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线源配置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_ConfigWindowTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_ConfigWindowTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 确认 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Confirm_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Confirm_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 连接射线源 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_ConnectButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_ConnectButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 已连接 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Connected {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Connected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 连接射线源设备 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_ConnectTooltip {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_ConnectTooltip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 连接变量 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_ConnectVariablesButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_ConnectVariablesButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 电流 (μA) 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_CurrentLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_CurrentLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 断开射线源 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_DisconnectButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_DisconnectButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 未连接 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Disconnected {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Disconnected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 断开射线源设备 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_DisconnectTooltip {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_DisconnectTooltip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源紧急停止 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_EmergencyStop {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_EmergencyStop", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线开启时无法调整参数 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Error_CannotAdjustWhileXrayOn {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Error_CannotAdjustWhileXrayOn", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 电流超出范围 (10-1000 μA) 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Error_CurrentOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Error_CurrentOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 电压超出范围 (20-225 kV) 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Error_VoltageOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Error_VoltageOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 是否确认执行灯丝校准操作? 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentCalibration_Confirm {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentCalibration_Confirm", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 正在执行灯丝校准操作,请稍候... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentCalibration_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentCalibration_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 正在执行灯丝校准操作,请稍候... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentCalibration_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentCalibration_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 灯丝校准 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentCalibrationButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentCalibrationButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 灯丝校准: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 灯丝寿命: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentLifetimeLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentLifetimeLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 确认 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentLifetimeWarningConfirm {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentLifetimeWarningConfirm", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 灯丝累计使用 {0} 小时,寿命阈值 {1} 小时,已使用 {2}%。建议尽快更换灯丝。 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentLifetimeWarningMessage {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentLifetimeWarningMessage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 灯丝寿命预警 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_FilamentLifetimeWarningTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_FilamentLifetimeWarningTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 High Power 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_HighPowerButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_HighPowerButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源初始化失败 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_InitFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_InitFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 初始化 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_InitializeButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_InitializeButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源初始化成功 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_InitSuccess {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_InitSuccess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 激活 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_InterlockActive {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_InterlockActive", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 连锁: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_InterlockLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_InterlockLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 正常 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_InterlockNormal {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_InterlockNormal", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 最大值 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_MaxValueLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_MaxValueLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Micro Focus 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_MicroFocusButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_MicroFocusButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 最小值 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_MinValueLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_MinValueLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源操作 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_OperateWindowTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_OperateWindowTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 操作指令已发送,等待设备执行... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Operation_Sent {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Operation_Sent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 功率 (W) 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_PowerLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_PowerLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 功率模式: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_PowerModeLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_PowerModeLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 设置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_SettingsButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_SettingsButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线源类型: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_SourceTypeLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_SourceTypeLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源启动失败 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StartFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StartFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源启动成功 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StartSuccess {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StartSuccess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 启动: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StartUpLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StartUpLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 错误 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_Error {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_Error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 故障 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_Fault {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_Fault", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 空闲 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_Idle {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_Idle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 初始化中 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_Initializing {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_Initializing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 就绪 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_Ready {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_Ready", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 预热中 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_Warmup {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_Warmup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线开启 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Status_XrayOn {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Status_XrayOn", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线源
|
||||
///已关闭 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StatusClosed {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StatusClosed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 状态 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StatusLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StatusLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线源
|
||||
///已开启 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StatusOpened {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StatusOpened", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线源
|
||||
///不可用 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StatusUnavailable {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StatusUnavailable", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源关闭失败 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StopFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StopFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源关闭成功 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_StopSuccess {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_StopSuccess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 系统 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_SystemButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_SystemButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 温度 (°C) 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TemperatureLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TemperatureLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源控制 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 是否确认执行训机操作? 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Training_Confirm {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Training_Confirm", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 正在执行训机操作,请稍候... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Training_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Training_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 训机设置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Training_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Training_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 训机设置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TrainingSettingButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TrainingSettingButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 关闭射线源 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TurnOffButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TurnOffButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 开启射线源 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TurnOnButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TurnOnButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 TXI OFF 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TxiOffButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TxiOffButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 TXI ON 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TxiOnButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TxiOnButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 TXI状态: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_TxiStatusLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_TxiStatusLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 真空度 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_VacuumLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_VacuumLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 变量已连接 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_VariablesConnected {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_VariablesConnected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 电压 (kV) 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_VoltageLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_VoltageLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 是否确认执行暖机操作? 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmUp_Confirm {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmUp_Confirm", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 正在执行暖机操作,请稍候... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmUp_Message {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmUp_Message", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 暖机设置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmUp_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmUp_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源预热完成 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmupComplete {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmupComplete", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 X射线源预热中... 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmupInProgress {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmupInProgress", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 暖机: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmUpLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmUpLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 暖机设置 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WarmUpSettingButton {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WarmUpSettingButton", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 温度过高警告 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Warning_HighTemperature {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Warning_HighTemperature", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 真空度过低警告 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_Warning_LowVacuum {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_Warning_LowVacuum", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 看门狗: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_WatchdogLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_WatchdogLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 关闭 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_XRayOff {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_XRayOff", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 开启 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_XRayOn {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_XRayOn", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 射线状态: 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string RaySource_XRayOnLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("RaySource_XRayOnLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="RaySource_Title" xml:space="preserve">
|
||||
<value>X-Ray Source Control</value>
|
||||
</data>
|
||||
<data name="RaySource_OperateWindowTitle" xml:space="preserve">
|
||||
<value>X-Ray Source Operation</value>
|
||||
</data>
|
||||
<data name="RaySource_StartSuccess" xml:space="preserve">
|
||||
<value>X-Ray source started successfully</value>
|
||||
</data>
|
||||
<data name="RaySource_StartFailed" xml:space="preserve">
|
||||
<value>X-Ray source start failed</value>
|
||||
</data>
|
||||
<data name="RaySource_StopSuccess" xml:space="preserve">
|
||||
<value>X-Ray source stopped successfully</value>
|
||||
</data>
|
||||
<data name="RaySource_StopFailed" xml:space="preserve">
|
||||
<value>X-Ray source stop failed</value>
|
||||
</data>
|
||||
<data name="RaySource_InitSuccess" xml:space="preserve">
|
||||
<value>X-Ray source initialized successfully</value>
|
||||
</data>
|
||||
<data name="RaySource_InitFailed" xml:space="preserve">
|
||||
<value>X-Ray source initialization failed</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupInProgress" xml:space="preserve">
|
||||
<value>X-Ray source warming up...</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupComplete" xml:space="preserve">
|
||||
<value>X-Ray source warmup complete</value>
|
||||
</data>
|
||||
<data name="RaySource_EmergencyStop" xml:space="preserve">
|
||||
<value>X-Ray source emergency stop</value>
|
||||
</data>
|
||||
<data name="RaySource_VoltageLabel" xml:space="preserve">
|
||||
<value>Voltage (kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_CurrentLabel" xml:space="preserve">
|
||||
<value>Current (μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusLabel" xml:space="preserve">
|
||||
<value>Status</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerLabel" xml:space="preserve">
|
||||
<value>Power (W)</value>
|
||||
</data>
|
||||
<data name="RaySource_TemperatureLabel" xml:space="preserve">
|
||||
<value>Temperature (°C)</value>
|
||||
</data>
|
||||
<data name="RaySource_VacuumLabel" xml:space="preserve">
|
||||
<value>Vacuum</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Idle" xml:space="preserve">
|
||||
<value>Idle</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Initializing" xml:space="preserve">
|
||||
<value>Initializing</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Warmup" xml:space="preserve">
|
||||
<value>Warming Up</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Ready" xml:space="preserve">
|
||||
<value>Ready</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_XrayOn" xml:space="preserve">
|
||||
<value>X-Ray On</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Error" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Fault" xml:space="preserve">
|
||||
<value>Fault</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_VoltageOutOfRange" xml:space="preserve">
|
||||
<value>Voltage out of range (20-225 kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CurrentOutOfRange" xml:space="preserve">
|
||||
<value>Current out of range (10-1000 μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CannotAdjustWhileXrayOn" xml:space="preserve">
|
||||
<value>Cannot adjust parameters while X-Ray is on</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_HighTemperature" xml:space="preserve">
|
||||
<value>High temperature warning</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_LowVacuum" xml:space="preserve">
|
||||
<value>Low vacuum warning</value>
|
||||
</data>
|
||||
<data name="RaySource_ActualValueLabel" xml:space="preserve">
|
||||
<value>Actual {0}</value>
|
||||
</data>
|
||||
<data name="RaySource_MinValueLabel" xml:space="preserve">
|
||||
<value>Min</value>
|
||||
</data>
|
||||
<data name="RaySource_MaxValueLabel" xml:space="preserve">
|
||||
<value>Max</value>
|
||||
</data>
|
||||
<data name="RaySource_TurnOnButton" xml:space="preserve">
|
||||
<value>Turn On X-Ray</value>
|
||||
</data>
|
||||
<data name="RaySource_TurnOffButton" xml:space="preserve">
|
||||
<value>Turn Off X-Ray</value>
|
||||
</data>
|
||||
<data name="RaySource_SettingsButton" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="RaySource_AdvanceButton" xml:space="preserve">
|
||||
<value>Advance</value>
|
||||
</data>
|
||||
<data name="RaySource_SystemButton" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusClosed" xml:space="preserve">
|
||||
<value>X-Ray
|
||||
Closed</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusOpened" xml:space="preserve">
|
||||
<value>X-Ray
|
||||
Opened</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusUnavailable" xml:space="preserve">
|
||||
<value>X-Ray
|
||||
Unavailable</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectButton" xml:space="preserve">
|
||||
<value>Connect Ray Source</value>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectButton" xml:space="preserve">
|
||||
<value>Disconnect Ray Source</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectTooltip" xml:space="preserve">
|
||||
<value>Connect ray source device</value>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectTooltip" xml:space="preserve">
|
||||
<value>Disconnect ray source device</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectVariablesButton" xml:space="preserve">
|
||||
<value>Connect Variables</value>
|
||||
</data>
|
||||
<data name="RaySource_InitializeButton" xml:space="preserve">
|
||||
<value>Initialize</value>
|
||||
</data>
|
||||
<data name="RaySource_SourceTypeLabel" xml:space="preserve">
|
||||
<value>Source Type:</value>
|
||||
</data>
|
||||
<data name="RaySource_Connected" xml:space="preserve">
|
||||
<value>Connected</value>
|
||||
</data>
|
||||
<data name="RaySource_Disconnected" xml:space="preserve">
|
||||
<value>Disconnected</value>
|
||||
</data>
|
||||
<data name="RaySource_VariablesConnected" xml:space="preserve">
|
||||
<value>Variables Connected</value>
|
||||
</data>
|
||||
<data name="RaySource_ConfigButton" xml:space="preserve">
|
||||
<value>Config</value>
|
||||
</data>
|
||||
<data name="RaySource_ConfigWindowTitle" xml:space="preserve">
|
||||
<value>X-Ray Source Configuration</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpLabel" xml:space="preserve">
|
||||
<value>Warm-Up:</value>
|
||||
</data>
|
||||
<data name="RaySource_StartUpLabel" xml:space="preserve">
|
||||
<value>Startup:</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterLabel" xml:space="preserve">
|
||||
<value>Auto-Center:</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLabel" xml:space="preserve">
|
||||
<value>Filament Adj:</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerModeLabel" xml:space="preserve">
|
||||
<value>Power Mode:</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockLabel" xml:space="preserve">
|
||||
<value>Interlock:</value>
|
||||
</data>
|
||||
<data name="RaySource_WatchdogLabel" xml:space="preserve">
|
||||
<value>Watchdog:</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockActive" xml:space="preserve">
|
||||
<value>Active</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockNormal" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOnLabel" xml:space="preserve">
|
||||
<value>X-Ray:</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOn" xml:space="preserve">
|
||||
<value>On</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOff" xml:space="preserve">
|
||||
<value>Off</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiStatusLabel" xml:space="preserve">
|
||||
<value>TXI Status:</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiOnButton" xml:space="preserve">
|
||||
<value>TXI ON</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiOffButton" xml:space="preserve">
|
||||
<value>TXI OFF</value>
|
||||
</data>
|
||||
<data name="RaySource_HighPowerButton" xml:space="preserve">
|
||||
<value>High Power</value>
|
||||
</data>
|
||||
<data name="RaySource_MicroFocusButton" xml:space="preserve">
|
||||
<value>Micro Focus</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpSettingButton" xml:space="preserve">
|
||||
<value>Warm-Up Setting</value>
|
||||
</data>
|
||||
<data name="RaySource_TrainingSettingButton" xml:space="preserve">
|
||||
<value>Training Setting</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibrationButton" xml:space="preserve">
|
||||
<value>Filament Calibration</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterSettingButton" xml:space="preserve">
|
||||
<value>Auto-Center</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeLabel" xml:space="preserve">
|
||||
<value>Filament Lifetime:</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningTitle" xml:space="preserve">
|
||||
<value>Filament Lifetime Warning</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningMessage" xml:space="preserve">
|
||||
<value>Filament has been used for {0} hours, lifetime threshold is {1} hours, {2}% used. Please replace the filament as soon as possible.</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningConfirm" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="RaySource_Confirm_Title" xml:space="preserve">
|
||||
<value>Confirmation</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Confirm" xml:space="preserve">
|
||||
<value>Are you sure you want to perform the warm-up operation?</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Title" xml:space="preserve">
|
||||
<value>Warm-Up Setting</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Message" xml:space="preserve">
|
||||
<value>Performing warm-up operation, please wait...</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Confirm" xml:space="preserve">
|
||||
<value>Are you sure you want to perform the training operation?</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Title" xml:space="preserve">
|
||||
<value>Training Setting</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Message" xml:space="preserve">
|
||||
<value>Performing training operation, please wait...</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Confirm" xml:space="preserve">
|
||||
<value>Are you sure you want to perform the filament calibration?</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Title" xml:space="preserve">
|
||||
<value>Filament Calibration</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Message" xml:space="preserve">
|
||||
<value>Performing filament calibration, please wait...</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Confirm" xml:space="preserve">
|
||||
<value>Are you sure you want to perform the full voltage auto-center operation?</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Title" xml:space="preserve">
|
||||
<value>Auto-Center</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Message" xml:space="preserve">
|
||||
<value>Performing full voltage auto-center operation, please wait...</value>
|
||||
</data>
|
||||
<data name="RaySource_Operation_Sent" xml:space="preserve">
|
||||
<value>Operation command sent, waiting for device execution...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,386 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="RaySource_Title" xml:space="preserve">
|
||||
<value>X射线源控制</value>
|
||||
</data>
|
||||
<data name="RaySource_OperateWindowTitle" xml:space="preserve">
|
||||
<value>X射线源操作</value>
|
||||
<comment>RaySourceOperateWindow - 窗口标题 | Window title</comment>
|
||||
</data>
|
||||
<data name="RaySource_StartSuccess" xml:space="preserve">
|
||||
<value>X射线源启动成功</value>
|
||||
</data>
|
||||
<data name="RaySource_StartFailed" xml:space="preserve">
|
||||
<value>X射线源启动失败</value>
|
||||
</data>
|
||||
<data name="RaySource_StopSuccess" xml:space="preserve">
|
||||
<value>X射线源关闭成功</value>
|
||||
</data>
|
||||
<data name="RaySource_StopFailed" xml:space="preserve">
|
||||
<value>X射线源关闭失败</value>
|
||||
</data>
|
||||
<data name="RaySource_InitSuccess" xml:space="preserve">
|
||||
<value>X射线源初始化成功</value>
|
||||
</data>
|
||||
<data name="RaySource_InitFailed" xml:space="preserve">
|
||||
<value>X射线源初始化失败</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupInProgress" xml:space="preserve">
|
||||
<value>X射线源预热中...</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupComplete" xml:space="preserve">
|
||||
<value>X射线源预热完成</value>
|
||||
</data>
|
||||
<data name="RaySource_EmergencyStop" xml:space="preserve">
|
||||
<value>X射线源紧急停止</value>
|
||||
</data>
|
||||
<data name="RaySource_VoltageLabel" xml:space="preserve">
|
||||
<value>电压 (kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_CurrentLabel" xml:space="preserve">
|
||||
<value>电流 (μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusLabel" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerLabel" xml:space="preserve">
|
||||
<value>功率 (W)</value>
|
||||
</data>
|
||||
<data name="RaySource_TemperatureLabel" xml:space="preserve">
|
||||
<value>温度 (°C)</value>
|
||||
</data>
|
||||
<data name="RaySource_VacuumLabel" xml:space="preserve">
|
||||
<value>真空度</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Idle" xml:space="preserve">
|
||||
<value>空闲</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Initializing" xml:space="preserve">
|
||||
<value>初始化中</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Warmup" xml:space="preserve">
|
||||
<value>预热中</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Ready" xml:space="preserve">
|
||||
<value>就绪</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_XrayOn" xml:space="preserve">
|
||||
<value>射线开启</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Error" xml:space="preserve">
|
||||
<value>错误</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Fault" xml:space="preserve">
|
||||
<value>故障</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_VoltageOutOfRange" xml:space="preserve">
|
||||
<value>电压超出范围 (20-225 kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CurrentOutOfRange" xml:space="preserve">
|
||||
<value>电流超出范围 (10-1000 μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CannotAdjustWhileXrayOn" xml:space="preserve">
|
||||
<value>射线开启时无法调整参数</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_HighTemperature" xml:space="preserve">
|
||||
<value>温度过高警告</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_LowVacuum" xml:space="preserve">
|
||||
<value>真空度过低警告</value>
|
||||
</data>
|
||||
<data name="RaySource_ActualValueLabel" xml:space="preserve">
|
||||
<value>实际 {0}</value>
|
||||
<comment>RaySourceOperateView - 实际值标签({0}为数值)| Actual value label ({0} is the value)</comment>
|
||||
</data>
|
||||
<data name="RaySource_MinValueLabel" xml:space="preserve">
|
||||
<value>最小值</value>
|
||||
<comment>RaySourceOperateView - 最小值标签 | Minimum value label</comment>
|
||||
</data>
|
||||
<data name="RaySource_MaxValueLabel" xml:space="preserve">
|
||||
<value>最大值</value>
|
||||
<comment>RaySourceOperateView - 最大值标签 | Maximum value label</comment>
|
||||
</data>
|
||||
<data name="RaySource_TurnOnButton" xml:space="preserve">
|
||||
<value>开启射线源</value>
|
||||
<comment>RaySourceOperateView - 开启射线源按钮 | Turn on X-Ray button</comment>
|
||||
</data>
|
||||
<data name="RaySource_TurnOffButton" xml:space="preserve">
|
||||
<value>关闭射线源</value>
|
||||
<comment>RaySourceOperateView - 关闭射线源按钮 | Turn off X-Ray button</comment>
|
||||
</data>
|
||||
<data name="RaySource_SettingsButton" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
<comment>RaySourceOperateView - 设置按钮 | Settings button</comment>
|
||||
</data>
|
||||
<data name="RaySource_AdvanceButton" xml:space="preserve">
|
||||
<value>高级</value>
|
||||
<comment>RaySourceOperateView - 高级按钮 | Advance button</comment>
|
||||
</data>
|
||||
<data name="RaySource_SystemButton" xml:space="preserve">
|
||||
<value>系统</value>
|
||||
<comment>RaySourceOperateView - 系统按钮 | System button</comment>
|
||||
</data>
|
||||
<data name="RaySource_StatusClosed" xml:space="preserve">
|
||||
<value>射线源
|
||||
已关闭</value>
|
||||
<comment>RaySourceOperateView - 射线源关闭状态文本 | X-Ray closed status text</comment>
|
||||
</data>
|
||||
<data name="RaySource_StatusOpened" xml:space="preserve">
|
||||
<value>射线源
|
||||
已开启</value>
|
||||
<comment>RaySourceOperateView - 射线源开启状态文本 | X-Ray opened status text</comment>
|
||||
</data>
|
||||
<data name="RaySource_StatusUnavailable" xml:space="preserve">
|
||||
<value>射线源
|
||||
不可用</value>
|
||||
<comment>RaySourceOperateView - 射线源不可用状态文本 | X-Ray unavailable status text</comment>
|
||||
</data>
|
||||
<data name="RaySource_ConnectButton" xml:space="preserve">
|
||||
<value>连接射线源</value>
|
||||
<comment>RaySourceOperateWindow - 连接射线源按钮 | Connect ray source button</comment>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectButton" xml:space="preserve">
|
||||
<value>断开射线源</value>
|
||||
<comment>RaySourceOperateWindow - 断开射线源按钮 | Disconnect ray source button</comment>
|
||||
</data>
|
||||
<data name="RaySource_ConnectTooltip" xml:space="preserve">
|
||||
<value>连接射线源设备</value>
|
||||
<comment>RaySourceOperateWindow - 连接射线源提示 | Connect ray source tooltip</comment>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectTooltip" xml:space="preserve">
|
||||
<value>断开射线源设备</value>
|
||||
<comment>RaySourceOperateWindow - 断开射线源提示 | Disconnect ray source tooltip</comment>
|
||||
</data>
|
||||
<data name="RaySource_ConnectVariablesButton" xml:space="preserve">
|
||||
<value>连接变量</value>
|
||||
<comment>RaySourceOperateView - 连接 PVI 变量按钮 | Connect PVI variables button</comment>
|
||||
</data>
|
||||
<data name="RaySource_InitializeButton" xml:space="preserve">
|
||||
<value>初始化</value>
|
||||
<comment>RaySourceConfigView - 初始化射线源按钮 | Initialize ray source button</comment>
|
||||
</data>
|
||||
<data name="RaySource_SourceTypeLabel" xml:space="preserve">
|
||||
<value>射线源类型:</value>
|
||||
<comment>RaySourceConfigView - 射线源类型标签 | Ray source type label</comment>
|
||||
</data>
|
||||
<data name="RaySource_Connected" xml:space="preserve">
|
||||
<value>已连接</value>
|
||||
<comment>RaySourceConfigView - 已连接状态文本 | Connected status text</comment>
|
||||
</data>
|
||||
<data name="RaySource_Disconnected" xml:space="preserve">
|
||||
<value>未连接</value>
|
||||
<comment>RaySourceConfigView - 未连接状态文本 | Disconnected status text</comment>
|
||||
</data>
|
||||
<data name="RaySource_VariablesConnected" xml:space="preserve">
|
||||
<value>变量已连接</value>
|
||||
<comment>RaySourceConfigView - 变量已连接状态文本 | Variables connected status text</comment>
|
||||
</data>
|
||||
<data name="RaySource_ConfigButton" xml:space="preserve">
|
||||
<value>配置</value>
|
||||
<comment>RaySourceOperateView - 配置按钮 | Config button</comment>
|
||||
</data>
|
||||
<data name="RaySource_ConfigWindowTitle" xml:space="preserve">
|
||||
<value>射线源配置</value>
|
||||
<comment>RaySourceConfigWindow - 窗口标题 | Window title</comment>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpLabel" xml:space="preserve">
|
||||
<value>暖机:</value>
|
||||
<comment>RaySourceConfigView - 暖机状态标签 | Warm-up status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_StartUpLabel" xml:space="preserve">
|
||||
<value>启动:</value>
|
||||
<comment>RaySourceConfigView - 启动状态标签 | Startup status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterLabel" xml:space="preserve">
|
||||
<value>自动定心:</value>
|
||||
<comment>RaySourceConfigView - 自动定心状态标签 | Auto-center status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLabel" xml:space="preserve">
|
||||
<value>灯丝校准:</value>
|
||||
<comment>RaySourceConfigView - 灯丝校准状态标签 | Filament adjust status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_PowerModeLabel" xml:space="preserve">
|
||||
<value>功率模式:</value>
|
||||
<comment>RaySourceConfigView - 功率模式标签 | Power mode label</comment>
|
||||
</data>
|
||||
<data name="RaySource_InterlockLabel" xml:space="preserve">
|
||||
<value>连锁:</value>
|
||||
<comment>RaySourceConfigView - 连锁状态标签 | Interlock status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_WatchdogLabel" xml:space="preserve">
|
||||
<value>看门狗:</value>
|
||||
<comment>RaySourceConfigView - 看门狗状态标签 | Watchdog status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_InterlockActive" xml:space="preserve">
|
||||
<value>激活</value>
|
||||
<comment>RaySourceConfigView - 连锁激活状态 | Interlock active status</comment>
|
||||
</data>
|
||||
<data name="RaySource_InterlockNormal" xml:space="preserve">
|
||||
<value>正常</value>
|
||||
<comment>RaySourceConfigView - 连锁正常状态 | Interlock normal status</comment>
|
||||
</data>
|
||||
<data name="RaySource_XRayOnLabel" xml:space="preserve">
|
||||
<value>射线状态:</value>
|
||||
<comment>RaySourceConfigView - 射线开启状态标签 | X-ray on status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_XRayOn" xml:space="preserve">
|
||||
<value>开启</value>
|
||||
<comment>RaySourceConfigView - 射线已开启 | X-ray on</comment>
|
||||
</data>
|
||||
<data name="RaySource_XRayOff" xml:space="preserve">
|
||||
<value>关闭</value>
|
||||
<comment>RaySourceConfigView - 射线已关闭 | X-ray off</comment>
|
||||
</data>
|
||||
<data name="RaySource_TxiStatusLabel" xml:space="preserve">
|
||||
<value>TXI状态:</value>
|
||||
<comment>RaySourceConfigView - TXI状态标签 | TXI status label</comment>
|
||||
</data>
|
||||
<data name="RaySource_TxiOnButton" xml:space="preserve">
|
||||
<value>TXI ON</value>
|
||||
<comment>RaySourceConfigView - TXI开启按钮 | TXI on button</comment>
|
||||
</data>
|
||||
<data name="RaySource_TxiOffButton" xml:space="preserve">
|
||||
<value>TXI OFF</value>
|
||||
<comment>RaySourceConfigView - TXI关闭按钮 | TXI off button</comment>
|
||||
</data>
|
||||
<data name="RaySource_HighPowerButton" xml:space="preserve">
|
||||
<value>High Power</value>
|
||||
<comment>RaySourceConfigView - 高功率模式按钮 | High power mode button</comment>
|
||||
</data>
|
||||
<data name="RaySource_MicroFocusButton" xml:space="preserve">
|
||||
<value>Micro Focus</value>
|
||||
<comment>RaySourceConfigView - 微焦点模式按钮 | Micro focus mode button</comment>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpSettingButton" xml:space="preserve">
|
||||
<value>暖机设置</value>
|
||||
<comment>RaySourceConfigView - 暖机设置按钮 | Warm-up setting button</comment>
|
||||
</data>
|
||||
<data name="RaySource_TrainingSettingButton" xml:space="preserve">
|
||||
<value>训机设置</value>
|
||||
<comment>RaySourceConfigView - 训机设置按钮 | Training setting button</comment>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibrationButton" xml:space="preserve">
|
||||
<value>灯丝校准</value>
|
||||
<comment>RaySourceConfigView - 灯丝校准按钮 | Filament calibration button</comment>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterSettingButton" xml:space="preserve">
|
||||
<value>自动定心</value>
|
||||
<comment>RaySourceConfigView - 自动定心按钮 | Auto-center setting button</comment>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeLabel" xml:space="preserve">
|
||||
<value>灯丝寿命:</value>
|
||||
<comment>RaySourceConfigView - 灯丝寿命标签 | Filament lifetime label</comment>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningTitle" xml:space="preserve">
|
||||
<value>灯丝寿命预警</value>
|
||||
<comment>灯丝寿命预警弹窗标题 | Filament lifetime warning dialog title</comment>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningMessage" xml:space="preserve">
|
||||
<value>灯丝累计使用 {0} 小时,寿命阈值 {1} 小时,已使用 {2}%。建议尽快更换灯丝。</value>
|
||||
<comment>灯丝寿命预警弹窗内容({0}=累计时长,{1}=阈值,{2}=百分比)| Filament lifetime warning message</comment>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningConfirm" xml:space="preserve">
|
||||
<value>确认</value>
|
||||
<comment>灯丝寿命预警弹窗确认按钮 | Filament lifetime warning confirm button</comment>
|
||||
</data>
|
||||
<data name="RaySource_Confirm_Title" xml:space="preserve">
|
||||
<value>确认</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行暖机操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Title" xml:space="preserve">
|
||||
<value>暖机设置</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Message" xml:space="preserve">
|
||||
<value>正在执行暖机操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行训机操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Title" xml:space="preserve">
|
||||
<value>训机设置</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Message" xml:space="preserve">
|
||||
<value>正在执行训机操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行灯丝校准操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Title" xml:space="preserve">
|
||||
<value>正在执行灯丝校准操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Message" xml:space="preserve">
|
||||
<value>正在执行灯丝校准操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行全部电压自动定心操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Title" xml:space="preserve">
|
||||
<value>自动定心</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Message" xml:space="preserve">
|
||||
<value>正在执行全部电压自动定心操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_Operation_Sent" xml:space="preserve">
|
||||
<value>操作指令已发送,等待设备执行...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,337 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="RaySource_Title" xml:space="preserve">
|
||||
<value>X射线源控制</value>
|
||||
</data>
|
||||
<data name="RaySource_OperateWindowTitle" xml:space="preserve">
|
||||
<value>X射线源操作</value>
|
||||
</data>
|
||||
<data name="RaySource_StartSuccess" xml:space="preserve">
|
||||
<value>X射线源启动成功</value>
|
||||
</data>
|
||||
<data name="RaySource_StartFailed" xml:space="preserve">
|
||||
<value>X射线源启动失败</value>
|
||||
</data>
|
||||
<data name="RaySource_StopSuccess" xml:space="preserve">
|
||||
<value>X射线源关闭成功</value>
|
||||
</data>
|
||||
<data name="RaySource_StopFailed" xml:space="preserve">
|
||||
<value>X射线源关闭失败</value>
|
||||
</data>
|
||||
<data name="RaySource_InitSuccess" xml:space="preserve">
|
||||
<value>X射线源初始化成功</value>
|
||||
</data>
|
||||
<data name="RaySource_InitFailed" xml:space="preserve">
|
||||
<value>X射线源初始化失败</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupInProgress" xml:space="preserve">
|
||||
<value>X射线源预热中...</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupComplete" xml:space="preserve">
|
||||
<value>X射线源预热完成</value>
|
||||
</data>
|
||||
<data name="RaySource_EmergencyStop" xml:space="preserve">
|
||||
<value>X射线源紧急停止</value>
|
||||
</data>
|
||||
<data name="RaySource_VoltageLabel" xml:space="preserve">
|
||||
<value>电压 (kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_CurrentLabel" xml:space="preserve">
|
||||
<value>电流 (μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusLabel" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerLabel" xml:space="preserve">
|
||||
<value>功率 (W)</value>
|
||||
</data>
|
||||
<data name="RaySource_TemperatureLabel" xml:space="preserve">
|
||||
<value>温度 (°C)</value>
|
||||
</data>
|
||||
<data name="RaySource_VacuumLabel" xml:space="preserve">
|
||||
<value>真空度</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Idle" xml:space="preserve">
|
||||
<value>空闲</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Initializing" xml:space="preserve">
|
||||
<value>初始化中</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Warmup" xml:space="preserve">
|
||||
<value>预热中</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Ready" xml:space="preserve">
|
||||
<value>就绪</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_XrayOn" xml:space="preserve">
|
||||
<value>射线开启</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Error" xml:space="preserve">
|
||||
<value>错误</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Fault" xml:space="preserve">
|
||||
<value>故障</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_VoltageOutOfRange" xml:space="preserve">
|
||||
<value>电压超出范围 (20-225 kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CurrentOutOfRange" xml:space="preserve">
|
||||
<value>电流超出范围 (10-1000 μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CannotAdjustWhileXrayOn" xml:space="preserve">
|
||||
<value>射线开启时无法调整参数</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_HighTemperature" xml:space="preserve">
|
||||
<value>温度过高警告</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_LowVacuum" xml:space="preserve">
|
||||
<value>真空度过低警告</value>
|
||||
</data>
|
||||
<data name="RaySource_ActualValueLabel" xml:space="preserve">
|
||||
<value>实际 {0}</value>
|
||||
</data>
|
||||
<data name="RaySource_MinValueLabel" xml:space="preserve">
|
||||
<value>最小值</value>
|
||||
</data>
|
||||
<data name="RaySource_MaxValueLabel" xml:space="preserve">
|
||||
<value>最大值</value>
|
||||
</data>
|
||||
<data name="RaySource_TurnOnButton" xml:space="preserve">
|
||||
<value>开启射线源</value>
|
||||
</data>
|
||||
<data name="RaySource_TurnOffButton" xml:space="preserve">
|
||||
<value>关闭射线源</value>
|
||||
</data>
|
||||
<data name="RaySource_SettingsButton" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="RaySource_AdvanceButton" xml:space="preserve">
|
||||
<value>高级</value>
|
||||
</data>
|
||||
<data name="RaySource_SystemButton" xml:space="preserve">
|
||||
<value>系统</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusClosed" xml:space="preserve">
|
||||
<value>射线源
|
||||
已关闭</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusOpened" xml:space="preserve">
|
||||
<value>射线源
|
||||
已开启</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusUnavailable" xml:space="preserve">
|
||||
<value>射线源
|
||||
不可用</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectButton" xml:space="preserve">
|
||||
<value>连接射线源</value>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectButton" xml:space="preserve">
|
||||
<value>断开射线源</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectTooltip" xml:space="preserve">
|
||||
<value>连接射线源设备</value>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectTooltip" xml:space="preserve">
|
||||
<value>断开射线源设备</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectVariablesButton" xml:space="preserve">
|
||||
<value>连接变量</value>
|
||||
</data>
|
||||
<data name="RaySource_InitializeButton" xml:space="preserve">
|
||||
<value>初始化</value>
|
||||
</data>
|
||||
<data name="RaySource_SourceTypeLabel" xml:space="preserve">
|
||||
<value>射线源类型:</value>
|
||||
</data>
|
||||
<data name="RaySource_Connected" xml:space="preserve">
|
||||
<value>已连接</value>
|
||||
</data>
|
||||
<data name="RaySource_Disconnected" xml:space="preserve">
|
||||
<value>未连接</value>
|
||||
</data>
|
||||
<data name="RaySource_VariablesConnected" xml:space="preserve">
|
||||
<value>变量已连接</value>
|
||||
</data>
|
||||
<data name="RaySource_ConfigButton" xml:space="preserve">
|
||||
<value>配置</value>
|
||||
</data>
|
||||
<data name="RaySource_ConfigWindowTitle" xml:space="preserve">
|
||||
<value>射线源配置</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpLabel" xml:space="preserve">
|
||||
<value>暖机:</value>
|
||||
</data>
|
||||
<data name="RaySource_StartUpLabel" xml:space="preserve">
|
||||
<value>启动:</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterLabel" xml:space="preserve">
|
||||
<value>自动定心:</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLabel" xml:space="preserve">
|
||||
<value>灯丝校准:</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerModeLabel" xml:space="preserve">
|
||||
<value>功率模式:</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockLabel" xml:space="preserve">
|
||||
<value>连锁:</value>
|
||||
</data>
|
||||
<data name="RaySource_WatchdogLabel" xml:space="preserve">
|
||||
<value>看门狗:</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockActive" xml:space="preserve">
|
||||
<value>激活</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockNormal" xml:space="preserve">
|
||||
<value>正常</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOnLabel" xml:space="preserve">
|
||||
<value>射线状态:</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOn" xml:space="preserve">
|
||||
<value>开启</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOff" xml:space="preserve">
|
||||
<value>关闭</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiStatusLabel" xml:space="preserve">
|
||||
<value>TXI状态:</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiOnButton" xml:space="preserve">
|
||||
<value>TXI ON</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiOffButton" xml:space="preserve">
|
||||
<value>TXI OFF</value>
|
||||
</data>
|
||||
<data name="RaySource_HighPowerButton" xml:space="preserve">
|
||||
<value>High Power</value>
|
||||
</data>
|
||||
<data name="RaySource_MicroFocusButton" xml:space="preserve">
|
||||
<value>Micro Focus</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpSettingButton" xml:space="preserve">
|
||||
<value>暖机设置</value>
|
||||
</data>
|
||||
<data name="RaySource_TrainingSettingButton" xml:space="preserve">
|
||||
<value>训机设置</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibrationButton" xml:space="preserve">
|
||||
<value>灯丝校准</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterSettingButton" xml:space="preserve">
|
||||
<value>自动定心</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeLabel" xml:space="preserve">
|
||||
<value>灯丝寿命:</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningTitle" xml:space="preserve">
|
||||
<value>灯丝寿命预警</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningMessage" xml:space="preserve">
|
||||
<value>灯丝累计使用 {0} 小时,寿命阈值 {1} 小时,已使用 {2}%。建议尽快更换灯丝。</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningConfirm" xml:space="preserve">
|
||||
<value>确认</value>
|
||||
</data>
|
||||
<data name="RaySource_Confirm_Title" xml:space="preserve">
|
||||
<value>确认</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行暖机操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Title" xml:space="preserve">
|
||||
<value>暖机设置</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Message" xml:space="preserve">
|
||||
<value>正在执行暖机操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行训机操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Title" xml:space="preserve">
|
||||
<value>训机设置</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Message" xml:space="preserve">
|
||||
<value>正在执行训机操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行灯丝校准操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Title" xml:space="preserve">
|
||||
<value>灯丝校准</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Message" xml:space="preserve">
|
||||
<value>正在执行灯丝校准操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Confirm" xml:space="preserve">
|
||||
<value>是否确认执行全部电压自动定心操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Title" xml:space="preserve">
|
||||
<value>自动定心</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Message" xml:space="preserve">
|
||||
<value>正在执行全部电压自动定心操作,请稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_Operation_Sent" xml:space="preserve">
|
||||
<value>操作指令已发送,等待设备执行...</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,337 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="RaySource_Title" xml:space="preserve">
|
||||
<value>X射線源控制</value>
|
||||
</data>
|
||||
<data name="RaySource_OperateWindowTitle" xml:space="preserve">
|
||||
<value>X射線源操作</value>
|
||||
</data>
|
||||
<data name="RaySource_StartSuccess" xml:space="preserve">
|
||||
<value>X射線源啟動成功</value>
|
||||
</data>
|
||||
<data name="RaySource_StartFailed" xml:space="preserve">
|
||||
<value>X射線源啟動失敗</value>
|
||||
</data>
|
||||
<data name="RaySource_StopSuccess" xml:space="preserve">
|
||||
<value>X射線源關閉成功</value>
|
||||
</data>
|
||||
<data name="RaySource_StopFailed" xml:space="preserve">
|
||||
<value>X射線源關閉失敗</value>
|
||||
</data>
|
||||
<data name="RaySource_InitSuccess" xml:space="preserve">
|
||||
<value>X射線源初始化成功</value>
|
||||
</data>
|
||||
<data name="RaySource_InitFailed" xml:space="preserve">
|
||||
<value>X射線源初始化失敗</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupInProgress" xml:space="preserve">
|
||||
<value>X射線源預熱中...</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmupComplete" xml:space="preserve">
|
||||
<value>X射線源預熱完成</value>
|
||||
</data>
|
||||
<data name="RaySource_EmergencyStop" xml:space="preserve">
|
||||
<value>X射線源緊急停止</value>
|
||||
</data>
|
||||
<data name="RaySource_VoltageLabel" xml:space="preserve">
|
||||
<value>電壓 (kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_CurrentLabel" xml:space="preserve">
|
||||
<value>電流 (μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusLabel" xml:space="preserve">
|
||||
<value>狀態</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerLabel" xml:space="preserve">
|
||||
<value>功率 (W)</value>
|
||||
</data>
|
||||
<data name="RaySource_TemperatureLabel" xml:space="preserve">
|
||||
<value>溫度 (°C)</value>
|
||||
</data>
|
||||
<data name="RaySource_VacuumLabel" xml:space="preserve">
|
||||
<value>真空度</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Idle" xml:space="preserve">
|
||||
<value>閒置</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Initializing" xml:space="preserve">
|
||||
<value>初始化中</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Warmup" xml:space="preserve">
|
||||
<value>預熱中</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Ready" xml:space="preserve">
|
||||
<value>就緒</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_XrayOn" xml:space="preserve">
|
||||
<value>射線開啟</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Error" xml:space="preserve">
|
||||
<value>錯誤</value>
|
||||
</data>
|
||||
<data name="RaySource_Status_Fault" xml:space="preserve">
|
||||
<value>故障</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_VoltageOutOfRange" xml:space="preserve">
|
||||
<value>電壓超出範圍 (20-225 kV)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CurrentOutOfRange" xml:space="preserve">
|
||||
<value>電流超出範圍 (10-1000 μA)</value>
|
||||
</data>
|
||||
<data name="RaySource_Error_CannotAdjustWhileXrayOn" xml:space="preserve">
|
||||
<value>射線開啟時無法調整參數</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_HighTemperature" xml:space="preserve">
|
||||
<value>溫度過高警告</value>
|
||||
</data>
|
||||
<data name="RaySource_Warning_LowVacuum" xml:space="preserve">
|
||||
<value>真空度過低警告</value>
|
||||
</data>
|
||||
<data name="RaySource_ActualValueLabel" xml:space="preserve">
|
||||
<value>實際 {0}</value>
|
||||
</data>
|
||||
<data name="RaySource_MinValueLabel" xml:space="preserve">
|
||||
<value>最小值</value>
|
||||
</data>
|
||||
<data name="RaySource_MaxValueLabel" xml:space="preserve">
|
||||
<value>最大值</value>
|
||||
</data>
|
||||
<data name="RaySource_TurnOnButton" xml:space="preserve">
|
||||
<value>開啟射線源</value>
|
||||
</data>
|
||||
<data name="RaySource_TurnOffButton" xml:space="preserve">
|
||||
<value>關閉射線源</value>
|
||||
</data>
|
||||
<data name="RaySource_SettingsButton" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="RaySource_AdvanceButton" xml:space="preserve">
|
||||
<value>進階</value>
|
||||
</data>
|
||||
<data name="RaySource_SystemButton" xml:space="preserve">
|
||||
<value>系統</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusClosed" xml:space="preserve">
|
||||
<value>射線源
|
||||
已關閉</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusOpened" xml:space="preserve">
|
||||
<value>射線源
|
||||
已開啟</value>
|
||||
</data>
|
||||
<data name="RaySource_StatusUnavailable" xml:space="preserve">
|
||||
<value>射線源
|
||||
不可用</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectButton" xml:space="preserve">
|
||||
<value>連接射線源</value>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectButton" xml:space="preserve">
|
||||
<value>斷開射線源</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectTooltip" xml:space="preserve">
|
||||
<value>連接射線源設備</value>
|
||||
</data>
|
||||
<data name="RaySource_DisconnectTooltip" xml:space="preserve">
|
||||
<value>斷開射線源設備</value>
|
||||
</data>
|
||||
<data name="RaySource_ConnectVariablesButton" xml:space="preserve">
|
||||
<value>連接變量</value>
|
||||
</data>
|
||||
<data name="RaySource_InitializeButton" xml:space="preserve">
|
||||
<value>初始化</value>
|
||||
</data>
|
||||
<data name="RaySource_SourceTypeLabel" xml:space="preserve">
|
||||
<value>射線源類型:</value>
|
||||
</data>
|
||||
<data name="RaySource_Connected" xml:space="preserve">
|
||||
<value>已連接</value>
|
||||
</data>
|
||||
<data name="RaySource_Disconnected" xml:space="preserve">
|
||||
<value>未連接</value>
|
||||
</data>
|
||||
<data name="RaySource_VariablesConnected" xml:space="preserve">
|
||||
<value>變量已連接</value>
|
||||
</data>
|
||||
<data name="RaySource_ConfigButton" xml:space="preserve">
|
||||
<value>配置</value>
|
||||
</data>
|
||||
<data name="RaySource_ConfigWindowTitle" xml:space="preserve">
|
||||
<value>射線源配置</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpLabel" xml:space="preserve">
|
||||
<value>暖機:</value>
|
||||
</data>
|
||||
<data name="RaySource_StartUpLabel" xml:space="preserve">
|
||||
<value>啟動:</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterLabel" xml:space="preserve">
|
||||
<value>自動定心:</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLabel" xml:space="preserve">
|
||||
<value>燈絲校準:</value>
|
||||
</data>
|
||||
<data name="RaySource_PowerModeLabel" xml:space="preserve">
|
||||
<value>功率模式:</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockLabel" xml:space="preserve">
|
||||
<value>連鎖:</value>
|
||||
</data>
|
||||
<data name="RaySource_WatchdogLabel" xml:space="preserve">
|
||||
<value>看門狗:</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockActive" xml:space="preserve">
|
||||
<value>啟動</value>
|
||||
</data>
|
||||
<data name="RaySource_InterlockNormal" xml:space="preserve">
|
||||
<value>正常</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOnLabel" xml:space="preserve">
|
||||
<value>射線狀態:</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOn" xml:space="preserve">
|
||||
<value>開啟</value>
|
||||
</data>
|
||||
<data name="RaySource_XRayOff" xml:space="preserve">
|
||||
<value>關閉</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiStatusLabel" xml:space="preserve">
|
||||
<value>TXI狀態:</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiOnButton" xml:space="preserve">
|
||||
<value>TXI ON</value>
|
||||
</data>
|
||||
<data name="RaySource_TxiOffButton" xml:space="preserve">
|
||||
<value>TXI OFF</value>
|
||||
</data>
|
||||
<data name="RaySource_HighPowerButton" xml:space="preserve">
|
||||
<value>High Power</value>
|
||||
</data>
|
||||
<data name="RaySource_MicroFocusButton" xml:space="preserve">
|
||||
<value>Micro Focus</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUpSettingButton" xml:space="preserve">
|
||||
<value>暖機設置</value>
|
||||
</data>
|
||||
<data name="RaySource_TrainingSettingButton" xml:space="preserve">
|
||||
<value>訓機設置</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibrationButton" xml:space="preserve">
|
||||
<value>燈絲校準</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenterSettingButton" xml:space="preserve">
|
||||
<value>自動定心</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeLabel" xml:space="preserve">
|
||||
<value>燈絲壽命:</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningTitle" xml:space="preserve">
|
||||
<value>燈絲壽命預警</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningMessage" xml:space="preserve">
|
||||
<value>燈絲累計使用 {0} 小時,壽命閾值 {1} 小時,已使用 {2}%。建議盡快更換燈絲。</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentLifetimeWarningConfirm" xml:space="preserve">
|
||||
<value>確認</value>
|
||||
</data>
|
||||
<data name="RaySource_Confirm_Title" xml:space="preserve">
|
||||
<value>確認</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Confirm" xml:space="preserve">
|
||||
<value>是否確認執行暖機操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Title" xml:space="preserve">
|
||||
<value>暖機設置</value>
|
||||
</data>
|
||||
<data name="RaySource_WarmUp_Message" xml:space="preserve">
|
||||
<value>正在執行暖機操作,請稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Confirm" xml:space="preserve">
|
||||
<value>是否確認執行訓機操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Title" xml:space="preserve">
|
||||
<value>訓機設置</value>
|
||||
</data>
|
||||
<data name="RaySource_Training_Message" xml:space="preserve">
|
||||
<value>正在執行訓機操作,請稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Confirm" xml:space="preserve">
|
||||
<value>是否確認執行燈絲校準操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Title" xml:space="preserve">
|
||||
<value>燈絲校準</value>
|
||||
</data>
|
||||
<data name="RaySource_FilamentCalibration_Message" xml:space="preserve">
|
||||
<value>正在執行燈絲校準操作,請稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Confirm" xml:space="preserve">
|
||||
<value>是否確認執行全部電壓自動定心操作?</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Title" xml:space="preserve">
|
||||
<value>自動定心</value>
|
||||
</data>
|
||||
<data name="RaySource_AutoCenter_Message" xml:space="preserve">
|
||||
<value>正在執行全部電壓自動定心操作,請稍候...</value>
|
||||
</data>
|
||||
<data name="RaySource_Operation_Sent" xml:space="preserve">
|
||||
<value>操作指令已發送,等待設備執行...</value>
|
||||
</data>
|
||||
</root>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,577 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using XP.Common.Database.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
|
||||
namespace XP.Hardware.RaySource.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 灯丝寿命管理服务实现 | Filament Lifetime Management Service Implementation
|
||||
/// 负责灯丝使用时长的记录、计算、异常恢复和预警判断
|
||||
/// Responsible for filament usage duration recording, calculation, anomaly recovery, and warning evaluation
|
||||
/// </summary>
|
||||
public class FilamentLifetimeService : IFilamentLifetimeService
|
||||
{
|
||||
private readonly IDbContext _dbContext;
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// 内存状态 | In-memory state
|
||||
private DateTime? _filamentStartTime;
|
||||
private string _sourceType = "";
|
||||
private string _serialNumber = "";
|
||||
private double _thresholdSeconds;
|
||||
private readonly object _filamentLock = new object();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsFilamentOn => _filamentStartTime != null;
|
||||
|
||||
#region 构造函数 | Constructor
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,注入依赖 | Constructor with dependency injection
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文 | Database context</param>
|
||||
/// <param name="config">射线源配置 | Ray source configuration</param>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public FilamentLifetimeService(IDbContext dbContext, RaySourceConfig config, ILoggerService logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_logger = logger?.ForModule<FilamentLifetimeService>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化 | Initialization
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 读取配置 | Read configuration
|
||||
_sourceType = _config.SourceType;
|
||||
_serialNumber = _config.SerialNumber;
|
||||
_thresholdSeconds = _config.TotalLifeThreshold * 3600;
|
||||
|
||||
// 2. 验证配置(SourceType 或 SerialNumber 为空时阻止初始化)
|
||||
// Validate configuration (block initialization if SourceType or SerialNumber is empty)
|
||||
if (string.IsNullOrWhiteSpace(_sourceType) || string.IsNullOrWhiteSpace(_serialNumber))
|
||||
{
|
||||
_logger.Error(null, "灯丝寿命服务初始化失败:SourceType 或 SerialNumber 为空,SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"Filament lifetime service initialization failed: SourceType or SerialNumber is empty, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
_sourceType ?? "", _serialNumber ?? "");
|
||||
IsInitialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 建表(CREATE TABLE IF NOT EXISTS)| Create tables
|
||||
if (!CreateTables())
|
||||
{
|
||||
IsInitialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 异常恢复(关闭未结束记录)| Anomaly recovery (close unclosed records)
|
||||
RecoverUnclosedRecords();
|
||||
|
||||
// 5. 重算累计寿命 | Recalculate accumulated lifetime
|
||||
RecalculateTotalLifeSeconds();
|
||||
|
||||
IsInitialized = true;
|
||||
_logger.Info("灯丝寿命服务初始化成功,SourceType={SourceType},SerialNumber={SerialNumber},阈值={Threshold}小时 | " +
|
||||
"Filament lifetime service initialized successfully, SourceType={SourceType}, SerialNumber={SerialNumber}, threshold={Threshold} hours",
|
||||
_sourceType, _serialNumber, _config.TotalLifeThreshold);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "灯丝寿命服务初始化异常 | Filament lifetime service initialization exception: {Message}", ex.Message);
|
||||
IsInitialized = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建数据库表 | Create database tables
|
||||
/// </summary>
|
||||
private bool CreateTables()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建累计统计表 | Create lifetime statistics table
|
||||
var createStatsSql = @"
|
||||
CREATE TABLE IF NOT EXISTS RaySourceFilamentLifetimeStatistics (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
SourceType TEXT NOT NULL,
|
||||
SerialNumber TEXT NOT NULL,
|
||||
TotalLifeSeconds REAL NOT NULL DEFAULT 0,
|
||||
LastUpdateTime TEXT NOT NULL DEFAULT '1970-01-01T00:00:00Z'
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_lifetime_serial ON RaySourceFilamentLifetimeStatistics(SerialNumber);
|
||||
CREATE INDEX IF NOT EXISTS idx_lifetime_source_type ON RaySourceFilamentLifetimeStatistics(SourceType);";
|
||||
|
||||
var statsResult = _dbContext.ExecuteNonQuery(createStatsSql);
|
||||
if (!statsResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(statsResult.Exception, "创建统计表失败:{Message} | Failed to create statistics table: {Message}", statsResult.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建使用流水表 | Create usage logs table
|
||||
var createLogsSql = @"
|
||||
CREATE TABLE IF NOT EXISTS RaySourceFilamentUsageLogs (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
SourceType TEXT NOT NULL,
|
||||
SerialNumber TEXT NOT NULL,
|
||||
StartTime TEXT NOT NULL,
|
||||
EndTime TEXT,
|
||||
DurationSeconds REAL NOT NULL DEFAULT 0,
|
||||
Status INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_serial ON RaySourceFilamentUsageLogs(SerialNumber);";
|
||||
|
||||
var logsResult = _dbContext.ExecuteNonQuery(createLogsSql);
|
||||
if (!logsResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(logsResult.Exception, "创建流水表失败:{Message} | Failed to create usage logs table: {Message}", logsResult.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "建表异常 | Exception creating tables: {Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异常恢复:关闭未结束的流水记录 | Anomaly recovery: close unclosed usage log records
|
||||
/// </summary>
|
||||
private void RecoverUnclosedRecords()
|
||||
{
|
||||
try
|
||||
{
|
||||
var querySql = @"
|
||||
SELECT Id, StartTime FROM RaySourceFilamentUsageLogs
|
||||
WHERE SourceType = @SourceType AND SerialNumber = @SerialNumber AND EndTime IS NULL";
|
||||
|
||||
var queryParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var (queryResult, dataTable) = _dbContext.ExecuteDataTable(querySql, queryParams);
|
||||
if (!queryResult.IsSuccess || dataTable == null || dataTable.Rows.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Warn("检测到 {Count} 条未关闭的灯丝使用记录,开始异常恢复 | " +
|
||||
"Detected {Count} unclosed filament usage records, starting anomaly recovery",
|
||||
dataTable.Rows.Count);
|
||||
|
||||
foreach (DataRow row in dataTable.Rows)
|
||||
{
|
||||
var id = Convert.ToInt64(row["Id"]);
|
||||
var startTimeStr = row["StartTime"]?.ToString() ?? "";
|
||||
|
||||
// 将未关闭记录的 EndTime 设为 StartTime,DurationSeconds 设为 0,Status 设为 1(异常中断)
|
||||
// Set unclosed record's EndTime to StartTime, DurationSeconds to 0, Status to 1 (abnormal interruption)
|
||||
var updateSql = @"
|
||||
UPDATE RaySourceFilamentUsageLogs
|
||||
SET EndTime = StartTime, DurationSeconds = 0, Status = 1
|
||||
WHERE Id = @Id";
|
||||
|
||||
var updateParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "Id", id }
|
||||
};
|
||||
|
||||
_dbContext.ExecuteNonQuery(updateSql, updateParams);
|
||||
_logger.Warn("异常恢复:关闭未结束记录,SourceType={SourceType},SerialNumber={SerialNumber},StartTime={StartTime} | " +
|
||||
"Anomaly recovery: closed unclosed record, SourceType={SourceType}, SerialNumber={SerialNumber}, StartTime={StartTime}",
|
||||
_sourceType, _serialNumber, startTimeStr);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "异常恢复失败 | Anomaly recovery failed: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于流水表重算累计寿命 | Recalculate accumulated lifetime based on usage logs
|
||||
/// </summary>
|
||||
private void RecalculateTotalLifeSeconds()
|
||||
{
|
||||
try
|
||||
{
|
||||
var sumSql = @"
|
||||
SELECT COALESCE(SUM(DurationSeconds), 0) FROM RaySourceFilamentUsageLogs
|
||||
WHERE SourceType = @SourceType AND SerialNumber = @SerialNumber";
|
||||
|
||||
var sumParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var (sumResult, totalSeconds) = _dbContext.ExecuteScalar<double>(sumSql, sumParams);
|
||||
if (!sumResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(sumResult.Exception, "重算累计寿命失败:查询 DurationSeconds 总和出错,{Message} | " +
|
||||
"Failed to recalculate lifetime: error querying DurationSeconds sum, {Message}", sumResult.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var nowUtc = DateTime.UtcNow.ToString("o");
|
||||
|
||||
// 更新或插入统计表 | Update or insert statistics table
|
||||
var upsertSql = @"
|
||||
INSERT INTO RaySourceFilamentLifetimeStatistics (SourceType, SerialNumber, TotalLifeSeconds, LastUpdateTime)
|
||||
VALUES (@SourceType, @SerialNumber, @TotalLifeSeconds, @LastUpdateTime)
|
||||
ON CONFLICT(SerialNumber) DO UPDATE SET TotalLifeSeconds = @TotalLifeSeconds, LastUpdateTime = @LastUpdateTime";
|
||||
|
||||
var updateParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "TotalLifeSeconds", totalSeconds },
|
||||
{ "LastUpdateTime", nowUtc },
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var updateResult = _dbContext.ExecuteNonQuery(upsertSql, updateParams);
|
||||
if (!updateResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(updateResult.Exception, "重算累计寿命失败:更新统计表出错,{Message} | " +
|
||||
"Failed to recalculate lifetime: error updating statistics table, {Message}", updateResult.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "重算累计寿命异常 | Exception recalculating lifetime: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 灯丝开启与关闭 | Filament Start and Stop
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool StartFilamentUsage()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
_logger.Warn("灯丝寿命服务未初始化,无法开始记录 | Filament lifetime service not initialized, cannot start recording");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_filamentLock)
|
||||
{
|
||||
// 锁内再次检查,防止并发重复开始 | Double-check inside lock to prevent concurrent duplicate start
|
||||
if (_filamentStartTime != null)
|
||||
{
|
||||
_logger.Warn("灯丝已处于开启状态,忽略重复开始请求 | Filament already on, ignoring duplicate start request");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 确保统计表中存在当前设备记录 | Ensure current device record exists in statistics table
|
||||
var ensureStatsSql = @"
|
||||
INSERT OR IGNORE INTO RaySourceFilamentLifetimeStatistics (SourceType, SerialNumber, TotalLifeSeconds, LastUpdateTime)
|
||||
VALUES (@SourceType, @SerialNumber, 0, @LastUpdateTime)";
|
||||
|
||||
var ensureParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber },
|
||||
{ "LastUpdateTime", DateTime.UtcNow.ToString("o") }
|
||||
};
|
||||
|
||||
var ensureResult = _dbContext.ExecuteNonQuery(ensureStatsSql, ensureParams);
|
||||
if (!ensureResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(ensureResult.Exception, "确保统计记录存在失败:{Message},SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"Failed to ensure statistics record exists: {Message}, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
ensureResult.Message, _sourceType, _serialNumber);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 向流水表插入新记录 | Insert new usage log record
|
||||
var startTime = DateTime.UtcNow;
|
||||
var startTimeStr = startTime.ToString("o");
|
||||
|
||||
var insertLogSql = @"
|
||||
INSERT INTO RaySourceFilamentUsageLogs (SourceType, SerialNumber, StartTime, EndTime, DurationSeconds, Status)
|
||||
VALUES (@SourceType, @SerialNumber, @StartTime, NULL, 0, 0)";
|
||||
|
||||
var insertParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber },
|
||||
{ "StartTime", startTimeStr }
|
||||
};
|
||||
|
||||
var insertResult = _dbContext.ExecuteNonQuery(insertLogSql, insertParams);
|
||||
if (!insertResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(insertResult.Exception, "插入灯丝使用流水记录失败:{Message},SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"Failed to insert filament usage log: {Message}, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
insertResult.Message, _sourceType, _serialNumber);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 记录内存起始时间(仅在数据库插入成功后更新)| Record in-memory start time
|
||||
_filamentStartTime = startTime;
|
||||
|
||||
_logger.Info("灯丝使用开始,SourceType={SourceType},SerialNumber={SerialNumber},StartTime={StartTime} | " +
|
||||
"Filament usage started, SourceType={SourceType}, SerialNumber={SerialNumber}, StartTime={StartTime}",
|
||||
_sourceType, _serialNumber, startTimeStr);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "开始灯丝使用记录异常,SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"Exception starting filament usage recording, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
_sourceType, _serialNumber);
|
||||
return false;
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool StopFilamentUsage()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
_logger.Warn("灯丝寿命服务未初始化,无法停止记录 | Filament lifetime service not initialized, cannot stop recording");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_filamentLock)
|
||||
{
|
||||
// 锁内再次检查,防止并发重复停止 | Double-check inside lock to prevent concurrent duplicate stop
|
||||
if (_filamentStartTime == null)
|
||||
{
|
||||
_logger.Warn("灯丝已处于关闭状态,忽略重复停止请求 | Filament already off, ignoring duplicate stop request");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 查找当前设备最近一条未关闭的记录 | Find the most recent unclosed record
|
||||
var querySql = @"
|
||||
SELECT Id, StartTime FROM RaySourceFilamentUsageLogs
|
||||
WHERE SourceType = @SourceType AND SerialNumber = @SerialNumber AND EndTime IS NULL
|
||||
ORDER BY Id DESC LIMIT 1";
|
||||
|
||||
var queryParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var (queryResult, dataTable) = _dbContext.ExecuteDataTable(querySql, queryParams);
|
||||
if (!queryResult.IsSuccess || dataTable == null || dataTable.Rows.Count == 0)
|
||||
{
|
||||
_logger.Warn("未找到当前设备的未结束流水记录,SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"No unclosed usage log found, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
_sourceType, _serialNumber);
|
||||
return false;
|
||||
}
|
||||
|
||||
var row = dataTable.Rows[0];
|
||||
var recordId = Convert.ToInt64(row["Id"]);
|
||||
var startTimeStr = row["StartTime"]?.ToString() ?? "";
|
||||
|
||||
// 2. 计算 DurationSeconds | Calculate DurationSeconds
|
||||
var endTime = DateTime.UtcNow;
|
||||
var endTimeStr = endTime.ToString("o");
|
||||
|
||||
double durationSeconds = 0;
|
||||
if (DateTime.TryParse(startTimeStr, null, System.Globalization.DateTimeStyles.RoundtripKind, out var startTime))
|
||||
{
|
||||
durationSeconds = (endTime - startTime).TotalSeconds;
|
||||
if (durationSeconds < 0) durationSeconds = 0;
|
||||
}
|
||||
|
||||
// 3. 顺序更新流水表和统计表(ExecuteNonQuery 不支持外部事务参数,改为顺序执行)
|
||||
// Sequential update: usage log then statistics
|
||||
|
||||
// 3.1 更新流水记录 | Update usage log
|
||||
var updateLogSql = @"
|
||||
UPDATE RaySourceFilamentUsageLogs
|
||||
SET EndTime = @EndTime, DurationSeconds = @DurationSeconds, Status = 1
|
||||
WHERE Id = @Id";
|
||||
|
||||
var updateLogParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "EndTime", endTimeStr },
|
||||
{ "DurationSeconds", durationSeconds },
|
||||
{ "Id", recordId }
|
||||
};
|
||||
|
||||
var updateLogResult = _dbContext.ExecuteNonQuery(updateLogSql, updateLogParams);
|
||||
if (!updateLogResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(updateLogResult.Exception, "更新流水记录失败:{Message} | Failed to update usage log: {Message}", updateLogResult.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.2 累加统计表 | Accumulate statistics
|
||||
var updateStatsSql = @"
|
||||
UPDATE RaySourceFilamentLifetimeStatistics
|
||||
SET TotalLifeSeconds = TotalLifeSeconds + @DurationSeconds, LastUpdateTime = @LastUpdateTime
|
||||
WHERE SourceType = @SourceType AND SerialNumber = @SerialNumber";
|
||||
|
||||
var updateStatsParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "DurationSeconds", durationSeconds },
|
||||
{ "LastUpdateTime", endTimeStr },
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var updateStatsResult = _dbContext.ExecuteNonQuery(updateStatsSql, updateStatsParams);
|
||||
if (!updateStatsResult.IsSuccess)
|
||||
{
|
||||
_logger.Error(updateStatsResult.Exception, "更新统计记录失败:{Message} | Failed to update statistics: {Message}", updateStatsResult.Message);
|
||||
// 流水已更新但统计失败,记录警告(下次 RecalculateTotalLifeSeconds 可恢复)
|
||||
_logger.Warn("统计表更新失败但流水记录已关闭,可通过重算恢复 | Statistics update failed but usage log closed, recoverable via recalculation");
|
||||
}
|
||||
|
||||
// 4. 清除内存起始时间 | Clear in-memory start time
|
||||
_filamentStartTime = null;
|
||||
|
||||
// 5. 查询更新后的 TotalLifeSeconds 用于日志 | Query updated TotalLifeSeconds for logging
|
||||
var totalLifeSeconds = 0.0;
|
||||
var totalSql = @"
|
||||
SELECT TotalLifeSeconds FROM RaySourceFilamentLifetimeStatistics
|
||||
WHERE SourceType = @SourceType AND SerialNumber = @SerialNumber";
|
||||
|
||||
var totalParams = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var (totalResult, totalData) = _dbContext.ExecuteScalar<double>(totalSql, totalParams);
|
||||
if (totalResult.IsSuccess)
|
||||
{
|
||||
totalLifeSeconds = totalData;
|
||||
}
|
||||
|
||||
_logger.Info("灯丝使用结束,SourceType={SourceType},SerialNumber={SerialNumber},DurationSeconds={DurationSeconds},TotalLifeSeconds={TotalLifeSeconds} | " +
|
||||
"Filament usage stopped, SourceType={SourceType}, SerialNumber={SerialNumber}, DurationSeconds={DurationSeconds}, TotalLifeSeconds={TotalLifeSeconds}",
|
||||
_sourceType, _serialNumber, durationSeconds, totalLifeSeconds);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "停止灯丝使用记录异常,SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"Exception stopping filament usage recording, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
_sourceType, _serialNumber);
|
||||
return false;
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 查询与计算 | Query and Calculation
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double GetTotalLifeSeconds()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
_logger.Warn("服务未初始化,返回默认值 0 | Service not initialized, returning default value 0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sql = @"
|
||||
SELECT COALESCE(TotalLifeSeconds, 0) FROM RaySourceFilamentLifetimeStatistics
|
||||
WHERE SourceType = @SourceType AND SerialNumber = @SerialNumber";
|
||||
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "SourceType", _sourceType },
|
||||
{ "SerialNumber", _serialNumber }
|
||||
};
|
||||
|
||||
var (result, totalSeconds) = _dbContext.ExecuteScalar<double>(sql, parameters);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_logger.Error(result.Exception, "查询累计使用秒数失败,{Message} | Failed to query total life seconds, {Message}", result.Message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return totalSeconds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "查询累计使用秒数异常,SourceType={SourceType},SerialNumber={SerialNumber} | " +
|
||||
"Exception querying total life seconds, SourceType={SourceType}, SerialNumber={SerialNumber}",
|
||||
_sourceType, _serialNumber);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double GetCurrentTotalLifeSeconds()
|
||||
{
|
||||
var dbSeconds = GetTotalLifeSeconds();
|
||||
|
||||
// 如果灯丝正在运行,加上本次运行时长 | If filament is on, add current session duration
|
||||
if (_filamentStartTime.HasValue)
|
||||
{
|
||||
var currentSessionSeconds = (DateTime.UtcNow - _filamentStartTime.Value).TotalSeconds;
|
||||
dbSeconds += currentSessionSeconds;
|
||||
}
|
||||
|
||||
return dbSeconds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double GetThresholdSeconds()
|
||||
{
|
||||
return _thresholdSeconds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double GetLifetimePercentage()
|
||||
{
|
||||
// 处理阈值为 0 的边界情况,避免除零 | Handle zero threshold to avoid division by zero
|
||||
if (_thresholdSeconds <= 0)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
var totalSeconds = GetCurrentTotalLifeSeconds();
|
||||
return Math.Min(totalSeconds / _thresholdSeconds * 100, 100);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ShouldShowLifetimeWarning()
|
||||
{
|
||||
return GetLifetimePercentage() >= 90;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
namespace XP.Hardware.RaySource.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 灯丝寿命管理服务接口 | Filament Lifetime Management Service Interface
|
||||
/// 负责灯丝使用时长的记录、计算、异常恢复和预警判断
|
||||
/// Responsible for filament usage duration recording, calculation, anomaly recovery, and warning evaluation
|
||||
/// </summary>
|
||||
public interface IFilamentLifetimeService
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否已成功初始化 | Whether the service has been successfully initialized
|
||||
/// </summary>
|
||||
bool IsInitialized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝是否处于开启状态 | Whether the filament is currently on
|
||||
/// </summary>
|
||||
bool IsFilamentOn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化服务(建表、异常恢复、重算累计寿命)
|
||||
/// Initialize service (create tables, recover anomalies, recalculate accumulated lifetime)
|
||||
/// </summary>
|
||||
/// <returns>是否初始化成功 | Whether initialization succeeded</returns>
|
||||
bool Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 开始灯丝使用记录 | Start filament usage recording
|
||||
/// </summary>
|
||||
/// <returns>是否成功 | Whether the operation succeeded</returns>
|
||||
bool StartFilamentUsage();
|
||||
|
||||
/// <summary>
|
||||
/// 停止灯丝使用记录 | Stop filament usage recording
|
||||
/// </summary>
|
||||
/// <returns>是否成功 | Whether the operation succeeded</returns>
|
||||
bool StopFilamentUsage();
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库中的累计使用秒数 | Get accumulated usage seconds from database
|
||||
/// </summary>
|
||||
/// <returns>累计使用秒数 | Accumulated usage seconds</returns>
|
||||
double GetTotalLifeSeconds();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前实时累计使用秒数(含本次运行时长)
|
||||
/// Get real-time total seconds (including current session duration)
|
||||
/// </summary>
|
||||
/// <returns>实时累计秒数 | Real-time accumulated seconds</returns>
|
||||
double GetCurrentTotalLifeSeconds();
|
||||
|
||||
/// <summary>
|
||||
/// 获取寿命阈值(秒)| Get lifetime threshold in seconds
|
||||
/// </summary>
|
||||
/// <returns>阈值秒数 | Threshold in seconds</returns>
|
||||
double GetThresholdSeconds();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前寿命使用百分比(0-100)
|
||||
/// Get current lifetime usage percentage (0-100)
|
||||
/// </summary>
|
||||
/// <returns>寿命百分比 | Lifetime percentage</returns>
|
||||
double GetLifetimePercentage();
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否需要弹出寿命预警(百分比 >= 90%)
|
||||
/// Check if lifetime warning should be shown (percentage >= 90%)
|
||||
/// </summary>
|
||||
/// <returns>是否需要预警 | Whether warning should be shown</returns>
|
||||
bool ShouldShowLifetimeWarning();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
|
||||
namespace XP.Hardware.RaySource.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源业务服务接口 | X-Ray Source Business Service Interface
|
||||
/// 封装初始化、操作调度、状态管理和业务规则校验 | Encapsulates initialization, operation dispatch, status management, and business rule validation
|
||||
/// </summary>
|
||||
public interface IRaySourceService : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前射线源实例 | Current X-ray source instance
|
||||
/// </summary>
|
||||
IXRaySource CurrentSource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化 | Is initialized
|
||||
/// </summary>
|
||||
bool IsInitialized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// PVI 变量是否已连接 | Whether PVI variables are connected
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否射线开启 | Is X-ray on
|
||||
/// </summary>
|
||||
bool IsXRayOn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前射线源状态 | Current X-ray source status
|
||||
/// </summary>
|
||||
RaySourceStatus CurrentStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化射线源(射线源类型从配置文件读取)| Initialize X-ray source (source type read from configuration)
|
||||
/// </summary>
|
||||
XRayResult Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 连接变量并启动实时状态通讯 | Connect variables and start real-time status communication
|
||||
/// </summary>
|
||||
XRayResult ConnectVariables();
|
||||
|
||||
/// <summary>
|
||||
/// 断开射线源连接 | Disconnect X-ray source
|
||||
/// </summary>
|
||||
XRayResult Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线 | Turn on X-ray
|
||||
/// </summary>
|
||||
XRayResult TurnOn();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线 | Turn off X-ray
|
||||
/// </summary>
|
||||
XRayResult TurnOff();
|
||||
|
||||
/// <summary>
|
||||
/// 紧急关闭(最高优先级)| Emergency shutdown (highest priority)
|
||||
/// </summary>
|
||||
XRayResult EmergencyShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// 设置电压 | Set voltage
|
||||
/// </summary>
|
||||
/// <param name="voltage">电压值(kV)| Voltage value (kV)</param>
|
||||
XRayResult SetVoltage(float voltage);
|
||||
|
||||
/// <summary>
|
||||
/// 设置电流 | Set current
|
||||
/// </summary>
|
||||
/// <param name="current">电流值(μA)| Current value (μA)</param>
|
||||
XRayResult SetCurrent(float current);
|
||||
|
||||
/// <summary>
|
||||
/// 读取实际电压 | Read actual voltage
|
||||
/// </summary>
|
||||
XRayResult ReadVoltage();
|
||||
|
||||
/// <summary>
|
||||
/// 读取实际电流 | Read actual current
|
||||
/// </summary>
|
||||
XRayResult ReadCurrent();
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统状态 | Read system status
|
||||
/// </summary>
|
||||
XRayResult ReadSystemStatus();
|
||||
|
||||
/// <summary>
|
||||
/// 检查错误 | Check errors
|
||||
/// </summary>
|
||||
XRayResult CheckErrors();
|
||||
|
||||
/// <summary>
|
||||
/// 清除错误 | Clear errors
|
||||
/// </summary>
|
||||
XRayResult ClearErrors();
|
||||
|
||||
/// <summary>
|
||||
/// TXI 开启 | TXI On
|
||||
/// </summary>
|
||||
XRayResult TxiOn();
|
||||
|
||||
/// <summary>
|
||||
/// TXI 关闭 | TXI Off
|
||||
/// </summary>
|
||||
XRayResult TxiOff();
|
||||
|
||||
/// <summary>
|
||||
/// 暖机设置 | Warm-up setting
|
||||
/// </summary>
|
||||
XRayResult WarmUp();
|
||||
|
||||
/// <summary>
|
||||
/// 训机设置 | Training setting
|
||||
/// </summary>
|
||||
XRayResult Training();
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准 | Filament calibration
|
||||
/// </summary>
|
||||
XRayResult FilamentCalibration();
|
||||
|
||||
/// <summary>
|
||||
/// 全部电压自动定心 | Auto-center all voltages
|
||||
/// </summary>
|
||||
XRayResult AutoCenter();
|
||||
|
||||
/// <summary>
|
||||
/// 设置功率模式 | Set power mode
|
||||
/// </summary>
|
||||
/// <param name="mode">功率模式值:1=Micro Focus,2=High Power</param>
|
||||
XRayResult SetPowerMode(int mode);
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行初始化 + 连接变量的完整流程 | Async full sequence: Initialize + ConnectVariables
|
||||
/// 供 ViewModel 在界面加载时调用,避免阻塞 UI 线程 | For ViewModel to call on view loaded without blocking UI
|
||||
/// </summary>
|
||||
/// <returns>最终操作结果 | Final operation result</returns>
|
||||
System.Threading.Tasks.Task<XRayResult> InitializeAndConnectAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
|
||||
namespace XP.Hardware.RaySource.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作类型枚举 | Operation type enum
|
||||
/// </summary>
|
||||
public enum OperationType
|
||||
{
|
||||
WarmUp,
|
||||
Training,
|
||||
FilamentCalibration,
|
||||
AutoCenter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作进度计算结果 | Operation progress calculation result
|
||||
/// </summary>
|
||||
public class ProgressResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 进度百分比(0-100)| Progress percentage (0-100)
|
||||
/// </summary>
|
||||
public int Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作是否已完成 | Whether operation is completed
|
||||
/// </summary>
|
||||
public bool IsCompleted { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作进度计算器 | Operation Progress Calculator
|
||||
/// 根据实时电压/电流/状态字符串估算暖机、训机、灯丝校准、自动定心的进度
|
||||
/// Estimates progress for warm-up, training, filament calibration, auto-center based on real-time voltage/current/status strings
|
||||
/// </summary>
|
||||
public class OperationProgressCalculator
|
||||
{
|
||||
private readonly OperationType _operationType;
|
||||
private readonly RaySourceConfig _config;
|
||||
private double _maxRatio;
|
||||
private int _trainingPhase = 1;
|
||||
|
||||
public OperationProgressCalculator(OperationType operationType, RaySourceConfig config)
|
||||
{
|
||||
_operationType = operationType;
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_maxRatio = 0;
|
||||
_trainingPhase = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据最新状态数据计算进度 | Calculate progress based on latest status data
|
||||
/// </summary>
|
||||
public ProgressResult Calculate(SystemStatusData data)
|
||||
{
|
||||
if (data == null) return new ProgressResult { Progress = (int)_maxRatio, IsCompleted = false };
|
||||
|
||||
return _operationType switch
|
||||
{
|
||||
OperationType.WarmUp => CalculateWarmUp(data),
|
||||
OperationType.Training => CalculateTraining(data),
|
||||
OperationType.FilamentCalibration => CalculateFilamentCalibration(data),
|
||||
OperationType.AutoCenter => CalculateAutoCenter(data),
|
||||
_ => new ProgressResult { Progress = 0, IsCompleted = false }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暖机进度计算 | Warm-up progress calculation
|
||||
/// 参照老项目:根据电压/电流阶段性变化估算,完成条件为 WarmUpStatus 包含"完成"
|
||||
/// </summary>
|
||||
private ProgressResult CalculateWarmUp(SystemStatusData data)
|
||||
{
|
||||
double ratio = 0;
|
||||
float voltage = data.ActualVoltage;
|
||||
float current = data.ActualCurrent;
|
||||
float maxVoltage = _config.MaxVoltage; // 225
|
||||
|
||||
if (current < 10 && voltage <= 50)
|
||||
{
|
||||
// 第一阶段:电压升到 50kV,占 25%
|
||||
ratio = voltage / 50.0 * 25.0;
|
||||
}
|
||||
else if (current >= 10 && current <= 100 && voltage >= 49)
|
||||
{
|
||||
// 第二阶段:电流升到 100μA,占 25%-75%
|
||||
ratio = current / 100.0 * 50.0 + 25.0;
|
||||
}
|
||||
else if (current >= 98 && voltage >= 55)
|
||||
{
|
||||
// 第三阶段:电压升到最大值,占 75%-100%
|
||||
ratio = voltage / maxVoltage * 100.0;
|
||||
}
|
||||
|
||||
if (ratio > _maxRatio) _maxRatio = ratio;
|
||||
|
||||
// 完成条件:code = "2"(暖机完成)且电压超过最大值的 90% | Complete: code "2" and voltage > 90% max
|
||||
bool isCompleted = data.WarmUpStatus == "2" && voltage > maxVoltage * 0.9f;
|
||||
|
||||
if (isCompleted) _maxRatio = 100;
|
||||
|
||||
return new ProgressResult
|
||||
{
|
||||
Progress = Math.Min((int)_maxRatio, 100),
|
||||
IsCompleted = isCompleted
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 训机进度计算 | Training progress calculation
|
||||
/// 参照老项目:分 6 个阶段,根据电压/电流和阶段号计算进度
|
||||
/// </summary>
|
||||
private ProgressResult CalculateTraining(SystemStatusData data)
|
||||
{
|
||||
double ratio = 0;
|
||||
float voltage = data.ActualVoltage;
|
||||
float current = data.ActualCurrent;
|
||||
float maxVoltage = _config.MaxVoltage;
|
||||
float maxCurrent = _config.MaxCurrent;
|
||||
|
||||
switch (_trainingPhase)
|
||||
{
|
||||
case 1:
|
||||
if (current < 10 && voltage <= 50)
|
||||
ratio = voltage / 50.0 * 20.0;
|
||||
if (ratio > 17) _trainingPhase = 2;
|
||||
break;
|
||||
case 2:
|
||||
if (current >= 10 && current <= 100 && voltage >= 45)
|
||||
ratio = current / 100.0 * 30.0;
|
||||
if (ratio > 27) _trainingPhase = 3;
|
||||
break;
|
||||
case 3:
|
||||
if (current >= 98 && voltage >= 60)
|
||||
ratio = voltage / maxVoltage * 40.0;
|
||||
if (ratio > 37) _trainingPhase = 4;
|
||||
break;
|
||||
case 4:
|
||||
if (current >= 98 && voltage >= 98)
|
||||
ratio = current / maxCurrent * 60.0;
|
||||
if (ratio > 57) _trainingPhase = 5;
|
||||
break;
|
||||
case 5:
|
||||
if (current >= 45 && voltage >= 30)
|
||||
ratio = voltage / maxVoltage * 80.0;
|
||||
if (ratio > 77) _trainingPhase = 6;
|
||||
break;
|
||||
case 6:
|
||||
if (current >= 45 && voltage >= 30)
|
||||
ratio = voltage / maxVoltage * 100.0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ratio > _maxRatio) _maxRatio = ratio;
|
||||
|
||||
// 完成条件:code = "2"(训机完成)且处于最后阶段 | Complete: code "2" and final phase
|
||||
bool isCompleted = data.StartUpStatus == "2" && _trainingPhase >= 6;
|
||||
|
||||
if (isCompleted) _maxRatio = 100;
|
||||
|
||||
return new ProgressResult
|
||||
{
|
||||
Progress = Math.Min((int)_maxRatio, 100),
|
||||
IsCompleted = isCompleted
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准进度计算 | Filament calibration progress calculation
|
||||
/// 参照老项目:电流 / 880 * 100,完成条件为状态包含"完成"且电流 > 800
|
||||
/// </summary>
|
||||
private ProgressResult CalculateFilamentCalibration(SystemStatusData data)
|
||||
{
|
||||
double ratio = data.ActualCurrent / 880.0 * 100.0;
|
||||
if (ratio > _maxRatio) _maxRatio = ratio;
|
||||
|
||||
// 完成条件:code = "2"(灯丝校准完成)且电流 > 800 | Complete: code "2" and current > 800
|
||||
bool isCompleted = data.FilamentAdjustStatus == "2" && data.ActualCurrent > 800;
|
||||
|
||||
if (isCompleted) _maxRatio = 100;
|
||||
|
||||
return new ProgressResult
|
||||
{
|
||||
Progress = Math.Min((int)_maxRatio, 100),
|
||||
IsCompleted = isCompleted
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动定心进度计算 | Auto-center progress calculation
|
||||
/// 参照老项目:电压 / 220 * 100,完成条件为状态包含"完成"且电压 > 200
|
||||
/// </summary>
|
||||
private ProgressResult CalculateAutoCenter(SystemStatusData data)
|
||||
{
|
||||
double ratio = data.ActualVoltage / 220.0 * 100.0;
|
||||
if (ratio > _maxRatio) _maxRatio = ratio;
|
||||
|
||||
// 完成条件:code = "2"(自动定心完成)且电压 > 200 | Complete: code "2" and voltage > 200
|
||||
bool isCompleted = data.AutoCenterStatus == "2" && data.ActualVoltage > 200;
|
||||
|
||||
if (isCompleted) _maxRatio = 100;
|
||||
|
||||
return new ProgressResult
|
||||
{
|
||||
Progress = Math.Min((int)_maxRatio, 100),
|
||||
IsCompleted = isCompleted
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,996 @@
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
|
||||
namespace XP.Hardware.RaySource.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源业务服务实现类 | X-Ray Source Business Service Implementation
|
||||
/// 单例模式,调度工厂+业务规则校验+事件驱动 | Singleton pattern, factory dispatch + business rule validation + event-driven
|
||||
/// </summary>
|
||||
public class RaySourceService : IRaySourceService
|
||||
{
|
||||
private readonly IRaySourceFactory _factory;
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly IFilamentLifetimeService _filamentLifetimeService;
|
||||
private IXRaySource _currentSource;
|
||||
private bool _isDisposed = false;
|
||||
private readonly object _lockObj = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 上一次灯丝记录对应的射线状态(用于去重,防止设备推送抖动)| Last filament recording status (for dedup, prevents device push jitter)
|
||||
/// </summary>
|
||||
private RaySourceStatus? _lastFilamentRecordedStatus;
|
||||
|
||||
/// <summary>
|
||||
/// 当前射线源实例 | Current X-ray source instance
|
||||
/// </summary>
|
||||
public IXRaySource CurrentSource => _currentSource;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化(且连接正常)| Is initialized (and connected)
|
||||
/// </summary>
|
||||
public bool IsInitialized => _isInitializedFlag && _currentSource != null;
|
||||
|
||||
private bool _isInitializedFlag = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否射线开启(由 CurrentStatus 派生,Opened 即为 true)| Is X-ray on (derived from CurrentStatus, true when Opened)
|
||||
/// </summary>
|
||||
public bool IsXRayOn => CurrentStatus == RaySourceStatus.Opened;
|
||||
|
||||
/// <summary>
|
||||
/// 射线源是否已建立完整连接(由设备层 RaySourceConnected 推送确认)| Whether RaySource has established full connection (confirmed by device RaySourceConnected push)
|
||||
/// </summary>
|
||||
public bool IsConnected => _currentSource?.IsConnected ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// 当前射线源状态 | Current X-ray source status
|
||||
/// </summary>
|
||||
public RaySourceStatus CurrentStatus { get; private set; } = RaySourceStatus.Unavailable;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public RaySourceService(
|
||||
IRaySourceFactory factory,
|
||||
RaySourceConfig config,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger,
|
||||
IFilamentLifetimeService filamentLifetimeService)
|
||||
{
|
||||
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule("RaySource.Service");
|
||||
_filamentLifetimeService = filamentLifetimeService ?? throw new ArgumentNullException(nameof(filamentLifetimeService));
|
||||
|
||||
_logger.Info("射线源服务已创建 | RaySource service created");
|
||||
|
||||
// 订阅射线源状态变更事件(处理状态同步和异常断联)| Subscribe to ray source status changed event
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Subscribe(OnRaySourceStatusChangedFromDevice);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化射线源(射线源类型从配置文件读取)| Initialize X-ray source (source type read from configuration)
|
||||
/// </summary>
|
||||
public XRayResult Initialize()
|
||||
{
|
||||
var sourceType = _config.SourceType;
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始初始化射线源,类型: {SourceType} | Starting X-ray source initialization, type: {SourceType}", sourceType);
|
||||
|
||||
if (IsInitialized)
|
||||
{
|
||||
_logger.Warn("射线源已初始化,请勿重复初始化 | X-ray source already initialized");
|
||||
return XRayResult.Error("射线源已初始化,请勿重复初始化");
|
||||
}
|
||||
|
||||
// 验证配置 | Validate configuration
|
||||
_logger.Debug("验证配置参数 | Validating configuration parameters");
|
||||
var (isValid, errorMessage) = _config.Validate();
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.Error(null, "配置验证失败: {ErrorMessage} | Configuration validation failed: {ErrorMessage}", errorMessage);
|
||||
return XRayResult.Error($"配置验证失败: {errorMessage}");
|
||||
}
|
||||
|
||||
// 复用已有实例或创建新实例 | Reuse existing instance or create new one
|
||||
if (_currentSource == null)
|
||||
{
|
||||
_logger.Debug("创建射线源实例: {SourceType} | Creating X-ray source instance: {SourceType}", sourceType);
|
||||
_currentSource = _factory.CreateRaySource(sourceType);
|
||||
if (_currentSource == null)
|
||||
{
|
||||
_logger.Error(null, "创建射线源实例失败: {SourceType} | Failed to create X-ray source instance: {SourceType}", sourceType);
|
||||
return XRayResult.Error($"创建射线源实例失败: {sourceType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("复用已有射线源实例 | Reusing existing X-ray source instance");
|
||||
}
|
||||
|
||||
// 初始化射线源 | Initialize X-ray source
|
||||
_logger.Info("调用射线源初始化方法 | Calling X-ray source initialization method");
|
||||
var result = _currentSource.Initialize();
|
||||
if (result.Success)
|
||||
{
|
||||
_isInitializedFlag = true;
|
||||
_logger.Info("射线源初始化成功: {SourceType} | X-ray source initialized successfully: {SourceType}", sourceType);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "射线源初始化失败: {ErrorMessage} | X-ray source initialization failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"初始化异常: {ex.Message}";
|
||||
_logger.Error(ex, "射线源初始化异常: {Message} | X-ray source initialization exception: {Message}", ex.Message);
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接 PVI 变量并启动实时状态通讯 | Connect PVI variables and start real-time status communication
|
||||
/// 必须在 Initialize 成功后、TurnOn 之前调用 | Must be called after Initialize succeeds and before TurnOn
|
||||
/// </summary>
|
||||
public XRayResult ConnectVariables()
|
||||
{
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法连接变量 | X-ray source not initialized, cannot connect variables");
|
||||
return XRayResult.Error("射线源未初始化,无法连接变量");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始连接 PVI 变量 | Starting PVI variable connection");
|
||||
var result = _currentSource.ConnectVariables();
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("PVI 变量连接命令已发送,等待设备层确认 | PVI variable connection command sent, waiting for device confirmation");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "PVI 变量连接失败: {ErrorMessage} | PVI variable connection failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"连接变量异常: {ex.Message}";
|
||||
_logger.Error(ex, "连接变量异常: {Message} | Connect variables exception: {Message}", ex.Message);
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线 | Turn on X-ray
|
||||
/// </summary>
|
||||
public XRayResult TurnOn()
|
||||
{
|
||||
// 业务规则校验:未初始化禁止操作 | Business rule: operation forbidden if not initialized
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法开启射线 | X-ray source not initialized, cannot turn on");
|
||||
return XRayResult.Error("射线源未初始化,无法开启射线");
|
||||
}
|
||||
|
||||
// 快速检查:已开启则直接返回,避免不必要的锁竞争 | Quick check: return early if already opened
|
||||
if (CurrentStatus == RaySourceStatus.Opened)
|
||||
{
|
||||
_logger.Warn("射线已处于开启状态,忽略重复请求 | X-ray already opened, ignoring duplicate request");
|
||||
return XRayResult.Ok("射线已开启");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取锁后再次检查状态,防止重复开启 | Double-check status after acquiring lock
|
||||
if (CurrentStatus == RaySourceStatus.Opened)
|
||||
{
|
||||
_logger.Warn("射线已处于开启状态,忽略重复请求 | X-ray already opened, ignoring duplicate request");
|
||||
return XRayResult.Ok("射线已开启");
|
||||
}
|
||||
|
||||
_logger.Info("开始开启射线 | Starting to turn on X-ray");
|
||||
var result = _currentSource.TurnOn();
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
CurrentStatus = RaySourceStatus.Opened;
|
||||
_logger.Info("射线开启成功,状态更新为 Opened | X-ray turned on successfully, status updated to Opened");
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(CurrentStatus);
|
||||
// 灯丝记录由 OnRaySourceStatusChangedFromDevice 统一处理,避免重复调用
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "射线开启失败: {ErrorMessage} | X-ray turn on failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "开启射线异常: {Message} | Exception turning on X-ray: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线 | Turn off X-ray
|
||||
/// </summary>
|
||||
public XRayResult TurnOff()
|
||||
{
|
||||
// 业务规则校验:未初始化禁止操作 | Business rule: operation forbidden if not initialized
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法关闭射线 | X-ray source not initialized, cannot turn off");
|
||||
return XRayResult.Error("射线源未初始化,无法关闭射线");
|
||||
}
|
||||
|
||||
// 快速检查:已关闭则直接返回,避免不必要的锁竞争 | Quick check: return early if already closed
|
||||
if (CurrentStatus == RaySourceStatus.Closed)
|
||||
{
|
||||
_logger.Warn("射线已处于关闭状态,忽略重复请求 | X-ray already closed, ignoring duplicate request");
|
||||
return XRayResult.Ok("射线已关闭");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取锁后再次检查状态,防止重复关闭 | Double-check status after acquiring lock
|
||||
if (CurrentStatus == RaySourceStatus.Closed)
|
||||
{
|
||||
_logger.Warn("射线已处于关闭状态,忽略重复请求 | X-ray already closed, ignoring duplicate request");
|
||||
return XRayResult.Ok("射线已关闭");
|
||||
}
|
||||
|
||||
_logger.Info("开始关闭射线 | Starting to turn off X-ray");
|
||||
var result = _currentSource.TurnOff();
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
CurrentStatus = RaySourceStatus.Closed;
|
||||
_logger.Info("射线关闭成功,状态更新为 Closed | X-ray turned off successfully, status updated to Closed");
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(CurrentStatus);
|
||||
// 灯丝记录由 OnRaySourceStatusChangedFromDevice 统一处理,避免重复调用
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "射线关闭失败: {ErrorMessage} | X-ray turn off failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "关闭射线异常: {Message} | Exception turning off X-ray: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开射线源连接(保留实例以便重连)| Disconnect X-ray source (retain instance for reconnection)
|
||||
/// </summary>
|
||||
public XRayResult Disconnect()
|
||||
{
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始断开射线源连接 | Starting X-ray source disconnection");
|
||||
|
||||
if (_currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源实例不存在,无需断开 | X-ray source instance does not exist, no need to disconnect");
|
||||
_isInitializedFlag = false;
|
||||
CurrentStatus = RaySourceStatus.Unavailable;
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(CurrentStatus);
|
||||
return XRayResult.Ok("射线源已断开");
|
||||
}
|
||||
|
||||
// 同步调用 CloseOff 进行优雅关闭 | Call CloseOff for graceful shutdown
|
||||
try
|
||||
{
|
||||
var closeResult = _currentSource.CloseOff();
|
||||
if (!closeResult.Success)
|
||||
{
|
||||
_logger.Warn("优雅关闭返回失败: {ErrorMessage} | Graceful shutdown returned failure: {ErrorMessage}", closeResult.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("优雅关闭异常: {Message} | Graceful shutdown exception: {Message}", ex.Message);
|
||||
}
|
||||
|
||||
// 不 Dispose 实例,保留 PVI Service 以便重连 | Don't dispose instance, retain PVI Service for reconnection
|
||||
_isInitializedFlag = false;
|
||||
CurrentStatus = RaySourceStatus.Unavailable;
|
||||
|
||||
// 断开连接时停止灯丝使用记录 | Stop filament usage recording on disconnect
|
||||
if (_filamentLifetimeService.IsInitialized && _filamentLifetimeService.IsFilamentOn)
|
||||
{
|
||||
_filamentLifetimeService.StopFilamentUsage();
|
||||
_lastFilamentRecordedStatus = RaySourceStatus.Unavailable;
|
||||
}
|
||||
|
||||
_logger.Info("射线源连接已断开,状态更新为 Unavailable | X-ray source disconnected, status updated to Unavailable");
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(CurrentStatus);
|
||||
return XRayResult.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"断开连接异常: {ex.Message}";
|
||||
_logger.Error(ex, "射线源断开连接异常: {Message} | X-ray source disconnection exception: {Message}", ex.Message);
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 紧急关闭(最高优先级,强制中断所有操作)| Emergency shutdown (highest priority, force interrupt all operations)
|
||||
/// </summary>
|
||||
public XRayResult EmergencyShutdown()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Warn("执行紧急关闭程序 | Executing emergency shutdown");
|
||||
|
||||
if (_currentSource != null)
|
||||
{
|
||||
// 先关闭射线 | Turn off X-ray first
|
||||
_logger.Info("紧急关闭:关闭射线 | Emergency shutdown: turning off X-ray");
|
||||
_currentSource.TurnOff();
|
||||
|
||||
// 完全关闭设备 | Fully shut down device
|
||||
_logger.Info("紧急关闭:完全关闭设备 | Emergency shutdown: fully shutting down device");
|
||||
_currentSource.CloseOff();
|
||||
}
|
||||
|
||||
_isInitializedFlag = false;
|
||||
CurrentStatus = RaySourceStatus.Unavailable;
|
||||
|
||||
// 紧急关闭时停止灯丝使用记录 | Stop filament usage recording on emergency shutdown
|
||||
if (_filamentLifetimeService.IsInitialized && _filamentLifetimeService.IsFilamentOn)
|
||||
{
|
||||
_filamentLifetimeService.StopFilamentUsage();
|
||||
_lastFilamentRecordedStatus = RaySourceStatus.Unavailable;
|
||||
}
|
||||
|
||||
_logger.Info("紧急关闭完成,状态更新为 Unavailable | Emergency shutdown completed, status updated to Unavailable");
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Publish(CurrentStatus);
|
||||
_eventAggregator.GetEvent<OperationResultEvent>()
|
||||
.Publish(new OperationResultData("紧急关闭", true, "紧急关闭完成"));
|
||||
|
||||
return XRayResult.Ok("紧急关闭完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"紧急关闭异常: {ex.Message}";
|
||||
_logger.Error(ex, "紧急关闭异常: {Message} | Emergency shutdown exception: {Message}", ex.Message);
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电压 | Set voltage
|
||||
/// </summary>
|
||||
public XRayResult SetVoltage(float voltage)
|
||||
{
|
||||
// 业务规则校验:未初始化禁止操作 | Business rule: operation forbidden if not initialized
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法设置电压 | X-ray source not initialized, cannot set voltage");
|
||||
return XRayResult.Error("射线源未初始化,无法设置电压");
|
||||
}
|
||||
|
||||
// 参数范围校验 | Parameter range validation
|
||||
if (voltage < _config.MinVoltage || voltage > _config.MaxVoltage)
|
||||
{
|
||||
_logger.Warn("电压参数超出范围: {Voltage}kV,有效范围: {Min}-{Max}kV | Voltage out of range: {Voltage}kV, valid range: {Min}-{Max}kV", voltage, _config.MinVoltage, _config.MaxVoltage);
|
||||
return XRayResult.Error($"电压参数超出范围:{_config.MinVoltage}-{_config.MaxVoltage}kV");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("设置电压: {Voltage}kV | Setting voltage: {Voltage}kV", voltage);
|
||||
var result = _currentSource.SetVoltage(voltage);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("电压设置成功: {Voltage}kV | Voltage set successfully: {Voltage}kV", voltage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "电压设置失败: {ErrorMessage} | Voltage setting failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "设置电压异常: {Message} | Exception setting voltage: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电流 | Set current
|
||||
/// </summary>
|
||||
public XRayResult SetCurrent(float current)
|
||||
{
|
||||
// 业务规则校验:未初始化禁止操作 | Business rule: operation forbidden if not initialized
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法设置电流 | X-ray source not initialized, cannot set current");
|
||||
return XRayResult.Error("射线源未初始化,无法设置电流");
|
||||
}
|
||||
|
||||
// 参数范围校验 | Parameter range validation
|
||||
if (current < _config.MinCurrent || current > _config.MaxCurrent)
|
||||
{
|
||||
_logger.Warn("电流参数超出范围: {Current}μA,有效范围: {Min}-{Max}μA | Current out of range: {Current}μA, valid range: {Min}-{Max}μA", current, _config.MinCurrent, _config.MaxCurrent);
|
||||
return XRayResult.Error($"电流参数超出范围:{_config.MinCurrent}-{_config.MaxCurrent}μA");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("设置电流: {Current}μA | Setting current: {Current}μA", current);
|
||||
var result = _currentSource.SetCurrent(current);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("电流设置成功: {Current}μA | Current set successfully: {Current}μA", current);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "电流设置失败: {ErrorMessage} | Current setting failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "设置电流异常: {Message} | Exception setting current: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取实际电压 | Read actual voltage
|
||||
/// </summary>
|
||||
public XRayResult ReadVoltage()
|
||||
{
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法读取电压 | X-ray source not initialized, cannot read voltage");
|
||||
return XRayResult.Error("射线源未初始化");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("读取实际电压 | Reading actual voltage");
|
||||
var result = _currentSource.ReadVoltage();
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Debug("电压读取成功: {Data}kV | Voltage read successfully: {Data}kV", result.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("电压读取失败: {ErrorMessage} | Voltage reading failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "读取电压异常: {Message} | Exception reading voltage: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取实际电流 | Read actual current
|
||||
/// </summary>
|
||||
public XRayResult ReadCurrent()
|
||||
{
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法读取电流 | X-ray source not initialized, cannot read current");
|
||||
return XRayResult.Error("射线源未初始化");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("读取实际电流 | Reading actual current");
|
||||
var result = _currentSource.ReadCurrent();
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Debug("电流读取成功: {Data}μA | Current read successfully: {Data}μA", result.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("电流读取失败: {ErrorMessage} | Current reading failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "读取电流异常: {Message} | Exception reading current: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统状态 | Read system status
|
||||
/// </summary>
|
||||
public XRayResult ReadSystemStatus()
|
||||
{
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法读取系统状态 | X-ray source not initialized, cannot read system status");
|
||||
return XRayResult.Error("射线源未初始化");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("读取系统状态 | Reading system status");
|
||||
return _currentSource.ReadSystemStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "读取系统状态异常: {Message} | Exception reading system status: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查错误 | Check errors
|
||||
/// </summary>
|
||||
public XRayResult CheckErrors()
|
||||
{
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法检查错误 | X-ray source not initialized, cannot check errors");
|
||||
return XRayResult.Error("射线源未初始化");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("检查错误状态 | Checking error status");
|
||||
return _currentSource.CheckErrors();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "检查错误异常: {Message} | Exception checking errors: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除错误 | Clear errors
|
||||
/// </summary>
|
||||
public XRayResult ClearErrors()
|
||||
{
|
||||
if (!IsInitialized || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未初始化,无法清除错误 | X-ray source not initialized, cannot clear errors");
|
||||
return XRayResult.Error("射线源未初始化");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Info("开始清除错误 | Starting to clear errors");
|
||||
|
||||
_eventAggregator.GetEvent<OperationResultEvent>()
|
||||
.Publish(new OperationResultData("清除错误", true));
|
||||
|
||||
_logger.Info("错误清除完成 | Errors cleared successfully");
|
||||
return XRayResult.Ok("错误已清除");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"清除错误失败: {ex.Message}";
|
||||
_logger.Error(ex, "清除错误异常: {Message} | Exception clearing errors: {Message}", ex.Message);
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(errorMsg);
|
||||
return XRayResult.Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 开启 | TXI On
|
||||
/// </summary>
|
||||
public XRayResult TxiOn()
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法执行 TXI ON | X-ray source not connected, cannot execute TXI ON");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始执行 TXI ON | Starting TXI ON");
|
||||
var result = _currentSource.TxiOn();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("TXI ON 执行成功 | TXI ON executed successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "TXI ON 执行失败: {ErrorMessage} | TXI ON failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "TXI ON 异常: {Message} | TXI ON exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"TXI ON 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 关闭 | TXI Off
|
||||
/// </summary>
|
||||
public XRayResult TxiOff()
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法执行 TXI OFF | X-ray source not connected, cannot execute TXI OFF");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始执行 TXI OFF | Starting TXI OFF");
|
||||
var result = _currentSource.TxiOff();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("TXI OFF 执行成功 | TXI OFF executed successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "TXI OFF 执行失败: {ErrorMessage} | TXI OFF failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "TXI OFF 异常: {Message} | TXI OFF exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"TXI OFF 异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暖机设置 | Warm-up setting
|
||||
/// </summary>
|
||||
public XRayResult WarmUp()
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法执行暖机 | X-ray source not connected, cannot execute warm-up");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始执行暖机设置 | Starting warm-up setting");
|
||||
var result = _currentSource.WarmUp();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("暖机设置已发送 | Warm-up setting sent successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "暖机设置失败: {ErrorMessage} | Warm-up setting failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "暖机设置异常: {Message} | Warm-up setting exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"暖机设置异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 训机设置 | Training setting
|
||||
/// </summary>
|
||||
public XRayResult Training()
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法执行训机 | X-ray source not connected, cannot execute training");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始执行训机设置 | Starting training setting");
|
||||
var result = _currentSource.Training();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("训机设置已发送 | Training setting sent successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "训机设置失败: {ErrorMessage} | Training setting failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "训机设置异常: {Message} | Training setting exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"训机设置异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准 | Filament calibration
|
||||
/// </summary>
|
||||
public XRayResult FilamentCalibration()
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法执行灯丝校准 | X-ray source not connected, cannot execute filament calibration");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始执行灯丝校准 | Starting filament calibration");
|
||||
var result = _currentSource.FilamentCalibration();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("灯丝校准已发送 | Filament calibration sent successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "灯丝校准失败: {ErrorMessage} | Filament calibration failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "灯丝校准异常: {Message} | Filament calibration exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"灯丝校准异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全部电压自动定心 | Auto-center all voltages
|
||||
/// </summary>
|
||||
public XRayResult AutoCenter()
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法执行自动定心 | X-ray source not connected, cannot execute auto-center");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始执行全部电压自动定心 | Starting auto-center all voltages");
|
||||
var result = _currentSource.AutoCenter();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("自动定心已发送 | Auto-center sent successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "自动定心失败: {ErrorMessage} | Auto-center failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "自动定心异常: {Message} | Auto-center exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"自动定心异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置功率模式 | Set power mode
|
||||
/// </summary>
|
||||
/// <param name="mode">功率模式值:1=Micro Focus,2=High Power</param>
|
||||
public XRayResult SetPowerMode(int mode)
|
||||
{
|
||||
if (!IsConnected || _currentSource == null)
|
||||
{
|
||||
_logger.Warn("射线源未连接,无法设置功率模式 | X-ray source not connected, cannot set power mode");
|
||||
return XRayResult.Error("射线源未连接");
|
||||
}
|
||||
|
||||
lock (_lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始设置功率模式: Mode={Mode} | Starting to set power mode: Mode={Mode}", mode);
|
||||
var result = _currentSource.SetPowerMode(mode);
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("功率模式设置成功: Mode={Mode} | Power mode set successfully: Mode={Mode}", mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(null, "功率模式设置失败: {ErrorMessage} | Power mode setting failed: {ErrorMessage}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "功率模式设置异常: {Message} | Power mode setting exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"功率模式设置异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行初始化 + 连接变量的完整流程 | Async full sequence: Initialize + ConnectVariables
|
||||
/// </summary>
|
||||
public async Task<XRayResult> InitializeAndConnectAsync()
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 步骤1:初始化 | Step 1: Initialize
|
||||
if (!IsInitialized)
|
||||
{
|
||||
_logger.Info("自动初始化:执行 Initialize | Auto-init: executing Initialize");
|
||||
var initResult = Initialize();
|
||||
if (!initResult.Success)
|
||||
{
|
||||
_logger.Warn("自动初始化失败: {Error} | Auto-initialization failed: {Error}", initResult.ErrorMessage);
|
||||
return initResult;
|
||||
}
|
||||
_logger.Info("自动初始化成功 | Auto-initialization succeeded");
|
||||
}
|
||||
|
||||
// 步骤2:连接变量 | Step 2: Connect Variables
|
||||
if (!IsConnected)
|
||||
{
|
||||
_logger.Info("自动连接变量:执行 ConnectVariables | Auto-init: executing ConnectVariables");
|
||||
var connectResult = ConnectVariables();
|
||||
if (!connectResult.Success)
|
||||
{
|
||||
_logger.Warn("自动连接变量失败: {Error} | Auto connect variables failed: {Error}", connectResult.ErrorMessage);
|
||||
return connectResult;
|
||||
}
|
||||
_logger.Info("自动连接变量成功 | Auto connect variables succeeded");
|
||||
}
|
||||
|
||||
_logger.Info("自动初始化流程完成 | Auto-initialization sequence completed");
|
||||
return XRayResult.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "自动初始化流程异常: {Message} | Auto-initialization sequence exception: {Message}", ex.Message);
|
||||
return XRayResult.Error($"自动初始化异常: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 射线源状态变更事件处理(设备层回调)| Ray source status changed event handler (device callback)
|
||||
/// </summary>
|
||||
private void OnRaySourceStatusChangedFromDevice(RaySourceStatus newStatus)
|
||||
{
|
||||
// 更新当前状态(IsXRayOn 由 CurrentStatus 自动派生)| Update current status (IsXRayOn is auto-derived from CurrentStatus)
|
||||
CurrentStatus = newStatus;
|
||||
_logger.Info("服务层状态更新: {Status} | Service layer status updated: {Status}", newStatus);
|
||||
|
||||
// 根据射线源状态变更记录灯丝使用(带去重,防止设备推送抖动)| Record filament usage with dedup
|
||||
if (_filamentLifetimeService.IsInitialized && newStatus != _lastFilamentRecordedStatus)
|
||||
{
|
||||
if (newStatus == RaySourceStatus.Opened && !_filamentLifetimeService.IsFilamentOn)
|
||||
{
|
||||
if (_filamentLifetimeService.StartFilamentUsage())
|
||||
_lastFilamentRecordedStatus = newStatus;
|
||||
}
|
||||
else if (newStatus != RaySourceStatus.Opened && _filamentLifetimeService.IsFilamentOn)
|
||||
{
|
||||
if (_filamentLifetimeService.StopFilamentUsage())
|
||||
_lastFilamentRecordedStatus = newStatus;
|
||||
}
|
||||
}
|
||||
|
||||
if (newStatus == RaySourceStatus.Unavailable && _isInitializedFlag)
|
||||
{
|
||||
// 异常断联处理 | Handle unexpected disconnection
|
||||
_logger.Error(null, "服务层检测到连接丢失(状态变为 Unavailable)| Service layer detected connection lost (status changed to Unavailable)");
|
||||
_isInitializedFlag = false;
|
||||
|
||||
// 通知 UI 更新状态 | Notify UI to update status
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Publish(new SystemStatusData
|
||||
{
|
||||
IsXRayOn = false,
|
||||
ActualVoltage = 0,
|
||||
ActualCurrent = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 先执行紧急关闭 | Execute emergency shutdown first
|
||||
EmergencyShutdown();
|
||||
|
||||
// 释放射线源实例 | Dispose X-ray source instance
|
||||
_currentSource?.Dispose();
|
||||
_currentSource = null;
|
||||
|
||||
_isDisposed = true;
|
||||
_logger.Info("服务层资源已释放 | Service layer resources disposed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("服务层资源释放异常: {Message} | Service layer resource disposal exception: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using XP.Common.Localization.Interfaces;
|
||||
|
||||
namespace XP.Hardware.RaySource.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备状态码多语言映射工具 | Device status code localization mapper
|
||||
/// 将 Comet.Host 传递的原始 code 码映射为多语言显示文本
|
||||
/// </summary>
|
||||
public static class StatusCodeMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// 暖机状态码映射 | Warm-up status code mapping
|
||||
/// </summary>
|
||||
public static string MapWarmUpStatus(string code, ILocalizationService loc)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"0" => loc.GetString("RaySource_Status_WarmUp_0") ?? "初始状态",
|
||||
"1" => loc.GetString("RaySource_Status_WarmUp_1") ?? "暖机中",
|
||||
"2" => loc.GetString("RaySource_Status_WarmUp_2") ?? "暖机完成",
|
||||
"3" => loc.GetString("RaySource_Status_WarmUp_3") ?? "暖机失败",
|
||||
"33" => loc.GetString("RaySource_Status_WarmUp_33") ?? "需要暖机",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 真空状态码映射 | Vacuum status code mapping
|
||||
/// </summary>
|
||||
public static string MapVacuumStatus(string code, ILocalizationService loc)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"0" => loc.GetString("RaySource_Status_Vacuum_0") ?? "初始状态",
|
||||
"1" => loc.GetString("RaySource_Status_Vacuum_1") ?? "抽真空中",
|
||||
"2" => loc.GetString("RaySource_Status_Vacuum_2") ?? "真空准备好",
|
||||
"22" => loc.GetString("RaySource_Status_Vacuum_22") ?? "真空值<1e-5mbar",
|
||||
"30" => loc.GetString("RaySource_Status_Vacuum_30") ?? "真空泵未使能",
|
||||
"40" => loc.GetString("RaySource_Status_Vacuum_40") ?? "真空泵通风",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 训机状态码映射 | Training status code mapping
|
||||
/// </summary>
|
||||
public static string MapStartUpStatus(string code, ILocalizationService loc)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"0" => loc.GetString("RaySource_Status_StartUp_0") ?? "初始状态",
|
||||
"1" => loc.GetString("RaySource_Status_StartUp_1") ?? "训机中",
|
||||
"2" => loc.GetString("RaySource_Status_StartUp_2") ?? "训机完成",
|
||||
"3" => loc.GetString("RaySource_Status_StartUp_3") ?? "训机失败",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动定心状态码映射 | Auto-center status code mapping
|
||||
/// </summary>
|
||||
public static string MapAutoCenterStatus(string code, ILocalizationService loc)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"0" => loc.GetString("RaySource_Status_AutoCenter_0") ?? "初始状态",
|
||||
"1" => loc.GetString("RaySource_Status_AutoCenter_1") ?? "自动定心中",
|
||||
"2" => loc.GetString("RaySource_Status_AutoCenter_2") ?? "自动定心完成",
|
||||
"3" => loc.GetString("RaySource_Status_AutoCenter_3") ?? "定心失败",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝调整状态码映射 | Filament adjust status code mapping
|
||||
/// </summary>
|
||||
public static string MapFilamentAdjustStatus(string code, ILocalizationService loc)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"0" => loc.GetString("RaySource_Status_Filament_0") ?? "初始状态",
|
||||
"1" => loc.GetString("RaySource_Status_Filament_1") ?? "灯丝校准中",
|
||||
"2" => loc.GetString("RaySource_Status_Filament_2") ?? "灯丝校准完成",
|
||||
"3" => loc.GetString("RaySource_Status_Filament_3") ?? "灯丝校准失败",
|
||||
"33" => loc.GetString("RaySource_Status_Filament_33") ?? "需要灯丝校准",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 功率模式码映射 | Power mode code mapping
|
||||
/// </summary>
|
||||
public static string MapPowerMode(string code, ILocalizationService loc)
|
||||
{
|
||||
var trimmed = (code ?? "").Trim();
|
||||
if (int.TryParse(trimmed, out var intVal))
|
||||
{
|
||||
return intVal switch
|
||||
{
|
||||
1 => loc.GetString("RaySource_Status_PowerMode_1") ?? "Micro Focus",
|
||||
2 => loc.GetString("RaySource_Status_PowerMode_2") ?? "High Power",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
if (string.IsNullOrEmpty(trimmed))
|
||||
return "--";
|
||||
return loc.GetString("RaySource_Status_Unknown") ?? "未知状态";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 状态码映射 | TXI status code mapping
|
||||
/// </summary>
|
||||
public static string MapTxiStatus(string code, ILocalizationService loc)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
"True" => loc.GetString("RaySource_Status_TXI_On") ?? "TXI 开启",
|
||||
"False" => loc.GetString("RaySource_Status_TXI_Off") ?? "TXI 关闭",
|
||||
_ => loc.GetString("RaySource_Status_Unknown") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,919 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using System.Threading;
|
||||
using XP.Common.GeneralForm.Views;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
|
||||
namespace XP.Hardware.RaySource.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源配置视图模型 | X-Ray Source Config ViewModel
|
||||
/// 负责射线源初始化、连接、断开及设备状态监控 | Responsible for X-ray source init, connect, disconnect and device status monitoring
|
||||
/// </summary>
|
||||
public class RaySourceConfigViewModel : BindableBase, IDisposable
|
||||
{
|
||||
#region 字段 | Fields
|
||||
|
||||
private readonly IRaySourceService _raySourceService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IFilamentLifetimeService _filamentLifetimeService;
|
||||
private bool _isOperating = false;
|
||||
private bool _isDisposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝寿命定时刷新定时器(60 秒间隔)| Filament lifetime refresh timer (60-second interval)
|
||||
/// </summary>
|
||||
private DispatcherTimer _lifetimeRefreshTimer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 连接属性 | Connection Properties
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化(代理到服务层)| Whether initialized (delegated to service layer)
|
||||
/// </summary>
|
||||
public bool IsInitialized => _raySourceService.IsInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 通知 IsInitialized 变更并刷新相关命令状态 | Notify IsInitialized changed and refresh related command states
|
||||
/// </summary>
|
||||
private void NotifyInitializedChanged()
|
||||
{
|
||||
RaisePropertyChanged(nameof(IsInitialized));
|
||||
InitializeCommand?.RaiseCanExecuteChanged();
|
||||
ConnectVariablesCommand?.RaiseCanExecuteChanged();
|
||||
DisconnectCommand?.RaiseCanExecuteChanged();
|
||||
RaisePropertyChanged(nameof(ConnectionStatusText));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 射线源是否已建立完整连接(代理到服务层)| Whether fully connected (delegated to service layer)
|
||||
/// </summary>
|
||||
public bool IsConnected => _raySourceService.IsConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 通知 IsConnected 变更并刷新相关命令状态 | Notify IsConnected changed and refresh related command states
|
||||
/// </summary>
|
||||
private void NotifyConnectedChanged()
|
||||
{
|
||||
RaisePropertyChanged(nameof(IsConnected));
|
||||
ConnectVariablesCommand?.RaiseCanExecuteChanged();
|
||||
DisconnectCommand?.RaiseCanExecuteChanged();
|
||||
RaisePropertyChanged(nameof(ConnectionStatusText));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接状态文本 | Connection status text
|
||||
/// </summary>
|
||||
public string ConnectionStatusText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsConnected)
|
||||
return _localizationService.GetString("RaySource_VariablesConnected") ?? "变量已连接";
|
||||
if (IsInitialized)
|
||||
return _localizationService.GetString("RaySource_Connected") ?? "已连接";
|
||||
return _localizationService.GetString("RaySource_Disconnected") ?? "未连接";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 射线源类型显示 | Ray source type display
|
||||
/// </summary>
|
||||
public string SourceTypeText => _config.SourceType;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备状态属性 | Device Status Properties
|
||||
|
||||
private string _warmUpStatus = "--";
|
||||
/// <summary>
|
||||
/// 暖机状态 | Warm-up status
|
||||
/// </summary>
|
||||
public string WarmUpStatus
|
||||
{
|
||||
get => _warmUpStatus;
|
||||
private set => SetProperty(ref _warmUpStatus, value);
|
||||
}
|
||||
|
||||
private string _vacuumStatus = "--";
|
||||
/// <summary>
|
||||
/// 真空状态 | Vacuum status
|
||||
/// </summary>
|
||||
public string VacuumStatus
|
||||
{
|
||||
get => _vacuumStatus;
|
||||
private set => SetProperty(ref _vacuumStatus, value);
|
||||
}
|
||||
|
||||
private string _startUpStatus = "--";
|
||||
/// <summary>
|
||||
/// 启动状态 | Startup status
|
||||
/// </summary>
|
||||
public string StartUpStatus
|
||||
{
|
||||
get => _startUpStatus;
|
||||
private set => SetProperty(ref _startUpStatus, value);
|
||||
}
|
||||
|
||||
private string _autoCenterStatus = "--";
|
||||
/// <summary>
|
||||
/// 自动定心状态 | Auto-center status
|
||||
/// </summary>
|
||||
public string AutoCenterStatus
|
||||
{
|
||||
get => _autoCenterStatus;
|
||||
private set => SetProperty(ref _autoCenterStatus, value);
|
||||
}
|
||||
|
||||
private string _filamentAdjustStatus = "--";
|
||||
/// <summary>
|
||||
/// 灯丝校准状态 | Filament adjust status
|
||||
/// </summary>
|
||||
public string FilamentAdjustStatus
|
||||
{
|
||||
get => _filamentAdjustStatus;
|
||||
private set => SetProperty(ref _filamentAdjustStatus, value);
|
||||
}
|
||||
|
||||
private string _powerMode = "--";
|
||||
/// <summary>
|
||||
/// 功率模式 | Power mode
|
||||
/// </summary>
|
||||
public string PowerMode
|
||||
{
|
||||
get => _powerMode;
|
||||
private set => SetProperty(ref _powerMode, value);
|
||||
}
|
||||
|
||||
private bool _isInterlockActive;
|
||||
/// <summary>
|
||||
/// 连锁是否激活 | Whether interlock is active
|
||||
/// </summary>
|
||||
public bool IsInterlockActive
|
||||
{
|
||||
get => _isInterlockActive;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isInterlockActive, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(InterlockStatusText));
|
||||
RefreshCommandStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连锁状态文本 | Interlock status text
|
||||
/// </summary>
|
||||
public string InterlockStatusText => IsInterlockActive
|
||||
? (_localizationService.GetString("RaySource_InterlockActive") ?? "激活")
|
||||
: (_localizationService.GetString("RaySource_InterlockNormal") ?? "未激活");
|
||||
|
||||
private string _watchdogStatus = "--";
|
||||
/// <summary>
|
||||
/// 看门狗状态 | Watchdog status
|
||||
/// </summary>
|
||||
public string WatchdogStatus
|
||||
{
|
||||
get => _watchdogStatus;
|
||||
private set => SetProperty(ref _watchdogStatus, value);
|
||||
}
|
||||
|
||||
private bool _isXRayOn;
|
||||
/// <summary>
|
||||
/// 射线开启状态 | X-ray on status
|
||||
/// </summary>
|
||||
public bool IsXRayOn
|
||||
{
|
||||
get => _isXRayOn;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isXRayOn, value))
|
||||
RaisePropertyChanged(nameof(XRayOnStatusText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 射线开启状态文本 | X-ray on status text
|
||||
/// </summary>
|
||||
public string XRayOnStatusText => IsXRayOn
|
||||
? (_localizationService.GetString("RaySource_XRayOn") ?? "开启")
|
||||
: (_localizationService.GetString("RaySource_XRayOff") ?? "关闭");
|
||||
|
||||
private string _txiStatus = "--";
|
||||
/// <summary>
|
||||
/// TXI状态 | TXI status
|
||||
/// </summary>
|
||||
public string TxiStatus
|
||||
{
|
||||
get => _txiStatus;
|
||||
private set => SetProperty(ref _txiStatus, value);
|
||||
}
|
||||
|
||||
private double _filamentLifetime;
|
||||
/// <summary>
|
||||
/// 灯丝寿命百分比(0-100)| Filament lifetime percentage (0-100)
|
||||
/// </summary>
|
||||
public double FilamentLifetime
|
||||
{
|
||||
get => _filamentLifetime;
|
||||
private set => SetProperty(ref _filamentLifetime, value);
|
||||
}
|
||||
|
||||
private string _filamentLifetimeText = "";
|
||||
/// <summary>
|
||||
/// 灯丝寿命显示文本(如 "123.4h / 1000.0h (12.3%)")
|
||||
/// Filament lifetime display text (e.g. "123.4h / 1000.0h (12.3%)")
|
||||
/// </summary>
|
||||
public string FilamentLifetimeText
|
||||
{
|
||||
get => _filamentLifetimeText;
|
||||
private set => SetProperty(ref _filamentLifetimeText, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令 | Commands
|
||||
|
||||
public DelegateCommand InitializeCommand { get; }
|
||||
public DelegateCommand ConnectVariablesCommand { get; }
|
||||
public DelegateCommand DisconnectCommand { get; }
|
||||
public DelegateCommand TxiOnCommand { get; }
|
||||
public DelegateCommand TxiOffCommand { get; }
|
||||
public DelegateCommand HighPowerCommand { get; }
|
||||
public DelegateCommand MicroFocusCommand { get; }
|
||||
public DelegateCommand WarmUpSettingCommand { get; }
|
||||
public DelegateCommand TrainingSettingCommand { get; }
|
||||
public DelegateCommand FilamentCalibrationCommand { get; }
|
||||
public DelegateCommand AutoCenterSettingCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数 | Constructor
|
||||
|
||||
public RaySourceConfigViewModel(
|
||||
IRaySourceService raySourceService,
|
||||
IEventAggregator eventAggregator,
|
||||
RaySourceConfig config,
|
||||
ILoggerService logger,
|
||||
ILocalizationService localizationService,
|
||||
IFilamentLifetimeService filamentLifetimeService)
|
||||
{
|
||||
_raySourceService = raySourceService ?? throw new ArgumentNullException(nameof(raySourceService));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule("RaySource.ConfigVM");
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
_filamentLifetimeService = filamentLifetimeService ?? throw new ArgumentNullException(nameof(filamentLifetimeService));
|
||||
|
||||
InitializeCommand = new DelegateCommand(ExecuteInitialize, CanExecuteInitialize);
|
||||
ConnectVariablesCommand = new DelegateCommand(ExecuteConnectVariables, CanExecuteConnectVariables);
|
||||
DisconnectCommand = new DelegateCommand(ExecuteDisconnect, CanExecuteDisconnect);
|
||||
TxiOnCommand = new DelegateCommand(ExecuteTxiOn, CanExecuteDeviceCommand);
|
||||
TxiOffCommand = new DelegateCommand(ExecuteTxiOff, CanExecuteDeviceCommand);
|
||||
HighPowerCommand = new DelegateCommand(ExecuteHighPower, CanExecuteDeviceCommand);
|
||||
MicroFocusCommand = new DelegateCommand(ExecuteMicroFocus, CanExecuteDeviceCommand);
|
||||
WarmUpSettingCommand = new DelegateCommand(ExecuteWarmUpSetting, CanExecuteDeviceCommand);
|
||||
TrainingSettingCommand = new DelegateCommand(ExecuteTrainingSetting, CanExecuteDeviceCommand);
|
||||
FilamentCalibrationCommand = new DelegateCommand(ExecuteFilamentCalibration, CanExecuteDeviceCommand);
|
||||
AutoCenterSettingCommand = new DelegateCommand(ExecuteAutoCenterSetting, CanExecuteDeviceCommand);
|
||||
|
||||
// 订阅事件 | Subscribe events
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Subscribe(OnRaySourceStatusChanged, ThreadOption.UIThread);
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Subscribe(OnStatusUpdated, ThreadOption.UIThread);
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Subscribe(OnVariablesConnectedChanged, ThreadOption.UIThread);
|
||||
|
||||
// 同步当前状态 | Sync current state
|
||||
NotifyInitializedChanged();
|
||||
NotifyConnectedChanged();
|
||||
|
||||
// 初始刷新灯丝寿命数据 | Initial refresh of filament lifetime data
|
||||
RefreshFilamentLifetime();
|
||||
|
||||
// 启动灯丝寿命定时刷新(60 秒间隔)| Start filament lifetime periodic refresh (60-second interval)
|
||||
StartLifetimeRefreshTimer();
|
||||
|
||||
_logger.Info("射线源配置视图模型已创建 | X-ray source config view model created");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件处理 | Event Handlers
|
||||
|
||||
private void OnRaySourceStatusChanged(RaySourceStatus newStatus)
|
||||
{
|
||||
NotifyInitializedChanged();
|
||||
|
||||
if (newStatus == RaySourceStatus.Unavailable && IsConnected)
|
||||
{
|
||||
NotifyConnectedChanged();
|
||||
ResetDeviceStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态更新事件处理:刷新设备状态面板 | Status updated event handler: refresh device status panel
|
||||
/// </summary>
|
||||
private void OnStatusUpdated(SystemStatusData data)
|
||||
{
|
||||
if (data == null) return;
|
||||
|
||||
// 使用 StatusCodeMapper 将原始 code 映射为多语言文本 | Map raw codes to localized text
|
||||
WarmUpStatus = StatusCodeMapper.MapWarmUpStatus(data.WarmUpStatus, _localizationService);
|
||||
VacuumStatus = StatusCodeMapper.MapVacuumStatus(data.VacuumStatus, _localizationService);
|
||||
StartUpStatus = StatusCodeMapper.MapStartUpStatus(data.StartUpStatus, _localizationService);
|
||||
AutoCenterStatus = StatusCodeMapper.MapAutoCenterStatus(data.AutoCenterStatus, _localizationService);
|
||||
FilamentAdjustStatus = StatusCodeMapper.MapFilamentAdjustStatus(data.FilamentAdjustStatus, _localizationService);
|
||||
PowerMode = StatusCodeMapper.MapPowerMode(data.PowerMode, _localizationService);
|
||||
IsInterlockActive = data.IsInterlockActive;
|
||||
WatchdogStatus = data.WatchdogStatus ?? "--";
|
||||
IsXRayOn = data.IsXRayOn;
|
||||
TxiStatus = StatusCodeMapper.MapTxiStatus(data.TxiStatus, _localizationService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置设备状态为默认值 | Reset device status to defaults
|
||||
/// </summary>
|
||||
private void ResetDeviceStatus()
|
||||
{
|
||||
WarmUpStatus = "--";
|
||||
VacuumStatus = "--";
|
||||
StartUpStatus = "--";
|
||||
AutoCenterStatus = "--";
|
||||
FilamentAdjustStatus = "--";
|
||||
PowerMode = "--";
|
||||
IsInterlockActive = false;
|
||||
WatchdogStatus = "--";
|
||||
IsXRayOn = false;
|
||||
TxiStatus = "--";
|
||||
FilamentLifetime = 0;
|
||||
FilamentLifetimeText = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量连接状态变更事件处理 | Variables connected state changed event handler
|
||||
/// </summary>
|
||||
private void OnVariablesConnectedChanged(bool isConnected)
|
||||
{
|
||||
NotifyConnectedChanged();
|
||||
// 同步初始化状态(自动初始化后服务状态可能已变)| Sync init state (service state may have changed after auto-init)
|
||||
NotifyInitializedChanged();
|
||||
_logger.Info("ConfigVM 收到变量连接状态变更: {IsConnected} | ConfigVM received variables connection state change: {IsConnected}", isConnected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令执行逻辑 | Command Execution Logic
|
||||
|
||||
/// <summary>
|
||||
/// 初始化命令执行逻辑 | Initialize command execution logic
|
||||
/// </summary>
|
||||
private async void ExecuteInitialize()
|
||||
{
|
||||
_isOperating = true;
|
||||
RefreshCommandStates();
|
||||
try
|
||||
{
|
||||
_logger.Info("用户请求初始化射线源 | User requested to initialize X-ray source");
|
||||
var result = await Task.Run(() => _raySourceService.Initialize());
|
||||
if (result.Success)
|
||||
{
|
||||
NotifyInitializedChanged();
|
||||
_logger.Info("射线源初始化成功 | X-ray source initialized successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("射线源初始化失败: {Message} | X-ray source initialization failed: {Message}", result.ErrorMessage);
|
||||
MessageBox.Show($"初始化失败: {result.ErrorMessage}", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "初始化射线源异常: {Message} | Exception initializing X-ray source: {Message}", ex.Message);
|
||||
MessageBox.Show($"初始化异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isOperating = false;
|
||||
RefreshCommandStates();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化命令是否可执行 | Can execute initialize command
|
||||
/// </summary>
|
||||
private bool CanExecuteInitialize() => !_isOperating && !IsInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 连接变量命令执行逻辑 | Connect variables command execution logic
|
||||
/// </summary>
|
||||
private async void ExecuteConnectVariables()
|
||||
{
|
||||
_isOperating = true;
|
||||
RefreshCommandStates();
|
||||
try
|
||||
{
|
||||
_logger.Info("用户请求连接 PVI 变量 | User requested to connect PVI variables");
|
||||
var result = await Task.Run(() => _raySourceService.ConnectVariables());
|
||||
if (result.Success)
|
||||
{
|
||||
// 不在此处设置 IsConnected,等待设备层 RaySourceConnected 推送通过 VariablesConnectedEvent 事件确认
|
||||
// IsConnected will be set by VariablesConnectedEvent from device layer RaySourceConnected push
|
||||
_logger.Info("PVI 变量连接命令已发送,等待设备确认 | PVI variable connection command sent, waiting for device confirmation");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("PVI 变量连接失败: {Message} | PVI variables connection failed: {Message}", result.ErrorMessage);
|
||||
MessageBox.Show($"连接变量失败: {result.ErrorMessage}", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "连接变量异常: {Message} | Exception connecting variables: {Message}", ex.Message);
|
||||
MessageBox.Show($"连接变量异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isOperating = false;
|
||||
RefreshCommandStates();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接变量命令是否可执行 | Can execute connect variables command
|
||||
/// </summary>
|
||||
private bool CanExecuteConnectVariables() => !_isOperating && IsInitialized && !IsConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 断开命令执行逻辑 | Disconnect command execution logic
|
||||
/// </summary>
|
||||
private async void ExecuteDisconnect()
|
||||
{
|
||||
_isOperating = true;
|
||||
RefreshCommandStates();
|
||||
try
|
||||
{
|
||||
_logger.Info("用户请求断开射线源 | User requested to disconnect X-ray source");
|
||||
var result = await Task.Run(() => _raySourceService.Disconnect());
|
||||
if (result.Success)
|
||||
{
|
||||
NotifyConnectedChanged();
|
||||
NotifyInitializedChanged();
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Publish(false);
|
||||
ResetDeviceStatus();
|
||||
_logger.Info("射线源已断开 | X-ray source disconnected");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("断开射线源失败: {Error} | Disconnect X-ray source failed: {Error}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage,
|
||||
_localizationService.GetString("Message_OperationFailed") ?? "操作失败",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "断开射线源异常: {Message} | Exception disconnecting X-ray source: {Message}", ex.Message);
|
||||
MessageBox.Show($"断开异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isOperating = false;
|
||||
RefreshCommandStates();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开命令是否可执行 | Can execute disconnect command
|
||||
/// </summary>
|
||||
private bool CanExecuteDisconnect() => !_isOperating && IsInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新所有命令的可执行状态 | Refresh all command CanExecute states
|
||||
/// </summary>
|
||||
private void RefreshCommandStates()
|
||||
{
|
||||
InitializeCommand?.RaiseCanExecuteChanged();
|
||||
ConnectVariablesCommand?.RaiseCanExecuteChanged();
|
||||
DisconnectCommand?.RaiseCanExecuteChanged();
|
||||
TxiOnCommand?.RaiseCanExecuteChanged();
|
||||
TxiOffCommand?.RaiseCanExecuteChanged();
|
||||
HighPowerCommand?.RaiseCanExecuteChanged();
|
||||
MicroFocusCommand?.RaiseCanExecuteChanged();
|
||||
WarmUpSettingCommand?.RaiseCanExecuteChanged();
|
||||
TrainingSettingCommand?.RaiseCanExecuteChanged();
|
||||
FilamentCalibrationCommand?.RaiseCanExecuteChanged();
|
||||
AutoCenterSettingCommand?.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备命令是否可执行(需已连接变量)| Can execute device command (requires variables connected)
|
||||
/// </summary>
|
||||
private bool CanExecuteDeviceCommand() => !_isOperating && IsConnected && IsInitialized && IsInterlockActive;
|
||||
|
||||
/// <summary>
|
||||
/// TXI 开启 | TXI On
|
||||
/// </summary>
|
||||
private void ExecuteTxiOn()
|
||||
{
|
||||
_logger.Info("用户请求 TXI ON | User requested TXI ON");
|
||||
try
|
||||
{
|
||||
var result = _raySourceService.TxiOn();
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Error(null, "TXI ON 失败: {ErrorMessage}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage,
|
||||
_localizationService.GetString("RaySource_OperationFailed") ?? "操作失败",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "TXI ON 异常: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TXI 关闭 | TXI Off
|
||||
/// </summary>
|
||||
private void ExecuteTxiOff()
|
||||
{
|
||||
_logger.Info("用户请求 TXI OFF | User requested TXI OFF");
|
||||
try
|
||||
{
|
||||
var result = _raySourceService.TxiOff();
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Error(null, "TXI OFF 失败: {ErrorMessage}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage,
|
||||
_localizationService.GetString("RaySource_OperationFailed") ?? "操作失败",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "TXI OFF 异常: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到高功率模式 | Switch to High Power mode
|
||||
/// </summary>
|
||||
private void ExecuteHighPower()
|
||||
{
|
||||
_logger.Info("用户请求切换到 High Power 模式 | User requested switch to High Power mode");
|
||||
try
|
||||
{
|
||||
var result = _raySourceService.SetPowerMode(2);
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Error(null, "切换 High Power 模式失败: {ErrorMessage}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage,
|
||||
_localizationService.GetString("RaySource_OperationFailed") ?? "操作失败",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "切换 High Power 模式异常: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到微焦点模式 | Switch to Micro Focus mode
|
||||
/// </summary>
|
||||
private void ExecuteMicroFocus()
|
||||
{
|
||||
_logger.Info("用户请求切换到 Micro Focus 模式 | User requested switch to Micro Focus mode");
|
||||
try
|
||||
{
|
||||
var result = _raySourceService.SetPowerMode(1);
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Error(null, "切换 Micro Focus 模式失败: {ErrorMessage}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage,
|
||||
_localizationService.GetString("RaySource_OperationFailed") ?? "操作失败",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "切换 Micro Focus 模式异常: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暖机设置 | Warm-up setting
|
||||
/// 参照旧代码:确认对话框 → 显示进度条 → 发送暖机命令
|
||||
/// </summary>
|
||||
private async void ExecuteWarmUpSetting()
|
||||
{
|
||||
_logger.Info("用户打开暖机设置 | User opened warm-up setting");
|
||||
|
||||
var confirmResult = MessageBox.Show(
|
||||
_localizationService.GetString("RaySource_WarmUp_Confirm") ?? "是否确认执行暖机操作?",
|
||||
_localizationService.GetString("RaySource_Confirm_Title") ?? "确认",
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
|
||||
if (confirmResult != MessageBoxResult.Yes)
|
||||
{
|
||||
_logger.Info("用户取消暖机操作 | User cancelled warm-up operation");
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteLongRunningOperation(
|
||||
_localizationService.GetString("RaySource_WarmUp_Title") ?? "暖机设置",
|
||||
_localizationService.GetString("RaySource_WarmUp_Message") ?? "正在执行暖机操作,请稍候...",
|
||||
() => _raySourceService.WarmUp(),
|
||||
OperationType.WarmUp,
|
||||
_config.WarmUpTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 训机设置 | Training setting
|
||||
/// 参照旧代码:确认对话框 → 显示进度条 → 发送训机命令
|
||||
/// </summary>
|
||||
private async void ExecuteTrainingSetting()
|
||||
{
|
||||
_logger.Info("用户打开训机设置 | User opened training setting");
|
||||
|
||||
var confirmResult = MessageBox.Show(
|
||||
_localizationService.GetString("RaySource_Training_Confirm") ?? "是否确认执行训机操作?",
|
||||
_localizationService.GetString("RaySource_Confirm_Title") ?? "确认",
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
|
||||
if (confirmResult != MessageBoxResult.Yes)
|
||||
{
|
||||
_logger.Info("用户取消训机操作 | User cancelled training operation");
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteLongRunningOperation(
|
||||
_localizationService.GetString("RaySource_Training_Title") ?? "训机设置",
|
||||
_localizationService.GetString("RaySource_Training_Message") ?? "正在执行训机操作,请稍候...",
|
||||
() => _raySourceService.Training(),
|
||||
OperationType.Training,
|
||||
_config.StartUpTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 灯丝校准 | Filament calibration
|
||||
/// 参照旧代码:确认对话框 → 显示进度条 → 发送灯丝校准命令
|
||||
/// </summary>
|
||||
private async void ExecuteFilamentCalibration()
|
||||
{
|
||||
_logger.Info("用户请求灯丝校准 | User requested filament calibration");
|
||||
|
||||
var confirmResult = MessageBox.Show(
|
||||
_localizationService.GetString("RaySource_FilamentCalibration_Confirm") ?? "是否确认执行灯丝校准操作?",
|
||||
_localizationService.GetString("RaySource_Confirm_Title") ?? "确认",
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
|
||||
if (confirmResult != MessageBoxResult.Yes)
|
||||
{
|
||||
_logger.Info("用户取消灯丝校准操作 | User cancelled filament calibration operation");
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteLongRunningOperation(
|
||||
_localizationService.GetString("RaySource_FilamentCalibration_Title") ?? "灯丝校准",
|
||||
_localizationService.GetString("RaySource_FilamentCalibration_Message") ?? "正在执行灯丝校准操作,请稍候...",
|
||||
() => _raySourceService.FilamentCalibration(),
|
||||
OperationType.FilamentCalibration,
|
||||
_config.FilamentAdjustTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动定心设置 | Auto-center setting
|
||||
/// 参照旧代码:确认对话框 → 显示进度条 → 发送全部电压自动定心命令
|
||||
/// </summary>
|
||||
private async void ExecuteAutoCenterSetting()
|
||||
{
|
||||
_logger.Info("用户请求自动定心 | User requested auto-center");
|
||||
|
||||
var confirmResult = MessageBox.Show(
|
||||
_localizationService.GetString("RaySource_AutoCenter_Confirm") ?? "是否确认执行全部电压自动定心操作?",
|
||||
_localizationService.GetString("RaySource_Confirm_Title") ?? "确认",
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
|
||||
if (confirmResult != MessageBoxResult.Yes)
|
||||
{
|
||||
_logger.Info("用户取消自动定心操作 | User cancelled auto-center operation");
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteLongRunningOperation(
|
||||
_localizationService.GetString("RaySource_AutoCenter_Title") ?? "自动定心",
|
||||
_localizationService.GetString("RaySource_AutoCenter_Message") ?? "正在执行全部电压自动定心操作,请稍候...",
|
||||
() => _raySourceService.AutoCenter(),
|
||||
OperationType.AutoCenter,
|
||||
_config.AutoCenterTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行长时间运行操作的通用方法(显示进度条窗口,实时跟踪设备进度)
|
||||
/// 发送指令后订阅 StatusUpdatedEvent,根据实时电压/电流/状态字符串计算进度
|
||||
/// </summary>
|
||||
/// <param name="title">进度窗口标题</param>
|
||||
/// <param name="message">进度窗口提示信息</param>
|
||||
/// <param name="operation">要执行的操作(发送指令)</param>
|
||||
/// <param name="operationType">操作类型(用于选择进度计算策略)</param>
|
||||
/// <param name="timeoutMs">超时时间(毫秒)</param>
|
||||
private async Task ExecuteLongRunningOperation(
|
||||
string title, string message, Func<XRayResult> operation,
|
||||
OperationType operationType, int timeoutMs)
|
||||
{
|
||||
var progressWindow = new ProgressWindow(
|
||||
title: title,
|
||||
message: message,
|
||||
isCancelable: false,
|
||||
logger: _logger);
|
||||
|
||||
var calculator = new OperationProgressCalculator(operationType, _config);
|
||||
var completionSource = new TaskCompletionSource<bool>();
|
||||
var cts = new CancellationTokenSource(timeoutMs);
|
||||
|
||||
// 状态更新事件处理:计算进度并更新窗口 | Status update handler: calculate progress and update window
|
||||
void OnProgressStatusUpdated(SystemStatusData data)
|
||||
{
|
||||
if (data == null) return;
|
||||
var result = calculator.Calculate(data);
|
||||
progressWindow.UpdateProgress(message, result.Progress);
|
||||
|
||||
if (result.IsCompleted && !completionSource.Task.IsCompleted)
|
||||
{
|
||||
completionSource.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isOperating = true;
|
||||
RefreshCommandStates();
|
||||
|
||||
progressWindow.Show();
|
||||
progressWindow.UpdateProgress(message, 0);
|
||||
|
||||
// 订阅状态更新事件以跟踪进度 | Subscribe to status update event for progress tracking
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Subscribe(OnProgressStatusUpdated, ThreadOption.UIThread);
|
||||
|
||||
// 在后台线程发送操作指令 | Send operation command on background thread
|
||||
var cmdResult = await Task.Run(() => operation());
|
||||
|
||||
if (!cmdResult.Success)
|
||||
{
|
||||
_logger.Error(null, "{Title} 操作失败: {ErrorMessage}", title, cmdResult.ErrorMessage);
|
||||
MessageBox.Show(cmdResult.ErrorMessage,
|
||||
_localizationService.GetString("RaySource_OperationFailed") ?? "操作失败",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info("{Title} 操作指令已发送,等待设备完成 | {Title} command sent, waiting for device completion", title);
|
||||
|
||||
// 等待完成或超时 | Wait for completion or timeout
|
||||
using (cts.Token.Register(() => completionSource.TrySetResult(false)))
|
||||
{
|
||||
var completed = await completionSource.Task;
|
||||
|
||||
if (completed)
|
||||
{
|
||||
progressWindow.UpdateProgress(
|
||||
_localizationService.GetString("RaySource_Operation_Completed") ?? "操作已完成", 100);
|
||||
_logger.Info("{Title} 操作已完成 | {Title} operation completed", title);
|
||||
await Task.Delay(1500); // 短暂停留让用户看到完成状态 | Brief pause for user to see completion
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{Title} 操作超时({Timeout}ms)| {Title} operation timed out ({Timeout}ms)", title, timeoutMs);
|
||||
MessageBox.Show(
|
||||
_localizationService.GetString("RaySource_Operation_Timeout") ?? "操作超时,请检查设备状态。",
|
||||
_localizationService.GetString("RaySource_Warning") ?? "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "{Title} 操作异常: {Message}", title, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 取消订阅状态更新事件 | Unsubscribe from status update event
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Unsubscribe(OnProgressStatusUpdated);
|
||||
cts.Dispose();
|
||||
_isOperating = false;
|
||||
RefreshCommandStates();
|
||||
progressWindow.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从灯丝寿命服务获取最新数据并刷新 UI 属性(纯展示,不含计算逻辑)
|
||||
/// Refresh filament lifetime UI properties from service (display only, no calculation logic)
|
||||
/// </summary>
|
||||
private void RefreshFilamentLifetime()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_filamentLifetimeService.IsInitialized)
|
||||
return;
|
||||
|
||||
// 从服务获取数据 | Get data from service
|
||||
var percentage = _filamentLifetimeService.GetLifetimePercentage();
|
||||
var totalSeconds = _filamentLifetimeService.GetCurrentTotalLifeSeconds();
|
||||
var thresholdSeconds = _filamentLifetimeService.GetThresholdSeconds();
|
||||
|
||||
// 转换为小时(保留一位小数)| Convert to hours (one decimal place)
|
||||
var totalHours = totalSeconds / 3600.0;
|
||||
var thresholdHours = thresholdSeconds / 3600.0;
|
||||
|
||||
// 更新 UI 属性 | Update UI properties
|
||||
FilamentLifetime = percentage;
|
||||
FilamentLifetimeText = $"{totalHours:F1}h / {thresholdHours:F1}h ({percentage:F1}%)";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "刷新灯丝寿命数据失败 | Failed to refresh filament lifetime data: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动灯丝寿命定时刷新定时器 | Start filament lifetime periodic refresh timer
|
||||
/// </summary>
|
||||
private void StartLifetimeRefreshTimer()
|
||||
{
|
||||
_lifetimeRefreshTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(60)
|
||||
};
|
||||
_lifetimeRefreshTimer.Tick += OnLifetimeRefreshTimerTick;
|
||||
_lifetimeRefreshTimer.Start();
|
||||
_logger.Debug("灯丝寿命定时刷新已启动(间隔 60 秒)| Filament lifetime refresh timer started (60s interval)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止灯丝寿命定时刷新定时器 | Stop filament lifetime periodic refresh timer
|
||||
/// </summary>
|
||||
private void StopLifetimeRefreshTimer()
|
||||
{
|
||||
if (_lifetimeRefreshTimer != null)
|
||||
{
|
||||
_lifetimeRefreshTimer.Stop();
|
||||
_lifetimeRefreshTimer.Tick -= OnLifetimeRefreshTimerTick;
|
||||
_lifetimeRefreshTimer = null;
|
||||
_logger.Debug("灯丝寿命定时刷新已停止 | Filament lifetime refresh timer stopped");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时器 Tick 事件处理:刷新灯丝寿命数据 | Timer tick handler: refresh filament lifetime data
|
||||
/// </summary>
|
||||
private void OnLifetimeRefreshTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
RefreshFilamentLifetime();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable 实现 | IDisposable Implementation
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源:停止定时器、取消事件订阅 | Dispose resources: stop timer, unsubscribe events
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
// 停止灯丝寿命定时刷新 | Stop filament lifetime refresh timer
|
||||
StopLifetimeRefreshTimer();
|
||||
|
||||
// 取消事件订阅 | Unsubscribe events
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Unsubscribe(OnRaySourceStatusChanged);
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Unsubscribe(OnStatusUpdated);
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Unsubscribe(OnVariablesConnectedChanged);
|
||||
|
||||
_isDisposed = true;
|
||||
_logger.Info("射线源配置视图模型已释放 | X-ray source config view model disposed");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,771 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using XP.Common.Helpers;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.RaySource.Abstractions;
|
||||
using XP.Hardware.RaySource.Abstractions.Enums;
|
||||
using XP.Hardware.RaySource.Abstractions.Events;
|
||||
using XP.Hardware.RaySource.Config;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
|
||||
namespace XP.Hardware.RaySource.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源操作视图模型 | X-Ray Source Operate ViewModel
|
||||
/// 负责射线源开关控制、电压电流调节、状态监控 | Responsible for X-ray on/off control, voltage/current adjustment, status monitoring
|
||||
/// </summary>
|
||||
public class RaySourceOperateViewModel : BindableBase, IDisposable
|
||||
{
|
||||
#region 字段 | Fields
|
||||
|
||||
private readonly IRaySourceService _raySourceService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly RaySourceConfig _config;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private bool _isDisposed = false;
|
||||
private bool _isOperating = false;
|
||||
private bool _isVariablesConnected = false;
|
||||
private bool _autoInitialized = false;
|
||||
private Views.RaySourceConfigWindow _configWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 标记当前是否正在从设备反馈更新值(防止 code-behind 误触发写入)
|
||||
/// Flag indicating whether values are being updated from device feedback (prevents code-behind from triggering writes)
|
||||
/// </summary>
|
||||
public bool IsUpdatingFromDevice { get; private set; } = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 状态属性 | Status Properties
|
||||
|
||||
private RaySourceStatus _raySourceStatus = RaySourceStatus.Unavailable;
|
||||
/// <summary>
|
||||
/// 射线源状态(不可用/关闭/开启)| X-ray source status (Unavailable/Closed/Opened)
|
||||
/// </summary>
|
||||
public RaySourceStatus RaySourceStatus
|
||||
{
|
||||
get => _raySourceStatus;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _raySourceStatus, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(StatusText));
|
||||
RaisePropertyChanged(nameof(IsSlidersEnabled));
|
||||
TurnOnCommand?.RaiseCanExecuteChanged();
|
||||
TurnOffCommand?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态文本(绑定到腰圆内的文字)| Status text (bound to capsule text)
|
||||
/// </summary>
|
||||
public string StatusText
|
||||
{
|
||||
get
|
||||
{
|
||||
return RaySourceStatus switch
|
||||
{
|
||||
RaySourceStatus.Unavailable => $"{_localizationService.GetString("RaySource_StatusUnavailable")}",
|
||||
RaySourceStatus.Closed => _localizationService.GetString("RaySource_StatusClosed"),
|
||||
RaySourceStatus.Opened => _localizationService.GetString("RaySource_StatusOpened"),
|
||||
_ => _localizationService.GetString("RaySource_StatusUnavailable")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 滑块是否启用(仅服务已初始化时可调节)| Sliders enabled (only when service is initialized)
|
||||
/// </summary>
|
||||
public bool IsSlidersEnabled => _raySourceService.IsInitialized && _isVariablesConnected;
|
||||
|
||||
/// <summary>
|
||||
/// PVI 变量是否已连接 | Whether PVI variables are connected
|
||||
/// </summary>
|
||||
public bool IsVariablesConnected
|
||||
{
|
||||
get => _isVariablesConnected;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isVariablesConnected, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(IsSlidersEnabled));
|
||||
TurnOnCommand?.RaiseCanExecuteChanged();
|
||||
TurnOffCommand?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isInterlockActive;
|
||||
/// <summary>
|
||||
/// 连锁是否激活 | Whether interlock is active
|
||||
/// </summary>
|
||||
public bool IsInterlockActive
|
||||
{
|
||||
get => _isInterlockActive;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isInterlockActive, value))
|
||||
{
|
||||
TurnOnCommand?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 电压属性 | Voltage Properties
|
||||
|
||||
private double _voltageMin;
|
||||
/// <summary>
|
||||
/// 电压最小值(kV)| Voltage minimum (kV)
|
||||
/// </summary>
|
||||
public double VoltageMin
|
||||
{
|
||||
get => _voltageMin;
|
||||
set => SetProperty(ref _voltageMin, value);
|
||||
}
|
||||
|
||||
private double _voltageMax;
|
||||
/// <summary>
|
||||
/// 电压最大值(kV)| Voltage maximum (kV)
|
||||
/// </summary>
|
||||
public double VoltageMax
|
||||
{
|
||||
get => _voltageMax;
|
||||
set => SetProperty(ref _voltageMax, value);
|
||||
}
|
||||
|
||||
private double _voltageValue;
|
||||
/// <summary>
|
||||
/// 电压滑块值(kV),仅更新UI显示,不自动写入硬件 | Voltage slider value (kV), UI display only, no auto write to hardware
|
||||
/// </summary>
|
||||
public double VoltageValue
|
||||
{
|
||||
get => _voltageValue;
|
||||
set => SetProperty(ref _voltageValue, Math.Round(value, 0));
|
||||
}
|
||||
|
||||
private double _voltageActual;
|
||||
/// <summary>
|
||||
/// 电压实际值(kV)| Voltage actual value (kV)
|
||||
/// </summary>
|
||||
public double VoltageActual
|
||||
{
|
||||
get => _voltageActual;
|
||||
set => SetProperty(ref _voltageActual, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电压实际值显示文本 | Voltage actual value display text
|
||||
/// </summary>
|
||||
public string VoltageActualText => string.Format(_localizationService.GetString("RaySource_ActualValueLabel"), VoltageActual.ToString("F1"));
|
||||
|
||||
#endregion
|
||||
|
||||
#region 电流属性 | Current Properties
|
||||
|
||||
private double _currentMin;
|
||||
/// <summary>
|
||||
/// 电流最小值(μA)| Current minimum (μA)
|
||||
/// </summary>
|
||||
public double CurrentMin
|
||||
{
|
||||
get => _currentMin;
|
||||
set => SetProperty(ref _currentMin, value);
|
||||
}
|
||||
|
||||
private double _currentMax;
|
||||
/// <summary>
|
||||
/// 电流最大值(μA)| Current maximum (μA)
|
||||
/// </summary>
|
||||
public double CurrentMax
|
||||
{
|
||||
get => _currentMax;
|
||||
set => SetProperty(ref _currentMax, value);
|
||||
}
|
||||
|
||||
private double _currentValue;
|
||||
/// <summary>
|
||||
/// 电流滑块值(μA),仅更新UI显示,不自动写入硬件 | Current slider value (μA), UI display only, no auto write to hardware
|
||||
/// </summary>
|
||||
public double CurrentValue
|
||||
{
|
||||
get => _currentValue;
|
||||
set => SetProperty(ref _currentValue, Math.Round(value, 0));
|
||||
}
|
||||
|
||||
private double _currentActual;
|
||||
/// <summary>
|
||||
/// 电流实际值(μA)| Current actual value (μA)
|
||||
/// </summary>
|
||||
public double CurrentActual
|
||||
{
|
||||
get => _currentActual;
|
||||
set => SetProperty(ref _currentActual, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电流实际值显示文本 | Current actual value display text
|
||||
/// </summary>
|
||||
public string CurrentActualText => string.Format(_localizationService.GetString("RaySource_ActualValueLabel"), CurrentActual.ToString("F1"));
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令 | Commands
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线源命令 | Turn on X-ray command
|
||||
/// </summary>
|
||||
public DelegateCommand TurnOnCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线源命令 | Turn off X-ray command
|
||||
/// </summary>
|
||||
public DelegateCommand TurnOffCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 提交电压值命令(滑块松手/输入框失焦时触发)| Apply voltage command (triggered on slider drag complete / input lost focus)
|
||||
/// </summary>
|
||||
public DelegateCommand ApplyVoltageCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 提交电流值命令(滑块松手/输入框失焦时触发)| Apply current command (triggered on slider drag complete / input lost focus)
|
||||
/// </summary>
|
||||
public DelegateCommand ApplyCurrentCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置命令(预留接口)| Settings command (reserved interface)
|
||||
/// </summary>
|
||||
public DelegateCommand SettingsCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置命令(打开射线源配置窗口)| Config command (open ray source config window)
|
||||
/// </summary>
|
||||
public DelegateCommand ConfigCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数 | Constructor
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public RaySourceOperateViewModel(
|
||||
IRaySourceService raySourceService,
|
||||
IEventAggregator eventAggregator,
|
||||
RaySourceConfig config,
|
||||
ILoggerService logger,
|
||||
ILocalizationService localizationService)
|
||||
{
|
||||
_raySourceService = raySourceService ?? throw new ArgumentNullException(nameof(raySourceService));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule("RaySource.ViewModel");
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
|
||||
_logger.Info("射线源操作视图模型已创建 | X-ray source operate view model created");
|
||||
|
||||
// 初始化极值 | Initialize min/max values
|
||||
VoltageMin = _config.MinVoltage;
|
||||
VoltageMax = _config.MaxVoltage;
|
||||
CurrentMin = _config.MinCurrent;
|
||||
CurrentMax = _config.MaxCurrent;
|
||||
|
||||
// 初始化滑块默认值 | Initialize slider default values
|
||||
VoltageValue = _config.MinVoltage; //中间值:(_config.MinVoltage + _config.MaxVoltage) / 2;
|
||||
CurrentValue = _config.MinCurrent; //中间值:(_config.MinCurrent + _config.MaxCurrent) / 2;
|
||||
|
||||
// 初始化命令 | Initialize commands
|
||||
TurnOnCommand = new DelegateCommand(ExecuteTurnOn, CanExecuteTurnOn);
|
||||
TurnOffCommand = new DelegateCommand(ExecuteTurnOff, CanExecuteTurnOff);
|
||||
ApplyVoltageCommand = new DelegateCommand(ApplyVoltage);
|
||||
ApplyCurrentCommand = new DelegateCommand(ApplyCurrent);
|
||||
SettingsCommand = new DelegateCommand(ExecuteSettings, CanExecuteSettings);
|
||||
ConfigCommand = new DelegateCommand(ExecuteConfig);
|
||||
|
||||
// 订阅事件 | Subscribe to events
|
||||
SubscribeEvents();
|
||||
|
||||
// 初始化状态 | Initialize status
|
||||
UpdateStatusFromService();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件订阅 | Event Subscription
|
||||
|
||||
/// <summary>
|
||||
/// 订阅事件 | Subscribe to events
|
||||
/// </summary>
|
||||
private void SubscribeEvents()
|
||||
{
|
||||
// 订阅射线源状态变更事件(三态)| Subscribe to ray source status changed event (tri-state)
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Subscribe(OnRaySourceStatusChanged, ThreadOption.UIThread);
|
||||
|
||||
// 订阅状态更新事件 | Subscribe to status updated event
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Subscribe(OnStatusUpdated, ThreadOption.UIThread);
|
||||
|
||||
// 订阅错误事件 | Subscribe to error event
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Subscribe(OnErrorOccurred, ThreadOption.UIThread);
|
||||
|
||||
// 订阅操作结果事件 | Subscribe to operation result event
|
||||
_eventAggregator.GetEvent<OperationResultEvent>().Subscribe(OnOperationResult, ThreadOption.UIThread);
|
||||
|
||||
// 订阅变量连接状态变更事件 | Subscribe to variables connected event
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Subscribe(OnVariablesConnectedChanged, ThreadOption.UIThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 射线源状态变更事件处理(三态)| Ray source status changed event handler (tri-state)
|
||||
/// </summary>
|
||||
private void OnRaySourceStatusChanged(RaySourceStatus newStatus)
|
||||
{
|
||||
var previousStatus = RaySourceStatus;
|
||||
RaySourceStatus = newStatus;
|
||||
RaisePropertyChanged(nameof(IsSlidersEnabled));
|
||||
|
||||
// 连接恢复处理:从不可用变为已连接,主动读取最新状态刷新UI | Connection restored: from unavailable to connected, actively read latest status to refresh UI
|
||||
if (previousStatus == RaySourceStatus.Unavailable &&
|
||||
(newStatus == RaySourceStatus.Opened || newStatus == RaySourceStatus.Closed))
|
||||
{
|
||||
_logger.Info("射线源连接已建立 | Ray Source Connected");
|
||||
}
|
||||
|
||||
// 异常断联处理:从已连接状态变为不可用 | Handle unexpected disconnection: from connected to unavailable
|
||||
if (newStatus == RaySourceStatus.Unavailable &&
|
||||
(previousStatus == RaySourceStatus.Opened || previousStatus == RaySourceStatus.Closed))
|
||||
{
|
||||
// 断联时重置变量连接状态 | Reset variable connection state on disconnection
|
||||
IsVariablesConnected = false;
|
||||
_logger.Warn("ViewModel 检测到连接丢失(状态变为 Unavailable)| ViewModel detected connection lost (status changed to Unavailable)");
|
||||
//MessageBox.Show("射线源连接丢失,请检查设备连接后重新初始化。", "连接丢失", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态更新事件处理 | Status updated event handler
|
||||
/// 仅在实际值真正变化时更新滑块/输入框,避免无意义赋值触发事件链
|
||||
/// Only update slider/input when actual value truly changed, avoiding unnecessary assignment triggering event chain
|
||||
/// </summary>
|
||||
private void OnStatusUpdated(SystemStatusData statusData)
|
||||
{
|
||||
if (statusData == null) return;
|
||||
|
||||
IsUpdatingFromDevice = true;
|
||||
try
|
||||
{
|
||||
// 电压实际值变化时,仅更新实际值标签显示 | When actual voltage changes, only update actual value label
|
||||
if (Math.Abs(statusData.ActualVoltage - VoltageActual) > 0.01)
|
||||
{
|
||||
VoltageActual = statusData.ActualVoltage;
|
||||
RaisePropertyChanged(nameof(VoltageActualText));
|
||||
}
|
||||
|
||||
// 电流实际值变化时,仅更新实际值标签显示 | When actual current changes, only update actual value label
|
||||
if (Math.Abs(statusData.ActualCurrent - CurrentActual) > 0.01)
|
||||
{
|
||||
CurrentActual = statusData.ActualCurrent;
|
||||
RaisePropertyChanged(nameof(CurrentActualText));
|
||||
}
|
||||
|
||||
// 设定电压反馈同步到滑块(仅设备反馈时更新,人工操作时 IsUpdatingFromDevice=false 不会进入此处)
|
||||
// Sync set voltage feedback to slider (only during device feedback, not during manual operation)
|
||||
if (statusData.SetVoltage >= _config.MinVoltage && Math.Abs(statusData.SetVoltage - VoltageValue) > 0.5)
|
||||
{
|
||||
VoltageValue = Math.Round(statusData.SetVoltage, 0);
|
||||
}
|
||||
|
||||
// 设定电流反馈同步到滑块 | Sync set current feedback to slider
|
||||
if (statusData.SetCurrent >= _config.MinCurrent && Math.Abs(statusData.SetCurrent - CurrentValue) > 0.5)
|
||||
{
|
||||
CurrentValue = Math.Round(statusData.SetCurrent, 0);
|
||||
}
|
||||
|
||||
// 使用服务层的权威状态 | Use authoritative status from service layer
|
||||
RaySourceStatus = _raySourceService.CurrentStatus;
|
||||
|
||||
// 同步连锁状态 | Sync interlock status
|
||||
IsInterlockActive = statusData.IsInterlockActive;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsUpdatingFromDevice = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误事件处理 | Error event handler
|
||||
/// </summary>
|
||||
private void OnErrorOccurred(string errorMessage)
|
||||
{
|
||||
// 显示错误消息 | Show error message
|
||||
MessageBox.Show(errorMessage, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作结果事件处理 | Operation result event handler
|
||||
/// </summary>
|
||||
private void OnOperationResult(OperationResultData resultData)
|
||||
{
|
||||
if (resultData == null) return;
|
||||
|
||||
if (!resultData.IsSuccess)
|
||||
{
|
||||
// 操作失败时显示消息 | Show message when operation fails
|
||||
MessageBox.Show(resultData.Message, "操作失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量连接状态变更事件处理 | Variables connected state changed event handler
|
||||
/// </summary>
|
||||
private void OnVariablesConnectedChanged(bool isConnected)
|
||||
{
|
||||
IsVariablesConnected = isConnected;
|
||||
_logger.Info("收到变量连接状态变更: {IsConnected} | Received variables connection state change: {IsConnected}", isConnected);
|
||||
if (isConnected)
|
||||
{
|
||||
_logger.Info("变量已连接,主动读取最新状态 | Variables Connected, actively reading latest status");
|
||||
RefreshStatusFromDevice();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令执行逻辑 | Command Execution Logic
|
||||
|
||||
/// <summary>
|
||||
/// 开启射线源执行逻辑 | Turn on X-ray execution logic
|
||||
/// </summary>
|
||||
private void ExecuteTurnOn()
|
||||
{
|
||||
_isOperating = true;
|
||||
TurnOnCommand.RaiseCanExecuteChanged();
|
||||
TurnOffCommand.RaiseCanExecuteChanged();
|
||||
try
|
||||
{
|
||||
_logger.Info("用户请求开启射线 | User requested to turn on X-ray");
|
||||
var result = _raySourceService.TurnOn();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("射线开启指令发送成功 | X-ray turned on successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("射线开启指令发送失败: {Error} | X-ray turn on failed: {Error}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage, "开启失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "开启射线源异常: {Message} | Exception turning on X-ray: {Message}", ex.Message);
|
||||
MessageBox.Show($"开启射线源异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isOperating = false;
|
||||
TurnOnCommand.RaiseCanExecuteChanged();
|
||||
TurnOffCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开启命令是否可执行(仅关闭状态且连锁激活时可执行)| Can execute turn on command (only when closed and interlock active)
|
||||
/// </summary>
|
||||
private bool CanExecuteTurnOn()
|
||||
{
|
||||
return !_isOperating && _isVariablesConnected && IsInterlockActive && RaySourceStatus == RaySourceStatus.Closed && _raySourceService.IsInitialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭射线源执行逻辑 | Turn off X-ray execution logic
|
||||
/// </summary>
|
||||
private void ExecuteTurnOff()
|
||||
{
|
||||
_isOperating = true;
|
||||
TurnOnCommand.RaiseCanExecuteChanged();
|
||||
TurnOffCommand.RaiseCanExecuteChanged();
|
||||
try
|
||||
{
|
||||
_logger.Info("用户请求关闭射线 | User requested to turn off X-ray");
|
||||
var result = _raySourceService.TurnOff();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("射线关闭指令发送成功 | X-ray turned off successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("射线关闭指令发送失败: {Error} | X-ray turn off failed: {Error}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage, "关闭失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "关闭射线源异常: {Message} | Exception turning off X-ray: {Message}", ex.Message);
|
||||
MessageBox.Show($"关闭射线源异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isOperating = false;
|
||||
TurnOnCommand.RaiseCanExecuteChanged();
|
||||
TurnOffCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭命令是否可执行(仅开启状态可执行)| Can execute turn off command (only when opened)
|
||||
/// </summary>
|
||||
private bool CanExecuteTurnOff()
|
||||
{
|
||||
return !_isOperating && _isVariablesConnected && RaySourceStatus == RaySourceStatus.Opened && _raySourceService.IsInitialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置命令执行逻辑:打开外部高级设置程序 | Settings command: launch external advance settings program
|
||||
/// 如果程序已运行则将窗口置前 | If program is already running, bring its window to front
|
||||
/// </summary>
|
||||
private void ExecuteSettings()
|
||||
{
|
||||
var exePath = _config.AdvanceExePath;
|
||||
_logger.Info("用户点击高级按钮,目标程序: {ExePath} | User clicked advance button, target program: {ExePath}", exePath);
|
||||
|
||||
var result = ProcessHelper.StartOrActivate(exePath, true);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
var action = result.IsNewlyStarted ? "已启动 | started" : "已激活 | activated";
|
||||
_logger.Info("外部程序{Action} | External program {Action}", action, action);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("外部程序操作失败: {Error} | External program operation failed: {Error}", result.ErrorMessage);
|
||||
MessageBox.Show(result.ErrorMessage,
|
||||
_localizationService.GetString("RaySource_Warning") ?? "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置命令是否可执行 | Can execute settings command
|
||||
/// </summary>
|
||||
private bool CanExecuteSettings()
|
||||
{
|
||||
return true; // 始终可用 | Always available
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置命令执行逻辑:打开射线源配置窗口 | Config command: open ray source config window
|
||||
/// </summary>
|
||||
private void ExecuteConfig()
|
||||
{
|
||||
_logger.Info("用户点击配置按钮 | User clicked config button");
|
||||
|
||||
// 如果窗口已存在且未关闭,激活它而不是重复创建
|
||||
if (_configWindow != null && _configWindow.IsLoaded)
|
||||
{
|
||||
_configWindow.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
_configWindow = new Views.RaySourceConfigWindow
|
||||
{
|
||||
Owner = Application.Current.MainWindow,
|
||||
ShowInTaskbar = false
|
||||
};
|
||||
_configWindow.Closed += (s, e) => _configWindow = null;
|
||||
_configWindow.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交电压值到硬件(滑块松手/输入框失焦时调用)| Apply voltage to hardware (called on slider drag complete / input lost focus)
|
||||
/// </summary>
|
||||
private void ApplyVoltage()
|
||||
{
|
||||
var voltage = (float)VoltageValue;
|
||||
|
||||
if (!IsSlidersEnabled) return;
|
||||
|
||||
// 范围校验 | Range validation
|
||||
if (voltage < _config.MinVoltage || voltage > _config.MaxVoltage)
|
||||
{
|
||||
_logger.Warn("电压值超出范围: {Voltage}kV,忽略提交 | Voltage out of range: {Voltage}kV, ignoring apply", voltage);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("用户确认电压值: {Voltage}kV | User confirmed voltage: {Voltage}kV", voltage);
|
||||
var result = _raySourceService.SetVoltage(voltage);
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Warn("电压设置失败: {Error} | Voltage setting failed: {Error}", result.ErrorMessage);
|
||||
// 设置失败时恢复滑块值(仅当实际值在有效范围内时)| Restore slider value when setting fails (only when actual value is in valid range)
|
||||
if (VoltageActual >= _config.MinVoltage)
|
||||
VoltageValue = VoltageActual;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "设置电压异常: {Message} | Exception setting voltage: {Message}", ex.Message);
|
||||
MessageBox.Show($"设置电压异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
if (VoltageActual >= _config.MinVoltage)
|
||||
VoltageValue = VoltageActual;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交电流值到硬件(滑块松手/输入框失焦时调用)| Apply current to hardware (called on slider drag complete / input lost focus)
|
||||
/// </summary>
|
||||
private void ApplyCurrent()
|
||||
{
|
||||
var current = (float)CurrentValue;
|
||||
|
||||
if (!IsSlidersEnabled) return;
|
||||
|
||||
// 范围校验 | Range validation
|
||||
if (current < _config.MinCurrent || current > _config.MaxCurrent)
|
||||
{
|
||||
_logger.Warn("电流值超出范围: {Current}μA,忽略提交 | Current out of range: {Current}μA, ignoring apply", current);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("用户确认电流值: {Current}μA | User confirmed current: {Current}μA", current);
|
||||
var result = _raySourceService.SetCurrent(current);
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.Warn("电流设置失败: {Error} | Current setting failed: {Error}", result.ErrorMessage);
|
||||
// 设置失败时恢复滑块值(仅当实际值在有效范围内时)| Restore slider value when setting fails (only when actual value is in valid range)
|
||||
if (CurrentActual >= _config.MinCurrent)
|
||||
CurrentValue = CurrentActual;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "设置电流异常: {Message} | Exception setting current: {Message}", ex.Message);
|
||||
MessageBox.Show($"设置电流异常: {ex.Message}", "异常", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
if (CurrentActual >= _config.MinCurrent)
|
||||
CurrentValue = CurrentActual;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务更新状态 | Update status from service
|
||||
/// </summary>
|
||||
private void UpdateStatusFromService()
|
||||
{
|
||||
RaySourceStatus = _raySourceService.CurrentStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从设备主动读取最新状态并刷新UI | Actively read latest status from device and refresh UI
|
||||
/// </summary>
|
||||
private void RefreshStatusFromDevice()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _raySourceService.ReadSystemStatus();
|
||||
if (result.Success && result.Data is SystemStatusData statusData)
|
||||
{
|
||||
OnStatusUpdated(statusData);
|
||||
_logger.Info("已从设备刷新最新状态 | Refreshed latest status from device");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("读取设备状态失败: {Error} | Failed to read device status: {Error}", result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "刷新设备状态异常: {Message} | Exception refreshing device status: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 界面加载时自动执行初始化和连接变量流程(仅执行一次)
|
||||
/// Auto-execute Initialize and ConnectVariables on view loaded (runs only once)
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 自动初始化:异步执行初始化 + 连接变量 | Auto-initialize: async Initialize + ConnectVariables
|
||||
/// 由 View 的 Loaded 事件触发,仅执行一次 | Triggered by View's Loaded event, runs only once
|
||||
/// </summary>
|
||||
public async Task AutoInitializeAsync()
|
||||
{
|
||||
// 防止重复执行 | Prevent duplicate execution
|
||||
if (_autoInitialized) return;
|
||||
_autoInitialized = true;
|
||||
|
||||
// 如果已经初始化并连接,跳过 | Skip if already initialized and connected
|
||||
if (_raySourceService.IsInitialized && _isVariablesConnected)
|
||||
{
|
||||
_logger.Info("射线源已初始化且变量已连接,跳过自动初始化 | Ray source already initialized and variables connected, skipping auto-init");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info("开始自动初始化流程 | Starting auto-initialization sequence");
|
||||
|
||||
var result = await _raySourceService.InitializeAndConnectAsync();
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.Info("自动初始化命令已发送,等待设备确认 | Auto-initialization command sent, waiting for device confirmation");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("自动初始化流程失败: {Error} | Auto-initialization sequence failed: {Error}", result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
// 断开射线源连接 | Disconnect X-ray source
|
||||
if (_raySourceService.IsInitialized)
|
||||
{
|
||||
_logger.Info("界面关闭,执行断开操作 | View closing, executing disconnect");
|
||||
try
|
||||
{
|
||||
Task.Run(() => _raySourceService.Disconnect()).Wait(5000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("断开射线源异常: {Message} | Disconnect exception: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭配置窗口 | Close config window
|
||||
if (_configWindow != null && _configWindow.IsLoaded)
|
||||
{
|
||||
_configWindow.Close();
|
||||
_configWindow = null;
|
||||
}
|
||||
|
||||
// 取消订阅事件 | Unsubscribe events
|
||||
_eventAggregator.GetEvent<RaySourceStatusChangedEvent>().Unsubscribe(OnRaySourceStatusChanged);
|
||||
_eventAggregator.GetEvent<StatusUpdatedEvent>().Unsubscribe(OnStatusUpdated);
|
||||
_eventAggregator.GetEvent<ErrorOccurredEvent>().Unsubscribe(OnErrorOccurred);
|
||||
_eventAggregator.GetEvent<OperationResultEvent>().Unsubscribe(OnOperationResult);
|
||||
_eventAggregator.GetEvent<VariablesConnectedEvent>().Unsubscribe(OnVariablesConnectedChanged);
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<UserControl x:Class="XP.Hardware.RaySource.Views.RaySourceConfigView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
|
||||
xmlns:converters="clr-namespace:XP.Hardware.RaySource.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="420" d:DesignWidth="500"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Background="White">
|
||||
<UserControl.Resources>
|
||||
<converters:FilamentLifetimeColorConverter x:Key="LifetimeColorConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 第1行:设备信息 | Row 1: Device Info -->
|
||||
<Grid Grid.Row="0" Margin="0 0 0 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{loc:Localization RaySource_SourceTypeLabel}"
|
||||
FontSize="13" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding SourceTypeText}"
|
||||
FontSize="13" FontWeight="Bold" VerticalAlignment="Center" Margin="8 0 0 0"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding ConnectionStatusText}"
|
||||
FontSize="12" VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<!-- 默认灰色:未连接 -->
|
||||
<Setter Property="Foreground" Value="#FF9E9E9E"/>
|
||||
<Style.Triggers>
|
||||
<!-- 已初始化但变量未连接:橙色 -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsInitialized}" Value="True"/>
|
||||
<Condition Binding="{Binding IsVariablesConnected}" Value="False"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Foreground" Value="#FFFF9800"/>
|
||||
</MultiDataTrigger>
|
||||
<!-- 变量已连接:绿色 -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsInitialized}" Value="True"/>
|
||||
<Condition Binding="{Binding IsVariablesConnected}" Value="True"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Foreground" Value="#FF4CAF50"/>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<!-- 第2行:操作按钮(3列等宽均布)| Row 2: Action Buttons (3 equal-width columns) -->
|
||||
<Grid Grid.Row="1" Margin="0 0 0 12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<telerik:RadButton Grid.Column="0" Content="{loc:Localization RaySource_InitializeButton}"
|
||||
Command="{Binding InitializeCommand}"
|
||||
Height="32" Margin="0 0 4 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadButton.Background>
|
||||
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
|
||||
<GradientStop Color="White"/>
|
||||
<GradientStop Color="#FF62B8E0" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</telerik:RadButton.Background>
|
||||
</telerik:RadButton>
|
||||
<telerik:RadButton Grid.Column="1" Content="{loc:Localization RaySource_ConnectVariablesButton}"
|
||||
Command="{Binding ConnectVariablesCommand}"
|
||||
Height="32" Margin="4 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadButton.Background>
|
||||
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
|
||||
<GradientStop Color="White"/>
|
||||
<GradientStop Color="#FF77E062" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</telerik:RadButton.Background>
|
||||
</telerik:RadButton>
|
||||
<telerik:RadButton Grid.Column="2" Content="{loc:Localization RaySource_DisconnectButton}"
|
||||
Command="{Binding DisconnectCommand}"
|
||||
Height="32" Margin="4 0 0 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadButton.Background>
|
||||
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
|
||||
<GradientStop Color="White"/>
|
||||
<GradientStop Color="#FFFFA1A1" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</telerik:RadButton.Background>
|
||||
</telerik:RadButton>
|
||||
</Grid>
|
||||
|
||||
<!-- 第3行:分隔线 | Row 3: Separator -->
|
||||
<Separator Grid.Row="2" Margin="0 0 0 8"/>
|
||||
|
||||
<!-- 第4行:设备状态监控面板 | Row 4: Device Status Monitor Panel -->
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 暖机状态 | Warm-up Status -->
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal" Margin="0 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_WarmUpLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding WarmUpStatus}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 真空状态 | Vacuum Status -->
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="8 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_VacuumLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding VacuumStatus}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 启动状态 | Startup Status -->
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" Margin="0 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_StartUpLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding StartUpStatus}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 自动定心 | Auto-center Status -->
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" Margin="8 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_AutoCenterLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding AutoCenterStatus}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 灯丝校准 | Filament Adjust Status -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Orientation="Horizontal" Margin="0 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_FilamentLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding FilamentAdjustStatus}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 射线开启状态 | X-Ray On Status -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" Margin="8 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_XRayOnLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding XRayOnStatusText}" FontSize="12" FontWeight="SemiBold">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsXRayOn}" Value="True">
|
||||
<Setter Property="Foreground" Value="#FF4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 连锁状态 | Interlock Status -->
|
||||
<StackPanel Grid.Row="3" Grid.Column="0" Orientation="Horizontal" Margin="0 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_InterlockLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding InterlockStatusText}" FontSize="12" FontWeight="SemiBold">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<!-- 默认未激活:红色 | Default inactive: red -->
|
||||
<Setter Property="Foreground" Value="#FFE53935"/>
|
||||
<Style.Triggers>
|
||||
<!-- 激活时:绿色 | Active: green -->
|
||||
<DataTrigger Binding="{Binding IsInterlockActive}" Value="True">
|
||||
<Setter Property="Foreground" Value="#FF4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 看门狗 | Watchdog Status -->
|
||||
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal" Margin="8 2">
|
||||
<TextBlock Text="{loc:Localization RaySource_WatchdogLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding WatchdogStatus}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- TXI状态 + TXI ON/OFF 按钮(同一行)| TXI Status + TXI ON/OFF Buttons (same row) -->
|
||||
<StackPanel Grid.Row="4" Grid.Column="0" Orientation="Horizontal" Margin="0 4 0 2" VerticalAlignment="Center">
|
||||
<TextBlock Text="{loc:Localization RaySource_TxiStatusLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding TxiStatus}" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="4" Grid.Column="1" Margin="8 2 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<telerik:RadButton Grid.Column="0" Content="{loc:Localization RaySource_TxiOnButton}"
|
||||
Command="{Binding TxiOnCommand}"
|
||||
Height="20" Padding="4 0" FontSize="11"
|
||||
Margin="0 0 2 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
<telerik:RadButton Grid.Column="1" Content="{loc:Localization RaySource_TxiOffButton}"
|
||||
Command="{Binding TxiOffCommand}"
|
||||
Height="20" Padding="4 0" FontSize="11"
|
||||
Margin="2 0 0 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 功率模式 + High Power/Micro Focus 按钮(同一行)| Power Mode + HP/MF Buttons (same row) -->
|
||||
<StackPanel Grid.Row="5" Grid.Column="0" Orientation="Horizontal" Margin="0 4 0 2" VerticalAlignment="Center">
|
||||
<TextBlock Text="{loc:Localization RaySource_PowerModeLabel}" FontSize="12" Foreground="#616161" Width="80"/>
|
||||
<TextBlock Text="{Binding PowerMode}" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="5" Grid.Column="1" Margin="8 2 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<telerik:RadButton Grid.Column="0" Content="{loc:Localization RaySource_HighPowerButton}"
|
||||
Command="{Binding HighPowerCommand}"
|
||||
Height="20" Padding="4 0" FontSize="11"
|
||||
Margin="0 0 2 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
<telerik:RadButton Grid.Column="1" Content="{loc:Localization RaySource_MicroFocusButton}"
|
||||
Command="{Binding MicroFocusCommand}"
|
||||
Height="20" Padding="4 0" FontSize="11"
|
||||
Margin="2 0 0 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- 第5行:分隔线 | Row 5: Separator -->
|
||||
<Separator Grid.Row="4" Margin="0 8 0 8"/>
|
||||
|
||||
<!-- 第6行:功能设置按钮 | Row 6: Function Setting Buttons -->
|
||||
<Grid Grid.Row="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<telerik:RadButton Grid.Column="0" Content="{loc:Localization RaySource_WarmUpSettingButton}"
|
||||
Command="{Binding WarmUpSettingCommand}"
|
||||
Height="30" Margin="0 0 4 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
<telerik:RadButton Grid.Column="1" Content="{loc:Localization RaySource_TrainingSettingButton}"
|
||||
Command="{Binding TrainingSettingCommand}"
|
||||
Height="30" Margin="4 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
<telerik:RadButton Grid.Column="2" Content="{loc:Localization RaySource_FilamentCalibrationButton}"
|
||||
Command="{Binding FilamentCalibrationCommand}"
|
||||
Height="30" Margin="4 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
<telerik:RadButton Grid.Column="3" Content="{loc:Localization RaySource_AutoCenterSettingButton}"
|
||||
Command="{Binding AutoCenterSettingCommand}"
|
||||
Height="30" Margin="4 0 0 0" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 第7行:分隔线 | Row 7: Separator -->
|
||||
<Separator Grid.Row="6" Margin="0 8 0 8"/>
|
||||
|
||||
<!-- 第8行:灯丝寿命进度条 | Row 8: Filament Lifetime Progress Bar -->
|
||||
<Grid Grid.Row="7">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{loc:Localization RaySource_FilamentLifetimeLabel}"
|
||||
FontSize="12" Foreground="#616161" VerticalAlignment="Center" Margin="0 0 8 0"/>
|
||||
<telerik:RadProgressBar Grid.Column="1"
|
||||
Minimum="0" Maximum="100"
|
||||
Value="{Binding FilamentLifetime, Mode=OneWay}"
|
||||
Foreground="{Binding FilamentLifetime, Converter={StaticResource LifetimeColorConverter}}"
|
||||
Height="8" VerticalAlignment="Center"
|
||||
telerik:StyleManager.Theme="Crystal"/>
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding FilamentLifetimeText}"
|
||||
FontSize="11" Foreground="#616161" VerticalAlignment="Center" Margin="8 0 0 0"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace XP.Hardware.RaySource.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ViewA.xaml
|
||||
/// </summary>
|
||||
public partial class RaySourceConfigView : UserControl
|
||||
{
|
||||
public RaySourceConfigView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Window x:Class="XP.Hardware.RaySource.Views.RaySourceConfigWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
|
||||
xmlns:raySourceViews="clr-namespace:XP.Hardware.RaySource.Views"
|
||||
Title="{loc:Localization RaySource_ConfigWindowTitle}"
|
||||
Height="350" Width="600"
|
||||
MinHeight="350" MinWidth="400"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="CanResize">
|
||||
<Grid>
|
||||
<raySourceViews:RaySourceConfigView />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace XP.Hardware.RaySource.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源配置窗口 | Ray source config window
|
||||
/// </summary>
|
||||
public partial class RaySourceConfigWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public RaySourceConfigWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 继承主窗口图标
|
||||
if (Application.Current?.MainWindow != null)
|
||||
{
|
||||
Icon = Application.Current.MainWindow.Icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
<UserControl
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:XP.Hardware.RaySource.Converters"
|
||||
xmlns:enums="clr-namespace:XP.Hardware.RaySource.Abstractions.Enums"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" x:Class="XP.Hardware.RaySource.Views.RaySourceOperateView"
|
||||
mc:Ignorable="d" d:DesignWidth="350"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True" Height="249" Background="White" >
|
||||
<UserControl.Resources>
|
||||
<!-- 转换器 | Converters -->
|
||||
<converters:RaySourceStatusToColorConverter x:Key="StatusToColorConverter"/>
|
||||
<converters:RaySourceStatusToBorderColorConverter x:Key="StatusToBorderColorConverter"/>
|
||||
|
||||
<!-- 呼吸闪烁动画 | Breathing Flash Animation -->
|
||||
<Storyboard x:Key="BreathingAnimation" RepeatBehavior="Forever">
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||
From="1.0" To="0.6" Duration="0:0:0.75"
|
||||
AutoReverse="True"/>
|
||||
</Storyboard>
|
||||
|
||||
<!-- 按钮样式 | Button Style -->
|
||||
<Style x:Key="RaySourceButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="#E0E0E0"/>
|
||||
<Setter Property="BorderBrush" Value="#BDBDBD"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Foreground" Value="Black"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="Medium"/>
|
||||
<Setter Property="Padding" Value="8"/>
|
||||
<Setter Property="Width" Value="85"/>
|
||||
<Setter Property="Height" Value="30"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border CornerRadius="4" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#BDBDBD"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#9E9E9E"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Background" Value="#F5F5F5"/>
|
||||
<Setter Property="Foreground" Value="#BDBDBD"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" MinHeight="70" />
|
||||
<RowDefinition Height="Auto" MinHeight="70" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 1. 状态区:腰圆指示器 + 按钮 | Status Area: Capsule Indicator + Buttons -->
|
||||
<Grid Grid.Row="0" Margin="5,5,5,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 腰圆状态指示器 | Capsule Status Indicator -->
|
||||
<Grid Grid.Column="0" Width="200" Height="80" Margin="20 0 0 5">
|
||||
<Border x:Name="StatusIndicator"
|
||||
Width="200" Height="80"
|
||||
Background="{Binding RaySourceStatus, Converter={StaticResource StatusToColorConverter}}"
|
||||
BorderBrush="{Binding RaySourceStatus, Converter={StaticResource StatusToBorderColorConverter}}"
|
||||
BorderThickness="2"
|
||||
CornerRadius="40">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding RaySourceStatus}"
|
||||
Value="{x:Static enums:RaySourceStatus.Opened}">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard Name="BreathingStoryboard" Storyboard="{StaticResource BreathingAnimation}"/>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<StopStoryboard BeginStoryboardName="BreathingStoryboard"/>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding StatusText}"
|
||||
Foreground="White" FontSize="18" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" LineHeight="20"/>
|
||||
<!-- 连锁状态图标(腰圆右侧圆弧区域)| Interlock status icon (right arc area of capsule) -->
|
||||
<Image Width="22" Height="22"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,20,0"
|
||||
RenderOptions.BitmapScalingMode="HighQuality">
|
||||
<Image.Style>
|
||||
<Style TargetType="Image">
|
||||
<!-- 默认未激活:解锁图标 | Default inactive: unlocked icon -->
|
||||
<Setter Property="Source" Value="/XP.Hardware.RaySource;component/Resources/Unlocked.png"/>
|
||||
<Setter Property="ToolTip" Value="{loc:Localization RaySource_InterlockNormal}"/>
|
||||
<Style.Triggers>
|
||||
<!-- 激活时:锁定图标 | Active: locked icon -->
|
||||
<DataTrigger Binding="{Binding IsInterlockActive}" Value="True">
|
||||
<Setter Property="Source" Value="/XP.Hardware.RaySource;component/Resources/Locked.png"/>
|
||||
<Setter Property="ToolTip" Value="{loc:Localization RaySource_InterlockActive}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Image.Style>
|
||||
</Image>
|
||||
</Grid>
|
||||
|
||||
<!-- 按钮区 | Button Area -->
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||
Margin="20 0 20 0">
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_TurnOnButton}"
|
||||
Command="{Binding TurnOnCommand}"
|
||||
Margin="0 0 0 8" Height="35" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadButton.Background>
|
||||
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
|
||||
<GradientStop Color="White"/>
|
||||
<GradientStop Color="#FF77E062" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</telerik:RadButton.Background>
|
||||
</telerik:RadButton>
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_TurnOffButton}"
|
||||
Command="{Binding TurnOffCommand}"
|
||||
Margin="0 0 0 0" Height="35" HorizontalAlignment="Stretch"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadButton.Background>
|
||||
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
|
||||
<GradientStop Color="White"/>
|
||||
<GradientStop Color="#FFFFA1A1" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</telerik:RadButton.Background>
|
||||
</telerik:RadButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 2. 电压区 | Voltage Area -->
|
||||
<Grid Grid.Row="1" Margin="20,0,20,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标签+实际值 | Label + Actual Value -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{loc:Localization RaySource_VoltageLabel}"
|
||||
FontSize="14" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding VoltageActualText}"
|
||||
FontSize="10" VerticalAlignment="Center" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<telerik:RadSlider x:Name="VoltageSlider" Grid.Column="0" HorizontalAlignment="Stretch" Margin="0,5,10,0" VerticalAlignment="Center"
|
||||
telerik:StyleManager.Theme="Crystal"
|
||||
Minimum="{Binding VoltageMin}"
|
||||
Maximum="{Binding VoltageMax}"
|
||||
Value="{Binding VoltageValue, Mode=TwoWay}"
|
||||
SmallChange="1" LargeChange="10"
|
||||
IsDeferredDraggingEnabled="True"
|
||||
IsEnabled="{Binding IsSlidersEnabled}"/>
|
||||
<telerik:RadNumericUpDown x:Name="VoltageNumeric" Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Center" Width="70"
|
||||
Minimum="{Binding VoltageMin}"
|
||||
Maximum="{Binding VoltageMax}"
|
||||
Value="{Binding VoltageValue, Mode=TwoWay}"
|
||||
SmallChange="1" LargeChange="10"
|
||||
IsEnabled="{Binding IsSlidersEnabled}"
|
||||
NumberDecimalDigits="0" telerik:StyleManager.Theme="Crystal" FontSize="11"/>
|
||||
<!-- 最小值 | Minimum Value -->
|
||||
<TextBlock Grid.Column="0" Text="{Binding VoltageMin, StringFormat={}{0:F0}}"
|
||||
FontSize="12" Foreground="#616161"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Bottom"
|
||||
Margin="0 0 0 -17"/>
|
||||
<!-- 最大值 | Maximum Value -->
|
||||
<TextBlock Grid.Column="0" Text="{Binding VoltageMax, StringFormat={}{0:F0}}"
|
||||
FontSize="12" Foreground="#616161"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" TextAlignment="Right"
|
||||
Margin="0,0,10,-17"/>
|
||||
</Grid>
|
||||
<!-- 按钮行:配置 | Button Row: Config -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,0">
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_ConfigButton}"
|
||||
Command="{Binding ConfigCommand}"
|
||||
Width="70"
|
||||
telerik:StyleManager.Theme="Crystal" FontSize="10"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 3. 电流区 | Current Area -->
|
||||
<Grid Grid.Row="1" Margin="20,75,20,0" Grid.RowSpan="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标签+实际值 | Label + Actual Value -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{loc:Localization RaySource_CurrentLabel}"
|
||||
FontSize="14" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding CurrentActualText}"
|
||||
FontSize="10" VerticalAlignment="Center" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 滑块 | Slider -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<telerik:RadSlider x:Name="CurrentSlider" Grid.Column="0" HorizontalAlignment="Stretch" Margin="0,6,10,0" VerticalAlignment="Top"
|
||||
telerik:StyleManager.Theme="Crystal"
|
||||
Minimum="{Binding CurrentMin}"
|
||||
Maximum="{Binding CurrentMax}"
|
||||
Value="{Binding CurrentValue, Mode=TwoWay}"
|
||||
SmallChange="10" LargeChange="100"
|
||||
IsDeferredDraggingEnabled="True"
|
||||
IsEnabled="{Binding IsSlidersEnabled}"/>
|
||||
<telerik:RadNumericUpDown x:Name="CurrentNumeric" Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Center" Width="70"
|
||||
Minimum="{Binding CurrentMin}"
|
||||
Maximum="{Binding CurrentMax}"
|
||||
Value="{Binding CurrentValue, Mode=TwoWay}"
|
||||
SmallChange="10" LargeChange="100"
|
||||
IsEnabled="{Binding IsSlidersEnabled}"
|
||||
NumberDecimalDigits="0" telerik:StyleManager.Theme="Crystal" FontSize="11"/>
|
||||
<!-- 最小值 | Minimum Value -->
|
||||
<TextBlock Grid.Column="0" Text="{Binding CurrentMin, StringFormat={}{0:F0}}"
|
||||
FontSize="12" Foreground="#616161"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Bottom"
|
||||
Margin="0 0 0 -17"/>
|
||||
<!-- 最大值 | Maximum Value -->
|
||||
<TextBlock Grid.Column="0" Text="{Binding CurrentMax, StringFormat={}{0:F0}}"
|
||||
FontSize="12" Foreground="#616161"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" TextAlignment="Right"
|
||||
Margin="0,0,10,-17"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 按钮行:高级 | Button Row: Advance -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,0">
|
||||
<telerik:RadButton Content="{loc:Localization RaySource_AdvanceButton}"
|
||||
Command="{Binding SettingsCommand}"
|
||||
Width="70"
|
||||
telerik:StyleManager.Theme="Crystal" FontSize="10"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Telerik.Windows.Controls;
|
||||
using XP.Hardware.RaySource.ViewModels;
|
||||
|
||||
namespace XP.Hardware.RaySource.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 射线源操作视图 code-behind | X-Ray Source Operate View code-behind
|
||||
/// 负责将滑块 DragCompleted / 输入框确认操作桥接到 ViewModel 命令
|
||||
/// Bridges slider DragCompleted / numeric confirm actions to ViewModel commands
|
||||
/// </summary>
|
||||
public partial class RaySourceOperateView : UserControl
|
||||
{
|
||||
// 防止初始化阶段触发写入 | Prevent writes during initialization
|
||||
private bool _isLoaded = false;
|
||||
|
||||
// 标记输入框是否正在键盘输入中(区分手动输入和箭头点击)
|
||||
// Flag whether numeric input is in keyboard editing (distinguish manual input from arrow click)
|
||||
private bool _isVoltageEditing = false;
|
||||
private bool _isCurrentEditing = false;
|
||||
|
||||
public RaySourceOperateView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 滑块拖动完成时提交值到硬件 | Apply value to hardware when slider drag completes
|
||||
VoltageSlider.DragCompleted += VoltageSlider_DragCompleted;
|
||||
CurrentSlider.DragCompleted += CurrentSlider_DragCompleted;
|
||||
|
||||
// 输入框事件 | Numeric input events
|
||||
VoltageNumeric.GotKeyboardFocus += (s, e) => _isVoltageEditing = true;
|
||||
VoltageNumeric.LostFocus += VoltageNumeric_LostFocus;
|
||||
VoltageNumeric.KeyDown += VoltageNumeric_KeyDown;
|
||||
VoltageNumeric.ValueChanged += VoltageNumeric_ValueChanged;
|
||||
|
||||
CurrentNumeric.GotKeyboardFocus += (s, e) => _isCurrentEditing = true;
|
||||
CurrentNumeric.LostFocus += CurrentNumeric_LostFocus;
|
||||
CurrentNumeric.KeyDown += CurrentNumeric_KeyDown;
|
||||
CurrentNumeric.ValueChanged += CurrentNumeric_ValueChanged;
|
||||
|
||||
Loaded += OnViewLoaded;
|
||||
Unloaded += OnViewUnloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 界面加载完成:标记已加载并触发自动初始化流程
|
||||
/// View loaded: mark as loaded and trigger auto-initialization sequence
|
||||
/// 参考旧代码 AreaDetector_Load 模式 | Follows AreaDetector_Load pattern from legacy code
|
||||
/// </summary>
|
||||
private async void OnViewLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isLoaded = true;
|
||||
|
||||
if (DataContext is RaySourceOperateViewModel vm)
|
||||
{
|
||||
await vm.AutoInitializeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 界面卸载:触发 ViewModel 的 Dispose 执行断开操作
|
||||
/// View unloaded: trigger ViewModel Dispose to execute disconnect
|
||||
/// </summary>
|
||||
private void OnViewUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is RaySourceOperateViewModel vm)
|
||||
{
|
||||
vm.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以提交(已加载、非设备反馈更新中)| Check if can apply (loaded and not updating from device)
|
||||
/// </summary>
|
||||
private bool CanApply(out RaySourceOperateViewModel vm)
|
||||
{
|
||||
vm = null;
|
||||
if (!_isLoaded) return false;
|
||||
if (DataContext is not RaySourceOperateViewModel viewModel) return false;
|
||||
if (viewModel.IsUpdatingFromDevice) return false;
|
||||
vm = viewModel;
|
||||
return true;
|
||||
}
|
||||
|
||||
#region 滑块事件 | Slider Events
|
||||
|
||||
/// <summary>
|
||||
/// 电压滑块拖动完成 | Voltage slider drag completed
|
||||
/// </summary>
|
||||
private void VoltageSlider_DragCompleted(object sender, RadDragCompletedEventArgs e)
|
||||
{
|
||||
if (CanApply(out var vm))
|
||||
vm.ApplyVoltageCommand.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电流滑块拖动完成 | Current slider drag completed
|
||||
/// </summary>
|
||||
private void CurrentSlider_DragCompleted(object sender, RadDragCompletedEventArgs e)
|
||||
{
|
||||
if (CanApply(out var vm))
|
||||
vm.ApplyCurrentCommand.Execute();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 电压输入框事件 | Voltage Numeric Events
|
||||
|
||||
/// <summary>
|
||||
/// 电压输入框值变更:仅在非键盘编辑时提交(即箭头按钮点击)
|
||||
/// Voltage numeric value changed: only apply when not keyboard editing (i.e. arrow button click)
|
||||
/// </summary>
|
||||
private void VoltageNumeric_ValueChanged(object sender, RadRangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
if (!_isVoltageEditing && CanApply(out var vm))
|
||||
vm.ApplyVoltageCommand.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电压输入框回车确认 | Voltage numeric Enter key confirm
|
||||
/// </summary>
|
||||
private void VoltageNumeric_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && CanApply(out var vm))
|
||||
{
|
||||
_isVoltageEditing = false;
|
||||
vm.ApplyVoltageCommand.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电压输入框失焦确认 | Voltage numeric lost focus confirm
|
||||
/// </summary>
|
||||
private void VoltageNumeric_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isVoltageEditing && CanApply(out var vm))
|
||||
{
|
||||
_isVoltageEditing = false;
|
||||
vm.ApplyVoltageCommand.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 电流输入框事件 | Current Numeric Events
|
||||
|
||||
/// <summary>
|
||||
/// 电流输入框值变更:仅在非键盘编辑时提交(即箭头按钮点击)
|
||||
/// Current numeric value changed: only apply when not keyboard editing (i.e. arrow button click)
|
||||
/// </summary>
|
||||
private void CurrentNumeric_ValueChanged(object sender, RadRangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
if (!_isCurrentEditing && CanApply(out var vm))
|
||||
vm.ApplyCurrentCommand.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电流输入框回车确认 | Current numeric Enter key confirm
|
||||
/// </summary>
|
||||
private void CurrentNumeric_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && CanApply(out var vm))
|
||||
{
|
||||
_isCurrentEditing = false;
|
||||
vm.ApplyCurrentCommand.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 电流输入框失焦确认 | Current numeric lost focus confirm
|
||||
/// </summary>
|
||||
private void CurrentNumeric_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isCurrentEditing && CanApply(out var vm))
|
||||
{
|
||||
_isCurrentEditing = false;
|
||||
vm.ApplyCurrentCommand.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Prism.Wpf" Version="9.0.537" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
||||
<PackageReference Include="Telerik.UI.for.Wpf.NetCore.Xaml" Version="2024.1.408" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="ViewModels\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Locked.png" />
|
||||
<Resource Include="Resources\Unlocked.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\XP.Common\XP.Common.csproj" />
|
||||
<ProjectReference Include="..\XP.Hardware.RaySource.Comet.Messages\XP.Hardware.RaySource.Comet.Messages.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user