111111
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
|
||||
using Evergreen.Health.Application.Health.Sleep;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Devices.Dehumidifier;
|
||||
|
||||
public sealed class BedPresenceDehumidifierPreview(IHomeAssistantGateway gateway, IOptions<HomeAssistantOptions> options,
|
||||
TimeProvider clock) : IBedPresenceAction
|
||||
{
|
||||
private HomeAssistantOptions Configuration => options.Value;
|
||||
private int Threshold => Configuration.SleepScene.TargetHumidity;
|
||||
|
||||
public string Key => "dehumidifier-humidity-preview";
|
||||
public string Description => "湿度偏高时开启除湿机";
|
||||
public bool PreviewOnly => true;
|
||||
public string ConditionLabel => $"室内相对湿度超过 {Threshold}% 时记录开机计划。";
|
||||
|
||||
public bool SupportsTarget(string entityId) => DehumidifierHumidityCondition.SupportsTarget(Configuration, entityId);
|
||||
|
||||
public async Task<string> ExecuteAsync(string entityId, CancellationToken token)
|
||||
{
|
||||
if (!SupportsTarget(entityId)) return "target-not-configured";
|
||||
var states = await gateway.GetStatesAsync(token);
|
||||
var reason = DehumidifierHumidityCondition.SkipReason(states, Configuration, clock.GetUtcNow());
|
||||
return reason is null ? "preview-would-start" : $"preview-{reason}";
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
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.Dehumidifier;
|
||||
|
||||
public sealed class BedPresenceDehumidifierStartAction(DehumidifierModule dehumidifier, DeviceActionRunner runner,
|
||||
IOptions<HomeAssistantOptions> options, TimeProvider clock) : IBedPresenceAction
|
||||
{
|
||||
private HomeAssistantOptions Configuration => options.Value;
|
||||
|
||||
public string Key => "dehumidifier-humidity-start";
|
||||
public string Description => "湿度偏高时开启除湿机";
|
||||
public string ConditionLabel => $"室内相对湿度超过 {Configuration.SleepScene.TargetHumidity}% 且除湿机关机时,发送开机指令。";
|
||||
|
||||
public bool SupportsTarget(string entityId) => entityId == dehumidifier.PrimaryEntityId &&
|
||||
DehumidifierHumidityCondition.SupportsTarget(Configuration, entityId);
|
||||
|
||||
public async Task<string> ExecuteAsync(string entityId, CancellationToken token)
|
||||
{
|
||||
if (!SupportsTarget(entityId)) return "target-not-configured";
|
||||
var command = dehumidifier.BuildStartCommand() with
|
||||
{
|
||||
ExpectedState = "on",
|
||||
RetryOnVerificationMismatch = false
|
||||
};
|
||||
var reason = await runner.SendIfAsync(command, states => GetSkipReason(states, entityId), token);
|
||||
if (reason is not null) return reason;
|
||||
|
||||
try
|
||||
{
|
||||
await runner.VerifyParameterAsync(command, token);
|
||||
return "readback-confirmed";
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return "sent-readback-unconfirmed";
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetSkipReason(IReadOnlyList<HomeAssistantState> states, string entityId)
|
||||
{
|
||||
var reason = DehumidifierHumidityCondition.SkipReason(states, Configuration, clock.GetUtcNow());
|
||||
if (reason is not null) return reason;
|
||||
|
||||
var state = states.FirstOrDefault(item => string.Equals(item.EntityId, entityId, StringComparison.OrdinalIgnoreCase));
|
||||
if (state is null || !DeviceActionRunner.IsAvailable(state)) return "dehumidifier-unavailable";
|
||||
return state.State switch
|
||||
{
|
||||
"off" => null,
|
||||
"on" => "dehumidifier-already-on",
|
||||
_ => "dehumidifier-state-not-off"
|
||||
};
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
|
||||
using Evergreen.Health.Application.Health.Scenes;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Devices.Dehumidifier;
|
||||
|
||||
internal static class DehumidifierHumidityCondition
|
||||
{
|
||||
private static readonly TimeSpan MaxHumidityAge = TimeSpan.FromMinutes(30);
|
||||
|
||||
public static bool SupportsTarget(HomeAssistantOptions configuration, string entityId) =>
|
||||
configuration.Enabled && configuration.SleepScene.TargetHumidity is >= 40 and <= 70 &&
|
||||
entityId?.StartsWith("humidifier.", StringComparison.Ordinal) == true &&
|
||||
entityId == configuration.Entities.Dehumidifier &&
|
||||
configuration.Entities.Humidity?.StartsWith("sensor.", StringComparison.Ordinal) == true;
|
||||
|
||||
public static string? SkipReason(IReadOnlyList<HomeAssistantState> states, HomeAssistantOptions configuration, DateTimeOffset now)
|
||||
{
|
||||
var humidity = states.FirstOrDefault(state =>
|
||||
string.Equals(state.EntityId, configuration.Entities.Humidity, StringComparison.OrdinalIgnoreCase));
|
||||
if (humidity is null || !DeviceActionRunner.IsAvailable(humidity)) return "humidity-unavailable";
|
||||
|
||||
var observedAt = humidity.LastReported ?? humidity.LastUpdated;
|
||||
if (observedAt is null || now - observedAt > MaxHumidityAge || observedAt > now.AddMinutes(1))
|
||||
return "humidity-stale";
|
||||
|
||||
if (humidity.GetStringAttribute("unit_of_measurement") != "%" ||
|
||||
!double.TryParse(humidity.State, NumberStyles.Float, CultureInfo.InvariantCulture, out var percent) ||
|
||||
!double.IsFinite(percent) || percent is < 0 or > 100)
|
||||
return "humidity-invalid";
|
||||
|
||||
return percent > configuration.SleepScene.TargetHumidity ? null : "humidity-normal";
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,12 @@ public sealed class DehumidifierModule : HealthDeviceModule
|
||||
public override string PrimaryEntityId => _entities.Dehumidifier;
|
||||
public override IReadOnlyList<DeviceParameterDefinition> Parameters { get; }
|
||||
|
||||
internal DeviceCommand BuildStartCommand() => new("开启除湿机", PrimaryEntityId, "humidifier", "turn_on");
|
||||
|
||||
public override async Task ExecuteSleepAsync(DeviceSceneContext context, PersonalizedSceneConfig config,
|
||||
string constitutionCode, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.RunAsync(new("开启除湿机", PrimaryEntityId, "humidifier", "turn_on"), cancellationToken);
|
||||
await context.RunAsync(BuildStartCommand(), cancellationToken);
|
||||
await context.RunAsync(BuildParameterCommand("humidity", config.TargetHumidity), cancellationToken);
|
||||
await context.RunAsync(BuildParameterCommand("dehumidifier_mode", config.DehumidifierMode), cancellationToken);
|
||||
await context.RunAsync(BuildParameterCommand("fan_level", config.FanLevel), cancellationToken);
|
||||
|
||||
Reference in New Issue
Block a user