Remove sampleclient (#45)
This commit is contained in:
parent
bf76923975
commit
668bd9d0bc
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"python.linting.pylintEnabled": true,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.pythonPath": "/usr/local/bin/python"
|
||||||
|
}
|
10
README.md
10
README.md
@ -4,8 +4,6 @@ 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
|
user, but as a "blueprint" that custom component developers can build
|
||||||
upon, to make more awesome stuff.
|
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! 😎
|
HAVE FUN! 😎
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
@ -29,6 +27,7 @@ File | Purpose
|
|||||||
`.vscode/tasks.json` | Tasks for the devcontainer.
|
`.vscode/tasks.json` | Tasks for the devcontainer.
|
||||||
`custom_components/blueprint/translations/*` | [Translation files.](https://developers.home-assistant.io/docs/internationalization/custom_integration)
|
`custom_components/blueprint/translations/*` | [Translation files.](https://developers.home-assistant.io/docs/internationalization/custom_integration)
|
||||||
`custom_components/blueprint/__init__.py` | The component file for the integration.
|
`custom_components/blueprint/__init__.py` | The component file for the integration.
|
||||||
|
`custom_components/blueprint/api.py` | This is a sample API client.
|
||||||
`custom_components/blueprint/binary_sensor.py` | Binary sensor platform 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/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/const.py` | A file to hold shared variables/constants for the entire integration.
|
||||||
@ -104,10 +103,11 @@ Platform | Description
|
|||||||
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:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
custom_components/blueprint/.translations/en.json
|
custom_components/blueprint/translations/en.json
|
||||||
custom_components/blueprint/.translations/nb.json
|
custom_components/blueprint/translations/nb.json
|
||||||
custom_components/blueprint/.translations/sensor.nb.json
|
custom_components/blueprint/translations/sensor.nb.json
|
||||||
custom_components/blueprint/__init__.py
|
custom_components/blueprint/__init__.py
|
||||||
|
custom_components/blueprint/api.py
|
||||||
custom_components/blueprint/binary_sensor.py
|
custom_components/blueprint/binary_sensor.py
|
||||||
custom_components/blueprint/config_flow.py
|
custom_components/blueprint/config_flow.py
|
||||||
custom_components/blueprint/const.py
|
custom_components/blueprint/const.py
|
||||||
|
@ -11,10 +11,12 @@ import logging
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import Config, HomeAssistant
|
from homeassistant.core import Config, HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
from sampleclient.client import Client
|
|
||||||
|
|
||||||
from custom_components.blueprint.const import (
|
from .api import BlueprintApiClient
|
||||||
|
|
||||||
|
from .const import (
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -24,7 +26,7 @@ from custom_components.blueprint.const import (
|
|||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=30)
|
SCAN_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: Config):
|
async def async_setup(hass: HomeAssistant, config: Config):
|
||||||
@ -41,9 +43,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
username = entry.data.get(CONF_USERNAME)
|
username = entry.data.get(CONF_USERNAME)
|
||||||
password = entry.data.get(CONF_PASSWORD)
|
password = entry.data.get(CONF_PASSWORD)
|
||||||
|
|
||||||
coordinator = BlueprintDataUpdateCoordinator(
|
session = async_get_clientsession(hass)
|
||||||
hass, username=username, password=password
|
client = BlueprintApiClient(username, password, session)
|
||||||
)
|
|
||||||
|
coordinator = BlueprintDataUpdateCoordinator(hass, client=client)
|
||||||
await coordinator.async_refresh()
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
if not coordinator.last_update_success:
|
if not coordinator.last_update_success:
|
||||||
@ -65,9 +68,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
class BlueprintDataUpdateCoordinator(DataUpdateCoordinator):
|
class BlueprintDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
"""Class to manage fetching data from the API."""
|
"""Class to manage fetching data from the API."""
|
||||||
|
|
||||||
def __init__(self, hass, username, password):
|
def __init__(self, hass: HomeAssistant, client: BlueprintApiClient) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.api = Client(username, password)
|
self.api: BlueprintApiClient = client
|
||||||
self.platforms = []
|
self.platforms = []
|
||||||
|
|
||||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
|
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
|
||||||
@ -75,13 +78,12 @@ class BlueprintDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
async def _async_update_data(self):
|
async def _async_update_data(self):
|
||||||
"""Update data via library."""
|
"""Update data via library."""
|
||||||
try:
|
try:
|
||||||
data = await self.api.async_get_data()
|
return await self.api.async_get_data()
|
||||||
return data.get("data", {})
|
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
raise UpdateFailed(exception)
|
raise UpdateFailed() from exception
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Handle removal of an entry."""
|
"""Handle removal of an entry."""
|
||||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
unloaded = all(
|
unloaded = all(
|
||||||
@ -99,7 +101,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
return unloaded
|
return unloaded
|
||||||
|
|
||||||
|
|
||||||
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Reload config entry."""
|
"""Reload config entry."""
|
||||||
await async_unload_entry(hass, entry)
|
await async_unload_entry(hass, entry)
|
||||||
await async_setup_entry(hass, entry)
|
await async_setup_entry(hass, entry)
|
||||||
|
75
custom_components/blueprint/api.py
Normal file
75
custom_components/blueprint/api.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""Sample API Client."""
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
import socket
|
||||||
|
from typing import Optional
|
||||||
|
import aiohttp
|
||||||
|
import async_timeout
|
||||||
|
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
HEADERS = {"Content-type": "application/json; charset=UTF-8"}
|
||||||
|
|
||||||
|
|
||||||
|
class BlueprintApiClient:
|
||||||
|
def __init__(
|
||||||
|
self, username: str, password: str, session: aiohttp.ClientSession
|
||||||
|
) -> None:
|
||||||
|
"""Sample API Client."""
|
||||||
|
self._username = username
|
||||||
|
self._passeword = password
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
async def async_get_data(self) -> dict:
|
||||||
|
"""Get data from the API."""
|
||||||
|
url = "https://jsonplaceholder.typicode.com/posts/1"
|
||||||
|
return await self.api_wrapper("get", url)
|
||||||
|
|
||||||
|
async def async_set_title(self, value: str) -> None:
|
||||||
|
"""Get data from the API."""
|
||||||
|
url = "https://jsonplaceholder.typicode.com/posts/1"
|
||||||
|
await self.api_wrapper("patch", url, data={"title": value}, headers=HEADERS)
|
||||||
|
|
||||||
|
async def api_wrapper(
|
||||||
|
self, method: str, url: str, data: dict = {}, headers: dict = {}
|
||||||
|
) -> dict:
|
||||||
|
"""Get information from the API."""
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()):
|
||||||
|
if method == "get":
|
||||||
|
response = await self._session.get(url, headers=headers)
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
elif method == "put":
|
||||||
|
await self._session.put(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
elif method == "patch":
|
||||||
|
await self._session.patch(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
elif method == "post":
|
||||||
|
await self._session.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
except asyncio.TimeoutError as exception:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Timeout error fetching information from %s - %s",
|
||||||
|
url,
|
||||||
|
exception,
|
||||||
|
)
|
||||||
|
|
||||||
|
except (KeyError, TypeError) as exception:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Error parsing information from %s - %s",
|
||||||
|
url,
|
||||||
|
exception,
|
||||||
|
)
|
||||||
|
except (aiohttp.ClientError, socket.gaierror) as exception:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Error fetching information from %s - %s",
|
||||||
|
url,
|
||||||
|
exception,
|
||||||
|
)
|
||||||
|
except Exception as exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.error("Something really wrong happend! - %s", exception)
|
@ -1,13 +1,13 @@
|
|||||||
"""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 custom_components.blueprint.const import (
|
from .const import (
|
||||||
BINARY_SENSOR,
|
BINARY_SENSOR,
|
||||||
BINARY_SENSOR_DEVICE_CLASS,
|
BINARY_SENSOR_DEVICE_CLASS,
|
||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from custom_components.blueprint.entity import BlueprintEntity
|
from .entity import BlueprintEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry, async_add_devices):
|
async def async_setup_entry(hass, entry, async_add_devices):
|
||||||
@ -32,4 +32,4 @@ class BlueprintBinarySensor(BlueprintEntity, 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.coordinator.data.get("bool_on", False)
|
return self.coordinator.data.get("title", "") == "foo"
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Adds config flow for Blueprint."""
|
"""Adds config flow for Blueprint."""
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from sampleclient.client import Client
|
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from custom_components.blueprint.const import ( # pylint: disable=unused-import
|
from .api import BlueprintApiClient
|
||||||
|
from .const import (
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -22,9 +23,7 @@ class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self._errors = {}
|
self._errors = {}
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(self, user_input=None):
|
||||||
self, user_input=None # pylint: disable=bad-continuation
|
|
||||||
):
|
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
self._errors = {}
|
self._errors = {}
|
||||||
|
|
||||||
@ -65,7 +64,8 @@ class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
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)
|
session = async_create_clientsession(self.hass)
|
||||||
|
client = BlueprintApiClient(username, password, session)
|
||||||
await client.async_get_data()
|
await client.async_get_data()
|
||||||
return True
|
return True
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
|
@ -4,7 +4,7 @@ NAME = "Blueprint"
|
|||||||
DOMAIN = "blueprint"
|
DOMAIN = "blueprint"
|
||||||
DOMAIN_DATA = f"{DOMAIN}_data"
|
DOMAIN_DATA = f"{DOMAIN}_data"
|
||||||
VERSION = "0.0.1"
|
VERSION = "0.0.1"
|
||||||
|
ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/"
|
||||||
ISSUE_URL = "https://github.com/custom-components/blueprint/issues"
|
ISSUE_URL = "https://github.com/custom-components/blueprint/issues"
|
||||||
|
|
||||||
# Icons
|
# Icons
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""BlueprintEntity class"""
|
"""BlueprintEntity class"""
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from custom_components.blueprint.const import DOMAIN, NAME, VERSION
|
from .const import DOMAIN, NAME, VERSION, ATTRIBUTION
|
||||||
|
|
||||||
|
|
||||||
class BlueprintEntity(CoordinatorEntity):
|
class BlueprintEntity(CoordinatorEntity):
|
||||||
@ -27,6 +27,7 @@ class BlueprintEntity(CoordinatorEntity):
|
|||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
return {
|
return {
|
||||||
"time": str(self.coordinator.data.get("time")),
|
"attribution": ATTRIBUTION,
|
||||||
"static": self.coordinator.data.get("static"),
|
"id": str(self.coordinator.data.get("id")),
|
||||||
|
"integration": DOMAIN,
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
"domain": "blueprint",
|
"domain": "blueprint",
|
||||||
"name": "Blueprint",
|
"name": "Blueprint",
|
||||||
"documentation": "https://github.com/custom-components/blueprint",
|
"documentation": "https://github.com/custom-components/blueprint",
|
||||||
|
"issue_tracker": "https://github.com/custom-components/blueprint/issues",
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@ludeeus"
|
"@ludeeus"
|
||||||
],
|
],
|
||||||
"requirements": [
|
"requirements": []
|
||||||
"sampleclient"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Sensor platform for blueprint."""
|
"""Sensor platform for blueprint."""
|
||||||
from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SENSOR
|
from .const import DEFAULT_NAME, DOMAIN, ICON, SENSOR
|
||||||
from custom_components.blueprint.entity import BlueprintEntity
|
from .entity import BlueprintEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry, async_add_devices):
|
async def async_setup_entry(hass, entry, async_add_devices):
|
||||||
@ -20,7 +20,7 @@ class BlueprintSensor(BlueprintEntity):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self.coordinator.data.get("static")
|
return self.coordinator.data.get("body")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Switch platform for blueprint."""
|
"""Switch platform for blueprint."""
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
|
||||||
from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SWITCH
|
from .const import DEFAULT_NAME, DOMAIN, ICON, SWITCH
|
||||||
from custom_components.blueprint.entity import BlueprintEntity
|
from .entity import BlueprintEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry, async_add_devices):
|
async def async_setup_entry(hass, entry, async_add_devices):
|
||||||
@ -11,17 +11,17 @@ async def async_setup_entry(hass, entry, async_add_devices):
|
|||||||
async_add_devices([BlueprintBinarySwitch(coordinator, entry)])
|
async_add_devices([BlueprintBinarySwitch(coordinator, entry)])
|
||||||
|
|
||||||
|
|
||||||
class BlueprintBinarySwitch(BlueprintEntity, SwitchDevice):
|
class BlueprintBinarySwitch(BlueprintEntity, SwitchEntity):
|
||||||
"""blueprint switch class."""
|
"""blueprint switch class."""
|
||||||
|
|
||||||
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.coordinator.api.async_change_something(True)
|
await self.coordinator.api.async_set_title("bar")
|
||||||
await self.coordinator.async_request_refresh()
|
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.coordinator.api.async_change_something(False)
|
await self.coordinator.api.async_set_title("foo")
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -37,4 +37,4 @@ class BlueprintBinarySwitch(BlueprintEntity, 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.coordinator.api.something
|
return self.coordinator.data.get("title", "") == "foo"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "Blueprint",
|
"name": "Blueprint",
|
||||||
"hacs": "0.24.0",
|
"hacs": "1.6.0",
|
||||||
"domains": [
|
"domains": [
|
||||||
"binary_sensor",
|
"binary_sensor",
|
||||||
"sensor",
|
"sensor",
|
||||||
"switch"
|
"switch"
|
||||||
],
|
],
|
||||||
"iot_class": "Cloud Polling",
|
"iot_class": "Cloud Polling",
|
||||||
"homeassistant": "0.115.0"
|
"homeassistant": "0.118.0"
|
||||||
}
|
}
|
||||||
|
2
info.md
2
info.md
@ -16,7 +16,7 @@ _Component to integrate with [blueprint][blueprint]._
|
|||||||
Platform | Description
|
Platform | Description
|
||||||
-- | --
|
-- | --
|
||||||
`binary_sensor` | Show something `True` or `False`.
|
`binary_sensor` | Show something `True` or `False`.
|
||||||
`sensor` | Show info from blueprint API.
|
`sensor` | Show info from API.
|
||||||
`switch` | Switch something `True` or `False`.
|
`switch` | Switch something `True` or `False`.
|
||||||
|
|
||||||
![example][exampleimg]
|
![example][exampleimg]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user