This commit is contained in:
fuenmao
2026-09-24 16:12:47 +08:00
parent 6a622c9bd5
commit 973efa34cc
2 changed files with 47 additions and 78 deletions
@@ -123,15 +123,6 @@ public sealed class HomeAssistantLightTransitionOutput
public string Message { get; set; } = string.Empty;
}
public sealed class HomeAssistantSpeakerDemoOutput
{
public string AudioUrl { get; set; } = string.Empty;
public bool SpeakerOnline { get; set; }
public bool DirectPlaybackSupported { get; set; }
public bool CommandAccepted { get; set; }
public string Message { get; set; } = string.Empty;
}
public sealed class HomeAssistantSetParameterInput
{
public string DeviceId { get; set; } = string.Empty;
@@ -14,8 +14,6 @@ namespace Evergreen.Health.Application.Health.Services;
public sealed class HealthHomeAssistantService : IDynamicApiController, ITransient
{
private const string HealthUserIdClaim = "health_user_id";
private const int PlayMediaFeature = 512;
private const string DemoAudioUrl = "https://oss.myzxjgt.com/App.Live/uploads/%E9%9F%B3%E9%A2%91/2026-05-29/57a6a64d8dfa4a1bb876a15bb8fdfd24.mp3";
private readonly HomeAssistantClient _homeAssistant;
private readonly HomeAssistantLightTransitionService _lightTransition;
private readonly HomeAssistantRegistryService _registryService;
@@ -73,69 +71,6 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
catch (Exception exception) { return new HomeAssistantLightTransitionOutput { Success = false, Message = exception.Message }; }
}
[Authorize]
[HttpGet("/api/health/home-assistant/speaker/demo")]
public async Task<HomeAssistantSpeakerDemoOutput> GetSpeakerDemo(CancellationToken cancellationToken)
{
var result = new HomeAssistantSpeakerDemoOutput { AudioUrl = DemoAudioUrl };
try
{
var entityId = _options.Entities.Speaker;
if (string.IsNullOrWhiteSpace(entityId))
{
result.Message = "尚未配置音箱实体";
return result;
}
var state = (await _homeAssistant.GetStatesAsync(cancellationToken))
.FirstOrDefault(item => string.Equals(item.EntityId, entityId, StringComparison.OrdinalIgnoreCase));
result.SpeakerOnline = state is not null && IsAvailable(state);
if (!result.SpeakerOnline)
{
result.Message = "音箱当前离线或不可用";
return result;
}
var features = (long)(state!.GetDoubleAttribute("supported_features") ?? 0);
result.DirectPlaybackSupported = (features & PlayMediaFeature) != 0;
result.Message = result.DirectPlaybackSupported
? "音箱声明支持指定音频播放,可以尝试 OSS 示例音频"
: "当前 Xiaomi Home 音箱实体不支持播放指定音频;OSS 链接可在浏览器试听,但暂不能投放到音箱";
}
catch (Exception exception)
{
result.Message = $"音箱能力检测失败:{exception.Message}";
}
return result;
}
[Authorize]
[HttpPost("/api/health/home-assistant/speaker/play-demo")]
public async Task<HomeAssistantSpeakerDemoOutput> PlaySpeakerDemo(CancellationToken cancellationToken)
{
var result = await GetSpeakerDemo(cancellationToken);
if (!result.SpeakerOnline || !result.DirectPlaybackSupported) return result;
try
{
await _homeAssistant.CallServiceAsync("media_player", "play_media", new Dictionary<string, object?>
{
["entity_id"] = _options.Entities.Speaker,
["media_content_id"] = DemoAudioUrl,
["media_content_type"] = "music"
}, cancellationToken);
result.CommandAccepted = true;
result.Message = "播放指令已发送,请确认音箱是否实际出声;接口接受不等于播放成功";
}
catch (Exception exception)
{
result.Message = $"音箱未接受播放指令:{exception.Message}";
}
return result;
}
[Authorize]
[HttpGet("/api/health/home-assistant/status")]
public async Task<HomeAssistantStatusOutput> GetStatus(CancellationToken cancellationToken)
@@ -834,6 +769,42 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
new Dictionary<string, object?> { ["option"] = mattressFirmness }, cancellationToken);
}
}
// 软硬度调节可能占用床垫气泵;等这些指令完成后,最后启动按摩。
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
await RunAction(states, results, "停止床垫左侧按摩", entities.MattressLeftMassagePart, "select", "select_option",
new Dictionary<string, object?> { ["option"] = "无" }, cancellationToken);
await RunAction(states, results, "设置床垫轻柔按摩", entities.MattressMassageLevel, "select", "select_option",
new Dictionary<string, object?> { ["option"] = "轻柔" }, cancellationToken);
const string rightMassageActionName = "开启床垫右侧全身按摩";
await RunAction(states, results, rightMassageActionName, entities.MattressRightMassagePart, "select", "select_option",
new Dictionary<string, object?> { ["option"] = "全身" }, cancellationToken);
// HA 接受指令不等于设备保持该状态;此前曾在两秒后自动复位为“无”。
var rightMassageAction = results.LastOrDefault(item => item.Name == rightMassageActionName);
if (rightMassageAction?.Status == "Succeeded")
{
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
try
{
var currentStates = await _homeAssistant.GetStatesAsync(cancellationToken);
var currentRightMassage = currentStates.FirstOrDefault(item =>
string.Equals(item.EntityId, entities.MattressRightMassagePart, StringComparison.OrdinalIgnoreCase));
if (currentRightMassage?.State != "全身")
{
rightMassageAction.Status = "Failed";
rightMassageAction.Message = $"指令已发送,但右侧按摩未保持全身模式(当前:{currentRightMassage?.State ?? "未读取到状态"})";
}
else
{
rightMassageAction.Message = "已确认右侧全身按摩保持开启";
}
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
rightMassageAction.Status = "Failed";
rightMassageAction.Message = $"指令已发送,但无法确认右侧按摩状态:{exception.Message}";
}
}
}
}
@@ -851,6 +822,10 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
await RunAction(states, results, "关闭音箱睡眠模式", entities.SpeakerSleepMode, "switch", "turn_off", null, cancellationToken);
await RunAction(states, results, "静音音箱", entities.Speaker, "media_player", "volume_mute",
new Dictionary<string, object?> { ["is_volume_muted"] = true }, cancellationToken);
await RunAction(states, results, "关闭床垫右侧按摩", entities.MattressRightMassagePart, "select", "select_option",
new Dictionary<string, object?> { ["option"] = "无" }, cancellationToken);
await RunAction(states, results, "关闭床垫左侧按摩", entities.MattressLeftMassagePart, "select", "select_option",
new Dictionary<string, object?> { ["option"] = "无" }, cancellationToken);
if (_options.SleepScene.EnableMattress)
{
await RunAction(states, results, "关闭床垫左侧加热", entities.MattressLeftHeatingLevel, "number", "set_value",
@@ -1466,12 +1441,14 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
DeviceName = "8H AI智能好睡眠床垫X",
Category = nameof(HealthDeviceCategory.Sleep),
EntityId = _options.Entities.MattressLeftHeatingLevel,
Action = "按天气和 BMI 调节床垫",
Detail = $"左右加热 {heatingLevel} 档 · 软硬度{firmness ?? "待填写 BMI"}",
Action = "按天气和 BMI 调节床垫并开启右侧按摩",
Detail = $"左右加热 {heatingLevel} 档 · 软硬度{firmness ?? "待填写 BMI"} · 右侧全身轻柔按摩",
Parameters = new()
{
["heating_level"] = heatingLevel,
["firmness"] = firmness ?? "未自动调节"
["firmness"] = firmness ?? "未自动调节",
["right_massage_part"] = "全身",
["massage_level"] = "轻柔"
}
});
}
@@ -1488,5 +1465,6 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
}
private static bool IsAvailable(HomeAssistantState state)
=> state.State is not ("unavailable" or "unknown");
=> state.State != "unavailable" &&
(state.State != "unknown" || state.EntityId.StartsWith("button.", StringComparison.OrdinalIgnoreCase));
}