-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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: better generator support for multi-endpoint devices #6930
feat: better generator support for multi-endpoint devices #6930
Conversation
src/lib/modernExtend.ts
Outdated
@@ -67,13 +67,15 @@ async function setupAttributes( | |||
|
|||
export function setupConfigureForReporting( | |||
cluster: string | number, attribute: ReportingConfigAttribute, config: ReportingConfigWithoutAttribute, access: Access, | |||
endpointID?: number, |
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 is this needed? setupAttributes
automatically check the clusters.
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.
It is true, but if device has, say, 10 endpoints with temperature cluster - then setupAttributes
function currently will iterate all endpoints and for each endpoint with temperature cluster it will try to bind it, setup reporting and possibly read the value of the attribute. As temperature cluster is present in 10 endpoints - setupAttributes
will be called 10 times. This results in 10^2 * 3 = 300 operations.
If instead endpoint will be provided - setupAttributes
will only do necessary operations for each endpoint and cluster exactly once, reducing operations to 10 * 3 = 30.
Plus by providing endpointID to the extender we can set exposed value to be withEndpoint
, defining same exposed attributes separately for each endpoint.
Does this sound reasonable?
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.
But why are there 10 endpoints with the temperature cluster on the 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.
Just an arbitrary example.
Still, it is possible that device could have more than one endpoint with same cluster. And if it would be cluster like temperature or pressure(or any numeric one) - would trigger this behavior.
As another example: device has multiple environmental sensors attached, and those sensors have overlapping and distinct clusters. I.e. all sensors can have temperature cluster, but only one of them will have CO2, another one can have VOC, etc. Think like board can have attached Bosch BME280 & Sensirion SCD4X sensors. They both have temp & humidity clusters, but BME280 additionally has pressure, while SCD4X has CO2.
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 I understand correctly, for your use-case it's important that the generate definition works without requiring any external converters. How would the generate definition decide for which endpoints to setup temperature reporting for if e.g. 10 of the endpoints have the temperature cluster? It sounds like we are fixing something in z2m here while it should be fixed at the device side instead.
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.
How would the generate definition decide for which endpoints to setup temperature reporting for if e.g. 10 of the endpoints have the temperature cluster?
If we provide specific endpoint we can then tailor extender for that specific cluster & endpoint(would it be cluster attribute value or endpoint to bind to).
It sounds like we are fixing something in z2m here while it should be fixed at the device side instead.
Maybe we have a bit of misunderstanding.
For my example I chose temp cluster, because I tested with it, but consider other examples of clusters that also rely on this functionality for generated definition:
I am suggesting that device can have multiple endpoints with the same cluster, and we should try to support it by generated definitions as well.
For example devices that have multiple endpoints with same cluster:
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 am suggesting that device can have multiple endpoints with the same cluster, and we should try to support it by generated definitions as well.
Yes that would be nice, but instead of having e.g. temperature(endpointID: 1)
, temperatature(endpointID: 2
), we can do something similar to what we already do for onOff. The only thing that needs to be added multiple times is the expose, to/fromZigbee converters don't need to be duplicated.
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.
Ah, I see.
Thank you for explanation, will uldate the implementation 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.
Sorry, took a bit longer then I expected. Now from
and to
entities are not duplicated.
00e2f8e
to
ab7a161
Compare
src/lib/generateDefinition.ts
Outdated
|
||
interface GeneratedExtend {extend: ModernExtend, source: string, lib?: string} | ||
interface GeneratedExtend {extend?: ModernExtend, extendFn?: (a: object) => ModernExtend, args?: object, source: string, lib?: string} |
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 would prefer it to have fields like extendFn?: (a: T) => ModernExtend, args?: T
, but I am not sure if it is possible to achieve this.
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 not?
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 am not strong at Typescript(that's one reason), but from other languages I know that if I would to add a type parameter here, then it would mean that in all uses of this interface there should be a type parameter specified.
For example, if GeneratedExtend
would be GeneratedExtend<T>
, then what this should look like:
type ExtendGenerator = (endpoints?: Zh.Endpoint[]) => Promise<GeneratedExtend[]>;
? If to provide single type here then we will not be able to have extender functions that will accept different set of argument types.
Maybe the other way is to provide a class that has two methods like
class Provider {
getModernExtend(): ModernExtend
getSource(): string
}
and to have this be implemented by other type, that is generic?
What would you suggest?
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 tried to do it with the generic but failed to do so. For functions like extenderOnOffLight
it's also quite complicated as args can be either OnOffArgs
or LightArgs
. Therefore the provider class sounds like the best solution
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.
Updated. Please check if solution is good.
Also, while this MR works properly with reported attributes and fetching attributes from dev console, when you try to fetch the attribute value from the I think this is related to |
a9cfd07
to
aaf8045
Compare
oops. It turns out I also made support for multi endpoints... maybe I shouldn’t have done it if it works here too. |
src/lib/generateDefinition.ts
Outdated
const imports: {[s: string]: string[]} = {}; | ||
const importsDeduplication = new Map<string, boolean>(); |
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 can't this be a 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.
Thank you. Updated.
test/generateDefinition.test.ts
Outdated
model: 'combo', | ||
vendor: '', | ||
description: 'Automatically generated definition', | ||
extend: [temperature({"endpoints":["1","2"]}), onOff({"powerOnBehavior":false})], |
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 other modernExtend the endpoints passed is an object, e.g.
extend: [onOff({endpoints: {left: 1, center: 2, right: 3}})], |
This allows to specify a friendly name for the 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.
Honestly I think that in this particular example the way that onOff
handles some of its argument is "excessive".
I would propose to split it in to more extenders, like
[onOff({endpoints: ["left","center","right"]}), deviceEndpoints({left:1, center:2, right:3}), ota(someDefinition)]
, as I think it should not define which endpoints device has, nor, even more so, define the OTA configuration.
What do you think?
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.
But then some check should be in place to check endpoints: ["left","center","right"]
against deviceEndpoints({left:1, center:2, right:3})
, it creates some unnecessary duplication in my opinion.
Update: I think what you propose is better indeed, but we should do some kind of check. If modernExtend also returns the endpoint names we can do it in index.ts
at runtime
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.
Updated. I am not sure what specific check to do, so I didn't do any. If you would suggest which checks to do - I can give them a go.
I also didn't update current usages of onOff
with endpoints, as it would increase the size of this MR. I can do it in the next MR, if this one will be merged.
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.
Sounds good! Already merged this to get it into the 1 Feb release.
ZCL spec 4 .5 .2 .2 .1 .1 tells that attribute value is 10x of pressure in kPa
Updated pressure, temperature & humidy reporting for better values
Supported extenders will be called only once for a cluster for all endpoints. Other extenders will still run for each endpoint separately.
be9073d
to
85cefcb
Compare
This will allow for better separation of extention options. onOff behavior is unchanged, as it will require big changes, which are out of scope.
This allows to have generators with typed arguments.
} = args; | ||
|
||
let endpoints = args.endpoints; |
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 for a next pr, can remove the endpoint
arg completely? If you want a single endpoint, just pass endpoints
with a single element
Previously if multiple endpoints had second endpoint generated definition would still try to fetch attribute value from the first endpoint. Fetching attribute value from developer console worked properly when using correct endpoint.