2
2
# MIT license; Copyright (c) 2021 Jim Mussared
3
3
4
4
from micropython import const
5
+ from collections import deque
5
6
import bluetooth
6
7
import uasyncio as asyncio
7
8
34
35
_FLAG_WRITE_AUTHENTICATED = const (0x2000 )
35
36
_FLAG_WRITE_AUTHORIZED = const (0x4000 )
36
37
38
+ _FLAG_WRITE_CAPTURE = const (0x10000 )
39
+
37
40
_FLAG_DESC_READ = const (1 )
38
41
_FLAG_DESC_WRITE = const (2 )
39
42
40
43
44
+ _WRITE_CAPTURE_QUEUE_LIMIT = const (10 )
45
+
46
+
41
47
def _server_irq (event , data ):
42
48
if event == _IRQ_GATTS_WRITE :
43
49
conn_handle , attr_handle = data
@@ -89,26 +95,54 @@ def write(self, data):
89
95
else :
90
96
ble .gatts_write (self ._value_handle , data )
91
97
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.
94
101
async def written (self , timeout_ms = None ):
95
102
if not self ._write_event :
96
103
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 :
99
110
with DeviceTimeout (None , timeout_ms ):
100
111
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 ()
104
116
105
117
def on_read (self , connection ):
106
118
return 0
107
119
108
120
def _remote_write (conn_handle , value_handle ):
109
121
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 ()
112
146
113
147
def _remote_read (conn_handle , value_handle ):
114
148
if characteristic := _registered_characteristics .get (value_handle , None ):
@@ -126,6 +160,7 @@ def __init__(
126
160
notify = False ,
127
161
indicate = False ,
128
162
initial = None ,
163
+ capture = False ,
129
164
):
130
165
service .characteristics .append (self )
131
166
self .descriptors = []
@@ -137,8 +172,13 @@ def __init__(
137
172
flags |= (_FLAG_WRITE if write else 0 ) | (
138
173
_FLAG_WRITE_NO_RESPONSE if write_no_response else 0
139
174
)
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
141
180
self ._write_event = asyncio .ThreadSafeFlag ()
181
+ self ._write_queue = deque ((), _WRITE_CAPTURE_QUEUE_LIMIT if capture else 1 )
142
182
if notify :
143
183
flags |= _FLAG_NOTIFY
144
184
if indicate :
0 commit comments