using Evergreen.Health.Application.Health.Devices.Contracts; using Evergreen.Health.Application.Health.Integrations.HomeAssistant; using Evergreen.Health.Application.Health.Models; using Evergreen.Health.Application.Health.Scenes; using Microsoft.Extensions.Options; using Evergreen.Health.Application.Health.Services; namespace Evergreen.Health.Application.Health.Devices.Mattress; public sealed class MattressModule : HealthDeviceModule, ILightOffHandler { private readonly HomeAssistantOptions _options; private HomeAssistantEntityOptions Entities => _options.Entities; public MattressModule(DeviceActionRunner runner, IOptions options) : base(runner) { _options = options.Value; var modes = new[] { "无", "瑜伽助眠", "智能适配", "智能腰托" }; var parts = new[] { "无", "背部", "腰部", "牵引", "腿部", "全身" }; var parameters = new List { DeviceValues.Number("mattress_left_heating", Entities.MattressLeftHeatingLevel, 0, 4, "床垫左侧加热档位"), DeviceValues.Number("mattress_right_heating", Entities.MattressRightHeatingLevel, 0, 4, "床垫右侧加热档位"), DeviceValues.Select("mattress_left_mode", Entities.MattressLeftMode, modes, "床垫左侧承托模式", true), DeviceValues.Select("mattress_right_mode", Entities.MattressRightMode, modes, "床垫右侧承托模式", true), DeviceValues.Select("mattress_massage_left_part", Entities.MattressLeftMassagePart, parts, "左侧按摩部位", intent: DeviceCommandIntent.StartMassage), DeviceValues.Select("mattress_massage_right_part", Entities.MattressRightMassagePart, parts, "右侧按摩部位", intent: DeviceCommandIntent.StartMassage), DeviceValues.Select("mattress_massage_level", Entities.MattressMassageLevel, ["轻柔", "强劲"], "按摩力度") }; foreach (var (zone, entityId) in Entities.MattressFirmness) parameters.Add(DeviceValues.Select($"mattress_firmness_{zone}", entityId, ["最软", "很软", "稍软", "稍硬", "很硬", "最硬"], $"床垫{ZoneName(zone)}软硬度")); Parameters = parameters; } public override string Key => "mattress"; public override string Category => nameof(HealthDeviceCategory.Sleep); public override string DisplayName => "智能床垫"; public override string PrimaryEntityId => ""; // 床垫没有单一总开关,不能把加热实体冒充开关。 public override IReadOnlyList Parameters { get; } public override async Task ExecuteSleepAsync(DeviceSceneContext context, PersonalizedSceneConfig config, string constitutionCode, CancellationToken cancellationToken) { if (!_options.SleepScene.EnableMattress) return; await context.RunAsync(BuildParameterCommand("mattress_left_heating", config.MattressHeatingLevel), cancellationToken); await context.RunAsync(BuildParameterCommand("mattress_right_heating", config.MattressHeatingLevel), cancellationToken); var firmness = MattressComfortService.FirmnessName(config.MattressLeftFirmness); if (firmness is null) context.Results.Add(new() { Name = "按 BMI 调节床垫软硬度", Status = "Skipped", Message = "健康档案缺少身高或体重,未自动修改软硬度" }); else foreach (var zone in Entities.MattressFirmness.Keys) await context.RunAsync(BuildParameterCommand($"mattress_firmness_{zone}", firmness), cancellationToken); var modeCommand = BuildParameterCommand("mattress_left_mode", "智能适配"); await context.RunAsync(modeCommand, cancellationToken); if (context.Results.Last().Status == "Succeeded") await Runner.VerifyBatchAsync([modeCommand], context.Results, cancellationToken); // 气泵调节结束后再启动按摩,保持真实设备所需的顺序。 await Runner.DelayAsync(TimeSpan.FromSeconds(2), cancellationToken); await context.RunAsync(BuildParameterCommand("mattress_massage_left_part", "无"), cancellationToken); await context.RunAsync(BuildParameterCommand("mattress_massage_level", "轻柔"), cancellationToken); var massage = BuildParameterCommand("mattress_massage_right_part", "全身") with { ExpectedState = "全身" }; await context.RunAsync(massage, cancellationToken); if (context.Results.Last().Status == "Succeeded") { await Runner.DelayAsync(TimeSpan.FromSeconds(3), cancellationToken); await Runner.VerifyBatchAsync([massage], context.Results, cancellationToken); } } public IEnumerable MassageOffCommands() => [ new("关闭床垫右侧按摩", Entities.MattressRightMassagePart, "select", "select_option", new Dictionary { ["option"] = "无" }, "无"), new("关闭床垫左侧按摩", Entities.MattressLeftMassagePart, "select", "select_option", new Dictionary { ["option"] = "无" }, "无") ]; public override IEnumerable GetShutdownCommands() { foreach (var command in MassageOffCommands()) yield return command; // 未参与自动启动不等于无需关闭:手动开启的加热也要随一键关闭停止。 yield return new("关闭床垫左侧加热", Entities.MattressLeftHeatingLevel, "number", "set_value", new Dictionary { ["value"] = 0 }, "0"); yield return new("关闭床垫右侧加热", Entities.MattressRightHeatingLevel, "number", "set_value", new Dictionary { ["value"] = 0 }, "0"); } public async Task HandleAsync(CancellationToken cancellationToken) { var commands = MassageOffCommands().Where(command => !string.IsNullOrWhiteSpace(command.EntityId)).ToArray(); var results = new List(); foreach (var command in commands) await Runner.RunAsync(new Dictionary(), results, command, cancellationToken, false); await Runner.VerifyBatchAsync(commands, results, cancellationToken); if (results.Any(result => result.Status == "Failed")) throw new InvalidOperationException("床垫按摩未确认关闭:" + string.Join(";", results.Where(result => result.Status == "Failed").Select(result => result.Message))); } public override HomeAssistantScenePresetItem? BuildPreview(PersonalizedSceneConfig config, string constitutionCode) { if (!_options.SleepScene.EnableMattress || !EntityIds.Any()) return null; var firmness = MattressComfortService.FirmnessName(config.MattressLeftFirmness) ?? "未自动调节"; var preview = Preview("按室温和 BMI 调节床垫并开启右侧按摩", $"左右加热 {config.MattressHeatingLevel} 档 · 软硬度{firmness} · 右侧全身轻柔按摩", new() { ["heating_level"] = config.MattressHeatingLevel, ["firmness"] = firmness, ["right_massage_part"] = "全身", ["massage_level"] = "轻柔" }); preview.EntityId = EntityIds.First(); return preview; } public static string ZoneName(string zone) => zone switch { "left_shoulder" => "左肩", "right_shoulder" => "右肩", "left_back" => "左背", "right_back" => "右背", "left_waist" => "左腰", "right_waist" => "右腰", "left_lumbar" => "左腰椎", "right_lumbar" => "右腰椎", "left_hip" => "左臀", "right_hip" => "右臀", "left_leg" => "左腿", "right_leg" => "右腿", _ => zone }; }