This commit is contained in:
fuenmao
2026-09-21 16:52:27 +08:00
parent 54d5c4fda3
commit 8c922a3c7f
8 changed files with 496 additions and 0 deletions
@@ -0,0 +1,63 @@
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
namespace Evergreen.Health.Application.Health.Integrations.HomeAssistant;
public sealed class HomeAssistantClient
{
private readonly HttpClient _httpClient;
private readonly HomeAssistantOptions _options;
public HomeAssistantClient(HttpClient httpClient, IOptions<HomeAssistantOptions> options)
{
_httpClient = httpClient;
_options = options.Value;
_httpClient.Timeout = TimeSpan.FromSeconds(Math.Clamp(_options.TimeoutSeconds, 3, 60));
}
public async Task<IReadOnlyList<HomeAssistantState>> GetStatesAsync(CancellationToken cancellationToken = default)
{
using var request = CreateRequest(HttpMethod.Get, "api/states");
using var response = await _httpClient.SendAsync(request, cancellationToken);
await EnsureSuccessAsync(response, cancellationToken);
return await response.Content.ReadFromJsonAsync<List<HomeAssistantState>>(cancellationToken: cancellationToken) ?? [];
}
public async Task CallServiceAsync(
string domain,
string service,
IReadOnlyDictionary<string, object?> data,
CancellationToken cancellationToken = default)
{
using var request = CreateRequest(HttpMethod.Post, $"api/services/{domain}/{service}");
request.Content = JsonContent.Create(data);
using var response = await _httpClient.SendAsync(request, cancellationToken);
await EnsureSuccessAsync(response, cancellationToken);
}
private HttpRequestMessage CreateRequest(HttpMethod method, string path)
{
if (!_options.Enabled)
throw new InvalidOperationException("Home Assistant 接入未启用");
if (string.IsNullOrWhiteSpace(_options.BaseUrl))
throw new InvalidOperationException("Home Assistant 地址未配置");
if (string.IsNullOrWhiteSpace(_options.AccessToken))
throw new InvalidOperationException("Home Assistant 访问令牌未配置");
var baseUrl = _options.BaseUrl.TrimEnd('/') + "/";
var request = new HttpRequestMessage(method, new Uri(new Uri(baseUrl), path));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return request;
}
private static async Task EnsureSuccessAsync(HttpResponseMessage response, CancellationToken cancellationToken)
{
if (response.IsSuccessStatusCode) return;
var body = await response.Content.ReadAsStringAsync(cancellationToken);
var message = body.Length > 300 ? body[..300] : body;
throw new HttpRequestException($"Home Assistant 返回 {(int)response.StatusCode}: {message}");
}
}
@@ -0,0 +1,33 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Evergreen.Health.Application.Health.Integrations.HomeAssistant;
public sealed class HomeAssistantState
{
[JsonPropertyName("entity_id")]
public string EntityId { get; set; } = string.Empty;
[JsonPropertyName("state")]
public string State { get; set; } = string.Empty;
[JsonPropertyName("attributes")]
public JsonElement Attributes { get; set; }
[JsonPropertyName("last_updated")]
public DateTimeOffset? LastUpdated { get; set; }
public string? GetStringAttribute(string name)
{
if (!Attributes.TryGetProperty(name, out var value)) return null;
return value.ValueKind == JsonValueKind.String ? value.GetString() : value.ToString();
}
}
public sealed class HomeAssistantActionResult
{
public string Name { get; set; } = string.Empty;
public string EntityId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
}
@@ -0,0 +1,43 @@
using Furion.ConfigurableOptions;
namespace Evergreen.Health.Application.Health.Integrations.HomeAssistant;
public sealed class HomeAssistantOptions : IConfigurableOptions
{
public bool Enabled { get; set; } = true;
public string BaseUrl { get; set; } = string.Empty;
public string AccessToken { get; set; } = string.Empty;
public int TimeoutSeconds { get; set; } = 15;
public HomeAssistantEntityOptions Entities { get; set; } = new();
public HomeAssistantSleepSceneOptions SleepScene { get; set; } = new();
}
public sealed class HomeAssistantEntityOptions
{
public string Dehumidifier { get; set; } = string.Empty;
public string DehumidifierFanLevel { get; set; } = string.Empty;
public string DehumidifierUv { get; set; } = string.Empty;
public string FragranceSwitch { get; set; } = string.Empty;
public string FragranceLight { get; set; } = string.Empty;
public string AmbientLight { get; set; } = string.Empty;
public string Speaker { get; set; } = string.Empty;
public string SpeakerSleepMode { get; set; } = string.Empty;
public string Temperature { get; set; } = string.Empty;
public string Humidity { get; set; } = string.Empty;
public string Pm25 { get; set; } = string.Empty;
public string Co2 { get; set; } = string.Empty;
public string Formaldehyde { get; set; } = string.Empty;
public string Tvoc { get; set; } = string.Empty;
}
public sealed class HomeAssistantSleepSceneOptions
{
public int TargetHumidity { get; set; } = 55;
public string DehumidifierMode { get; set; } = "睡眠";
public string FanLevel { get; set; } = "1挡";
public int LightBrightnessPercent { get; set; } = 20;
public int LightColorTemperatureKelvin { get; set; } = 2700;
public bool EnableFragrance { get; set; } = true;
public bool EnableSpeakerSleepMode { get; set; }
public bool EnableUv { get; set; }
}