8000 Add support for ELV-SH-CTV Sensor to homematicip_cloud by hahn-th · Pull Request #143737 · home-assistant/core · GitHub
[go: up one dir, main page]

Skip to content

Add support for ELV-SH-CTV Sensor to homematicip_cloud #143737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions homeassistant/components/homematicip_cloud/icons.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
{
"entity": {
"sensor": {
"tilt_state": {
"state": {
"neutral": "mdi:garage",
"non_neutral": "mdi:garage-open",
"tilted": "mdi:garage-alert"
}
}
}
},
"services": {
"activate_eco_mode_with_duration": {
"service": "mdi:leaf"
Expand Down
313 changes: 215 additions & 98 deletions homeassistant/components/homematicip_cloud/sensor.py
F6B0
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
FunctionalChannel,
)
from homematicip.device import (
Device,
EnergySensorsInterface,
FloorTerminalBlock6,
FloorTerminalBlock10,
Expand All @@ -31,6 +32,7 @@
TemperatureHumiditySensorDisplay,
TemperatureHumiditySensorOutdoor,
TemperatureHumiditySensorWithoutDisplay,
TiltVibrationSensor,
WeatherSensor,
WeatherSensorPlus,
WeatherSensorPro,
Expand All @@ -44,6 +46,7 @@
)
from homeassistant.const import (
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
DEGREE,
LIGHT_LUX,
PERCENTAGE,
UnitOfEnergy,
Expand All @@ -62,6 +65,11 @@
from .hap import HomematicIPConfigEntry, HomematicipHAP
from .helpers import get_channels_from_device

ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION = "acceleration_sensor_neutral_position"
ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE = "acceleration_sensor_trigger_angle"
ATTR_ACCELERATION_SENSOR_SECOND_TRIGGER_ANGLE = (
"acceleration_sensor_second_trigger_angle"
)
ATTR_CURRENT_ILLUMINATION = "current_illumination"
ATTR_LOWEST_ILLUMINATION = "lowest_illumination"
ATTR_HIGHEST_ILLUMINATION = "highest_illumination"
Expand Down Expand Up @@ -89,6 +97,136 @@
"highestIllumination": ATTR_HIGHEST_ILLUMINATION,
}

TILT_STATE_VALUES = ["neutral", "tilted", "non_neutral"]


def get_device_handlers(hap: HomematicipHAP) -> dict[type, Callable]:
"""Generate a mapping of device types to handler functions."""
return {
HomeControlAccessPoint: lambda device: [
HomematicipAccesspointDutyCycle(hap, device)
],
HeatingThermostat: lambda device: [
HomematicipHeatingThermostat(hap, device),
HomematicipTemperatureSensor(hap, device),
],
HeatingThermostatCompact: lambda device: [
HomematicipHeatingThermostat(hap, device),
HomematicipTemperatureSensor(hap, device),
],
HeatingThermostatEvo: lambda device: [
HomematicipHeatingThermostat(hap, device),
HomematicipTemperatureSensor(hap, device),
],
TemperatureHumiditySensorDisplay: lambda device: [
HomematicipTemperatureSensor(hap, device),
HomematicipHumiditySensor(hap, device),
HomematicipAbsoluteHumiditySensor(hap, device),
],
TemperatureHumiditySensorWithoutDisplay: lambda device: [
HomematicipTemperatureSensor(hap, device),
HomematicipHumiditySensor(hap, device),
HomematicipAbsoluteHumiditySensor(hap, device),
],
TemperatureHumiditySensorOutdoor: lambda device: [
HomematicipTemperatureSensor(hap, device),
HomematicipHumiditySensor(hap, device),
HomematicipAbsoluteHumiditySensor(hap, device),
],
RoomControlDeviceAnalog: lambda device: [
HomematicipTemperatureSensor(hap, device),
],
LightSensor: lambda device: [
HomematicipIlluminanceSensor(hap, device),
],
MotionDetectorIndoor: lambda device: [
HomematicipIlluminanceSensor(hap, device),
],
MotionDetectorOutdoor: lambda device: [
HomematicipIlluminanceSensor(hap, device),
],
MotionDetectorPushButton: lambda device: [
HomematicipIlluminanceSensor(hap, device),
],
PresenceDetectorIndoor: lambda device: [
HomematicipIlluminanceSensor(hap, device),
],
SwitchMeasuring: lambda device: [
HomematicipPowerSensor(hap, device),
HomematicipEnergySensor(hap, device),
],
PassageDetector: lambda device: [
HomematicipPassageDetectorDeltaCounter(hap, device),
],
TemperatureDifferenceSensor2: lambda device: [
HomematicpTemperatureExternalSensorCh1(hap, device),
HomematicpTemperatureExternalSensorCh2(hap, device),
HomematicpTemperatureExternalSensorDelta(hap, device),
],
TiltVibrationSensor: lambda device: [
HomematicipTiltStateSensor(hap, device),
HomematicipTiltAngleSensor(hap, device),
],
WeatherSensor: lambda device: [
HomematicipTemperatureSensor(hap, device),
HomematicipHumiditySensor(hap, device),
HomematicipIlluminanceSensor(hap, device),
HomematicipWindspeedSensor(hap, device),
HomematicipAbsoluteHumiditySensor(hap, device),
],
WeatherSensorPlus: lambda device: [
HomematicipTemperatureSensor(hap, device),
HomematicipHumiditySensor(hap, device),
HomematicipIlluminanceSensor(hap, device),
HomematicipWindspeedSensor(hap, device),
HomematicipTodayRainSensor(hap, device),
HomematicipAbsoluteHumiditySensor(hap, device),
],
WeatherSensorPro: lambda device: [
HomematicipTemperatureSensor(hap, device),
HomematicipHumiditySensor(hap, device),
HomematicipIlluminanceSensor(hap, device),
HomematicipWindspeedSensor(hap, device),
HomematicipTodayRainSensor(hap, device),
HomematicipAbsoluteHumiditySensor(hap, device),
],
EnergySensorsInterface: lambda device: _handle_energy_sensor_interface(
hap, device
),
}


def _handle_energy_sensor_interface(
hap: HomematicipHAP, device: Device
) -> list[HomematicipGenericEntity]:
"""Handle energy sensor interface devices."""
result: list[HomematicipGenericEntity] = []
for ch in get_channels_from_device(
device, FunctionalChannelType.ENERGY_SENSORS_INTERFACE_CHANNEL
):
if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_IEC:
if ch.currentPowerConsumption is not None:
result.append(HmipEsiIecPowerConsumption(hap, device))
if ch.energyCounterOneType != ESI_TYPE_UNKNOWN:
result.append(HmipEsiIecEnergyCounterHighTariff(hap, device))
if ch.energyCounterTwoType != ESI_TYPE_UNKNOWN:
result.append(HmipEsiIecEnergyCounterLowTariff(hap, device))
if ch.energyCounterThreeType != ESI_TYPE_UNKNOWN:
result.append(HmipEsiIecEnergyCounterInputSingleTariff(hap, device))

if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_GAS:
if ch.currentGasFlow is not None:
result.append(HmipEsiGasCurrentGasFlow(hap, device))
if ch.gasVolume is not None:
result.append(HmipEsiGasGasVolume(hap, device))

if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_LED:
if ch.currentPowerConsumption is not None:
result.append(HmipEsiLedCurrentPowerConsumption(hap, device))
result.append(HmipEsiLedEnergyCounterHighTariff(hap, device))

return result


async def async_setup_entry(
hass: HomeAssistant,
Expand All @@ -98,109 +236,88 @@ async def async_setup_entry(
"""Set up the HomematicIP Cloud sensors from a config entry."""
hap = config_entry.runtime_data
entities: list[HomematicipGenericEntity] = []

# Get device handlers dynamically
device_handlers = get_device_handlers(hap)

# Process all devices
for device in hap.home.devices:
if isinstance(device, HomeControlAccessPoint):
entities.append(HomematicipAccesspointDutyCycle(hap, device))
if isinstance(
device,
(
HeatingThermostat,
HeatingThermostatCompact,
HeatingThermostatEvo,
),
):
entities.append(HomematicipHeatingThermostat(hap, device))
entities.append(HomematicipTemperatureSensor(hap, device))
if isinstance(
device,
(
TemperatureHumiditySensorDisplay,
TemperatureHumiditySensorWithoutDisplay,
TemperatureHumiditySensorOutdoor,
WeatherSensor,
WeatherSensorPlus,
WeatherSensorPro,
),
):
entities.append(HomematicipTemperatureSensor(hap, device))
entities.append(HomematicipHumiditySensor(hap, device))
entities.append(HomematicipAbsoluteHumiditySensor(hap, device))
elif isinstance(device, (RoomControlDeviceAnalog,)):
entities.append(HomematicipTemperatureSensor(hap, device))
if isinstance(
device,
(
LightSensor,
MotionDetectorIndoor,
MotionDetectorOutdoor,
MotionDetectorPushButton,
PresenceDetectorIndoor,
WeatherSensor,
WeatherSensorPlus,
WeatherSensorPro,
),
):
entities.append(HomematicipIlluminanceSensor(hap, device))
if isinstance(device, SwitchMeasuring):
entities.append(HomematicipPowerSensor(hap, device))
entities.append(HomematicipEnergySensor(hap, device))
if isinstance(device, (WeatherSensor, WeatherSensorPlus, WeatherSensorPro)):
entities.append(HomematicipWindspeedSensor(hap, device))
if isinstance(device, (WeatherSensorPlus, WeatherSensorPro)):
entities.append(HomematicipTodayRainSensor(hap, device))
if isinstance(device, PassageDetector):
entities.append(HomematicipPassageDetectorDeltaCounter(hap, device))
if isinstance(device, TemperatureDifferenceSensor2):
entities.append(HomematicpTemperatureExternalSensorCh1(hap, device))
entities.append(HomematicpTemperatureExternalSensorCh2(hap, device))
entities.append(HomematicpTemperatureExternalSensorDelta(hap, device))
if isinstance(device, EnergySensorsInterface):
for ch in get_channels_from_device(
device, FunctionalChannelType.ENERGY_SENSORS_INTERFACE_CHANNEL
):
if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_IEC:
if ch.currentPowerConsumption is not None:
entities.append(HmipEsiIecPowerConsumption(hap, device))
if ch.energyCounterOneType != ESI_TYPE_UNKNOWN:
entities.append(HmipEsiIecEnergyCounterHighTariff(hap, device))
if ch.energyCounterTwoType != ESI_TYPE_UNKNOWN:
entities.append(HmipEsiIecEnergyCounterLowTariff(hap, device))
if ch.energyCounterThreeType != ESI_TYPE_UNKNOWN:
entities.append(
HmipEsiIecEnergyCounterInputSingleTariff(hap, device)
)

if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_GAS:
if ch.currentGasFlow is not None:
entities.append(HmipEsiGasCurrentGasFlow(hap, device))
if ch.gasVolume is not None:
entities.append(HmipEsiGasGasVolume(hap, device))

if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_LED:
if ch.currentPowerConsumption is not None:
entities.append(HmipEsiLedCurrentPowerConsumption(hap, device))
entities.append(HmipEsiLedEnergyCounterHighTariff(hap, device))
if isinstance(
device,
(
FloorTerminalBlock6,
FloorTerminalBlock10,
FloorTerminalBlock12,
WiredFloorTerminalBlock12,
),
):
entities.extend(
HomematicipFloorTerminalBlockMechanicChannelValve(
hap, device, channel=channel.index
)
for channel in device.functionalChannels
if isinstance(channel, FloorTerminalBlockMechanicChannel)
and getattr(channel, "valvePosition", None) is not None
)
for device_class, handler in device_handlers.items():
if isinstance(device, device_class):
entities.extend(handler(device))

# Handle floor terminal blocks separately
floor_terminal_blocks = (
FloorTerminalBlock6,
FloorTerminalBlock10,
FloorTerminalBlock12,
WiredFloorTerminalBlock12,
)
entities.extend(
HomematicipFloorTerminalBlockMechanicChannelValve(
hap, device, channel=channel.index
)
for device in hap.home.devices
if isinstance(device, floor_terminal_blocks)
for channel in device.functionalChannels
if isinstance(channel, FloorTerminalBlockMechanicChannel)
and getattr(channel, "valvePosition", None) is not None
)

async_add_entities(entities)


class HomematicipTiltAngleSensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP tilt angle sensor."""

_attr_native_unit_of_measurement = DEGREE
_attr_state_class = SensorStateClass.MEASUREMENT_ANGLE

def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the tilt angle sensor device."""
super().__init__(hap, device, post="Tilt Angle")

@property
def native_value(self) -> int | None:
"""Return the state."""
return getattr(self.functional_channel, "absoluteAngle", None)


class HomematicipTiltStateSensor(HomematicipGenericEntity, SensorEntity):
"""Representation of the HomematicIP tilt sensor."""

_attr_device_class = SensorDeviceClass.ENUM
_attr_options = TILT_STATE_VALUES
_attr_translation_key = "tilt_state"

def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize the tilt sensor device."""
super().__init__(hap, device, post="Tilt State")

@property
def native_value(self) -> str | None:
"""Return the state."""
tilt_state = getattr(self.functional_channel, "tiltState", None)
return tilt_state.lower() if tilt_state is not None else None

@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes of the tilt sensor."""
state_attr = super().extra_state_attributes

state_attr[ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION] = getattr(
self.functional_channel, "accelerationSensorNeutralPosition", None
)
state_attr[ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE] = getattr(
self.functional_channel, "accelerationSensorTriggerAngle", None
)
state_attr[ATTR_ACCELERATION_SENSOR_SECOND_TRIGGER_ANGLE] = getattr(
self.functional_channel, "accelerationSensorSecondTriggerAngle", None
)

return state_attr


class HomematicipFloorTerminalBlockMechanicChannelValve(
HomematicipGenericEntity, SensorEntity
):
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/homematicip_cloud/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"entity": {
"sensor": {
"tilt_state": {
"state": {
"neutral": "Neutral",
"non_neutral": "Non-neutral",
"tilted": "Tilted"
}
}
}
},
"exceptions": {
"access_point_not_found": {
"message": "No matching access point found for access point ID {id}"
Expand Down
Loading
Loading
0