From 4283b2358c2c271867e8482c5431b9d203f1521e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 5 Jan 2022 15:55:57 -1000 Subject: [PATCH] Add support for Steamist steam showers (#63251) Co-authored-by: Franck Nijhof --- .strict-typing | 1 + CODEOWNERS | 2 + homeassistant/components/steamist/__init__.py | 34 ++++++++ .../components/steamist/config_flow.py | 52 ++++++++++++ homeassistant/components/steamist/const.py | 9 +++ .../components/steamist/coordinator.py | 35 ++++++++ homeassistant/components/steamist/entity.py | 35 ++++++++ .../components/steamist/manifest.json | 9 +++ .../components/steamist/strings.json | 18 +++++ homeassistant/components/steamist/switch.py | 46 +++++++++++ .../components/steamist/translations/en.json | 18 +++++ homeassistant/generated/config_flows.py | 1 + mypy.ini | 11 +++ requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/steamist/__init__.py | 46 +++++++++++ tests/components/steamist/test_config_flow.py | 80 ++++++++++++++++++ tests/components/steamist/test_init.py | 48 +++++++++++ tests/components/steamist/test_switch.py | 81 +++++++++++++++++++ 19 files changed, 532 insertions(+) create mode 100644 homeassistant/components/steamist/__init__.py create mode 100644 homeassistant/components/steamist/config_flow.py create mode 100644 homeassistant/components/steamist/const.py create mode 100644 homeassistant/components/steamist/coordinator.py create mode 100644 homeassistant/components/steamist/entity.py create mode 100644 homeassistant/components/steamist/manifest.json create mode 100644 homeassistant/components/steamist/strings.json create mode 100644 homeassistant/components/steamist/switch.py create mode 100644 homeassistant/components/steamist/translations/en.json create mode 100644 tests/components/steamist/__init__.py create mode 100644 tests/components/steamist/test_config_flow.py create mode 100644 tests/components/steamist/test_init.py create mode 100644 tests/components/steamist/test_switch.py diff --git a/.strict-typing b/.strict-typing index 0dbd24f6c60..5879c9899ae 100644 --- a/.strict-typing +++ b/.strict-typing @@ -130,6 +130,7 @@ homeassistant.components.sonos.media_player homeassistant.components.ssdp.* homeassistant.components.stookalert.* homeassistant.components.statistics.* +homeassistant.components.steamist.* homeassistant.components.stream.* homeassistant.components.sun.* homeassistant.components.surepetcare.* diff --git a/CODEOWNERS b/CODEOWNERS index 8cec6382e57..98cadaf2920 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -882,6 +882,8 @@ homeassistant/components/starline/* @anonym-tsk tests/components/starline/* @anonym-tsk homeassistant/components/statistics/* @fabaff @ThomDietrich tests/components/statistics/* @fabaff @ThomDietrich +homeassistant/components/steamist/* @bdraco +tests/components/steamist/* @bdraco homeassistant/components/stiebel_eltron/* @fucm homeassistant/components/stookalert/* @fwestenberg @frenck tests/components/stookalert/* @fwestenberg @frenck diff --git a/homeassistant/components/steamist/__init__.py b/homeassistant/components/steamist/__init__.py new file mode 100644 index 00000000000..fd4191d70e2 --- /dev/null +++ b/homeassistant/components/steamist/__init__.py @@ -0,0 +1,34 @@ +"""The Steamist integration.""" +from __future__ import annotations + +from aiosteamist import Steamist + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DOMAIN +from .coordinator import SteamistDataUpdateCoordinator + +PLATFORMS: list[str] = [Platform.SWITCH] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Steamist from a config entry.""" + coordinator = SteamistDataUpdateCoordinator( + hass, + Steamist(entry.data[CONF_HOST], async_get_clientsession(hass)), + entry.data[CONF_HOST], + ) + await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/steamist/config_flow.py b/homeassistant/components/steamist/config_flow.py new file mode 100644 index 00000000000..76e5bc034cd --- /dev/null +++ b/homeassistant/components/steamist/config_flow.py @@ -0,0 +1,52 @@ +"""Config flow for Steamist integration.""" +from __future__ import annotations + +import logging +from typing import Any + +from aiosteamist import Steamist +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import CONNECTION_EXCEPTIONS, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Steamist.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + errors = {} + + if user_input is not None: + try: + await Steamist( + user_input[CONF_HOST], + async_get_clientsession(self.hass), + ).async_get_status() + except CONNECTION_EXCEPTIONS: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]}) + return self.async_create_entry( + title=user_input[CONF_HOST], data=user_input + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + errors=errors, + ) diff --git a/homeassistant/components/steamist/const.py b/homeassistant/components/steamist/const.py new file mode 100644 index 00000000000..dc5b7a36ea4 --- /dev/null +++ b/homeassistant/components/steamist/const.py @@ -0,0 +1,9 @@ +"""Constants for the Steamist integration.""" + +import asyncio + +import aiohttp + +DOMAIN = "steamist" + +CONNECTION_EXCEPTIONS = (asyncio.TimeoutError, aiohttp.ClientError) diff --git a/homeassistant/components/steamist/coordinator.py b/homeassistant/components/steamist/coordinator.py new file mode 100644 index 00000000000..68c48f736f9 --- /dev/null +++ b/homeassistant/components/steamist/coordinator.py @@ -0,0 +1,35 @@ +"""DataUpdateCoordinator for steamist.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +from aiosteamist import Steamist, SteamistStatus + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + + +class SteamistDataUpdateCoordinator(DataUpdateCoordinator[SteamistStatus]): + """DataUpdateCoordinator to gather data from a steamist steam shower.""" + + def __init__( + self, + hass: HomeAssistant, + client: Steamist, + host: str, + ) -> None: + """Initialize DataUpdateCoordinator to gather data for specific steamist.""" + self.client = client + super().__init__( + hass, + _LOGGER, + name=f"Steamist {host}", + update_interval=timedelta(seconds=5), + ) + + async def _async_update_data(self) -> SteamistStatus: + """Fetch data from steamist.""" + return await self.client.async_get_status() diff --git a/homeassistant/components/steamist/entity.py b/homeassistant/components/steamist/entity.py new file mode 100644 index 00000000000..c375ba8c6d0 --- /dev/null +++ b/homeassistant/components/steamist/entity.py @@ -0,0 +1,35 @@ +"""Support for Steamist sensors.""" +from __future__ import annotations + +from aiosteamist import SteamistStatus + +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.entity import Entity, EntityDescription +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +from .coordinator import SteamistDataUpdateCoordinator + + +class SteamistEntity(CoordinatorEntity, Entity): + """Representation of an Steamist entity.""" + + coordinator: SteamistDataUpdateCoordinator + + def __init__( + self, + coordinator: DataUpdateCoordinator, + entry: ConfigEntry, + description: EntityDescription, + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{entry.entry_id}_{description.key}" + + @property + def _status(self) -> SteamistStatus: + """Return the steamist status.""" + return self.coordinator.data diff --git a/homeassistant/components/steamist/manifest.json b/homeassistant/components/steamist/manifest.json new file mode 100644 index 00000000000..31109119aec --- /dev/null +++ b/homeassistant/components/steamist/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "steamist", + "name": "Steamist", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/steamist", + "requirements": ["aiosteamist==0.3.1"], + "codeowners": ["@bdraco"], + "iot_class": "local_polling" +} \ No newline at end of file diff --git a/homeassistant/components/steamist/strings.json b/homeassistant/components/steamist/strings.json new file mode 100644 index 00000000000..f7caf169ce3 --- /dev/null +++ b/homeassistant/components/steamist/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/steamist/switch.py b/homeassistant/components/steamist/switch.py new file mode 100644 index 00000000000..1a8ef7f64de --- /dev/null +++ b/homeassistant/components/steamist/switch.py @@ -0,0 +1,46 @@ +"""Support for Steamist switches.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN +from .entity import SteamistEntity + +ACTIVE_SWITCH = SwitchEntityDescription( + key="active", icon="mdi:pot-steam", name="Steam Active" +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up sensors.""" + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities([SteamistSwitchEntity(coordinator, config_entry, ACTIVE_SWITCH)]) + + +class SteamistSwitchEntity(SteamistEntity, SwitchEntity): + """Representation of an Steamist steam switch.""" + + @property + def is_on(self) -> bool: + """Return if the steam is active.""" + return self._status.active + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the steam on.""" + await self.coordinator.client.async_turn_on_steam() + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the steam off.""" + await self.coordinator.client.async_turn_off_steam() + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/steamist/translations/en.json b/homeassistant/components/steamist/translations/en.json new file mode 100644 index 00000000000..0a4ba36e285 --- /dev/null +++ b/homeassistant/components/steamist/translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a64a3bf9b53..df59ade9338 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -298,6 +298,7 @@ FLOWS = [ "squeezebox", "srp_energy", "starline", + "steamist", "stookalert", "subaru", "surepetcare", diff --git a/mypy.ini b/mypy.ini index c3679e8cc9c..166fa96c0c0 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1441,6 +1441,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.steamist.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.stream.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index ee9c3fab1b4..45aeb60e364 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -253,6 +253,9 @@ aioridwell==2021.12.2 # homeassistant.components.shelly aioshelly==1.0.7 +# homeassistant.components.steamist +aiosteamist==0.3.1 + # homeassistant.components.switcher_kis aioswitcher==2.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 05b5cc4f114..724041472ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -185,6 +185,9 @@ aioridwell==2021.12.2 # homeassistant.components.shelly aioshelly==1.0.7 +# homeassistant.components.steamist +aiosteamist==0.3.1 + # homeassistant.components.switcher_kis aioswitcher==2.0.6 diff --git a/tests/components/steamist/__init__.py b/tests/components/steamist/__init__.py new file mode 100644 index 00000000000..5de5a586280 --- /dev/null +++ b/tests/components/steamist/__init__.py @@ -0,0 +1,46 @@ +"""Tests for the Steamist integration.""" +from contextlib import contextmanager +from unittest.mock import AsyncMock, MagicMock, patch + +from aiosteamist import Steamist, SteamistStatus + +MOCK_ASYNC_GET_STATUS_INACTIVE = SteamistStatus( + temp=70, temp_units="F", minutes_remain=0, active=False +) +MOCK_ASYNC_GET_STATUS_ACTIVE = SteamistStatus( + temp=102, temp_units="F", minutes_remain=14, active=True +) + + +def _mocked_steamist() -> Steamist: + client = MagicMock(auto_spec=Steamist) + client.async_turn_on_steam = AsyncMock() + client.async_turn_off_steam = AsyncMock() + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_ACTIVE) + return client + + +def _patch_status_active(client=None): + if client is None: + client = _mocked_steamist() + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_ACTIVE) + + @contextmanager + def _patcher(): + with patch("homeassistant.components.steamist.Steamist", return_value=client): + yield + + return _patcher() + + +def _patch_status_inactive(client=None): + if client is None: + client = _mocked_steamist() + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_INACTIVE) + + @contextmanager + def _patcher(): + with patch("homeassistant.components.steamist.Steamist", return_value=client): + yield + + return _patcher() diff --git a/tests/components/steamist/test_config_flow.py b/tests/components/steamist/test_config_flow.py new file mode 100644 index 00000000000..746c5c792b1 --- /dev/null +++ b/tests/components/steamist/test_config_flow.py @@ -0,0 +1,80 @@ +"""Test the Steamist config flow.""" +import asyncio +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components.steamist.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.steamist.config_flow.Steamist.async_get_status" + ), patch( + "homeassistant.components.steamist.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "127.0.0.1", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "127.0.0.1" + assert result2["data"] == { + "host": "127.0.0.1", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect(hass: HomeAssistant) -> None: + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.steamist.config_flow.Steamist.async_get_status", + side_effect=asyncio.TimeoutError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "127.0.0.1", + }, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unknown_exception(hass: HomeAssistant) -> None: + """Test we handle unknown exceptions.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.steamist.config_flow.Steamist.async_get_status", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "127.0.0.1", + }, + ) + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} diff --git a/tests/components/steamist/test_init.py b/tests/components/steamist/test_init.py new file mode 100644 index 00000000000..65f2b8bb2e1 --- /dev/null +++ b/tests/components/steamist/test_init.py @@ -0,0 +1,48 @@ +"""Tests for the steamist component.""" +from __future__ import annotations + +import asyncio +from unittest.mock import patch + +from homeassistant.components import steamist +from homeassistant.components.steamist.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from . import _patch_status_active + +from tests.common import MockConfigEntry + + +async def test_config_entry_reload(hass: HomeAssistant) -> None: + """Test that a config entry can be reloaded.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "127.0.0.1"}, + ) + config_entry.add_to_hass(hass) + with _patch_status_active(): + await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.LOADED + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.NOT_LOADED + + +async def test_config_entry_retry_later(hass: HomeAssistant) -> None: + """Test that a config entry retry on connection error.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "127.0.0.1"}, + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.steamist.Steamist.async_get_status", + side_effect=asyncio.TimeoutError, + ): + await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.SETUP_RETRY diff --git a/tests/components/steamist/test_switch.py b/tests/components/steamist/test_switch.py new file mode 100644 index 00000000000..072513eaa83 --- /dev/null +++ b/tests/components/steamist/test_switch.py @@ -0,0 +1,81 @@ +"""Tests for the steamist switch.""" +from __future__ import annotations + +from datetime import timedelta +from unittest.mock import AsyncMock + +from homeassistant.components import steamist +from homeassistant.components.steamist.const import DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from . import ( + MOCK_ASYNC_GET_STATUS_ACTIVE, + MOCK_ASYNC_GET_STATUS_INACTIVE, + _mocked_steamist, + _patch_status_active, + _patch_status_inactive, +) + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_steam_active(hass: HomeAssistant) -> None: + """Test that the binary sensors are setup with the expected values when steam is active.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "127.0.0.1"}, + ) + config_entry.add_to_hass(hass) + client = _mocked_steamist() + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_ACTIVE) + with _patch_status_active(client): + await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.LOADED + + assert len(hass.states.async_all("switch")) == 1 + assert hass.states.get("switch.steam_active").state == STATE_ON + + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_INACTIVE) + await hass.services.async_call( + SWITCH_DOMAIN, + "turn_off", + {ATTR_ENTITY_ID: "switch.steam_active"}, + blocking=True, + ) + client.async_turn_off_steam.assert_called_once() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) + await hass.async_block_till_done() + assert hass.states.get("switch.steam_active").state == STATE_OFF + + +async def test_steam_inactive(hass: HomeAssistant) -> None: + """Test that the binary sensors are setup with the expected values when steam is not active.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "127.0.0.1"}, + ) + config_entry.add_to_hass(hass) + client = _mocked_steamist() + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_INACTIVE) + with _patch_status_inactive(client): + await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.LOADED + + assert len(hass.states.async_all("switch")) == 1 + assert hass.states.get("switch.steam_active").state == STATE_OFF + + client.async_get_status = AsyncMock(return_value=MOCK_ASYNC_GET_STATUS_ACTIVE) + await hass.services.async_call( + SWITCH_DOMAIN, "turn_on", {ATTR_ENTITY_ID: "switch.steam_active"}, blocking=True + ) + client.async_turn_on_steam.assert_called_once() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) + await hass.async_block_till_done() + assert hass.states.get("switch.steam_active").state == STATE_ON