Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f7bddbb
Implement water_heater platform for DHW function
bouwew Jun 7, 2026
6d56366
Ruffed
bouwew Jun 7, 2026
367cbb2
Add test_water_heater.py
bouwew Jun 7, 2026
88d261f
Improve water_heater and supported_features detection
bouwew Jun 7, 2026
baada33
Correct mocked_adam to fixture with water_heater
bouwew Jun 7, 2026
ca9842f
Fixes
bouwew Jun 7, 2026
38a476d
Save new test_water_heater snapshot
bouwew Jun 7, 2026
c23e788
Add anna_v4_dhw fixture
bouwew Jun 7, 2026
d1f5714
Add 2nd water_heater testcase
bouwew Jun 7, 2026
90b23bc
Save updates: snapshot, ruffed
bouwew Jun 7, 2026
84a31e3
Update related test asserts
bouwew Jun 7, 2026
81bebfd
Add water_heater async_set_temperature()
bouwew Jun 7, 2026
4b57714
Add test case
bouwew Jun 7, 2026
9434959
Add missing import
bouwew Jun 7, 2026
18ed93c
Ruffed
bouwew Jun 7, 2026
e620c6e
Number: remove max_dhw-temperature, handled by water_heater
bouwew Jun 7, 2026
cca185e
Revert assert update after remove double number entity
bouwew Jun 7, 2026
b728755
Save updated number snapshot
bouwew Jun 7, 2026
4b7f07c
hass -> _hass
bouwew Jun 7, 2026
59576b8
Correct current_operation modes, as suggested
bouwew Jun 7, 2026
d8aa416
Re-ruffed
bouwew Jun 7, 2026
0647495
Improve, as suggested
bouwew Jun 7, 2026
fe2269a
Change modes, add set_operation_mode(), and more
bouwew Jun 7, 2026
7f2085a
Correct W0237
bouwew Jun 7, 2026
970a013
Save snapshot updates
bouwew Jun 7, 2026
34c158d
Tighten water_heater criterium
bouwew Jun 13, 2026
d31e3dd
Adapt async_set_operation_mode() to backend update
bouwew Jun 13, 2026
538aed7
Call set_dwh_mode() directly
bouwew Jun 14, 2026
5cdbede
Link to updated library
bouwew Jun 14, 2026
08ed08a
Refresh test-fixtures
bouwew Jun 14, 2026
70cddf2
Remove dhw_cm_switch, function moved to water_heater platform
bouwew Jun 14, 2026
1e34876
Save updated select snapshot
bouwew Jun 14, 2026
734cb74
Cleaning up
bouwew Jun 14, 2026
16c2ebb
Simplify, remove logging, add pragma-no cover
bouwew Jun 14, 2026
dc5d527
Add set_operation_mode test
bouwew Jun 14, 2026
e9ffdb0
Revert LOGGER removal
bouwew Jun 14, 2026
6080c8a
Update current_operation property
bouwew Jun 14, 2026
d5cc98c
Collect operation_list from device
bouwew Jun 14, 2026
c6a29cb
Fix test
bouwew Jun 14, 2026
71bd824
Fix water_heater typing
bouwew Jun 14, 2026
4115ebd
Save updated test snapshot
bouwew Jun 14, 2026
59ea17a
Update strings.json, translations
bouwew Jun 14, 2026
e0a37f2
Prettier fixes
bouwew Jun 14, 2026
14e8479
Rework water_heater strings
bouwew Jun 14, 2026
f1f0306
Pass length of operation_list to determine set-method
bouwew Jun 14, 2026
6e5d5c8
Fix set_dhw_mode() args
bouwew Jun 16, 2026
4f6b6bc
Fix test assert
bouwew Jun 16, 2026
5ba1e83
Add pragma-no-cover
bouwew Jun 16, 2026
ed21630
Replace strings by constants
bouwew Jun 16, 2026
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
2 changes: 2 additions & 0 deletions custom_components/plugwise/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@

# Select constants
AVAILABLE_SCHEDULES: Final = "available_schedules"
DHW_MODE: Final = "dhw_mode"
DHW_MODES: Final = "dhw_modes"
GATEWAY_MODES: Final = "gateway_modes"
REGULATION_MODES: Final = "regulation_modes"
Expand Down Expand Up @@ -165,6 +166,7 @@
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.WATER_HEATER,
]
SERVICE_DELETE: Final = "delete_notification"
SEVERITIES: Final[list[str]] = ["other", "info", "message", "warning", "error"]
Expand Down
4 changes: 3 additions & 1 deletion custom_components/plugwise/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["plugwise"],
"requirements": ["plugwise==1.11.4"],
"requirements": [
"plugwise@git+https://github.com/plugwise/python-plugwise.git/@dhw_update"
],
"version": "0.64.4",
"zeroconf": ["_plugwise._tcp.local."]
}
8 changes: 0 additions & 8 deletions custom_components/plugwise/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
LOGGER,
LOWER_BOUND,
MAX_BOILER_TEMP,
MAX_DHW_TEMP,
RESOLUTION,
TEMPERATURE_OFFSET,
UPPER_BOUND,
Expand Down Expand Up @@ -47,13 +46,6 @@ class PlugwiseNumberEntityDescription(NumberEntityDescription):
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
PlugwiseNumberEntityDescription(
key=MAX_DHW_TEMP,
translation_key=MAX_DHW_TEMP,
device_class=NumberDeviceClass.TEMPERATURE,
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
PlugwiseNumberEntityDescription(
key=TEMPERATURE_OFFSET,
translation_key=TEMPERATURE_OFFSET,
Expand Down
16 changes: 14 additions & 2 deletions custom_components/plugwise/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@
"select_dhw_mode": {
"name": "DHW mode",
"state": {
"auto": "Auto",
"boost": "Boost",
"comfort": "Comfort",
"off": "Off"
}
Expand Down Expand Up @@ -299,6 +297,20 @@
"relay": {
"name": "Relay"
}
},
"water_heater": {
"plugwise": {
"state_attributes": {
"dhw_mode": {
"state": {
"auto": "Auto",
"boost": "Boost",
"comfort": "Comfort",
"off": "Off"
}
}
}
}
}
},
"exceptions": {
Expand Down
7 changes: 0 additions & 7 deletions custom_components/plugwise/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from .const import (
COOLING_ENA_SWITCH,
DHW_CM_SWITCH,
LOCK,
LOGGER, # pw-beta
MEMBERS,
Expand All @@ -41,12 +40,6 @@ class PlugwiseSwitchEntityDescription(SwitchEntityDescription):

# Upstream consts
PLUGWISE_SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = (
PlugwiseSwitchEntityDescription(
key=DHW_CM_SWITCH,
translation_key=DHW_CM_SWITCH,
device_class=SwitchDeviceClass.SWITCH,
entity_category=EntityCategory.CONFIG,
),
PlugwiseSwitchEntityDescription(
key=LOCK,
translation_key=LOCK,
Expand Down
16 changes: 14 additions & 2 deletions custom_components/plugwise/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@
"select_dhw_mode": {
"name": "DHW mode",
"state": {
"auto": "Auto",
"boost": "Boost",
"comfort": "Comfort",
"off": "Off"
}
Expand Down Expand Up @@ -299,6 +297,20 @@
"relay": {
"name": "Relay"
}
},
"water_heater": {
"plugwise": {
"state_attributes": {
"dhw_mode": {
"state": {
"auto": "Auto",
"boost": "Boost",
"comfort": "Comfort",
"off": "Off"
}
}
}
}
}
},
"exceptions": {
Expand Down
16 changes: 14 additions & 2 deletions custom_components/plugwise/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@
"select_dhw_mode": {
"name": "SWW modus",
"state": {
"auto": "Automatisch",
"boost": "Boost",
"comfort": "Comfort",
"off": "Uit"
}
Expand Down Expand Up @@ -299,6 +297,20 @@
"relay": {
"name": "Schakelaar"
}
},
"water_heater": {
"plugwise": {
"state_attributes": {
"dhw_mode": {
"state": {
"auto": "Automatisch",
"boost": "Boost",
"comfort": "Comfort",
"off": "Off"
}
}
}
}
}
},
"exceptions": {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
118 changes: 118 additions & 0 deletions custom_components/plugwise/water_heater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Plugwise water heater component for HomeAssistant."""

from typing import Any

from homeassistant.components.water_heater import (
WaterHeaterEntity,
WaterHeaterEntityFeature,
)
from homeassistant.const import (
ATTR_NAME,
ATTR_TEMPERATURE,
STATE_OFF,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import (
DHW_MODE,
DHW_MODES,
DHW_SETPOINT,
LOGGER,
LOWER_BOUND,
MAX_DHW_TEMP,
SENSORS,
TARGET_TEMP,
UPPER_BOUND,
)
from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity
from .util import plugwise_command


async def async_setup_entry(

Check warning on line 34 in custom_components/plugwise/water_heater.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=plugwise_plugwise-beta&issues=AZ6iBNOYDIaptS4_727P&open=AZ6iBNOYDIaptS4_727P&pullRequest=1085
_hass: HomeAssistant,
entry: PlugwiseConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Plugwise water_heater from a config entry."""
coordinator = entry.runtime_data

@callback
def _add_entities() -> None:
"""Add Entities."""
if not coordinator.new_devices:
return

entities: list[PlugwiseWaterHeaterEntity] = []
for device_id in coordinator.new_devices:
device = coordinator.data[device_id]
if device.get(MAX_DHW_TEMP) is not None:
entities.append(PlugwiseWaterHeaterEntity(coordinator, device_id))
LOGGER.debug("Add %s water_heater", device[ATTR_NAME])
async_add_entities(entities)

_add_entities()
entry.async_on_unload(coordinator.async_add_listener(_add_entities))


class PlugwiseWaterHeaterEntity(PlugwiseEntity, WaterHeaterEntity):
"""Representation of a Plugwise water heater."""

_attr_name = None
_attr_temperature_unit = UnitOfTemperature.CELSIUS

def __init__(
self,
coordinator: PlugwiseDataUpdateCoordinator,
device_id: str,
) -> None:
"""Initialise the water_heater."""
super().__init__(coordinator, device_id)
self._attr_unique_id = f"{device_id}-water_heater"

max_dhw_temp_bounds = self.device.get(MAX_DHW_TEMP, {})
if max_dhw_temp_bounds is not None:
self._attr_max_temp = max_dhw_temp_bounds.get(UPPER_BOUND, 75.0)
self._attr_min_temp = max_dhw_temp_bounds.get(LOWER_BOUND, 40.0)
self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE
self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE


@property
def current_operation(self) -> str | None:
"""Return current readable operation mode."""
return self.device.get(DHW_MODE)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
@property
def current_temperature(self) -> float | None:
"""Return the current water temperature."""
return self.device.get(SENSORS, {}).get("water_temperature")

@property
def operation_list(self) -> list[str]:
"""Return the list of available operation modes."""
if (op_list := self.device.get(DHW_MODES, [])):
return op_list
return [STATE_OFF] # pragma: no cover

@property
def target_temperature(self) -> float | None:
"""Return the water temperature we try to reach."""
return (
self.device.get(MAX_DHW_TEMP, {}).get(TARGET_TEMP)
or self.device.get(SENSORS, {}).get(DHW_SETPOINT)
)

@plugwise_command
async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set the operation mode."""
list_type: int = len(self.operation_list)
await self.coordinator.api.set_dhw_mode(DHW_MODE, self._dev_id, list_type, operation_mode)

@plugwise_command
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None:
await self.coordinator.api.set_number(self._dev_id, MAX_DHW_TEMP, float(temperature))
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"heating_state": true
},
"dev_class": "heater_central",
"dhw_modes": [
"comfort",
"off"
],
"location": "bc93488efab249e5bc54fd7e175a6f91",
"maximum_boiler_temperature": {
"lower_bound": 25.0,
Expand All @@ -16,12 +20,10 @@
},
"model": "Generic heater",
"name": "OpenTherm",
"select_dhw_mode": "off",
"sensors": {
"intended_boiler_temperature": 22.5,
"water_temperature": 43.0
},
"switches": {
"dhw_cm_switch": false
}
},
"10016900610d4c7481df78c89606ef22": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"secondary_boiler_state": false
},
"dev_class": "heater_central",
"dhw_mode": "off",
"dhw_modes": [
"comfort",
"off"
],
"location": "a57efe5f145f498c9be62a9b63626fbf",
"max_dhw_temperature": {
"lower_bound": 35.0,
Expand All @@ -53,9 +58,6 @@
"water_pressure": 1.57,
"water_temperature": 29.1
},
"switches": {
"dhw_cm_switch": false
},
"vendor": "Techneco"
},
"3cb70739631c4d17a86b8b12e8a5161b": {
Expand Down
8 changes: 5 additions & 3 deletions tests/components/plugwise/fixtures/anna_p1/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,21 @@
"heating_state": false
},
"dev_class": "heater_central",
"dhw_modes": [
"comfort",
"off"
],
"location": "da7be222ab3b420c927f3e49fade0304",
"model": "Generic heater",
"model_id": "HR24",
"name": "OpenTherm",
"select_dhw_mode": "comfort",
"sensors": {
"intended_boiler_temperature": 0.0,
"modulation_level": 0.0,
"water_pressure": 6.0,
"water_temperature": 35.0
},
"switches": {
"dhw_cm_switch": true
},
"vendor": "Intergas"
},
"53130847be2f436cb946b78dedb9053a": {
Expand Down
8 changes: 5 additions & 3 deletions tests/components/plugwise/fixtures/anna_v4/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
"heating_state": true
},
"dev_class": "heater_central",
"dhw_mode": "off",
"dhw_modes": [
"comfort",
"off"
],
"location": "94c107dc6ac84ed98e9f68c0dd06bf71",
"max_dhw_temperature": {
"lower_bound": 30.0,
Expand All @@ -90,9 +95,6 @@
"water_pressure": 2.2,
"water_temperature": 45.0
},
"switches": {
"dhw_cm_switch": false
},
"vendor": "Bosch Thermotechniek B.V."
}
}
Loading
Loading