hbldh / bleak

A cross platform Bluetooth Low Energy Client for Python using asyncio
MIT License
1.58k stars 277 forks source link

Bleak is connecting to my BLEperipheral, but after some seconds it disconnects. #1492

Closed Jackiii1989 closed 4 months ago

Jackiii1989 commented 4 months ago

Description

I am communicating with the Nordic nRF52840DK device using the Bleak code example uart_service and the Nordic code example Nordic UART Service (NUS). I modified the bleak uart_service code a bit: integrated PyQt6 to create a GUI for displaying sensor data from the nRF52840DK, and also changed the code from nordic.

What I Did

Initially, I developed a functional version of the code that worked fine. However, when I encapsulated the code in a class, the code behaved strangely. The class code successfully establishes a connection with the Nordic nRF52840DK, but it disconnects after some seconds. On the Nordic nRF52840DK side I get an error BLE errror 19 (hex 0x13), which means that the remote device has forced a disconnection. This means that Bleak has cut the connection.

image

On The python console I get this error:

image

Can someone explain me why is happening? Thanks

Below is the python code:

import sys
import asyncio
from threading import Thread, Event
from bleak import BleakClient, BleakScanner
from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from PyQt6.QtWidgets import QApplication, QGridLayout, QLabel, QPushButton, QWidget
import pyqtgraph as pg  # type: ignore
from pglive.sources.data_connector import DataConnector
from pglive.sources.live_plot import LiveLinePlot
from pglive.sources.live_plot_widget import LivePlotWidget

UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"

async def uart_terminal(connector_x, connector_y, connector_z, label, event_start, event_stop):
    loop_continue = True

    def match_nus_uuid(device: BLEDevice, adv: AdvertisementData):
        if UART_SERVICE_UUID.lower() in adv.service_uuids:
            return True
        return False

    device = await BleakScanner.find_device_by_filter(match_nus_uuid)
    if device is None:
        print("no matching device found, you may need to edit match_nus_uuid().")
        sys.exit(1)

    def handle_disconnect(_: BleakClient):
        print("Device was disconnected, goodbye.")
        # cancelling all tasks effectively ends the program
        loop_continue = False
        label.setText("BLE: Disconnected")
        for task in asyncio.all_tasks():
            task.cancel()

    def handle_rx(_: BleakGATTCharacteristic, data_l: bytearray):
        rtext = data_l.decode('utf-8')  # receive data here
        values = [float(i) for i in rtext.split(',')]
        connector_x.cb_append_data_point(values[1], values[0] / 1000)
        connector_y.cb_append_data_point(values[2], values[0] / 1000)
        connector_z.cb_append_data_point(values[3], values[0] / 1000)

    async with BleakClient(device, disconnected_callback=handle_disconnect) as client:
        await client.start_notify(UART_TX_CHAR_UUID, handle_rx)
        label.setText("BLE: Connected")
        nus = client.services.get_service(UART_SERVICE_UUID)
        rx_char = nus.get_characteristic(UART_RX_CHAR_UUID)
        await asyncio.sleep(0.5)
        await client.write_gatt_char(rx_char, "1", response=False)
        while loop_continue:
            if event_stop.is_set():
                await client.write_gatt_char(rx_char, b"0\r\n")
                print("stop")
                connector_x.clear()
                connector_y.clear()
                connector_z.clear()
                event_stop.clear()
            elif event_start.is_set():
                await client.write_gatt_char(rx_char, b"1\r\n")
                print("start")
                event_start.clear()
            await asyncio.sleep(0.5)
        label.setText("BLE: not connected")
        await asyncio.sleep(2)

def bleak_process(connector_x, connector_y, connector_z, event_start, event_stop, label):
    try:
        asyncio.run(uart_terminal(connector_x, connector_y,
                                  connector_z, label,
                                  event_start, event_stop))
    except asyncio.CancelledError:
        # task is cancelled on disconnect, so we ignore this error
        pass

class qt_app:
    def __init__(self):
        self.event_start = Event()
        self.event_stop = Event()
        self.app = QApplication(sys.argv)

        # Create parent widget
        self.parent_widget = QWidget()
        parent_layout = QGridLayout()
        self.parent_widget.setLayout(parent_layout)

        plot_widget = LivePlotWidget(title="Line Plot ")
        plot_curve_x = LiveLinePlot(pen="red")
        plot_curve_y = LiveLinePlot(pen="green")
        plot_curve_z = LiveLinePlot(pen="blue")
        plot_widget.addItem(plot_curve_x)
        plot_widget.addItem(plot_curve_y)
        plot_widget.addItem(plot_curve_z)

        self.ch_status_value = QLabel("BLE: not connected")
        connect_button = QPushButton("connect")
        start_button = QPushButton("start")
        stop_button = QPushButton("stop")

        parent_layout.addWidget(plot_widget, 0, 0, 1, 2)
        parent_layout.addWidget(connect_button, 1, 0)
        parent_layout.addWidget(self.ch_status_value, 1, 1)
        parent_layout.addWidget(start_button, 2, 0)
        parent_layout.addWidget(stop_button, 2, 1)

        # Connect signals with respective methods
        connect_button.clicked.connect(self.start_thread)
        start_button.clicked.connect(self.start)
        stop_button.clicked.connect(self.stop)

        TIME_IN_S = 180
        SAMPLE_RATE = 200
        POINTS_SIZE = TIME_IN_S * SAMPLE_RATE

        self.data_connector_x = DataConnector(plot_curve_x, max_points=POINTS_SIZE, update_rate=200)
        self.data_connector_y = DataConnector(plot_curve_y, max_points=POINTS_SIZE, update_rate=200)
        self.data_connector_z = DataConnector(plot_curve_z, max_points=POINTS_SIZE, update_rate=200)

    def stop(self):
        self.event_stop.set()

    def start(self):
        self.event_start.set()

    def start_thread(self):
        self.ch_status_value.setText("BLE: Connecting.")
        Thread(target=bleak_process,
               args=(self.data_connector_x, self.data_connector_y, self.data_connector_z, self.event_start,
                     self.event_stop, self.ch_status_value)).start()

    def run(self):
        self.parent_widget.show()
        self.app.exec()

if __name__ == "__main__":
    gui = qt_app()
    gui.run()
Jackiii1989 commented 4 months ago

I'm sorry about that. I have found the error.