236 lines
7.8 KiB
Python
236 lines
7.8 KiB
Python
"""Config flow to configure OpenHASP component."""
|
|
import json
|
|
import logging
|
|
import os
|
|
|
|
from homeassistant import config_entries, data_entry_flow, exceptions
|
|
from homeassistant.components.mqtt import valid_subscribe_topic
|
|
from homeassistant.const import CONF_NAME
|
|
from homeassistant.core import callback
|
|
import homeassistant.helpers.config_validation as cv
|
|
import voluptuous as vol
|
|
|
|
from .const import (
|
|
CONF_DIMLIGHTS,
|
|
CONF_HWID,
|
|
CONF_IDLE_BRIGHTNESS,
|
|
CONF_INPUT,
|
|
CONF_LIGHTS,
|
|
CONF_NODE,
|
|
CONF_PAGES,
|
|
CONF_PAGES_PATH,
|
|
CONF_RELAYS,
|
|
CONF_TOPIC,
|
|
DEFAULT_IDLE_BRIGHNESS,
|
|
DISCOVERED_DIM,
|
|
DISCOVERED_HWID,
|
|
DISCOVERED_INPUT,
|
|
DISCOVERED_LIGHT,
|
|
DISCOVERED_MANUFACTURER,
|
|
DISCOVERED_MODEL,
|
|
DISCOVERED_NODE,
|
|
DISCOVERED_PAGES,
|
|
DISCOVERED_POWER,
|
|
DISCOVERED_URL,
|
|
DISCOVERED_VERSION,
|
|
DOMAIN,
|
|
MAJOR,
|
|
MINOR,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def validate_jsonl(path):
|
|
"""Validate that the value is an existing file."""
|
|
if path is None:
|
|
raise InvalidJSONL()
|
|
file_in = os.path.expanduser(str(path))
|
|
|
|
if not os.path.isfile(file_in):
|
|
raise InvalidJSONL("not a file")
|
|
if not os.access(file_in, os.R_OK):
|
|
raise InvalidJSONL("file not readable")
|
|
return file_in
|
|
|
|
|
|
@config_entries.HANDLERS.register(DOMAIN)
|
|
class OpenHASPFlowHandler(config_entries.ConfigFlow):
|
|
"""Config flow for OpenHASP component."""
|
|
|
|
VERSION = 1
|
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
|
|
|
def __init__(self):
|
|
"""Init OpenHASPFlowHandler."""
|
|
self._errors = {}
|
|
self.config_data = {
|
|
DISCOVERED_MANUFACTURER: "openHASP",
|
|
DISCOVERED_MODEL: None,
|
|
CONF_RELAYS: [],
|
|
}
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
"""Handle a flow initialized by User."""
|
|
_LOGGER.error("Discovery Only")
|
|
|
|
await self.hass.components.mqtt.async_publish(
|
|
self.hass,
|
|
"hasp/broadcast/command/discovery",
|
|
"discovery",
|
|
qos=0,
|
|
retain=False,
|
|
)
|
|
|
|
return self.async_abort(reason="discovery_only")
|
|
|
|
async def async_step_mqtt(self, discovery_info=None):
|
|
"""Handle a flow initialized by MQTT discovery."""
|
|
_discovered = json.loads(discovery_info.payload)
|
|
_LOGGER.debug("Discovered: %s", _discovered)
|
|
|
|
await self.async_set_unique_id(_discovered[DISCOVERED_HWID], raise_on_progress=False)
|
|
self._abort_if_unique_id_configured()
|
|
|
|
version = _discovered.get(DISCOVERED_VERSION)
|
|
if version.split(".")[0:2] != [MAJOR, MINOR]:
|
|
_LOGGER.error(
|
|
"Version mismatch! Your plate: %s - openHASP Component: %s",
|
|
version,
|
|
f"{MAJOR}.{MINOR}.x",
|
|
)
|
|
raise data_entry_flow.AbortFlow("mismatch_version")
|
|
|
|
self.config_data[DISCOVERED_VERSION] = version
|
|
|
|
self.config_data[CONF_HWID] = _discovered[DISCOVERED_HWID]
|
|
self.config_data[CONF_NODE] = self.config_data[CONF_NAME] = _discovered[
|
|
DISCOVERED_NODE
|
|
]
|
|
self.config_data[
|
|
CONF_TOPIC
|
|
] = f"{discovery_info.topic.split('/')[0]}/{self.config_data[CONF_NODE]}"
|
|
|
|
self.config_data[DISCOVERED_URL] = _discovered.get(DISCOVERED_URL)
|
|
self.config_data[DISCOVERED_MANUFACTURER] = _discovered.get(
|
|
DISCOVERED_MANUFACTURER
|
|
)
|
|
self.config_data[DISCOVERED_MODEL] = _discovered.get(DISCOVERED_MODEL)
|
|
self.config_data[CONF_PAGES] = _discovered.get(DISCOVERED_PAGES)
|
|
self.config_data[CONF_RELAYS] = _discovered.get(DISCOVERED_POWER)
|
|
self.config_data[CONF_LIGHTS] = _discovered.get(DISCOVERED_LIGHT)
|
|
self.config_data[CONF_DIMLIGHTS] = _discovered.get(DISCOVERED_DIM)
|
|
self.config_data[CONF_INPUT] = _discovered.get(DISCOVERED_INPUT)
|
|
|
|
return await self.async_step_personalize()
|
|
|
|
async def async_step_personalize(self, user_input=None):
|
|
"""Handle a flow initialized by the user."""
|
|
self._errors = {}
|
|
|
|
if user_input is not None:
|
|
self.config_data = {**self.config_data, **user_input}
|
|
|
|
if self.config_data[
|
|
CONF_NAME
|
|
] not in self.hass.config_entries.async_entries(DOMAIN):
|
|
# Remove / from base topic
|
|
if user_input[CONF_TOPIC].endswith("/"):
|
|
user_input[CONF_TOPIC] = user_input[CONF_TOPIC][:-1]
|
|
|
|
try:
|
|
valid_subscribe_topic(self.config_data[CONF_TOPIC])
|
|
|
|
if CONF_PAGES_PATH in user_input:
|
|
self.config_data[CONF_PAGES_PATH] = validate_jsonl(
|
|
user_input[CONF_PAGES_PATH]
|
|
)
|
|
|
|
await self.async_set_unique_id(self.config_data[CONF_HWID])
|
|
|
|
return self.async_create_entry(
|
|
title=user_input[CONF_NAME], data=self.config_data
|
|
)
|
|
|
|
except vol.Invalid:
|
|
return self.async_abort(reason="invalid_discovery_info")
|
|
|
|
except InvalidJSONL:
|
|
self._errors[CONF_PAGES_PATH] = "invalid_jsonl_path"
|
|
else:
|
|
self._errors[CONF_NAME] = "name_exists"
|
|
|
|
return self.async_show_form(
|
|
step_id="personalize",
|
|
data_schema=vol.Schema(
|
|
{
|
|
vol.Required(
|
|
CONF_TOPIC, default=self.config_data.get(CONF_TOPIC, "hasp")
|
|
): str,
|
|
vol.Required(
|
|
CONF_NAME, default=self.config_data.get(CONF_NAME)
|
|
): str,
|
|
vol.Optional(
|
|
CONF_IDLE_BRIGHTNESS, default=DEFAULT_IDLE_BRIGHNESS
|
|
): vol.All(int, vol.Range(min=0, max=255)),
|
|
vol.Optional(CONF_PAGES_PATH): str,
|
|
}
|
|
),
|
|
errors=self._errors,
|
|
)
|
|
|
|
@staticmethod
|
|
@callback
|
|
def async_get_options_flow(config_entry):
|
|
"""Set the OptionsFlowHandler."""
|
|
return OpenHASPOptionsFlowHandler(config_entry)
|
|
|
|
|
|
class OpenHASPOptionsFlowHandler(config_entries.OptionsFlow):
|
|
"""ConfigOptions flow for openHASP."""
|
|
|
|
def __init__(self, config_entry):
|
|
"""Initialize openHASP options flow."""
|
|
self.config_entry = config_entry
|
|
|
|
async def async_step_init(self, user_input=None):
|
|
"""Manage the options."""
|
|
if user_input is not None:
|
|
# Actually check path is a file
|
|
|
|
try:
|
|
if len(user_input[CONF_PAGES_PATH]):
|
|
user_input[CONF_PAGES_PATH] = validate_jsonl(
|
|
user_input[CONF_PAGES_PATH]
|
|
)
|
|
except InvalidJSONL:
|
|
return self.async_abort(reason="invalid_jsonl_path")
|
|
|
|
return self.async_create_entry(title="", data=user_input)
|
|
|
|
return self.async_show_form(
|
|
step_id="init",
|
|
data_schema=vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_IDLE_BRIGHTNESS,
|
|
default=self.config_entry.options.get(
|
|
CONF_IDLE_BRIGHTNESS,
|
|
self.config_entry.data[CONF_IDLE_BRIGHTNESS],
|
|
),
|
|
): vol.All(int, vol.Range(min=0, max=255)),
|
|
vol.Optional(
|
|
CONF_PAGES_PATH,
|
|
default=self.config_entry.options.get(
|
|
CONF_PAGES_PATH,
|
|
self.config_entry.data.get(CONF_PAGES_PATH, ""),
|
|
),
|
|
): cv.string,
|
|
}
|
|
),
|
|
)
|
|
|
|
|
|
class InvalidJSONL(exceptions.HomeAssistantError):
|
|
"""Error to indicate we cannot load JSONL."""
|