Skip to content
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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

ccutrer
Copy link

@ccutrer ccutrer commented Feb 12, 2025

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).

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).
/**
* Get custom clusters that all members share.
*/
get customClusters(): CustomClusters {
Copy link
Collaborator

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.

Copy link
Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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.

Copy link
Author

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.


const commonClusters: CustomClusters = {};
for (const clusterName in customClusters) {
if (Array.from(this._members).every((member) => clusterName in member.getDevice().customClusters)) {
Copy link
Collaborator

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).

Copy link
Author

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.

Copy link
Collaborator

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).

*/
get customClusters(): CustomClusters {
if (this._customClusters) {
return this._customClusters;
Copy link
Owner

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?

Copy link
Collaborator

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)

Copy link
Author

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

@ccutrer ccutrer force-pushed the custom-clusters-to-groups branch from a5c090f to f66199d Compare February 16, 2025 23:43
@@ -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());
Copy link
Collaborator

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)

Copy link
Owner

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;
Copy link
Collaborator

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 {
Copy link
Collaborator

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?

Copy link
Collaborator

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>;
Copy link
Collaborator

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.

Copy link
Owner

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants