111111
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
using Evergreen.Health.Application.Health.Devices.Contracts;
|
||||
using Evergreen.Health.Application.Health.Devices.Environment;
|
||||
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
|
||||
using Evergreen.Health.Application.Health.Policies;
|
||||
using Evergreen.Health.Application.Health.Scenes;
|
||||
using Evergreen.Health.Application.Health.Sleep;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Devices.Climate;
|
||||
|
||||
public sealed class BedPresenceClimateHeatAction(ClimateModule climate, IndoorEnvironmentReader environment,
|
||||
DeviceActionRunner runner, IOptions<HomeAssistantOptions> options) : IBedPresenceAction
|
||||
{
|
||||
private const int TargetTemperature = 22;
|
||||
|
||||
public string Key => "climate-heat-start";
|
||||
public string Description => "室温偏低时开启空调制热";
|
||||
public string ConditionLabel => $"室温低于 {TargetTemperature}℃ 且空调关机时,开启制热并设为 {TargetTemperature}℃。";
|
||||
public TimeSpan ExecutionTimeout => TimeSpan.FromSeconds(40);
|
||||
|
||||
public bool SupportsTarget(string entityId) => options.Value.Enabled &&
|
||||
entityId?.StartsWith("climate.", StringComparison.Ordinal) == true && entityId == climate.PrimaryEntityId &&
|
||||
options.Value.Entities.Temperature?.StartsWith("sensor.", StringComparison.Ordinal) == true;
|
||||
|
||||
public async Task<string> ExecuteAsync(string entityId, CancellationToken token)
|
||||
{
|
||||
if (!SupportsTarget(entityId)) return "target-not-configured";
|
||||
var mode = climate.BuildParameterCommand("climate_mode", "heat");
|
||||
var temperature = climate.BuildParameterCommand("climate_temperature", TargetTemperature) with
|
||||
{
|
||||
ExpectedEntityState = "heat"
|
||||
};
|
||||
|
||||
var reason = await runner.SendIfAsync(mode, states => GetStartSkipReason(states, mode, temperature), token);
|
||||
if (reason is not null) return reason;
|
||||
|
||||
try { await runner.VerifyParameterAsync(mode, token); }
|
||||
catch (InvalidOperationException) { return "climate-heat-mode-unconfirmed"; }
|
||||
|
||||
try
|
||||
{
|
||||
reason = await runner.SendIfAsync(temperature, states =>
|
||||
CurrentState(states, entityId)?.State == "heat" ? null : "climate-heat-state-changed", token);
|
||||
if (reason is not null) return reason;
|
||||
await runner.VerifyParameterAsync(temperature, token);
|
||||
return "readback-confirmed";
|
||||
}
|
||||
catch (Exception error) when (error is not OperationCanceledException)
|
||||
{
|
||||
return "climate-heat-target-unconfirmed";
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetStartSkipReason(IReadOnlyList<HomeAssistantState> states, DeviceCommand mode, DeviceCommand temperature)
|
||||
{
|
||||
var sample = environment.Read(states);
|
||||
if (sample.Celsius is not { } celsius)
|
||||
return sample.Issue switch
|
||||
{
|
||||
IndoorTemperatureIssue.Stale => "temperature-stale",
|
||||
IndoorTemperatureIssue.Invalid => "temperature-invalid",
|
||||
_ => "temperature-unavailable"
|
||||
};
|
||||
if (celsius >= TargetTemperature) return "temperature-not-low";
|
||||
|
||||
var state = CurrentState(states, mode.EntityId);
|
||||
if (state is null || !DeviceActionRunner.IsAvailable(state)) return "climate-unavailable";
|
||||
if (state.State != "off") return "climate-not-off";
|
||||
|
||||
// 启动前同时校验制热模式和目标温度。
|
||||
try
|
||||
{
|
||||
mode.ValidateCurrentState!(state);
|
||||
temperature.ValidateCurrentState!(state);
|
||||
return null;
|
||||
}
|
||||
catch (InvalidOperationException) { return "climate-heat-unsupported"; }
|
||||
}
|
||||
|
||||
private static HomeAssistantState? CurrentState(IReadOnlyList<HomeAssistantState> states, string entityId) =>
|
||||
states.FirstOrDefault(state => string.Equals(state.EntityId, entityId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Evergreen.Health.Application.Health.Devices.Environment;
|
||||
using Evergreen.Health.Application.Health.Integrations.HomeAssistant;
|
||||
using Evergreen.Health.Application.Health.Policies;
|
||||
using Evergreen.Health.Application.Health.Sleep;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Evergreen.Health.Application.Health.Devices.Climate;
|
||||
|
||||
public sealed class BedPresenceClimatePreview(IHomeAssistantGateway gateway, IndoorEnvironmentReader environment,
|
||||
IOptions<HomeAssistantOptions> options) : IBedPresenceAction
|
||||
{
|
||||
private HomeAssistantOptions Configuration => options.Value;
|
||||
|
||||
public string Key => "climate-temperature-preview";
|
||||
public string Description => "按室温判断空调模式";
|
||||
public bool PreviewOnly => true;
|
||||
public string ConditionLabel => $"室温高于 {RecommendedRoomRange.Default.Maximum}℃ 时计划制冷,低于 {RecommendedRoomRange.Default.Minimum}℃ 时计划制热。";
|
||||
|
||||
public bool SupportsTarget(string entityId) => Configuration.Enabled &&
|
||||
entityId?.StartsWith("climate.", StringComparison.Ordinal) == true && entityId == Configuration.Entities.Climate &&
|
||||
Configuration.Entities.Temperature?.StartsWith("sensor.", StringComparison.Ordinal) == true;
|
||||
|
||||
public async Task<string> ExecuteAsync(string entityId, CancellationToken token)
|
||||
{
|
||||
if (!SupportsTarget(entityId)) return "target-not-configured";
|
||||
IReadOnlyList<HomeAssistantState> states;
|
||||
try { states = await gateway.GetStatesAsync(token); }
|
||||
catch (Exception error) when (error is not OperationCanceledException)
|
||||
{
|
||||
return "preview-temperature-unavailable";
|
||||
}
|
||||
|
||||
var sample = environment.Read(states);
|
||||
return RoomTemperaturePolicy.Evaluate(sample, RecommendedRoomRange.Default).Mode switch
|
||||
{
|
||||
"cool" => "preview-climate-cool",
|
||||
"heat" => "preview-climate-heat",
|
||||
"off" => "preview-temperature-within-range",
|
||||
_ => sample.Issue switch
|
||||
{
|
||||
IndoorTemperatureIssue.Stale => "preview-temperature-stale",
|
||||
IndoorTemperatureIssue.Invalid => "preview-temperature-invalid",
|
||||
_ => "preview-temperature-unavailable"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user