22# MIT license; Copyright (c) 2021 Jim Mussared
33
44from micropython import const
5+ from collections import deque
56import bluetooth
67import uasyncio as asyncio
78
3435_FLAG_WRITE_AUTHENTICATED = const (0x2000 )
3536_FLAG_WRITE_AUTHORIZED = const (0x4000 )
3637
38+ _FLAG_WRITE_CAPTURE = const (0x10000 )
39+
3740_FLAG_DESC_READ = const (1 )
3841_FLAG_DESC_WRITE = const (2 )
3942
4043
44+ _WRITE_CAPTURE_QUEUE_LIMIT = const (10 )
45+
46+
4147def _server_irq (event , data ):
4248 if event == _IRQ_GATTS_WRITE :
4349 conn_handle , attr_handle = data
@@ -89,26 +95,54 @@ def write(self, data):
8995 else :
9096 ble .gatts_write (self ._value_handle , data )
9197
92- # Wait for a write on this characteristic.
93- # Returns the device that did the write.
98+ # Wait for a write on this characteristic. Returns the connection that did
99+ # the write, or a tuple of (connection, value) if capture is enabled for
100+ # this characteristics.
94101 async def written (self , timeout_ms = None ):
95102 if not self ._write_event :
96103 raise ValueError ()
97- data = self ._write_connection
98- if data is None :
104+
105+ # If the queue is empty, then we need to wait. However, if the queue
106+ # has a single item, we also need to do a no-op wait in order to
107+ # clear the event flag (because the queue will become empty and
108+ # therefore the event should be cleared).
109+ if len (self ._write_queue ) <= 1 :
99110 with DeviceTimeout (None , timeout_ms ):
100111 await self ._write_event .wait ()
101- data = self ._write_connection
102- self ._write_connection = None
103- return data
112+
113+ # Either we started > 1 item, or the wait completed successfully, return
114+ # the front of the queue.
115+ return self ._write_queue .popleft ()
104116
105117 def on_read (self , connection ):
106118 return 0
107119
108120 def _remote_write (conn_handle , value_handle ):
109121 if characteristic := _registered_characteristics .get (value_handle , None ):
110- characteristic ._write_connection = DeviceConnection ._connected .get (conn_handle , None )
111- characteristic ._write_event .set ()
122+ # If we've gone from empty to one item, then wake something
123+ # blocking on `await char.written()`.
124+ wake = len (characteristic ._write_queue ) == 0
125+
126+ conn = DeviceConnection ._connected .get (conn_handle , None )
127+ q = characteristic ._write_queue
128+
129+ if characteristic .flags & _FLAG_WRITE_CAPTURE :
130+ # For capture, we append both the connection and the written
131+ # value to the queue. The deque will enforce the max queue len.
132+ data = characteristic .read ()
133+ q .append ((conn , data ))
134+ else :
135+ # Use the queue as a single slot -- it has max length of 1,
136+ # so if there's an existing item it will be replaced.
137+ q .append (conn )
138+
139+ if wake :
140+ # Queue is now non-empty. If something is waiting, it will be
141+ # worken. If something isn't waiting right now, then a future
142+ # caller to `await char.written()` will see the queue is
143+ # non-empty, and wait on the event if it's going to empty the
144+ # queue.
145+ characteristic ._write_event .set ()
112146
113147 def _remote_read (conn_handle , value_handle ):
114148 if characteristic := _registered_characteristics .get (value_handle , None ):
@@ -126,6 +160,7 @@ def __init__(
126160 notify = False ,
127161 indicate = False ,
128162 initial = None ,
163+ capture = False ,
129164 ):
130165 service .characteristics .append (self )
131166 self .descriptors = []
@@ -137,8 +172,13 @@ def __init__(
137172 flags |= (_FLAG_WRITE if write else 0 ) | (
138173 _FLAG_WRITE_NO_RESPONSE if write_no_response else 0
139174 )
140- self ._write_connection = None
175+ if capture :
176+ # Capture means that we keep track of all writes, and capture
177+ # their values (and connection) in a queue. Otherwise we just
178+ # track the most recent connection.
179+ flags |= _FLAG_WRITE_CAPTURE
141180 self ._write_event = asyncio .ThreadSafeFlag ()
181+ self ._write_queue = deque ((), _WRITE_CAPTURE_QUEUE_LIMIT if capture else 1 )
142182 if notify :
143183 flags |= _FLAG_NOTIFY
144184 if indicate :
0 commit comments