-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
CircuitPython version and board name
Adafruit CircuitPython 10.0.3 on 2025-10-17; M5Stack DinMeter with ESP32S3Code/REPL
import _bleio
from adafruit_ble import BLERadio
from adafruit_ble.attributes import Attribute
from adafruit_ble.advertising import Advertisement
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services import Service
from adafruit_ble.characteristics import Characteristic, ComplexCharacteristic
from adafruit_ble.uuid import VendorUUID
UUID_TODO_SERV = '023331ce-5455-49b0-98c9-107904d21918'
UUID_TODO_CHAR = '54783f34-f513-43f3-b13f-b7f212736c1f'
class TodoCharacteristic(ComplexCharacteristic):
def __init__(self):
super().__init__(uuid=VendorUUID(UUID_TODO_CHAR), properties=(Characteristic.INDICATE,), read_perm=Attribute.OPEN, write_perm=Attribute.OPEN, max_length=0, fixed_length=False, initial_value=None)
def bind(self, service):
bound_characteristic = super().bind(service)
return _bleio.PacketBuffer(bound_characteristic, buffer_size=8)
def __get__(self, obj, cls = None):
if obj is None:
return self
return super().__get__(obj, cls)
def __set__(self, obj, value):
super().__set__(obj, value)
class TodoService(Service):
uuid = VendorUUID(UUID_TODO_SERV)
todo = TodoCharacteristic()
def __init__(self, *, service = None):
super().__init__(service=service)
self.data = TodoService.todo.bind(self)
ble = BLERadio()
for adv in ble.start_scan(ProvideServicesAdvertisement, Advertisement, timeout=1):
if isinstance(adv, ProvideServicesAdvertisement) and TodoService in adv.services:
ble.stop_scan()
ble_connection = ble.connect(adv)
ble_service = ble_connection[TodoService]Behavior
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
File "/lib/adafruit_ble/__init__.py", line 101, in __getitem__
File "/lib/adafruit_ble/__init__.py", line 66, in _discover_remote
_bleio.BluetoothError: Unknown BLE error: 7
Description
In this scenario we are a GATT client, simply discovering the remote services - but with (at least) one characteristic being read-only on the GATT server. What happens is a 0-byte long write on the characteristic - even if it declared as read-only (like with notify/indicate).
In _discovered_characteristic_cb() (ports/espressif/common-hal/_bleio/Connection.c) the following call is made:
common_hal_bleio_characteristic_construct(
characteristic, service, chr->val_handle, uuid,
props, SECURITY_MODE_OPEN, SECURITY_MODE_OPEN,
GATT_MAX_DATA_LENGTH, false, // max_length, fixed_length: values don't matter for gattc, but don't use 0
&mp_const_empty_bytes_bufinfo,
NULL);
This leads to the BLE error shown above - the culprit is the second-last parameter ("initial_value_bufinfo", with value "&mp_const_empty_bytes_bufinfo") in the invocation of common_hal_bleio_characteristic_construct() (implemented in ports/espressif/common-hal/_bleio/Characteristic.c), as it always leads to a GATT write on the characteristic, because the following condition in common_hal_bleio_characteristic_construct() is always true:
if (initial_value_bufinfo != NULL) {
common_hal_bleio_characteristic_set_value(self, initial_value_bufinfo);
}
Important notices:
- The provided UUIDs in the Code/REPL section were randomly generated, it should be reproducible with any GATT server that has any read-only characteristic (during discovery).
- The issue is not chip/model specific (like the esp32s3 I used) applies to any board with BLE/GATT in the espressif port.
Additional information
I am not entirely sure what the proper initialization state should be, presumably NULL because of the existing NULL-condition check in common_hal_bleio_characteristic_construct() - but on the other hand the implemented initial value of mp_const_empty_bytes_bufinfo was probably chosen for some reason (but maybe it was chosen under the assumption for a GATT server?). I guess another condition check whether the characteristic is read-only (and remote?) could be used to differentiate - but I am not sure whether GATT client vs. server is the "correct" differentiator here (I haven't looked at the "history" of these files or searched for related github issues).