-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
feat(knx): enable sensor creation via API #133979
Conversation
Hey there @Julius2342, @farmio, @marvin-w, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_info)}) | ||
|
||
# If no entity description is defined, fall back to the entity_config dictionary | ||
if entity_config and not getattr(self, "entity_description", None): |
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.
if entity_config and not getattr(self, "entity_description", None): | |
if entity_config and not hasattr(self, "entity_description"): |
When we start using EntityDescription, I think in a separate PR, we should just move all existing UI configs to EntityDescription and remove this check as well as the last 2 positional arguments of __init__()
instead of using 2 different ways of configuration.
from .const import ATTR_SOURCE, DOMAIN, KNX_MODULE_KEY | ||
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity | ||
from .light import CONF_GA_STATE, CONF_SYNC_STATE | ||
from .schema import SensorSchema | ||
from .storage.const import CONF_ALWAYS_CALLBACK, CONF_DEVICE_INFO, CONF_ENTITY | ||
from .storage.entity_store_schema import CONF_GA_SENSOR, CONF_VALUE_TYPE |
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.
from .const import ATTR_SOURCE, DOMAIN, KNX_MODULE_KEY | |
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity | |
from .light import CONF_GA_STATE, CONF_SYNC_STATE | |
from .schema import SensorSchema | |
from .storage.const import CONF_ALWAYS_CALLBACK, CONF_DEVICE_INFO, CONF_ENTITY | |
from .storage.entity_store_schema import CONF_GA_SENSOR, CONF_VALUE_TYPE | |
from .const import ATTR_SOURCE, CONF_SYNC_STATE, DOMAIN, KNX_MODULE_KEY | |
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity | |
from .schema import SensorSchema | |
from .storage.const import ( | |
CONF_ALWAYS_CALLBACK, | |
CONF_DEVICE_INFO, | |
CONF_ENTITY, | |
CONF_GA_SENSOR, | |
CONF_GA_STATE, | |
CONF_VALUE_TYPE, | |
) |
@@ -49,10 +55,10 @@ class KNXSystemEntityDescription(SensorEntityDescription): | |||
entity_category: EntityCategory = EntityCategory.DIAGNOSTIC | |||
has_entity_name: bool = True | |||
should_poll: bool = True | |||
value_fn: Callable[[KNXModule], StateType | datetime] = lambda knx: None | |||
value_fn: Callable[[KNXModule], StateType | datetime | None] = lambda knx: None |
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.
value_fn: Callable[[KNXModule], StateType | datetime | None] = lambda knx: None | |
value_fn: Callable[[KNXModule], StateType | datetime] = lambda knx: None |
StateType already includes None
|
||
self._attr_device_info = knx.interface_device.device_info | ||
self._attr_should_poll = description.should_poll | ||
self._attr_translation_key = description.key | ||
self._attr_unique_id = f"_{knx.entry.entry_id}_{description.key}" | ||
|
||
@property | ||
def native_value(self) -> StateType | datetime: | ||
def native_value(self) -> StateType | datetime | None: |
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.
def native_value(self) -> StateType | datetime | None: | |
def native_value(self) -> StateType | datetime: |
StateType already includes None
|
||
@dataclass(frozen=True, kw_only=True) | ||
class KnxSensorDescription(SensorEntityDescription): | ||
"""Class describing KNX system sensor entities.""" |
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.
"""Class describing KNX system sensor entities.""" | |
"""Class describing KNX sensor entities.""" |
_state_class: SensorStateClass | None = ( | ||
SensorStateClass(state_class) if state_class else None | ||
) | ||
|
||
_entity_category: EntityCategory | None = ( | ||
EntityCategory(entity_category) if entity_category else None | ||
) | ||
|
||
_device_class: SensorDeviceClass | None = ( | ||
SensorDeviceClass(device_class) if device_class else None | ||
) | ||
|
||
if not _device_class: | ||
_device_class = try_parse_enum(SensorDeviceClass, device.ha_device_class()) |
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.
YAML config is already coerced to those StrEnum
members by voluptuous. UI aren't. I'd move these to the ui-specific branch and have arguments state_class: SensorStateClass | None
instead of state_class: str | None
here.
_device_info: DeviceInfo | None = ( | ||
DeviceInfo(identifiers={(DOMAIN, device_info)}) if device_info else None | ||
) |
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.
YAML doesn't support devices, so move to UI-specific branch and set default in arguments here device_info: DeviceInfo | None = None
CONF_STATE_CLASS: Final = "state_class" | ||
CONF_DEVICE_CLASS: Final = "device_class" |
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.
state_class
can be imported from homeassistant.components.sensor.const
and device_class
from homeassistant.const
vol.Optional(CONF_STATE_CLASS, default=None): vol.Maybe(str), | ||
vol.Optional(CONF_DEVICE_CLASS, default=None): vol.Maybe(str), |
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.
state_class
and device_class
are sensor platform specific - so should not be added to BASE_ENTITY_SCHEMA
and should be validated by their respective schema instead of str
.
vol.Required(CONF_GA_SENSOR): GASelector(state_required=True), | ||
vol.Required(CONF_VALUE_TYPE): str, |
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.
vol.Required(CONF_GA_SENSOR): GASelector(state_required=True), | |
vol.Required(CONF_VALUE_TYPE): str, | |
vol.Required(CONF_GA_SENSOR): GASelector( | |
write=False, | |
state_required=True, | |
dpt=[], | |
), |
The interesting thing for UI configurable sensor entities will be how to pass and validate available dpt
from xknx.dpt
up to the UI and back.
value_type
can be omitted then.
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.
Got your point. That means DPT is a mandatory field when it comes to a sensor, right?
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.
The group address selector does return it this way. It is not yet tuned to support such many DPTs though.
I was also thinking of serializing all selectors (including valid dpts for each key - which are many for sensor 🙃) to pass it to the frontend (like https://github.com/home-assistant-libs/voluptuous-serialize) so the schema is only hardcoded in the backend, and doesn't have to be replicated in the frontend - it didn't happen yet though.
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 believe it makes the most sense to handle mapping, exposing, and validation of DPTs directly within the xknx library. I've created a PR XKNX/xknx#1629 to address this, aiming to centralize the KNX-specific logic in one repository. What are your thoughts?
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.
left a comment there as well. HA SensorEntity has different constraints than xknx Sensor.
I'd like to keep HA specifics in the HA knx integration and move the few things we still have in xknx out of there in the long run. Eg. ha_device_class
should move into the integration, as well as unit of measurement so we can use HA enums and add unit translations etc. one day
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
device_info: DeviceInfo | None = None | ||
|
||
|
||
class KnxSensorDescriptionFactory: |
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.
since this only holds @staticmethod
, it can be either omitted and moved to plain module functions, or if you prefer, moved to methods or classmethods of KnxSensorDescription
Hi 👋!
Just a small remark: I don't think it is necessary to keep YAML and UI entity behaviour 100% consistent. If there are features for UI entities that aren't possible or allowed for YAML then that is fine for me (eg. HA devices).
Would it make sense to build both, the backend and frontend, before merging into HA Core? |
Thanks @farmio for your review! I’m currently on Christmas/New Year leave, but I’ll take a closer look at your proposals in the coming days. I see the factory pattern as a good way to keep configuration and entity logic separate. Even if, down the road, only UI-based configuration will be supported, this approach helps maintain a clear distinction between the configuration store and the platform logic. Entity objects would only need changes when their behavior actually evolves, which I’d expect to happen rarely—especially in something stable like the KNX integration. The factory classes, on the other hand, act as a bridge, bundling the integration-specific business logic. They define how entities, devices, or other components are created from the configuration data specific to that integration. That said, I’d expect the factory classes themselves to undergo more frequent adjustments until the migration from YAML-based configuration to UI-based configuration is fully completed. I agree, let’s merge everything together once we have a clear overview of both the UI and backend changes. Guten Rutsch! |
f7e98e4
to
2b2fc48
Compare
As a side note: it may be useful to have a WS-api endpoint to read valid sensor types - in some form - from the integration. When we have this information in a store globally available in the knx-frontend (like project information), we can not only use for sensor creation dialog, but also match DPTs on the project panel to provide some kind of menu for creating sensors / binary sensors from there - given the DPT is supported. |
Closed in favor of PR #136293. |
Breaking change
NONE
Proposed change
This PR introduces the necessary backend changes to allow KNX sensors to be created and managed via the websocket API/frontend. While no UI changes are contributed yet, this only lays the technical foundation.
In addition, I’d like to suggest using EntityDescription objects as a general approach moving forward. This allows us to separate the entity logic from the configuration mapping (how it’s set up via YAML or the UI). By keeping these concerns apart, we make the code easier to maintain, reduce duplication, and ensure consistent behavior across different configuration methods.
Type of change
Additional information
I’d be happy to implement the necessary UI changes as well. However, due to time constraints, it will likely take around two weeks before I can start working on them.
Checklist
ruff format homeassistant tests
)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest
.requirements_all.txt
.Updated by running
python3 -m script.gen_requirements_all
.To help with the load of incoming pull requests: