diff --git a/apps/abstraction/main.py b/apps/abstraction/main.py
index 3da42f4..54616df 100644
--- a/apps/abstraction/main.py
+++ b/apps/abstraction/main.py
@@ -15,7 +15,7 @@ import uuid
from aiomqtt import Client
from pydantic import ValidationError
-from packages.home_capabilities import LightState, ThermostatState, ContactState
+from packages.home_capabilities import LightState, ThermostatState, ContactState, TempHumidityState
from apps.abstraction.transformation import (
transform_abstract_to_vendor,
transform_vendor_to_abstract
@@ -222,12 +222,16 @@ async def handle_vendor_state(
elif device_type in {"contact", "contact_sensor"}:
# Validate contact sensor state
ContactState.model_validate(abstract_payload)
+ elif device_type in {"temp_humidity", "temp_humidity_sensor"}:
+ # Validate temperature & humidity sensor state
+ TempHumidityState.model_validate(abstract_payload)
except ValidationError as e:
logger.error(f"Validation failed for {device_type} STATE {device_id}: {e}")
return
# Normalize device type for topic (use 'contact' for both 'contact' and 'contact_sensor')
topic_type = "contact" if device_type in {"contact", "contact_sensor"} else device_type
+ topic_type = "temp_humidity" if device_type in {"temp_humidity", "temp_humidity_sensor"} else topic_type
# Publish to abstract state topic (retained)
abstract_topic = f"home/{topic_type}/{device_id}/state"
diff --git a/apps/abstraction/transformation.py b/apps/abstraction/transformation.py
index f84f1af..3c56f73 100644
--- a/apps/abstraction/transformation.py
+++ b/apps/abstraction/transformation.py
@@ -265,6 +265,48 @@ def _transform_contact_sensor_max_to_abstract(payload: str | bool | dict[str, An
}
+# ============================================================================
+# HANDLER FUNCTIONS: temp_humidity_sensor - zigbee2mqtt technology
+# ============================================================================
+
+def _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]:
+ """Transform abstract temp/humidity sensor payload to zigbee2mqtt format.
+
+ Temp/humidity sensors are read-only, so this should not be called for SET commands.
+ Returns payload as-is for compatibility.
+ """
+ return payload
+
+
+def _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract(payload: dict[str, Any]) -> dict[str, Any]:
+ """Transform zigbee2mqtt temp/humidity sensor payload to abstract format.
+
+ Passthrough - zigbee2mqtt provides temperature, humidity, battery, linkquality directly.
+ """
+ return payload
+
+
+# ============================================================================
+# HANDLER FUNCTIONS: temp_humidity_sensor - MAX! technology
+# ============================================================================
+
+def _transform_temp_humidity_sensor_max_to_vendor(payload: dict[str, Any]) -> dict[str, Any]:
+ """Transform abstract temp/humidity sensor payload to MAX! format.
+
+ Temp/humidity sensors are read-only, so this should not be called for SET commands.
+ Returns payload as-is for compatibility.
+ """
+ return payload
+
+
+def _transform_temp_humidity_sensor_max_to_abstract(payload: dict[str, Any]) -> dict[str, Any]:
+ """Transform MAX! temp/humidity sensor payload to abstract format.
+
+ Passthrough - MAX! provides temperature, humidity, battery directly.
+ """
+ return payload
+
+
# ============================================================================
# HANDLER FUNCTIONS: max technology (Homegear MAX!)
# ============================================================================
@@ -368,6 +410,16 @@ TRANSFORM_HANDLERS: dict[tuple[str, str, str], TransformHandler] = {
("contact", "zigbee2mqtt", "to_abstract"): _transform_contact_sensor_zigbee2mqtt_to_abstract,
("contact", "max", "to_vendor"): _transform_contact_sensor_max_to_vendor,
("contact", "max", "to_abstract"): _transform_contact_sensor_max_to_abstract,
+
+ # Temperature & humidity sensor transformations (support both type aliases)
+ ("temp_humidity_sensor", "zigbee2mqtt", "to_vendor"): _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor,
+ ("temp_humidity_sensor", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract,
+ ("temp_humidity_sensor", "max", "to_vendor"): _transform_temp_humidity_sensor_max_to_vendor,
+ ("temp_humidity_sensor", "max", "to_abstract"): _transform_temp_humidity_sensor_max_to_abstract,
+ ("temp_humidity", "zigbee2mqtt", "to_vendor"): _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor,
+ ("temp_humidity", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract,
+ ("temp_humidity", "max", "to_vendor"): _transform_temp_humidity_sensor_max_to_vendor,
+ ("temp_humidity", "max", "to_abstract"): _transform_temp_humidity_sensor_max_to_abstract,
}
diff --git a/apps/api/main.py b/apps/api/main.py
index c083099..461c8a7 100644
--- a/apps/api/main.py
+++ b/apps/api/main.py
@@ -19,9 +19,11 @@ from packages.home_capabilities import (
LIGHT_VERSION,
THERMOSTAT_VERSION,
CONTACT_SENSOR_VERSION,
+ TEMP_HUMIDITY_SENSOR_VERSION,
LightState,
ThermostatState,
- ContactState
+ ContactState,
+ TempHumidityState
)
logger = logging.getLogger(__name__)
@@ -145,7 +147,8 @@ async def spec() -> dict[str, dict[str, str]]:
"capabilities": {
"light": LIGHT_VERSION,
"thermostat": THERMOSTAT_VERSION,
- "contact": CONTACT_SENSOR_VERSION
+ "contact": CONTACT_SENSOR_VERSION,
+ "temp_humidity": TEMP_HUMIDITY_SENSOR_VERSION
}
}
@@ -377,6 +380,12 @@ async def set_device(device_id: str, request: SetDeviceRequest) -> dict[str, str
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
detail="Contact sensors are read-only devices"
)
+ elif request.type in {"temp_humidity", "temp_humidity_sensor"}:
+ # Temperature & humidity sensors are read-only
+ raise HTTPException(
+ status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
+ detail="Temperature & humidity sensors are read-only devices"
+ )
else:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
diff --git a/apps/ui/templates/dashboard.html b/apps/ui/templates/dashboard.html
index 2be5834..a9f4c1c 100644
--- a/apps/ui/templates/dashboard.html
+++ b/apps/ui/templates/dashboard.html
@@ -426,6 +426,58 @@
text-align: center;
}
+ /* Temperature & Humidity Sensor Styles */
+ .temp-humidity-display {
+ padding: 1rem;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ color: white;
+ margin: 1rem 0;
+ }
+
+ .temp-humidity-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.75rem 0;
+ }
+
+ .temp-humidity-row:not(:last-child) {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ }
+
+ .temp-humidity-label {
+ font-size: 0.875rem;
+ font-weight: 500;
+ opacity: 0.9;
+ }
+
+ .temp-humidity-value {
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: -0.5px;
+ }
+
+ .temp-humidity-battery {
+ font-size: 0.75rem;
+ color: rgba(255, 255, 255, 0.8);
+ text-align: center;
+ padding: 0.5rem;
+ margin-top: 0.5rem;
+ background: rgba(0, 0, 0, 0.1);
+ border-radius: 6px;
+ }
+
+ .temp-humidity-info {
+ font-size: 0.75rem;
+ color: #999;
+ margin-top: 1rem;
+ padding: 0.5rem;
+ background: #f8f9fa;
+ border-radius: 4px;
+ text-align: center;
+ }
+
.events {
margin-top: 2rem;
background: white;
@@ -607,6 +659,28 @@
🔒 Nur-Lesen Gerät • Keine Steuerung möglich
+ {% elif device.type == "temp_humidity" or device.type == "temp_humidity_sensor" %}
+
+
+ 🌡️ Temperatur:
+ --
+ °C
+
+
+ 💧 Luftfeuchte:
+ --
+ %
+
+
+
+
+ 🔋 --%
+
+
+
+ 🔒 Nur-Lesen Gerät • Keine Steuerung möglich
+
+
{% endif %}
{% endfor %}
@@ -922,6 +996,39 @@
}
}
+ // Update temperature & humidity sensor UI
+ function updateTempHumidityUI(deviceId, payload) {
+ const tempSpan = document.getElementById(`th-${deviceId}-t`);
+ const humiditySpan = document.getElementById(`th-${deviceId}-h`);
+ const batteryDiv = document.getElementById(`th-${deviceId}-battery`);
+ const batteryValueSpan = document.getElementById(`th-${deviceId}-battery-value`);
+
+ if (!tempSpan || !humiditySpan) {
+ console.warn(`No temp/humidity elements found for device ${deviceId}`);
+ return;
+ }
+
+ // Update temperature (rounded to 1 decimal)
+ if (payload.temperature !== undefined && payload.temperature !== null) {
+ tempSpan.textContent = payload.temperature.toFixed(1);
+ }
+
+ // Update humidity (rounded to 0-1 decimals)
+ if (payload.humidity !== undefined && payload.humidity !== null) {
+ // Round to 1 decimal if has decimals, otherwise integer
+ const humidity = payload.humidity;
+ humiditySpan.textContent = (humidity % 1 === 0) ? humidity.toFixed(0) : humidity.toFixed(1);
+ }
+
+ // Update battery if present
+ if (payload.battery !== undefined && payload.battery !== null && batteryDiv && batteryValueSpan) {
+ batteryValueSpan.textContent = payload.battery;
+ batteryDiv.style.display = 'block';
+ } else if (batteryDiv) {
+ batteryDiv.style.display = 'none';
+ }
+ }
+
// Add event to list
function addEvent(event) {
const eventList = document.getElementById('event-list');
@@ -992,6 +1099,11 @@
if (data.payload.contact !== undefined) {
updateContactUI(data.device_id, data.payload.contact);
}
+
+ // Check if it's a temp/humidity sensor
+ if (data.payload.temperature !== undefined || data.payload.humidity !== undefined) {
+ updateTempHumidityUI(data.device_id, data.payload);
+ }
}
};
@@ -1113,6 +1225,9 @@
} else if (state.contact !== undefined) {
// It's a contact sensor
updateContactUI(deviceId, state.contact);
+ } else if (state.temperature !== undefined || state.humidity !== undefined) {
+ // It's a temp/humidity sensor
+ updateTempHumidityUI(deviceId, state);
}
}
} catch (error) {
diff --git a/config/devices.yaml b/config/devices.yaml
index 91ef9cf..f0476d5 100644
--- a/config/devices.yaml
+++ b/config/devices.yaml
@@ -639,4 +639,85 @@ devices:
topics:
state: homegear/instance1/plain/44/1/STATE
features: {}
+- device_id: sensor_schlafzimmer
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d00043292dc
+ features: {}
+- device_id: sensor_wohnzimmer
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d0008975707
+ features: {}
+- device_id: sensor_kueche
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d00083299bb
+ features: {}
+- device_id: sensor_arbeitszimmer_patty
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d0003f052b7
+ features: {}
+- device_id: sensor_arbeitszimmer_wolfgang
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d000543fb99
+ features: {}
+- device_id: sensor_bad_oben
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d00093e8987
+ features: {}
+- device_id: sensor_bad_unten
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d00093e662a
+ features: {}
+- device_id: sensor_flur
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d000836ccc6
+ features: {}
+- device_id: sensor_waschkueche
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d000449f3bc
+ features: {}
+- device_id: sensor_sportzimmer
+ type: temp_humidity_sensor
+ name: Temperatur & Luftfeuchte
+ cap_version: temp_humidity_sensor@1.0.0
+ technology: zigbee2mqtt
+ topics:
+ state: zigbee2mqtt/0x00158d0009421422
+ features: {}
+
diff --git a/config/layout.yaml b/config/layout.yaml
index 77f78bc..1599af1 100644
--- a/config/layout.yaml
+++ b/config/layout.yaml
@@ -25,6 +25,10 @@ rooms:
title: Kontakt Straße
icon: 🪟
rank: 46
+ - device_id: sensor_schlafzimmer
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 47
- name: Esszimmer
devices:
- device_id: deckenlampe_esszimmer
@@ -89,6 +93,10 @@ rooms:
title: Kontakt Garten links
icon: 🪟
rank: 137
+ - device_id: sensor_wohnzimmer
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 138
- name: Küche
devices:
- device_id: kueche_deckenlampe
@@ -115,6 +123,10 @@ rooms:
title: Kontakt Straße links
icon: 🪟
rank: 154
+ - device_id: sensor_kueche
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 155
- name: Arbeitszimmer Patty
devices:
- device_id: leselampe_patty
@@ -145,6 +157,10 @@ rooms:
title: Kontakt Straße
icon: 🪟
rank: 188
+ - device_id: sensor_arbeitszimmer_patty
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 189
- name: Arbeitszimmer Wolfgang
devices:
- device_id: thermostat_wolfgang
@@ -159,6 +175,10 @@ rooms:
title: Kontakt Garten
icon: 🪟
rank: 201
+ - device_id: sensor_arbeitszimmer_wolfgang
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 202
- name: Flur
devices:
- device_id: deckenlampe_flur_oben
@@ -173,6 +193,10 @@ rooms:
title: Licht Flur oben am Spiegel
icon: 💡
rank: 230
+ - device_id: sensor_flur
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 235
- name: Sportzimmer
devices:
- device_id: sportlicht_regal
@@ -187,6 +211,10 @@ rooms:
title: Sportlicht am Fernseher, Studierzimmer
icon: 🏃
rank: 260
+ - device_id: sensor_sportzimmer
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 265
- name: Bad Oben
devices:
- device_id: thermostat_bad_oben
@@ -197,6 +225,10 @@ rooms:
title: Kontakt Straße
icon: 🪟
rank: 271
+ - device_id: sensor_bad_oben
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 272
- name: Bad Unten
devices:
- device_id: thermostat_bad_unten
@@ -207,4 +239,14 @@ rooms:
title: Kontakt Straße
icon: 🪟
rank: 281
+ - device_id: sensor_bad_unten
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 282
+- name: Waschküche
+ devices:
+ - device_id: sensor_waschkueche
+ title: Temperatur & Luftfeuchte
+ icon: 🌡️
+ rank: 290
diff --git a/config/raeume.txt b/config/raeume.txt
index b845d74..3a6f88e 100644
--- a/config/raeume.txt
+++ b/config/raeume.txt
@@ -1,33 +1,31 @@
Schlafzimmer
- 52 Straße
-
+ 0x00158d00043292dc
Esszimmer
- 26 Straße rechts
- 27 Straße links
Wohnzimmer
- 28 Garten rechts
- 29 Garten links
+ 0x00158d0008975707
Küche
- 0x00158d008b332785 Garten Fenster
- 0x00158d008b332788 Garten Tür
- 0x00158d008b151803 Straße rechts
- 0x00158d008b331d0b Straße links
-
+ 0x00158d00083299bb
Arbeitszimmer Patty
- 18 Garten rechts
- 22 Garten links
- 0x00158d000af457cf Straße
+ 0x00158d0003f052b7
Arbeitszimmer Wolfgang
- 0x00158d008b3328da Garten
-
+ 0x00158d000543fb99
Bad Oben
- 0x00158d008b333aec Straße
+ 0x00158d00093e8987
Bad Unten
- 44 Straße
\ No newline at end of file
+ 0x00158d00093e662a
+
+Flur
+ 0x00158d000836ccc6
+
+Waschküche
+ 0x00158d000449f3bc
+
+Sportzimmer
+ 0x00158d0009421422
\ No newline at end of file
diff --git a/packages/home_capabilities/__init__.py b/packages/home_capabilities/__init__.py
index 0b164e2..069d54f 100644
--- a/packages/home_capabilities/__init__.py
+++ b/packages/home_capabilities/__init__.py
@@ -6,6 +6,8 @@ from packages.home_capabilities.thermostat import CAP_VERSION as THERMOSTAT_VERS
from packages.home_capabilities.thermostat import ThermostatState
from packages.home_capabilities.contact_sensor import CAP_VERSION as CONTACT_SENSOR_VERSION
from packages.home_capabilities.contact_sensor import ContactState
+from packages.home_capabilities.temp_humidity_sensor import CAP_VERSION as TEMP_HUMIDITY_SENSOR_VERSION
+from packages.home_capabilities.temp_humidity_sensor import TempHumidityState
from packages.home_capabilities.layout import DeviceTile, Room, UiLayout, load_layout
__all__ = [
@@ -15,6 +17,8 @@ __all__ = [
"THERMOSTAT_VERSION",
"ContactState",
"CONTACT_SENSOR_VERSION",
+ "TempHumidityState",
+ "TEMP_HUMIDITY_SENSOR_VERSION",
"DeviceTile",
"Room",
"UiLayout",