From db21a7688534ae1198c01531b283f4c9bdd7d780 Mon Sep 17 00:00:00 2001 From: ludeeus Date: Thu, 25 Jul 2019 09:42:40 +0000 Subject: [PATCH] Add support for config_flow configuration --- .../blueprint/.translations/en.json | 21 +++++ .../blueprint/.translations/nb.json | 21 +++++ custom_components/blueprint/__init__.py | 90 ++++++++++++++++++- custom_components/blueprint/binary_sensor.py | 28 +++++- custom_components/blueprint/config_flow.py | 83 +++++++++++++++++ custom_components/blueprint/manifest.json | 13 ++- custom_components/blueprint/sensor.py | 22 ++++- custom_components/blueprint/switch.py | 22 ++++- 8 files changed, 292 insertions(+), 8 deletions(-) create mode 100644 custom_components/blueprint/.translations/en.json create mode 100644 custom_components/blueprint/.translations/nb.json create mode 100644 custom_components/blueprint/config_flow.py diff --git a/custom_components/blueprint/.translations/en.json b/custom_components/blueprint/.translations/en.json new file mode 100644 index 0000000..c9aeae6 --- /dev/null +++ b/custom_components/blueprint/.translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "title": "Blueprint", + "step": { + "user": { + "title": "Blueprint", + "description": "If you need help with the configuration have a look here: https://github.com/custom-components/blueprint", + "data": { + "username": "Username", + "password": "Password" + } + } + }, + "error": { + "auth": "Username/Password is wrong." + }, + "abort": { + "single_instance_allowed": "Only a single configuration of Blueprint is allowed." + } + } +} \ No newline at end of file diff --git a/custom_components/blueprint/.translations/nb.json b/custom_components/blueprint/.translations/nb.json new file mode 100644 index 0000000..3b47f66 --- /dev/null +++ b/custom_components/blueprint/.translations/nb.json @@ -0,0 +1,21 @@ +{ + "config": { + "title": "Blueprint", + "step": { + "user": { + "title": "Blueprint", + "description": "Hvis du trenger hjep til konfigurasjon ta en titt her: https://github.com/custom-components/blueprint", + "data": { + "username": "Brukernavn", + "password": "Passord" + } + } + }, + "error": { + "auth": "Brukernavn/Passord er feil." + }, + "abort": { + "single_instance_allowed": "Du kan konfigurere Blueprint kun en gang." + } + } +} \ No newline at end of file diff --git a/custom_components/blueprint/__init__.py b/custom_components/blueprint/__init__.py index 151e462..e281e53 100644 --- a/custom_components/blueprint/__init__.py +++ b/custom_components/blueprint/__init__.py @@ -8,6 +8,7 @@ import os from datetime import timedelta import logging import voluptuous as vol +from homeassistant import config_entries import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery from homeassistant.util import Throttle @@ -76,9 +77,15 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): - """Set up this component.""" + """Set up this component using YAML.""" + if config.get(DOMAIN) is None: + # We get her if the integration is set up using config flow + return True + # Print startup message - _LOGGER.info(CC_STARTUP_VERSION.format(name=DOMAIN, version=VERSION, issue_link=ISSUE_URL)) + _LOGGER.info( + CC_STARTUP_VERSION.format(name=DOMAIN, version=VERSION, issue_link=ISSUE_URL) + ) # Check that all required files are present file_check = await check_files(hass) @@ -117,6 +124,60 @@ async def async_setup(hass, config): hass, platform, DOMAIN, entry_config, config ) ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} + ) + ) + return True + + +async def async_setup_entry(hass, config_entry): + """Set up this integration using UI.""" + conf = hass.data.get(DOMAIN_DATA) + if config_entry.source == config_entries.SOURCE_IMPORT: + if conf is None: + hass.async_create_task( + hass.config_entries.async_remove(config_entry.entry_id) + ) + return False + + # Print startup message + _LOGGER.info( + CC_STARTUP_VERSION.format(name=DOMAIN, version=VERSION, issue_link=ISSUE_URL) + ) + + # Check that all required files are present + file_check = await check_files(hass) + if not file_check: + return False + + # Create DATA dict + hass.data[DOMAIN_DATA] = {} + + # Get "global" configuration. + username = config_entry.data.get(CONF_USERNAME) + password = config_entry.data.get(CONF_PASSWORD) + + # Configure the client. + client = Client(username, password) + hass.data[DOMAIN_DATA]["client"] = BlueprintData(hass, client) + + # Add binary_sensor + hass.async_add_job( + hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") + ) + + # Add sensor + hass.async_add_job( + hass.config_entries.async_forward_entry_setup(config_entry, "sensor") + ) + + # Add switch + hass.async_add_job( + hass.config_entries.async_forward_entry_setup(config_entry, "switch") + ) + return True @@ -156,3 +217,28 @@ async def check_files(hass): returnvalue = True return returnvalue + + +async def async_remove_entry(hass, config_entry): + """Handle removal of an entry.""" + try: + await hass.config_entries.async_forward_entry_unload( + config_entry, "binary_sensor" + ) + _LOGGER.info( + "Successfully removed binary_sensor from the blueprint integration" + ) + except ValueError: + pass + + try: + await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") + _LOGGER.info("Successfully removed sensor from the blueprint integration") + except ValueError: + pass + + try: + await hass.config_entries.async_forward_entry_unload(config_entry, "switch") + _LOGGER.info("Successfully removed switch from the blueprint integration") + except ValueError: + pass diff --git a/custom_components/blueprint/binary_sensor.py b/custom_components/blueprint/binary_sensor.py index 5c92716..552995d 100644 --- a/custom_components/blueprint/binary_sensor.py +++ b/custom_components/blueprint/binary_sensor.py @@ -1,6 +1,12 @@ """Binary sensor platform for blueprint.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from .const import ATTRIBUTION, BINARY_SENSOR_DEVICE_CLASS, DEFAULT_NAME, DOMAIN_DATA +from .const import ( + ATTRIBUTION, + BINARY_SENSOR_DEVICE_CLASS, + DEFAULT_NAME, + DOMAIN_DATA, + DOMAIN, +) async def async_setup_platform( @@ -10,6 +16,11 @@ async def async_setup_platform( async_add_entities([BlueprintBinarySensor(hass, discovery_info)], True) +async def async_setup_entry(hass, config_entry, async_add_devices): + """Setup sensor platform.""" + async_add_devices([BlueprintBinarySensor(hass, {})], True) + + class BlueprintBinarySensor(BinarySensorDevice): """blueprint binary_sensor class.""" @@ -38,6 +49,21 @@ class BlueprintBinarySensor(BinarySensorDevice): self.attr["time"] = str(updated.get("time")) self.attr["static"] = updated.get("static") + @property + def unique_id(self): + """Return a unique ID to use for this binary_sensor.""" + return ( + "0919a0cd-745c-48fd" + ) # Don't had code this, use something from the device/service. + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "manufacturer": "Blueprint", + } + @property def name(self): """Return the name of the binary_sensor.""" diff --git a/custom_components/blueprint/config_flow.py b/custom_components/blueprint/config_flow.py new file mode 100644 index 0000000..92a0e0e --- /dev/null +++ b/custom_components/blueprint/config_flow.py @@ -0,0 +1,83 @@ +"""Adds config flow for Blueprint.""" +from collections import OrderedDict + +import voluptuous as vol +from sampleclient.client import Client +from homeassistant import config_entries + +from .const import DOMAIN + + +@config_entries.HANDLERS.register(DOMAIN) +class BlueprintFlowHandler(config_entries.ConfigFlow): + """Config flow for Blueprint.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize.""" + self._errors = {} + + async def async_step_user( + self, user_input={} + ): # pylint: disable=dangerous-default-value + """Handle a flow initialized by the user.""" + self._errors = {} + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + if self.hass.data.get(DOMAIN): + return self.async_abort(reason="single_instance_allowed") + + if user_input is not None: + valid = await self._test_credentials( + user_input["username"], user_input["password"] + ) + if valid: + return self.async_create_entry(title="", data=user_input) + else: + self._errors["base"] = "auth" + + return await self._show_config_form(user_input) + + return await self._show_config_form(user_input) + + async def _show_config_form(self, user_input): + """Show the configuration form to edit location data.""" + + # Defaults + username = "" + password = "" + + if user_input is not None: + if "username" in user_input: + username = user_input["username"] + if "password" in user_input: + password = user_input["password"] + + data_schema = OrderedDict() + data_schema[vol.Required("username", default=username)] = str + data_schema[vol.Required("password", default=password)] = str + return self.async_show_form( + step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors + ) + + async def async_step_import(self, user_input): # pylint: disable=unused-argument + """Import a config entry. + Special type of import, we're not actually going to store any data. + Instead, we're going to rely on the values that are in config file. + """ + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return self.async_create_entry(title="configuration.yaml", data={}) + + async def _test_credentials(self, username, password): + """Return true if credentials is valid.""" + try: + client = Client(username, password) + client.get_data() + return True + except Exception: # pylint: disable=broad-except + pass + return False diff --git a/custom_components/blueprint/manifest.json b/custom_components/blueprint/manifest.json index 4e36451..767d033 100644 --- a/custom_components/blueprint/manifest.json +++ b/custom_components/blueprint/manifest.json @@ -3,6 +3,13 @@ "name": "Blueprint", "documentation": "https://github.com/custom-components/blueprint", "dependencies": [], - "codeowners": ["@ludeeus"], - "requirements": ["sampleclient", "integrationhelper"] -} + "config_flow": true, + "codeowners": [ + "@ludeeus" + ], + "requirements": [ + "sampleclient", + "integrationhelper" + ], + "homeassistant": "0.96.0" +} \ No newline at end of file diff --git a/custom_components/blueprint/sensor.py b/custom_components/blueprint/sensor.py index 7eb53cb..5ea9cdd 100644 --- a/custom_components/blueprint/sensor.py +++ b/custom_components/blueprint/sensor.py @@ -1,6 +1,6 @@ """Sensor platform for blueprint.""" from homeassistant.helpers.entity import Entity -from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON +from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN async def async_setup_platform( @@ -10,6 +10,11 @@ async def async_setup_platform( async_add_entities([BlueprintSensor(hass, discovery_info)], True) +async def async_setup_entry(hass, config_entry, async_add_devices): + """Setup sensor platform.""" + async_add_devices([BlueprintSensor(hass, {})], True) + + class BlueprintSensor(Entity): """blueprint Sensor class.""" @@ -38,6 +43,21 @@ class BlueprintSensor(Entity): self.attr["time"] = str(updated.get("time")) self.attr["none"] = updated.get("none") + @property + def unique_id(self): + """Return a unique ID to use for this sensor.""" + return ( + "0717a0cd-745c-48fd" + ) # Don't had code this, use something from the device/service. + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "manufacturer": "Blueprint", + } + @property def name(self): """Return the name of the sensor.""" diff --git a/custom_components/blueprint/switch.py b/custom_components/blueprint/switch.py index 051de45..d44c825 100644 --- a/custom_components/blueprint/switch.py +++ b/custom_components/blueprint/switch.py @@ -1,6 +1,6 @@ """Switch platform for blueprint.""" from homeassistant.components.switch import SwitchDevice -from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON +from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN async def async_setup_platform( @@ -10,6 +10,11 @@ async def async_setup_platform( async_add_entities([BlueprintBinarySwitch(hass, discovery_info)], True) +async def async_setup_entry(hass, config_entry, async_add_devices): + """Setup sensor platform.""" + async_add_devices([BlueprintBinarySwitch(hass, {})], True) + + class BlueprintBinarySwitch(SwitchDevice): """blueprint switch class.""" @@ -43,6 +48,21 @@ class BlueprintBinarySwitch(SwitchDevice): """Turn off the switch.""" await self.hass.data[DOMAIN_DATA]["client"].client.change_something(False) + @property + def unique_id(self): + """Return a unique ID to use for this switch.""" + return ( + "0818a0cd-745c-48fd" + ) # Don't had code this, use something from the device/service. + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "manufacturer": "Blueprint", + } + @property def name(self): """Return the name of the switch."""