88
99import serial
1010from zigpy .config import CONF_DEVICE_PATH , SCHEMA_DEVICE
11+ from zigpy .datastructures import PriorityLock
1112from zigpy .exceptions import APIException , DeliveryError
1213import zigpy .types as t
1314
@@ -289,7 +290,7 @@ def __init__(self, device_config: Dict[str, Any]) -> None:
289290 self ._cmd_mode_future : Optional [asyncio .Future ] = None
290291 self ._reset : asyncio .Event = asyncio .Event ()
291292 self ._running : asyncio .Event = asyncio .Event ()
292- self ._send_lock = asyncio . Lock ()
293+ self ._send_lock = PriorityLock ()
293294
294295 @property
295296 def reset_event (self ):
@@ -334,33 +335,43 @@ def close(self):
334335 self ._uart .close ()
335336 self ._uart = None
336337
337- def _command (self , name , * args , mask_frame_id = False ):
338+ def _get_command_priority (self , name : str , * args ) -> int :
339+ return {
340+ "tx_explicit" : - 1 ,
341+ "remote_at" : - 1 ,
342+ }.get (name , 0 )
343+
344+ async def _command (self , name , * args , mask_frame_id = False ):
338345 """Send API frame to the device."""
339- LOGGER .debug ("Command %s %s" , name , args )
340346 if self ._uart is None :
341347 raise APIException ("API is not running" )
342- frame_id = 0 if mask_frame_id else self ._seq
343- data , needs_response = self ._api_frame (name , frame_id , * args )
344- self ._uart .send (data )
345- future = None
346- if needs_response and frame_id :
348+
349+ async with self ._send_lock (priority = self ._get_command_priority (name )):
350+ LOGGER .debug ("Command %s %s" , name , args )
351+ frame_id = 0 if mask_frame_id else self ._seq
352+ data , needs_response = self ._api_frame (name , frame_id , * args )
353+ self ._uart .send (data )
354+
355+ if not needs_response or not frame_id :
356+ return
357+
347358 future = asyncio .Future ()
348359 self ._awaiting [frame_id ] = (future ,)
349- self ._seq = (self ._seq % 255 ) + 1
350- return future
360+ self ._seq = (self ._seq % 255 ) + 1
361+
362+ return await future
351363
352364 async def _remote_at_command (self , ieee , nwk , options , name , * args ):
353365 """Execute AT command on a different XBee module in the network."""
354366 LOGGER .debug ("Remote AT command: %s %s" , name , args )
355367 data = t .serialize (args , (AT_COMMANDS [name ],))
356368 try :
357- async with self ._send_lock :
358- return await asyncio .wait_for (
359- self ._command (
360- "remote_at" , ieee , nwk , options , name .encode ("ascii" ), data
361- ),
362- timeout = REMOTE_AT_COMMAND_TIMEOUT ,
363- )
369+ return await asyncio .wait_for (
370+ self ._command (
371+ "remote_at" , ieee , nwk , options , name .encode ("ascii" ), data
372+ ),
373+ timeout = REMOTE_AT_COMMAND_TIMEOUT ,
374+ )
364375 except asyncio .TimeoutError :
365376 LOGGER .warning ("No response to %s command" , name )
366377 raise
@@ -369,11 +380,10 @@ async def _at_partial(self, cmd_type, name, *args):
369380 LOGGER .debug ("%s command: %s %s" , cmd_type , name , args )
370381 data = t .serialize (args , (AT_COMMANDS [name ],))
371382 try :
372- async with self ._send_lock :
373- return await asyncio .wait_for (
374- self ._command (cmd_type , name .encode ("ascii" ), data ),
375- timeout = AT_COMMAND_TIMEOUT ,
376- )
383+ return await asyncio .wait_for (
384+ self ._command (cmd_type , name .encode ("ascii" ), data ),
385+ timeout = AT_COMMAND_TIMEOUT ,
386+ )
377387 except asyncio .TimeoutError :
378388 LOGGER .warning ("%s: No response to %s command" , cmd_type , name )
379389 raise
0 commit comments