-
-
Notifications
You must be signed in to change notification settings - Fork 31.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
Add zha device entity #14579
Add zha device entity #14579
Conversation
What's the reasoning behind having this as a "ZhaEndpointEntity" and not as a "ZhaDeviceEntity"? as another thought, I think it would be great to have 'nwk' address included in the device_state_attributes, so it would be easier to correlate debug messages like:
|
I initially started with a ZhaDeviceEntity, but on the other hand manufacturer, model and battery levels are cluster attributes bound to an endpoint. So (theoretically) those can differ between different endpoints. I guess having both (device and endpoint) is overkill, so I chose the endpoint entity to have a correct mapping for the mentioned attributes, though on the other hand you might have some redundant information when multiple endpoints are used. In reality most of the devices only have a single endpoint so it should not really make a difference. |
Not quite in my case. I have a lot of GE wall switches and those do have two endpoints. Currently out of 25 devices, 9 of those have two endpoints and currently there's only a single centralite motion sensor. And I have about 7 more switches waiting to go in :) as about LQI/RSSI, those are "device" attributes, as those are related to "radio" and it's the "radio" which may serve those multiple endpoints, but not vice versa. And zigpy/device.py points the same thing. On the other hand ZDO itself is Enpoint #0, isn't it? So yeah, RSSI/LQI are attributed to the endpoint #0, but are they really attributed to the "application" endpoints? As for the battery status, we already get battery sensors in HA. We could include battery status in ZhaDeviceEntity, but how much benefit would it provide? I'd rather see there hardware/firmware versions there. And I still think that it is extremely unlikely that different endpoints are going to be using different batteries or being manufactured by different vendors and my reasoning for this is as follows: all those endpoints belong to the same radio which has an unique IEEE address. I can see why manufacturer may want to expose the "same" battery on different endpoints, but I don't see a practical reason to have two different batteries dedicated for its own endpoint and housed in the same battery powered device. So for now my opinion is not changed and as my personal preference I'd like to see these as ZhaDeviceEntities. Unless there're other reason which I fail to see for now? |
We actually don't have any battery status for |
Yep, you're right. I forgot that PR didn't get merge. I'm running it in my private branch and so far I get 100% battery reports. Guess need to wait a bit to drain the battery, because it's coded for 2.8V max battery voltage and currently my battery measures 3V
Agreed, but personally I like the battery in a form of a sensor. Although I'm open to idea of having battery indicator as an attribute of a ZhaEndpoint/DeviceEntity. Maybe should add another binary_sensor "battery_low"... |
@damarco Great work. I think this is going to be very helpful. Question... how come these are hidden by default? The zwave component already does something similar to this and those entities are visible by default. Shouldn't these be visible to stay consistent w/ other components that have similar functionality? |
I agree with @dmulcahey I actually like making it similar to zwave. @dmulcahey any plans to add in battery sensors again or do you think they would be better fit in this entity? I remember seeing some mention about where best to place it. |
*edit - moved comment inline with code. |
@@ -245,6 +248,16 @@ def device_removed(self, device): | |||
join, | |||
) | |||
|
|||
endpoint_key = "zha-{}".format(device_key) |
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.
@damarco I’ve been thinking about this some more while also tinkering with it locally. I agree with the comments on the PR... I think ZhaDeviceEntity is a better fit for what this really represents. I completely understand how you ended up where you did. I would have done the same thing.
That being said, many devices have multiple endpoints and leaving this as is will lead to everyone getting duplicate entities for them that really represent the same information.
Would changing the name to ZhaDeviceEntity and only creating these for the ZHA endpoint and therefore only creating 1 per device make sense in your opinion? Something like this:
if endpoint.profile_id == zigpy.profiles.zha.PROFILE_ID:
endpoint_key = "zha-{}".format(device_key)
endpoint_entity = ZhaEndpointEntity(
endpoint,
discovered_info['manufacturer'],
discovered_info['model'],
self,
endpoint_key,
)
await self._component.async_add_entities([endpoint_entity])
This would avoid duplicate entities and give us a place to put other device specific attributes such as battery level, neighbor tables, route information, etc and it would end up matching what was done for zwave pretty well. It also leaves open the possibility of creating another entity for endpoints if in fact we find a use for that. Just a thought...
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.
We should just use device_key
for endpoint_key. No need to add zha to it.
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.
Wouldn't this create a conflict if the same unique_id is used twice? It is already used in the if component: ...
block above.
There is an interesting side effect in the last_seen / hass state update logic for polled entities. It creates a slightly confusing ui: The screenshot was taken at 8:25:48 am (11 seconds after the last_update time) because the device is polled for electrical measurements it is updated every ~30 seconds for a new current reading. last_seen updates accordingly but because the state of this entity doesn't "update" the time at the top isn't updated and it's a bit misleading. I'm not sure what the best way to handle this is. I was going to suggest setting force_update to true on the entity but that breaks the online / offline handling because this entity is polled and it would cause it to update on every poll. I think we want to force update hass every time the last_seen property on the device in zigpy is updated. Does that make sense? edit if you think this makes sense, this may work: add this to the entity class in init:
add this as a property:
and modify async_update to be something like this:
There may be a better way to do this but this seems to work. |
Thanks for all the comments.
|
I see your point here and I agree.
I have yet to see a device where the manufacturer and model differ on endpoints. I think it is very unlikely that there would ever be a single device with endpoints where this information differs.
This is why I suggested locking the creation of these to the ZHA endpoint by profile id. This allows everything else to stay the same as it is currently and you only get 1 per device. I have devices from several manufacturers: centralite, sengled, climax technologies, securifi, SmartThings, etc. Most of them have a manufacturer specific endpoint that repeats everything on the ZHA endpoint. because of this multiple entities are created with exactly the same information with this change. I can see this leading to user confusion even with these hidden by default (which I am still not convinced that they should be.) Also, every attribute on this entity is actually pulled from the device. You're using the endpoint to back up to the device to get the data. so why not try to make these represent a device since that's what it seems to be doing anyway...
I agree that if you know what you're looking at and you have some knowledge of the internals that it makes sense. Most users won't have that knowledge and I think that this has the potential to confuse casual users.
I understand. My view here is that consistency leads to a better overall user experience. zwave has already created this type of entity and they are visible by default. |
Per ZCL specs, the "Battery Settings Attribute Set" should be writable. If we could allow users to set "BatterySize" (attr id 0x0032) and/or "BatteryRatedVoltage" (attr id 0x0034) then battery sensor could do some approximation based on the above and current voltage.
+1 |
self._device_state_attributes = {} | ||
ieee = endpoint.device.ieee | ||
ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) | ||
if manufacturer and model is not 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.
Are you trying to check that both manufacturer and model are not none?
if self._endpoint.device.last_seen is not None: | ||
time_struct = time.localtime(self._endpoint.device.last_seen) | ||
update_time = time.strftime("%Y-%m-%dT%H:%M:%S", time_struct) | ||
self._device_state_attributes['last_update'] = update_time |
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.
We should not store update time.
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.
Maybe update_time or last_update is not the best name. This is essentially the time when the last message was received from the device. I thought this might be useful for verification or troubleshooting purposes.
self._state = 'offline' | ||
else: | ||
self._state = 'online' | ||
self.schedule_update_ha_state() |
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.
Do not call this. This is a polling entity and so this will already be called.
ieee = endpoint.device.ieee | ||
ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) | ||
if manufacturer and model is not None: | ||
self.entity_id = "{}.{}_{}_{}_{}".format( |
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.
There is no need to create such complicated entity ids. Since it has a unique ID, a unique entity ID will be assigned once and used every time. You don't have to assign one at all and it will just work.
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 wanted to keep the naming consistent with the other zha entities. Otherwise the user would have no way of identifying which entities belong together.
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.
For me personally, having "manufacturer" and "model" in the entity_id provides me with a better user experience correctly identifying devices, especially if you join more than one device at a time.
class ZhaEndpointEntity(entity.Entity): | ||
"""A base class for ZHA endpoints.""" | ||
|
||
_domain = DOMAIN |
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.
Why?
) | ||
|
||
nwk = endpoint.device.nwk | ||
self._device_state_attributes['nwk'] = '0x{0:04x}'.format(nwk) |
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 you're assigning this all in the constructor, just do it all at once.
self._device_state_attributes = {
'ieee': str(…),
# etc
}
Why not to keep "last_seen" maybe?
…On Tue, May 29, 2018, 09:59 damarco ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In homeassistant/components/zha/__init__.py
<#14579 (comment)>
:
> + """Return the state of the entity."""
+ return self._state
+
+ @Property
+ def hidden(self) -> bool:
+ """Hide by default."""
+ return True
+
+ @Property
+ def device_state_attributes(self):
+ """Return device specific state attributes."""
+ update_time = None
+ if self._endpoint.device.last_seen is not None:
+ time_struct = time.localtime(self._endpoint.device.last_seen)
+ update_time = time.strftime("%Y-%m-%dT%H:%M:%S", time_struct)
+ self._device_state_attributes['last_update'] = update_time
Maybe update_time or last_update is not the best name. This is essentially
the time when the last message was received from the device. I thought this
might be useful for verification or troubleshooting purposes.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#14579 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AFjmcKhGH7C-c5iUpNZBqjwG-ES8-aMjks5t3VRAgaJpZM4UHc_O>
.
|
I hope all of the comments are addressed now and this is ready to merge. |
I wonder if we should put this PR on hold until we have official notion of devices added to Home Assistant. Discussion here: home-assistant/architecture#36 |
@balloob any chance we could get this in now and then adapt it as necessary once the official notion of devices is settled? |
"""Return device specific state attributes.""" | ||
update_time = None | ||
if self._device.last_seen is not None: | ||
time_struct = time.localtime(self._device.last_seen) |
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.
We should not add a last seen. This will be updated too often, causing a lot of state changes.
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.
This is quite useful bit of information, especially when troubleshooting issues with your zigbee network.
What if we don't include "last_seen" while a device is healthy/online, but have it in the state attributes once a devices becomes unresponsive? at least we could see when there was last communication from the device that way?
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.
That could work, however, I think that it makes more sense if someone would contribute a management panel like we have for Z-Wave.
cdda9ae
to
a02e325
Compare
@balloob "last_seen" is now only shown if the device is offline. I would like to add support for the new device registry in a separate PR, though as far as I understood we would need to add support for config entries first. |
Correct. |
Description:
This adds a separate entity to expose device level state. The last_seen device attribute is used to derive an online/offline state.
Checklist: