-
-
Notifications
You must be signed in to change notification settings - Fork 238
Implement feature categories #846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| from .device import Device | ||
|
|
||
|
|
||
| # TODO: This is only useful for Feature, so maybe move to Feature.Type? | ||
| class FeatureType(Enum): | ||
| """Type to help decide how to present the feature.""" | ||
|
|
||
|
|
@@ -24,6 +25,22 @@ class FeatureType(Enum): | |
| class Feature: | ||
| """Feature defines a generic interface for device features.""" | ||
|
|
||
| class Category(Enum): | ||
| """Category hint for downstreams.""" | ||
|
|
||
| #: Primary features control the device state directly. | ||
| #: Examples including turning the device on, or adjust its brightness. | ||
| Primary = auto() | ||
| #: Config features change device behavior without immediate state changes. | ||
| Config = auto() | ||
| #: Informative/sensor features deliver some potentially interesting information. | ||
| Info = auto() | ||
| #: Debug features deliver more verbose information then informative features. | ||
| #: You may want to hide these per default to avoid cluttering your UI. | ||
| Debug = auto() | ||
| #: The default category if none is specified. | ||
| Unset = -1 | ||
|
|
||
| #: Device instance required for getting and setting values | ||
| device: Device | ||
| #: User-friendly short description | ||
|
|
@@ -38,6 +55,8 @@ class Feature: | |
| icon: str | None = None | ||
| #: Unit, if applicable | ||
| unit: str | None = None | ||
| #: Category hint for downstreams | ||
| category: Feature.Category = Category.Unset | ||
| #: Type of the feature | ||
| type: FeatureType = FeatureType.Sensor | ||
|
|
||
|
|
@@ -50,14 +69,29 @@ class Feature: | |
| #: If set, this property will be used to set *minimum_value* and *maximum_value*. | ||
| range_getter: str | None = None | ||
|
|
||
| #: Identifier | ||
| id: str | None = None | ||
|
|
||
| def __post_init__(self): | ||
| """Handle late-binding of members.""" | ||
| # Set id, if unset | ||
| if self.id is None: | ||
| self.id = self.name.lower().replace(" ", "_") | ||
|
|
||
| # Populate minimum & maximum values, if range_getter is given | ||
| container = self.container if self.container is not None else self.device | ||
| if self.range_getter is not None: | ||
| self.minimum_value, self.maximum_value = getattr( | ||
| container, self.range_getter | ||
| ) | ||
|
|
||
| # Set the category, if unset | ||
| if self.category is Feature.Category.Unset: | ||
| if self.attribute_setter: | ||
| self.category = Feature.Category.Config | ||
| else: | ||
| self.category = Feature.Category.Info | ||
|
|
||
| @property | ||
| def value(self): | ||
| """Return the current value.""" | ||
|
|
@@ -79,3 +113,13 @@ async def set_value(self, value): | |
|
|
||
| container = self.container if self.container is not None else self.device | ||
| return await getattr(container, self.attribute_setter)(value) | ||
|
|
||
| def __repr__(self): | ||
| s = f"{self.name} ({self.id}): {self.value}" | ||
| if self.unit is not None: | ||
| s += f" {self.unit}" | ||
|
|
||
| if self.type == FeatureType.Number: | ||
| s += f" (range: {self.minimum_value}-{self.maximum_value})" | ||
|
|
||
| return s | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added this here to make printing out features simpler inside |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,7 +176,14 @@ async def _initialize_modules(self): | |
|
|
||
| async def _initialize_features(self): | ||
| """Initialize device features.""" | ||
| self._add_feature(Feature(self, "Device ID", attribute_getter="device_id")) | ||
| self._add_feature( | ||
| Feature( | ||
| self, | ||
| "Device ID", | ||
| attribute_getter="device_id", | ||
| category=Feature.Category.Debug, | ||
| ) | ||
| ) | ||
| if "device_on" in self._info: | ||
| self._add_feature( | ||
| Feature( | ||
|
|
@@ -185,6 +192,7 @@ async def _initialize_features(self): | |
| attribute_getter="is_on", | ||
| attribute_setter="set_state", | ||
| type=FeatureType.Switch, | ||
| category=Feature.Category.Primary, | ||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -195,6 +203,7 @@ async def _initialize_features(self): | |
| "Signal Level", | ||
| attribute_getter=lambda x: x._info["signal_level"], | ||
| icon="mdi:signal", | ||
| category=Feature.Category.Info, | ||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -205,13 +214,18 @@ async def _initialize_features(self): | |
| "RSSI", | ||
| attribute_getter=lambda x: x._info["rssi"], | ||
| icon="mdi:signal", | ||
| category=Feature.Category.Debug, | ||
| ) | ||
| ) | ||
|
|
||
| if "ssid" in self._info: | ||
| self._add_feature( | ||
| Feature( | ||
| device=self, name="SSID", attribute_getter="ssid", icon="mdi:wifi" | ||
| device=self, | ||
| name="SSID", | ||
| attribute_getter="ssid", | ||
| icon="mdi:wifi", | ||
| category=Feature.Category.Debug, | ||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -223,6 +237,7 @@ async def _initialize_features(self): | |
| attribute_getter=lambda x: x._info["overheated"], | ||
| icon="mdi:heat-wave", | ||
| type=FeatureType.BinarySensor, | ||
| category=Feature.Category.Debug, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we should have a separate category for warnings, or make them informational? |
||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -235,6 +250,7 @@ async def _initialize_features(self): | |
| name="On since", | ||
| attribute_getter="on_since", | ||
| icon="mdi:clock", | ||
| category=Feature.Category.Debug, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be info, too. |
||
| ) | ||
| ) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the id generation into
Featureto allow overriding it, which we may need to do to keep backwards compat with homeassistant's emeter sensors.