Skip to content
Closed
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
6 changes: 0 additions & 6 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def zdo_packet(cluster_id: int, data: bytes, src: t.NWK = DEVICE_NWK) -> t.Zigbe
src_ep=t.uint8_t(0),
dst=t.AddrModeAddress(addr_mode=t.AddrMode.NWK, address=t.NWK(0x0000)),
dst_ep=t.uint8_t(0),
tsn=t.uint8_t(data[0]),
profile_id=t.uint16_t(0x0000),
cluster_id=t.uint16_t(cluster_id),
data=t.SerializableBytes(data),
Expand All @@ -84,7 +83,6 @@ def zdo_packet(cluster_id: int, data: bytes, src: t.NWK = DEVICE_NWK) -> t.Zigbe
def aps_packet(
dst: t.AddrModeAddress,
*,
tsn: int = 33,
src_ep: int = 1,
dst_ep: int = 1,
tx_options: t.TransmitOptions = t.TransmitOptions.NONE,
Expand All @@ -95,7 +93,6 @@ def aps_packet(
src_ep=t.uint8_t(src_ep),
dst=dst,
dst_ep=t.uint8_t(dst_ep),
tsn=t.uint8_t(tsn),
profile_id=t.uint16_t(0x0104),
cluster_id=t.uint16_t(0x0006),
data=t.SerializableBytes(data),
Expand Down Expand Up @@ -668,7 +665,6 @@ async def test_send_packet_unicast(
assert not send.aps_encryption
assert not send.sleepy_destination
assert send.profile_id == 0x0104
assert send.aps_seq == 33
assert send.radius == 30
assert send.priority == 0
assert bytes(send.asdu) == b"\x01\x02\x03"
Expand Down Expand Up @@ -750,7 +746,6 @@ async def test_send_packet_broadcast(
send = server.sent(p.SendBroadcast)[-1]
assert send.destination == t.NWK(0xFFFC)
assert send.dst_ep == 255
assert send.aps_seq == 33
assert bytes(send.asdu) == b"\x01\x02\x03"


Expand All @@ -766,7 +761,6 @@ async def test_send_packet_groupcast(

send = server.sent(p.SendGroupcast)[-1]
assert send.group_id == 0x0002
assert send.aps_seq == 33
assert bytes(send.asdu) == b"\x01\x02\x03"


Expand Down
5 changes: 0 additions & 5 deletions tests/test_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,6 @@ async def test_legacy_send_packet(
src_ep=t.uint8_t(1),
dst=dst,
dst_ep=t.uint8_t(1),
tsn=t.uint8_t(33),
profile_id=t.uint16_t(0x0104),
cluster_id=t.uint16_t(0x0006),
data=t.SerializableBytes(b"\x01\x02\x03"),
Expand All @@ -587,7 +586,6 @@ async def test_legacy_send_packet(

request = legacy_server.sent(commands.SendAps)[-1]
assert request.data == b"\x01\x02\x03"
assert request.aps_seq == 33
assert request.radius == 30
for field, value in expected.items():
assert getattr(request, field) == value
Expand All @@ -609,7 +607,6 @@ async def fail(command: commands.SendAps, request_id: int) -> commands.Status:
src_ep=t.uint8_t(1),
dst=t.AddrModeAddress(addr_mode=t.AddrMode.NWK, address=DEVICE_NWK),
dst_ep=t.uint8_t(1),
tsn=t.uint8_t(34),
profile_id=t.uint16_t(0x0104),
cluster_id=t.uint16_t(0x0006),
data=t.SerializableBytes(b"\x04"),
Expand Down Expand Up @@ -638,7 +635,6 @@ async def fail_confirm(
src_ep=t.uint8_t(1),
dst=t.AddrModeAddress(addr_mode=t.AddrMode.NWK, address=DEVICE_NWK),
dst_ep=t.uint8_t(1),
tsn=t.uint8_t(35),
profile_id=t.uint16_t(0x0104),
cluster_id=t.uint16_t(0x0006),
data=t.SerializableBytes(b"\x05"),
Expand All @@ -663,7 +659,6 @@ async def fail_ack(command: commands.SendAps, request_id: int) -> commands.Statu
src_ep=t.uint8_t(1),
dst=t.AddrModeAddress(addr_mode=t.AddrMode.NWK, address=DEVICE_NWK),
dst_ep=t.uint8_t(1),
tsn=t.uint8_t(36),
profile_id=t.uint16_t(0x0104),
cluster_id=t.uint16_t(0x0006),
data=t.SerializableBytes(b"\x06"),
Expand Down
19 changes: 16 additions & 3 deletions zigpy_ziggurat/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import math
import os
import random
import statistics
from typing import Any, cast

Expand Down Expand Up @@ -107,6 +108,10 @@ def __init__(self, config: dict[str, Any]) -> None:
self._api: ZigguratApi | None = None
self._start_time: datetime | None = None

# Randomized so a reconnect does not replay counters a device still remembers
# for duplicate rejection, which it would ack but not deliver
self._aps_counter = random.randint(0x00, 0xFF)

async def connect(self) -> None:
# The device path is either the WebSocket URL of a ziggurat server or the
# serial port of a ziggurat firmware (e.g. an ESP32-C6 over USB-Serial-JTAG).
Expand Down Expand Up @@ -798,6 +803,11 @@ def _handle_received_aps_command(self, command: p.ReceivedAps) -> None:
)
self.packet_received(packet)

def _next_aps_counter(self) -> int:
"""Allocate the APS counter for the next outgoing frame."""
self._aps_counter = (self._aps_counter + 1) % 256
return self._aps_counter

async def send_packet(self, packet: t.ZigbeePacket) -> None:
dst = packet.dst
assert dst is not None and dst.address is not None
Expand Down Expand Up @@ -834,14 +844,17 @@ async def send_packet(self, packet: t.ZigbeePacket) -> None:

send: p.SendUnicast | p.SendBroadcast | p.SendGroupcast
async with self._limit_concurrency(priority=packet.priority):
# Allocated inside the semaphore so counters advance in send order
aps_seq = self._next_aps_counter()

if dst.addr_mode == t.AddrMode.Group:
assert destination is not None
send = p.SendGroupcast.build(
group_id=int(destination),
profile_id=packet.profile_id,
cluster_id=packet.cluster_id or 0x0000,
src_ep=packet.src_ep or 0,
aps_seq=packet.tsn,
aps_seq=aps_seq,
radius=radius,
priority=priority,
asdu=asdu,
Expand All @@ -854,7 +867,7 @@ async def send_packet(self, packet: t.ZigbeePacket) -> None:
cluster_id=packet.cluster_id or 0x0000,
src_ep=packet.src_ep or 0,
dst_ep=packet.dst_ep or 0,
aps_seq=packet.tsn,
aps_seq=aps_seq,
radius=radius,
priority=priority,
asdu=asdu,
Expand Down Expand Up @@ -899,7 +912,7 @@ async def send_packet(self, packet: t.ZigbeePacket) -> None:
cluster_id=packet.cluster_id or 0x0000,
src_ep=packet.src_ep or 0,
dst_ep=packet.dst_ep or 0,
aps_seq=packet.tsn,
aps_seq=aps_seq,
radius=radius,
priority=priority,
route_control=route_control,
Expand Down