WIP - convert zha remotes from binary sensors to events#14902
WIP - convert zha remotes from binary sensors to events#14902dmulcahey wants to merge 9 commits intohome-assistant:devfrom
Conversation
| EventOrigin.remote) | ||
| self._level = level | ||
| self._hass.bus.fire('zha.level_change', { 'device': self._id, | ||
| 'level': self._level }, EventOrigin.remote) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
whitespace before '}'
| self._hass.bus.fire('zha.on', { 'device': self._id }, | ||
| EventOrigin.remote) | ||
| self._level = level | ||
| self._hass.bus.fire('zha.level_change', { 'device': self._id, |
| self._hass.bus.fire('zha.off', { 'device': self._id }, | ||
| EventOrigin.remote) | ||
| elif level > 0 and self._level == 0: | ||
| self._hass.bus.fire('zha.on', { 'device': self._id }, |
There was a problem hiding this comment.
whitespace after '{'
whitespace before '}'
| def set_level(self, level): | ||
| """Set the level.""" | ||
| if level == 0 and self._level > 0: | ||
| self._hass.bus.fire('zha.off', { 'device': self._id }, |
There was a problem hiding this comment.
whitespace after '{'
whitespace before '}'
| def __init__(self, hass, cluster, discovery_info): | ||
| """Initialize Switch.""" | ||
| super().__init__(hass, cluster, discovery_info) | ||
| self._level = 0 |
There was a problem hiding this comment.
indentation is not a multiple of four
|
|
||
| def __init__(self, hass, cluster, discovery_info): | ||
| """Initialize Switch.""" | ||
| super().__init__(hass, cluster, discovery_info) |
There was a problem hiding this comment.
indentation is not a multiple of four
| """Switch / remote event for zha""" | ||
|
|
||
| def __init__(self, hass, cluster, discovery_info): | ||
| """Initialize Switch.""" |
There was a problem hiding this comment.
indentation is not a multiple of four
| self._ieee = discovery_info['endpoint'].device.ieee | ||
| self._ieeetail = ''.join(['%02x' % (o, ) for o in self._ieee[-4:]]) | ||
| if discovery_info['manufacturer'] and discovery_info['model'] is not \ | ||
| None: |
There was a problem hiding this comment.
continuation line over-indented for visual indent
| await safe(cluster.bind()) | ||
| await safe(cluster.configure_reporting(0, 1, 600, 1)) | ||
| self._hass.data[DATA_ZHA_EVENT].append(ZHALevelEvent(self._hass, | ||
| cluster, discovery_info)) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| await safe(cluster.bind()) | ||
| await safe(cluster.configure_reporting(0, 0, 600, 1)) | ||
| self._hass.data[DATA_ZHA_EVENT].append(ZHASwitchEvent(self._hass, | ||
| cluster, discovery_info)) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| EventOrigin.remote) | ||
| self._level = level | ||
| self._hass.bus.fire('zha.level_change', {'device': self._id, | ||
| 'level': self._level}, EventOrigin.remote) |
There was a problem hiding this comment.
line too long (91 > 79 characters)
| await safe(cluster.bind()) | ||
| await safe(cluster.configure_reporting(0, 1, 600, 1)) | ||
| self._hass.data[DATA_ZHA_EVENT].append(ZHALevelEvent(self._hass, | ||
| cluster, discovery_info)) |
There was a problem hiding this comment.
line too long (90 > 79 characters)
| await safe(cluster.bind()) | ||
| await safe(cluster.configure_reporting(0, 0, 600, 1)) | ||
| self._hass.data[DATA_ZHA_EVENT].append(ZHASwitchEvent(self._hass, | ||
| cluster, discovery_info)) |
There was a problem hiding this comment.
line too long (91 > 79 characters)
| self._ieeetail = ''.join(['%02x' % (o, ) for o in self._ieee[-4:]]) | ||
| if discovery_info['manufacturer'] and discovery_info['model'] is not \ | ||
| None: | ||
| self._id = "{}.{}_{}_{}_{}{}".format( |
There was a problem hiding this comment.
I kept it what it was before to try to minimize the impact of the breaking change. Zigbee devices consist of endpoints that expose functionality on clusters as attributes. device -> endpoints -> clusters -> attributes and commands. 1 physical device may expose several functions. so what has been done up to this point is:
domain.manufacturer_model_short_address_endpoint_id_cluster_id
here is an example of a single device that exposes motion and temperature sensors:
binary_sensor.centralite_3326l_0e35a368_1_1280
sensor.centralite_3326l_0e35a368_1_1026
does this make sense?
| ) | ||
| else: | ||
| self._id = "{}.zha_{}_{}{}".format( | ||
| 'zha', |
There was a problem hiding this comment.
So eh, now it will be zha.zha_ ? Anything that's not dynamic you can just put in the string.
There was a problem hiding this comment.
I'll take a look at this case. Pretty sure that is supposed to be domain and I messed something up there.
| def cluster_command(self, tsn, command_id, args): | ||
| """Handle commands received to this cluster.""" | ||
| if command_id in (0x0000, 0x0040): | ||
| self._hass.bus.fire('zha.off', {'device': self._id}, |
There was a problem hiding this comment.
Please extract the event names as EVENT_XXX constants at the top
| @callback | ||
| def cluster_command(self, tsn, command_id, args): | ||
| """Handle commands received to this cluster.""" | ||
| if command_id in (0x0000, 0x0004): # move_to_level, -with_on_off |
There was a problem hiding this comment.
I would love to see all things like 0x0000 and 0x0004 be extracted into constants with names like MOVE_TO_LEVEL etc. These constants should at least live at the top of the file, but preferably they are moved to ZHA.
There was a problem hiding this comment.
agreed. I can try to tackle that this week in separate PR's to both repos.
There was a problem hiding this comment.
Can you start with putting them in constants defined at the top of this file
There was a problem hiding this comment.
something like?
cmd = zigpy.zcl.clusters.general.LevelControl.server_commands.get(command_id, ('unknown'))[0]
if cmd in ('move_to_level', 'move_to_level_with_on_off'):
pass
| EventOrigin.remote) | ||
| self._level = level | ||
| self._hass.bus.fire(LEVEL_CHANGE_EVENT_KEY, {DEVICE: self._id, | ||
| LEVEL: self._level}, EventOrigin.remote) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| self._cluster.add_listener(self) | ||
| self._ieee = discovery_info['endpoint'].device.ieee | ||
| self._ieeetail = ''.join(['%02x' % (o, ) for o in self._ieee[-4:]]) | ||
| self._domain = domain; |
| await safe(cluster.bind()) | ||
| await safe(cluster.configure_reporting(0, 1, 600, 1)) | ||
| self._hass.data[DATA_ZHA_EVENT].append(ZHALevelEvent(self._hass, | ||
| cluster, discovery_info)) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| """Increment the level.""" | ||
| self.set_level(min(255, max(0, self._level + change))) | ||
|
|
||
| def set_level(self, level): |
There was a problem hiding this comment.
This is called from a method annotated with @callback. This means that it is run inside the event loop. Please also annotate this method with callback to indicate that. Preferably you prefix all methods likeasync_set_level, to indicate it's inside the event loop.
There was a problem hiding this comment.
cluster_command is a function called by zigpy. I may have added @callback incorrectly. Is there a way to determine if this is the case?
There was a problem hiding this comment.
I think that zigpy runs inside the event loop, check the internals of the module or look at how the other code that interacts with zigpy does it.
| def set_level(self, level): | ||
| """Set the level.""" | ||
| if level == 0 and self._level > 0: | ||
| self._hass.bus.fire( |
There was a problem hiding this comment.
Inside the event loop, you need to call bus.async_fire
| self._cluster.add_listener(self) | ||
| self._ieee = discovery_info['endpoint'].device.ieee | ||
| self._ieeetail = ''.join(['%02x' % (o, ) for o in self._ieee[-4:]]) | ||
| self._domain = domain |
There was a problem hiding this comment.
This instance variable is not used.
| self._hass = hass | ||
| self._cluster = cluster | ||
| self._cluster.add_listener(self) | ||
| self._ieee = discovery_info['endpoint'].device.ieee |
There was a problem hiding this comment.
This instance variable is not used.
| ) | ||
| self._level = level | ||
| self._hass.bus.fire( | ||
| LEVEL_CHANGE_EVENT_KEY, |
There was a problem hiding this comment.
Wouldn't something that has a level be a sensor ?
There was a problem hiding this comment.
I think that's why this was originally added as a binary sensor. the binary sensor was trying to track on vs. off and level was an attribute.
There was a problem hiding this comment.
I'm not sure how to feel about "level". On one hand I'd expect event to be stateless, so no actual level value, just "level_up" or "level_down" events, but on the other hand it is nice to have everything wired together out of the box.
| if args[0] == 0xff: | ||
| rate = 10 # Should read default move rate | ||
| self.move_level(-rate if args[0] else rate) | ||
| elif command_id == 0x0002: # step |
There was a problem hiding this comment.
there's 'step' and 'step_with_on_off' == '0x06. We should handle both of those. Check #14756
| self._hass.bus.fire( | ||
| OFF_EVENT_KEY, | ||
| {DEVICE: self._id}, | ||
| EventOrigin.remote |
There was a problem hiding this comment.
What about either having an attribute indicating how many times the key was pressed in the last second or two (clicks_per_second), or maybe a separate event indicating double/triple clicks? eg for the 1st "off" command received in the last 2s time window we fire OFF_EVENT_KEY, for the 2nd command received in the last 2s windows, we fire OFF_DOUBLE_EVEN_KEY or anything like this?
I could get some fancy automation with events like this, like controlling group of lights with a single remote.
There was a problem hiding this comment.
Maybe in a subsequent PR. For now I just want to focus on the functionality that was already here.
| cluster = out_clusters[LevelControl.cluster_id] | ||
| if discovery_info['new_join']: | ||
| await safe(cluster.bind()) | ||
| await safe(cluster.configure_reporting(0, 1, 600, 1)) |
There was a problem hiding this comment.
I'm not sure having "min_report_interval" at such low value is a good idea. Two reasons:
- remotes are unlikely to send attribute reports anyway, as those usually just send "cluster commands"
- if for some reason it would send attribute reports (which I still have to see), you are risking to put too much traffic onto network. I've tried it with the lights and when dimming more then one light at the same time, those reports were overloading the network to the point it was causing Delivery Timeout exceptions.
There was a problem hiding this comment.
All of this came from the binary_sensor component of zha. It is all pre-existing logic. I’ll look into it as well though.
| ) | ||
| from zigpy.zcl.clusters.general import OnOff | ||
| if component == 'binary_sensor' and OnOff.cluster_id == \ | ||
| cluster.cluster_id: |
There was a problem hiding this comment.
Should we better add something like SINGLE_OUTPUT_CLUSTER_EVENT_CLASS to zha/const.py and handle those in a separate pass from handling SINGLE_INPUT/OUTPUT_DEVICE_CLASSes??? in current implementation, a "hypothetical" IAS device with an OnOff input cluster would cause the event handler to be instantiated, even if there's no OnOff output cluster, wouldn't it?
There was a problem hiding this comment.
Yes, was speaking to @damarco about this as well. I am going to fix this. I ordered some Xiaomi sensors to test this case specifically because apparently they work this way. I have to add a sensor class back into binary_sensor to fix this as well.
eb8bc8c to
e6d4350
Compare
|
What's the status of this, merge conflicts and it is still marked as WIP |
|
I’ll try to pick this back up this weekend |
| self._config, | ||
| ) | ||
|
|
||
| async def _async_setup_remote(self, discovery_info): |
There was a problem hiding this comment.
should we move this into a separate file, just to keep __init__.py less cluttered?
| pass | ||
|
|
||
|
|
||
| class ZHAEvent(object): |
|
closing this and starting fresh off of current dev. will reopen and reference this. |
Description:
This PR removes zha binary_sensor entities and converts them to events based on feedback from this PR: #14795