kopia lustrzana https://github.com/micropython/micropython-lib
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>pull/437/head
rodzic
3c383f6d28
commit
5a86aa5866
|
@ -2,6 +2,7 @@
|
||||||
# MIT license; Copyright (c) 2021 Jim Mussared
|
# MIT license; Copyright (c) 2021 Jim Mussared
|
||||||
|
|
||||||
from micropython import const
|
from micropython import const
|
||||||
|
from collections import deque
|
||||||
import bluetooth
|
import bluetooth
|
||||||
import uasyncio as asyncio
|
import uasyncio as asyncio
|
||||||
|
|
||||||
|
@ -34,10 +35,15 @@ _FLAG_WRITE_ENCRYPTED = const(0x1000)
|
||||||
_FLAG_WRITE_AUTHENTICATED = const(0x2000)
|
_FLAG_WRITE_AUTHENTICATED = const(0x2000)
|
||||||
_FLAG_WRITE_AUTHORIZED = const(0x4000)
|
_FLAG_WRITE_AUTHORIZED = const(0x4000)
|
||||||
|
|
||||||
|
_FLAG_WRITE_CAPTURE = const(0x10000)
|
||||||
|
|
||||||
_FLAG_DESC_READ = const(1)
|
_FLAG_DESC_READ = const(1)
|
||||||
_FLAG_DESC_WRITE = const(2)
|
_FLAG_DESC_WRITE = const(2)
|
||||||
|
|
||||||
|
|
||||||
|
_WRITE_CAPTURE_QUEUE_LIMIT = const(10)
|
||||||
|
|
||||||
|
|
||||||
def _server_irq(event, data):
|
def _server_irq(event, data):
|
||||||
if event == _IRQ_GATTS_WRITE:
|
if event == _IRQ_GATTS_WRITE:
|
||||||
conn_handle, attr_handle = data
|
conn_handle, attr_handle = data
|
||||||
|
@ -89,26 +95,54 @@ class BaseCharacteristic:
|
||||||
else:
|
else:
|
||||||
ble.gatts_write(self._value_handle, data)
|
ble.gatts_write(self._value_handle, data)
|
||||||
|
|
||||||
# Wait for a write on this characteristic.
|
# Wait for a write on this characteristic. Returns the connection that did
|
||||||
# Returns the device that did the write.
|
# the write, or a tuple of (connection, value) if capture is enabled for
|
||||||
|
# this characteristics.
|
||||||
async def written(self, timeout_ms=None):
|
async def written(self, timeout_ms=None):
|
||||||
if not self._write_event:
|
if not self._write_event:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
data = self._write_connection
|
|
||||||
if data is None:
|
# If the queue is empty, then we need to wait. However, if the queue
|
||||||
|
# has a single item, we also need to do a no-op wait in order to
|
||||||
|
# clear the event flag (because the queue will become empty and
|
||||||
|
# therefore the event should be cleared).
|
||||||
|
if len(self._write_queue) <= 1:
|
||||||
with DeviceTimeout(None, timeout_ms):
|
with DeviceTimeout(None, timeout_ms):
|
||||||
await self._write_event.wait()
|
await self._write_event.wait()
|
||||||
data = self._write_connection
|
|
||||||
self._write_connection = None
|
# Either we started > 1 item, or the wait completed successfully, return
|
||||||
return data
|
# the front of the queue.
|
||||||
|
return self._write_queue.popleft()
|
||||||
|
|
||||||
def on_read(self, connection):
|
def on_read(self, connection):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _remote_write(conn_handle, value_handle):
|
def _remote_write(conn_handle, value_handle):
|
||||||
if characteristic := _registered_characteristics.get(value_handle, None):
|
if characteristic := _registered_characteristics.get(value_handle, None):
|
||||||
characteristic._write_connection = DeviceConnection._connected.get(conn_handle, None)
|
# If we've gone from empty to one item, then wake something
|
||||||
characteristic._write_event.set()
|
# blocking on `await char.written()`.
|
||||||
|
wake = len(characteristic._write_queue) == 0
|
||||||
|
|
||||||
|
conn = DeviceConnection._connected.get(conn_handle, None)
|
||||||
|
q = characteristic._write_queue
|
||||||
|
|
||||||
|
if characteristic.flags & _FLAG_WRITE_CAPTURE:
|
||||||
|
# For capture, we append both the connection and the written
|
||||||
|
# value to the queue. The deque will enforce the max queue len.
|
||||||
|
data = characteristic.read()
|
||||||
|
q.append((conn, data))
|
||||||
|
else:
|
||||||
|
# Use the queue as a single slot -- it has max length of 1,
|
||||||
|
# so if there's an existing item it will be replaced.
|
||||||
|
q.append(conn)
|
||||||
|
|
||||||
|
if wake:
|
||||||
|
# Queue is now non-empty. If something is waiting, it will be
|
||||||
|
# worken. If something isn't waiting right now, then a future
|
||||||
|
# caller to `await char.written()` will see the queue is
|
||||||
|
# non-empty, and wait on the event if it's going to empty the
|
||||||
|
# queue.
|
||||||
|
characteristic._write_event.set()
|
||||||
|
|
||||||
def _remote_read(conn_handle, value_handle):
|
def _remote_read(conn_handle, value_handle):
|
||||||
if characteristic := _registered_characteristics.get(value_handle, None):
|
if characteristic := _registered_characteristics.get(value_handle, None):
|
||||||
|
@ -126,6 +160,7 @@ class Characteristic(BaseCharacteristic):
|
||||||
notify=False,
|
notify=False,
|
||||||
indicate=False,
|
indicate=False,
|
||||||
initial=None,
|
initial=None,
|
||||||
|
capture=False,
|
||||||
):
|
):
|
||||||
service.characteristics.append(self)
|
service.characteristics.append(self)
|
||||||
self.descriptors = []
|
self.descriptors = []
|
||||||
|
@ -137,8 +172,13 @@ class Characteristic(BaseCharacteristic):
|
||||||
flags |= (_FLAG_WRITE if write else 0) | (
|
flags |= (_FLAG_WRITE if write else 0) | (
|
||||||
_FLAG_WRITE_NO_RESPONSE if write_no_response else 0
|
_FLAG_WRITE_NO_RESPONSE if write_no_response else 0
|
||||||
)
|
)
|
||||||
self._write_connection = None
|
if capture:
|
||||||
|
# Capture means that we keep track of all writes, and capture
|
||||||
|
# their values (and connection) in a queue. Otherwise we just
|
||||||
|
# track the most recent connection.
|
||||||
|
flags |= _FLAG_WRITE_CAPTURE
|
||||||
self._write_event = asyncio.ThreadSafeFlag()
|
self._write_event = asyncio.ThreadSafeFlag()
|
||||||
|
self._write_queue = deque((), _WRITE_CAPTURE_QUEUE_LIMIT if capture else 1)
|
||||||
if notify:
|
if notify:
|
||||||
flags |= _FLAG_NOTIFY
|
flags |= _FLAG_NOTIFY
|
||||||
if indicate:
|
if indicate:
|
||||||
|
|
Ładowanie…
Reference in New Issue