Code refactoring

This commit is contained in:
Robin Lintermann 2025-04-23 14:41:44 +00:00
parent baeb70af8a
commit 7b2641d136
6 changed files with 44 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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