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")
AIOBLE - Stuck on exhange MTU
in AIOBLE, when exchanging MTU, it will call ble.gattc_exchange_mtu() method, and wait for the Exchange_MTU event is back.
And in some scenario, the peripheral will disconnect the central when receiving the MTU exchange request.
And at this moment, the Exhange_MTU event will never return. It causes the stuck.
OK I will have to look into this in more detail, and I agree this should be handled by the
exchange_mtufunction.In the meantime you can use
But the correct solution is probably to make
exchange_mtutake a timeout argument, like many of the other methods do.Note:
with connection.timeout()is will also terminate the enclosed code on disconnection (as well as, of course, timeout).Quick followup... in general you can use
which will automatically disconnect (or terminate if the other side disconnects). This may not be appropriate if you've got multiple tasks sharing the connection though, but just FYI.
I've addressed exactly this with a timeout argument added to mtu requests in https://github.com/micropython/micropython-lib/pull/465
#465 resolves this issue, if there's more discuss on the issue lets do so there.