Skip to content

Commit 5a86aa5

Browse files
jimmodpgeorge
authored andcommitted
aioble: Add a write queue for gatt server.
This fixes a bug where an incoming write before `written` is awaited causes `written` to return None. It also introduces a mechanism for a server to "capture" all incoming written values (instead of only having access to the most recent value). Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent 3c383f6 commit 5a86aa5

File tree

1 file changed

+50
-10
lines changed

1 file changed

+50
-10
lines changed

micropython/bluetooth/aioble/aioble/server.py

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# MIT license; Copyright (c) 2021 Jim Mussared
33

44
from micropython import const
5+
from collections import deque
56
import bluetooth
67
import uasyncio as asyncio
78

@@ -34,10 +35,15 @@
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+
4147
def _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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy