1111
This commit is contained in:
+62
-2
@@ -155,7 +155,20 @@ public sealed class HomeAssistantLightTransitionService : IHostedService
|
||||
var light = await ReadAvailableLightAsync(cancellationToken);
|
||||
if (!string.Equals(light.State, "on", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SetStatus(false, false, mode, 0, mode == "wake" ? WakeTargetPercent : 0, "灯已从其他位置关闭,渐变已停止");
|
||||
if (mode == "sleep")
|
||||
{
|
||||
try
|
||||
{
|
||||
await StopMattressMassageAsync(cancellationToken);
|
||||
SetStatus(true, false, mode, 0, 0, "灯已关闭,床垫按摩已停止");
|
||||
}
|
||||
catch (Exception exception) when (exception is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning(exception, "灯已关闭,但床垫按摩停止失败");
|
||||
SetStatus(false, false, mode, 0, 0, exception.Message);
|
||||
}
|
||||
}
|
||||
else SetStatus(false, false, mode, 0, WakeTargetPercent, "灯已从其他位置关闭,渐变已停止");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -178,7 +191,17 @@ public sealed class HomeAssistantLightTransitionService : IHostedService
|
||||
{
|
||||
["entity_id"] = LightEntityId
|
||||
}, cancellationToken);
|
||||
SetStatus(true, false, mode, 0, 0, "助眠渐暗已完成,灯光已关闭");
|
||||
SetStatus(true, false, mode, 0, 0, "灯光已关闭,正在停止床垫按摩");
|
||||
try
|
||||
{
|
||||
await StopMattressMassageAsync(cancellationToken);
|
||||
SetStatus(true, false, mode, 0, 0, "助眠渐暗已完成,灯光与床垫按摩已关闭");
|
||||
}
|
||||
catch (Exception exception) when (exception is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning(exception, "助眠渐暗已完成,但床垫按摩停止失败");
|
||||
SetStatus(false, false, mode, 0, 0, exception.Message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -204,6 +227,43 @@ public sealed class HomeAssistantLightTransitionService : IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StopMattressMassageAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var massageEntities = new[]
|
||||
{
|
||||
_options.Entities.MattressLeftMassagePart,
|
||||
_options.Entities.MattressRightMassagePart
|
||||
}.Where(entityId => !string.IsNullOrWhiteSpace(entityId)).Distinct().ToArray();
|
||||
var failures = new List<string>();
|
||||
foreach (var entityId in massageEntities)
|
||||
{
|
||||
try
|
||||
{
|
||||
var confirmed = false;
|
||||
for (var attempt = 0; attempt < 2 && !confirmed; attempt++)
|
||||
{
|
||||
await _homeAssistant.CallServiceAsync("select", "select_option", new Dictionary<string, object?>
|
||||
{
|
||||
["entity_id"] = entityId,
|
||||
["option"] = "无"
|
||||
}, cancellationToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
|
||||
var current = (await _homeAssistant.GetStatesAsync(cancellationToken))
|
||||
.FirstOrDefault(item => string.Equals(item.EntityId, entityId, StringComparison.OrdinalIgnoreCase));
|
||||
confirmed = current?.State == "无";
|
||||
}
|
||||
if (!confirmed) failures.Add(entityId);
|
||||
}
|
||||
catch (Exception exception) when (exception is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning(exception, "灯光熄灭后未能关闭床垫按摩 {EntityId}", entityId);
|
||||
failures.Add(entityId);
|
||||
}
|
||||
}
|
||||
if (failures.Count > 0)
|
||||
throw new InvalidOperationException($"灯光已关闭,但床垫按摩未确认关闭:{string.Join("、", failures)}");
|
||||
}
|
||||
|
||||
private async Task StopCurrentAsync()
|
||||
{
|
||||
if (_activeCancellation is null) return;
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Evergreen.Health.Application.Health.Integrations.MiAirX;
|
||||
|
||||
public sealed class TcmMusicOptions : IConfigurableOptions
|
||||
{
|
||||
public int StartVolume { get; set; } = 60;
|
||||
public int StartVolume { get; set; } = 50;
|
||||
public int FadeStepSeconds { get; set; } = 5;
|
||||
public Dictionary<string, List<TcmMusicTrackOptions>> Tracks { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
private readonly TcmMusicOptions _options;
|
||||
private readonly ILogger<TcmMusicTransitionService> _logger;
|
||||
private readonly SemaphoreSlim _operationLock = new(1, 1);
|
||||
private readonly object _statusLock = new();
|
||||
private TcmMusicTransitionOutput _status = new();
|
||||
private CancellationTokenSource? _activeCancellation;
|
||||
private Task? _activeTask;
|
||||
|
||||
@@ -28,6 +30,18 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public TcmMusicTransitionOutput GetStatus()
|
||||
{
|
||||
lock (_statusLock)
|
||||
return new TcmMusicTransitionOutput
|
||||
{
|
||||
Running = _status.Running,
|
||||
HasVolume = _status.HasVolume,
|
||||
VolumePercent = _status.VolumePercent,
|
||||
Message = _status.Message
|
||||
};
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try { await StopMusicAsync(cancellationToken); }
|
||||
@@ -56,6 +70,7 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
await CancelCurrentLoopAsync();
|
||||
await _miAirX.SetVolumeAsync(_options.StartVolume, cancellationToken);
|
||||
await _miAirX.PlayUrlAsync(tracks[0].Url, cancellationToken);
|
||||
UpdateStatus(true, _options.StartVolume, "体质音乐正在自动渐弱");
|
||||
_activeCancellation = new CancellationTokenSource();
|
||||
_activeTask = RunFadeAsync(tracks, constitutionCode, _activeCancellation.Token);
|
||||
}
|
||||
@@ -69,6 +84,7 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
{
|
||||
await CancelCurrentLoopAsync();
|
||||
await _miAirX.PlayAsync(cancellationToken);
|
||||
UpdateStatus(false, null, "正在播放测试音乐");
|
||||
}
|
||||
finally { _operationLock.Release(); }
|
||||
}
|
||||
@@ -81,6 +97,7 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
// 手动调音量接管当前播放,避免下一次自动渐弱立刻覆盖用户选择。
|
||||
await CancelCurrentLoopAsync();
|
||||
await _miAirX.SetVolumeAsync(volume, cancellationToken);
|
||||
UpdateStatus(false, volume, "音量已手动设置");
|
||||
}
|
||||
finally { _operationLock.Release(); }
|
||||
}
|
||||
@@ -92,6 +109,7 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
{
|
||||
await CancelCurrentLoopAsync();
|
||||
await _miAirX.StopAsync(cancellationToken);
|
||||
UpdateStatus(false, null, "音乐已停止");
|
||||
}
|
||||
finally { _operationLock.Release(); }
|
||||
}
|
||||
@@ -116,12 +134,14 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
{
|
||||
currentVolume--;
|
||||
await _miAirX.SetVolumeAsync(currentVolume, cancellationToken);
|
||||
UpdateStatus(true, currentVolume, "体质音乐正在自动渐弱");
|
||||
nextFadeSecond += _options.FadeStepSeconds;
|
||||
}
|
||||
|
||||
if (currentVolume == 0)
|
||||
{
|
||||
await _miAirX.StopAsync(cancellationToken);
|
||||
UpdateStatus(false, 0, "音量已降至 0,音乐已停止");
|
||||
_logger.LogInformation("体质 {ConstitutionCode} 的助眠音乐已渐弱至 0 并停止", constitutionCode);
|
||||
return;
|
||||
}
|
||||
@@ -140,6 +160,7 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
_logger.LogWarning(exception, "体质音乐渐弱或续播中断");
|
||||
try { await _miAirX.StopAsync(CancellationToken.None); }
|
||||
catch (Exception stopException) { _logger.LogWarning(stopException, "体质音乐中断后停止音箱失败"); }
|
||||
UpdateStatus(false, null, "自动渐弱中断,请查看后端日志");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +174,21 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
cancellation.Cancel();
|
||||
try { if (task is not null) await task; }
|
||||
finally { cancellation.Dispose(); }
|
||||
UpdateStatus(false, null, "自动渐弱已停止");
|
||||
}
|
||||
|
||||
private void UpdateStatus(bool running, int? volume, string message)
|
||||
{
|
||||
lock (_statusLock)
|
||||
{
|
||||
_status.Running = running;
|
||||
if (volume.HasValue)
|
||||
{
|
||||
_status.HasVolume = true;
|
||||
_status.VolumePercent = volume.Value;
|
||||
}
|
||||
_status.Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidTrack(TcmMusicTrackOptions track)
|
||||
@@ -160,3 +196,11 @@ public sealed class TcmMusicTransitionService : IHostedService
|
||||
Uri.TryCreate(track.Url, UriKind.Absolute, out var uri) &&
|
||||
uri.Scheme is "http" or "https";
|
||||
}
|
||||
|
||||
public sealed class TcmMusicTransitionOutput
|
||||
{
|
||||
public bool Running { get; set; }
|
||||
public bool HasVolume { get; set; }
|
||||
public int VolumePercent { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user