67 lines
3.8 KiB
C#
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;
|
|
}
|
|
}
|