Skip to content

Commit

Permalink
Support setting source routes as well
Browse files Browse the repository at this point in the history
  • Loading branch information
puddly committed May 28, 2024
1 parent 292ca31 commit 5aa260d
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 26 deletions.
27 changes: 19 additions & 8 deletions bellows/ezsp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,12 +638,12 @@ async def write_config(self, config: dict) -> None:
)
continue

async def get_supported_firmware_features(
async def xncp_get_supported_firmware_features(
self,
) -> custom_commands.FirmwareFeatures:
"""Get supported firmware extensions."""
req = custom_commands.CustomCommand(
command_id=custom_commands.CustomCommandId.CMD_GET_SUPPORTED_FEATURES_REQ,
command_id=custom_commands.CustomCommandId.CMD_GET_SUPPORTED_FEATURES,
payload=custom_commands.GetSupportedFeaturesReq().serialize(),
)

Expand All @@ -655,11 +655,22 @@ async def get_supported_firmware_features(
if not data:
return custom_commands.FirmwareFeatures.NONE

rsp_cmd, _ = custom_commands.CustomCommand.deserialize(data)
assert (
rsp_cmd.command_id
== custom_commands.CustomCommandId.CMD_GET_SUPPORTED_FEATURES_RSP
rsp, _ = custom_commands.GetSupportedFeaturesRsp.deserialize(data)
return rsp.features

async def xncp_set_manual_source_route(
self, destination: t.NWK, route: list[t.NWK]
) -> None:
"""Set a manual source route."""
req = custom_commands.CustomCommand(
command_id=custom_commands.CustomCommandId.CMD_SET_SOURCE_ROUTE,
payload=custom_commands.SetSourceRouteReq(
destination=destination, source_route=route
).serialize(),
)

rsp, _ = custom_commands.GetSupportedFeaturesRsp.deserialize(rsp_cmd.payload)
return rsp.features
status, data = await self.customFrame(req.serialize())
if status != self.types.EmberStatus.SUCCESS:
raise EzspError(f"Failed to set source route: {status}")

return None
19 changes: 14 additions & 5 deletions bellows/ezsp/custom_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ def __repr__(self) -> str:


class CustomCommandId(t.enum16):
CMD_GET_PROTOCOL_VERSION_REQ = 0x0000
CMD_GET_PROTOCOL_VERSION_RSP = 0x8000

CMD_GET_SUPPORTED_FEATURES_REQ = 0x0001
CMD_GET_SUPPORTED_FEATURES_RSP = 0x8001
CMD_GET_SUPPORTED_FEATURES = 0x0000
CMD_SET_SOURCE_ROUTE = 0x0001


class CustomCommand(t.Struct):
Expand All @@ -41,10 +38,22 @@ class FirmwareFeatures(t.bitmap32):
# The firmware passes through all group traffic, regardless of group membership
MEMBER_OF_ALL_GROUPS = 1 << 0

# Source routes can be overridden by the application
MANUAL_SOURCE_ROUTE = 1 << 1


class GetSupportedFeaturesReq(t.Struct):
pass


class GetSupportedFeaturesRsp(t.Struct):
features: FirmwareFeatures


class SetSourceRouteReq(t.Struct):
destination: t.NWK
source_route: t.List[t.NWK]


class SetSourceRouteRsp(t.Struct):
pass
26 changes: 13 additions & 13 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(self, config: dict):
self._pending = zigpy.util.Requests()
self._watchdog_failures = 0
self._watchdog_feed_counter = 0
self._custom_features = FirmwareFeatures.NONE

self._req_lock = asyncio.Lock()

Expand Down Expand Up @@ -205,12 +206,12 @@ async def start_network(self):
ezsp.add_callback(self.ezsp_callback_handler)
self.controller_event.set()

custom_features = await self._ezsp.get_supported_firmware_features()
LOGGER.debug("Supported custom firmware features: %r", custom_features)
self._custom_features = await self._ezsp.xncp_get_supported_firmware_features()
LOGGER.debug("Supported custom firmware features: %r", self._custom_features)

group_membership = {}

if FirmwareFeatures.MEMBER_OF_ALL_GROUPS in custom_features:
if FirmwareFeatures.MEMBER_OF_ALL_GROUPS in self._custom_features:
# If the firmware passes through all incoming group messages, do nothing
endpoint_cls = EZSPEndpoint
else:
Expand Down Expand Up @@ -241,7 +242,7 @@ async def start_network(self):

await ezsp_device.schedule_initialize()

if FirmwareFeatures.MEMBER_OF_ALL_GROUPS not in custom_features:
if FirmwareFeatures.MEMBER_OF_ALL_GROUPS not in self._custom_features:
ezsp_device.endpoints[1].member_of.update(group_membership)
self._multicast = bellows.multicast.Multicast(ezsp)
await self._multicast.startup(ezsp_device)
Expand Down Expand Up @@ -784,8 +785,12 @@ async def _reset_mfg_id(self, mfg_id: int) -> None:
async def _set_source_route(
self, nwk: zigpy.types.NWK, relays: list[zigpy.types.NWK]
) -> bool:
(res,) = await self._ezsp.setSourceRoute(nwk, relays)
return res == t.EmberStatus.SUCCESS
if FirmwareFeatures.MANUAL_SOURCE_ROUTE in self._custom_features:
await self._ezsp.xncp_set_manual_source_route(nwk, relays)
return True
else:
(res,) = await self._ezsp.setSourceRoute(nwk, relays)
return res == t.EmberStatus.SUCCESS

async def energy_scan(
self, channels: t.Channels, duration_exp: int, count: int
Expand Down Expand Up @@ -864,15 +869,10 @@ async def send_packet(self, packet: zigpy.types.ZigbeePacket) -> None:
if packet.extended_timeout and device is not None:
await self._ezsp.setExtendedTimeout(device.ieee, True)

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

status, _ = await self._ezsp.sendUnicast(
t.EmberOutgoingMessageType.OUTGOING_DIRECT,
Expand Down

0 comments on commit 5aa260d

Please sign in to comment.