From 877bfcb308ceb4abe15e637402f0f006d7b1b5df Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 8 Nov 2020 18:09:43 +0100 Subject: [PATCH] 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> --- .../components/plugwise/binary_sensor.py | 48 +++++------ .../components/plugwise/config_flow.py | 30 +++++-- homeassistant/components/plugwise/const.py | 38 ++++----- homeassistant/components/plugwise/gateway.py | 21 +++-- homeassistant/components/plugwise/sensor.py | 38 ++++----- .../components/plugwise/strings.json | 5 +- homeassistant/components/plugwise/switch.py | 44 +++++++--- tests/components/plugwise/conftest.py | 33 ++++++++ tests/components/plugwise/test_config_flow.py | 80 +++++++++++++++---- tests/components/plugwise/test_sensor.py | 18 ++++- tests/components/plugwise/test_switch.py | 46 +++++++++++ .../plugwise/stretch_v31/get_all_devices.json | 1 + .../059e4d03c7a34d278add5c7a4a781d19.json | 1 + .../5871317346d045bc9f6b987ef25ee638.json | 1 + .../aac7b735042c4832ac9ff33aae4f453b.json | 1 + .../cfe95cf3de1948c0b8955125bf754614.json | 1 + .../d950b314e9d8499f968e6db8d82ef78c.json | 1 + .../e1c884e7dede431dadee09506ec4f859.json | 1 + 18 files changed, 300 insertions(+), 108 deletions(-) create mode 100644 tests/fixtures/plugwise/stretch_v31/get_all_devices.json create mode 100644 tests/fixtures/plugwise/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json create mode 100644 tests/fixtures/plugwise/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json create mode 100644 tests/fixtures/plugwise/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json create mode 100644 tests/fixtures/plugwise/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json create mode 100644 tests/fixtures/plugwise/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json create mode 100644 tests/fixtures/plugwise/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 67dcc10a289..2ba85326265 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -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() diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 364f58007c6..6fd7cde44bc 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -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 diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 056606307e5..5c0cf2b097a 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -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", } diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 610fff18e4b..5ba6eda2770 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -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, ) diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 483153c6709..578f1bc7f7e 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -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) diff --git a/homeassistant/components/plugwise/strings.json b/homeassistant/components/plugwise/strings.json index 4e6ea18017b..2ed6721bab3 100644 --- a/homeassistant/components/plugwise/strings.json +++ b/homeassistant/components/plugwise/strings.json @@ -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" } } }, diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index d7b45f4999a..8221bc2cb57 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -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: diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index 5af2bf2ccd3..0f0e4551b1c 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -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 diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 9b67f06e469..dea42dfb01d 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -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 diff --git a/tests/components/plugwise/test_sensor.py b/tests/components/plugwise/test_sensor.py index bc586120517..a727337d747 100644 --- a/tests/components/plugwise/test_sensor.py +++ b/tests/components/plugwise/test_sensor.py @@ -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 diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index a58ebf83caa..ded21113f2b 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -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" diff --git a/tests/fixtures/plugwise/stretch_v31/get_all_devices.json b/tests/fixtures/plugwise/stretch_v31/get_all_devices.json new file mode 100644 index 00000000000..f40e902d5a9 --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_all_devices.json @@ -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}} \ No newline at end of file diff --git a/tests/fixtures/plugwise/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json b/tests/fixtures/plugwise/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json new file mode 100644 index 00000000000..b08f6d6093a --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_device_data/059e4d03c7a34d278add5c7a4a781d19.json @@ -0,0 +1 @@ +{"electricity_consumed": 0.0, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/fixtures/plugwise/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json b/tests/fixtures/plugwise/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json new file mode 100644 index 00000000000..529c8b76d95 --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_device_data/5871317346d045bc9f6b987ef25ee638.json @@ -0,0 +1 @@ +{"electricity_consumed": 1.19, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/fixtures/plugwise/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json b/tests/fixtures/plugwise/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json new file mode 100644 index 00000000000..35ce04f51cf --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_device_data/aac7b735042c4832ac9ff33aae4f453b.json @@ -0,0 +1 @@ +{"electricity_consumed": 0.0, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/fixtures/plugwise/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json b/tests/fixtures/plugwise/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json new file mode 100644 index 00000000000..42de4d3338b --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_device_data/cfe95cf3de1948c0b8955125bf754614.json @@ -0,0 +1 @@ +{"electricity_consumed": 0.0, "electricity_consumed_interval": 1.06, "electricity_produced": 0.0, "relay": true} \ No newline at end of file diff --git a/tests/fixtures/plugwise/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json b/tests/fixtures/plugwise/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json new file mode 100644 index 00000000000..de5baf4c9a6 --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_device_data/d950b314e9d8499f968e6db8d82ef78c.json @@ -0,0 +1 @@ +{"relay": false} \ No newline at end of file diff --git a/tests/fixtures/plugwise/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json b/tests/fixtures/plugwise/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json new file mode 100644 index 00000000000..1a7249b68d5 --- /dev/null +++ b/tests/fixtures/plugwise/stretch_v31/get_device_data/e1c884e7dede431dadee09506ec4f859.json @@ -0,0 +1 @@ +{"electricity_consumed": 53.2, "electricity_produced": 0.0, "relay": true} \ No newline at end of file