Add generic interface for accessing device features (#741)

This adds a generic interface for all device classes to introspect available device features,
that is necessary to make it easier to support a wide variety of supported devices with different set of features.
This will allow constructing generic interfaces (e.g., in homeassistant) that fetch and change these features without hard-coding the API calls.

`Device.features()` now returns a mapping of `<identifier, Feature>` where the `Feature` contains all necessary information (like the name, the icon, a way to get and change the setting) to present and change the defined feature through its interface.
This commit is contained in:
Teemu R
2024-02-15 16:25:08 +01:00
committed by GitHub
parent 57835276e3
commit 64da736717
12 changed files with 345 additions and 28 deletions

View File

@@ -2,7 +2,7 @@
import base64
import logging
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Set, cast
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, cast
from ..aestransport import AesTransport
from ..device import Device, WifiNetwork
@@ -10,6 +10,7 @@ from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..emeterstatus import EmeterStatus
from ..exceptions import AuthenticationException, SmartDeviceException
from ..feature import Feature, FeatureType
from ..smartprotocol import SmartProtocol
_LOGGER = logging.getLogger(__name__)
@@ -124,6 +125,11 @@ class SmartDevice(Device):
for info in child_info["child_device_list"]:
self._children[info["device_id"]].update_internal_state(info)
# We can first initialize the features after the first update.
# We make here an assumption that every device has at least a single feature.
if not self._features:
await self._initialize_features()
_LOGGER.debug("Got an update: %s", self._last_update)
async def _initialize_modules(self):
@@ -131,6 +137,51 @@ class SmartDevice(Device):
if "energy_monitoring" in self._components:
self.emeter_type = "emeter"
async def _initialize_features(self):
"""Initialize device features."""
self._add_feature(
Feature(
self,
"Signal Level",
attribute_getter=lambda x: x._info["signal_level"],
icon="mdi:signal",
)
)
self._add_feature(
Feature(
self,
"RSSI",
attribute_getter=lambda x: x._info["rssi"],
icon="mdi:signal",
)
)
self._add_feature(
Feature(device=self, name="SSID", attribute_getter="ssid", icon="mdi:wifi")
)
if "overheated" in self._info:
self._add_feature(
Feature(
self,
"Overheated",
attribute_getter=lambda x: x._info["overheated"],
icon="mdi:heat-wave",
type=FeatureType.BinarySensor,
)
)
# We check for the key available, and not for the property truthiness,
# as the value is falsy when the device is off.
if "on_time" in self._info:
self._add_feature(
Feature(
device=self,
name="On since",
attribute_getter="on_since",
icon="mdi:clock",
)
)
@property
def sys_info(self) -> Dict[str, Any]:
"""Returns the device info."""
@@ -221,23 +272,21 @@ class SmartDevice(Device):
return res
@property
def state_information(self) -> Dict[str, Any]:
"""Return the key state information."""
def ssid(self) -> str:
"""Return ssid of the connected wifi ap."""
ssid = self._info.get("ssid")
ssid = base64.b64decode(ssid).decode() if ssid else "No SSID"
return ssid
@property
def state_information(self) -> Dict[str, Any]:
"""Return the key state information."""
return {
"overheated": self._info.get("overheated"),
"signal_level": self._info.get("signal_level"),
"SSID": ssid,
"SSID": self.ssid,
}
@property
def features(self) -> Set[str]:
"""Return the list of supported features."""
# TODO:
return set()
@property
def has_emeter(self) -> bool:
"""Return if the device has emeter."""