← index #7584Issue #8258
Related · high · value 1.228
QUERY · ISSUE

BLE.gattc_exchange_mtu() callback OSError: [Errno 120] EALREADY

openby 1996xjmopened 2021-07-25updated 2021-07-25

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")
CANDIDATE · ISSUE

mtu vs MP_BLUETOOTH_DEFAULT_ATTR_LEN

closedby stephanelsmithopened 2022-02-03updated 2022-02-04
extmod

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):

  1. activate ble
  2. setting MTU ble.config(mtu = xxx)
  3. setting rxbuf ble.config(rxbuf = xxx)
  4. exchanging MTU ble.gattc_exchange_mtu(conn_handle)
  5. 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.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied