Usage

This section will describe some high level information of how to use the Open GoPro module. For more detailed information, see the Interfaces section. For just running the demos, see the QuickStart section.

Overview

There are two top-level interfaces to communicate with a GoPro camera:

  • WiredGoPro to communicate with the GoPro via USB HTTP

  • WirelessGoPro to communicate with the GoPro via BLE and optionally WiFi HTTP

An individual instance of one of the above classes corresponds to a (potentially not yet) connected GoPro camera resource. The general procedure to communicate with the GoPro is:

  1. Identify and open the connection to the target GoPro

  2. Send Messages and Receive Responses via BLE / HTTP

  3. Gracefully close the connection with the GoPro

Tip

There is a lot of useful logging throughout the Open GoPro package. See troubleshooting for more info.

Asyncio

This package is asyncio-based which means that its awaitable methods need to be called from an async coroutine. For the code snippets throughout this documentation, assume that this is accomplished in the same manner as the demo scripts provided:

import asyncio

async def main() -> None:
    # Put our code here

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

Opening

Before communicating with a camera, the camera resource must be “opened”. This can be done either with or without the context manager. See the below sections for opening information specific to Wired / Wireless.

Wireless Opening

The Wireless GoPro client can be opened either with the context manager:

from open_gopro import WirelessGoPro

async with WirelessGoPro() as gopro:
    print("Yay! I'm connected via BLE, Wifi, opened, and ready to send / get data now!")
    # Send some messages now

...or without the context manager:

from open_gopro import WirelessGoPro

gopro = WirelessGoPro()
await gopro.open()
print("Yay! I'm connected via BLE, Wifi, opened, and ready to send / get data now!")
# Send some messages now

In either case, the following will have occurred before the camera is ready to communicate:

  1. scan for camera

  2. connect to camera via BLE

  3. enable notifications

  4. pair (if needed)

  5. discover characteristics

  6. initialize (register for internal status notifications)

  7. discover Open GoPro version

  8. connect via WiFi (unless specified not to via argument)

Note

While a BLE connection is always needed, the WiFi connection is optional. To configure this (and other instance arguments) see the API Reference for WirelessGoPro

Wired Opening

The Wired GoPro client can be opened either with the context manager:

from open_gopro import WiredGoPro

async with WiredGoPro() as gopro:
    print("Yay! I'm connected via USB, opened, and ready to send / get data now!")
    # Send some messages now

...or without the context manager:

from open_gopro import WiredGoPro

gopro = WiredGoPro()
await gopro.open()
print("Yay! I'm connected via USB, opened, and ready to send / get data now!")
# Send some messages now

If, as above, an identifier is not passed to the WiredGoPro, the mDNS server will be queried during opening to search for a connected GoPro.

Common Opening

The GoPro’s state can be checked via several properties.

API Version

One of the steps during the opening sequence is to query the camera’s Open GoPro API version. This SDK only supports Open GoPro API Version 2.0 so will raise an InvalidOpenGoProVersion if the connected camera is using anything else.

The version string can be accessed via the version() property.

Camera Readiness

A message can not be sent to the camera if it is not ready where “ready” is defined as not encoding and not busy. These two states are managed automatically by the WirelessGoPro instance such that a call to any message will block until the camera is ready. They are combined into the following ready state:

For example,

async with WirelessGoPro() as gopro:
    # A naive check for it to be ready
    while not await gopro.is_ready:
        pass

To reiterate…it is not needed or recommended to worry about this as the internal state is managed automatically by the WirelessGoPro instance. Just know that most commands will be (asynchronously) blocked until the camera is ready.

Sending Messages

Once a WirelessGoPro or WiredGoPro instance has been opened, it is now possible to send messages to the camera (provided that the camera is ready). Messages are accessed by transport protocol where the superset of message groups are:

Message Group

WiredGoPro

WirelessGoPro (WiFi Enabled)

WirelessGoPro (WiFi Disabled)

http_command()

✔️

✔️

http_setting()

✔️

✔️

ble_command()

✔️

✔️

ble_setting()

✔️

✔️

ble_status()

✔️

✔️

In the case where a given group of messages is not supported, a NotImplementedError will be returned when the relevant property is accessed.

All messages are communicated via one of two strategies:

Note

For the remainder of this document, the term (a)synchronous is in the context of communication with the camera. Do not confuse this with asyncio: all operations from the user’s perspective are awaitable.

Both of these patterns will be expanded upon below. But first, a note on selecting parameters for use with messages…

Selecting Parameters

Whenever a parameter is required for a message, it will be type-hinted in the method definition as either a standard Python type or an Enum from the Params module.

Here is a full example for clarity:

from open_gopro import WirelessGoPro, Params

with WirelessGoPro() as gopro:
    gopro.ble_command.set_shutter(Params.Toggle.ENABLE)

Tip

The message signature can also be found from the API Reference. For example, here is the documentation of the above message: set_shutter()

Synchronous Data Operations

Note

Unless explicitly specified in the Asynchronous section, all messages are synchronous messages.

This section refers to sending commands, getting settings / statuses, and setting settings. In all cases here, the method will await until a response is received.

Commands

Commands are callable instance attributes of a Messages class instance (i.e. BleCommands or HttpCommands), thus they can be called directly:

async with WirelessGoPro() as gopro:
    await gopro.ble_command.set_shutter(shutter=Params.Toggle.ENABLE)
    await gopro.http_command.set_shutter(shutter=Params.Toggle.DISABLE)

Warning

Most commands specifically require keyword-only arguments. You can not optionally use positional arguments in such cases as this will affect functionality.

Statuses

Statuses are instances of a BleStatus(BleStatusFacade). They can be read synchronously using their get_value method as such:

async with WirelessGoPro() as gopro:
    is_encoding = await gopro.ble_status.encoding_active.get_value()
    battery = await gopro.ble_status.int_batt_per.get_value()

It is also possible to read all statuses at once via:

async with WirelessGoPro() as gopro:
    statuses = await gopro.ble_command.get_camera_statuses()

Note

HTTP can not access individual statuses. Instead it can use get_camera_state() to retrieve all of them (as well as all of the settings) at once

Settings

Settings are instances of a BleSetting(BleSettingFacade) or HttpSetting(HttpSetting). They can be interacted synchronously in several ways.

Their values can be read (via BLE only) using the get_value method as such:

async with WirelessGoPro() as gopro:
    resolution = await gopro.ble_setting.resolution.get_value()
    fov = await gopro.ble_setting.video_field_of_view.get_value()

It is also possible to read all settings at once via:

async with WirelessGoPro() as gopro:
    settings = await gopro.ble_command.get_camera_settings()

Note

HTTP can not access individual settings. Instead it can use get_camera_state() to retrieve all of them (as well as all of the statuses) at once.

Depending on the camera’s current state, settings will have differing capabilities. It is possible to query the current capabilities for a given setting (via BLE only) using the get_capabilities_values method as such:

async with WirelessGoPro() as gopro:
    capabilities = await gopro.ble_setting.resolution.get_capabilities_values()

Settings’ values can be set (via either BLE or WiFI) using the set method as such:

async with WirelessGoPro() as gopro:
    await gopro.ble_setting.resolution.set(Params.Resolution.RES_4K)
    await gopro.http_setting.fps.set(Params.FPS.FPS_30)

Asynchronous Push Notifications

This section describes how to register for and handle asynchronous push notifications. This is only relevant for BLE.

It is possible to enable push notifications for any of the following:

Firstly, the desired settings / ID must be registered for and given a callback to handle received notifications.

register_update().

Once registered, the camera will send a push notification when the relevant setting / status changes. These responses will then be sent to the registered callback for handling.

It is possible to stop receiving notifications by issuing the relevant unregister command, i.e.:

Here is an example of registering for and receiving FOV updates:

async def process_battery_notifications(update: types.UpdateType, value: int) -> None:
    if update == constants.StatusId.INT_BATT_PER:
        ...
    elif update == constants.StatusId.BATT_LEVEL:
        ...

async with WirelessGoPro() as gopro:
    await gopro.ble_status.int_batt_per.register_value_update(process_battery_notifications)
    await gopro.ble_status.batt_level.register_value_update(process_battery_notifications)

Note

The register_XXX_update methods also return the current value / capabilities.

Warning

The coupling of command ID to command is not obvious. This is a temporary solution and will be improved upon in a future release.

It is also possible to register / unregister for all settings, statuses, and / or capabilities via one API call using the following commands:

Handling Responses

Unless otherwise stated, all commands, settings, and status operations return a GoProResp (GoProResp) which is a container around a JSON serializable dict with some helper functions.

Response Structure

A GoProResp has the following relevant attributes / properties for the end user:

  • identifier(): identifier of the completed operation.
    This will vary based on what type the response is and will also contain the most specific identification information.
    • UUID if a direct BLE characteristic read

    • CmdId if an Open GoPro BLE Operation

    • endpoint string if a Wifi HTTP operation

  • protocol(): the communication protocol where the response was received

  • status(): the status returned from the camera

  • data(): JSON serializable dict containing the responded data

  • ok(): Is this a successful response?

The response object can be serialized to a JSON string with the default Python str() function. Note that the identifier and status attributes are appended to the JSON.

For example, first let’s connect, send a command, and then store the response:

>>> gopro = WirelessGoPro()
>>> await gopro.open()
>>> response = await (gopro.ble_setting.resolution).get_value()
>>> print(response)

Now let’s print the response (as JSON):

>>> print(response)
{
    "id" : "QueryCmdId.GET_SETTING_VAL",
    "status" : "ErrorCode.SUCCESS",
    "protocol" : "Protocol.BLE",
    "data" : {
        "SettingId.RESOLUTION" : "Resolution.RES_4K_16_9",
    },
}

Now let’s inspect the responses various attributes / properties:

>>> print(response.status)
ErrorCode.SUCCESS
>>> print(response.ok)
True
>>> print(response.identifier)
QueryCmdId.GET_SETTING_VAL
>>> print(response.protocol)
Protocol.BLE

Data Access

The response data is stored in the data attribute (data()) and its type is specified via the Generic type specified in the corresponding command signature where the response is defined.

For example, consider get_hardware_info(). It’s signature is:

async def get_hardware_info(self) -> GoProResp[CameraInfo]:
    ...

Therefore, its response’s data property is of type CameraInfo(). Continuing the example from above:

>>> gopro = WirelessGoPro(enable_wifi=False)
>>> await gopro.open()
>>> response = await gopro.ble_command.get_hardware_info()
>>> print(response.data)
{
    "model_number" : "62",
    "model_name" : "HERO12 Black",
    "firmware_version" : "H23.01.01.09.67",
    "serial_number" : "C3501324500702",
    "ap_mac_addr" : "2674f7f66104",
    "ap_ssid" : "GP24500702",
}

Closing

It is important to close the camera resource when you are done with it. This can be done in two ways. If the context manager was used, it will automatically be closed when exiting, i.e.:

with WirelessGoPro() as gopro:
    # Do some things.
    pass
    # Then when finished...
# The camera resource is closed now!!

Otherwise, you will need to manually call the close method, i.e.:

gopro = WirelessGoPro()
await gopro.open()
print("Yay! I'm connected via BLE, Wifi, opened, and ready to send / get data now!")
# When we're done...
await gopro.close()
# The camera resource is closed now!!

The close method will handle gracefully disconnecting BLE and Wifi.

Warning

If the resource is not closed correctly, it is possible that your OS will maintain the BLE connection after the program exits. This will cause reconnection problems as your OS will not discover devices it is already connected to.