Files
Evergreen-Health/Evergreen.Health.Application/Health/Devices/DeviceParameterController.cs
T
2026-10-09 18:06:11 +08:00

67 lines
3.8 KiB
C#

using Evergreen.Health.Application.Health.Devices.Contracts;
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
using Evergreen.Health.Application.Health.Models;
using Evergreen.Health.Application.Health.Scenes;
namespace Evergreen.Health.Application.Health.Devices;
/// <summary>共用归属校验和路由;标准能力处理普通参数,专属模块保留自己的执行与场景逻辑。</summary>
public sealed class DeviceParameterController
{
private readonly DeviceModuleRegistry _modules;
private readonly DeviceActionRunner _runner;
private readonly Dictionary<string, IStandardDeviceControl> _standard = new(StringComparer.OrdinalIgnoreCase);
public DeviceParameterController(DeviceModuleRegistry modules, DeviceActionRunner runner,
IEnumerable<IStandardDeviceControl> standard)
{
(_modules, _runner) = (modules, runner);
foreach (var adapter in standard)
foreach (var key in adapter.ParameterKeys)
if (!_standard.TryAdd(key, adapter)) throw new InvalidOperationException($"标准设备参数重复:{key}");
}
public async Task<DeviceCommand> ExecuteAsync(HomeAssistantDiscoveredDeviceOutput device,
IReadOnlyDictionary<string, HomeAssistantState> states, string parameterType, object? value,
string? requestedEntityId, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
IHealthDeviceModule? module;
DeviceCommand command;
if (_standard.TryGetValue(parameterType, out var adapter))
{
var primary = device.PrimaryControlEntityId;
if (string.IsNullOrWhiteSpace(primary) || !device.Entities.Any(entity =>
string.Equals(entity.EntityId, primary, StringComparison.OrdinalIgnoreCase)) ||
!states.TryGetValue(primary, out var state))
throw new InvalidOperationException("设备主控实体未确认");
command = adapter.Build(device, state, parameterType, value);
module = _modules.ForEntities(device.Entities.Select(entity => entity.EntityId));
// 附属灯不能借用另一主控的执行钩子;仅复用该实例所属的专属模块。
if (module is not null && !string.Equals(module.PrimaryEntityId, command.EntityId, StringComparison.OrdinalIgnoreCase))
module = null;
}
else
{
module = _modules.ForParameter(parameterType);
command = module.BuildParameterCommand(parameterType, value);
}
if (string.IsNullOrWhiteSpace(command.EntityId))
throw new InvalidOperationException("该参数尚未配置 Home Assistant 实体");
if (!string.IsNullOrWhiteSpace(requestedEntityId) &&
!string.Equals(requestedEntityId, command.EntityId, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("请求实体与该参数的受信任实体不匹配");
if (!device.Entities.Any(entity => string.Equals(entity.EntityId, command.EntityId, StringComparison.OrdinalIgnoreCase)))
throw new InvalidOperationException("该参数不属于指定设备");
if (module?.ParameterUsesIndependentGateway(command) != true &&
(!states.TryGetValue(command.EntityId, out var current) || !DeviceActionRunner.IsAvailable(current)))
throw new InvalidOperationException("目标参数实体当前离线或不可用");
if (module is not null) await module.ExecuteParameterAsync(command, cancellationToken);
else
{
await _runner.SendAsync(command, cancellationToken);
if (command.ExpectedState is not null) await _runner.VerifyParameterAsync(command, cancellationToken);
}
return command;
}
}