Skip to content

Commit 0236a63

Browse files
committed
Reduce diff size
1 parent da98c6a commit 0236a63

8 files changed

Lines changed: 582 additions & 1010 deletions

File tree

bellows/ezsp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def frame_received(self, data: bytes) -> None:
365365
try:
366366
self._protocol(data)
367367
except Exception:
368-
LOGGER.warning("Failed to parse frame. This is a bug!", exc_info=True)
368+
LOGGER.warning("Failed to parse frame, ignoring")
369369

370370
async def get_board_info(
371371
self,

bellows/ezsp/protocol.py

Lines changed: 50 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@
55
from asyncio import timeout as asyncio_timeout
66
import binascii
77
from collections.abc import AsyncGenerator, Callable, Iterable
8-
from dataclasses import dataclass
98
import functools
109
import logging
1110
import time
12-
from typing import TYPE_CHECKING, Any, Final
11+
from typing import TYPE_CHECKING, Any
1312

1413
from zigpy.datastructures import PriorityDynamicBoundedSemaphore
15-
from zigpy.event.event_base import EventBase
1614
import zigpy.state
17-
import zigpy.types
1815

1916
from bellows.config import CONF_EZSP_POLICIES
2017
from bellows.exception import InvalidCommandError
@@ -30,62 +27,13 @@
3027
MAX_COMMAND_CONCURRENCY = 1
3128

3229

33-
@dataclass(frozen=True, kw_only=True)
34-
class MessageSentEvent:
35-
event_type: Final[str] = "message_sent"
36-
37-
status: t.sl_Status
38-
message_type: t.EmberOutgoingMessageType
39-
destination: t.uint16_t
40-
aps_frame: t.EmberApsFrame
41-
message_tag: t.uint8_t
42-
message_contents: t.LVBytes
43-
44-
45-
@dataclass(frozen=True, kw_only=True)
46-
class PacketReceivedEvent:
47-
event_type: Final[str] = "packet_received"
48-
49-
packet: zigpy.types.ZigbeePacket
50-
51-
52-
@dataclass(frozen=True, kw_only=True)
53-
class TrustCenterJoinEvent:
54-
event_type: Final[str] = "trust_center_join"
55-
56-
nwk: t.EmberNodeId
57-
ieee: t.EUI64
58-
device_update_status: t.EmberDeviceUpdate
59-
decision: t.EmberJoinDecision
60-
parent_nwk: t.EmberNodeId
61-
62-
63-
@dataclass(frozen=True, kw_only=True)
64-
class RouteRecordEvent:
65-
event_type: Final[str] = "route_record"
66-
67-
nwk: t.EmberNodeId
68-
ieee: t.EUI64
69-
lqi: t.uint8_t
70-
rssi: t.int8s
71-
relays: t.LVList[t.EmberNodeId]
72-
73-
74-
@dataclass(frozen=True, kw_only=True)
75-
class IdConflictEvent:
76-
event_type: Final[str] = "id_conflict"
77-
78-
nwk: t.EmberNodeId
79-
80-
81-
class ProtocolHandler(EventBase, abc.ABC):
30+
class ProtocolHandler(abc.ABC):
8231
"""EZSP protocol specific handler."""
8332

8433
COMMANDS = {}
8534
VERSION = None
8635

8736
def __init__(self, cb_handler: Callable, gateway: Gateway) -> None:
88-
super().__init__()
8937
self._handle_callback = cb_handler
9038
self._awaiting = {}
9139
self._gw = gateway
@@ -231,6 +179,52 @@ def __call__(self, data: bytes) -> None:
231179
if data:
232180
LOGGER.debug("Frame contains trailing data: %s", data)
233181

182+
if (
183+
frame_name == "incomingMessageHandler"
184+
and result[1].options & t.EmberApsOption.APS_OPTION_FRAGMENT
185+
):
186+
# Extract received APS frame and sender
187+
aps_frame = result[1]
188+
sender = result[4]
189+
190+
# The fragment count and index are encoded in the groupId field
191+
fragment_count = (aps_frame.groupId >> 8) & 0xFF
192+
fragment_index = aps_frame.groupId & 0xFF
193+
194+
(
195+
complete,
196+
reassembled,
197+
frag_count,
198+
frag_index,
199+
) = self._fragment_manager.handle_incoming_fragment(
200+
sender_nwk=sender,
201+
aps_sequence=aps_frame.sequence,
202+
profile_id=aps_frame.profileId,
203+
cluster_id=aps_frame.clusterId,
204+
fragment_count=fragment_count,
205+
fragment_index=fragment_index,
206+
payload=result[7],
207+
)
208+
209+
ack_task = asyncio.create_task(
210+
self._send_fragment_ack(sender, aps_frame, frag_count, frag_index)
211+
) # APS Ack
212+
213+
self._fragment_ack_tasks.add(ack_task)
214+
ack_task.add_done_callback(lambda t: self._fragment_ack_tasks.discard(t))
215+
216+
if not complete:
217+
# Do not pass partial data up the stack
218+
LOGGER.debug("Fragment reassembly not complete. waiting for more data.")
219+
return
220+
221+
# Replace partial data with fully reassembled data
222+
result[7] = reassembled
223+
224+
LOGGER.debug(
225+
"Reassembled fragmented message. Proceeding with normal handling."
226+
)
227+
234228
if sequence in self._awaiting:
235229
expected_id, schema, future = self._awaiting.pop(sequence)
236230
try:
@@ -252,20 +246,8 @@ def __call__(self, data: bytes) -> None:
252246
sequence,
253247
self.COMMANDS_BY_ID.get(expected_id, [expected_id])[0],
254248
)
255-
256-
return
257-
258-
self.handle_parsed_callback(frame_name, result)
259-
260-
# Legacy callback system for CLI tools
261-
self._handle_callback(frame_name, result)
262-
263-
def handle_parsed_callback(self, frame_name: str, args: list[Any]) -> None:
264-
"""Dispatch a callback frame to the appropriate handler method."""
265-
handler = getattr(self, f"_handle_{frame_name}", None)
266-
267-
if handler is not None:
268-
handler(*args)
249+
else:
250+
self._handle_callback(frame_name, result)
269251

270252
async def _send_fragment_ack(
271253
self,
@@ -293,135 +275,6 @@ async def _send_fragment_ack(
293275
status = await self.sendReply(sender, ackFrame, b"")
294276
return status[0]
295277

296-
def _handle_incoming_message(
297-
self,
298-
message_type: t.EmberIncomingMessageType,
299-
aps_frame: t.EmberApsFrame,
300-
sender: zigpy.types.NWK,
301-
eui64: zigpy.types.EUI64 | None,
302-
binding_index: t.uint8_t,
303-
address_index: t.uint8_t,
304-
lqi: t.uint8_t,
305-
rssi: t.int8s,
306-
timestamp: t.uint32_t | None,
307-
message: t.LVBytes,
308-
) -> None:
309-
"""Handle incomingMessageHandler callback and maybe return a packet."""
310-
311-
if aps_frame.options & t.EmberApsOption.APS_OPTION_FRAGMENT:
312-
fragment_count = (aps_frame.groupId >> 8) & 0xFF
313-
fragment_index = aps_frame.groupId & 0xFF
314-
315-
(
316-
complete,
317-
reassembled,
318-
frag_count,
319-
frag_index,
320-
) = self._fragment_manager.handle_incoming_fragment(
321-
sender_nwk=sender,
322-
aps_sequence=aps_frame.sequence,
323-
profile_id=aps_frame.profileId,
324-
cluster_id=aps_frame.clusterId,
325-
fragment_count=fragment_count,
326-
fragment_index=fragment_index,
327-
payload=message,
328-
)
329-
330-
ack_task = asyncio.create_task(
331-
self._send_fragment_ack(sender, aps_frame, frag_count, frag_index)
332-
)
333-
self._fragment_ack_tasks.add(ack_task)
334-
ack_task.add_done_callback(lambda t: self._fragment_ack_tasks.discard(t))
335-
336-
if not complete:
337-
LOGGER.debug("Fragment reassembly not complete, waiting for more data")
338-
return
339-
340-
LOGGER.debug("Reassembled fragmented message, proceeding with handling")
341-
message = reassembled
342-
343-
# Determine destination address based on message type
344-
if message_type == t.EmberIncomingMessageType.INCOMING_BROADCAST:
345-
dst = zigpy.types.AddrModeAddress(
346-
addr_mode=zigpy.types.AddrMode.Broadcast,
347-
address=zigpy.types.BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR,
348-
)
349-
elif message_type == t.EmberIncomingMessageType.INCOMING_MULTICAST:
350-
dst = zigpy.types.AddrModeAddress(
351-
addr_mode=zigpy.types.AddrMode.Group,
352-
address=aps_frame.groupId,
353-
)
354-
elif message_type == t.EmberIncomingMessageType.INCOMING_UNICAST:
355-
dst = None # We don't know the current NWK here
356-
else:
357-
LOGGER.debug("Ignoring message type: %r", message_type)
358-
return
359-
360-
self.emit(
361-
PacketReceivedEvent.event_type,
362-
PacketReceivedEvent(
363-
packet=zigpy.types.ZigbeePacket(
364-
src=zigpy.types.AddrModeAddress(
365-
addr_mode=zigpy.types.AddrMode.NWK,
366-
address=zigpy.types.NWK(sender),
367-
),
368-
src_ep=aps_frame.sourceEndpoint,
369-
dst=dst,
370-
dst_ep=aps_frame.destinationEndpoint,
371-
tsn=aps_frame.sequence,
372-
profile_id=aps_frame.profileId,
373-
cluster_id=aps_frame.clusterId,
374-
data=zigpy.types.SerializableBytes(message),
375-
lqi=lqi,
376-
rssi=rssi,
377-
)
378-
),
379-
)
380-
381-
def _handle_trustCenterJoinHandler(
382-
self,
383-
nwk: t.EmberNodeId,
384-
ieee: t.EUI64,
385-
device_update_status: t.EmberDeviceUpdate,
386-
decision: t.EmberJoinDecision,
387-
parent_nwk: t.EmberNodeId,
388-
) -> None:
389-
self.emit(
390-
TrustCenterJoinEvent.event_type,
391-
TrustCenterJoinEvent(
392-
nwk=nwk,
393-
ieee=ieee,
394-
device_update_status=device_update_status,
395-
decision=decision,
396-
parent_nwk=parent_nwk,
397-
),
398-
)
399-
400-
def _handle_incomingRouteRecordHandler(
401-
self,
402-
nwk: t.EmberNodeId,
403-
ieee: t.EUI64,
404-
lqi: t.uint8_t,
405-
rssi: t.int8s,
406-
relays: t.LVList[t.EmberNodeId],
407-
) -> None:
408-
self.emit(
409-
RouteRecordEvent.event_type,
410-
RouteRecordEvent(
411-
nwk=nwk,
412-
ieee=ieee,
413-
lqi=lqi,
414-
rssi=rssi,
415-
relays=relays,
416-
),
417-
)
418-
419-
def _handle_idConflictHandler(self, nwk: t.EmberNodeId) -> None:
420-
self.emit(
421-
IdConflictEvent.event_type,
422-
IdConflictEvent(nwk=nwk),
423-
)
424-
425278
def __getattr__(self, name: str) -> Callable:
426279
if name not in self.COMMANDS:
427280
raise AttributeError(f"{name} not found in COMMANDS")

bellows/ezsp/v14/__init__.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,17 @@
22
from __future__ import annotations
33

44
from collections.abc import AsyncGenerator
5-
import logging
65

76
import voluptuous as vol
87
from zigpy.exceptions import NetworkNotFormed
98
import zigpy.state
10-
import zigpy.types
119

1210
import bellows.config
1311
import bellows.types as t
1412

1513
from . import commands, config
16-
from ..protocol import MessageSentEvent
1714
from ..v13 import EZSPv13
1815

19-
LOGGER = logging.getLogger(__name__)
20-
2116

2217
class EZSPv14(EZSPv13):
2318
"""EZSP Version 14 Protocol version handler."""
@@ -149,50 +144,3 @@ async def send_broadcast(
149144
)
150145

151146
return status, sequence
152-
153-
def _handle_incomingMessageHandler(
154-
self,
155-
message_type: t.EmberIncomingMessageType,
156-
aps_frame: t.EmberApsFrame,
157-
sender: t.EmberNodeId,
158-
eui64: t.EUI64,
159-
binding_index: t.uint8_t,
160-
address_index: t.uint8_t,
161-
lqi: t.uint8_t,
162-
rssi: t.int8s,
163-
timestamp: t.uint32_t,
164-
message: t.LVBytes,
165-
) -> None:
166-
self._handle_incoming_message(
167-
message_type=message_type,
168-
aps_frame=aps_frame,
169-
sender=sender,
170-
eui64=None,
171-
binding_index=binding_index,
172-
address_index=address_index,
173-
lqi=lqi,
174-
rssi=rssi,
175-
timestamp=None,
176-
message=message,
177-
)
178-
179-
def _handle_messageSentHandler(
180-
self,
181-
status: t.sl_Status,
182-
message_type: t.EmberOutgoingMessageType,
183-
destination: t.EmberNodeId,
184-
aps_frame: t.EmberApsFrame,
185-
message_tag: t.uint8_t,
186-
message: t.LVBytes,
187-
) -> None:
188-
self.emit(
189-
MessageSentEvent.event_type,
190-
MessageSentEvent(
191-
status=status,
192-
message_type=message_type,
193-
destination=destination,
194-
aps_frame=aps_frame,
195-
message_tag=message_tag,
196-
message_contents=message,
197-
),
198-
)

0 commit comments

Comments
 (0)