diff --git a/homeassistant/components/smarla/const.py b/homeassistant/components/smarla/const.py index 84acb908073..7125e3f7270 100644 --- a/homeassistant/components/smarla/const.py +++ b/homeassistant/components/smarla/const.py @@ -1,7 +1,12 @@ """Constants for the Swing2Sleep Smarla integration.""" +from homeassistant.const import Platform + DOMAIN = "smarla" HOST = "https://devices.swing2sleep.de" -PLATFORMS = ["switch"] +PLATFORMS = [Platform.SWITCH] + +DEVICE_MODEL_NAME = "Smarla" +MANUFACTURER_NAME = "Swing2Sleep" diff --git a/homeassistant/components/smarla/entity.py b/homeassistant/components/smarla/entity.py index 1557b924088..773cb19b553 100644 --- a/homeassistant/components/smarla/entity.py +++ b/homeassistant/components/smarla/entity.py @@ -5,12 +5,14 @@ from pysmarlaapi import Federwiege from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity -from .const import DOMAIN +from .const import DEVICE_MODEL_NAME, DOMAIN, MANUFACTURER_NAME class SmarlaBaseEntity(Entity): """Common Base Entity class for defining Smarla device.""" + _attr_has_entity_name = True + def __init__( self, federwiege: Federwiege, @@ -18,12 +20,10 @@ class SmarlaBaseEntity(Entity): """Initialise the entity.""" super().__init__() - self._attr_has_entity_name = True - self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, federwiege.serial_number)}, - name="Smarla", - model="Smarla", - manufacturer="Swing2Sleep", + name=DEVICE_MODEL_NAME, + model=DEVICE_MODEL_NAME, + manufacturer=MANUFACTURER_NAME, serial_number=federwiege.serial_number, ) diff --git a/homeassistant/components/smarla/manifest.json b/homeassistant/components/smarla/manifest.json index aec83a78cda..ef006917539 100644 --- a/homeassistant/components/smarla/manifest.json +++ b/homeassistant/components/smarla/manifest.json @@ -3,7 +3,6 @@ "name": "Swing2Sleep Smarla", "codeowners": ["@explicatis", "@rlint-explicatis"], "config_flow": true, - "dependencies": [], "documentation": "https://www.home-assistant.io/integrations/smarla", "integration_type": "device", "iot_class": "cloud_push", diff --git a/homeassistant/components/smarla/strings.json b/homeassistant/components/smarla/strings.json index 4d146b93814..75e4ae9a177 100644 --- a/homeassistant/components/smarla/strings.json +++ b/homeassistant/components/smarla/strings.json @@ -10,7 +10,7 @@ "access_token": "[%key:common::config_flow::data::access_token%]" }, "data_description": { - "access_token": "The access token generated from the App." + "access_token": "The access token generated by the Swing2Sleep app." } } }, diff --git a/homeassistant/components/smarla/switch.py b/homeassistant/components/smarla/switch.py index 9eeb4008ff6..a40e5537311 100644 --- a/homeassistant/components/smarla/switch.py +++ b/homeassistant/components/smarla/switch.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from typing import Any from pysmarlaapi import Federwiege +from pysmarlaapi.federwiege.classes import Property from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.core import HomeAssistant @@ -15,13 +16,13 @@ from .entity import SmarlaBaseEntity @dataclass(frozen=True, kw_only=True) class SmarlaSwitchEntityDescription(SwitchEntityDescription): - """Class describing Swing2Sleep Smarla switch entities.""" + """Class describing Swing2Sleep Smarla switch entity.""" service: str property: str -NUMBER_TYPES: list[SmarlaSwitchEntityDescription] = [ +SWITCHES: list[SmarlaSwitchEntityDescription] = [ SmarlaSwitchEntityDescription( key="cradle", name=None, @@ -43,20 +44,17 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Smarla switches from config entry.""" - federwiege = config_entry.runtime_data - - entities: list[SwitchEntity] = [] - - for desc in NUMBER_TYPES: - entity = SmarlaSwitch(federwiege, desc) - entities.append(entity) - - async_add_entities(entities) + federwiege: Federwiege = config_entry.runtime_data + async_add_entities(SmarlaSwitch(federwiege, desc) for desc in SWITCHES) class SmarlaSwitch(SmarlaBaseEntity, SwitchEntity): """Representation of Smarla switch.""" + entity_description: SmarlaSwitchEntityDescription + _property: Property + _attr_should_poll = False + async def on_change(self, value: Any): """Notify ha when state changes.""" self.async_write_ha_state() @@ -68,30 +66,29 @@ class SmarlaSwitch(SmarlaBaseEntity, SwitchEntity): ) -> None: """Initialize a Smarla switch.""" super().__init__(federwiege) - self.property = federwiege.get_service(description.service).get_property( + self._property = federwiege.get_service(description.service).get_property( description.property ) self.entity_description = description - self._attr_should_poll = False self._attr_unique_id = f"{federwiege.serial_number}-{description.key}" async def async_added_to_hass(self) -> None: """Run when this Entity has been added to HA.""" - await self.property.add_listener(self.on_change) + await self._property.add_listener(self.on_change) async def async_will_remove_from_hass(self) -> None: """Entity being removed from hass.""" - await self.property.remove_listener(self.on_change) + await self._property.remove_listener(self.on_change) @property def is_on(self) -> bool: """Return the entity value to represent the entity state.""" - return self.property.get() + return self._property.get() def turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" - self.property.set(True) + self._property.set(True) def turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" - self.property.set(False) + self._property.set(False) diff --git a/tests/components/smarla/test_config_flow.py b/tests/components/smarla/test_config_flow.py index d92d7356a85..cfb1585c7a9 100644 --- a/tests/components/smarla/test_config_flow.py +++ b/tests/components/smarla/test_config_flow.py @@ -1,7 +1,9 @@ """Test config flow for Swing2Sleep Smarla integration.""" +import json from unittest.mock import AsyncMock, patch +from homeassistant import config_entries from homeassistant.components.smarla.config_flow import Connection from homeassistant.components.smarla.const import DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN @@ -10,9 +12,12 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -MOCK_ACCESS_TOKEN = "eyJyZWZyZXNoVG9rZW4iOiJ0ZXN0IiwidG9rZW4iOiJ0ZXN0IiwiZGF0ZUNyZWF0ZWQiOiIyMDI1LTAxLTAxVDIzOjU5OjU5Ljk5OTk5OVoiLCJhcHBJZGVudGlmaWVyIjoiSEEtaG9tZWFzc2lzdGFudHRlc3QiLCJzZXJpYWxOdW1iZXIiOiJBQkNELUFCQ0QiLCJhcHBWZXJzaW9uIjoidW5rbm93biIsImFwcEN1bHR1cmUiOiJkZSJ9" -MOCK_ACCESS_TOKEN_JSON = '{"refreshToken": "test", "token": "test", "dateCreated": "2025-01-01T23:59:59.999999Z", "appIdentifier": "HA-homeassistanttest", "serialNumber": "ABCD-ABCD", "appVersion": "unknown", "appCulture": "de"}' -MOCK_SERIAL = "ABCD-ABCD" +MOCK_ACCESS_TOKEN = "eyJyZWZyZXNoVG9rZW4iOiJ0ZXN0IiwiYXBwSWRlbnRpZmllciI6IkhBLXRlc3QiLCJzZXJpYWxOdW1iZXIiOiJBQkNEIn0=" +MOCK_ACCESS_TOKEN_JSON = { + "refreshToken": "test", + "appIdentifier": "HA-test", + "serialNumber": "ABCD", +} async def test_show_form(hass: HomeAssistant) -> None: @@ -30,13 +35,13 @@ async def test_create_entry(hass: HomeAssistant) -> None: with patch.object(Connection, "get_token", new=AsyncMock(return_value=True)): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": "user"}, + context={"source": config_entries.SOURCE_USER}, data={CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN}, ) assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == MOCK_SERIAL - assert result["data"] == {CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN_JSON} + assert result["title"] == MOCK_ACCESS_TOKEN_JSON["serialNumber"] + assert result["data"] == {CONF_ACCESS_TOKEN: json.dumps(MOCK_ACCESS_TOKEN_JSON)} async def test_invalid_auth(hass: HomeAssistant) -> None: @@ -44,7 +49,7 @@ async def test_invalid_auth(hass: HomeAssistant) -> None: with patch.object(Connection, "get_token", new=AsyncMock(return_value=None)): result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": "user"}, + context={"source": config_entries.SOURCE_USER}, data={CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN}, ) @@ -56,7 +61,7 @@ async def test_invalid_token(hass: HomeAssistant) -> None: """Test we handle invalid/malformed tokens.""" result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": "user"}, + context={"source": config_entries.SOURCE_USER}, data={CONF_ACCESS_TOKEN: "invalid_token"}, ) @@ -68,8 +73,8 @@ async def test_device_exists_abort(hass: HomeAssistant) -> None: """Test we abort config flow if Smarla device already configured.""" config_entry = MockConfigEntry( domain=DOMAIN, - unique_id=MOCK_SERIAL, - source="user", + unique_id=MOCK_ACCESS_TOKEN_JSON["serialNumber"], + source=config_entries.SOURCE_USER, ) config_entry.add_to_hass(hass) @@ -77,7 +82,7 @@ async def test_device_exists_abort(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, - context={"source": "user"}, + context={"source": config_entries.SOURCE_USER}, data={CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN}, )