Prepare Plugwise integration for USB products (#41201)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/41440/head
parent
343e5d64b8
commit
b8f291d58e
|
@ -1,41 +1,21 @@
|
|||
"""Plugwise platform for Home Assistant Core."""
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from Plugwise_Smile.Smile import Smile
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
COORDINATOR,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
)
|
||||
from .const import ALL_PLATFORMS, DOMAIN, UNDO_UPDATE_LISTENER
|
||||
from .gateway import async_setup_entry_gw
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_PLATFORMS = ["sensor"]
|
||||
ALL_PLATFORMS = ["binary_sensor", "climate", "sensor", "switch"]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Plugwise platform."""
|
||||
|
@ -43,108 +23,11 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Plugwise Smiles from a config entry."""
|
||||
websession = async_get_clientsession(hass, verify_ssl=False)
|
||||
|
||||
api = Smile(
|
||||
host=entry.data[CONF_HOST],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
|
||||
timeout=30,
|
||||
websession=websession,
|
||||
)
|
||||
|
||||
try:
|
||||
connected = await api.connect()
|
||||
|
||||
if not connected:
|
||||
_LOGGER.error("Unable to connect to Smile")
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
except Smile.InvalidAuthentication:
|
||||
_LOGGER.error("Invalid Smile ID")
|
||||
return False
|
||||
|
||||
except Smile.PlugwiseError as err:
|
||||
_LOGGER.error("Error while communicating to device")
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
except asyncio.TimeoutError as err:
|
||||
_LOGGER.error("Timeout while connecting to Smile")
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
update_interval = timedelta(
|
||||
seconds=entry.options.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL[api.smile_type]
|
||||
)
|
||||
)
|
||||
|
||||
async def async_update_data():
|
||||
"""Update data via API endpoint."""
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
await api.full_update_device()
|
||||
return True
|
||||
except Smile.XMLDataMissingError as err:
|
||||
raise UpdateFailed("Smile update failed") from err
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Smile",
|
||||
update_method=async_update_data,
|
||||
update_interval=update_interval,
|
||||
)
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
||||
if not coordinator.last_update_success:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
api.get_all_devices()
|
||||
|
||||
if entry.unique_id is None:
|
||||
if api.smile_version[0] != "1.8.0":
|
||||
hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname)
|
||||
|
||||
undo_listener = entry.add_update_listener(_update_listener)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||
"api": api,
|
||||
COORDINATOR: coordinator,
|
||||
UNDO_UPDATE_LISTENER: undo_listener,
|
||||
}
|
||||
|
||||
device_registry = await dr.async_get_registry(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, api.gateway_id)},
|
||||
manufacturer="Plugwise",
|
||||
name=entry.title,
|
||||
model=f"Smile {api.smile_name}",
|
||||
sw_version=api.smile_version[0],
|
||||
)
|
||||
|
||||
single_master_thermostat = api.single_master_thermostat()
|
||||
|
||||
platforms = ALL_PLATFORMS
|
||||
if single_master_thermostat is None:
|
||||
platforms = SENSOR_PLATFORMS
|
||||
|
||||
for component in platforms:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Handle options update."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]
|
||||
coordinator.update_interval = timedelta(
|
||||
seconds=entry.options.get(CONF_SCAN_INTERVAL)
|
||||
)
|
||||
"""Set up Plugwise components from a config entry."""
|
||||
if entry.data.get(CONF_HOST):
|
||||
return await async_setup_entry_gw(hass, entry)
|
||||
# PLACEHOLDER USB entry setup
|
||||
return False
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
|
@ -164,60 +47,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class SmileGateway(CoordinatorEntity):
|
||||
"""Represent Smile Gateway."""
|
||||
|
||||
def __init__(self, api, coordinator, name, dev_id):
|
||||
"""Initialise the gateway."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._api = api
|
||||
self._name = name
|
||||
self._dev_id = dev_id
|
||||
|
||||
self._unique_id = None
|
||||
self._model = None
|
||||
|
||||
self._entity_name = self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the entity, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_info(self) -> Dict[str, any]:
|
||||
"""Return the device information."""
|
||||
|
||||
device_information = {
|
||||
"identifiers": {(DOMAIN, self._dev_id)},
|
||||
"name": self._entity_name,
|
||||
"manufacturer": "Plugwise",
|
||||
}
|
||||
|
||||
if self._model is not None:
|
||||
device_information["model"] = self._model.replace("_", " ").title()
|
||||
|
||||
if self._dev_id != self._api.gateway_id:
|
||||
device_information["via_device"] = (DOMAIN, self._api.gateway_id)
|
||||
|
||||
return device_information
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to updates."""
|
||||
self._async_process_data()
|
||||
self.async_on_remove(
|
||||
self.coordinator.async_add_listener(self._async_process_data)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_process_data(self):
|
||||
"""Interpret and process API data."""
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -18,7 +18,6 @@ from homeassistant.components.climate.const import (
|
|||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import SmileGateway
|
||||
from .const import (
|
||||
COORDINATOR,
|
||||
DEFAULT_MAX_TEMP,
|
||||
|
@ -27,6 +26,7 @@ from .const import (
|
|||
SCHEDULE_OFF,
|
||||
SCHEDULE_ON,
|
||||
)
|
||||
from .gateway import SmileGateway
|
||||
|
||||
HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO]
|
||||
HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO]
|
||||
|
|
|
@ -14,35 +14,30 @@ from .const import ( # pylint:disable=unused-import
|
|||
DEFAULT_PORT,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
ZEROCONF_MAP,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ZEROCONF_MAP = {
|
||||
"smile": "P1 DSMR",
|
||||
"smile_thermo": "Climate (Anna)",
|
||||
"smile_open_therm": "Climate (Adam)",
|
||||
}
|
||||
|
||||
|
||||
def _base_schema(discovery_info):
|
||||
"""Generate base schema."""
|
||||
base_schema = {}
|
||||
def _base_gw_schema(discovery_info):
|
||||
"""Generate base schema for gateways."""
|
||||
base_gw_schema = {}
|
||||
|
||||
if not discovery_info:
|
||||
base_schema[vol.Required(CONF_HOST)] = str
|
||||
base_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int
|
||||
base_gw_schema[vol.Required(CONF_HOST)] = str
|
||||
base_gw_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int
|
||||
|
||||
base_schema[vol.Required(CONF_PASSWORD)] = str
|
||||
base_gw_schema[vol.Required(CONF_PASSWORD)] = str
|
||||
|
||||
return vol.Schema(base_schema)
|
||||
return vol.Schema(base_gw_schema)
|
||||
|
||||
|
||||
async def validate_input(hass: core.HomeAssistant, data):
|
||||
async def validate_gw_input(hass: core.HomeAssistant, data):
|
||||
"""
|
||||
Validate the user input allows us to connect.
|
||||
Validate the user input allows us to connect to the gateray.
|
||||
|
||||
Data has the keys from _base_schema() with values provided by the user.
|
||||
Data has the keys from _base_gw_schema() with values provided by the user.
|
||||
"""
|
||||
websession = async_get_clientsession(hass, verify_ssl=False)
|
||||
|
||||
|
@ -64,6 +59,9 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||
return api
|
||||
|
||||
|
||||
# PLACEHOLDER USB connection validation
|
||||
|
||||
|
||||
class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Plugwise Smile."""
|
||||
|
||||
|
@ -95,8 +93,8 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
}
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
async def async_step_user_gateway(self, user_input=None):
|
||||
"""Handle the initial step for gateways."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
|
@ -110,7 +108,7 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_abort(reason="already_configured")
|
||||
|
||||
try:
|
||||
api = await validate_input(self.hass, user_input)
|
||||
api = await validate_gw_input(self.hass, user_input)
|
||||
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
|
@ -128,11 +126,19 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_create_entry(title=api.smile_name, data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=_base_schema(self.discovery_info),
|
||||
step_id="user_gateway",
|
||||
data_schema=_base_gw_schema(self.discovery_info),
|
||||
errors=errors or {},
|
||||
)
|
||||
|
||||
# PLACEHOLDER USB async_step_user_usb and async_step_user_usb_manual_paht
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
|
||||
# PLACEHOLDER USB vs Gateway Logic
|
||||
return await self.async_step_user_gateway()
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""Constant for Plugwise component."""
|
||||
DOMAIN = "plugwise"
|
||||
|
||||
SENSOR_PLATFORMS = ["sensor"]
|
||||
ALL_PLATFORMS = ["binary_sensor", "climate", "sensor", "switch"]
|
||||
|
||||
# Sensor mapping
|
||||
SENSOR_MAP_MODEL = 0
|
||||
SENSOR_MAP_UOM = 1
|
||||
|
@ -42,3 +45,9 @@ FLOW_ON_ICON = "mdi:water-pump"
|
|||
|
||||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
COORDINATOR = "coordinator"
|
||||
|
||||
ZEROCONF_MAP = {
|
||||
"smile": "P1",
|
||||
"smile_thermo": "Anna",
|
||||
"smile_open_therm": "Adam",
|
||||
}
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
"""Plugwise platform for Home Assistant Core."""
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from Plugwise_Smile.Smile import Smile
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
ALL_PLATFORMS,
|
||||
COORDINATOR,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
SENSOR_PLATFORMS,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Plugwise Smiles from a config entry."""
|
||||
websession = async_get_clientsession(hass, verify_ssl=False)
|
||||
|
||||
api = Smile(
|
||||
host=entry.data[CONF_HOST],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
|
||||
timeout=30,
|
||||
websession=websession,
|
||||
)
|
||||
|
||||
try:
|
||||
connected = await api.connect()
|
||||
|
||||
if not connected:
|
||||
_LOGGER.error("Unable to connect to Smile")
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
except Smile.InvalidAuthentication:
|
||||
_LOGGER.error("Invalid Smile ID")
|
||||
return False
|
||||
|
||||
except Smile.PlugwiseError as err:
|
||||
_LOGGER.error("Error while communicating to device")
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
except asyncio.TimeoutError as err:
|
||||
_LOGGER.error("Timeout while connecting to Smile")
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
update_interval = timedelta(
|
||||
seconds=entry.options.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL[api.smile_type]
|
||||
)
|
||||
)
|
||||
|
||||
async def async_update_data():
|
||||
"""Update data via API endpoint."""
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
await api.full_update_device()
|
||||
return True
|
||||
except Smile.XMLDataMissingError as err:
|
||||
raise UpdateFailed("Smile update failed") from err
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Smile",
|
||||
update_method=async_update_data,
|
||||
update_interval=update_interval,
|
||||
)
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
||||
if not coordinator.last_update_success:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
api.get_all_devices()
|
||||
|
||||
if entry.unique_id is None:
|
||||
if api.smile_version[0] != "1.8.0":
|
||||
hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname)
|
||||
|
||||
undo_listener = entry.add_update_listener(_update_listener)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||
"api": api,
|
||||
COORDINATOR: coordinator,
|
||||
UNDO_UPDATE_LISTENER: undo_listener,
|
||||
}
|
||||
|
||||
device_registry = await dr.async_get_registry(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, api.gateway_id)},
|
||||
manufacturer="Plugwise",
|
||||
name=entry.title,
|
||||
model=f"Smile {api.smile_name}",
|
||||
sw_version=api.smile_version[0],
|
||||
)
|
||||
|
||||
single_master_thermostat = api.single_master_thermostat()
|
||||
|
||||
platforms = ALL_PLATFORMS
|
||||
if single_master_thermostat is None:
|
||||
platforms = SENSOR_PLATFORMS
|
||||
|
||||
for component in platforms:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Handle options update."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR]
|
||||
coordinator.update_interval = timedelta(
|
||||
seconds=entry.options.get(CONF_SCAN_INTERVAL)
|
||||
)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
for component in ALL_PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]()
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class SmileGateway(CoordinatorEntity):
|
||||
"""Represent Smile Gateway."""
|
||||
|
||||
def __init__(self, api, coordinator, name, dev_id):
|
||||
"""Initialise the gateway."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._api = api
|
||||
self._name = name
|
||||
self._dev_id = dev_id
|
||||
|
||||
self._unique_id = None
|
||||
self._model = None
|
||||
|
||||
self._entity_name = self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the entity, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_info(self) -> Dict[str, any]:
|
||||
"""Return the device information."""
|
||||
|
||||
device_information = {
|
||||
"identifiers": {(DOMAIN, self._dev_id)},
|
||||
"name": self._entity_name,
|
||||
"manufacturer": "Plugwise",
|
||||
}
|
||||
|
||||
if self._model is not None:
|
||||
device_information["model"] = self._model.replace("_", " ").title()
|
||||
|
||||
if self._dev_id != self._api.gateway_id:
|
||||
device_information["via_device"] = (DOMAIN, self._api.gateway_id)
|
||||
|
||||
return device_information
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to updates."""
|
||||
self._async_process_data()
|
||||
self.async_on_remove(
|
||||
self.coordinator.async_add_listener(self._async_process_data)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_process_data(self):
|
||||
"""Interpret and process API data."""
|
||||
raise NotImplementedError
|
|
@ -19,7 +19,6 @@ from homeassistant.const import (
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import SmileGateway
|
||||
from .const import (
|
||||
COOL_ICON,
|
||||
COORDINATOR,
|
||||
|
@ -32,6 +31,7 @@ from .const import (
|
|||
SENSOR_MAP_UOM,
|
||||
UNIT_LUMEN,
|
||||
)
|
||||
from .gateway import SmileGateway
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -12,6 +12,13 @@
|
|||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Plugwise type",
|
||||
"description": "Product:",
|
||||
"data": {
|
||||
"flow_type": "Connection type"
|
||||
}
|
||||
},
|
||||
"user_gateway": {
|
||||
"title": "Connect to the Smile",
|
||||
"description": "Please enter:",
|
||||
"data": {
|
||||
|
|
|
@ -7,8 +7,8 @@ from Plugwise_Smile.Smile import Smile
|
|||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.core import callback
|
||||
|
||||
from . import SmileGateway
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .gateway import SmileGateway
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from Plugwise_Smile.Smile import Smile
|
|||
import jsonpickle
|
||||
import pytest
|
||||
|
||||
from tests.async_mock import AsyncMock, patch
|
||||
from tests.async_mock import AsyncMock, Mock, patch
|
||||
from tests.common import load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
@ -47,7 +47,7 @@ def mock_smile_error(aioclient_mock: AiohttpClientMocker) -> None:
|
|||
@pytest.fixture(name="mock_smile_notconnect")
|
||||
def mock_smile_notconnect():
|
||||
"""Mock the Plugwise Smile general connection failure for Home Assistant."""
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.PlugwiseError = Smile.PlugwiseError
|
||||
|
@ -64,7 +64,7 @@ def _get_device_data(chosen_env, device_id):
|
|||
def mock_smile_adam():
|
||||
"""Create a Mock Adam environment for testing exceptions."""
|
||||
chosen_env = "adam_multiple_devices_per_zone"
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.XMLDataMissingError = Smile.XMLDataMissingError
|
||||
|
@ -79,6 +79,9 @@ def mock_smile_adam():
|
|||
smile_mock.return_value.full_update_device.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.single_master_thermostat.side_effect = Mock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_schedule_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
@ -104,7 +107,7 @@ def mock_smile_adam():
|
|||
def mock_smile_anna():
|
||||
"""Create a Mock Anna environment for testing exceptions."""
|
||||
chosen_env = "anna_heatpump"
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.XMLDataMissingError = Smile.XMLDataMissingError
|
||||
|
@ -119,6 +122,9 @@ def mock_smile_anna():
|
|||
smile_mock.return_value.full_update_device.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.single_master_thermostat.side_effect = Mock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_schedule_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
@ -144,7 +150,7 @@ def mock_smile_anna():
|
|||
def mock_smile_p1():
|
||||
"""Create a Mock P1 DSMR environment for testing exceptions."""
|
||||
chosen_env = "p1v3_full_option"
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
with patch("homeassistant.components.plugwise.gateway.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.XMLDataMissingError = Smile.XMLDataMissingError
|
||||
|
@ -160,6 +166,10 @@ def mock_smile_p1():
|
|||
return_value=True
|
||||
)
|
||||
|
||||
smile_mock.return_value.single_master_thermostat.side_effect = Mock(
|
||||
return_value=None
|
||||
)
|
||||
|
||||
smile_mock.return_value.get_all_devices.return_value = _read_json(
|
||||
chosen_env, "get_all_devices"
|
||||
)
|
||||
|
|
|
@ -4,11 +4,14 @@ import asyncio
|
|||
|
||||
from Plugwise_Smile.Smile import Smile
|
||||
|
||||
from homeassistant.components.plugwise import DOMAIN
|
||||
from homeassistant.components.plugwise.gateway import async_unload_entry
|
||||
from homeassistant.config_entries import (
|
||||
ENTRY_STATE_SETUP_ERROR,
|
||||
ENTRY_STATE_SETUP_RETRY,
|
||||
)
|
||||
|
||||
from tests.common import AsyncMock
|
||||
from tests.components.plugwise.common import async_init_integration
|
||||
|
||||
|
||||
|
@ -43,3 +46,12 @@ async def test_smile_adam_xmlerror(hass, mock_smile_adam):
|
|||
mock_smile_adam.full_update_device.side_effect = Smile.XMLDataMissingError
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||
|
||||
|
||||
async def test_unload_entry(hass, mock_smile_adam):
|
||||
"""Test being able to unload an entry."""
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
|
||||
mock_smile_adam.async_reset = AsyncMock(return_value=True)
|
||||
assert await async_unload_entry(hass, entry)
|
||||
assert not hass.data[DOMAIN]
|
||||
|
|
Loading…
Reference in New Issue