Mill data coordinator (#53603)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Daniel Hjelseth Høyer 2021-08-18 21:30:37 +02:00 committed by GitHub
parent f9fa5fa804
commit 98e8e89364
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 116 deletions

View file

@ -1,15 +1,43 @@
"""The mill component."""
from datetime import timedelta
import logging
from mill import Mill
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["climate", "sensor"]
class MillDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Mill data."""
def __init__(
self,
hass: HomeAssistant,
*,
mill_data_connection: Mill,
) -> None:
"""Initialize global Mill data updater."""
self.mill_data_connection = mill_data_connection
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_method=mill_data_connection.fetch_heater_data,
update_interval=timedelta(seconds=30),
)
async def async_setup_entry(hass, entry):
"""Set up the Mill heater."""
mill_data_connection = Mill(
@ -20,9 +48,12 @@ async def async_setup_entry(hass, entry):
if not await mill_data_connection.connect():
raise ConfigEntryNotReady
await mill_data_connection.find_all_heaters()
hass.data[DOMAIN] = MillDataUpdateCoordinator(
hass,
mill_data_connection=mill_data_connection,
)
hass.data[DOMAIN] = mill_data_connection
await hass.data[DOMAIN].async_config_entry_first_refresh()
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True

View file

@ -11,8 +11,10 @@ from homeassistant.components.climate.const import (
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
ATTR_AWAY_TEMP,
@ -41,11 +43,11 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema(
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Mill climate."""
mill_data_connection = hass.data[DOMAIN]
mill_data_coordinator = hass.data[DOMAIN]
dev = []
for heater in mill_data_connection.heaters.values():
dev.append(MillHeater(heater, mill_data_connection))
for heater in mill_data_coordinator.data.values():
dev.append(MillHeater(mill_data_coordinator, heater))
async_add_entities(dev)
async def set_room_temp(service):
@ -54,7 +56,7 @@ async def async_setup_entry(hass, entry, async_add_entities):
sleep_temp = service.data.get(ATTR_SLEEP_TEMP)
comfort_temp = service.data.get(ATTR_COMFORT_TEMP)
away_temp = service.data.get(ATTR_AWAY_TEMP)
await mill_data_connection.set_room_temperatures_by_name(
await mill_data_coordinator.mill_data_connection.set_room_temperatures_by_name(
room_name, sleep_temp, comfort_temp, away_temp
)
@ -63,122 +65,97 @@ async def async_setup_entry(hass, entry, async_add_entities):
)
class MillHeater(ClimateEntity):
class MillHeater(CoordinatorEntity, ClimateEntity):
"""Representation of a Mill Thermostat device."""
_attr_fan_modes = [FAN_ON, HVAC_MODE_OFF]
_attr_max_temp = MAX_TEMP
_attr_min_temp = MIN_TEMP
_attr_supported_features = SUPPORT_FLAGS
_attr_target_temperature_step = 1
_attr_target_temperature_step = PRECISION_WHOLE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, heater, mill_data_connection):
def __init__(self, coordinator, heater):
"""Initialize the thermostat."""
self._heater = heater
self._conn = mill_data_connection
super().__init__(coordinator)
self._id = heater.device_id
self._attr_unique_id = heater.device_id
self._attr_name = heater.name
@property
def available(self):
"""Return True if entity is available."""
return self._heater.available
@property
def extra_state_attributes(self):
"""Return the state attributes."""
res = {
"open_window": self._heater.open_window,
"heating": self._heater.is_heating,
"controlled_by_tibber": self._heater.tibber_control,
"heater_generation": 1 if self._heater.is_gen1 else 2,
self._attr_device_info = {
"identifiers": {(DOMAIN, heater.device_id)},
"name": self.name,
"manufacturer": MANUFACTURER,
"model": f"generation {1 if heater.is_gen1 else 2}",
}
if self._heater.room:
res["room"] = self._heater.room.name
res["avg_room_temp"] = self._heater.room.avg_temp
if heater.is_gen1:
self._attr_hvac_modes = [HVAC_MODE_HEAT]
else:
res["room"] = "Independent device"
return res
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._heater.set_temp
@property
def current_temperature(self):
"""Return the current temperature."""
return self._heater.current_temp
@property
def fan_mode(self):
"""Return the fan setting."""
return FAN_ON if self._heater.fan_status == 1 else HVAC_MODE_OFF
@property
def hvac_action(self):
"""Return current hvac i.e. heat, cool, idle."""
if self._heater.is_gen1 or self._heater.is_heating == 1:
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if self._heater.is_gen1 or self._heater.power_status == 1:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
if self._heater.is_gen1:
return [HVAC_MODE_HEAT]
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
self._attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
self._update_attr(heater)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._conn.set_heater_temp(self._heater.device_id, int(temperature))
await self.coordinator.mill_data_connection.set_heater_temp(
self._id, int(temperature)
)
await self.coordinator.async_request_refresh()
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
fan_status = 1 if fan_mode == FAN_ON else 0
await self._conn.heater_control(self._heater.device_id, fan_status=fan_status)
await self.coordinator.mill_data_connection.heater_control(
self._id, fan_status=fan_status
)
await self.coordinator.async_request_refresh()
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
heater = self.coordinator.data[self._id]
if hvac_mode == HVAC_MODE_HEAT:
await self._conn.heater_control(self._heater.device_id, power_status=1)
elif hvac_mode == HVAC_MODE_OFF and not self._heater.is_gen1:
await self._conn.heater_control(self._heater.device_id, power_status=0)
await self.coordinator.mill_data_connection.heater_control(
self._id, power_status=1
)
await self.coordinator.async_request_refresh()
elif hvac_mode == HVAC_MODE_OFF and not heater.is_gen1:
await self.coordinator.mill_data_connection.heater_control(
self._id, power_status=0
)
await self.coordinator.async_request_refresh()
async def async_update(self):
"""Retrieve latest state."""
self._heater = await self._conn.update_device(self._heater.device_id)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._update_attr(self.coordinator.data[self._id])
self.async_write_ha_state()
@property
def device_id(self):
"""Return the ID of the physical device this sensor is part of."""
return self._heater.device_id
@property
def device_info(self):
"""Return the device_info of the device."""
device_info = {
"identifiers": {(DOMAIN, self.device_id)},
"name": self.name,
"manufacturer": MANUFACTURER,
"model": f"generation {1 if self._heater.is_gen1 else 2}",
@callback
def _update_attr(self, heater):
self._attr_available = heater.available
self._attr_extra_state_attributes = {
"open_window": heater.open_window,
"heating": heater.is_heating,
"controlled_by_tibber": heater.tibber_control,
"heater_generation": 1 if heater.is_gen1 else 2,
}
return device_info
if heater.room:
self._attr_extra_state_attributes["room"] = heater.room.name
self._attr_extra_state_attributes["avg_room_temp"] = heater.room.avg_temp
else:
self._attr_extra_state_attributes["room"] = "Independent device"
self._attr_target_temperature = heater.set_temp
self._attr_current_temperature = heater.current_temp
self._attr_fan_mode = FAN_ON if heater.fan_status == 1 else HVAC_MODE_OFF
if heater.is_gen1 or heater.is_heating == 1:
self._attr_hvac_action = CURRENT_HVAC_HEAT
else:
self._attr_hvac_action = CURRENT_HVAC_IDLE
if heater.is_gen1 or heater.power_status == 1:
self._attr_hvac_mode = HVAC_MODE_HEAT
else:
self._attr_hvac_mode = HVAC_MODE_OFF

View file

@ -2,7 +2,7 @@
"domain": "mill",
"name": "Mill",
"documentation": "https://www.home-assistant.io/integrations/mill",
"requirements": ["millheater==0.5.0"],
"requirements": ["millheater==0.5.2"],
"codeowners": ["@danielhiversen"],
"config_flow": true,
"iot_class": "cloud_polling"

View file

@ -6,6 +6,8 @@ from homeassistant.components.sensor import (
SensorEntity,
)
from homeassistant.const import ENERGY_KILO_WATT_HOUR
from homeassistant.core import callback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONSUMPTION_TODAY, CONSUMPTION_YEAR, DOMAIN, MANUFACTURER
@ -13,27 +15,28 @@ from .const import CONSUMPTION_TODAY, CONSUMPTION_YEAR, DOMAIN, MANUFACTURER
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Mill sensor."""
mill_data_connection = hass.data[DOMAIN]
mill_data_coordinator = hass.data[DOMAIN]
entities = [
MillHeaterEnergySensor(heater, mill_data_connection, sensor_type)
MillHeaterEnergySensor(mill_data_coordinator, sensor_type, heater)
for sensor_type in (CONSUMPTION_TODAY, CONSUMPTION_YEAR)
for heater in mill_data_connection.heaters.values()
for heater in mill_data_coordinator.data.values()
]
async_add_entities(entities)
class MillHeaterEnergySensor(SensorEntity):
class MillHeaterEnergySensor(CoordinatorEntity, SensorEntity):
"""Representation of a Mill Sensor device."""
_attr_device_class = DEVICE_CLASS_ENERGY
_attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
_attr_state_class = STATE_CLASS_TOTAL_INCREASING
def __init__(self, heater, mill_data_connection, sensor_type):
def __init__(self, coordinator, sensor_type, heater):
"""Initialize the sensor."""
super().__init__(coordinator)
self._id = heater.device_id
self._conn = mill_data_connection
self._sensor_type = sensor_type
self._attr_name = f"{heater.name} {sensor_type.replace('_', ' ')}"
@ -44,20 +47,19 @@ class MillHeaterEnergySensor(SensorEntity):
"manufacturer": MANUFACTURER,
"model": f"generation {1 if heater.is_gen1 else 2}",
}
self._update_attr(heater)
async def async_update(self):
"""Retrieve latest state."""
heater = await self._conn.update_device(self._id)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._update_attr(self.coordinator.data[self._id])
self.async_write_ha_state()
@callback
def _update_attr(self, heater):
self._attr_available = heater.available
if self._sensor_type == CONSUMPTION_TODAY:
_state = heater.day_consumption
self._attr_native_value = heater.day_consumption
elif self._sensor_type == CONSUMPTION_YEAR:
_state = heater.year_consumption
else:
_state = None
if _state is None:
self._attr_native_value = _state
return
self._attr_native_value = _state
self._attr_native_value = heater.year_consumption

View file

@ -980,7 +980,7 @@ micloud==0.3
miflora==0.7.0
# homeassistant.components.mill
millheater==0.5.0
millheater==0.5.2
# homeassistant.components.minio
minio==4.0.9

View file

@ -551,7 +551,7 @@ mficlient==0.3.0
micloud==0.3
# homeassistant.components.mill
millheater==0.5.0
millheater==0.5.2
# homeassistant.components.minio
minio==4.0.9