Skip to content

Commit

Permalink
Source routing fixes (#626)
Browse files Browse the repository at this point in the history
* Enable address discovery when source routing is in use

* Only use `setSourceRoute` < EZSPv8

* Add back missing branch to guard against missing source route

* Implement `set_source_route` on the EZSP class instead of the application

* Add unit tests

* Fix unit tests

* Move `set_source_route` stub to EZSPv9, not v8
  • Loading branch information
puddly authored Jul 23, 2024
1 parent 8eb257d commit 93c671b
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 22 deletions.
4 changes: 4 additions & 0 deletions bellows/ezsp/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,7 @@ async def send_broadcast(
data: bytes,
) -> tuple[t.sl_Status, t.uint8_t]:
raise NotImplementedError

@abc.abstractmethod
async def set_source_route(self, nwk: t.NWK, relays: list[t.NWK]) -> t.sl_Status:
raise NotImplementedError
4 changes: 4 additions & 0 deletions bellows/ezsp/v4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,7 @@ async def send_broadcast(
)

return t.sl_Status.from_ember_status(status), sequence

async def set_source_route(self, nwk: t.NWK, relays: list[t.NWK]) -> t.sl_Status:
(res,) = await self.setSourceRoute(destination=nwk, relayList=relays)
return t.sl_Status.from_ember_status(res)
4 changes: 4 additions & 0 deletions bellows/ezsp/v9/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ async def write_child_data(self, children: dict[t.EUI64, t.NWK]) -> None:
timeout=0,
),
)

async def set_source_route(self, nwk: t.NWK, relays: list[t.NWK]) -> t.sl_Status:
# While the command may succeed, it does absolutely nothing
return t.sl_Status.OK
21 changes: 7 additions & 14 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,12 +671,6 @@ async def _reset_mfg_id(self, mfg_id: int) -> None:
await asyncio.sleep(MFG_ID_RESET_DELAY)
await self._ezsp.setManufacturerCode(code=DEFAULT_MFG_ID)

async def _set_source_route(
self, nwk: zigpy.types.NWK, relays: list[zigpy.types.NWK]
) -> bool:
(res,) = await self._ezsp.setSourceRoute(destination=nwk, relayList=relays)
return t.sl_Status.from_ember_status(res) == t.sl_Status.OK

async def energy_scan(
self, channels: t.Channels, duration_exp: int, count: int
) -> dict[int, float]:
Expand Down Expand Up @@ -743,6 +737,9 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:

if not self.config[zigpy.config.CONF_SOURCE_ROUTING]:
aps_frame.options |= t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
else:
# Source routing uses address discovery to discover routes
aps_frame.options |= t.EmberApsOption.APS_OPTION_ENABLE_ADDRESS_DISCOVERY

async with self._limit_concurrency():
message_tag = self.get_sequence()
Expand All @@ -756,14 +753,10 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
remoteEui64=device.ieee, extendedTimeout=True
)

if (
packet.source_route is not None
and not await self._set_source_route(
packet.dst.address, packet.source_route
)
):
aps_frame.options |= (
t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
if packet.source_route is not None:
await self._ezsp.set_source_route(
nwk=packet.dst.address,
relays=packet.source_route,
)

status, _ = await self._ezsp.send_unicast(
Expand Down
19 changes: 11 additions & 8 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,20 +831,23 @@ async def test_send_packet_unicast_ieee_no_fallback(app, packet, caplog):
assert app._ezsp.send_unicast.call_count == 0


async def test_send_packet_unicast_source_route_ezsp7(make_app, packet):
async def test_send_packet_unicast_source_route(make_app, packet):
app = make_app({zigpy.config.CONF_SOURCE_ROUTING: True})
app._ezsp.ezsp_version = 7
app._ezsp.setSourceRoute = AsyncMock(return_value=(t.EmberStatus.SUCCESS,))
app._ezsp.set_source_route = AsyncMock(return_value=(t.sl_Status.OK,))

packet.source_route = [0x0001, 0x0002]
await _test_send_packet_unicast(
app, packet, options=(t.EmberApsOption.APS_OPTION_RETRY)
app,
packet,
options=(
t.EmberApsOption.APS_OPTION_RETRY
| t.EmberApsOption.APS_OPTION_ENABLE_ADDRESS_DISCOVERY
),
)

assert app._ezsp.setSourceRoute.await_count == 1
app._ezsp.setSourceRoute.assert_called_once_with(
destination=packet.dst.address,
relayList=[0x0001, 0x0002],
app._ezsp.set_source_route.assert_called_once_with(
nwk=packet.dst.address,
relays=[0x0001, 0x0002],
)


Expand Down
11 changes: 11 additions & 0 deletions tests/test_ezsp_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,14 @@ async def test_send_broadcast(ezsp_f) -> None:
messageContents=b"hello",
)
]


async def test_source_route(ezsp_f) -> None:
ezsp_f.setSourceRoute = AsyncMock(return_value=(t.EmberStatus.SUCCESS,))

status = await ezsp_f.set_source_route(nwk=0x1234, relays=[0x5678, 0xABCD])
assert status == t.sl_Status.OK

assert ezsp_f.setSourceRoute.mock_calls == [
call(destination=0x1234, relayList=[0x5678, 0xABCD])
]
10 changes: 10 additions & 0 deletions tests/test_ezsp_v9.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,13 @@ async def test_write_child_data(ezsp_f) -> None:
),
),
]


async def test_source_route(ezsp_f) -> None:
ezsp_f.setSourceRoute = AsyncMock(return_value=(t.EmberStatus.SUCCESS,))

status = await ezsp_f.set_source_route(nwk=0x1234, relays=[0x5678, 0xABCD])
assert status == t.sl_Status.OK

# Nothing happens
assert ezsp_f.setSourceRoute.mock_calls == []

0 comments on commit 93c671b

Please sign in to comment.