usage.rst 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. :github_url: https://github.com/gopro/OpenGoPro/tree/main/demos/python/sdk_wireless_camera_control
  2. Usage
  3. *****
  4. This section will describe some high level information of how to use the Open GoPro module. For more detailed
  5. information, see the :ref:`Interfaces<API Reference>` section. For just running the demos, see the
  6. :ref:`QuickStart<QuickStart Guide>` section.
  7. Overview
  8. ========
  9. There are two top-level interfaces to communicate with a GoPro camera:
  10. - :class:`~open_gopro.gopro_wired.WiredGoPro` to communicate with the GoPro via USB HTTP
  11. - :class:`~open_gopro.gopro_wireless.WirelessGoPro` to communicate with the GoPro via BLE and optionally HTTP (WiFi / Camera on the Home Network)
  12. An individual instance of one of the above classes corresponds to a (potentially not yet) connected GoPro
  13. camera resource. The general procedure to communicate with the GoPro is:
  14. 1. Identify and :ref:`open<Opening>` the connection to the target GoPro
  15. 2. :ref:`Send Messages<Sending Messages>` and :ref:`Receive Responses<Handling Responses>` via BLE / HTTP
  16. 3. Gracefully :ref:`close<Closing>` the connection with the GoPro
  17. .. tip:: There is a lot of useful logging throughout the Open GoPro package. See
  18. :ref:`troubleshooting <troubleshooting>` for more info.
  19. Asyncio
  20. =======
  21. This package is `asyncio <https://docs.python.org/3/library/asyncio.html>`_-based which means that its awaitable
  22. methods need to be called from an async coroutine. For the code snippets throughout this documentation, assume that this
  23. is accomplished in the same manner as the demo scripts provided:
  24. .. code-block:: python
  25. import asyncio
  26. async def main() -> None:
  27. # Put our code here
  28. if __name__ == "__main__":
  29. asyncio.run(main())
  30. Opening
  31. =======
  32. Before communicating with a camera, the camera resource must be "opened". This can be done either with or without
  33. the context manager. See the below sections for opening information specific to Wired / Wireless.
  34. Wireless Opening
  35. ----------------
  36. The Wireless GoPro client can be opened either with the context manager:
  37. .. code-block:: python
  38. from open_gopro import WirelessGoPro
  39. async with WirelessGoPro() as gopro:
  40. print("Yay! I'm connected via BLE, Wifi, opened, and ready to send / get data now!")
  41. # Send some messages now
  42. \...or without the context manager:
  43. .. code-block:: python
  44. from open_gopro import WirelessGoPro
  45. gopro = WirelessGoPro()
  46. await gopro.open()
  47. print("Yay! I'm connected via BLE, Wifi, opened, and ready to send / get data now!")
  48. # Send some messages now
  49. If, as above, an identifier is not passed to the `WirelessGoPro`, a network scan will occur and the first discovered
  50. GoPro will be used.
  51. See the API Reference for :class:`~open_gopro.gopro_wireless.WirelessGoPro` for all the arguments that can be passed.
  52. The most common argument to configure is the `interface` argument which specifies the type of wireless connections to use.
  53. The most common combinations are:
  54. - :attr:`~open_gopro.gopro_wireless.WirelessGoPro.Interface.BLE`: The GoPro will only be connected via BLE. The following will be performed:
  55. #. scan BLE advertisements for camera
  56. #. connect to camera via BLE
  57. #. enable notifications
  58. #. pair (if needed)
  59. #. discover characteristics
  60. #. initialize (register for internal state notifications)
  61. #. discover Open GoPro version
  62. #. set the camera's datetime to match the host computer's datetime
  63. - :attr:`~open_gopro.gopro_wireless.WirelessGoPro.Interface.BLE` and :attr:`~open_gopro.gopro_wireless.WirelessGoPro.Interface.WIFI_AP`: The GoPro will first connect via BLE (performing the above steps) and then connect via WiFi. The following will be performed:
  64. #. discover camera's WiFi SSID and password
  65. #. connect via WiFi
  66. - :attr:`~open_gopro.gopro_wireless.WirelessGoPro.Interface.COHN`: Communicate only via COHN to a previously provisioned GoPro. The following will be performed:
  67. #. mDNS scan for camera
  68. #. attempt to retrieve camera's COHN credentials from COHN database
  69. #. use COHN credentials when communicating via HTTP
  70. Wired Opening
  71. -------------
  72. The Wired GoPro client can be opened either with the context manager:
  73. .. code-block:: python
  74. from open_gopro import WiredGoPro
  75. async with WiredGoPro() as gopro:
  76. print("Yay! I'm connected via USB, opened, and ready to send / get data now!")
  77. # Send some messages now
  78. \...or without the context manager:
  79. .. code-block:: python
  80. from open_gopro import WiredGoPro
  81. gopro = WiredGoPro()
  82. await gopro.open()
  83. print("Yay! I'm connected via USB, opened, and ready to send / get data now!")
  84. # Send some messages now
  85. If, as above, an identifier is not passed to the `WiredGoPro`, the mDNS server will be queried during opening to search
  86. for a connected GoPro.
  87. Common Opening
  88. --------------
  89. The GoPro's state can be checked via several properties.
  90. - :meth:`~open_gopro.gopro_base.GoProBase.is_ble_connected`
  91. - :meth:`~open_gopro.gopro_base.GoProBase.is_http_connected`: Do we have a WiFi AP or COHN communication channel?
  92. - :meth:`~open_gopro.gopro_base.GoProBase.is_open`: Is the camera ready for the client to use it for communication?
  93. API Version
  94. ^^^^^^^^^^^
  95. One of the steps during the opening sequence is to query the camera's Open GoPro API version. This SDK only
  96. supports Open GoPro API Version 2.0 so will raise an `InvalidOpenGoProVersion` if the connected camera is
  97. using anything else.
  98. The version string can be accessed via the :meth:`~open_gopro.gopro_base.GoProBase.version` property.
  99. Camera Readiness
  100. ^^^^^^^^^^^^^^^^
  101. A message can not be sent to the camera if it is not ready where "ready" is defined as not encoding and not
  102. busy. These two states are managed automatically by the `WirelessGoPro` instance such that a call to any
  103. message will block until the camera is ready. They are combined into the following ready state:
  104. - :meth:`~open_gopro.gopro_base.GoProBase.is_ready`
  105. For example,
  106. .. code-block:: python
  107. async with WirelessGoPro() as gopro:
  108. # A naive check for it to be ready
  109. while not await gopro.is_ready:
  110. pass
  111. To reiterate...it is not needed or recommended to worry about this as the internal state is managed automatically
  112. by the `WirelessGoPro` instance. Just know that most commands will be (asynchronously) blocked until the camera is ready.
  113. Sending Messages
  114. ================
  115. Once a `WirelessGoPro` or `WiredGoPro` instance has been :ref:`opened<opening>`, it is now possible to send
  116. messages to the camera (provided that the camera is :ref:`ready<camera readiness>`). Messages are accessed
  117. by transport protocol where the superset of message groups are:
  118. .. list-table::
  119. :widths: 50 50 50 50 50
  120. :header-rows: 1
  121. * - Message Group
  122. - WiredGoPro
  123. - WirelessGoPro (HTTP and BLE available)
  124. - WirelessGoPro (only BLE available)
  125. - WirelessGoPro (only HTTP available)
  126. * - :meth:`~open_gopro.gopro_base.GoProBase.http_command`
  127. - ✔️
  128. - ✔️
  129. - ❌
  130. - ✔️
  131. * - :meth:`~open_gopro.gopro_base.GoProBase.http_setting`
  132. - ✔️
  133. - ✔️
  134. - ❌
  135. - ✔️
  136. * - :meth:`~open_gopro.gopro_base.GoProBase.ble_command`
  137. - ❌
  138. - ✔️
  139. - ✔️
  140. - ❌
  141. * - :meth:`~open_gopro.gopro_base.GoProBase.ble_setting`
  142. - ❌
  143. - ✔️
  144. - ✔️
  145. - ❌
  146. * - :meth:`~open_gopro.gopro_base.GoProBase.ble_status`
  147. - ❌
  148. - ✔️
  149. - ✔️
  150. - ❌
  151. In the case where a given group of messages is not supported, a `NotImplementedError` will be returned when
  152. the relevant property is accessed.
  153. All messages are communicated via one of two strategies:
  154. - Performing synchronous :ref:`data operations<Synchronous Data Operations>` to send a message and receive a GoPro Response
  155. - Registering for :ref:`asynchronous push notifications<Asynchronous Push Notifications>` to retrieve an observable stream.
  156. .. note:: For the remainder of this document, the term (a)synchronous is in the context of communication with the camera.
  157. Do not confuse this with `asyncio`: all operations from the user's perspective are awaitable.
  158. Both of these patterns will be expanded upon below. But first, a note on selecting parameters for use with messages...
  159. Synchronous Data Operations
  160. ---------------------------
  161. .. note:: Unless explicitly specified in the :ref:`Asynchronous<Asynchronous Push Notifications>` section,
  162. all messages are synchronous messages.
  163. This section refers to sending commands, getting settings / statuses, and setting settings. In all cases here,
  164. the method will await until a :ref:`response<handling responses>` is received.
  165. Commands
  166. ^^^^^^^^
  167. Commands are callable instance attributes of a Messages class instance
  168. (i.e. :class:`~open_gopro.api.ble_commands.BleCommands` or
  169. :class:`~open_gopro.api.http_commands.HttpCommands`), thus they can be called directly:
  170. .. code-block:: python
  171. async with WirelessGoPro() as gopro:
  172. await gopro.ble_command.set_shutter(shutter=Params.Toggle.ENABLE)
  173. await gopro.http_command.set_shutter(shutter=Params.Toggle.DISABLE)
  174. .. warning:: Most commands specifically require `keyword-only arguments <https://peps.python.org/pep-3102/>`_. You can
  175. not optionally use positional arguments in such cases as this will affect functionality.
  176. Statuses
  177. ^^^^^^^^
  178. Statuses are instances of a BleStatus(:class:`~open_gopro.api.builders.BleStatusFacade`). They can be read
  179. synchronously using their `get_value` method as such:
  180. .. code-block:: python
  181. async with WirelessGoPro() as gopro:
  182. is_encoding = await gopro.ble_status.encoding.get_value()
  183. battery = await gopro.ble_status.internal_battery_percentage.get_value()
  184. It is also possible to read all statuses at once via:
  185. .. code-block:: python
  186. async with WirelessGoPro() as gopro:
  187. statuses = await gopro.ble_command.get_camera_statuses()
  188. .. note::
  189. HTTP can not access individual statuses. Instead it can use
  190. :meth:`~open_gopro.api.http_commands.HttpCommands.get_camera_state`
  191. to retrieve all of them (as well as all of the settings) at once
  192. Settings
  193. ^^^^^^^^
  194. Settings are instances of a BleSetting(:class:`~open_gopro.api.builders.BleSettingFacade`)
  195. or HttpSetting(:class:`~open_gopro.api.builders.HttpSetting`). They can be interacted synchronously in several
  196. ways.
  197. Their values can be read (via BLE only) using the `get_value` method as such:
  198. .. code-block:: python
  199. async with WirelessGoPro() as gopro:
  200. resolution = await gopro.ble_setting.video_resolution.get_value()
  201. fov = await gopro.ble_setting.video_lens.get_value()
  202. It is also possible to read all settings at once via:
  203. .. code-block:: python
  204. async with WirelessGoPro() as gopro:
  205. settings = await gopro.ble_command.get_camera_settings()
  206. .. note::
  207. HTTP can not access individual settings. Instead it can use
  208. :meth:`~open_gopro.api.http_commands.HttpCommands.get_camera_state`
  209. to retrieve all of them (as well as all of the statuses) at once.
  210. Depending on the camera's current state, settings will have differing capabilities. It is possible to query
  211. the current capabilities for a given setting (via BLE only) using the `get_capabilities_values` method as such:
  212. .. code-block:: python
  213. async with WirelessGoPro() as gopro:
  214. capabilities = await gopro.ble_setting.video_resolution.get_capabilities_values()
  215. Settings' values can be set (via either BLE or WiFI) using the `set` method as such:
  216. .. code-block:: python
  217. async with WirelessGoPro() as gopro:
  218. await gopro.ble_setting.video_resolution.set(constants.settings.VideoResolution.NUM_4K)
  219. await gopro.http_setting.video_lens.set(constants.settings.VideoLens.LINEAR)
  220. Asynchronous Push Notifications
  221. -------------------------------
  222. This section describes how to register for and handle asynchronous push notifications. This is only relevant for BLE.
  223. These operations will send commands to the camera to register for update notifications and then return an
  224. :class:`~open_gopro.domain.gopro_observable.GoProObservable` object which can be used to retrieve
  225. create :class:`~open_gopro.domain.observable.Observer` objects to asynchronously retrieve the updates.
  226. .. note::
  227. It is recommended to use the :class:`~open_gopro.domain.gopro_observable.GoProObservable` as a context manager to ensure that
  228. notifications are properly unregistered for when finished.
  229. It is possible to enable push notifications for any of the following:
  230. - setting values via :meth:`~open_gopro.api.builders.BleSettingFacade.get_value_observable`
  231. - setting capabilities via :meth:`~open_gopro.api.builders.BleSettingFacade.get_capabilities_observable`
  232. - status values via :meth:`~open_gopro.api.builders.BleStatusFacade.get_value_observable`
  233. Once executed, the camera will send a push notification when the relevant setting / status changes.
  234. .. note::
  235. All of these methods return the `Observable` wrapped in a `Result <https://returns.readthedocs.io/en/latest/pages/result.html>`_
  236. object for failure handling. The `Result` object can be unwrapped using the `unwrap` method.
  237. Here is an example of registering for and receiving FPS updates:
  238. .. code-block:: python
  239. async with WirelessGoPro() as gopro:
  240. # Send a command to register for FPS value notifications and unwrap the result to get the observable
  241. async with (await gopro.ble_setting.frames_per_second.get_value_observable()).unwrap() as fps_observable:
  242. # Start observing to print each update
  243. async for fps in fps_observable.observe():
  244. console.print(f"FPS: {fps}")
  245. # break at some point...
  246. break
  247. # FPS value updates are registered when exiting the context manager
  248. The observer can be mapped, filtered, etc by functions from the `asyncstdlib <https://asyncstdlib.readthedocs.io/en/stable/>`_
  249. as well as helper methods from the base :class:`~open_gopro.domain.observable.Observer`.
  250. It is also possible to register / unregister for **all** settings, statuses, and / or capabilities
  251. via one API call using the following commands:
  252. - register for all setting notifications via :meth:`~open_gopro.api.ble_commands.BleCommands.get_observable_for_all_settings`
  253. - register for all status notifications via :meth:`~open_gopro.api.ble_commands.BleCommands.get_observable_for_all_statuses`
  254. - register for all capability notifications via :meth:`~open_gopro.api.ble_commands.BleCommands.get_observable_for_all_capabilities`
  255. Handling Responses
  256. ==================
  257. Unless otherwise stated, all commands, settings, and status operations return a `GoProResp`
  258. (:class:`~open_gopro.models.response.GoProResp`) which is a container around the `data` payload with some helper
  259. functions.
  260. Response Structure
  261. ------------------
  262. A `GoProResp` has the following relevant attributes / properties for the end user:
  263. - | :meth:`~open_gopro.models.response.GoProResp.identifier`: identifier of the completed operation.
  264. | This will vary based on what type the response is and will also contain the most specific identification information.
  265. - UUID if a direct BLE characteristic read
  266. - CmdId if an Open GoPro BLE Operation
  267. - endpoint string if a Wifi HTTP operation
  268. - :meth:`~open_gopro.models.response.GoProResp.protocol`: the communication protocol where the response was received
  269. - :meth:`~open_gopro.models.response.GoProResp.status`: the status returned from the camera
  270. - :meth:`~open_gopro.models.response.GoProResp.data`: JSON serializable dict containing the responded data
  271. - :meth:`~open_gopro.models.response.GoProResp.ok`: Is this a successful response?
  272. The response object can be serialized to a JSON string with the default Python `str()` function. Note that
  273. the `identifier` and `status` attributes are appended to the JSON.
  274. For example, first let's connect, send a command, and print the repsonse:
  275. .. code-block:: python
  276. async with WirelessGoPro() as gopro:
  277. response = await gopro.ble_setting.video_resolution.get_value()
  278. print(response)
  279. This prints as:
  280. .. code-block:: console
  281. {
  282. "id" : "QueryCmdId.GET_SETTING_VAL",
  283. "status" : "ErrorCode.SUCCESS",
  284. "protocol" : "Protocol.BLE",
  285. "data" : "VideoResolution.NUM_4K",
  286. }
  287. Now let's inspect the responses various attributes / properties:
  288. .. code-block:: python
  289. print(response.identifier)
  290. print(response.status)
  291. print(response.protocol)
  292. print(response.data)
  293. which prints as:
  294. .. code-block:: console
  295. QueryCmdId.GET_SETTING_VAL
  296. ErrorCode.SUCCESS
  297. Protocol.BLE
  298. VideoResolution.NUM_4
  299. Data Access
  300. -----------
  301. The response data is stored in the `data` attribute (:meth:`~open_gopro.models.response.GoProResp.data`) and its type
  302. is specified via the Generic type specified in the corresponding command signature where the response is defined.
  303. For example, consider :meth:`~open_gopro.api.ble_commands.BleCommands.get_hardware_info`. It's signature is:
  304. .. code-block:: python
  305. async def get_hardware_info(self) -> GoProResp[CameraInfo]:
  306. ...
  307. Therefore, its response's `data` property is of type :meth:`~open_gopro.models.general.CameraInfo`. Continuing the
  308. example from above:
  309. .. code-block:: python
  310. async with WirelessGoPro() as gopro:
  311. response = await gopro.ble_command.get_hardware_info()
  312. print(response.data)
  313. which prints as:
  314. .. code-block:: console
  315. {
  316. "model_number" : "62",
  317. "model_name" : "HERO12 Black",
  318. "firmware_version" : "H23.01.01.99.54",
  319. "serial_number" : "C3501324500711",
  320. "ap_mac_addr" : "2674f7f65f38",
  321. "ap_ssid" : "GP24500711",
  322. }
  323. Closing
  324. =======
  325. It is important to close the camera resource when you are done with it. This can be done in two ways. If the context
  326. manager was used, it will automatically be closed when exiting, i.e.:
  327. .. code-block:: python
  328. with WirelessGoPro() as gopro:
  329. # Do some things.
  330. pass
  331. # Then when finished...
  332. # The camera resource is closed now!!
  333. Otherwise, you will need to manually call the `close` method, i.e.:
  334. .. code-block:: python
  335. gopro = WirelessGoPro()
  336. await gopro.open()
  337. print("Yay! I'm connected via BLE, Wifi, opened, and ready to send / get data now!")
  338. # When we're done...
  339. await gopro.close()
  340. # The camera resource is closed now!!
  341. The `close` method will handle gracefully disconnecting BLE and Wifi.
  342. .. warning::
  343. If the resource is not closed correctly, it is possible that your OS will maintain the BLE connection after
  344. the program exits. This will cause reconnection problems as your OS will not discover devices it is
  345. already connected to.