using Evergreen.Health.Application.Health.Integrations.HomeAssistant; using Evergreen.Health.Application.Health.Scenes; using Evergreen.Health.Application.Health.Sleep; using Microsoft.Extensions.Options; namespace Evergreen.Health.Application.Health.Devices.Curtain; public sealed class BedPresenceCurtainCloseAction(CurtainModule curtain, DeviceActionRunner runner, IOptions options) : IBedPresenceAction { public string Key => "curtain-close"; public string Description => "关闭窗帘"; public TimeSpan ExecutionTimeout => TimeSpan.FromSeconds(55); public bool SupportsTarget(string entityId) => options.Value.Enabled && entityId?.StartsWith("cover.", StringComparison.Ordinal) == true && entityId == curtain.PrimaryEntityId; public async Task ExecuteAsync(string entityId, CancellationToken token) { if (!SupportsTarget(entityId)) return "target-not-configured"; var state = await runner.ReadAvailableStateAsync(entityId, token); if (state.State == "closed") return "already-closed"; if (state.State == "closing") return "already-closing"; if (state.State != "open") return "state-not-open"; var close = curtain.BuildParameterCommand("curtain_action", "close"); var command = close with { ExpectedState = "closed", ValidateCurrentState = current => { close.ValidateCurrentState?.Invoke(current); if (current.State != "open") throw new InvalidOperationException("窗帘状态已改变,跳过关帘"); } }; await runner.SendAsync(command, token); try { // 机械行程需要较长回读时间。沿用窗帘命令的只读回读配置,失败不重发关帘。 await runner.VerifyParameterAsync(command, token, verificationAttempts: 20); return "readback-confirmed"; } catch (InvalidOperationException) { return "sent-readback-unconfirmed"; } } }