From e07ca1a1bce8065dbc11dbe74ae6a39687dd1105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Sat, 9 Nov 2024 13:35:16 +0100 Subject: [PATCH 1/4] Mill, new features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/mill/climate.py | 41 ++++++++- homeassistant/components/mill/const.py | 4 + homeassistant/components/mill/coordinator.py | 97 +++++++++++++++++++- homeassistant/components/mill/icons.json | 3 + homeassistant/components/mill/manifest.json | 3 +- homeassistant/components/mill/sensor.py | 29 +++++- homeassistant/components/mill/services.yaml | 17 ++++ homeassistant/components/mill/strings.json | 14 +++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 10 files changed, 201 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index 5c5c7882634..54791eac92f 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -14,6 +14,7 @@ from homeassistant.components.climate import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_IP_ADDRESS, CONF_USERNAME, @@ -29,6 +30,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_AWAY_TEMP, ATTR_COMFORT_TEMP, + ATTR_MAX_HEATING_POWER, ATTR_ROOM_NAME, ATTR_SLEEP_TEMP, CLOUD, @@ -38,6 +40,7 @@ from .const import ( MANUFACTURER, MAX_TEMP, MIN_TEMP, + SERVICE_MAX_HEATING_POWER, SERVICE_SET_ROOM_TEMP, ) from .coordinator import MillDataUpdateCoordinator @@ -50,6 +53,12 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema( vol.Optional(ATTR_SLEEP_TEMP): cv.positive_int, } ) +LIMIT_HEATING_POWER_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_MAX_HEATING_POWER): vol.Range(min=0, max=2000), + } +) async def async_setup_entry( @@ -84,6 +93,25 @@ async def async_setup_entry( DOMAIN, SERVICE_SET_ROOM_TEMP, set_room_temp, schema=SET_ROOM_TEMP_SCHEMA ) + async def max_heating_power(service: ServiceCall) -> None: + """Limit heating power.""" + entity_id = service.data.get(ATTR_ENTITY_ID) + heating_power = service.data.get(ATTR_MAX_HEATING_POWER) + for entity in entities: + if entity.entity_id == entity_id: + await mill_data_coordinator.mill_data_connection.max_heating_power( + entity.heater_id, heating_power + ) + return + raise ValueError(f"Entity id {entity_id} not found") + + hass.services.async_register( + DOMAIN, + SERVICE_MAX_HEATING_POWER, + max_heating_power, + schema=LIMIT_HEATING_POWER_SCHEMA, + ) + class MillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntity): """Representation of a Mill Thermostat device.""" @@ -111,7 +139,7 @@ class MillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntity): self._available = False - self._id = heater.device_id + self.heater_id = heater.device_id self._attr_unique_id = heater.device_id self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, heater.device_id)}, @@ -127,7 +155,10 @@ class MillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntity): if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return await self.coordinator.mill_data_connection.set_heater_temp( - self._id, float(temperature) + self.heater_id, float(temperature) + ) + await self.coordinator.mill_data_connection.fetch_historic_energy_usage( + self.heater_id ) await self.coordinator.async_request_refresh() @@ -135,12 +166,12 @@ class MillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntity): """Set new target hvac mode.""" if hvac_mode == HVACMode.HEAT: await self.coordinator.mill_data_connection.heater_control( - self._id, power_status=True + self.heater_id, power_status=True ) await self.coordinator.async_request_refresh() elif hvac_mode == HVACMode.OFF: await self.coordinator.mill_data_connection.heater_control( - self._id, power_status=False + self.heater_id, power_status=False ) await self.coordinator.async_request_refresh() @@ -152,7 +183,7 @@ class MillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntity): @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - self._update_attr(self.coordinator.data[self._id]) + self._update_attr(self.coordinator.data[self.heater_id]) self.async_write_ha_state() @callback diff --git a/homeassistant/components/mill/const.py b/homeassistant/components/mill/const.py index c42747920bf..1f2337631f9 100644 --- a/homeassistant/components/mill/const.py +++ b/homeassistant/components/mill/const.py @@ -2,6 +2,7 @@ ATTR_AWAY_TEMP = "away_temp" ATTR_COMFORT_TEMP = "comfort_temp" +ATTR_MAX_HEATING_POWER = "max_heating_power" ATTR_ROOM_NAME = "room_name" ATTR_SLEEP_TEMP = "sleep_temp" BATTERY = "battery" @@ -9,6 +10,8 @@ CLOUD = "Cloud" CONNECTION_TYPE = "connection_type" CONSUMPTION_TODAY = "day_consumption" CONSUMPTION_YEAR = "year_consumption" +CONTROL_SIGNAL = "control_signal" +CURRENT_POWER = "current_power" DOMAIN = "mill" ECO2 = "eco2" HUMIDITY = "humidity" @@ -17,5 +20,6 @@ MANUFACTURER = "Mill" MAX_TEMP = 35 MIN_TEMP = 5 SERVICE_SET_ROOM_TEMP = "set_room_temperature" +SERVICE_MAX_HEATING_POWER = "max_heating_power" TEMPERATURE = "current_temp" TVOC = "tvoc" diff --git a/homeassistant/components/mill/coordinator.py b/homeassistant/components/mill/coordinator.py index 9821519ca84..fbfc930c04c 100644 --- a/homeassistant/components/mill/coordinator.py +++ b/homeassistant/components/mill/coordinator.py @@ -4,12 +4,22 @@ from __future__ import annotations from datetime import timedelta import logging +from typing import cast -from mill import Mill +from mill import Heater, Mill from mill_local import Mill as MillLocal +from homeassistant.components.recorder import get_instance +from homeassistant.components.recorder.models import StatisticData, StatisticMetaData +from homeassistant.components.recorder.statistics import ( + async_add_external_statistics, + get_last_statistics, + statistics_during_period, +) +from homeassistant.const import UnitOfEnergy from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util import dt as dt_util, slugify from .const import DOMAIN @@ -28,11 +38,94 @@ class MillDataUpdateCoordinator(DataUpdateCoordinator): ) -> None: """Initialize global Mill data updater.""" self.mill_data_connection = mill_data_connection + self._last_stats_time = dt_util.utcnow() - timedelta(days=1) super().__init__( hass, _LOGGER, name=DOMAIN, - update_method=mill_data_connection.fetch_heater_and_sensor_data, update_interval=update_interval, ) + + async def _async_update_data(self) -> dict: + """Update data via API.""" + data = await self.mill_data_connection.fetch_heater_and_sensor_data() + if isinstance(self.mill_data_connection, Mill): + await self._insert_statistics() + return data + + async def _insert_statistics(self) -> None: + """Insert Mill statistics.""" + now = dt_util.utcnow() + if self._last_stats_time > now - timedelta(hours=1): + return + for dev_id, heater in self.mill_data_connection.devices.items(): + if not isinstance(heater, Heater): + continue + statistic_id = f"{DOMAIN}:energy_{slugify(dev_id)}_7" + + last_stats = await get_instance(self.hass).async_add_executor_job( + get_last_statistics, self.hass, 1, statistic_id, True, set() + ) + + if not last_stats or not last_stats.get(statistic_id): + hourly_data = ( + await self.mill_data_connection.fetch_historic_energy_usage(dev_id) + ) + _sum = 0.0 + last_stats_time = None + else: + hourly_data = ( + await self.mill_data_connection.fetch_historic_energy_usage( + dev_id, + n_days=( + now + - dt_util.utc_from_timestamp( + last_stats[statistic_id][0]["start"] + ) + ).days + + 1, + ) + ) + if not hourly_data: + return + stats = await get_instance(self.hass).async_add_executor_job( + statistics_during_period, + self.hass, + next(iter(hourly_data)), + None, + {statistic_id}, + "hour", + None, + {"sum", "state"}, + ) + stat = stats[statistic_id][0] + + _sum = cast(float, stat["sum"]) + last_stats_time = dt_util.utc_from_timestamp(stat["start"]) + + statistics = [] + + for start, state in hourly_data.items(): + if state is None: + continue + if last_stats_time and (start < last_stats_time or start > now): + continue + _sum += state + statistics.append( + StatisticData( + start=start, + state=state, + sum=_sum, + ) + ) + metadata = StatisticMetaData( + has_mean=False, + has_sum=True, + name=f"{heater.name}", + source=DOMAIN, + statistic_id=statistic_id, + unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + ) + async_add_external_statistics(self.hass, metadata, statistics) + self._last_stats_time = now.replace(minute=0, second=0) diff --git a/homeassistant/components/mill/icons.json b/homeassistant/components/mill/icons.json index f2595f28057..40329b0226e 100644 --- a/homeassistant/components/mill/icons.json +++ b/homeassistant/components/mill/icons.json @@ -2,6 +2,9 @@ "services": { "set_room_temperature": { "service": "mdi:thermometer" + }, + "max_heating_power": { + "service": "mdi:power" } } } diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json index 16e7bf552ba..05ec456162d 100644 --- a/homeassistant/components/mill/manifest.json +++ b/homeassistant/components/mill/manifest.json @@ -3,8 +3,9 @@ "name": "Mill", "codeowners": ["@danielhiversen"], "config_flow": true, + "dependencies": ["recorder"], "documentation": "https://www.home-assistant.io/integrations/mill", "iot_class": "local_polling", "loggers": ["mill", "mill_local"], - "requirements": ["millheater==0.11.8", "mill-local==0.3.0"] + "requirements": ["millheater==0.12.0", "mill-local==0.3.0"] } diff --git a/homeassistant/components/mill/sensor.py b/homeassistant/components/mill/sensor.py index 64b9008a82b..c19f29152b7 100644 --- a/homeassistant/components/mill/sensor.py +++ b/homeassistant/components/mill/sensor.py @@ -33,6 +33,8 @@ from .const import ( CONNECTION_TYPE, CONSUMPTION_TODAY, CONSUMPTION_YEAR, + CONTROL_SIGNAL, + CURRENT_POWER, DOMAIN, ECO2, HUMIDITY, @@ -57,6 +59,19 @@ HEATER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, ), + SensorEntityDescription( + key=CURRENT_POWER, + translation_key="current_power", + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=UnitOfPower.WATT, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key=CONTROL_SIGNAL, + translation_key="control_signal", + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), ) SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( @@ -94,6 +109,16 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( ), ) +SOCKET_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key=HUMIDITY, + device_class=SensorDeviceClass.HUMIDITY, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), + *HEATER_SENSOR_TYPES, +) + LOCAL_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="control_signal", @@ -145,7 +170,9 @@ async def async_setup_entry( ) for mill_device in mill_data_coordinator.data.values() for entity_description in ( - HEATER_SENSOR_TYPES + SOCKET_SENSOR_TYPES + if isinstance(mill_device, mill.Socket) + else HEATER_SENSOR_TYPES if isinstance(mill_device, mill.Heater) else SENSOR_TYPES ) diff --git a/homeassistant/components/mill/services.yaml b/homeassistant/components/mill/services.yaml index 14e2196eb83..5b58a49ef07 100644 --- a/homeassistant/components/mill/services.yaml +++ b/homeassistant/components/mill/services.yaml @@ -23,3 +23,20 @@ set_room_temperature: min: 0 max: 100 unit_of_measurement: "°" + +max_heating_power: + fields: + entity_id: + required: true + example: "climate.house" + selector: + entity: + integration: mill + domain: climate + max_heating_power: + required: true + selector: + number: + min: 0 + max: 2000 + unit_of_measurement: "W" diff --git a/homeassistant/components/mill/strings.json b/homeassistant/components/mill/strings.json index 21e3e7a44a5..4f0837adf39 100644 --- a/homeassistant/components/mill/strings.json +++ b/homeassistant/components/mill/strings.json @@ -53,6 +53,20 @@ } }, "services": { + "max_heating_power": { + "description": "Sets max power heater can use.", + "fields": { + "entity_id": { + "description": "Entity id of heater to change.", + "name": "Entity id" + }, + "max_heating_power": { + "description": "Max heating power.", + "name": "Max heating power" + } + }, + "name": "Set max heating power" + }, "set_room_temperature": { "name": "Set room temperature", "description": "Sets Mill room temperatures.", diff --git a/requirements_all.txt b/requirements_all.txt index 322d8feb611..671cd6b1f78 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1373,7 +1373,7 @@ microBeesPy==0.3.2 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.11.8 +millheater==0.12.0 # homeassistant.components.minio minio==7.1.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 26bdb41b5b0..10ed0c5618a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1142,7 +1142,7 @@ microBeesPy==0.3.2 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.11.8 +millheater==0.12.0 # homeassistant.components.minio minio==7.1.12 From a40eeefc8c9ef03942cb6b5fd5391f37df2bff68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Tue, 12 Nov 2024 06:42:26 +0100 Subject: [PATCH 2/4] typo --- homeassistant/components/mill/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mill/coordinator.py b/homeassistant/components/mill/coordinator.py index fbfc930c04c..a83d28fc031 100644 --- a/homeassistant/components/mill/coordinator.py +++ b/homeassistant/components/mill/coordinator.py @@ -62,7 +62,7 @@ class MillDataUpdateCoordinator(DataUpdateCoordinator): for dev_id, heater in self.mill_data_connection.devices.items(): if not isinstance(heater, Heater): continue - statistic_id = f"{DOMAIN}:energy_{slugify(dev_id)}_7" + statistic_id = f"{DOMAIN}:energy_{slugify(dev_id)}" last_stats = await get_instance(self.hass).async_add_executor_job( get_last_statistics, self.hass, 1, statistic_id, True, set() From 182f8b33f7929325f41a975c8e2df8dcb7393b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Tue, 12 Nov 2024 07:54:59 +0100 Subject: [PATCH 3/4] tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Hjelseth Høyer --- tests/components/mill/test_config_flow.py | 21 ++++++++++++++------- tests/components/mill/test_init.py | 23 +++++++++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/tests/components/mill/test_config_flow.py b/tests/components/mill/test_config_flow.py index 832aaef3b19..45a9340b821 100644 --- a/tests/components/mill/test_config_flow.py +++ b/tests/components/mill/test_config_flow.py @@ -4,6 +4,7 @@ from unittest.mock import patch from homeassistant import config_entries from homeassistant.components.mill.const import CLOUD, CONNECTION_TYPE, DOMAIN, LOCAL +from homeassistant.components.recorder import Recorder from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -11,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -async def test_show_config_form(hass: HomeAssistant) -> None: +async def test_show_config_form(recorder_mock: Recorder, hass: HomeAssistant) -> None: """Test show configuration form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -21,7 +22,7 @@ async def test_show_config_form(hass: HomeAssistant) -> None: assert result["step_id"] == "user" -async def test_create_entry(hass: HomeAssistant) -> None: +async def test_create_entry(recorder_mock: Recorder, hass: HomeAssistant) -> None: """Test create entry from user input.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -56,7 +57,9 @@ async def test_create_entry(hass: HomeAssistant) -> None: } -async def test_flow_entry_already_exists(hass: HomeAssistant) -> None: +async def test_flow_entry_already_exists( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test user input for config_entry that already exists.""" test_data = { @@ -96,7 +99,7 @@ async def test_flow_entry_already_exists(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" -async def test_connection_error(hass: HomeAssistant) -> None: +async def test_connection_error(recorder_mock: Recorder, hass: HomeAssistant) -> None: """Test connection error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -125,7 +128,7 @@ async def test_connection_error(hass: HomeAssistant) -> None: assert result["errors"] == {"base": "cannot_connect"} -async def test_local_create_entry(hass: HomeAssistant) -> None: +async def test_local_create_entry(recorder_mock: Recorder, hass: HomeAssistant) -> None: """Test create entry from user input.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -165,7 +168,9 @@ async def test_local_create_entry(hass: HomeAssistant) -> None: assert result["data"] == test_data -async def test_local_flow_entry_already_exists(hass: HomeAssistant) -> None: +async def test_local_flow_entry_already_exists( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test user input for config_entry that already exists.""" test_data = { @@ -215,7 +220,9 @@ async def test_local_flow_entry_already_exists(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" -async def test_local_connection_error(hass: HomeAssistant) -> None: +async def test_local_connection_error( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test connection error.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/mill/test_init.py b/tests/components/mill/test_init.py index bf0026b8c6c..9e7b800792f 100644 --- a/tests/components/mill/test_init.py +++ b/tests/components/mill/test_init.py @@ -4,6 +4,7 @@ import asyncio from unittest.mock import patch from homeassistant.components import mill +from homeassistant.components.recorder import Recorder from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component @@ -11,7 +12,9 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -async def test_setup_with_cloud_config(hass: HomeAssistant) -> None: +async def test_setup_with_cloud_config( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test setup of cloud config.""" entry = MockConfigEntry( domain=mill.DOMAIN, @@ -31,7 +34,9 @@ async def test_setup_with_cloud_config(hass: HomeAssistant) -> None: assert len(mock_connect.mock_calls) == 1 -async def test_setup_with_cloud_config_fails(hass: HomeAssistant) -> None: +async def test_setup_with_cloud_config_fails( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test setup of cloud config.""" entry = MockConfigEntry( domain=mill.DOMAIN, @@ -47,7 +52,9 @@ async def test_setup_with_cloud_config_fails(hass: HomeAssistant) -> None: assert entry.state is ConfigEntryState.SETUP_RETRY -async def test_setup_with_cloud_config_times_out(hass: HomeAssistant) -> None: +async def test_setup_with_cloud_config_times_out( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test setup of cloud config will retry if timed out.""" entry = MockConfigEntry( domain=mill.DOMAIN, @@ -63,7 +70,9 @@ async def test_setup_with_cloud_config_times_out(hass: HomeAssistant) -> None: assert entry.state is ConfigEntryState.SETUP_RETRY -async def test_setup_with_old_cloud_config(hass: HomeAssistant) -> None: +async def test_setup_with_old_cloud_config( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test setup of old cloud config.""" entry = MockConfigEntry( domain=mill.DOMAIN, @@ -82,7 +91,9 @@ async def test_setup_with_old_cloud_config(hass: HomeAssistant) -> None: assert len(mock_connect.mock_calls) == 1 -async def test_setup_with_local_config(hass: HomeAssistant) -> None: +async def test_setup_with_local_config( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: """Test setup of local config.""" entry = MockConfigEntry( domain=mill.DOMAIN, @@ -119,7 +130,7 @@ async def test_setup_with_local_config(hass: HomeAssistant) -> None: assert len(mock_connect.mock_calls) == 1 -async def test_unload_entry(hass: HomeAssistant) -> None: +async def test_unload_entry(recorder_mock: Recorder, hass: HomeAssistant) -> None: """Test removing mill client.""" entry = MockConfigEntry( domain=mill.DOMAIN, From 29c68f3615fb729cdee377cb4ac4a6e6814adee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Wed, 13 Nov 2024 18:37:24 +0100 Subject: [PATCH 4/4] mill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/mill/climate.py | 35 ++++++++------------ homeassistant/components/mill/coordinator.py | 8 +++-- homeassistant/components/mill/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index 54791eac92f..e4ffbcba23e 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -24,7 +24,10 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_platform import ( + AddEntitiesCallback, + async_get_current_platform, +) from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( @@ -53,12 +56,6 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema( vol.Optional(ATTR_SLEEP_TEMP): cv.positive_int, } ) -LIMIT_HEATING_POWER_SCHEMA = vol.Schema( - { - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_MAX_HEATING_POWER): vol.Range(min=0, max=2000), - } -) async def async_setup_entry( @@ -93,23 +90,19 @@ async def async_setup_entry( DOMAIN, SERVICE_SET_ROOM_TEMP, set_room_temp, schema=SET_ROOM_TEMP_SCHEMA ) - async def max_heating_power(service: ServiceCall) -> None: + async def max_heating_power(entity: MillHeater, service: ServiceCall) -> None: """Limit heating power.""" - entity_id = service.data.get(ATTR_ENTITY_ID) - heating_power = service.data.get(ATTR_MAX_HEATING_POWER) - for entity in entities: - if entity.entity_id == entity_id: - await mill_data_coordinator.mill_data_connection.max_heating_power( - entity.heater_id, heating_power - ) - return - raise ValueError(f"Entity id {entity_id} not found") + await mill_data_coordinator.mill_data_connection.max_heating_power( + entity.heater_id, service.data[ATTR_MAX_HEATING_POWER] + ) - hass.services.async_register( - DOMAIN, + async_get_current_platform().async_register_entity_service( SERVICE_MAX_HEATING_POWER, - max_heating_power, - schema=LIMIT_HEATING_POWER_SCHEMA, + schema={ + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_MAX_HEATING_POWER): vol.Range(min=0, max=2000), + }, + func=max_heating_power, ) diff --git a/homeassistant/components/mill/coordinator.py b/homeassistant/components/mill/coordinator.py index a83d28fc031..f5e2b1ba6dd 100644 --- a/homeassistant/components/mill/coordinator.py +++ b/homeassistant/components/mill/coordinator.py @@ -72,6 +72,7 @@ class MillDataUpdateCoordinator(DataUpdateCoordinator): hourly_data = ( await self.mill_data_connection.fetch_historic_energy_usage(dev_id) ) + hourly_data = dict(sorted(hourly_data.items(), key=lambda x: x[0])) _sum = 0.0 last_stats_time = None else: @@ -84,15 +85,18 @@ class MillDataUpdateCoordinator(DataUpdateCoordinator): last_stats[statistic_id][0]["start"] ) ).days - + 1, + + 2, ) ) if not hourly_data: return + hourly_data = dict(sorted(hourly_data.items(), key=lambda x: x[0])) + start_time = next(iter(hourly_data)) + stats = await get_instance(self.hass).async_add_executor_job( statistics_during_period, self.hass, - next(iter(hourly_data)), + start_time, None, {statistic_id}, "hour", diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json index 05ec456162d..8dd1424e2ba 100644 --- a/homeassistant/components/mill/manifest.json +++ b/homeassistant/components/mill/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/mill", "iot_class": "local_polling", "loggers": ["mill", "mill_local"], - "requirements": ["millheater==0.12.0", "mill-local==0.3.0"] + "requirements": ["millheater==0.12.1", "mill-local==0.3.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 671cd6b1f78..806b1604d10 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1373,7 +1373,7 @@ microBeesPy==0.3.2 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.12.0 +millheater==0.12.1 # homeassistant.components.minio minio==7.1.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 10ed0c5618a..d67da8efe6b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1142,7 +1142,7 @@ microBeesPy==0.3.2 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.12.0 +millheater==0.12.1 # homeassistant.components.minio minio==7.1.12