Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bellows/ezsp/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ async def write_child_data(self, children: dict[t.EUI64, t.NWK]) -> None:
async def initialize_network(self) -> t.sl_Status:
raise NotImplementedError

@abc.abstractmethod
async def clear_gp_tables(self) -> None:
"""Clear the Green Power proxy and sink tables of the NCP, where it has them."""
raise NotImplementedError

@abc.abstractmethod
async def factory_reset(self) -> None:
raise NotImplementedError
Expand Down Expand Up @@ -363,7 +368,10 @@ async def send_multicast(
non_member_radius: t.uint8_t,
message_tag: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
"""Send a multicast, as `alias` with the NWK sequence `alias_seq` if given."""
raise NotImplementedError

@abc.abstractmethod
Expand All @@ -375,7 +383,10 @@ async def send_broadcast(
message_tag: t.uint8_t,
aps_sequence: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
"""Send a broadcast, as `alias` with the NWK sequence `alias_seq` if given."""
raise NotImplementedError

@abc.abstractmethod
Expand Down
14 changes: 10 additions & 4 deletions bellows/ezsp/v14/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,16 @@ async def send_multicast(
non_member_radius: t.uint8_t,
message_tag: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
status, sequence = await self.sendMulticast(
aps_frame=aps_frame,
hops=radius,
broadcast_addr=t.BroadcastAddress.RX_ON_WHEN_IDLE,
alias=NULL_NODE_ID,
sequence=0x00, # NWK sequence, only used with an alias
alias=NULL_NODE_ID if alias is None else alias,
# NWK sequence, only used with an alias
sequence=0x00 if alias_seq is None else alias_seq,
message_tag=message_tag,
message=data,
)
Expand All @@ -143,13 +146,16 @@ async def send_broadcast(
message_tag: t.uint8_t,
aps_sequence: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
# `aps_sequence` is not used

status, sequence = await self.sendBroadcast(
alias=NULL_NODE_ID,
alias=NULL_NODE_ID if alias is None else alias,
destination=address,
sequence=0x00, # NWK sequence, only used with an alias
# NWK sequence, only used with an alias
sequence=0x00 if alias_seq is None else alias_seq,
aps_frame=aps_frame,
radius=radius,
message_tag=message_tag,
Expand Down
21 changes: 21 additions & 0 deletions bellows/ezsp/v14/commands.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from zigpy.types import EUI64, NWK, BroadcastAddress, Struct, StructField
import zigpy.zgp.types as zgp_t

import bellows.types as t

Expand Down Expand Up @@ -377,6 +378,26 @@ class GetTokenDataRsp(Struct):
"status": t.EmberStatus,
},
),
"gpepIncomingMessageHandler": (
0x00C5,
{},
{
# The GP status codes migrate to their own enum, not to `sl_Status`
"status": t.sl_GpStatus,
"gpdLink": zgp_t.GPPGPDLink,
"sequenceNumber": t.uint8_t,
"addr": t.EmberGpAddress,
"gpdfSecurityLevel": t.EmberGpSecurityLevel,
"gpdfSecurityKeyType": t.EmberGpKeyType,
"autoCommissioning": t.Bool,
"bidirectionalInfo": t.EmberGpBidirectionalInfo,
"gpdSecurityFrameCounter": t.uint32_t,
"gpdCommandId": zgp_t.GPDCommandID,
"mic": t.uint32_t,
"proxyTableIndex": t.uint8_t,
"gpdCommandPayload": t.LVBytes,
},
),
# Gained an `options` argument, which the NCP reads even if we don't send it
"leaveNetwork": (
0x0020,
Expand Down
12 changes: 12 additions & 0 deletions bellows/ezsp/v17/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
""""EZSP Protocol version 17 protocol handler."""
from __future__ import annotations

import logging

import voluptuous as vol

import bellows.config
from bellows.exception import InvalidCommandError

from . import commands, config
from ..v16 import EZSPv16

LOGGER = logging.getLogger(__name__)


class EZSPv17(EZSPv16):
"""EZSP Version 17 Protocol version handler."""
Expand All @@ -18,3 +23,10 @@ class EZSPv17(EZSPv16):
bellows.config.CONF_EZSP_CONFIG: vol.Schema(config.EZSP_SCHEMA),
bellows.config.CONF_EZSP_POLICIES: vol.Schema(config.EZSP_POLICIES_SCH),
}

async def clear_gp_tables(self) -> None:
try:
await self.gpSinkTableClearAll()
await self.gpClearProxyTable()
except InvalidCommandError:
LOGGER.debug("The NCP has no Green Power tables")
11 changes: 8 additions & 3 deletions bellows/ezsp/v17/commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import zigpy.zgp.types as zgp_t

import bellows.types as t

from ..v16.commands import COMMANDS as COMMANDS_v16
Expand Down Expand Up @@ -29,19 +31,22 @@
{},
{
"status": t.sl_GpStatus,
"gpdLink": t.uint8_t,
"gpdLink": zgp_t.GPPGPDLink,
"sequenceNumber": t.uint8_t,
"addr": t.EmberGpAddress,
"gpdfSecurityLevel": t.EmberGpSecurityLevel,
"gpdfSecurityKeyType": t.EmberGpKeyType,
"autoCommissioning": t.Bool,
"bidirectionalInfo": t.uint8_t,
"bidirectionalInfo": t.EmberGpBidirectionalInfo,
"gpdSecurityFrameCounter": t.uint32_t,
"gpdCommandId": t.uint8_t,
"mic": t.uint32_t,
"proxyTableIndex": t.uint8_t,
"gpdCommandPayload": t.LVBytes,
"packetInfo": t.SlRxPacketInfo,
# XXX: `packetInfo` is the final field according to the protocol YAML
# definitions and all SDK code but it is never actually appended by the
# firmware.
# "packetInfo": t.SlRxPacketInfo,
},
),
}
25 changes: 25 additions & 0 deletions bellows/ezsp/v4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ async def initialize_network(self) -> t.sl_Status:
)
return t.sl_Status.from_ember_status(init_status)

async def clear_gp_tables(self) -> None:
Comment thread
puddly marked this conversation as resolved.
Resolved
# No Green Power table clearing before EZSPv6
pass
Comment thread
puddly marked this conversation as resolved.
Resolved

async def factory_reset(self) -> None:
await self.clearKeyTable()

Expand Down Expand Up @@ -163,7 +167,13 @@ async def send_multicast(
non_member_radius: t.uint8_t,
message_tag: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
if alias is not None:
# TODO: `sendMulticastWithAlias`, from EZSPv6
raise NotImplementedError("Aliased multicasts are not supported")

status, sequence = await self.sendMulticast(
apsFrame=aps_frame,
hops=radius,
Expand All @@ -182,9 +192,24 @@ async def send_broadcast(
message_tag: t.uint8_t,
aps_sequence: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
# `aps_sequence` is not used

if alias is not None:
status, sequence = await self.proxyBroadcast(
source=t.EmberNodeId(alias),
destination=address,
nwkSequence=t.uint8_t(0 if alias_seq is None else alias_seq),
apsFrame=aps_frame,
radius=radius,
messageTag=message_tag,
messageContents=data,
)

return t.sl_Status.from_ember_status(status), sequence

status, sequence = await self.sendBroadcast(
destination=address,
apsFrame=aps_frame,
Expand Down
16 changes: 8 additions & 8 deletions bellows/ezsp/v4/commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import zigpy.zgp.types as zgp_t

import bellows.types as t

COMMANDS = {
Expand Down Expand Up @@ -1991,20 +1993,18 @@
0x00C5,
{},
{
# The NCP sends the legacy GP status codes, `EmberStatus` 0x78-0x7F, and
# `SUCCESS` for a GPDF it authenticated itself
"status": t.EmberStatus,
"gpdLink": t.uint8_t,
"gpdLink": zgp_t.GPPGPDLink,
"sequenceNumber": t.uint8_t,
"addrType": t.uint8_t,
"addr": t.uint32_t,
"applicationId": t.uint8_t,
"address": t.EUI64, # TODO: `sourceId` if appId == 0 else `gpdIeeeAddress`
"endpoint": t.uint8_t,
"addr": t.EmberGpAddress,
"gpdfSecurityLevel": t.EmberGpSecurityLevel,
"gpdfSecurityKeyType": t.EmberGpKeyType,
"autoCommissioning": t.Bool,
"bidirectionalInfo": t.Bool,
"bidirectionalInfo": t.EmberGpBidirectionalInfo,
"gpdSecurityFrameCounter": t.uint32_t,
"gpdCommandId": t.uint8_t,
"gpdCommandId": zgp_t.GPDCommandID,
"mic": t.uint32_t,
"proxyTableIndex": t.uint8_t,
"gpdCommandPayload": t.LVBytes,
Expand Down
39 changes: 39 additions & 0 deletions bellows/ezsp/v6/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import voluptuous

import bellows.config
from bellows.exception import InvalidCommandError
import bellows.types as t

from . import commands, config
Expand All @@ -27,3 +28,41 @@ async def initialize_network(self) -> t.sl_Status:
networkInitBitmask=t.EmberNetworkInitBitmask.NETWORK_INIT_NO_OPTIONS
)
return t.sl_Status.from_ember_status(init_status)

async def clear_gp_tables(self) -> None:
# The proxy table has no bulk clear before EZSPv17
try:
await self.gpClearSinkTable()
except InvalidCommandError:
LOGGER.debug("The NCP has no Green Power sink table")

async def send_multicast(
self,
aps_frame: t.EmberApsFrame,
radius: t.uint8_t,
non_member_radius: t.uint8_t,
message_tag: t.uint8_t,
data: bytes,
alias: t.NWK | None = None,
alias_seq: t.uint8_t | None = None,
) -> tuple[t.sl_Status, t.uint8_t]:
if alias is None:
return await super().send_multicast(
aps_frame=aps_frame,
radius=radius,
non_member_radius=non_member_radius,
message_tag=message_tag,
data=data,
)

status, sequence = await self.sendMulticastWithAlias(
apsFrame=aps_frame,
hops=radius,
nonmemberRadius=non_member_radius,
alias=t.EmberNodeId(alias),
nwkSequence=t.uint8_t(0 if alias_seq is None else alias_seq),
messageTag=message_tag,
messageContents=data,
)

return t.sl_Status.from_ember_status(status), sequence
8 changes: 4 additions & 4 deletions bellows/ezsp/v6/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,12 @@
"sendMulticastWithAlias": (
0x3A,
{
"hops": t.EmberApsFrame,
"apsFrame": t.EmberApsFrame,
"hops": t.uint8_t,
"nonmemberRadius": t.uint8_t,
"alias": t.uint8_t,
"nwkSequence": t.uint16_t,
"alias": t.EmberNodeId,
"nwkSequence": t.uint8_t,
"messageTag": t.uint8_t,
"messageLength": t.uint8_t,
"messageContents": t.LVBytes,
},
{
Expand Down
8 changes: 8 additions & 0 deletions bellows/ezsp/v7/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import voluptuous

import bellows.config
from bellows.exception import InvalidCommandError
import bellows.types as t

from . import commands, config
Expand All @@ -25,6 +26,13 @@ class EZSPv7(EZSPv6):
bellows.config.CONF_EZSP_POLICIES: voluptuous.Schema(config.EZSP_POLICIES_SCH),
}

async def clear_gp_tables(self) -> None:
# `gpClearSinkTable` is renamed in EZSPv7
try:
await self.gpSinkTableClearAll()
except InvalidCommandError:
LOGGER.debug("The NCP has no Green Power sink table")

async def read_child_data(
self,
) -> AsyncGenerator[tuple[t.NWK, t.EUI64, t.EmberNodeType]]:
Expand Down
10 changes: 10 additions & 0 deletions bellows/types/named.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ class EmberGpKeyType(basic.uint8_t):
pass


class EmberGpBidirectionalInfo(basic.bitmap8):
# Bidirectional information of a received GPDF.

# The incoming GPDF has its rxAfterTx bit set: the GPD briefly opens its receiver
# after the frame and can be sent a GPDF in response.
RX_AFTER_TX = 0x01
# The transmit queue of the green power stub is able to hold an outgoing GPDF.
TX_QUEUE_AVAILABLE = 0x02


class Bool(basic.enum8):
# Boolean type with values true and false.

Expand Down
Loading
Loading