From a53b0f75a16eb0d93d37542ee3fe14f50d4d5950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 17 Apr 2020 19:42:59 +0200 Subject: [PATCH] =?UTF-8?q?Spring=20cleaning=20=E2=98=80=EF=B8=8F=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Spring cleaning * Actions * Fix branches * Changes for config_flow --- .devcontainer/Dockerfile | 17 -- .devcontainer/README.md | 53 ---- .devcontainer/configuration.yaml | 17 +- .devcontainer/custom_component_helper | 26 -- .devcontainer/devcontainer.json | 17 +- .devcontainer/images/reopen.png | Bin 8177 -> 0 bytes .gitattributes | 1 + .github/settings.yml | 23 -- .../workflows/{hassfest.yaml => cron.yaml} | 9 +- .github/workflows/pull.yml | 23 ++ .github/workflows/push.yml | 26 ++ .gitignore | 1 + .vscode/tasks.json | 54 +--- LICENSE | 2 +- README.md | 53 +--- .../blueprint/.translations/en.json | 14 +- .../blueprint/.translations/nb.json | 14 +- custom_components/blueprint/__init__.py | 277 +++++------------- custom_components/blueprint/binary_sensor.py | 70 +---- custom_components/blueprint/config_flow.py | 94 +++--- custom_components/blueprint/const.py | 37 ++- custom_components/blueprint/entity.py | 54 ++++ custom_components/blueprint/manifest.json | 3 +- custom_components/blueprint/sensor.py | 67 +---- custom_components/blueprint/switch.py | 71 +---- hacs.json | 5 + info.md | 50 +--- requirements.txt | 2 - 28 files changed, 349 insertions(+), 731 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/README.md delete mode 100644 .devcontainer/custom_component_helper delete mode 100644 .devcontainer/images/reopen.png create mode 100644 .gitattributes delete mode 100644 .github/settings.yml rename .github/workflows/{hassfest.yaml => cron.yaml} (51%) create mode 100644 .github/workflows/pull.yml create mode 100644 .github/workflows/push.yml create mode 100644 .gitignore create mode 100644 custom_components/blueprint/entity.py create mode 100644 hacs.json delete mode 100644 requirements.txt diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index ef8a9b4..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM python:3.7 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - git \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -RUN python -m pip install --upgrade colorlog black pylint -RUN python -m pip install --upgrade git+https://github.com/home-assistant/home-assistant@dev -RUN cd && mkdir -p /config/custom_components - - -WORKDIR /workspace - -# Set the default shell to bash instead of sh -ENV SHELL /bin/bash \ No newline at end of file diff --git a/.devcontainer/README.md b/.devcontainer/README.md deleted file mode 100644 index f52282a..0000000 --- a/.devcontainer/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Devcontainer - -_The easiest way to contribute to and/or test this repository._ - -## Requirements - -- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -- [docker](https://docs.docker.com/install/) -- [VS Code](https://code.visualstudio.com/) -- [Remote - Containers (VSC Extention)](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) - -[More info about requirements and devcontainer in general](https://code.visualstudio.com/docs/remote/containers#_getting-started) - -## How to use Devcontainer for development/test - -1. Make sure your computer meets the requirements. -1. Fork this repository. -1. Clone the repository to your computer. -1. Open the repository using VS Code. - -When you open this repository with VSCode and your computer meets the requirements you are asked to "Reopen in Container", do that. - -![reopen](images/reopen.png) - -If you don't see this notification, open the command pallet (ctrl+shift+p) and select `Remote-Containers: Reopen Folder in Container`. - -_It will now build the devcontainer._ - -The container have some "tasks" to help you testing your changes. - -## Custom Tasks in this repository - -_Start "tasks" by opening the the command pallet (ctrl+shift+p) and select `Tasks: Run Task`_ - -Running tasks like `Start Home Assistant on port 8124` can be restarted by opening the the command pallet (ctrl+shift+p) and select `Tasks: Restart Running Task`, then select the task you want to restart. - -### Start Home Assistant on port 8124 - -This will copy the configuration and the integration files to the expected location in the container. - -And start up Home Assistant on [port 8124.](http://localhost:8124) - -### Upgrade Home Assistant to latest dev - -This will upgrade Home Assistant to the latest dev version. - -### Set Home Assistant Version - -This allows you to specify a version of Home Assistant to install inside the devcontainer. - -### Home Assistant Config Check - -This runs a config check to make sure your config is valid. diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index adbd88c..e693ddc 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -2,19 +2,4 @@ default_config: logger: default: error logs: - custom_components.blueprint: debug - - - -blueprint: - username: my_username - password: my_password - binary_sensor: - - enabled: true - name: My custom name - sensor: - - enabled: true - name: My custom name - switch: - - enabled: true - name: My custom name \ No newline at end of file + custom_components.blueprint: debug \ No newline at end of file diff --git a/.devcontainer/custom_component_helper b/.devcontainer/custom_component_helper deleted file mode 100644 index 189d759..0000000 --- a/.devcontainer/custom_component_helper +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -function StartHomeAssistant { - echo "Copy configuration.yaml" - cp -f .devcontainer/configuration.yaml /config || echo ".devcontainer/configuration.yaml are missing!" exit 1 - - echo "Copy the custom component" - rm -R /config/custom_components/ || echo "" - cp -r custom_components /config/custom_components/ || echo "Could not copy the custom_component" exit 1 - - echo "Start Home Assistant" - hass -c /config -} - -function UpdgradeHomeAssistantDev { - python -m pip install --upgrade git+https://github.com/home-assistant/home-assistant@dev -} - -function SetHomeAssistantVersion { - read -p 'Version: ' version - python -m pip install --upgrade homeassistant==$version -} - -function HomeAssistantConfigCheck { - hass -c /config --script check_config -} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 906fb42..46a77f9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,25 @@ // See https://aka.ms/vscode-remote/devcontainer.json for format details. { + "image": "ludeeus/container:integration", "context": "..", - "dockerFile": "Dockerfile", - "appPort": "8124:8123", + "appPort": [ + "9123:8123" + ], + "postCreateCommand": "dc install", "runArgs": [ - "-e", - "GIT_EDTIOR='code --wait'" + "-v", + "${env:HOME}${env:USERPROFILE}/.ssh:/tmp/.ssh" ], "extensions": [ "ms-python.python", + "github.vscode-pull-request-github", "tabnine.tabnine-vscode" ], "settings": { - "python.pythonPath": "/usr/local/bin/python", + "files.eol": "\n", + "editor.tabSize": 4, + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/bin/python3", "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.formatting.provider": "black", diff --git a/.devcontainer/images/reopen.png b/.devcontainer/images/reopen.png deleted file mode 100644 index cbcec3c7b87925be0a0e3c6c7db5c32d03380b1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8177 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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/settings.yml b/.github/settings.yml deleted file mode 100644 index 717c121..0000000 --- a/.github/settings.yml +++ /dev/null @@ -1,23 +0,0 @@ -repository: - private: false - has_issues: true - has_projects: false - has_wiki: false - has_downloads: false - default_branch: master - allow_squash_merge: true - allow_merge_commit: false - allow_rebase_merge: false -labels: - - name: "Feature Request" - color: "fbca04" - - name: "Bug" - color: "b60205" - - name: "Wont Fix" - color: "ffffff" - - name: "Enhancement" - color: a2eeef - - name: "Documentation" - color: "008672" - - name: "Stale" - color: "930191" \ No newline at end of file diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/cron.yaml similarity index 51% rename from .github/workflows/hassfest.yaml rename to .github/workflows/cron.yaml index cf71cc2..ea26a76 100644 --- a/.github/workflows/hassfest.yaml +++ b/.github/workflows/cron.yaml @@ -1,14 +1,13 @@ -name: Validate with hassfest +name: Cron actions on: - push: - pull_request: schedule: - cron: '0 0 * * *' jobs: - validate: + hassfest: runs-on: "ubuntu-latest" + name: Validate with hassfest steps: - uses: "actions/checkout@v2" - - uses: home-assistant/actions/hassfest@master \ No newline at end of file + - uses: "home-assistant/actions/hassfest@master" \ No newline at end of file diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml new file mode 100644 index 0000000..437049d --- /dev/null +++ b/.github/workflows/pull.yml @@ -0,0 +1,23 @@ +name: Pull actions + +on: + pull_request: + +jobs: + hassfest: + runs-on: "ubuntu-latest" + name: Validate with hassfest + steps: + - uses: "actions/checkout@v2" + - uses: "home-assistant/actions/hassfest@master" + + style: + runs-on: "ubuntu-latest" + name: Check style formatting + steps: + - uses: "actions/checkout@v2" + - uses: "actions/setup-python@v1" + with: + python-version: "3.x" + - run: python3 -m pip install black + - run: black . \ No newline at end of file diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..aacb233 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,26 @@ +name: Push actions + +on: + push: + branches: + - master + - dev + +jobs: + hassfest: + runs-on: "ubuntu-latest" + name: Validate with hassfest + steps: + - uses: "actions/checkout@v2" + - uses: "home-assistant/actions/hassfest@master" + + style: + runs-on: "ubuntu-latest" + name: Check style formatting + steps: + - uses: "actions/checkout@v2" + - uses: "actions/setup-python@v1" + with: + python-version: "3.x" + - run: python3 -m pip install black + - run: black . \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 31504d9..ad1c9bf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,59 +2,27 @@ "version": "2.0.0", "tasks": [ { - "label": "Start Home Assistant on port 8124", + "label": "Run Home Assistant on port 9123", "type": "shell", - "command": "source .devcontainer/custom_component_helper && StartHomeAssistant", - "group": { - "kind": "test", - "isDefault": true, - }, - "presentation": { - "reveal": "always", - "panel": "new" - }, + "command": "dc start", + "problemMatcher": [] + }, + { + "label": "Run Home Assistant configuration against /config", + "type": "shell", + "command": "dc check", "problemMatcher": [] }, { "label": "Upgrade Home Assistant to latest dev", "type": "shell", - "command": "source .devcontainer/custom_component_helper && UpdgradeHomeAssistantDev", - "group": { - "kind": "test", - "isDefault": true, - }, - "presentation": { - "reveal": "always", - "panel": "new" - }, + "command": "dc install", "problemMatcher": [] }, { - "label": "Set Home Assistant Version", + "label": "Install a spesific version of Home Assistant", "type": "shell", - "command": "source .devcontainer/custom_component_helper && SetHomeAssistantVersion", - "group": { - "kind": "test", - "isDefault": true, - }, - "presentation": { - "reveal": "always", - "panel": "new" - }, - "problemMatcher": [] - }, - { - "label": "Home Assistant Config Check", - "type": "shell", - "command": "source .devcontainer/custom_component_helper && HomeAssistantConfigCheck", - "group": { - "kind": "test", - "isDefault": true, - }, - "presentation": { - "reveal": "always", - "panel": "new" - }, + "command": "dc set-version", "problemMatcher": [] } ] diff --git a/LICENSE b/LICENSE index 8d266b9..8a418c6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Joakim Sørensen @ludeeus +Copyright (c) 2020 Joakim Sørensen @ludeeus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b552046..5f9716a 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,7 @@ Platform | Description 4. Download _all_ the files from the `custom_components/blueprint/` directory (folder) in this repository. 5. Place the files you downloaded in the new directory (folder) you created. 6. Restart Home Assistant -7. Choose: - - Add `blueprint:` to your HA configuration. - - In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Blueprint" +7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Blueprint" Using your HA configuration directory (folder) as a starting point you should now also have this: @@ -98,54 +96,9 @@ custom_components/blueprint/sensor.py custom_components/blueprint/switch.py ``` -## Example configuration.yaml +## Configuration is done in the UI -```yaml -blueprint: - username: my_username - password: my_password - binary_sensor: - - enabled: true - name: My custom name - sensor: - - enabled: true - name: My custom name - switch: - - enabled: true - name: My custom name -``` - -## Configuration options - -Key | Type | Required | Description --- | -- | -- | -- -`username` | `string` | `False` | Username for the client. -`password` | `string` | `False` | Password for the client. -`binary_sensor` | `list` | `False` | Configuration for the `binary_sensor` platform. -`sensor` | `list` | `False` | Configuration for the `sensor` platform. -`switch` | `list` | `False` | Configuration for the `switch` platform. - -### Configuration options for `binary_sensor` list - -Key | Type | Required | Default | Description --- | -- | -- | -- | -- -`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform. -`name` | `string` | `False` | `blueprint` | Custom name for the entity. - -### Configuration options for `sensor` list - -Key | Type | Required | Default | Description --- | -- | -- | -- | -- -`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform. -`name` | `string` | `False` | `blueprint` | Custom name for the entity. - - -### Configuration options for `switch` list - -Key | Type | Required | Default | Description --- | -- | -- | -- | -- -`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform. -`name` | `string` | `False` | `blueprint` | Custom name for the entity. + ## Contributions are welcome! diff --git a/custom_components/blueprint/.translations/en.json b/custom_components/blueprint/.translations/en.json index c9aeae6..a266873 100644 --- a/custom_components/blueprint/.translations/en.json +++ b/custom_components/blueprint/.translations/en.json @@ -13,9 +13,17 @@ }, "error": { "auth": "Username/Password is wrong." - }, - "abort": { - "single_instance_allowed": "Only a single configuration of Blueprint is allowed." + } + }, + "options": { + "step": { + "user": { + "data": { + "binary_sensor": "Binary sensor enabled", + "sensor": "Sensor enabled", + "switch": "Switch enabled" + } + } } } } \ No newline at end of file diff --git a/custom_components/blueprint/.translations/nb.json b/custom_components/blueprint/.translations/nb.json index 3b47f66..6151e02 100644 --- a/custom_components/blueprint/.translations/nb.json +++ b/custom_components/blueprint/.translations/nb.json @@ -13,9 +13,17 @@ }, "error": { "auth": "Brukernavn/Passord er feil." - }, - "abort": { - "single_instance_allowed": "Du kan konfigurere Blueprint kun en gang." + } + }, + "options": { + "step": { + "user": { + "data": { + "binary_sensor": "Binær sensor aktivert", + "sensor": "Sensor aktivert", + "switch": "Bryter aktivert" + } + } } } } \ No newline at end of file diff --git a/custom_components/blueprint/__init__.py b/custom_components/blueprint/__init__.py index 4528d63..3c3b94e 100644 --- a/custom_components/blueprint/__init__.py +++ b/custom_components/blueprint/__init__.py @@ -1,244 +1,107 @@ """ -Component to integrate with blueprint. +Custom integration to integrate blueprint with Home Assistant. -For more details about this component, please refer to +For more details about this integration, please refer to https://github.com/custom-components/blueprint """ -import os -from datetime import timedelta +import asyncio 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 +from datetime import timedelta +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import Config, HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from sampleclient.client import Client -from integrationhelper.const import CC_STARTUP_VERSION from .const import ( - CONF_BINARY_SENSOR, - CONF_ENABLED, - CONF_NAME, CONF_PASSWORD, - CONF_SENSOR, - CONF_SWITCH, CONF_USERNAME, - DEFAULT_NAME, - DOMAIN_DATA, DOMAIN, - ISSUE_URL, PLATFORMS, - REQUIRED_FILES, - VERSION, + STARTUP_MESSAGE, ) -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) +SCAN_INTERVAL = timedelta(seconds=30) _LOGGER = logging.getLogger(__name__) -BINARY_SENSOR_SCHEMA = vol.Schema( - { - vol.Optional(CONF_ENABLED, default=True): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } -) -SENSOR_SCHEMA = vol.Schema( - { - vol.Optional(CONF_ENABLED, default=True): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } -) - -SWITCH_SCHEMA = vol.Schema( - { - vol.Optional(CONF_ENABLED, default=True): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } -) - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_BINARY_SENSOR): vol.All( - cv.ensure_list, [BINARY_SENSOR_SCHEMA] - ), - vol.Optional(CONF_SENSOR): vol.All(cv.ensure_list, [SENSOR_SCHEMA]), - vol.Optional(CONF_SWITCH): vol.All(cv.ensure_list, [SWITCH_SCHEMA]), - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass, config): - """Set up this component using YAML.""" - if config.get(DOMAIN) is None: - # We get here 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) - ) - - # 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[DOMAIN].get(CONF_USERNAME) - password = config[DOMAIN].get(CONF_PASSWORD) - - # Configure the client. - client = Client(username, password) - hass.data[DOMAIN_DATA]["client"] = BlueprintData(hass, client) - - # Load platforms - for platform in PLATFORMS: - # Get platform specific configuration - platform_config = config[DOMAIN].get(platform, {}) - - # If platform is not enabled, skip. - if not platform_config: - continue - - for entry in platform_config: - entry_config = entry - - # If entry is not enabled, skip. - if not entry_config[CONF_ENABLED]: - continue - - hass.async_create_task( - discovery.async_load_platform( - hass, platform, DOMAIN, entry_config, config - ) - ) - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} - ) - ) +async def async_setup(hass: HomeAssistant, config: Config): + """Set up this integration using YAML is not supported.""" return True -async def async_setup_entry(hass, config_entry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """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) + if hass.data.get(DOMAIN) is None: + hass.data.setdefault(DOMAIN, {}) + _LOGGER.info(STARTUP_MESSAGE) + + username = entry.data.get(CONF_USERNAME) + password = entry.data.get(CONF_PASSWORD) + + coordinator = BlueprintDataUpdateCoordinator( + hass, username=username, password=password + ) + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][entry.entry_id] = coordinator + + for platform in PLATFORMS: + if entry.options.get(platform, True): + coordinator.platforms.append(platform) + hass.async_add_job( + hass.config_entries.async_forward_entry_setup(entry, platform) ) - 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") - ) + entry.add_update_listener(async_reload_entry) return True -class BlueprintData: - """This class handle communication and stores the data.""" +class BlueprintDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the API.""" - def __init__(self, hass, client): - """Initialize the class.""" - self.hass = hass - self.client = client + def __init__(self, hass, username, password): + """Initialize.""" + self.api = Client(username, password) + self.platforms = [] - @Throttle(MIN_TIME_BETWEEN_UPDATES) - async def update_data(self): - """Update data.""" - # This is where the main logic to update platform data goes. + super().__init__( + hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL, + ) + + async def _async_update_data(self): + """Update data via library.""" try: - data = self.client.get_data() - self.hass.data[DOMAIN_DATA]["data"] = data - except Exception as error: # pylint: disable=broad-except - _LOGGER.error("Could not update data - %s", error) + data = await self.api.async_get_data() + return data.get("data", {}) + except Exception as exception: + raise UpdateFailed(exception) -async def check_files(hass): - """Return bool that indicates if all files are present.""" - # Verify that the user downloaded all files. - base = f"{hass.config.path()}/custom_components/{DOMAIN}/" - missing = [] - for file in REQUIRED_FILES: - fullpath = "{}{}".format(base, file) - if not os.path.exists(fullpath): - missing.append(file) - - if missing: - _LOGGER.critical("The following files are missing: %s", str(missing)) - returnvalue = False - else: - returnvalue = True - - return returnvalue - - -async def async_remove_entry(hass, config_entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Handle removal of an entry.""" - try: - await hass.config_entries.async_forward_entry_unload( - config_entry, "binary_sensor" + coordinator = hass.data[DOMAIN][entry.entry_id] + unloaded = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + if platform in coordinator.platforms + ] ) - _LOGGER.info( - "Successfully removed binary_sensor from the blueprint integration" - ) - except ValueError: - pass + ) + if unloaded: + hass.data[DOMAIN].pop(entry.entry_id) - try: - await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") - _LOGGER.info("Successfully removed sensor from the blueprint integration") - except ValueError: - pass + return unloaded - try: - await hass.config_entries.async_forward_entry_unload(config_entry, "switch") - _LOGGER.info("Successfully removed switch from the blueprint integration") - except ValueError: - pass + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Reload config entry.""" + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) diff --git a/custom_components/blueprint/binary_sensor.py b/custom_components/blueprint/binary_sensor.py index b83ba77..ebb8411 100644 --- a/custom_components/blueprint/binary_sensor.py +++ b/custom_components/blueprint/binary_sensor.py @@ -1,73 +1,28 @@ """Binary sensor platform for blueprint.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from .const import ( - ATTRIBUTION, + +from custom_components.blueprint.const import ( + BINARY_SENSOR, BINARY_SENSOR_DEVICE_CLASS, DEFAULT_NAME, - DOMAIN_DATA, DOMAIN, ) +from custom_components.blueprint.entity import BlueprintEntity -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None -): # pylint: disable=unused-argument +async def async_setup_entry(hass, entry, async_add_devices): """Setup binary_sensor platform.""" - async_add_entities([BlueprintBinarySensor(hass, discovery_info)], True) + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_devices([BlueprintBinarySensor(coordinator, entry)]) -async def async_setup_entry(hass, config_entry, async_add_devices): - """Setup sensor platform.""" - async_add_devices([BlueprintBinarySensor(hass, {})], True) - - -class BlueprintBinarySensor(BinarySensorDevice): +class BlueprintBinarySensor(BlueprintEntity, BinarySensorDevice): """blueprint binary_sensor class.""" - def __init__(self, hass, config): - self.hass = hass - self.attr = {} - self._status = False - self._name = config.get("name", DEFAULT_NAME) - - async def async_update(self): - """Update the binary_sensor.""" - # Send update "signal" to the component - await self.hass.data[DOMAIN_DATA]["client"].update_data() - - # Get new data (if any) - updated = self.hass.data[DOMAIN_DATA]["data"].get("data", {}) - - # Check the data and update the value. - if updated.get("bool_on") is None: - self._status = self._status - else: - self._status = updated.get("bool_on") - - # Set/update attributes - self.attr["attribution"] = ATTRIBUTION - 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 hard 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.""" - return self._name + return f"{DEFAULT_NAME}_{BINARY_SENSOR}" @property def device_class(self): @@ -77,9 +32,4 @@ class BlueprintBinarySensor(BinarySensorDevice): @property def is_on(self): """Return true if the binary_sensor is on.""" - return self._status - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self.attr + return self.coordinator.data.get("bool_on", False) diff --git a/custom_components/blueprint/config_flow.py b/custom_components/blueprint/config_flow.py index 92a0e0e..567e351 100644 --- a/custom_components/blueprint/config_flow.py +++ b/custom_components/blueprint/config_flow.py @@ -4,12 +4,17 @@ from collections import OrderedDict import voluptuous as vol from sampleclient.client import Client from homeassistant import config_entries +from homeassistant.core import callback -from .const import DOMAIN +from custom_components.blueprint.const import ( + DOMAIN, + CONF_PASSWORD, + CONF_USERNAME, + PLATFORMS, +) -@config_entries.HANDLERS.register(DOMAIN) -class BlueprintFlowHandler(config_entries.ConfigFlow): +class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for Blueprint.""" VERSION = 1 @@ -24,17 +29,14 @@ class BlueprintFlowHandler(config_entries.ConfigFlow): ): # 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"] + user_input[CONF_USERNAME], user_input[CONF_PASSWORD] ) if valid: - return self.async_create_entry(title="", data=user_input) + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) else: self._errors["base"] = "auth" @@ -42,42 +44,62 @@ class BlueprintFlowHandler(config_entries.ConfigFlow): return await self._show_config_form(user_input) + @staticmethod + @callback + def async_get_options_flow(config_entry): + return BlueprintOptionsFlowHandler(config_entry) + 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 + step_id="user", + data_schema=vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str,} + ), + 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() + await client.async_get_data() return True except Exception: # pylint: disable=broad-except pass return False + + +class BlueprintOptionsFlowHandler(config_entries.OptionsFlow): + """Blueprint config flow options handler.""" + + def __init__(self, config_entry): + """Initialize HACS options flow.""" + self.config_entry = config_entry + self.options = dict(config_entry.options) + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_user() + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if user_input is not None: + self.options.update(user_input) + return await self._update_options() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(x, default=self.options.get(x, True)): bool + for x in sorted(PLATFORMS) + } + ), + ) + + async def _update_options(self): + """Update config entry options.""" + return self.async_create_entry( + title=self.config_entry.data.get(CONF_USERNAME), data=self.options + ) diff --git a/custom_components/blueprint/const.py b/custom_components/blueprint/const.py index 45d6e9c..86530cb 100644 --- a/custom_components/blueprint/const.py +++ b/custom_components/blueprint/const.py @@ -1,20 +1,11 @@ """Constants for blueprint.""" # Base component constants +NAME = "Blueprint" DOMAIN = "blueprint" DOMAIN_DATA = f"{DOMAIN}_data" VERSION = "0.0.1" -PLATFORMS = ["binary_sensor", "sensor", "switch"] -REQUIRED_FILES = [ - ".translations/en.json", - "binary_sensor.py", - "const.py", - "config_flow.py", - "manifest.json", - "sensor.py", - "switch.py", -] + ISSUE_URL = "https://github.com/custom-components/blueprint/issues" -ATTRIBUTION = "Data from this is provided by blueprint." # Icons ICON = "mdi:format-quote-close" @@ -22,14 +13,28 @@ ICON = "mdi:format-quote-close" # Device classes BINARY_SENSOR_DEVICE_CLASS = "connectivity" -# Configuration -CONF_BINARY_SENSOR = "binary_sensor" -CONF_SENSOR = "sensor" -CONF_SWITCH = "switch" +# Platforms +BINARY_SENSOR = "binary_sensor" +SENSOR = "sensor" +SWITCH = "switch" +PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH] + + +# Configuration and options CONF_ENABLED = "enabled" -CONF_NAME = "name" CONF_USERNAME = "username" CONF_PASSWORD = "password" # Defaults DEFAULT_NAME = DOMAIN + + +STARTUP_MESSAGE = f""" +------------------------------------------------------------------- +{NAME} +Version: {VERSION} +This is a custom integration! +If you have any issues with this you need to open an issue here: +{ISSUE_URL} +------------------------------------------------------------------- +""" diff --git a/custom_components/blueprint/entity.py b/custom_components/blueprint/entity.py new file mode 100644 index 0000000..5c181d6 --- /dev/null +++ b/custom_components/blueprint/entity.py @@ -0,0 +1,54 @@ +"""BlueprintEntity class""" +from homeassistant.helpers import entity + +from custom_components.blueprint.const import DOMAIN, VERSION, NAME + + +class BlueprintEntity(entity.Entity): + def __init__(self, coordinator, config_entry): + self.coordinator = coordinator + self.config_entry = config_entry + + @property + def should_poll(self): + """No need to poll. Coordinator notifies entity of updates.""" + return False + + @property + def available(self): + """Return if entity is available.""" + return self.coordinator.last_update_success + + @property + def unique_id(self): + """Return a unique ID to use for this entity.""" + return self.config_entry.entry_id + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": NAME, + "model": VERSION, + "manufacturer": NAME, + } + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + "time": str(self.coordinator.data.get("time")), + "static": self.coordinator.data.get("static"), + } + + async def async_added_to_hass(self): + """Connect to dispatcher listening for entity data notifications.""" + self.coordinator.async_add_listener(self.async_write_ha_state) + + async def async_will_remove_from_hass(self): + """Disconnect from update signal.""" + self.coordinator.async_remove_listener(self.async_write_ha_state) + + async def async_update(self): + """Update Brother entity.""" + await self.coordinator.async_request_refresh() diff --git a/custom_components/blueprint/manifest.json b/custom_components/blueprint/manifest.json index 11a22d0..5456514 100644 --- a/custom_components/blueprint/manifest.json +++ b/custom_components/blueprint/manifest.json @@ -8,7 +8,6 @@ "@ludeeus" ], "requirements": [ - "sampleclient", - "integrationhelper" + "sampleclient" ] } \ No newline at end of file diff --git a/custom_components/blueprint/sensor.py b/custom_components/blueprint/sensor.py index ee21f82..db1891c 100644 --- a/custom_components/blueprint/sensor.py +++ b/custom_components/blueprint/sensor.py @@ -1,79 +1,28 @@ """Sensor platform for blueprint.""" -from homeassistant.helpers.entity import Entity -from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN +from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SENSOR +from custom_components.blueprint.entity import BlueprintEntity -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None -): # pylint: disable=unused-argument +async def async_setup_entry(hass, entry, async_add_devices): """Setup sensor platform.""" - async_add_entities([BlueprintSensor(hass, discovery_info)], True) + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_devices([BlueprintSensor(coordinator, entry)]) -async def async_setup_entry(hass, config_entry, async_add_devices): - """Setup sensor platform.""" - async_add_devices([BlueprintSensor(hass, {})], True) - - -class BlueprintSensor(Entity): +class BlueprintSensor(BlueprintEntity): """blueprint Sensor class.""" - def __init__(self, hass, config): - self.hass = hass - self.attr = {} - self._state = None - self._name = config.get("name", DEFAULT_NAME) - - async def async_update(self): - """Update the sensor.""" - # Send update "signal" to the component - await self.hass.data[DOMAIN_DATA]["client"].update_data() - - # Get new data (if any) - updated = self.hass.data[DOMAIN_DATA]["data"].get("data", {}) - - # Check the data and update the value. - if updated.get("static") is None: - self._state = self._state - else: - self._state = updated.get("static") - - # Set/update attributes - self.attr["attribution"] = ATTRIBUTION - 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 hard 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.""" - return self._name + return f"{DEFAULT_NAME}_{SENSOR}" @property def state(self): """Return the state of the sensor.""" - return self._state + return self.coordinator.data.get("static") @property def icon(self): """Return the icon of the sensor.""" return ICON - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self.attr diff --git a/custom_components/blueprint/switch.py b/custom_components/blueprint/switch.py index c5a058c..9ff0543 100644 --- a/custom_components/blueprint/switch.py +++ b/custom_components/blueprint/switch.py @@ -1,72 +1,34 @@ """Switch platform for blueprint.""" from homeassistant.components.switch import SwitchDevice -from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN + +from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SWITCH + +from custom_components.blueprint.entity import BlueprintEntity -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None -): # pylint: disable=unused-argument - """Setup switch platform.""" - async_add_entities([BlueprintBinarySwitch(hass, discovery_info)], True) - - -async def async_setup_entry(hass, config_entry, async_add_devices): +async def async_setup_entry(hass, entry, async_add_devices): """Setup sensor platform.""" - async_add_devices([BlueprintBinarySwitch(hass, {})], True) + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_devices([BlueprintBinarySwitch(coordinator, entry)]) -class BlueprintBinarySwitch(SwitchDevice): +class BlueprintBinarySwitch(BlueprintEntity, SwitchDevice): """blueprint switch class.""" - def __init__(self, hass, config): - self.hass = hass - self.attr = {} - self._status = False - self._name = config.get("name", DEFAULT_NAME) - - async def async_update(self): - """Update the switch.""" - # Send update "signal" to the component - await self.hass.data[DOMAIN_DATA]["client"].update_data() - - # Get new data (if any) - updated = self.hass.data[DOMAIN_DATA]["data"].get("data", {}) - - # Check the data and update the value. - self._status = self.hass.data[DOMAIN_DATA]["client"].client.something - - # Set/update attributes - self.attr["attribution"] = ATTRIBUTION - self.attr["time"] = str(updated.get("time")) - self.attr["static"] = updated.get("static") - async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument """Turn on the switch.""" - await self.hass.data[DOMAIN_DATA]["client"].client.change_something(True) + await self.coordinator.api.async_change_something(True) + await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs): # pylint: disable=unused-argument """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 hard code this, use something from the device/service. - - @property - def device_info(self): - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, - "manufacturer": "Blueprint", - } + await self.coordinator.api.async_change_something(False) + await self.coordinator.async_request_refresh() @property def name(self): """Return the name of the switch.""" - return self._name + return f"{DEFAULT_NAME}_{SWITCH}" @property def icon(self): @@ -76,9 +38,4 @@ class BlueprintBinarySwitch(SwitchDevice): @property def is_on(self): """Return true if the switch is on.""" - return self._status - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self.attr + return self.coordinator.api.something diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..e908271 --- /dev/null +++ b/hacs.json @@ -0,0 +1,5 @@ +{ + "name": "Blueprint", + "hacs": "0.19.0", + "homeassistant": "0.97.0" +} \ No newline at end of file diff --git a/info.md b/info.md index c893c7d..e4a2094 100644 --- a/info.md +++ b/info.md @@ -25,58 +25,14 @@ Platform | Description ## Installation 1. Click install. -1. Add `blueprint:` to your HA configuration. +1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Blueprint". {% endif %} -## Example configuration.yaml - -```yaml -blueprint: - username: my_username - password: my_password - binary_sensor: - - enabled: true - name: My custom name - sensor: - - enabled: true - name: My custom name - switch: - - enabled: true - name: My custom name -``` - -## Configuration options - -Key | Type | Required | Description --- | -- | -- | -- -`username` | `string` | `False` | Username for the client. -`password` | `string` | `False` | Password for the client. -`binary_sensor` | `list` | `False` | Configuration for the `binary_sensor` platform. -`sensor` | `list` | `False` | Configuration for the `sensor` platform. -`switch` | `list` | `False` | Configuration for the `switch` platform. - -### Configuration options for `binary_sensor` list - -Key | Type | Required | Default | Description --- | -- | -- | -- | -- -`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform. -`name` | `string` | `False` | `blueprint` | Custom name for the entity. - -### Configuration options for `sensor` list - -Key | Type | Required | Default | Description --- | -- | -- | -- | -- -`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform. -`name` | `string` | `False` | `blueprint` | Custom name for the entity. -### Configuration options for `switch` list - -Key | Type | Required | Default | Description --- | -- | -- | -- | -- -`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform. -`name` | `string` | `False` | `blueprint` | Custom name for the entity. +## Configuration is done in the UI + *** diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7019fc7..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -integrationhelper -sampleclient \ No newline at end of file