Files
rikku-home-assistant/custom_components/cync_lights/config_flow.py
T
2025-04-21 21:03:30 -04:00

289 lines
14 KiB
Python

"""Config flow for Cync Room Lights integration."""
from __future__ import annotations
import logging
import voluptuous as vol
from typing import Any
from homeassistant import config_entries
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.core import callback
from .const import DOMAIN
from .cync_hub import CyncUserData
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
)
STEP_TWO_FACTOR_CODE = vol.Schema(
{
vol.Required("two_factor_code"): str,
}
)
async def cync_login(hub, user_input: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input"""
response = await hub.authenticate(user_input["username"], user_input["password"])
if response['authorized']:
return {'title':'cync_lights_'+ user_input['username'],'data':{'cync_credentials': hub.auth_code, 'user_input':user_input}}
else:
if response['two_factor_code_required']:
raise TwoFactorCodeRequired
else:
raise InvalidAuth
async def submit_two_factor_code(hub, user_input: dict[str, Any]) -> dict[str, Any]:
"""Validate the two factor code"""
response = await hub.auth_two_factor(user_input["two_factor_code"])
if response['authorized']:
return {'title':'cync_lights_'+ hub.username,'data':{'cync_credentials': hub.auth_code, 'user_input': {'username':hub.username,'password':hub.password}}}
else:
raise InvalidAuth
class CyncConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Cync Room Lights."""
def __init__(self):
self.cync_hub = CyncUserData()
self.data ={}
self.options = {}
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle user and password for Cync account."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
errors = {}
try:
info = await cync_login(self.cync_hub, user_input)
info["data"]["cync_config"] = await self.cync_hub.get_cync_config()
except TwoFactorCodeRequired:
return await self.async_step_two_factor_code()
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception as e: # pylint: disable=broad-except
_LOGGER.error(str(type(e).__name__) + ": " + str(e))
errors["base"] = "unknown"
else:
self.data = info
return await self.async_step_finish_setup()
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_two_factor_code(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle two factor authentication for Cync account."""
if user_input is None:
return self.async_show_form(
step_id="two_factor_code", data_schema=STEP_TWO_FACTOR_CODE
)
errors = {}
try:
info = await submit_two_factor_code(self.cync_hub, user_input)
info["data"]["cync_config"] = await self.cync_hub.get_cync_config()
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception as e: # pylint: disable=broad-except
_LOGGER.error(str(type(e).__name__) + ": " + str(e))
errors["base"] = "unknown"
else:
self.data = info
return await self.async_step_select_switches()
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_select_switches(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Select rooms and individual switches for entity creation"""
if user_input is not None:
self.options = user_input
return await self._async_finish_setup()
switches_data_schema = vol.Schema(
{
vol.Optional(
"rooms",
description = {"suggested_value" : [room for room in self.data["data"]["cync_config"]["rooms"].keys() if not self.data["data"]["cync_config"]["rooms"][room]['isSubgroup']]},
): cv.multi_select({room : f'{room_info["name"]} ({room_info["home_name"]})' for room,room_info in self.data["data"]["cync_config"]["rooms"].items() if not self.data["data"]["cync_config"]["rooms"][room]['isSubgroup']}),
vol.Optional(
"subgroups",
description = {"suggested_value" : [room for room in self.data["data"]["cync_config"]["rooms"].keys() if self.data["data"]["cync_config"]["rooms"][room]['isSubgroup']]},
): cv.multi_select({room : f'{room_info["name"]} ({room_info.get("parent_room","")}:{room_info["home_name"]})' for room,room_info in self.data["data"]["cync_config"]["rooms"].items() if self.data["data"]["cync_config"]["rooms"][room]['isSubgroup']}),
vol.Optional(
"switches",
description = {"suggested_value" : [device_id for device_id,device_info in self.data["data"]["cync_config"]["devices"].items() if device_info['FAN']]},
): cv.multi_select({switch_id : f'{sw_info["name"]} ({sw_info["room_name"]}:{sw_info["home_name"]})' for switch_id,sw_info in self.data["data"]["cync_config"]["devices"].items() if sw_info.get('ONOFF',False) and sw_info.get('MULTIELEMENT',1) == 1}),
vol.Optional(
"motion_sensors",
description = {"suggested_value" : [device_id for device_id,device_info in self.data["data"]["cync_config"]["devices"].items() if device_info['MOTION']]},
): cv.multi_select({device_id : f'{device_info["name"]} ({device_info["room_name"]}:{device_info["home_name"]})' for device_id,device_info in self.data["data"]["cync_config"]["devices"].items() if device_info.get('MOTION',False)}),
vol.Optional(
"ambient_light_sensors",
description = {"suggested_value" : [device_id for device_id,device_info in self.data["data"]["cync_config"]["devices"].items() if device_info['AMBIENT_LIGHT']]},
): cv.multi_select({device_id : f'{device_info["name"]} ({device_info["room_name"]}:{device_info["home_name"]})' for device_id,device_info in self.data["data"]["cync_config"]["devices"].items() if device_info.get('AMBIENT_LIGHT',False)}),
}
)
return self.async_show_form(step_id="select_switches", data_schema=switches_data_schema)
async def _async_finish_setup(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Finish setup and create entry"""
existing_entry = await self.async_set_unique_id(self.data['title'])
if not existing_entry:
return self.async_create_entry(title=self.data["title"], data=self.data["data"], options=self.options)
else:
self.hass.config_entries.async_update_entry(existing_entry, data=self.data['data'], options=self.options)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.hass.config_entries.async_abort(reason="reauth_successful")
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return CyncOptionsFlowHandler(config_entry)
class CyncOptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.entry = config_entry
self.cync_hub = CyncUserData()
self.data = {}
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
if user_input['re-authenticate'] == "No":
return await self.async_step_select_switches()
else:
return await self.async_step_auth()
data_schema = vol.Schema(
{
vol.Required(
"re-authenticate",default="No"): vol.In(["Yes","No"]),
}
)
return self.async_show_form(step_id="init", data_schema=data_schema)
async def async_step_auth(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Attempt to re-authenticate"""
errors = {}
try:
info = await cync_login(self.cync_hub, self.entry.data['user_input'])
info["data"]["cync_config"] = await self.cync_hub.get_cync_config()
except TwoFactorCodeRequired:
return await self.async_step_two_factor_code()
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception as e: # pylint: disable=broad-except
_LOGGER.error(str(type(e).__name__) + ": " + str(e))
errors["base"] = "unknown"
else:
self.data = info
return await self.async_step_select_switches()
async def async_step_two_factor_code(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle two factor authentication for Cync account."""
if user_input is None:
return self.async_show_form(
step_id="two_factor_code", data_schema=STEP_TWO_FACTOR_CODE
)
errors = {}
try:
info = await submit_two_factor_code(self.cync_hub, user_input)
info["data"]["cync_config"] = await self.cync_hub.get_cync_config()
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception as e: # pylint: disable=broad-except
_LOGGER.error(str(type(e).__name__) + ": " + str(e))
errors["base"] = "unknown"
else:
self.data = info
return await self.async_step_select_switches()
return self.async_show_form(
step_id="two_factor_code", data_schema=STEP_TWO_FACTOR_CODE, errors=errors
)
async def async_step_select_switches(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if "data" in self.data and self.data["data"] != self.entry.data:
self.hass.config_entries.async_update_entry(self.entry, data = self.data["data"])
if user_input is not None:
return self.async_create_entry(title="",data=user_input)
switches_data_schema = vol.Schema(
{
vol.Optional(
"rooms",
description = {"suggested_value" : [room for room in self.entry.options["rooms"] if room in self.entry.data["cync_config"]["rooms"].keys()]},
): cv.multi_select({room : f'{room_info["name"]} ({room_info["home_name"]})' for room,room_info in self.entry.data["cync_config"]["rooms"].items() if not self.data["data"]["cync_config"]["rooms"][room]['isSubgroup']}),
vol.Optional(
"subgroups",
description = {"suggested_value" : [room for room in self.entry.options["subgroups"] if room in self.entry.data["cync_config"]["rooms"].keys()]},
): cv.multi_select({room : f'{room_info["name"]} ({room_info.get("parent_room","")}:{room_info["home_name"]})' for room,room_info in self.entry.data["cync_config"]["rooms"].items() if self.data["data"]["cync_config"]["rooms"][room]['isSubgroup']}),
vol.Optional(
"switches",
description = {"suggested_value" : [sw for sw in self.entry.options["switches"] if sw in self.entry.data["cync_config"]["devices"].keys()]},
): cv.multi_select({switch_id : f'{sw_info["name"]} ({sw_info["room_name"]}:{sw_info["home_name"]})' for switch_id,sw_info in self.entry.data["cync_config"]["devices"].items() if sw_info.get('ONOFF',False) and sw_info.get('MULTIELEMENT',1) == 1}),
vol.Optional(
"motion_sensors",
description = {"suggested_value" : [sensor for sensor in self.entry.options["motion_sensors"] if sensor in self.entry.data["cync_config"]["devices"].keys()]},
): cv.multi_select({device_id : f'{device_info["name"]} ({device_info["room_name"]}:{device_info["home_name"]})' for device_id,device_info in self.entry.data["cync_config"]["devices"].items() if device_info.get('MOTION',False)}),
vol.Optional(
"ambient_light_sensors",
description = {"suggested_value" : [sensor for sensor in self.entry.options["ambient_light_sensors"] if sensor in self.entry.data["cync_config"]["devices"].keys()]},
): cv.multi_select({device_id : f'{device_info["name"]} ({device_info["room_name"]}:{device_info["home_name"]})' for device_id,device_info in self.entry.data["cync_config"]["devices"].items() if device_info.get('AMBIENT_LIGHT',False)}),
}
)
return self.async_show_form(step_id="select_switches", data_schema=switches_data_schema)
class TwoFactorCodeRequired(HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""