commit 20afa2245f2b74099ad7165c4967fa9c5dc3aea6 Author: Yordan Suarez Date: Tue Dec 17 15:55:07 2019 -0500 Initial commit diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..ef8a9b4 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..f52282a --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,53 @@ +# 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 new file mode 100644 index 0000000..adbd88c --- /dev/null +++ b/.devcontainer/configuration.yaml @@ -0,0 +1,20 @@ +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 diff --git a/.devcontainer/custom_component_helper b/.devcontainer/custom_component_helper new file mode 100644 index 0000000..189d759 --- /dev/null +++ b/.devcontainer/custom_component_helper @@ -0,0 +1,26 @@ +#!/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 new file mode 100644 index 0000000..906fb42 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +// See https://aka.ms/vscode-remote/devcontainer.json for format details. +{ + "context": "..", + "dockerFile": "Dockerfile", + "appPort": "8124:8123", + "runArgs": [ + "-e", + "GIT_EDTIOR='code --wait'" + ], + "extensions": [ + "ms-python.python", + "tabnine.tabnine-vscode" + ], + "settings": { + "python.pythonPath": "/usr/local/bin/python", + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } +} \ No newline at end of file diff --git a/.devcontainer/images/reopen.png b/.devcontainer/images/reopen.png new file mode 100644 index 0000000..cbcec3c Binary files /dev/null and b/.devcontainer/images/reopen.png differ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..507f06e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 0000000..70f3d5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,42 @@ +--- +name: Issue +about: Create a report to help us improve + +--- + + + +## Version of the custom_component + + +## Configuration + +```yaml + +Add your logs here. + +``` + +## Describe the bug +A clear and concise description of what the bug is. + + +## Debug log + + + +```text + +Add your logs here. + +``` \ No newline at end of file diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..717c121 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,23 @@ +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/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..31504d9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,61 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Home Assistant on port 8124", + "type": "shell", + "command": "source .devcontainer/custom_component_helper && StartHomeAssistant", + "group": { + "kind": "test", + "isDefault": true, + }, + "presentation": { + "reveal": "always", + "panel": "new" + }, + "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" + }, + "problemMatcher": [] + }, + { + "label": "Set Home Assistant Version", + "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" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f291aca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contribution guidelines + +Contributing to this project should be as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features + +## Github is used for everything + +Github is used to host code, to track issues and feature requests, as well as accept pull requests. + +Pull requests are the best way to propose changes to the codebase. + +1. Fork the repo and create your branch from `master`. +2. If you've changed something, update the documentation. +3. Make sure your code lints (using black). +4. Issue that pull request! + +## Any contributions you make will be under the MIT Software License + +In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](../../issues) + +GitHub issues are used to track public bugs. +Report a bug by [opening a new issue](../../issues/new/choose); it's that easy! + +## Write bug reports with detail, background, and sample code + +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +People *love* thorough bug reports. I'm not even kidding. + +## Use a Consistent Coding Style + +Use [black](https://github.com/ambv/black) to make sure the code follows the style. + +## License + +By contributing, you agree that your contributions will be licensed under its MIT License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8d266b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b552046 --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# Notice + +The component and platforms in this repository are not meant to be used by a +user, but as a "blueprint" that custom component developers can build +upon, to make more awesome stuff. + +This blueprint uses ['sampleclient'](https://github.com/ludeeus/sampleclient) to simulate what you actually might use in your integration. + +HAVE FUN! 😎 + +## Why? + +This is simple, by having custom_components look (README + structure) the same +it is easier for developers to help each other and for users to start using them. + +If you are a developer and you want to add things to this "blueprint" that you think more +developers will have use for, please open a PR to add it :) + +## What? + +This repository contains multiple files, here is a overview: + +File | Purpose +-- | -- +`.devcontainer/*` | Used for development/testing with VSCODE, more info in the readme file in that dir. +`.github/ISSUE_TEMPLATE/feature_request.md` | Template for Feature Requests +`.github/ISSUE_TEMPLATE/issue.md` | Template for issues +`.github/settings.yml` | Probot settings to control the repository settings. +`.vscode/tasks.json` | Tasks for the devcontainer. +`custom_components/blueprint/.translations/*` | [Translation files.](https://developers.home-assistant.io/docs/en/next/internationalization_custom_component_localization.html#translation-strings) +`custom_components/blueprint/__init__.py` | The component file for the integration. +`custom_components/blueprint/binary_sensor.py` | Binary sensor platform for the integration. +`custom_components/blueprint/config_flow.py` | Config flow file, this adds the UI configuration possibilities. +`custom_components/blueprint/const.py` | A file to hold shared variables/constants for the entire integration. +`custom_components/blueprint/manifest.json` | A [manifest file](https://developers.home-assistant.io/docs/en/creating_integration_manifest.html) for Home Assistant. +`custom_components/blueprint/sensor.py` | Sensor platform for the integration. +`custom_components/blueprint/switch.py` | Switch sensor platform for the integration. +`CONTRIBUTING.md` | Guidelines on how to contribute. +`example.png` | Screenshot that demonstrate how it might look in the UI. +`info.md` | An example on a info file (used by [hacs][hacs]). +`LICENSE` | The license file for the project. +`README.md` | The file you are reading now, should contain info about the integration, installation and configuration instructions. +`requirements.txt` | Python packages used by this integration. + +*** +README content if this was a published component: +*** + +# blueprint + +[![GitHub Release][releases-shield]][releases] +[![GitHub Activity][commits-shield]][commits] +[![License][license-shield]](LICENSE.md) + +[![hacs][hacsbadge]][hacs] +![Project Maintenance][maintenance-shield] +[![BuyMeCoffee][buymecoffeebadge]][buymecoffee] + +[![Discord][discord-shield]][discord] +[![Community Forum][forum-shield]][forum] + +_Component to integrate with [blueprint][blueprint]._ + +**This component will set up the following platforms.** + +Platform | Description +-- | -- +`binary_sensor` | Show something `True` or `False`. +`sensor` | Show info from blueprint API. +`switch` | Switch something `True` or `False`. + +![example][exampleimg] + +## Installation + +1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). +2. If you do not have a `custom_components` directory (folder) there, you need to create it. +3. In the `custom_components` directory (folder) create a new folder called `blueprint`. +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" + +Using your HA configuration directory (folder) as a starting point you should now also have this: + +```text +custom_components/blueprint/.translations/en.json +custom_components/blueprint/.translations/nb.json +custom_components/blueprint/.translations/sensor.nb.json +custom_components/blueprint/__init__.py +custom_components/blueprint/binary_sensor.py +custom_components/blueprint/config_flow.py +custom_components/blueprint/const.py +custom_components/blueprint/manifest.json +custom_components/blueprint/sensor.py +custom_components/blueprint/switch.py +``` + +## 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. + +## Contributions are welcome! + +If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md) + +*** + +[blueprint]: https://github.com/custom-components/blueprint +[buymecoffee]: https://www.buymeacoffee.com/ludeeus +[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge +[commits-shield]: https://img.shields.io/github/commit-activity/y/custom-components/blueprint.svg?style=for-the-badge +[commits]: https://github.com/custom-components/blueprint/commits/master +[hacs]: https://github.com/custom-components/hacs +[hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge +[discord]: https://discord.gg/Qa5fW2R +[discord-shield]: https://img.shields.io/discord/330944238910963714.svg?style=for-the-badge +[exampleimg]: example.png +[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge +[forum]: https://community.home-assistant.io/ +[license-shield]: https://img.shields.io/github/license/custom-components/blueprint.svg?style=for-the-badge +[maintenance-shield]: https://img.shields.io/badge/maintainer-Joakim%20Sørensen%20%40ludeeus-blue.svg?style=for-the-badge +[releases-shield]: https://img.shields.io/github/release/custom-components/blueprint.svg?style=for-the-badge +[releases]: https://github.com/custom-components/blueprint/releases 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/.translations/sensor.nb.json b/custom_components/blueprint/.translations/sensor.nb.json new file mode 100644 index 0000000..ed34ceb --- /dev/null +++ b/custom_components/blueprint/.translations/sensor.nb.json @@ -0,0 +1,5 @@ +{ + "state": { + "Some sample static text.": "Eksempel tekst." + } +} \ No newline at end of file diff --git a/custom_components/blueprint/__init__.py b/custom_components/blueprint/__init__.py new file mode 100644 index 0000000..e281e53 --- /dev/null +++ b/custom_components/blueprint/__init__.py @@ -0,0 +1,244 @@ +""" +Component to integrate with blueprint. + +For more details about this component, please refer to +https://github.com/custom-components/blueprint +""" +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 + +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, +) + +MIN_TIME_BETWEEN_UPDATES = 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 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) + ) + + # 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={} + ) + ) + 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 + + +class BlueprintData: + """This class handle communication and stores the data.""" + + def __init__(self, hass, client): + """Initialize the class.""" + self.hass = hass + self.client = client + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + async def update_data(self): + """Update data.""" + # This is where the main logic to update platform data goes. + 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) + + +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): + """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 new file mode 100644 index 0000000..552995d --- /dev/null +++ b/custom_components/blueprint/binary_sensor.py @@ -0,0 +1,85 @@ +"""Binary sensor platform for blueprint.""" +from homeassistant.components.binary_sensor import BinarySensorDevice +from .const import ( + ATTRIBUTION, + BINARY_SENSOR_DEVICE_CLASS, + DEFAULT_NAME, + DOMAIN_DATA, + DOMAIN, +) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None +): # pylint: disable=unused-argument + """Setup binary_sensor 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.""" + + 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 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.""" + return self._name + + @property + def device_class(self): + """Return the class of this binary_sensor.""" + return BINARY_SENSOR_DEVICE_CLASS + + @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 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/const.py b/custom_components/blueprint/const.py new file mode 100644 index 0000000..45d6e9c --- /dev/null +++ b/custom_components/blueprint/const.py @@ -0,0 +1,35 @@ +"""Constants for blueprint.""" +# Base component constants +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" + +# Device classes +BINARY_SENSOR_DEVICE_CLASS = "connectivity" + +# Configuration +CONF_BINARY_SENSOR = "binary_sensor" +CONF_SENSOR = "sensor" +CONF_SWITCH = "switch" +CONF_ENABLED = "enabled" +CONF_NAME = "name" +CONF_USERNAME = "username" +CONF_PASSWORD = "password" + +# Defaults +DEFAULT_NAME = DOMAIN diff --git a/custom_components/blueprint/manifest.json b/custom_components/blueprint/manifest.json new file mode 100644 index 0000000..767d033 --- /dev/null +++ b/custom_components/blueprint/manifest.json @@ -0,0 +1,15 @@ +{ + "domain": "blueprint", + "name": "Blueprint", + "documentation": "https://github.com/custom-components/blueprint", + "dependencies": [], + "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 new file mode 100644 index 0000000..5ea9cdd --- /dev/null +++ b/custom_components/blueprint/sensor.py @@ -0,0 +1,79 @@ +"""Sensor platform for blueprint.""" +from homeassistant.helpers.entity import Entity +from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None +): # pylint: disable=unused-argument + """Setup sensor 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.""" + + 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 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.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @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 new file mode 100644 index 0000000..d44c825 --- /dev/null +++ b/custom_components/blueprint/switch.py @@ -0,0 +1,84 @@ +"""Switch platform for blueprint.""" +from homeassistant.components.switch import SwitchDevice +from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN + + +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): + """Setup sensor platform.""" + async_add_devices([BlueprintBinarySwitch(hass, {})], True) + + +class BlueprintBinarySwitch(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) + + 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 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.""" + return self._name + + @property + def icon(self): + """Return the icon of this switch.""" + return ICON + + @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 diff --git a/example.png b/example.png new file mode 100644 index 0000000..c2d4244 Binary files /dev/null and b/example.png differ diff --git a/info.md b/info.md new file mode 100644 index 0000000..c893c7d --- /dev/null +++ b/info.md @@ -0,0 +1,98 @@ +[![GitHub Release][releases-shield]][releases] +[![GitHub Activity][commits-shield]][commits] +[![License][license-shield]](LICENSE.md) + +[![hacs][hacsbadge]](hacs) +![Project Maintenance][maintenance-shield] +[![BuyMeCoffee][buymecoffeebadge]][buymecoffee] + +[![Discord][discord-shield]][discord] +[![Community Forum][forum-shield]][forum] + +_Component to integrate with [blueprint][blueprint]._ + +**This component will set up the following platforms.** + +Platform | Description +-- | -- +`binary_sensor` | Show something `True` or `False`. +`sensor` | Show info from blueprint API. +`switch` | Switch something `True` or `False`. + +![example][exampleimg] + +{% if not installed %} +## Installation + +1. Click install. +1. Add `blueprint:` to your HA configuration. + +{% 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. + + +*** + +[blueprint]: https://github.com/custom-components/blueprint +[buymecoffee]: https://www.buymeacoffee.com/ludeeus +[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge +[commits-shield]: https://img.shields.io/github/commit-activity/y/custom-components/blueprint.svg?style=for-the-badge +[commits]: https://github.com/custom-components/blueprint/commits/master +[hacs]: https://github.com/custom-components/hacs +[hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge +[discord]: https://discord.gg/Qa5fW2R +[discord-shield]: https://img.shields.io/discord/330944238910963714.svg?style=for-the-badge +[exampleimg]: example.png +[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge +[forum]: https://community.home-assistant.io/ +[license-shield]: https://img.shields.io/github/license/custom-components/blueprint.svg?style=for-the-badge +[maintenance-shield]: https://img.shields.io/badge/maintainer-Joakim%20Sørensen%20%40ludeeus-blue.svg?style=for-the-badge +[releases-shield]: https://img.shields.io/github/release/custom-components/blueprint.svg?style=for-the-badge +[releases]: https://github.com/custom-components/blueprint/releases diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7019fc7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +integrationhelper +sampleclient \ No newline at end of file