mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Add config entry to iOS (#16580)
* Add config entry to iOS * Add translation
This commit is contained in:
parent
2682d26f79
commit
3824582e68
14
homeassistant/components/ios/.translations/en.json
Normal file
14
homeassistant/components/ios/.translations/en.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Only a single configuration of Home Assistant iOS is necessary."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Do you want to set up the Home Assistant iOS component?",
|
||||
"title": "Home Assistant iOS"
|
||||
}
|
||||
},
|
||||
"title": "Home Assistant iOS"
|
||||
}
|
||||
}
|
@ -11,13 +11,14 @@ import datetime
|
||||
import voluptuous as vol
|
||||
# from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR,
|
||||
HTTP_BAD_REQUEST)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv, discovery, config_entry_flow)
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
|
||||
@ -164,62 +165,70 @@ IDENTIFY_SCHEMA = vol.Schema({
|
||||
|
||||
CONFIGURATION_FILE = '.ios.conf'
|
||||
|
||||
CONFIG_FILE = {ATTR_DEVICES: {}}
|
||||
|
||||
CONFIG_FILE_PATH = ""
|
||||
|
||||
|
||||
def devices_with_push():
|
||||
def devices_with_push(hass):
|
||||
"""Return a dictionary of push enabled targets."""
|
||||
targets = {}
|
||||
for device_name, device in CONFIG_FILE[ATTR_DEVICES].items():
|
||||
for device_name, device in hass.data[DOMAIN][ATTR_DEVICES].items():
|
||||
if device.get(ATTR_PUSH_ID) is not None:
|
||||
targets[device_name] = device.get(ATTR_PUSH_ID)
|
||||
return targets
|
||||
|
||||
|
||||
def enabled_push_ids():
|
||||
def enabled_push_ids(hass):
|
||||
"""Return a list of push enabled target push IDs."""
|
||||
push_ids = list()
|
||||
for device in CONFIG_FILE[ATTR_DEVICES].values():
|
||||
for device in hass.data[DOMAIN][ATTR_DEVICES].values():
|
||||
if device.get(ATTR_PUSH_ID) is not None:
|
||||
push_ids.append(device.get(ATTR_PUSH_ID))
|
||||
return push_ids
|
||||
|
||||
|
||||
def devices():
|
||||
def devices(hass):
|
||||
"""Return a dictionary of all identified devices."""
|
||||
return CONFIG_FILE[ATTR_DEVICES]
|
||||
return hass.data[DOMAIN][ATTR_DEVICES]
|
||||
|
||||
|
||||
def device_name_for_push_id(push_id):
|
||||
def device_name_for_push_id(hass, push_id):
|
||||
"""Return the device name for the push ID."""
|
||||
for device_name, device in CONFIG_FILE[ATTR_DEVICES].items():
|
||||
for device_name, device in hass.data[DOMAIN][ATTR_DEVICES].items():
|
||||
if device.get(ATTR_PUSH_ID) is push_id:
|
||||
return device_name
|
||||
return None
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the iOS component."""
|
||||
global CONFIG_FILE
|
||||
global CONFIG_FILE_PATH
|
||||
conf = config.get(DOMAIN)
|
||||
|
||||
CONFIG_FILE_PATH = hass.config.path(CONFIGURATION_FILE)
|
||||
ios_config = await hass.async_add_executor_job(
|
||||
load_json, hass.config.path(CONFIGURATION_FILE))
|
||||
|
||||
CONFIG_FILE = load_json(CONFIG_FILE_PATH)
|
||||
if ios_config == {}:
|
||||
ios_config[ATTR_DEVICES] = {}
|
||||
|
||||
if CONFIG_FILE == {}:
|
||||
CONFIG_FILE[ATTR_DEVICES] = {}
|
||||
ios_config[CONF_PUSH] = (conf or {}).get(CONF_PUSH, {})
|
||||
|
||||
hass.data[DOMAIN] = ios_config
|
||||
|
||||
# No entry support for notify component yet
|
||||
discovery.load_platform(hass, 'notify', DOMAIN, {}, config)
|
||||
|
||||
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
|
||||
if conf is not None:
|
||||
hass.async_create_task(hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': config_entries.SOURCE_IMPORT}))
|
||||
|
||||
hass.http.register_view(iOSIdentifyDeviceView)
|
||||
return True
|
||||
|
||||
app_config = config.get(DOMAIN, {})
|
||||
hass.http.register_view(iOSPushConfigView(app_config.get(CONF_PUSH, {})))
|
||||
|
||||
async def async_setup_entry(hass, entry):
|
||||
"""Set up an iOS entry."""
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, 'sensor'))
|
||||
|
||||
hass.http.register_view(
|
||||
iOSIdentifyDeviceView(hass.config.path(CONFIGURATION_FILE)))
|
||||
hass.http.register_view(iOSPushConfigView(hass.data[DOMAIN][CONF_PUSH]))
|
||||
|
||||
return True
|
||||
|
||||
@ -247,6 +256,10 @@ class iOSIdentifyDeviceView(HomeAssistantView):
|
||||
url = '/api/ios/identify'
|
||||
name = 'api:ios:identify'
|
||||
|
||||
def __init__(self, config_path):
|
||||
"""Initiliaze the view."""
|
||||
self._config_path = config_path
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
"""Handle the POST request for device identification."""
|
||||
@ -255,6 +268,8 @@ class iOSIdentifyDeviceView(HomeAssistantView):
|
||||
except ValueError:
|
||||
return self.json_message("Invalid JSON", HTTP_BAD_REQUEST)
|
||||
|
||||
hass = request.app['hass']
|
||||
|
||||
# Commented for now while iOS app is getting frequent updates
|
||||
# try:
|
||||
# data = IDENTIFY_SCHEMA(req_data)
|
||||
@ -266,12 +281,16 @@ class iOSIdentifyDeviceView(HomeAssistantView):
|
||||
|
||||
name = data.get(ATTR_DEVICE_ID)
|
||||
|
||||
CONFIG_FILE[ATTR_DEVICES][name] = data
|
||||
hass.data[DOMAIN][ATTR_DEVICES][name] = data
|
||||
|
||||
try:
|
||||
save_json(CONFIG_FILE_PATH, CONFIG_FILE)
|
||||
save_json(self._config_path, hass.data[DOMAIN])
|
||||
except HomeAssistantError:
|
||||
return self.json_message("Error saving device.",
|
||||
HTTP_INTERNAL_SERVER_ERROR)
|
||||
|
||||
return self.json({"status": "registered"})
|
||||
|
||||
|
||||
config_entry_flow.register_discovery_flow(
|
||||
DOMAIN, 'Home Assistant iOS', lambda *_: True)
|
14
homeassistant/components/ios/strings.json
Normal file
14
homeassistant/components/ios/strings.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Home Assistant iOS",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "Home Assistant iOS",
|
||||
"description": "Do you want to set up the Home Assistant iOS component?"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "Only a single configuration of Home Assistant iOS is necessary."
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ DEPENDENCIES = ["ios"]
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def log_rate_limits(target, resp, level=20):
|
||||
def log_rate_limits(hass, target, resp, level=20):
|
||||
"""Output rate limit log line at given level."""
|
||||
rate_limits = resp["rateLimits"]
|
||||
resetsAt = dt_util.parse_datetime(rate_limits["resetsAt"])
|
||||
@ -33,7 +33,7 @@ def log_rate_limits(target, resp, level=20):
|
||||
"%d sent, %d allowed, %d errors, "
|
||||
"resets in %s")
|
||||
_LOGGER.log(level, rate_limit_msg,
|
||||
ios.device_name_for_push_id(target),
|
||||
ios.device_name_for_push_id(hass, target),
|
||||
rate_limits["successful"],
|
||||
rate_limits["maximum"], rate_limits["errors"],
|
||||
str(resetsAtTime).split(".")[0])
|
||||
@ -45,7 +45,7 @@ def get_service(hass, config, discovery_info=None):
|
||||
# Need this to enable requirements checking in the app.
|
||||
hass.config.components.add("notify.ios")
|
||||
|
||||
if not ios.devices_with_push():
|
||||
if not ios.devices_with_push(hass):
|
||||
_LOGGER.error("The notify.ios platform was loaded but no "
|
||||
"devices exist! Please check the documentation at "
|
||||
"https://home-assistant.io/ecosystem/ios/notifications"
|
||||
@ -64,7 +64,7 @@ class iOSNotificationService(BaseNotificationService):
|
||||
@property
|
||||
def targets(self):
|
||||
"""Return a dictionary of registered targets."""
|
||||
return ios.devices_with_push()
|
||||
return ios.devices_with_push(self.hass)
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
"""Send a message to the Lambda APNS gateway."""
|
||||
@ -78,13 +78,13 @@ class iOSNotificationService(BaseNotificationService):
|
||||
targets = kwargs.get(ATTR_TARGET)
|
||||
|
||||
if not targets:
|
||||
targets = ios.enabled_push_ids()
|
||||
targets = ios.enabled_push_ids(self.hass)
|
||||
|
||||
if kwargs.get(ATTR_DATA) is not None:
|
||||
data[ATTR_DATA] = kwargs.get(ATTR_DATA)
|
||||
|
||||
for target in targets:
|
||||
if target not in ios.enabled_push_ids():
|
||||
if target not in ios.enabled_push_ids(self.hass):
|
||||
_LOGGER.error("The target (%s) does not exist in .ios.conf.",
|
||||
targets)
|
||||
return
|
||||
@ -102,8 +102,8 @@ class iOSNotificationService(BaseNotificationService):
|
||||
message = req.json().get("message", fallback_message)
|
||||
if req.status_code == 429:
|
||||
_LOGGER.warning(message)
|
||||
log_rate_limits(target, req.json(), 30)
|
||||
log_rate_limits(self.hass, target, req.json(), 30)
|
||||
else:
|
||||
_LOGGER.error(message)
|
||||
else:
|
||||
log_rate_limits(target, req.json())
|
||||
log_rate_limits(self.hass, target, req.json())
|
||||
|
@ -21,14 +21,17 @@ DEFAULT_ICON_STATE = 'mdi:power-plug'
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the iOS sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
# Leave here for if someone accidentally adds platform: ios to config
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up iOS from a config entry."""
|
||||
dev = list()
|
||||
for device_name, device in ios.devices().items():
|
||||
for device_name, device in ios.devices(hass).items():
|
||||
for sensor_type in ('level', 'state'):
|
||||
dev.append(IOSSensor(sensor_type, device_name, device))
|
||||
|
||||
add_entities(dev, True)
|
||||
async_add_entities(dev, True)
|
||||
|
||||
|
||||
class IOSSensor(Entity):
|
||||
@ -102,5 +105,5 @@ class IOSSensor(Entity):
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
self._device = ios.devices().get(self._device_name)
|
||||
self._device = ios.devices(self.hass).get(self._device_name)
|
||||
self._state = self._device[ios.ATTR_BATTERY][self.type]
|
||||
|
@ -140,6 +140,7 @@ FLOWS = [
|
||||
'deconz',
|
||||
'homematicip_cloud',
|
||||
'hue',
|
||||
'ios',
|
||||
'nest',
|
||||
'openuv',
|
||||
'sonos',
|
||||
|
1
tests/components/ios/__init__.py
Normal file
1
tests/components/ios/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the iOS component."""
|
61
tests/components/ios/test_init.py
Normal file
61
tests/components/ios/test_init.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Tests for the iOS init file."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.components import ios
|
||||
|
||||
from tests.common import mock_component, mock_coro
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_load_json():
|
||||
"""Mock load_json."""
|
||||
with patch('homeassistant.components.ios.load_json', return_value={}):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_dependencies(hass):
|
||||
"""Mock dependencies loaded."""
|
||||
mock_component(hass, 'zeroconf')
|
||||
mock_component(hass, 'device_tracker')
|
||||
|
||||
|
||||
async def test_creating_entry_sets_up_sensor(hass):
|
||||
"""Test setting up iOS loads the sensor component."""
|
||||
with patch('homeassistant.components.sensor.ios.async_setup_entry',
|
||||
return_value=mock_coro(True)) as mock_setup:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
ios.DOMAIN, context={'source': config_entries.SOURCE_USER})
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_configuring_ios_creates_entry(hass):
|
||||
"""Test that specifying config will create an entry."""
|
||||
with patch('homeassistant.components.ios.async_setup_entry',
|
||||
return_value=mock_coro(True)) as mock_setup:
|
||||
await async_setup_component(hass, ios.DOMAIN, {
|
||||
'ios': {
|
||||
'push': {}
|
||||
}
|
||||
})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_not_configuring_ios_not_creates_entry(hass):
|
||||
"""Test that no config will not create an entry."""
|
||||
with patch('homeassistant.components.ios.async_setup_entry',
|
||||
return_value=mock_coro(True)) as mock_setup:
|
||||
await async_setup_component(hass, ios.DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_setup.mock_calls) == 0
|
Loading…
x
Reference in New Issue
Block a user