Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bleak not disconnecting automatically when run and stopped/interrupted with PyCharm #613

Closed
kazola opened this issue Aug 2, 2021 · 11 comments
Labels
Backend: BlueZ Issues and PRs relating to the BlueZ backend

Comments

@kazola
Copy link

kazola commented Aug 2, 2021

  • bleak version: 0.12.1
  • Python version: 3.9
  • Operating System: PopOS 21.04 (based on Ubuntu)
  • BlueZ version (bluetoothctl -v) in case of Linux: 5.56

Description

I connect to my BLE hardware device using python3 and Bleak and I send a LED command every 5 seconds indefinitely. The device blinks its LEDs and answers back. It works perfectly. However, when I stop the code (I use Pycharm and I press the red stop button), the snippet never works again unless I reset the Bluetooth subsystem (systemctl restart bluetooth). It seems I am suffering from the issue in line #348 of client.py in master branch, which states something like " Otherwise BlueZ will keep the device connected even after Python exits". I think I can confirm this since, when I run the code again it comes up with the error "bleak.exc.BleakError: Device with address <my_mac_below> was not found." It cannot be found because it is not advertising since it is still connected in the background :(

Also, in my hardware device logging subsystem, I see Bleak never tells it to disconnect. My other apps with other frameworks automatically disconnect if code is no longer working. I have seen this issue around the forum but tested some of the suggestions (some of the branches are not existing anymore) and it does not seem to be solved. Maybe I am missing some parameters on BleakClient()? Gonna check issue #377 again meanwhile.

This seems a great project. Let me know if I can do some testing around all this.

What I Did

Please see my code example :) I simply activate notifications on UUID_C. Then I write commands on UUID_W.

import logging
import asyncio
import bleak.exc
from bleak import BleakClient, discover


logging.basicConfig(level=logging.INFO)
UUID_C = 'f0001132-0451-4000-b000-000000000000'
UUID_W = 'f0001131-0451-4000-b000-000000000000'
addr = '04:EE:03:6C:EF:30'


def notification_handler(_, data):
    print(data)


async def main(mac):
    try:
        disconnected_event = asyncio.Event()

        def disconnected_callback(client):
            print("Disconnected callback called!")
            disconnected_event.set()

        async with BleakClient(mac, disconnected_callback=disconnected_callback) as client:
            await client.start_notify(UUID_C, notification_handler)
            # this blinks some LEDs in my hardware device :)
            v = b'LED \r'
            while 1:
                await client.write_gatt_char(UUID_W, v)
                await asyncio.sleep(5)

    except bleak.exc.BleakError as ex:
        print('-- {}'.format(ex))


if __name__ == "__main__":
    asyncio.run(main(addr))
@dlech
Copy link
Collaborator

dlech commented Aug 2, 2021

How does PyCharm end the Python process when you press the stop button? SIGINT, SIGTERM, SIGKILL, etc.?

@kazola
Copy link
Author

kazola commented Aug 2, 2021

Hi @dlech !
Process finished with exit code 130 (interrupted by signal 2: SIGINT)

@dlech
Copy link
Collaborator

dlech commented Aug 2, 2021

It seems like this should raise a KeyboardInterrupt in your program. And the program is correctly written (using async with BleakClient and asyncio.run()) so it should disconnect when interrupted. So I'm not sure why it isn't working.

Does it disconnect if you just run the program from a regular terminal window instead of through PyChrarm?

@kazola
Copy link
Author

kazola commented Aug 2, 2021

Hi @dlech

I have added the following signal handler thanks to your hint.

import logging
import asyncio
import bleak.exc
import signal
from signal import SIGINT
from bleak import BleakClient, discover


logging.basicConfig(level=logging.INFO)
UUID_C = 'f0001132-0451-4000-b000-000000000000'
UUID_W = 'f0001131-0451-4000-b000-000000000000'
addr = '60:a4:23:d4:5e:8c'
disconnected_event = asyncio.Event()


def handler(signal_received, frame):
    # Handle any cleanup here
    print('SIGINT or CTRL-C detected. Exiting gracefully')
    disconnected_event.set()
    exit(0)


def notification_handler(_, data):
    print(data)


async def main(mac):
    signal.signal(SIGINT, handler)
    try:

        def disconnected_callback(client):
            print("Disconnected callback called!")
            disconnected_event.set()

        async with BleakClient(mac, disconnected_callback=disconnected_callback) as client:
            print('connected to {}'.format(mac))

            # await client.start_notify(UUID_C, notification_handler)
            # this blinks some LEDs in my hardware device :)
            # v = b'LED \r'

            while 1:
                print('.')
                # await client.write_gatt_char(UUID_W, v)
                await asyncio.sleep(3)


    except bleak.exc.BleakError as ex:
        print('-- {}'.format(ex))


if __name__ == "__main__":
    asyncio.run(main(addr))

These seems to work with another hardware design I have. I left the one I wrote this issue for at home so I cannot test right now. I will keep you updated, just tell me if you think this is clearly NOT the solution.

@dlech
Copy link
Collaborator

dlech commented Aug 2, 2021

Adding a signal handler should not be necessary. (Also, @ mentioning me creates extra notifications, so please don't use it on every post - only use it if you really need to get my attention)

@kazola
Copy link
Author

kazola commented Aug 2, 2021

Did not know about the @ thing, sorry :) will totally avoid it.
Ok, will test with the first device, the one originating this post, and let you know.
Maybe it is only necessary on Pycharm? I will try both Pycharm and standalone python execution from command line and update this post.

@dlech
Copy link
Collaborator

dlech commented Aug 2, 2021

sorry :) will totally avoid it.

Using it occasionally is no problem.

Maybe it is only necessary on Pycharm?

Since the signal handler calls exit(0) then the Python exits immediately it is certain that it will not have a chance to disconnect properly, so this doesn't seem like what you want.

@dlech dlech added the Backend: BlueZ Issues and PRs relating to the BlueZ backend label Aug 4, 2021
@koenvervloesem
Copy link
Contributor

I see the same behaviour as OP, but in a plain terminal session, no PyCharm.

  • bleak version: 0.12.1
  • Python version: 3.8.10
  • Operating System: Ubuntu 20.04.2 LTS
  • BlueZ version (bluetoothctl -v) in case of Linux: 5.53

My code to read heart rate measurement notifications from a fitness tracker:

import asyncio
import sys

import bleak

HEART_RATE_MEASUREMENT_UUID = "0000{0:x}-0000-1000-8000-00805f9b34fb".format(0x2A37)


def heart_rate_changed(handle: int, data: bytearray):
    print(f"Heart rate: {data[1]}")


async def run(address):
    try:
        async with bleak.BleakClient(address) as client:
            print(f"Connected to {address}")

            await client.start_notify(HEART_RATE_MEASUREMENT_UUID, heart_rate_changed)
            print(f"Notifications started...")
            while True:
                await asyncio.sleep(1)

    except asyncio.exceptions.TimeoutError:
        print(f"Can't connect to device {address}. Does it run a GATT server?")


if __name__ == "__main__":

    if len(sys.argv) == 2:
        ADDRESS = sys.argv[1]
        loop = asyncio.get_event_loop()
        loop.run_until_complete(run(ADDRESS))
    else:
        print("Please specify the BLE MAC address on the command line.")

If the device isn't connected before the program starts, notifications work. If I press Ctrl+C and run the program again, the device isn't found because it's still connected in the background. If I disconnect the device with bluetoothcl and run the program again, it works:

$ python3 heart-rate.py EB:76:55:B9:56:18                                    
Connected to EB:76:55:B9:56:18                                                                           
Notifications started...                                                                                                                                                                                           
Heart rate: 67                                                                                           
Heart rate: 68                                                                                                                                                                                                     
Heart rate: 68                                                                                           
Heart rate: 68                                                                                                                                                                                                     
Heart rate: 67                                                                                           
^CTraceback (most recent call last):
  File "heart-rate.py", line 32, in <module>
    loop.run_until_complete(run(ADDRESS))
  File "/usr/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
    self.run_forever()                              
  File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
    self._run_once()                                
  File "/usr/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
    event_list = self._selector.select(timeout)
  File "/usr/lib/python3.8/selectors.py", line 468, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt                                   

$ python3 heart-rate.py EB:76:55:B9:56:18                                                 
Traceback (most recent call last):
  File "heart-rate.py", line 32, in <module>
    loop.run_until_complete(run(ADDRESS))
  File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "heart-rate.py", line 15, in run
    async with bleak.BleakClient(address) as client: 
  File "/home/koan/.local/lib/python3.8/site-packages/bleak/backends/client.py", line 61, in __aenter__
    await self.connect()                            
  File "/home/koan/.local/lib/python3.8/site-packages/bleak/backends/bluezdbus/client.py", line 137, in connect                                              
    raise BleakError(bleak.exc.BleakError: Device with address EB:76:55:B9:56:18 was not found.
$ bluetoothctl disconnect EB:76:55:B9:56:18
Attempting to disconnect from EB:76:55:B9:56:18
[CHG] Device EB:76:55:B9:56:18 ServicesResolved: no
Successful disconnected
$ python3 heart-rate.py EB:76:55:B9:56:18                                                 
Connected to EB:76:55:B9:56:18
Notifications started...                            
Heart rate: 65                                      
Heart rate: 65                                      
Heart rate: 65

@dlech
Copy link
Collaborator

dlech commented Aug 28, 2021

In order to make disconnecting work correctly after an unhandled exception, replace:

        loop = asyncio.get_event_loop()
        loop.run_until_complete(run(ADDRESS))

with

        asyncio.run(run(ADDRESS))

@koenvervloesem
Copy link
Contributor

Cool, this works, thanks! I should have looked closer at OP's code.

@dlech
Copy link
Collaborator

dlech commented Oct 7, 2021

I just tried to reproduce the issue using a fresh install of:

PyCharm 2021.2.2 (Community Edition)
Build #PC-212.5284.44, built on September 14, 2021
Runtime version: 11.0.12+7-b1504.28 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Linux 5.11.0-37-generic
GC: G1 Young Generation, G1 Old Generation
Memory: 750M
Cores: 8

Current Desktop: ubuntu:GNOME

and examples/disconnect_callback.py.

The device disconnected as expected when pressing the stop button in PyCharm. So I'm not sure why your PyCharm would be different.

@dlech dlech changed the title Bleak not disconnecting automatically Bleak not disconnecting automatically when run and stopped/interrupted with PyCharm Oct 7, 2021
@kazola kazola closed this as completed Nov 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backend: BlueZ Issues and PRs relating to the BlueZ backend
Projects
None yet
Development

No branches or pull requests

3 participants