allow overriding host api url in config flow (#33481)
* allow overriding host api url in config flow * fix typo * capitalize URL
This commit is contained in:
parent
b892dbc6ea
commit
955c94e313
7 changed files with 91 additions and 20 deletions
|
@ -33,6 +33,9 @@
|
||||||
"abort": {
|
"abort": {
|
||||||
"not_konn_panel": "Not a recognized Konnected.io device"
|
"not_konn_panel": "Not a recognized Konnected.io device"
|
||||||
},
|
},
|
||||||
|
"error": {
|
||||||
|
"bad_host": "Invalid Override API host url"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"options_binary": {
|
"options_binary": {
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -82,7 +85,9 @@
|
||||||
},
|
},
|
||||||
"options_misc": {
|
"options_misc": {
|
||||||
"data": {
|
"data": {
|
||||||
"blink": "Blink panel LED on when sending state change"
|
"api_host": "Override API host URL (optional)",
|
||||||
|
"blink": "Blink panel LED on when sending state change",
|
||||||
|
"override_api_host": "Override default Home Assistant API host panel URL"
|
||||||
},
|
},
|
||||||
"description": "Please select the desired behavior for your panel",
|
"description": "Please select the desired behavior for your panel",
|
||||||
"title": "Configure Misc"
|
"title": "Configure Misc"
|
||||||
|
|
|
@ -91,7 +91,7 @@ def ensure_zone(value):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
def import_validator(config):
|
def import_device_validator(config):
|
||||||
"""Validate zones and reformat for import."""
|
"""Validate zones and reformat for import."""
|
||||||
config = copy.deepcopy(config)
|
config = copy.deepcopy(config)
|
||||||
io_cfgs = {}
|
io_cfgs = {}
|
||||||
|
@ -117,10 +117,22 @@ def import_validator(config):
|
||||||
config.pop(CONF_SWITCHES, None)
|
config.pop(CONF_SWITCHES, None)
|
||||||
config.pop(CONF_BLINK, None)
|
config.pop(CONF_BLINK, None)
|
||||||
config.pop(CONF_DISCOVERY, None)
|
config.pop(CONF_DISCOVERY, None)
|
||||||
|
config.pop(CONF_API_HOST, None)
|
||||||
config.pop(CONF_IO, None)
|
config.pop(CONF_IO, None)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def import_validator(config):
|
||||||
|
"""Reformat for import."""
|
||||||
|
config = copy.deepcopy(config)
|
||||||
|
|
||||||
|
# push api_host into device configs
|
||||||
|
for device in config.get(CONF_DEVICES, []):
|
||||||
|
device[CONF_API_HOST] = config.get(CONF_API_HOST, "")
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
# configuration.yaml schemas (legacy)
|
# configuration.yaml schemas (legacy)
|
||||||
BINARY_SENSOR_SCHEMA_YAML = vol.All(
|
BINARY_SENSOR_SCHEMA_YAML = vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
|
@ -179,23 +191,27 @@ DEVICE_SCHEMA_YAML = vol.All(
|
||||||
vol.Inclusive(CONF_HOST, "host_info"): cv.string,
|
vol.Inclusive(CONF_HOST, "host_info"): cv.string,
|
||||||
vol.Inclusive(CONF_PORT, "host_info"): cv.port,
|
vol.Inclusive(CONF_PORT, "host_info"): cv.port,
|
||||||
vol.Optional(CONF_BLINK, default=True): cv.boolean,
|
vol.Optional(CONF_BLINK, default=True): cv.boolean,
|
||||||
|
vol.Optional(CONF_API_HOST, default=""): vol.Any("", cv.url),
|
||||||
vol.Optional(CONF_DISCOVERY, default=True): cv.boolean,
|
vol.Optional(CONF_DISCOVERY, default=True): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
import_validator,
|
import_device_validator,
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.All(
|
||||||
{
|
import_validator,
|
||||||
vol.Required(CONF_ACCESS_TOKEN): cv.string,
|
vol.Schema(
|
||||||
vol.Optional(CONF_API_HOST): vol.Url(),
|
{
|
||||||
vol.Optional(CONF_DEVICES): vol.All(
|
vol.Required(CONF_ACCESS_TOKEN): cv.string,
|
||||||
cv.ensure_list, [DEVICE_SCHEMA_YAML]
|
vol.Optional(CONF_API_HOST): vol.Url(),
|
||||||
),
|
vol.Optional(CONF_DEVICES): vol.All(
|
||||||
}
|
cv.ensure_list, [DEVICE_SCHEMA_YAML]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
|
|
@ -31,6 +31,7 @@ from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_ACTIVATION,
|
CONF_ACTIVATION,
|
||||||
|
CONF_API_HOST,
|
||||||
CONF_BLINK,
|
CONF_BLINK,
|
||||||
CONF_DEFAULT_OPTIONS,
|
CONF_DEFAULT_OPTIONS,
|
||||||
CONF_DISCOVERY,
|
CONF_DISCOVERY,
|
||||||
|
@ -61,6 +62,8 @@ CONF_MORE_STATES = "more_states"
|
||||||
CONF_YES = "Yes"
|
CONF_YES = "Yes"
|
||||||
CONF_NO = "No"
|
CONF_NO = "No"
|
||||||
|
|
||||||
|
CONF_OVERRIDE_API_HOST = "override_api_host"
|
||||||
|
|
||||||
KONN_MANUFACTURER = "konnected.io"
|
KONN_MANUFACTURER = "konnected.io"
|
||||||
KONN_PANEL_MODEL_NAMES = {
|
KONN_PANEL_MODEL_NAMES = {
|
||||||
KONN_MODEL: "Konnected Alarm Panel",
|
KONN_MODEL: "Konnected Alarm Panel",
|
||||||
|
@ -138,6 +141,7 @@ OPTIONS_SCHEMA = vol.Schema(
|
||||||
vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSOR_SCHEMA]),
|
vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSOR_SCHEMA]),
|
||||||
vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCH_SCHEMA]),
|
vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCH_SCHEMA]),
|
||||||
vol.Optional(CONF_BLINK, default=True): cv.boolean,
|
vol.Optional(CONF_BLINK, default=True): cv.boolean,
|
||||||
|
vol.Optional(CONF_API_HOST, default=""): vol.Any("", cv.url),
|
||||||
vol.Optional(CONF_DISCOVERY, default=True): cv.boolean,
|
vol.Optional(CONF_DISCOVERY, default=True): cv.boolean,
|
||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
@ -785,8 +789,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
"""Allow the user to configure the LED behavior."""
|
"""Allow the user to configure the LED behavior."""
|
||||||
errors = {}
|
errors = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
self.new_opt[CONF_BLINK] = user_input[CONF_BLINK]
|
# config schema only does basic schema val so check url here
|
||||||
return self.async_create_entry(title="", data=self.new_opt)
|
try:
|
||||||
|
if user_input[CONF_OVERRIDE_API_HOST]:
|
||||||
|
cv.url(user_input.get(CONF_API_HOST, ""))
|
||||||
|
else:
|
||||||
|
user_input[CONF_API_HOST] = ""
|
||||||
|
except vol.Invalid:
|
||||||
|
errors["base"] = "bad_host"
|
||||||
|
else:
|
||||||
|
# no need to store the override - can infer
|
||||||
|
del user_input[CONF_OVERRIDE_API_HOST]
|
||||||
|
self.new_opt.update(user_input)
|
||||||
|
return self.async_create_entry(title="", data=self.new_opt)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="options_misc",
|
step_id="options_misc",
|
||||||
|
@ -795,6 +810,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_BLINK, default=self.current_opt.get(CONF_BLINK, True)
|
CONF_BLINK, default=self.current_opt.get(CONF_BLINK, True)
|
||||||
): bool,
|
): bool,
|
||||||
|
vol.Required(
|
||||||
|
CONF_OVERRIDE_API_HOST,
|
||||||
|
default=bool(self.current_opt.get(CONF_API_HOST)),
|
||||||
|
): bool,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_API_HOST, default=self.current_opt.get(CONF_API_HOST, "")
|
||||||
|
): str,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
errors=errors,
|
errors=errors,
|
||||||
|
|
|
@ -294,7 +294,9 @@ class AlarmPanel:
|
||||||
@callback
|
@callback
|
||||||
def async_desired_settings_payload(self):
|
def async_desired_settings_payload(self):
|
||||||
"""Return a dict representing the desired device configuration."""
|
"""Return a dict representing the desired device configuration."""
|
||||||
desired_api_host = (
|
# keeping self.hass.data check for backwards compatibility
|
||||||
|
# newly configured integrations store this in the config entry
|
||||||
|
desired_api_host = self.options.get(CONF_API_HOST) or (
|
||||||
self.hass.data[DOMAIN].get(CONF_API_HOST) or self.hass.config.api.base_url
|
self.hass.data[DOMAIN].get(CONF_API_HOST) or self.hass.config.api.base_url
|
||||||
)
|
)
|
||||||
desired_api_endpoint = desired_api_host + ENDPOINT_ROOT
|
desired_api_endpoint = desired_api_host + ENDPOINT_ROOT
|
||||||
|
|
|
@ -94,11 +94,15 @@
|
||||||
"title": "Configure Misc",
|
"title": "Configure Misc",
|
||||||
"description": "Please select the desired behavior for your panel",
|
"description": "Please select the desired behavior for your panel",
|
||||||
"data": {
|
"data": {
|
||||||
"blink": "Blink panel LED on when sending state change"
|
"blink": "Blink panel LED on when sending state change",
|
||||||
|
"override_api_host": "Override default Home Assistant API host panel URL",
|
||||||
|
"api_host": "Override API host URL (optional)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {},
|
"error": {
|
||||||
|
"bad_host": "Invalid Override API host url"
|
||||||
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"not_konn_panel": "Not a recognized Konnected.io device"
|
"not_konn_panel": "Not a recognized Konnected.io device"
|
||||||
}
|
}
|
||||||
|
|
|
@ -450,6 +450,7 @@ async def test_import_existing_config(hass, mock_panel):
|
||||||
"alarm1": "Switchable Output",
|
"alarm1": "Switchable Output",
|
||||||
},
|
},
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "",
|
||||||
"discovery": True,
|
"discovery": True,
|
||||||
"binary_sensors": [
|
"binary_sensors": [
|
||||||
{"zone": "2", "type": "door", "inverse": False},
|
{"zone": "2", "type": "door", "inverse": False},
|
||||||
|
@ -628,6 +629,7 @@ async def test_import_pin_config(hass, mock_panel):
|
||||||
"out": "Switchable Output",
|
"out": "Switchable Output",
|
||||||
},
|
},
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "",
|
||||||
"discovery": True,
|
"discovery": True,
|
||||||
"binary_sensors": [
|
"binary_sensors": [
|
||||||
{"zone": "1", "type": "door", "inverse": False},
|
{"zone": "1", "type": "door", "inverse": False},
|
||||||
|
@ -778,9 +780,21 @@ async def test_option_flow(hass, mock_panel):
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "options_misc"
|
assert result["step_id"] == "options_misc"
|
||||||
|
# make sure we enforce url format
|
||||||
result = await hass.config_entries.options.async_configure(
|
result = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"], user_input={"blink": True},
|
result["flow_id"],
|
||||||
|
user_input={"blink": True, "override_api_host": True, "api_host": "badhosturl"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "options_misc"
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
"blink": True,
|
||||||
|
"override_api_host": True,
|
||||||
|
"api_host": "http://overridehost:1111",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
|
@ -792,6 +806,7 @@ async def test_option_flow(hass, mock_panel):
|
||||||
"out": "Switchable Output",
|
"out": "Switchable Output",
|
||||||
},
|
},
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "http://overridehost:1111",
|
||||||
"binary_sensors": [
|
"binary_sensors": [
|
||||||
{"zone": "2", "type": "door", "inverse": False},
|
{"zone": "2", "type": "door", "inverse": False},
|
||||||
{"zone": "6", "type": "window", "name": "winder", "inverse": True},
|
{"zone": "6", "type": "window", "name": "winder", "inverse": True},
|
||||||
|
@ -958,7 +973,7 @@ async def test_option_flow_pro(hass, mock_panel):
|
||||||
assert result["step_id"] == "options_misc"
|
assert result["step_id"] == "options_misc"
|
||||||
|
|
||||||
result = await hass.config_entries.options.async_configure(
|
result = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"], user_input={"blink": True},
|
result["flow_id"], user_input={"blink": True, "override_api_host": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "create_entry"
|
assert result["type"] == "create_entry"
|
||||||
|
@ -976,6 +991,7 @@ async def test_option_flow_pro(hass, mock_panel):
|
||||||
"out1": "Switchable Output",
|
"out1": "Switchable Output",
|
||||||
},
|
},
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "",
|
||||||
"binary_sensors": [
|
"binary_sensors": [
|
||||||
{"zone": "2", "type": "door", "inverse": False},
|
{"zone": "2", "type": "door", "inverse": False},
|
||||||
{"zone": "6", "type": "window", "name": "winder", "inverse": True},
|
{"zone": "6", "type": "window", "name": "winder", "inverse": True},
|
||||||
|
@ -1121,7 +1137,7 @@ async def test_option_flow_import(hass, mock_panel):
|
||||||
schema = result["data_schema"]({})
|
schema = result["data_schema"]({})
|
||||||
assert schema["blink"] is True
|
assert schema["blink"] is True
|
||||||
result = await hass.config_entries.options.async_configure(
|
result = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"], user_input={"blink": False},
|
result["flow_id"], user_input={"blink": False, "override_api_host": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
# verify the updated fields
|
# verify the updated fields
|
||||||
|
@ -1129,6 +1145,7 @@ async def test_option_flow_import(hass, mock_panel):
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
"io": {"1": "Binary Sensor", "2": "Digital Sensor", "3": "Switchable Output"},
|
"io": {"1": "Binary Sensor", "2": "Digital Sensor", "3": "Switchable Output"},
|
||||||
"blink": False,
|
"blink": False,
|
||||||
|
"api_host": "",
|
||||||
"binary_sensors": [
|
"binary_sensors": [
|
||||||
{"zone": "1", "type": "door", "inverse": True, "name": "winder"},
|
{"zone": "1", "type": "door", "inverse": True, "name": "winder"},
|
||||||
],
|
],
|
||||||
|
|
|
@ -43,6 +43,7 @@ async def test_config_schema(hass):
|
||||||
"""Test that config schema is imported properly."""
|
"""Test that config schema is imported properly."""
|
||||||
config = {
|
config = {
|
||||||
konnected.DOMAIN: {
|
konnected.DOMAIN: {
|
||||||
|
konnected.CONF_API_HOST: "http://1.1.1.1:8888",
|
||||||
konnected.CONF_ACCESS_TOKEN: "abcdefgh",
|
konnected.CONF_ACCESS_TOKEN: "abcdefgh",
|
||||||
konnected.CONF_DEVICES: [{konnected.CONF_ID: "aabbccddeeff"}],
|
konnected.CONF_DEVICES: [{konnected.CONF_ID: "aabbccddeeff"}],
|
||||||
}
|
}
|
||||||
|
@ -50,10 +51,12 @@ async def test_config_schema(hass):
|
||||||
assert konnected.CONFIG_SCHEMA(config) == {
|
assert konnected.CONFIG_SCHEMA(config) == {
|
||||||
"konnected": {
|
"konnected": {
|
||||||
"access_token": "abcdefgh",
|
"access_token": "abcdefgh",
|
||||||
|
"api_host": "http://1.1.1.1:8888",
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"default_options": {
|
"default_options": {
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "http://1.1.1.1:8888",
|
||||||
"discovery": True,
|
"discovery": True,
|
||||||
"io": {
|
"io": {
|
||||||
"1": "Disabled",
|
"1": "Disabled",
|
||||||
|
@ -96,6 +99,7 @@ async def test_config_schema(hass):
|
||||||
{
|
{
|
||||||
"default_options": {
|
"default_options": {
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "",
|
||||||
"discovery": True,
|
"discovery": True,
|
||||||
"io": {
|
"io": {
|
||||||
"1": "Disabled",
|
"1": "Disabled",
|
||||||
|
@ -162,6 +166,7 @@ async def test_config_schema(hass):
|
||||||
{
|
{
|
||||||
"default_options": {
|
"default_options": {
|
||||||
"blink": True,
|
"blink": True,
|
||||||
|
"api_host": "",
|
||||||
"discovery": True,
|
"discovery": True,
|
||||||
"io": {
|
"io": {
|
||||||
"1": "Binary Sensor",
|
"1": "Binary Sensor",
|
||||||
|
|
Loading…
Add table
Reference in a new issue