Skip to content
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

Support for Matter 1.4 Water Heater #131505

Open
wants to merge 70 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
04fdb50
Create water_heater.json
lboue Nov 21, 2024
bf1de2c
Update water_heater.json
lboue Nov 21, 2024
c84ea01
Update water_heater.json
lboue Nov 21, 2024
6bbef11
TankVolume
lboue Nov 21, 2024
9e053dc
TankPercentage
lboue Nov 21, 2024
41128e6
WaterHeaterMode
lboue Nov 21, 2024
091e867
Update sensor.py
lboue Nov 21, 2024
52769b9
ruff-format
lboue Nov 21, 2024
aa14636
Update water_heater.json
lboue Nov 22, 2024
7d3d677
Update test_sensor.py
lboue Nov 22, 2024
7fbb10d
Update test_sensor.py
lboue Nov 22, 2024
1e95f44
SensorDeviceClass=VOLUME_STORAGE for `TankVolume`
lboue Dec 6, 2024
1beb6bd
`BoostStateEnum` map
lboue Dec 6, 2024
395233a
WaterHeaterManagementBoostState
lboue Dec 6, 2024
72b8399
Update sensor.py
lboue Dec 6, 2024
6e7cf99
WaterHeaterManagementEstimatedHeatRequired
lboue Dec 6, 2024
b0c9f20
Fix UnitOfEnergy
lboue Dec 6, 2024
152f2f6
Format
lboue Dec 6, 2024
1df29b0
Add `device_types.WaterHeater` to Climate
lboue Dec 6, 2024
2b3ebdc
Strings for Tank sensors
lboue Dec 10, 2024
3881f08
WaterHeater icons
lboue Dec 10, 2024
b281f97
Update icons.json
lboue Dec 10, 2024
6a2f4ce
Update strings.json
lboue Dec 10, 2024
bca6658
Update water_heater.json
lboue Dec 19, 2024
54c6ff7
ruff-format
lboue Dec 30, 2024
e50399e
Merge branch 'dev' into WaterHeater
lboue Dec 30, 2024
6eb3ad7
Fix tests
lboue Dec 30, 2024
d5fe4e1
Fix sensor.py
lboue Dec 30, 2024
67e9d18
Fix icons
lboue Dec 30, 2024
b7c1d81
WaterHeaterManagementEstimatedHeatRequired
lboue Dec 30, 2024
3173f4f
WaterHeaterManagementBoostState
lboue Dec 30, 2024
01a0f80
BoostState as a binary sensor
lboue Dec 30, 2024
7736015
ElectricalPowerMeasurement values
lboue Dec 30, 2024
60aff94
Fix tests
lboue Dec 30, 2024
3c50c53
Create water_heater.py
lboue Jan 28, 2025
b234d9c
Update climate.py from dev branch
lboue Jan 28, 2025
a065876
Resolve conflicts
lboue Jan 28, 2025
77607c0
Merge branch 'dev' into WaterHeater
lboue Jan 28, 2025
bdfb9fc
ruff-format
lboue Jan 28, 2025
f4b6358
Add Platform.WATER_HEATER
lboue Jan 28, 2025
cf98fb5
Merge branch 'dev' into WaterHeater
lboue Jan 28, 2025
fa2ef33
Update water_heater.py
lboue Jan 28, 2025
49e27c1
Update water_heater.py
lboue Jan 28, 2025
eb1aaa3
Update water_heater.py
lboue Jan 28, 2025
ce9b646
Update water_heater.py
lboue Jan 28, 2025
6639cfe
Merge branch 'dev' into WaterHeater
lboue Jan 29, 2025
a754995
Add WaterHeaterManagement sensors
lboue Jan 29, 2025
792d51f
Update tests
lboue Jan 29, 2025
b573d9e
Add select test
lboue Jan 29, 2025
a6f5b24
Add strings
lboue Jan 29, 2025
a342d05
First try with water_heater
lboue Jan 29, 2025
93a2f14
Testing current_operation
lboue Jan 30, 2025
9eb0f73
BoostState attribute
lboue Jan 30, 2025
ed3150f
target_temperature attributes
lboue Jan 30, 2025
bbb6e97
target_temperature attribute
lboue Jan 30, 2025
79b01c4
set_temperature and set_operation_mode
lboue Jan 31, 2025
7de0c5d
turn_on / turn_off
lboue Jan 31, 2025
5e75311
Trigger Boost command
lboue Jan 31, 2025
f7ee6ea
Fix WaterHeaterBoostInfoStruct
lboue Jan 31, 2025
7e722e2
Add test file
lboue Jan 31, 2025
8111064
Add climate cluster to fixture
lboue Jan 31, 2025
7a49667
Add climate cluster to fixture
lboue Jan 31, 2025
5126b37
Add tests
lboue Jan 31, 2025
1fd713b
Add ON_OFF feature
lboue Jan 31, 2025
97883b2
Update tests
lboue Jan 31, 2025
344978b
Update tests
lboue Jan 31, 2025
26b8c1d
Translate WaterHeaterMode
lboue Feb 4, 2025
8996a94
Change description
lboue Feb 4, 2025
e4fa5b2
Update test and snapshots
lboue Feb 4, 2025
79a359c
Update snapshots
lboue Feb 4, 2025
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
12 changes: 12 additions & 0 deletions homeassistant/components/matter/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,16 @@ def _update_from_device(self) -> None:
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.InterconnectCOAlarm,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="WaterHeaterManagementBoostStateSensor",
translation_key="boost_state",
measurement_to_ha=lambda x: (
x == clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.WaterHeaterManagement.Attributes.BoostState,),
),
]
2 changes: 2 additions & 0 deletions homeassistant/components/matter/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .update import DISCOVERY_SCHEMAS as UPDATE_SCHEMAS
from .vacuum import DISCOVERY_SCHEMAS as VACUUM_SCHEMAS
from .valve import DISCOVERY_SCHEMAS as VALVE_SCHEMAS
from .water_heater import DISCOVERY_SCHEMAS as WATER_HEATER_SCHEMAS

DISCOVERY_SCHEMAS: dict[Platform, list[MatterDiscoverySchema]] = {
Platform.BINARY_SENSOR: BINARY_SENSOR_SCHEMAS,
Expand All @@ -44,6 +45,7 @@
Platform.UPDATE: UPDATE_SCHEMAS,
Platform.VACUUM: VACUUM_SCHEMAS,
Platform.VALVE: VALVE_SCHEMAS,
Platform.WATER_HEATER: WATER_HEATER_SCHEMAS,
}
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS)

Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/matter/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@
"operational_state": {
"default": "mdi:play-pause"
},
"tank_volume": {
"default": "mdi:water-boiler"
},
"tank_percentage": {
"default": "mdi:water-boiler"
},
"valve_position": {
"default": "mdi:valve"
},
Expand Down
13 changes: 13 additions & 0 deletions homeassistant/components/matter/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
| clusters.DishwasherMode
| clusters.EnergyEvseMode
| clusters.DeviceEnergyManagementMode
| clusters.WaterHeaterMode
)


Expand Down Expand Up @@ -302,6 +303,18 @@ def _update_from_device(self) -> None:
clusters.DeviceEnergyManagementMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterWaterHeaterMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.WaterHeaterMode.Attributes.CurrentMode,
clusters.WaterHeaterMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
Expand Down
48 changes: 47 additions & 1 deletion homeassistant/components/matter/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
UnitOfPower,
UnitOfPressure,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant, callback
Expand Down Expand Up @@ -65,7 +66,6 @@
clusters.SmokeCoAlarm.Enums.ContaminationStateEnum.kCritical: "critical",
}


OPERATIONAL_STATE_MAP = {
# enum with known Operation state values which we can translate
clusters.OperationalState.Enums.OperationalStateEnum.kStopped: "stopped",
Expand All @@ -77,6 +77,12 @@
clusters.RvcOperationalState.Enums.OperationalStateEnum.kDocked: "docked",
}

BOOST_STATE_MAP = {
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kInactive: "inactive",
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive: "active",
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kUnknownEnumValue: None,
}


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -892,4 +898,44 @@ def _update_from_device(self) -> None:
clusters.OvenCavityOperationalState.Attributes.OperationalStateList,
),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="WaterHeaterManagementTankVolume",
translation_key="tank_volume",
device_class=SensorDeviceClass.VOLUME_STORAGE,
native_unit_of_measurement=UnitOfVolume.LITERS,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(clusters.WaterHeaterManagement.Attributes.TankVolume,),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="WaterHeaterManagementTankPercentage",
translation_key="tank_percentage",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(clusters.WaterHeaterManagement.Attributes.TankPercentage,),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="WaterHeaterManagementEstimatedHeatRequired",
translation_key="estimated_heat_required",
device_class=SensorDeviceClass.ENERGY,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfEnergy.MILLIWATT_HOUR,
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
suggested_display_precision=3,
state_class=SensorStateClass.TOTAL,
),
entity_class=MatterSensor,
required_attributes=(
clusters.WaterHeaterManagement.Attributes.EstimatedHeatRequired,
),
),
]
17 changes: 17 additions & 0 deletions homeassistant/components/matter/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
},
"muted": {
"name": "Muted"
},
"boost_state": {
"name": "Boost state"
}
},
"button": {
Expand Down Expand Up @@ -267,6 +270,15 @@
"switch_current_position": {
"name": "Current switch position"
},
"tank_volume": {
"name": "Tank volume"
},
"tank_percentage": {
"name": "Tank percentage"
},
"estimated_heat_required": {
"name": "Heat energy needed"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong:
If that is the estimated energy needed to heat the water that should be "Required heating energy"

"heat energy" sounds more like the energy contained in hot water.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you. I will fix it.

},
"valve_position": {
"name": "Valve position"
},
Expand Down Expand Up @@ -297,6 +309,11 @@
"valve": {
"name": "[%key:component::valve::title%]"
}
},
"water_heater": {
"water_heater": {
"name": "Water heater"
}
}
},
"issues": {
Expand Down
205 changes: 205 additions & 0 deletions homeassistant/components/matter/water_heater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
"""Matter water heater platform."""

from __future__ import annotations

from typing import Any, cast

from chip.clusters import Objects as clusters
from matter_server.client.models import device_types
from matter_server.common.helpers.util import create_attribute_path_from_attribute

from homeassistant.components.water_heater import (
STATE_ECO,
STATE_HIGH_DEMAND,
STATE_OFF,
WaterHeaterEntity,
WaterHeaterEntityDescription,
WaterHeaterEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_TEMPERATURE,
PRECISION_WHOLE,
Platform,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .entity import MatterEntity
from .helpers import get_matter
from .models import MatterDiscoverySchema

SUPPORT_FLAGS_HEATER = (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
| WaterHeaterEntityFeature.OPERATION_MODE
)
TEMPERATURE_SCALING_FACTOR = 100

WATER_HEATER_SYSTEM_MODE_MAP = {
STATE_ECO: 4,
STATE_HIGH_DEMAND: 4,
STATE_OFF: 0,
}

BOOST_STATE_MAP = {
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kInactive: "inactive",
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive: "active",
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kUnknownEnumValue: None,
}


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Matter WaterHeater platform from Config Entry."""
matter = get_matter(hass)
matter.register_platform_handler(Platform.WATER_HEATER, async_add_entities)


class MatterWaterHeater(MatterEntity, WaterHeaterEntity):
"""Representation of a Matter WaterHeater entity."""

_attr_current_temperature: float | None = None
_attr_current_operation: str
_attr_operation_list = [
STATE_ECO,
STATE_HIGH_DEMAND,
STATE_OFF,
]
_attr_precision = PRECISION_WHOLE
_attr_supported_features = (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
| WaterHeaterEntityFeature.OPERATION_MODE
)
_attr_target_temperature: float | None = None
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_platform_translation_key = "water_heater"

async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperatures."""
target_temperature: float | None = kwargs.get(ATTR_TEMPERATURE)
self._attr_target_temperature = target_temperature
if target_temperature is not None:
if self.target_temperature != target_temperature:
matter_attribute = (

Check warning on line 89 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L85-L89

Added lines #L85 - L89 were not covered by tests
clusters.Thermostat.Attributes.OccupiedHeatingSetpoint
)
await self.write_attribute(

Check warning on line 92 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L92

Added line #L92 was not covered by tests
value=int(target_temperature * TEMPERATURE_SCALING_FACTOR),
matter_attribute=matter_attribute,
)
return

Check warning on line 96 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L96

Added line #L96 was not covered by tests

async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set new operation mode."""
self._attr_current_operation = operation_mode

Check warning on line 100 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L100

Added line #L100 was not covered by tests
# Boost 1h (3600s)
boostInfo: type[

Check warning on line 102 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L102

Added line #L102 was not covered by tests
clusters.WaterHeaterManagement.Structs.WaterHeaterBoostInfoStruct
] = clusters.WaterHeaterManagement.Structs.WaterHeaterBoostInfoStruct(
duration=3600
)
system_mode_value = WATER_HEATER_SYSTEM_MODE_MAP.get(operation_mode)
if system_mode_value is None:
raise ValueError(f"Unsupported hvac mode {operation_mode} in Matter")
await self.write_attribute(

Check warning on line 110 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L107-L110

Added lines #L107 - L110 were not covered by tests
value=system_mode_value,
matter_attribute=clusters.Thermostat.Attributes.SystemMode,
)
system_mode_path = create_attribute_path_from_attribute(

Check warning on line 114 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L114

Added line #L114 was not covered by tests
endpoint_id=self._endpoint.endpoint_id,
attribute=clusters.Thermostat.Attributes.SystemMode,
)
self._endpoint.set_attribute_value(system_mode_path, system_mode_value)
self._update_from_device()

Check warning on line 119 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L118-L119

Added lines #L118 - L119 were not covered by tests
# Trigger Boost command
if operation_mode == STATE_HIGH_DEMAND:
await self.send_device_command(

Check warning on line 122 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L121-L122

Added lines #L121 - L122 were not covered by tests
clusters.WaterHeaterManagement.Commands.Boost(boostInfo=boostInfo)
)

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on water heater."""
await self.async_set_operation_mode("eco")

Check warning on line 128 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L128

Added line #L128 was not covered by tests

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off water heater."""
await self.async_set_operation_mode("off")

Check warning on line 132 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L132

Added line #L132 was not covered by tests

@callback
def _update_from_device(self) -> None:
"""Update from device."""
# self._calculate_features()
self._attr_current_temperature = self._get_temperature_in_degrees(
clusters.Thermostat.Attributes.LocalTemperature
)
self._attr_target_temperature = self._get_temperature_in_degrees(
clusters.Thermostat.Attributes.OccupiedHeatingSetpoint
)
BoostState = self.get_matter_attribute_value(
clusters.WaterHeaterManagement.Attributes.BoostState
)
if (BoostState) == clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive:
self._attr_current_operation = STATE_HIGH_DEMAND

Check warning on line 148 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L148

Added line #L148 was not covered by tests
else:
self._attr_current_operation = STATE_ECO
self._attr_temperature = cast(
float,
self._get_temperature_in_degrees(
clusters.Thermostat.Attributes.OccupiedHeatingSetpoint
),
)
self._attr_min_temp = cast(
float,
self._get_temperature_in_degrees(
clusters.Thermostat.Attributes.AbsMinHeatSetpointLimit
),
)
self._attr_max_temp = cast(
float,
self._get_temperature_in_degrees(
clusters.Thermostat.Attributes.AbsMaxHeatSetpointLimit
),
)
self._attr_target_temperature_low = self._attr_min_temp
self._attr_target_temperature_high = self._attr_max_temp

@callback
def _get_temperature_in_degrees(
self, attribute: type[clusters.ClusterAttributeDescriptor]
) -> float | None:
"""Return the scaled temperature value for the given attribute."""
if value := self.get_matter_attribute_value(attribute):
return float(value) / TEMPERATURE_SCALING_FACTOR
return None

Check warning on line 179 in homeassistant/components/matter/water_heater.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/matter/water_heater.py#L179

Added line #L179 was not covered by tests


# Discovery schema(s) to map Matter Attributes to HA entities
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.WATER_HEATER,
entity_description=WaterHeaterEntityDescription(
key="MatterWaterHeater",
),
entity_class=MatterWaterHeater,
required_attributes=(
clusters.Thermostat.Attributes.OccupiedHeatingSetpoint,
clusters.Thermostat.Attributes.AbsMinHeatSetpointLimit,
clusters.Thermostat.Attributes.AbsMaxHeatSetpointLimit,
clusters.Thermostat.Attributes.LocalTemperature,
clusters.WaterHeaterManagement.Attributes.FeatureMap,
),
optional_attributes=(
clusters.WaterHeaterManagement.Attributes.HeaterTypes,
clusters.WaterHeaterManagement.Attributes.BoostState,
clusters.WaterHeaterManagement.Attributes.HeatDemand,
),
device_type=(device_types.WaterHeater,),
allow_multi=True, # also used for sensor entity
),
]
1 change: 1 addition & 0 deletions tests/components/matter/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ async def integration_fixture(
"room_airconditioner",
"silabs_dishwasher",
"silabs_laundrywasher",
"silabs_water_heater",
"smoke_detector",
"switch_unit",
"temperature_sensor",
Expand Down
Loading
Loading