Compare commits

...

1 Commits

Author SHA1 Message Date
bc8bec6aa9 Improve dashboard categories and loading
Some checks failed
quality / test (3.11) (push) Has been cancelled
quality / test (3.13) (push) Has been cancelled
2026-06-16 23:46:33 +02:00
2 changed files with 80 additions and 43 deletions

View File

@@ -105,6 +105,7 @@ _CONTEXT_DOMAINS = frozenset({
"input_datetime", "input_datetime",
"input_number", "input_number",
"input_select", "input_select",
"input_text",
"person", "person",
"sun", "sun",
"weather", "weather",
@@ -122,6 +123,29 @@ _NUMERIC_STATE_CLASSES = frozenset({"measurement", "total", "total_increasing"})
def classify_entity(entity: HaEntitySummary) -> DiscoveredEntity: def classify_entity(entity: HaEntitySummary) -> DiscoveredEntity:
if entity.domain in _ACTUATOR_DOMAINS:
return _result(
entity,
EntityRole.ACTUATOR,
category=_actuator_category(entity),
learnable=False,
reason="Aktor ist ein mögliches Automationsziel, aber kein Trainingssensor.",
)
if entity.domain in _CONTEXT_DOMAINS:
learnable = entity.domain in _LEARNABLE_CONTEXT_DOMAINS
return _result(
entity,
EntityRole.CONTEXT,
category=_context_category(entity),
learnable=learnable,
reason=(
"Kontextquelle für Training und Erklärungen."
if learnable
else "Kontextquelle ohne direkte Trainingsfreigabe."
),
)
if entity.domain == "sensor" and ( if entity.domain == "sensor" and (
entity.state_class in _NUMERIC_STATE_CLASSES entity.state_class in _NUMERIC_STATE_CLASSES
or entity.device_class in _MEASUREMENT_CLASSES or entity.device_class in _MEASUREMENT_CLASSES
@@ -144,29 +168,6 @@ def classify_entity(entity: HaEntitySummary) -> DiscoveredEntity:
reason="Binärer Kontextsensor für Zustands- und Anwesenheitsmuster.", reason="Binärer Kontextsensor für Zustands- und Anwesenheitsmuster.",
) )
if entity.domain in _CONTEXT_DOMAINS:
learnable = entity.domain in _LEARNABLE_CONTEXT_DOMAINS
return _result(
entity,
EntityRole.CONTEXT,
category=_context_category(entity),
learnable=learnable,
reason=(
"Kontextquelle für Training und Erklärungen."
if learnable
else "Kontextquelle ohne direkte Trainingsfreigabe."
),
)
if entity.domain in _ACTUATOR_DOMAINS:
return _result(
entity,
EntityRole.ACTUATOR,
category=_actuator_category(entity),
learnable=False,
reason="Aktor ist ein mögliches Automationsziel, aber kein Trainingssensor.",
)
return _result( return _result(
entity, entity,
EntityRole.UNSUPPORTED, EntityRole.UNSUPPORTED,
@@ -226,26 +227,33 @@ def _actuator_category(entity: HaEntitySummary) -> str:
if entity.domain == "lock": if entity.domain == "lock":
return "lock" return "lock"
if entity.domain == "fan": if entity.domain == "fan":
return "fan" return "ventilation"
if entity.domain == "humidifier":
return "climate"
if entity.domain in {"media_player", "remote"}: if entity.domain in {"media_player", "remote"}:
return "media_tv" return "media_tv"
if entity.domain in {"input_boolean", "number"}: if entity.domain == "input_boolean" or entity.domain == "number" or entity.domain == "input_button":
return "helper" return "helper"
if entity.domain == "sensor" and (entity.device_class or entity.unit_of_measurement):
return "measurement"
return entity.domain return entity.domain
def _measurement_category(entity: HaEntitySummary) -> str: def _measurement_category(entity: HaEntitySummary) -> str:
device_class = entity.device_class or "" device_class = entity.device_class or ""
if device_class == "illuminance": unit = (entity.unit_of_measurement or "").lower()
if device_class == "illuminance" or unit == "lx":
return "brightness" return "brightness"
if device_class == "temperature": if device_class == "temperature" or unit in {"°c", "°f", "k"}:
return "temperature" return "temperature"
if device_class in {"humidity", "moisture"}: if device_class in {"humidity", "moisture"} or unit in {"%", "rh", "g/m³", "kg/m³"}:
return "humidity" return "humidity"
if device_class in {"power", "energy", "current", "voltage"}: if device_class in {"power", "energy", "current", "voltage"} or unit in {"w", "kw", "kwh", "a", "v", "va", "var"}:
return "energy_power" return "energy_power"
if device_class in {"battery", "signal_strength"}: if device_class in {"battery", "signal_strength"} or unit in {"%", "dbm"}:
return "diagnostic" return "diagnostic"
if unit:
return f"measurement:{unit}"
return "measurement" return "measurement"
@@ -253,7 +261,7 @@ def _binary_category(entity: HaEntitySummary) -> str:
device_class = entity.device_class or "" device_class = entity.device_class or ""
if device_class in {"motion", "occupancy", "presence"}: if device_class in {"motion", "occupancy", "presence"}:
return "presence_motion" return "presence_motion"
if device_class in {"door", "garage_door", "opening", "window"}: if device_class in {"door", "garage_door", "opening", "window", "button"}:
return "opening" return "opening"
if device_class in {"smoke", "safety", "problem"}: if device_class in {"smoke", "safety", "problem"}:
return "safety" return "safety"
@@ -265,4 +273,8 @@ def _context_category(entity: HaEntitySummary) -> str:
return "helper" return "helper"
if entity.domain in {"person", "device_tracker", "zone"}: if entity.domain in {"person", "device_tracker", "zone"}:
return "presence_location" return "presence_location"
if entity.domain == "sun":
return "weather"
if entity.domain == "weather":
return "weather"
return entity.domain return entity.domain

View File

@@ -354,20 +354,17 @@ async function loadActuatorSuggestions() {
function actuatorGroupLabel(domain) { function actuatorGroupLabel(domain) {
const labels = { const labels = {
button: "Buttons",
climate: "Heizungen / Klima",
light: "Lichter", light: "Lichter",
input_boolean: "Helper-Schalter", switch_socket: "Steckdosen / Schalter",
input_button: "Helper-Buttons", button: "Buttons",
cover_shutter: "Rollläden / Fenster",
heating: "Heizungen / Klima",
ventilation: "Lüftung / Ventilatoren",
climate: "Klima",
helper: "Helper",
measurement: "Sensoren / Messung",
lock: "Schlösser", lock: "Schlösser",
media_player: "TV / Medien", media_tv: "TV / Medien",
number: "Numerische Helper",
remote: "Fernbedienungen",
switch: "Schalter / Steckdosen",
cover: "Rollläden / Cover",
fan: "Lüftung / Ventilatoren",
humidifier: "Befeuchter / Entfeuchter",
valve: "Ventile",
}; };
return labels[domain] || domain; return labels[domain] || domain;
} }
@@ -815,3 +812,31 @@ loadOverview();
</script> </script>
</body> </body>
</html> </html>
function categoryLabel(category) {
const labels = {
light: "Licht",
switch_socket: "Steckdosen / Schalter",
button: "Buttons",
cover_shutter: "Rollläden / Cover",
heating: "Heizungen / Klima",
fan: "Lüftung / Ventilatoren",
ventilation: "Lüftung / Ventilatoren",
media_tv: "TV / Medien",
helper: "Helper",
presence_motion: "Bewegung / Präsenz",
opening: "Fenster / Türen",
weather: "Wetter",
brightness: "Helligkeit",
temperature: "Temperatur",
humidity: "Feuchtigkeit",
energy_power: "Strom / Energie",
diagnostic: "Diagnose",
measurement: "Messung",
binary: "Binär",
presence_location: "Anwesenheit / Ort",
climate: "Klima",
};
return labels[category] || category;
}