Files
hassos_config/custom_components/openhasp/config_flow.py
2022-12-20 21:26:47 +01:00

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."""