1
mirror of https://github.com/home-assistant/core synced 2024-09-12 15:16:21 +02:00
ha-core/homeassistant/components/smappee/config_flow.py

201 lines
7.4 KiB
Python
Raw Normal View History

"""Config flow for Smappee."""
import logging
from pysmappee import helper, mqtt
import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_entry_oauth2_flow
from . import api
from .const import (
CONF_SERIALNUMBER,
DOMAIN,
ENV_CLOUD,
ENV_LOCAL,
SUPPORTED_LOCAL_DEVICES,
)
class SmappeeFlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config Smappee config flow."""
DOMAIN = DOMAIN
async def async_oauth_create_entry(self, data):
"""Create an entry for the flow."""
await self.async_set_unique_id(unique_id=f"{DOMAIN}Cloud")
return self.async_create_entry(title=f"{DOMAIN}Cloud", data=data)
@property
def logger(self) -> logging.Logger:
"""Return logger."""
return logging.getLogger(__name__)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""
if not discovery_info.hostname.startswith(SUPPORTED_LOCAL_DEVICES):
return self.async_abort(reason="invalid_mdns")
serial_number = discovery_info.hostname.replace(".local.", "").replace(
"Smappee", ""
)
# Check if already configured (local)
await self.async_set_unique_id(serial_number)
self._abort_if_unique_id_configured()
# Check if already configured (cloud)
if self.is_cloud_device_already_added():
return self.async_abort(reason="already_configured_device")
self.context.update(
{
CONF_IP_ADDRESS: discovery_info.host,
CONF_SERIALNUMBER: serial_number,
"title_placeholders": {"name": serial_number},
}
)
return await self.async_step_zeroconf_confirm()
async def async_step_zeroconf_confirm(self, user_input=None):
"""Confirm zeroconf flow."""
errors = {}
# Check if already configured (cloud)
if self.is_cloud_device_already_added():
return self.async_abort(reason="already_configured_device")
if user_input is None:
serialnumber = self.context.get(CONF_SERIALNUMBER)
return self.async_show_form(
step_id="zeroconf_confirm",
description_placeholders={"serialnumber": serialnumber},
errors=errors,
)
ip_address = self.context.get(CONF_IP_ADDRESS)
serial_number = self.context.get(CONF_SERIALNUMBER)
# Attempt to make a connection to the local device
if helper.is_smappee_genius(serial_number):
# next generation device, attempt connect to the local mqtt broker
smappee_mqtt = mqtt.SmappeeLocalMqtt(serial_number=serial_number)
connect = await self.hass.async_add_executor_job(smappee_mqtt.start_attempt)
if not connect:
return self.async_abort(reason="cannot_connect")
else:
# legacy devices, without local mqtt broker, try api access
smappee_api = api.api.SmappeeLocalApi(ip=ip_address)
logon = await self.hass.async_add_executor_job(smappee_api.logon)
if logon is None:
return self.async_abort(reason="cannot_connect")
return self.async_create_entry(
title=f"{DOMAIN}{serial_number}",
data={CONF_IP_ADDRESS: ip_address, CONF_SERIALNUMBER: serial_number},
)
async def async_step_user(self, user_input=None):
"""Handle a flow initiated by the user."""
# If there is a CLOUD entry already, abort a new LOCAL entry
if self.is_cloud_device_already_added():
return self.async_abort(reason="already_configured_device")
return await self.async_step_environment()
async def async_step_environment(self, user_input=None):
"""Decide environment, cloud or local."""
if user_input is None:
return self.async_show_form(
step_id="environment",
data_schema=vol.Schema(
{
vol.Required("environment", default=ENV_CLOUD): vol.In(
[ENV_CLOUD, ENV_LOCAL]
)
}
),
errors={},
)
# Environment chosen, request additional host information for LOCAL or OAuth2 flow for CLOUD
# Ask for host detail
if user_input["environment"] == ENV_LOCAL:
return await self.async_step_local()
# Abort cloud option if a LOCAL entry has already been added
if user_input["environment"] == ENV_CLOUD and self._async_current_entries():
return self.async_abort(reason="already_configured_local_device")
return await self.async_step_pick_implementation()
async def async_step_local(self, user_input=None):
"""Handle local flow."""
if user_input is None:
return self.async_show_form(
step_id="local",
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors={},
)
# In a LOCAL setup we still need to resolve the host to serial number
ip_address = user_input["host"]
serial_number = None
# Attempt 1: try to use the local api (older generation) to resolve host to serialnumber
smappee_api = api.api.SmappeeLocalApi(ip=ip_address)
logon = await self.hass.async_add_executor_job(smappee_api.logon)
if logon is not None:
advanced_config = await self.hass.async_add_executor_job(
smappee_api.load_advanced_config
)
for config_item in advanced_config:
if config_item["key"] == "mdnsHostName":
serial_number = config_item["value"]
else:
# Attempt 2: try to use the local mqtt broker (newer generation) to resolve host to serialnumber
smappee_mqtt = mqtt.SmappeeLocalMqtt()
connect = await self.hass.async_add_executor_job(smappee_mqtt.start_attempt)
if not connect:
return self.async_abort(reason="cannot_connect")
serial_number = await self.hass.async_add_executor_job(
smappee_mqtt.start_and_wait_for_config
)
await self.hass.async_add_executor_job(smappee_mqtt.stop)
if serial_number is None:
return self.async_abort(reason="cannot_connect")
if serial_number is None or not serial_number.startswith(
SUPPORTED_LOCAL_DEVICES
):
return self.async_abort(reason="invalid_mdns")
serial_number = serial_number.replace("Smappee", "")
# Check if already configured (local)
await self.async_set_unique_id(serial_number, raise_on_progress=False)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=f"{DOMAIN}{serial_number}",
data={CONF_IP_ADDRESS: ip_address, CONF_SERIALNUMBER: serial_number},
)
def is_cloud_device_already_added(self):
"""Check if a CLOUD device has already been added."""
for entry in self._async_current_entries():
if entry.unique_id is not None and entry.unique_id == f"{DOMAIN}Cloud":
return True
return False