This commit is contained in:
fuenmao
2026-11-13 14:03:25 +08:00
parent 718b138033
commit 9cfd9e24d7
6 changed files with 105 additions and 9 deletions
@@ -35,6 +35,16 @@
"Tvoc": "sensor.mirsz_cn_2027039858_et71_tvoc_density_p_3_8"
},
"OnlyConfiguredDevices": true,
"ManualLightEntityIds": [],
"AdditionalSleepLights": [
{
"Key": "roof-light-strip-1",
"Name": "超薄智能灯带",
"EntityId": "light.hekai1_cn_2224036048_wy0a01_s_2_light",
"SleepEffect": "助眠模式",
"StartSleepOnManualPowerOn": false
}
],
"SleepScene": {
"TargetHumidity": 55,
"DehumidifierMode": "睡眠",
@@ -12,8 +12,14 @@ public sealed class HomeAssistantDeviceCatalog
{
private readonly HomeAssistantOptions _options;
private readonly DeviceModuleRegistry _modules;
private readonly HashSet<string> _manualLightIds;
public HomeAssistantDeviceCatalog(IOptions<HomeAssistantOptions> options, DeviceModuleRegistry modules)
=> (_options, _modules) = (options.Value, modules);
{
(_options, _modules) = (options.Value, modules);
_manualLightIds = _options.ManualLightEntityIds
.Where(id => !string.IsNullOrWhiteSpace(id) && id.StartsWith("light.", StringComparison.Ordinal))
.ToHashSet(StringComparer.OrdinalIgnoreCase);
}
public List<HomeAssistantDiscoveredDeviceOutput> BuildDiscoveredDevices(
HomeAssistantRegistries? registries,
@@ -42,7 +48,8 @@ public sealed class HomeAssistantDeviceCatalog
var entityIds = regEntities.Select(entity => entity.EntityId).ToHashSet(StringComparer.OrdinalIgnoreCase);
var module = _modules.ForEntities(entityIds);
var displayOnly = _options.DisplayOnlyDevices.FirstOrDefault(item => entityIds.Contains(item.EntityId));
if (_options.OnlyConfiguredDevices && module is null && displayOnly is null) continue;
var manualLightId = regEntities.FirstOrDefault(entity => _manualLightIds.Contains(entity.EntityId))?.EntityId;
if (_options.OnlyConfiguredDevices && module is null && displayOnly is null && manualLightId is null) continue;
var entityOutputs = new List<HomeAssistantEntityOutput>();
foreach (var regEntity in regEntities)
@@ -80,9 +87,11 @@ public sealed class HomeAssistantDeviceCatalog
? an
: null;
var category = module?.Category ?? displayOnly?.Category ?? CategorizeDevice(displayName, device.Model ?? "", device.Manufacturer ?? "", entityOutputs);
var category = module?.Category ?? (manualLightId is not null ? nameof(HealthDeviceCategory.Lighting) :
displayOnly?.Category ?? CategorizeDevice(displayName, device.Model ?? "", device.Manufacturer ?? "", entityOutputs));
var metrics = ExtractDeviceMetrics(category, entityOutputs);
var primaryControlEntityId = module is null && displayOnly is not null ? null : FindPrimaryControlEntityId(category, entityOutputs);
var primaryControlEntityId = module is not null ? FindPrimaryControlEntityId(category, entityOutputs) :
manualLightId ?? (displayOnly is not null ? null : FindPrimaryControlEntityId(category, entityOutputs));
discovered.Add(new HomeAssistantDiscoveredDeviceOutput
{
@@ -137,9 +146,27 @@ public sealed class HomeAssistantDeviceCatalog
Entities = entityOutputs, Metrics = ExtractDeviceMetrics(module.Category, entityOutputs)
});
}
foreach (var id in _manualLightIds)
{
if (_modules.ForEntities([id]) is not null ||
_options.ExcludedDeviceEntityIds.Contains(id, StringComparer.OrdinalIgnoreCase) ||
!stateMap.TryGetValue(id, out var state)) continue;
var name = state.GetStringAttribute("friendly_name") ?? id;
var available = IsAvailable(state);
list.Add(new()
{
DeviceId = $"fallback_{id}", Name = name, Category = nameof(HealthDeviceCategory.Lighting), Online = available,
PrimaryControlEntityId = state.EntityId, Entities = [new()
{
EntityId = state.EntityId, Domain = "light", Name = name, State = state.State,
Available = available, Attributes = ExtractSafeAttributes(state)
}]
});
}
foreach (var device in _options.DisplayOnlyDevices)
{
if (string.IsNullOrWhiteSpace(device.EntityId) || _modules.ForEntities([device.EntityId]) is not null ||
_manualLightIds.Contains(device.EntityId) ||
_options.ExcludedDeviceEntityIds.Contains(device.EntityId, StringComparer.OrdinalIgnoreCase)) continue;
stateMap.TryGetValue(device.EntityId, out var state);
var available = state is not null && IsAvailable(state);
@@ -25,8 +25,17 @@ public sealed class ConfiguredSleepLightModule : HealthDeviceModule
}
public override async Task SetPowerAsync(bool turnOn, CancellationToken token)
{
if (turnOn) { await _transition.StartSleepAsync(2700, token); return; }
if (turnOn && _light.StartSleepOnManualPowerOn)
{
await _transition.StartSleepAsync(2700, token);
return;
}
await _transition.CancelAsync(token);
if (turnOn)
{
await base.SetPowerAsync(true, token);
return;
}
var command = GetShutdownCommands().Single();
await Runner.SendAsync(command, token);
await Runner.VerifyParameterAsync(command, token);
@@ -12,6 +12,8 @@ public sealed class HomeAssistantOptions : IConfigurableOptions
public List<string> ExcludedDeviceEntityIds { get; set; } = [];
public bool OnlyConfiguredDevices { get; set; }
public List<DisplayOnlyDeviceOptions> DisplayOnlyDevices { get; set; } = [];
// 每条独立灯具登记自己的主 light 实体,名称和可控能力读取自 HA。
public List<string> ManualLightEntityIds { get; set; } = [];
public HomeAssistantEntityOptions Entities { get; set; } = new();
public HomeAssistantSleepSceneOptions SleepScene { get; set; } = new();
public List<SceneLightOptions> AdditionalSleepLights { get; set; } = [];
@@ -31,6 +33,7 @@ public sealed class SceneLightOptions
public string Name { get; set; } = string.Empty;
public string EntityId { get; set; } = string.Empty;
public string? SleepEffect { get; set; }
public bool StartSleepOnManualPowerOn { get; set; } = true;
}
public sealed class HomeAssistantEntityOptions