Skip to content

Commit

Permalink
Additional sensor for Weheat integration (#125524)
Browse files Browse the repository at this point in the history
* Added additional sensor to Weheat

* Added tests for old and new sensors

* Added energy sensor

* Changed tests to use snapshot

* Removed unused value and regenerated the ambr

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <[email protected]>

* changed DHW sensor creation

* Wrapped lambda function

---------

Co-authored-by: Joost Lekkerkerker <[email protected]>
  • Loading branch information
jesperraemaekers and joostlek authored Sep 14, 2024
1 parent 9eb3d84 commit e92d931
Show file tree
Hide file tree
Showing 10 changed files with 974 additions and 12 deletions.
1 change: 1 addition & 0 deletions homeassistant/components/weheat/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@

DISPLAY_PRECISION_WATTS = 0
DISPLAY_PRECISION_COP = 1
DISPLAY_PRECISION_WATER_TEMP = 1
14 changes: 7 additions & 7 deletions homeassistant/components/weheat/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,27 @@ def __init__(
name=DOMAIN,
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)
self._heat_pump_info = heat_pump
self._heat_pump_data = HeatPump(API_URL, self._heat_pump_info.uuid)
self.heat_pump_info = heat_pump
self._heat_pump_data = HeatPump(API_URL, heat_pump.uuid)

self.session = session

@property
def heatpump_id(self) -> str:
"""Return the heat pump id."""
return self._heat_pump_info.uuid
return self.heat_pump_info.uuid

@property
def readable_name(self) -> str | None:
"""Return the readable name of the heat pump."""
if self._heat_pump_info.name:
return self._heat_pump_info.name
return self._heat_pump_info.model
if self.heat_pump_info.name:
return self.heat_pump_info.name
return self.heat_pump_info.model

@property
def model(self) -> str:
"""Return the model of the heat pump."""
return self._heat_pump_info.model
return self.heat_pump_info.model

def fetch_data(self) -> HeatPump:
"""Get the data from the API."""
Expand Down
24 changes: 24 additions & 0 deletions homeassistant/components/weheat/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@
},
"cop": {
"default": "mdi:speedometer"
},
"water_inlet_temperature": {
"default": "mdi:thermometer"
},
"water_outlet_temperature": {
"default": "mdi:thermometer"
},
"ch_inlet_temperature": {
"default": "mdi:radiator"
},
"outside_temperature": {
"default": "mdi:home-thermometer-outline"
},
"dhw_top_temperature": {
"default": "mdi:thermometer"
},
"dhw_bottom_temperature": {
"default": "mdi:thermometer"
},
"heat_pump_state": {
"default": "mdi:state-machine"
},
"electricity_used": {
"default": "mdi:flash"
}
}
}
Expand Down
96 changes: 93 additions & 3 deletions homeassistant/components/weheat/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfPower
from homeassistant.const import UnitOfEnergy, UnitOfPower, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from . import WeheatConfigEntry
from .const import DISPLAY_PRECISION_COP, DISPLAY_PRECISION_WATTS
from .const import (
DISPLAY_PRECISION_COP,
DISPLAY_PRECISION_WATER_TEMP,
DISPLAY_PRECISION_WATTS,
)
from .coordinator import WeheatDataUpdateCoordinator
from .entity import WeheatEntity

Expand Down Expand Up @@ -55,6 +59,84 @@ class WeHeatSensorEntityDescription(SensorEntityDescription):
suggested_display_precision=DISPLAY_PRECISION_COP,
value_fn=lambda status: status.cop,
),
WeHeatSensorEntityDescription(
translation_key="water_inlet_temperature",
key="water_inlet_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=DISPLAY_PRECISION_WATER_TEMP,
value_fn=lambda status: status.water_inlet_temperature,
),
WeHeatSensorEntityDescription(
translation_key="water_outlet_temperature",
key="water_outlet_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=DISPLAY_PRECISION_WATER_TEMP,
value_fn=lambda status: status.water_outlet_temperature,
),
WeHeatSensorEntityDescription(
translation_key="ch_inlet_temperature",
key="ch_inlet_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=DISPLAY_PRECISION_WATER_TEMP,
value_fn=lambda status: status.water_house_in_temperature,
),
WeHeatSensorEntityDescription(
translation_key="outside_temperature",
key="outside_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=DISPLAY_PRECISION_WATER_TEMP,
value_fn=lambda status: status.air_inlet_temperature,
),
WeHeatSensorEntityDescription(
translation_key="heat_pump_state",
key="heat_pump_state",
name=None,
device_class=SensorDeviceClass.ENUM,
options=[s.name.lower() for s in HeatPump.State],
value_fn=(
lambda status: status.heat_pump_state.name.lower()
if status.heat_pump_state
else None
),
),
WeHeatSensorEntityDescription(
translation_key="electricity_used",
key="electricity_used",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda status: status.energy_total,
),
]


DHW_SENSORS = [
WeHeatSensorEntityDescription(
translation_key="dhw_top_temperature",
key="dhw_top_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=DISPLAY_PRECISION_WATER_TEMP,
value_fn=lambda status: status.dhw_top_temperature,
),
WeHeatSensorEntityDescription(
translation_key="dhw_bottom_temperature",
key="dhw_bottom_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=DISPLAY_PRECISION_WATER_TEMP,
value_fn=lambda status: status.dhw_bottom_temperature,
),
]


Expand All @@ -64,12 +146,20 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the sensors for weheat heat pump."""
async_add_entities(
entities = [
WeheatHeatPumpSensor(coordinator, entity_description)
for entity_description in SENSORS
for coordinator in entry.runtime_data
]
entities.extend(
WeheatHeatPumpSensor(coordinator, entity_description)
for entity_description in DHW_SENSORS
for coordinator in entry.runtime_data
if coordinator.heat_pump_info.has_dhw
)

async_add_entities(entities)


class WeheatHeatPumpSensor(WeheatEntity, SensorEntity):
"""Defines a Weheat heat pump sensor."""
Expand Down
34 changes: 34 additions & 0 deletions homeassistant/components/weheat/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,40 @@
},
"cop": {
"name": "COP"
},
"water_inlet_temperature": {
"name": "Water inlet temperature"
},
"water_outlet_temperature": {
"name": "Water outlet temperature"
},
"ch_inlet_temperature": {
"name": "Central heating inlet temperature"
},
"outside_temperature": {
"name": "Outside temperature"
},
"dhw_top_temperature": {
"name": "DHW top temperature"
},
"dhw_bottom_temperature": {
"name": "DHW bottom temperature"
},
"heat_pump_state": {
"state": {
"standby": "[%key:common::state::standby%]",
"water_check": "Checking water temperature",
"heating": "Heating",
"cooling": "Cooling",
"dhw": "Heating DHW",
"legionella_prevention": "Legionella prevention",
"defrosting": "Defrosting",
"self_test": "Self test",
"manual_control": "Manual control"
}
},
"electricity_used": {
"name": "Electricity used"
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions tests/components/weheat/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
"""Tests for the Weheat integration."""

from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry


async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the component."""
config_entry.add_to_hass(hass)

await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
84 changes: 82 additions & 2 deletions tests/components/weheat/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Fixtures for Weheat tests."""

from unittest.mock import patch
from collections.abc import Generator
from time import time
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from weheat.abstractions.discovery import HeatPumpDiscovery
from weheat.abstractions.heat_pump import HeatPump

from homeassistant.components.application_credentials import (
DOMAIN as APPLICATION_CREDENTIALS,
Expand All @@ -13,7 +17,9 @@
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

from .const import CLIENT_ID, CLIENT_SECRET
from .const import CLIENT_ID, CLIENT_SECRET, TEST_HP_UUID, TEST_MODEL, TEST_SN

from tests.common import MockConfigEntry


@pytest.fixture(autouse=True)
Expand All @@ -34,3 +40,77 @@ def mock_setup_entry():
"homeassistant.components.weheat.async_setup_entry", return_value=True
) as mock_setup:
yield mock_setup


@pytest.fixture
def mock_heat_pump_info() -> HeatPumpDiscovery.HeatPumpInfo:
"""Create a HeatPumpInfo with default settings."""
return HeatPumpDiscovery.HeatPumpInfo(TEST_HP_UUID, None, TEST_MODEL, TEST_SN, True)


@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock a config entry."""
return MockConfigEntry(
domain=DOMAIN,
title="Weheat",
data={
"id": "12345",
"auth_implementation": DOMAIN,
"token": {
"refresh_token": "mock-refresh-token",
"access_token": "mock-access-token",
"type": "Bearer",
"expires_in": 60,
"expires_at": time() + 60,
},
},
unique_id="123456789",
)


@pytest.fixture
def mock_weheat_discover(mock_heat_pump_info) -> Generator[AsyncMock]:
"""Mock an Weheat discovery."""
with (
patch(
"homeassistant.components.weheat.HeatPumpDiscovery.discover_active",
autospec=True,
) as mock_discover,
):
mock_discover.return_value = [mock_heat_pump_info]

yield mock_discover


@pytest.fixture
def mock_weheat_heat_pump_instance() -> MagicMock:
"""Mock an Weheat heat pump instance with a set of default values."""
mock_heat_pump_instance = MagicMock(spec_set=HeatPump)

mock_heat_pump_instance.water_inlet_temperature = 11
mock_heat_pump_instance.water_outlet_temperature = 22
mock_heat_pump_instance.water_house_in_temperature = 33
mock_heat_pump_instance.air_inlet_temperature = 44
mock_heat_pump_instance.power_input = 55
mock_heat_pump_instance.power_output = 66
mock_heat_pump_instance.dhw_top_temperature = 77
mock_heat_pump_instance.dhw_bottom_temperature = 88
mock_heat_pump_instance.cop = 4.5
mock_heat_pump_instance.heat_pump_state = HeatPump.State.HEATING
mock_heat_pump_instance.energy_total = 12345

return mock_heat_pump_instance


@pytest.fixture
def mock_weheat_heat_pump(mock_weheat_heat_pump_instance) -> Generator[AsyncMock]:
"""Mock the coordinator HeatPump data."""
with (
patch(
"homeassistant.components.weheat.coordinator.HeatPump",
) as mock_heat_pump,
):
mock_heat_pump.return_value = mock_weheat_heat_pump_instance

yield mock_weheat_heat_pump_instance
5 changes: 5 additions & 0 deletions tests/components/weheat/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@
CONF_AUTH_IMPLEMENTATION = "auth_implementation"
MOCK_REFRESH_TOKEN = "mock_refresh_token"
MOCK_ACCESS_TOKEN = "mock_access_token"

TEST_HP_UUID = "0000-1111-2222-3333"
TEST_NAME = "Test Heat Pump"
TEST_MODEL = "Test Model"
TEST_SN = "SN-Test-This"
Loading

0 comments on commit e92d931

Please sign in to comment.