Spring cleaning ☀️ (#23)

* Spring cleaning

* Actions

* Fix branches

* Changes for config_flow
This commit is contained in:
Joakim Sørensen 2020-04-17 19:42:59 +02:00 committed by GitHub
parent d408fbec4a
commit a53b0f75a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 349 additions and 731 deletions

View File

@ -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

View File

@ -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.

View File

@ -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
custom_components.blueprint: debug

View File

@ -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
}

View File

@ -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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

23
.github/settings.yml vendored
View File

@ -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"

View File

@ -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
- uses: "home-assistant/actions/hassfest@master"

23
.github/workflows/pull.yml vendored Normal file
View File

@ -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 .

26
.github/workflows/push.yml vendored Normal file
View File

@ -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 .

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

54
.vscode/tasks.json vendored
View File

@ -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": []
}
]

View File

@ -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

View File

@ -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!

View File

@ -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"
}
}
}
}
}

View File

@ -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"
}
}
}
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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
)

View File

@ -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}
-------------------------------------------------------------------
"""

View File

@ -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()

View File

@ -8,7 +8,6 @@
"@ludeeus"
],
"requirements": [
"sampleclient",
"integrationhelper"
"sampleclient"
]
}

View File

@ -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

View File

@ -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

5
hacs.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Blueprint",
"hacs": "0.19.0",
"homeassistant": "0.97.0"
}

50
info.md
View File

@ -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
<!---->
***

View File

@ -1,2 +0,0 @@
integrationhelper
sampleclient