Reconnect and keep reading from device with asyncio and BleakClient - python
This is a question on how to reach a certain goal, but also for help explaining some behaviors I don't understand since I'm quite new to both bleak and async programming in python.
I have a Bluetooth device that sends data and the goal is to read from it and in case it turns off, wait for it to turn back on again and keep reading. But I'm having problems with that last part.
Here's what I have so far (the function that does it all, I just call it with asyncio.run(read_data())):
async def read_data():
def handle_disconnect(_: BleakClient):
print("Device was disconnected, goodbye.")
def handle_response(_: int, data: bytearray):
print("received:", data)
while True:
device = await BleakScanner.find_device_by_filter(
lambda d, ad: d.name and d.name.lower() == GLOVEName.lower()
)
try:
print(vars(device))
except:
print(device)
if device is None:
await asyncio.sleep(1)
continue
client = BleakClient(device, disconnected_callback=handle_disconnect)
print(client.is_connected)
try:
await client.connect()
await client.start_notify(UART_TX_CHAR_UUID, handle_response)
except BleakError as e:
print(e)
await client.disconnect()
Here's the prints of an execution with a device disconnection in between:
{'address': 'F4:1D:97:AD:FE:39', 'name': 'GLOVE-fe39', 'details': _RawAdvData(adv=<_bleak_winrt_Windows_Devices_Bluetooth_Advertisement.BluetoothLEAdvertisementReceivedEventArgs object at 0x00000290950A8490>, scan=<_bleak_winrt_Windows_Devices_Bluetooth_Advertisement.BluetoothLEAdvertisementReceivedEventArgs object at 0x00000290950A8310>), 'rssi': -52, 'metadata': {'uuids': [], 'manufacturer_data': {89: b'\x19\x1a\x1b\x1c\x1d', 4660: b'\x01\x02\x03\x04\x05\x06\x07\x08'}}}
False
received: bytearray(b'fe39,1,281,293,312,316,293,272,315,314')
received: bytearray(b'fe39,2,290,297,312,316,296,276,307,308')
received: bytearray(b'fe39,3,295,300,313,318,298,277,302,301')
received: bytearray(b'fe39,4,279,284,292,291,276,259,282,284')
received: bytearray(b'fe39,5,263,266,266,263,249,236,254,259')
received: bytearray(b'fe39,6,279,284,292,293,276,259,277,272')
received: bytearray(b'fe39,7,244,242,236,234,219,210,228,232')
received: bytearray(b'fe39,8,224,224,207,208,192,183,201,208')
received: bytearray(b'fe39,9,228,225,216,213,195,191,207,208')
received: bytearray(b'fe39,10,202,204,187,182,172,168,183,192')
None
None
received: bytearray(b'fe39,2,290,297,312,316,296,276,307,308')
received: bytearray(b'fe39,3,295,300,313,318,298,277,302,301')
received: bytearray(b'fe39,4,279,284,292,291,276,259,282,284')
received: bytearray(b'fe39,5,263,266,266,263,249,236,254,259')
received: bytearray(b'fe39,6,279,284,292,293,276,259,277,272')
received: bytearray(b'fe39,7,244,242,236,234,219,210,228,232')
received: bytearray(b'fe39,8,224,224,207,208,192,183,201,208')
received: bytearray(b'fe39,9,228,225,216,213,195,191,207,208')
None
Device was disconnected, goodbye.
None
{'address': 'F4:1D:97:AD:FE:39', 'name': 'GLOVE-fe39', 'details': _RawAdvData(adv=<_bleak_winrt_Windows_Devices_Bluetooth_Advertisement.BluetoothLEAdvertisementReceivedEventArgs object at 0x00000290951D73F0>, scan=<_bleak_winrt_Windows_Devices_Bluetooth_Advertisement.BluetoothLEAdvertisementReceivedEventArgs object at 0x00000290951D7030>), 'rssi': -63, 'metadata': {'uuids': [], 'manufacturer_data': {89: b'\x19\x1a\x1b\x1c\x1d', 4660: b'\x01\x02\x03\x04\x05\x06\x07\x08'}}}
False
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.3568.0_x64__qbz5n2kfra8p0\lib\asyncio\locks.py", line 226, in wait
await fut
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\tomas\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\bleak\backends\winrt\client.py", line 380, in connect
await asyncio.gather(wait_connect_task, wait_get_services_task)
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\tomas\Documents\Glooma\py_files\uart_service.py", line 110, in <module>
asyncio.run(uart_terminal())
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.3568.0_x64__qbz5n2kfra8p0\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.3568.0_x64__qbz5n2kfra8p0\lib\asyncio\base_events.py", line 647, in run_until_complete
return future.result()
File "C:\Users\tomas\Documents\Glooma\py_files\uart_service.py", line 76, in uart_terminal
await client.connect()
File "C:\Users\tomas\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\bleak\__init__.py", line 471, in connect
return await self._backend.connect(**kwargs)
File "C:\Users\tomas\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\bleak\backends\winrt\client.py", line 380, in connect
await asyncio.gather(wait_connect_task, wait_get_services_task)
File "C:\Users\tomas\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\async_timeout\__init__.py", line 129, in __aexit__
self._do_exit(exc_type)
File "C:\Users\tomas\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\async_timeout\__init__.py", line 212, in _do_exit
raise asyncio.TimeoutError
asyncio.exceptions.TimeoutError
So, first it connects well, and then comes my first question, why does the client.is_connected return false if it is connected?
I then press the button on the device which sends data and gets printed in the terminal, I then press the button again and it stops. So far so good.
I can keep pressing the button as many times as I like and it will keep starting and stopping sending and printing data which is the intended behaviour. Another question I have is that it seems like there's two things happening here, the loop checking for the device and then printing None because since it was already connected the Scanner can't find it, which goes inside the if clause, waits a second and starts again. But at the same time the notifications are going through and getting printed. Seems like two threads going on printing on the terminal, which I don't understand. I assume it is related to asynchronous programming.
After that I turn off the device and turn it back on. The device is found again going by the prints, again with a False in the is_connected. But this second time it fails with a time out error in await client.connect(). Why? What am I doing wrong here?
I've tried adding some code to run the await client.disconnect when I turn off the device, before connecting again, and that doesn't throw the timeout error on await client.connect(), but it just stays in the loop printing None and doesn't read anything when the device sends data.
Any help either explaining this behavior or with a solution to the problem would be appreciated.
Thanks.
Related
Could someone help me find the error in this code?
Here is my code: from discord.ext import commands import os import discord import requests import json my_secret = os.environ['MY_TOKEN'] GUILD = os.getenv('DISCORD_GUILD') bot = commands.Bot(command_prefix='!') #prefix discord.CustomActivity("Jaxon\'s brain melt", ActivityType="watching", emoji=None) #bot.command(name="enfr", help="Translates text from english to french") async def enfr(ctx, text): response = requests.get("https://api.mymemory.translated.net/get?q=" + text + "&langpair=en|fr") json_data = json.loads(response.text) translation = json_data.get("translatedText") + "test" print(response.status_code) await ctx.channel.send(translation) bot.run(my_secret) This is the error I get: 200 Ignoring exception in command enfr: Traceback (most recent call last): File "/home/runner/translate-Bot/venv/lib/python3.8/site-packages/discord/ext/commands/core.py", line 85, in wrapped ret = await coro(*args, **kwargs) File "main.py", line 22, in enfr await ctx.channel.send(translation) File "/home/runner/translate-Bot/venv/lib/python3.8/site-packages/discord/abc.py", line 1065, in send data = await state.http.send_message(channel.id, content, tts=tts, embed=embed, File "/home/runner/translate-Bot/venv/lib/python3.8/site-packages/discord/http.py", line 254, in request raise HTTPException(r, data) discord.errors.HTTPException: 400 Bad Request (error code: 50006): Cannot send an empty message The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/home/runner/translate-Bot/venv/lib/python3.8/site-packages/discord/ext/commands/bot.py", line 939, in invoke await ctx.command.invoke(ctx) File "/home/runner/translate-Bot/venv/lib/python3.8/site-packages/discord/ext/commands/core.py", line 863, in invoke await injected(*ctx.args, **ctx.kwargs) File "/home/runner/translate-Bot/venv/lib/python3.8/site-packages/discord/ext/commands/core.py", line 94, in wrapped raise CommandInvokeError(exc) from exc discord.ext.commands.errors.CommandInvokeError: Command raised an exception: HTTPException: 400 Bad Request (error code: 50006): Cannot send an empty message It worked a few days ago and now it doesn't :( There is one other command, but it doesn't don't affect this one so I removed it from the code to clear it up a bit. I get that error when I try to run the command !enfr hello I don't know if this is the correct place to ask, my last questions were disliked by some people.
The traceback specifies the error is Cannot send an empty message. Specifically: discord.ext.commands.errors.CommandInvokeError: Command raised an exception: HTTPException: 400 Bad Request (error code: 50006): Cannot send an empty message Looking at it more closely you can find the line of your file where the Exception is being thrown. File "main.py", line 22, in enfr await ctx.channel.send(translation) From these it seems quite clear that translation is None or an empty string, and that the function ctx.channel.send throws an Exception when that is the case. Now try to figure out why translation is empty by reviewing how you're creating it in the first place, and account for these edge-cases. Note: Reading traceback logs might seem daunting at first, but they're quite informative (at worst showing you where in your code the issue is). Don't panic and just go through them slowly :)
Cannot close a running event loop
What is the way to close the discord.py bot loop once tasks are done? Added: nest_asyncio.apply() Tried: bot.logout(), bot.close(), bot.cancel() Code: async def helper(bot, discord_user_feed): nest_asyncio.apply() await bot.wait_until_ready() # await asyncio.sleep(10) for each_id, events_list in discord_user_feed.items(): discord_user = await bot.fetch_user(int(each_id)) for each_one in events_list: msg_sent = True while msg_sent: await discord_user.send(each_one) msg_sent = False await bot.close() return 'discord messages sent' async def discord_headlines(request): nest_asyncio.apply() discord_user_feed = await get_details() bot = commands.Bot(command_prefix='!') bot.loop.create_task(helper(bot, discord_user_feed)) message = bot.run('my_token') return HttpResponse(message) I can be able to send messages to discord users using id and discord bot. But, even after, django view api is continuously running and getting the error. It needs to return a response message - discord messages sent. Error: Exception in callback <TaskWakeupMethWrapper object at 0x000001C852D2DA38>(<Future finis...SWdZVtXT7E']}>) handle: <Handle <TaskWakeupMethWrapper object at 0x000001C852D2DA38>(<Future finis...SWdZVtXT7E']}>)> Traceback (most recent call last): File "E:\sd\envs\port\lib\asyncio\events.py", line 145, in _run self._callback(*self._args) KeyError: <_WindowsSelectorEventLoop running=True closed=False debug=False> Internal Server Error: /api/v0/discord_headlines/ Traceback (most recent call last): File "E:\sd\envs\port\lib\site-packages\django\core\handlers\exception.py", line 47, in inner response = get_response(request) File "E:\sd\envs\port\lib\site-packages\django\core\handlers\base.py", line 179, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "E:\sd\envs\port\lib\site-packages\asgiref\sync.py", line 139, in __call__ return call_result.result() File "E:\sd\envs\port\lib\concurrent\futures\_base.py", line 425, in result return self.__get_result() File "E:\sd\envs\port\lib\concurrent\futures\_base.py", line 384, in __get_result raise self._exception File "E:\sd\envs\port\lib\site-packages\asgiref\sync.py", line 204, in main_wrap result = await self.awaitable(*args, **kwargs) File "E:\back-end\port_api\views\discord_integration.py", line 110, in discord_headlines message = bot.run('my_token') File "E:\sd\envs\port\lib\site-packages\discord\client.py", line 719, in run _cleanup_loop(loop) File "E:\sd\envs\port\lib\site-packages\discord\client.py", line 95, in _cleanup_loop loop.close() File "E:\sd\envs\port\lib\asyncio\selector_events.py", line 107, in close raise RuntimeError("Cannot close a running event loop") RuntimeError: Cannot close a running event loop [13/Jun/2021 01:07:34] "GET /api/v0/discord_headlines/ HTTP/1.1" 500 109010 These are the versions: Python 3.6.4, Django 3.1, discord.py 1.7.3, Asyncio, Anaconda-4.9.2, Windows-10
I've not used older versions so I can't attest to the accuracy of this answer for pre-2021 releases (v1.6.0+) Discord.py. When starting a discord Client or Bot via run(*args, **kwargs) there is actually no class method to stop it. Per the Discord.py Documentations Discord.py discord.Client.run: A blocking call that abstracts away the event loop initialisation from you. If you want more control over the event loop then this function should not be used. Use start() coroutine or connect() + login() And it's effectively equal to: # run(*args, **kwargs) is basically try: # Start the bot on asyncio event loop with using .start() coroutine loop.run_until_complete(start(*args, **kwargs)) except KeyboardInterrupt: # Invoke close() coroutine on the same loop if interrupt loop.run_until_complete(close()) # Cancel any other lingering tasks and such finally: loop.close() So the unrewarding but accurate answer with your current attempted implementation is simply... you can't. If you wanna drop some more info on the webserver library your using in response or via a message, I can help craft you something functional. If your webserver is fully asyncio integrated, your best bet is seeing if the library has a way to add tasks to its existing event loop, or if you pass one to it. Sanic allows for this in its decorator startup/shutdown methods (ex. after_server_start, etc). Store the reference to the bot instance somewhere and pass it around as needed. I'll try and keep an eye on here for a response or DM and try to help get you functional and we can post updated code answer here, there's a big lack of solid answers for using discord.py in more complex cases within other applications/webservices and I just ran into a similiar issue with writing a discord.py bot that accepted webhook data and posted embeds to appropriate channels based on some filters.
Django Channels Redis: Exception inside application: Lock is not acquired
Fully loaded multi-tenant Django application with 1000's of WebSockets using Daphne/Channels, running fine for a few months and suddenly tenants all calling it the support line the application running slow or outright hanging. Narrowed it down to WebSockets as HTTP REST API hits came through fast and error free. None of the application logs or OS logs indicate some issue, so only thing to go on is the exception noted below. It happened over and over again here and there throughout 2 days. I don't expect any deep debugging help, just some off-the-cuff advice on possibilities. AWS Linux 1 Python 3.6.4 Elasticache Redis 5.0 channels==2.4.0 channels-redis==2.4.2 daphne==2.5.0 Django==2.2.13 Split configuration HTTP served by uwsgi, daphne serves asgi, Nginx May 10 08:08:16 prod-b-web1: [pid 15053] [version 119.5.10.5086] [tenant_id -] [domain_name -] [pathname /opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/daphne/server.py] [lineno 288] [priority ERROR] [funcname application_checker] [request_path -] [request_method -] [request_data -] [request_user -] [request_stack -] Exception inside application: Lock is not acquired. Traceback (most recent call last): File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels_redis/core.py", line 435, in receive real_channel File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels_redis/core.py", line 484, in receive_single await self.receive_clean_locks.acquire(channel_key) File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels_redis/core.py", line 152, in acquire return await self.locks[channel].acquire() File "/opt/python3.6/lib/python3.6/asyncio/locks.py", line 176, in acquire yield from fut concurrent.futures._base.CancelledError During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels/sessions.py", line 183, in __call__ return await self.inner(receive, self.send) File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels/middleware.py", line 41, in coroutine_call await inner_instance(receive, send) File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels/consumer.py", line 59, in __call__ [receive, self.channel_receive], self.dispatch File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels/utils.py", line 58, in await_many_dispatch await task File "/opt/releases/r119.5.10.5086/env/lib/python3.6/site-packages/channels_redis/core.py", line 447, in receive self.receive_lock.release() File "/opt/python3.6/lib/python3.6/asyncio/locks.py", line 201, in release raise RuntimeError('Lock is not acquired.') RuntimeError: Lock is not acquired.
First, lets have a look at the source of the RuntimeError: Lock is not acquired. error. As given by the traceback, the release() method in the file /opt/python3.6/lib/python3.6/asyncio/locks.py is defined like so: def release(self): """Release a lock. When the lock is locked, reset it to unlocked, and return. If any other coroutines are blocked waiting for the lock to become unlocked, allow exactly one of them to proceed. When invoked on an unlocked lock, a RuntimeError is raised. There is no return value. """ if self._locked: self._locked = False self._wake_up_first() else: raise RuntimeError('Lock is not acquired.') A primitive lock is a synchronization primitive that is not owned by a particular thread when locked. When attempting to release an unlocked lock by calling the release() method, the RuntimeError will be raised, as the method should only be called in the locked state. The state changes to unlocked when called in the locked state. Now for the previous error raised in the acquire() method in the same file, the acquire() method is defined like so: async def acquire(self): """Acquire a lock. This method blocks until the lock is unlocked, then sets it to locked and returns True. """ if (not self._locked and (self._waiters is None or all(w.cancelled() for w in self._waiters))): self._locked = True return True if self._waiters is None: self._waiters = collections.deque() fut = self._loop.create_future() self._waiters.append(fut) # Finally block should be called before the CancelledError # handling as we don't want CancelledError to call # _wake_up_first() and attempt to wake up itself. try: try: await fut finally: self._waiters.remove(fut) except exceptions.CancelledError: if not self._locked: self._wake_up_first() raise self._locked = True return True So in order for the concurrent.futures._base.CancelledError error you're getting to be raised, the await fut must've caused the issue. To fix it, you can have a look at Awaiting an asyncio.Future raises concurrent.futures._base.CancelledError instead of waiting for a value/exception to be set Basically, you might have an awaitable in your code that you didn't await, and by not awaiting it, you never handed control back to the event loop or store the awaitable, causing it to be immediately cleaned up, completely cancelling it (and all of the awaitables it controlled). Simply make sure you await the results of the awaitables in your code, finding any you missed.
When running bot sample code, I get this error
My code is exactly This (With my token of course) When I run it, my bot starts up as normal, but when a new person is added to the server, i get this. ------ Ignoring exception in on_member_join Traceback (most recent call last): File "C:\Users\USRNAME\AppData\Local\Programs\Python\Python36\lib\site-packages\discord\client.py", line 307, in _run_event yield from getattr(self, event)(*args, **kwargs) File "test.py", line 9, in on_member_join await client.send_message(server, fmt.format(member, server)) File "C:\Users\USRNAME\AppData\Local\Programs\Python\Python36\lib\site-packages\discord\client.py", line 1152, in send_message data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts, embed=embed) File "C:\Users\USRNAME\AppData\Local\Programs\Python\Python36\lib\site-packages\discord\http.py", line 198, in request raise NotFound(r, data) discord.errors.NotFound: NOT FOUND (status code: 404): Unknown Channel (Sorry about it not being in a code block, I'm new to stack exchange) Any help would be appreciated. Thanks
This will not work anymore because discord removed default channels so sending it to server will not work. You should replace server with discord.Object('insert channel id') if you are using async or discord.Object(insert channel id) if you are using the rewrite branch. Note the string vs int difference. Good luck :)
Google PubSub python client returning StatusCode.UNAVAILABLE
I am trying to establish a long running Pull subscription to a Google Cloud PubSub topic. I am using a code very similar to the example given in the documentation here, i.e.: def receive_messages(project, subscription_name): """Receives messages from a pull subscription.""" subscriber = pubsub_v1.SubscriberClient() subscription_path = subscriber.subscription_path( project, subscription_name) def callback(message): print('Received message: {}'.format(message)) message.ack() subscriber.subscribe(subscription_path, callback=callback) # The subscriber is non-blocking, so we must keep the main thread from # exiting to allow it to process messages in the background. print('Listening for messages on {}'.format(subscription_path)) while True: time.sleep(60) The problem is that I'm receiving the following traceback sometimes: Exception in thread Consumer helper: consume bidirectional stream: Traceback (most recent call last): File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner self.run() File "/usr/lib/python3.5/threading.py", line 862, in run self._target(*self._args, **self._kwargs) File "/path/to/google/cloud/pubsub_v1/subscriber/_consumer.py", line 248, in _blocking_consume self._policy.on_exception(exc) File "/path/to/google/cloud/pubsub_v1/subscriber/policy/thread.py", line 135, in on_exception raise exception File "/path/to/google/cloud/pubsub_v1/subscriber/_consumer.py", line 234, in _blocking_consume for response in response_generator: File "/path/to/grpc/_channel.py", line 348, in __next__ return self._next() File "/path/to/grpc/_channel.py", line 342, in _next raise self grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with (StatusCode.UNAVAILABLE, The service was unable to fulfill your request. Please try again. [code=8a75])> I saw that this was referenced in another question but here I am asking to how to handle it properly in Python. I have tried to wrap the request in an exception but it seems to run in the background and I am not able to retry in case of that error.
A somewhat hacky approach that is working for me is a custom policy_class. The default one has an on_exception function that ignores DEADLINE_EXCEEDED. You can make a class that inherits the default and also ignores UNAVAILABLE. Mine looks like this: from google.cloud import pubsub from google.cloud.pubsub_v1.subscriber.policy import thread import grpc class AvailablePolicy(thread.Policy): def on_exception(self, exception): """The parent ignores DEADLINE_EXCEEDED. Let's also ignore UNAVAILABLE. I'm not sure what triggers that error, but if you ignore it, your subscriber seems to work just fine. It's probably an intermittent thing and it reconnects later if you just give it a chance. """ # If this is UNAVAILABLE, then we want to retry. # That entails just returning None. unavailable = grpc.StatusCode.UNAVAILABLE if getattr(exception, 'code', lambda: None)() == unavailable: return # For anything else, fallback on super. super(AvailablePolicy, self).on_exception(exception) subscriber = pubsub.SubscriberClient(policy_class=AvailablePolicy) # Continue to set up as normal. It looks a lot like the original on_exception just ignores a different error. If you want, you can add some logging whenever the exception is thrown and verify that everything still works. Future messages will still come through.