-
Notifications
You must be signed in to change notification settings - Fork 320
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: Allow using custom clusters with groups #1318
base: master
Are you sure you want to change the base?
Conversation
But only if ALL devices in the group support the cluster. It's highly dependendt on individual devices if they will support read/write/command for custom clusters. In my testing, my Inovelli switches are responding to writing attributes, but not commands (such as LED commands).
src/controller/model/group.ts
Outdated
/** | ||
* Get custom clusters that all members share. | ||
*/ | ||
get customClusters(): CustomClusters { |
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 can foresee sync issues with this logic.
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.
Just with members of the group being out of date, or something else? At first I didn't cache this at all, but I didn't want to run this calculation every time a read/write/command is sent. I could go back to that.
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.
Definitely needs some kind of caching due to the amount of calls that could be generated, but it needs to be invalidated by some events. I can think of at least OTA updates (otherwise it breaks without a restart), and re-interview (and re-configure?), basically anything that can change the clusters of a device.
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.
Another thought. We should also be looking out for potential race conditions since this is triggered from various places.
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.
Is there any documentation on the threading model so I can get more familiar with it? If not, that's fine, I'm happy to go code diving and figure it out.
src/controller/model/group.ts
Outdated
|
||
const commonClusters: CustomClusters = {}; | ||
for (const clusterName in customClusters) { | ||
if (Array.from(this._members).every((member) => clusterName in member.getDevice().customClusters)) { |
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.
Should optimize this for performance. And needs testing with large-ish groups, to see the impact on first exec (could "freeze" for extended periods, worse than it already does in some cases).
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.
What would you recommend? I'm not a JS/TS guy. But I can definitely do performance testing here. I have a group with 85 members in my network that I can send a write to. What do you want me to measure? But not for a custom cluster, though it shouldn't matter, as currently structured custom clusters for the group are calculated regardless of the first write to the group. BUT... I could change that to only calculate the custom clusters for the group if we couldn't find the cluster at first in a read/write/command. That would make this change essentially a no-op for 99.9% of users.
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'm not overly familiar with Z2M group logic, Koenkk should be able to answer that better than me.
I'd first look into optimizing the number of nested loops as much as possible (avoiding the array conversion would be a good start).
No-oping for majority of users is also always preferred when possible.
As for testing, we need to compare what the first execution adds (i.e. when it's first cached) to current time for the same function call, make sure it's not going to create a bottleneck (mostly for low resource scenario).
src/controller/model/group.ts
Outdated
*/ | ||
get customClusters(): CustomClusters { | ||
if (this._customClusters) { | ||
return this._customClusters; |
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 value should be re-calculated once the group members change. Maybe make a private computeCustomClusters
which is called when first called and when group members change?
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 goes back to what I mentioned here #1318 (comment)
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.
yes, that's exactly what it's already doing, but @Nerivec brought up a good point that the clusters available on device/endpoint could change without group membership changing, so I need to handle several of those cases as well
a5c090f
to
f66199d
Compare
@@ -27,6 +28,7 @@ export class Group extends Entity { | |||
private databaseID: number; | |||
public readonly groupID: number; | |||
private readonly _members: Set<Endpoint>; | |||
private _customClusters: CustomClusters | undefined; | |||
get members(): Endpoint[] { | |||
return Array.from(this._members).filter((e) => e.getDevice()); |
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.
@Koenkk remember the reasoning behind this logic? (not related to the PR, but odd)
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 think to filter out deleted devices
@@ -27,6 +28,7 @@ export class Group extends Entity { | |||
private databaseID: number; | |||
public readonly groupID: number; | |||
private readonly _members: Set<Endpoint>; | |||
private _customClusters: CustomClusters | undefined; |
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 think we should name this _commonCustomClusters
to avoid confusion (could be all clusters of all devices with current name).
/** | ||
* Calculate, store, and return custom clusters that all members share. | ||
*/ | ||
private calculateCustomClusters(): CustomClusters { |
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.
calculate
doesn't seem to quite fit. Maybe identifyCommonCustomClusters
?
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 shouldn't need to be changed.
Also, considering the changes, and the fact tests are still passing, looks like there are gaps in coverage that are not being properly detected (hence, could be misbehaving from previous logic and we wouldn't know).
@@ -27,6 +28,7 @@ export class Group extends Entity { | |||
private databaseID: number; | |||
public readonly groupID: number; | |||
private readonly _members: Set<Endpoint>; |
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.
@Koenkk I think between the logic in members
and now the new logic from this PR, it would be more efficient to just use an array here, and have a check in add
instead of using a Set
that ends up often converted to an array... or we get rid of Array-specific functions, use for (const member of this._members)
and keep the Set
.
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'm fine with that!
But only if ALL devices in the group support the cluster. It's highly dependent on individual devices if they will support read/write/command for custom clusters. In my testing, my Inovelli switches are responding to writing attributes, but not commands (such as LED commands).