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:
Tom 2020-11-08 18:09:43 +01:00 committed by GitHub
parent 55cdec8c4e
commit 877bfcb308
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 300 additions and 108 deletions

View File

@ -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()

View File

@ -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

View File

@ -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",
}

View File

@ -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,
)

View File

@ -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)

View File

@ -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"
}
}
},

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View 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}}

View File

@ -0,0 +1 @@
{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "relay": true}

View File

@ -0,0 +1 @@
{"electricity_consumed": 1.19, "electricity_produced": 0.0, "relay": true}

View File

@ -0,0 +1 @@
{"electricity_consumed": 0.0, "electricity_produced": 0.0, "relay": true}

View File

@ -0,0 +1 @@
{"electricity_consumed": 0.0, "electricity_consumed_interval": 1.06, "electricity_produced": 0.0, "relay": true}

View File

@ -0,0 +1 @@
{"relay": false}

View File

@ -0,0 +1 @@
{"electricity_consumed": 53.2, "electricity_produced": 0.0, "relay": true}