Mill data coordinator (#53603)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
f9fa5fa804
commit
98e8e89364
6 changed files with 126 additions and 116 deletions
|
@ -1,15 +1,43 @@
|
||||||
"""The mill component."""
|
"""The mill component."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
from mill import Mill
|
from mill import Mill
|
||||||
|
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = ["climate", "sensor"]
|
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):
|
async def async_setup_entry(hass, entry):
|
||||||
"""Set up the Mill heater."""
|
"""Set up the Mill heater."""
|
||||||
mill_data_connection = Mill(
|
mill_data_connection = Mill(
|
||||||
|
@ -20,9 +48,12 @@ async def async_setup_entry(hass, entry):
|
||||||
if not await mill_data_connection.connect():
|
if not await mill_data_connection.connect():
|
||||||
raise ConfigEntryNotReady
|
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)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -11,8 +11,10 @@ from homeassistant.components.climate.const import (
|
||||||
SUPPORT_FAN_MODE,
|
SUPPORT_FAN_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
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 import config_validation as cv
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_AWAY_TEMP,
|
ATTR_AWAY_TEMP,
|
||||||
|
@ -41,11 +43,11 @@ SET_ROOM_TEMP_SCHEMA = vol.Schema(
|
||||||
async def async_setup_entry(hass, entry, async_add_entities):
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
"""Set up the Mill climate."""
|
"""Set up the Mill climate."""
|
||||||
|
|
||||||
mill_data_connection = hass.data[DOMAIN]
|
mill_data_coordinator = hass.data[DOMAIN]
|
||||||
|
|
||||||
dev = []
|
dev = []
|
||||||
for heater in mill_data_connection.heaters.values():
|
for heater in mill_data_coordinator.data.values():
|
||||||
dev.append(MillHeater(heater, mill_data_connection))
|
dev.append(MillHeater(mill_data_coordinator, heater))
|
||||||
async_add_entities(dev)
|
async_add_entities(dev)
|
||||||
|
|
||||||
async def set_room_temp(service):
|
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)
|
sleep_temp = service.data.get(ATTR_SLEEP_TEMP)
|
||||||
comfort_temp = service.data.get(ATTR_COMFORT_TEMP)
|
comfort_temp = service.data.get(ATTR_COMFORT_TEMP)
|
||||||
away_temp = service.data.get(ATTR_AWAY_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
|
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."""
|
"""Representation of a Mill Thermostat device."""
|
||||||
|
|
||||||
_attr_fan_modes = [FAN_ON, HVAC_MODE_OFF]
|
_attr_fan_modes = [FAN_ON, HVAC_MODE_OFF]
|
||||||
_attr_max_temp = MAX_TEMP
|
_attr_max_temp = MAX_TEMP
|
||||||
_attr_min_temp = MIN_TEMP
|
_attr_min_temp = MIN_TEMP
|
||||||
_attr_supported_features = SUPPORT_FLAGS
|
_attr_supported_features = SUPPORT_FLAGS
|
||||||
_attr_target_temperature_step = 1
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
_attr_temperature_unit = TEMP_CELSIUS
|
_attr_temperature_unit = TEMP_CELSIUS
|
||||||
|
|
||||||
def __init__(self, heater, mill_data_connection):
|
def __init__(self, coordinator, heater):
|
||||||
"""Initialize the thermostat."""
|
"""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_unique_id = heater.device_id
|
||||||
self._attr_name = heater.name
|
self._attr_name = heater.name
|
||||||
|
self._attr_device_info = {
|
||||||
@property
|
"identifiers": {(DOMAIN, heater.device_id)},
|
||||||
def available(self):
|
"name": self.name,
|
||||||
"""Return True if entity is available."""
|
"manufacturer": MANUFACTURER,
|
||||||
return self._heater.available
|
"model": f"generation {1 if heater.is_gen1 else 2}",
|
||||||
|
|
||||||
@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,
|
|
||||||
}
|
}
|
||||||
if self._heater.room:
|
if heater.is_gen1:
|
||||||
res["room"] = self._heater.room.name
|
self._attr_hvac_modes = [HVAC_MODE_HEAT]
|
||||||
res["avg_room_temp"] = self._heater.room.avg_temp
|
|
||||||
else:
|
else:
|
||||||
res["room"] = "Independent device"
|
self._attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
return res
|
self._update_attr(heater)
|
||||||
|
|
||||||
@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]
|
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
return
|
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):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
fan_status = 1 if fan_mode == FAN_ON else 0
|
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):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
|
heater = self.coordinator.data[self._id]
|
||||||
|
|
||||||
if hvac_mode == HVAC_MODE_HEAT:
|
if hvac_mode == HVAC_MODE_HEAT:
|
||||||
await self._conn.heater_control(self._heater.device_id, power_status=1)
|
await self.coordinator.mill_data_connection.heater_control(
|
||||||
elif hvac_mode == HVAC_MODE_OFF and not self._heater.is_gen1:
|
self._id, power_status=1
|
||||||
await self._conn.heater_control(self._heater.device_id, power_status=0)
|
)
|
||||||
|
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):
|
@callback
|
||||||
"""Retrieve latest state."""
|
def _handle_coordinator_update(self) -> None:
|
||||||
self._heater = await self._conn.update_device(self._heater.device_id)
|
"""Handle updated data from the coordinator."""
|
||||||
|
self._update_attr(self.coordinator.data[self._id])
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def device_id(self):
|
def _update_attr(self, heater):
|
||||||
"""Return the ID of the physical device this sensor is part of."""
|
self._attr_available = heater.available
|
||||||
return self._heater.device_id
|
self._attr_extra_state_attributes = {
|
||||||
|
"open_window": heater.open_window,
|
||||||
@property
|
"heating": heater.is_heating,
|
||||||
def device_info(self):
|
"controlled_by_tibber": heater.tibber_control,
|
||||||
"""Return the device_info of the device."""
|
"heater_generation": 1 if heater.is_gen1 else 2,
|
||||||
device_info = {
|
|
||||||
"identifiers": {(DOMAIN, self.device_id)},
|
|
||||||
"name": self.name,
|
|
||||||
"manufacturer": MANUFACTURER,
|
|
||||||
"model": f"generation {1 if self._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
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"domain": "mill",
|
"domain": "mill",
|
||||||
"name": "Mill",
|
"name": "Mill",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/mill",
|
"documentation": "https://www.home-assistant.io/integrations/mill",
|
||||||
"requirements": ["millheater==0.5.0"],
|
"requirements": ["millheater==0.5.2"],
|
||||||
"codeowners": ["@danielhiversen"],
|
"codeowners": ["@danielhiversen"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
|
|
|
@ -6,6 +6,8 @@ from homeassistant.components.sensor import (
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ENERGY_KILO_WATT_HOUR
|
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
|
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):
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
"""Set up the Mill sensor."""
|
"""Set up the Mill sensor."""
|
||||||
|
|
||||||
mill_data_connection = hass.data[DOMAIN]
|
mill_data_coordinator = hass.data[DOMAIN]
|
||||||
|
|
||||||
entities = [
|
entities = [
|
||||||
MillHeaterEnergySensor(heater, mill_data_connection, sensor_type)
|
MillHeaterEnergySensor(mill_data_coordinator, sensor_type, heater)
|
||||||
for sensor_type in (CONSUMPTION_TODAY, CONSUMPTION_YEAR)
|
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)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class MillHeaterEnergySensor(SensorEntity):
|
class MillHeaterEnergySensor(CoordinatorEntity, SensorEntity):
|
||||||
"""Representation of a Mill Sensor device."""
|
"""Representation of a Mill Sensor device."""
|
||||||
|
|
||||||
_attr_device_class = DEVICE_CLASS_ENERGY
|
_attr_device_class = DEVICE_CLASS_ENERGY
|
||||||
_attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
|
_attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
|
||||||
_attr_state_class = STATE_CLASS_TOTAL_INCREASING
|
_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."""
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._id = heater.device_id
|
self._id = heater.device_id
|
||||||
self._conn = mill_data_connection
|
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
|
|
||||||
self._attr_name = f"{heater.name} {sensor_type.replace('_', ' ')}"
|
self._attr_name = f"{heater.name} {sensor_type.replace('_', ' ')}"
|
||||||
|
@ -44,20 +47,19 @@ class MillHeaterEnergySensor(SensorEntity):
|
||||||
"manufacturer": MANUFACTURER,
|
"manufacturer": MANUFACTURER,
|
||||||
"model": f"generation {1 if heater.is_gen1 else 2}",
|
"model": f"generation {1 if heater.is_gen1 else 2}",
|
||||||
}
|
}
|
||||||
|
self._update_attr(heater)
|
||||||
|
|
||||||
async def async_update(self):
|
@callback
|
||||||
"""Retrieve latest state."""
|
def _handle_coordinator_update(self) -> None:
|
||||||
heater = await self._conn.update_device(self._id)
|
"""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
|
self._attr_available = heater.available
|
||||||
|
|
||||||
if self._sensor_type == CONSUMPTION_TODAY:
|
if self._sensor_type == CONSUMPTION_TODAY:
|
||||||
_state = heater.day_consumption
|
self._attr_native_value = heater.day_consumption
|
||||||
elif self._sensor_type == CONSUMPTION_YEAR:
|
elif self._sensor_type == CONSUMPTION_YEAR:
|
||||||
_state = heater.year_consumption
|
self._attr_native_value = heater.year_consumption
|
||||||
else:
|
|
||||||
_state = None
|
|
||||||
if _state is None:
|
|
||||||
self._attr_native_value = _state
|
|
||||||
return
|
|
||||||
|
|
||||||
self._attr_native_value = _state
|
|
||||||
|
|
|
@ -980,7 +980,7 @@ micloud==0.3
|
||||||
miflora==0.7.0
|
miflora==0.7.0
|
||||||
|
|
||||||
# homeassistant.components.mill
|
# homeassistant.components.mill
|
||||||
millheater==0.5.0
|
millheater==0.5.2
|
||||||
|
|
||||||
# homeassistant.components.minio
|
# homeassistant.components.minio
|
||||||
minio==4.0.9
|
minio==4.0.9
|
||||||
|
|
|
@ -551,7 +551,7 @@ mficlient==0.3.0
|
||||||
micloud==0.3
|
micloud==0.3
|
||||||
|
|
||||||
# homeassistant.components.mill
|
# homeassistant.components.mill
|
||||||
millheater==0.5.0
|
millheater==0.5.2
|
||||||
|
|
||||||
# homeassistant.components.minio
|
# homeassistant.components.minio
|
||||||
minio==4.0.9
|
minio==4.0.9
|
||||||
|
|
Loading…
Add table
Reference in a new issue