diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index e8706593a06..e80ae0cc79e 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -3,24 +3,12 @@ from datetime import timedelta import logging import async_timeout -from pyatag import AtagDataStore, AtagException +from pyatag import AtagException, AtagOne from homeassistant.components.climate import DOMAIN as CLIMATE from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.components.water_heater import DOMAIN as WATER_HEATER from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_ICON, - ATTR_ID, - ATTR_MODE, - ATTR_NAME, - ATTR_UNIT_OF_MEASUREMENT, - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, - PRESSURE_BAR, - TEMP_CELSIUS, -) from homeassistant.core import HomeAssistant, asyncio from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -30,94 +18,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda _LOGGER = logging.getLogger(__name__) DOMAIN = "atag" -DATA_LISTENER = f"{DOMAIN}_listener" -SIGNAL_UPDATE_ATAG = f"{DOMAIN}_update" PLATFORMS = [CLIMATE, WATER_HEATER, SENSOR] -HOUR = "h" -FIRE = "fire" -PERCENTAGE = "%" - -ICONS = { - TEMP_CELSIUS: "mdi:thermometer", - PRESSURE_BAR: "mdi:gauge", - FIRE: "mdi:fire", - ATTR_MODE: "mdi:settings", -} - -ENTITY_TYPES = { - SENSOR: [ - { - ATTR_NAME: "Outside Temperature", - ATTR_ID: "outside_temp", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: ICONS[TEMP_CELSIUS], - }, - { - ATTR_NAME: "Average Outside Temperature", - ATTR_ID: "tout_avg", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: ICONS[TEMP_CELSIUS], - }, - { - ATTR_NAME: "Weather Status", - ATTR_ID: "weather_status", - ATTR_UNIT_OF_MEASUREMENT: None, - ATTR_DEVICE_CLASS: None, - ATTR_ICON: None, - }, - { - ATTR_NAME: "CH Water Pressure", - ATTR_ID: "ch_water_pres", - ATTR_UNIT_OF_MEASUREMENT: PRESSURE_BAR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, - ATTR_ICON: ICONS[PRESSURE_BAR], - }, - { - ATTR_NAME: "CH Water Temperature", - ATTR_ID: "ch_water_temp", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: ICONS[TEMP_CELSIUS], - }, - { - ATTR_NAME: "CH Return Temperature", - ATTR_ID: "ch_return_temp", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: ICONS[TEMP_CELSIUS], - }, - { - ATTR_NAME: "Burning Hours", - ATTR_ID: "burning_hours", - ATTR_UNIT_OF_MEASUREMENT: HOUR, - ATTR_DEVICE_CLASS: None, - ATTR_ICON: ICONS[FIRE], - }, - { - ATTR_NAME: "Flame", - ATTR_ID: "rel_mod_level", - ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, - ATTR_DEVICE_CLASS: None, - ATTR_ICON: ICONS[FIRE], - }, - ], - CLIMATE: { - ATTR_NAME: DOMAIN.title(), - ATTR_ID: CLIMATE, - ATTR_UNIT_OF_MEASUREMENT: None, - ATTR_DEVICE_CLASS: None, - ATTR_ICON: None, - }, - WATER_HEATER: { - ATTR_NAME: DOMAIN.title(), - ATTR_ID: WATER_HEATER, - ATTR_UNIT_OF_MEASUREMENT: None, - ATTR_DEVICE_CLASS: None, - ATTR_ICON: None, - }, -} async def async_setup(hass: HomeAssistant, config): @@ -130,8 +31,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): session = async_get_clientsession(hass) coordinator = AtagDataUpdateCoordinator(hass, session, entry) - - await coordinator.async_refresh() + try: + await coordinator.async_refresh() + except AtagException: + raise ConfigEntryNotReady if not coordinator.last_update_success: raise ConfigEntryNotReady @@ -152,7 +55,7 @@ class AtagDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass, session, entry): """Initialize.""" - self.atag = AtagDataStore(session, paired=True, **entry.data) + self.atag = AtagOne(session=session, **entry.data) super().__init__( hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=30) @@ -162,12 +65,12 @@ class AtagDataUpdateCoordinator(DataUpdateCoordinator): """Update data via library.""" with async_timeout.timeout(20): try: - await self.atag.async_update() - if not self.atag.sensordata: + await self.atag.update() + if not self.atag.report: raise UpdateFailed("No data") - except (AtagException) as error: + except AtagException as error: raise UpdateFailed(error) - return self.atag.sensordata + return self.atag.report async def async_unload_entry(hass, entry): @@ -188,24 +91,21 @@ async def async_unload_entry(hass, entry): class AtagEntity(Entity): """Defines a base Atag entity.""" - def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_type: dict) -> None: + def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None: """Initialize the Atag entity.""" self.coordinator = coordinator - self._id = atag_type[ATTR_ID] - self._name = atag_type[ATTR_NAME] - self._icon = atag_type[ATTR_ICON] - self._unit = atag_type[ATTR_UNIT_OF_MEASUREMENT] - self._class = atag_type[ATTR_DEVICE_CLASS] + self._id = atag_id + self._name = DOMAIN.title() @property def device_info(self) -> dict: """Return info for device registry.""" - device = self.coordinator.atag.device + device = self.coordinator.atag.id version = self.coordinator.atag.apiversion return { "identifiers": {(DOMAIN, device)}, - ATTR_NAME: "Atag Thermostat", + "name": "Atag Thermostat", "model": "Atag One", "sw_version": version, "manufacturer": "Atag", @@ -216,14 +116,6 @@ class AtagEntity(Entity): """Return the name of the entity.""" return self._name - @property - def icon(self) -> str: - """Return the mdi icon of the entity.""" - self._icon = ( - self.coordinator.data.get(self._id, {}).get(ATTR_ICON) or self._icon - ) - return self._icon - @property def should_poll(self) -> bool: """Return the polling requirement of the entity.""" @@ -232,12 +124,7 @@ class AtagEntity(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - return self._unit - - @property - def device_class(self): - """Return the device class.""" - return self._class + return self.coordinator.atag.climate.temp_unit @property def available(self): @@ -247,7 +134,7 @@ class AtagEntity(Entity): @property def unique_id(self): """Return a unique ID to use for this entity.""" - return f"{self.coordinator.atag.device}-{self._id}" + return f"{self.coordinator.atag.id}-{self._id}" async def async_added_to_hass(self): """Connect to dispatcher listening for entity data notifications.""" diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index 40bd8cd4cc7..4c39b2ea8f8 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -1,7 +1,7 @@ """Initialization of ATAG One climate platform.""" from typing import List, Optional -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -14,7 +14,7 @@ from homeassistant.components.climate.const import ( ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT -from . import CLIMATE, DOMAIN, ENTITY_TYPES, AtagEntity +from . import CLIMATE, DOMAIN, AtagEntity PRESET_SCHEDULE = "Auto" PRESET_MANUAL = "Manual" @@ -33,10 +33,10 @@ HVAC_MODES = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] async def async_setup_entry(hass, entry, async_add_entities): """Load a config entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([AtagThermostat(coordinator, ENTITY_TYPES[CLIMATE])]) + async_add_entities([AtagThermostat(coordinator, CLIMATE)]) -class AtagThermostat(AtagEntity, ClimateDevice): +class AtagThermostat(AtagEntity, ClimateEntity): """Atag climate device.""" @property @@ -47,8 +47,8 @@ class AtagThermostat(AtagEntity, ClimateDevice): @property def hvac_mode(self) -> Optional[str]: """Return hvac operation ie. heat, cool mode.""" - if self.coordinator.atag.hvac_mode in HVAC_MODES: - return self.coordinator.atag.hvac_mode + if self.coordinator.atag.climate.hvac_mode in HVAC_MODES: + return self.coordinator.atag.climate.hvac_mode return None @property @@ -59,31 +59,31 @@ class AtagThermostat(AtagEntity, ClimateDevice): @property def hvac_action(self) -> Optional[str]: """Return the current running hvac operation.""" - if self.coordinator.atag.cv_status: + if self.coordinator.atag.climate.status: return CURRENT_HVAC_HEAT return CURRENT_HVAC_IDLE @property def temperature_unit(self): """Return the unit of measurement.""" - if self.coordinator.atag.temp_unit in [TEMP_CELSIUS, TEMP_FAHRENHEIT]: - return self.coordinator.atag.temp_unit + if self.coordinator.atag.climate.temp_unit in [TEMP_CELSIUS, TEMP_FAHRENHEIT]: + return self.coordinator.atag.climate.temp_unit return None @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" - return self.coordinator.atag.temperature + return self.coordinator.atag.climate.temperature @property def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" - return self.coordinator.atag.target_temperature + return self.coordinator.atag.climate.target_temperature @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., auto, manual, fireplace, extend, etc.""" - return self.coordinator.atag.hold_mode + return self.coordinator.atag.climate.preset_mode @property def preset_modes(self) -> Optional[List[str]]: @@ -92,15 +92,15 @@ class AtagThermostat(AtagEntity, ClimateDevice): async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - await self.coordinator.atag.set_temp(kwargs.get(ATTR_TEMPERATURE)) + await self.coordinator.atag.climate.set_temp(kwargs.get(ATTR_TEMPERATURE)) self.async_write_ha_state() async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.coordinator.atag.set_hvac_mode(hvac_mode) + await self.coordinator.atag.climate.set_hvac_mode(hvac_mode) self.async_write_ha_state() async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.coordinator.atag.set_hold_mode(preset_mode) + await self.coordinator.atag.climate.set_preset_mode(preset_mode) self.async_write_ha_state() diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index 27b2b7a42f6..369f4b98587 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -1,9 +1,9 @@ """Config flow for the Atag component.""" -from pyatag import DEFAULT_PORT, AtagDataStore, AtagException +from pyatag import DEFAULT_PORT, AtagException, AtagOne import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_DEVICE, CONF_EMAIL, CONF_HOST, CONF_PORT from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -11,6 +11,7 @@ from . import DOMAIN # pylint: disable=unused-import DATA_SCHEMA = { vol.Required(CONF_HOST): str, + vol.Optional(CONF_EMAIL): str, vol.Required(CONF_PORT, default=DEFAULT_PORT): vol.Coerce(int), } @@ -31,14 +32,15 @@ class AtagConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._show_form() session = async_get_clientsession(self.hass) try: - atag = AtagDataStore(session, **user_input) - await atag.async_check_pair_status() + atag = AtagOne(session=session, **user_input) + await atag.authorize() + await atag.update(force=True) except AtagException: return await self._show_form({"base": "connection_error"}) - user_input.update({CONF_DEVICE: atag.device}) - return self.async_create_entry(title=atag.device, data=user_input) + user_input.update({CONF_DEVICE: atag.id}) + return self.async_create_entry(title=atag.id, data=user_input) @callback async def _show_form(self, errors=None): diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index 902da2bff75..b28f6875fa4 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -3,6 +3,6 @@ "name": "Atag", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/atag/", - "requirements": ["pyatag==0.2.19"], + "requirements": ["pyatag==0.3.1.1"], "codeowners": ["@MatsNL"] } diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index 743b50ef40d..d5ff0b7bbde 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -1,14 +1,31 @@ """Initialization of ATAG One sensor platform.""" -from homeassistant.const import ATTR_STATE +from homeassistant.const import ( + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + PRESSURE_BAR, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) -from . import DOMAIN, ENTITY_TYPES, SENSOR, AtagEntity +from . import DOMAIN, AtagEntity + +SENSORS = { + "Outside Temperature": "outside_temp", + "Average Outside Temperature": "tout_avg", + "Weather Status": "weather_status", + "CH Water Pressure": "ch_water_pres", + "CH Water Temperature": "ch_water_temp", + "CH Return Temperature": "ch_return_temp", + "Burning Hours": "burning_hours", + "Flame": "rel_mod_level", +} async def async_setup_entry(hass, config_entry, async_add_entities): """Initialize sensor platform from config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] entities = [] - for sensor in ENTITY_TYPES[SENSOR]: + for sensor in SENSORS: entities.append(AtagSensor(coordinator, sensor)) async_add_entities(entities) @@ -16,7 +33,38 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class AtagSensor(AtagEntity): """Representation of a AtagOne Sensor.""" + def __init__(self, coordinator, sensor): + """Initialize Atag sensor.""" + super().__init__(coordinator, SENSORS[sensor]) + self._name = sensor + @property def state(self): """Return the state of the sensor.""" - return self.coordinator.data[self._id][ATTR_STATE] + return self.coordinator.data[self._id].state + + @property + def icon(self): + """Return icon.""" + return self.coordinator.data[self._id].icon + + @property + def device_class(self): + """Return deviceclass.""" + if self.coordinator.data[self._id].sensorclass in [ + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + ]: + return self.coordinator.data[self._id].sensorclass + return None + + @property + def unit_of_measurement(self): + """Return measure.""" + if self.coordinator.data[self._id].measure in [ + PRESSURE_BAR, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + ]: + return self.coordinator.data[self._id].measure + return None diff --git a/homeassistant/components/atag/strings.json b/homeassistant/components/atag/strings.json index 094fde70dc9..d182c21ce0f 100644 --- a/homeassistant/components/atag/strings.json +++ b/homeassistant/components/atag/strings.json @@ -6,6 +6,7 @@ "title": "Connect to the device", "data": { "host": "Host", + "email": "Email (Optional)", "port": "Port (10000)" } } diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index 2b2093c3c7e..311ff56985b 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -3,11 +3,11 @@ from homeassistant.components.water_heater import ( ATTR_TEMPERATURE, STATE_ECO, STATE_PERFORMANCE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import STATE_OFF, TEMP_CELSIUS -from . import DOMAIN, ENTITY_TYPES, WATER_HEATER, AtagEntity +from . import DOMAIN, WATER_HEATER, AtagEntity SUPPORT_FLAGS_HEATER = 0 OPERATION_LIST = [STATE_OFF, STATE_ECO, STATE_PERFORMANCE] @@ -16,10 +16,10 @@ OPERATION_LIST = [STATE_OFF, STATE_ECO, STATE_PERFORMANCE] async def async_setup_entry(hass, config_entry, async_add_entities): """Initialize DHW device from config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities([AtagWaterHeater(coordinator, ENTITY_TYPES[WATER_HEATER])]) + async_add_entities([AtagWaterHeater(coordinator, WATER_HEATER)]) -class AtagWaterHeater(AtagEntity, WaterHeaterDevice): +class AtagWaterHeater(AtagEntity, WaterHeaterEntity): """Representation of an ATAG water heater.""" @property @@ -35,12 +35,12 @@ class AtagWaterHeater(AtagEntity, WaterHeaterDevice): @property def current_temperature(self): """Return the current temperature.""" - return self.coordinator.atag.dhw_temperature + return self.coordinator.atag.dhw.temperature @property def current_operation(self): """Return current operation.""" - if self.coordinator.atag.dhw_status: + if self.coordinator.atag.dhw.status: return STATE_PERFORMANCE return STATE_OFF @@ -51,20 +51,20 @@ class AtagWaterHeater(AtagEntity, WaterHeaterDevice): async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - if await self.coordinator.atag.dhw_set_temp(kwargs.get(ATTR_TEMPERATURE)): + if await self.coordinator.atag.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)): self.async_write_ha_state() @property def target_temperature(self): """Return the setpoint if water demand, otherwise return base temp (comfort level).""" - return self.coordinator.atag.dhw_target_temperature + return self.coordinator.atag.dhw.target_temperature @property def max_temp(self): """Return the maximum temperature.""" - return self.coordinator.atag.dhw_max_temp + return self.coordinator.atag.dhw.max_temp @property def min_temp(self): """Return the minimum temperature.""" - return self.coordinator.atag.dhw_min_temp + return self.coordinator.atag.dhw.min_temp diff --git a/requirements_all.txt b/requirements_all.txt index ef47039adc4..c7c8e706ba6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1212,7 +1212,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.2.19 +pyatag==0.3.1.1 # homeassistant.components.netatmo pyatmo==3.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 21eed4d7d6d..79b82d2e2ac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -515,7 +515,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.2.19 +pyatag==0.3.1.1 # homeassistant.components.netatmo pyatmo==3.3.1 diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index 9885c9081dd..65583340524 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -3,13 +3,14 @@ from pyatag import AtagException from homeassistant import config_entries, data_entry_flow from homeassistant.components.atag import DOMAIN -from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_DEVICE, CONF_EMAIL, CONF_HOST, CONF_PORT from tests.async_mock import PropertyMock, patch from tests.common import MockConfigEntry FIXTURE_USER_INPUT = { CONF_HOST: "127.0.0.1", + CONF_EMAIL: "test@domain.com", CONF_PORT: 10000, } FIXTURE_COMPLETE_ENTRY = FIXTURE_USER_INPUT.copy() @@ -42,7 +43,7 @@ async def test_connection_error(hass): """Test we show user form on Atag connection error.""" with patch( - "homeassistant.components.atag.config_flow.AtagDataStore.async_check_pair_status", + "homeassistant.components.atag.config_flow.AtagOne.authorize", side_effect=AtagException(), ): result = await hass.config_entries.flow.async_init( @@ -58,10 +59,10 @@ async def test_connection_error(hass): async def test_full_flow_implementation(hass): """Test registering an integration and finishing flow works.""" - with patch( - "homeassistant.components.atag.AtagDataStore.async_check_pair_status", + with patch("homeassistant.components.atag.AtagOne.authorize",), patch( + "homeassistant.components.atag.AtagOne.update", ), patch( - "homeassistant.components.atag.AtagDataStore.device", + "homeassistant.components.atag.AtagOne.id", new_callable=PropertyMock(return_value="device_identifier"), ): result = await hass.config_entries.flow.async_init(