New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add config entry for SimpliSafe #17148
Changes from 22 commits
cc0a131
ef625e7
6925885
ae99209
2434078
eee766f
51ee40a
4f2f812
cb8b3c3
0b1fd6e
f3273a5
6e1fe3e
acb18ef
5e54d3b
cd96163
41a7216
c055cf3
d02c9ca
a47fda8
ccbcdc6
ceb55db
5b65cf3
1a5a0a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"config": { | ||
"error": { | ||
"identifier_exists": "Account already registered", | ||
"invalid_credentials": "Invalid credentials" | ||
}, | ||
"step": { | ||
"user": { | ||
"data": { | ||
"code": "Code (for Home Assistant)", | ||
"password": "Password", | ||
"username": "Email Address" | ||
}, | ||
"title": "Fill in your information" | ||
} | ||
}, | ||
"title": "SimpliSafe" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
""" | ||
Support for SimpliSafe alarm systems. | ||
|
||
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/simplisafe/ | ||
""" | ||
import logging | ||
from datetime import timedelta | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import SOURCE_IMPORT | ||
from homeassistant.const import ( | ||
CONF_CODE, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_USERNAME) | ||
from homeassistant.core import callback | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
from homeassistant.helpers import aiohttp_client | ||
from homeassistant.helpers.dispatcher import async_dispatcher_send | ||
from homeassistant.helpers.event import async_track_time_interval | ||
|
||
from homeassistant.helpers import config_validation as cv | ||
|
||
from .config_flow import configured_instances | ||
from .const import DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN, TOPIC_UPDATE | ||
|
||
REQUIREMENTS = ['simplisafe-python==3.1.7'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
CONF_ACCOUNTS = 'accounts' | ||
|
||
DATA_LISTENER = 'listener' | ||
|
||
ACCOUNT_CONFIG_SCHEMA = vol.Schema({ | ||
vol.Required(CONF_USERNAME): cv.string, | ||
vol.Required(CONF_PASSWORD): cv.string, | ||
vol.Optional(CONF_CODE): cv.string, | ||
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): | ||
cv.time_period | ||
}) | ||
|
||
CONFIG_SCHEMA = vol.Schema({ | ||
DOMAIN: vol.Schema({ | ||
vol.Optional(CONF_ACCOUNTS): | ||
vol.All(cv.ensure_list, [ACCOUNT_CONFIG_SCHEMA]), | ||
}), | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
|
||
@callback | ||
def _async_save_refresh_token(hass, config_entry, token): | ||
hass.config_entries.async_update_entry( | ||
config_entry, | ||
data={ | ||
CONF_USERNAME: config_entry.data[CONF_USERNAME], | ||
CONF_TOKEN: token, | ||
CONF_SCAN_INTERVAL: config_entry.data[CONF_SCAN_INTERVAL], | ||
}) | ||
|
||
|
||
async def async_setup(hass, config): | ||
"""Set up the SimpliSafe component.""" | ||
hass.data[DOMAIN] = {} | ||
hass.data[DOMAIN][DATA_CLIENT] = {} | ||
hass.data[DOMAIN][DATA_LISTENER] = {} | ||
|
||
if DOMAIN not in config: | ||
return True | ||
|
||
conf = config[DOMAIN] | ||
|
||
for account in conf[CONF_ACCOUNTS]: | ||
if account[CONF_USERNAME] in configured_instances(hass): | ||
continue | ||
|
||
hass.async_create_task( | ||
hass.config_entries.flow.async_init( | ||
DOMAIN, | ||
context={'source': SOURCE_IMPORT}, | ||
data={ | ||
CONF_USERNAME: account[CONF_USERNAME], | ||
CONF_PASSWORD: account[CONF_PASSWORD], | ||
CONF_CODE: account.get(CONF_CODE), | ||
CONF_SCAN_INTERVAL: account[CONF_SCAN_INTERVAL], | ||
})) | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass, config_entry): | ||
"""Set up SimpliSafe as config entry.""" | ||
from simplipy import API | ||
from simplipy.errors import SimplipyError | ||
|
||
websession = aiohttp_client.async_get_clientsession(hass) | ||
|
||
try: | ||
simplisafe = await API.login_via_token( | ||
config_entry.data[CONF_TOKEN], websession) | ||
except SimplipyError as err: | ||
bachya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if 403 in str(err): | ||
_LOGGER.error('Invalid credentials provided') | ||
return False | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank line contains whitespace |
||
_LOGGER.error('Config entry failed: %s', err) | ||
raise ConfigEntryNotReady | ||
|
||
_async_save_refresh_token(hass, config_entry, simplisafe.refresh_token) | ||
|
||
systems = await simplisafe.get_systems() | ||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = systems | ||
|
||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup( | ||
config_entry, 'alarm_control_panel')) | ||
|
||
async def refresh(event_time): | ||
"""Refresh data from the SimpliSafe account.""" | ||
for system in systems: | ||
_LOGGER.debug('Updating system data: %s', system.system_id) | ||
await system.update() | ||
async_dispatcher_send(hass, TOPIC_UPDATE.format(system.system_id)) | ||
|
||
if system.api.refresh_token_dirty: | ||
bachya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_async_save_refresh_token( | ||
hass, config_entry, system.api.refresh_token) | ||
|
||
hass.data[DOMAIN][DATA_LISTENER][ | ||
config_entry.entry_id] = async_track_time_interval( | ||
hass, | ||
refresh, | ||
timedelta(seconds=config_entry.data[CONF_SCAN_INTERVAL])) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass, entry): | ||
"""Unload a SimpliSafe config entry.""" | ||
await hass.config_entries.async_forward_entry_unload( | ||
entry, 'alarm_control_panel') | ||
|
||
hass.data[DOMAIN][DATA_CLIENT].pop(entry.entry_id) | ||
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(entry.entry_id) | ||
remove_listener() | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forward entry unload to all platforms here. That will unload the alarm_control_panel platform. See deconz component for example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added that code in my latest commit; the alarm control panel still doesn't get removed. FYI: I've noticed that recently (in the last version or so), my OpenUV config entry has the same issue (and didn't previously). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it the entity that is still left? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct: the config entry (thus, the component) goes away, but the alarm control panel entity remains. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any errors in the logs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Negative, unfortunately. A reboot of HASS clears away the alarm control panel, but even then, the logs don’t show anything. I’ll trace the unload calls and see if I can figure out where the break occurs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No break that I can see. The only strangeness (to me) are these logs (with some of my debugging statements in place):
From what I'm reading:
I'm not the expert on this subsystem, but it seems like there might be two areas of strangeness:
Apologies I'm not more famliar. Happy to help debugging if you can direct me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check which component is used for generating the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, tracking this issue separately: #17370. |
||
return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also works and is less likely to break in the future:
{**config_entry.data, CONF_TOKEN: token}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/