Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config_flow for configuration via Integrations UI #28

Merged
merged 1 commit into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions custom_components/solarman/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
"""The Solarman Collector integration."""

import logging

from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME

from .const import *

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[str] = ["sensor"]

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Solarman Collector from a config entry."""
_LOGGER.debug(f'__init__.py:async_setup_entry({entry.as_dict()})')
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug(f'__init__.py:async_unload_entry({entry.as_dict()})')
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok


async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
_LOGGER.debug(f'__init__.py:update_listener({entry.as_dict()})')
hass.data[DOMAIN][entry.entry_id].config(entry)
entry.title = entry.options[CONF_NAME]
159 changes: 159 additions & 0 deletions custom_components/solarman/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""Config flow for SolarMAN logger integration."""
from __future__ import annotations

import logging
from typing import Any
from socket import getaddrinfo, herror, gaierror, timeout

import voluptuous as vol
from voluptuous.schema_builder import Schema

from homeassistant import config_entries
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError

from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME, CONF_SCAN_INTERVAL)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from .const import *

_LOGGER = logging.getLogger(__name__)


def step_user_data_schema(data: dict[str, Any] = {CONF_NAME: SENSOR_PREFIX, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE}) -> Schema:
_LOGGER.debug(f'config_flow.py:step_user_data_schema: {data}')
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME, default=data.get(CONF_NAME)): str,
vol.Required(CONF_INVERTER_HOST, default=data.get(CONF_INVERTER_HOST)): str,
vol.Required(CONF_INVERTER_PORT, default=data.get(CONF_INVERTER_PORT)): int,
vol.Required(CONF_INVERTER_SERIAL, default=data.get(CONF_INVERTER_SERIAL)): int,
vol.Optional(CONF_LOOKUP_FILE, default=data.get(CONF_LOOKUP_FILE)): vol.In(LOOKUP_FILES),
},
extra=vol.PREVENT_EXTRA
)
_LOGGER.debug(
f'config_flow.py:step_user_data_schema: STEP_USER_DATA_SCHEMA: {STEP_USER_DATA_SCHEMA}')
return STEP_USER_DATA_SCHEMA


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""
Validate the user input allows us to connect.

Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""

_LOGGER.debug(f'config_flow.py:validate_input: {data}')

try:
getaddrinfo(
data[CONF_INVERTER_HOST], data[CONF_INVERTER_PORT], family=0, type=0, proto=0, flags=0
)
except herror:
raise InvalidHost
except gaierror:
raise CannotConnect
except timeout:
raise CannotConnect

return {"title": data[CONF_INVERTER_HOST]}


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SolarMAN logger."""

VERSION = 1

@staticmethod
@callback
def async_get_options_flow(entry: config_entries.ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
_LOGGER.debug(f'config_flow.py:ConfigFlow.async_get_options_flow: {entry}')
return OptionsFlow(entry)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
_LOGGER.debug(f'config_flow.py:ConfigFlow.async_step_user: {user_input}')
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=step_user_data_schema()
)

errors = {}

try:
info = await validate_input(self.hass, user_input)
except InvalidHost:
errors["base"] = "invalid_host"
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
_LOGGER.debug(f'config_flow.py:ConfigFlow.async_step_user: validation passed: {user_input}')
# await self.async_set_unique_id(user_input.device_id) # not sure this is permitted as the user can change the device_id
# self._abort_if_unique_id_configured()
return self.async_create_entry(
title=info["title"], data=user_input, options=user_input
)

_LOGGER.debug(f'config_flow.py:ConfigFlow.async_step_user: validation failed: {user_input}')

return self.async_show_form(
step_id="user",
data_schema=step_user_data_schema(user_input),
errors=errors,
)


class OptionsFlow(config_entries.OptionsFlow):
"""Handle options."""

def __init__(self, entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
_LOGGER.debug(f'config_flow.py:OptionsFlow.__init__: {entry}')
self.entry = entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
_LOGGER.debug(f'config_flow.py:OptionsFlow.async_step_init: {user_input}')
if user_input is None:
return self.async_show_form(
step_id="init",
data_schema=step_user_data_schema(self.entry.options),
)

errors = {}

try:
info = await validate_input(self.hass, user_input)
except InvalidHost:
errors["base"] = "invalid_host"
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=user_input)

return self.async_show_form(
step_id="init",
data_schema=step_user_data_schema(user_input),
errors=errors,
)


class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidHost(HomeAssistantError):
"""Error to indicate there is invalid hostname or IP address."""
4 changes: 4 additions & 0 deletions custom_components/solarman/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from datetime import timedelta

DOMAIN = 'solarman'

DEFAULT_PORT_INVERTER = 8899
DEFAULT_LOOKUP_FILE = 'parameters.yaml'
LOOKUP_FILES = ['parameters.yaml', 'sofar_lsw3.yaml', 'deye_string.yaml']

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)

CONF_INVERTER_HOST = 'inverter_host'
Expand Down
1 change: 1 addition & 0 deletions custom_components/solarman/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"name": "Solarman",
"documentation": "https://github.com/StephanJoubert/home_assistant_solarman/blob/main/README.md",
"version": "1.0.0",
"config_flow": true,
"dependencies": [],
"codeowners": ["@StephanJoubert"],
"issue_tracker": "https://github.com/StephanJoubert/home_assistant_solarman/issues",
Expand Down
Loading