111
This commit is contained in:
@@ -123,15 +123,6 @@ public sealed class HomeAssistantLightTransitionOutput
|
|||||||
public string Message { get; set; } = string.Empty;
|
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 sealed class HomeAssistantSetParameterInput
|
||||||
{
|
{
|
||||||
public string DeviceId { get; set; } = string.Empty;
|
public string DeviceId { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ namespace Evergreen.Health.Application.Health.Services;
|
|||||||
public sealed class HealthHomeAssistantService : IDynamicApiController, ITransient
|
public sealed class HealthHomeAssistantService : IDynamicApiController, ITransient
|
||||||
{
|
{
|
||||||
private const string HealthUserIdClaim = "health_user_id";
|
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 HomeAssistantClient _homeAssistant;
|
||||||
private readonly HomeAssistantLightTransitionService _lightTransition;
|
private readonly HomeAssistantLightTransitionService _lightTransition;
|
||||||
private readonly HomeAssistantRegistryService _registryService;
|
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 }; }
|
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]
|
[Authorize]
|
||||||
[HttpGet("/api/health/home-assistant/status")]
|
[HttpGet("/api/health/home-assistant/status")]
|
||||||
public async Task<HomeAssistantStatusOutput> GetStatus(CancellationToken cancellationToken)
|
public async Task<HomeAssistantStatusOutput> GetStatus(CancellationToken cancellationToken)
|
||||||
@@ -834,6 +769,42 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
|
|||||||
new Dictionary<string, object?> { ["option"] = mattressFirmness }, cancellationToken);
|
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.SpeakerSleepMode, "switch", "turn_off", null, cancellationToken);
|
||||||
await RunAction(states, results, "静音音箱", entities.Speaker, "media_player", "volume_mute",
|
await RunAction(states, results, "静音音箱", entities.Speaker, "media_player", "volume_mute",
|
||||||
new Dictionary<string, object?> { ["is_volume_muted"] = true }, cancellationToken);
|
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)
|
if (_options.SleepScene.EnableMattress)
|
||||||
{
|
{
|
||||||
await RunAction(states, results, "关闭床垫左侧加热", entities.MattressLeftHeatingLevel, "number", "set_value",
|
await RunAction(states, results, "关闭床垫左侧加热", entities.MattressLeftHeatingLevel, "number", "set_value",
|
||||||
@@ -1466,12 +1441,14 @@ public sealed class HealthHomeAssistantService : IDynamicApiController, ITransie
|
|||||||
DeviceName = "8H AI智能好睡眠床垫X",
|
DeviceName = "8H AI智能好睡眠床垫X",
|
||||||
Category = nameof(HealthDeviceCategory.Sleep),
|
Category = nameof(HealthDeviceCategory.Sleep),
|
||||||
EntityId = _options.Entities.MattressLeftHeatingLevel,
|
EntityId = _options.Entities.MattressLeftHeatingLevel,
|
||||||
Action = "按天气和 BMI 调节床垫",
|
Action = "按天气和 BMI 调节床垫并开启右侧按摩",
|
||||||
Detail = $"左右加热 {heatingLevel} 档 · 软硬度{firmness ?? "待填写 BMI"}",
|
Detail = $"左右加热 {heatingLevel} 档 · 软硬度{firmness ?? "待填写 BMI"} · 右侧全身轻柔按摩",
|
||||||
Parameters = new()
|
Parameters = new()
|
||||||
{
|
{
|
||||||
["heating_level"] = heatingLevel,
|
["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)
|
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