111
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user