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: logger:
default: error default: error
logs: logs:
custom_components.blueprint: debug 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

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. // See https://aka.ms/vscode-remote/devcontainer.json for format details.
{ {
"image": "ludeeus/container:integration",
"context": "..", "context": "..",
"dockerFile": "Dockerfile", "appPort": [
"appPort": "8124:8123", "9123:8123"
],
"postCreateCommand": "dc install",
"runArgs": [ "runArgs": [
"-e", "-v",
"GIT_EDTIOR='code --wait'" "${env:HOME}${env:USERPROFILE}/.ssh:/tmp/.ssh"
], ],
"extensions": [ "extensions": [
"ms-python.python", "ms-python.python",
"github.vscode-pull-request-github",
"tabnine.tabnine-vscode" "tabnine.tabnine-vscode"
], ],
"settings": { "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.pylintEnabled": true,
"python.linting.enabled": true, "python.linting.enabled": true,
"python.formatting.provider": "black", "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: on:
push:
pull_request:
schedule: schedule:
- cron: '0 0 * * *' - cron: '0 0 * * *'
jobs: jobs:
validate: hassfest:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
name: Validate with hassfest
steps: steps:
- uses: "actions/checkout@v2" - 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", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "Start Home Assistant on port 8124", "label": "Run Home Assistant on port 9123",
"type": "shell", "type": "shell",
"command": "source .devcontainer/custom_component_helper && StartHomeAssistant", "command": "dc start",
"group": { "problemMatcher": []
"kind": "test", },
"isDefault": true, {
}, "label": "Run Home Assistant configuration against /config",
"presentation": { "type": "shell",
"reveal": "always", "command": "dc check",
"panel": "new"
},
"problemMatcher": [] "problemMatcher": []
}, },
{ {
"label": "Upgrade Home Assistant to latest dev", "label": "Upgrade Home Assistant to latest dev",
"type": "shell", "type": "shell",
"command": "source .devcontainer/custom_component_helper && UpdgradeHomeAssistantDev", "command": "dc install",
"group": {
"kind": "test",
"isDefault": true,
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": [] "problemMatcher": []
}, },
{ {
"label": "Set Home Assistant Version", "label": "Install a spesific version of Home Assistant",
"type": "shell", "type": "shell",
"command": "source .devcontainer/custom_component_helper && SetHomeAssistantVersion", "command": "dc set-version",
"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": [] "problemMatcher": []
} }
] ]

View File

@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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. 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. 5. Place the files you downloaded in the new directory (folder) you created.
6. Restart Home Assistant 6. Restart Home Assistant
7. Choose: 7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Blueprint"
- 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: 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 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! ## Contributions are welcome!

View File

@ -13,9 +13,17 @@
}, },
"error": { "error": {
"auth": "Username/Password is wrong." "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": { "error": {
"auth": "Brukernavn/Passord er feil." "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 https://github.com/custom-components/blueprint
""" """
import os import asyncio
from datetime import timedelta
import logging import logging
import voluptuous as vol from datetime import timedelta
from homeassistant import config_entries
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
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 sampleclient.client import Client
from integrationhelper.const import CC_STARTUP_VERSION
from .const import ( from .const import (
CONF_BINARY_SENSOR,
CONF_ENABLED,
CONF_NAME,
CONF_PASSWORD, CONF_PASSWORD,
CONF_SENSOR,
CONF_SWITCH,
CONF_USERNAME, CONF_USERNAME,
DEFAULT_NAME,
DOMAIN_DATA,
DOMAIN, DOMAIN,
ISSUE_URL,
PLATFORMS, PLATFORMS,
REQUIRED_FILES, STARTUP_MESSAGE,
VERSION,
) )
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER = logging.getLogger(__name__) _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( async def async_setup(hass: HomeAssistant, config: Config):
{ """Set up this integration using YAML is not supported."""
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={}
)
)
return True 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.""" """Set up this integration using UI."""
conf = hass.data.get(DOMAIN_DATA) if hass.data.get(DOMAIN) is None:
if config_entry.source == config_entries.SOURCE_IMPORT: hass.data.setdefault(DOMAIN, {})
if conf is None: _LOGGER.info(STARTUP_MESSAGE)
hass.async_create_task(
hass.config_entries.async_remove(config_entry.entry_id) 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 return True
class BlueprintData: class BlueprintDataUpdateCoordinator(DataUpdateCoordinator):
"""This class handle communication and stores the data.""" """Class to manage fetching data from the API."""
def __init__(self, hass, client): def __init__(self, hass, username, password):
"""Initialize the class.""" """Initialize."""
self.hass = hass self.api = Client(username, password)
self.client = client self.platforms = []
@Throttle(MIN_TIME_BETWEEN_UPDATES) super().__init__(
async def update_data(self): hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL,
"""Update data.""" )
# This is where the main logic to update platform data goes.
async def _async_update_data(self):
"""Update data via library."""
try: try:
data = self.client.get_data() data = await self.api.async_get_data()
self.hass.data[DOMAIN_DATA]["data"] = data return data.get("data", {})
except Exception as error: # pylint: disable=broad-except except Exception as exception:
_LOGGER.error("Could not update data - %s", error) raise UpdateFailed(exception)
async def check_files(hass): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""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.""" """Handle removal of an entry."""
try: coordinator = hass.data[DOMAIN][entry.entry_id]
await hass.config_entries.async_forward_entry_unload( unloaded = all(
config_entry, "binary_sensor" 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" if unloaded:
) hass.data[DOMAIN].pop(entry.entry_id)
except ValueError:
pass
try: return unloaded
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") async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.info("Successfully removed switch from the blueprint integration") """Reload config entry."""
except ValueError: await async_unload_entry(hass, entry)
pass await async_setup_entry(hass, entry)

View File

@ -1,73 +1,28 @@
"""Binary sensor platform for blueprint.""" """Binary sensor platform for blueprint."""
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from .const import (
ATTRIBUTION, from custom_components.blueprint.const import (
BINARY_SENSOR,
BINARY_SENSOR_DEVICE_CLASS, BINARY_SENSOR_DEVICE_CLASS,
DEFAULT_NAME, DEFAULT_NAME,
DOMAIN_DATA,
DOMAIN, DOMAIN,
) )
from custom_components.blueprint.entity import BlueprintEntity
async def async_setup_platform( async def async_setup_entry(hass, entry, async_add_devices):
hass, config, async_add_entities, discovery_info=None
): # pylint: disable=unused-argument
"""Setup binary_sensor platform.""" """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): class BlueprintBinarySensor(BlueprintEntity, BinarySensorDevice):
"""Setup sensor platform."""
async_add_devices([BlueprintBinarySensor(hass, {})], True)
class BlueprintBinarySensor(BinarySensorDevice):
"""blueprint binary_sensor class.""" """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 @property
def name(self): def name(self):
"""Return the name of the binary_sensor.""" """Return the name of the binary_sensor."""
return self._name return f"{DEFAULT_NAME}_{BINARY_SENSOR}"
@property @property
def device_class(self): def device_class(self):
@ -77,9 +32,4 @@ class BlueprintBinarySensor(BinarySensorDevice):
@property @property
def is_on(self): def is_on(self):
"""Return true if the binary_sensor is on.""" """Return true if the binary_sensor is on."""
return self._status return self.coordinator.data.get("bool_on", False)
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self.attr

View File

@ -4,12 +4,17 @@ from collections import OrderedDict
import voluptuous as vol import voluptuous as vol
from sampleclient.client import Client from sampleclient.client import Client
from homeassistant import config_entries 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, domain=DOMAIN):
class BlueprintFlowHandler(config_entries.ConfigFlow):
"""Config flow for Blueprint.""" """Config flow for Blueprint."""
VERSION = 1 VERSION = 1
@ -24,17 +29,14 @@ class BlueprintFlowHandler(config_entries.ConfigFlow):
): # pylint: disable=dangerous-default-value ): # pylint: disable=dangerous-default-value
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
self._errors = {} 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: if user_input is not None:
valid = await self._test_credentials( valid = await self._test_credentials(
user_input["username"], user_input["password"] user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
) )
if valid: 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: else:
self._errors["base"] = "auth" self._errors["base"] = "auth"
@ -42,42 +44,62 @@ class BlueprintFlowHandler(config_entries.ConfigFlow):
return await self._show_config_form(user_input) 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): async def _show_config_form(self, user_input):
"""Show the configuration form to edit location data.""" """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( 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): async def _test_credentials(self, username, password):
"""Return true if credentials is valid.""" """Return true if credentials is valid."""
try: try:
client = Client(username, password) client = Client(username, password)
client.get_data() await client.async_get_data()
return True return True
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
pass pass
return False 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.""" """Constants for blueprint."""
# Base component constants # Base component constants
NAME = "Blueprint"
DOMAIN = "blueprint" DOMAIN = "blueprint"
DOMAIN_DATA = f"{DOMAIN}_data" DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "0.0.1" 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" ISSUE_URL = "https://github.com/custom-components/blueprint/issues"
ATTRIBUTION = "Data from this is provided by blueprint."
# Icons # Icons
ICON = "mdi:format-quote-close" ICON = "mdi:format-quote-close"
@ -22,14 +13,28 @@ ICON = "mdi:format-quote-close"
# Device classes # Device classes
BINARY_SENSOR_DEVICE_CLASS = "connectivity" BINARY_SENSOR_DEVICE_CLASS = "connectivity"
# Configuration # Platforms
CONF_BINARY_SENSOR = "binary_sensor" BINARY_SENSOR = "binary_sensor"
CONF_SENSOR = "sensor" SENSOR = "sensor"
CONF_SWITCH = "switch" SWITCH = "switch"
PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH]
# Configuration and options
CONF_ENABLED = "enabled" CONF_ENABLED = "enabled"
CONF_NAME = "name"
CONF_USERNAME = "username" CONF_USERNAME = "username"
CONF_PASSWORD = "password" CONF_PASSWORD = "password"
# Defaults # Defaults
DEFAULT_NAME = DOMAIN 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" "@ludeeus"
], ],
"requirements": [ "requirements": [
"sampleclient", "sampleclient"
"integrationhelper"
] ]
} }

View File

@ -1,79 +1,28 @@
"""Sensor platform for blueprint.""" """Sensor platform for blueprint."""
from homeassistant.helpers.entity import Entity from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SENSOR
from .const import ATTRIBUTION, DEFAULT_NAME, DOMAIN_DATA, ICON, DOMAIN from custom_components.blueprint.entity import BlueprintEntity
async def async_setup_platform( async def async_setup_entry(hass, entry, async_add_devices):
hass, config, async_add_entities, discovery_info=None
): # pylint: disable=unused-argument
"""Setup sensor platform.""" """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): class BlueprintSensor(BlueprintEntity):
"""Setup sensor platform."""
async_add_devices([BlueprintSensor(hass, {})], True)
class BlueprintSensor(Entity):
"""blueprint Sensor class.""" """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 @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return self._name return f"{DEFAULT_NAME}_{SENSOR}"
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self.coordinator.data.get("static")
@property @property
def icon(self): def icon(self):
"""Return the icon of the sensor.""" """Return the icon of the sensor."""
return ICON 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.""" """Switch platform for blueprint."""
from homeassistant.components.switch import SwitchDevice 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( async def async_setup_entry(hass, entry, async_add_devices):
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.""" """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.""" """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 async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument
"""Turn on the switch.""" """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 async def async_turn_off(self, **kwargs): # pylint: disable=unused-argument
"""Turn off the switch.""" """Turn off the switch."""
await self.hass.data[DOMAIN_DATA]["client"].client.change_something(False) await self.coordinator.api.async_change_something(False)
await self.coordinator.async_request_refresh()
@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",
}
@property @property
def name(self): def name(self):
"""Return the name of the switch.""" """Return the name of the switch."""
return self._name return f"{DEFAULT_NAME}_{SWITCH}"
@property @property
def icon(self): def icon(self):
@ -76,9 +38,4 @@ class BlueprintBinarySwitch(SwitchDevice):
@property @property
def is_on(self): def is_on(self):
"""Return true if the switch is on.""" """Return true if the switch is on."""
return self._status return self.coordinator.api.something
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self.attr

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 ## Installation
1. Click install. 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 %} {% 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 ## Configuration is done in the UI
Key | Type | Required | Default | Description
-- | -- | -- | -- | --
`enabled` | `boolean` | `False` | `True` | Boolean to enable/disable the platform.
`name` | `string` | `False` | `blueprint` | Custom name for the entity.
<!---->
*** ***

View File

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