mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 01:11:51 -04:00
Resolve the HomematicIP tilt channel by type instead of by position (#180807)
This commit is contained in:
@@ -6,6 +6,7 @@ from typing import Any, override
|
||||
|
||||
from homematicip.base.enums import (
|
||||
BinaryBehaviorType,
|
||||
FunctionalChannelType,
|
||||
LockState,
|
||||
SmokeDetectorAlarmType,
|
||||
WindowState,
|
||||
@@ -48,7 +49,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from .const import DOMAIN
|
||||
from .entity import HomematicipGenericEntity
|
||||
from .hap import HomematicIPConfigEntry, HomematicipHAP
|
||||
from .helpers import smoke_detector_channel_data_exists
|
||||
from .helpers import get_channel_index_by_type, smoke_detector_channel_data_exists
|
||||
|
||||
ATTR_ACCELERATION_SENSOR_MODE = "acceleration_sensor_mode"
|
||||
ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION = "acceleration_sensor_neutral_position"
|
||||
@@ -427,7 +428,14 @@ class HomematicipTiltVibrationSensor(HomematicipBaseActionSensor):
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
"""Initialize the tilt vibration sensor."""
|
||||
super().__init__(hap, device, feature_id="tilt_vibration")
|
||||
super().__init__(
|
||||
hap,
|
||||
device,
|
||||
feature_id="tilt_vibration",
|
||||
channel_real_index=get_channel_index_by_type(
|
||||
device, FunctionalChannelType.TILT_VIBRATION_SENSOR_CHANNEL
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class HomematicipMultiContactInterface(HomematicipGenericEntity, BinarySensorEntity):
|
||||
|
||||
@@ -372,8 +372,7 @@ class HomematicipGenericEntity(Entity):
|
||||
"""Return the FunctionalChannel for the device.
|
||||
|
||||
Resolution priority:
|
||||
1. For multi-channel entities with a real index, find
|
||||
channel by index match.
|
||||
1. With a real index, find channel by index match.
|
||||
2. For multi-channel entities without a real index, use
|
||||
the provided channel position.
|
||||
3. For non multi-channel entities with >1 channels, use
|
||||
@@ -388,20 +387,20 @@ class HomematicipGenericEntity(Entity):
|
||||
" has no functionalChannels"
|
||||
)
|
||||
|
||||
# Prefer real index mapping when provided to avoid ordering issues.
|
||||
if self._channel_real_index is not None:
|
||||
for channel in functional_channels:
|
||||
if channel.index == self._channel_real_index:
|
||||
return channel
|
||||
raise ValueError(
|
||||
f"Real channel index"
|
||||
f" {self._channel_real_index}"
|
||||
" not found for device"
|
||||
f" {getattr(self._device, 'id', 'unknown')}"
|
||||
)
|
||||
|
||||
# Multi-channel handling
|
||||
if self._is_multi_channel:
|
||||
# Prefer real index mapping when provided to avoid
|
||||
# ordering issues.
|
||||
if self._channel_real_index is not None:
|
||||
for channel in functional_channels:
|
||||
if channel.index == self._channel_real_index:
|
||||
return channel
|
||||
raise ValueError(
|
||||
f"Real channel index"
|
||||
f" {self._channel_real_index}"
|
||||
" not found for device"
|
||||
f" {getattr(self._device, 'id', 'unknown')}"
|
||||
)
|
||||
# Fallback: positional channel (already sorted as strings upstream).
|
||||
if self._channel is not None and 0 <= self._channel < len(
|
||||
functional_channels
|
||||
|
||||
@@ -61,6 +61,14 @@ def get_channels_from_device(device: Device, channel_type: FunctionalChannelType
|
||||
]
|
||||
|
||||
|
||||
def get_channel_index_by_type(
|
||||
device: Device, channel_type: FunctionalChannelType
|
||||
) -> int | None:
|
||||
"""Return the index of the device's first channel of the given type."""
|
||||
channels = get_channels_from_device(device, channel_type)
|
||||
return channels[0].index if channels else None
|
||||
|
||||
|
||||
def smoke_detector_channel_data_exists(device: Device, field: str) -> bool:
|
||||
"""Check if a smoke detector's channel payload contains a specific field.
|
||||
|
||||
|
||||
@@ -67,7 +67,11 @@ from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .entity import HomematicipGenericEntity
|
||||
from .hap import HomematicIPConfigEntry, HomematicipHAP
|
||||
from .helpers import get_channels_from_device, smoke_detector_channel_data_exists
|
||||
from .helpers import (
|
||||
get_channel_index_by_type,
|
||||
get_channels_from_device,
|
||||
smoke_detector_channel_data_exists,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -144,6 +148,8 @@ class HmipSensorDescription[_DeviceT: Device](SensorEntityDescription):
|
||||
extra_attrs_fn: Callable[[_DeviceT], dict[str, Any]] | None = None
|
||||
icon_fn: Callable[[_DeviceT], str] | None = None
|
||||
channel: int
|
||||
# for devices whose channel does not sit at the position `channel` names
|
||||
channel_type: FunctionalChannelType | None = None
|
||||
|
||||
|
||||
ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION = "acceleration_sensor_neutral_position"
|
||||
@@ -393,6 +399,20 @@ TILT_ANGLE_DESC = HmipSensorDescription[Device](
|
||||
state_class=SensorStateClass.MEASUREMENT_ANGLE,
|
||||
channel_value_fn=lambda channel: getattr(channel, "absoluteAngle", None),
|
||||
channel=1,
|
||||
channel_type=FunctionalChannelType.TILT_VIBRATION_SENSOR_CHANNEL,
|
||||
)
|
||||
|
||||
# Only the ELV-SH-TACO carries a temperature channel next to the tilt channel.
|
||||
TILT_TEMPERATURE_DESC = HmipSensorDescription[Device](
|
||||
key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=_temperature_value,
|
||||
extra_attrs_fn=_temperature_extras,
|
||||
exists_fn=lambda d: hasattr(d, "actualTemperature"),
|
||||
channel=1,
|
||||
channel_type=FunctionalChannelType.TEMPERATURE_SENSOR_CHANNEL,
|
||||
)
|
||||
|
||||
|
||||
@@ -433,7 +453,7 @@ SENSOR_DESCRIPTIONS_BY_DEVICE: dict[
|
||||
TEMPERATURE_EXTERNAL_CH2_DESC,
|
||||
TEMPERATURE_EXTERNAL_DELTA_DESC,
|
||||
),
|
||||
TiltVibrationSensor: (TILT_ANGLE_DESC,),
|
||||
TiltVibrationSensor: (TILT_ANGLE_DESC, TILT_TEMPERATURE_DESC),
|
||||
WeatherSensor: (
|
||||
TEMPERATURE_DESC,
|
||||
HUMIDITY_DESC,
|
||||
@@ -721,7 +741,15 @@ class HomematicipTiltStateSensor(HomematicipGenericEntity, SensorEntity):
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device) -> None:
|
||||
"""Initialize the tilt sensor device."""
|
||||
super().__init__(hap, device, post="Tilt State", feature_id="tilt_state")
|
||||
super().__init__(
|
||||
hap,
|
||||
device,
|
||||
post="Tilt State",
|
||||
feature_id="tilt_state",
|
||||
channel_real_index=get_channel_index_by_type(
|
||||
device, FunctionalChannelType.TILT_VIBRATION_SENSOR_CHANNEL
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
@@ -1054,11 +1082,17 @@ class HomematicipSensor[_DeviceT: Device](HomematicipGenericEntity, SensorEntity
|
||||
description: HmipSensorDescription[_DeviceT],
|
||||
) -> None:
|
||||
"""Initialize the described sensor."""
|
||||
channel_real_index = (
|
||||
get_channel_index_by_type(device, description.channel_type)
|
||||
if description.channel_type is not None
|
||||
else None
|
||||
)
|
||||
super().__init__(
|
||||
hap,
|
||||
device,
|
||||
feature_id=description.key,
|
||||
channel=description.channel,
|
||||
channel_real_index=channel_real_index,
|
||||
use_description_name=True,
|
||||
)
|
||||
self.entity_description = description
|
||||
|
||||
@@ -665,6 +665,93 @@
|
||||
"type": "TILT_VIBRATION_SENSOR",
|
||||
"updateState": "UP_TO_DATE"
|
||||
},
|
||||
"3014F711000000000000TACO": {
|
||||
"availableFirmwareVersion": "1.2.4",
|
||||
"connectionType": "HMIP_RF",
|
||||
"firmwareVersion": "1.2.4",
|
||||
"firmwareVersionInteger": 66052,
|
||||
"functionalChannels": {
|
||||
"0": {
|
||||
"busConfigMismatch": null,
|
||||
"coProFaulty": false,
|
||||
"coProRestartNeeded": false,
|
||||
"coProUpdateFailure": false,
|
||||
"configPending": false,
|
||||
"deviceId": "3014F711000000000000TACO",
|
||||
"deviceOverheated": false,
|
||||
"deviceOverloaded": false,
|
||||
"devicePowerFailureDetected": false,
|
||||
"deviceUndervoltage": false,
|
||||
"dutyCycle": false,
|
||||
"functionalChannelType": "DEVICE_BASE",
|
||||
"groupIndex": 0,
|
||||
"groups": [],
|
||||
"index": 0,
|
||||
"label": "",
|
||||
"lowBat": false,
|
||||
"multicastRoutingEnabled": false,
|
||||
"powerShortCircuit": null,
|
||||
"routerModuleEnabled": false,
|
||||
"routerModuleSupported": false,
|
||||
"rssiDeviceValue": -80,
|
||||
"rssiPeerValue": null,
|
||||
"shortCircuitDataLine": null,
|
||||
"supportedOptionalFeatures": {
|
||||
"IFeatureDeviceTemperatureOutOfRange": true,
|
||||
"IFeatureRssiValue": true,
|
||||
"IOptionalFeatureDutyCycle": true,
|
||||
"IOptionalFeatureLowBat": true
|
||||
},
|
||||
"temperatureOutOfRange": false,
|
||||
"unreach": false
|
||||
},
|
||||
"1": {
|
||||
"actualTemperature": 22.9,
|
||||
"channelRole": "WEATHER_SENSOR",
|
||||
"deviceId": "3014F711000000000000TACO",
|
||||
"functionalChannelType": "TEMPERATURE_SENSOR_CHANNEL",
|
||||
"groupIndex": 1,
|
||||
"groups": [],
|
||||
"index": 1,
|
||||
"label": ""
|
||||
},
|
||||
"2": {
|
||||
"absoluteAngle": 92,
|
||||
"accelerationSensorEventFilterPeriod": 3.0,
|
||||
"accelerationSensorMode": "ANY_MOTION",
|
||||
"accelerationSensorNeutralPosition": "HORIZONTAL",
|
||||
"accelerationSensorSecondTriggerAngle": 75,
|
||||
"accelerationSensorSensitivity": "SENSOR_RANGE_2G",
|
||||
"accelerationSensorTriggerAngle": 20,
|
||||
"accelerationSensorTriggered": true,
|
||||
"channelRole": "ACCELERATION_SENSOR",
|
||||
"deviceId": "3014F711000000000000TACO",
|
||||
"functionalChannelType": "TILT_VIBRATION_SENSOR_CHANNEL",
|
||||
"groupIndex": 2,
|
||||
"groups": [],
|
||||
"index": 2,
|
||||
"label": "",
|
||||
"supportedOptionalFeatures": {
|
||||
"IOptionalFeatureTiltDetection": true
|
||||
},
|
||||
"tiltState": "NON_NEUTRAL",
|
||||
"tiltVisualization": "GENERIC"
|
||||
}
|
||||
},
|
||||
"homeId": "00000000-0000-0000-0000-000000000001",
|
||||
"id": "3014F711000000000000TACO",
|
||||
"label": "Wassertemperatursensor",
|
||||
"lastStatusUpdate": 1598610615630,
|
||||
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
|
||||
"manufacturerCode": 1,
|
||||
"modelId": 546,
|
||||
"modelType": "ELV-SH-TACO",
|
||||
"oem": "eQ-3",
|
||||
"permanentlyReachable": false,
|
||||
"serializedGlobalTradeItemNumber": "3014F711000000000000TACO",
|
||||
"type": "TEMPERATURE_TILT_VIBRATION_SENSOR",
|
||||
"updateState": "UP_TO_DATE"
|
||||
},
|
||||
"3014F711000WIREDSWITCH8": {
|
||||
"availableFirmwareVersion": "0.0.0",
|
||||
"connectionType": "HMIP_WIRED",
|
||||
|
||||
@@ -32,6 +32,7 @@ from homeassistant.components.homematicip_cloud.entity import (
|
||||
)
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .helper import HomeFactory, async_manipulate_test_data, get_and_check_entity_basics
|
||||
|
||||
@@ -228,6 +229,31 @@ async def test_hmip_tilt_vibration_sensor(
|
||||
assert len(hmip_device.mock_calls) == service_call_counter + 2
|
||||
|
||||
|
||||
async def test_hmip_temperature_tilt_vibration_sensor(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
default_mock_hap_factory: HomeFactory,
|
||||
) -> None:
|
||||
"""Test the ELV-SH-TACO, whose tilt channel sits at index 2, not 1."""
|
||||
entity_id = "binary_sensor.wassertemperatursensor_moving"
|
||||
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
|
||||
test_devices=["Wassertemperatursensor"]
|
||||
)
|
||||
|
||||
ha_state, hmip_device = get_and_check_entity_basics(
|
||||
hass, mock_hap, entity_id, "Wassertemperatursensor Moving", "ELV-SH-TACO"
|
||||
)
|
||||
assert ha_state.state == STATE_ON
|
||||
|
||||
entity = entity_registry.async_get(entity_id)
|
||||
assert entity.unique_id == "3014F711000000000000TACO_2_tilt_vibration"
|
||||
|
||||
await async_manipulate_test_data(
|
||||
hass, hmip_device, "accelerationSensorTriggered", False
|
||||
)
|
||||
assert hass.states.get(entity_id).state == STATE_OFF
|
||||
|
||||
|
||||
async def test_hmip_contact_interface(
|
||||
hass: HomeAssistant, default_mock_hap_factory: HomeFactory
|
||||
) -> None:
|
||||
|
||||
@@ -23,7 +23,7 @@ async def test_hmip_load_all_supported_devices(
|
||||
test_devices=None, test_groups=None
|
||||
)
|
||||
|
||||
assert len(mock_hap.hmip_device_by_entity_id) == 385
|
||||
assert len(mock_hap.hmip_device_by_entity_id) == 390
|
||||
|
||||
|
||||
async def test_hmip_remove_device(
|
||||
|
||||
@@ -822,6 +822,47 @@ async def test_hmip_tilt_vibration_sensor_tilt_angle(
|
||||
assert ha_state.state == "89"
|
||||
|
||||
|
||||
async def test_hmip_temperature_tilt_vibration_sensor(
|
||||
hass: HomeAssistant, default_mock_hap_factory: HomeFactory
|
||||
) -> None:
|
||||
"""Test the ELV-SH-TACO, whose tilt channel sits at index 2, not 1."""
|
||||
device_model = "ELV-SH-TACO"
|
||||
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
|
||||
test_devices=["Wassertemperatursensor"]
|
||||
)
|
||||
|
||||
ha_state, hmip_device = get_and_check_entity_basics(
|
||||
hass,
|
||||
mock_hap,
|
||||
"sensor.wassertemperatursensor_tilt_state",
|
||||
"Wassertemperatursensor Tilt State",
|
||||
device_model,
|
||||
)
|
||||
assert ha_state.state == "non_neutral"
|
||||
|
||||
await async_manipulate_test_data(hass, hmip_device, "tiltState", "TILTED", 2)
|
||||
ha_state = hass.states.get("sensor.wassertemperatursensor_tilt_state")
|
||||
assert ha_state.state == "tilted"
|
||||
|
||||
ha_state, _ = get_and_check_entity_basics(
|
||||
hass,
|
||||
mock_hap,
|
||||
"sensor.wassertemperatursensor_tilt_angle",
|
||||
"Wassertemperatursensor Tilt angle",
|
||||
device_model,
|
||||
)
|
||||
assert ha_state.state == "92"
|
||||
|
||||
ha_state, _ = get_and_check_entity_basics(
|
||||
hass,
|
||||
mock_hap,
|
||||
"sensor.wassertemperatursensor_temperature",
|
||||
"Wassertemperatursensor Temperature",
|
||||
device_model,
|
||||
)
|
||||
assert ha_state.state == "22.9"
|
||||
|
||||
|
||||
async def test_hmip_absolute_humidity_sensor(
|
||||
hass: HomeAssistant, default_mock_hap_factory: HomeFactory
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user