Skip to content

use DataRequest instead of DataRequestExt if possible #174

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

Closed

Conversation

dumpfheimer
Copy link
Contributor

According to Docs the DataRequestExt is sequential:

"And any AF_DATA_REQUEST_EXT with a huge data byte count must be completed (or timed-out) before another will be started"

This PR would use the normal data request instead of the extended data request if possible.

@puddly
Copy link
Collaborator

puddly commented Sep 15, 2022

The two commands actually lead to the exact same function (ti/zstack/mt/mt_af.c):

    case MT_AF_DATA_REQUEST:
    case MT_AF_DATA_REQUEST_EXT:
      MT_AfDataRequest(pBuf);
      break;

The sequential behavior you mention seems to be triggered when the length of the packet exceeds 230 bytes for _ext and 240 bytes for the normal request and requires a second command to be sent (MT_AF_DATA_STORE) to do something to send it. I believe this is Z-Stack handling fragmentation.

Do you ever see packets larger than 230 bytes at runtime? I believe even with OTA running, the maximum packet length is never greater than 80 bytes. It may be better to guard against this codepath with an explicit if len(data) > 230: raise ValueError("Packet is too large to send without fragmentation") before even trying to send it.

@dumpfheimer
Copy link
Contributor Author

True, they do 😮‍💨. Inside that function they do differetiate between the two requests, to be fair, but it seems to not make a noticable difference anyways.

But while looking at the code I found something interesting. It seems like DataRequest can be sent as both, synchronous and asynchronous request which could greatly reduces time spent in the sync lock in api.py

Do you have any thoughts about this?

@puddly
Copy link
Collaborator

puddly commented Sep 17, 2022

Interesting. My understanding of the request flow is that you queue up a request, Z-Stack responds with the Rsp once it has been sent (or errors out), and then sends back a Callback once a confirmation is received or fails. Is there a more asynchronous way to send requests?

@dumpfheimer
Copy link
Contributor Author

Not sure if there is a callback later on. This is the end of the MT_AfDataRequest function:

if (MT_RPC_CMD_SREQ == (cmd0 & MT_RPC_CMD_TYPE_MASK))
{
  MT_BuildAndSendZToolResponse(((uint8_t)MT_RPC_CMD_SRSP|(uint8_t)MT_RPC_SYS_AF), cmd1, 1, &retValue);
}

I testet this with changing api.py (added to the beginning of async def request(...) ):

        if type(request) is c.AF.DataRequest.Req and request.Callback is None:
            LOGGER.info("Sending DataRequest as AREQ %s " % type(request))
            request.header.with_type(t.CommandType.AREQ)

This right now ignores the response but I guess it would not be difficult to add a Callback to the firmware.

I think its worth checking this out, toggling a bunch of lights gets really fast especially when using scenes that change not only the state but also color. I think it might be possible to get out even more when turn_on creates a task instead of running asynchronously (maybe HA waits for the first light to finish its state change before it starts with the second?)

@dumpfheimer
Copy link
Contributor Author

Hmm, not sure if this is the only relevant change atm.

@puddly
Copy link
Collaborator

puddly commented Sep 17, 2022

Not waiting for DataConfirm.Callback I think will lead to memory errors. You can send more quickly by ignoring it, but you'll have no indication of receipt (since that's the command that relays it) and no flow control.

Setting the request concurrency to 9999 would have the same effect. If I remember correctly, Z-Stack's API reference notes that concurrency should be capped at one, hence the reason for Req/Rsp/Callback as opposed to just Req/Rsp.

@dumpfheimer
Copy link
Contributor Author

Do you know if the coordinator returns before or after transmitting the frame?
If it sends the frame before answering, aren't we effectively limited to a single request at a time? Does the max concurrent setting then even make a difference?

Memory could be an issue at some point but that might be managed by reserving memory for n messages. Zigpy coulf load and use that number as max concurrent.

@puddly
Copy link
Collaborator

puddly commented Sep 18, 2022

Do you know if the coordinator returns before or after transmitting the frame?

There are two responses:

  1. The Rsp: it's sent after the packet is enqueued or no route exists. This should happen almost instantly.
  2. The Callback: this is sent after the packet is sent (or ACK'ed, depending on the type).

If it sends the frame before answering, aren't we effectively limited to a single request at a time?

No. zigpy-znp has two concurrency limits:

  1. A global synchronous command lock (i.e. you must receive a Rsp before sending another Req). This is limited to a single request, as per the spec.
  2. A request concurrency limit, which enforces no more than 16 in-flight requests without a corresponding DataConfirm.Callbacks.

Does the max concurrent setting then even make a difference?

Of course. Try setting max_concurrent_requests: 1 and max_concurrent_requests: 9999 and see what happens to your network as you rapidly send requests.

@dumpfheimer
Copy link
Contributor Author

dumpfheimer commented Sep 18, 2022

Okay, thanks, so turning eg a light on will:

  1. Send a message to the controller.
  2. The controller will respond with success

Now the controller should send the France OTA and receive an ACK from the light

  1. The controller sends a callback message to zigpy

Are only 1 and 2 in the sync lock? Or is 3 blocking it?

@puddly
Copy link
Collaborator

puddly commented Sep 18, 2022

No, 3 is not blocking it. The device can send Callback messages at any point but the application can't send a second Req message without receiving a Rsp:

The Z-Tool program must send one message at a time and wait for either the expected response message to a timeout before sending the next message or resending the current message.

Just pretend Req/Rsp/Callback are called Req/Ack/Rsp in this case and I think it will make more sense.

@dumpfheimer
Copy link
Contributor Author

Okay nice, I am starting to understand the bigger picture.
Thanks for taking the time to explain 🙏

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.

2 participants