This commit is contained in:
fuenmao
2026-11-12 15:45:52 +08:00
parent 61e25175db
commit 718b138033
30 changed files with 1511 additions and 99 deletions
@@ -46,6 +46,14 @@ public sealed class HomeAssistantEntityOptions
public string FragranceDuration { get; set; } = string.Empty;
public string FragranceInterval { get; set; } = string.Empty;
public string FragranceQuickRelease { get; set; } = string.Empty;
public string WakeFragranceSwitch { get; set; } = string.Empty;
public string WakeFragranceLight { get; set; } = string.Empty;
public string WakeFragranceDuration { get; set; } = string.Empty;
public string WakeFragranceInterval { get; set; } = string.Empty;
public string WakeFragranceQuickRelease { get; set; } = string.Empty;
public string WakeFragranceAutoRelease { get; set; } = string.Empty;
public string WakeFragranceAutoLight { get; set; } = string.Empty;
public string WakeFragranceLightAutoOff { get; set; } = string.Empty;
public string FragranceAutoRelease { get; set; } = string.Empty;
public string FragranceAutoLight { get; set; } = string.Empty;
public string FragranceLightAutoOff { get; set; } = string.Empty;
@@ -0,0 +1,21 @@
using Evergreen.Health.Application.Health.Sleep;
namespace Evergreen.Health.Application.Health.Integrations.Ib43;
public sealed class Ib43AutomationExecutionLog(IServiceScopeFactory scopes) : IBedPresenceExecutionLog
{
public async Task<string?> BeginAsync(string bindingId, long userId, string action, string target,
DateTimeOffset triggeredAt, CancellationToken token)
{
using var scope = scopes.CreateScope();
return await scope.ServiceProvider.GetRequiredService<Ib43DataRepository>()
.BeginAutomation(bindingId, userId, action, target, triggeredAt.ToUnixTimeMilliseconds(), token);
}
public async Task CompleteAsync(string id, string result, DateTimeOffset completedAt)
{
using var scope = scopes.CreateScope();
await scope.ServiceProvider.GetRequiredService<Ib43DataRepository>()
.CompleteAutomation(id, result, completedAt.ToUnixTimeMilliseconds());
}
}
@@ -31,6 +31,26 @@ public sealed class Ib43DataRepository(ISqlSugarClient db)
await db.Queryable<HealthIb43Binding>().Where(b => b.Id == id && b.UserId == userId).FirstAsync()
?? throw new AppFriendlyException("床位绑定不存在或无权访问", null) { StatusCode = 404 };
public Task<List<HealthIb43AutomationExecution>> AutomationHistory(long userId, string bindingId) =>
db.Queryable<HealthIb43AutomationExecution>().Where(row => row.UserId == userId && row.BindingId == bindingId)
.OrderBy(row => row.TriggeredAtMs, OrderByType.Desc).Take(20).ToListAsync();
public async Task<string?> BeginAutomation(string bindingId, long userId, string action, string target, long triggeredAtMs, CancellationToken token)
{
token.ThrowIfCancellationRequested();
if (!await UserEnabled(userId) || !await db.Queryable<HealthIb43Binding>().AnyAsync(b => b.Id == bindingId &&
b.UserId == userId && b.Simulation && b.EndedAtMs == null && b.StartedAtMs <= triggeredAtMs)) return null;
var record = new HealthIb43AutomationExecution
{ BindingId = bindingId, UserId = userId, ActionKey = action, TargetLabel = target, TriggeredAtMs = triggeredAtMs };
token.ThrowIfCancellationRequested();
await db.Insertable(record).ExecuteCommandAsync();
return record.Id;
}
public Task<int> CompleteAutomation(string id, string result, long completedAtMs) => db.Updateable<HealthIb43AutomationExecution>()
.SetColumns(row => new HealthIb43AutomationExecution { Result = result, CompletedAtMs = completedAtMs })
.Where(row => row.Id == id && row.CompletedAtMs == null).ExecuteCommandAsync();
public async Task Assign(HealthIb43Binding binding)
{
if (await db.Queryable<HealthIb43Binding>().AnyAsync(b => b.ActiveSlot == binding.ActiveSlot))
@@ -61,7 +61,13 @@ public sealed class TcmMusicTransitionService : IHostedService, ITcmMusicRuntime
tracks is not null && tracks.Count > 0 &&
tracks.All(track => IsValidTrack(track));
public async Task StartForConstitutionAsync(string constitutionCode, CancellationToken cancellationToken)
public Task StartForConstitutionAsync(string constitutionCode, CancellationToken cancellationToken) =>
StartAsync(constitutionCode, onlyIfIdle: false, cancellationToken);
internal Task<bool> TryStartIdlePlaybackAsync(string constitutionCode, CancellationToken cancellationToken) =>
StartAsync(constitutionCode, onlyIfIdle: true, cancellationToken);
private async Task<bool> StartAsync(string constitutionCode, bool onlyIfIdle, CancellationToken cancellationToken)
{
if (!HasPlayableTracks(constitutionCode))
throw new InvalidOperationException($"{constitutionCode} 的体质音乐 OSS URL 尚未配置完整");
@@ -69,12 +75,14 @@ public sealed class TcmMusicTransitionService : IHostedService, ITcmMusicRuntime
await _operationLock.WaitAsync(cancellationToken);
try
{
if (onlyIfIdle && _activeTask is { IsCompleted: false }) return false;
await CancelCurrentLoopAsync();
await _miAirX.SetVolumeAsync(DemoStartVolume, cancellationToken);
await _miAirX.PlayUrlAsync(tracks[0].Url, cancellationToken);
UpdateStatus(true, DemoStartVolume, "体质音乐正在自动渐弱");
_activeCancellation = new CancellationTokenSource();
_activeTask = RunFadeAsync(tracks, constitutionCode, _activeCancellation.Token);
return true;
}
finally { _operationLock.Release(); }
}