Add Hive config flow (#47300)
* Add Hive UI * Fix tests and review updates * Slimmed down config_flow * Fix tests * Updated Services.yaml with extra ui attributes * cleanup config flow * Update config entry * Remove ATTR_AVAILABLE * Fix Re-Auth Test * Added more tests. * Update testspull/47946/head
parent
1aa4fd4cc9
commit
cfeb8eb06a
|
@ -386,7 +386,13 @@ omit =
|
|||
homeassistant/components/hikvisioncam/switch.py
|
||||
homeassistant/components/hisense_aehw4a1/*
|
||||
homeassistant/components/hitron_coda/device_tracker.py
|
||||
homeassistant/components/hive/*
|
||||
homeassistant/components/hive/__init__.py
|
||||
homeassistant/components/hive/climate.py
|
||||
homeassistant/components/hive/binary_sensor.py
|
||||
homeassistant/components/hive/light.py
|
||||
homeassistant/components/hive/sensor.py
|
||||
homeassistant/components/hive/switch.py
|
||||
homeassistant/components/hive/water_heater.py
|
||||
homeassistant/components/hlk_sw16/__init__.py
|
||||
homeassistant/components/hlk_sw16/switch.py
|
||||
homeassistant/components/home_connect/*
|
||||
|
|
|
@ -1,43 +1,26 @@
|
|||
"""Support for the Hive devices and services."""
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
import logging
|
||||
|
||||
from pyhiveapi import Hive
|
||||
from aiohttp.web_exceptions import HTTPException
|
||||
from apyhiveapi import Hive
|
||||
from apyhiveapi.helper.hive_exceptions import HiveReauthRequired
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_PASSWORD,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import DOMAIN, PLATFORM_LOOKUP, PLATFORMS
|
||||
|
||||
ATTR_AVAILABLE = "available"
|
||||
DOMAIN = "hive"
|
||||
DATA_HIVE = "data_hive"
|
||||
SERVICES = ["Heating", "HotWater", "TRV"]
|
||||
SERVICE_BOOST_HOT_WATER = "boost_hot_water"
|
||||
SERVICE_BOOST_HEATING = "boost_heating"
|
||||
ATTR_TIME_PERIOD = "time_period"
|
||||
ATTR_MODE = "on_off"
|
||||
DEVICETYPES = {
|
||||
"binary_sensor": "device_list_binary_sensor",
|
||||
"climate": "device_list_climate",
|
||||
"water_heater": "device_list_water_heater",
|
||||
"light": "device_list_light",
|
||||
"switch": "device_list_plug",
|
||||
"sensor": "device_list_sensor",
|
||||
}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -52,101 +35,88 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
BOOST_HEATING_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(ATTR_TIME_PERIOD): vol.All(
|
||||
cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60
|
||||
),
|
||||
vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float),
|
||||
}
|
||||
)
|
||||
|
||||
BOOST_HOT_WATER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All(
|
||||
cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60
|
||||
),
|
||||
vol.Required(ATTR_MODE): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Hive Component."""
|
||||
"""Hive configuration setup."""
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
async def heating_boost(service):
|
||||
"""Handle the service call."""
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
entity_lookup = hass.data[DOMAIN]["entity_lookup"]
|
||||
hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID])
|
||||
if not hive_id:
|
||||
# log or raise error
|
||||
_LOGGER.error("Cannot boost entity id entered")
|
||||
return
|
||||
conf = config[DOMAIN]
|
||||
|
||||
minutes = service.data[ATTR_TIME_PERIOD]
|
||||
temperature = service.data[ATTR_TEMPERATURE]
|
||||
if not hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_USERNAME: conf[CONF_USERNAME],
|
||||
CONF_PASSWORD: conf[CONF_PASSWORD],
|
||||
},
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
hive.heating.turn_boost_on(hive_id, minutes, temperature)
|
||||
|
||||
async def hot_water_boost(service):
|
||||
"""Handle the service call."""
|
||||
entity_lookup = hass.data[DOMAIN]["entity_lookup"]
|
||||
hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID])
|
||||
if not hive_id:
|
||||
# log or raise error
|
||||
_LOGGER.error("Cannot boost entity id entered")
|
||||
return
|
||||
minutes = service.data[ATTR_TIME_PERIOD]
|
||||
mode = service.data[ATTR_MODE]
|
||||
async def async_setup_entry(hass, entry):
|
||||
"""Set up Hive from a config entry."""
|
||||
|
||||
if mode == "on":
|
||||
hive.hotwater.turn_boost_on(hive_id, minutes)
|
||||
elif mode == "off":
|
||||
hive.hotwater.turn_boost_off(hive_id)
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
hive = Hive(websession)
|
||||
hive_config = dict(entry.data)
|
||||
|
||||
hive = Hive()
|
||||
hive_config["options"] = {}
|
||||
hive_config["options"].update(
|
||||
{CONF_SCAN_INTERVAL: dict(entry.options).get(CONF_SCAN_INTERVAL, 120)}
|
||||
)
|
||||
hass.data[DOMAIN][entry.entry_id] = hive
|
||||
|
||||
config = {}
|
||||
config["username"] = config[DOMAIN][CONF_USERNAME]
|
||||
config["password"] = config[DOMAIN][CONF_PASSWORD]
|
||||
config["update_interval"] = config[DOMAIN][CONF_SCAN_INTERVAL]
|
||||
|
||||
devices = await hive.session.startSession(config)
|
||||
|
||||
if devices is None:
|
||||
_LOGGER.error("Hive API initialization failed")
|
||||
try:
|
||||
devices = await hive.session.startSession(hive_config)
|
||||
except HTTPException as error:
|
||||
_LOGGER.error("Could not connect to the internet: %s", error)
|
||||
raise ConfigEntryNotReady() from error
|
||||
except HiveReauthRequired:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={
|
||||
"source": config_entries.SOURCE_REAUTH,
|
||||
"unique_id": entry.unique_id,
|
||||
},
|
||||
data=entry.data,
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN][DATA_HIVE] = hive
|
||||
hass.data[DOMAIN]["entity_lookup"] = {}
|
||||
|
||||
for ha_type in DEVICETYPES:
|
||||
devicelist = devices.get(DEVICETYPES[ha_type])
|
||||
if devicelist:
|
||||
for ha_type, hive_type in PLATFORM_LOOKUP.items():
|
||||
device_list = devices.get(hive_type)
|
||||
if device_list:
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, ha_type, DOMAIN, devicelist, config)
|
||||
hass.config_entries.async_forward_entry_setup(entry, ha_type)
|
||||
)
|
||||
if ha_type == "climate":
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_BOOST_HEATING,
|
||||
heating_boost,
|
||||
schema=BOOST_HEATING_SCHEMA,
|
||||
)
|
||||
if ha_type == "water_heater":
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_BOOST_HOT_WATER,
|
||||
hot_water_boost,
|
||||
schema=BOOST_HOT_WATER_SCHEMA,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
"""Unload a config entry."""
|
||||
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
def refresh_system(func):
|
||||
"""Force update all entities after state change."""
|
||||
|
||||
|
@ -173,6 +143,3 @@ class HiveEntity(Entity):
|
|||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, DOMAIN, self.async_write_ha_state)
|
||||
)
|
||||
if self.device["hiveType"] in SERVICES:
|
||||
entity_lookup = self.hass.data[DOMAIN]["entity_lookup"]
|
||||
entity_lookup[self.entity_id] = self.device["hiveID"]
|
||||
|
|
|
@ -10,7 +10,8 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorEntity,
|
||||
)
|
||||
|
||||
from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity
|
||||
from . import HiveEntity
|
||||
from .const import ATTR_MODE, DOMAIN
|
||||
|
||||
DEVICETYPE = {
|
||||
"contactsensor": DEVICE_CLASS_OPENING,
|
||||
|
@ -24,13 +25,11 @@ PARALLEL_UPDATES = 0
|
|||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Hive Binary Sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Hive thermostat based on a config entry."""
|
||||
|
||||
hive = hass.data[DOMAIN].get(DATA_HIVE)
|
||||
devices = hive.devices.get("binary_sensor")
|
||||
hive = hass.data[DOMAIN][entry.entry_id]
|
||||
devices = hive.session.deviceList.get("binary_sensor")
|
||||
entities = []
|
||||
if devices:
|
||||
for dev in devices:
|
||||
|
@ -49,7 +48,14 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device["device_id"])},
|
||||
"name": self.device["device_name"],
|
||||
"model": self.device["deviceData"]["model"],
|
||||
"manufacturer": self.device["deviceData"]["manufacturer"],
|
||||
"sw_version": self.device["deviceData"]["version"],
|
||||
"via_device": (DOMAIN, self.device["parentDevice"]),
|
||||
}
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
|
@ -72,7 +78,6 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity):
|
|||
def extra_state_attributes(self):
|
||||
"""Show Device Attributes."""
|
||||
return {
|
||||
ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE),
|
||||
ATTR_MODE: self.attributes.get(ATTR_MODE),
|
||||
}
|
||||
|
||||
|
@ -84,5 +89,5 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity):
|
|||
async def async_update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.sensor.get_sensor(self.device)
|
||||
self.device = await self.hive.sensor.getSensor(self.device)
|
||||
self.attributes = self.device.get("attributes", {})
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Support for the Hive climate devices."""
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_HEAT,
|
||||
|
@ -15,8 +17,10 @@ from homeassistant.components.climate.const import (
|
|||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
|
||||
from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system
|
||||
from . import HiveEntity, refresh_system
|
||||
from .const import ATTR_TIME_PERIOD, DOMAIN, SERVICE_BOOST_HEATING
|
||||
|
||||
HIVE_TO_HASS_STATE = {
|
||||
"SCHEDULE": HVAC_MODE_AUTO,
|
||||
|
@ -45,19 +49,32 @@ PARALLEL_UPDATES = 0
|
|||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Hive thermostat."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Hive thermostat based on a config entry."""
|
||||
|
||||
hive = hass.data[DOMAIN].get(DATA_HIVE)
|
||||
devices = hive.devices.get("climate")
|
||||
hive = hass.data[DOMAIN][entry.entry_id]
|
||||
devices = hive.session.deviceList.get("climate")
|
||||
entities = []
|
||||
if devices:
|
||||
for dev in devices:
|
||||
entities.append(HiveClimateEntity(hive, dev))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_BOOST_HEATING,
|
||||
{
|
||||
vol.Required(ATTR_TIME_PERIOD): vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta,
|
||||
lambda td: td.total_seconds() // 60,
|
||||
),
|
||||
vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float),
|
||||
},
|
||||
"async_heating_boost",
|
||||
)
|
||||
|
||||
|
||||
class HiveClimateEntity(HiveEntity, ClimateEntity):
|
||||
"""Hive Climate Device."""
|
||||
|
@ -76,7 +93,14 @@ class HiveClimateEntity(HiveEntity, ClimateEntity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device["device_id"])},
|
||||
"name": self.device["device_name"],
|
||||
"model": self.device["deviceData"]["model"],
|
||||
"manufacturer": self.device["deviceData"]["manufacturer"],
|
||||
"sw_version": self.device["deviceData"]["version"],
|
||||
"via_device": (DOMAIN, self.device["parentDevice"]),
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -93,11 +117,6 @@ class HiveClimateEntity(HiveEntity, ClimateEntity):
|
|||
"""Return if the device is available."""
|
||||
return self.device["deviceData"]["online"]
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Show Device Attributes."""
|
||||
return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)}
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available hvac operation modes.
|
||||
|
@ -160,27 +179,31 @@ class HiveClimateEntity(HiveEntity, ClimateEntity):
|
|||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
new_mode = HASS_TO_HIVE_STATE[hvac_mode]
|
||||
await self.hive.heating.set_mode(self.device, new_mode)
|
||||
await self.hive.heating.setMode(self.device, new_mode)
|
||||
|
||||
@refresh_system
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
new_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
if new_temperature is not None:
|
||||
await self.hive.heating.set_target_temperature(self.device, new_temperature)
|
||||
await self.hive.heating.setTargetTemperature(self.device, new_temperature)
|
||||
|
||||
@refresh_system
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set new preset mode."""
|
||||
if preset_mode == PRESET_NONE and self.preset_mode == PRESET_BOOST:
|
||||
await self.hive.heating.turn_boost_off(self.device)
|
||||
await self.hive.heating.turnBoostOff(self.device)
|
||||
elif preset_mode == PRESET_BOOST:
|
||||
curtemp = round(self.current_temperature * 2) / 2
|
||||
temperature = curtemp + 0.5
|
||||
await self.hive.heating.turn_boost_on(self.device, 30, temperature)
|
||||
await self.hive.heating.turnBoostOn(self.device, 30, temperature)
|
||||
|
||||
@refresh_system
|
||||
async def async_heating_boost(self, time_period, temperature):
|
||||
"""Handle boost heating service call."""
|
||||
await self.hive.heating.turnBoostOn(self.device, time_period, temperature)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.heating.get_heating(self.device)
|
||||
self.attributes.update(self.device.get("attributes", {}))
|
||||
self.device = await self.hive.heating.getHeating(self.device)
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
"""Config Flow for Hive."""
|
||||
|
||||
from apyhiveapi import Auth
|
||||
from apyhiveapi.helper.hive_exceptions import (
|
||||
HiveApiError,
|
||||
HiveInvalid2FACode,
|
||||
HiveInvalidPassword,
|
||||
HiveInvalidUsername,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import ( # pylint:disable=unused-import
|
||||
CONF_CODE,
|
||||
CONFIG_ENTRY_VERSION,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a Hive config flow."""
|
||||
|
||||
VERSION = CONFIG_ENTRY_VERSION
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the config flow."""
|
||||
self.hive_auth = None
|
||||
self.data = {}
|
||||
self.tokens = {}
|
||||
self.entry = None
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Prompt user input. Create or edit entry."""
|
||||
errors = {}
|
||||
# Login to Hive with user data.
|
||||
if user_input is not None:
|
||||
self.data.update(user_input)
|
||||
self.hive_auth = Auth(
|
||||
username=self.data[CONF_USERNAME], password=self.data[CONF_PASSWORD]
|
||||
)
|
||||
|
||||
# Get user from existing entry and abort if already setup
|
||||
self.entry = await self.async_set_unique_id(self.data[CONF_USERNAME])
|
||||
if self.context["source"] != config_entries.SOURCE_REAUTH:
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
# Login to the Hive.
|
||||
try:
|
||||
self.tokens = await self.hive_auth.login()
|
||||
except HiveInvalidUsername:
|
||||
errors["base"] = "invalid_username"
|
||||
except HiveInvalidPassword:
|
||||
errors["base"] = "invalid_password"
|
||||
except HiveApiError:
|
||||
errors["base"] = "no_internet_available"
|
||||
|
||||
if self.tokens.get("ChallengeName") == "SMS_MFA":
|
||||
# Complete SMS 2FA.
|
||||
return await self.async_step_2fa()
|
||||
|
||||
if not errors:
|
||||
# Complete the entry setup.
|
||||
try:
|
||||
return await self.async_setup_hive_entry()
|
||||
except UnknownHiveError:
|
||||
errors["base"] = "unknown"
|
||||
|
||||
# Show User Input form.
|
||||
schema = vol.Schema(
|
||||
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||
)
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
|
||||
async def async_step_2fa(self, user_input=None):
|
||||
"""Handle 2fa step."""
|
||||
errors = {}
|
||||
|
||||
if user_input and user_input["2fa"] == "0000":
|
||||
self.tokens = await self.hive_auth.login()
|
||||
elif user_input:
|
||||
try:
|
||||
self.tokens = await self.hive_auth.sms_2fa(
|
||||
user_input["2fa"], self.tokens
|
||||
)
|
||||
except HiveInvalid2FACode:
|
||||
errors["base"] = "invalid_code"
|
||||
except HiveApiError:
|
||||
errors["base"] = "no_internet_available"
|
||||
|
||||
if not errors:
|
||||
try:
|
||||
return await self.async_setup_hive_entry()
|
||||
except UnknownHiveError:
|
||||
errors["base"] = "unknown"
|
||||
|
||||
schema = vol.Schema({vol.Required(CONF_CODE): str})
|
||||
return self.async_show_form(step_id="2fa", data_schema=schema, errors=errors)
|
||||
|
||||
async def async_setup_hive_entry(self):
|
||||
"""Finish setup and create the config entry."""
|
||||
|
||||
if "AuthenticationResult" not in self.tokens:
|
||||
raise UnknownHiveError
|
||||
|
||||
# Setup the config entry
|
||||
self.data["tokens"] = self.tokens
|
||||
if self.context["source"] == config_entries.SOURCE_REAUTH:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.entry, title=self.data["username"], data=self.data
|
||||
)
|
||||
await self.hass.config_entries.async_reload(self.entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
return self.async_create_entry(title=self.data["username"], data=self.data)
|
||||
|
||||
async def async_step_reauth(self, user_input=None):
|
||||
"""Re Authenticate a user."""
|
||||
data = {
|
||||
CONF_USERNAME: user_input[CONF_USERNAME],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
}
|
||||
return await self.async_step_user(data)
|
||||
|
||||
async def async_step_import(self, user_input=None):
|
||||
"""Import user."""
|
||||
return await self.async_step_user(user_input)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Hive options callback."""
|
||||
return HiveOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class HiveOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Config flow options for Hive."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize Hive options flow."""
|
||||
self.hive = None
|
||||
self.config_entry = config_entry
|
||||
self.interval = config_entry.options.get(CONF_SCAN_INTERVAL, 120)
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
self.hive = self.hass.data["hive"][self.config_entry.entry_id]
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
new_interval = user_input.get(CONF_SCAN_INTERVAL)
|
||||
await self.hive.updateInterval(new_interval)
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=self.interval): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=30)
|
||||
)
|
||||
}
|
||||
)
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
|
||||
|
||||
class UnknownHiveError(Exception):
|
||||
"""Catch unknown hive error."""
|
|
@ -0,0 +1,20 @@
|
|||
"""Constants for Hive."""
|
||||
ATTR_MODE = "mode"
|
||||
ATTR_TIME_PERIOD = "time_period"
|
||||
ATTR_ONOFF = "on_off"
|
||||
CONF_CODE = "2fa"
|
||||
CONFIG_ENTRY_VERSION = 1
|
||||
DEFAULT_NAME = "Hive"
|
||||
DOMAIN = "hive"
|
||||
PLATFORMS = ["binary_sensor", "climate", "light", "sensor", "switch", "water_heater"]
|
||||
PLATFORM_LOOKUP = {
|
||||
"binary_sensor": "binary_sensor",
|
||||
"climate": "climate",
|
||||
"light": "light",
|
||||
"sensor": "sensor",
|
||||
"switch": "switch",
|
||||
"water_heater": "water_heater",
|
||||
}
|
||||
SERVICE_BOOST_HOT_WATER = "boost_hot_water"
|
||||
SERVICE_BOOST_HEATING = "boost_heating"
|
||||
WATER_HEATER_MODES = ["on", "off"]
|
|
@ -12,19 +12,18 @@ from homeassistant.components.light import (
|
|||
)
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system
|
||||
from . import HiveEntity, refresh_system
|
||||
from .const import ATTR_MODE, DOMAIN
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Hive Light."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Hive thermostat based on a config entry."""
|
||||
|
||||
hive = hass.data[DOMAIN].get(DATA_HIVE)
|
||||
devices = hive.devices.get("light")
|
||||
hive = hass.data[DOMAIN][entry.entry_id]
|
||||
devices = hive.session.deviceList.get("light")
|
||||
entities = []
|
||||
if devices:
|
||||
for dev in devices:
|
||||
|
@ -43,7 +42,14 @@ class HiveDeviceLight(HiveEntity, LightEntity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device["device_id"])},
|
||||
"name": self.device["device_name"],
|
||||
"model": self.device["deviceData"]["model"],
|
||||
"manufacturer": self.device["deviceData"]["manufacturer"],
|
||||
"sw_version": self.device["deviceData"]["version"],
|
||||
"via_device": (DOMAIN, self.device["parentDevice"]),
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -59,7 +65,6 @@ class HiveDeviceLight(HiveEntity, LightEntity):
|
|||
def extra_state_attributes(self):
|
||||
"""Show Device Attributes."""
|
||||
return {
|
||||
ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE),
|
||||
ATTR_MODE: self.attributes.get(ATTR_MODE),
|
||||
}
|
||||
|
||||
|
@ -117,14 +122,14 @@ class HiveDeviceLight(HiveEntity, LightEntity):
|
|||
saturation = int(get_new_color[1])
|
||||
new_color = (hue, saturation, 100)
|
||||
|
||||
await self.hive.light.turn_on(
|
||||
await self.hive.light.turnOn(
|
||||
self.device, new_brightness, new_color_temp, new_color
|
||||
)
|
||||
|
||||
@refresh_system
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Instruct the light to turn off."""
|
||||
await self.hive.light.turn_off(self.device)
|
||||
await self.hive.light.turnOff(self.device)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -142,5 +147,5 @@ class HiveDeviceLight(HiveEntity, LightEntity):
|
|||
async def async_update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.light.get_light(self.device)
|
||||
self.device = await self.hive.light.getLight(self.device)
|
||||
self.attributes.update(self.device.get("attributes", {}))
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"domain": "hive",
|
||||
"name": "Hive",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/hive",
|
||||
"requirements": [
|
||||
"pyhiveapi==0.3.4.4"
|
||||
"pyhiveapi==0.3.9"
|
||||
],
|
||||
"codeowners": [
|
||||
"@Rendili",
|
||||
|
|
|
@ -5,7 +5,8 @@ from datetime import timedelta
|
|||
from homeassistant.components.sensor import DEVICE_CLASS_BATTERY
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity
|
||||
from . import HiveEntity
|
||||
from .const import DOMAIN
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
@ -14,18 +15,15 @@ DEVICETYPE = {
|
|||
}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Hive Sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Hive thermostat based on a config entry."""
|
||||
|
||||
hive = hass.data[DOMAIN].get(DATA_HIVE)
|
||||
devices = hive.devices.get("sensor")
|
||||
hive = hass.data[DOMAIN][entry.entry_id]
|
||||
devices = hive.session.deviceList.get("sensor")
|
||||
entities = []
|
||||
if devices:
|
||||
for dev in devices:
|
||||
if dev["hiveType"] in DEVICETYPE:
|
||||
entities.append(HiveSensorEntity(hive, dev))
|
||||
entities.append(HiveSensorEntity(hive, dev))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
|
@ -40,7 +38,14 @@ class HiveSensorEntity(HiveEntity, Entity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device["device_id"])},
|
||||
"name": self.device["device_name"],
|
||||
"model": self.device["deviceData"]["model"],
|
||||
"manufacturer": self.device["deviceData"]["manufacturer"],
|
||||
"sw_version": self.device["deviceData"]["version"],
|
||||
"via_device": (DOMAIN, self.device["parentDevice"]),
|
||||
}
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
|
@ -67,12 +72,7 @@ class HiveSensorEntity(HiveEntity, Entity):
|
|||
"""Return the state of the sensor."""
|
||||
return self.device["status"]["state"]
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)}
|
||||
|
||||
async def async_update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.sensor.get_sensor(self.device)
|
||||
self.device = await self.hive.sensor.getSensor(self.device)
|
||||
|
|
|
@ -1,24 +1,62 @@
|
|||
boost_heating:
|
||||
name: Boost Heating
|
||||
description: Set the boost mode ON defining the period of time and the desired target temperature for the boost.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Enter the entity_id for the device required to set the boost mode.
|
||||
example: "climate.heating"
|
||||
name: Entity ID
|
||||
description: Select entity_id to boost.
|
||||
required: true
|
||||
example: climate.heating
|
||||
selector:
|
||||
entity:
|
||||
integration: hive
|
||||
domain: climate
|
||||
time_period:
|
||||
name: Time Period
|
||||
description: Set the time period for the boost.
|
||||
example: "01:30:00"
|
||||
required: true
|
||||
example: 01:30:00
|
||||
selector:
|
||||
time:
|
||||
temperature:
|
||||
name: Temperature
|
||||
description: Set the target temperature for the boost period.
|
||||
example: "20.5"
|
||||
required: true
|
||||
example: 20.5
|
||||
selector:
|
||||
number:
|
||||
min: 7
|
||||
max: 35
|
||||
step: 0.5
|
||||
unit_of_measurement: degrees
|
||||
mode: slider
|
||||
boost_hot_water:
|
||||
description: "Set the boost mode ON or OFF defining the period of time for the boost."
|
||||
name: Boost Hotwater
|
||||
description: Set the boost mode ON or OFF defining the period of time for the boost.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Enter the entity_id for the device reuired to set the boost mode.
|
||||
example: "water_heater.hot_water"
|
||||
name: Entity ID
|
||||
description: Select entity_id to boost.
|
||||
required: true
|
||||
example: water_heater.hot_water
|
||||
selector:
|
||||
entity:
|
||||
integration: hive
|
||||
domain: water_heater
|
||||
time_period:
|
||||
name: Time Period
|
||||
description: Set the time period for the boost.
|
||||
example: "01:30:00"
|
||||
required: true
|
||||
example: 01:30:00
|
||||
selector:
|
||||
time:
|
||||
on_off:
|
||||
name: Mode
|
||||
description: Set the boost function on or off.
|
||||
required: true
|
||||
example: "on"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "on"
|
||||
- "off"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Hive Login",
|
||||
"description": "Enter your Hive login information and configuration.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"scan_interval": "Scan Interval (seconds)"
|
||||
}
|
||||
},
|
||||
"2fa": {
|
||||
"title": "Hive Two-factor Authentication.",
|
||||
"description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.",
|
||||
"data": {
|
||||
"2fa": "Two-factor code"
|
||||
}
|
||||
},
|
||||
"reauth": {
|
||||
"title": "Hive Login",
|
||||
"description": "Re-enter your Hive login information.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_username": "Failed to sign into Hive. Your email address is not recognised.",
|
||||
"invalid_password": "Failed to sign into Hive. Incorrect password please try again.",
|
||||
"invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.",
|
||||
"no_internet_available": "An internet connection is required to connect to Hive.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"unknown_entry": "Unable to find existing entry.",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Options for Hive",
|
||||
"description": "Update the scan interval to poll for data more often.",
|
||||
"data": {
|
||||
"scan_interval": "Scan Interval (seconds)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,19 +3,18 @@ from datetime import timedelta
|
|||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
|
||||
from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system
|
||||
from . import HiveEntity, refresh_system
|
||||
from .const import ATTR_MODE, DOMAIN
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Hive Switch."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Hive thermostat based on a config entry."""
|
||||
|
||||
hive = hass.data[DOMAIN].get(DATA_HIVE)
|
||||
devices = hive.devices.get("switch")
|
||||
hive = hass.data[DOMAIN][entry.entry_id]
|
||||
devices = hive.session.deviceList.get("switch")
|
||||
entities = []
|
||||
if devices:
|
||||
for dev in devices:
|
||||
|
@ -34,7 +33,15 @@ class HiveDevicePlug(HiveEntity, SwitchEntity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
|
||||
if self.device["hiveType"] == "activeplug":
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device["device_id"])},
|
||||
"name": self.device["device_name"],
|
||||
"model": self.device["deviceData"]["model"],
|
||||
"manufacturer": self.device["deviceData"]["manufacturer"],
|
||||
"sw_version": self.device["deviceData"]["version"],
|
||||
"via_device": (DOMAIN, self.device["parentDevice"]),
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -50,7 +57,6 @@ class HiveDevicePlug(HiveEntity, SwitchEntity):
|
|||
def extra_state_attributes(self):
|
||||
"""Show Device Attributes."""
|
||||
return {
|
||||
ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE),
|
||||
ATTR_MODE: self.attributes.get(ATTR_MODE),
|
||||
}
|
||||
|
||||
|
@ -67,16 +73,14 @@ class HiveDevicePlug(HiveEntity, SwitchEntity):
|
|||
@refresh_system
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
if self.device["hiveType"] == "activeplug":
|
||||
await self.hive.switch.turn_on(self.device)
|
||||
await self.hive.switch.turnOn(self.device)
|
||||
|
||||
@refresh_system
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
if self.device["hiveType"] == "activeplug":
|
||||
await self.hive.switch.turn_off(self.device)
|
||||
await self.hive.switch.turnOff(self.device)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.switch.get_plug(self.device)
|
||||
self.device = await self.hive.switch.getPlug(self.device)
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Hive Login",
|
||||
"description": "Enter your Hive login information and configuration.",
|
||||
"data": {
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"scan_interval": "Scan Interval (seconds)"
|
||||
}
|
||||
},
|
||||
"2fa": {
|
||||
"title": "Hive Two-factor Authentication.",
|
||||
"description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.",
|
||||
"data": {
|
||||
"2fa": "Two-factor code"
|
||||
}
|
||||
},
|
||||
"reauth": {
|
||||
"title": "Hive Login",
|
||||
"description": "Re-enter your Hive login information.",
|
||||
"data": {
|
||||
"username": "Username",
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_username": "Failed to sign into Hive. Your email address is not recognised.",
|
||||
"invalid_password": "Failed to sign into Hive. Incorrect password please try again.",
|
||||
"invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.",
|
||||
"no_internet_available": "An internet connection is required to connect to Hive.",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Account is already configured",
|
||||
"unknown_entry": "Unable to find existing entry.",
|
||||
"reauth_successful": "Re-authentication was successful"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Options for Hive",
|
||||
"description": "Update the scan interval to poll for data more often.",
|
||||
"data": {
|
||||
"scan_interval": "Scan Interval (seconds)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.water_heater import (
|
||||
STATE_ECO,
|
||||
STATE_OFF,
|
||||
|
@ -10,8 +12,16 @@ from homeassistant.components.water_heater import (
|
|||
WaterHeaterEntity,
|
||||
)
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
|
||||
from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system
|
||||
from . import HiveEntity, refresh_system
|
||||
from .const import (
|
||||
ATTR_ONOFF,
|
||||
ATTR_TIME_PERIOD,
|
||||
DOMAIN,
|
||||
SERVICE_BOOST_HOT_WATER,
|
||||
WATER_HEATER_MODES,
|
||||
)
|
||||
|
||||
SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE
|
||||
HOTWATER_NAME = "Hot Water"
|
||||
|
@ -32,19 +42,32 @@ HASS_TO_HIVE_STATE = {
|
|||
SUPPORT_WATER_HEATER = [STATE_ECO, STATE_ON, STATE_OFF]
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Hive Hotwater."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Hive thermostat based on a config entry."""
|
||||
|
||||
hive = hass.data[DOMAIN].get(DATA_HIVE)
|
||||
devices = hive.devices.get("water_heater")
|
||||
hive = hass.data[DOMAIN][entry.entry_id]
|
||||
devices = hive.session.deviceList.get("water_heater")
|
||||
entities = []
|
||||
if devices:
|
||||
for dev in devices:
|
||||
entities.append(HiveWaterHeater(hive, dev))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_BOOST_HOT_WATER,
|
||||
{
|
||||
vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta,
|
||||
lambda td: td.total_seconds() // 60,
|
||||
),
|
||||
vol.Required(ATTR_ONOFF): vol.In(WATER_HEATER_MODES),
|
||||
},
|
||||
"async_hot_water_boost",
|
||||
)
|
||||
|
||||
|
||||
class HiveWaterHeater(HiveEntity, WaterHeaterEntity):
|
||||
"""Hive Water Heater Device."""
|
||||
|
@ -57,7 +80,14 @@ class HiveWaterHeater(HiveEntity, WaterHeaterEntity):
|
|||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name}
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device["device_id"])},
|
||||
"name": self.device["device_name"],
|
||||
"model": self.device["deviceData"]["model"],
|
||||
"manufacturer": self.device["deviceData"]["manufacturer"],
|
||||
"sw_version": self.device["deviceData"]["version"],
|
||||
"via_device": (DOMAIN, self.device["parentDevice"]),
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -92,20 +122,28 @@ class HiveWaterHeater(HiveEntity, WaterHeaterEntity):
|
|||
@refresh_system
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on hotwater."""
|
||||
await self.hive.hotwater.set_mode(self.device, "MANUAL")
|
||||
await self.hive.hotwater.setMode(self.device, "MANUAL")
|
||||
|
||||
@refresh_system
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn on hotwater."""
|
||||
await self.hive.hotwater.set_mode(self.device, "OFF")
|
||||
await self.hive.hotwater.setMode(self.device, "OFF")
|
||||
|
||||
@refresh_system
|
||||
async def async_set_operation_mode(self, operation_mode):
|
||||
"""Set operation mode."""
|
||||
new_mode = HASS_TO_HIVE_STATE[operation_mode]
|
||||
await self.hive.hotwater.set_mode(self.device, new_mode)
|
||||
await self.hive.hotwater.setMode(self.device, new_mode)
|
||||
|
||||
@refresh_system
|
||||
async def async_hot_water_boost(self, time_period, on_off):
|
||||
"""Handle the service call."""
|
||||
if on_off == "on":
|
||||
await self.hive.hotwater.turnBoostOn(self.device, time_period)
|
||||
elif on_off == "off":
|
||||
await self.hive.hotwater.turnBoostOff(self.device)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
await self.hive.session.updateData(self.device)
|
||||
self.device = await self.hive.hotwater.get_hotwater(self.device)
|
||||
self.device = await self.hive.hotwater.getHotwater(self.device)
|
||||
|
|
|
@ -92,6 +92,7 @@ FLOWS = [
|
|||
"harmony",
|
||||
"heos",
|
||||
"hisense_aehw4a1",
|
||||
"hive",
|
||||
"hlk_sw16",
|
||||
"home_connect",
|
||||
"homekit",
|
||||
|
|
|
@ -1428,7 +1428,7 @@ pyheos==0.7.2
|
|||
pyhik==0.2.8
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhiveapi==0.3.4.4
|
||||
pyhiveapi==0.3.9
|
||||
|
||||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.72
|
||||
|
|
|
@ -747,6 +747,9 @@ pyhaversion==3.4.2
|
|||
# homeassistant.components.heos
|
||||
pyheos==0.7.2
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhiveapi==0.3.9
|
||||
|
||||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.72
|
||||
|
||||
|
|
|
@ -0,0 +1,576 @@
|
|||
"""Test the Hive config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from apyhiveapi.helper import hive_exceptions
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow, setup
|
||||
from homeassistant.components.hive.const import CONF_CODE, DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
USERNAME = "username@home-assistant.com"
|
||||
UPDATED_USERNAME = "updated_username@home-assistant.com"
|
||||
PASSWORD = "test-password"
|
||||
UPDATED_PASSWORD = "updated-password"
|
||||
INCORRECT_PASSWORD = "incoreect-password"
|
||||
SCAN_INTERVAL = 120
|
||||
UPDATED_SCAN_INTERVAL = 60
|
||||
MFA_CODE = "1234"
|
||||
MFA_RESEND_CODE = "0000"
|
||||
MFA_INVALID_CODE = "HIVE"
|
||||
|
||||
|
||||
async def test_import_flow(hass):
|
||||
"""Check import flow."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SUCCESS",
|
||||
"AuthenticationResult": {
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.hive.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.hive.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == USERNAME
|
||||
assert result["data"] == {
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
"tokens": {
|
||||
"AuthenticationResult": {
|
||||
"AccessToken": "mock-access-token",
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
},
|
||||
"ChallengeName": "SUCCESS",
|
||||
},
|
||||
}
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_user_flow(hass):
|
||||
"""Test the user flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SUCCESS",
|
||||
"AuthenticationResult": {
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.hive.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.hive.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == USERNAME
|
||||
assert result2["data"] == {
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
"tokens": {
|
||||
"AuthenticationResult": {
|
||||
"AccessToken": "mock-access-token",
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
},
|
||||
"ChallengeName": "SUCCESS",
|
||||
},
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
|
||||
|
||||
async def test_user_flow_2fa(hass):
|
||||
"""Test user flow with 2FA."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SMS_MFA",
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == CONF_CODE
|
||||
assert result2["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.sms_2fa",
|
||||
return_value={
|
||||
"ChallengeName": "SUCCESS",
|
||||
"AuthenticationResult": {
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.hive.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.hive.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"], {CONF_CODE: MFA_CODE}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result3["title"] == USERNAME
|
||||
assert result3["data"] == {
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
"tokens": {
|
||||
"AuthenticationResult": {
|
||||
"AccessToken": "mock-access-token",
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
},
|
||||
"ChallengeName": "SUCCESS",
|
||||
},
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
|
||||
|
||||
async def test_reauth_flow(hass):
|
||||
"""Test the reauth flow."""
|
||||
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=USERNAME,
|
||||
data={
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: INCORRECT_PASSWORD,
|
||||
"tokens": {
|
||||
"AccessToken": "mock-access-token",
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
},
|
||||
},
|
||||
)
|
||||
mock_config.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
side_effect=hive_exceptions.HiveInvalidPassword(),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={
|
||||
"source": config_entries.SOURCE_REAUTH,
|
||||
"unique_id": mock_config.unique_id,
|
||||
},
|
||||
data=mock_config.data,
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "invalid_password"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SUCCESS",
|
||||
"AuthenticationResult": {
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: UPDATED_PASSWORD,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config.data.get("username") == USERNAME
|
||||
assert mock_config.data.get("password") == UPDATED_PASSWORD
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result2["reason"] == "reauth_successful"
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
|
||||
|
||||
async def test_option_flow(hass):
|
||||
"""Test config flow options."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title=USERNAME,
|
||||
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.options.async_init(
|
||||
entry.entry_id,
|
||||
data=None,
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={CONF_SCAN_INTERVAL: UPDATED_SCAN_INTERVAL}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"][CONF_SCAN_INTERVAL] == UPDATED_SCAN_INTERVAL
|
||||
|
||||
|
||||
async def test_user_flow_2fa_send_new_code(hass):
|
||||
"""Resend a 2FA code if it didn't arrive."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SMS_MFA",
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == CONF_CODE
|
||||
assert result2["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SMS_MFA",
|
||||
},
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"], {CONF_CODE: MFA_RESEND_CODE}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result3["step_id"] == CONF_CODE
|
||||
assert result3["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.sms_2fa",
|
||||
return_value={
|
||||
"ChallengeName": "SUCCESS",
|
||||
"AuthenticationResult": {
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
"AccessToken": "mock-access-token",
|
||||
},
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.hive.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.hive.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"], {CONF_CODE: MFA_CODE}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result4["title"] == USERNAME
|
||||
assert result4["data"] == {
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
"tokens": {
|
||||
"AuthenticationResult": {
|
||||
"AccessToken": "mock-access-token",
|
||||
"RefreshToken": "mock-refresh-token",
|
||||
},
|
||||
"ChallengeName": "SUCCESS",
|
||||
},
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
|
||||
|
||||
async def test_abort_if_existing_entry(hass):
|
||||
"""Check flow abort when an entry already exist."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=USERNAME,
|
||||
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
options={CONF_SCAN_INTERVAL: SCAN_INTERVAL},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={
|
||||
CONF_USERNAME: USERNAME,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_user_flow_invalid_username(hass):
|
||||
"""Test user flow with invalid username."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
side_effect=hive_exceptions.HiveInvalidUsername(),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "user"
|
||||
assert result2["errors"] == {"base": "invalid_username"}
|
||||
|
||||
|
||||
async def test_user_flow_invalid_password(hass):
|
||||
"""Test user flow with invalid password."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
side_effect=hive_exceptions.HiveInvalidPassword(),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "user"
|
||||
assert result2["errors"] == {"base": "invalid_password"}
|
||||
|
||||
|
||||
async def test_user_flow_no_internet_connection(hass):
|
||||
"""Test user flow with no internet connection."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
side_effect=hive_exceptions.HiveApiError(),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "user"
|
||||
assert result2["errors"] == {"base": "no_internet_available"}
|
||||
|
||||
|
||||
async def test_user_flow_2fa_no_internet_connection(hass):
|
||||
"""Test user flow with no internet connection."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SMS_MFA",
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == CONF_CODE
|
||||
assert result2["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.sms_2fa",
|
||||
side_effect=hive_exceptions.HiveApiError(),
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_CODE: MFA_CODE},
|
||||
)
|
||||
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result3["step_id"] == CONF_CODE
|
||||
assert result3["errors"] == {"base": "no_internet_available"}
|
||||
|
||||
|
||||
async def test_user_flow_2fa_invalid_code(hass):
|
||||
"""Test user flow with 2FA."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SMS_MFA",
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == CONF_CODE
|
||||
assert result2["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.sms_2fa",
|
||||
side_effect=hive_exceptions.HiveInvalid2FACode(),
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_CODE: MFA_INVALID_CODE},
|
||||
)
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result3["step_id"] == CONF_CODE
|
||||
assert result3["errors"] == {"base": "invalid_code"}
|
||||
|
||||
|
||||
async def test_user_flow_unknown_error(hass):
|
||||
"""Test user flow when unknown error occurs."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={"ChallengeName": "FAILED", "InvalidAuthenticationResult": {}},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["errors"] == {"base": "unknown"}
|
||||
|
||||
|
||||
async def test_user_flow_2fa_unknown_error(hass):
|
||||
"""Test 2fa flow when unknown error occurs."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.login",
|
||||
return_value={
|
||||
"ChallengeName": "SMS_MFA",
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == CONF_CODE
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hive.config_flow.Auth.sms_2fa",
|
||||
return_value={"ChallengeName": "FAILED", "InvalidAuthenticationResult": {}},
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{CONF_CODE: MFA_CODE},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result3["errors"] == {"base": "unknown"}
|
Loading…
Reference in New Issue