Add Huisbaasje integration (#42716)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
Dennis Schroer 2021-01-27 15:53:25 +01:00 committed by GitHub
parent b4af17e02d
commit 8d572af77a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1038 additions and 0 deletions

View File

@ -203,6 +203,7 @@ homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop @fphammerle
homeassistant/components/huawei_router/* @abmantis
homeassistant/components/hue/* @balloob @frenck
homeassistant/components/huisbaasje/* @denniss17
homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
homeassistant/components/hunterdouglas_powerview/* @bdraco
homeassistant/components/hvv_departures/* @vigonotion

View File

@ -0,0 +1,168 @@
"""The Huisbaasje integration."""
from datetime import timedelta
import logging
import async_timeout
from huisbaasje import Huisbaasje, HuisbaasjeException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
DATA_COORDINATOR,
DOMAIN,
FETCH_TIMEOUT,
POLLING_INTERVAL,
SENSOR_TYPE_RATE,
SENSOR_TYPE_THIS_DAY,
SENSOR_TYPE_THIS_MONTH,
SENSOR_TYPE_THIS_WEEK,
SENSOR_TYPE_THIS_YEAR,
SOURCE_TYPES,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Huisbaasje component."""
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Set up Huisbaasje from a config entry."""
# Create the Huisbaasje client
huisbaasje = Huisbaasje(
username=config_entry.data[CONF_USERNAME],
password=config_entry.data[CONF_PASSWORD],
source_types=SOURCE_TYPES,
request_timeout=FETCH_TIMEOUT,
)
# Attempt authentication. If this fails, an exception is thrown
try:
await huisbaasje.authenticate()
except HuisbaasjeException as exception:
_LOGGER.error("Authentication failed: %s", str(exception))
return False
async def async_update_data():
return await async_update_huisbaasje(huisbaasje)
# Create a coordinator for polling updates
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="sensor",
update_method=async_update_data,
update_interval=timedelta(seconds=POLLING_INTERVAL),
)
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise ConfigEntryNotReady
# Load the client in the data of home assistant
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = {
DATA_COORDINATOR: coordinator
}
# Offload the loading of entities to the platform
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Unload a config entry."""
# Forward the unloading of the entry to the platform
unload_ok = await hass.config_entries.async_forward_entry_unload(
config_entry, "sensor"
)
# If successful, unload the Huisbaasje client
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
async def async_update_huisbaasje(huisbaasje):
"""Update the data by performing a request to Huisbaasje."""
try:
# Note: asyncio.TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with async_timeout.timeout(FETCH_TIMEOUT):
if not huisbaasje.is_authenticated():
_LOGGER.warning("Huisbaasje is unauthenticated. Reauthenticating...")
await huisbaasje.authenticate()
current_measurements = await huisbaasje.current_measurements()
return {
source_type: {
SENSOR_TYPE_RATE: _get_measurement_rate(
current_measurements, source_type
),
SENSOR_TYPE_THIS_DAY: _get_cumulative_value(
current_measurements, source_type, SENSOR_TYPE_THIS_DAY
),
SENSOR_TYPE_THIS_WEEK: _get_cumulative_value(
current_measurements, source_type, SENSOR_TYPE_THIS_WEEK
),
SENSOR_TYPE_THIS_MONTH: _get_cumulative_value(
current_measurements, source_type, SENSOR_TYPE_THIS_MONTH
),
SENSOR_TYPE_THIS_YEAR: _get_cumulative_value(
current_measurements, source_type, SENSOR_TYPE_THIS_YEAR
),
}
for source_type in SOURCE_TYPES
}
except HuisbaasjeException as exception:
raise UpdateFailed(f"Error communicating with API: {exception}") from exception
def _get_cumulative_value(
current_measurements: dict,
source_type: str,
period_type: str,
):
"""
Get the cumulative energy consumption for a certain period.
:param current_measurements: The result from the Huisbaasje client
:param source_type: The source of energy (electricity or gas)
:param period_type: The period for which cumulative value should be given.
"""
if source_type in current_measurements.keys():
if (
period_type in current_measurements[source_type]
and current_measurements[source_type][period_type] is not None
):
return current_measurements[source_type][period_type]["value"]
else:
_LOGGER.error(
"Source type %s not present in %s", source_type, current_measurements
)
return None
def _get_measurement_rate(current_measurements: dict, source_type: str):
if source_type in current_measurements:
if (
"measurement" in current_measurements[source_type]
and current_measurements[source_type]["measurement"] is not None
):
return current_measurements[source_type]["measurement"]["rate"]
else:
_LOGGER.error(
"Source type %s not present in %s", source_type, current_measurements
)
return None

View File

@ -0,0 +1,84 @@
"""Config flow for Huisbaasje integration."""
import logging
from huisbaasje import Huisbaasje, HuisbaasjeConnectionException, HuisbaasjeException
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import AbortFlow
from .const import DOMAIN # pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
)
class HuisbaasjeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Huisbaasje."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
async def async_step_user(self, user_input=None):
"""Handle a flow initiated by the user."""
if user_input is None:
return await self._show_setup_form(user_input)
errors = {}
try:
user_id = await self._validate_input(user_input)
_LOGGER.info("Input for Huisbaasje is valid!")
# Set user id as unique id
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_configured()
# Create entry
return self.async_create_entry(
title=user_input[CONF_USERNAME],
data={
CONF_ID: user_id,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
},
)
except HuisbaasjeConnectionException as exception:
_LOGGER.warning(exception)
errors["base"] = "connection_exception"
except HuisbaasjeException as exception:
_LOGGER.warning(exception)
errors["base"] = "invalid_auth"
except AbortFlow as exception:
raise exception
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
return await self._show_setup_form(user_input, errors)
async def _show_setup_form(self, user_input, errors=None):
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors or {}
)
async def _validate_input(self, user_input):
"""Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
username = user_input[CONF_USERNAME]
password = user_input[CONF_PASSWORD]
huisbaasje = Huisbaasje(username, password)
# Attempt authentication. If this fails, an HuisbaasjeException will be thrown
await huisbaasje.authenticate()
return huisbaasje.get_user_id()

View File

@ -0,0 +1,142 @@
"""Constants for the Huisbaasje integration."""
from huisbaasje.const import (
SOURCE_TYPE_ELECTRICITY,
SOURCE_TYPE_ELECTRICITY_IN,
SOURCE_TYPE_ELECTRICITY_IN_LOW,
SOURCE_TYPE_ELECTRICITY_OUT,
SOURCE_TYPE_ELECTRICITY_OUT_LOW,
SOURCE_TYPE_GAS,
)
from homeassistant.const import (
DEVICE_CLASS_POWER,
ENERGY_KILO_WATT_HOUR,
TIME_HOURS,
VOLUME_CUBIC_METERS,
)
DATA_COORDINATOR = "coordinator"
DOMAIN = "huisbaasje"
FLOW_CUBIC_METERS_PER_HOUR = f"{VOLUME_CUBIC_METERS}/{TIME_HOURS}"
"""Interval in seconds between polls to huisbaasje."""
POLLING_INTERVAL = 20
"""Timeout for fetching sensor data"""
FETCH_TIMEOUT = 10
SENSOR_TYPE_RATE = "rate"
SENSOR_TYPE_THIS_DAY = "thisDay"
SENSOR_TYPE_THIS_WEEK = "thisWeek"
SENSOR_TYPE_THIS_MONTH = "thisMonth"
SENSOR_TYPE_THIS_YEAR = "thisYear"
SOURCE_TYPES = [
SOURCE_TYPE_ELECTRICITY,
SOURCE_TYPE_ELECTRICITY_IN,
SOURCE_TYPE_ELECTRICITY_IN_LOW,
SOURCE_TYPE_ELECTRICITY_OUT,
SOURCE_TYPE_ELECTRICITY_OUT_LOW,
SOURCE_TYPE_GAS,
]
SENSORS_INFO = [
{
"name": "Huisbaasje Current Power",
"device_class": DEVICE_CLASS_POWER,
"source_type": SOURCE_TYPE_ELECTRICITY,
},
{
"name": "Huisbaasje Current Power In",
"device_class": DEVICE_CLASS_POWER,
"source_type": SOURCE_TYPE_ELECTRICITY_IN,
},
{
"name": "Huisbaasje Current Power In Low",
"device_class": DEVICE_CLASS_POWER,
"source_type": SOURCE_TYPE_ELECTRICITY_IN_LOW,
},
{
"name": "Huisbaasje Current Power Out",
"device_class": DEVICE_CLASS_POWER,
"source_type": SOURCE_TYPE_ELECTRICITY_OUT,
},
{
"name": "Huisbaasje Current Power Out Low",
"device_class": DEVICE_CLASS_POWER,
"source_type": SOURCE_TYPE_ELECTRICITY_OUT_LOW,
},
{
"name": "Huisbaasje Energy Today",
"unit_of_measurement": ENERGY_KILO_WATT_HOUR,
"source_type": SOURCE_TYPE_ELECTRICITY,
"sensor_type": SENSOR_TYPE_THIS_DAY,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Energy This Week",
"unit_of_measurement": ENERGY_KILO_WATT_HOUR,
"source_type": SOURCE_TYPE_ELECTRICITY,
"sensor_type": SENSOR_TYPE_THIS_WEEK,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Energy This Month",
"unit_of_measurement": ENERGY_KILO_WATT_HOUR,
"source_type": SOURCE_TYPE_ELECTRICITY,
"sensor_type": SENSOR_TYPE_THIS_MONTH,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Energy This Year",
"unit_of_measurement": ENERGY_KILO_WATT_HOUR,
"source_type": SOURCE_TYPE_ELECTRICITY,
"sensor_type": SENSOR_TYPE_THIS_YEAR,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Current Gas",
"unit_of_measurement": FLOW_CUBIC_METERS_PER_HOUR,
"source_type": SOURCE_TYPE_GAS,
"icon": "mdi:fire",
"precision": 1,
},
{
"name": "Huisbaasje Gas Today",
"unit_of_measurement": VOLUME_CUBIC_METERS,
"source_type": SOURCE_TYPE_GAS,
"sensor_type": SENSOR_TYPE_THIS_DAY,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Gas This Week",
"unit_of_measurement": VOLUME_CUBIC_METERS,
"source_type": SOURCE_TYPE_GAS,
"sensor_type": SENSOR_TYPE_THIS_WEEK,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Gas This Month",
"unit_of_measurement": VOLUME_CUBIC_METERS,
"source_type": SOURCE_TYPE_GAS,
"sensor_type": SENSOR_TYPE_THIS_MONTH,
"icon": "mdi:counter",
"precision": 1,
},
{
"name": "Huisbaasje Gas This Year",
"unit_of_measurement": VOLUME_CUBIC_METERS,
"source_type": SOURCE_TYPE_GAS,
"sensor_type": SENSOR_TYPE_THIS_YEAR,
"icon": "mdi:counter",
"precision": 1,
},
]

View File

@ -0,0 +1,10 @@
{
"domain": "huisbaasje",
"name": "Huisbaasje",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/huisbaasje",
"requirements": [
"huisbaasje-client==0.1.0"
],
"codeowners": ["@denniss17"]
}

View File

@ -0,0 +1,95 @@
"""Platform for sensor integration."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ID, POWER_WATT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from .const import DATA_COORDINATOR, DOMAIN, SENSOR_TYPE_RATE, SENSORS_INFO
async def async_setup_entry(
hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities
):
"""Set up the sensor platform."""
coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR]
user_id = config_entry.data[CONF_ID]
async_add_entities(
HuisbaasjeSensor(coordinator, user_id=user_id, **sensor_info)
for sensor_info in SENSORS_INFO
)
class HuisbaasjeSensor(CoordinatorEntity):
"""Defines a Huisbaasje sensor."""
def __init__(
self,
coordinator: DataUpdateCoordinator,
user_id: str,
name: str,
source_type: str,
device_class: str = None,
sensor_type: str = SENSOR_TYPE_RATE,
unit_of_measurement: str = POWER_WATT,
icon: str = "mdi:lightning-bolt",
precision: int = 0,
):
"""Initialize the sensor."""
super().__init__(coordinator)
self._user_id = user_id
self._name = name
self._device_class = device_class
self._unit_of_measurement = unit_of_measurement
self._source_type = source_type
self._sensor_type = sensor_type
self._icon = icon
self._precision = precision
@property
def unique_id(self) -> str:
"""Return an unique id for the sensor."""
return f"{DOMAIN}_{self._user_id}_{self._source_type}_{self._sensor_type}"
@property
def name(self) -> str:
"""Return the name of the sensor."""
return self._name
@property
def device_class(self) -> str:
"""Return the device class of the sensor."""
return self._device_class
@property
def icon(self) -> str:
"""Return the icon to use for the sensor."""
return self._icon
@property
def state(self):
"""Return the state of the sensor."""
if self.coordinator.data[self._source_type][self._sensor_type] is not None:
return round(
self.coordinator.data[self._source_type][self._sensor_type],
self._precision,
)
return None
@property
def unit_of_measurement(self) -> str:
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
super().available
and self.coordinator.data
and self._source_type in self.coordinator.data
and self.coordinator.data[self._source_type]
)

View File

@ -0,0 +1,21 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unauthenticated_exception": "[%key:common::config_flow::error::invalid_auth%]",
"connection_exception": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@ -94,6 +94,7 @@ FLOWS = [
"homematicip_cloud",
"huawei_lte",
"hue",
"huisbaasje",
"hunterdouglas_powerview",
"hvv_departures",
"hyperion",

View File

@ -786,6 +786,9 @@ httplib2==0.18.1
# homeassistant.components.huawei_lte
huawei-lte-api==1.4.17
# homeassistant.components.huisbaasje
huisbaasje-client==0.1.0
# homeassistant.components.hydrawise
hydrawiser==0.2

View File

@ -415,6 +415,9 @@ httplib2==0.18.1
# homeassistant.components.huawei_lte
huawei-lte-api==1.4.17
# homeassistant.components.huisbaasje
huisbaasje-client==0.1.0
# homeassistant.components.hyperion
hyperion-py==0.7.0

View File

@ -0,0 +1 @@
"""Tests for the Huisbaasje integration."""

View File

@ -0,0 +1,157 @@
"""Test the Huisbaasje config flow."""
from unittest.mock import patch
from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.components.huisbaasje.config_flow import (
HuisbaasjeConnectionException,
HuisbaasjeException,
)
from homeassistant.components.huisbaasje.const import DOMAIN
from tests.common import MockConfigEntry
async def test_form(hass):
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {}
with patch(
"huisbaasje.Huisbaasje.authenticate", return_value=None
) as mock_authenticate, patch(
"huisbaasje.Huisbaasje.get_user_id",
return_value="test-id",
) as mock_get_user_id, patch(
"homeassistant.components.huisbaasje.async_setup", return_value=True
) as mock_setup, patch(
"homeassistant.components.huisbaasje.async_setup_entry",
return_value=True,
) as mock_setup_entry:
form_result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert form_result["type"] == "create_entry"
assert form_result["title"] == "test-username"
assert form_result["data"] == {
"id": "test-id",
"username": "test-username",
"password": "test-password",
}
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_get_user_id.mock_calls) == 1
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass):
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"huisbaasje.Huisbaasje.authenticate",
side_effect=HuisbaasjeException,
):
form_result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert form_result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert form_result["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass):
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"huisbaasje.Huisbaasje.authenticate",
side_effect=HuisbaasjeConnectionException,
):
form_result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert form_result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert form_result["errors"] == {"base": "connection_exception"}
async def test_form_unknown_error(hass):
"""Test we handle an unknown error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"huisbaasje.Huisbaasje.authenticate",
side_effect=Exception,
):
form_result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert form_result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert form_result["errors"] == {"base": "unknown"}
async def test_form_entry_exists(hass):
"""Test we handle an already existing entry."""
MockConfigEntry(
unique_id="test-id",
domain=DOMAIN,
data={
"id": "test-id",
"username": "test-username",
"password": "test-password",
},
title="test-username",
).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch("huisbaasje.Huisbaasje.authenticate", return_value=None), patch(
"huisbaasje.Huisbaasje.get_user_id",
return_value="test-id",
), patch(
"homeassistant.components.huisbaasje.async_setup", return_value=True
), patch(
"homeassistant.components.huisbaasje.async_setup_entry",
return_value=True,
):
form_result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert form_result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert form_result["reason"] == "already_configured"

View File

@ -0,0 +1,79 @@
"""Test data for the tests of the Huisbaasje integration."""
MOCK_CURRENT_MEASUREMENTS = {
"electricity": {
"measurement": {
"time": "2020-11-18T15:17:24.000Z",
"rate": 1011.6666666666667,
"value": 0.0033333333333333335,
"costPerHour": 0.20233333333333337,
"counterValue": 409.17166666631937,
},
"thisDay": {"value": 3.296665869, "cost": 0.6593331738},
"thisWeek": {"value": 17.509996085, "cost": 3.5019992170000003},
"thisMonth": {"value": 103.28830788, "cost": 20.657661576000002},
"thisYear": {"value": 672.9781177300001, "cost": 134.595623546},
},
"electricityIn": {
"measurement": {
"time": "2020-11-18T15:17:24.000Z",
"rate": 1011.6666666666667,
"value": 0.0033333333333333335,
"costPerHour": 0.20233333333333337,
"counterValue": 409.17166666631937,
},
"thisDay": {"value": 2.669999453, "cost": 0.5339998906},
"thisWeek": {"value": 15.328330291, "cost": 3.0656660582},
"thisMonth": {"value": 72.986651896, "cost": 14.5973303792},
"thisYear": {"value": 409.214880212, "cost": 81.84297604240001},
},
"electricityInLow": {
"measurement": None,
"thisDay": {"value": 0.6266664160000001, "cost": 0.1253332832},
"thisWeek": {"value": 2.181665794, "cost": 0.43633315880000006},
"thisMonth": {"value": 30.301655984000003, "cost": 6.060331196800001},
"thisYear": {"value": 263.76323751800004, "cost": 52.75264750360001},
},
"electricityOut": {
"measurement": None,
"thisDay": {"value": 0.0, "cost": 0.0},
"thisWeek": {"value": 0.0, "cost": 0.0},
"thisMonth": {"value": 0.0, "cost": 0.0},
"thisYear": {"value": 0.0, "cost": 0.0},
},
"electricityOutLow": {
"measurement": None,
"thisDay": {"value": 0.0, "cost": 0.0},
"thisWeek": {"value": 0.0, "cost": 0.0},
"thisMonth": {"value": 0.0, "cost": 0.0},
"thisYear": {"value": 0.0, "cost": 0.0},
},
"gas": {
"measurement": {
"time": "2020-11-18T15:17:29.000Z",
"rate": 0.0,
"value": 0.0,
"costPerHour": 0.0,
"counterValue": 116.73000000002281,
},
"thisDay": {"value": 1.07, "cost": 0.642},
"thisWeek": {"value": 5.634224386000001, "cost": 3.3805346316000007},
"thisMonth": {"value": 39.14, "cost": 23.483999999999998},
"thisYear": {"value": 116.73, "cost": 70.038},
},
}
MOCK_LIMITED_CURRENT_MEASUREMENTS = {
"electricity": {
"measurement": {
"time": "2020-11-18T15:17:24.000Z",
"rate": 1011.6666666666667,
"value": 0.0033333333333333335,
"costPerHour": 0.20233333333333337,
"counterValue": 409.17166666631937,
},
"thisDay": {"value": 3.296665869, "cost": 0.6593331738},
"thisWeek": {"value": 17.509996085, "cost": 3.5019992170000003},
"thisMonth": {"value": 103.28830788, "cost": 20.657661576000002},
"thisYear": {"value": 672.9781177300001, "cost": 134.595623546},
}
}

View File

@ -0,0 +1,153 @@
"""Test cases for the initialisation of the Huisbaasje integration."""
from unittest.mock import patch
from huisbaasje import HuisbaasjeException
from homeassistant.components import huisbaasje
from homeassistant.config_entries import (
CONN_CLASS_CLOUD_POLL,
ENTRY_STATE_LOADED,
ENTRY_STATE_NOT_LOADED,
ENTRY_STATE_SETUP_ERROR,
ConfigEntry,
)
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.components.huisbaasje.test_data import MOCK_CURRENT_MEASUREMENTS
async def test_setup(hass: HomeAssistant):
"""Test for successfully setting up the platform."""
assert await async_setup_component(hass, huisbaasje.DOMAIN, {})
await hass.async_block_till_done()
assert huisbaasje.DOMAIN in hass.config.components
async def test_setup_entry(hass: HomeAssistant):
"""Test for successfully setting a config entry."""
with patch(
"huisbaasje.Huisbaasje.authenticate", return_value=None
) as mock_authenticate, patch(
"huisbaasje.Huisbaasje.is_authenticated", return_value=True
) as mock_is_authenticated, patch(
"huisbaasje.Huisbaasje.current_measurements",
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
assert config_entry.state == ENTRY_STATE_NOT_LOADED
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Assert integration is loaded
assert config_entry.state == ENTRY_STATE_LOADED
assert huisbaasje.DOMAIN in hass.config.components
assert huisbaasje.DOMAIN in hass.data
assert config_entry.entry_id in hass.data[huisbaasje.DOMAIN]
# Assert entities are loaded
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 14
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1
async def test_setup_entry_error(hass: HomeAssistant):
"""Test for successfully setting a config entry."""
with patch(
"huisbaasje.Huisbaasje.authenticate", side_effect=HuisbaasjeException
) as mock_authenticate:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
assert config_entry.state == ENTRY_STATE_NOT_LOADED
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Assert integration is loaded with error
assert config_entry.state == ENTRY_STATE_SETUP_ERROR
assert huisbaasje.DOMAIN not in hass.data
# Assert entities are not loaded
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 0
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
async def test_unload_entry(hass: HomeAssistant):
"""Test for successfully unloading the config entry."""
with patch(
"huisbaasje.Huisbaasje.authenticate", return_value=None
) as mock_authenticate, patch(
"huisbaasje.Huisbaasje.is_authenticated", return_value=True
) as mock_is_authenticated, patch(
"huisbaasje.Huisbaasje.current_measurements",
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
# Load config entry
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state == ENTRY_STATE_LOADED
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 14
# Unload config entry
await hass.config_entries.async_unload(config_entry.entry_id)
assert config_entry.state == ENTRY_STATE_NOT_LOADED
entities = hass.states.async_entity_ids("sensor")
assert len(entities) == 0
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1

View File

@ -0,0 +1,120 @@
"""Test cases for the sensors of the Huisbaasje integration."""
from unittest.mock import patch
from homeassistant.components import huisbaasje
from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL, ConfigEntry
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.components.huisbaasje.test_data import (
MOCK_CURRENT_MEASUREMENTS,
MOCK_LIMITED_CURRENT_MEASUREMENTS,
)
async def test_setup_entry(hass: HomeAssistant):
"""Test for successfully loading sensor states."""
with patch(
"huisbaasje.Huisbaasje.authenticate", return_value=None
) as mock_authenticate, patch(
"huisbaasje.Huisbaasje.is_authenticated", return_value=True
) as mock_is_authenticated, patch(
"huisbaasje.Huisbaasje.current_measurements",
return_value=MOCK_CURRENT_MEASUREMENTS,
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Assert data is loaded
assert hass.states.get("sensor.huisbaasje_current_power").state == "1012.0"
assert hass.states.get("sensor.huisbaasje_current_power_in").state == "1012.0"
assert (
hass.states.get("sensor.huisbaasje_current_power_in_low").state == "unknown"
)
assert hass.states.get("sensor.huisbaasje_current_power_out").state == "unknown"
assert (
hass.states.get("sensor.huisbaasje_current_power_out_low").state
== "unknown"
)
assert hass.states.get("sensor.huisbaasje_current_gas").state == "0.0"
assert hass.states.get("sensor.huisbaasje_energy_today").state == "3.3"
assert hass.states.get("sensor.huisbaasje_energy_this_week").state == "17.5"
assert hass.states.get("sensor.huisbaasje_energy_this_month").state == "103.3"
assert hass.states.get("sensor.huisbaasje_energy_this_year").state == "673.0"
assert hass.states.get("sensor.huisbaasje_gas_today").state == "1.1"
assert hass.states.get("sensor.huisbaasje_gas_this_week").state == "5.6"
assert hass.states.get("sensor.huisbaasje_gas_this_month").state == "39.1"
assert hass.states.get("sensor.huisbaasje_gas_this_year").state == "116.7"
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1
async def test_setup_entry_absent_measurement(hass: HomeAssistant):
"""Test for successfully loading sensor states when response does not contain all measurements."""
with patch(
"huisbaasje.Huisbaasje.authenticate", return_value=None
) as mock_authenticate, patch(
"huisbaasje.Huisbaasje.is_authenticated", return_value=True
) as mock_is_authenticated, patch(
"huisbaasje.Huisbaasje.current_measurements",
return_value=MOCK_LIMITED_CURRENT_MEASUREMENTS,
) as mock_current_measurements:
hass.config.components.add(huisbaasje.DOMAIN)
config_entry = ConfigEntry(
1,
huisbaasje.DOMAIN,
"userId",
{
CONF_ID: "userId",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
},
"test",
CONN_CLASS_CLOUD_POLL,
system_options={},
)
hass.config_entries._entries.append(config_entry)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Assert data is loaded
assert hass.states.get("sensor.huisbaasje_current_power").state == "1012.0"
assert hass.states.get("sensor.huisbaasje_current_power_in").state == "unknown"
assert (
hass.states.get("sensor.huisbaasje_current_power_in_low").state == "unknown"
)
assert hass.states.get("sensor.huisbaasje_current_power_out").state == "unknown"
assert (
hass.states.get("sensor.huisbaasje_current_power_out_low").state
== "unknown"
)
assert hass.states.get("sensor.huisbaasje_current_gas").state == "unknown"
assert hass.states.get("sensor.huisbaasje_energy_today").state == "3.3"
assert hass.states.get("sensor.huisbaasje_gas_today").state == "unknown"
# Assert mocks are called
assert len(mock_authenticate.mock_calls) == 1
assert len(mock_is_authenticated.mock_calls) == 1
assert len(mock_current_measurements.mock_calls) == 1