Yoda-x/ha-zha-new

possible bug in bellows/send_zdo_broadcast()

Closed this issue · 6 comments

I think await self._ezsp.sendBroadcast(0xfffd, aps_frame, radius, len(data), data) is being called with incorrect parameters.
I believe a correct way of calling it would be:
await self._ezsp.sendBroadcast(0xfffd, aps_frame, radius, aps_frame.sequence, data
and here's my reasoning. Per EZSP reference guide, sendBroadcast needs the following arguments:

Type Parameter
EmberNodeID Broadcast Address
EmberApsFrame aps frame
uint8_t radius
uint8_t messageTag -- Sequence number
uint8_t message length -- len(data)
uint8_t[] message content

which is defined in bellows/commands.py as

'sendBroadcast': (0x36, (t.EmberNodeId, t.EmberApsFrame, t.uint8_t, t.uint8_t, t.LVBytes), (t.EmberStatus, t.uint8_t)),

the actual last parameter is of t.LVBytes type, which is represents [len(data), data], therefore you don't need to provide len(data), but you do need to provide the messageTag/sequence number, so the right way of calling it should be
await self._ezsp.sendBroadcast(0xfffd, aps_frame, radius, aps_frame.sequence, data

Thoughts?

optionally we could push some asyncio.Future() into self._pending, so EZSP does not complain on an "unexpected send". something like:

...
        send_fut = asyncio.Future()
        self._pending[sequence] = (send_fut, None)

        v = await self._ezsp.sendBroadcast(0xfffd, aps_frame, radius,
                                           sequence, data)
        if v[0] != t.EmberStatus.SUCCESS:
            self._pending.pop(sequence)
            send_fut.cancel()
            raise DeliveryError("Message send failure {}".format((v[0], )))

        # Wait for messageSentHandler message
        v = await send_fut
        return v

similar to request() method.

the sequence number is included in the data
data = aps_frame.sequence.to_bytes(1, 'little')
schema = zigpy.zdo.types.CLUSTERS[command][2]
data += t.serialize(args, schema)

as it is part of the payload
the ug100 document is a little bit unclear, here is a better description for the permit join request.

why do you think it's buggy? do you have a problem with joining?

Can you check the link? I found a few KB articles from 2012/07/02 but can't figure out which one are you referencing.
So upon second look on my original message I do see some error, but what I actually meant that the order of the parameters should be different, ie _ezsp.sendBroadcast expects the following order of the parameters

Type Parameter implemented
EmberNodeID Broadcast Address correct
EmberApsFrame aps frame correct
uint8_t radius correct
uint8_t messageTag -- Sequence number incorrect, swapped with length
uint8_t message length -- len(data) incorrect -- swapped with seq # and incorrectly accounts for the length of seq #
uint8_t[] message content correct

however you are giving it: _ezsp.sendBroadcast(broadcastaddress, aps_frame, radius, len(data), data) where data is [sequence_number, permit_join_time, tc_significance]
in other words in your variant the sequence number is after the data length, but it should be before.
something like

        seq = aps_frame.sequence.to_bytes(1, 'little')
        schema = zigpy.zdo.types.CLUSTERS[command][2]
        data = t.serialize(args, schema)
        LOGGER.debug("zdo-broadcast: %s - %s", aps_frame, data)
        await self._ezsp.sendBroadcast(0xfffd, aps_frame, radius, seq, len(data), data)

and I still trying to figure out whether len(data) should be there at all. I'm just comparing _ezsp.sendBroadacast() to _ezsp.sendUnicast() which has very similar arrangement of arguments
image
yet bellows.zigbee.application.request() which leverage _ezsp.sendUnicast() does not use len(data) unless I'm missing it somewhere 😕

so my current thinking, you don't need to provide len(data) to _ezsp.sendBroadcast(), because schema for sendBroadcast command is defined as:
'sendBroadcast': (0x36, (t.EmberNodeId, t.EmberApsFrame, t.uint8_t, t.uint8_t, t.LVBytes), (t.EmberStatus, t.uint8_t)),

so when serialize method is going to be called for t.LVBytes it is going to prefix the length automatically, so I currently believe that we need to call it like:

        seq = aps_frame.sequence.to_bytes(1, 'little')
        schema = zigpy.zdo.types.CLUSTERS[command][2]
        data = t.serialize(args, schema)
        LOGGER.debug("zdo-broadcast: %s - %s", aps_frame, data)
        await self._ezsp.sendBroadcast(0xfffd, aps_frame, radius, seq, data)

hi, the current version works for me over 4 floors. Please test your recommendation and see how it works.
if your version is ok, please create a pull request
Thanks

can't say your variant doesn't work. Both your and mine variants worked. fine. I've been running my version for a while now without any issues.
I'll create a pull request for a review.