mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add support for Stretch product to Plugwise integration (#40108)
* Initial switch-group/stretch with failing wk_lisa_battery test * Adding switch tests, but TypeErrors, needs more investigation * Fixes and tests aligned * Review updates * Use const * Comments * Add stretch hostname for testing part * Remove unused consts * Revert guardings in line with -beta * Catchup with dev (mostly with ourselves from #41201) * Update docstring * Remove debug logging * Fix for #42725 (incorrect entity namingi) * Fix naming for gas interval * Add missing CONF_USERNAME and use of it * Change "dummy" to "class" * Don't use "class" * Fix CONF_USERNAME default, dummy and other consts Co-authored-by: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com>
This commit is contained in:
parent
55cdec8c4e
commit
877bfcb308
@ -33,24 +33,23 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
||||
all_devices = api.get_all_devices()
|
||||
for dev_id, device_properties in all_devices.items():
|
||||
if device_properties["class"] != "heater_central":
|
||||
continue
|
||||
if device_properties["class"] == "heater_central":
|
||||
data = api.get_device_data(dev_id)
|
||||
|
||||
data = api.get_device_data(dev_id)
|
||||
for binary_sensor, dummy in BINARY_SENSOR_MAP.items():
|
||||
if binary_sensor not in data:
|
||||
continue
|
||||
for binary_sensor in BINARY_SENSOR_MAP:
|
||||
if binary_sensor not in data:
|
||||
continue
|
||||
|
||||
entities.append(
|
||||
PwBinarySensor(
|
||||
api,
|
||||
coordinator,
|
||||
device_properties["name"],
|
||||
binary_sensor,
|
||||
dev_id,
|
||||
device_properties["class"],
|
||||
entities.append(
|
||||
PwBinarySensor(
|
||||
api,
|
||||
coordinator,
|
||||
device_properties["name"],
|
||||
dev_id,
|
||||
binary_sensor,
|
||||
device_properties["class"],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
@ -58,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class PwBinarySensor(SmileSensor, BinarySensorEntity):
|
||||
"""Representation of a Plugwise binary_sensor."""
|
||||
|
||||
def __init__(self, api, coordinator, name, binary_sensor, dev_id, model):
|
||||
def __init__(self, api, coordinator, name, dev_id, binary_sensor, model):
|
||||
"""Set up the Plugwise API."""
|
||||
super().__init__(api, coordinator, name, dev_id, binary_sensor)
|
||||
|
||||
@ -74,11 +73,6 @@ class PwBinarySensor(SmileSensor, BinarySensorEntity):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._is_on
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._icon
|
||||
|
||||
@callback
|
||||
def _async_process_data(self):
|
||||
"""Update the entity."""
|
||||
@ -95,16 +89,10 @@ class PwBinarySensor(SmileSensor, BinarySensorEntity):
|
||||
|
||||
self._is_on = data[self._binary_sensor]
|
||||
|
||||
self._state = STATE_OFF
|
||||
self._state = STATE_ON if self._is_on else STATE_OFF
|
||||
if self._binary_sensor == "dhw_state":
|
||||
self._icon = FLOW_OFF_ICON
|
||||
self._icon = FLOW_ON_ICON if self._is_on else FLOW_OFF_ICON
|
||||
if self._binary_sensor == "slave_boiler_state":
|
||||
self._icon = IDLE_ICON
|
||||
if self._is_on:
|
||||
self._state = STATE_ON
|
||||
if self._binary_sensor == "dhw_state":
|
||||
self._icon = FLOW_ON_ICON
|
||||
if self._binary_sensor == "slave_boiler_state":
|
||||
self._icon = FLAME_ICON
|
||||
self._icon = FLAME_ICON if self._is_on else IDLE_ICON
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
@ -5,7 +5,15 @@ from Plugwise_Smile.Smile import Smile
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, core, exceptions
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL
|
||||
from homeassistant.const import (
|
||||
CONF_BASE,
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
@ -28,14 +36,21 @@ def _base_gw_schema(discovery_info):
|
||||
base_gw_schema[vol.Required(CONF_HOST)] = str
|
||||
base_gw_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int
|
||||
|
||||
base_gw_schema[vol.Required(CONF_PASSWORD)] = str
|
||||
base_gw_schema.update(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_USERNAME, default="smile", description={"suggested_value": "smile"}
|
||||
): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
return vol.Schema(base_gw_schema)
|
||||
|
||||
|
||||
async def validate_gw_input(hass: core.HomeAssistant, data):
|
||||
"""
|
||||
Validate the user input allows us to connect to the gateray.
|
||||
Validate whether the user input allows us to connect to the gateray.
|
||||
|
||||
Data has the keys from _base_gw_schema() with values provided by the user.
|
||||
"""
|
||||
@ -45,6 +60,7 @@ async def validate_gw_input(hass: core.HomeAssistant, data):
|
||||
host=data[CONF_HOST],
|
||||
password=data[CONF_PASSWORD],
|
||||
port=data[CONF_PORT],
|
||||
username=data[CONF_USERNAME],
|
||||
timeout=30,
|
||||
websession=websession,
|
||||
)
|
||||
@ -89,7 +105,7 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self.context["title_placeholders"] = {
|
||||
CONF_HOST: discovery_info[CONF_HOST],
|
||||
CONF_PORT: discovery_info.get(CONF_PORT, DEFAULT_PORT),
|
||||
"name": _name,
|
||||
CONF_NAME: _name,
|
||||
}
|
||||
return await self.async_step_user()
|
||||
|
||||
@ -111,12 +127,12 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
api = await validate_gw_input(self.hass, user_input)
|
||||
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
errors[CONF_BASE] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
errors["base"] = "invalid_auth"
|
||||
errors[CONF_BASE] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
errors[CONF_BASE] = "unknown"
|
||||
if not errors:
|
||||
await self.async_set_unique_id(
|
||||
api.smile_hostname or api.gateway_id, raise_on_progress=False
|
||||
|
@ -1,53 +1,53 @@
|
||||
"""Constant for Plugwise component."""
|
||||
DOMAIN = "plugwise"
|
||||
|
||||
SENSOR_PLATFORMS = ["sensor"]
|
||||
SENSOR_PLATFORMS = ["sensor", "switch"]
|
||||
ALL_PLATFORMS = ["binary_sensor", "climate", "sensor", "switch"]
|
||||
|
||||
# Sensor mapping
|
||||
SENSOR_MAP_DEVICE_CLASS = 2
|
||||
SENSOR_MAP_ICON = 3
|
||||
SENSOR_MAP_MODEL = 0
|
||||
SENSOR_MAP_UOM = 1
|
||||
SENSOR_MAP_DEVICE_CLASS = 2
|
||||
|
||||
# Default directives
|
||||
DEFAULT_NAME = "Smile"
|
||||
DEFAULT_USERNAME = "smile"
|
||||
DEFAULT_TIMEOUT = 10
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_MIN_TEMP = 4
|
||||
DEFAULT_MAX_TEMP = 30
|
||||
DEFAULT_SCAN_INTERVAL = {"thermostat": 60, "power": 10}
|
||||
DEFAULT_NAME = "Smile"
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_USERNAME = "smile"
|
||||
DEFAULT_SCAN_INTERVAL = {"power": 10, "stretch": 60, "thermostat": 60}
|
||||
DEFAULT_TIMEOUT = 60
|
||||
|
||||
# Configuration directives
|
||||
CONF_MIN_TEMP = "min_temp"
|
||||
CONF_MAX_TEMP = "max_temp"
|
||||
CONF_THERMOSTAT = "thermostat"
|
||||
CONF_POWER = "power"
|
||||
CONF_HEATER = "heater"
|
||||
CONF_SOLAR = "solar"
|
||||
CONF_BASE = "base"
|
||||
CONF_GAS = "gas"
|
||||
CONF_MAX_TEMP = "max_temp"
|
||||
CONF_MIN_TEMP = "min_temp"
|
||||
CONF_POWER = "power"
|
||||
CONF_THERMOSTAT = "thermostat"
|
||||
|
||||
ATTR_ILLUMINANCE = "illuminance"
|
||||
UNIT_LUMEN = "lm"
|
||||
|
||||
CURRENT_HVAC_DHW = "hot_water"
|
||||
UNIT_LUMEN = "lm"
|
||||
|
||||
DEVICE_STATE = "device_state"
|
||||
|
||||
SCHEDULE_ON = "true"
|
||||
SCHEDULE_OFF = "false"
|
||||
SCHEDULE_ON = "true"
|
||||
|
||||
COOL_ICON = "mdi:snowflake"
|
||||
FLAME_ICON = "mdi:fire"
|
||||
IDLE_ICON = "mdi:circle-off-outline"
|
||||
FLOW_OFF_ICON = "mdi:water-pump-off"
|
||||
FLOW_ON_ICON = "mdi:water-pump"
|
||||
IDLE_ICON = "mdi:circle-off-outline"
|
||||
SWITCH_ICON = "mdi:electric-switch"
|
||||
|
||||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
COORDINATOR = "coordinator"
|
||||
|
||||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
ZEROCONF_MAP = {
|
||||
"smile": "P1",
|
||||
"smile_thermo": "Anna",
|
||||
"smile_open_therm": "Adam",
|
||||
"stretch": "Stretch",
|
||||
}
|
||||
|
@ -10,7 +10,13 @@ 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.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@ -26,6 +32,8 @@ from .const import (
|
||||
COORDINATOR,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_TIMEOUT,
|
||||
DEFAULT_USERNAME,
|
||||
DOMAIN,
|
||||
SENSOR_PLATFORMS,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
@ -42,6 +50,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
api = Smile(
|
||||
host=entry.data[CONF_HOST],
|
||||
username=entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
port=entry.data.get(CONF_PORT, DEFAULT_PORT),
|
||||
timeout=30,
|
||||
@ -56,15 +65,15 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
except Smile.InvalidAuthentication:
|
||||
_LOGGER.error("Invalid Smile ID")
|
||||
_LOGGER.error("Invalid username or Smile ID")
|
||||
return False
|
||||
|
||||
except Smile.PlugwiseError as err:
|
||||
_LOGGER.error("Error while communicating to device")
|
||||
_LOGGER.error("Error while communicating to device %s", api.smile_name)
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
except asyncio.TimeoutError as err:
|
||||
_LOGGER.error("Timeout while connecting to Smile")
|
||||
_LOGGER.error("Timeout while connecting to Smile %s", api.smile_name)
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
update_interval = timedelta(
|
||||
@ -76,7 +85,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_update_data():
|
||||
"""Update data via API endpoint."""
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
async with async_timeout.timeout(DEFAULT_TIMEOUT):
|
||||
await api.full_update_device()
|
||||
return True
|
||||
except Smile.XMLDataMissingError as err:
|
||||
@ -85,7 +94,7 @@ async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="Smile",
|
||||
name=f"Smile {api.smile_name}",
|
||||
update_method=async_update_data,
|
||||
update_interval=update_interval,
|
||||
)
|
||||
|
@ -116,26 +116,30 @@ ENERGY_SENSOR_MAP = {
|
||||
DEVICE_CLASS_POWER,
|
||||
],
|
||||
"electricity_produced_off_peak_point": [
|
||||
"Current Consumed Power (off peak)",
|
||||
"Current Produced Power (off peak)",
|
||||
POWER_WATT,
|
||||
DEVICE_CLASS_POWER,
|
||||
],
|
||||
"electricity_produced_peak_point": [
|
||||
"Current Consumed Power",
|
||||
"Current Produced Power",
|
||||
POWER_WATT,
|
||||
DEVICE_CLASS_POWER,
|
||||
],
|
||||
"electricity_produced_off_peak_cumulative": [
|
||||
"Cumulative Consumed Power (off peak)",
|
||||
"Cumulative Produced Power (off peak)",
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
DEVICE_CLASS_POWER,
|
||||
],
|
||||
"electricity_produced_peak_cumulative": [
|
||||
"Cumulative Consumed Power",
|
||||
"Cumulative Produced Power",
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
DEVICE_CLASS_POWER,
|
||||
],
|
||||
"gas_consumed_interval": ["Current Consumed Gas", VOLUME_CUBIC_METERS, None],
|
||||
"gas_consumed_interval": [
|
||||
"Current Consumed Gas Interval",
|
||||
VOLUME_CUBIC_METERS,
|
||||
None,
|
||||
],
|
||||
"gas_consumed_cumulative": ["Cumulative Consumed Gas", VOLUME_CUBIC_METERS, None],
|
||||
"net_electricity_point": ["Current net Power", POWER_WATT, DEVICE_CLASS_POWER],
|
||||
"net_electricity_cumulative": [
|
||||
@ -242,6 +246,7 @@ class SmileSensor(SmileGateway):
|
||||
self._sensor = sensor
|
||||
|
||||
self._dev_class = None
|
||||
self._icon = None
|
||||
self._state = None
|
||||
self._unit_of_measurement = None
|
||||
|
||||
@ -261,9 +266,14 @@ class SmileSensor(SmileGateway):
|
||||
"""Device class of this entity."""
|
||||
return self._dev_class
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of this entity."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Device class of this entity."""
|
||||
"""Return the state of this entity."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
@ -273,7 +283,7 @@ class SmileSensor(SmileGateway):
|
||||
|
||||
|
||||
class PwThermostatSensor(SmileSensor, Entity):
|
||||
"""Thermostat and climate sensor entities."""
|
||||
"""Thermostat (or generic) sensor devices."""
|
||||
|
||||
def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type):
|
||||
"""Set up the Plugwise API."""
|
||||
@ -296,10 +306,8 @@ class PwThermostatSensor(SmileSensor, Entity):
|
||||
|
||||
if data.get(self._sensor) is not None:
|
||||
measurement = data[self._sensor]
|
||||
if self._sensor == "battery" or self._sensor == "valve_position":
|
||||
measurement = measurement * 100
|
||||
if self._unit_of_measurement == PERCENTAGE:
|
||||
measurement = int(measurement)
|
||||
measurement = int(measurement * 100)
|
||||
self._state = measurement
|
||||
self._icon = CUSTOM_ICONS.get(self._sensor, self._icon)
|
||||
|
||||
@ -307,7 +315,7 @@ class PwThermostatSensor(SmileSensor, Entity):
|
||||
|
||||
|
||||
class PwAuxDeviceSensor(SmileSensor, Entity):
|
||||
"""Auxiliary sensor entities for the heating/cooling device."""
|
||||
"""Auxiliary Device Sensors."""
|
||||
|
||||
def __init__(self, api, coordinator, name, dev_id, sensor):
|
||||
"""Set up the Plugwise API."""
|
||||
@ -315,12 +323,6 @@ class PwAuxDeviceSensor(SmileSensor, Entity):
|
||||
|
||||
self._cooling_state = False
|
||||
self._heating_state = False
|
||||
self._icon = None
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._icon
|
||||
|
||||
@callback
|
||||
def _async_process_data(self):
|
||||
@ -380,7 +382,7 @@ class PwPowerSensor(SmileSensor, Entity):
|
||||
if data.get(self._sensor) is not None:
|
||||
measurement = data[self._sensor]
|
||||
if self._unit_of_measurement == ENERGY_KILO_WATT_HOUR:
|
||||
measurement = int(measurement / 1000)
|
||||
measurement = round((measurement / 1000), 1)
|
||||
self._state = measurement
|
||||
self._icon = CUSTOM_ICONS.get(self._sensor, self._icon)
|
||||
|
||||
|
@ -20,11 +20,12 @@
|
||||
},
|
||||
"user_gateway": {
|
||||
"title": "Connect to the Smile",
|
||||
"description": "Please enter:",
|
||||
"description": "Please enter",
|
||||
"data": {
|
||||
"password": "Smile ID",
|
||||
"host": "[%key:common::config_flow::data::ip%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
"username" : "Smile Username"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -7,7 +7,7 @@ from Plugwise_Smile.Smile import Smile
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .const import COORDINATOR, DOMAIN, SWITCH_ICON
|
||||
from .gateway import SmileGateway
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -19,12 +19,27 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR]
|
||||
|
||||
entities = []
|
||||
switch_classes = ["plug", "switch_group"]
|
||||
|
||||
all_devices = api.get_all_devices()
|
||||
for dev_id, device_properties in all_devices.items():
|
||||
if "plug" in device_properties["types"]:
|
||||
model = "Metered Switch"
|
||||
members = None
|
||||
model = None
|
||||
|
||||
if any(
|
||||
switch_class in device_properties["types"]
|
||||
for switch_class in switch_classes
|
||||
):
|
||||
if "plug" in device_properties["types"]:
|
||||
model = "Metered Switch"
|
||||
if "switch_group" in device_properties["types"]:
|
||||
members = device_properties["members"]
|
||||
model = "Switch Group"
|
||||
|
||||
entities.append(
|
||||
PwSwitch(api, coordinator, device_properties["name"], dev_id, model)
|
||||
PwSwitch(
|
||||
api, coordinator, device_properties["name"], dev_id, members, model
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities, True)
|
||||
@ -33,13 +48,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class PwSwitch(SmileGateway, SwitchEntity):
|
||||
"""Representation of a Plugwise plug."""
|
||||
|
||||
def __init__(self, api, coordinator, name, dev_id, model):
|
||||
def __init__(self, api, coordinator, name, dev_id, members, model):
|
||||
"""Set up the Plugwise API."""
|
||||
super().__init__(api, coordinator, name, dev_id)
|
||||
|
||||
self._members = members
|
||||
self._model = model
|
||||
|
||||
self._is_on = False
|
||||
self._icon = SWITCH_ICON
|
||||
|
||||
self._unique_id = f"{dev_id}-plug"
|
||||
|
||||
@ -48,10 +65,18 @@ class PwSwitch(SmileGateway, SwitchEntity):
|
||||
"""Return true if device is on."""
|
||||
return self._is_on
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of this entity."""
|
||||
return self._icon
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
try:
|
||||
if await self._api.set_relay_state(self._dev_id, "on"):
|
||||
state_on = await self._api.set_relay_state(
|
||||
self._dev_id, self._members, "on"
|
||||
)
|
||||
if state_on:
|
||||
self._is_on = True
|
||||
self.async_write_ha_state()
|
||||
except Smile.PlugwiseError:
|
||||
@ -60,7 +85,10 @@ class PwSwitch(SmileGateway, SwitchEntity):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
try:
|
||||
if await self._api.set_relay_state(self._dev_id, "off"):
|
||||
state_off = await self._api.set_relay_state(
|
||||
self._dev_id, self._members, "off"
|
||||
)
|
||||
if state_off:
|
||||
self._is_on = False
|
||||
self.async_write_ha_state()
|
||||
except Smile.PlugwiseError:
|
||||
@ -69,8 +97,6 @@ class PwSwitch(SmileGateway, SwitchEntity):
|
||||
@callback
|
||||
def _async_process_data(self):
|
||||
"""Update the data from the Plugs."""
|
||||
_LOGGER.debug("Update switch called")
|
||||
|
||||
data = self._api.get_device_data(self._dev_id)
|
||||
|
||||
if not data:
|
||||
|
@ -178,3 +178,36 @@ def mock_smile_p1():
|
||||
)
|
||||
|
||||
yield smile_mock.return_value
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_stretch")
|
||||
def mock_stretch():
|
||||
"""Create a Mock Stretch environment for testing exceptions."""
|
||||
chosen_env = "stretch_v31"
|
||||
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
|
||||
|
||||
smile_mock.return_value.gateway_id = "259882df3c05415b99c2d962534ce820"
|
||||
smile_mock.return_value.heater_id = None
|
||||
smile_mock.return_value.smile_version = "3.1.11"
|
||||
smile_mock.return_value.smile_type = "stretch"
|
||||
smile_mock.return_value.smile_hostname = "stretch98765"
|
||||
|
||||
smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True)
|
||||
smile_mock.return_value.full_update_device.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_relay_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
||||
smile_mock.return_value.get_all_devices.return_value = _read_json(
|
||||
chosen_env, "get_all_devices"
|
||||
)
|
||||
smile_mock.return_value.get_device_data.side_effect = partial(
|
||||
_get_device_data, chosen_env
|
||||
)
|
||||
|
||||
yield smile_mock.return_value
|
||||
|
@ -9,7 +9,14 @@ from homeassistant.components.plugwise.const import (
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_SCAN_INTERVAL
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
|
||||
from tests.async_mock import MagicMock, patch
|
||||
from tests.common import MockConfigEntry
|
||||
@ -18,6 +25,8 @@ TEST_HOST = "1.1.1.1"
|
||||
TEST_HOSTNAME = "smileabcdef"
|
||||
TEST_PASSWORD = "test_password"
|
||||
TEST_PORT = 81
|
||||
TEST_USERNAME = "smile"
|
||||
TEST_USERNAME2 = "stretch"
|
||||
|
||||
TEST_DISCOVERY = {
|
||||
"host": TEST_HOST,
|
||||
@ -66,16 +75,17 @@ async def test_form(hass):
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"host": TEST_HOST, "password": TEST_PASSWORD},
|
||||
{CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["data"] == {
|
||||
"host": TEST_HOST,
|
||||
"password": TEST_PASSWORD,
|
||||
"port": DEFAULT_PORT,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_PORT: DEFAULT_PORT,
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
@ -105,16 +115,58 @@ async def test_zeroconf_form(hass):
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"password": TEST_PASSWORD},
|
||||
{CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["data"] == {
|
||||
"host": TEST_HOST,
|
||||
"password": TEST_PASSWORD,
|
||||
"port": DEFAULT_PORT,
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_PORT: DEFAULT_PORT,
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_username(hass):
|
||||
"""Test we get the username data back."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.config_flow.Smile.connect",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.plugwise.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.plugwise.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_USERNAME: TEST_USERNAME2,
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["data"] == {
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_PORT: DEFAULT_PORT,
|
||||
CONF_USERNAME: TEST_USERNAME2,
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
@ -140,7 +192,7 @@ async def test_zeroconf_form(hass):
|
||||
) as mock_setup_entry:
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
{"password": TEST_PASSWORD},
|
||||
{CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
@ -160,7 +212,7 @@ async def test_form_invalid_auth(hass, mock_smile):
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"host": TEST_HOST, "password": TEST_PASSWORD},
|
||||
{CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
@ -178,7 +230,7 @@ async def test_form_cannot_connect(hass, mock_smile):
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"host": TEST_HOST, "password": TEST_PASSWORD},
|
||||
{CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
@ -196,7 +248,7 @@ async def test_form_cannot_connect_port(hass, mock_smile):
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"host": TEST_HOST, "password": TEST_PASSWORD, "port": TEST_PORT},
|
||||
{CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: TEST_PORT},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
@ -214,7 +266,7 @@ async def test_form_other_problem(hass, mock_smile):
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"host": TEST_HOST, "password": TEST_PASSWORD},
|
||||
{CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
@ -27,7 +27,7 @@ async def test_adam_climate_sensor_entities(hass, mock_smile_adam):
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.zone_lisa_wk_battery")
|
||||
assert float(state.state) == 34
|
||||
assert int(state.state) == 34
|
||||
|
||||
|
||||
async def test_anna_climate_sensor_entities(hass, mock_smile_anna):
|
||||
@ -54,13 +54,25 @@ async def test_p1_dsmr_sensor_entities(hass, mock_smile_p1):
|
||||
assert float(state.state) == -2761.0
|
||||
|
||||
state = hass.states.get("sensor.p1_electricity_consumed_off_peak_cumulative")
|
||||
assert int(state.state) == 551
|
||||
assert float(state.state) == 551.1
|
||||
|
||||
state = hass.states.get("sensor.p1_electricity_produced_peak_point")
|
||||
assert float(state.state) == 2761.0
|
||||
|
||||
state = hass.states.get("sensor.p1_electricity_consumed_peak_cumulative")
|
||||
assert int(state.state) == 442
|
||||
assert float(state.state) == 442.9
|
||||
|
||||
state = hass.states.get("sensor.p1_gas_consumed_cumulative")
|
||||
assert float(state.state) == 584.9
|
||||
|
||||
|
||||
async def test_stretch_sensor_entities(hass, mock_stretch):
|
||||
"""Test creation of power related sensor entities."""
|
||||
entry = await async_init_integration(hass, mock_stretch)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("sensor.koelkast_92c4a_electricity_consumed")
|
||||
assert float(state.state) == 53.2
|
||||
|
||||
state = hass.states.get("sensor.droger_52559_electricity_consumed_interval")
|
||||
assert float(state.state) == 1.06
|
||||
|
@ -48,3 +48,49 @@ async def test_adam_climate_switch_changes(hass, mock_smile_adam):
|
||||
)
|
||||
state = hass.states.get("switch.fibaro_hc2")
|
||||
assert str(state.state) == "on"
|
||||
|
||||
|
||||
async def test_stretch_switch_entities(hass, mock_stretch):
|
||||
"""Test creation of climate related switch entities."""
|
||||
entry = await async_init_integration(hass, mock_stretch)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("switch.koelkast_92c4a")
|
||||
assert str(state.state) == "on"
|
||||
|
||||
state = hass.states.get("switch.droger_52559")
|
||||
assert str(state.state) == "on"
|
||||
|
||||
|
||||
async def test_stretch_switch_changes(hass, mock_stretch):
|
||||
"""Test changing of power related switch entities."""
|
||||
entry = await async_init_integration(hass, mock_stretch)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_off",
|
||||
{"entity_id": "switch.koelkast_92c4a"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("switch.koelkast_92c4a")
|
||||
assert str(state.state) == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"toggle",
|
||||
{"entity_id": "switch.droger_52559"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("switch.droger_52559")
|
||||
assert str(state.state) == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"toggle",
|
||||
{"entity_id": "switch.droger_52559"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("switch.droger_52559")
|
||||
assert str(state.state) == "on"
|
||||
|
1
tests/fixtures/plugwise/stretch_v31/get_all_devices.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_all_devices.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"cfe95cf3de1948c0b8955125bf754614": {"name": "Droger (52559)", "types": {"py/set": ["plug", "power"]}, "class": "dryer", "location": 0}, "aac7b735042c4832ac9ff33aae4f453b": {"name": "Vaatwasser (2a1ab)", "types": {"py/set": ["plug", "power"]}, "class": "dishwasher", "location": 0}, "5871317346d045bc9f6b987ef25ee638": {"name": "Boiler (1EB31)", "types": {"py/set": ["plug", "power"]}, "class": "water_heater_vessel", "location": 0}, "059e4d03c7a34d278add5c7a4a781d19": {"name": "Wasmachine (52AC1)", "types": {"py/set": ["plug", "power"]}, "class": "washingmachine", "location": 0}, "e1c884e7dede431dadee09506ec4f859": {"name": "Koelkast (92C4A)", "types": {"py/set": ["plug", "power"]}, "class": "refrigerator", "location": 0}, "d950b314e9d8499f968e6db8d82ef78c": {"name": "Stroomvreters", "types": {"py/set": ["switch_group"]}, "class": "switching", "members": [], "location": null}}
|
1
tests/fixtures/plugwise/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "relay": true}
|
1
tests/fixtures/plugwise/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"electricity_consumed": 1.19, "electricity_produced": 0.0, "relay": true}
|
1
tests/fixtures/plugwise/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"electricity_consumed": 0.0, "electricity_produced": 0.0, "relay": true}
|
1
tests/fixtures/plugwise/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"electricity_consumed": 0.0, "electricity_consumed_interval": 1.06, "electricity_produced": 0.0, "relay": true}
|
1
tests/fixtures/plugwise/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"relay": false}
|
1
tests/fixtures/plugwise/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json
vendored
Normal file
1
tests/fixtures/plugwise/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"electricity_consumed": 53.2, "electricity_produced": 0.0, "relay": true}
|
Loading…
x
Reference in New Issue
Block a user