86 lines
4.0 KiB
C#
86 lines
4.0 KiB
C#
using Evergreen.Health.Application.Health.Models;
|
|
using Evergreen.Health.Application.Health.Scenes;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Evergreen.Health.Application.Health.Integrations.HomeAssistant;
|
|
|
|
/// <summary>管理明确配置的场景灯;共享单灯渐变实现,但不共享亮度、计时或取消状态。</summary>
|
|
public sealed class HomeAssistantLightTransitionService : IHostedService, ILightTransitionRuntime
|
|
{
|
|
private readonly LightTransitionWorker _primary;
|
|
private readonly Dictionary<string, LightTransitionWorker> _lights = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public HomeAssistantLightTransitionService(IHomeAssistantGateway gateway, LightOffDispatcher lightOff,
|
|
IOptions<HomeAssistantOptions> options, ILogger<HomeAssistantLightTransitionService> logger,
|
|
SceneLightSafetyState safety, TimeProvider time)
|
|
{
|
|
_primary = new(gateway, lightOff, options, logger, safety, time);
|
|
var primaryId = options.Value.Entities.AmbientLight;
|
|
if (!string.IsNullOrWhiteSpace(primaryId)) _lights.Add(primaryId, _primary);
|
|
foreach (var light in options.Value.AdditionalSleepLights)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(light.Key) || string.IsNullOrWhiteSpace(light.Name) ||
|
|
!light.EntityId.StartsWith("light.", StringComparison.Ordinal))
|
|
throw new InvalidOperationException("额外场景灯需要有效的模块键、名称和 light 实体");
|
|
if (!_lights.TryAdd(light.EntityId, new(gateway, lightOff, options, logger, safety, time, light)))
|
|
throw new InvalidOperationException($"场景灯实体重复配置:{light.EntityId}");
|
|
}
|
|
}
|
|
|
|
public ILightTransitionRuntime ForEntity(string entityId)
|
|
=> ResolveLight(entityId);
|
|
|
|
public Task<HomeAssistantLightTransitionOutput?> StartSleepIfIdleAsync(string entityId, int kelvin, CancellationToken token)
|
|
=> ResolveLight(entityId).StartSleepIfIdleAsync(kelvin, token);
|
|
|
|
private LightTransitionWorker ResolveLight(string entityId)
|
|
=> !string.IsNullOrWhiteSpace(entityId) && _lights.TryGetValue(entityId, out var light) ? light
|
|
: throw new InvalidOperationException("该灯未配置为场景灯");
|
|
|
|
public HomeAssistantLightTransitionOutput GetStatus()
|
|
{
|
|
var output = _primary.GetStatus();
|
|
output.Lights = _lights.Values.Select(light => light.GetStatus()).ToList();
|
|
return output;
|
|
}
|
|
|
|
// 旧助眠/晨起接口只作用于原卧室灯;额外灯由各自场景模块调用。
|
|
public async Task<HomeAssistantLightTransitionOutput> StartSleepAsync(int kelvin, CancellationToken token)
|
|
{
|
|
EnsurePrimaryConfigured();
|
|
await _primary.StartSleepAsync(kelvin, token);
|
|
return GetStatus();
|
|
}
|
|
public async Task<HomeAssistantLightTransitionOutput> StartWakeAsync(CancellationToken token)
|
|
{
|
|
EnsurePrimaryConfigured();
|
|
await _primary.StartWakeAsync(token);
|
|
return GetStatus();
|
|
}
|
|
public async Task<HomeAssistantLightTransitionOutput> CancelPrimaryAsync(CancellationToken token)
|
|
{
|
|
EnsurePrimaryConfigured();
|
|
await _primary.CancelAsync(token);
|
|
return GetStatus();
|
|
}
|
|
|
|
private void EnsurePrimaryConfigured()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(_primary.GetStatus().EntityId)) throw new InvalidOperationException("卧室灯光未配置");
|
|
}
|
|
|
|
// 一键关闭/场景替换必须取消全部灯的计时任务,防止后续 tick 重新点亮灯。
|
|
public async Task<HomeAssistantLightTransitionOutput> CancelAsync(CancellationToken token)
|
|
{
|
|
foreach (var light in _lights.Values) await light.CancelAsync(token);
|
|
return GetStatus();
|
|
}
|
|
public Task StartAsync(CancellationToken token) => Task.CompletedTask;
|
|
public async Task StopAsync(CancellationToken token)
|
|
{
|
|
foreach (var light in _lights.Values) await light.StopAsync(token);
|
|
}
|
|
}
|