feat: rename the project to Infrarize
This commit is contained in:
@@ -0,0 +1,668 @@
|
||||
"""Config flow for Infrarize — 6-step UI wizard with device list selection."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import aiofiles
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.selector import (
|
||||
BooleanSelector,
|
||||
EntitySelector,
|
||||
EntitySelectorConfig,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
TextSelector,
|
||||
TextSelectorConfig,
|
||||
TextSelectorType,
|
||||
)
|
||||
|
||||
from . import COMPONENT_ABS_DIR, Helper
|
||||
from .const import (
|
||||
ALL_CONTROLLERS_SENTINEL,
|
||||
CONF_CONTROLLER_DATA,
|
||||
CONF_DELAY,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_DEVICE_CODE,
|
||||
CONF_DEVICE_NAME,
|
||||
CONF_HUMIDITY_SENSOR,
|
||||
CONF_PLATFORM,
|
||||
CONF_POWER_SENSOR,
|
||||
CONF_POWER_SENSOR_RESTORE_STATE,
|
||||
CONF_SOURCE_NAMES,
|
||||
CONF_TEMPERATURE_SENSOR,
|
||||
CONTROLLER_HINTS,
|
||||
DEFAULT_DELAY,
|
||||
DEFAULT_DEVICE_NAMES,
|
||||
ENTITY_BASED_CONTROLLERS,
|
||||
INDEX_FILENAME,
|
||||
MANUAL_ENTRY_SENTINEL,
|
||||
PLATFORM_SUBDIR,
|
||||
PLATFORMS,
|
||||
)
|
||||
|
||||
DOMAIN = "infrarize"
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ── Module-level helpers ──────────────────────────────────────────────────────
|
||||
|
||||
async def _load_device_json(platform: str, device_code: int) -> dict | None:
|
||||
"""Return device JSON dict, downloading from GitHub if not cached locally."""
|
||||
subdir = PLATFORM_SUBDIR[platform]
|
||||
codes_dir = os.path.join(COMPONENT_ABS_DIR, "codes", subdir)
|
||||
os.makedirs(codes_dir, exist_ok=True)
|
||||
path = os.path.join(codes_dir, f"{device_code}.json")
|
||||
|
||||
if not os.path.exists(path):
|
||||
source = (
|
||||
"https://raw.githubusercontent.com/"
|
||||
f"kobimx/Infrarize/master/codes/{subdir}/{device_code}.json"
|
||||
)
|
||||
try:
|
||||
await Helper.downloader(source, path)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
try:
|
||||
async with aiofiles.open(path, mode="r") as f:
|
||||
return json.loads(await f.read())
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _controller_schema(
|
||||
controller_type: str,
|
||||
current_data: str = "",
|
||||
current_delay: str = DEFAULT_DELAY,
|
||||
) -> vol.Schema:
|
||||
"""Build controller step schema; entity picker for Broadlink/Xiaomi, text for rest."""
|
||||
if controller_type in ENTITY_BASED_CONTROLLERS:
|
||||
data_field: object = EntitySelector(EntitySelectorConfig(domain="remote"))
|
||||
else:
|
||||
data_field = TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT))
|
||||
|
||||
fields: dict = {}
|
||||
if current_data:
|
||||
fields[
|
||||
vol.Required(
|
||||
CONF_CONTROLLER_DATA,
|
||||
description={"suggested_value": current_data},
|
||||
)
|
||||
] = data_field
|
||||
else:
|
||||
fields[vol.Required(CONF_CONTROLLER_DATA)] = data_field
|
||||
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_DELAY,
|
||||
description={"suggested_value": current_delay},
|
||||
)
|
||||
] = TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT))
|
||||
|
||||
return vol.Schema(fields)
|
||||
|
||||
|
||||
def _options_schema(platform: str, defaults: dict | None = None) -> vol.Schema:
|
||||
"""Build optional-sensors schema for the given platform."""
|
||||
d = defaults or {}
|
||||
|
||||
def _opt(key: str) -> vol.Optional:
|
||||
val = d.get(key)
|
||||
if val is not None and val != "":
|
||||
return vol.Optional(key, description={"suggested_value": val})
|
||||
return vol.Optional(key)
|
||||
|
||||
# Power sensors are only ever checked for on/off state (see
|
||||
# InfrarizeClimate._async_power_sensor_changed and the equivalent methods in
|
||||
# fan.py, light.py, media_player.py, which compare .state to STATE_ON/STATE_OFF)
|
||||
# so restrict the picker to domains that expose a binary on/off state.
|
||||
power_sensor_selector = EntitySelector(
|
||||
EntitySelectorConfig(domain=["binary_sensor", "switch", "input_boolean"])
|
||||
)
|
||||
|
||||
if platform == "climate":
|
||||
return vol.Schema(
|
||||
{
|
||||
_opt(CONF_TEMPERATURE_SENSOR): EntitySelector(
|
||||
EntitySelectorConfig(domain="sensor", device_class="temperature")
|
||||
),
|
||||
_opt(CONF_HUMIDITY_SENSOR): EntitySelector(
|
||||
EntitySelectorConfig(domain="sensor", device_class="humidity")
|
||||
),
|
||||
_opt(CONF_POWER_SENSOR): power_sensor_selector,
|
||||
vol.Optional(
|
||||
CONF_POWER_SENSOR_RESTORE_STATE,
|
||||
default=bool(d.get(CONF_POWER_SENSOR_RESTORE_STATE, False)),
|
||||
): BooleanSelector(),
|
||||
}
|
||||
)
|
||||
|
||||
if platform == "media_player":
|
||||
src_existing = d.get(CONF_SOURCE_NAMES, {})
|
||||
src_str = (
|
||||
json.dumps(src_existing)
|
||||
if isinstance(src_existing, dict) and src_existing
|
||||
else ""
|
||||
)
|
||||
fields: dict = {
|
||||
_opt(CONF_POWER_SENSOR): power_sensor_selector,
|
||||
vol.Optional(
|
||||
CONF_DEVICE_CLASS,
|
||||
description={"suggested_value": d.get(CONF_DEVICE_CLASS, "tv")},
|
||||
): TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT)),
|
||||
}
|
||||
src_key = (
|
||||
vol.Optional(
|
||||
CONF_SOURCE_NAMES,
|
||||
description={"suggested_value": src_str},
|
||||
)
|
||||
if src_str
|
||||
else vol.Optional(CONF_SOURCE_NAMES)
|
||||
)
|
||||
fields[src_key] = TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.TEXT, multiline=True)
|
||||
)
|
||||
return vol.Schema(fields)
|
||||
|
||||
# fan and light
|
||||
return vol.Schema({_opt(CONF_POWER_SENSOR): power_sensor_selector})
|
||||
|
||||
|
||||
def _parse_options(platform: str, user_input: dict) -> dict:
|
||||
"""Clean and coerce options form input before storing."""
|
||||
out: dict = {}
|
||||
|
||||
def _maybe(key: str) -> None:
|
||||
val = user_input.get(key, "")
|
||||
if isinstance(val, str):
|
||||
val = val.strip()
|
||||
if val:
|
||||
out[key] = val
|
||||
|
||||
if platform == "climate":
|
||||
_maybe(CONF_TEMPERATURE_SENSOR)
|
||||
_maybe(CONF_HUMIDITY_SENSOR)
|
||||
_maybe(CONF_POWER_SENSOR)
|
||||
out[CONF_POWER_SENSOR_RESTORE_STATE] = bool(
|
||||
user_input.get(CONF_POWER_SENSOR_RESTORE_STATE, False)
|
||||
)
|
||||
elif platform == "media_player":
|
||||
_maybe(CONF_POWER_SENSOR)
|
||||
out[CONF_DEVICE_CLASS] = (
|
||||
(user_input.get(CONF_DEVICE_CLASS) or "tv").strip() or "tv"
|
||||
)
|
||||
src_raw = user_input.get(CONF_SOURCE_NAMES, "")
|
||||
if isinstance(src_raw, str) and src_raw.strip():
|
||||
try:
|
||||
parsed = json.loads(src_raw.strip())
|
||||
if isinstance(parsed, dict):
|
||||
out[CONF_SOURCE_NAMES] = parsed
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
elif isinstance(src_raw, dict) and src_raw:
|
||||
out[CONF_SOURCE_NAMES] = src_raw
|
||||
else: # fan, light
|
||||
_maybe(CONF_POWER_SENSOR)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def _device_placeholders(device_data: dict, platform: str) -> dict:
|
||||
"""Build description_placeholders dict for the options step."""
|
||||
p: dict = {
|
||||
"manufacturer": device_data.get("manufacturer", "—"),
|
||||
"models": ", ".join(device_data.get("supportedModels", [])) or "—",
|
||||
"controller_type": device_data.get("supportedController", "—"),
|
||||
"device_code": str(device_data.get("device_code", "—")),
|
||||
}
|
||||
if platform == "climate":
|
||||
p["temp_range"] = (
|
||||
f"{device_data.get('minTemperature', '—')} – "
|
||||
f"{device_data.get('maxTemperature', '—')} °C"
|
||||
)
|
||||
p["operation_modes"] = ", ".join(device_data.get("operationModes", []))
|
||||
p["fan_modes"] = ", ".join(device_data.get("fanModes", []))
|
||||
swing = device_data.get("swingModes", [])
|
||||
p["swing_modes"] = ", ".join(swing) if swing else "none"
|
||||
return p
|
||||
|
||||
|
||||
# ── Config Flow ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class InfrarizeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle multi-step UI config flow for Infrarize."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._platform: str = ""
|
||||
self._controller_filter: str = ALL_CONTROLLERS_SENTINEL
|
||||
self._manufacturer: str = ""
|
||||
self._device_code: int = 0
|
||||
self._device_data: dict = {}
|
||||
self._device_name: str = ""
|
||||
self._controller_data: str = ""
|
||||
self._delay: str = DEFAULT_DELAY
|
||||
self._index_cache: list[dict] | None = None
|
||||
|
||||
# ── Index helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
async def _get_index(self) -> list[dict]:
|
||||
"""Load and cache index entries for the current platform."""
|
||||
if self._index_cache is not None:
|
||||
return self._index_cache
|
||||
path = os.path.join(COMPONENT_ABS_DIR, INDEX_FILENAME)
|
||||
try:
|
||||
async with aiofiles.open(path, mode="r") as f:
|
||||
data = json.loads(await f.read())
|
||||
self._index_cache = data.get(self._platform, [])
|
||||
except Exception:
|
||||
_LOGGER.warning("Could not load %s", INDEX_FILENAME)
|
||||
self._index_cache = []
|
||||
return self._index_cache
|
||||
|
||||
def _filtered(self, entries: list[dict]) -> list[dict]:
|
||||
if self._controller_filter == ALL_CONTROLLERS_SENTINEL:
|
||||
return entries
|
||||
return [e for e in entries if e.get("controller") == self._controller_filter]
|
||||
|
||||
# ── Step 1: platform ──────────────────────────────────────────────────────
|
||||
|
||||
async def async_step_user(self, user_input: dict | None = None) -> FlowResult:
|
||||
if user_input is not None:
|
||||
self._platform = user_input["platform"]
|
||||
return await self.async_step_controller_filter()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required("platform"): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[
|
||||
{"label": v, "value": k}
|
||||
for k, v in PLATFORMS.items()
|
||||
],
|
||||
mode=SelectSelectorMode.LIST,
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# ── Step 2: controller filter ─────────────────────────────────────────────
|
||||
|
||||
async def async_step_controller_filter(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
entries = await self._get_index()
|
||||
|
||||
if user_input is not None:
|
||||
self._controller_filter = user_input.get(
|
||||
"controller_type", ALL_CONTROLLERS_SENTINEL
|
||||
)
|
||||
return await self.async_step_manufacturer()
|
||||
|
||||
available = sorted({e["controller"] for e in entries})
|
||||
options = [{"label": "Any (show all)", "value": ALL_CONTROLLERS_SENTINEL}]
|
||||
options += [{"label": c, "value": c} for c in available]
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="controller_filter",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
"controller_type", default=ALL_CONTROLLERS_SENTINEL
|
||||
): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=options, mode=SelectSelectorMode.LIST
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# ── Step 3: manufacturer ──────────────────────────────────────────────────
|
||||
|
||||
async def async_step_manufacturer(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
entries = self._filtered(await self._get_index())
|
||||
manufacturers = sorted({e["manufacturer"] for e in entries})
|
||||
|
||||
if user_input is not None:
|
||||
sel = user_input.get("manufacturer", "")
|
||||
if sel == MANUAL_ENTRY_SENTINEL:
|
||||
return await self.async_step_device_manual()
|
||||
self._manufacturer = sel
|
||||
return await self.async_step_device_select()
|
||||
|
||||
options = [
|
||||
{"label": "— Enter code manually —", "value": MANUAL_ENTRY_SENTINEL}
|
||||
]
|
||||
options += [{"label": m, "value": m} for m in manufacturers]
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="manufacturer",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required("manufacturer"): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=options, mode=SelectSelectorMode.DROPDOWN
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
# ── Step 4a: device from index list ───────────────────────────────────────
|
||||
|
||||
async def async_step_device_select(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
entries = [
|
||||
e
|
||||
for e in self._filtered(await self._get_index())
|
||||
if e.get("manufacturer") == self._manufacturer
|
||||
]
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
sel = user_input.get("device_code", "")
|
||||
if sel == MANUAL_ENTRY_SENTINEL:
|
||||
return await self.async_step_device_manual()
|
||||
try:
|
||||
device_code = int(sel)
|
||||
except (ValueError, TypeError):
|
||||
errors["device_code"] = "invalid_device_code"
|
||||
else:
|
||||
device_data = await _load_device_json(self._platform, device_code)
|
||||
if device_data is None:
|
||||
errors["device_code"] = "invalid_device_code"
|
||||
else:
|
||||
self._device_code = device_code
|
||||
self._device_data = device_data
|
||||
models = device_data.get("supportedModels", [])
|
||||
self._device_name = (
|
||||
f"{self._manufacturer} {models[0]}"
|
||||
if models
|
||||
else DEFAULT_DEVICE_NAMES[self._platform]
|
||||
)
|
||||
return await self.async_step_device_name()
|
||||
|
||||
options = [
|
||||
{"label": "— Enter code manually —", "value": MANUAL_ENTRY_SENTINEL}
|
||||
]
|
||||
for e in sorted(entries, key=lambda x: x["code"]):
|
||||
models_str = ", ".join(e.get("models", []))
|
||||
options.append(
|
||||
{"label": f"{e['code']} — {models_str}", "value": str(e["code"])}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device_select",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required("device_code"): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=options, mode=SelectSelectorMode.DROPDOWN
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
description_placeholders={"manufacturer": self._manufacturer},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
# ── Step 4b: manual code entry ────────────────────────────────────────────
|
||||
|
||||
async def async_step_device_manual(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
try:
|
||||
device_code = int(user_input["device_code"])
|
||||
if device_code <= 0:
|
||||
raise ValueError("non-positive")
|
||||
except (ValueError, KeyError, TypeError):
|
||||
errors["device_code"] = "invalid_device_code"
|
||||
else:
|
||||
device_data = await _load_device_json(self._platform, device_code)
|
||||
if device_data is None:
|
||||
errors["device_code"] = "invalid_device_code"
|
||||
else:
|
||||
self._device_code = device_code
|
||||
self._device_data = device_data
|
||||
manufacturer = device_data.get("manufacturer", "")
|
||||
models = device_data.get("supportedModels", [])
|
||||
self._manufacturer = manufacturer
|
||||
self._device_name = (
|
||||
f"{manufacturer} {models[0]}"
|
||||
if models
|
||||
else DEFAULT_DEVICE_NAMES[self._platform]
|
||||
)
|
||||
return await self.async_step_device_name()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device_manual",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required("device_code"): TextSelector(
|
||||
TextSelectorConfig(type=TextSelectorType.NUMBER)
|
||||
)
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
# ── Step 4c: confirm / edit name ──────────────────────────────────────────
|
||||
|
||||
async def async_step_device_name(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
if user_input is not None:
|
||||
name = (user_input.get("name") or "").strip() or self._device_name
|
||||
self._device_name = name
|
||||
return await self.async_step_controller()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device_name",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
"name",
|
||||
description={"suggested_value": self._device_name},
|
||||
): TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT))
|
||||
}
|
||||
),
|
||||
description_placeholders={
|
||||
"manufacturer": self._device_data.get("manufacturer", ""),
|
||||
"models": ", ".join(self._device_data.get("supportedModels", [])),
|
||||
"controller_type": self._device_data.get("supportedController", ""),
|
||||
"device_code": str(self._device_code),
|
||||
},
|
||||
)
|
||||
|
||||
# ── Step 5: controller data ───────────────────────────────────────────────
|
||||
|
||||
async def async_step_controller(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
errors: dict[str, str] = {}
|
||||
controller_type = self._device_data.get("supportedController", "")
|
||||
hint = CONTROLLER_HINTS.get(controller_type, "Enter the controller connection string.")
|
||||
|
||||
if user_input is not None:
|
||||
controller_data = user_input.get(CONF_CONTROLLER_DATA, "")
|
||||
if isinstance(controller_data, str):
|
||||
controller_data = controller_data.strip()
|
||||
if not controller_data:
|
||||
errors[CONF_CONTROLLER_DATA] = "controller_data_required"
|
||||
else:
|
||||
self._controller_data = controller_data
|
||||
delay_raw = user_input.get(CONF_DELAY, DEFAULT_DELAY)
|
||||
try:
|
||||
float(delay_raw)
|
||||
self._delay = str(delay_raw)
|
||||
except (ValueError, TypeError):
|
||||
self._delay = DEFAULT_DELAY
|
||||
|
||||
uid = (
|
||||
f"infrarize_{self._platform}_{self._device_code}"
|
||||
f"_{self._controller_data}"
|
||||
)
|
||||
await self.async_set_unique_id(uid)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return await self.async_step_options_platform()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="controller",
|
||||
data_schema=_controller_schema(
|
||||
controller_type, self._controller_data, self._delay
|
||||
),
|
||||
description_placeholders={
|
||||
"controller_type": controller_type,
|
||||
"controller_hint": hint,
|
||||
},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
# ── Step 6: optional sensors / platform settings ──────────────────────────
|
||||
|
||||
async def async_step_options_platform(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
if user_input is not None:
|
||||
return self._create_entry(user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="options_platform",
|
||||
data_schema=_options_schema(self._platform),
|
||||
description_placeholders=_device_placeholders(
|
||||
self._device_data, self._platform
|
||||
),
|
||||
)
|
||||
|
||||
# ── Entry creation ────────────────────────────────────────────────────────
|
||||
|
||||
def _create_entry(self, options_input: dict) -> FlowResult:
|
||||
try:
|
||||
delay_float = float(self._delay)
|
||||
except (ValueError, TypeError):
|
||||
delay_float = 0.5
|
||||
|
||||
# Immutable identification stored in data
|
||||
data: dict = {
|
||||
CONF_PLATFORM: self._platform,
|
||||
CONF_DEVICE_CODE: self._device_code,
|
||||
CONF_DEVICE_NAME: self._device_name,
|
||||
}
|
||||
|
||||
# Mutable settings stored in options (editable via options flow)
|
||||
options: dict = {
|
||||
CONF_CONTROLLER_DATA: self._controller_data,
|
||||
CONF_DELAY: delay_float,
|
||||
}
|
||||
options.update(_parse_options(self._platform, options_input))
|
||||
|
||||
mfr = self._device_data.get("manufacturer", "Infrarize")
|
||||
title = f"{mfr} — {self._device_name}"
|
||||
return self.async_create_entry(title=title, data=data, options=options)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry: ConfigEntry) -> InfrarizeOptionsFlow:
|
||||
return InfrarizeOptionsFlow(config_entry)
|
||||
|
||||
|
||||
# ── Options Flow ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class InfrarizeOptionsFlow(OptionsFlow):
|
||||
"""Edit controller and sensor settings for an existing Infrarize entry."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
self._entry = config_entry
|
||||
self._platform: str = config_entry.data.get(CONF_PLATFORM, "")
|
||||
self._device_code: int = config_entry.data.get(CONF_DEVICE_CODE, 0)
|
||||
self._device_data: dict = {}
|
||||
# Pre-fill from current options
|
||||
self._controller_data: str = config_entry.options.get(CONF_CONTROLLER_DATA, "")
|
||||
self._delay: str = str(config_entry.options.get(CONF_DELAY, DEFAULT_DELAY))
|
||||
|
||||
async def async_step_init(self, user_input: dict | None = None) -> FlowResult:
|
||||
if not self._device_data:
|
||||
self._device_data = (
|
||||
await _load_device_json(self._platform, self._device_code) or {}
|
||||
)
|
||||
return await self.async_step_controller()
|
||||
|
||||
async def async_step_controller(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
errors: dict[str, str] = {}
|
||||
controller_type = self._device_data.get("supportedController", "")
|
||||
hint = CONTROLLER_HINTS.get(controller_type, "Enter the controller connection string.")
|
||||
|
||||
if user_input is not None:
|
||||
controller_data = user_input.get(CONF_CONTROLLER_DATA, "")
|
||||
if isinstance(controller_data, str):
|
||||
controller_data = controller_data.strip()
|
||||
if not controller_data:
|
||||
errors[CONF_CONTROLLER_DATA] = "controller_data_required"
|
||||
else:
|
||||
self._controller_data = controller_data
|
||||
delay_raw = user_input.get(CONF_DELAY, DEFAULT_DELAY)
|
||||
try:
|
||||
float(delay_raw)
|
||||
self._delay = str(delay_raw)
|
||||
except (ValueError, TypeError):
|
||||
self._delay = DEFAULT_DELAY
|
||||
return await self.async_step_options_platform()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="controller",
|
||||
data_schema=_controller_schema(
|
||||
controller_type, self._controller_data, self._delay
|
||||
),
|
||||
description_placeholders={
|
||||
"controller_type": controller_type,
|
||||
"controller_hint": hint,
|
||||
},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_options_platform(
|
||||
self, user_input: dict | None = None
|
||||
) -> FlowResult:
|
||||
if user_input is not None:
|
||||
try:
|
||||
delay_float = float(self._delay)
|
||||
except (ValueError, TypeError):
|
||||
delay_float = 0.5
|
||||
|
||||
updated: dict = {
|
||||
CONF_CONTROLLER_DATA: self._controller_data,
|
||||
CONF_DELAY: delay_float,
|
||||
}
|
||||
updated.update(_parse_options(self._platform, user_input))
|
||||
return self.async_create_entry(title="", data=updated)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="options_platform",
|
||||
data_schema=_options_schema(self._platform, defaults=self._entry.options),
|
||||
description_placeholders=_device_placeholders(
|
||||
self._device_data, self._platform
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user