Skip to content
Open
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
25 changes: 25 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,37 @@ async def withhold(request: p.Request, request_id: int) -> None:
assert api.disconnects == [None]


async def test_startup_hello_ignored(
api: RecordingApi, transport: SyntheticBinaryTransport
) -> None:
async def start_then_reply(request: p.Request, request_id: int) -> None:
transport.notify(
p.NotificationCommand.HELLO,
0,
p.Hello(
protocol_version=t.uint8_t(p.PROTOCOL_VERSION),
configured=t.Bool(False),
),
)
transport.ok(request.command, request_id)

# Embedded firmware starts its stack on the first host frame and announces it
# with a `hello` before the reply: that opens the session, it is not a reboot
transport.handlers[p.RequestCommand.RESET] = start_then_reply
assert await api.request(p.Reset(hard=t.Bool(False))) is None

assert api.disconnects == []
assert api.notifications == []


async def test_hello_reported_as_disconnect(
api: RecordingApi, transport: SyntheticBinaryTransport
) -> None:
async def withhold(request: p.Request, request_id: int) -> None:
return None

assert await api.request(p.Shutdown()) is None

transport.handlers[p.RequestCommand.SHUTDOWN] = withhold
request = asyncio.ensure_future(api.request(p.Shutdown()))
await asyncio.sleep(0)
Expand Down
9 changes: 8 additions & 1 deletion zigpy_ziggurat/zigbee/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def __init__(
] = {}
self._awaiting_aps_ack: set[int] = set()
self._transport: Transport | None = None
self._received_frame = False

async def connect(self) -> None:
self._transport = await connect_transport(
Expand Down Expand Up @@ -201,6 +202,8 @@ def _handle_frame(self, frame: bytes) -> None:
_LOGGER.debug("Received notification (id=%d): %r", request_id, notification)
self._handle_notification(request_id, notification)

self._received_frame = True

def _handle_response(self, request_id: int, body: bytes) -> None:
pending = self._pending.get(request_id)
if pending is None or pending.response.done():
Expand Down Expand Up @@ -261,7 +264,11 @@ def _handle_notification(
if confirm is not None and not confirm.done():
confirm.set_result(notification)
elif isinstance(notification, p.Hello):
# The firmware only sends `hello` when it reboots
# Embedded firmware starts its stack on the first frame the host sends, and
# the stack announces itself before it answers. So a `hello` that opens the
# session is that start. Any later `hello` means the firmware rebooted.
if not self._received_frame:
return
Comment on lines +270 to +271
self._connection_lost(ConnectionError("Ziggurat firmware reset"))
elif isinstance(notification, p.LastReset):
logging.getLogger("ziggurat.fw").warning(
Expand Down
Loading