BLE.gattc_exchange_mtu() callback OSError: [Errno 120] EALREADY
I created a BLE server for bluetooth keyboard. When my computer sent a MTU exchange request The _IRQ_MTU_EXCHANGED event, I called the function BLE.gattc_exchange_mtu(conn_handle) where I set the MTU value with 515 using BLE.config(mtu=515). But I received a OSError: [Errno 120] EALREADY. I tried to reduce the value to 128, but not worked. The micropython version I used was esp32-20210623-v1.16.bin. I also tried other version but I got the same result. My board is ESP32-S AI-Thinker
Errer output
event num: 1
data: (0, 0, <memoryview>)
connected
event num: 21
data: (0, 128)
MTU_EXCHANGED
Traceback (most recent call last):
File "<stdin>", line 185, in bt_irq
OSError: [Errno 120] EALREADY
My full code
from machine import Pin, Timer
from time import sleep_ms
import bluetooth
from micropython import const
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)
_IRQ_GATTS_INDICATE_DONE = const(20)
_IRQ_MTU_EXCHANGED = const(21)
_IRQ_L2CAP_ACCEPT = const(22)
_IRQ_L2CAP_CONNECT = const(23)
_IRQ_L2CAP_DISCONNECT = const(24)
_IRQ_L2CAP_RECV = const(25)
_IRQ_L2CAP_SEND_READY = const(26)
_IRQ_CONNECTION_UPDATE = const(27)
_IRQ_ENCRYPTION_UPDATE = const(28)
_IRQ_GET_SECRET = const(29)
_IRQ_SET_SECRET = const(30)
class BLE():
def __init__(self, name):
self.ble = bluetooth.BLE()
self.ble.active(True)
self.ble.config(gap_name=name)
self.ble.config(rxbuf=1024)
# 设置最大数据传输单元,蓝牙连接后双方会发送各自的MTU,选其中小的来进行后续的数据通信
self.ble.config(mtu=128)
self.led = Pin(33, Pin.OUT)
self.timer1 = Timer(0)
self.timer2 = Timer(1)
self.disconnected()
self.ble.irq(self.bt_irq)
self.register()
self.advertiser()
def connected(self):
self.timer1.deinit()
self.timer2.deinit()
def shakeLed(self):
self.led(0)
sleep_ms(100)
self.led(1)
def disconnected(self):
self.timer1.init(period=1000, mode=Timer.PERIODIC, callback=lambda t: self.led(1))
sleep_ms(200)
self.timer2.init(period=1000, mode=Timer.PERIODIC, callback=lambda t: self.led(0))
def ble_irq1(self, event, data):
print("event num:", event)
print("data:", data)
if event == 1:
'''Central connected'''
print("connected")
self.connected()
self.led(1)
elif event == 2:
'''Central disconnected'''
print("disconnected")
self.advertiser()
self.disconnected()
elif event == 3:
'''New message received'''
self.shakeLed()
buffer = self.ble.gatts_read(self.rx)
print(buffer)
try:
message = buffer.decode()
print(message)
except:
print("decode err!!!!")
def bt_irq(self, event, data):
print("event num:", event)
print("data:", data)
if event == _IRQ_CENTRAL_CONNECT:
# A central has connected to this peripheral.
print("connected")
self.connected()
self.led(1)
self.ble.gatts_notify(0, self.batttx, "hello" + '\n')
conn_handle, addr_type, addr = data
elif event == _IRQ_CENTRAL_DISCONNECT:
# A central has disconnected from this peripheral.
print("disconnected")
self.advertiser()
self.disconnected()
conn_handle, addr_type, addr = data
elif event == _IRQ_GATTS_WRITE:
# A client has written to this characteristic or descriptor.
conn_handle, attr_handle = data
elif event == _IRQ_GATTS_READ_REQUEST:
# A client has issued a read. Note: this is only supported on STM32.
# Return a non-zero integer to deny the read (see below), or zero (or None)
# to accept the read.
print("READ_REQUEST")
conn_handle, attr_handle = data
elif event == _IRQ_SCAN_RESULT:
print("scan result")
# A single scan result.
addr_type, addr, adv_type, rssi, adv_data = data
elif event == _IRQ_SCAN_DONE:
# Scan duration finished or manually stopped.
pass
elif event == _IRQ_PERIPHERAL_CONNECT:
# A successful gap_connect().
print("successful gap_connect")
conn_handle, addr_type, addr = data
elif event == _IRQ_PERIPHERAL_DISCONNECT:
# Connected peripheral has disconnected.
conn_handle, addr_type, addr = data
elif event == _IRQ_GATTC_SERVICE_RESULT:
# Called for each service found by gattc_discover_services().
conn_handle, start_handle, end_handle, uuid = data
elif event == _IRQ_GATTC_SERVICE_DONE:
# Called once service discovery is complete.
# Note: Status will be zero on success, implementation-specific value otherwise.
conn_handle, status = data
elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
# Called for each characteristic found by gattc_discover_services().
conn_handle, def_handle, value_handle, properties, uuid = data
elif event == _IRQ_GATTC_CHARACTERISTIC_DONE:
# Called once service discovery is complete.
# Note: Status will be zero on success, implementation-specific value otherwise.
conn_handle, status = data
elif event == _IRQ_GATTC_DESCRIPTOR_RESULT:
# Called for each descriptor found by gattc_discover_descriptors().
conn_handle, dsc_handle, uuid = data
elif event == _IRQ_GATTC_DESCRIPTOR_DONE:
# Called once service discovery is complete.
# Note: Status will be zero on success, implementation-specific value otherwise.
conn_handle, status = data
elif event == _IRQ_GATTC_READ_RESULT:
# A gattc_read() has completed.
conn_handle, value_handle, char_data = data
elif event == _IRQ_GATTC_READ_DONE:
# A gattc_read() has completed.
# Note: The value_handle will be zero on btstack (but present on NimBLE).
# Note: Status will be zero on success, implementation-specific value otherwise.
conn_handle, value_handle, status = data
elif event == _IRQ_GATTC_WRITE_DONE:
# A gattc_write() has completed.
# Note: The value_handle will be zero on btstack (but present on NimBLE).
# Note: Status will be zero on success, implementation-specific value otherwise.
conn_handle, value_handle, status = data
elif event == _IRQ_GATTC_NOTIFY:
# A server has sent a notify request.
conn_handle, value_handle, notify_data = data
elif event == _IRQ_GATTC_INDICATE:
# A server has sent an indicate request.
conn_handle, value_handle, notify_data = data
elif event == _IRQ_GATTS_INDICATE_DONE:
# A client has acknowledged the indication.
# Note: Status will be zero on successful acknowledgment, implementation-specific value otherwise.
conn_handle, value_handle, status = data
elif event == _IRQ_MTU_EXCHANGED:
# ATT MTU exchange complete (either initiated by us or the remote device).
print("MTU_EXCHANGED")
conn_handle, mtu = data
#发送自己的MTU
self.ble.gattc_exchange_mtu(conn_handle)
elif event == _IRQ_L2CAP_ACCEPT:
# A new channel has been accepted.
# Return a non-zero integer to reject the connection, or zero (or None) to accept.
conn_handle, cid, psm, our_mtu, peer_mtu = data
elif event == _IRQ_L2CAP_CONNECT:
# A new channel is now connected (either as a result of connecting or accepting).
conn_handle, cid, psm, our_mtu, peer_mtu = data
elif event == _IRQ_L2CAP_DISCONNECT:
# Existing channel has disconnected (status is zero), or a connection attempt failed (non-zero status).
conn_handle, cid, psm, status = data
elif event == _IRQ_L2CAP_RECV:
# New data is available on the channel. Use l2cap_recvinto to read.
conn_handle, cid = data
elif event == _IRQ_L2CAP_SEND_READY:
# A previous l2cap_send that returned False has now completed and the channel is ready to send again.
# If status is non-zero, then the transmit buffer overflowed and the application should re-send the data.
conn_handle, cid, status = data
elif event == _IRQ_CONNECTION_UPDATE:
# The remote device has updated connection parameters.
conn_handle, conn_interval, conn_latency, supervision_timeout, status = data
elif event == _IRQ_ENCRYPTION_UPDATE:
# The encryption state has changed (likely as a result of pairing or bonding).
conn_handle, encrypted, authenticated, bonded, key_size = data
elif event == _IRQ_GET_SECRET:
# Return a stored secret.
# If key is None, return the index'th value of this sec_type.
# Otherwise return the corresponding value for this sec_type and key.
sec_type, index, key = data
return value
elif event == _IRQ_SET_SECRET:
# Save a secret to the store for this sec_type and key.
sec_type, key, value = data
return True
elif event == _IRQ_PASSKEY_ACTION:
# Respond to a passkey request during pairing.
# See gap_passkey() for details.
# action will be an action that is compatible with the configured "io" config.
# passkey will be non-zero if action is "numeric comparison".
conn_handle, action, passkey = data
def reg1(self):
# Nordic UART Service (NUS)
NUS_UUID = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E'
RX_UUID = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E'
TX_UUID = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E'
BLE_NUS = bluetooth.UUID(NUS_UUID)
BLE_RX = (bluetooth.UUID(RX_UUID), bluetooth.FLAG_WRITE)
BLE_TX = (bluetooth.UUID(TX_UUID), bluetooth.FLAG_NOTIFY)
BLE_UART = (BLE_NUS, (BLE_TX, BLE_RX,))
SERVICES = (BLE_UART, )
((self.tx, self.rx,), ) = self.ble.gatts_register_services(SERVICES)
self.ble.gatts_set_buffer(self.rx,1024)
def register(self):
# 键盘
NUS_UUID = 0x180A #Service UUID
TX_UUID1 = 0x2A50 #Characteristics UUID
TX_UUID2 = 0x2A29 #Characteristics UUID
BLE_NUS = bluetooth.UUID(NUS_UUID)
BLE_TX1 = (bluetooth.UUID(TX_UUID1), bluetooth.FLAG_READ)
BLE_TX2 = (bluetooth.UUID(TX_UUID2), bluetooth.FLAG_READ)
BLE_Keyboard = (BLE_NUS, (BLE_TX1, BLE_TX2,))
# 电池
Batt_Service_UUID = 0x180F #Service UUID
Batt_Characteristic_UUID = 0x2A19 #Characteristics UUID
Batt_Service_NUS = bluetooth.UUID(Batt_Service_UUID)
Batt_Characteristic_TX = (bluetooth.UUID(Batt_Characteristic_UUID), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY)
BLE_Batt = (Batt_Service_NUS, (Batt_Characteristic_TX,))
SERVICES = (BLE_Keyboard, BLE_Batt, )
((self.keytx1, self.keytx2,), (self.batttx,) ) = self.ble.gatts_register_services(SERVICES)
# 设置可接收的数据长度
# self.ble.gatts_set_buffer(self.rx,1024)
def send(self, data):
self.ble.gatts_notify(0, self.tx, data + '\n')
def advertiser(self):
print("advertiser........")
#广播包:https://www.cnblogs.com/aikm/p/5022502.html
#localName
#BLE广播包的用户数据单元由多个AD Structure组成
#每个AD Structure 由Length |AD Type|AD Data
#Length 等于AD Type和AD Data的长度和
localname = bytes("xiaochi_local", 'UTF-8')
ln_len = bytes([len(localname) + 1])
ln_type = bytes([0x09])#0x09表示设备名称
ln_AD_Data = ln_len + ln_type + localname
#AD Appearance: 0x03C1 表示键盘
Appearance_len = bytes([3])
Appearance_type = bytes([0x19])
Appearance_UUID = bytes([0xC1,0x03]) #UUID是低字节在前
Appearance_UUID_AD_Data = Appearance_len + Appearance_type + Appearance_UUID
#AD service UUID
#0x1812:特指人机接口设备 键盘 鼠标这些 要广播这个ID才能被电脑手机蓝牙直接扫描
service_len = bytes([3])
service_type = bytes([0x03])
service_UUID = bytes([0x12,0x18]) #UUID是低字节在前
service_UUID_AD_Data = service_len + service_type + service_UUID
self.ble.gap_advertise(100, bytearray('\x02\x01\x06') + ln_AD_Data + Appearance_UUID_AD_Data + service_UUID_AD_Data)
#self.ble.gap_advertise(100)
red_led = Pin(33, Pin.OUT)
ble = BLE("xiaochi_local")
mtu vs MP_BLUETOOTH_DEFAULT_ATTR_LEN
Hope all is well. Reporting a snag for me which may or may not be an actual issue. Either way, I have a work-around, but wanted to raise.
After configuring MTU, ble.gatts_notify is using the new mtu, writing mtu-3 bytes.
However, ble.gattc_write still writes 20 bytes max.
Process (brief):
- activate ble
- setting MTU
ble.config(mtu = xxx) - setting rxbuf
ble.config(rxbuf = xxx) - exchanging MTU
ble.gattc_exchange_mtu(conn_handle) - confirmed w/ IRQ_MTU_EXCHANGED
The work-around for me is simple. In mpconfigboard.h, I've added:
#define MP_BLUETOOTH_DEFAULT_ATTR_LEN (xxx)
As long as MP_BLUETOOTH_DEFAULT_ATTR_LEN is greater than the mtu, the payload is the right size for both ble.gatts_notify and ble.gattc_write. Side-effects to be concerned about?
I took a poke around to see if I could find anything obvious to my eyes. I played around with exchanging the MTU before discovering characteristics in case there's a race condition or order of operation. No effect.