using System.Diagnostics;
|
using Aging.Infrastructure.Contracts;
|
using log4net;
|
|
namespace Aging.Infrastructure.Services
|
{
|
/// <summary>
|
/// 电源配置命令
|
/// </summary>
|
public sealed class PowerCommand
|
{
|
/// <summary>
|
/// 目标电压值(可选,单位: V)
|
/// </summary>
|
public double? Voltage { get; set; }
|
|
/// <summary>
|
/// 目标电流限制值(可选,单位: A)
|
/// </summary>
|
public double? CurrentLimit { get; set; }
|
|
/// <summary>
|
/// 输出开关状态(true=ON, false=OFF, null=不改变)
|
/// </summary>
|
public bool? Output { get; set; }
|
}
|
|
/// <summary>
|
/// 电源操作结果
|
/// </summary>
|
public sealed class PowerOperationResult
|
{
|
/// <summary>
|
/// 操作是否成功
|
/// </summary>
|
public bool Success { get; set; }
|
|
/// <summary>
|
/// 实测电压值(单位: V)
|
/// </summary>
|
public double? MeasuredVoltage { get; set; }
|
|
/// <summary>
|
/// 实测电流值(单位: A)
|
/// </summary>
|
public double? MeasuredCurrent { get; set; }
|
|
/// <summary>
|
/// 设备原始响应字符串
|
/// </summary>
|
public string? RawResponse { get; set; }
|
|
/// <summary>
|
/// 错误代码
|
/// </summary>
|
public string? ErrorCode { get; set; }
|
|
/// <summary>
|
/// 错误信息
|
/// </summary>
|
public string? ErrorMessage { get; set; }
|
|
/// <summary>
|
/// 操作延迟(毫秒)
|
/// </summary>
|
public double LatencyMs { get; set; }
|
|
/// <summary>
|
/// 创建一个成功的结果
|
/// </summary>
|
public static PowerOperationResult Succeed(double? voltage, double? current, string? rawResponse, double latencyMs)
|
{
|
return new PowerOperationResult
|
{
|
Success = true,
|
MeasuredVoltage = voltage,
|
MeasuredCurrent = current,
|
RawResponse = rawResponse,
|
LatencyMs = latencyMs
|
};
|
}
|
|
/// <summary>
|
/// 创建一个失败的结果
|
/// </summary>
|
public static PowerOperationResult Failed(string errorCode, string errorMessage, double latencyMs)
|
{
|
return new PowerOperationResult
|
{
|
Success = false,
|
ErrorCode = errorCode,
|
ErrorMessage = errorMessage,
|
LatencyMs = latencyMs
|
};
|
}
|
}
|
|
/// <summary>
|
/// 电源配置服务 - 面向 Slot 的高层电源操作。
|
/// 通过 SlotDeviceMappingResolver 解析 Slot 到具体设备端点的映射,
|
/// 协调 ResourceManager 和 DeviceManager 完成电源配置、测量和状态查询。
|
/// </summary>
|
public sealed class PowerSupplyService
|
{
|
private static readonly ILog Log = LogManager.GetLogger(typeof(PowerSupplyService));
|
|
/// <summary>
|
/// 资源管理器
|
/// </summary>
|
private readonly IResourceManager _resourceManager;
|
|
/// <summary>
|
/// 设备管理器
|
/// </summary>
|
private readonly IDeviceManager _deviceManager;
|
|
/// <summary>
|
/// Slot-设备-通道映射解析器
|
/// </summary>
|
private readonly SlotDeviceMappingResolver _slotMappingResolver;
|
|
/// <summary>
|
/// 初始化电源配置服务实例
|
/// </summary>
|
/// <param name="resourceManager">资源管理器</param>
|
/// <param name="deviceManager">设备管理器</param>
|
/// <param name="slotMappingResolver">Slot 映射解析器</param>
|
public PowerSupplyService(
|
IResourceManager resourceManager,
|
IDeviceManager deviceManager,
|
SlotDeviceMappingResolver slotMappingResolver)
|
{
|
_resourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager));
|
_deviceManager = deviceManager ?? throw new ArgumentNullException(nameof(deviceManager));
|
_slotMappingResolver = slotMappingResolver ?? throw new ArgumentNullException(nameof(slotMappingResolver));
|
}
|
|
/// <summary>
|
/// 异步配置 Slot 对应的电源参数。
|
/// 根据 SlotId 解析映射获取设备端点,验证资源预约后发送 SCPI 命令配置电源。
|
/// </summary>
|
/// <param name="slotId">Slot 标识符</param>
|
/// <param name="command">电源配置命令</param>
|
/// <param name="owner">所有者令牌</param>
|
/// <param name="ct">取消令牌</param>
|
/// <returns>电源操作结果,包含测量值</returns>
|
public async Task<PowerOperationResult> ConfigureAsync(
|
string slotId, PowerCommand command, ResourceOwnerToken owner, CancellationToken ct)
|
{
|
if (string.IsNullOrWhiteSpace(slotId))
|
throw new ArgumentException("SlotId 不能为空", nameof(slotId));
|
if (command == null)
|
throw new ArgumentNullException(nameof(command));
|
if (owner == null)
|
throw new ArgumentNullException(nameof(owner));
|
|
var stopwatch = Stopwatch.StartNew();
|
|
try
|
{
|
Log.Info($"[PowerSupplyService] 配置电源: Slot={slotId}, Owner={owner.OwnerId}");
|
|
// 1. 解析 Slot 映射
|
var endpoint = _slotMappingResolver.ResolveEndpoint(slotId, "PowerSupply");
|
if (endpoint == null)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] Slot {slotId} 未找到 PowerSupply 映射");
|
return PowerOperationResult.Failed("MAPPING_NOT_FOUND",
|
$"Slot {slotId} 未找到 PowerSupply 类型的设备映射", stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
var resourceKey = endpoint.ToResourceKey();
|
|
// 2. 验证预约
|
if (!_resourceManager.ValidateOwner(owner, resourceKey))
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 权限验证失败: Slot={slotId}, Device={endpoint.DeviceId}, Owner={owner.OwnerId}");
|
return PowerOperationResult.Failed("PERMISSION_DENIED",
|
$"所有者 {owner.OwnerId} 未预约 Slot {slotId} 对应的设备 {endpoint.DeviceId} 或令牌已过期",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
// 3. 获取租约
|
using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false);
|
|
// 4. 获取设备驱动
|
var driver = _deviceManager.GetDriver(endpoint.DeviceId);
|
if (driver == null)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 设备 {endpoint.DeviceId} 驱动不存在");
|
return PowerOperationResult.Failed("DRIVER_NOT_FOUND",
|
$"设备 {endpoint.DeviceId} 的驱动未注册", stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
// 5. 构建并发送 SCPI 命令
|
var scpiCommands = BuildScpiCommands(command);
|
foreach (var scpiCommand in scpiCommands)
|
{
|
Log.Info($"[PowerSupplyService] 发送 SCPI 命令: Device={endpoint.DeviceId}, Command={scpiCommand}");
|
await driver.SendCommandAsync(scpiCommand, ct).ConfigureAwait(false);
|
}
|
|
// 6. 回读测量值
|
string rawResponse;
|
try
|
{
|
// 优先尝试 MEAS:ALL? 一次性获取所有测量值
|
rawResponse = await driver.QueryAsync("MEAS:ALL?", ct).ConfigureAwait(false);
|
Log.Info($"[PowerSupplyService] MEAS:ALL? 响应: {rawResponse}");
|
}
|
catch
|
{
|
// 如果 MEAS:ALL? 失败,分别查询电压和电流
|
Log.Info($"[PowerSupplyService] MEAS:ALL? 失败,尝试分别查询 VOLT? 和 CURR?");
|
var voltageResponse = await driver.QueryAsync("VOLT?", ct).ConfigureAwait(false);
|
var currentResponse = await driver.QueryAsync("CURR?", ct).ConfigureAwait(false);
|
rawResponse = $"{voltageResponse},{currentResponse}";
|
}
|
|
// 7. 解析回读值
|
ParseMeasurementResponse(rawResponse, out var measuredVoltage, out var measuredCurrent);
|
|
stopwatch.Stop();
|
Log.Info($"[PowerSupplyService] 电源配置完成: Slot={slotId}, Device={endpoint.DeviceId}, " +
|
$"V={measuredVoltage}, I={measuredCurrent}, 延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms");
|
|
return PowerOperationResult.Succeed(measuredVoltage, measuredCurrent, rawResponse,
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (ResourceConflictException ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 资源冲突: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("RESOURCE_CONFLICT", ex.Message, stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (OperationCanceledException)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 电源配置被取消: Slot={slotId}");
|
return PowerOperationResult.Failed("OPERATION_CANCELLED", "操作已被取消",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (TimeoutException ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 电源配置超时: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("TIMEOUT", $"电源配置超时: {ex.Message}",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (Exception ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 电源配置异常: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("UNKNOWN_ERROR", $"电源配置失败: {ex.Message}",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
}
|
|
/// <summary>
|
/// 异步测量 Slot 对应电源的输出值(电压、电流)。
|
/// 根据 SlotId 解析映射获取设备端点,查询实时测量值。
|
/// </summary>
|
/// <param name="slotId">Slot 标识符</param>
|
/// <param name="owner">所有者令牌</param>
|
/// <param name="ct">取消令牌</param>
|
/// <returns>电源操作结果,包含测量值</returns>
|
public async Task<PowerOperationResult> MeasureAsync(
|
string slotId, ResourceOwnerToken owner, CancellationToken ct)
|
{
|
if (string.IsNullOrWhiteSpace(slotId))
|
throw new ArgumentException("SlotId 不能为空", nameof(slotId));
|
if (owner == null)
|
throw new ArgumentNullException(nameof(owner));
|
|
var stopwatch = Stopwatch.StartNew();
|
|
try
|
{
|
Log.Info($"[PowerSupplyService] 测量电源: Slot={slotId}, Owner={owner.OwnerId}");
|
|
// 1. 解析 Slot 映射
|
var endpoint = _slotMappingResolver.ResolveEndpoint(slotId, "PowerSupply");
|
if (endpoint == null)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] Slot {slotId} 未找到 PowerSupply 映射");
|
return PowerOperationResult.Failed("MAPPING_NOT_FOUND",
|
$"Slot {slotId} 未找到 PowerSupply 类型的设备映射", stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
var resourceKey = endpoint.ToResourceKey();
|
|
// 2. 验证预约
|
if (!_resourceManager.ValidateOwner(owner, resourceKey))
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 权限验证失败: Slot={slotId}, Device={endpoint.DeviceId}, Owner={owner.OwnerId}");
|
return PowerOperationResult.Failed("PERMISSION_DENIED",
|
$"所有者 {owner.OwnerId} 未预约 Slot {slotId} 对应的设备 {endpoint.DeviceId} 或令牌已过期",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
// 3. 获取租约
|
using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false);
|
|
// 4. 获取设备驱动
|
var driver = _deviceManager.GetDriver(endpoint.DeviceId);
|
if (driver == null)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 设备 {endpoint.DeviceId} 驱动不存在");
|
return PowerOperationResult.Failed("DRIVER_NOT_FOUND",
|
$"设备 {endpoint.DeviceId} 的驱动未注册", stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
// 5. 查询测量值
|
string rawResponse;
|
try
|
{
|
rawResponse = await driver.QueryAsync("MEAS:ALL?", ct).ConfigureAwait(false);
|
}
|
catch
|
{
|
var voltageResponse = await driver.QueryAsync("VOLT?", ct).ConfigureAwait(false);
|
var currentResponse = await driver.QueryAsync("CURR?", ct).ConfigureAwait(false);
|
rawResponse = $"{voltageResponse},{currentResponse}";
|
}
|
|
// 6. 解析回读值
|
ParseMeasurementResponse(rawResponse, out var measuredVoltage, out var measuredCurrent);
|
|
stopwatch.Stop();
|
Log.Info($"[PowerSupplyService] 测量完成: Slot={slotId}, Device={endpoint.DeviceId}, " +
|
$"V={measuredVoltage}, I={measuredCurrent}, 延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms");
|
|
return PowerOperationResult.Succeed(measuredVoltage, measuredCurrent, rawResponse,
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (ResourceConflictException ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 资源冲突: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("RESOURCE_CONFLICT", ex.Message, stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (OperationCanceledException)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 测量操作被取消: Slot={slotId}");
|
return PowerOperationResult.Failed("OPERATION_CANCELLED", "操作已被取消",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (TimeoutException ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 测量操作超时: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("TIMEOUT", $"测量操作超时: {ex.Message}",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (Exception ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 测量操作异常: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("UNKNOWN_ERROR", $"测量操作失败: {ex.Message}",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
}
|
|
/// <summary>
|
/// 异步获取 Slot 对应电源的当前状态(电压、电流、输出开关状态)。
|
/// </summary>
|
/// <param name="slotId">Slot 标识符</param>
|
/// <param name="owner">所有者令牌</param>
|
/// <param name="ct">取消令牌</param>
|
/// <returns>电源操作结果,包含状态信息</returns>
|
public async Task<PowerOperationResult> GetPowerStatusAsync(
|
string slotId, ResourceOwnerToken owner, CancellationToken ct)
|
{
|
if (string.IsNullOrWhiteSpace(slotId))
|
throw new ArgumentException("SlotId 不能为空", nameof(slotId));
|
if (owner == null)
|
throw new ArgumentNullException(nameof(owner));
|
|
var stopwatch = Stopwatch.StartNew();
|
|
try
|
{
|
Log.Info($"[PowerSupplyService] 查询电源状态: Slot={slotId}, Owner={owner.OwnerId}");
|
|
// 1. 解析 Slot 映射
|
var endpoint = _slotMappingResolver.ResolveEndpoint(slotId, "PowerSupply");
|
if (endpoint == null)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] Slot {slotId} 未找到 PowerSupply 映射");
|
return PowerOperationResult.Failed("MAPPING_NOT_FOUND",
|
$"Slot {slotId} 未找到 PowerSupply 类型的设备映射", stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
var resourceKey = endpoint.ToResourceKey();
|
|
// 2. 验证预约
|
if (!_resourceManager.ValidateOwner(owner, resourceKey))
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 权限验证失败: Slot={slotId}, Device={endpoint.DeviceId}, Owner={owner.OwnerId}");
|
return PowerOperationResult.Failed("PERMISSION_DENIED",
|
$"所有者 {owner.OwnerId} 未预约 Slot {slotId} 对应的设备 {endpoint.DeviceId} 或令牌已过期",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
// 3. 获取租约
|
using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false);
|
|
// 4. 获取设备驱动
|
var driver = _deviceManager.GetDriver(endpoint.DeviceId);
|
if (driver == null)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 设备 {endpoint.DeviceId} 驱动不存在");
|
return PowerOperationResult.Failed("DRIVER_NOT_FOUND",
|
$"设备 {endpoint.DeviceId} 的驱动未注册", stopwatch.Elapsed.TotalMilliseconds);
|
}
|
|
// 5. 查询电压、电流和输出状态
|
var voltageResponse = await driver.QueryAsync("VOLT?", ct).ConfigureAwait(false);
|
var currentResponse = await driver.QueryAsync("CURR?", ct).ConfigureAwait(false);
|
var outputResponse = await driver.QueryAsync("OUTP?", ct).ConfigureAwait(false);
|
|
var rawResponse = $"VOLT={voltageResponse},CURR={currentResponse},OUTP={outputResponse}";
|
|
// 6. 解析测量值
|
ParseMeasurementResponse($"{voltageResponse},{currentResponse}",
|
out var measuredVoltage, out var measuredCurrent);
|
|
stopwatch.Stop();
|
Log.Info($"[PowerSupplyService] 电源状态查询完成: Slot={slotId}, Device={endpoint.DeviceId}, " +
|
$"V={measuredVoltage}, I={measuredCurrent}, OUTP={outputResponse}, " +
|
$"延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms");
|
|
return new PowerOperationResult
|
{
|
Success = true,
|
MeasuredVoltage = measuredVoltage,
|
MeasuredCurrent = measuredCurrent,
|
RawResponse = rawResponse,
|
LatencyMs = stopwatch.Elapsed.TotalMilliseconds
|
};
|
}
|
catch (ResourceConflictException ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 资源冲突: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("RESOURCE_CONFLICT", ex.Message, stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (OperationCanceledException)
|
{
|
stopwatch.Stop();
|
Log.Warn($"[PowerSupplyService] 状态查询被取消: Slot={slotId}");
|
return PowerOperationResult.Failed("OPERATION_CANCELLED", "操作已被取消",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (TimeoutException ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 状态查询超时: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("TIMEOUT", $"状态查询超时: {ex.Message}",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
catch (Exception ex)
|
{
|
stopwatch.Stop();
|
Log.Error($"[PowerSupplyService] 状态查询异常: Slot={slotId}, 错误: {ex.Message}", ex);
|
return PowerOperationResult.Failed("UNKNOWN_ERROR", $"状态查询失败: {ex.Message}",
|
stopwatch.Elapsed.TotalMilliseconds);
|
}
|
}
|
|
/// <summary>
|
/// 根据电源配置命令构建 SCPI 命令列表
|
/// </summary>
|
/// <param name="command">电源配置命令</param>
|
/// <returns>SCPI 命令字符串列表</returns>
|
private static List<string> BuildScpiCommands(PowerCommand command)
|
{
|
var commands = new List<string>();
|
|
if (command.Voltage.HasValue)
|
{
|
commands.Add($"VOLT {command.Voltage.Value:G}");
|
}
|
|
if (command.CurrentLimit.HasValue)
|
{
|
commands.Add($"CURR {command.CurrentLimit.Value:G}");
|
}
|
|
if (command.Output.HasValue)
|
{
|
commands.Add(command.Output.Value ? "OUTP ON" : "OUTP OFF");
|
}
|
|
return commands;
|
}
|
|
/// <summary>
|
/// 解析设备返回的测量值响应字符串。
|
/// 支持格式: "voltage,current" 或单个数值。
|
/// </summary>
|
/// <param name="rawResponse">原始响应字符串</param>
|
/// <param name="voltage">解析后的电压值</param>
|
/// <param name="current">解析后的电流值</param>
|
private static void ParseMeasurementResponse(string rawResponse, out double? voltage, out double? current)
|
{
|
voltage = null;
|
current = null;
|
|
if (string.IsNullOrWhiteSpace(rawResponse))
|
return;
|
|
try
|
{
|
var parts = rawResponse.Split(',');
|
|
if (parts.Length >= 1 && double.TryParse(parts[0].Trim(), out var v))
|
{
|
voltage = v;
|
}
|
|
if (parts.Length >= 2 && double.TryParse(parts[1].Trim(), out var c))
|
{
|
current = c;
|
}
|
}
|
catch (Exception)
|
{
|
// 解析失败时保持 null 值
|
}
|
}
|
}
|
}
|