189 files
This commit is contained in:
1 parent
79fefc0a7c
commit
303886f212
189 files changed
+21402
-3514
No files matched your search
@@ -58,6 +58,25 @@ def _catalog_name_variants() -> set[str]:
|
||||
return variants
|
||||
|
||||
|
||||
def _matches_catalog_key(entry: er.RegistryEntry, key: str, catalog_keys: set[str]) -> bool:
|
||||
"""``_entity_matches`` where a LONGER key of the same catalog that ends
|
||||
in ``key`` owns the entity.
|
||||
|
||||
The entity_id-suffix fallback made dreame's ``secondary_filter_left``
|
||||
sensor a second source of "Replace Filter" (``…_filter_left``) next to
|
||||
its own "Replace Secondary Filter" duty — the secondary filter's wear
|
||||
then fired the main-filter task (bug review 2026-09-04). An exact
|
||||
translation_key match is the integration's own word and needs no guard.
|
||||
"""
|
||||
if entry.translation_key == key:
|
||||
return True
|
||||
if not _entity_matches(entry, key):
|
||||
return False
|
||||
return not any(
|
||||
other != key and other.endswith(f"_{key}") and _entity_matches(entry, other) for other in catalog_keys
|
||||
)
|
||||
|
||||
|
||||
def discover_integration_setups(hass: HomeAssistant) -> list[dict[str, Any]]:
|
||||
"""Devices of catalogued integrations with their matchable task wiring.
|
||||
|
||||
@@ -107,6 +126,7 @@ def discover_integration_setups(hass: HomeAssistant) -> list[dict[str, Any]]:
|
||||
if device is not None and getattr(device, "parent_device_id", None) is None
|
||||
else ""
|
||||
).lower()
|
||||
catalog_keys = {key for s in catalog.tasks for key in s.keys}
|
||||
for sig in catalog.tasks:
|
||||
# Device-type gates: registry model substring and/or a
|
||||
# type-identifying sibling entity (watched siblings still count —
|
||||
@@ -127,7 +147,7 @@ def discover_integration_setups(hass: HomeAssistant) -> list[dict[str, Any]]:
|
||||
continue
|
||||
# Empty keys (non-sensor domains only, tripwire-enforced) match
|
||||
# the device's single entity of that domain — THE lawn_mower.
|
||||
if sig.keys and not any(_entity_matches(entry, key) for key in sig.keys):
|
||||
if sig.keys and not any(_matches_catalog_key(entry, key, catalog_keys) for key in sig.keys):
|
||||
continue
|
||||
group = matched.setdefault(device_id, {}).setdefault(
|
||||
(integration, sig.task_name, sig.direction),
|
||||
|
||||
@@ -75,8 +75,12 @@ _DEFAULT_ABOVE_HOURS = 100
|
||||
# Canonical units between services for lifetime counters (usage_delta mode):
|
||||
# hours for operating-time counters, kilometres for odometers.
|
||||
_DEFAULT_DELTA_UNITS = 500
|
||||
# Percent floor for percent-remaining consumables (ink, toner, drum, brush %).
|
||||
_DEFAULT_BELOW_PERCENT = 10
|
||||
# Percent floor for percent-remaining consumables (ink, toner, drum, brush %)
|
||||
# — the catalog default is "follow the household setting" (#146), so the
|
||||
# field is None unless a signature pins its own floor. A sentinel value
|
||||
# (the old 10) could not tell "pinned at 10" from "default" (bug review
|
||||
# 2026-09-04).
|
||||
_DEFAULT_BELOW_PERCENT: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -87,7 +91,7 @@ class ConsumableSignature:
|
||||
task_name: str # EN task name; localized through templates_i18n
|
||||
direction: str # duration_left | percent_left | usage_above | event_present | usage_delta | runtime_hours
|
||||
below_hours: int = _DEFAULT_BELOW_HOURS
|
||||
below_percent: int = _DEFAULT_BELOW_PERCENT
|
||||
below_percent: int | None = _DEFAULT_BELOW_PERCENT
|
||||
above_hours: int = _DEFAULT_ABOVE_HOURS
|
||||
delta_units: int = _DEFAULT_DELTA_UNITS
|
||||
# runtime_hours signatures target a non-sensor STATE entity; empty keys
|
||||
@@ -246,8 +250,8 @@ def _threshold_for(sig: ConsumableSignature, hass: HomeAssistant, entity_id: str
|
||||
return float(sig.delta_units)
|
||||
if sig.direction == "percent_left":
|
||||
# #146: catalog-default floors follow the household setting; a
|
||||
# signature that pins its own floor keeps it.
|
||||
if sig.below_percent == _DEFAULT_BELOW_PERCENT:
|
||||
# signature that pins its own floor (any value, 10 included) keeps it.
|
||||
if sig.below_percent is None:
|
||||
from ..global_options import get_consumable_threshold
|
||||
|
||||
return float(get_consumable_threshold(hass))
|
||||
|
||||
Reference in new issue
Block a user