Add BSBLan Climate integration (#32375)
* Initial commit for BSBLan Climate component The most basic climate functions work. * Delete manifest 2.json wrongly added to commit * fix incorrect name current_hvac_mode * update coverage to exclude bsblan * sorted and add configflow * removed unused code, etc * fix hvac, preset mix up now it sets hvac mode to none and preset to eco * fix naming * removed commented code and cleaned code that isn't needed * Add test for the configflow * Update requirements fixing some issues in bsblan Lib * Update coverage file to include configflow bsblan * Fix hvac preset is not in hvac mode rewrote how to handle presets. * Add passkey option My device had a passkey so I needed to push this functionality to do testing * Update constants include passkey and added some more for device indentification * add passkey for configflow * Fix use discovery_info instead of user_input also added passkey * Fix name * Fix for discovery_info[CONF_PORT] is None * Fix get value CONF_PORT * Fix move translation to new location * Fix get the right info * Fix remove zeroconf and fix the code * Add init for mockConfigEntry * Fix removed zeroconfig and fix code * Fix changed ClimateDevice to ClimatEntity * Fix log error message * Removed debug code * Change name of device. * Remove check This is done in the configflow * Remove period from logging message * Update homeassistant/components/bsblan/strings.json Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add passkey Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
e2b622fb78
commit
cf30895460
15 changed files with 635 additions and 0 deletions
|
@ -100,6 +100,9 @@ omit =
|
|||
homeassistant/components/brottsplatskartan/sensor.py
|
||||
homeassistant/components/browser/*
|
||||
homeassistant/components/brunt/cover.py
|
||||
homeassistant/components/bsblan/__init__.py
|
||||
homeassistant/components/bsblan/climate.py
|
||||
homeassistant/components/bsblan/const.py
|
||||
homeassistant/components/bt_home_hub_5/device_tracker.py
|
||||
homeassistant/components/bt_smarthub/device_tracker.py
|
||||
homeassistant/components/buienradar/sensor.py
|
||||
|
|
|
@ -63,6 +63,7 @@ homeassistant/components/braviatv/* @robbiet480 @bieniu
|
|||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||
homeassistant/components/brother/* @bieniu
|
||||
homeassistant/components/brunt/* @eavanvalkenburg
|
||||
homeassistant/components/bsblan/* @liudger
|
||||
homeassistant/components/bt_smarthub/* @jxwolstenholme
|
||||
homeassistant/components/buienradar/* @mjj4791 @ties
|
||||
homeassistant/components/cast/* @emontnemery
|
||||
|
|
64
homeassistant/components/bsblan/__init__.py
Normal file
64
homeassistant/components/bsblan/__init__.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
"""The BSB-Lan integration."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from bsblan import BSBLan, BSBLanConnectionError
|
||||
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import CONF_PASSKEY, DATA_BSBLAN_CLIENT, DOMAIN
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the BSB-Lan component."""
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up BSB-Lan from a config entry."""
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
bsblan = BSBLan(
|
||||
entry.data[CONF_HOST],
|
||||
passkey=entry.data[CONF_PASSKEY],
|
||||
loop=hass.loop,
|
||||
port=entry.data[CONF_PORT],
|
||||
session=session,
|
||||
)
|
||||
|
||||
try:
|
||||
await bsblan.info()
|
||||
except BSBLanConnectionError as exception:
|
||||
raise ConfigEntryNotReady from exception
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = {DATA_BSBLAN_CLIENT: bsblan}
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, CLIMATE_DOMAIN)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload BSBLan config entry."""
|
||||
|
||||
await hass.config_entries.async_forward_entry_unload(entry, CLIMATE_DOMAIN)
|
||||
|
||||
# Cleanup
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
if not hass.data[DOMAIN]:
|
||||
del hass.data[DOMAIN]
|
||||
|
||||
return True
|
237
homeassistant/components/bsblan/climate.py
Normal file
237
homeassistant/components/bsblan/climate.py
Normal file
|
@ -0,0 +1,237 @@
|
|||
"""BSBLAN platform to control a compatible Climate Device."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from bsblan import BSBLan, BSBLanError, Info, State
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_PRESET_MODE,
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
PRESET_ECO,
|
||||
PRESET_NONE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_NAME,
|
||||
ATTR_TEMPERATURE,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import (
|
||||
ATTR_IDENTIFIERS,
|
||||
ATTR_MANUFACTURER,
|
||||
ATTR_MODEL,
|
||||
ATTR_TARGET_TEMPERATURE,
|
||||
DATA_BSBLAN_CLIENT,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
SCAN_INTERVAL = timedelta(seconds=20)
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
HVAC_MODES = [
|
||||
HVAC_MODE_AUTO,
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_OFF,
|
||||
]
|
||||
|
||||
PRESET_MODES = [
|
||||
PRESET_ECO,
|
||||
PRESET_NONE,
|
||||
]
|
||||
|
||||
HA_STATE_TO_BSBLAN = {
|
||||
HVAC_MODE_AUTO: "1",
|
||||
HVAC_MODE_HEAT: "3",
|
||||
HVAC_MODE_OFF: "0",
|
||||
}
|
||||
|
||||
BSBLAN_TO_HA_STATE = {value: key for key, value in HA_STATE_TO_BSBLAN.items()}
|
||||
|
||||
HA_PRESET_TO_BSBLAN = {
|
||||
PRESET_ECO: "2",
|
||||
}
|
||||
|
||||
BSBLAN_TO_HA_PRESET = {
|
||||
2: PRESET_ECO,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: Callable[[List[Entity], bool], None],
|
||||
) -> None:
|
||||
"""Set up BSBLan device based on a config entry."""
|
||||
bsblan: BSBLan = hass.data[DOMAIN][entry.entry_id][DATA_BSBLAN_CLIENT]
|
||||
info = await bsblan.info()
|
||||
async_add_entities([BSBLanClimate(entry.entry_id, bsblan, info)], True)
|
||||
|
||||
|
||||
class BSBLanClimate(ClimateEntity):
|
||||
"""Defines a BSBLan climate device."""
|
||||
|
||||
def __init__(
|
||||
self, entry_id: str, bsblan: BSBLan, info: Info,
|
||||
):
|
||||
"""Initialize BSBLan climate device."""
|
||||
self._current_temperature: Optional[float] = None
|
||||
self._available = True
|
||||
self._current_hvac_mode: Optional[int] = None
|
||||
self._target_temperature: Optional[float] = None
|
||||
self._info: Info = info
|
||||
self.bsblan = bsblan
|
||||
self._temperature_unit = None
|
||||
self._hvac_mode = None
|
||||
self._preset_mode = None
|
||||
self._store_hvac_mode = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._info.device_identification
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID for this sensor."""
|
||||
return self._info.device_identification
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement which this thermostat uses."""
|
||||
if self._temperature_unit == "°C":
|
||||
return TEMP_CELSIUS
|
||||
return TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current temperature."""
|
||||
return self._current_temperature
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
"""Return the current operation mode."""
|
||||
return self._current_hvac_mode
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
"""Return the list of available operation modes."""
|
||||
return HVAC_MODES
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the temperature we try to reach."""
|
||||
return self._target_temperature
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
"""List of available preset modes."""
|
||||
return PRESET_MODES
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the preset_mode."""
|
||||
return self._preset_mode
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set preset mode."""
|
||||
_LOGGER.debug("Setting preset mode to: %s", preset_mode)
|
||||
if preset_mode == PRESET_NONE:
|
||||
# restore previous hvac mode
|
||||
self._current_hvac_mode = self._store_hvac_mode
|
||||
else:
|
||||
# Store hvac mode.
|
||||
self._store_hvac_mode = self._current_hvac_mode
|
||||
await self.async_set_data(preset_mode=preset_mode)
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set HVAC mode."""
|
||||
_LOGGER.debug("Setting HVAC mode to: %s", hvac_mode)
|
||||
# preset should be none when hvac mode is set
|
||||
self._preset_mode = PRESET_NONE
|
||||
await self.async_set_data(hvac_mode=hvac_mode)
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperatures."""
|
||||
await self.async_set_data(**kwargs)
|
||||
|
||||
async def async_set_data(self, **kwargs: Any) -> None:
|
||||
"""Set device settings using BSBLan."""
|
||||
data = {}
|
||||
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
data[ATTR_TARGET_TEMPERATURE] = kwargs[ATTR_TEMPERATURE]
|
||||
_LOGGER.debug("Set temperature data = %s", data)
|
||||
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
data[ATTR_HVAC_MODE] = HA_STATE_TO_BSBLAN[kwargs[ATTR_HVAC_MODE]]
|
||||
_LOGGER.debug("Set hvac mode data = %s", data)
|
||||
|
||||
if ATTR_PRESET_MODE in kwargs:
|
||||
# for now we set the preset as hvac_mode as the api expect this
|
||||
data[ATTR_HVAC_MODE] = HA_PRESET_TO_BSBLAN[kwargs[ATTR_PRESET_MODE]]
|
||||
|
||||
try:
|
||||
await self.bsblan.thermostat(**data)
|
||||
except BSBLanError:
|
||||
_LOGGER.error("An error occurred while updating the BSBLan device")
|
||||
self._available = False
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update BSBlan entity."""
|
||||
try:
|
||||
state: State = await self.bsblan.state()
|
||||
except BSBLanError:
|
||||
if self._available:
|
||||
_LOGGER.error("An error occurred while updating the BSBLan device")
|
||||
self._available = False
|
||||
return
|
||||
|
||||
self._available = True
|
||||
|
||||
self._current_temperature = float(state.current_temperature)
|
||||
self._target_temperature = float(state.target_temperature)
|
||||
|
||||
# check if preset is active else get hvac mode
|
||||
_LOGGER.debug("state hvac/preset mode: %s", state.current_hvac_mode)
|
||||
if state.current_hvac_mode == "2":
|
||||
self._preset_mode = PRESET_ECO
|
||||
else:
|
||||
self._current_hvac_mode = BSBLAN_TO_HA_STATE[state.current_hvac_mode]
|
||||
self._preset_mode = PRESET_NONE
|
||||
|
||||
self._temperature_unit = state.temperature_unit
|
||||
|
||||
@property
|
||||
def device_info(self) -> Dict[str, Any]:
|
||||
"""Return device information about this BSBLan device."""
|
||||
return {
|
||||
ATTR_IDENTIFIERS: {(DOMAIN, self._info.device_identification)},
|
||||
ATTR_NAME: "BSBLan Device",
|
||||
ATTR_MANUFACTURER: "BSBLan",
|
||||
ATTR_MODEL: self._info.controller_variant,
|
||||
}
|
81
homeassistant/components/bsblan/config_flow.py
Normal file
81
homeassistant/components/bsblan/config_flow.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
"""Config flow for BSB-Lan integration."""
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from bsblan import BSBLan, BSBLanError, Info
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.helpers import ConfigType
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import ( # pylint:disable=unused-import
|
||||
CONF_DEVICE_IDENT,
|
||||
CONF_PASSKEY,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a BSBLan config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: Optional[ConfigType] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Handle a flow initiated by the user."""
|
||||
if user_input is None:
|
||||
return self._show_setup_form()
|
||||
|
||||
try:
|
||||
info = await self._get_bsblan_info(
|
||||
host=user_input[CONF_HOST],
|
||||
port=user_input[CONF_PORT],
|
||||
passkey=user_input[CONF_PASSKEY],
|
||||
)
|
||||
except BSBLanError:
|
||||
return self._show_setup_form({"base": "connection_error"})
|
||||
|
||||
# Check if already configured
|
||||
await self.async_set_unique_id(info.device_identification)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=info.device_identification,
|
||||
data={
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
CONF_PASSKEY: user_input[CONF_PASSKEY],
|
||||
CONF_DEVICE_IDENT: info.device_identification,
|
||||
},
|
||||
)
|
||||
|
||||
def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]:
|
||||
"""Show the setup form to the user."""
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Optional(CONF_PORT, default=80): int,
|
||||
vol.Optional(CONF_PASSKEY, default=""): str,
|
||||
}
|
||||
),
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
async def _get_bsblan_info(
|
||||
self, host: str, passkey: Optional[str], port: int
|
||||
) -> Info:
|
||||
"""Get device information from an BSBLan device."""
|
||||
session = async_get_clientsession(self.hass)
|
||||
_LOGGER.debug("request bsblan.info:")
|
||||
bsblan = BSBLan(
|
||||
host, passkey=passkey, port=port, session=session, loop=self.hass.loop
|
||||
)
|
||||
return await bsblan.info()
|
26
homeassistant/components/bsblan/const.py
Normal file
26
homeassistant/components/bsblan/const.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""Constants for the BSB-Lan integration."""
|
||||
|
||||
DOMAIN = "bsblan"
|
||||
|
||||
DATA_BSBLAN_CLIENT = "bsblan_client"
|
||||
DATA_BSBLAN_TIMER = "bsblan_timer"
|
||||
DATA_BSBLAN_UPDATED = "bsblan_updated"
|
||||
|
||||
ATTR_IDENTIFIERS = "identifiers"
|
||||
ATTR_MODEL = "model"
|
||||
ATTR_MANUFACTURER = "manufacturer"
|
||||
|
||||
ATTR_TARGET_TEMPERATURE = "target_temperature"
|
||||
ATTR_INSIDE_TEMPERATURE = "inside_temperature"
|
||||
ATTR_OUTSIDE_TEMPERATURE = "outside_temperature"
|
||||
|
||||
ATTR_STATE_ON = "on"
|
||||
ATTR_STATE_OFF = "off"
|
||||
|
||||
CONF_DEVICE_IDENT = "device_identification"
|
||||
CONF_CONTROLLER_FAM = "controller_family"
|
||||
CONF_CONTROLLER_VARI = "controller_variant"
|
||||
|
||||
SENSOR_TYPE_TEMPERATURE = "temperature"
|
||||
|
||||
CONF_PASSKEY = "passkey"
|
8
homeassistant/components/bsblan/manifest.json
Normal file
8
homeassistant/components/bsblan/manifest.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"domain": "bsblan",
|
||||
"name": "BSB-Lan",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/bsblan",
|
||||
"requirements": ["bsblan==0.3.6"],
|
||||
"codeowners": ["@liudger"]
|
||||
}
|
23
homeassistant/components/bsblan/strings.json
Normal file
23
homeassistant/components/bsblan/strings.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"title": "BSB-Lan",
|
||||
"config": {
|
||||
"flow_title": "BSB-Lan: {name}",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Connect to the BSB-Lan device",
|
||||
"description": "Set up you BSB-Lan device to integrate with Home Assistant.",
|
||||
"data": {
|
||||
"host": "Host or IP address",
|
||||
"port": "Port number",
|
||||
"passkey": "Passkey string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Failed to connect to BSB-Lan device."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
}
|
||||
}
|
||||
}
|
26
homeassistant/components/bsblan/translations/en.json
Normal file
26
homeassistant/components/bsblan/translations/en.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"config": {
|
||||
"title": "BSB-Lan",
|
||||
"flow_title": "BSB-Lan: {name}",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Connect to the BSB-Lan device",
|
||||
"description": "Set up you BSB-Lan device to integrate with Home Assistant.",
|
||||
"data": {
|
||||
"host": "Host or IP address",
|
||||
"port": "Port number",
|
||||
"passkey": "Passkey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Failed to connect to BSB-Lan device.",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured",
|
||||
"connection_error": "Failed to connect to BSB-Lan device."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ FLOWS = [
|
|||
"blebox",
|
||||
"braviatv",
|
||||
"brother",
|
||||
"bsblan",
|
||||
"cast",
|
||||
"cert_expiry",
|
||||
"coolmaster",
|
||||
|
|
|
@ -382,6 +382,9 @@ brottsplatskartan==0.0.1
|
|||
# homeassistant.components.brunt
|
||||
brunt==0.1.3
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
bsblan==0.3.6
|
||||
|
||||
# homeassistant.components.bluetooth_tracker
|
||||
bt_proximity==0.2
|
||||
|
||||
|
|
|
@ -161,6 +161,9 @@ broadlink==0.13.2
|
|||
# homeassistant.components.brother
|
||||
brother==0.1.14
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
bsblan==0.3.6
|
||||
|
||||
# homeassistant.components.buienradar
|
||||
buienradar==1.0.4
|
||||
|
||||
|
|
44
tests/components/bsblan/__init__.py
Normal file
44
tests/components/bsblan/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
"""Tests for the bsblan integration."""
|
||||
|
||||
from homeassistant.components.bsblan.const import (
|
||||
CONF_DEVICE_IDENT,
|
||||
CONF_PASSKEY,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
async def init_integration(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, skip_setup: bool = False,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the BSBLan integration in Home Assistant."""
|
||||
|
||||
aioclient_mock.post(
|
||||
"http://example.local:80/1234/JQ?Parameter=6224,6225,6226",
|
||||
params={"Parameter": "6224,6225,6226"},
|
||||
text=load_fixture("bsblan/info.json"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="RVS21.831F/127",
|
||||
data={
|
||||
CONF_HOST: "example.local",
|
||||
CONF_PASSKEY: "1234",
|
||||
CONF_PORT: 80,
|
||||
CONF_DEVICE_IDENT: "RVS21.831F/127",
|
||||
},
|
||||
)
|
||||
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
if not skip_setup:
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
92
tests/components/bsblan/test_config_flow.py
Normal file
92
tests/components/bsblan/test_config_flow.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
"""Tests for the BSBLan device config flow."""
|
||||
import aiohttp
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.bsblan import config_flow
|
||||
from homeassistant.components.bsblan.const import CONF_DEVICE_IDENT, CONF_PASSKEY
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import init_integration
|
||||
|
||||
from tests.common import load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
async def test_show_user_form(hass: HomeAssistant) -> None:
|
||||
"""Test that the user set up form is served."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN, context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
|
||||
async def test_connection_error(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test we show user form on BSBLan connection error."""
|
||||
aioclient_mock.post(
|
||||
"http://example.local:80/1234/JQ?Parameter=6224,6225,6226",
|
||||
exc=aiohttp.ClientError,
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_HOST: "example.local", CONF_PASSKEY: "1234", CONF_PORT: 80},
|
||||
)
|
||||
|
||||
assert result["errors"] == {"base": "connection_error"}
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
|
||||
async def test_user_device_exists_abort(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||
) -> None:
|
||||
"""Test we abort zeroconf flow if BSBLan device already configured."""
|
||||
await init_integration(hass, aioclient_mock)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_HOST: "example.local", CONF_PASSKEY: "1234", CONF_PORT: 80},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
|
||||
|
||||
async def test_full_user_flow_implementation(
|
||||
hass: HomeAssistant, aioclient_mock
|
||||
) -> None:
|
||||
"""Test the full manual user flow from start to finish."""
|
||||
aioclient_mock.post(
|
||||
"http://example.local:80/1234/JQ?Parameter=6224,6225,6226",
|
||||
text=load_fixture("bsblan/info.json"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
config_flow.DOMAIN, context={"source": SOURCE_USER},
|
||||
)
|
||||
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_HOST: "example.local", CONF_PASSKEY: "1234", CONF_PORT: 80},
|
||||
)
|
||||
|
||||
assert result["data"][CONF_HOST] == "example.local"
|
||||
assert result["data"][CONF_PASSKEY] == "1234"
|
||||
assert result["data"][CONF_PORT] == 80
|
||||
assert result["data"][CONF_DEVICE_IDENT] == "RVS21.831F/127"
|
||||
assert result["title"] == "RVS21.831F/127"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
entries = hass.config_entries.async_entries(config_flow.DOMAIN)
|
||||
assert entries[0].unique_id == "RVS21.831F/127"
|
23
tests/fixtures/bsblan/info.json
vendored
Normal file
23
tests/fixtures/bsblan/info.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"6224": {
|
||||
"name": "Geräte-Identifikation",
|
||||
"value": "RVS21.831F/127",
|
||||
"unit": "",
|
||||
"desc": "",
|
||||
"dataType": 7
|
||||
},
|
||||
"6225": {
|
||||
"name": "Device family",
|
||||
"value": "211",
|
||||
"unit": "",
|
||||
"desc": "",
|
||||
"dataType": 0
|
||||
},
|
||||
"6226": {
|
||||
"name": "Device variant",
|
||||
"value": "127",
|
||||
"unit": "",
|
||||
"desc": "",
|
||||
"dataType": 0
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue