将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -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") ?? "未知状态"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user