mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Add abode config entries and device registry (#26699)
* Adds support for config entries and device registry * Fixing string formatting for logger * Add unit test for abode config flow * Fix for lights, only allow one config, add ability to unload entry * Fix for subscribing to hass_events on adding abode component * Several fixes from code review * Several fixes from second code review * Several fixes from third code review * Update documentation url to fix branch conflict * Fixes config flow and removes unused constants * Fix for switches, polling entry option, improved tests * Update .coveragerc, disable pylint W0611, remove polling from UI * Multiple fixes and edits to adhere to style guidelines * Removed unique_id * Minor correction for formatting error in rebase * Resolves issue causing CI to fail * Bump abodepy version * Add remove device callback and minor clean up * Fix incorrect method name * Docstring edits * Fix duplicate import issues from rebase * Add logout_listener attribute to AbodeSystem * Add additional test for complete coverage
This commit is contained in:
parent
d96cd4c4ea
commit
1774a1427b
10
.coveragerc
10
.coveragerc
@ -10,7 +10,15 @@ omit =
|
|||||||
homeassistant/util/async.py
|
homeassistant/util/async.py
|
||||||
|
|
||||||
# omit pieces of code that rely on external devices being present
|
# omit pieces of code that rely on external devices being present
|
||||||
homeassistant/components/abode/*
|
homeassistant/components/abode/__init__.py
|
||||||
|
homeassistant/components/abode/alarm_control_panel.py
|
||||||
|
homeassistant/components/abode/binary_sensor.py
|
||||||
|
homeassistant/components/abode/camera.py
|
||||||
|
homeassistant/components/abode/cover.py
|
||||||
|
homeassistant/components/abode/light.py
|
||||||
|
homeassistant/components/abode/lock.py
|
||||||
|
homeassistant/components/abode/sensor.py
|
||||||
|
homeassistant/components/abode/switch.py
|
||||||
homeassistant/components/acer_projector/switch.py
|
homeassistant/components/acer_projector/switch.py
|
||||||
homeassistant/components/actiontec/device_tracker.py
|
homeassistant/components/actiontec/device_tracker.py
|
||||||
homeassistant/components/adguard/__init__.py
|
homeassistant/components/adguard/__init__.py
|
||||||
|
@ -13,6 +13,7 @@ homeassistant/util/* @home-assistant/core
|
|||||||
homeassistant/scripts/check_config.py @kellerza
|
homeassistant/scripts/check_config.py @kellerza
|
||||||
|
|
||||||
# Integrations
|
# Integrations
|
||||||
|
homeassistant/components/abode/* @shred86
|
||||||
homeassistant/components/adguard/* @frenck
|
homeassistant/components/adguard/* @frenck
|
||||||
homeassistant/components/airly/* @bieniu
|
homeassistant/components/airly/* @bieniu
|
||||||
homeassistant/components/airvisual/* @bachya
|
homeassistant/components/airvisual/* @bachya
|
||||||
|
@ -1,49 +1,36 @@
|
|||||||
"""Support for Abode Home Security system."""
|
"""Support for the Abode Security System."""
|
||||||
import logging
|
from asyncio import gather
|
||||||
|
from copy import deepcopy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from requests.exceptions import HTTPError, ConnectTimeout
|
import logging
|
||||||
import abodepy
|
|
||||||
import abodepy.helpers.constants as CONST
|
from abodepy import Abode
|
||||||
from abodepy.exceptions import AbodeException
|
from abodepy.exceptions import AbodeException
|
||||||
import abodepy.helpers.timeline as TIMELINE
|
import abodepy.helpers.timeline as TIMELINE
|
||||||
|
from requests.exceptions import ConnectTimeout, HTTPError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_DATE,
|
ATTR_DATE,
|
||||||
ATTR_TIME,
|
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
CONF_USERNAME,
|
ATTR_TIME,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_EXCLUDE,
|
CONF_USERNAME,
|
||||||
CONF_NAME,
|
|
||||||
CONF_LIGHTS,
|
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
EVENT_HOMEASSISTANT_START,
|
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers import discovery
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from .const import ATTRIBUTION, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTRIBUTION = "Data provided by goabode.com"
|
|
||||||
|
|
||||||
CONF_POLLING = "polling"
|
CONF_POLLING = "polling"
|
||||||
|
|
||||||
DOMAIN = "abode"
|
|
||||||
DEFAULT_CACHEDB = "./abodepy_cache.pickle"
|
DEFAULT_CACHEDB = "./abodepy_cache.pickle"
|
||||||
|
|
||||||
NOTIFICATION_ID = "abode_notification"
|
|
||||||
NOTIFICATION_TITLE = "Abode Security Setup"
|
|
||||||
|
|
||||||
EVENT_ABODE_ALARM = "abode_alarm"
|
|
||||||
EVENT_ABODE_ALARM_END = "abode_alarm_end"
|
|
||||||
EVENT_ABODE_AUTOMATION = "abode_automation"
|
|
||||||
EVENT_ABODE_FAULT = "abode_panel_fault"
|
|
||||||
EVENT_ABODE_RESTORE = "abode_panel_restore"
|
|
||||||
|
|
||||||
SERVICE_SETTINGS = "change_setting"
|
SERVICE_SETTINGS = "change_setting"
|
||||||
SERVICE_CAPTURE_IMAGE = "capture_image"
|
SERVICE_CAPTURE_IMAGE = "capture_image"
|
||||||
SERVICE_TRIGGER = "trigger_quick_action"
|
SERVICE_TRIGGER = "trigger_quick_action"
|
||||||
@ -67,10 +54,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
vol.Required(CONF_PASSWORD): cv.string,
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
vol.Optional(CONF_NAME): cv.string,
|
|
||||||
vol.Optional(CONF_POLLING, default=False): cv.boolean,
|
vol.Optional(CONF_POLLING, default=False): cv.boolean,
|
||||||
vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA,
|
|
||||||
vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -100,73 +84,80 @@ ABODE_PLATFORMS = [
|
|||||||
class AbodeSystem:
|
class AbodeSystem:
|
||||||
"""Abode System class."""
|
"""Abode System class."""
|
||||||
|
|
||||||
def __init__(self, username, password, cache, name, polling, exclude, lights):
|
def __init__(self, abode, polling):
|
||||||
"""Initialize the system."""
|
"""Initialize the system."""
|
||||||
|
|
||||||
self.abode = abodepy.Abode(
|
self.abode = abode
|
||||||
username,
|
|
||||||
password,
|
|
||||||
auto_login=True,
|
|
||||||
get_devices=True,
|
|
||||||
get_automations=True,
|
|
||||||
cache_path=cache,
|
|
||||||
)
|
|
||||||
self.name = name
|
|
||||||
self.polling = polling
|
self.polling = polling
|
||||||
self.exclude = exclude
|
|
||||||
self.lights = lights
|
|
||||||
self.devices = []
|
self.devices = []
|
||||||
|
self.logout_listener = None
|
||||||
def is_excluded(self, device):
|
|
||||||
"""Check if a device is configured to be excluded."""
|
|
||||||
return device.device_id in self.exclude
|
|
||||||
|
|
||||||
def is_automation_excluded(self, automation):
|
|
||||||
"""Check if an automation is configured to be excluded."""
|
|
||||||
return automation.automation_id in self.exclude
|
|
||||||
|
|
||||||
def is_light(self, device):
|
|
||||||
"""Check if a switch device is configured as a light."""
|
|
||||||
|
|
||||||
return device.generic_type == CONST.TYPE_LIGHT or (
|
|
||||||
device.generic_type == CONST.TYPE_SWITCH and device.device_id in self.lights
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up Abode component."""
|
"""Set up Abode integration."""
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
conf = config[DOMAIN]
|
||||||
username = conf.get(CONF_USERNAME)
|
|
||||||
password = conf.get(CONF_PASSWORD)
|
hass.async_create_task(
|
||||||
name = conf.get(CONF_NAME)
|
hass.config_entries.flow.async_init(
|
||||||
polling = conf.get(CONF_POLLING)
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=deepcopy(conf)
|
||||||
exclude = conf.get(CONF_EXCLUDE)
|
)
|
||||||
lights = conf.get(CONF_LIGHTS)
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up Abode integration from a config entry."""
|
||||||
|
username = config_entry.data.get(CONF_USERNAME)
|
||||||
|
password = config_entry.data.get(CONF_PASSWORD)
|
||||||
|
polling = config_entry.data.get(CONF_POLLING)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cache = hass.config.path(DEFAULT_CACHEDB)
|
cache = hass.config.path(DEFAULT_CACHEDB)
|
||||||
hass.data[DOMAIN] = AbodeSystem(
|
abode = await hass.async_add_executor_job(
|
||||||
username, password, cache, name, polling, exclude, lights
|
Abode, username, password, True, True, True, cache
|
||||||
)
|
)
|
||||||
|
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
||||||
|
|
||||||
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
||||||
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
||||||
|
|
||||||
hass.components.persistent_notification.create(
|
|
||||||
"Error: {}<br />"
|
|
||||||
"You will need to restart hass after fixing."
|
|
||||||
"".format(ex),
|
|
||||||
title=NOTIFICATION_TITLE,
|
|
||||||
notification_id=NOTIFICATION_ID,
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
setup_hass_services(hass)
|
for platform in ABODE_PLATFORMS:
|
||||||
setup_hass_events(hass)
|
hass.async_create_task(
|
||||||
setup_abode_events(hass)
|
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||||
|
)
|
||||||
|
|
||||||
|
await setup_hass_events(hass)
|
||||||
|
await hass.async_add_executor_job(setup_hass_services, hass)
|
||||||
|
await hass.async_add_executor_job(setup_abode_events, hass)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
|
||||||
|
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
|
||||||
|
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER)
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
|
||||||
for platform in ABODE_PLATFORMS:
|
for platform in ABODE_PLATFORMS:
|
||||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
tasks.append(
|
||||||
|
hass.config_entries.async_forward_entry_unload(config_entry, platform)
|
||||||
|
)
|
||||||
|
|
||||||
|
await gather(*tasks)
|
||||||
|
|
||||||
|
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
|
||||||
|
await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout)
|
||||||
|
|
||||||
|
hass.data[DOMAIN].logout_listener()
|
||||||
|
hass.data.pop(DOMAIN)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -223,13 +214,9 @@ def setup_hass_services(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_hass_events(hass):
|
async def setup_hass_events(hass):
|
||||||
"""Home Assistant start and stop callbacks."""
|
"""Home Assistant start and stop callbacks."""
|
||||||
|
|
||||||
def startup(event):
|
|
||||||
"""Listen for push events."""
|
|
||||||
hass.data[DOMAIN].abode.events.start()
|
|
||||||
|
|
||||||
def logout(event):
|
def logout(event):
|
||||||
"""Logout of Abode."""
|
"""Logout of Abode."""
|
||||||
if not hass.data[DOMAIN].polling:
|
if not hass.data[DOMAIN].polling:
|
||||||
@ -239,9 +226,11 @@ def setup_hass_events(hass):
|
|||||||
_LOGGER.info("Logged out of Abode")
|
_LOGGER.info("Logged out of Abode")
|
||||||
|
|
||||||
if not hass.data[DOMAIN].polling:
|
if not hass.data[DOMAIN].polling:
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, startup)
|
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
|
||||||
|
|
||||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, logout)
|
hass.data[DOMAIN].logout_listener = hass.bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_STOP, logout
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_abode_events(hass):
|
def setup_abode_events(hass):
|
||||||
@ -282,30 +271,36 @@ class AbodeDevice(Entity):
|
|||||||
"""Representation of an Abode device."""
|
"""Representation of an Abode device."""
|
||||||
|
|
||||||
def __init__(self, data, device):
|
def __init__(self, data, device):
|
||||||
"""Initialize a sensor for Abode device."""
|
"""Initialize Abode device."""
|
||||||
self._data = data
|
self._data = data
|
||||||
self._device = device
|
self._device = device
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Subscribe Abode events."""
|
"""Subscribe to device events."""
|
||||||
self.hass.async_add_job(
|
self.hass.async_add_job(
|
||||||
self._data.abode.events.add_device_callback,
|
self._data.abode.events.add_device_callback,
|
||||||
self._device.device_id,
|
self._device.device_id,
|
||||||
self._update_callback,
|
self._update_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Unsubscribe from device events."""
|
||||||
|
self.hass.async_add_job(
|
||||||
|
self._data.abode.events.remove_all_device_callbacks, self._device.device_id
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return the polling state."""
|
"""Return the polling state."""
|
||||||
return self._data.polling
|
return self._data.polling
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update automation state."""
|
"""Update device and automation states."""
|
||||||
self._device.refresh()
|
self._device.refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the device."""
|
||||||
return self._device.name
|
return self._device.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -319,6 +314,21 @@ class AbodeDevice(Entity):
|
|||||||
"device_type": self._device.type,
|
"device_type": self._device.type,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID to use for this device."""
|
||||||
|
return self._device.device_uuid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return device registry information for this entity."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self._device.device_id)},
|
||||||
|
"manufacturer": "Abode",
|
||||||
|
"name": self._device.name,
|
||||||
|
"device_type": self._device.type,
|
||||||
|
}
|
||||||
|
|
||||||
def _update_callback(self, device):
|
def _update_callback(self, device):
|
||||||
"""Update the device state."""
|
"""Update the device state."""
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
@ -353,7 +363,7 @@ class AbodeAutomation(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the automation."""
|
||||||
return self._automation.name
|
return self._automation.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -367,6 +377,6 @@ class AbodeAutomation(Entity):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _update_callback(self, device):
|
def _update_callback(self, device):
|
||||||
"""Update the device state."""
|
"""Update the automation state."""
|
||||||
self._automation.refresh()
|
self._automation.refresh()
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
@ -9,32 +9,31 @@ from homeassistant.const import (
|
|||||||
STATE_ALARM_DISARMED,
|
STATE_ALARM_DISARMED,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import ATTRIBUTION, DOMAIN as ABODE_DOMAIN, AbodeDevice
|
from . import AbodeDevice
|
||||||
|
from .const import ATTRIBUTION, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ICON = "mdi:security"
|
ICON = "mdi:security"
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up an alarm control panel for an Abode device."""
|
"""Set up an alarm control panel for an Abode device."""
|
||||||
data = hass.data[ABODE_DOMAIN]
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)]
|
async_add_entities(
|
||||||
|
[AbodeAlarm(data, await hass.async_add_executor_job(data.abode.get_alarm))]
|
||||||
data.devices.extend(alarm_devices)
|
)
|
||||||
|
|
||||||
add_entities(alarm_devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
||||||
"""An alarm_control_panel implementation for Abode."""
|
"""An alarm_control_panel implementation for Abode."""
|
||||||
|
|
||||||
def __init__(self, data, device, name):
|
|
||||||
"""Initialize the alarm control panel."""
|
|
||||||
super().__init__(data, device)
|
|
||||||
self._name = name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Return the icon."""
|
"""Return the icon."""
|
||||||
@ -65,11 +64,6 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
|||||||
"""Send arm away command."""
|
"""Send arm away command."""
|
||||||
self._device.set_away()
|
self._device.set_away()
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the alarm."""
|
|
||||||
return self._name or super().name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
|
@ -6,15 +6,20 @@ import abodepy.helpers.timeline as TIMELINE
|
|||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice
|
from . import AbodeAutomation, AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up a sensor for an Abode device."""
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
data = hass.data[ABODE_DOMAIN]
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up a sensor for an Abode device."""
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
device_types = [
|
device_types = [
|
||||||
CONST.TYPE_CONNECTIVITY,
|
CONST.TYPE_CONNECTIVITY,
|
||||||
@ -25,25 +30,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
]
|
]
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for device in data.abode.get_devices(generic_type=device_types):
|
|
||||||
if data.is_excluded(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
for device in data.abode.get_devices(generic_type=device_types):
|
||||||
devices.append(AbodeBinarySensor(data, device))
|
devices.append(AbodeBinarySensor(data, device))
|
||||||
|
|
||||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION):
|
for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION):
|
||||||
if data.is_automation_excluded(automation):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(
|
devices.append(
|
||||||
AbodeQuickActionBinarySensor(
|
AbodeQuickActionBinarySensor(
|
||||||
data, automation, TIMELINE.AUTOMATION_EDIT_GROUP
|
data, automation, TIMELINE.AUTOMATION_EDIT_GROUP
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
||||||
|
@ -2,35 +2,36 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
import abodepy.helpers.timeline as TIMELINE
|
import abodepy.helpers.timeline as TIMELINE
|
||||||
|
import requests
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera
|
from homeassistant.components.camera import Camera
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
from . import AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up Abode camera devices."""
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
data = hass.data[ABODE_DOMAIN]
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up a camera for an Abode device."""
|
||||||
|
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA):
|
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA):
|
||||||
if data.is_excluded(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE))
|
devices.append(AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE))
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeCamera(AbodeDevice, Camera):
|
class AbodeCamera(AbodeDevice, Camera):
|
||||||
|
79
homeassistant/components/abode/config_flow.py
Normal file
79
homeassistant/components/abode/config_flow.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""Config flow for the Abode Security System component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from abodepy import Abode
|
||||||
|
from abodepy.exceptions import AbodeException
|
||||||
|
from requests.exceptions import ConnectTimeout, HTTPError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import callback
|
||||||
|
|
||||||
|
from .const import DOMAIN # pylint: disable=W0611
|
||||||
|
|
||||||
|
CONF_POLLING = "polling"
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Config flow for Abode."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize."""
|
||||||
|
self.data_schema = {
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
return self._show_form()
|
||||||
|
|
||||||
|
username = user_input[CONF_USERNAME]
|
||||||
|
password = user_input[CONF_PASSWORD]
|
||||||
|
polling = user_input.get(CONF_POLLING, False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(Abode, username, password, True)
|
||||||
|
|
||||||
|
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
||||||
|
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
||||||
|
if ex.errcode == 400:
|
||||||
|
return self._show_form({"base": "invalid_credentials"})
|
||||||
|
return self._show_form({"base": "connection_error"})
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input[CONF_USERNAME],
|
||||||
|
data={
|
||||||
|
CONF_USERNAME: username,
|
||||||
|
CONF_PASSWORD: password,
|
||||||
|
CONF_POLLING: polling,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _show_form(self, errors=None):
|
||||||
|
"""Show the form to the user."""
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(self.data_schema),
|
||||||
|
errors=errors if errors else {},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import a config entry from configuration.yaml."""
|
||||||
|
if self._async_current_entries():
|
||||||
|
_LOGGER.warning("Only one configuration of abode is allowed.")
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
return await self.async_step_user(import_config)
|
3
homeassistant/components/abode/const.py
Normal file
3
homeassistant/components/abode/const.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""Constants for the Abode Security System component."""
|
||||||
|
DOMAIN = "abode"
|
||||||
|
ATTRIBUTION = "Data provided by goabode.com"
|
@ -5,26 +5,27 @@ import abodepy.helpers.constants as CONST
|
|||||||
|
|
||||||
from homeassistant.components.cover import CoverDevice
|
from homeassistant.components.cover import CoverDevice
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
from . import AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode cover devices."""
|
"""Set up Abode cover devices."""
|
||||||
|
|
||||||
data = hass.data[ABODE_DOMAIN]
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER):
|
for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER):
|
||||||
if data.is_excluded(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(AbodeCover(data, device))
|
devices.append(AbodeCover(data, device))
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeCover(AbodeDevice, CoverDevice):
|
class AbodeCover(AbodeDevice, CoverDevice):
|
||||||
|
@ -18,30 +18,27 @@ from homeassistant.util.color import (
|
|||||||
color_temperature_mired_to_kelvin,
|
color_temperature_mired_to_kelvin,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
from . import AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode light devices."""
|
"""Set up Abode light devices."""
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
data = hass.data[ABODE_DOMAIN]
|
|
||||||
|
|
||||||
device_types = [CONST.TYPE_LIGHT, CONST.TYPE_SWITCH]
|
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
# Get all regular lights that are not excluded or switches marked as lights
|
for device in data.abode.get_devices(generic_type=CONST.TYPE_LIGHT):
|
||||||
for device in data.abode.get_devices(generic_type=device_types):
|
|
||||||
if data.is_excluded(device) or not data.is_light(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(AbodeLight(data, device))
|
devices.append(AbodeLight(data, device))
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeLight(AbodeDevice, Light):
|
class AbodeLight(AbodeDevice, Light):
|
||||||
|
@ -1,30 +1,31 @@
|
|||||||
"""Support for Abode Security System locks."""
|
"""Support for the Abode Security System locks."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockDevice
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
from . import AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode lock devices."""
|
"""Set up Abode lock devices."""
|
||||||
|
|
||||||
data = hass.data[ABODE_DOMAIN]
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK):
|
for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK):
|
||||||
if data.is_excluded(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(AbodeLock(data, device))
|
devices.append(AbodeLock(data, device))
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeLock(AbodeDevice, LockDevice):
|
class AbodeLock(AbodeDevice, LockDevice):
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"domain": "abode",
|
"domain": "abode",
|
||||||
"name": "Abode",
|
"name": "Abode",
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"abodepy==0.15.0"
|
"abodepy==0.16.5"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": []
|
"codeowners": [
|
||||||
|
"@shred86"
|
||||||
|
]
|
||||||
}
|
}
|
@ -9,7 +9,8 @@ from homeassistant.const import (
|
|||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeDevice
|
from . import AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -21,22 +22,22 @@ SENSOR_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up a sensor for an Abode device."""
|
"""Set up a sensor for an Abode device."""
|
||||||
|
|
||||||
data = hass.data[ABODE_DOMAIN]
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
|
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
|
||||||
if data.is_excluded(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for sensor_type in SENSOR_TYPES:
|
for sensor_type in SENSOR_TYPES:
|
||||||
devices.append(AbodeSensor(data, device, sensor_type))
|
devices.append(AbodeSensor(data, device, sensor_type))
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeSensor(AbodeDevice):
|
class AbodeSensor(AbodeDevice):
|
||||||
|
22
homeassistant/components/abode/strings.json
Normal file
22
homeassistant/components/abode/strings.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Abode",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Fill in your Abode login information",
|
||||||
|
"data": {
|
||||||
|
"username": "Email Address",
|
||||||
|
"password": "Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"identifier_exists": "Account already registered.",
|
||||||
|
"invalid_credentials": "Invalid credentials.",
|
||||||
|
"connection_error": "Unable to connect to Abode."
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Only a single configuration of Abode is allowed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,37 +6,32 @@ import abodepy.helpers.timeline as TIMELINE
|
|||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
|
||||||
from . import DOMAIN as ABODE_DOMAIN, AbodeAutomation, AbodeDevice
|
from . import AbodeAutomation, AbodeDevice
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
"""Set up Abode switch devices."""
|
"""Platform uses config entry setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
data = hass.data[ABODE_DOMAIN]
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up Abode switch devices."""
|
||||||
|
data = hass.data[DOMAIN]
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
# Get all regular switches that are not excluded or marked as lights
|
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SWITCH):
|
for device in data.abode.get_devices(generic_type=CONST.TYPE_SWITCH):
|
||||||
if data.is_excluded(device) or data.is_light(device):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(AbodeSwitch(data, device))
|
devices.append(AbodeSwitch(data, device))
|
||||||
|
|
||||||
# Get all Abode automations that can be enabled/disabled
|
|
||||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION):
|
for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION):
|
||||||
if data.is_automation_excluded(automation):
|
|
||||||
continue
|
|
||||||
|
|
||||||
devices.append(
|
devices.append(
|
||||||
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
|
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
|
||||||
)
|
)
|
||||||
|
|
||||||
data.devices.extend(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
add_entities(devices)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeSwitch(AbodeDevice, SwitchDevice):
|
class AbodeSwitch(AbodeDevice, SwitchDevice):
|
||||||
|
@ -6,6 +6,7 @@ To update, run python3 -m script.hassfest
|
|||||||
# fmt: off
|
# fmt: off
|
||||||
|
|
||||||
FLOWS = [
|
FLOWS = [
|
||||||
|
"abode",
|
||||||
"adguard",
|
"adguard",
|
||||||
"airly",
|
"airly",
|
||||||
"ambiclimate",
|
"ambiclimate",
|
||||||
|
@ -103,7 +103,7 @@ WazeRouteCalculator==0.10
|
|||||||
YesssSMS==0.4.1
|
YesssSMS==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
abodepy==0.15.0
|
abodepy==0.16.5
|
||||||
|
|
||||||
# homeassistant.components.mcp23017
|
# homeassistant.components.mcp23017
|
||||||
adafruit-blinka==1.2.1
|
adafruit-blinka==1.2.1
|
||||||
|
@ -45,6 +45,9 @@ RtmAPI==0.7.2
|
|||||||
# homeassistant.components.yessssms
|
# homeassistant.components.yessssms
|
||||||
YesssSMS==0.4.1
|
YesssSMS==0.4.1
|
||||||
|
|
||||||
|
# homeassistant.components.abode
|
||||||
|
abodepy==0.16.5
|
||||||
|
|
||||||
# homeassistant.components.androidtv
|
# homeassistant.components.androidtv
|
||||||
adb-shell==0.0.4
|
adb-shell==0.0.4
|
||||||
|
|
||||||
|
1
tests/components/abode/__init__.py
Normal file
1
tests/components/abode/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Abode component."""
|
120
tests/components/abode/test_config_flow.py
Normal file
120
tests/components/abode/test_config_flow.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
"""Tests for the Abode config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from abodepy.exceptions import AbodeAuthenticationException
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components.abode import config_flow
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
CONF_POLLING = "polling"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_show_form(hass):
|
||||||
|
"""Test that the form is served with no input."""
|
||||||
|
flow = config_flow.AbodeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_user(user_input=None)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_one_config_allowed(hass):
|
||||||
|
"""Test that only one Abode configuration is allowed."""
|
||||||
|
flow = config_flow.AbodeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
MockConfigEntry(
|
||||||
|
domain="abode",
|
||||||
|
data={CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"},
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
|
step_user_result = await flow.async_step_user()
|
||||||
|
|
||||||
|
assert step_user_result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert step_user_result["reason"] == "single_instance_allowed"
|
||||||
|
|
||||||
|
conf = {
|
||||||
|
CONF_USERNAME: "user@email.com",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_POLLING: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
import_config_result = await flow.async_step_import(conf)
|
||||||
|
|
||||||
|
assert import_config_result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert import_config_result["reason"] == "single_instance_allowed"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_invalid_credentials(hass):
|
||||||
|
"""Test that invalid credentials throws an error."""
|
||||||
|
conf = {CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"}
|
||||||
|
|
||||||
|
flow = config_flow.AbodeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.abode.config_flow.Abode",
|
||||||
|
side_effect=AbodeAuthenticationException((400, "auth error")),
|
||||||
|
):
|
||||||
|
result = await flow.async_step_user(user_input=conf)
|
||||||
|
assert result["errors"] == {"base": "invalid_credentials"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connection_error(hass):
|
||||||
|
"""Test other than invalid credentials throws an error."""
|
||||||
|
conf = {CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"}
|
||||||
|
|
||||||
|
flow = config_flow.AbodeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.abode.config_flow.Abode",
|
||||||
|
side_effect=AbodeAuthenticationException((500, "connection error")),
|
||||||
|
):
|
||||||
|
result = await flow.async_step_user(user_input=conf)
|
||||||
|
assert result["errors"] == {"base": "connection_error"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_import(hass):
|
||||||
|
"""Test that the import step works."""
|
||||||
|
conf = {
|
||||||
|
CONF_USERNAME: "user@email.com",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_POLLING: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
flow = config_flow.AbodeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
with patch("homeassistant.components.abode.config_flow.Abode"):
|
||||||
|
result = await flow.async_step_import(import_config=conf)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
result = await flow.async_step_user(user_input=result["data"])
|
||||||
|
assert result["title"] == "user@email.com"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_USERNAME: "user@email.com",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_POLLING: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_user(hass):
|
||||||
|
"""Test that the user step works."""
|
||||||
|
conf = {CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"}
|
||||||
|
|
||||||
|
flow = config_flow.AbodeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
with patch("homeassistant.components.abode.config_flow.Abode"):
|
||||||
|
result = await flow.async_step_user(user_input=conf)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "user@email.com"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_USERNAME: "user@email.com",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_POLLING: False,
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user