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

Prepare to move specific ZDO requests out of Adapter #1187

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

Conversation

Nerivec
Copy link
Collaborator

@Nerivec Nerivec commented Sep 14, 2024

Final goal is to remove the following functions from Adapter in favor of a generic sendZdo with requests built and sent directly from Controller/Device/Endpoint:

  • changeChannel
  • permitJoin
  • lqi
  • routingTable
  • nodeDescriptor
  • activeEndpoints
  • simpleDescriptor
  • bind
  • unbind
  • removeDevice

This should make the API far more robust against changes (by not requiring updates in every adapter) and offer full payloads to the upstream code (instead of the current limited set of properties), which could lead to a few new features.
Due to the sizeable overhaul required in some stacks, this PR will focus first on introducing the sendZdo function while keeping above functions in place until they can safely be removed (all adapters are required to have support before that can happen).

A working example (albeit using an older implementation) of the full update for ember (as well as some tentative partial implementations in other stacks) can be found in this branch.

TODO:

  • Determine & implement per-stack request payloads variations (see *-zdo.refactor.test.ts)
  • Refactor above functions to use sendZdo
    • ember
    • zstack (done, need testing)
    • deconz (done, need testing)
    • zigate (need testing)
    • zboss (need testing)
    • ezsp
  • Refactor payload parsing to use Zdo.Buffalo.readResponse for ZDO frames
    • ember
    • zstack
    • deconz (done, need testing)
    • zigate (need testing)
    • zboss (need testing)
    • ezsp

TODO separate PR:

  • Move ZDO requests to upstream code (done but not yet added to the PR for compat.)
  • Remove deviceAnnounce and networkAddress event from Adapter
  • Remove above functions from Adapter & impl.
  • Fix tests
    • Controller
    • ember
    • zstack

CC: @kirovilya @ChrisHae @G1K @devbis contributions/ideas on specific stacks welcome!

@Nerivec
Copy link
Collaborator Author

Nerivec commented Sep 18, 2024

@kirovilya Could you test out zboss?
@devbis @fairecasoimeme Could you test out zigate?

Lots of moving parts, need to confirm nothing broke. If you can report back issues.
A round of permit join + join device (interview) + network map + remove device should pretty much trigger all involved ZDOs.
Change channel requires changing the channel in configuration.yaml then starting Z2M again. (Though depending on the start logic of the adapter, it might not trigger at all.)

@kirovilya

This comment was marked as resolved.

// `authentication`: TC significance always 1 (zb specs)
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, clusterId, seconds, 1, []);

await this.sendZdo(ZSpec.BLANK_EUI64, ZSpec.BroadcastAddress.DEFAULT, clusterId, zdoPayload, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a special feature for the ZBOSS - you need to send 2 commands: to the coordinator and the broadcast.
await this.sendZdo(ZSpec.BLANK_EUI64, networkAddress, clusterId, zdoPayload, true);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So proper logic is like this?

if (networkAddress) {
    // device-only when network > 0
    await this.sendZdo(ZSpec.BLANK_EUI64, networkAddress, clusterId, zdoPayload, true);
} else {
    // coordinator-only when networkAddress === 0 or undefined (all)
    await this.sendZdo(ZSpec.BLANK_EUI64, ZSpec.COORDINATOR_ADDRESS, clusterId, zdoPayload, true);

    if (networkAddress === undefined) {
        // all
        await this.sendZdo(ZSpec.BLANK_EUI64, ZSpec.BroadcastAddress.DEFAULT, clusterId, zdoPayload, true);
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. or combine in one block with 2 commands sendZdo - always send a broadcast, even if we connect it to some router

Copy link
Collaborator Author

@Nerivec Nerivec Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not separating it would break what Z2M expects (from the dropdown in frontend).

  • 123 => device-only
  • 0 => coordinator-only
  • undefined => all (coordinator+broadcast)

So I'll push like that, let me know if that doesn't work out in zboss.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to join to the coordinator, I have to send 2 commands: one with the coordinator's address (0), and the second with the broadcast. only in this case, the joining of devices begins.
to join to the router, I sent only one command (without broadcast), but the device did not connect to it (the concentrator did not give permission to connect) and I haven't been able to beat it yet. maybe we should send another broadcast team, but I haven't checked yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing a zb_nlme_permit_joining_request(duration) in espressif zboss library. Could it be similar to emberznet, where there's a special function to open the network on the coordinator-only without going through a ZDO?

@@ -221,14 +214,25 @@ class ZStackAdapter extends Adapter {
}

public async permitJoin(seconds: number, networkAddress?: number): Promise<void> {
const addrmode = networkAddress === undefined ? 0x0f : 0x02;
const dstaddr = networkAddress || 0xfffc;
const clusterId = Zdo.ClusterId.PERMIT_JOINING_REQUEST;
Copy link
Owner

@Koenkk Koenkk Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return result;
} catch (error) {
logger.debug(`Node descriptor request for '${networkAddress}' failed (${error}), retry`, NS);
// Doing a route discovery after simple descriptor request fails makes it succeed sometimes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is lost now, I guess we need to move the logic up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The discovery bit? I made a note of that.

I'm thinking maybe adding a function to Adapter for failed requests, something like this: onFailedRequest(ieeeAddress, networkAddress, profileId, clusterId), which might also allow to make the other various retry logic more generic in the future (upstream in Controller/Device instead of Adapter).
Thoughts?
I guess we could also have a discoverRoute function in Adapter (which may not do anything if not needed). This is quite stack-specific, emberznet does this automatically for any frame via APS option for example.
I guess we could temporarily add it back in sendZdo, I expect this would more or less do the same?

            if (clusterId === Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST) {
                await this.discoverRoute(networkAddress);
            }

            await this.znp.requestZdo(clusterId, payload, waiter?.ID);

case Zdo.ClusterId.PERMIT_JOINING_REQUEST: {
const finalPayload = Buffer.alloc(payload.length + 3);
finalPayload.writeUInt8(ZSpec.BroadcastAddress[networkAddress] ? AddressMode.ADDR_BROADCAST : AddressMode.ADDR_16BIT, 0);
// TODO: confirm zstack uses AddressMode.ADDR_16BIT + ZSpec.BroadcastAddress.DEFAULT to signal "coordinator-only" (assumed from previous code)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self; check this

@@ -308,6 +312,30 @@ class Znp extends events.EventEmitter {
});
}

public requestZdo(clusterId: ZdoClusterId, payload: Buffer, waiterID?: number): Promise<void> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation was that we don't use the ZNP specific ZDO commands but just AF_DATA_REQUEST, wouldn't it be easier to use that?

Copy link
Collaborator Author

@Nerivec Nerivec Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know if the ZDO rsp payloads will match the same? Responses would still come in as AREQ/ZDO?
Probably have to rework the req payloads (looks like the network address would no longer be prefixing at least?).
Does it work for NETWORK_ADDRESS_REQUEST too (just wondering since it has to use the ieee from the zdo payload and not dstaddr from data req)?
Something like this would work out-of-the-box?

// await this.znp.requestZdo(clusterId, payload, waiter?.ID);
await this.dataRequest(networkAddress, Zdo.ZDO_ENDPOINT, Zdo.ZDO_ENDPOINT, clusterId, Constants.AF.DEFAULT_RADIUS, payload, 15000);

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