gopro / OpenGoPro

An open source interface specification to communicate with a GoPro camera with accompanying demos and tutorials.
https://gopro.github.io/OpenGoPro/
MIT License
677 stars 149 forks source link

Support Multiple Simultaneous Connections in Python SDK #124

Open PipGrylls opened 2 years ago

PipGrylls commented 2 years ago

This ticket is being used to track development / testing of simultaneous connections. The original bug request is shown below.

===================================================================

Connecting to cameras simultaneously seems to cause issues when disconnecting.

Hardware: 2x GoProHero10, Raspberry Pi 4b 2Gb

I am using an ExitStack to control the contexts but I can recreate the issue without using a context manager. Note: the wifi is not enabled as the Pi is controlled on the network over wifi and it will be disconnected if wifi is enabled.

I think there is an issue in releasing the async disconnect event. One camera will connect and disconnect fine.

Tomorrow I will edit the GoPro class to try to release whatever event is blocking and will update the issue/create a PR if I find anything.

Minimal reproduction:

from contextlib import ExitStack

from open_gopro import GoPro

camera_list = [
    {
        'target': "GoPro ****",
        'enable_wifi': False,
    },
    {
        'target': "GoPro ****",
        'enable_wifi': False,
    },
]

stack = ExitStack()
cameras = [
    stack.enter_context(
        GoPro(
            target=camera_args.get('target'),
            enable_wifi=camera_args.get('enable_wifi', False)
        )
    )
    for camera_args in camera_list
]

for camera in cameras:
    print(camera.is_ble_connected)

stack.pop_all().close()

And here is the traceback:

Traceback (most recent call last):
  File "/home/pi/GoPro/minimal_test.py", line 31, in <module>
    stack.pop_all().close()
  File "/usr/lib/python3.9/contextlib.py", line 521, in close
    self.__exit__(None, None, None)
  File "/usr/lib/python3.9/contextlib.py", line 513, in __exit__
    raise exc_details[1]
  File "/usr/lib/python3.9/contextlib.py", line 498, in __exit__
    if cb(*exc_details):
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/gopro.py", line 185, in __exit__
    self.close()
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/gopro.py", line 350, in close
    self._close_ble()
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/gopro.py", line 554, in _close_ble
    self._ble.close()
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/ble/client.py", line 108, in close
    self._controller.disconnect(self._handle)
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/ble/adapters/bleak_wrapper.py", line 299, in disconnect
    return self._as_coroutine(_async_disconnect)
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/ble/adapters/bleak_wrapper.py", line 67, in _as_coroutine
    return asyncio.run_coroutine_threadsafe(action(), self._module_loop).result(timeout)
  File "/usr/lib/python3.9/concurrent/futures/_base.py", line 440, in result
    return self.__get_result()
  File "/usr/lib/python3.9/concurrent/futures/_base.py", line 389, in __get_result
    raise self._exception
  File "/home/pi/.local/lib/python3.9/site-packages/open_gopro/ble/adapters/bleak_wrapper.py", line 295, in _async_disconnect
    await handle.disconnect()
  File "/home/pi/.local/lib/python3.9/site-packages/bleak/backends/bluezdbus/client.py", line 459, in disconnect
    reply = await self._bus.call(
  File "/home/pi/.local/lib/python3.9/site-packages/dbus_next/aio/message_bus.py", line 305, in call
    await future
RuntimeError: Task <Task pending name='Task-30' coro=<BleakWrapperController.disconnect.<locals>._async_disconnect() running at /home/pi/.local/lib/python3.9/site-packages/open_gopro/ble/adapters/bleak_wrapper.py:295> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.9/asyncio/futures.py:391]> got Future <Future pending> attached to a different loop
PipGrylls commented 2 years ago

Update: https://github.com/PipGrylls/OpenGoPro/issues/1#issuecomment-1029798791

PipGrylls commented 2 years ago

Using the following code I can connect and disconnect from two cameras without hitting the issue. This leads me to follow the logic that the event loop needs to be able to have cameras added to it or threads need to specifically carry their own cameras to work in parallel? See simple connect disconnect on a single event loop here https://gist.github.com/PipGrylls/79b3cc09a5f90e9fd3acba8cfa803100

tcamise-gpsw commented 2 years ago

Hello. While the SDK has been designed to allow control of multiple cameras simultaneously, this is currently largely untested. I do plan on getting around to this at some point but I can't say when that will be at this time.

PengKunPROO commented 1 year ago

Hi! I found the same problem, have you fixed up this issue?

tcamise-gpsw commented 1 year ago

I have not done any testing on this. It definitely needs some work.