From 1ee1fa612162d388194ecced0cecb93544b6bba3 Mon Sep 17 00:00:00 2001
From: github-actions
Date: Fri, 17 Nov 2023 19:44:03 +0000
Subject: [PATCH] COHN + Hero 12 v1.30 support (#193)
---
.../docs/_static/coverage.svg | 4 +-
.../sdk_wireless_camera_control/docs/api.rst | 2 +
.../docs/changelog.rst | 6 +
.../docs/quickstart.rst | 32 -
.../sdk_wireless_camera_control/noxfile.py | 2 +-
.../open_gopro/api/ble_commands.py | 120 ++-
.../open_gopro/api/builders.py | 14 +-
.../open_gopro/api/http_commands.py | 11 +-
.../open_gopro/api/params.py | 107 ++-
.../open_gopro/api/parsers.py | 14 +-
.../open_gopro/constants.py | 30 +-
.../open_gopro/demos/cohn.py | 78 ++
.../demos/gui/components/controllers.py | 812 ----------------
.../open_gopro/demos/gui/components/models.py | 450 ---------
.../open_gopro/demos/gui/components/views.py | 896 ------------------
.../open_gopro/demos/gui/gui_demo.py | 231 -----
.../open_gopro/demos/gui/webcam.py | 22 +-
.../open_gopro/enum.py | 29 +-
.../open_gopro/gopro_base.py | 29 +-
.../open_gopro/gopro_wired.py | 35 +-
.../open_gopro/gopro_wireless.py | 124 ++-
.../open_gopro/models/general.py | 22 +
.../open_gopro/models/response.py | 4 +-
.../open_gopro/proto/__init__.py | 13 +-
.../open_gopro/proto/cohn_pb2.py | 37 +
.../open_gopro/proto/cohn_pb2.pyi | 263 +++++
.../open_gopro/proto/live_streaming_pb2.py | 34 +-
.../open_gopro/proto/live_streaming_pb2.pyi | 186 ++--
.../proto/network_management_pb2.py | 56 +-
.../proto/network_management_pb2.pyi | 352 ++++---
.../open_gopro/proto/preset_status_pb2.py | 34 +-
.../open_gopro/proto/preset_status_pb2.pyi | 181 +++-
.../proto/request_get_preset_status_pb2.py | 2 +-
.../proto/request_get_preset_status_pb2.pyi | 26 +-
.../open_gopro/proto/response_generic_pb2.py | 2 +-
.../open_gopro/proto/response_generic_pb2.pyi | 10 +-
.../proto/set_camera_control_status_pb2.py | 2 +-
.../proto/set_camera_control_status_pb2.pyi | 17 +-
.../open_gopro/proto/turbo_transfer_pb2.py | 2 +-
.../open_gopro/proto/turbo_transfer_pb2.pyi | 10 +-
.../pyproject.toml | 10 +-
.../tests/{unit => }/test_ble_commands.py | 0
.../tests/{unit => }/test_bleak_wrapper.py | 0
.../tests/{unit => }/test_enums.py | 4 +-
.../tests/{unit => }/test_gopro_ble.py | 0
.../tests/{unit => }/test_gopro_wifi.py | 0
.../tests/{unit => }/test_http_commands.py | 6 +
.../tests/{unit => }/test_models.py | 0
.../tests/{unit => }/test_parsers.py | 10 +-
.../tests/{unit => }/test_responses.py | 0
.../tests/{unit => }/test_services.py | 0
.../tests/{unit => }/test_wifi_adapter.py | 0
.../tests/{unit => }/test_wired_gopro.py | 0
.../tests/{unit => }/test_wireless_gopro.py | 0
docs/specs/ble_versions/ble_2_0.md | 230 ++++-
docs/specs/capabilities.xlsx | 4 +-
docs/specs/http_versions/http_2_0.md | 355 +++++--
protobuf/cohn.proto | 99 ++
protobuf/live_streaming.proto | 132 ++-
protobuf/network_management.proto | 213 +++--
protobuf/preset_status.proto | 258 +++--
protobuf/request_get_preset_status.proto | 33 +-
protobuf/response_generic.proto | 25 +-
protobuf/set_camera_control_status.proto | 27 +-
protobuf/turbo_transfer.proto | 25 +-
tools/test_media_server/README.md | 29 +-
66 files changed, 2466 insertions(+), 3295 deletions(-)
create mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/demos/cohn.py
delete mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/controllers.py
delete mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/models.py
delete mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/views.py
delete mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/gui_demo.py
create mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.py
create mode 100644 demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.pyi
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_ble_commands.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_bleak_wrapper.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_enums.py (93%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_gopro_ble.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_gopro_wifi.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_http_commands.py (87%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_models.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_parsers.py (82%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_responses.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_services.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_wifi_adapter.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_wired_gopro.py (100%)
rename demos/python/sdk_wireless_camera_control/tests/{unit => }/test_wireless_gopro.py (100%)
create mode 100644 protobuf/cohn.proto
diff --git a/demos/python/sdk_wireless_camera_control/docs/_static/coverage.svg b/demos/python/sdk_wireless_camera_control/docs/_static/coverage.svg
index 75518b71..321ee2ee 100644
--- a/demos/python/sdk_wireless_camera_control/docs/_static/coverage.svg
+++ b/demos/python/sdk_wireless_camera_control/docs/_static/coverage.svg
@@ -15,7 +15,7 @@
coverage
coverage
- 77%
- 77%
+ 76%
+ 76%
diff --git a/demos/python/sdk_wireless_camera_control/docs/api.rst b/demos/python/sdk_wireless_camera_control/docs/api.rst
index 4b57944d..cb769bf4 100644
--- a/demos/python/sdk_wireless_camera_control/docs/api.rst
+++ b/demos/python/sdk_wireless_camera_control/docs/api.rst
@@ -81,6 +81,8 @@ GoPro Enum
.. autoclass:: open_gopro.enum.GoProEnum
+.. autoclass:: open_gopro.enum.GoProIntEnum
+
BLE Setting
^^^^^^^^^^^
diff --git a/demos/python/sdk_wireless_camera_control/docs/changelog.rst b/demos/python/sdk_wireless_camera_control/docs/changelog.rst
index b0a58725..7cd906aa 100644
--- a/demos/python/sdk_wireless_camera_control/docs/changelog.rst
+++ b/demos/python/sdk_wireless_camera_control/docs/changelog.rst
@@ -9,6 +9,12 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog `_,
and this project adheres to `Semantic Versioning `_.
+Unreleased
+----------
+* Add alpha support for COHN (Camera-on-the-Home-Network)
+ * A real implementation is going to require a major rearchitecture to dynamically add connection types.
+* Remove TKinter GUI. Will be replaced with Textual TUI in the future
+
0.14.1 (September-21-2022)
--------------------------
* Fix BLE notifications not being routed correctly
diff --git a/demos/python/sdk_wireless_camera_control/docs/quickstart.rst b/demos/python/sdk_wireless_camera_control/docs/quickstart.rst
index 329282a7..46e239f0 100644
--- a/demos/python/sdk_wireless_camera_control/docs/quickstart.rst
+++ b/demos/python/sdk_wireless_camera_control/docs/quickstart.rst
@@ -227,35 +227,3 @@ For more information, do:
--identifier IDENTIFIER
Last 4 digits of GoPro serial number, which is the last 4 digits of the default camera SSID. If not used, first
discovered GoPro will be connected to
-
-API GUI Demo
--------------
-
-.. warning::
- This is a work in progress and some complex responses are not yet easily viewed.
-
-This is a GUI which allows the user to connect a camera and send any command, view status / setting
-updates, view a video stream, and log sent / received messages. It can be started with:
-
-.. code-block:: console
-
- $ gopro-gui
-
-This will launch a camera chooser screen where the user can either manually enter a camera to connect to
-or automatically connect to the first found camera. Once connected, the GUI will appear. Usages is as follows:
-
-- Choose a command from the Command Pallette on the left
-
- - Note that besides supporting all of the commands from the Open GoPro API, there is also a "Compound" commands
- section which contains commands that combine API functionality. One of these, for example, is Livestream
- which will connect Wifi, configure and start livestreaming.
-- Once chosen, enter the desired parameters in the entry form at the top middle
-- In the same entry form, click the button to send the command
-- The sent command and received response will be logged in the log in the bottom middle as well as any
- asynchronously received messages.
-- Any log messages with a down arrow can be expanded to view their details
-- Any received statuses, settings, and setting capabilities will be updated in the pane at the top right.
-
- - The most recently received updates will be highlighted in blue
-- A network stream can be started using the video pane in the bottom right. This will automatically get started
- after sending the Livestream command
diff --git a/demos/python/sdk_wireless_camera_control/noxfile.py b/demos/python/sdk_wireless_camera_control/noxfile.py
index f3b429a3..e6947e66 100644
--- a/demos/python/sdk_wireless_camera_control/noxfile.py
+++ b/demos/python/sdk_wireless_camera_control/noxfile.py
@@ -56,7 +56,7 @@ def tests(session) -> None:
"coverage[toml]",
"requests-mock",
)
- session.run("pytest", "tests/unit", "--cov-fail-under=65")
+ session.run("pytest", "tests", "--cov-fail-under=65")
@session(python=SUPPORTED_VERSIONS[-1])
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/api/ble_commands.py b/demos/python/sdk_wireless_camera_control/open_gopro/api/ble_commands.py
index 73311fe5..b4f60a08 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/api/ble_commands.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/api/ble_commands.py
@@ -161,7 +161,7 @@ async def enable_wifi_ap(self, *, enable: bool) -> GoProResp[None]:
"""
@ble_write_command(GoProUUIDs.CQ_COMMAND, CmdId.LOAD_PRESET_GROUP, Int16ub)
- async def load_preset_group(self, *, group: proto.EnumPresetGroup) -> GoProResp[None]:
+ async def load_preset_group(self, *, group: proto.EnumPresetGroup.ValueType) -> GoProResp[None]:
"""Load a Preset Group.
Once complete, the most recently used preset in this group will be active.
@@ -453,7 +453,9 @@ async def unregister_for_all_capabilities(self, callback: types.UpdateCb) -> GoP
request_proto=proto.RequestSetCameraControlStatus,
response_proto=proto.ResponseGeneric,
)
- async def set_camera_control(self, *, camera_control_status: proto.EnumCameraControlStatus) -> GoProResp[None]:
+ async def set_camera_control(
+ self, *, camera_control_status: proto.EnumCameraControlStatus.ValueType
+ ) -> GoProResp[None]:
"""Tell the camera that the app (i.e. External Control) wishes to claim control of the camera.
Args:
@@ -494,8 +496,8 @@ async def set_turbo_mode(self, *, mode: Params.Toggle) -> GoProResp[None]:
async def get_preset_status(
self,
*,
- register: list[proto.EnumRegisterPresetStatus] | None = None,
- unregister: list[proto.EnumRegisterPresetStatus] | None = None,
+ register: list[proto.EnumRegisterPresetStatus.ValueType] | None = None,
+ unregister: list[proto.EnumRegisterPresetStatus.ValueType] | None = None,
) -> GoProResp[proto.NotifyPresetStatus]:
"""Get information about what Preset Groups and Presets the camera supports in its current state
@@ -609,11 +611,11 @@ async def set_livestream_mode(
self,
*,
url: str,
- window_size: proto.EnumWindowSize,
+ window_size: proto.EnumWindowSize.ValueType,
minimum_bitrate: int,
maximum_bitrate: int,
starting_bitrate: int,
- lens: proto.EnumLens,
+ lens: proto.EnumLens.ValueType,
certs: list[Path] | None = None,
) -> GoProResp[None]:
"""Initiate livestream to any site that accepts an RTMP URL and simultaneously encode to camera.
@@ -661,8 +663,8 @@ async def set_livestream_mode(
async def register_livestream_status(
self,
*,
- register: list[proto.EnumRegisterLiveStreamStatus] | None = None,
- unregister: list[proto.EnumRegisterLiveStreamStatus] | None = None,
+ register: list[proto.EnumRegisterLiveStreamStatus.ValueType] | None = None,
+ unregister: list[proto.EnumRegisterLiveStreamStatus.ValueType] | None = None,
) -> GoProResp[proto.NotifyLiveStreamStatus]:
"""Register / unregister to receive asynchronous livestream statuses
@@ -677,6 +679,108 @@ async def register_livestream_status(
"""
return {"register_live_stream_status": register or [], "unregister_live_stream_status": unregister or []} # type: ignore
+ @ble_proto_command(
+ uuid=GoProUUIDs.CQ_COMMAND,
+ feature_id=FeatureId.COMMAND,
+ action_id=ActionId.RELEASE_NETWORK,
+ response_action_id=ActionId.RELEASE_NETWORK_RSP,
+ request_proto=proto.RequestReleaseNetwork,
+ response_proto=proto.ResponseGeneric,
+ )
+ async def release_network(self) -> GoProResp[None]:
+ """Disconnect the camera Wifi network in STA mode so that it returns to AP mode.
+
+ Returns:
+ GoProResp: status of release request
+ """
+
+ @ble_proto_command(
+ uuid=GoProUUIDs.CQ_QUERY,
+ feature_id=FeatureId.QUERY,
+ action_id=ActionId.REQUEST_GET_COHN_STATUS,
+ response_action_id=ActionId.RESPONSE_GET_COHN_STATUS,
+ request_proto=proto.RequestGetCOHNStatus,
+ response_proto=proto.NotifyCOHNStatus,
+ )
+ async def cohn_get_status(self, *, register: bool) -> GoProResp[proto.NotifyCOHNStatus]:
+ """Get (and optionally register for) the current COHN status
+
+ Args:
+ register (bool): whether or not to register
+
+ Returns:
+ GoProResp[proto.NotifyCOHNStatus]: current COHN status
+ """
+ return {"register_cohn_status": int(register)} # type: ignore
+
+ @ble_proto_command(
+ uuid=GoProUUIDs.CQ_COMMAND,
+ feature_id=FeatureId.COMMAND,
+ action_id=ActionId.REQUEST_CREATE_COHN_CERT,
+ response_action_id=ActionId.RESPONSE_CREATE_COHN_CERT,
+ request_proto=proto.RequestCreateCOHNCert,
+ response_proto=proto.ResponseGeneric,
+ )
+ async def cohn_create_certificate(self, *, override: bool = False) -> GoProResp[None]:
+ """Create an SSL certificate on the camera to use for COHN
+
+ Args:
+ override (bool): Should the current cert be overwritten?. Defaults to True.
+
+ Returns:
+ GoProResp[None]: certificate creation status
+ """
+ return {"override": int(override)} # type: ignore
+
+ @ble_proto_command(
+ uuid=GoProUUIDs.CQ_COMMAND,
+ feature_id=FeatureId.COMMAND,
+ action_id=ActionId.REQUEST_CLEAR_COHN_CERT,
+ response_action_id=ActionId.RESPONSE_CLEAR_COHN_CERT,
+ request_proto=proto.RequestClearCOHNCert,
+ response_proto=proto.ResponseGeneric,
+ )
+ async def cohn_clear_certificate(self) -> GoProResp[None]:
+ """Clear the current SSL certificate on the camera that is used for COHN
+
+ Returns:
+ GoProResp[None]: was the clear successful?
+ """
+
+ @ble_proto_command(
+ uuid=GoProUUIDs.CQ_QUERY,
+ feature_id=FeatureId.QUERY,
+ action_id=ActionId.REQUEST_GET_COHN_CERT,
+ response_action_id=ActionId.RESPONSE_GET_COHN_CERT,
+ request_proto=proto.RequestCOHNCert,
+ response_proto=proto.ResponseCOHNCert,
+ )
+ async def cohn_get_certificate(self) -> GoProResp[proto.ResponseCOHNCert]:
+ """Get the current SSL certificate that the camera is using for COHN.
+
+ Returns:
+ GoProResp[proto.ResponseCOHNCert]: the certificate
+ """
+
+ @ble_proto_command(
+ uuid=GoProUUIDs.CQ_COMMAND,
+ feature_id=FeatureId.COMMAND,
+ action_id=ActionId.REQUEST_COHN_SETTING,
+ response_action_id=ActionId.RESPONSE_COHN_SETTING,
+ request_proto=proto.RequestSetCOHNSetting,
+ response_proto=proto.ResponseGeneric,
+ )
+ async def cohn_set_setting(self, *, mode: Params.Toggle) -> GoProResp[None]:
+ """Set a COHN specific setting.
+
+ Args:
+ mode (open_gopro.api.params.Toggle): should camera auto connect to home network?
+
+ Returns:
+ GoProResp[None]: status of set
+ """
+ return {"cohn_active": mode} # type: ignore
+
class BleSettings(BleMessages[BleSetting, SettingId]):
# pylint: disable=missing-class-docstring, unused-argument
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/api/builders.py b/demos/python/sdk_wireless_camera_control/open_gopro/api/builders.py
index 630d8e10..a79611ab 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/api/builders.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/api/builders.py
@@ -38,7 +38,7 @@
SettingId,
StatusId,
)
-from open_gopro.enum import GoProEnum
+from open_gopro.enum import GoProIntEnum
from open_gopro.logger import Logger
from open_gopro.models.general import HttpInvalidSettingResponse
from open_gopro.models.response import GlobalParsers, GoProResp
@@ -50,7 +50,7 @@
ValueType = TypeVar("ValueType")
IdType = TypeVar("IdType")
-QueryParserType = Union[construct.Construct, type[GoProEnum], BytesParserBuilder]
+QueryParserType = Union[construct.Construct, type[GoProIntEnum], BytesParserBuilder]
######################################################## BLE #################################################
@@ -142,7 +142,7 @@ async def __call__(self, __communicator__: GoProBle, **kwargs: Any) -> GoProResp
response = await __communicator__._send_ble_message(
self._uuid, data, self._identifier, rules=self._evaluate_rules(**kwargs)
)
- logger.info(Logger.build_log_rx_str(response))
+ # logger.info(Logger.build_log_rx_str(response))
return response
def __str__(self) -> str:
@@ -284,7 +284,7 @@ async def __call__(self, __communicator__: GoProBle, **kwargs: Any) -> GoProResp
data = self.build_data(**kwargs)
# Allow exception to pass through if protobuf not completely initialized
response = await __communicator__._send_ble_message(self._uuid, data, self.response_action_id)
- logger.info(Logger.build_log_rx_str(response))
+ # logger.info(Logger.build_log_rx_str(response))
return response
def __str__(self) -> str:
@@ -465,7 +465,7 @@ def __init__(self, communicator: GoProBle, identifier: SettingId, parser_builder
parser.byte_json_adapter = ByteParserBuilders.Construct(parser_builder)
elif isinstance(parser_builder, BytesParserBuilder):
parser.byte_json_adapter = parser_builder
- elif issubclass(parser_builder, GoProEnum):
+ elif issubclass(parser_builder, GoProIntEnum):
parser.byte_json_adapter = ByteParserBuilders.GoProEnum(parser_builder)
else:
raise TypeError(f"Unexpected {parser_builder=}")
@@ -661,7 +661,7 @@ def __init__(self, communicator: GoProBle, identifier: StatusId, parser: QueryPa
parser_builder.byte_json_adapter = ByteParserBuilders.Construct(parser)
elif isinstance(parser, BytesParserBuilder):
parser_builder.byte_json_adapter = parser
- elif issubclass(parser, GoProEnum):
+ elif issubclass(parser, GoProIntEnum):
parser_builder.byte_json_adapter = ByteParserBuilders.GoProEnum(parser)
else:
raise TypeError(f"Unexpected {parser_builder=}")
@@ -697,7 +697,7 @@ async def _send_query(self, response_id: QueryCmdId) -> GoProResp:
data = self._build_cmd(response_id)
logger.info(Logger.build_log_tx_str(pretty_print(self._as_dict(f"{response_id.name}.{str(self._identifier)}"))))
response = await self._communicator._send_ble_message(self.UUID, data, response_id)
- logger.info(Logger.build_log_rx_str(response))
+ # logger.info(Logger.build_log_rx_str(response))
return response
def _as_dict( # pylint: disable = arguments-differ
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/api/http_commands.py b/demos/python/sdk_wireless_camera_control/open_gopro/api/http_commands.py
index 7ebb2b4f..01652e39 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/api/http_commands.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/api/http_commands.py
@@ -159,7 +159,7 @@ async def load_preset(self, *, preset: int) -> GoProResp[None]:
return {"id": preset} # type: ignore
@http_get_json_command(endpoint="gopro/camera/presets/set_group", arguments=["id"])
- async def load_preset_group(self, *, group: proto.EnumPresetGroup) -> GoProResp[None]:
+ async def load_preset_group(self, *, group: proto.EnumPresetGroup.ValueType) -> GoProResp[None]:
"""Set the active preset group.
The most recently used Preset in this group will be set.
@@ -330,7 +330,7 @@ async def webcam_preview(self) -> GoProResp[WebcamResponse]:
@http_get_json_command(
endpoint="gopro/webcam/start",
- arguments=["res", "fov"],
+ arguments=["res", "fov", "port", "protocol"],
parser=Parser(json_parser=JsonParsers.PydanticAdapter(WebcamResponse)),
)
async def webcam_start(
@@ -338,6 +338,8 @@ async def webcam_start(
*,
resolution: Params.WebcamResolution | None = None,
fov: Params.WebcamFOV | None = None,
+ port: int | None = None,
+ protocol: Params.WebcamProtocol | None = None,
) -> GoProResp[WebcamResponse]:
"""Start the webcam.
@@ -346,11 +348,14 @@ async def webcam_start(
camera default will be used.
fov (Optional[open_gopro.api.params.WebcamFOV]): field of view to use. If not set, camera
default will be used.
+ port (Optional[int]): port to use for streaming. If not set, camera default of 8554 will be used.
+ protocol (Optional[open_gopro.api.params.WebcamProtocol]): streaming protocol to use. If not set, camera
+ default of TS will be used.
Returns:
GoProResp: command status
"""
- return {"res": resolution, "fov": fov} # type: ignore
+ return {"res": resolution, "fov": fov, "port": port, "protocol": protocol} # type: ignore
@http_get_json_command(
endpoint="gopro/webcam/stop",
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/api/params.py b/demos/python/sdk_wireless_camera_control/open_gopro/api/params.py
index eba5d28d..d7451fc4 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/api/params.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/api/params.py
@@ -7,10 +7,10 @@
from __future__ import annotations
-from open_gopro.constants import GoProEnum
+from open_gopro.enum import GoProEnum, GoProIntEnum
-class Resolution(GoProEnum):
+class Resolution(GoProIntEnum):
NOT_APPLICABLE = 0
RES_4K = 1
RES_2_7K = 4
@@ -34,14 +34,14 @@ class Resolution(GoProEnum):
RES_1080_16_9 = 106
-class WebcamResolution(GoProEnum):
+class WebcamResolution(GoProIntEnum):
NOT_APPLICABLE = 0
RES_480 = 4
RES_720 = 7
RES_1080 = 12
-class FPS(GoProEnum):
+class FPS(GoProIntEnum):
FPS_240 = 0
FPS_120 = 1
FPS_100 = 2
@@ -53,7 +53,7 @@ class FPS(GoProEnum):
FPS_200 = 13
-class AutoOff(GoProEnum):
+class AutoOff(GoProIntEnum):
NEVER = 0
MIN_1 = 1
MIN_5 = 4
@@ -63,12 +63,12 @@ class AutoOff(GoProEnum):
SEC_30 = 12
-class LensMode(GoProEnum):
+class LensMode(GoProIntEnum):
SINGLE = 0
DUAL = 5
-class VideoFOV(GoProEnum):
+class VideoFOV(GoProIntEnum):
WIDE = 0
NARROW = 2
SUPERVIEW = 3
@@ -80,14 +80,19 @@ class VideoFOV(GoProEnum):
MAX_HYPERVIEW = 11
-class WebcamFOV(GoProEnum):
+class WebcamFOV(GoProIntEnum):
WIDE = 0
NARROW = 2
SUPERVIEW = 3
LINEAR = 4
-class PhotoFOV(GoProEnum):
+class WebcamProtocol(GoProEnum):
+ TS = "TS"
+ RTSP = "RTSP"
+
+
+class PhotoFOV(GoProIntEnum):
NOT_APPLICABLE = 0
HYPERVIEW = 9
NARROW = 19
@@ -97,7 +102,7 @@ class PhotoFOV(GoProEnum):
LINEAR_HORIZON = 121
-class MultishotFOV(GoProEnum):
+class MultishotFOV(GoProIntEnum):
NOT_APPLICABLE = 0
NARROW = 19
MAX_SUPERVIEW = 100
@@ -105,7 +110,7 @@ class MultishotFOV(GoProEnum):
LINEAR = 102
-class LED(GoProEnum):
+class LED(GoProIntEnum):
LED_2 = 2
ALL_ON = 3
ALL_OFF = 4
@@ -113,7 +118,7 @@ class LED(GoProEnum):
BLE_KEEP_ALIVE = 66
-class PairState(GoProEnum):
+class PairState(GoProIntEnum):
NOT_STARTED = 0
IN_PROGRESS = 1
FAILED = 2
@@ -121,14 +126,14 @@ class PairState(GoProEnum):
COMPLETED = 4
-class PairType(GoProEnum):
+class PairType(GoProIntEnum):
NOT_PAIRING = 0
PAIRING_APP = 1
PAIRING_REMOTE_CONTROL = 2
PAIRING_BLUETOOTH = 3
-class WAPState(GoProEnum):
+class WAPState(GoProIntEnum):
NEVER_STARTED = 0
STARTED = 1
ABORTED = 2
@@ -136,7 +141,7 @@ class WAPState(GoProEnum):
COMPLETED = 4
-class SDStatus(GoProEnum):
+class SDStatus(GoProIntEnum):
OK = 0
FULL = 1
REMOVED = 2
@@ -146,7 +151,7 @@ class SDStatus(GoProEnum):
UNKNOWN = 0xFF
-class OTAStatus(GoProEnum):
+class OTAStatus(GoProIntEnum):
IDLE = 0
DOWNLOADING = 1
VERIFYING = 2
@@ -160,45 +165,45 @@ class OTAStatus(GoProEnum):
GOPRO_APP_READY = 10
-class AnalyticsState(GoProEnum):
+class AnalyticsState(GoProIntEnum):
NOT_READY = 0
READY = 1
ON_CONNECT = 2
-class ExposureMode(GoProEnum):
+class ExposureMode(GoProIntEnum):
DISABLED = 0
AUTO = 1
ISO_LOCK = 2
HEMISPHERE = 3
-class AccMicStatus(GoProEnum):
+class AccMicStatus(GoProIntEnum):
NOT_CONNECTED = 0
CONNECTED = 1
CONNECTED_AND_PLUGGED_IN = 2
-class WifiBand(GoProEnum):
+class WifiBand(GoProIntEnum):
BAND_2_4_GHZ = 0
BAND_5_GHZ = 1
BAND_MAX = 2
-class Orientation(GoProEnum):
+class Orientation(GoProIntEnum):
UPRIGHT = 0
UPSIDE_DOWN = 1
ON_RIGHT_SIDE = 2
ON_LEFT_SIDE = 3
-class MediaModMicStatus(GoProEnum):
+class MediaModMicStatus(GoProIntEnum):
REMOVED = 0
ONLY = 1
WITH_EXTERNAL = 2
-class TimeWarpSpeed(GoProEnum):
+class TimeWarpSpeed(GoProIntEnum):
SPEED_15X = 0
SPEED_30X = 1
SPEED_60X = 2
@@ -214,12 +219,12 @@ class TimeWarpSpeed(GoProEnum):
SPEED_HALF = 12
-class MaxLensMode(GoProEnum):
+class MaxLensMode(GoProIntEnum):
DEFAULT = 0
MAX_LENS = 1
-class MediaModStatus(GoProEnum):
+class MediaModStatus(GoProIntEnum):
SELFIE_0_HDMI_0_MEDIAMODCONNECTED_FALSE = 0
SELFIE_0_HDMI_0_MEDIAMODCONNECTED_TRUE = 1
SELFIE_0_HDMI_1_MEDIAMODCONNECTED_FALSE = 2
@@ -230,7 +235,7 @@ class MediaModStatus(GoProEnum):
SELFIE_1_HDMI_1_MEDIAMODCONNECTED_TRUE = 7
-class Flatmode(GoProEnum):
+class Flatmode(GoProIntEnum):
NOT_APPLICABLE = 0
VIDEO = 12
LOOPING = 15
@@ -248,12 +253,12 @@ class Flatmode(GoProEnum):
UNKNOWN = 28
-class Toggle(GoProEnum):
+class Toggle(GoProIntEnum):
ENABLE = 1
DISABLE = 0
-class HypersmoothMode(GoProEnum):
+class HypersmoothMode(GoProIntEnum):
OFF = 0
ON = 1
HIGH = 2
@@ -262,42 +267,42 @@ class HypersmoothMode(GoProEnum):
STANDARD = 100
-class CameraControl(GoProEnum):
+class CameraControl(GoProIntEnum):
IDLE = 0
CAMERA = 1
EXTERNAL = 2
-class PerformanceMode(GoProEnum):
+class PerformanceMode(GoProIntEnum):
MAX_PERFORMANCE = 0
EXTENDED_BATTERY = 1
STATIONARY = 2
-class MediaFormat(GoProEnum):
+class MediaFormat(GoProIntEnum):
TIME_LAPSE_VIDEO = 13
TIME_LAPSE_PHOTO = 20
NIGHT_LAPSE_PHOTO = 21
NIGHT_LAPSE_VIDEO = 26
-class AntiFlicker(GoProEnum):
+class AntiFlicker(GoProIntEnum):
HZ_60 = 2
HZ_50 = 3
-class CameraUxMode(GoProEnum):
+class CameraUxMode(GoProIntEnum):
EASY = 0
PRO = 1
-class HorizonLeveling(GoProEnum):
+class HorizonLeveling(GoProIntEnum):
OFF = 0
ON = 1
LOCKED = 2
-class Speed(GoProEnum):
+class Speed(GoProIntEnum):
ULTRA_SLO_MO_8X = 0
SUPER_SLO_MO_4X = 1
SLO_MO_2X = 2
@@ -349,63 +354,63 @@ class Speed(GoProEnum):
SPEED_1X_LOW_LIGHT_12 = 47
-class PhotoEasyMode(GoProEnum):
+class PhotoEasyMode(GoProIntEnum):
OFF = 0
ON = 1
SUPER_PHOTO = 100
NIGHT_PHOTO = 101
-class StarTrailLength(GoProEnum):
+class StarTrailLength(GoProIntEnum):
NOT_APPLICABLE = 0
SHORT = 1
LONG = 2
MAX = 3
-class SystemVideoMode(GoProEnum):
+class SystemVideoMode(GoProIntEnum):
HIGHEST_QUALITY = 0
EXTENDED_BATTERY = 1
EXTENDED_BATTERY_GREEN_ICON = 101
LONGEST_BATTERY_GREEN_ICON = 102
-class BitRate(GoProEnum):
+class BitRate(GoProIntEnum):
STANDARD = 0
HIGH = 1
-class BitDepth(GoProEnum):
+class BitDepth(GoProIntEnum):
BIT_8 = 0
BIT_10 = 2
-class VideoProfile(GoProEnum):
+class VideoProfile(GoProIntEnum):
STANDARD = 0
HDR = 1
LOG = 2
-class VideoAspectRatio(GoProEnum):
+class VideoAspectRatio(GoProIntEnum):
RATIO_4_3 = 0
RATIO_16_9 = 1
RATIO_8_7 = 3
RATIO_9_16 = 4
-class EasyAspectRatio(GoProEnum):
+class EasyAspectRatio(GoProIntEnum):
WIDESCREEN = 0
MOBILE = 1
UNIVERSAL = 2
-class VideoMode(GoProEnum):
+class VideoMode(GoProIntEnum):
HIGHEST = 0
STANDARD = 1
BASIC = 2
-class TimelapseMode(GoProEnum):
+class TimelapseMode(GoProIntEnum):
TIMEWARP = 0
STAR_TRAILS = 1
LIGHT_PAINTING = 2
@@ -416,30 +421,30 @@ class TimelapseMode(GoProEnum):
MAX_VEHICLE_LIGHTS = 7
-class PhotoMode(GoProEnum):
+class PhotoMode(GoProIntEnum):
SUPER = 0
NIGHT = 1
-class Framing(GoProEnum):
+class Framing(GoProIntEnum):
WIDESCREEN = 0
VERTICAL = 1
FULL = 2
-class MaxLensModType(GoProEnum):
+class MaxLensModType(GoProIntEnum):
NONE = 0
V1 = 1
V2 = 2
-class Hindsight(GoProEnum):
+class Hindsight(GoProIntEnum):
SEC_15 = 2
SEC_30 = 3
OFF = 4
-class PhotoInterval(GoProEnum):
+class PhotoInterval(GoProIntEnum):
OFF = 0
SEC_0_5 = 2
SEC_1 = 3
@@ -452,7 +457,7 @@ class PhotoInterval(GoProEnum):
SEC_3 = 10
-class PhotoDuration(GoProEnum):
+class PhotoDuration(GoProIntEnum):
OFF = 0
SEC_15 = 1
SEC_30 = 2
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/api/parsers.py b/demos/python/sdk_wireless_camera_control/open_gopro/api/parsers.py
index 344eba2d..41e51a62 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/api/parsers.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/api/parsers.py
@@ -17,7 +17,7 @@
from open_gopro import types
from open_gopro.constants import SettingId, StatusId
-from open_gopro.enum import GoProEnum, enum_factory
+from open_gopro.enum import GoProIntEnum, enum_factory
from open_gopro.parser_interface import (
BytesBuilder,
BytesParser,
@@ -33,6 +33,7 @@
ProtobufPrinter = google.protobuf.json_format._Printer # type: ignore # noqa
original_field_to_json = ProtobufPrinter._FieldToJsonObject
+
# TODO move into below class
def construct_adapter_factory(target: Construct) -> BytesParserBuilder:
"""Build a construct parser adapter from a construct
@@ -120,7 +121,7 @@ def parse(self, data: types.JsonDict) -> types.CameraState:
"""
parsed: dict = {}
# Parse status and settings values into nice human readable things
- for (name, id_map) in [("status", StatusId), ("settings", SettingId)]:
+ for name, id_map in [("status", StatusId), ("settings", SettingId)]:
for k, v in data[name].items():
identifier = cast(types.ResponseType, id_map(int(k)))
try:
@@ -207,18 +208,17 @@ class GoProEnum(BytesParserBuilder):
target (type[GoProEnum]): enum type to parse into
"""
- def __init__(self, target: type[GoProEnum]) -> None:
-
+ def __init__(self, target: type[GoProIntEnum]) -> None:
self._container = target
- def parse(self, data: bytes) -> GoProEnum:
+ def parse(self, data: bytes) -> GoProIntEnum:
"""Parse bytes into GoPro enum
Args:
data (bytes): bytes to parse
Returns:
- GoProEnum: parsed enum
+ GoProIntEnum: parsed enum
"""
return self._container(data[0])
@@ -299,7 +299,7 @@ def build(self, obj: datetime.datetime, tzone: int | None = None, is_dst: bool |
bytes: bytestream built from datetime
"""
byte_data = [*Int16ub.build(obj.year), obj.month, obj.day, obj.hour, obj.minute, obj.second]
- if tzone and is_dst:
+ if tzone is not None and is_dst is not None:
byte_data.extend([*Int16sb.build(tzone), *Flag.build(is_dst)])
return bytes(byte_data)
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/constants.py b/demos/python/sdk_wireless_camera_control/open_gopro/constants.py
index 195de895..faab20d6 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/constants.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/constants.py
@@ -9,7 +9,7 @@
from typing import Final
from open_gopro.ble import BleUUID, UUIDs
-from open_gopro.enum import GoProEnum
+from open_gopro.enum import GoProIntEnum
GOPRO_BASE_UUID: Final = "b5f9{}-aa8d-11e3-9046-0002a5d5c51b"
@@ -50,7 +50,7 @@ class GoProUUIDs(UUIDs):
INTERNAL_84 = BleUUID("Internal 84", hex=GOPRO_BASE_UUID.format("0084"))
-class ErrorCode(GoProEnum):
+class ErrorCode(GoProIntEnum):
"""Status Codes."""
SUCCESS = 0
@@ -59,7 +59,7 @@ class ErrorCode(GoProEnum):
UNKNOWN = -1
-class CmdId(GoProEnum):
+class CmdId(GoProIntEnum):
"""Command ID's that are written to GoProUUIDs.CQ_COMMAND."""
SET_SHUTTER = 0x01
@@ -89,7 +89,7 @@ class CmdId(GoProEnum):
UNREGISTER_ALL_CAPABILITIES = 0x82
-class ActionId(GoProEnum):
+class ActionId(GoProIntEnum):
"""Action ID's that identify a protobuf command."""
SCAN_WIFI_NETWORKS = 0x02
@@ -108,8 +108,18 @@ class ActionId(GoProEnum):
GET_AP_ENTRIES_RSP = 0x83
REQUEST_WIFI_CONNECT_RSP = 0x84
REQUEST_WIFI_CONNECT_NEW_RSP = 0x85
+ REQUEST_COHN_SETTING = 0x65
+ REQUEST_CLEAR_COHN_CERT = 0x66
+ REQUEST_CREATE_COHN_CERT = 0x67
+ REQUEST_GET_COHN_CERT = 0x6E
+ REQUEST_GET_COHN_STATUS = 0x6F
+ RESPONSE_COHN_SETTING = 0xE5
+ RESPONSE_CLEAR_COHN_CERT = 0xE6
+ RESPONSE_CREATE_COHN_CERT = 0xE7
SET_CAMERA_CONTROL_RSP = 0xE9
SET_TURBO_MODE_RSP = 0xEB
+ RESPONSE_GET_COHN_CERT = 0xEE
+ RESPONSE_GET_COHN_STATUS = 0xEF
GET_PRESET_STATUS_RSP = 0xF2
PRESET_MODIFIED_NOTIFICATION = 0xF3
LIVESTREAM_STATUS_RSP = 0xF4
@@ -119,7 +129,7 @@ class ActionId(GoProEnum):
INTERNAL_FF = 0xFF
-class FeatureId(GoProEnum):
+class FeatureId(GoProIntEnum):
"""ID's that group protobuf commands"""
NETWORK_MANAGEMENT = 0x02
@@ -128,7 +138,7 @@ class FeatureId(GoProEnum):
QUERY = 0xF5
-class SettingId(GoProEnum):
+class SettingId(GoProIntEnum):
"""Setting ID's that identify settings and are written to GoProUUIDs.CQ_SETTINGS."""
RESOLUTION = 2
@@ -252,7 +262,7 @@ class SettingId(GoProEnum):
INVALID_FOR_TESTING = 0xFF
-class QueryCmdId(GoProEnum):
+class QueryCmdId(GoProIntEnum):
"""Command ID that is written to GoProUUIDs.CQ_QUERY."""
GET_SETTING_VAL = 0x12
@@ -273,7 +283,7 @@ class QueryCmdId(GoProEnum):
INVALID_FOR_TESTING = 0xFF
-class StatusId(GoProEnum):
+class StatusId(GoProIntEnum):
"""Status ID to identify statuses sent to GoProUUIDs.CQ_QUERY or received from GoProUUIDs.CQ_QUERY_RESP."""
BATT_PRESENT = 1
@@ -380,7 +390,7 @@ class StatusId(GoProEnum):
TOTAL_SD_SPACE_KB = 117
-class WebcamStatus(GoProEnum):
+class WebcamStatus(GoProIntEnum):
"""Webcam Statuses / states"""
OFF = 0
@@ -389,7 +399,7 @@ class WebcamStatus(GoProEnum):
LOW_POWER_PREVIEW = 3
-class WebcamError(GoProEnum):
+class WebcamError(GoProIntEnum):
"""Errors common among Webcam commands"""
SUCCESS = 0
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/cohn.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/cohn.py
new file mode 100644
index 00000000..2bf126c6
--- /dev/null
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/demos/cohn.py
@@ -0,0 +1,78 @@
+# cohn.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
+# This copyright was auto-generated on Tue Oct 24 19:08:07 UTC 2023
+
+"""Entrypoint for configuring and demonstrating Camera On the Home Network (COHN)."""
+
+from __future__ import annotations
+
+import argparse
+import asyncio
+import logging
+
+from rich.console import Console
+
+from open_gopro import WirelessGoPro
+from open_gopro.logger import setup_logging
+from open_gopro.util import add_cli_args_and_parse
+
+console = Console() # rich consoler printer
+
+logger: logging.Logger
+
+MDNS_SERVICE = "_gopro-web._tcp.local."
+
+
+async def main(args: argparse.Namespace) -> None:
+ global logger
+ logger = setup_logging(__name__, args.log)
+
+ gopro: WirelessGoPro | None = None
+ try:
+ # Start with Wifi Disabled (i.e. don't allow camera in AP mode).
+ async with WirelessGoPro(args.identifier, enable_wifi=False) as gopro:
+ if await gopro.is_cohn_provisioned:
+ console.print("[yellow]COHN is already provisioned")
+ else:
+ if not args.ssid or not args.password:
+ raise ValueError("COHN needs to be provisioned but you didn't pass SSID credentials.")
+ assert await gopro.connect_to_access_point(args.ssid, args.password)
+ assert await gopro.configure_cohn()
+
+ console.print("[blue]COHN is ready for communication. Dropping the BLE connection.")
+
+ # Prove we can communicate via the COHN HTTP channel without a BLE or Wifi connection
+ assert (await gopro.http_command.get_camera_state()).ok
+
+ except Exception as e: # pylint: disable = broad-except
+ logger.error(repr(e))
+
+ if gopro:
+ await gopro.close()
+
+
+def parse_arguments() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Provision / connect a camera for COHN. SSID and password must be passed if COHN is not currently provisioned."
+ )
+ parser.add_argument(
+ "--ssid",
+ type=str,
+ help="WiFi SSID to connect to if not currently provisioned for COHN.",
+ default=None,
+ )
+ parser.add_argument(
+ "--password",
+ type=str,
+ help="Password of WiFi SSID.",
+ default=None,
+ )
+ return add_cli_args_and_parse(parser, wifi=False)
+
+
+# Needed for poetry scripts defined in pyproject.toml
+def entrypoint() -> None:
+ asyncio.run(main(parse_arguments()))
+
+
+if __name__ == "__main__":
+ entrypoint()
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/controllers.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/controllers.py
deleted file mode 100644
index e2a7a8e1..00000000
--- a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/controllers.py
+++ /dev/null
@@ -1,812 +0,0 @@
-# controllers.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Wed Aug 17 20:05:18 UTC 2022
-
-"""GUI controllers and associated common functionality"""
-
-from __future__ import annotations
-
-import asyncio
-import datetime
-import enum
-import logging
-import queue
-import tkinter as tk
-from abc import ABC, abstractmethod
-from dataclasses import dataclass
-from functools import partial
-from pathlib import Path
-from typing import Any, Callable, Optional, Union, no_type_check
-
-import cv2
-import PIL.Image
-import PIL.ImageTk
-import wrapt
-
-from open_gopro.demos.gui.components import models, views
-from open_gopro.logger import add_logging_handler, setup_logging
-from open_gopro.util import pretty_print
-
-ResponseHandlerType = Callable[[str, models.GoProResp], None]
-
-
-def create_widget(
- master: tk.Widget, view: type[views.View], controller: Controller, *args: Any, **kwargs: Any
-) -> views.View:
- """Create a widget, binding a view to a controller
-
- Args:
- master (tk.Widget): master to use for widget
- view (type[views.View]): type of view to use
- controller (Controller): controller instance to use
- args (Any): positional arguments
- kwargs (Any): keyword arguments
-
- Returns:
- views.View: instantiated view (i.e. widget)
- """
- v = view(master, *args, **kwargs)
- controller.bind(v)
- return v
-
-
-class MissingArgument(Exception):
- """Exception raised when a message is attempted to be sent without all of its arguments"""
-
- def __init__(self, argument: Optional[str]) -> None:
- super().__init__(f"Missing required argument for {argument}")
-
-
-class BadArgumentValue(Exception):
- """Exception raised when an argument fails a validator check"""
-
- def __init__(self, argument: Optional[str]) -> None:
- super().__init__(f"Bad value for argument {argument}")
-
-
-@wrapt.decorator
-def background(wrapped: Callable, instance: Controller, args: Any, kwargs: Any) -> None:
- """Perform an action in the background in order to not block the main GUI processing
-
- Args:
- wrapped (Callable): action to perform
- instance (Controller): controller that owns action
- args (Any): positional arguments
- kwargs (Any): keyword arguments
- """
- instance.loop.create_task(wrapped(*args, **kwargs))
-
-
-class Controller(ABC):
- """Controller base class
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- """
-
- controllers: list[Controller] = []
-
- def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
- self.loop = loop
- self._logger: Optional[logging.Logger] = None
- Controller.controllers.append(self)
-
- @property
- def logger(self) -> logging.Logger:
- """Get the controller's logger
-
- Raises:
- RuntimeError: logger has not yet been configured
-
- Returns:
- logging.Logger: logger
- """
- if self._logger:
- return self._logger
- raise RuntimeError("Logger not yet configured")
-
- @logger.setter
- def logger(self, logger: logging.Logger) -> None:
- self._logger = logger
-
- @no_type_check
- @abstractmethod
- def bind(self, view: views.View) -> None:
- """Bind a view to the controller
-
- Args:
- view (views.View): view to bind
- """
- raise NotImplementedError
-
- def as_async(self, action: Callable, *args: Any, **kwargs: Any) -> Any:
- """Create an asynchronous coroutine from a synchronous function / method
-
- Args:
- action (Callable): function / method
- *args (Any): positional arguments to action
- **kwargs (Any): keyword arguments to action
-
- Returns:
- Any: asynchronous coroutine. Should be awaited in an async method.
- """
- return self.loop.run_in_executor(None, partial(action, *args, **kwargs))
-
-
-class SplashScreen(Controller):
- """Splash screen used to connect a GoPro
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- model (models.GoProModel): GoPro device
- """
-
- def __init__(self, loop: asyncio.AbstractEventLoop, model: models.GoProModel) -> None:
- super().__init__(loop)
- self.model = model
- self._are_models_started = False
- self.view: views.SplashScreen
-
- @property
- def are_models_started(self) -> bool:
- """Are all of the models started?
-
- Returns:
- bool: yes or no
- """
- return self._are_models_started
-
- def bind(self, view: views.SplashScreen) -> None:
- """Bind a splash screen view
-
- Args:
- view (views.SplashScreen): view to bind
- """
- self.view = view
- self.view.create_view(lambda: self.view.after(1, self.open_models))
-
- async def update(self) -> None:
- """Update the GUI to display changes"""
- self.view.root.update()
- await asyncio.sleep(0)
-
- @background
- async def open_models(self) -> None:
- """Open models in the background"""
- device = self.view.device_select.get()
- await self.as_async(self.model.start, device)
- self._are_models_started = True
-
-
-class Menubar(Controller):
- """Menu bar controller
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- on_quit (Callable): command to call when quit is clicked
- """
-
- def __init__(self, loop: asyncio.AbstractEventLoop, on_quit: Callable) -> None:
- super().__init__(loop)
- self.quit = on_quit
- self.view: views.Menubar
-
- def bind(self, view: views.Menubar) -> None:
- """Bind a menubar view
-
- Args:
- view (views.Menubar): view to bind
- """
- self.view = view
- view.create_view()
- self.view.fileMenu.add_command(label="Exit", command=self.quit)
-
-
-class StatusBar(Controller):
- """Status bar
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- """
-
- class Ble(enum.Enum):
- """BLE status options"""
-
- IDLE = views.StatusBar.Color.RED
- CONNECTING = views.StatusBar.Color.YELLOW
- CONNECTED = views.StatusBar.Color.GREEN
-
- class Wifi(enum.Enum):
- """WiFi status options"""
-
- IDLE = views.StatusBar.Color.RED
- CONNECTING = views.StatusBar.Color.YELLOW
- CONNECTED = views.StatusBar.Color.GREEN
-
- class Encoding(enum.Enum):
- """Encoding status options"""
-
- ON = views.StatusBar.Color.RED
- OFF = views.StatusBar.Color.GREEN
-
- class Ready(enum.Enum):
- """Ready status options"""
-
- READY = views.StatusBar.Color.GREEN
- BUSY = views.StatusBar.Color.RED
-
- class Stream(enum.Enum):
- """Stream status options"""
-
- IDLE = views.StatusBar.Color.RED
- STARTING = views.StatusBar.Color.YELLOW
- READY = views.StatusBar.Color.GREEN
-
- StatusType = Union[Ble, Wifi, Encoding, Ready, Stream]
-
- def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
- super().__init__(loop)
- self.view: views.StatusBar
-
- def bind(self, view: views.StatusBar) -> None:
- """Bind a StatusBar view
-
- Args:
- view (views.StatusBar): view to bind
- """
- self.view = view
- view.create_view()
-
- def update_status(self, status: StatusType) -> None:
- """Update a status. The relevant status icon will be found based on the passed in status
-
- Args:
- status (StatusType): new status
-
- Raises:
- ValueError: a status was passed that is not associated with any status icon
- """
-
- if status in StatusBar.Ble:
- self.view.update_status(self.view.ble_status, status.value, status.name.replace("_", " ").title())
- elif status in StatusBar.Wifi:
- self.view.update_status(self.view.wifi_status, status.value, status.name.replace("_", " ").title())
- elif status in StatusBar.Ready:
- self.view.update_status(self.view.ready_status, status.value, status.name.replace("_", " ").title())
- elif status in StatusBar.Encoding:
- self.view.update_status(self.view.encoding_status, status.value, status.name.replace("_", " ").title())
- elif status in StatusBar.Stream:
- self.view.update_status(self.view.stream_status, status.value, status.name.replace("_", " ").title())
- else:
- raise ValueError(f"No handler for status {status}")
-
- def handle_updates(self, identifier: enum.Enum, value: Any) -> None:
- """Check to see if an update requires a status change
-
- Args:
- identifier (enum.Enum): identifier to analyze
- value (Any): value to analyze
- """
- if identifier == models.constants.StatusId.ENCODING:
- self.update_status(StatusBar.Encoding.ON if value else StatusBar.Encoding.OFF)
- elif identifier == models.constants.StatusId.SYSTEM_BUSY:
- self.update_status(StatusBar.Ready.BUSY if value else StatusBar.Ready.READY)
-
-
-class Video(Controller):
- """Displays a video source in a tkinter window
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- """
-
- # pylint: disable=no-member
- def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
- super().__init__(loop)
- self.image: PIL.Image.Image
- self.photo: PIL.ImageTk.PhotoImage
- self.vid: Any = None
- self.height: int
- self.width: int
- self.status_bar: StatusBar
- self.should_stop = False
- self.view: views.Video
-
- @property
- def is_open(self) -> bool:
- """Is the video currently open?
-
- Returns:
- bool: yes if open, no otherwise
- """
- return self.vid is not None and self.vid.isOpened()
-
- def bind(self, view: views.Video) -> None:
- """Bind a video view
-
- Args:
- view (views.Video): view to bind
- """
- self.view = view
- self.view.create_view(self.start, self.stop)
-
- def bind_status_bar(self, controller: StatusBar) -> None:
- """Bind a status bar controller to use for updating statuses
-
- Args:
- controller (StatusBar): controller to bind
- """
- self.status_bar = controller
-
- def start(self, source: Optional[str]) -> None:
- """Start playing a video
-
- If source is not passed, it will be extracted from the input entry
-
- Args:
- source (Optional[str]): desired video source or None to get from View
- """
- if self.is_open:
- views.popup_message("Error", "Stream is already started. Must stop first.")
- return
-
- video_source = source or self.view.url_entry.get()
- if video_source in (None, ""):
- views.popup_message("Error", "Missing required stream URL")
- return
- assert video_source
- # See if this is to be interpreted as an int
- self.status_bar.update_status(StatusBar.Stream.STARTING)
- # Open the video source
- self.logger.debug(f"Starting video from: {video_source}")
- self.vid = cv2.VideoCapture(video_source)
- if not self.is_open:
- views.popup_message("Error", f"Unable to open video source {video_source}")
- self.height = self.view.canvas.winfo_height()
- self.width = self.view.canvas.winfo_width()
- self.should_stop = False
- self.status_bar.update_status(StatusBar.Stream.READY)
- self.get_frame()
-
- def handle_auto_start(self, identifier: str, response: models.GoProResp) -> None:
- """Auto start live or preview stream
-
- Args:
- identifier (str): message that was sent
- response (models.GoProResp): response that was received
- """
- # Get binary's (which are of type Path) are not handled. TODO updating typing for this
- if not isinstance(response, models.GoProResp) or not response.ok:
- return
-
- # TODO This is broken
- video_source: Optional[str] = None
- if str(identifier).lower() == "livestream":
- video_source = response.identifier # type: ignore
- elif str(identifier).lower() == "set preview stream" and "start" in (str(response.identifier) or ""):
- video_source = models.PREVIEW_STREAM_URL
-
- if video_source:
- if self.is_open:
- self.logger.debug("Stopping current stream due to new stream starting.")
- self.stop()
- self.start(video_source)
-
- def stop(self) -> None:
- """Stop playing the video"""
- if not self.is_open:
- views.popup_message("Error", "Stream is not started.")
- return
- self.should_stop = True
-
- def get_frame(self) -> None:
- """Get an individual video frame to display"""
- ret = False
- frame = None
- if self.vid.isOpened():
- ret, frame = self.vid.read()
-
- if ret and frame is not None:
- frame = cv2.resize(frame, (self.width, self.height))
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
- self.image = PIL.Image.fromarray(frame)
- self.photo = PIL.ImageTk.PhotoImage(image=self.image)
- self.view.display_frame(self.photo)
-
- if self.should_stop:
- self.vid.release()
- self.status_bar.update_status(StatusBar.Stream.IDLE)
- else:
- self.view.after(5, self.get_frame)
-
- def __del__(self) -> None:
- # Release the video source when the object is destroyed
- if self.vid.isOpened():
- self.vid.release()
-
-
-class Log(Controller, logging.Handler):
- """Controller to use for displaying logs.
-
- Is capable of binding multiple views and then selecting per-record which one to use
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- level (logging._Level, optional): log level to start at. Defaults to logging.INFO.
- """
-
- def __init__(self, loop: asyncio.AbstractEventLoop, level: int = logging.INFO) -> None:
- Controller.__init__(self, loop)
- logging.Handler.__init__(self, level)
- self.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d %(message)s", datefmt="%H:%M:%S"))
- self.setLevel(level)
- self.log_q: queue.Queue[logging.LogRecord] = queue.Queue()
- # Configure logging
- try:
- logger = setup_logging(__name__, Path("gui.log"))
- except RuntimeError:
- # logger is already configured, just get it
- logger = logging.getLogger(__name__)
- add_logging_handler(self)
- self.logger = logger
- self.views: list[views.Log] = []
-
- def bind(self, view: views.Log) -> None:
- """Bind a log view
-
- Args:
- view (views.Log): view to bind
- """
- view.create_view()
- self.views.append(view)
-
- def emit(self, record: logging.LogRecord) -> None:
- """Enqueue the log record string for later displaying in GUI thread
-
- Args:
- record (logging.LogRecord): record to enqueue
- """
- try:
- self.log_q.put_nowait(record)
- except ValueError:
- return
-
- def log_queued_messages(self, view: views.Log) -> None:
- """Dequeue log records and display them until the queue is empty
-
- WARNING! This must be called from the main GUI thread
-
- Args:
- view (views.Log): view to display records in
-
- Raises:
- RuntimeError: this view is not bound
- """
- if view not in self.views:
- raise RuntimeError("This view has not been bound to the log controller")
- while not self.log_q.empty() and (record := self.log_q.get_nowait()):
- view.create_log_entry(record)
-
-
-class MessagePalette(Controller):
- """Pallet to hierarchically display messages and allow user to select.
-
- This control is used to control a message palette and param form
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- model (models.GoProModel): model to get messages from
- """
-
- @dataclass
- class MessageMeta:
- """Couple the message and its identifier"""
-
- identifier: str
- message: Callable
-
- @dataclass
- class Argument:
- """Get, store, and process a message argument
-
- Args:
- getter (Callable): used to get the string value of the argument from a GUI element
- adapter (Callable): used to adapter the argument string value
- validator (Callable): used to validate the adapted value
- name (Optional[str]): name of argument
- """
-
- getter: Callable[[], Any]
- adapter: Callable[[Any], Any]
- validator: Callable[[Any], None]
- name: Optional[str] = None
-
- def process(self, validate: bool = True) -> Any:
- """Get, adapt, and optionally validate an argument
-
- Args:
- validate (bool): Should argument value be validated?. Defaults to True.
-
- Raises:
- MissingArgument: was not able to get the argument value
- BadArgumentValue: validation failed
-
- Returns:
- Any: argument value
- """
- value = self.getter()
- try:
- value = self.adapter(value)
- except Exception as e:
- raise MissingArgument(self.name) from e
- if validate:
- try:
- self.validator(value)
- except Exception as e:
- raise BadArgumentValue(self.name) from e
- return value
-
- def __init__(self, loop: asyncio.AbstractEventLoop, model: models.GoProModel) -> None:
- Controller.__init__(self, loop)
- self.model = model
- self.active_message: Optional[MessagePalette.MessageMeta] = None
- self.active_arguments: list[MessagePalette.Argument] = []
- self.param_form: views.ParamForm
- self.message_palette: views.MessagePalette
- self.response_handlers: list[ResponseHandlerType] = []
-
- def _bind_message_palette(self, view: views.MessagePalette) -> None:
- """Bind message pallet view
-
- Args:
- view (views.MessagePalette): view to bind
- """
- self.message_palette = view
- self.message_palette.create_view(self.model.message_dict)
- self.message_palette.tv.bind("", self.on_message_selection)
-
- def _bind_param_form(self, view: views.ParamForm) -> None:
- """Bind param form view
-
- Args:
- view (views.ParamForm): view to bind
- """
- self.param_form = view
- self.param_form.create_view()
-
- def bind(self, view: Union[views.MessagePalette, views.ParamForm]) -> None:
- """Bind a view (message palette or param form)
-
- Args:
- view (Union[views.MessagePalette, views.ParamForm]): view to bind
-
- Raises:
- TypeError: Invalid view
- """
- if isinstance(view, views.MessagePalette):
- self._bind_message_palette(view)
- elif isinstance(view, views.ParamForm):
- self._bind_param_form(view)
- else:
- raise TypeError("Only MessagePalette and ParamForms can be bound to MessagePalette Controller")
-
- def register_response_handler(self, handler: ResponseHandlerType) -> None:
- """Register response handler for message responses
-
- Args:
- handler (ResponseHandlerType): response handler controller
- """
- self.response_handlers.append(handler)
-
- def build_param_entries(self, message: models.Message) -> None:
- """Display all params for a given message in the param form
-
- Args:
- message (MessageType): message to build params for
-
- Raises:
- ValueError: found a parameter with a currently unhandled argument type
- """
- for adapter, validator, arg_type, arg_name in zip(*self.model.get_message_info(message)):
- getter: Callable
- if arg_type is None:
- continue
- if arg_type == bool:
- getter = self.param_form.create_option_menu(arg_name, ["True", "False"])
- elif arg_type in (int, float, str, bytes, bytearray, Path):
- getter = self.param_form.create_entry(arg_name)
- elif issubclass(arg_type, enum.Enum):
- getter = self.param_form.create_option_menu(arg_name, [x.name for x in arg_type])
- elif arg_type == datetime.datetime:
- getters: list[views.GetterType] = []
- getters.append(self.param_form.create_entry("year"))
- getters.append(self.param_form.create_entry("month"))
- getters.append(self.param_form.create_entry("day"))
- getters.append(self.param_form.create_entry("hour"))
- getters.append(self.param_form.create_entry("minute"))
- getters.append(self.param_form.create_entry("second"))
- getter = lambda getters=getters: tuple(int(get()) for get in getters)
- validator = lambda x: isinstance(x, datetime.datetime)
- adapter = lambda x: datetime.datetime(*x)
- else:
- raise ValueError("Unexpected argument type")
-
- self.active_arguments.append(MessagePalette.Argument(getter, adapter, validator, arg_name))
-
- def on_message_selection(self, event: Any) -> None:
- """Called when a message is selected to create param entries
-
- Args:
- event (Any): GUI event that prompted message selection
- """
- try:
- item_id = int(self.message_palette.tv.identify("item", event.x, event.y))
- if item_id >= views.MAX_TREEVIEW_ID:
- return
- except ValueError: # User clicked outside of message area
- return
- self.param_form.reset_view()
- self.active_message = None
- self.active_arguments.clear()
- identifier, message = self.model.messages[item_id]
- self.param_form.create_message(identifier)
-
- if self.model.is_command(message):
- if not self.model.is_compound_command(message):
- self.active_arguments.append(
- MessagePalette.Argument(
- getter=lambda: self.model.gopro,
- adapter=lambda x: x,
- validator=lambda x: x.is_open,
- )
- )
- self.build_param_entries(message)
- self.param_form.create_button("Send message", self.message_sender_factory(use_args=True))
- else: # Setting or Status
- if self.model.is_ble(message):
- self.param_form.create_button("Get Value", self.message_sender_factory("get_value"))
- self.param_form.create_button(
- "Register for Value Updates", self.message_sender_factory("register_value_update")
- )
- self.param_form.create_button(
- "Unregister for Value Updates", self.message_sender_factory("unregister_value_update")
- )
- if self.model.is_setting(message):
- self.param_form.create_button(
- "Get Capabilities", self.message_sender_factory("get_capabilities_values")
- )
- self.param_form.create_button(
- "Register for Capability Updates",
- self.message_sender_factory("register_capability_update"),
- )
-
- self.param_form.create_button(
- "Unregister for Capability Updates",
- self.message_sender_factory("unregister_capability_update"),
- )
- if not self.model.is_status(message):
- self.param_form.create_button("Set Value", self.message_sender_factory("set", use_args=True))
- self.build_param_entries(message)
-
- self.active_message = MessagePalette.MessageMeta(identifier, message)
-
- def message_sender_factory(self, attribute: Optional[str] = None, use_args: bool = False) -> Callable:
- """Build a method to be called when a message is requested to be sent
-
- Args:
- attribute (Optional[str], optional): Method of active message to use. Defaults to None (use active
- message directly).
- use_args (bool): Should the active args be passed to the message?. Defaults to False.
-
- Returns:
- Callable: method to use
- """
-
- @background
- async def on_message_send(self: MessagePalette) -> Optional[models.GoProResp]:
- assert self.active_message
- method = (
- self.active_message.message if attribute is None else getattr(self.active_message.message, attribute)
- )
- args = []
- kwargs = {}
- if use_args:
- for arg in self.active_arguments:
- value = arg.process()
- if name := arg.name:
- kwargs[name] = value
- else:
- args.append(value)
-
- try:
- response = await self.as_async(method, *args, **kwargs)
- except Exception as e: # pylint: disable=broad-except
- views.popup_message("Error", str(e))
- return None
-
- for handler in self.response_handlers:
- handler(self.active_message.identifier, response)
- return response
-
- return on_message_send.__get__(self, None)
-
-
-class StatusTab(Controller):
- """Status tab controller to display updates in various status elements.
-
- New updates will be highlighted
-
- Args:
- loop (asyncio.AbstractEventLoop): already running asyncio loop to use for controllers
- model (models.GoProModel): model to build updates from
- poll_period (int, optional): how often to refresh updates (in ms). Defaults to 200.
- """
-
- def __init__(self, loop: asyncio.AbstractEventLoop, model: models.GoProModel, poll_period: int = 200) -> None:
- super().__init__(loop)
- self.model = model
- self.period = poll_period
- self.statusbar: Optional[StatusBar] = None
- self.view: views.StatusTab
-
- def bind(self, view: views.StatusTab) -> None:
- """Bind a status tab view
-
- Args:
- view (views.StatusTab): view to bind
- """
- self.view = view
- view.create_view()
-
- def bind_statusbar(self, statusbar: StatusBar) -> None:
- """Bind a status bar
-
- Args:
- statusbar (StatusBar): view to bind
- """
- self.statusbar = statusbar
-
- def _display_updates(self, response: Optional[models.GoProResp] = None) -> None:
- """Display any updates take from the response
-
- Args:
- response (Optional[models.GoProResp], optional): response to get updates from. Defaults to None.
- If none, model will check for asynchronous updates
- """
-
- cleared = False
- for identifier, value, update_type in self.model.updates(response):
- # Clear recents
- if not cleared:
- self.view.clear_recent_tags()
- cleared = True
-
- # A list must be a capability update
- if update_type is models.GoProModel.Update.CAPABILITY:
- self.view.update_capability(identifier, pretty_print(value))
- continue
- if update_type is models.GoProModel.Update.PROTOBUF:
- continue
- if update_type is models.GoProModel.Update.SETTING:
- self.view.update_setting(identifier, pretty_print(value))
- elif update_type is models.GoProModel.Update.STATUS:
- self.view.update_status(identifier, pretty_print(value))
-
- if self.statusbar:
- self.statusbar.handle_updates(identifier, value)
-
- def display_async_updates(self) -> None:
- """Display any updates that were received asynchronously (i.e. not message responses)"""
- self._display_updates()
-
- def display_response_updates(self, _: str, response: Optional[models.GoProResp]) -> None:
- """Display updates from the message response
-
- Args:
- response (Optional[models.GoProResp]): response to get updates from
- """
- self._display_updates(response)
-
- def poll(self) -> None:
- """Display updates every poll period ms"""
- self.display_async_updates()
- self.view.after(self.period, self.poll)
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/models.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/models.py
deleted file mode 100644
index f94d5cf7..00000000
--- a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/models.py
+++ /dev/null
@@ -1,450 +0,0 @@
-# models.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Wed Aug 17 20:05:18 UTC 2022
-
-"""GUI models and associated common functionality"""
-
-# pylint: disable = reimported, unused-import
-# NOTE! The reason for the seemingly redundant, unnecessary import here is because we are using eval
-# to dynamically build messages
-
-from __future__ import annotations
-
-import asyncio
-import datetime
-import enum
-import inspect
-import logging
-import re
-import typing
-from pathlib import Path
-from typing import (
- Any,
- Callable,
- Final,
- Generator,
- Optional,
- Pattern,
- Union,
- no_type_check,
-)
-
-import construct
-from wrapt.decorators import BoundFunctionWrapper
-
-import open_gopro.api.params # needed for dynamic execution
-import open_gopro.api.params as Params
-from open_gopro import WirelessGoPro, constants, proto, types
-from open_gopro.api import BleSetting, BleStatus, HttpSetting
-from open_gopro.communicator_interface import (
- BleMessage,
- GoProBle,
- GoProHttp,
- HttpMessage,
- Message,
- Messages,
-)
-from open_gopro.models.response import GoProResp
-
-PREVIEW_STREAM_URL: Final = r"udp://127.0.0.1:8554"
-
-logger = logging.getLogger(__name__)
-
-
-class CompoundGoPro(WirelessGoPro):
- """A GoPro that supports sending compound commands"""
-
- def __init__(self, target: Optional[Pattern] = None) -> None:
- """Constructor
-
- Args:
- target (Optional[Pattern], optional): BLE device (camera) to search for. Defaults to None (first
- found camera will be connected to).
- """
-
- super().__init__(target)
- self.compound_command = CompoundCommands(self)
-
-
-class GoProModel:
- """GoPro model interface for controllers"""
-
- class Update(enum.Enum):
- """The type of update that the response / element corresponds to"""
-
- SETTING = enum.auto()
- STATUS = enum.auto()
- CAPABILITY = enum.auto()
- PROTOBUF = enum.auto()
-
- def __init__(self) -> None:
- # Initial instantiation just to get command strings
- self.gopro: CompoundGoPro = CompoundGoPro()
-
- async def start(self, identifier: Optional[Pattern]) -> None:
- """Open the model (i.e. connect BLE and Wifi to camera)
-
- Args:
- identifier (Optional[Pattern]): regex to connect to. Defaults to None (connect to first camera)
- """
- # Reinstantiate
- self.gopro = CompoundGoPro(target=identifier)
- await self.gopro.open()
-
- # NOTE: the following properties must be evaluated dynamically since self.gopro is changing
- # TODO hash and evaluate lazily
-
- @property
- def _message_types(self) -> list[Messages]:
- """Get the top level containers of the message types supported by the GoPro model
-
- Returns:
- list[Union[Commands, SettingsStatuses]]: list of message type containers
- """
- return [
- self.gopro.ble_command,
- self.gopro.ble_setting,
- self.gopro.ble_status,
- self.gopro.http_command,
- self.gopro.http_setting,
- self.gopro.compound_command,
- ]
-
- @property
- def messages(self) -> list[tuple[str, Message]]:
- """Get all of the available BLE and Wifi Messages
-
- Returns:
- list[Message]: list of available messages
- """
- messages = []
- for message_type in self._message_types:
- c = list(message_type.items())
- c.sort(key=lambda x: str(x[0]))
- messages.extend(c)
- return messages
-
- @property
- def message_dict(self) -> dict[str, list[str]]:
- """Get flattened dictionary of message indexed by their string name
-
- Returns:
- dict[str, list[str]]: flattened dict
- """
- d = {}
- for messages in self._message_types:
- d[type(messages).__name__] = [str(message) for message in messages]
- d[type(messages).__name__].sort()
- return d
-
- @classmethod
- def is_ble(cls, message: Message) -> bool:
- """Is this message a BLE message?
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- bool: True if yes, False otherwise
- """
- return type(message) in (BleSetting, BleStatus, BleMessage)
-
- @classmethod
- def is_wifi(cls, message: Message) -> bool:
- """Is this message a Wifi Message?
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- bool: True if yes, False otherwise
- """
- return type(message) in (HttpMessage, HttpSetting)
-
- @classmethod
- def is_setting(cls, message: Message) -> bool:
- """Is this message a setting?
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- bool: True if yes, False otherwise
- """
- return type(message) in (BleSetting, HttpSetting)
-
- @classmethod
- def is_status(cls, message: Message) -> bool:
- """Is this message a status?
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- bool: True if yes, False otherwise
- """
- return isinstance(message, BleStatus)
-
- @classmethod
- def is_command(cls, message: Message) -> bool:
- """Is this message a command (i.e. not setting or status)?
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- bool: True if yes, False otherwise
- """
- return isinstance(message, (BoundFunctionWrapper, CompoundCommand))
-
- @classmethod
- def is_compound_command(cls, message: Message) -> bool:
- """Is this message a compound command (i.e. a series of commands only used in the GUI)?
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- bool: True if yes, False otherwise
- """
- return isinstance(message, CompoundCommand)
-
- @classmethod
- @no_type_check
- def get_args_info(cls, message: Message) -> tuple[list[str], list[type]]:
- """Get the argument names and types for a given message
-
- Args:
- message (Message): Message to analyze
-
- Returns:
- tuple[list[str], list[type]]: (list[argument names], list[argument types])
- """
- arg_types: list[type] = []
- arg_names: list[str] = []
- method_info = inspect.getfullargspec(message if (is_command := cls.is_command(message)) else message.set)
- for arg in method_info.kwonlyargs if is_command else method_info.args[1:]:
- if arg.startswith("_"):
- continue
- try:
- # Assume this is a generic and try to get the standard type of its original class
- arg_type = re.search(r"\[.*\]", str(message.__orig_class__))[0].strip("[]")
- except AttributeError:
- # This is not a generic so use the annotations from inspect
- arg_type = method_info.annotations[arg]
-
- # TODO temporary workaround to avoid handling parameter sequences. This is not an actual solution
- if "optional" in arg_type.lower():
- continue
- arg_types.append(eval(arg_type)) # pylint: disable = eval-used
- arg_names.append(arg)
- return arg_names, arg_types
-
- def get_message_info(self, message: Message) -> tuple[list[Callable], list[Callable], list[type], list[str]]:
- """For a given message, get its adapters, validator, argument types, and argument names
-
- Args:
- message (Message): Message to analyze
-
- Raises:
- ValueError: unhandled type of argument
-
- Returns:
- tuple[list[Callable], list[Callable], list[type], list[str]]:
- (list[adapters], list[validators], list[arg_types], list[arg_names])
- """
- adapters: list[Callable] = []
- validators: list[Callable] = []
- names, arg_types = self.get_args_info(message)
-
- # Build adapters and validators
- # NOTE! These lambdas must define default variables since they are lost once the for loop scope exits
- for arg_type in arg_types:
- if "enum" in str(arg_type).lower():
- try:
- format_field = message.param_builder.fmtstr # type: ignore
- for construct_field in (construct.Int8ub, construct.Int32ub, construct.Int16ub):
- if construct_field.fmtstr == format_field:
- validators.append(lambda x, cs=construct_field: cs.build(int(x)))
- adapters.append(lambda x, adapt=arg_type: adapt[x])
- break
- else:
- raise ValueError("unrecognized format field" + str(format_field))
- except AttributeError:
- validators.append(lambda x, adapt=arg_type: adapt(x))
- adapters.append(lambda x, adapt=arg_type: adapt[x])
- elif arg_type is datetime.datetime:
- # Special case to be handled by controller
- adapters.append(datetime.datetime)
- validators.append(datetime.datetime)
- else:
- validators.append(lambda _: True)
- if arg_type is bool:
- adapters.append(lambda x: bool(x.lower() == "true"))
- if arg_type in [bytes, bytearray]: # type: ignore
- adapters.append(lambda x, adapt=arg_type: adapt(x, "utf-8"))
- else:
- adapters.append(lambda x, adapt=arg_type: adapt(x))
-
- return adapters, validators, arg_types, names
-
- def updates(
- self, response: Optional[GoProResp] = None
- ) -> Generator[tuple[enum.IntEnum, Any, GoProModel.Update], None, None]:
- """Generate updates from a response or any asynchronous updates
-
- Args:
- response (Optional[GoProResp], optional): Response to analyze. If none, get all asynchronous
- updates. Defaults to None.
-
- Yields:
- Generator[tuple[enum.IntEnum, Any, GoProModel.Update], None, None]: generates (updates identifier,
- update value, update type)
- """
-
- def get_update_type(container: GoProResp, identifier: types.ResponseType) -> GoProModel.Update:
- if container.protocol is GoProResp.Protocol.BLE:
- if container.identifier in [
- constants.QueryCmdId.GET_CAPABILITIES_VAL,
- constants.QueryCmdId.REG_CAPABILITIES_UPDATE,
- constants.QueryCmdId.SETTING_CAPABILITY_PUSH,
- ]:
- return GoProModel.Update.CAPABILITY
- if container.identifier in [
- constants.QueryCmdId.GET_SETTING_VAL,
- constants.QueryCmdId.REG_SETTING_VAL_UPDATE,
- constants.QueryCmdId.SETTING_VAL_PUSH,
- ]:
- return GoProModel.Update.SETTING
- if container.identifier in [
- constants.QueryCmdId.GET_STATUS_VAL,
- constants.QueryCmdId.REG_STATUS_VAL_UPDATE,
- constants.QueryCmdId.STATUS_VAL_PUSH,
- ]:
- return GoProModel.Update.STATUS
- # Must be protobuf
- return GoProModel.Update.PROTOBUF
- if container.protocol is GoProResp.Protocol.HTTP:
- if isinstance(identifier, constants.StatusId):
- return GoProModel.Update.STATUS
- if isinstance(identifier, constants.SettingId):
- return GoProModel.Update.SETTING
- raise TypeError(f"Received unexpected WiFi identifier: {identifier}")
- raise TypeError(f"Received unexpected protocol {container.protocol}")
-
- if isinstance(response, GoProResp):
- for identifier, value in response.data():
- if type(identifier) in (constants.SettingId, constants.StatusId):
- yield identifier, value, get_update_type(response, identifier)
- elif response is None:
- # TODO need to update to new method. This is broken
- while update := self.gopro.get_notification(0): # type: ignore
- for identifier, value in update.items():
- yield identifier, value, get_update_type(update, identifier)
-
-
-class CompoundCommand(Message):
- """Functionality that consists of multiple BLE and / or Wifi Messages"""
-
- def __init__(self, communicator: WirelessGoPro, identifier: Any, parser: Any = None) -> None:
- self._communicator = communicator
- super().__init__(identifier, parser)
-
- def __str__(self) -> str:
- return self._identifier
-
- def _as_dict(self, *_: Any, **kwargs: Any) -> types.JsonDict:
- """Return the command as a dict
-
- Args:
- *_ (Any): unused
- **kwargs (Any) : additional dict keys to append
-
- Returns:
- types.JsonDict: Message as dict
- """
- return {"protocol": "Complex", "id": self._identifier} | kwargs
-
-
-# pylint: disable = missing-class-docstring, arguments-differ
-class CompoundCommands(Messages[CompoundCommand, str, Union[GoProBle, GoProHttp]]):
- """The container for the compound commands"""
-
- def __init__(self, communicator: WirelessGoPro) -> None:
- """Constructor
-
- Args:
- communicator (WirelessGoPro): the communicator to send the commands
- """
-
- class LiveStream(CompoundCommand):
- async def __call__( # type: ignore
- self,
- *,
- ssid: str,
- password: str,
- url: str,
- window_size: proto.EnumWindowSize,
- lens_type: proto.EnumLens,
- min_bit: int,
- max_bit: int,
- start_bit: int,
- ) -> GoProResp:
- """Disable shutter, connect to WiFi, start livestream, and set shutter
-
- Args:
- ssid (str): SSID to connect to
- password (str): password of WiFi network
- url (str): url used to stream. Set to empty string to invalidate/cancel stream
- window_size (open_gopro.api.proto.EnumWindowSize): Streaming video resolution
- lens_type (open_gopro.api.proto.EnumLens): Streaming Field of View
- min_bit (int): Desired minimum streaming bitrate (>= 800)
- max_bit (int): Desired maximum streaming bitrate (<= 8000)
- start_bit (int): Initial streaming bitrate (honored if 800 <= value <= 8000)
-
- Returns:
- GoProResp: status and url to start livestream
- """
- await self._communicator.ble_command.set_shutter(shutter=Params.Toggle.DISABLE)
- await self._communicator.ble_command.register_livestream_status(
- register=[proto.EnumRegisterLiveStreamStatus]
- )
-
- await self._communicator.connect_to_access_point(ssid, password)
-
- # Start livestream
- await self._communicator.ble_command.set_livestream_mode(
- url=url,
- window_size=window_size,
- cert=bytes(),
- minimum_bitrate=min_bit,
- maximum_bitrate=max_bit,
- starting_bitrate=start_bit,
- lens=lens_type,
- )
-
- live_stream_ready = asyncio.Event()
-
- async def wait_for_livestream_ready(_: Any, value: proto.NotifyLiveStreamStatus) -> None:
- if value.live_stream_status == proto.EnumLiveStreamStatus.LIVE_STREAM_STATE_READY:
- live_stream_ready.set()
-
- self._communicator.register_update(
- wait_for_livestream_ready, constants.ActionId.LIVESTREAM_STATUS_NOTIF
- )
- logger.info("Starting livestream")
- assert (await self._communicator.ble_command.set_shutter(shutter=Params.Toggle.ENABLE)).ok
- logger.info("Waiting for livestream to be ready...\n")
- await live_stream_ready.wait()
-
- assert self._communicator.ble_command.set_shutter(shutter=Params.Toggle.DISABLE)
-
- return GoProResp(
- protocol=GoProResp.Protocol.BLE,
- status=constants.ErrorCode.SUCCESS,
- data=None,
- identifier="LiveStream",
- )
-
- self.livestream = LiveStream(communicator, "Livestream")
-
- super().__init__(communicator)
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/views.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/views.py
deleted file mode 100644
index 9d2393bc..00000000
--- a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/components/views.py
+++ /dev/null
@@ -1,896 +0,0 @@
-# views.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Wed Aug 17 20:05:18 UTC 2022
-
-"""GUI views and associated common functionality"""
-
-# pylint: disable = arguments-differ
-
-from __future__ import annotations
-
-import enum
-import json
-import logging
-import tkinter as tk
-import tkinter.scrolledtext as ScrolledText
-from abc import ABC, abstractmethod
-from tkinter import font, ttk
-from typing import (
- Any,
- Callable,
- Generator,
- Generic,
- Optional,
- Sequence,
- TypeVar,
- Union,
- cast,
- no_type_check,
-)
-from urllib.parse import parse_qs, urlparse
-
-from PIL import Image, ImageDraw, ImageTk
-
-from open_gopro.demos.gui.components import THEME, models
-from open_gopro.util import pretty_print
-
-MAX_TREEVIEW_ID = 1000000
-
-GetterType = Callable[[], Any]
-T = TypeVar("T")
-
-
-def sanitize_identifier(identifier: str) -> str:
- """Make the 'id' value human readable so that it looks the same for BLE and Wifi
-
- Args:
- identifier (str): identifier to sanitize
-
- Returns:
- str: sanitized response
- """
- identifier = (
- identifier.replace("_", " ")
- .lower()
- .removeprefix("cmdid.")
- .removeprefix("querycmdid.")
- .removeprefix("actionid.")
- .removeprefix("gopro/")
- .replace("/", " ")
- .title()
- )
-
- try:
- identifier = identifier.split("?")[0]
- except IndexError:
- pass
-
- return identifier
-
-
-class DefaultTextEntry(ttk.Entry, Generic[T]):
- """A Tkinter Entry with default text that disappears when clicked
-
- Args:
- default (T): default value to show in entry
-
- Raises:
- ValueError: invalid entry type
- """
-
- def __init__(self, default: T, *args: Any, **kwargs: Any) -> None:
- super().__init__(*args, **kwargs)
- tk_var_type: type[tk.Variable]
- if isinstance(default, str):
- tk_var_type = tk.StringVar
- elif isinstance(default, bool):
- tk_var_type = tk.BooleanVar
- elif isinstance(default, int):
- tk_var_type = tk.IntVar
- elif isinstance(default, float):
- tk_var_type = tk.DoubleVar
- else:
- raise ValueError("entry_type must be one of [str, bool, int, float]")
- self.default: T = default
- self.var: tk.Variable = tk_var_type(value=self.default)
- self.insert(0, self.default) # type: ignore
- self.bind("", self.focus_in)
-
- def focus_in(self, *_: Any) -> None:
- """Called when entry is focused on, to remove default text
-
- Args:
- *_ (Any): Not used
- """
- if self.var.get() == self.default:
- self.delete(0, "end")
-
- def get(self) -> Optional[T]: # type: ignore
- """Get the entry value. Will return none if the value is still default
-
- Returns:
- Optional[T]: value or None
- """
- return None if (current := cast(T, super().get())) == self.default else current
-
-
-def popup_message(title: str, message: str, button: str = "OK") -> None:
- """Pop up a message in a separate window with an exit button.
-
- Args:
- title (str): Title of window
- message (str): message to display
- button (str): text to display in exit button. Defaults to "OK".
- """
- top = tk.Toplevel()
- # Get current mouse position
- abs_coord_x = top.winfo_pointerx() - top.winfo_rootx()
- abs_coord_y = top.winfo_pointery() - top.winfo_rooty()
- # Place window at mouse
- top.geometry(f"400x200+{abs_coord_x}+{abs_coord_y}")
- top.title(title)
- label = tk.Label(top, text=message, font=("Mistral 10 bold"), wraplength=350, pady=20)
- label.pack()
-
- def exit_btn() -> None:
- """On exit, close window"""
- top.destroy()
- top.update()
-
- btn = ttk.Button(top, text=button, command=exit_btn)
- btn.pack()
-
-
-class View(ABC, tk.Widget):
- """View interface"""
-
- @no_type_check
- @abstractmethod
- def create_view(self) -> None:
- """Display the view (i.e. pack, grid, etc)"""
- raise NotImplementedError
-
-
-class SplashScreen(View, tk.Frame):
- """Initial splash screen to connect a device"""
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- width = kwargs.pop("width", 200)
- height = kwargs.pop("height", 200)
- tk.Frame.__init__(self, *args, width=width, height=height, **kwargs)
- self.root = tk.Tk()
- self.style = ttk.Style(self.root)
- self.style.theme_use(THEME)
- self.root.title("Open GoPro Device Selection")
- self.defaultFont = font.nametofont("TkDefaultFont")
- self.root.geometry(f"{width}x{height}")
- # Beta labal
- self.beta_label = ttk.Label(
- self.root,
- text="This is beta software!! Not all commands have been tested yet.",
- font="26",
- foreground="red",
- )
- # Choose
- self.choose_label = ttk.Label(self.root, text="Choose either: ", font="18")
- # Specific device input
- self.device_select_input = ttk.Frame(self.root)
- self.device_select = DefaultTextEntry(default="GoPro Device Name", master=self.device_select_input)
- self.device_select.grid(row=0, column=0, sticky="NSEW", padx=5)
- self.device_select_button = ttk.Button(self.device_select_input, text="Connect")
- self.device_select_button.grid(row=0, column=1)
- # Or..
- self.or_label = ttk.Label(self.root, text="or...", font="18")
- # Auto connect
- self.auto_connect_button = ttk.Button(self.root, text="Auto Connect To First Found GoPro")
-
- def create_view(self, command: Callable) -> None:
- """Display the Splash Screen
-
- Args:
- command (Callable): command to be called when buttons are clicked
- """
- self.beta_label.pack(pady=10)
- self.choose_label.pack(pady=10)
- self.device_select_input.pack()
- self.device_select_button.configure(command=command)
- self.or_label.pack()
- self.auto_connect_button.pack(pady=10)
- self.auto_connect_button.configure(command=command)
-
-
-class Menubar(View, tk.Menu):
- """Menu Bar view (i.e exit, etc)"""
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Menu.__init__(self, *args, **kwargs)
- self.master.configure(menu=self) # type: ignore
- self.fileMenu = tk.Menu(self)
-
- def create_view(self) -> None:
- """Display the menu bar"""
- self.add_cascade(label="File", menu=self.fileMenu)
-
-
-class StatusBar(View, tk.Frame):
- """Status Bar View"""
-
- LABEL_WIDTH = 15
- STATUS_WIDTH = 25
-
- class Color(enum.Enum):
- """Predefined color values"""
-
- RED = "#f5c6d0"
- YELLOW = "#fcffa1"
- GREEN = "#a1ffaa"
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Frame.__init__(self, *args, **kwargs)
- self.statuses: list[ttk.Label] = []
- self.ble_label: ttk.Label
- self.ble_status: ttk.Label
- self.wifi_label: ttk.Label
- self.wifi_status: ttk.Label
- self.ready_label: ttk.Label
- self.ready_status: ttk.Label
- self.stream_label: ttk.Label
- self.stream_status: ttk.Label
- self.encoding_label: ttk.Label
- self.encoding_status: ttk.Label
-
- def create_view(self) -> None:
- """Display the status bar"""
- self.pack(side=tk.BOTTOM, fill=tk.X)
- # BLE status
- self.ble_label = ttk.Label(self, text="BLE State:", relief=tk.RAISED, width=self.LABEL_WIDTH)
- self.ble_label.grid(row=0, column=0)
- self.ble_status = ttk.Label(
- self, text="Idle", relief=tk.RAISED, width=self.STATUS_WIDTH, background=StatusBar.Color.RED.value
- )
- self.ble_status.grid(row=0, column=1)
- # Wifi Status
- self.wifi_label = ttk.Label(self, text="Wifi State", relief=tk.RAISED, width=self.LABEL_WIDTH)
- self.wifi_label.grid(row=0, column=2)
- self.wifi_status = ttk.Label(
- self, text="Idle", relief=tk.RAISED, width=self.STATUS_WIDTH, background=StatusBar.Color.RED.value
- )
- self.wifi_status.grid(row=0, column=3)
- # Busy Status
- self.ready_label = ttk.Label(self, text="Ready State:", relief=tk.RAISED, width=self.LABEL_WIDTH)
- self.ready_label.grid(row=0, column=4)
- self.ready_status = ttk.Label(self, text="", relief=tk.RAISED, width=self.STATUS_WIDTH)
- self.ready_status.grid(row=0, column=5)
- # Encoding status
- self.encoding_label = ttk.Label(self, text="Encoding State:", relief=tk.RAISED, width=self.LABEL_WIDTH)
- self.encoding_label.grid(row=0, column=6)
- self.encoding_status = ttk.Label(self, text="", relief=tk.RAISED, width=self.STATUS_WIDTH)
- self.encoding_status.grid(row=0, column=7)
- # Stream Status
- self.stream_label = ttk.Label(self, text="Stream State:", relief=tk.RAISED, width=self.LABEL_WIDTH)
- self.stream_label.grid(row=0, column=8)
- self.stream_status = ttk.Label(
- self, text="IDLE", relief=tk.RAISED, width=self.STATUS_WIDTH, background=StatusBar.Color.RED.value
- )
- self.stream_status.grid(row=0, column=9)
-
- @staticmethod
- def update_status(status: ttk.Label, color: StatusBar.Color, text: str) -> None:
- """Update a status with a given color and text
-
- Args:
- status (ttk.Label): label to update
- color (StatusBar.Color): color to set the label to
- text (str): text to display on the label
- """
- status.configure(text=text, background=color.value)
-
-
-class Video(View, tk.Frame):
- """Displays a video source in a tkinter window"""
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Frame.__init__(self, *args, **kwargs)
- self.canvas = tk.Canvas(self)
- self.frame_label = ttk.Label(self, text="Video Stream")
- self.input_frame = tk.Frame(self)
- self.url_entry = DefaultTextEntry(default="Stream URL", master=self.input_frame)
- self.url_entry.grid(row=0, column=0, sticky="NSEW", padx=5)
- self.start_button = ttk.Button(self.input_frame, text="Start")
- self.start_button.grid(row=0, column=1)
- self.stop_button = ttk.Button(self.input_frame, text="Stop")
- self.stop_button.grid(row=0, column=2)
- self.input_frame.grid_columnconfigure(0, weight=6)
- self.input_frame.grid_columnconfigure(1, weight=1)
- self.input_frame.grid_columnconfigure(1, weight=1)
-
- def create_view(self, start: Callable, stop: Callable) -> None:
- """Display the initial video view with no image
-
- Args:
- start (Callable): command to call when start is clicked
- stop (Callable): command to call when stop is clicked
- """
- self.frame_label.pack(side=tk.TOP)
- self.input_frame.pack(side=tk.TOP, fill=tk.X)
- self.canvas.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
- self.pack(expand=True, fill=tk.BOTH)
- self.start_button.config(command=start)
- self.stop_button.config(command=stop)
-
- def display_frame(self, photo: ImageTk.PhotoImage) -> None:
- """Display an individual video frame
-
- Args:
- photo (ImageTk.PhotoImage): frame to display
- """
- self.canvas.create_image(0, 0, image=photo, anchor=tk.NW)
-
-
-class Log(View, tk.Frame, ABC):
- """Log View interface"""
-
- class Format(enum.Enum):
- """Type of message to log
-
- For now, just sets the color
- """
-
- TX = "white"
- RX = "beige"
- ERROR = "#f5c6d0"
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Frame.__init__(self, *args, **kwargs)
-
- @abstractmethod
- def create_log_entry(self, record: logging.LogRecord) -> None:
- """Display and individual log entry
-
- Args:
- record (logging.LogRecord): record to log
- """
- raise NotImplementedError
-
-
-class TreeViewLog(Log):
- """A Log displayed as a TreeView"""
-
- # custom indicator images
- im_open = Image.new("RGBA", (15, 15), "#00000000")
- im_empty = Image.new("RGBA", (15, 15), "#00000000")
- draw = ImageDraw.Draw(im_open)
- draw.polygon([(0, 4), (14, 4), (7, 11)], fill="white", outline="black")
- im_close = im_open.rotate(90)
-
- class Message:
- """An individual log message to displayed by a Tree View Log
-
- Args:
- record (logging.LogRecord): logger record that will comprise this message
- fmt (str): string to format a tuple of values
-
- Raises:
- ValueError: Not able to build a Message from this log record
- """
-
- ASYNC_TOKEN = "-ASYNC-"
-
- # NOTE! Direction is from perspective of PC
- def __init__(self, record: logging.LogRecord, fmt: str) -> None:
- self.fmt = fmt
- self.message = record.message.strip("\n")
- if self.message[2] == "<":
- self.direction = Log.Format.TX
- elif self.message[-3] == ">":
- self.direction = Log.Format.RX
- else:
- raise ValueError("Log message must start with a directional arrow")
- self.time = record.asctime
- self.is_async = self.ASYNC_TOKEN in self.message
- self.sanitize_message()
- try:
- self.data: dict = json.loads("{" + self.message + "}")
- except Exception as e:
- raise ValueError(f"Can not build log message from {self.message}") from e
- self.data["target"] = self.data.pop("uuid", None) or self.data.pop("endpoint", None)
- self.data["id"] = sanitize_identifier(self.data["id"])
- # Special case to handle wifi set setting response
- if self.data["id"].lower() == "camera setting":
- parsed_url = urlparse(self.data["target"])
- setting_id = int(parse_qs(parsed_url.query)["setting"][0])
- self.data["id"] = models.constants.SettingId(setting_id)
- self.data.pop("command", None)
-
- def sanitize_message(self) -> None:
- """Clean up the directional headers from the logger message string"""
- self.message = (
- self.message.replace(self.ASYNC_TOKEN, "")
- .strip("<>")
- .strip("-")
- .strip("<>")
- .replace("\t", "")
- .replace("\n", "")
- .strip(",")
- )
-
- @property
- def is_ok(self) -> bool:
- """Is the status ok or unknown?
-
- Returns:
- bool: True if yes, False otherwise
- """
- try:
- return self.data["status"].lower() in ["success", "unknown"]
- except KeyError:
- return True
-
- def logview_entries(self) -> Generator[tuple[str, tuple[str, ...]], None, None]:
- """From the Log Message, generate entries suitable for the Log View to consume
-
- Generates:
- 1. top level description of message (formatted by self.fmt)
- 2-N: individual elements of message as tuple(id, value)
-
- Yields:
- Generator[tuple[str, tuple[str, ...]], None, None]: see note in command description
- """
- work_dict = {**self.data, "log_time": self.time}
- # First yield top level
- out: list[str] = []
- for fmtstr in self.fmt:
- out.append(pretty_print(work_dict.pop(fmtstr, ""), should_quote=False))
- yield pretty_print(work_dict.pop("id"), should_quote=False), tuple(out)
- # Yield any additional items
- for key, value in work_dict.items():
- yield pretty_print(key, should_quote=False), (pretty_print(str(value), should_quote=False),)
-
- def __str__(self) -> str:
- return json.dumps(self.data, indent=4)
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- super().__init__(*args, **kwargs)
- self.tv = ttk.Treeview(self)
- self.tv["columns"] = ("log_time", "protocol", "target", "status")
- self.tv.column("log_time", width=20)
- self.tv.column("protocol", width=20)
- self.tv.column("status", width=50)
- self.tv.column("target", width=50)
- self.tv.heading("log_time", text="Time / Value")
- self.tv.heading("protocol", text="Protocol")
- self.tv.heading("status", text="Status")
- self.tv.heading("target", text="UUID / Endpoint")
- self.tv.tag_configure(Log.Format.TX.name, background=Log.Format.TX.value, foreground="black")
- self.tv.tag_configure(Log.Format.RX.name, background=Log.Format.RX.value, foreground="black")
- self.tv.tag_configure(Log.Format.ERROR.name, background=Log.Format.ERROR.value, foreground="black")
- self.sbv = tk.Scrollbar(self, orient=tk.VERTICAL)
- self.tv.config(yscrollcommand=self.sbv.set)
- self.sbv.config(command=self.tv.yview)
- self.index = 0
- self.img_open = ImageTk.PhotoImage(TreeViewLog.im_open, name="img_open", master=self.winfo_toplevel())
- self.img_close = ImageTk.PhotoImage(TreeViewLog.im_close, name="img_close", master=self.winfo_toplevel())
- self.img_empty = ImageTk.PhotoImage(TreeViewLog.im_empty, name="img_empty", master=self.winfo_toplevel())
- self.style = ttk.Style(self.winfo_toplevel())
- self.style.element_create(
- "Treeitem.myindicator",
- "image",
- "img_close",
- ("user1", "!user2", "img_open"),
- ("user2", "img_empty"),
- sticky="w",
- width=15,
- )
- # replace Treeitem.indicator by custom one
- self.style.layout(
- "Treeview.Item",
- [
- (
- "Treeitem.padding",
- {
- "sticky": "nswe",
- "children": [
- ("Treeitem.myindicator", {"side": "left", "sticky": ""}),
- ("Treeitem.image", {"side": "left", "sticky": ""}),
- (
- "Treeitem.focus",
- {
- "side": "left",
- "sticky": "",
- "children": [("Treeitem.text", {"side": "left", "sticky": ""})],
- },
- ),
- ],
- },
- )
- ],
- )
-
- def create_view(self) -> None:
- """Display the TreeView Log"""
- self.sbv.pack(side=tk.RIGHT, fill=tk.Y)
- self.tv.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
- self.pack(fill=tk.Y, expand=True)
-
- def create_log_entry_element(
- self, name: str, values: tuple[str, ...], fmt: Log.Format, parent: Optional[int] = None
- ) -> int:
- """Display an individual log entry element, potentially as a subtree
-
- Args:
- name (str): top level name of element
- values (tuple[str, ...]): values in element
- fmt (Log.Format): how to format the element
- parent (Optional[int], optional): Parent of element if part of subtree. Defaults to None.
-
- Returns:
- int: identifier of this created element
- """
- self.tv.insert(
- parent=str(parent or ""),
- index="end",
- iid=str(self.index),
- text=name,
- values=values,
- tags=fmt.name,
- )
- ret_index = self.index
- self.index += 1
-
- # Scroll to bottom
- self.tv.yview_moveto(1)
-
- return ret_index
-
- def create_log_entry(self, record: logging.LogRecord) -> None:
- """Create a log entry from a logging record
-
- Args:
- record (logging.LogRecord): record to analyze
- """
- try:
- msg = TreeViewLog.Message(record, self.tv["columns"])
- except ValueError: # It doesn't have an arrow so we can't log it
- return
- logview_entry_gen = msg.logview_entries()
- name, values = next(logview_entry_gen)
- parent = self.create_log_entry_element(name, values, msg.direction if msg.is_ok else Log.Format.ERROR)
- for name, values in logview_entry_gen:
- self.create_log_entry_element(name, values, msg.direction, parent)
-
-
-class TextLog(Log):
- """A Log View displayed as scrolled text"""
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- super().__init__(*args, **kwargs)
- self.viewer = ScrolledText.ScrolledText(self, state="disabled")
-
- def create_view(self) -> None:
- """Display the text log view"""
- self.viewer.pack(expand=True, fill=tk.X)
-
- def create_log_entry(self, record: logging.LogRecord) -> None:
- """Create a text log entry from a logging record
-
- Args:
- record (logging.LogRecord): logging record to analyze
- """
- self.viewer.configure(state="normal")
- self.viewer.insert(tk.END, record.message + "\n")
- self.viewer.configure(state="disabled")
- # Autoscroll to the bottom
- self.viewer.yview(tk.END)
-
-
-class ParamForm(View, tk.Frame):
- """A entry form to gather message arguments"""
-
- FRAME_TITLE_ROW = 0
- MESSAGE_NAME_ROW = 1
- PARAM_START_ROW = 2
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Frame.__init__(self, *args, **kwargs)
- self.value_inputs: list[Union[tk.Variable, ttk.Entry]] = []
- self.arg_frames: list[tk.Frame] = []
- self.frame_label: ttk.Label
- self.message_label: ttk.Label
- self.value_label: ttk.Label
- self.value_menu: ttk.OptionMenu
- self.send_button: ttk.Button
-
- def create_view(self) -> None:
- """Display the parameter form"""
- self.frame_label = ttk.Label(self, text="Parameter Configuration", name="frame_label")
- self.frame_label.pack()
- self.pack(fill=tk.BOTH, expand=True, side=tk.TOP)
-
- @property
- def param_row(self) -> int:
- """Get the current parameter row as the argument entry form is built
-
- Returns:
- int: parameter row
- """
- return len(self.value_inputs) + self.PARAM_START_ROW
-
- @property
- def active_arg_frame(self) -> tk.Frame:
- """Get the active tkinter frame of the argument that is currently being built
-
- Returns:
- tk.Frame: active argument frame
- """
- return self.arg_frames[-1]
-
- def reset_view(self) -> None:
- """Destroy the current argument entries"""
- for widget in self.winfo_children():
- if "frame_label" not in str(widget):
- widget.destroy()
- self.value_inputs.clear()
- self.arg_frames.clear()
-
- def create_message(self, message: str) -> None:
- """Create a message label
-
- Args:
- message (str): name of message
- """
- self.message_label = ttk.Label(self, text=message)
- self.message_label.pack()
-
- def _create_with_args(self, param: str) -> None:
- """Create an entry that includes an argument
-
- Args:
- param (str): text of argument
- """
- self.arg_frames.append(tk.Frame(self))
- self.active_arg_frame.pack()
- self.value_label = ttk.Label(self.active_arg_frame, text=param, anchor="w")
- self.value_label.pack(side=tk.LEFT)
-
- def create_option_menu(self, param: str, options: Sequence[str], default: Optional[str] = None) -> GetterType:
- """Create a drop down menu for an argument
-
- Args:
- param (str): name of argument
- options (Sequence[str]): all possible argument options
- default (Optional[str], optional): The default option. If none, the first option will be chosen.
- Defaults to None.
-
- Returns:
- GetterType: a getter to get this option menu value
- """
- self._create_with_args(param)
- value_input = tk.StringVar(self.active_arg_frame)
- default = default or options[0]
- value_input.set(default)
- self.value_menu = ttk.OptionMenu(self.active_arg_frame, value_input, default, *options)
- self.value_menu.pack(side=tk.RIGHT)
- self.value_inputs.append(value_input)
- return value_input.get
-
- def create_entry(self, param: str) -> GetterType:
- """Create a text entry for an argument
-
- Args:
- param (str): name of argument
-
- Returns:
- GetterType: A getter to get this text entry value
- """
- self._create_with_args(param)
- value_input = ttk.Entry(self.active_arg_frame)
- value_input.pack(side=tk.RIGHT)
- self.value_inputs.append(value_input)
- return value_input.get
-
- def create_button(self, label: str, command: Callable) -> None:
- """Create a button in the parameter entry form
-
- Args:
- label (str): string to place in button
- command (Callable): command to call when button is pressed
- """
- self.send_button = ttk.Button(self, text=label, command=command)
- self.send_button.pack()
-
-
-class MessagePalette(View, tk.Frame):
- """A hierarchal display of messages by functionality for the user to select"""
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Frame.__init__(self, *args, **kwargs)
- self.tv = ttk.Treeview(self)
- self.sbv = tk.Scrollbar(self, orient=tk.VERTICAL)
- self.tv.config(yscrollcommand=self.sbv.set)
- self.sbv.config(command=self.tv.yview)
- self.frame_label = ttk.Label(self, text="Message Palette")
-
- def create_view(self, messages: dict[str, list[str]]) -> None:
- """Display the message palette
-
- Args:
- messages (dict[str, list[str]]): dictionary to be mapped to message palette hierarchy
- """
- self.frame_label.pack()
- self.sbv.pack(side=tk.RIGHT, fill=tk.Y)
- self.tv.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
- self.pack(fill=tk.Y, expand=True)
- # Fill up tree view
- parent_index = MAX_TREEVIEW_ID
- child_index = 0
- for parent, children in messages.items():
- self.tv.insert(parent="", index="end", iid=str(parent_index), text=parent)
- for message in children:
- if "." in (identifier := sanitize_identifier(message)):
- identifier = " ".join(identifier.split(".")[1:])
- self.tv.insert(
- parent=str(parent_index),
- index="end",
- iid=str(child_index),
- text=identifier,
- )
- child_index += 1
- self.tv.item(str(parent_index), open=True)
- parent_index += 1
-
-
-class StatusTab(View, tk.Frame):
- """A Status window to display current setting values / capabilities and status values
-
- Most recent updates will be highlighted
- """
-
- STATUS_ID_OFFSET = int(MAX_TREEVIEW_ID / 2)
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- tk.Frame.__init__(self, *args, **kwargs)
- self.tv = ttk.Treeview(self)
- self.tv["columns"] = ("value", "capabilities")
- self.tv.column("value", width=200)
- self.tv.column("capabilities", width=400)
- self.tv.heading("value", text="Value")
- self.tv.heading("capabilities", text="Capabilities")
- self.tv.tag_configure("recent", background="#73ffff", foreground="black")
-
- self.sbv = tk.Scrollbar(self, orient=tk.VERTICAL)
- self.sbh = tk.Scrollbar(self, orient=tk.HORIZONTAL)
- self.tv.config(yscrollcommand=self.sbv.set)
- self.tv.config(xscrollcommand=self.sbh.set)
- self.sbv.config(command=self.tv.yview)
- self.sbh.config(command=self.tv.xview)
- self.setting_parent = str(MAX_TREEVIEW_ID)
- self.status_parent = str(int(self.setting_parent) + 1)
- self.setting_to_id: dict[int, str] = {}
- self.status_to_id: dict[int, str] = {}
- self.recent_tags: list[str] = []
-
- def create_view(self) -> None:
- """Display the Status Tab"""
- self.sbv.pack(side=tk.RIGHT, fill=tk.Y)
- self.sbh.pack(side=tk.TOP, fill=tk.X)
- self.tv.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
- self.pack(fill=tk.Y, expand=True)
- # Fill up tree view. Start with just parents labels
- self.tv.insert(parent="", index="end", iid=self.setting_parent, text="Settings")
- self.tv.item(self.setting_parent, open=True)
- self.tv.insert(parent="", index="end", iid=self.status_parent, text="Statuses")
- self.tv.item(self.status_parent, open=True)
-
- def store_new_status(self, index: int) -> None:
- """Add a new status to the internal status index-id map
-
- Args:
- index (int): status index
- """
- self.status_to_id[index] = str(index + self.STATUS_ID_OFFSET)
-
- def store_new_setting(self, index: int) -> None:
- """Add a new setting to the internal setting index-id map
-
- Args:
- index (int): setting index
- """
- self.setting_to_id[index] = str(index)
-
- def _common_update(
- self,
- identifier: enum.IntEnum,
- update_map: dict[int, str],
- parent: str,
- store: Callable[[int], None],
- value: Optional[str] = None,
- capability: Optional[str] = None,
- ) -> None:
- """Common functionality to update a setting / status, storing new index if needed.
-
- One of value or capability must be non-None
-
- Args:
- identifier (enum.IntEnum): ID of setting / status to update
- update_map (dict[int, str]): index-to-id translation map
- parent (str): parent of this identifier
- store (Callable[[int], None]): method to store new index in update_map
- value (Optional[str], optional): value of update. Defaults to None.
- capability (Optional[str], optional): capability of update. Defaults to None.
-
- Raises:
- ValueError: Either value or capability must be non-None
- """
- # Empty string is a valid input so explicitly check if is None
- if value is None and capability is None:
- raise ValueError("Require value or capability")
-
- # Does it already exist?
- if tree_index := update_map.get(int(identifier)):
- # The values key in TreeView will return all of its value (values and caps from our perspective)
- current_values = self.tv.item(tree_index)["values"]
- # Need to check is not None because falsy empty string is a valid input
- new_values = tuple(new if new is not None else old for new, old in zip((value, capability), current_values))
- self.tv.item(tree_index, tags=("recent",), values=new_values)
- else:
- store(int(identifier))
- tree_index = update_map[int(identifier)]
- self.tv.insert(
- parent=parent,
- index="end",
- iid=tree_index,
- text=pretty_print(identifier),
- values=(value or "", capability or ""),
- tags=("recent",),
- )
- self.recent_tags.append(tree_index)
-
- def update_setting(self, identifier: enum.IntEnum, value: str) -> None:
- """Display a setting value update
-
- Args:
- identifier (enum.IntEnum): setting that has been updated
- value (str): new value of setting
- """
- self._common_update(
- identifier,
- update_map=self.setting_to_id,
- store=self.store_new_setting,
- parent=self.setting_parent,
- value=value,
- )
-
- def update_capability(self, identifier: enum.IntEnum, capability: str) -> None:
- """Display a setting capability update
-
- Args:
- identifier (enum.IntEnum): setting that has been updated
- capability (str): new capability of setting
- """
- self._common_update(
- identifier,
- update_map=self.setting_to_id,
- parent=self.setting_parent,
- store=self.store_new_setting,
- capability=capability,
- )
-
- def update_status(self, identifier: enum.IntEnum, value: str) -> None:
- """Display a status value update
-
- Args:
- identifier (enum.IntEnum): status that has been updated
- value (str): new value of status
- """
- self._common_update(
- identifier,
- update_map=self.status_to_id,
- parent=self.status_parent,
- store=self.store_new_status,
- value=value,
- )
-
- def clear_recent_tags(self) -> None:
- """Set all currently marked 'recent' updates as not 'recent'"""
- for tree_index in self.recent_tags:
- self.tv.item(tree_index, tags=())
- self.recent_tags.clear()
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/gui_demo.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/gui_demo.py
deleted file mode 100644
index 229f04ff..00000000
--- a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/gui_demo.py
+++ /dev/null
@@ -1,231 +0,0 @@
-# gui_demo.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Sep 12 20:44:38 UTC 2022
-
-"""Top level of GUI"""
-
-from __future__ import annotations
-
-import asyncio
-import logging
-import tkinter as tk
-from tkinter import font, ttk
-
-from open_gopro.demos.gui.components import THEME, controllers, models, views
-
-logger = logging.getLogger(__name__)
-
-
-class App(tk.Frame):
- """Top level App frame"""
-
- HEIGHT = 1300
- ASPECT_RATIO = 16 / 9
- WIDTH = int(HEIGHT * ASPECT_RATIO)
- SASH_WIDTH = 2
- TOOLBAR_HEIGHT = 25
- SPLASH_WIDTH = 1000
- SPLASH_HEIGHT = 500
- FONT_SIZE = 12
-
- def __init__(self) -> None:
- self.loop = asyncio.get_event_loop()
- self.root = tk.Tk()
- self.style = ttk.Style(self.root)
- self.style.theme_use(THEME)
- self.style.configure("Treeview", font=(None, App.FONT_SIZE), rowheight=int(App.FONT_SIZE * 3))
- tk.Frame.__init__(self, master=self.root, width=self.WIDTH, height=self.HEIGHT)
- self.root.title("Open GoPro")
- self.defaultFont = font.nametofont("TkDefaultFont")
- self.defaultFont.configure(size=App.FONT_SIZE)
- self.views: list[views.View] = []
- self.create_models()
- self.create_controllers()
- self.create_views()
- self._should_quit = False
- self.root.withdraw() # Hide for now
-
- @property
- def controllers(self) -> list[controllers.Controller]:
- """List of created controllers
-
- Returns:
- list[controllers.Controller]: controllers
- """
- return controllers.Controller.controllers
-
- def create_models(self) -> None:
- """Create all models"""
- self.gopro_model = models.GoProModel()
- self.models = [self.gopro_model]
-
- def create_controllers(self) -> None:
- """Create all controllers"""
- self.message_palette_controller = controllers.MessagePalette(self.loop, self.gopro_model)
- self.main_log_controller = controllers.Log(self.loop, logging.INFO)
- self.statusbar_controller = controllers.StatusBar(self.loop)
- self.video_controller = controllers.Video(self.loop)
- self.video_controller.bind_status_bar(self.statusbar_controller)
- self.menubar_controller = controllers.Menubar(self.loop, self.quit)
- self.statustab_controller = controllers.StatusTab(self.loop, self.gopro_model)
- self.message_palette_controller.register_response_handler(self.statustab_controller.display_response_updates)
- self.message_palette_controller.register_response_handler(self.video_controller.handle_auto_start)
- self.statustab_controller.bind_statusbar(self.statusbar_controller)
- for controller in self.controllers:
- controller.logger = self.main_log_controller.logger
- self.splash_controller = controllers.SplashScreen(self.loop, self.gopro_model)
- self.splash_log_controller = controllers.Log(self.loop, logging.INFO)
- self.splash_controller.logger = self.splash_log_controller.logger
-
- def create_views(self) -> None:
- """Create all views"""
- self.splash = controllers.create_widget(
- master=self,
- view=views.SplashScreen,
- controller=self.splash_controller,
- width=self.SPLASH_WIDTH,
- height=self.SPLASH_HEIGHT,
- )
- self.splash_log = controllers.create_widget(
- master=self.splash.root, # type: ignore
- view=views.TextLog,
- controller=self.splash_log_controller,
- )
- self.splash_log.pack(expand=True, fill=tk.BOTH)
-
- # Set up paned window (everything except toolbars)
- self.panes = tk.PanedWindow(
- master=self,
- orient=tk.HORIZONTAL,
- bd=self.SASH_WIDTH,
- width=self.WIDTH,
- height=self.HEIGHT - self.TOOLBAR_HEIGHT,
- )
- self.panes.pack(fill=tk.BOTH, expand=True)
-
- # Left pane (Message palette)
- self.message_palette = controllers.create_widget(
- master=self.panes,
- view=views.MessagePalette,
- controller=self.message_palette_controller,
- width=int(self.WIDTH * 0.4),
- relief=tk.SUNKEN,
- )
- self.panes.add(self.message_palette, stretch="always")
-
- # Middle pane (param form / log)
- self.middle_pane = tk.PanedWindow(
- master=self.panes,
- orient=tk.VERTICAL,
- bd=self.SASH_WIDTH,
- width=int(self.WIDTH * 0.45),
- )
- self.middle_pane.propagate(False)
- self.middle_pane.pack(fill=tk.BOTH, expand=True)
- self.panes.add(self.middle_pane, stretch="always")
- self.param_form = controllers.create_widget(
- master=self.middle_pane,
- view=views.ParamForm,
- controller=self.message_palette_controller,
- height=int(self.HEIGHT * 0.2),
- bd=self.SASH_WIDTH,
- relief=tk.SUNKEN,
- )
- self.middle_pane.add(self.param_form, stretch="always", minsize=int(self.HEIGHT * 0.2))
- self.main_log = controllers.create_widget(
- master=self.middle_pane,
- view=views.TreeViewLog,
- controller=self.main_log_controller,
- height=int(self.HEIGHT * 0.8),
- bd=self.SASH_WIDTH,
- relief=tk.SUNKEN,
- )
- self.middle_pane.add(self.main_log, stretch="always", minsize=int(self.HEIGHT * 0.5))
-
- # Right Pane (data view / video)
- self.right_pane = tk.PanedWindow(
- master=self.panes,
- orient=tk.VERTICAL,
- width=int(self.WIDTH * 0.25),
- bd=self.SASH_WIDTH,
- )
- self.right_pane.pack(fill=tk.BOTH, expand=True)
- self.panes.add(self.right_pane, stretch="always")
- # Top of right pane (tabs)
- self.data = ttk.Notebook(master=self.right_pane, height=int(self.HEIGHT * 0.7))
- self.right_pane.add(self.data, stretch="always")
- self.status_tab = controllers.create_widget(
- master=self.data,
- view=views.StatusTab,
- controller=self.statustab_controller,
- )
- self.data.add(self.status_tab, text="Camera State")
- self.ble_tab = ttk.Treeview(master=self.data)
- self.ble_tab.pack(fill=tk.BOTH, expand=True)
- self.data.add(self.ble_tab, text="BLE Info")
- # Bottom of right pane (video).
- self.video = controllers.create_widget(
- master=self.right_pane,
- view=views.Video,
- controller=self.video_controller,
- bd=self.SASH_WIDTH,
- relief=tk.SUNKEN,
- )
- self.right_pane.add(self.video, stretch="always", minsize=700, hide=False)
- self.pack(fill=tk.BOTH, expand=True)
-
- # Menu bar
- self.menubar = controllers.create_widget(
- master=self.root, # type: ignore
- view=views.Menubar,
- controller=self.menubar_controller,
- )
-
- # StatusBar
- self.statusbar = controllers.create_widget(
- master=self,
- view=views.StatusBar,
- controller=self.statusbar_controller,
- height=self.TOOLBAR_HEIGHT,
- bd=self.SASH_WIDTH,
- background="gray",
- )
-
- async def run(self) -> None:
- """GUI main loop."""
- # Wait to be ready
- while not self.splash_controller.are_models_started:
- await self.splash_controller.update()
- self.splash_log_controller.log_queued_messages(self.splash_log) # type: ignore
- self.splash_controller.logger.setLevel(logging.CRITICAL)
- self.splash.root.destroy() # type: ignore
-
- # Update status bar and start its polling
- self.statusbar_controller.update_status(controllers.StatusBar.Ble.CONNECTED)
- self.statusbar_controller.update_status(controllers.StatusBar.Wifi.CONNECTED)
- self.statustab_controller.poll()
-
- # Unhide root
- self.root.deiconify()
-
- # Main loop. Continue until told to quit
- while not self._should_quit:
- self.main_log_controller.log_queued_messages(self.main_log) # type: ignore
- self.root.update()
- await asyncio.sleep(0)
- self.root.quit()
-
- def quit(self) -> None:
- """Stop the main loop and exit"""
- self._should_quit = True
-
-
-async def main() -> None:
- await App().run()
-
-
-def entrypoint() -> None:
- asyncio.run(main())
-
-
-if __name__ == "__main__":
- entrypoint()
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/webcam.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/webcam.py
index 02b03edc..72853aa4 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/webcam.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/webcam.py
@@ -57,12 +57,17 @@ async def main(args: argparse.Namespace) -> None:
try:
async with (
- WirelessGoPro(args.identifier, wifi_interface=args.wifi_interface) # type: ignore
- if args.wireless
+ WirelessGoPro(args.identifier, wifi_interface=args.wifi_interface, enable_wifi=not args.cohn) # type: ignore
+ if bool(args.cohn or args.wireless)
else WiredGoPro(args.identifier)
) as gopro:
assert gopro
- await gopro.http_command.wired_usb_control(control=Params.Toggle.DISABLE)
+
+ if args.cohn:
+ assert await gopro.is_cohn_provisioned
+ assert await gopro.configure_cohn()
+ else:
+ await gopro.http_command.wired_usb_control(control=Params.Toggle.DISABLE)
await gopro.http_command.set_shutter(shutter=Params.Toggle.DISABLE)
if (await gopro.http_command.webcam_status()).data.status not in {
@@ -94,12 +99,19 @@ async def main(args: argparse.Namespace) -> None:
def parse_arguments() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description="Setup and view a GoPro webcam.")
- parser.add_argument(
+ parser = argparse.ArgumentParser(description="Setup and view a GoPro webcam using TS protocol.")
+ protocol = parser.add_argument_group("protocol", "Mutually exclusive Protocol option if not default wired USB.")
+ group = protocol.add_mutually_exclusive_group()
+ group.add_argument(
"--wireless",
action="store_true",
help="Set to use wireless (BLE / WIFI) instead of wired (USB)) interface",
)
+ group.add_argument(
+ "--cohn",
+ action="store_true",
+ help="Communicate via COHN. Assumes COHN is already provisioned.",
+ )
return add_cli_args_and_parse(parser)
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/enum.py b/demos/python/sdk_wireless_camera_control/open_gopro/enum.py
index 308f345a..487ab45d 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/enum.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/enum.py
@@ -81,8 +81,8 @@ def __iter__(cls: type[T]) -> Iterator[T]:
return iter([x[1] for x in cls._member_map_.items() if x[0] not in GoProEnumMeta._iter_skip_names]) # type: ignore
-class GoProEnum(IntEnum, metaclass=GoProEnumMeta):
- """GoPro specific enum to be used for all settings, statuses, and parameters
+class GoProIntEnum(IntEnum, metaclass=GoProEnumMeta):
+ """GoPro specific integer enum to be used for all settings, statuses, and parameters
The names NOT_APPLICABLE and DESCRIPTOR are special as they will not be returned as part of the enum iterator
"""
@@ -105,7 +105,28 @@ def __str__(self) -> str:
return super(IntEnum, self).__str__()
-def enum_factory(proto_enum: ProtobufDescriptor) -> type[GoProEnum]:
+class GoProEnum(Enum, metaclass=GoProEnumMeta):
+ """GoPro specific enum to be used for all settings, statuses, and parameters
+
+ The names NOT_APPLICABLE and DESCRIPTOR are special as they will not be returned as part of the enum iterator
+ """
+
+ def __eq__(self, other: object) -> bool:
+ if type(self)._is_proto:
+ if isinstance(other, int):
+ return self.value == other
+ if isinstance(other, str):
+ return self.name == other
+ if isinstance(other, Enum):
+ return self.value == other.value
+ raise TypeError(f"Unexpected case: proto enum can only be str or int, not {type(other)}")
+ return super().__eq__(other)
+
+ def __hash__(self) -> Any:
+ return hash(self.name + str(self.value))
+
+
+def enum_factory(proto_enum: ProtobufDescriptor) -> type[GoProIntEnum]:
"""Dynamically build a GoProEnum from a protobuf enum
Args:
@@ -119,7 +140,7 @@ def enum_factory(proto_enum: ProtobufDescriptor) -> type[GoProEnum]:
# This has somehow changed between protobuf versions
if isinstance(proto_enum.values_by_number, dict):
values.reverse()
- return GoProEnum( # type: ignore # pylint: disable=too-many-function-args
+ return GoProIntEnum( # type: ignore # pylint: disable=too-many-function-args
proto_enum.name, # type: ignore
{
**dict(zip(keys, values)),
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/gopro_base.py b/demos/python/sdk_wireless_camera_control/open_gopro/gopro_base.py
index 86d3ee6e..35db422f 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/gopro_base.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/gopro_base.py
@@ -231,6 +231,33 @@ def is_http_connected(self) -> bool:
"""
raise NotImplementedError
+ @abstractmethod
+ async def configure_cohn(self, timeout: int = 60) -> bool:
+ """Prepare Camera on the Home Network
+
+ Provision if not provisioned
+ Then wait for COHN to be connected and ready
+
+ Args:
+ timeout (int): time in seconds to wait for COHN to be ready. Defaults to 60.
+
+ Returns:
+ bool: True if success, False otherwise
+ """
+ raise NotImplementedError
+
+ @property
+ @abstractmethod
+ async def is_cohn_provisioned(self) -> bool:
+ """Is COHN currently provisioned?
+
+ Get the current COHN status from the camera
+
+ Returns:
+ bool: True if COHN is provisioned, False otherwise
+ """
+ raise NotImplementedError
+
##########################################################################################################
# End Public API
##########################################################################################################
@@ -346,7 +373,7 @@ async def _http_get( # pylint: disable=unused-argument
request_args["verify"] = str(certificate)
response: Optional[GoProResp] = None
- for retry in range(GoProBase.HTTP_GET_RETRIES):
+ for retry in range(1, GoProBase.HTTP_GET_RETRIES + 1):
try:
request = requests.get(url, timeout=timeout, **request_args)
logger.trace(f"received raw json: {json.dumps(request.json() if request.text else {}, indent=4)}") # type: ignore
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wired.py b/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wired.py
index fb844bbf..262452fe 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wired.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wired.py
@@ -130,7 +130,7 @@ async def open(self, timeout: int = 10, retries: int = 1) -> None:
FailedToFindDevice: could not auto-discover GoPro via mDNS # noqa: DAR402
"""
if not self._serial:
- for retry in range(retries + 1):
+ for retry in range(1, retries + 1):
try:
ip_addr = await open_gopro.wifi.mdns_scanner.find_first_ip_addr(
WiredGoPro._MDNS_SERVICE_NAME, timeout
@@ -140,7 +140,7 @@ async def open(self, timeout: int = 10, retries: int = 1) -> None:
except GpException.FailedToFindDevice as e:
if retry == retries:
raise e
- logger.warning(f"Failed to discover GoPro. Retrying #{retry + 1}")
+ logger.warning(f"Failed to discover GoPro. Retrying #{retry}")
await self.http_command.wired_usb_control(control=Params.Toggle.ENABLE)
# Find and configure API version
@@ -291,6 +291,37 @@ def unregister_update(self, callback: types.UpdateCb, update: types.UpdateType |
"""
raise NotImplementedError
+ async def configure_cohn(self, timeout: int = 60) -> bool:
+ """Prepare Camera on the Home Network
+
+ Provision if not provisioned
+ Then wait for COHN to be connected and ready
+
+ Args:
+ timeout (int): time in seconds to wait for COHN to be ready. Defaults to 60.
+
+ Returns:
+ bool: True if success, False otherwise
+
+ Raises:
+ NotImplementedError: not yet possible
+ """
+ raise NotImplementedError
+
+ @property
+ async def is_cohn_provisioned(self) -> bool:
+ """Is COHN currently provisioned?
+
+ Get the current COHN status from the camera
+
+ Returns:
+ bool: True if COHN is provisioned, False otherwise
+
+ Raises:
+ NotImplementedError: not yet possible
+ """
+ raise NotImplementedError
+
##########################################################################################################
# End Public API
##########################################################################################################
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wireless.py b/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wireless.py
index a10c8dfc..5ecea303 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wireless.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/gopro_wireless.py
@@ -9,6 +9,7 @@
import logging
import queue
from collections import defaultdict
+from copy import deepcopy
from pathlib import Path
from typing import Any, Final, Pattern
@@ -30,6 +31,7 @@
from open_gopro.constants import ActionId, GoProUUIDs, StatusId
from open_gopro.gopro_base import GoProBase, GoProMessageInterface, MessageMethodType
from open_gopro.logger import Logger
+from open_gopro.models.general import CohnInfo
from open_gopro.models.response import BleRespBuilder, GoProResp
from open_gopro.parser_interface import Parser
from open_gopro.util import SnapshotQueue, get_current_dst_aware_time
@@ -197,6 +199,8 @@ def __init__(
self._busy: bool
self._encoding_started: asyncio.Event
+ self._cohn: CohnInfo | None = None
+
@property
def identifier(self) -> str:
"""Get a unique identifier for this instance.
@@ -233,7 +237,7 @@ def is_http_connected(self) -> bool:
bool: True if yes, False if no
"""
try:
- return self._wifi.is_connected
+ return bool(self._cohn) or self._wifi.is_connected
except AttributeError:
return False
@@ -314,12 +318,10 @@ async def open(self, timeout: int = 10, retries: int = 5) -> None:
try:
await self._open_ble(timeout, retries)
- # Set current dst-aware time
- assert (
- await self.ble_command.set_date_time_tz_dst(
- **dict(zip(("date_time", "tz_offset", "is_dst"), get_current_dst_aware_time()))
- )
- ).ok
+ # Set current dst-aware time. Don't assert on success since some old cameras don't support this command.
+ await self.ble_command.set_date_time_tz_dst(
+ **dict(zip(("date_time", "tz_offset", "is_dst"), get_current_dst_aware_time()))
+ )
# Find and configure API version
version = (await self.ble_command.get_open_gopro_api_version()).data
@@ -406,6 +408,88 @@ async def is_ready(self) -> bool:
# TODO message rules are a mess here. Since these send other commands that need message rules, we deadlock
# if we try to apply message rules to these
+ async def _provision_cohn(self, timeout: int) -> bool:
+ """Provision the camera for Camera on the Home Network
+
+ Args:
+ timeout (int): time in seconds to wait for provisioning success
+
+ Returns:
+ bool: True if success, False otherwise
+ """
+ logger.info("Provisioning COHN")
+ provisioned = asyncio.Event()
+
+ async def wait_for_cohn_provisioned(_: Any, status: proto.NotifyCOHNStatus) -> None:
+ if status.enabled is True:
+ provisioned.set()
+
+ self.register_update(wait_for_cohn_provisioned, ActionId.RESPONSE_GET_COHN_STATUS)
+ # ALways override. Assume if we're here, we are purposely (re)configuring COHN
+ assert (await self.ble_command.cohn_create_certificate(override=True)).ok
+ try:
+ logger.debug("Waiting for COHN to provision")
+ await self.ble_command.cohn_get_status(register=True)
+ await asyncio.wait_for(provisioned.wait(), timeout)
+ logger.info("COHN is provisioned!!")
+ return True
+ except TimeoutError:
+ return False
+
+ # TODO allow cohn_info to be passed in
+ # TODO validate if network is connected if COHN needs to be configured
+ @GoProBase._ensure_opened((GoProMessageInterface.BLE,))
+ async def configure_cohn(self, timeout: int = 60) -> bool:
+ """Prepare Camera on the Home Network
+
+ Provision if not provisioned
+ Then wait for COHN to be connected and ready
+
+ Args:
+ timeout (int): time in seconds to wait for COHN to be ready. Defaults to 60.
+
+ Returns:
+ bool: True if success, False otherwise
+ """
+ status = (await self.ble_command.cohn_get_status(register=False)).data
+ # We need to provision if nor currently provisioned
+ if not status.enabled:
+ assert await self._provision_cohn(timeout)
+
+ credentials: asyncio.Queue[tuple[str, str]] = asyncio.Queue()
+
+ async def wait_for_cohn_ready(_: Any, status: proto.NotifyCOHNStatus) -> None:
+ if status.state == proto.EnumCOHNNetworkState.COHN_STATE_NetworkConnected:
+ await credentials.put((status.ipaddress, status.password))
+
+ # Now we need to wait for COHN to be ready
+ try:
+ self.register_update(wait_for_cohn_ready, ActionId.RESPONSE_GET_COHN_STATUS)
+ logger.info("Waiting for COHN to be connected")
+ await self.ble_command.cohn_get_status(register=True)
+ ip_address, password = await asyncio.wait_for(credentials.get(), timeout)
+ cert = (await self.ble_command.cohn_get_certificate()).data.cert
+ self._cohn = CohnInfo(ip_address, "gopro", password, cert)
+ logger.info(f"Using COHN Credentials: {self._cohn}")
+ return True
+ except TimeoutError:
+ return False
+
+ @property
+ async def is_cohn_provisioned(self) -> bool:
+ """Is COHN currently provisioned?
+
+ Get the current COHN status from the camera
+
+ Returns:
+ bool: True if COHN is provisioned, False otherwise
+ """
+ return (await self.ble_command.cohn_get_status(register=False)).data.enabled
+
+ ##########################################################################################################
+ # End Public API
+ ##########################################################################################################
+
@GoProBase._ensure_opened((GoProMessageInterface.BLE,))
async def keep_alive(self) -> bool:
"""Send a heartbeat to prevent the BLE connection from dropping.
@@ -464,7 +548,9 @@ async def wait_for_provisioning(_: Any, result: proto.NotifProvisioningState) ->
logger.error(f"Provision failed: {str(presult.provisioning_state)}")
return False
self.unregister_update(wait_for_provisioning)
+ logger.info(f"Successfully connected to {ssid}")
return True
+ logger.error(f"Could not find ssid {ssid}")
return False
##########################################################################################################
@@ -516,7 +602,7 @@ async def _update_internal_state(self, update: types.UpdateType, value: int) ->
value (int): updated value
"""
have_lock = not await self.is_ready
- logger.trace(f"State update received {update.name} ==> {value}, current {self._encoding=} {self._busy=}") # type: ignore
+ logger.trace(f"State update received {update.name} ==> {value}") # type: ignore
should_notify_encoding = False
if update == StatusId.ENCODING:
self._encoding = bool(value)
@@ -524,6 +610,7 @@ async def _update_internal_state(self, update: types.UpdateType, value: int) ->
should_notify_encoding = True
elif update == StatusId.SYSTEM_BUSY:
self._busy = bool(value)
+ logger.trace(f"Current internal states: {self._encoding=} {self._busy=}") # type: ignore
ready_now = await self.is_ready
if have_lock and ready_now:
@@ -546,19 +633,21 @@ async def _route_response(self, response: GoProResp) -> None:
response (GoProResp): parsed response
"""
# Flatten data if possible
+ # from copy import copy
+
+ original_response = deepcopy(response)
if response._is_query and not response._is_push:
response.data = list(response.data.values())[0]
# Check if this is the awaited synchronous response (id matches). Note! these have to come in order.
- response_claimed = False
if await self._sync_resp_wait_q.peek_front() == response.identifier:
+ logger.info(Logger.build_log_rx_str(original_response, asynchronous=False))
# Dequeue it and put this on the ready queue
await self._sync_resp_wait_q.get()
await self._sync_resp_ready_q.put(response)
- response_claimed = True
# If this wasn't the awaited synchronous response...
- if not response_claimed:
- logger.info(Logger.build_log_rx_str(response, asynchronous=True))
+ else:
+ logger.info(Logger.build_log_rx_str(original_response, asynchronous=True))
if response._is_push:
for update_id, value in response.data.items():
await self._notify_listeners(update_id, value)
@@ -715,6 +804,15 @@ async def _http_get(
timeout: int = GoProBase.GET_TIMEOUT,
**kwargs: Any,
) -> GoProResp:
+ if self._cohn:
+ return await super()._http_get(
+ url,
+ parser,
+ headers=headers or {"Authorization": self._cohn.auth_token},
+ certificate=certificate or self._cohn.cert_path,
+ timeout=timeout,
+ **kwargs,
+ )
return await super()._http_get(url, parser, **kwargs)
@enforce_message_rules
@@ -723,7 +821,7 @@ async def _stream_to_file(self, url: str, file: Path) -> GoProResp[Path]:
@property
def _base_url(self) -> str:
- return "http://10.5.5.9:8080/"
+ return f"https://{self._cohn.ip_address}/" if self._cohn else "http://10.5.5.9:8080/"
@property
def _api(self) -> WirelessApi:
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/models/general.py b/demos/python/sdk_wireless_camera_control/open_gopro/models/general.py
index 63739f53..29bdf2ec 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/models/general.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/models/general.py
@@ -6,6 +6,9 @@
from __future__ import annotations
import datetime
+from base64 import b64encode
+from dataclasses import dataclass
+from pathlib import Path
from typing import Optional
from pydantic import Field
@@ -56,3 +59,22 @@ class HttpInvalidSettingResponse(CustomBaseModel):
setting_id: constants.SettingId
option_id: Optional[int] = Field(default=None)
supported_options: Optional[list[SupportedOption]] = Field(default=None)
+
+
+# TODO add to / from json methods
+@dataclass
+class CohnInfo:
+ """Data model to store Camera on the Home Network connection info"""
+
+ ip_address: str
+ username: str
+ password: str
+ certificate: str
+ cert_path: Path = Path("cohn.crt")
+
+ def __post_init__(self) -> None:
+ token = b64encode(f"{self.username}:{self.password}".encode("utf-8")).decode("ascii")
+ self.auth_token = f"Basic {token}"
+ # self.token = f"Basic {token}"
+ with open(self.cert_path, "w") as fp:
+ fp.write(self.certificate)
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/models/response.py b/demos/python/sdk_wireless_camera_control/open_gopro/models/response.py
index 748a1a91..dc9a022d 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/models/response.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/models/response.py
@@ -22,7 +22,7 @@
CmdId,
ErrorCode,
FeatureId,
- GoProEnum,
+ GoProIntEnum,
GoProUUIDs,
QueryCmdId,
SettingId,
@@ -122,7 +122,7 @@ def _as_dict(self) -> dict:
return d
def __eq__(self, obj: object) -> bool:
- if isinstance(obj, GoProEnum):
+ if isinstance(obj, GoProIntEnum):
return self.identifier == obj
if isinstance(obj, GoProResp):
return self.identifier == obj.identifier
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/__init__.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/__init__.py
index 825209bf..f4f31aff 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/__init__.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/__init__.py
@@ -6,6 +6,17 @@
They are imported here so they can be imported from open_gopro.proto
"""
+from open_gopro.proto.cohn_pb2 import (
+ EnumCOHNNetworkState,
+ EnumCOHNStatus,
+ NotifyCOHNStatus,
+ RequestClearCOHNCert,
+ RequestCOHNCert,
+ RequestCreateCOHNCert,
+ RequestGetCOHNStatus,
+ RequestSetCOHNSetting,
+ ResponseCOHNCert,
+)
from open_gopro.proto.live_streaming_pb2 import (
EnumLens,
EnumLiveStreamStatus,
@@ -16,7 +27,6 @@
RequestSetLiveStreamMode,
)
from open_gopro.proto.network_management_pb2 import (
- EnumNetworkOwner,
EnumProvisioning,
EnumScanEntryFlags,
EnumScanning,
@@ -31,7 +41,6 @@
ResponseConnectNew,
ResponseGetApEntries,
ResponseStartScanning,
- ScanEntry,
)
from open_gopro.proto.preset_status_pb2 import (
EnumPresetGroup,
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.py
new file mode 100644
index 00000000..639c8719
--- /dev/null
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.py
@@ -0,0 +1,37 @@
+# cohn_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
+# This copyright was auto-generated on Sat Nov 4 21:02:30 UTC 2023
+
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+
+_sym_db = _symbol_database.Default()
+from . import response_generic_pb2 as response__generic__pb2
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
+ b'\n\ncohn.proto\x12\nopen_gopro\x1a\x16response_generic.proto"4\n\x14RequestGetCOHNStatus\x12\x1c\n\x14register_cohn_status\x18\x01 \x01(\x08"\xd9\x01\n\x10NotifyCOHNStatus\x12*\n\x06status\x18\x01 \x01(\x0e2\x1a.open_gopro.EnumCOHNStatus\x12/\n\x05state\x18\x02 \x01(\x0e2 .open_gopro.EnumCOHNNetworkState\x12\x10\n\x08username\x18\x03 \x01(\t\x12\x10\n\x08password\x18\x04 \x01(\t\x12\x11\n\tipaddress\x18\x05 \x01(\t\x12\x0f\n\x07enabled\x18\x06 \x01(\x08\x12\x0c\n\x04ssid\x18\x07 \x01(\t\x12\x12\n\nmacaddress\x18\x08 \x01(\t")\n\x15RequestCreateCOHNCert\x12\x10\n\x08override\x18\x01 \x01(\x08"\x16\n\x14RequestClearCOHNCert"\x11\n\x0fRequestCOHNCert"O\n\x10ResponseCOHNCert\x12-\n\x06result\x18\x01 \x01(\x0e2\x1d.open_gopro.EnumResultGeneric\x12\x0c\n\x04cert\x18\x02 \x01(\t",\n\x15RequestSetCOHNSetting\x12\x13\n\x0bcohn_active\x18\x01 \x01(\x08*>\n\x0eEnumCOHNStatus\x12\x16\n\x12COHN_UNPROVISIONED\x10\x00\x12\x14\n\x10COHN_PROVISIONED\x10\x01*\xec\x01\n\x14EnumCOHNNetworkState\x12\x13\n\x0fCOHN_STATE_Init\x10\x00\x12\x14\n\x10COHN_STATE_Error\x10\x01\x12\x13\n\x0fCOHN_STATE_Exit\x10\x02\x12\x13\n\x0fCOHN_STATE_Idle\x10\x05\x12\x1f\n\x1bCOHN_STATE_NetworkConnected\x10\x1b\x12"\n\x1eCOHN_STATE_NetworkDisconnected\x10\x1c\x12"\n\x1eCOHN_STATE_ConnectingToNetwork\x10\x1d\x12\x16\n\x12COHN_STATE_Invalid\x10\x1e'
+)
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "cohn_pb2", globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+ DESCRIPTOR._options = None
+ _ENUMCOHNSTATUS._serialized_start = 537
+ _ENUMCOHNSTATUS._serialized_end = 599
+ _ENUMCOHNNETWORKSTATE._serialized_start = 602
+ _ENUMCOHNNETWORKSTATE._serialized_end = 838
+ _REQUESTGETCOHNSTATUS._serialized_start = 50
+ _REQUESTGETCOHNSTATUS._serialized_end = 102
+ _NOTIFYCOHNSTATUS._serialized_start = 105
+ _NOTIFYCOHNSTATUS._serialized_end = 322
+ _REQUESTCREATECOHNCERT._serialized_start = 324
+ _REQUESTCREATECOHNCERT._serialized_end = 365
+ _REQUESTCLEARCOHNCERT._serialized_start = 367
+ _REQUESTCLEARCOHNCERT._serialized_end = 389
+ _REQUESTCOHNCERT._serialized_start = 391
+ _REQUESTCOHNCERT._serialized_end = 408
+ _RESPONSECOHNCERT._serialized_start = 410
+ _RESPONSECOHNCERT._serialized_end = 489
+ _REQUESTSETCOHNSETTING._serialized_start = 491
+ _REQUESTSETCOHNSETTING._serialized_end = 535
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.pyi
new file mode 100644
index 00000000..59d59eea
--- /dev/null
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/cohn_pb2.pyi
@@ -0,0 +1,263 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+*
+Defines the structure of protobuf messages for Camera On the Home Network
+"""
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.internal.enum_type_wrapper
+import google.protobuf.message
+from . import response_generic_pb2
+import sys
+import typing
+
+if sys.version_info >= (3, 10):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+class _EnumCOHNStatus:
+ ValueType = typing.NewType("ValueType", builtins.int)
+ V: typing_extensions.TypeAlias = ValueType
+
+class _EnumCOHNStatusEnumTypeWrapper(
+ google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumCOHNStatus.ValueType], builtins.type
+):
+ DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
+ COHN_UNPROVISIONED: _EnumCOHNStatus.ValueType
+ COHN_PROVISIONED: _EnumCOHNStatus.ValueType
+
+class EnumCOHNStatus(_EnumCOHNStatus, metaclass=_EnumCOHNStatusEnumTypeWrapper): ...
+
+COHN_UNPROVISIONED: EnumCOHNStatus.ValueType
+COHN_PROVISIONED: EnumCOHNStatus.ValueType
+global___EnumCOHNStatus = EnumCOHNStatus
+
+class _EnumCOHNNetworkState:
+ ValueType = typing.NewType("ValueType", builtins.int)
+ V: typing_extensions.TypeAlias = ValueType
+
+class _EnumCOHNNetworkStateEnumTypeWrapper(
+ google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumCOHNNetworkState.ValueType], builtins.type
+):
+ DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
+ COHN_STATE_Init: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_Error: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_Exit: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_Idle: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_NetworkConnected: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_NetworkDisconnected: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_ConnectingToNetwork: _EnumCOHNNetworkState.ValueType
+ COHN_STATE_Invalid: _EnumCOHNNetworkState.ValueType
+
+class EnumCOHNNetworkState(_EnumCOHNNetworkState, metaclass=_EnumCOHNNetworkStateEnumTypeWrapper): ...
+
+COHN_STATE_Init: EnumCOHNNetworkState.ValueType
+COHN_STATE_Error: EnumCOHNNetworkState.ValueType
+COHN_STATE_Exit: EnumCOHNNetworkState.ValueType
+COHN_STATE_Idle: EnumCOHNNetworkState.ValueType
+COHN_STATE_NetworkConnected: EnumCOHNNetworkState.ValueType
+COHN_STATE_NetworkDisconnected: EnumCOHNNetworkState.ValueType
+COHN_STATE_ConnectingToNetwork: EnumCOHNNetworkState.ValueType
+COHN_STATE_Invalid: EnumCOHNNetworkState.ValueType
+global___EnumCOHNNetworkState = EnumCOHNNetworkState
+
+class RequestGetCOHNStatus(google.protobuf.message.Message):
+ """*
+ Get the current COHN status.
+
+ This always returns a @ref NotifyCOHNStatus
+
+ Additionally, asynchronous updates can also be registerd to return more @ref NotifyCOHNStatus when a value
+ changes.
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+ REGISTER_COHN_STATUS_FIELD_NUMBER: builtins.int
+ register_cohn_status: builtins.bool
+ " 1 to register, 0 to unregister"
+
+ def __init__(self, *, register_cohn_status: builtins.bool | None = ...) -> None: ...
+ def HasField(
+ self, field_name: typing_extensions.Literal["register_cohn_status", b"register_cohn_status"]
+ ) -> builtins.bool: ...
+ def ClearField(
+ self, field_name: typing_extensions.Literal["register_cohn_status", b"register_cohn_status"]
+ ) -> None: ...
+
+global___RequestGetCOHNStatus = RequestGetCOHNStatus
+
+class NotifyCOHNStatus(google.protobuf.message.Message):
+ """
+ Current COHN status triggered by a RequestGetCOHNStatus
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+ STATUS_FIELD_NUMBER: builtins.int
+ STATE_FIELD_NUMBER: builtins.int
+ USERNAME_FIELD_NUMBER: builtins.int
+ PASSWORD_FIELD_NUMBER: builtins.int
+ IPADDRESS_FIELD_NUMBER: builtins.int
+ ENABLED_FIELD_NUMBER: builtins.int
+ SSID_FIELD_NUMBER: builtins.int
+ MACADDRESS_FIELD_NUMBER: builtins.int
+ status: global___EnumCOHNStatus.ValueType
+ " Current COHN status"
+ state: global___EnumCOHNNetworkState.ValueType
+ " Current COHN network state"
+ username: builtins.str
+ " Username used for http basic auth header"
+ password: builtins.str
+ " Password used for http basic auth header"
+ ipaddress: builtins.str
+ " Camera’s IP address on the local network"
+ enabled: builtins.bool
+ " Is COHN currently enabled"
+ ssid: builtins.str
+ " Currently connected SSID"
+ macaddress: builtins.str
+ " MAC address of the wifi adapter"
+
+ def __init__(
+ self,
+ *,
+ status: global___EnumCOHNStatus.ValueType | None = ...,
+ state: global___EnumCOHNNetworkState.ValueType | None = ...,
+ username: builtins.str | None = ...,
+ password: builtins.str | None = ...,
+ ipaddress: builtins.str | None = ...,
+ enabled: builtins.bool | None = ...,
+ ssid: builtins.str | None = ...,
+ macaddress: builtins.str | None = ...
+ ) -> None: ...
+ def HasField(
+ self,
+ field_name: typing_extensions.Literal[
+ "enabled",
+ b"enabled",
+ "ipaddress",
+ b"ipaddress",
+ "macaddress",
+ b"macaddress",
+ "password",
+ b"password",
+ "ssid",
+ b"ssid",
+ "state",
+ b"state",
+ "status",
+ b"status",
+ "username",
+ b"username",
+ ],
+ ) -> builtins.bool: ...
+ def ClearField(
+ self,
+ field_name: typing_extensions.Literal[
+ "enabled",
+ b"enabled",
+ "ipaddress",
+ b"ipaddress",
+ "macaddress",
+ b"macaddress",
+ "password",
+ b"password",
+ "ssid",
+ b"ssid",
+ "state",
+ b"state",
+ "status",
+ b"status",
+ "username",
+ b"username",
+ ],
+ ) -> None: ...
+
+global___NotifyCOHNStatus = NotifyCOHNStatus
+
+class RequestCreateCOHNCert(google.protobuf.message.Message):
+ """*
+ Create the COHN certificate.
+
+ Returns a @ref ResponseGeneric with the status of the creation
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+ OVERRIDE_FIELD_NUMBER: builtins.int
+ override: builtins.bool
+ " Override current provisioning and create new cert"
+
+ def __init__(self, *, override: builtins.bool | None = ...) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["override", b"override"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["override", b"override"]) -> None: ...
+
+global___RequestCreateCOHNCert = RequestCreateCOHNCert
+
+class RequestClearCOHNCert(google.protobuf.message.Message):
+ """*
+ Clear the COHN certificate.
+
+ Returns a @ref ResponseGeneric with the status of the clear
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ def __init__(self) -> None: ...
+
+global___RequestClearCOHNCert = RequestClearCOHNCert
+
+class RequestCOHNCert(google.protobuf.message.Message):
+ """*
+ Get the COHN certificate.
+
+ Returns a @ref ResponseCOHNCert
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ def __init__(self) -> None: ...
+
+global___RequestCOHNCert = RequestCOHNCert
+
+class ResponseCOHNCert(google.protobuf.message.Message):
+ """
+ COHN Certificate response triggered by RequestCOHNCert
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+ RESULT_FIELD_NUMBER: builtins.int
+ CERT_FIELD_NUMBER: builtins.int
+ result: response_generic_pb2.EnumResultGeneric.ValueType
+ " Was request successful?"
+ cert: builtins.str
+ " Root CA cert (ASCII text)"
+
+ def __init__(
+ self, *, result: response_generic_pb2.EnumResultGeneric.ValueType | None = ..., cert: builtins.str | None = ...
+ ) -> None: ...
+ def HasField(
+ self, field_name: typing_extensions.Literal["cert", b"cert", "result", b"result"]
+ ) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["cert", b"cert", "result", b"result"]) -> None: ...
+
+global___ResponseCOHNCert = ResponseCOHNCert
+
+class RequestSetCOHNSetting(google.protobuf.message.Message):
+ """*
+ Enable and disable COHN if provisioned
+
+ Returns a @ref ResponseGeneric
+ """
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+ COHN_ACTIVE_FIELD_NUMBER: builtins.int
+ cohn_active: builtins.bool
+ " 1 to enable, 0 to disable"
+
+ def __init__(self, *, cohn_active: builtins.bool | None = ...) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["cohn_active", b"cohn_active"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["cohn_active", b"cohn_active"]) -> None: ...
+
+global___RequestSetCOHNSetting = RequestSetCOHNSetting
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.py
index 324426ab..e8acc7b6 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.py
@@ -1,5 +1,5 @@
# live_streaming_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:29 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
@@ -9,25 +9,25 @@
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
- b'\n\x14live_streaming.proto\x12\nopen_gopro"\xb8\x04\n\x16NotifyLiveStreamStatus\x12<\n\x12live_stream_status\x18\x01 \x01(\x0e2 .open_gopro.EnumLiveStreamStatus\x12:\n\x11live_stream_error\x18\x02 \x01(\x0e2\x1f.open_gopro.EnumLiveStreamError\x12\x1a\n\x12live_stream_encode\x18\x03 \x01(\x08\x12\x1b\n\x13live_stream_bitrate\x18\x04 \x01(\x05\x12K\n\'live_stream_window_size_supported_array\x18\x05 \x03(\x0e2\x1a.open_gopro.EnumWindowSize\x12$\n\x1clive_stream_encode_supported\x18\x06 \x01(\x08\x12(\n live_stream_max_lens_unsupported\x18\x07 \x01(\x08\x12*\n"live_stream_minimum_stream_bitrate\x18\x08 \x01(\x05\x12*\n"live_stream_maximum_stream_bitrate\x18\t \x01(\x05\x12"\n\x1alive_stream_lens_supported\x18\n \x01(\x08\x12>\n live_stream_lens_supported_array\x18\x0b \x03(\x0e2\x14.open_gopro.EnumLens\x12\x12\n\ndeprecated\x18\x0c \x01(\x08"\xbc\x01\n\x1aRequestGetLiveStreamStatus\x12M\n\x1bregister_live_stream_status\x18\x01 \x03(\x0e2(.open_gopro.EnumRegisterLiveStreamStatus\x12O\n\x1dunregister_live_stream_status\x18\x02 \x03(\x0e2(.open_gopro.EnumRegisterLiveStreamStatus"\x9f\x02\n\x18RequestSetLiveStreamMode\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x0e\n\x06encode\x18\x02 \x01(\x08\x12/\n\x0bwindow_size\x18\x03 \x01(\x0e2\x1a.open_gopro.EnumWindowSize\x12\x11\n\treserved1\x18\x04 \x01(\t\x12\x11\n\treserved2\x18\x05 \x01(\t\x12\x0c\n\x04cert\x18\x06 \x01(\x0c\x12\x17\n\x0fminimum_bitrate\x18\x07 \x01(\x05\x12\x17\n\x0fmaximum_bitrate\x18\x08 \x01(\x05\x12\x18\n\x10starting_bitrate\x18\t \x01(\x05\x12"\n\x04lens\x18\n \x01(\x0e2\x14.open_gopro.EnumLens\x12\x11\n\treserved3\x18\x0b \x01(\x05*>\n\x08EnumLens\x12\r\n\tLENS_WIDE\x10\x00\x12\x0f\n\x0bLENS_LINEAR\x10\x04\x12\x12\n\x0eLENS_SUPERVIEW\x10\x03*\xde\x03\n\x13EnumLiveStreamError\x12\x1a\n\x16LIVE_STREAM_ERROR_NONE\x10\x00\x12\x1d\n\x19LIVE_STREAM_ERROR_NETWORK\x10\x01\x12"\n\x1eLIVE_STREAM_ERROR_CREATESTREAM\x10\x02\x12!\n\x1dLIVE_STREAM_ERROR_OUTOFMEMORY\x10\x03\x12!\n\x1dLIVE_STREAM_ERROR_INPUTSTREAM\x10\x04\x12\x1e\n\x1aLIVE_STREAM_ERROR_INTERNET\x10\x05\x12\x1f\n\x1bLIVE_STREAM_ERROR_OSNETWORK\x10\x06\x12,\n(LIVE_STREAM_ERROR_SELECTEDNETWORKTIMEOUT\x10\x07\x12#\n\x1fLIVE_STREAM_ERROR_SSL_HANDSHAKE\x10\x08\x12$\n LIVE_STREAM_ERROR_CAMERA_BLOCKED\x10\t\x12\x1d\n\x19LIVE_STREAM_ERROR_UNKNOWN\x10\n\x12"\n\x1eLIVE_STREAM_ERROR_SD_CARD_FULL\x10(\x12%\n!LIVE_STREAM_ERROR_SD_CARD_REMOVED\x10)*\x80\x02\n\x14EnumLiveStreamStatus\x12\x1a\n\x16LIVE_STREAM_STATE_IDLE\x10\x00\x12\x1c\n\x18LIVE_STREAM_STATE_CONFIG\x10\x01\x12\x1b\n\x17LIVE_STREAM_STATE_READY\x10\x02\x12\x1f\n\x1bLIVE_STREAM_STATE_STREAMING\x10\x03\x12&\n"LIVE_STREAM_STATE_COMPLETE_STAY_ON\x10\x04\x12$\n LIVE_STREAM_STATE_FAILED_STAY_ON\x10\x05\x12"\n\x1eLIVE_STREAM_STATE_RECONNECTING\x10\x06*\xbc\x01\n\x1cEnumRegisterLiveStreamStatus\x12&\n"REGISTER_LIVE_STREAM_STATUS_STATUS\x10\x01\x12%\n!REGISTER_LIVE_STREAM_STATUS_ERROR\x10\x02\x12$\n REGISTER_LIVE_STREAM_STATUS_MODE\x10\x03\x12\'\n#REGISTER_LIVE_STREAM_STATUS_BITRATE\x10\x04*P\n\x0eEnumWindowSize\x12\x13\n\x0fWINDOW_SIZE_480\x10\x04\x12\x13\n\x0fWINDOW_SIZE_720\x10\x07\x12\x14\n\x10WINDOW_SIZE_1080\x10\x0c'
+ b'\n\x14live_streaming.proto\x12\nopen_gopro"\xa4\x04\n\x16NotifyLiveStreamStatus\x12<\n\x12live_stream_status\x18\x01 \x01(\x0e2 .open_gopro.EnumLiveStreamStatus\x12:\n\x11live_stream_error\x18\x02 \x01(\x0e2\x1f.open_gopro.EnumLiveStreamError\x12\x1a\n\x12live_stream_encode\x18\x03 \x01(\x08\x12\x1b\n\x13live_stream_bitrate\x18\x04 \x01(\x05\x12K\n\'live_stream_window_size_supported_array\x18\x05 \x03(\x0e2\x1a.open_gopro.EnumWindowSize\x12$\n\x1clive_stream_encode_supported\x18\x06 \x01(\x08\x12(\n live_stream_max_lens_unsupported\x18\x07 \x01(\x08\x12*\n"live_stream_minimum_stream_bitrate\x18\x08 \x01(\x05\x12*\n"live_stream_maximum_stream_bitrate\x18\t \x01(\x05\x12"\n\x1alive_stream_lens_supported\x18\n \x01(\x08\x12>\n live_stream_lens_supported_array\x18\x0b \x03(\x0e2\x14.open_gopro.EnumLens"\xbc\x01\n\x1aRequestGetLiveStreamStatus\x12M\n\x1bregister_live_stream_status\x18\x01 \x03(\x0e2(.open_gopro.EnumRegisterLiveStreamStatus\x12O\n\x1dunregister_live_stream_status\x18\x02 \x03(\x0e2(.open_gopro.EnumRegisterLiveStreamStatus"\xe6\x01\n\x18RequestSetLiveStreamMode\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x0e\n\x06encode\x18\x02 \x01(\x08\x12/\n\x0bwindow_size\x18\x03 \x01(\x0e2\x1a.open_gopro.EnumWindowSize\x12\x0c\n\x04cert\x18\x06 \x01(\x0c\x12\x17\n\x0fminimum_bitrate\x18\x07 \x01(\x05\x12\x17\n\x0fmaximum_bitrate\x18\x08 \x01(\x05\x12\x18\n\x10starting_bitrate\x18\t \x01(\x05\x12"\n\x04lens\x18\n \x01(\x0e2\x14.open_gopro.EnumLens*>\n\x08EnumLens\x12\r\n\tLENS_WIDE\x10\x00\x12\x0f\n\x0bLENS_LINEAR\x10\x04\x12\x12\n\x0eLENS_SUPERVIEW\x10\x03*\xde\x03\n\x13EnumLiveStreamError\x12\x1a\n\x16LIVE_STREAM_ERROR_NONE\x10\x00\x12\x1d\n\x19LIVE_STREAM_ERROR_NETWORK\x10\x01\x12"\n\x1eLIVE_STREAM_ERROR_CREATESTREAM\x10\x02\x12!\n\x1dLIVE_STREAM_ERROR_OUTOFMEMORY\x10\x03\x12!\n\x1dLIVE_STREAM_ERROR_INPUTSTREAM\x10\x04\x12\x1e\n\x1aLIVE_STREAM_ERROR_INTERNET\x10\x05\x12\x1f\n\x1bLIVE_STREAM_ERROR_OSNETWORK\x10\x06\x12,\n(LIVE_STREAM_ERROR_SELECTEDNETWORKTIMEOUT\x10\x07\x12#\n\x1fLIVE_STREAM_ERROR_SSL_HANDSHAKE\x10\x08\x12$\n LIVE_STREAM_ERROR_CAMERA_BLOCKED\x10\t\x12\x1d\n\x19LIVE_STREAM_ERROR_UNKNOWN\x10\n\x12"\n\x1eLIVE_STREAM_ERROR_SD_CARD_FULL\x10(\x12%\n!LIVE_STREAM_ERROR_SD_CARD_REMOVED\x10)*\x80\x02\n\x14EnumLiveStreamStatus\x12\x1a\n\x16LIVE_STREAM_STATE_IDLE\x10\x00\x12\x1c\n\x18LIVE_STREAM_STATE_CONFIG\x10\x01\x12\x1b\n\x17LIVE_STREAM_STATE_READY\x10\x02\x12\x1f\n\x1bLIVE_STREAM_STATE_STREAMING\x10\x03\x12&\n"LIVE_STREAM_STATE_COMPLETE_STAY_ON\x10\x04\x12$\n LIVE_STREAM_STATE_FAILED_STAY_ON\x10\x05\x12"\n\x1eLIVE_STREAM_STATE_RECONNECTING\x10\x06*\xbc\x01\n\x1cEnumRegisterLiveStreamStatus\x12&\n"REGISTER_LIVE_STREAM_STATUS_STATUS\x10\x01\x12%\n!REGISTER_LIVE_STREAM_STATUS_ERROR\x10\x02\x12$\n REGISTER_LIVE_STREAM_STATUS_MODE\x10\x03\x12\'\n#REGISTER_LIVE_STREAM_STATUS_BITRATE\x10\x04*P\n\x0eEnumWindowSize\x12\x13\n\x0fWINDOW_SIZE_480\x10\x04\x12\x13\n\x0fWINDOW_SIZE_720\x10\x07\x12\x14\n\x10WINDOW_SIZE_1080\x10\x0c'
)
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "live_streaming_pb2", globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
- _ENUMLENS._serialized_start = 1088
- _ENUMLENS._serialized_end = 1150
- _ENUMLIVESTREAMERROR._serialized_start = 1153
- _ENUMLIVESTREAMERROR._serialized_end = 1631
- _ENUMLIVESTREAMSTATUS._serialized_start = 1634
- _ENUMLIVESTREAMSTATUS._serialized_end = 1890
- _ENUMREGISTERLIVESTREAMSTATUS._serialized_start = 1893
- _ENUMREGISTERLIVESTREAMSTATUS._serialized_end = 2081
- _ENUMWINDOWSIZE._serialized_start = 2083
- _ENUMWINDOWSIZE._serialized_end = 2163
+ _ENUMLENS._serialized_start = 1011
+ _ENUMLENS._serialized_end = 1073
+ _ENUMLIVESTREAMERROR._serialized_start = 1076
+ _ENUMLIVESTREAMERROR._serialized_end = 1554
+ _ENUMLIVESTREAMSTATUS._serialized_start = 1557
+ _ENUMLIVESTREAMSTATUS._serialized_end = 1813
+ _ENUMREGISTERLIVESTREAMSTATUS._serialized_start = 1816
+ _ENUMREGISTERLIVESTREAMSTATUS._serialized_end = 2004
+ _ENUMWINDOWSIZE._serialized_start = 2006
+ _ENUMWINDOWSIZE._serialized_end = 2086
_NOTIFYLIVESTREAMSTATUS._serialized_start = 37
- _NOTIFYLIVESTREAMSTATUS._serialized_end = 605
- _REQUESTGETLIVESTREAMSTATUS._serialized_start = 608
- _REQUESTGETLIVESTREAMSTATUS._serialized_end = 796
- _REQUESTSETLIVESTREAMMODE._serialized_start = 799
- _REQUESTSETLIVESTREAMMODE._serialized_end = 1086
+ _NOTIFYLIVESTREAMSTATUS._serialized_end = 585
+ _REQUESTGETLIVESTREAMSTATUS._serialized_start = 588
+ _REQUESTGETLIVESTREAMSTATUS._serialized_end = 776
+ _REQUESTSETLIVESTREAMMODE._serialized_start = 779
+ _REQUESTSETLIVESTREAMMODE._serialized_end = 1009
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.pyi
index 4d310084..aa5fcad2 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/live_streaming_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf messages for working with Live Streams
"""
import builtins
@@ -47,60 +47,60 @@ class _EnumLiveStreamErrorEnumTypeWrapper(
):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
LIVE_STREAM_ERROR_NONE: _EnumLiveStreamError.ValueType
- "No error (success)"
+ " No error (success)"
LIVE_STREAM_ERROR_NETWORK: _EnumLiveStreamError.ValueType
- "General network error during the stream"
+ " General network error during the stream"
LIVE_STREAM_ERROR_CREATESTREAM: _EnumLiveStreamError.ValueType
- "Startup error: bad URL or valid with live stream server"
+ " Startup error: bad URL or valid with live stream server"
LIVE_STREAM_ERROR_OUTOFMEMORY: _EnumLiveStreamError.ValueType
- "Not enough memory on camera to complete task"
+ " Not enough memory on camera to complete task"
LIVE_STREAM_ERROR_INPUTSTREAM: _EnumLiveStreamError.ValueType
- "Failed to get stream from low level camera system"
+ " Failed to get stream from low level camera system"
LIVE_STREAM_ERROR_INTERNET: _EnumLiveStreamError.ValueType
- "No internet access detected on startup of streamer"
+ " No internet access detected on startup of streamer"
LIVE_STREAM_ERROR_OSNETWORK: _EnumLiveStreamError.ValueType
- "Error occured in linux networking stack. usually means the server closed the connection"
+ " Error occured in linux networking stack. usually means the server closed the connection"
LIVE_STREAM_ERROR_SELECTEDNETWORKTIMEOUT: _EnumLiveStreamError.ValueType
- "Timed out attemping to connect to the wifi network when attemping live stream"
+ " Timed out attemping to connect to the wifi network when attemping live stream"
LIVE_STREAM_ERROR_SSL_HANDSHAKE: _EnumLiveStreamError.ValueType
- "SSL handshake failed (commonly caused due to incorrect time / time zone)"
+ " SSL handshake failed (commonly caused due to incorrect time / time zone)"
LIVE_STREAM_ERROR_CAMERA_BLOCKED: _EnumLiveStreamError.ValueType
- "Low level camera system rejected attempt to start live stream"
+ " Low level camera system rejected attempt to start live stream"
LIVE_STREAM_ERROR_UNKNOWN: _EnumLiveStreamError.ValueType
- "Unknown"
+ " Unknown"
LIVE_STREAM_ERROR_SD_CARD_FULL: _EnumLiveStreamError.ValueType
- "Can not perform livestream because sd card is full"
+ " Can not perform livestream because sd card is full"
LIVE_STREAM_ERROR_SD_CARD_REMOVED: _EnumLiveStreamError.ValueType
- "Livestream stopped because sd card was removed"
+ " Livestream stopped because sd card was removed"
class EnumLiveStreamError(_EnumLiveStreamError, metaclass=_EnumLiveStreamErrorEnumTypeWrapper): ...
LIVE_STREAM_ERROR_NONE: EnumLiveStreamError.ValueType
-"No error (success)"
+" No error (success)"
LIVE_STREAM_ERROR_NETWORK: EnumLiveStreamError.ValueType
-"General network error during the stream"
+" General network error during the stream"
LIVE_STREAM_ERROR_CREATESTREAM: EnumLiveStreamError.ValueType
-"Startup error: bad URL or valid with live stream server"
+" Startup error: bad URL or valid with live stream server"
LIVE_STREAM_ERROR_OUTOFMEMORY: EnumLiveStreamError.ValueType
-"Not enough memory on camera to complete task"
+" Not enough memory on camera to complete task"
LIVE_STREAM_ERROR_INPUTSTREAM: EnumLiveStreamError.ValueType
-"Failed to get stream from low level camera system"
+" Failed to get stream from low level camera system"
LIVE_STREAM_ERROR_INTERNET: EnumLiveStreamError.ValueType
-"No internet access detected on startup of streamer"
+" No internet access detected on startup of streamer"
LIVE_STREAM_ERROR_OSNETWORK: EnumLiveStreamError.ValueType
-"Error occured in linux networking stack. usually means the server closed the connection"
+" Error occured in linux networking stack. usually means the server closed the connection"
LIVE_STREAM_ERROR_SELECTEDNETWORKTIMEOUT: EnumLiveStreamError.ValueType
-"Timed out attemping to connect to the wifi network when attemping live stream"
+" Timed out attemping to connect to the wifi network when attemping live stream"
LIVE_STREAM_ERROR_SSL_HANDSHAKE: EnumLiveStreamError.ValueType
-"SSL handshake failed (commonly caused due to incorrect time / time zone)"
+" SSL handshake failed (commonly caused due to incorrect time / time zone)"
LIVE_STREAM_ERROR_CAMERA_BLOCKED: EnumLiveStreamError.ValueType
-"Low level camera system rejected attempt to start live stream"
+" Low level camera system rejected attempt to start live stream"
LIVE_STREAM_ERROR_UNKNOWN: EnumLiveStreamError.ValueType
-"Unknown"
+" Unknown"
LIVE_STREAM_ERROR_SD_CARD_FULL: EnumLiveStreamError.ValueType
-"Can not perform livestream because sd card is full"
+" Can not perform livestream because sd card is full"
LIVE_STREAM_ERROR_SD_CARD_REMOVED: EnumLiveStreamError.ValueType
-"Livestream stopped because sd card was removed"
+" Livestream stopped because sd card was removed"
global___EnumLiveStreamError = EnumLiveStreamError
class _EnumLiveStreamStatus:
@@ -112,36 +112,36 @@ class _EnumLiveStreamStatusEnumTypeWrapper(
):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
LIVE_STREAM_STATE_IDLE: _EnumLiveStreamStatus.ValueType
- "Initial status. Livestream has not yet been configured"
+ " Initial status. Livestream has not yet been configured"
LIVE_STREAM_STATE_CONFIG: _EnumLiveStreamStatus.ValueType
- "Livestream is being configured"
+ " Livestream is being configured"
LIVE_STREAM_STATE_READY: _EnumLiveStreamStatus.ValueType
- "Livestream has finished configuration and is ready to start streaming"
+ "\n Livestream has finished configuration and is ready to start streaming\n "
LIVE_STREAM_STATE_STREAMING: _EnumLiveStreamStatus.ValueType
- "Livestream is actively streaming"
+ " Livestream is actively streaming"
LIVE_STREAM_STATE_COMPLETE_STAY_ON: _EnumLiveStreamStatus.ValueType
- "Live stream is exiting. No errors occured."
+ " Live stream is exiting. No errors occured."
LIVE_STREAM_STATE_FAILED_STAY_ON: _EnumLiveStreamStatus.ValueType
- "Live stream is exiting. An error occurred."
+ " Live stream is exiting. An error occurred."
LIVE_STREAM_STATE_RECONNECTING: _EnumLiveStreamStatus.ValueType
- "An error occurred during livestream and stream is attempting to reconnect."
+ " An error occurred during livestream and stream is attempting to reconnect."
class EnumLiveStreamStatus(_EnumLiveStreamStatus, metaclass=_EnumLiveStreamStatusEnumTypeWrapper): ...
LIVE_STREAM_STATE_IDLE: EnumLiveStreamStatus.ValueType
-"Initial status. Livestream has not yet been configured"
+" Initial status. Livestream has not yet been configured"
LIVE_STREAM_STATE_CONFIG: EnumLiveStreamStatus.ValueType
-"Livestream is being configured"
+" Livestream is being configured"
LIVE_STREAM_STATE_READY: EnumLiveStreamStatus.ValueType
-"Livestream has finished configuration and is ready to start streaming"
+"\nLivestream has finished configuration and is ready to start streaming\n"
LIVE_STREAM_STATE_STREAMING: EnumLiveStreamStatus.ValueType
-"Livestream is actively streaming"
+" Livestream is actively streaming"
LIVE_STREAM_STATE_COMPLETE_STAY_ON: EnumLiveStreamStatus.ValueType
-"Live stream is exiting. No errors occured."
+" Live stream is exiting. No errors occured."
LIVE_STREAM_STATE_FAILED_STAY_ON: EnumLiveStreamStatus.ValueType
-"Live stream is exiting. An error occurred."
+" Live stream is exiting. An error occurred."
LIVE_STREAM_STATE_RECONNECTING: EnumLiveStreamStatus.ValueType
-"An error occurred during livestream and stream is attempting to reconnect."
+" An error occurred during livestream and stream is attempting to reconnect."
global___EnumLiveStreamStatus = EnumLiveStreamStatus
class _EnumRegisterLiveStreamStatus:
@@ -149,8 +149,7 @@ class _EnumRegisterLiveStreamStatus:
V: typing_extensions.TypeAlias = ValueType
class _EnumRegisterLiveStreamStatusEnumTypeWrapper(
- google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumRegisterLiveStreamStatus.ValueType],
- builtins.type,
+ google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumRegisterLiveStreamStatus.ValueType], builtins.type
):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
REGISTER_LIVE_STREAM_STATUS_STATUS: _EnumRegisterLiveStreamStatus.ValueType
@@ -188,6 +187,14 @@ WINDOW_SIZE_1080: EnumWindowSize.ValueType
global___EnumWindowSize = EnumWindowSize
class NotifyLiveStreamStatus(google.protobuf.message.Message):
+ """*
+ Live Stream status
+
+ Sent either:
+ - as a syncrhonous response to initial @ref RequestGetLiveStreamStatus
+ - as asynchronous notifications registered for via @ref RequestGetLiveStreamStatus
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
LIVE_STREAM_STATUS_FIELD_NUMBER: builtins.int
LIVE_STREAM_ERROR_FIELD_NUMBER: builtins.int
@@ -200,40 +207,42 @@ class NotifyLiveStreamStatus(google.protobuf.message.Message):
LIVE_STREAM_MAXIMUM_STREAM_BITRATE_FIELD_NUMBER: builtins.int
LIVE_STREAM_LENS_SUPPORTED_FIELD_NUMBER: builtins.int
LIVE_STREAM_LENS_SUPPORTED_ARRAY_FIELD_NUMBER: builtins.int
- DEPRECATED_FIELD_NUMBER: builtins.int
live_stream_status: global___EnumLiveStreamStatus.ValueType
- "Live stream status"
+ " Live stream status"
live_stream_error: global___EnumLiveStreamError.ValueType
- "Live stream error"
+ " Live stream error"
live_stream_encode: builtins.bool
- "Is live stream encoding?"
+ " Is live stream encoding?"
live_stream_bitrate: builtins.int
- "Live stream bitrate (Kbps)"
+ " Live stream bitrate (Kbps)"
@property
def live_stream_window_size_supported_array(
self,
) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[global___EnumWindowSize.ValueType]:
- """Live stream resolution capabilities"""
+ """
+ List of supported resolutions returned when live stream is registered or requested
+
+ 1. register --> camera
+ 2. register response (with capabilities) --> mobile
+ 3. async notifications (without capabilities) --> mobile
+ """
live_stream_encode_supported: builtins.bool
- "Does the camera support encoding while live streaming?"
+ " Does the camera support encoding while live streaming?"
live_stream_max_lens_unsupported: builtins.bool
- "Is the Max Lens feature NOT supported?"
+ " Is the Max Lens feature NOT supported?"
live_stream_minimum_stream_bitrate: builtins.int
- "Camera-defined minimum bitrate (static) (Kbps)"
+ " Camera-defined minimum bitrate (static) (Kbps)"
live_stream_maximum_stream_bitrate: builtins.int
- "Camera-defined maximum bitrate (static) (Kbps)"
+ " Camera-defined maximum bitrate (static) (Kbps)"
live_stream_lens_supported: builtins.bool
- "Does camera support setting lens for live streaming?"
+ " Does camera support setting lens for live streaming?"
@property
def live_stream_lens_supported_array(
self,
) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[global___EnumLens.ValueType]:
"""Array of supported lenses for live streaming"""
- deprecated: builtins.bool
- "Deprecated"
-
def __init__(
self,
*,
@@ -248,14 +257,11 @@ class NotifyLiveStreamStatus(google.protobuf.message.Message):
live_stream_minimum_stream_bitrate: builtins.int | None = ...,
live_stream_maximum_stream_bitrate: builtins.int | None = ...,
live_stream_lens_supported: builtins.bool | None = ...,
- live_stream_lens_supported_array: collections.abc.Iterable[global___EnumLens.ValueType] | None = ...,
- deprecated: builtins.bool | None = ...
+ live_stream_lens_supported_array: collections.abc.Iterable[global___EnumLens.ValueType] | None = ...
) -> None: ...
def HasField(
self,
field_name: typing_extensions.Literal[
- "deprecated",
- b"deprecated",
"live_stream_bitrate",
b"live_stream_bitrate",
"live_stream_encode",
@@ -279,8 +285,6 @@ class NotifyLiveStreamStatus(google.protobuf.message.Message):
def ClearField(
self,
field_name: typing_extensions.Literal[
- "deprecated",
- b"deprecated",
"live_stream_bitrate",
b"live_stream_bitrate",
"live_stream_encode",
@@ -309,6 +313,12 @@ class NotifyLiveStreamStatus(google.protobuf.message.Message):
global___NotifyLiveStreamStatus = NotifyLiveStreamStatus
class RequestGetLiveStreamStatus(google.protobuf.message.Message):
+ """*
+ Get the current livestream status (and optionally register for future status changes)
+
+ Both current status and future status changes are sent via @ref NotifyLiveStreamStatus
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
REGISTER_LIVE_STREAM_STATUS_FIELD_NUMBER: builtins.int
UNREGISTER_LIVE_STREAM_STATUS_FIELD_NUMBER: builtins.int
@@ -348,40 +358,39 @@ class RequestGetLiveStreamStatus(google.protobuf.message.Message):
global___RequestGetLiveStreamStatus = RequestGetLiveStreamStatus
class RequestSetLiveStreamMode(google.protobuf.message.Message):
+ """*
+ Configure lives streaming
+
+ The current livestream status can be queried via @ref RequestGetLiveStreamStatus
+
+ TODO What is the response?
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
URL_FIELD_NUMBER: builtins.int
ENCODE_FIELD_NUMBER: builtins.int
WINDOW_SIZE_FIELD_NUMBER: builtins.int
- RESERVED1_FIELD_NUMBER: builtins.int
- RESERVED2_FIELD_NUMBER: builtins.int
CERT_FIELD_NUMBER: builtins.int
MINIMUM_BITRATE_FIELD_NUMBER: builtins.int
MAXIMUM_BITRATE_FIELD_NUMBER: builtins.int
STARTING_BITRATE_FIELD_NUMBER: builtins.int
LENS_FIELD_NUMBER: builtins.int
- RESERVED3_FIELD_NUMBER: builtins.int
url: builtins.str
- "RTMP(S) URL used for live stream"
+ " RTMP(S) URL used for live stream"
encode: builtins.bool
- "Save media to sdcard while streaming?"
+ " Save media to sdcard while streaming?"
window_size: global___EnumWindowSize.ValueType
- "Live stream resolution"
- reserved1: builtins.str
- "Reserved"
- reserved2: builtins.str
- "Reserved"
+ " Live stream resolution"
cert: builtins.bytes
- "Certificate for servers that require it"
+ " Certificate for servers that require it"
minimum_bitrate: builtins.int
- "Minimum desired bitrate (may or may not be honored)"
+ " Minimum desired bitrate (may or may not be honored)"
maximum_bitrate: builtins.int
- "Maximum desired bitrate (may or may not be honored)"
+ " Maximum desired bitrate (may or may not be honored)"
starting_bitrate: builtins.int
- "Starting bitrate"
+ " Starting bitrate"
lens: global___EnumLens.ValueType
- "Lens to use for live stream (see"
- reserved3: builtins.int
- "Reserved"
+ " Lens to use for live stream (see NotifyLiveStreamStatus.live_stream_lens_supported)"
def __init__(
self,
@@ -389,14 +398,11 @@ class RequestSetLiveStreamMode(google.protobuf.message.Message):
url: builtins.str | None = ...,
encode: builtins.bool | None = ...,
window_size: global___EnumWindowSize.ValueType | None = ...,
- reserved1: builtins.str | None = ...,
- reserved2: builtins.str | None = ...,
cert: builtins.bytes | None = ...,
minimum_bitrate: builtins.int | None = ...,
maximum_bitrate: builtins.int | None = ...,
starting_bitrate: builtins.int | None = ...,
- lens: global___EnumLens.ValueType | None = ...,
- reserved3: builtins.int | None = ...
+ lens: global___EnumLens.ValueType | None = ...
) -> None: ...
def HasField(
self,
@@ -411,12 +417,6 @@ class RequestSetLiveStreamMode(google.protobuf.message.Message):
b"maximum_bitrate",
"minimum_bitrate",
b"minimum_bitrate",
- "reserved1",
- b"reserved1",
- "reserved2",
- b"reserved2",
- "reserved3",
- b"reserved3",
"starting_bitrate",
b"starting_bitrate",
"url",
@@ -438,12 +438,6 @@ class RequestSetLiveStreamMode(google.protobuf.message.Message):
b"maximum_bitrate",
"minimum_bitrate",
b"minimum_bitrate",
- "reserved1",
- b"reserved1",
- "reserved2",
- b"reserved2",
- "reserved3",
- b"reserved3",
"starting_bitrate",
b"starting_bitrate",
"url",
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.py
index 1a3c5c69..309cd4dd 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.py
@@ -1,5 +1,5 @@
# network_management_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:29 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
@@ -11,41 +11,39 @@
from . import response_generic_pb2 as response__generic__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
- b'\n\x18network_management.proto\x12\nopen_gopro\x1a\x16response_generic.proto"R\n\x16NotifProvisioningState\x128\n\x12provisioning_state\x18\x01 \x02(\x0e2\x1c.open_gopro.EnumProvisioning"\x8d\x01\n\x12NotifStartScanning\x120\n\x0escanning_state\x18\x01 \x02(\x0e2\x18.open_gopro.EnumScanning\x12\x0f\n\x07scan_id\x18\x02 \x01(\x05\x12\x15\n\rtotal_entries\x18\x03 \x01(\x05\x12\x1d\n\x15total_configured_ssid\x18\x04 \x02(\x05"S\n\x0eRequestConnect\x12\x0c\n\x04ssid\x18\x01 \x02(\t\x123\n\rowner_purpose\x18\x02 \x01(\x0e2\x1c.open_gopro.EnumNetworkOwner"\xeb\x01\n\x11RequestConnectNew\x12\x0c\n\x04ssid\x18\x01 \x02(\t\x12\x10\n\x08password\x18\x02 \x02(\t\x12\x11\n\tstatic_ip\x18\x03 \x01(\x0c\x12\x0f\n\x07gateway\x18\x04 \x01(\x0c\x12\x0e\n\x06subnet\x18\x05 \x01(\x0c\x12\x13\n\x0bdns_primary\x18\x06 \x01(\x0c\x12\x15\n\rdns_secondary\x18\x07 \x01(\x0c\x12!\n\x19set_to_least_preferred_ap\x18\x08 \x01(\x08\x123\n\rowner_purpose\x18\t \x01(\x0e2\x1c.open_gopro.EnumNetworkOwner"P\n\x13RequestGetApEntries\x12\x13\n\x0bstart_index\x18\x01 \x02(\x05\x12\x13\n\x0bmax_entries\x18\x02 \x02(\x05\x12\x0f\n\x07scan_id\x18\x03 \x02(\x05"\x17\n\x15RequestReleaseNetwork"\x12\n\x10RequestStartScan"\x93\x01\n\x0fResponseConnect\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x128\n\x12provisioning_state\x18\x02 \x02(\x0e2\x1c.open_gopro.EnumProvisioning\x12\x17\n\x0ftimeout_seconds\x18\x03 \x02(\x05"\x96\x01\n\x12ResponseConnectNew\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x128\n\x12provisioning_state\x18\x02 \x02(\x0e2\x1c.open_gopro.EnumProvisioning\x12\x17\n\x0ftimeout_seconds\x18\x03 \x02(\x05"\xa4\x01\n\tScanEntry\x12\x0c\n\x04ssid\x18\x01 \x02(\t\x12\x1c\n\x14signal_strength_bars\x18\x02 \x02(\x05\x12\x1c\n\x14signal_frequency_mhz\x18\x04 \x02(\x05\x12\x18\n\x10scan_entry_flags\x18\x05 \x02(\x05\x123\n\rowner_purpose\x18\x06 \x01(\x0e2\x1c.open_gopro.EnumNetworkOwner"~\n\x14ResponseGetApEntries\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x12\x0f\n\x07scan_id\x18\x02 \x02(\x05\x12&\n\x07entries\x18\x03 \x03(\x0b2\x15.open_gopro.ScanEntry"x\n\x15ResponseStartScanning\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x120\n\x0escanning_state\x18\x02 \x02(\x0e2\x18.open_gopro.EnumScanning*\x90\x01\n\x10EnumNetworkOwner\x12\x10\n\x0cDEPRECATED_1\x10\x00\x12\x10\n\x0cDEPRECATED_2\x10\x01\x12\x10\n\x0cDEPRECATED_3\x10\x02\x12\x10\n\x0cDEPRECATED_4\x10\x03\x12\x10\n\x0cDEPRECATED_5\x10\x04\x12\x10\n\x0cDEPRECATED_6\x10\x08\x12\x10\n\x0cDEPRECATED_7\x10\x10*\xb5\x03\n\x10EnumProvisioning\x12\x18\n\x14PROVISIONING_UNKNOWN\x10\x00\x12\x1e\n\x1aPROVISIONING_NEVER_STARTED\x10\x01\x12\x18\n\x14PROVISIONING_STARTED\x10\x02\x12"\n\x1ePROVISIONING_ABORTED_BY_SYSTEM\x10\x03\x12"\n\x1ePROVISIONING_CANCELLED_BY_USER\x10\x04\x12\x1f\n\x1bPROVISIONING_SUCCESS_NEW_AP\x10\x05\x12\x1f\n\x1bPROVISIONING_SUCCESS_OLD_AP\x10\x06\x12*\n&PROVISIONING_ERROR_FAILED_TO_ASSOCIATE\x10\x07\x12$\n PROVISIONING_ERROR_PASSWORD_AUTH\x10\x08\x12$\n PROVISIONING_ERROR_EULA_BLOCKING\x10\t\x12"\n\x1ePROVISIONING_ERROR_NO_INTERNET\x10\n\x12\'\n#PROVISIONING_ERROR_UNSUPPORTED_TYPE\x10\x0b*\xac\x01\n\x0cEnumScanning\x12\x14\n\x10SCANNING_UNKNOWN\x10\x00\x12\x1a\n\x16SCANNING_NEVER_STARTED\x10\x01\x12\x14\n\x10SCANNING_STARTED\x10\x02\x12\x1e\n\x1aSCANNING_ABORTED_BY_SYSTEM\x10\x03\x12\x1e\n\x1aSCANNING_CANCELLED_BY_USER\x10\x04\x12\x14\n\x10SCANNING_SUCCESS\x10\x05*\xc2\x01\n\x12EnumScanEntryFlags\x12\x12\n\x0eSCAN_FLAG_OPEN\x10\x00\x12\x1b\n\x17SCAN_FLAG_AUTHENTICATED\x10\x01\x12\x18\n\x14SCAN_FLAG_CONFIGURED\x10\x02\x12\x17\n\x13SCAN_FLAG_BEST_SSID\x10\x04\x12\x18\n\x14SCAN_FLAG_ASSOCIATED\x10\x08\x12\x1e\n\x1aSCAN_FLAG_UNSUPPORTED_TYPE\x10\x10\x12\x0e\n\nDEPRECATED\x10 '
+ b'\n\x18network_management.proto\x12\nopen_gopro\x1a\x16response_generic.proto"R\n\x16NotifProvisioningState\x128\n\x12provisioning_state\x18\x01 \x02(\x0e2\x1c.open_gopro.EnumProvisioning"\x8d\x01\n\x12NotifStartScanning\x120\n\x0escanning_state\x18\x01 \x02(\x0e2\x18.open_gopro.EnumScanning\x12\x0f\n\x07scan_id\x18\x02 \x01(\x05\x12\x15\n\rtotal_entries\x18\x03 \x01(\x05\x12\x1d\n\x15total_configured_ssid\x18\x04 \x02(\x05"\x1e\n\x0eRequestConnect\x12\x0c\n\x04ssid\x18\x01 \x02(\t"\x93\x01\n\x11RequestConnectNew\x12\x0c\n\x04ssid\x18\x01 \x02(\t\x12\x10\n\x08password\x18\x02 \x02(\t\x12\x11\n\tstatic_ip\x18\x03 \x01(\x0c\x12\x0f\n\x07gateway\x18\x04 \x01(\x0c\x12\x0e\n\x06subnet\x18\x05 \x01(\x0c\x12\x13\n\x0bdns_primary\x18\x06 \x01(\x0c\x12\x15\n\rdns_secondary\x18\x07 \x01(\x0c"P\n\x13RequestGetApEntries\x12\x13\n\x0bstart_index\x18\x01 \x02(\x05\x12\x13\n\x0bmax_entries\x18\x02 \x02(\x05\x12\x0f\n\x07scan_id\x18\x03 \x02(\x05"\x17\n\x15RequestReleaseNetwork"\x12\n\x10RequestStartScan"\x93\x01\n\x0fResponseConnect\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x128\n\x12provisioning_state\x18\x02 \x02(\x0e2\x1c.open_gopro.EnumProvisioning\x12\x17\n\x0ftimeout_seconds\x18\x03 \x02(\x05"\x96\x01\n\x12ResponseConnectNew\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x128\n\x12provisioning_state\x18\x02 \x02(\x0e2\x1c.open_gopro.EnumProvisioning\x12\x17\n\x0ftimeout_seconds\x18\x03 \x02(\x05"\x84\x02\n\x14ResponseGetApEntries\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x12\x0f\n\x07scan_id\x18\x02 \x02(\x05\x12;\n\x07entries\x18\x03 \x03(\x0b2*.open_gopro.ResponseGetApEntries.ScanEntry\x1ao\n\tScanEntry\x12\x0c\n\x04ssid\x18\x01 \x02(\t\x12\x1c\n\x14signal_strength_bars\x18\x02 \x02(\x05\x12\x1c\n\x14signal_frequency_mhz\x18\x04 \x02(\x05\x12\x18\n\x10scan_entry_flags\x18\x05 \x02(\x05"x\n\x15ResponseStartScanning\x12-\n\x06result\x18\x01 \x02(\x0e2\x1d.open_gopro.EnumResultGeneric\x120\n\x0escanning_state\x18\x02 \x02(\x0e2\x18.open_gopro.EnumScanning*\xb5\x03\n\x10EnumProvisioning\x12\x18\n\x14PROVISIONING_UNKNOWN\x10\x00\x12\x1e\n\x1aPROVISIONING_NEVER_STARTED\x10\x01\x12\x18\n\x14PROVISIONING_STARTED\x10\x02\x12"\n\x1ePROVISIONING_ABORTED_BY_SYSTEM\x10\x03\x12"\n\x1ePROVISIONING_CANCELLED_BY_USER\x10\x04\x12\x1f\n\x1bPROVISIONING_SUCCESS_NEW_AP\x10\x05\x12\x1f\n\x1bPROVISIONING_SUCCESS_OLD_AP\x10\x06\x12*\n&PROVISIONING_ERROR_FAILED_TO_ASSOCIATE\x10\x07\x12$\n PROVISIONING_ERROR_PASSWORD_AUTH\x10\x08\x12$\n PROVISIONING_ERROR_EULA_BLOCKING\x10\t\x12"\n\x1ePROVISIONING_ERROR_NO_INTERNET\x10\n\x12\'\n#PROVISIONING_ERROR_UNSUPPORTED_TYPE\x10\x0b*\xac\x01\n\x0cEnumScanning\x12\x14\n\x10SCANNING_UNKNOWN\x10\x00\x12\x1a\n\x16SCANNING_NEVER_STARTED\x10\x01\x12\x14\n\x10SCANNING_STARTED\x10\x02\x12\x1e\n\x1aSCANNING_ABORTED_BY_SYSTEM\x10\x03\x12\x1e\n\x1aSCANNING_CANCELLED_BY_USER\x10\x04\x12\x14\n\x10SCANNING_SUCCESS\x10\x05*\xb2\x01\n\x12EnumScanEntryFlags\x12\x12\n\x0eSCAN_FLAG_OPEN\x10\x00\x12\x1b\n\x17SCAN_FLAG_AUTHENTICATED\x10\x01\x12\x18\n\x14SCAN_FLAG_CONFIGURED\x10\x02\x12\x17\n\x13SCAN_FLAG_BEST_SSID\x10\x04\x12\x18\n\x14SCAN_FLAG_ASSOCIATED\x10\x08\x12\x1e\n\x1aSCAN_FLAG_UNSUPPORTED_TYPE\x10\x10'
)
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "network_management_pb2", globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
- _ENUMNETWORKOWNER._serialized_start = 1463
- _ENUMNETWORKOWNER._serialized_end = 1607
- _ENUMPROVISIONING._serialized_start = 1610
- _ENUMPROVISIONING._serialized_end = 2047
- _ENUMSCANNING._serialized_start = 2050
- _ENUMSCANNING._serialized_end = 2222
- _ENUMSCANENTRYFLAGS._serialized_start = 2225
- _ENUMSCANENTRYFLAGS._serialized_end = 2419
+ _ENUMPROVISIONING._serialized_start = 1290
+ _ENUMPROVISIONING._serialized_end = 1727
+ _ENUMSCANNING._serialized_start = 1730
+ _ENUMSCANNING._serialized_end = 1902
+ _ENUMSCANENTRYFLAGS._serialized_start = 1905
+ _ENUMSCANENTRYFLAGS._serialized_end = 2083
_NOTIFPROVISIONINGSTATE._serialized_start = 64
_NOTIFPROVISIONINGSTATE._serialized_end = 146
_NOTIFSTARTSCANNING._serialized_start = 149
_NOTIFSTARTSCANNING._serialized_end = 290
_REQUESTCONNECT._serialized_start = 292
- _REQUESTCONNECT._serialized_end = 375
- _REQUESTCONNECTNEW._serialized_start = 378
- _REQUESTCONNECTNEW._serialized_end = 613
- _REQUESTGETAPENTRIES._serialized_start = 615
- _REQUESTGETAPENTRIES._serialized_end = 695
- _REQUESTRELEASENETWORK._serialized_start = 697
- _REQUESTRELEASENETWORK._serialized_end = 720
- _REQUESTSTARTSCAN._serialized_start = 722
- _REQUESTSTARTSCAN._serialized_end = 740
- _RESPONSECONNECT._serialized_start = 743
- _RESPONSECONNECT._serialized_end = 890
- _RESPONSECONNECTNEW._serialized_start = 893
- _RESPONSECONNECTNEW._serialized_end = 1043
- _SCANENTRY._serialized_start = 1046
- _SCANENTRY._serialized_end = 1210
- _RESPONSEGETAPENTRIES._serialized_start = 1212
- _RESPONSEGETAPENTRIES._serialized_end = 1338
- _RESPONSESTARTSCANNING._serialized_start = 1340
- _RESPONSESTARTSCANNING._serialized_end = 1460
+ _REQUESTCONNECT._serialized_end = 322
+ _REQUESTCONNECTNEW._serialized_start = 325
+ _REQUESTCONNECTNEW._serialized_end = 472
+ _REQUESTGETAPENTRIES._serialized_start = 474
+ _REQUESTGETAPENTRIES._serialized_end = 554
+ _REQUESTRELEASENETWORK._serialized_start = 556
+ _REQUESTRELEASENETWORK._serialized_end = 579
+ _REQUESTSTARTSCAN._serialized_start = 581
+ _REQUESTSTARTSCAN._serialized_end = 599
+ _RESPONSECONNECT._serialized_start = 602
+ _RESPONSECONNECT._serialized_end = 749
+ _RESPONSECONNECTNEW._serialized_start = 752
+ _RESPONSECONNECTNEW._serialized_end = 902
+ _RESPONSEGETAPENTRIES._serialized_start = 905
+ _RESPONSEGETAPENTRIES._serialized_end = 1165
+ _RESPONSEGETAPENTRIES_SCANENTRY._serialized_start = 1054
+ _RESPONSEGETAPENTRIES_SCANENTRY._serialized_end = 1165
+ _RESPONSESTARTSCANNING._serialized_start = 1167
+ _RESPONSESTARTSCANNING._serialized_end = 1287
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.pyi
index 5a385253..4d0dd2b2 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/network_management_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf messages for network management
"""
import builtins
@@ -20,33 +20,6 @@ else:
import typing_extensions
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
-class _EnumNetworkOwner:
- ValueType = typing.NewType("ValueType", builtins.int)
- V: typing_extensions.TypeAlias = ValueType
-
-class _EnumNetworkOwnerEnumTypeWrapper(
- google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumNetworkOwner.ValueType], builtins.type
-):
- DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
- DEPRECATED_1: _EnumNetworkOwner.ValueType
- DEPRECATED_2: _EnumNetworkOwner.ValueType
- DEPRECATED_3: _EnumNetworkOwner.ValueType
- DEPRECATED_4: _EnumNetworkOwner.ValueType
- DEPRECATED_5: _EnumNetworkOwner.ValueType
- DEPRECATED_6: _EnumNetworkOwner.ValueType
- DEPRECATED_7: _EnumNetworkOwner.ValueType
-
-class EnumNetworkOwner(_EnumNetworkOwner, metaclass=_EnumNetworkOwnerEnumTypeWrapper): ...
-
-DEPRECATED_1: EnumNetworkOwner.ValueType
-DEPRECATED_2: EnumNetworkOwner.ValueType
-DEPRECATED_3: EnumNetworkOwner.ValueType
-DEPRECATED_4: EnumNetworkOwner.ValueType
-DEPRECATED_5: EnumNetworkOwner.ValueType
-DEPRECATED_6: EnumNetworkOwner.ValueType
-DEPRECATED_7: EnumNetworkOwner.ValueType
-global___EnumNetworkOwner = EnumNetworkOwner
-
class _EnumProvisioning:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
@@ -118,37 +91,41 @@ class _EnumScanEntryFlagsEnumTypeWrapper(
):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
SCAN_FLAG_OPEN: _EnumScanEntryFlags.ValueType
- "This network does not require authentication"
+ " This network does not require authentication"
SCAN_FLAG_AUTHENTICATED: _EnumScanEntryFlags.ValueType
- "This network requires authentication"
+ " This network requires authentication"
SCAN_FLAG_CONFIGURED: _EnumScanEntryFlags.ValueType
- "This network has been previously provisioned"
+ " This network has been previously provisioned"
SCAN_FLAG_BEST_SSID: _EnumScanEntryFlags.ValueType
SCAN_FLAG_ASSOCIATED: _EnumScanEntryFlags.ValueType
- "camera is connected to this AP"
+ " camera is connected to this AP"
SCAN_FLAG_UNSUPPORTED_TYPE: _EnumScanEntryFlags.ValueType
- DEPRECATED: _EnumScanEntryFlags.ValueType
class EnumScanEntryFlags(_EnumScanEntryFlags, metaclass=_EnumScanEntryFlagsEnumTypeWrapper): ...
SCAN_FLAG_OPEN: EnumScanEntryFlags.ValueType
-"This network does not require authentication"
+" This network does not require authentication"
SCAN_FLAG_AUTHENTICATED: EnumScanEntryFlags.ValueType
-"This network requires authentication"
+" This network requires authentication"
SCAN_FLAG_CONFIGURED: EnumScanEntryFlags.ValueType
-"This network has been previously provisioned"
+" This network has been previously provisioned"
SCAN_FLAG_BEST_SSID: EnumScanEntryFlags.ValueType
SCAN_FLAG_ASSOCIATED: EnumScanEntryFlags.ValueType
-"camera is connected to this AP"
+" camera is connected to this AP"
SCAN_FLAG_UNSUPPORTED_TYPE: EnumScanEntryFlags.ValueType
-DEPRECATED: EnumScanEntryFlags.ValueType
global___EnumScanEntryFlags = EnumScanEntryFlags
class NotifProvisioningState(google.protobuf.message.Message):
+ """
+ Provision state notification
+
+ TODO refernce where this is triggered
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
PROVISIONING_STATE_FIELD_NUMBER: builtins.int
provisioning_state: global___EnumProvisioning.ValueType
- "Provisioning/connection state"
+ " Provisioning / connection state"
def __init__(self, *, provisioning_state: global___EnumProvisioning.ValueType | None = ...) -> None: ...
def HasField(
@@ -161,19 +138,25 @@ class NotifProvisioningState(google.protobuf.message.Message):
global___NotifProvisioningState = NotifProvisioningState
class NotifStartScanning(google.protobuf.message.Message):
+ """
+ Scanning state notification
+
+ Triggered via @ref RequestStartScan
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
SCANNING_STATE_FIELD_NUMBER: builtins.int
SCAN_ID_FIELD_NUMBER: builtins.int
TOTAL_ENTRIES_FIELD_NUMBER: builtins.int
TOTAL_CONFIGURED_SSID_FIELD_NUMBER: builtins.int
scanning_state: global___EnumScanning.ValueType
- "Scanning state"
+ " Scanning state"
scan_id: builtins.int
- "ID associated with scan results (included if scan was successful)"
+ " ID associated with scan results (included if scan was successful)"
total_entries: builtins.int
- "Number of APs found during scan (included if scan was successful)"
+ " Number of APs found during scan (included if scan was successful)"
total_configured_ssid: builtins.int
- "Total count of camera's provisioned SSIDs"
+ " Total count of camera's provisioned SSIDs"
def __init__(
self,
@@ -213,27 +196,36 @@ class NotifStartScanning(google.protobuf.message.Message):
global___NotifStartScanning = NotifStartScanning
class RequestConnect(google.protobuf.message.Message):
+ """*
+ Connect to (but do not authenticate with) an Access Point
+
+ This is intended to be used to connect to a previously-connected Access Point
+
+ Response: @ref ResponseConnect
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
SSID_FIELD_NUMBER: builtins.int
- OWNER_PURPOSE_FIELD_NUMBER: builtins.int
ssid: builtins.str
- "AP SSID"
- owner_purpose: global___EnumNetworkOwner.ValueType
- "Deprecated"
+ " AP SSID"
- def __init__(
- self, *, ssid: builtins.str | None = ..., owner_purpose: global___EnumNetworkOwner.ValueType | None = ...
- ) -> None: ...
- def HasField(
- self, field_name: typing_extensions.Literal["owner_purpose", b"owner_purpose", "ssid", b"ssid"]
- ) -> builtins.bool: ...
- def ClearField(
- self, field_name: typing_extensions.Literal["owner_purpose", b"owner_purpose", "ssid", b"ssid"]
- ) -> None: ...
+ def __init__(self, *, ssid: builtins.str | None = ...) -> None: ...
+ def HasField(self, field_name: typing_extensions.Literal["ssid", b"ssid"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing_extensions.Literal["ssid", b"ssid"]) -> None: ...
global___RequestConnect = RequestConnect
class RequestConnectNew(google.protobuf.message.Message):
+ """*
+ Connect to and authenticate with an Access Point
+
+ This is only intended to be used if the AP is not previously provisioned.
+
+ Response: @ref ResponseConnectNew sent immediately
+
+ Notification: @ref NotifProvisioningState sent periodically as provisioning state changes
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
SSID_FIELD_NUMBER: builtins.int
PASSWORD_FIELD_NUMBER: builtins.int
@@ -242,26 +234,20 @@ class RequestConnectNew(google.protobuf.message.Message):
SUBNET_FIELD_NUMBER: builtins.int
DNS_PRIMARY_FIELD_NUMBER: builtins.int
DNS_SECONDARY_FIELD_NUMBER: builtins.int
- SET_TO_LEAST_PREFERRED_AP_FIELD_NUMBER: builtins.int
- OWNER_PURPOSE_FIELD_NUMBER: builtins.int
ssid: builtins.str
- "AP SSID"
+ " AP SSID"
password: builtins.str
- "AP password"
+ " AP password"
static_ip: builtins.bytes
- "Static IP address"
+ " Static IP address"
gateway: builtins.bytes
- "Gateway IP address"
+ " Gateway IP address"
subnet: builtins.bytes
- "Subnet mask"
+ " Subnet mask"
dns_primary: builtins.bytes
- "Primary DNS"
+ " Primary DNS"
dns_secondary: builtins.bytes
- "Secondary DNS"
- set_to_least_preferred_ap: builtins.bool
- "Deprecated"
- owner_purpose: global___EnumNetworkOwner.ValueType
- "Deprecated"
+ " Secondary DNS"
def __init__(
self,
@@ -272,9 +258,7 @@ class RequestConnectNew(google.protobuf.message.Message):
gateway: builtins.bytes | None = ...,
subnet: builtins.bytes | None = ...,
dns_primary: builtins.bytes | None = ...,
- dns_secondary: builtins.bytes | None = ...,
- set_to_least_preferred_ap: builtins.bool | None = ...,
- owner_purpose: global___EnumNetworkOwner.ValueType | None = ...
+ dns_secondary: builtins.bytes | None = ...
) -> None: ...
def HasField(
self,
@@ -285,12 +269,8 @@ class RequestConnectNew(google.protobuf.message.Message):
b"dns_secondary",
"gateway",
b"gateway",
- "owner_purpose",
- b"owner_purpose",
"password",
b"password",
- "set_to_least_preferred_ap",
- b"set_to_least_preferred_ap",
"ssid",
b"ssid",
"static_ip",
@@ -308,12 +288,8 @@ class RequestConnectNew(google.protobuf.message.Message):
b"dns_secondary",
"gateway",
b"gateway",
- "owner_purpose",
- b"owner_purpose",
"password",
b"password",
- "set_to_least_preferred_ap",
- b"set_to_least_preferred_ap",
"ssid",
b"ssid",
"static_ip",
@@ -326,16 +302,22 @@ class RequestConnectNew(google.protobuf.message.Message):
global___RequestConnectNew = RequestConnectNew
class RequestGetApEntries(google.protobuf.message.Message):
+ """*
+ Get a list of Access Points found during a @ref RequestStartScan
+
+ Response: @ref ResponseGetApEntries
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
START_INDEX_FIELD_NUMBER: builtins.int
MAX_ENTRIES_FIELD_NUMBER: builtins.int
SCAN_ID_FIELD_NUMBER: builtins.int
start_index: builtins.int
- "Used for paging. 0 <= start_index < NotifStartScanning.total_entries"
+ " Used for paging. 0 <= start_index < @ref ResponseGetApEntries .total_entries"
max_entries: builtins.int
- "Used for paging. Value must be < NotifStartScanning.total_entries"
+ " Used for paging. Value must be < @ref ResponseGetApEntries .total_entries"
scan_id: builtins.int
- "ID corresponding to a set of scan results (i.e. NotifStartScanning.scan_id)"
+ " ID corresponding to a set of scan results (i.e. @ref ResponseGetApEntries .scan_id)"
def __init__(
self,
@@ -360,6 +342,12 @@ class RequestGetApEntries(google.protobuf.message.Message):
global___RequestGetApEntries = RequestGetApEntries
class RequestReleaseNetwork(google.protobuf.message.Message):
+ """*
+ Request to disconnect from current AP network
+
+ Response: @ref ResponseGeneric
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
def __init__(self) -> None: ...
@@ -367,6 +355,16 @@ class RequestReleaseNetwork(google.protobuf.message.Message):
global___RequestReleaseNetwork = RequestReleaseNetwork
class RequestStartScan(google.protobuf.message.Message):
+ """*
+ Start scanning for Access Points
+
+ @note Serialization of this object is zero bytes.
+
+ Response: @ref ResponseStartScanning are sent immediately after the camera receives this command
+
+ Notifications: @ref NotifStartScanning are sent periodically as scanning state changes. Use to detect scan complete.
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
def __init__(self) -> None: ...
@@ -374,16 +372,22 @@ class RequestStartScan(google.protobuf.message.Message):
global___RequestStartScan = RequestStartScan
class ResponseConnect(google.protobuf.message.Message):
+ """*
+ The status of an attempt to connect to an Access Point
+
+ Sent as the initial response to @ref RequestConnect
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
RESULT_FIELD_NUMBER: builtins.int
PROVISIONING_STATE_FIELD_NUMBER: builtins.int
TIMEOUT_SECONDS_FIELD_NUMBER: builtins.int
result: response_generic_pb2.EnumResultGeneric.ValueType
- "Generic pass/fail/error info"
+ " Generic pass/fail/error info"
provisioning_state: global___EnumProvisioning.ValueType
- "Provisioning/connection state"
+ " Provisioning/connection state"
timeout_seconds: builtins.int
- "Network connection timeout (seconds)"
+ " Network connection timeout (seconds)"
def __init__(
self,
@@ -395,38 +399,35 @@ class ResponseConnect(google.protobuf.message.Message):
def HasField(
self,
field_name: typing_extensions.Literal[
- "provisioning_state",
- b"provisioning_state",
- "result",
- b"result",
- "timeout_seconds",
- b"timeout_seconds",
+ "provisioning_state", b"provisioning_state", "result", b"result", "timeout_seconds", b"timeout_seconds"
],
) -> builtins.bool: ...
def ClearField(
self,
field_name: typing_extensions.Literal[
- "provisioning_state",
- b"provisioning_state",
- "result",
- b"result",
- "timeout_seconds",
- b"timeout_seconds",
+ "provisioning_state", b"provisioning_state", "result", b"result", "timeout_seconds", b"timeout_seconds"
],
) -> None: ...
global___ResponseConnect = ResponseConnect
class ResponseConnectNew(google.protobuf.message.Message):
+ """*
+ The status of an attempt to connect to an Access Point
+
+ Sent as the initial response to @ref RequestConnectNew
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
RESULT_FIELD_NUMBER: builtins.int
PROVISIONING_STATE_FIELD_NUMBER: builtins.int
TIMEOUT_SECONDS_FIELD_NUMBER: builtins.int
result: response_generic_pb2.EnumResultGeneric.ValueType
- "Status of Connect New request"
+ " Status of Connect New request"
provisioning_state: global___EnumProvisioning.ValueType
- "Current provisioning state of the network"
+ " Current provisioning state of the network"
timeout_seconds: builtins.int
+ "*\n number of seconds camera will wait before declaring a network connection attempt failed.\n "
def __init__(
self,
@@ -438,128 +439,121 @@ class ResponseConnectNew(google.protobuf.message.Message):
def HasField(
self,
field_name: typing_extensions.Literal[
- "provisioning_state",
- b"provisioning_state",
- "result",
- b"result",
- "timeout_seconds",
- b"timeout_seconds",
+ "provisioning_state", b"provisioning_state", "result", b"result", "timeout_seconds", b"timeout_seconds"
],
) -> builtins.bool: ...
def ClearField(
self,
field_name: typing_extensions.Literal[
- "provisioning_state",
- b"provisioning_state",
- "result",
- b"result",
- "timeout_seconds",
- b"timeout_seconds",
+ "provisioning_state", b"provisioning_state", "result", b"result", "timeout_seconds", b"timeout_seconds"
],
) -> None: ...
global___ResponseConnectNew = ResponseConnectNew
-class ScanEntry(google.protobuf.message.Message):
- DESCRIPTOR: google.protobuf.descriptor.Descriptor
- SSID_FIELD_NUMBER: builtins.int
- SIGNAL_STRENGTH_BARS_FIELD_NUMBER: builtins.int
- SIGNAL_FREQUENCY_MHZ_FIELD_NUMBER: builtins.int
- SCAN_ENTRY_FLAGS_FIELD_NUMBER: builtins.int
- OWNER_PURPOSE_FIELD_NUMBER: builtins.int
- ssid: builtins.str
- "AP SSID"
- signal_strength_bars: builtins.int
- "Signal strength (3 bars: >-70 dBm; 2 bars: >-85 dBm; 1 bar: <=-85"
- signal_frequency_mhz: builtins.int
- "Signal frequency (MHz)"
- scan_entry_flags: builtins.int
- "Bitmasked value from EnumScanEntryFlags"
- owner_purpose: global___EnumNetworkOwner.ValueType
- "Deprecated"
-
- def __init__(
- self,
- *,
- ssid: builtins.str | None = ...,
- signal_strength_bars: builtins.int | None = ...,
- signal_frequency_mhz: builtins.int | None = ...,
- scan_entry_flags: builtins.int | None = ...,
- owner_purpose: global___EnumNetworkOwner.ValueType | None = ...
- ) -> None: ...
- def HasField(
- self,
- field_name: typing_extensions.Literal[
- "owner_purpose",
- b"owner_purpose",
- "scan_entry_flags",
- b"scan_entry_flags",
- "signal_frequency_mhz",
- b"signal_frequency_mhz",
- "signal_strength_bars",
- b"signal_strength_bars",
- "ssid",
- b"ssid",
- ],
- ) -> builtins.bool: ...
- def ClearField(
- self,
- field_name: typing_extensions.Literal[
- "owner_purpose",
- b"owner_purpose",
- "scan_entry_flags",
- b"scan_entry_flags",
- "signal_frequency_mhz",
- b"signal_frequency_mhz",
- "signal_strength_bars",
- b"signal_strength_bars",
- "ssid",
- b"ssid",
- ],
- ) -> None: ...
+class ResponseGetApEntries(google.protobuf.message.Message):
+ """*
+ A list of scan entries describing a scanned Access Point
-global___ScanEntry = ScanEntry
+ This is sent in response to a @ref RequestGetApEntries
+ """
-class ResponseGetApEntries(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ class ScanEntry(google.protobuf.message.Message):
+ """The individual Scan Entry model"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+ SSID_FIELD_NUMBER: builtins.int
+ SIGNAL_STRENGTH_BARS_FIELD_NUMBER: builtins.int
+ SIGNAL_FREQUENCY_MHZ_FIELD_NUMBER: builtins.int
+ SCAN_ENTRY_FLAGS_FIELD_NUMBER: builtins.int
+ ssid: builtins.str
+ " AP SSID"
+ signal_strength_bars: builtins.int
+ " Signal strength (3 bars: >-70 dBm; 2 bars: >-85 dBm; 1 bar: <=-85 dBm)"
+ signal_frequency_mhz: builtins.int
+ " Signal frequency (MHz)"
+ scan_entry_flags: builtins.int
+ " Bitmasked value from @ref EnumScanEntryFlags"
+
+ def __init__(
+ self,
+ *,
+ ssid: builtins.str | None = ...,
+ signal_strength_bars: builtins.int | None = ...,
+ signal_frequency_mhz: builtins.int | None = ...,
+ scan_entry_flags: builtins.int | None = ...
+ ) -> None: ...
+ def HasField(
+ self,
+ field_name: typing_extensions.Literal[
+ "scan_entry_flags",
+ b"scan_entry_flags",
+ "signal_frequency_mhz",
+ b"signal_frequency_mhz",
+ "signal_strength_bars",
+ b"signal_strength_bars",
+ "ssid",
+ b"ssid",
+ ],
+ ) -> builtins.bool: ...
+ def ClearField(
+ self,
+ field_name: typing_extensions.Literal[
+ "scan_entry_flags",
+ b"scan_entry_flags",
+ "signal_frequency_mhz",
+ b"signal_frequency_mhz",
+ "signal_strength_bars",
+ b"signal_strength_bars",
+ "ssid",
+ b"ssid",
+ ],
+ ) -> None: ...
RESULT_FIELD_NUMBER: builtins.int
SCAN_ID_FIELD_NUMBER: builtins.int
ENTRIES_FIELD_NUMBER: builtins.int
result: response_generic_pb2.EnumResultGeneric.ValueType
- "Generic pass/fail/error info"
+ " Generic pass/fail/error info"
scan_id: builtins.int
- "ID associated with this batch of results"
+ " ID associated with this batch of results"
@property
def entries(
self,
- ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ScanEntry]:
+ ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ResponseGetApEntries.ScanEntry]:
"""Array containing details about discovered APs"""
def __init__(
self,
*,
result: response_generic_pb2.EnumResultGeneric.ValueType | None = ...,
scan_id: builtins.int | None = ...,
- entries: collections.abc.Iterable[global___ScanEntry] | None = ...
+ entries: collections.abc.Iterable[global___ResponseGetApEntries.ScanEntry] | None = ...
) -> None: ...
def HasField(
self, field_name: typing_extensions.Literal["result", b"result", "scan_id", b"scan_id"]
) -> builtins.bool: ...
def ClearField(
- self,
- field_name: typing_extensions.Literal["entries", b"entries", "result", b"result", "scan_id", b"scan_id"],
+ self, field_name: typing_extensions.Literal["entries", b"entries", "result", b"result", "scan_id", b"scan_id"]
) -> None: ...
global___ResponseGetApEntries = ResponseGetApEntries
class ResponseStartScanning(google.protobuf.message.Message):
+ """*
+ The current scanning state.
+
+ This is the initial response to a @ref RequestStartScan
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
RESULT_FIELD_NUMBER: builtins.int
SCANNING_STATE_FIELD_NUMBER: builtins.int
result: response_generic_pb2.EnumResultGeneric.ValueType
- "Generic pass/fail/error info"
+ " Generic pass/fail/error info"
scanning_state: global___EnumScanning.ValueType
- "Scanning state"
+ " Scanning state"
def __init__(
self,
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.py
index 2f94fa30..7279aec9 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.py
@@ -1,5 +1,5 @@
# preset_status_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:29 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
@@ -9,27 +9,27 @@
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
- b'\n\x13preset_status.proto\x12\nopen_gopro"I\n\x12NotifyPresetStatus\x123\n\x12preset_group_array\x18\x01 \x03(\x0b2\x17.open_gopro.PresetGroup"\x9a\x02\n\x06Preset\x12\n\n\x02id\x18\x01 \x01(\x05\x12&\n\x04mode\x18\x02 \x01(\x0e2\x18.open_gopro.EnumFlatMode\x12-\n\x08title_id\x18\x03 \x01(\x0e2\x1b.open_gopro.EnumPresetTitle\x12\x14\n\x0ctitle_number\x18\x04 \x01(\x05\x12\x14\n\x0cuser_defined\x18\x05 \x01(\x08\x12(\n\x04icon\x18\x06 \x01(\x0e2\x1a.open_gopro.EnumPresetIcon\x120\n\rsetting_array\x18\x07 \x03(\x0b2\x19.open_gopro.PresetSetting\x12\x13\n\x0bis_modified\x18\x08 \x01(\x08\x12\x10\n\x08is_fixed\x18\t \x01(\x08"\xa7\x01\n\x0bPresetGroup\x12\'\n\x02id\x18\x01 \x01(\x0e2\x1b.open_gopro.EnumPresetGroup\x12(\n\x0cpreset_array\x18\x02 \x03(\x0b2\x12.open_gopro.Preset\x12\x16\n\x0ecan_add_preset\x18\x03 \x01(\x08\x12-\n\x04icon\x18\x04 \x01(\x0e2\x1f.open_gopro.EnumPresetGroupIcon">\n\rPresetSetting\x12\n\n\x02id\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x05\x12\x12\n\nis_caption\x18\x03 \x01(\x08*\xfa\x04\n\x0cEnumFlatMode\x12\x1e\n\x11FLAT_MODE_UNKNOWN\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x12\x16\n\x12FLAT_MODE_PLAYBACK\x10\x04\x12\x13\n\x0fFLAT_MODE_SETUP\x10\x05\x12\x13\n\x0fFLAT_MODE_VIDEO\x10\x0c\x12\x1e\n\x1aFLAT_MODE_TIME_LAPSE_VIDEO\x10\r\x12\x15\n\x11FLAT_MODE_LOOPING\x10\x0f\x12\x1a\n\x16FLAT_MODE_PHOTO_SINGLE\x10\x10\x12\x13\n\x0fFLAT_MODE_PHOTO\x10\x11\x12\x19\n\x15FLAT_MODE_PHOTO_NIGHT\x10\x12\x12\x19\n\x15FLAT_MODE_PHOTO_BURST\x10\x13\x12\x1e\n\x1aFLAT_MODE_TIME_LAPSE_PHOTO\x10\x14\x12\x1f\n\x1bFLAT_MODE_NIGHT_LAPSE_PHOTO\x10\x15\x12\x1e\n\x1aFLAT_MODE_BROADCAST_RECORD\x10\x16\x12!\n\x1dFLAT_MODE_BROADCAST_BROADCAST\x10\x17\x12\x1d\n\x19FLAT_MODE_TIME_WARP_VIDEO\x10\x18\x12\x18\n\x14FLAT_MODE_LIVE_BURST\x10\x19\x12\x1f\n\x1bFLAT_MODE_NIGHT_LAPSE_VIDEO\x10\x1a\x12\x13\n\x0fFLAT_MODE_SLOMO\x10\x1b\x12\x12\n\x0eFLAT_MODE_IDLE\x10\x1c\x12\x1e\n\x1aFLAT_MODE_VIDEO_STAR_TRAIL\x10\x1d\x12"\n\x1eFLAT_MODE_VIDEO_LIGHT_PAINTING\x10\x1e\x12\x1f\n\x1bFLAT_MODE_VIDEO_LIGHT_TRAIL\x10\x1f*\xfd\x01\n\x0fEnumPresetGroup\x12\x1a\n\x15PRESET_GROUP_ID_VIDEO\x10\xe8\x07\x12\x1a\n\x15PRESET_GROUP_ID_PHOTO\x10\xe9\x07\x12\x1e\n\x19PRESET_GROUP_ID_TIMELAPSE\x10\xea\x07\x12$\n\x1fPRESET_GROUP_ID_VIDEO_DUAL_LENS\x10\xeb\x07\x12$\n\x1fPRESET_GROUP_ID_PHOTO_DUAL_LENS\x10\xec\x07\x12(\n#PRESET_GROUP_ID_TIMELAPSE_DUAL_LENS\x10\xed\x07\x12\x1c\n\x17PRESET_GROUP_ID_SPECIAL\x10\xee\x07*\xbc\x02\n\x13EnumPresetGroupIcon\x12\x1e\n\x1aPRESET_GROUP_VIDEO_ICON_ID\x10\x00\x12\x1e\n\x1aPRESET_GROUP_PHOTO_ICON_ID\x10\x01\x12"\n\x1ePRESET_GROUP_TIMELAPSE_ICON_ID\x10\x02\x12\'\n#PRESET_GROUP_LONG_BAT_VIDEO_ICON_ID\x10\x03\x12(\n$PRESET_GROUP_ENDURANCE_VIDEO_ICON_ID\x10\x04\x12"\n\x1ePRESET_GROUP_MAX_VIDEO_ICON_ID\x10\x05\x12"\n\x1ePRESET_GROUP_MAX_PHOTO_ICON_ID\x10\x06\x12&\n"PRESET_GROUP_MAX_TIMELAPSE_ICON_ID\x10\x07*\xa0\x0b\n\x0eEnumPresetIcon\x12\x15\n\x11PRESET_ICON_VIDEO\x10\x00\x12\x18\n\x14PRESET_ICON_ACTIVITY\x10\x01\x12\x19\n\x15PRESET_ICON_CINEMATIC\x10\x02\x12\x15\n\x11PRESET_ICON_PHOTO\x10\x03\x12\x1a\n\x16PRESET_ICON_LIVE_BURST\x10\x04\x12\x15\n\x11PRESET_ICON_BURST\x10\x05\x12\x1b\n\x17PRESET_ICON_PHOTO_NIGHT\x10\x06\x12\x18\n\x14PRESET_ICON_TIMEWARP\x10\x07\x12\x19\n\x15PRESET_ICON_TIMELAPSE\x10\x08\x12\x1a\n\x16PRESET_ICON_NIGHTLAPSE\x10\t\x12\x15\n\x11PRESET_ICON_SNAIL\x10\n\x12\x17\n\x13PRESET_ICON_VIDEO_2\x10\x0b\x12\x19\n\x15PRESET_ICON_360_VIDEO\x10\x0c\x12\x17\n\x13PRESET_ICON_PHOTO_2\x10\r\x12\x18\n\x14PRESET_ICON_PANORAMA\x10\x0e\x12\x17\n\x13PRESET_ICON_BURST_2\x10\x0f\x12\x1a\n\x16PRESET_ICON_TIMEWARP_2\x10\x10\x12\x1b\n\x17PRESET_ICON_TIMELAPSE_2\x10\x11\x12\x16\n\x12PRESET_ICON_CUSTOM\x10\x12\x12\x13\n\x0fPRESET_ICON_AIR\x10\x13\x12\x14\n\x10PRESET_ICON_BIKE\x10\x14\x12\x14\n\x10PRESET_ICON_EPIC\x10\x15\x12\x16\n\x12PRESET_ICON_INDOOR\x10\x16\x12\x15\n\x11PRESET_ICON_MOTOR\x10\x17\x12\x17\n\x13PRESET_ICON_MOUNTED\x10\x18\x12\x17\n\x13PRESET_ICON_OUTDOOR\x10\x19\x12\x13\n\x0fPRESET_ICON_POV\x10\x1a\x12\x16\n\x12PRESET_ICON_SELFIE\x10\x1b\x12\x15\n\x11PRESET_ICON_SKATE\x10\x1c\x12\x14\n\x10PRESET_ICON_SNOW\x10\x1d\x12\x15\n\x11PRESET_ICON_TRAIL\x10\x1e\x12\x16\n\x12PRESET_ICON_TRAVEL\x10\x1f\x12\x15\n\x11PRESET_ICON_WATER\x10 \x12\x17\n\x13PRESET_ICON_LOOPING\x10!\x12\x19\n\x15PRESET_ICON_MAX_VIDEO\x107\x12\x19\n\x15PRESET_ICON_MAX_PHOTO\x108\x12\x1c\n\x18PRESET_ICON_MAX_TIMEWARP\x109\x12\x15\n\x11PRESET_ICON_BASIC\x10:\x12\x1c\n\x18PRESET_ICON_ULTRA_SLO_MO\x10;\x12"\n\x1ePRESET_ICON_STANDARD_ENDURANCE\x10<\x12"\n\x1ePRESET_ICON_ACTIVITY_ENDURANCE\x10=\x12#\n\x1fPRESET_ICON_CINEMATIC_ENDURANCE\x10>\x12\x1f\n\x1bPRESET_ICON_SLOMO_ENDURANCE\x10?\x12\x1c\n\x18PRESET_ICON_STATIONARY_1\x10@\x12\x1c\n\x18PRESET_ICON_STATIONARY_2\x10A\x12\x1c\n\x18PRESET_ICON_STATIONARY_3\x10B\x12\x1c\n\x18PRESET_ICON_STATIONARY_4\x10C\x12\x1a\n\x16PRESET_ICON_STAR_TRAIL\x10L\x12\x1e\n\x1aPRESET_ICON_LIGHT_PAINTING\x10M\x12\x1b\n\x17PRESET_ICON_LIGHT_TRAIL\x10N\x12\x1a\n\x16PRESET_ICON_FULL_FRAME\x10O\x12 \n\x1bPRESET_ICON_TIMELAPSE_PHOTO\x10\xe8\x07\x12!\n\x1cPRESET_ICON_NIGHTLAPSE_PHOTO\x10\xe9\x07*\xf9\x0e\n\x0fEnumPresetTitle\x12\x19\n\x15PRESET_TITLE_ACTIVITY\x10\x00\x12\x19\n\x15PRESET_TITLE_STANDARD\x10\x01\x12\x1a\n\x16PRESET_TITLE_CINEMATIC\x10\x02\x12\x16\n\x12PRESET_TITLE_PHOTO\x10\x03\x12\x1b\n\x17PRESET_TITLE_LIVE_BURST\x10\x04\x12\x16\n\x12PRESET_TITLE_BURST\x10\x05\x12\x16\n\x12PRESET_TITLE_NIGHT\x10\x06\x12\x1a\n\x16PRESET_TITLE_TIME_WARP\x10\x07\x12\x1b\n\x17PRESET_TITLE_TIME_LAPSE\x10\x08\x12\x1c\n\x18PRESET_TITLE_NIGHT_LAPSE\x10\t\x12\x16\n\x12PRESET_TITLE_VIDEO\x10\n\x12\x16\n\x12PRESET_TITLE_SLOMO\x10\x0b\x12\x1a\n\x16PRESET_TITLE_360_VIDEO\x10\x0c\x12\x18\n\x14PRESET_TITLE_PHOTO_2\x10\r\x12\x19\n\x15PRESET_TITLE_PANORAMA\x10\x0e\x12\x1a\n\x16PRESET_TITLE_360_PHOTO\x10\x0f\x12\x1c\n\x18PRESET_TITLE_TIME_WARP_2\x10\x10\x12\x1e\n\x1aPRESET_TITLE_360_TIME_WARP\x10\x11\x12\x17\n\x13PRESET_TITLE_CUSTOM\x10\x12\x12\x14\n\x10PRESET_TITLE_AIR\x10\x13\x12\x15\n\x11PRESET_TITLE_BIKE\x10\x14\x12\x15\n\x11PRESET_TITLE_EPIC\x10\x15\x12\x17\n\x13PRESET_TITLE_INDOOR\x10\x16\x12\x16\n\x12PRESET_TITLE_MOTOR\x10\x17\x12\x18\n\x14PRESET_TITLE_MOUNTED\x10\x18\x12\x18\n\x14PRESET_TITLE_OUTDOOR\x10\x19\x12\x14\n\x10PRESET_TITLE_POV\x10\x1a\x12\x17\n\x13PRESET_TITLE_SELFIE\x10\x1b\x12\x16\n\x12PRESET_TITLE_SKATE\x10\x1c\x12\x15\n\x11PRESET_TITLE_SNOW\x10\x1d\x12\x16\n\x12PRESET_TITLE_TRAIL\x10\x1e\x12\x17\n\x13PRESET_TITLE_TRAVEL\x10\x1f\x12\x16\n\x12PRESET_TITLE_WATER\x10 \x12\x18\n\x14PRESET_TITLE_LOOPING\x10!\x12\x1e\n\x1aPRESET_TITLE_360_TIMELAPSE\x103\x12 \n\x1cPRESET_TITLE_360_NIGHT_LAPSE\x104\x12 \n\x1cPRESET_TITLE_360_NIGHT_PHOTO\x105\x12 \n\x1cPRESET_TITLE_PANO_TIME_LAPSE\x106\x12\x1a\n\x16PRESET_TITLE_MAX_VIDEO\x107\x12\x1a\n\x16PRESET_TITLE_MAX_PHOTO\x108\x12\x1d\n\x19PRESET_TITLE_MAX_TIMEWARP\x109\x12\x16\n\x12PRESET_TITLE_BASIC\x10:\x12\x1d\n\x19PRESET_TITLE_ULTRA_SLO_MO\x10;\x12#\n\x1fPRESET_TITLE_STANDARD_ENDURANCE\x10<\x12#\n\x1fPRESET_TITLE_ACTIVITY_ENDURANCE\x10=\x12$\n PRESET_TITLE_CINEMATIC_ENDURANCE\x10>\x12 \n\x1cPRESET_TITLE_SLOMO_ENDURANCE\x10?\x12\x1d\n\x19PRESET_TITLE_STATIONARY_1\x10@\x12\x1d\n\x19PRESET_TITLE_STATIONARY_2\x10A\x12\x1d\n\x19PRESET_TITLE_STATIONARY_3\x10B\x12\x1d\n\x19PRESET_TITLE_STATIONARY_4\x10C\x12\x1d\n\x19PRESET_TITLE_SIMPLE_VIDEO\x10D\x12!\n\x1dPRESET_TITLE_SIMPLE_TIME_WARP\x10E\x12#\n\x1fPRESET_TITLE_SIMPLE_SUPER_PHOTO\x10F\x12#\n\x1fPRESET_TITLE_SIMPLE_NIGHT_PHOTO\x10G\x12\'\n#PRESET_TITLE_SIMPLE_VIDEO_ENDURANCE\x10H\x12 \n\x1cPRESET_TITLE_HIGHEST_QUALITY\x10I\x12!\n\x1dPRESET_TITLE_EXTENDED_BATTERY\x10J\x12 \n\x1cPRESET_TITLE_LONGEST_BATTERY\x10K\x12\x1b\n\x17PRESET_TITLE_STAR_TRAIL\x10L\x12\x1f\n\x1bPRESET_TITLE_LIGHT_PAINTING\x10M\x12\x1c\n\x18PRESET_TITLE_LIGHT_TRAIL\x10N\x12\x1b\n\x17PRESET_TITLE_FULL_FRAME\x10O\x12\x1f\n\x1bPRESET_TITLE_MAX_LENS_VIDEO\x10P\x12"\n\x1ePRESET_TITLE_MAX_LENS_TIMEWARP\x10Q'
+ b'\n\x13preset_status.proto\x12\nopen_gopro"I\n\x12NotifyPresetStatus\x123\n\x12preset_group_array\x18\x01 \x03(\x0b2\x17.open_gopro.PresetGroup"\xaf\x02\n\x06Preset\x12\n\n\x02id\x18\x01 \x01(\x05\x12&\n\x04mode\x18\x02 \x01(\x0e2\x18.open_gopro.EnumFlatMode\x12-\n\x08title_id\x18\x03 \x01(\x0e2\x1b.open_gopro.EnumPresetTitle\x12\x14\n\x0ctitle_number\x18\x04 \x01(\x05\x12\x14\n\x0cuser_defined\x18\x05 \x01(\x08\x12(\n\x04icon\x18\x06 \x01(\x0e2\x1a.open_gopro.EnumPresetIcon\x120\n\rsetting_array\x18\x07 \x03(\x0b2\x19.open_gopro.PresetSetting\x12\x13\n\x0bis_modified\x18\x08 \x01(\x08\x12\x10\n\x08is_fixed\x18\t \x01(\x08\x12\x13\n\x0bcustom_name\x18\n \x01(\t"\xa7\x01\n\x0bPresetGroup\x12\'\n\x02id\x18\x01 \x01(\x0e2\x1b.open_gopro.EnumPresetGroup\x12(\n\x0cpreset_array\x18\x02 \x03(\x0b2\x12.open_gopro.Preset\x12\x16\n\x0ecan_add_preset\x18\x03 \x01(\x08\x12-\n\x04icon\x18\x04 \x01(\x0e2\x1f.open_gopro.EnumPresetGroupIcon">\n\rPresetSetting\x12\n\n\x02id\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x05\x12\x12\n\nis_caption\x18\x03 \x01(\x08*\xfa\x04\n\x0cEnumFlatMode\x12\x1e\n\x11FLAT_MODE_UNKNOWN\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x12\x16\n\x12FLAT_MODE_PLAYBACK\x10\x04\x12\x13\n\x0fFLAT_MODE_SETUP\x10\x05\x12\x13\n\x0fFLAT_MODE_VIDEO\x10\x0c\x12\x1e\n\x1aFLAT_MODE_TIME_LAPSE_VIDEO\x10\r\x12\x15\n\x11FLAT_MODE_LOOPING\x10\x0f\x12\x1a\n\x16FLAT_MODE_PHOTO_SINGLE\x10\x10\x12\x13\n\x0fFLAT_MODE_PHOTO\x10\x11\x12\x19\n\x15FLAT_MODE_PHOTO_NIGHT\x10\x12\x12\x19\n\x15FLAT_MODE_PHOTO_BURST\x10\x13\x12\x1e\n\x1aFLAT_MODE_TIME_LAPSE_PHOTO\x10\x14\x12\x1f\n\x1bFLAT_MODE_NIGHT_LAPSE_PHOTO\x10\x15\x12\x1e\n\x1aFLAT_MODE_BROADCAST_RECORD\x10\x16\x12!\n\x1dFLAT_MODE_BROADCAST_BROADCAST\x10\x17\x12\x1d\n\x19FLAT_MODE_TIME_WARP_VIDEO\x10\x18\x12\x18\n\x14FLAT_MODE_LIVE_BURST\x10\x19\x12\x1f\n\x1bFLAT_MODE_NIGHT_LAPSE_VIDEO\x10\x1a\x12\x13\n\x0fFLAT_MODE_SLOMO\x10\x1b\x12\x12\n\x0eFLAT_MODE_IDLE\x10\x1c\x12\x1e\n\x1aFLAT_MODE_VIDEO_STAR_TRAIL\x10\x1d\x12"\n\x1eFLAT_MODE_VIDEO_LIGHT_PAINTING\x10\x1e\x12\x1f\n\x1bFLAT_MODE_VIDEO_LIGHT_TRAIL\x10\x1f*\xfd\x01\n\x0fEnumPresetGroup\x12\x1a\n\x15PRESET_GROUP_ID_VIDEO\x10\xe8\x07\x12\x1a\n\x15PRESET_GROUP_ID_PHOTO\x10\xe9\x07\x12\x1e\n\x19PRESET_GROUP_ID_TIMELAPSE\x10\xea\x07\x12$\n\x1fPRESET_GROUP_ID_VIDEO_DUAL_LENS\x10\xeb\x07\x12$\n\x1fPRESET_GROUP_ID_PHOTO_DUAL_LENS\x10\xec\x07\x12(\n#PRESET_GROUP_ID_TIMELAPSE_DUAL_LENS\x10\xed\x07\x12\x1c\n\x17PRESET_GROUP_ID_SPECIAL\x10\xee\x07*\xbc\x02\n\x13EnumPresetGroupIcon\x12\x1e\n\x1aPRESET_GROUP_VIDEO_ICON_ID\x10\x00\x12\x1e\n\x1aPRESET_GROUP_PHOTO_ICON_ID\x10\x01\x12"\n\x1ePRESET_GROUP_TIMELAPSE_ICON_ID\x10\x02\x12\'\n#PRESET_GROUP_LONG_BAT_VIDEO_ICON_ID\x10\x03\x12(\n$PRESET_GROUP_ENDURANCE_VIDEO_ICON_ID\x10\x04\x12"\n\x1ePRESET_GROUP_MAX_VIDEO_ICON_ID\x10\x05\x12"\n\x1ePRESET_GROUP_MAX_PHOTO_ICON_ID\x10\x06\x12&\n"PRESET_GROUP_MAX_TIMELAPSE_ICON_ID\x10\x07*\x81\x11\n\x0eEnumPresetIcon\x12\x15\n\x11PRESET_ICON_VIDEO\x10\x00\x12\x18\n\x14PRESET_ICON_ACTIVITY\x10\x01\x12\x19\n\x15PRESET_ICON_CINEMATIC\x10\x02\x12\x15\n\x11PRESET_ICON_PHOTO\x10\x03\x12\x1a\n\x16PRESET_ICON_LIVE_BURST\x10\x04\x12\x15\n\x11PRESET_ICON_BURST\x10\x05\x12\x1b\n\x17PRESET_ICON_PHOTO_NIGHT\x10\x06\x12\x18\n\x14PRESET_ICON_TIMEWARP\x10\x07\x12\x19\n\x15PRESET_ICON_TIMELAPSE\x10\x08\x12\x1a\n\x16PRESET_ICON_NIGHTLAPSE\x10\t\x12\x15\n\x11PRESET_ICON_SNAIL\x10\n\x12\x17\n\x13PRESET_ICON_VIDEO_2\x10\x0b\x12\x19\n\x15PRESET_ICON_360_VIDEO\x10\x0c\x12\x17\n\x13PRESET_ICON_PHOTO_2\x10\r\x12\x18\n\x14PRESET_ICON_PANORAMA\x10\x0e\x12\x17\n\x13PRESET_ICON_BURST_2\x10\x0f\x12\x1a\n\x16PRESET_ICON_TIMEWARP_2\x10\x10\x12\x1b\n\x17PRESET_ICON_TIMELAPSE_2\x10\x11\x12\x16\n\x12PRESET_ICON_CUSTOM\x10\x12\x12\x13\n\x0fPRESET_ICON_AIR\x10\x13\x12\x14\n\x10PRESET_ICON_BIKE\x10\x14\x12\x14\n\x10PRESET_ICON_EPIC\x10\x15\x12\x16\n\x12PRESET_ICON_INDOOR\x10\x16\x12\x15\n\x11PRESET_ICON_MOTOR\x10\x17\x12\x17\n\x13PRESET_ICON_MOUNTED\x10\x18\x12\x17\n\x13PRESET_ICON_OUTDOOR\x10\x19\x12\x13\n\x0fPRESET_ICON_POV\x10\x1a\x12\x16\n\x12PRESET_ICON_SELFIE\x10\x1b\x12\x15\n\x11PRESET_ICON_SKATE\x10\x1c\x12\x14\n\x10PRESET_ICON_SNOW\x10\x1d\x12\x15\n\x11PRESET_ICON_TRAIL\x10\x1e\x12\x16\n\x12PRESET_ICON_TRAVEL\x10\x1f\x12\x15\n\x11PRESET_ICON_WATER\x10 \x12\x17\n\x13PRESET_ICON_LOOPING\x10!\x12\x15\n\x11PRESET_ICON_STARS\x10"\x12\x16\n\x12PRESET_ICON_ACTION\x10#\x12\x1a\n\x16PRESET_ICON_FOLLOW_CAM\x10$\x12\x14\n\x10PRESET_ICON_SURF\x10%\x12\x14\n\x10PRESET_ICON_CITY\x10&\x12\x15\n\x11PRESET_ICON_SHAKY\x10\'\x12\x16\n\x12PRESET_ICON_CHESTY\x10(\x12\x16\n\x12PRESET_ICON_HELMET\x10)\x12\x14\n\x10PRESET_ICON_BITE\x10*\x12\x13\n\x0fPRESET_ICON_MTB\x10+\x12\x19\n\x15PRESET_ICON_MAX_VIDEO\x107\x12\x19\n\x15PRESET_ICON_MAX_PHOTO\x108\x12\x1c\n\x18PRESET_ICON_MAX_TIMEWARP\x109\x12\x15\n\x11PRESET_ICON_BASIC\x10:\x12\x1c\n\x18PRESET_ICON_ULTRA_SLO_MO\x10;\x12"\n\x1ePRESET_ICON_STANDARD_ENDURANCE\x10<\x12"\n\x1ePRESET_ICON_ACTIVITY_ENDURANCE\x10=\x12#\n\x1fPRESET_ICON_CINEMATIC_ENDURANCE\x10>\x12\x1f\n\x1bPRESET_ICON_SLOMO_ENDURANCE\x10?\x12\x1c\n\x18PRESET_ICON_STATIONARY_1\x10@\x12\x1c\n\x18PRESET_ICON_STATIONARY_2\x10A\x12\x1c\n\x18PRESET_ICON_STATIONARY_3\x10B\x12\x1c\n\x18PRESET_ICON_STATIONARY_4\x10C\x12"\n\x1ePRESET_ICON_SIMPLE_SUPER_PHOTO\x10F\x12"\n\x1ePRESET_ICON_SIMPLE_NIGHT_PHOTO\x10G\x12%\n!PRESET_ICON_HIGHEST_QUALITY_VIDEO\x10I\x12&\n"PRESET_ICON_STANDARD_QUALITY_VIDEO\x10J\x12#\n\x1fPRESET_ICON_BASIC_QUALITY_VIDEO\x10K\x12\x1a\n\x16PRESET_ICON_STAR_TRAIL\x10L\x12\x1e\n\x1aPRESET_ICON_LIGHT_PAINTING\x10M\x12\x1b\n\x17PRESET_ICON_LIGHT_TRAIL\x10N\x12\x1a\n\x16PRESET_ICON_FULL_FRAME\x10O\x12\x1e\n\x1aPRESET_ICON_EASY_MAX_VIDEO\x10P\x12\x1e\n\x1aPRESET_ICON_EASY_MAX_PHOTO\x10Q\x12!\n\x1dPRESET_ICON_EASY_MAX_TIMEWARP\x10R\x12#\n\x1fPRESET_ICON_EASY_MAX_STAR_TRAIL\x10S\x12\'\n#PRESET_ICON_EASY_MAX_LIGHT_PAINTING\x10T\x12$\n PRESET_ICON_EASY_MAX_LIGHT_TRAIL\x10U\x12\x1e\n\x1aPRESET_ICON_MAX_STAR_TRAIL\x10Y\x12"\n\x1ePRESET_ICON_MAX_LIGHT_PAINTING\x10Z\x12\x1f\n\x1bPRESET_ICON_MAX_LIGHT_TRAIL\x10[\x12 \n\x1bPRESET_ICON_TIMELAPSE_PHOTO\x10\xe8\x07\x12!\n\x1cPRESET_ICON_NIGHTLAPSE_PHOTO\x10\xe9\x07*\xd3\x14\n\x0fEnumPresetTitle\x12\x19\n\x15PRESET_TITLE_ACTIVITY\x10\x00\x12\x19\n\x15PRESET_TITLE_STANDARD\x10\x01\x12\x1a\n\x16PRESET_TITLE_CINEMATIC\x10\x02\x12\x16\n\x12PRESET_TITLE_PHOTO\x10\x03\x12\x1b\n\x17PRESET_TITLE_LIVE_BURST\x10\x04\x12\x16\n\x12PRESET_TITLE_BURST\x10\x05\x12\x16\n\x12PRESET_TITLE_NIGHT\x10\x06\x12\x1a\n\x16PRESET_TITLE_TIME_WARP\x10\x07\x12\x1b\n\x17PRESET_TITLE_TIME_LAPSE\x10\x08\x12\x1c\n\x18PRESET_TITLE_NIGHT_LAPSE\x10\t\x12\x16\n\x12PRESET_TITLE_VIDEO\x10\n\x12\x16\n\x12PRESET_TITLE_SLOMO\x10\x0b\x12\x1a\n\x16PRESET_TITLE_360_VIDEO\x10\x0c\x12\x18\n\x14PRESET_TITLE_PHOTO_2\x10\r\x12\x19\n\x15PRESET_TITLE_PANORAMA\x10\x0e\x12\x1a\n\x16PRESET_TITLE_360_PHOTO\x10\x0f\x12\x1c\n\x18PRESET_TITLE_TIME_WARP_2\x10\x10\x12\x1e\n\x1aPRESET_TITLE_360_TIME_WARP\x10\x11\x12\x17\n\x13PRESET_TITLE_CUSTOM\x10\x12\x12\x14\n\x10PRESET_TITLE_AIR\x10\x13\x12\x15\n\x11PRESET_TITLE_BIKE\x10\x14\x12\x15\n\x11PRESET_TITLE_EPIC\x10\x15\x12\x17\n\x13PRESET_TITLE_INDOOR\x10\x16\x12\x16\n\x12PRESET_TITLE_MOTOR\x10\x17\x12\x18\n\x14PRESET_TITLE_MOUNTED\x10\x18\x12\x18\n\x14PRESET_TITLE_OUTDOOR\x10\x19\x12\x14\n\x10PRESET_TITLE_POV\x10\x1a\x12\x17\n\x13PRESET_TITLE_SELFIE\x10\x1b\x12\x16\n\x12PRESET_TITLE_SKATE\x10\x1c\x12\x15\n\x11PRESET_TITLE_SNOW\x10\x1d\x12\x16\n\x12PRESET_TITLE_TRAIL\x10\x1e\x12\x17\n\x13PRESET_TITLE_TRAVEL\x10\x1f\x12\x16\n\x12PRESET_TITLE_WATER\x10 \x12\x18\n\x14PRESET_TITLE_LOOPING\x10!\x12\x16\n\x12PRESET_TITLE_STARS\x10"\x12\x17\n\x13PRESET_TITLE_ACTION\x10#\x12\x1b\n\x17PRESET_TITLE_FOLLOW_CAM\x10$\x12\x15\n\x11PRESET_TITLE_SURF\x10%\x12\x15\n\x11PRESET_TITLE_CITY\x10&\x12\x16\n\x12PRESET_TITLE_SHAKY\x10\'\x12\x17\n\x13PRESET_TITLE_CHESTY\x10(\x12\x17\n\x13PRESET_TITLE_HELMET\x10)\x12\x15\n\x11PRESET_TITLE_BITE\x10*\x12\x14\n\x10PRESET_TITLE_MTB\x10+\x12\x1e\n\x1aPRESET_TITLE_360_TIMELAPSE\x103\x12 \n\x1cPRESET_TITLE_360_NIGHT_LAPSE\x104\x12 \n\x1cPRESET_TITLE_360_NIGHT_PHOTO\x105\x12 \n\x1cPRESET_TITLE_PANO_TIME_LAPSE\x106\x12\x1a\n\x16PRESET_TITLE_MAX_VIDEO\x107\x12\x1a\n\x16PRESET_TITLE_MAX_PHOTO\x108\x12\x1d\n\x19PRESET_TITLE_MAX_TIMEWARP\x109\x12\x16\n\x12PRESET_TITLE_BASIC\x10:\x12\x1d\n\x19PRESET_TITLE_ULTRA_SLO_MO\x10;\x12#\n\x1fPRESET_TITLE_STANDARD_ENDURANCE\x10<\x12#\n\x1fPRESET_TITLE_ACTIVITY_ENDURANCE\x10=\x12$\n PRESET_TITLE_CINEMATIC_ENDURANCE\x10>\x12 \n\x1cPRESET_TITLE_SLOMO_ENDURANCE\x10?\x12\x1d\n\x19PRESET_TITLE_STATIONARY_1\x10@\x12\x1d\n\x19PRESET_TITLE_STATIONARY_2\x10A\x12\x1d\n\x19PRESET_TITLE_STATIONARY_3\x10B\x12\x1d\n\x19PRESET_TITLE_STATIONARY_4\x10C\x12\x1d\n\x19PRESET_TITLE_SIMPLE_VIDEO\x10D\x12!\n\x1dPRESET_TITLE_SIMPLE_TIME_WARP\x10E\x12#\n\x1fPRESET_TITLE_SIMPLE_SUPER_PHOTO\x10F\x12#\n\x1fPRESET_TITLE_SIMPLE_NIGHT_PHOTO\x10G\x12\'\n#PRESET_TITLE_SIMPLE_VIDEO_ENDURANCE\x10H\x12 \n\x1cPRESET_TITLE_HIGHEST_QUALITY\x10I\x12!\n\x1dPRESET_TITLE_EXTENDED_BATTERY\x10J\x12 \n\x1cPRESET_TITLE_LONGEST_BATTERY\x10K\x12\x1b\n\x17PRESET_TITLE_STAR_TRAIL\x10L\x12\x1f\n\x1bPRESET_TITLE_LIGHT_PAINTING\x10M\x12\x1c\n\x18PRESET_TITLE_LIGHT_TRAIL\x10N\x12\x1b\n\x17PRESET_TITLE_FULL_FRAME\x10O\x12\x1f\n\x1bPRESET_TITLE_MAX_LENS_VIDEO\x10P\x12"\n\x1ePRESET_TITLE_MAX_LENS_TIMEWARP\x10Q\x12\'\n#PRESET_TITLE_STANDARD_QUALITY_VIDEO\x10R\x12$\n PRESET_TITLE_BASIC_QUALITY_VIDEO\x10S\x12\x1f\n\x1bPRESET_TITLE_EASY_MAX_VIDEO\x10T\x12\x1f\n\x1bPRESET_TITLE_EASY_MAX_PHOTO\x10U\x12"\n\x1ePRESET_TITLE_EASY_MAX_TIMEWARP\x10V\x12$\n PRESET_TITLE_EASY_MAX_STAR_TRAIL\x10W\x12(\n$PRESET_TITLE_EASY_MAX_LIGHT_PAINTING\x10X\x12%\n!PRESET_TITLE_EASY_MAX_LIGHT_TRAIL\x10Y\x12\x1f\n\x1bPRESET_TITLE_MAX_STAR_TRAIL\x10Z\x12#\n\x1fPRESET_TITLE_MAX_LIGHT_PAINTING\x10[\x12 \n\x1cPRESET_TITLE_MAX_LIGHT_TRAIL\x10\\\x12&\n"PRESET_TITLE_HIGHEST_QUALITY_VIDEO\x10]\x12)\n%PRESET_TITLE_USER_DEFINED_CUSTOM_NAME\x10^'
)
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "preset_status_pb2", globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
- _ENUMFLATMODE._serialized_start = 630
- _ENUMFLATMODE._serialized_end = 1264
- _ENUMPRESETGROUP._serialized_start = 1267
- _ENUMPRESETGROUP._serialized_end = 1520
- _ENUMPRESETGROUPICON._serialized_start = 1523
- _ENUMPRESETGROUPICON._serialized_end = 1839
- _ENUMPRESETICON._serialized_start = 1842
- _ENUMPRESETICON._serialized_end = 3282
- _ENUMPRESETTITLE._serialized_start = 3285
- _ENUMPRESETTITLE._serialized_end = 5198
+ _ENUMFLATMODE._serialized_start = 651
+ _ENUMFLATMODE._serialized_end = 1285
+ _ENUMPRESETGROUP._serialized_start = 1288
+ _ENUMPRESETGROUP._serialized_end = 1541
+ _ENUMPRESETGROUPICON._serialized_start = 1544
+ _ENUMPRESETGROUPICON._serialized_end = 1860
+ _ENUMPRESETICON._serialized_start = 1863
+ _ENUMPRESETICON._serialized_end = 4040
+ _ENUMPRESETTITLE._serialized_start = 4043
+ _ENUMPRESETTITLE._serialized_end = 6686
_NOTIFYPRESETSTATUS._serialized_start = 35
_NOTIFYPRESETSTATUS._serialized_end = 108
_PRESET._serialized_start = 111
- _PRESET._serialized_end = 393
- _PRESETGROUP._serialized_start = 396
- _PRESETGROUP._serialized_end = 563
- _PRESETSETTING._serialized_start = 565
- _PRESETSETTING._serialized_end = 627
+ _PRESET._serialized_end = 414
+ _PRESETGROUP._serialized_start = 417
+ _PRESETGROUP._serialized_end = 584
+ _PRESETSETTING._serialized_start = 586
+ _PRESETSETTING._serialized_end = 648
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.pyi
index 5a286fb0..d8ada2b2 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/preset_status_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf message received from camera containing preset status
"""
import builtins
@@ -174,8 +174,19 @@ class _EnumPresetIconEnumTypeWrapper(
PRESET_ICON_TRAVEL: _EnumPresetIcon.ValueType
PRESET_ICON_WATER: _EnumPresetIcon.ValueType
PRESET_ICON_LOOPING: _EnumPresetIcon.ValueType
+ PRESET_ICON_STARS: _EnumPresetIcon.ValueType
+ "*\n New custom icon (34 - 43)added for HERO 12\n "
+ PRESET_ICON_ACTION: _EnumPresetIcon.ValueType
+ PRESET_ICON_FOLLOW_CAM: _EnumPresetIcon.ValueType
+ PRESET_ICON_SURF: _EnumPresetIcon.ValueType
+ PRESET_ICON_CITY: _EnumPresetIcon.ValueType
+ PRESET_ICON_SHAKY: _EnumPresetIcon.ValueType
+ PRESET_ICON_CHESTY: _EnumPresetIcon.ValueType
+ PRESET_ICON_HELMET: _EnumPresetIcon.ValueType
+ PRESET_ICON_BITE: _EnumPresetIcon.ValueType
+ PRESET_ICON_MTB: _EnumPresetIcon.ValueType
PRESET_ICON_MAX_VIDEO: _EnumPresetIcon.ValueType
- "Reserved 34 - 50 for Custom presets"
+ "\n Reserved 44 - 50 for Custom presets. Add icons below for new presets starting from 51\n "
PRESET_ICON_MAX_PHOTO: _EnumPresetIcon.ValueType
PRESET_ICON_MAX_TIMEWARP: _EnumPresetIcon.ValueType
PRESET_ICON_BASIC: _EnumPresetIcon.ValueType
@@ -188,10 +199,24 @@ class _EnumPresetIconEnumTypeWrapper(
PRESET_ICON_STATIONARY_2: _EnumPresetIcon.ValueType
PRESET_ICON_STATIONARY_3: _EnumPresetIcon.ValueType
PRESET_ICON_STATIONARY_4: _EnumPresetIcon.ValueType
+ PRESET_ICON_SIMPLE_SUPER_PHOTO: _EnumPresetIcon.ValueType
+ PRESET_ICON_SIMPLE_NIGHT_PHOTO: _EnumPresetIcon.ValueType
+ PRESET_ICON_HIGHEST_QUALITY_VIDEO: _EnumPresetIcon.ValueType
+ PRESET_ICON_STANDARD_QUALITY_VIDEO: _EnumPresetIcon.ValueType
+ PRESET_ICON_BASIC_QUALITY_VIDEO: _EnumPresetIcon.ValueType
PRESET_ICON_STAR_TRAIL: _EnumPresetIcon.ValueType
PRESET_ICON_LIGHT_PAINTING: _EnumPresetIcon.ValueType
PRESET_ICON_LIGHT_TRAIL: _EnumPresetIcon.ValueType
PRESET_ICON_FULL_FRAME: _EnumPresetIcon.ValueType
+ PRESET_ICON_EASY_MAX_VIDEO: _EnumPresetIcon.ValueType
+ PRESET_ICON_EASY_MAX_PHOTO: _EnumPresetIcon.ValueType
+ PRESET_ICON_EASY_MAX_TIMEWARP: _EnumPresetIcon.ValueType
+ PRESET_ICON_EASY_MAX_STAR_TRAIL: _EnumPresetIcon.ValueType
+ PRESET_ICON_EASY_MAX_LIGHT_PAINTING: _EnumPresetIcon.ValueType
+ PRESET_ICON_EASY_MAX_LIGHT_TRAIL: _EnumPresetIcon.ValueType
+ PRESET_ICON_MAX_STAR_TRAIL: _EnumPresetIcon.ValueType
+ PRESET_ICON_MAX_LIGHT_PAINTING: _EnumPresetIcon.ValueType
+ PRESET_ICON_MAX_LIGHT_TRAIL: _EnumPresetIcon.ValueType
PRESET_ICON_TIMELAPSE_PHOTO: _EnumPresetIcon.ValueType
PRESET_ICON_NIGHTLAPSE_PHOTO: _EnumPresetIcon.ValueType
@@ -231,8 +256,19 @@ PRESET_ICON_TRAIL: EnumPresetIcon.ValueType
PRESET_ICON_TRAVEL: EnumPresetIcon.ValueType
PRESET_ICON_WATER: EnumPresetIcon.ValueType
PRESET_ICON_LOOPING: EnumPresetIcon.ValueType
+PRESET_ICON_STARS: EnumPresetIcon.ValueType
+"*\nNew custom icon (34 - 43)added for HERO 12\n"
+PRESET_ICON_ACTION: EnumPresetIcon.ValueType
+PRESET_ICON_FOLLOW_CAM: EnumPresetIcon.ValueType
+PRESET_ICON_SURF: EnumPresetIcon.ValueType
+PRESET_ICON_CITY: EnumPresetIcon.ValueType
+PRESET_ICON_SHAKY: EnumPresetIcon.ValueType
+PRESET_ICON_CHESTY: EnumPresetIcon.ValueType
+PRESET_ICON_HELMET: EnumPresetIcon.ValueType
+PRESET_ICON_BITE: EnumPresetIcon.ValueType
+PRESET_ICON_MTB: EnumPresetIcon.ValueType
PRESET_ICON_MAX_VIDEO: EnumPresetIcon.ValueType
-"Reserved 34 - 50 for Custom presets"
+"\nReserved 44 - 50 for Custom presets. Add icons below for new presets starting from 51\n"
PRESET_ICON_MAX_PHOTO: EnumPresetIcon.ValueType
PRESET_ICON_MAX_TIMEWARP: EnumPresetIcon.ValueType
PRESET_ICON_BASIC: EnumPresetIcon.ValueType
@@ -245,10 +281,24 @@ PRESET_ICON_STATIONARY_1: EnumPresetIcon.ValueType
PRESET_ICON_STATIONARY_2: EnumPresetIcon.ValueType
PRESET_ICON_STATIONARY_3: EnumPresetIcon.ValueType
PRESET_ICON_STATIONARY_4: EnumPresetIcon.ValueType
+PRESET_ICON_SIMPLE_SUPER_PHOTO: EnumPresetIcon.ValueType
+PRESET_ICON_SIMPLE_NIGHT_PHOTO: EnumPresetIcon.ValueType
+PRESET_ICON_HIGHEST_QUALITY_VIDEO: EnumPresetIcon.ValueType
+PRESET_ICON_STANDARD_QUALITY_VIDEO: EnumPresetIcon.ValueType
+PRESET_ICON_BASIC_QUALITY_VIDEO: EnumPresetIcon.ValueType
PRESET_ICON_STAR_TRAIL: EnumPresetIcon.ValueType
PRESET_ICON_LIGHT_PAINTING: EnumPresetIcon.ValueType
PRESET_ICON_LIGHT_TRAIL: EnumPresetIcon.ValueType
PRESET_ICON_FULL_FRAME: EnumPresetIcon.ValueType
+PRESET_ICON_EASY_MAX_VIDEO: EnumPresetIcon.ValueType
+PRESET_ICON_EASY_MAX_PHOTO: EnumPresetIcon.ValueType
+PRESET_ICON_EASY_MAX_TIMEWARP: EnumPresetIcon.ValueType
+PRESET_ICON_EASY_MAX_STAR_TRAIL: EnumPresetIcon.ValueType
+PRESET_ICON_EASY_MAX_LIGHT_PAINTING: EnumPresetIcon.ValueType
+PRESET_ICON_EASY_MAX_LIGHT_TRAIL: EnumPresetIcon.ValueType
+PRESET_ICON_MAX_STAR_TRAIL: EnumPresetIcon.ValueType
+PRESET_ICON_MAX_LIGHT_PAINTING: EnumPresetIcon.ValueType
+PRESET_ICON_MAX_LIGHT_TRAIL: EnumPresetIcon.ValueType
PRESET_ICON_TIMELAPSE_PHOTO: EnumPresetIcon.ValueType
PRESET_ICON_NIGHTLAPSE_PHOTO: EnumPresetIcon.ValueType
global___EnumPresetIcon = EnumPresetIcon
@@ -295,8 +345,19 @@ class _EnumPresetTitleEnumTypeWrapper(
PRESET_TITLE_TRAVEL: _EnumPresetTitle.ValueType
PRESET_TITLE_WATER: _EnumPresetTitle.ValueType
PRESET_TITLE_LOOPING: _EnumPresetTitle.ValueType
+ PRESET_TITLE_STARS: _EnumPresetTitle.ValueType
+ "\n New custom names (34 - 43)added for HERO 12\n "
+ PRESET_TITLE_ACTION: _EnumPresetTitle.ValueType
+ PRESET_TITLE_FOLLOW_CAM: _EnumPresetTitle.ValueType
+ PRESET_TITLE_SURF: _EnumPresetTitle.ValueType
+ PRESET_TITLE_CITY: _EnumPresetTitle.ValueType
+ PRESET_TITLE_SHAKY: _EnumPresetTitle.ValueType
+ PRESET_TITLE_CHESTY: _EnumPresetTitle.ValueType
+ PRESET_TITLE_HELMET: _EnumPresetTitle.ValueType
+ PRESET_TITLE_BITE: _EnumPresetTitle.ValueType
+ PRESET_TITLE_MTB: _EnumPresetTitle.ValueType
PRESET_TITLE_360_TIMELAPSE: _EnumPresetTitle.ValueType
- "Reserved 34 - 50 for custom presets."
+ "*\n Reserved 44 - 50 for custom presets.\n "
PRESET_TITLE_360_NIGHT_LAPSE: _EnumPresetTitle.ValueType
PRESET_TITLE_360_NIGHT_PHOTO: _EnumPresetTitle.ValueType
PRESET_TITLE_PANO_TIME_LAPSE: _EnumPresetTitle.ValueType
@@ -327,6 +388,19 @@ class _EnumPresetTitleEnumTypeWrapper(
PRESET_TITLE_FULL_FRAME: _EnumPresetTitle.ValueType
PRESET_TITLE_MAX_LENS_VIDEO: _EnumPresetTitle.ValueType
PRESET_TITLE_MAX_LENS_TIMEWARP: _EnumPresetTitle.ValueType
+ PRESET_TITLE_STANDARD_QUALITY_VIDEO: _EnumPresetTitle.ValueType
+ PRESET_TITLE_BASIC_QUALITY_VIDEO: _EnumPresetTitle.ValueType
+ PRESET_TITLE_EASY_MAX_VIDEO: _EnumPresetTitle.ValueType
+ PRESET_TITLE_EASY_MAX_PHOTO: _EnumPresetTitle.ValueType
+ PRESET_TITLE_EASY_MAX_TIMEWARP: _EnumPresetTitle.ValueType
+ PRESET_TITLE_EASY_MAX_STAR_TRAIL: _EnumPresetTitle.ValueType
+ PRESET_TITLE_EASY_MAX_LIGHT_PAINTING: _EnumPresetTitle.ValueType
+ PRESET_TITLE_EASY_MAX_LIGHT_TRAIL: _EnumPresetTitle.ValueType
+ PRESET_TITLE_MAX_STAR_TRAIL: _EnumPresetTitle.ValueType
+ PRESET_TITLE_MAX_LIGHT_PAINTING: _EnumPresetTitle.ValueType
+ PRESET_TITLE_MAX_LIGHT_TRAIL: _EnumPresetTitle.ValueType
+ PRESET_TITLE_HIGHEST_QUALITY_VIDEO: _EnumPresetTitle.ValueType
+ PRESET_TITLE_USER_DEFINED_CUSTOM_NAME: _EnumPresetTitle.ValueType
class EnumPresetTitle(_EnumPresetTitle, metaclass=_EnumPresetTitleEnumTypeWrapper): ...
@@ -364,8 +438,19 @@ PRESET_TITLE_TRAIL: EnumPresetTitle.ValueType
PRESET_TITLE_TRAVEL: EnumPresetTitle.ValueType
PRESET_TITLE_WATER: EnumPresetTitle.ValueType
PRESET_TITLE_LOOPING: EnumPresetTitle.ValueType
+PRESET_TITLE_STARS: EnumPresetTitle.ValueType
+"\nNew custom names (34 - 43)added for HERO 12\n"
+PRESET_TITLE_ACTION: EnumPresetTitle.ValueType
+PRESET_TITLE_FOLLOW_CAM: EnumPresetTitle.ValueType
+PRESET_TITLE_SURF: EnumPresetTitle.ValueType
+PRESET_TITLE_CITY: EnumPresetTitle.ValueType
+PRESET_TITLE_SHAKY: EnumPresetTitle.ValueType
+PRESET_TITLE_CHESTY: EnumPresetTitle.ValueType
+PRESET_TITLE_HELMET: EnumPresetTitle.ValueType
+PRESET_TITLE_BITE: EnumPresetTitle.ValueType
+PRESET_TITLE_MTB: EnumPresetTitle.ValueType
PRESET_TITLE_360_TIMELAPSE: EnumPresetTitle.ValueType
-"Reserved 34 - 50 for custom presets."
+"*\nReserved 44 - 50 for custom presets.\n"
PRESET_TITLE_360_NIGHT_LAPSE: EnumPresetTitle.ValueType
PRESET_TITLE_360_NIGHT_PHOTO: EnumPresetTitle.ValueType
PRESET_TITLE_PANO_TIME_LAPSE: EnumPresetTitle.ValueType
@@ -396,9 +481,30 @@ PRESET_TITLE_LIGHT_TRAIL: EnumPresetTitle.ValueType
PRESET_TITLE_FULL_FRAME: EnumPresetTitle.ValueType
PRESET_TITLE_MAX_LENS_VIDEO: EnumPresetTitle.ValueType
PRESET_TITLE_MAX_LENS_TIMEWARP: EnumPresetTitle.ValueType
+PRESET_TITLE_STANDARD_QUALITY_VIDEO: EnumPresetTitle.ValueType
+PRESET_TITLE_BASIC_QUALITY_VIDEO: EnumPresetTitle.ValueType
+PRESET_TITLE_EASY_MAX_VIDEO: EnumPresetTitle.ValueType
+PRESET_TITLE_EASY_MAX_PHOTO: EnumPresetTitle.ValueType
+PRESET_TITLE_EASY_MAX_TIMEWARP: EnumPresetTitle.ValueType
+PRESET_TITLE_EASY_MAX_STAR_TRAIL: EnumPresetTitle.ValueType
+PRESET_TITLE_EASY_MAX_LIGHT_PAINTING: EnumPresetTitle.ValueType
+PRESET_TITLE_EASY_MAX_LIGHT_TRAIL: EnumPresetTitle.ValueType
+PRESET_TITLE_MAX_STAR_TRAIL: EnumPresetTitle.ValueType
+PRESET_TITLE_MAX_LIGHT_PAINTING: EnumPresetTitle.ValueType
+PRESET_TITLE_MAX_LIGHT_TRAIL: EnumPresetTitle.ValueType
+PRESET_TITLE_HIGHEST_QUALITY_VIDEO: EnumPresetTitle.ValueType
+PRESET_TITLE_USER_DEFINED_CUSTOM_NAME: EnumPresetTitle.ValueType
global___EnumPresetTitle = EnumPresetTitle
class NotifyPresetStatus(google.protobuf.message.Message):
+ """*
+ Current Preset status
+
+ Sent either:
+ - synchronously via initial response to @ref RequestGetPresetStatus
+ - asynchronously when Preset change if registered in @rev RequestGetPresetStatus
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
PRESET_GROUP_ARRAY_FIELD_NUMBER: builtins.int
@@ -406,7 +512,7 @@ class NotifyPresetStatus(google.protobuf.message.Message):
def preset_group_array(
self,
) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PresetGroup]:
- """Array of Preset Groups"""
+ """Array of currently available Preset Groups"""
def __init__(self, *, preset_group_array: collections.abc.Iterable[global___PresetGroup] | None = ...) -> None: ...
def ClearField(
self, field_name: typing_extensions.Literal["preset_group_array", b"preset_group_array"]
@@ -415,6 +521,10 @@ class NotifyPresetStatus(google.protobuf.message.Message):
global___NotifyPresetStatus = NotifyPresetStatus
class Preset(google.protobuf.message.Message):
+ """*
+ An individual preset.
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ID_FIELD_NUMBER: builtins.int
MODE_FIELD_NUMBER: builtins.int
@@ -425,18 +535,19 @@ class Preset(google.protobuf.message.Message):
SETTING_ARRAY_FIELD_NUMBER: builtins.int
IS_MODIFIED_FIELD_NUMBER: builtins.int
IS_FIXED_FIELD_NUMBER: builtins.int
+ CUSTOM_NAME_FIELD_NUMBER: builtins.int
id: builtins.int
- "Preset ID"
+ " Preset ID"
mode: global___EnumFlatMode.ValueType
- "Preset flatmode ID"
+ " Preset flatmode ID"
title_id: global___EnumPresetTitle.ValueType
- "Preset Title ID"
+ " Preset Title ID"
title_number: builtins.int
- "Preset Title Number (e.g. 1/2/3 in Custom1, Custom2, Custom3)"
+ " Preset Title Number (e.g. 1/2/3 in Custom1, Custom2, Custom3)"
user_defined: builtins.bool
- "Is the Preset custom/user-defined?"
+ " Is the Preset custom/user-defined?"
icon: global___EnumPresetIcon.ValueType
- "Preset Icon ID"
+ " Preset Icon ID"
@property
def setting_array(
@@ -444,9 +555,11 @@ class Preset(google.protobuf.message.Message):
) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PresetSetting]:
"""Array of settings associated with this Preset"""
is_modified: builtins.bool
- "Has Preset been modified from factory"
+ " Has Preset been modified from factory defaults? (False for user-defined Presets)"
is_fixed: builtins.bool
- "Is this Preset mutable?"
+ " Is this Preset mutable?"
+ custom_name: builtins.str
+ " Custom string name given to this preset via @ref RequestCustomPresetUpdate"
def __init__(
self,
@@ -459,11 +572,14 @@ class Preset(google.protobuf.message.Message):
icon: global___EnumPresetIcon.ValueType | None = ...,
setting_array: collections.abc.Iterable[global___PresetSetting] | None = ...,
is_modified: builtins.bool | None = ...,
- is_fixed: builtins.bool | None = ...
+ is_fixed: builtins.bool | None = ...,
+ custom_name: builtins.str | None = ...
) -> None: ...
def HasField(
self,
field_name: typing_extensions.Literal[
+ "custom_name",
+ b"custom_name",
"icon",
b"icon",
"id",
@@ -485,6 +601,8 @@ class Preset(google.protobuf.message.Message):
def ClearField(
self,
field_name: typing_extensions.Literal[
+ "custom_name",
+ b"custom_name",
"icon",
b"icon",
"id",
@@ -509,23 +627,25 @@ class Preset(google.protobuf.message.Message):
global___Preset = Preset
class PresetGroup(google.protobuf.message.Message):
+ """
+ Preset Group meta information and contained Presets
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ID_FIELD_NUMBER: builtins.int
PRESET_ARRAY_FIELD_NUMBER: builtins.int
CAN_ADD_PRESET_FIELD_NUMBER: builtins.int
ICON_FIELD_NUMBER: builtins.int
id: global___EnumPresetGroup.ValueType
- "Preset Group ID"
+ " Preset Group ID"
@property
- def preset_array(
- self,
- ) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Preset]:
+ def preset_array(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Preset]:
"""Array of Presets contained in this Preset Group"""
can_add_preset: builtins.bool
- "Is there room in the group to add additional Presets?"
+ " Is there room in the group to add additional Presets?"
icon: global___EnumPresetGroupIcon.ValueType
- "The icon to display for this preset group"
+ " The icon to display for this preset group"
def __init__(
self,
@@ -536,8 +656,7 @@ class PresetGroup(google.protobuf.message.Message):
icon: global___EnumPresetGroupIcon.ValueType | None = ...
) -> None: ...
def HasField(
- self,
- field_name: typing_extensions.Literal["can_add_preset", b"can_add_preset", "icon", b"icon", "id", b"id"],
+ self, field_name: typing_extensions.Literal["can_add_preset", b"can_add_preset", "icon", b"icon", "id", b"id"]
) -> builtins.bool: ...
def ClearField(
self,
@@ -549,27 +668,29 @@ class PresetGroup(google.protobuf.message.Message):
global___PresetGroup = PresetGroup
class PresetSetting(google.protobuf.message.Message):
+ """*
+ Setting representation that comprises a @ref Preset
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ID_FIELD_NUMBER: builtins.int
VALUE_FIELD_NUMBER: builtins.int
IS_CAPTION_FIELD_NUMBER: builtins.int
id: builtins.int
- "Setting ID"
+ " Setting ID"
value: builtins.int
- "Setting value"
+ " Setting value"
is_caption: builtins.bool
- 'Does this setting appear on the Preset "pill" in the camera UI?'
+ ' Does this setting appear on the Preset "pill" in the camera UI?'
def __init__(
self, *, id: builtins.int | None = ..., value: builtins.int | None = ..., is_caption: builtins.bool | None = ...
) -> None: ...
def HasField(
- self,
- field_name: typing_extensions.Literal["id", b"id", "is_caption", b"is_caption", "value", b"value"],
+ self, field_name: typing_extensions.Literal["id", b"id", "is_caption", b"is_caption", "value", b"value"]
) -> builtins.bool: ...
def ClearField(
- self,
- field_name: typing_extensions.Literal["id", b"id", "is_caption", b"is_caption", "value", b"value"],
+ self, field_name: typing_extensions.Literal["id", b"id", "is_caption", b"is_caption", "value", b"value"]
) -> None: ...
global___PresetSetting = PresetSetting
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.py
index 6c3a98c1..95286488 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.py
@@ -1,5 +1,5 @@
# request_get_preset_status_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:29 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.pyi
index d6d8f750..6b4e0427 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/request_get_preset_status_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf messages for obtaining preset status
"""
import builtins
@@ -24,24 +24,31 @@ class _EnumRegisterPresetStatus:
V: typing_extensions.TypeAlias = ValueType
class _EnumRegisterPresetStatusEnumTypeWrapper(
- google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumRegisterPresetStatus.ValueType],
- builtins.type,
+ google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumRegisterPresetStatus.ValueType], builtins.type
):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
REGISTER_PRESET_STATUS_PRESET: _EnumRegisterPresetStatus.ValueType
- "Send notification when properties of a preset change"
+ " Send notification when properties of a preset change"
REGISTER_PRESET_STATUS_PRESET_GROUP_ARRAY: _EnumRegisterPresetStatus.ValueType
- "Send notification when properties of a preset group change"
+ " Send notification when properties of a preset group change"
class EnumRegisterPresetStatus(_EnumRegisterPresetStatus, metaclass=_EnumRegisterPresetStatusEnumTypeWrapper): ...
REGISTER_PRESET_STATUS_PRESET: EnumRegisterPresetStatus.ValueType
-"Send notification when properties of a preset change"
+" Send notification when properties of a preset change"
REGISTER_PRESET_STATUS_PRESET_GROUP_ARRAY: EnumRegisterPresetStatus.ValueType
-"Send notification when properties of a preset group change"
+" Send notification when properties of a preset group change"
global___EnumRegisterPresetStatus = EnumRegisterPresetStatus
class RequestGetPresetStatus(google.protobuf.message.Message):
+ """*
+ Get preset status (and optionally register to be notified when it changes)
+
+ Response: @ref NotifyPresetStatus sent immediately
+
+ Notification: @ref NotifyPresetStatus sent periodically as preset status changes, if registered.
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
REGISTER_PRESET_STATUS_FIELD_NUMBER: builtins.int
UNREGISTER_PRESET_STATUS_FIELD_NUMBER: builtins.int
@@ -65,10 +72,7 @@ class RequestGetPresetStatus(google.protobuf.message.Message):
def ClearField(
self,
field_name: typing_extensions.Literal[
- "register_preset_status",
- b"register_preset_status",
- "unregister_preset_status",
- b"unregister_preset_status",
+ "register_preset_status", b"register_preset_status", "unregister_preset_status", b"unregister_preset_status"
],
) -> None: ...
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.py
index 9e6f018a..872ca40a 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.py
@@ -1,5 +1,5 @@
# response_generic_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:30 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.pyi
index be7abd5a..2eaef01e 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/response_generic_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf message containing generic response to a command
"""
import builtins
@@ -43,10 +43,16 @@ RESULT_ARGUMENT_INVALID: EnumResultGeneric.ValueType
global___EnumResultGeneric = EnumResultGeneric
class ResponseGeneric(google.protobuf.message.Message):
+ """
+ Generic Response used across most response / notification messages
+
+ @ref EnumResultGeneric
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
RESULT_FIELD_NUMBER: builtins.int
result: global___EnumResultGeneric.ValueType
- "Generic pass/fail/error info"
+ " Generic pass/fail/error info"
def __init__(self, *, result: global___EnumResultGeneric.ValueType | None = ...) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["result", b"result"]) -> builtins.bool: ...
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.py
index 5892f377..f6cfc8f6 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.py
@@ -1,5 +1,5 @@
# set_camera_control_status_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:30 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.pyi
index c7aba5a7..51fd07e9 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/set_camera_control_status_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf messages for setting camera control status
"""
import builtins
@@ -22,28 +22,33 @@ class _EnumCameraControlStatus:
V: typing_extensions.TypeAlias = ValueType
class _EnumCameraControlStatusEnumTypeWrapper(
- google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumCameraControlStatus.ValueType],
- builtins.type,
+ google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_EnumCameraControlStatus.ValueType], builtins.type
):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
CAMERA_IDLE: _EnumCameraControlStatus.ValueType
CAMERA_CONTROL: _EnumCameraControlStatus.ValueType
- "Can only be set by camera, not by third-party"
+ " Can only be set by camera, not by app or third party"
CAMERA_EXTERNAL_CONTROL: _EnumCameraControlStatus.ValueType
class EnumCameraControlStatus(_EnumCameraControlStatus, metaclass=_EnumCameraControlStatusEnumTypeWrapper): ...
CAMERA_IDLE: EnumCameraControlStatus.ValueType
CAMERA_CONTROL: EnumCameraControlStatus.ValueType
-"Can only be set by camera, not by third-party"
+" Can only be set by camera, not by app or third party"
CAMERA_EXTERNAL_CONTROL: EnumCameraControlStatus.ValueType
global___EnumCameraControlStatus = EnumCameraControlStatus
class RequestSetCameraControlStatus(google.protobuf.message.Message):
+ """*
+ Set Camera Control Status (as part of Global Behaviors feature)
+
+ Response: @ref ResponseGeneric
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
CAMERA_CONTROL_STATUS_FIELD_NUMBER: builtins.int
camera_control_status: global___EnumCameraControlStatus.ValueType
- "Declare who is taking control of the camera"
+ " Declare who is taking control of the camera"
def __init__(self, *, camera_control_status: global___EnumCameraControlStatus.ValueType | None = ...) -> None: ...
def HasField(
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.py b/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.py
index d8e0191f..63ec47aa 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.py
@@ -1,5 +1,5 @@
# turbo_transfer_pb2.py/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro).
-# This copyright was auto-generated on Mon Jul 31 17:04:07 UTC 2023
+# This copyright was auto-generated on Sat Nov 4 21:02:29 UTC 2023
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.pyi b/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.pyi
index 6656135d..1ee284be 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.pyi
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/proto/turbo_transfer_pb2.pyi
@@ -1,7 +1,7 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
-
+*
Defines the structure of protobuf messages for enabling and disabling Turbo Transfer feature
"""
import builtins
@@ -16,10 +16,16 @@ else:
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
class RequestSetTurboActive(google.protobuf.message.Message):
+ """*
+ Enable/disable display of "Transferring Media" UI
+
+ Response: @ref ResponseGeneric
+ """
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ACTIVE_FIELD_NUMBER: builtins.int
active: builtins.bool
- "Enable or disable Turbo Transfer feature"
+ " Enable or disable Turbo Transfer feature"
def __init__(self, *, active: builtins.bool | None = ...) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["active", b"active"]) -> builtins.bool: ...
diff --git a/demos/python/sdk_wireless_camera_control/pyproject.toml b/demos/python/sdk_wireless_camera_control/pyproject.toml
index dfc1b184..79be32f1 100644
--- a/demos/python/sdk_wireless_camera_control/pyproject.toml
+++ b/demos/python/sdk_wireless_camera_control/pyproject.toml
@@ -31,6 +31,7 @@ gopro-webcam = "open_gopro.demos.gui.webcam:entrypoint"
gopro-livestream = "open_gopro.demos.gui.livestream:entrypoint"
gopro-preview-stream = "open_gopro.demos.gui.preview_stream:entrypoint"
gopro-gui = "open_gopro.demos.gui.gui_demo:entrypoint"
+gopro-cohn = "open_gopro.demos.cohn:entrypoint"
[build-system]
requires = ["poetry-core>=1.0.0"]
@@ -49,13 +50,12 @@ pexpect = "^4"
zeroconf = "^0"
pydantic = "^1"
opencv-python = { version = "^4", optional = true }
-tk = { version= "^0.1", optional = true }
Pillow = {version= "^9", optional = true}
pytz = "^2023.3"
tzlocal = "^5.0.1"
[tool.poetry.extras]
-gui = ["opencv-python", "tk", "pillow"]
+gui = ["opencv-python", "pillow"]
[tool.poetry.group.dev.dependencies]
pydocstyle = { extras = ["toml"], version = "^6" }
@@ -85,8 +85,8 @@ pytest-timeout = "^2"
isort = "^5"
protoletariat = "^3"
-[tool.poe.tasks.unit_tests]
-cmd = "pytest tests/unit --cov-fail-under=70"
+[tool.poe.tasks.tests]
+cmd = "pytest tests --cov-fail-under=70"
help = "Run unit tests"
[tool.poe.tasks.types]
@@ -158,7 +158,7 @@ sequence = ["clean_artifacts", "clean_tests", "clean_docs", "clean_build"]
help = "Clean everything"
[tool.poe.tasks.all]
-sequence = ["format", "types", "lint", "unit_tests", "docs"]
+sequence = ["format", "types", "lint", "tests", "docs"]
help = "Format, check types, lint, check docstrings, and run unit tests"
[tool.mypy]
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_ble_commands.py b/demos/python/sdk_wireless_camera_control/tests/test_ble_commands.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_ble_commands.py
rename to demos/python/sdk_wireless_camera_control/tests/test_ble_commands.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_bleak_wrapper.py b/demos/python/sdk_wireless_camera_control/tests/test_bleak_wrapper.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_bleak_wrapper.py
rename to demos/python/sdk_wireless_camera_control/tests/test_bleak_wrapper.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_enums.py b/demos/python/sdk_wireless_camera_control/tests/test_enums.py
similarity index 93%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_enums.py
rename to demos/python/sdk_wireless_camera_control/tests/test_enums.py
index 36263a1b..94fd9096 100644
--- a/demos/python/sdk_wireless_camera_control/tests/unit/test_enums.py
+++ b/demos/python/sdk_wireless_camera_control/tests/test_enums.py
@@ -6,10 +6,10 @@
import enum
from open_gopro import proto
-from open_gopro.enum import GoProEnum, enum_factory
+from open_gopro.enum import GoProIntEnum, enum_factory
-class EnumTest(GoProEnum):
+class EnumTest(GoProIntEnum):
RESULT_SUCCESS = 1
TWO = 2
NOT_APPLICABLE = 3
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_gopro_ble.py b/demos/python/sdk_wireless_camera_control/tests/test_gopro_ble.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_gopro_ble.py
rename to demos/python/sdk_wireless_camera_control/tests/test_gopro_ble.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_gopro_wifi.py b/demos/python/sdk_wireless_camera_control/tests/test_gopro_wifi.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_gopro_wifi.py
rename to demos/python/sdk_wireless_camera_control/tests/test_gopro_wifi.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_http_commands.py b/demos/python/sdk_wireless_camera_control/tests/test_http_commands.py
similarity index 87%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_http_commands.py
rename to demos/python/sdk_wireless_camera_control/tests/test_http_commands.py
index c1a52169..6f10c44f 100644
--- a/demos/python/sdk_wireless_camera_control/tests/unit/test_http_commands.py
+++ b/demos/python/sdk_wireless_camera_control/tests/test_http_commands.py
@@ -53,6 +53,12 @@ async def test_with_multiple_params(mock_wifi_communicator: GoProBase):
assert response.url == f"gopro/media/hilight/file?path={camera_file}&ms=2500"
+@pytest.mark.asyncio
+async def test_string_arg(mock_wifi_communicator: GoProBase):
+ response = await mock_wifi_communicator.http_command.webcam_start(protocol=Params.WebcamProtocol.RTSP)
+ assert response.url == f"gopro/webcam/start?protocol=RTSP"
+
+
def test_ensure_no_positional_args(mock_wifi_communicator: GoProBase):
for command in mock_wifi_communicator.http_command.values():
if inspect.getfullargspec(command).args != ["self"]:
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_models.py b/demos/python/sdk_wireless_camera_control/tests/test_models.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_models.py
rename to demos/python/sdk_wireless_camera_control/tests/test_models.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_parsers.py b/demos/python/sdk_wireless_camera_control/tests/test_parsers.py
similarity index 82%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_parsers.py
rename to demos/python/sdk_wireless_camera_control/tests/test_parsers.py
index 6b74da01..188c92f5 100644
--- a/demos/python/sdk_wireless_camera_control/tests/unit/test_parsers.py
+++ b/demos/python/sdk_wireless_camera_control/tests/test_parsers.py
@@ -9,7 +9,7 @@
from open_gopro.constants import CmdId
from open_gopro.models.response import GlobalParsers
from open_gopro.parser_interface import Parser
-from open_gopro.proto import EnumResultGeneric, ResponseGetApEntries, ScanEntry
+from open_gopro.proto import EnumResultGeneric, ResponseGetApEntries
def test_version_response(mock_ble_communicator: GoProBle):
@@ -21,8 +21,12 @@ def test_version_response(mock_ble_communicator: GoProBle):
def test_recursive_protobuf_proxying():
- scan1 = ScanEntry(ssid="one", signal_strength_bars=0, signal_frequency_mhz=0, scan_entry_flags=0)
- scan2 = ScanEntry(ssid="two", signal_strength_bars=0, signal_frequency_mhz=0, scan_entry_flags=0)
+ scan1 = ResponseGetApEntries.ScanEntry(
+ ssid="one", signal_strength_bars=0, signal_frequency_mhz=0, scan_entry_flags=0
+ )
+ scan2 = ResponseGetApEntries.ScanEntry(
+ ssid="two", signal_strength_bars=0, signal_frequency_mhz=0, scan_entry_flags=0
+ )
response = ResponseGetApEntries(result=EnumResultGeneric.RESULT_SUCCESS, scan_id=1, entries=[scan1, scan2])
raw = response.SerializeToString()
parser = Parser[ResponseGetApEntries](byte_json_adapter=ByteParserBuilders.Protobuf(ResponseGetApEntries))
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_responses.py b/demos/python/sdk_wireless_camera_control/tests/test_responses.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_responses.py
rename to demos/python/sdk_wireless_camera_control/tests/test_responses.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_services.py b/demos/python/sdk_wireless_camera_control/tests/test_services.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_services.py
rename to demos/python/sdk_wireless_camera_control/tests/test_services.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_wifi_adapter.py b/demos/python/sdk_wireless_camera_control/tests/test_wifi_adapter.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_wifi_adapter.py
rename to demos/python/sdk_wireless_camera_control/tests/test_wifi_adapter.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_wired_gopro.py b/demos/python/sdk_wireless_camera_control/tests/test_wired_gopro.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_wired_gopro.py
rename to demos/python/sdk_wireless_camera_control/tests/test_wired_gopro.py
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_wireless_gopro.py b/demos/python/sdk_wireless_camera_control/tests/test_wireless_gopro.py
similarity index 100%
rename from demos/python/sdk_wireless_camera_control/tests/unit/test_wireless_gopro.py
rename to demos/python/sdk_wireless_camera_control/tests/test_wireless_gopro.py
diff --git a/docs/specs/ble_versions/ble_2_0.md b/docs/specs/ble_versions/ble_2_0.md
index 9a59adc1..78db4128 100644
--- a/docs/specs/ble_versions/ble_2_0.md
+++ b/docs/specs/ble_versions/ble_2_0.md
@@ -4537,7 +4537,7 @@ Below is a table of supported status IDs.
36 |
Num group photos |
- How many group photos can be taken with current settings before sdcard is full |
+ Total number of group photos on sdcard |
integer |
* |
✔ |
@@ -5326,14 +5326,14 @@ For additional details, see Services and
Command |
0xF1 |
- 0x69, 0x6B, 0x79, 0xE9, 0xEB, 0xF9 |
+ 0x65, 0x66, 0x67, 0x69, 0x6B, 0x79, 0xE5, 0xE6, 0xE7, 0xE9, 0xEB, 0xF9 |
GP-0072 |
GP-0073 |
Query |
0xF5 |
- 0x72, 0x74, 0xF2, 0xF3, 0xF4, 0xF5 |
+ 0x6E, 0x6F, 0x72, 0x74, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5 |
GP-0076 |
GP-0077 |
@@ -5458,7 +5458,43 @@ For consistency, best practice is to always serialize the protobuf objects regar
✔ |
- 0xF1 |
+ 0xF1 |
+ 0x65 |
+ 0xE5 |
+ Request cohn setting |
+ RequestSetCOHNSetting |
+ ResponseGeneric |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
+ 0x66 |
+ 0xE6 |
+ Request clear cohn cert |
+ RequestClearCOHNCert |
+ ResponseGeneric |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
+ 0x67 |
+ 0xE7 |
+ Request create cohn cert |
+ RequestCreateCOHNCert |
+ ResponseGeneric |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
0x69 |
0xE9 |
Request set camera control status |
@@ -5495,7 +5531,43 @@ For consistency, best practice is to always serialize the protobuf objects regar
✔ |
- 0xF5 |
+ 0xF5 |
+ 0x6E |
+ 0xEE |
+ Request get cohn cert |
+ RequestCOHNCert |
+ ResponseCOHNCert |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
+ 0x6F |
+ 0xEF |
+ Request cohn status |
+ RequestGetCOHNStatus |
+ NotifyCOHNStatus |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
+ |
+ 0xEF |
+ Async status update |
+ |
+ NotifyCOHNStatus |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
0x72 |
0xF2 |
Request get preset status |
@@ -6126,3 +6198,151 @@ Command and enum details are available in Protobuf
+
+
+## Camera On the Home Network (COHN)
+
+Some cameras support Camera On the Home Network (COHN).
+This capability allows the client to perform command and control with the camera indirectly through an access point such as a router at home.
+For security purposes, all communications are performed over HTTPS.
+
+
+
+In order to use the COHN capability, the camera must first be provisioned for COHN.
+At a high level, the provisioning process is as follows:
+
+- Create the COHN certificate
+- Get the COHN certificate
+- Get Basic auth credentials
+- Connect the camera to an access point
+
+
+
+### HTTPS and SSL/TLS Certificates
+
+Secure communication with the camera over HTTPS requires two things: A trusted SSL/TLS certificate and Basic auth username/password used in the HTTPS header.
+
+
+#### SSL/TLS Certificate
+
+A provisioned camera has two certificates:
+
+- A Root CA cert provided to the client, which has a 1 year lifespan
+- A Camera cert, which contains the camera's current IP address on the local network and is signed by the Root CA cert
+
+
+
+
+This use of a certificate chain allows the camera's IP address to change
+(e.g. when DHCP lease expires or when access point is reset/replaced) without the client needing to download and install/trust a new certificate.
+
+
+### Provision COHN
+
+In order to use COHN for the first time or deauthenticate existing users,
+the client should clear and then create the COHN cert, get COHN status for basic authorization (username/password) and
+then connect the camera to an access point.
+
+
+```plantuml!
+
+
+title (Re-)Provision COHN
+
+actor client
+participant "GP-0072\n(Command)" as command
+participant "GP-0073\n(Command Response)" as command_response
+participant "GP-0076\n(Query)" as query
+participant "GP-0077\n(Query Response)" as query_response
+
+note over client, command
+Connect camera to access point
+end note
+
+' Get COHN status to check for provision state
+client -> query : RequestGetCOHNStatus
+client <-- query_response : NotifyCOHNStatus
+note right
+Contains:
+1. COHN status
+end note
+
+' if (already provisioned) -> clear existing certs
+' else -> pass
+alt NotifyCOHNStatus.status == EnumCOHNState.COHN_PROVISIONED
+ client -> command : RequestClearCOHNCert
+ note right
+ 1. Camera disconnects from access point
+ 2. Root CA and Camera certs are deleted
+ end note
+ client <-- command_response : ResponseGeneric
+else NotifyCOHNStatus.status == EnumCOHNState.COHN_UNPROVISIONED
+ client --> client : no-op
+end
+
+' Create COHN Cert
+client -> command : RequestCreateCOHNCert
+note right
+Camera creates Root CA cert
+
+If connected to access point, camera will:
+1. Create Camera cert
+2. Sign Camera cert with Root CA cert
+end note
+client <-- command_response : ResponseGeneric
+
+' Get COHN Cert
+client -> query : RequestCOHNCert
+client <-- query_response : ResponseCOHNCert
+note right
+Contains:
+1. Generic result
+2. Root CA cert
+end note
+client -> client : Store cert for future use
+
+' Get COHN status for Basic auth username/password
+client -> query : RequestGetCOHNStatus
+client <-- query_response : NotifyCOHNStatus
+note right
+Contains:
+1. Camera IP address on local network
+2. Basic auth username/password for HTTPS
+end note
+
+
+
+
+```
+
+### Verifying COHN Cert is Valid
+
+The camera acts as the Root Certificate Authority in creating the COHN certificate (Root CA cert).
+Clients can verify that the certificate is valid using utilities such as openssl:
+
+
+
+Example:
+
+```
+$ openssl verify -CAfile '/path/to/GoProRootCA.crt' '/path/to/GoProRootCA.crt'
+GoProRootCA.crt: OK
+```
+
+### View COHN Cert Details
+
+Most operating systems have utilities to view details about a SSL/TLS certificate:
+
+- MacOS: Right-mouse-click >> Quick Look
+- Windows: Right-mouse-click >> Properties
+- Ubuntu: Right-mouse-click >> Open with View File
+- Commandline:
openssl x509 -in /path/to/GoProRootCA.crt -noout -text
+
+
+
+### Communicate via COHN
+
+Once the camera is provisioned, the client can communicate with the camera via HTTPS.
+The camera supports nearly all functionality over HTTPS that it does over HTTP.
+For more details about HTTP/HTTPS, see the Open GoPro HTTP spec.
+
diff --git a/docs/specs/capabilities.xlsx b/docs/specs/capabilities.xlsx
index 8f72d835..bd36a05e 100644
--- a/docs/specs/capabilities.xlsx
+++ b/docs/specs/capabilities.xlsx
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e945fd0a85ea62dbe5ad37f7872a2af63cd958e58937f56d6feca688fcad8126
-size 103251
+oid sha256:8c3280d6b64629e34eba7947731c9cb4a06ffbe7a858c83d0ac9fb9d7d30c743
+size 103250
diff --git a/docs/specs/http_versions/http_2_0.md b/docs/specs/http_versions/http_2_0.md
index 2534074a..42cac6ab 100644
--- a/docs/specs/http_versions/http_2_0.md
+++ b/docs/specs/http_versions/http_2_0.md
@@ -302,6 +302,39 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
\>= v01.70.00 |
+
+ COHN: Get cert |
+ Get cohn cert |
+ GET |
+ /GoProRootCA.crt |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
+ COHN: Get status |
+ Get cohn status |
+ POST |
+ /gopro/cohn/status |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
+ COHN: Get status |
+ Get cohn status |
+ POST |
+ /gopro/cohn/status |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
Camera: Get State |
Get camera state (status + settings) |
@@ -336,6 +369,17 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.70.00 |
+ Get Hardware Info |
+ Get camera hardware info |
+ GET |
+ /gopro/camera/info |
+ ✔ |
+ ❌ |
+ ❌ |
+ ❌ |
+ ❌ |
+
+
Keep-alive |
Send keep-alive |
GET |
@@ -346,7 +390,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: GPMF |
Get GPMF data (JPG) |
GET |
@@ -357,7 +401,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: GPMF |
Get GPMF data (MP4) |
GET |
@@ -368,7 +412,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: HiLight (Add) |
Add hilight to 100GOPRO/xxx.JPG |
GET |
@@ -379,7 +423,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
\>= v01.70.00 |
-
+
Media: HiLight (Add) |
Add hilight to 100GOPRO/xxx.MP4 at offset 2500 ms |
GET |
@@ -390,7 +434,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
\>= v01.70.00 |
-
+
Media: HiLight (Remove) |
Remove hilight from 100GOPRO/xxx.JPG |
GET |
@@ -401,7 +445,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
\>= v01.70.00 |
-
+
Media: HiLight (Remove) |
Remove hilight from 100GOPRO/xxx.MP4 at offset 2500ms |
GET |
@@ -412,7 +456,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
\>= v01.70.00 |
-
+
Media: HiLight Moment |
Hilight moment during encoding |
GET |
@@ -423,7 +467,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
❌ |
-
+
Media: Info |
Get media info (JPG) |
GET |
@@ -434,7 +478,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Info |
Get media info (MP4) |
GET |
@@ -445,7 +489,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: List |
Get media list |
GET |
@@ -456,7 +500,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Screennail |
Get screennail for "100GOPRO/xxx.JPG" |
GET |
@@ -467,7 +511,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Screennail |
Get screennail for "100GOPRO/xxx.MP4" |
GET |
@@ -478,7 +522,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Telemetry |
Get telemetry track data (JPG) |
GET |
@@ -489,7 +533,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Telemetry |
Get telemetry track data (MP4) |
GET |
@@ -500,7 +544,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Thumbnail |
Get thumbnail for "100GOPRO/xxx.JPG" |
GET |
@@ -511,7 +555,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Thumbnail |
Get thumbnail for "100GOPRO/xxx.MP4" |
GET |
@@ -522,7 +566,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Turbo Transfer |
Turbo transfer: off |
GET |
@@ -533,7 +577,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Media: Turbo Transfer |
Turbo transfer: on |
GET |
@@ -544,7 +588,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
OTA Update |
Soft update: upload 12345 bytes starting at offset 67890 |
POST |
@@ -555,7 +599,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
OTA Update |
Soft update: mark upload complete |
POST |
@@ -566,7 +610,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Open GoPro |
Get version |
GET |
@@ -577,7 +621,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Presets: Get Status |
Get preset status |
GET |
@@ -588,7 +632,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Presets: Load |
Example preset id: 0x1234ABCD |
GET |
@@ -599,7 +643,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Presets: Load Group |
Video |
GET |
@@ -610,7 +654,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Presets: Load Group |
Photo |
GET |
@@ -621,7 +665,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Presets: Load Group |
Timelapse |
GET |
@@ -632,7 +676,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Set Camera Control Status |
Set camera control status to idle |
GET |
@@ -643,7 +687,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.20.00 |
❌ |
-
+
Set Camera Control Status |
Set camera control status to external_control |
GET |
@@ -654,7 +698,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.20.00 |
❌ |
-
+
Set Date/Time |
Set date/time to 2023-01-31 03:04:05 |
GET |
@@ -665,7 +709,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
\>= v01.70.00 |
-
+
Set Local Date/Time |
Set local date/time to: 2023-01-31 03:04:05 (utc-02:00) (dst: on) |
GET |
@@ -676,7 +720,7 @@ Below is a table of commands that can be sent to the camera and how to send them
❌ |
❌ |
-
+
Set shutter |
Shutter: on |
GET |
@@ -687,7 +731,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Set shutter |
Shutter: off |
GET |
@@ -698,7 +742,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Simple OTA Update |
Simple ota update with file: update.zip |
POST |
@@ -709,7 +753,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Soft Update |
Soft update: show canceled/failed ui on the camera |
GET |
@@ -720,7 +764,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Soft Update |
Soft update: delete cached update files |
GET |
@@ -731,7 +775,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Soft Update |
Soft update: get current update state |
GET |
@@ -742,7 +786,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Soft Update |
Soft update: display update ui on camera |
GET |
@@ -753,7 +797,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Soft Update |
Soft update: initiate firmware update |
GET |
@@ -764,7 +808,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Stream: Start |
Start preview stream |
GET |
@@ -775,7 +819,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Stream: Stop |
Stop preview stream |
GET |
@@ -786,7 +830,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
✔ |
-
+
Webcam: Exit |
Exit webcam mode |
GET |
@@ -797,7 +841,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Webcam: Preview |
Start preview stream |
GET |
@@ -808,7 +852,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Webcam: Start |
Start webcam |
GET |
@@ -819,9 +863,9 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.40.00 |
❌ |
-
+
Webcam: Start |
- Start webcam |
+ Start webcam (port: 12345) |
GET |
/gopro/webcam/start?port=12345 |
✔ |
@@ -830,7 +874,29 @@ Below is a table of commands that can be sent to the camera and how to send them
❌ |
❌ |
-
+
+ Webcam: Start |
+ Start webcam (port: 12345, protocol: rtsp) |
+ GET |
+ /gopro/webcam/start?port=12345&protocol=RTSP |
+ ✔ |
+ ❌ |
+ ✔ |
+ ✔ |
+ ❌ |
+
+
+ Webcam: Start |
+ Start webcam (port: 12345, protocol: ts) |
+ GET |
+ /gopro/webcam/start?port=12345&protocol=TS |
+ ✔ |
+ ❌ |
+ ✔ |
+ ✔ |
+ ❌ |
+
+
Webcam: Start |
Start webcam (res: resolution_1080, fov: wide) |
GET |
@@ -841,7 +907,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Webcam: Status |
Get webcam status |
GET |
@@ -852,7 +918,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Webcam: Stop |
Stop webcam |
GET |
@@ -863,7 +929,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Webcam: Version |
Get webcam api version |
GET |
@@ -874,7 +940,7 @@ Below is a table of commands that can be sent to the camera and how to send them
✔ |
❌ |
-
+
Wired USB Control |
Disable wired usb control |
GET |
@@ -885,7 +951,7 @@ Below is a table of commands that can be sent to the camera and how to send them
\>= v01.30.00 |
❌ |
-
+
Wired USB Control |
Enable wired usb control |
GET |
@@ -4660,7 +4726,7 @@ Below is a table of supported status IDs.
36 |
Num group photos |
- How many group photos can be taken with current settings before sdcard is full |
+ Total number of group photos on sdcard |
integer |
* |
✔ |
@@ -5800,14 +5866,33 @@ end
## Webcam
-The webcam feature enables developers who are interested in writing custom drivers to make the camera broadcast its
-video preview with a limited set of resolution and field of view options.
+The webcam feature enables developers who are interested in writing custom drivers to broadcast the camera's video preview with a limited set of resolution, field of view, port, and protocol options.
-While active, the webcam feature runs a UDP client that sends raw Transport Stream data to the connected client on port 8554.
-To test basic functionality, connect the camera to your system, start the webcam, and use an application such as VLC to
-start a network stream on udp://@0.0.0.0:8554.
+While active, the webcam feature sends raw data to the connected client using a supported protocol.
+To enable multi-cam support, some cameras support running on a user-specified port.
+Protocol and port details are provided in a table below.
+
+
+
+To test basic functionality, start the webcam, and use an application such as VLC to open a network stream:
+
+
+
+ Protocol |
+ VLC Network URL |
+
+
+ TS |
+ udp://@:{PORT} |
+
+
+ RTSP |
+ rtsp://{CAMERA_IP}:554/live |
+
+
+
@@ -5846,7 +5931,7 @@ LPP --> READY: Stop\nExit
### Webcam Commands
-Note: For USB connections, prior to issuing webcam commands, Wired USB Control must be disabled.
+Note: For USB connections, prior to issuing webcam commands, Wired USB Control should be disabled.
For details about how to send this and webcam commands, see Commands Quick Reference.
@@ -5864,7 +5949,7 @@ For details about how to send this and webcam commands, see known is
The best workaround for this is to call Webcam: Start followed by the Webcam: Stop after connecting USB in order to attain the true IDLE state.
-
-Note: If resolution is not set, 1080p will be used by default.
-If fov is not set, camera will default to the last-set fov or Wide if fov has never been set.
-
+#### Default Parameter Values
+
+
+
+ Parameter |
+ Default Value |
+
+
+ res |
+ 12 (1080p) |
+
+
+ fov |
+ Last-used or 0 (Wide) if FOV not previously set |
+
+
+ protocol |
+ "TS" |
+
+
+
+#### Webcam Capabilities
@@ -6038,6 +6141,47 @@ If fov is not set, camera will default to the last-set fov or Wide if fov has ne
+#### Supported Protocols
+
+
+
+ Camera |
+ Protocol |
+ Default Port |
+ Supports User-Defined Port? |
+
+
+ HERO12 Black |
+ TS |
+ 8554 |
+ ✔ |
+
+
+ RTSP |
+ 554 |
+ ❌ |
+
+
+ HERO11 Black |
+ TS |
+ 8554 |
+ ✔ |
+
+
+ HERO10 Black |
+ TS |
+ 8554 |
+ ❌ |
+
+
+ HERO9 Black |
+ TS |
+ 8554 |
+ ❌ |
+
+
+
+
### Webcam Stabilization
@@ -6084,6 +6228,91 @@ Note: The Low Hypersmooth option provides lower/lighter stabilization whe
+
+## Camera On the Home Network (COHN)
+
+Some cameras support Camera On the Home Network (COHN).
+This capability allows the client to perform command and control with the camera indirectly through an access point such as a router at home.
+For security purposes, all communications are performed over HTTPS.
+
+
+### Provisioning COHN
+
+In order to use the COHN capability, the camera must first be provisioned for COHN.
+For instructions on how to do this, see Open GoPro BLE spec.
+
+
+### Send Messages via HTTPS
+
+Once the camera is provisioned, the client can issue
+commands
+and set settings
+via HTTPS using the COHN certificate and Basic authorization (username/password) credentials obtained during provisioning or subsequently by querying for COHN status.
+
+
+### HTTPS Headers
+
+
+All HTTPS messages must contain Basic access authentication headers, using the username and password from the COHN status obtained during or after provisioning.
+
+
+### COHN Commands
+
+#### Command
+
+
+
+ Command |
+ Response Format |
+ Description |
+
+
+ /GoProRootCA.crt |
+ Text |
+ Get COHN cert |
+
+
+ /gopro/cohn/status |
+ JSON |
+ Get current COHN status |
+
+
+
+
+#### Get COHN Cert
+
+The /GoProRootCA.crt endpoint provides a way to obtain the COHN cert via HTTP(S).
+The response content is in plain text. For example:
+
+
+```
+-----BEGIN CERTIFICATE-----
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+-----END CERTIFICATE-----
+```
+
+#### Get COHN Status
+
+The /gopro/cohn/status endpoint provides a way to get the current status of COHN.
+The status's format is NotifyCOHNStatus (a Google Procol Buffer v2 message) converted into JSON.
+
+
+
+Example:
+
+```
+{
+ "status": "COHN_PROVISIONED",
+ "state": "COHN_STATE_NetworkConnected",
+ "username": "gopro",
+ "password": "xxxxxxxxxxxx",
+ "ipaddress": "xxx.xxx.xxx.xxx",
+ "enabled": true
+}
+```
+
# Limitations
## HERO12 Black
diff --git a/protobuf/cohn.proto b/protobuf/cohn.proto
new file mode 100644
index 00000000..718cde88
--- /dev/null
+++ b/protobuf/cohn.proto
@@ -0,0 +1,99 @@
+/* cohn.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
+
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
+
+/**
+ * Defines the structure of protobuf messages for Camera On the Home Network
+ */
+
+syntax = "proto2";
+package open_gopro;
+
+import "response_generic.proto";
+
+enum EnumCOHNStatus {
+ COHN_UNPROVISIONED = 0;
+ COHN_PROVISIONED = 1;
+}
+
+enum EnumCOHNNetworkState {
+ COHN_STATE_Init = 0;
+ COHN_STATE_Error = 1;
+ COHN_STATE_Exit = 2;
+ COHN_STATE_Idle = 5;
+ COHN_STATE_NetworkConnected = 27;
+ COHN_STATE_NetworkDisconnected = 28;
+ COHN_STATE_ConnectingToNetwork = 29;
+ COHN_STATE_Invalid = 30;
+}
+
+/**
+ * Get the current COHN status.
+ *
+ * This always returns a @ref NotifyCOHNStatus
+ *
+ * Additionally, asynchronous updates can also be registerd to return more @ref NotifyCOHNStatus when a value
+ * changes.
+ */
+message RequestGetCOHNStatus {
+ optional bool register_cohn_status = 1; // 1 to register, 0 to unregister
+}
+
+/*
+ * Current COHN status triggered by a RequestGetCOHNStatus
+ */
+message NotifyCOHNStatus {
+ optional EnumCOHNStatus status = 1; // Current COHN status
+ optional EnumCOHNNetworkState state = 2; // Current COHN network state
+ optional string username = 3; // Username used for http basic auth header
+ optional string password = 4; // Password used for http basic auth header
+ optional string ipaddress = 5; // Camera’s IP address on the local network
+ optional bool enabled = 6; // Is COHN currently enabled
+ optional string ssid = 7; // Currently connected SSID
+ optional string macaddress = 8; // MAC address of the wifi adapter
+}
+
+/**
+ * Create the COHN certificate.
+ *
+ * Returns a @ref ResponseGeneric with the status of the creation
+ */
+message RequestCreateCOHNCert {
+ optional bool override = 1; // Override current provisioning and create new cert
+}
+
+/**
+ * Clear the COHN certificate.
+ *
+ * Returns a @ref ResponseGeneric with the status of the clear
+ */
+message RequestClearCOHNCert {}
+
+/**
+ * Get the COHN certificate.
+ *
+ * Returns a @ref ResponseCOHNCert
+ */
+message RequestCOHNCert {}
+
+/*
+ * COHN Certificate response triggered by RequestCOHNCert
+ */
+message ResponseCOHNCert {
+ optional EnumResultGeneric result = 1; // Was request successful?
+ optional string cert = 2; // Root CA cert (ASCII text)
+}
+
+/**
+ * Enable and disable COHN if provisioned
+ *
+ * Returns a @ref ResponseGeneric
+ */
+message RequestSetCOHNSetting {
+ optional bool cohn_active = 1; // 1 to enable, 0 to disable
+}
diff --git a/protobuf/live_streaming.proto b/protobuf/live_streaming.proto
index c0837434..b486843c 100644
--- a/protobuf/live_streaming.proto
+++ b/protobuf/live_streaming.proto
@@ -1,13 +1,18 @@
/* live_streaming.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Wed Jul 5 19:32:05 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf messages for working with Live Streams
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
-syntax="proto2";
-package open_gopro;
+/**
+ * Defines the structure of protobuf messages for working with Live Streams
+ */
+syntax = "proto2";
+package open_gopro;
enum EnumLens {
LENS_WIDE = 0;
@@ -16,30 +21,32 @@ enum EnumLens {
}
enum EnumLiveStreamError {
- LIVE_STREAM_ERROR_NONE = 0; // No error (success)
- LIVE_STREAM_ERROR_NETWORK = 1; // General network error during the stream
- LIVE_STREAM_ERROR_CREATESTREAM = 2; // Startup error: bad URL or valid with live stream server
- LIVE_STREAM_ERROR_OUTOFMEMORY = 3; // Not enough memory on camera to complete task
- LIVE_STREAM_ERROR_INPUTSTREAM = 4; // Failed to get stream from low level camera system
- LIVE_STREAM_ERROR_INTERNET = 5; // No internet access detected on startup of streamer
- LIVE_STREAM_ERROR_OSNETWORK = 6; // Error occured in linux networking stack. usually means the server closed the connection
- LIVE_STREAM_ERROR_SELECTEDNETWORKTIMEOUT = 7; // Timed out attemping to connect to the wifi network when attemping live stream
- LIVE_STREAM_ERROR_SSL_HANDSHAKE = 8; // SSL handshake failed (commonly caused due to incorrect time / time zone)
- LIVE_STREAM_ERROR_CAMERA_BLOCKED = 9; // Low level camera system rejected attempt to start live stream
- LIVE_STREAM_ERROR_UNKNOWN = 10; // Unknown
- LIVE_STREAM_ERROR_SD_CARD_FULL = 40; // Can not perform livestream because sd card is full
- LIVE_STREAM_ERROR_SD_CARD_REMOVED = 41; // Livestream stopped because sd card was removed
+ LIVE_STREAM_ERROR_NONE = 0; // No error (success)
+ LIVE_STREAM_ERROR_NETWORK = 1; // General network error during the stream
+ LIVE_STREAM_ERROR_CREATESTREAM = 2; // Startup error: bad URL or valid with live stream server
+ LIVE_STREAM_ERROR_OUTOFMEMORY = 3; // Not enough memory on camera to complete task
+ LIVE_STREAM_ERROR_INPUTSTREAM = 4; // Failed to get stream from low level camera system
+ LIVE_STREAM_ERROR_INTERNET = 5; // No internet access detected on startup of streamer
+ LIVE_STREAM_ERROR_OSNETWORK = 6; // Error occured in linux networking stack. usually means the server closed the connection
+ LIVE_STREAM_ERROR_SELECTEDNETWORKTIMEOUT = 7; // Timed out attemping to connect to the wifi network when attemping live stream
+ LIVE_STREAM_ERROR_SSL_HANDSHAKE = 8; // SSL handshake failed (commonly caused due to incorrect time / time zone)
+ LIVE_STREAM_ERROR_CAMERA_BLOCKED = 9; // Low level camera system rejected attempt to start live stream
+ LIVE_STREAM_ERROR_UNKNOWN = 10; // Unknown
+ LIVE_STREAM_ERROR_SD_CARD_FULL = 40; // Can not perform livestream because sd card is full
+ LIVE_STREAM_ERROR_SD_CARD_REMOVED = 41; // Livestream stopped because sd card was removed
}
enum EnumLiveStreamStatus {
- LIVE_STREAM_STATE_IDLE = 0; // Initial status. Livestream has not yet been configured
- LIVE_STREAM_STATE_CONFIG = 1; // Livestream is being configured
- /* Livestream has finished configuration and is ready to start streaming */
+ LIVE_STREAM_STATE_IDLE = 0; // Initial status. Livestream has not yet been configured
+ LIVE_STREAM_STATE_CONFIG = 1; // Livestream is being configured
+ /*
+ * Livestream has finished configuration and is ready to start streaming
+ */
LIVE_STREAM_STATE_READY = 2;
- LIVE_STREAM_STATE_STREAMING = 3; // Livestream is actively streaming
- LIVE_STREAM_STATE_COMPLETE_STAY_ON = 4; // Live stream is exiting. No errors occured.
- LIVE_STREAM_STATE_FAILED_STAY_ON = 5; // Live stream is exiting. An error occurred.
- LIVE_STREAM_STATE_RECONNECTING = 6; // An error occurred during livestream and stream is attempting to reconnect.
+ LIVE_STREAM_STATE_STREAMING = 3; // Livestream is actively streaming
+ LIVE_STREAM_STATE_COMPLETE_STAY_ON = 4; // Live stream is exiting. No errors occured.
+ LIVE_STREAM_STATE_FAILED_STAY_ON = 5; // Live stream is exiting. An error occurred.
+ LIVE_STREAM_STATE_RECONNECTING = 6; // An error occurred during livestream and stream is attempting to reconnect.
}
enum EnumRegisterLiveStreamStatus {
@@ -55,37 +62,58 @@ enum EnumWindowSize {
WINDOW_SIZE_1080 = 12;
}
+/**
+ * Live Stream status
+ *
+ * Sent either:
+ * - as a syncrhonous response to initial @ref RequestGetLiveStreamStatus
+ * - as asynchronous notifications registered for via @ref RequestGetLiveStreamStatus
+ */
message NotifyLiveStreamStatus {
- optional EnumLiveStreamStatus live_stream_status = 1; // Live stream status
- optional EnumLiveStreamError live_stream_error = 2; // Live stream error
- optional bool live_stream_encode = 3; // Is live stream encoding?
- optional int32 live_stream_bitrate = 4; // Live stream bitrate (Kbps)
- repeated EnumWindowSize live_stream_window_size_supported_array = 5; // Live stream resolution capabilities
- optional bool live_stream_encode_supported = 6; // Does the camera support encoding while live streaming?
- optional bool live_stream_max_lens_unsupported = 7; // Is the Max Lens feature NOT supported?
- optional int32 live_stream_minimum_stream_bitrate = 8; // Camera-defined minimum bitrate (static) (Kbps)
- optional int32 live_stream_maximum_stream_bitrate = 9; // Camera-defined maximum bitrate (static) (Kbps)
- optional bool live_stream_lens_supported = 10; // Does camera support setting lens for live streaming?
- repeated EnumLens live_stream_lens_supported_array = 11; // Array of supported lenses for live streaming
- optional bool deprecated = 12; // Deprecated
+ optional EnumLiveStreamStatus live_stream_status = 1; // Live stream status
+ optional EnumLiveStreamError live_stream_error = 2; // Live stream error
+ optional bool live_stream_encode = 3; // Is live stream encoding?
+ optional int32 live_stream_bitrate = 4; // Live stream bitrate (Kbps)
+ /*
+ * List of supported resolutions returned when live stream is registered or requested
+ *
+ * 1. register --> camera
+ * 2. register response (with capabilities) --> mobile
+ * 3. async notifications (without capabilities) --> mobile
+ */
+ repeated EnumWindowSize live_stream_window_size_supported_array = 5;
+ optional bool live_stream_encode_supported = 6; // Does the camera support encoding while live streaming?
+ optional bool live_stream_max_lens_unsupported = 7; // Is the Max Lens feature NOT supported?
+ optional int32 live_stream_minimum_stream_bitrate = 8; // Camera-defined minimum bitrate (static) (Kbps)
+ optional int32 live_stream_maximum_stream_bitrate = 9; // Camera-defined maximum bitrate (static) (Kbps)
+ optional bool live_stream_lens_supported = 10; // Does camera support setting lens for live streaming?
+ repeated EnumLens live_stream_lens_supported_array = 11; // Array of supported lenses for live streaming
}
+/**
+ * Get the current livestream status (and optionally register for future status changes)
+ *
+ * Both current status and future status changes are sent via @ref NotifyLiveStreamStatus
+ */
message RequestGetLiveStreamStatus {
- repeated EnumRegisterLiveStreamStatus register_live_stream_status = 1; // Array of live stream statuses to be notified about
- repeated EnumRegisterLiveStreamStatus unregister_live_stream_status = 2; // Array of live stream statuses to stop being notified about
+ repeated EnumRegisterLiveStreamStatus register_live_stream_status = 1; // Array of live stream statuses to be notified about
+ repeated EnumRegisterLiveStreamStatus unregister_live_stream_status = 2; // Array of live stream statuses to stop being notified about
}
+/**
+ * Configure lives streaming
+ *
+ * The current livestream status can be queried via @ref RequestGetLiveStreamStatus
+ *
+ * TODO What is the response?
+ */
message RequestSetLiveStreamMode {
- optional string url = 1; // RTMP(S) URL used for live stream
- optional bool encode = 2; // Save media to sdcard while streaming?
- optional EnumWindowSize window_size = 3; // Live stream resolution
- optional string reserved1 = 4; // Reserved
- optional string reserved2 = 5; // Reserved
- optional bytes cert = 6; // Certificate for servers that require it
- optional int32 minimum_bitrate = 7; // Minimum desired bitrate (may or may not be honored)
- optional int32 maximum_bitrate = 8; // Maximum desired bitrate (may or may not be honored)
- optional int32 starting_bitrate = 9; // Starting bitrate
- optional EnumLens lens = 10; // Lens to use for live stream (see
- optional int32 reserved3 = 11; // Reserved
+ optional string url = 1; // RTMP(S) URL used for live stream
+ optional bool encode = 2; // Save media to sdcard while streaming?
+ optional EnumWindowSize window_size = 3; // Live stream resolution
+ optional bytes cert = 6; // Certificate for servers that require it
+ optional int32 minimum_bitrate = 7; // Minimum desired bitrate (may or may not be honored)
+ optional int32 maximum_bitrate = 8; // Maximum desired bitrate (may or may not be honored)
+ optional int32 starting_bitrate = 9; // Starting bitrate
+ optional EnumLens lens = 10; // Lens to use for live stream (see NotifyLiveStreamStatus.live_stream_lens_supported)
}
-
diff --git a/protobuf/network_management.proto b/protobuf/network_management.proto
index c5efc91f..67bba405 100644
--- a/protobuf/network_management.proto
+++ b/protobuf/network_management.proto
@@ -1,23 +1,20 @@
/* network_management.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Wed Jul 5 19:32:05 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf messages for network management
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
+
+/**
+ * Defines the structure of protobuf messages for network management
+ */
-syntax="proto2";
+syntax = "proto2";
package open_gopro;
-import "response_generic.proto";
-enum EnumNetworkOwner {
- DEPRECATED_1 = 0x00;
- DEPRECATED_2 = 0x01;
- DEPRECATED_3 = 0x02;
- DEPRECATED_4 = 0x03;
- DEPRECATED_5 = 0x04;
- DEPRECATED_6 = 0x08;
- DEPRECATED_7 = 0x10;
-}
+import "response_generic.proto";
enum EnumProvisioning {
PROVISIONING_UNKNOWN = 0;
@@ -43,86 +40,144 @@ enum EnumScanning {
SCANNING_SUCCESS = 5;
}
-enum EnumScanEntryFlags {
- SCAN_FLAG_OPEN = 0x00; // This network does not require authentication
- SCAN_FLAG_AUTHENTICATED = 0x01; // This network requires authentication
- SCAN_FLAG_CONFIGURED = 0x02; // This network has been previously provisioned
- SCAN_FLAG_BEST_SSID = 0x04;
- SCAN_FLAG_ASSOCIATED = 0x08; // camera is connected to this AP
- SCAN_FLAG_UNSUPPORTED_TYPE = 0x10;
- DEPRECATED = 0x20;
-}
-
+/*
+ * Provision state notification
+ *
+ * TODO refernce where this is triggered
+ */
message NotifProvisioningState {
- required EnumProvisioning provisioning_state = 1; // Provisioning/connection state
+ required EnumProvisioning provisioning_state = 1; // Provisioning / connection state
}
+/*
+ * Scanning state notification
+ *
+ * Triggered via @ref RequestStartScan
+ */
message NotifStartScanning {
- required EnumScanning scanning_state = 1; // Scanning state
- optional int32 scan_id = 2; // ID associated with scan results (included if scan was successful)
- optional int32 total_entries = 3; // Number of APs found during scan (included if scan was successful)
- required int32 total_configured_ssid = 4; // Total count of camera's provisioned SSIDs
-}
-
+ required EnumScanning scanning_state = 1; // Scanning state
+ optional int32 scan_id = 2; // ID associated with scan results (included if scan was successful)
+ optional int32 total_entries = 3; // Number of APs found during scan (included if scan was successful)
+ required int32 total_configured_ssid = 4; // Total count of camera's provisioned SSIDs
+}
+
+/**
+ * Connect to (but do not authenticate with) an Access Point
+ *
+ * This is intended to be used to connect to a previously-connected Access Point
+ *
+ * Response: @ref ResponseConnect
+ */
message RequestConnect {
- required string ssid = 1; // AP SSID
- optional EnumNetworkOwner owner_purpose = 2; // Deprecated
-}
-
+ required string ssid = 1; // AP SSID
+}
+
+/**
+ * Connect to and authenticate with an Access Point
+ *
+ * This is only intended to be used if the AP is not previously provisioned.
+ *
+ * Response: @ref ResponseConnectNew sent immediately
+ *
+ * Notification: @ref NotifProvisioningState sent periodically as provisioning state changes
+ */
message RequestConnectNew {
- required string ssid = 1; // AP SSID
- required string password = 2; // AP password
- optional bytes static_ip = 3; // Static IP address
- optional bytes gateway = 4; // Gateway IP address
- optional bytes subnet = 5; // Subnet mask
- optional bytes dns_primary = 6; // Primary DNS
- optional bytes dns_secondary = 7; // Secondary DNS
- optional bool set_to_least_preferred_ap = 8; // Deprecated
- optional EnumNetworkOwner owner_purpose = 9; // Deprecated
-}
-
+ required string ssid = 1; // AP SSID
+ required string password = 2; // AP password
+ optional bytes static_ip = 3; // Static IP address
+ optional bytes gateway = 4; // Gateway IP address
+ optional bytes subnet = 5; // Subnet mask
+ optional bytes dns_primary = 6; // Primary DNS
+ optional bytes dns_secondary = 7; // Secondary DNS
+}
+
+/**
+ * Get a list of Access Points found during a @ref RequestStartScan
+ *
+ * Response: @ref ResponseGetApEntries
+ */
message RequestGetApEntries {
- required int32 start_index = 1; // Used for paging. 0 <= start_index < NotifStartScanning.total_entries
- required int32 max_entries = 2; // Used for paging. Value must be < NotifStartScanning.total_entries
- required int32 scan_id = 3; // ID corresponding to a set of scan results (i.e. NotifStartScanning.scan_id)
-}
-
-message RequestReleaseNetwork {
-
-}
-
-message RequestStartScan {
-
-}
-
+ required int32 start_index = 1; // Used for paging. 0 <= start_index < @ref ResponseGetApEntries .total_entries
+ required int32 max_entries = 2; // Used for paging. Value must be < @ref ResponseGetApEntries .total_entries
+ required int32 scan_id = 3; // ID corresponding to a set of scan results (i.e. @ref ResponseGetApEntries .scan_id)
+}
+
+/**
+ * Request to disconnect from current AP network
+ *
+ * Response: @ref ResponseGeneric
+ */
+message RequestReleaseNetwork {}
+
+/**
+ * Start scanning for Access Points
+ *
+ * @note Serialization of this object is zero bytes.
+ *
+ * Response: @ref ResponseStartScanning are sent immediately after the camera receives this command
+ *
+ * Notifications: @ref NotifStartScanning are sent periodically as scanning state changes. Use to detect scan complete.
+ */
+message RequestStartScan {}
+
+/**
+ * The status of an attempt to connect to an Access Point
+ *
+ * Sent as the initial response to @ref RequestConnect
+ */
message ResponseConnect {
- required EnumResultGeneric result = 1; // Generic pass/fail/error info
- required EnumProvisioning provisioning_state = 2; // Provisioning/connection state
- required int32 timeout_seconds = 3; // Network connection timeout (seconds)
+ required EnumResultGeneric result = 1; // Generic pass/fail/error info
+ required EnumProvisioning provisioning_state = 2; // Provisioning/connection state
+ required int32 timeout_seconds = 3; // Network connection timeout (seconds)
}
+/**
+ * The status of an attempt to connect to an Access Point
+ *
+ * Sent as the initial response to @ref RequestConnectNew
+ */
message ResponseConnectNew {
- required EnumResultGeneric result = 1; // Status of Connect New request
- required EnumProvisioning provisioning_state = 2; // Current provisioning state of the network
+ required EnumResultGeneric result = 1; // Status of Connect New request
+ required EnumProvisioning provisioning_state = 2; // Current provisioning state of the network
+ /**
+ * number of seconds camera will wait before declaring a network connection attempt failed.
+ */
required int32 timeout_seconds = 3;
}
-message ScanEntry {
- required string ssid = 1; // AP SSID
- required int32 signal_strength_bars = 2; // Signal strength (3 bars: >-70 dBm; 2 bars: >-85 dBm; 1 bar: <=-85
- required int32 signal_frequency_mhz = 4; // Signal frequency (MHz)
- required int32 scan_entry_flags = 5; // Bitmasked value from EnumScanEntryFlags
- optional EnumNetworkOwner owner_purpose = 6; // Deprecated
+enum EnumScanEntryFlags {
+ SCAN_FLAG_OPEN = 0x00; // This network does not require authentication
+ SCAN_FLAG_AUTHENTICATED = 0x01; // This network requires authentication
+ SCAN_FLAG_CONFIGURED = 0x02; // This network has been previously provisioned
+ SCAN_FLAG_BEST_SSID = 0x04;
+ SCAN_FLAG_ASSOCIATED = 0x08; // camera is connected to this AP
+ SCAN_FLAG_UNSUPPORTED_TYPE = 0x10;
}
+/**
+ * A list of scan entries describing a scanned Access Point
+ *
+ * This is sent in response to a @ref RequestGetApEntries
+ */
message ResponseGetApEntries {
- required EnumResultGeneric result = 1; // Generic pass/fail/error info
- required int32 scan_id = 2; // ID associated with this batch of results
- repeated ScanEntry entries = 3; // Array containing details about discovered APs
-}
-
+ required EnumResultGeneric result = 1; // Generic pass/fail/error info
+ required int32 scan_id = 2; // ID associated with this batch of results
+ // The individual Scan Entry model
+ message ScanEntry {
+ required string ssid = 1; // AP SSID
+ required int32 signal_strength_bars = 2; // Signal strength (3 bars: >-70 dBm; 2 bars: >-85 dBm; 1 bar: <=-85 dBm)
+ required int32 signal_frequency_mhz = 4; // Signal frequency (MHz)
+ required int32 scan_entry_flags = 5; // Bitmasked value from @ref EnumScanEntryFlags
+ }
+ repeated ScanEntry entries = 3; // Array containing details about discovered APs
+}
+
+/**
+ * The current scanning state.
+ *
+ * This is the initial response to a @ref RequestStartScan
+ */
message ResponseStartScanning {
- required EnumResultGeneric result = 1; // Generic pass/fail/error info
- required EnumScanning scanning_state = 2; // Scanning state
+ required EnumResultGeneric result = 1; // Generic pass/fail/error info
+ required EnumScanning scanning_state = 2; // Scanning state
}
-
diff --git a/protobuf/preset_status.proto b/protobuf/preset_status.proto
index 0c6b5947..36c324cd 100644
--- a/protobuf/preset_status.proto
+++ b/protobuf/preset_status.proto
@@ -1,13 +1,18 @@
/* preset_status.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Fri Sep 8 18:51:54 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf message received from camera containing preset status
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
-syntax="proto2";
-package open_gopro;
+/**
+ * Defines the structure of protobuf message received from camera containing preset status
+ */
+syntax = "proto2";
+package open_gopro;
enum EnumFlatMode {
FLAT_MODE_UNKNOWN = -1;
@@ -90,7 +95,22 @@ enum EnumPresetIcon {
PRESET_ICON_TRAVEL = 31;
PRESET_ICON_WATER = 32;
PRESET_ICON_LOOPING = 33;
- /* Reserved 34 - 50 for Custom presets */
+ /**
+ * New custom icon (34 - 43)added for HERO 12
+ */
+ PRESET_ICON_STARS = 34;
+ PRESET_ICON_ACTION = 35;
+ PRESET_ICON_FOLLOW_CAM = 36;
+ PRESET_ICON_SURF = 37;
+ PRESET_ICON_CITY = 38;
+ PRESET_ICON_SHAKY = 39;
+ PRESET_ICON_CHESTY = 40;
+ PRESET_ICON_HELMET = 41;
+ PRESET_ICON_BITE = 42;
+ PRESET_ICON_MTB = 43;
+ /*
+ * Reserved 44 - 50 for Custom presets. Add icons below for new presets starting from 51
+ */
PRESET_ICON_MAX_VIDEO = 55;
PRESET_ICON_MAX_PHOTO = 56;
PRESET_ICON_MAX_TIMEWARP = 57;
@@ -127,112 +147,144 @@ enum EnumPresetIcon {
}
enum EnumPresetTitle {
- PRESET_TITLE_ACTIVITY = 0;
- PRESET_TITLE_STANDARD = 1;
- PRESET_TITLE_CINEMATIC = 2;
- PRESET_TITLE_PHOTO = 3;
- PRESET_TITLE_LIVE_BURST = 4;
- PRESET_TITLE_BURST = 5;
- PRESET_TITLE_NIGHT = 6;
- PRESET_TITLE_TIME_WARP = 7;
- PRESET_TITLE_TIME_LAPSE = 8;
- PRESET_TITLE_NIGHT_LAPSE = 9;
- PRESET_TITLE_VIDEO = 10;
- PRESET_TITLE_SLOMO = 11;
- PRESET_TITLE_360_VIDEO = 12;
- PRESET_TITLE_PHOTO_2 = 13;
- PRESET_TITLE_PANORAMA = 14;
- PRESET_TITLE_360_PHOTO = 15;
- PRESET_TITLE_TIME_WARP_2 = 16;
- PRESET_TITLE_360_TIME_WARP = 17;
- PRESET_TITLE_CUSTOM = 18;
- PRESET_TITLE_AIR = 19;
- PRESET_TITLE_BIKE = 20;
- PRESET_TITLE_EPIC = 21;
- PRESET_TITLE_INDOOR = 22;
- PRESET_TITLE_MOTOR = 23;
- PRESET_TITLE_MOUNTED = 24;
- PRESET_TITLE_OUTDOOR = 25;
- PRESET_TITLE_POV = 26;
- PRESET_TITLE_SELFIE = 27;
- PRESET_TITLE_SKATE = 28;
- PRESET_TITLE_SNOW = 29;
- PRESET_TITLE_TRAIL = 30;
- PRESET_TITLE_TRAVEL = 31;
- PRESET_TITLE_WATER = 32;
- PRESET_TITLE_LOOPING = 33;
- /* Reserved 34 - 50 for custom presets. */
- PRESET_TITLE_360_TIMELAPSE = 51;
- PRESET_TITLE_360_NIGHT_LAPSE = 52;
- PRESET_TITLE_360_NIGHT_PHOTO = 53;
- PRESET_TITLE_PANO_TIME_LAPSE = 54;
- PRESET_TITLE_MAX_VIDEO = 55;
- PRESET_TITLE_MAX_PHOTO = 56;
- PRESET_TITLE_MAX_TIMEWARP = 57;
- PRESET_TITLE_BASIC = 58;
- PRESET_TITLE_ULTRA_SLO_MO = 59;
- PRESET_TITLE_STANDARD_ENDURANCE = 60;
- PRESET_TITLE_ACTIVITY_ENDURANCE = 61;
- PRESET_TITLE_CINEMATIC_ENDURANCE = 62;
- PRESET_TITLE_SLOMO_ENDURANCE = 63;
- PRESET_TITLE_STATIONARY_1 = 64;
- PRESET_TITLE_STATIONARY_2 = 65;
- PRESET_TITLE_STATIONARY_3 = 66;
- PRESET_TITLE_STATIONARY_4 = 67;
- PRESET_TITLE_SIMPLE_VIDEO = 68;
- PRESET_TITLE_SIMPLE_TIME_WARP = 69;
- PRESET_TITLE_SIMPLE_SUPER_PHOTO = 70;
- PRESET_TITLE_SIMPLE_NIGHT_PHOTO = 71;
- PRESET_TITLE_SIMPLE_VIDEO_ENDURANCE = 72;
- PRESET_TITLE_HIGHEST_QUALITY = 73;
- PRESET_TITLE_EXTENDED_BATTERY = 74;
- PRESET_TITLE_LONGEST_BATTERY = 75;
- PRESET_TITLE_STAR_TRAIL = 76;
- PRESET_TITLE_LIGHT_PAINTING = 77;
- PRESET_TITLE_LIGHT_TRAIL = 78;
- PRESET_TITLE_FULL_FRAME = 79;
- PRESET_TITLE_MAX_LENS_VIDEO = 80;
- PRESET_TITLE_MAX_LENS_TIMEWARP = 81;
- PRESET_TITLE_STANDARD_QUALITY_VIDEO = 82;
- PRESET_TITLE_BASIC_QUALITY_VIDEO = 83;
- PRESET_TITLE_EASY_MAX_VIDEO = 84;
- PRESET_TITLE_EASY_MAX_PHOTO = 85;
- PRESET_TITLE_EASY_MAX_TIMEWARP = 86;
- PRESET_TITLE_EASY_MAX_STAR_TRAIL = 87;
- PRESET_TITLE_EASY_MAX_LIGHT_PAINTING = 88;
- PRESET_TITLE_EASY_MAX_LIGHT_TRAIL = 89;
- PRESET_TITLE_MAX_STAR_TRAIL = 90;
- PRESET_TITLE_MAX_LIGHT_PAINTING = 91;
- PRESET_TITLE_MAX_LIGHT_TRAIL = 92;
- PRESET_TITLE_HIGHEST_QUALITY_VIDEO = 93;
+ PRESET_TITLE_ACTIVITY = 0;
+ PRESET_TITLE_STANDARD = 1;
+ PRESET_TITLE_CINEMATIC = 2;
+ PRESET_TITLE_PHOTO = 3;
+ PRESET_TITLE_LIVE_BURST = 4;
+ PRESET_TITLE_BURST = 5;
+ PRESET_TITLE_NIGHT = 6;
+ PRESET_TITLE_TIME_WARP = 7;
+ PRESET_TITLE_TIME_LAPSE = 8;
+ PRESET_TITLE_NIGHT_LAPSE = 9;
+ PRESET_TITLE_VIDEO = 10;
+ PRESET_TITLE_SLOMO = 11;
+ PRESET_TITLE_360_VIDEO = 12;
+ PRESET_TITLE_PHOTO_2 = 13;
+ PRESET_TITLE_PANORAMA = 14;
+ PRESET_TITLE_360_PHOTO = 15;
+ PRESET_TITLE_TIME_WARP_2 = 16;
+ PRESET_TITLE_360_TIME_WARP = 17;
+ PRESET_TITLE_CUSTOM = 18;
+ PRESET_TITLE_AIR = 19;
+ PRESET_TITLE_BIKE = 20;
+ PRESET_TITLE_EPIC = 21;
+ PRESET_TITLE_INDOOR = 22;
+ PRESET_TITLE_MOTOR = 23;
+ PRESET_TITLE_MOUNTED = 24;
+ PRESET_TITLE_OUTDOOR = 25;
+ PRESET_TITLE_POV = 26;
+ PRESET_TITLE_SELFIE = 27;
+ PRESET_TITLE_SKATE = 28;
+ PRESET_TITLE_SNOW = 29;
+ PRESET_TITLE_TRAIL = 30;
+ PRESET_TITLE_TRAVEL = 31;
+ PRESET_TITLE_WATER = 32;
+ PRESET_TITLE_LOOPING = 33;
+ /*
+ * New custom names (34 - 43)added for HERO 12
+ */
+ PRESET_TITLE_STARS = 34;
+ PRESET_TITLE_ACTION = 35;
+ PRESET_TITLE_FOLLOW_CAM = 36;
+ PRESET_TITLE_SURF = 37;
+ PRESET_TITLE_CITY = 38;
+ PRESET_TITLE_SHAKY = 39;
+ PRESET_TITLE_CHESTY = 40;
+ PRESET_TITLE_HELMET = 41;
+ PRESET_TITLE_BITE = 42;
+ PRESET_TITLE_MTB = 43;
+ /**
+ * Reserved 44 - 50 for custom presets.
+ */
+ PRESET_TITLE_360_TIMELAPSE = 51;
+ PRESET_TITLE_360_NIGHT_LAPSE = 52;
+ PRESET_TITLE_360_NIGHT_PHOTO = 53;
+ PRESET_TITLE_PANO_TIME_LAPSE = 54;
+ PRESET_TITLE_MAX_VIDEO = 55;
+ PRESET_TITLE_MAX_PHOTO = 56;
+ PRESET_TITLE_MAX_TIMEWARP = 57;
+ PRESET_TITLE_BASIC = 58;
+ PRESET_TITLE_ULTRA_SLO_MO = 59;
+ PRESET_TITLE_STANDARD_ENDURANCE = 60;
+ PRESET_TITLE_ACTIVITY_ENDURANCE = 61;
+ PRESET_TITLE_CINEMATIC_ENDURANCE = 62;
+ PRESET_TITLE_SLOMO_ENDURANCE = 63;
+ PRESET_TITLE_STATIONARY_1 = 64;
+ PRESET_TITLE_STATIONARY_2 = 65;
+ PRESET_TITLE_STATIONARY_3 = 66;
+ PRESET_TITLE_STATIONARY_4 = 67;
+ PRESET_TITLE_SIMPLE_VIDEO = 68;
+ PRESET_TITLE_SIMPLE_TIME_WARP = 69;
+ PRESET_TITLE_SIMPLE_SUPER_PHOTO = 70;
+ PRESET_TITLE_SIMPLE_NIGHT_PHOTO = 71;
+ PRESET_TITLE_SIMPLE_VIDEO_ENDURANCE = 72;
+ PRESET_TITLE_HIGHEST_QUALITY = 73;
+ PRESET_TITLE_EXTENDED_BATTERY = 74;
+ PRESET_TITLE_LONGEST_BATTERY = 75;
+ PRESET_TITLE_STAR_TRAIL = 76;
+ PRESET_TITLE_LIGHT_PAINTING = 77;
+ PRESET_TITLE_LIGHT_TRAIL = 78;
+ PRESET_TITLE_FULL_FRAME = 79;
+ PRESET_TITLE_MAX_LENS_VIDEO = 80;
+ PRESET_TITLE_MAX_LENS_TIMEWARP = 81;
+ PRESET_TITLE_STANDARD_QUALITY_VIDEO = 82;
+ PRESET_TITLE_BASIC_QUALITY_VIDEO = 83;
+ PRESET_TITLE_EASY_MAX_VIDEO = 84;
+ PRESET_TITLE_EASY_MAX_PHOTO = 85;
+ PRESET_TITLE_EASY_MAX_TIMEWARP = 86;
+ PRESET_TITLE_EASY_MAX_STAR_TRAIL = 87;
+ PRESET_TITLE_EASY_MAX_LIGHT_PAINTING = 88;
+ PRESET_TITLE_EASY_MAX_LIGHT_TRAIL = 89;
+ PRESET_TITLE_MAX_STAR_TRAIL = 90;
+ PRESET_TITLE_MAX_LIGHT_PAINTING = 91;
+ PRESET_TITLE_MAX_LIGHT_TRAIL = 92;
+ PRESET_TITLE_HIGHEST_QUALITY_VIDEO = 93;
+ PRESET_TITLE_USER_DEFINED_CUSTOM_NAME = 94;
}
+/**
+ * Current Preset status
+ *
+ * Sent either:
+ * - synchronously via initial response to @ref RequestGetPresetStatus
+ * - asynchronously when Preset change if registered in @rev RequestGetPresetStatus
+ */
message NotifyPresetStatus {
- repeated PresetGroup preset_group_array = 1; // Array of Preset Groups
+ repeated PresetGroup preset_group_array = 1; // Array of currently available Preset Groups
}
+/**
+ * An individual preset.
+ */
message Preset {
- optional int32 id = 1; // Preset ID
- optional EnumFlatMode mode = 2; // Preset flatmode ID
- optional EnumPresetTitle title_id = 3; // Preset Title ID
- optional int32 title_number = 4; // Preset Title Number (e.g. 1/2/3 in Custom1, Custom2, Custom3)
- optional bool user_defined = 5; // Is the Preset custom/user-defined?
- optional EnumPresetIcon icon = 6; // Preset Icon ID
- repeated PresetSetting setting_array = 7; // Array of settings associated with this Preset
- optional bool is_modified = 8; // Has Preset been modified from factory
- optional bool is_fixed = 9; // Is this Preset mutable?
+ optional int32 id = 1; // Preset ID
+ optional EnumFlatMode mode = 2; // Preset flatmode ID
+ optional EnumPresetTitle title_id = 3; // Preset Title ID
+ optional int32 title_number = 4; // Preset Title Number (e.g. 1/2/3 in Custom1, Custom2, Custom3)
+ optional bool user_defined = 5; // Is the Preset custom/user-defined?
+ optional EnumPresetIcon icon = 6; // Preset Icon ID
+ repeated PresetSetting setting_array = 7; // Array of settings associated with this Preset
+ optional bool is_modified = 8; // Has Preset been modified from factory defaults? (False for user-defined Presets)
+ optional bool is_fixed = 9; // Is this Preset mutable?
+ optional string custom_name = 10; // Custom string name given to this preset via @ref RequestCustomPresetUpdate
}
+/*
+ * Preset Group meta information and contained Presets
+ */
message PresetGroup {
- optional EnumPresetGroup id = 1; // Preset Group ID
- repeated Preset preset_array = 2; // Array of Presets contained in this Preset Group
- optional bool can_add_preset = 3; // Is there room in the group to add additional Presets?
- optional EnumPresetGroupIcon icon = 4; // The icon to display for this preset group
+ optional EnumPresetGroup id = 1; // Preset Group ID
+ repeated Preset preset_array = 2; // Array of Presets contained in this Preset Group
+ optional bool can_add_preset = 3; // Is there room in the group to add additional Presets?
+ optional EnumPresetGroupIcon icon = 4; // The icon to display for this preset group
}
+/**
+ * Setting representation that comprises a @ref Preset
+ */
message PresetSetting {
- optional int32 id = 1; // Setting ID
- optional int32 value = 2; // Setting value
- optional bool is_caption = 3; // Does this setting appear on the Preset "pill" in the camera UI?
+ optional int32 id = 1; // Setting ID
+ optional int32 value = 2; // Setting value
+ optional bool is_caption = 3; // Does this setting appear on the Preset "pill" in the camera UI?
}
-
diff --git a/protobuf/request_get_preset_status.proto b/protobuf/request_get_preset_status.proto
index 3738e852..4dd10edb 100644
--- a/protobuf/request_get_preset_status.proto
+++ b/protobuf/request_get_preset_status.proto
@@ -1,21 +1,32 @@
/* request_get_preset_status.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Wed Jul 5 19:32:05 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf messages for obtaining preset status
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
-syntax="proto2";
-package open_gopro;
+/**
+ * Defines the structure of protobuf messages for obtaining preset status
+ */
+syntax = "proto2";
+package open_gopro;
enum EnumRegisterPresetStatus {
- REGISTER_PRESET_STATUS_PRESET = 1; // Send notification when properties of a preset change
- REGISTER_PRESET_STATUS_PRESET_GROUP_ARRAY = 2; // Send notification when properties of a preset group change
+ REGISTER_PRESET_STATUS_PRESET = 1; // Send notification when properties of a preset change
+ REGISTER_PRESET_STATUS_PRESET_GROUP_ARRAY = 2; // Send notification when properties of a preset group change
}
+/**
+ * Get preset status (and optionally register to be notified when it changes)
+ *
+ * Response: @ref NotifyPresetStatus sent immediately
+ *
+ * Notification: @ref NotifyPresetStatus sent periodically as preset status changes, if registered.
+ */
message RequestGetPresetStatus {
- repeated EnumRegisterPresetStatus register_preset_status = 1; // Array of Preset statuses to be notified about
- repeated EnumRegisterPresetStatus unregister_preset_status = 2; // Array of Preset statuses to stop being notified about
+ repeated EnumRegisterPresetStatus register_preset_status = 1; // Array of Preset statuses to be notified about
+ repeated EnumRegisterPresetStatus unregister_preset_status = 2; // Array of Preset statuses to stop being notified about
}
-
diff --git a/protobuf/response_generic.proto b/protobuf/response_generic.proto
index a1819412..8e73f0be 100644
--- a/protobuf/response_generic.proto
+++ b/protobuf/response_generic.proto
@@ -1,13 +1,18 @@
/* response_generic.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Fri Jun 9 22:49:36 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf message containing generic response to a command
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
-syntax="proto2";
-package open_gopro;
+/**
+ * Defines the structure of protobuf message containing generic response to a command
+ */
+syntax = "proto2";
+package open_gopro;
enum EnumResultGeneric {
RESULT_UNKNOWN = 0;
@@ -18,7 +23,11 @@ enum EnumResultGeneric {
RESULT_ARGUMENT_INVALID = 5;
}
+/*
+ * Generic Response used across most response / notification messages
+ *
+ * @ref EnumResultGeneric
+ */
message ResponseGeneric {
- required EnumResultGeneric result = 1; // Generic pass/fail/error info
+ required EnumResultGeneric result = 1; // Generic pass/fail/error info
}
-
diff --git a/protobuf/set_camera_control_status.proto b/protobuf/set_camera_control_status.proto
index 14a67537..550d94b5 100644
--- a/protobuf/set_camera_control_status.proto
+++ b/protobuf/set_camera_control_status.proto
@@ -1,21 +1,30 @@
/* set_camera_control_status.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Fri Jun 9 22:49:36 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf messages for setting camera control status
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
-syntax="proto2";
-package open_gopro;
+/**
+ * Defines the structure of protobuf messages for setting camera control status
+ */
+syntax = "proto2";
+package open_gopro;
enum EnumCameraControlStatus {
CAMERA_IDLE = 0;
- CAMERA_CONTROL = 1; // Can only be set by camera, not by third-party
+ CAMERA_CONTROL = 1; // Can only be set by camera, not by app or third party
CAMERA_EXTERNAL_CONTROL = 2;
}
+/**
+ * Set Camera Control Status (as part of Global Behaviors feature)
+ *
+ * Response: @ref ResponseGeneric
+ */
message RequestSetCameraControlStatus {
- required EnumCameraControlStatus camera_control_status = 1; // Declare who is taking control of the camera
+ required EnumCameraControlStatus camera_control_status = 1; // Declare who is taking control of the camera
}
-
diff --git a/protobuf/turbo_transfer.proto b/protobuf/turbo_transfer.proto
index c820f6fd..1587a20a 100644
--- a/protobuf/turbo_transfer.proto
+++ b/protobuf/turbo_transfer.proto
@@ -1,15 +1,24 @@
/* turbo_transfer.proto/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */
-/* This copyright was auto-generated on Fri Jun 9 22:49:36 UTC 2023 */
+/* This copyright was auto-generated on Sat Nov 4 21:02:36 UTC 2023 */
-/*
-Defines the structure of protobuf messages for enabling and disabling Turbo Transfer feature
-*/
+/***********************************************************************************************************************
+ *
+ * This file is automatically generated!!! Do not modify manually.
+ *
+ **********************************************************************************************************************/
-syntax="proto2";
-package open_gopro;
+/**
+ * Defines the structure of protobuf messages for enabling and disabling Turbo Transfer feature
+ */
+syntax = "proto2";
+package open_gopro;
+/**
+ * Enable/disable display of "Transferring Media" UI
+ *
+ * Response: @ref ResponseGeneric
+ */
message RequestSetTurboActive {
- required bool active = 1; // Enable or disable Turbo Transfer feature
+ required bool active = 1; // Enable or disable Turbo Transfer feature
}
-
diff --git a/tools/test_media_server/README.md b/tools/test_media_server/README.md
index 56460c95..6ef41271 100644
--- a/tools/test_media_server/README.md
+++ b/tools/test_media_server/README.md
@@ -3,7 +3,7 @@
- [SSL Configuration](#ssl-configuration)
- [Test Stream](#test-stream)
- [RTMP](#rtmp)
- - [RTMPS](#rtmps)
+ - [~~RTMPS~~](#rtmps)
- [More Information](#more-information)
# What is this?
@@ -11,7 +11,9 @@
This is a test server that can used for isolated testing of the following streams:
- RTMP
-- RTMP
+- ~~RTMPS~~
+
+> Note! RTMPS functionality is not currently working as indicated by strike-throughs below
# Usage
@@ -25,13 +27,13 @@ where `IP_ADDRESS` is the IP Address of the device the server is running on.
The server accepts communication on the following endpoints / ports:
-| Port | Endpoint | Communication Type | Example |
-| ---- | --------- | ---------------------- | ----------------------------------- |
-| 8080 | | HLS stream viewer | http://{IP_ADDRESS}:8080 |
-| 8080 | stats | stream stats via HTTP | http://{IP_ADDRESS}:8080/stats |
-| 8443 | stats | stream stats via HTTPS | https://{IP_ADDRESS}:8443/stats |
-| 1935 | live/test | RTMP stream | rtmp://{IP_ADDRESS}:1935/live/test |
-| 1936 | live/test | RTMPS stream | rtmps://{IP_ADDRESS}:1936/live/test |
+| Port | Endpoint | Communication Type | Example |
+| -------- | ------------- | ---------------------- | --------------------------------------- |
+| 8080 | | HLS stream viewer | http://{IP_ADDRESS}:8080 |
+| 8080 | stats | stream stats via HTTP | http://{IP_ADDRESS}:8080/stats |
+| 8443 | stats | stream stats via HTTPS | https://{IP_ADDRESS}:8443/stats |
+| 1935 | live/test | RTMP stream | rtmp://{IP_ADDRESS}:1935/live/test |
+| ~~1936~~ | ~~live/test~~ | ~~RTMPS stream~~ | ~~rtmps://{IP_ADDRESS}:1936/live/test~~ |
The general usage is:
@@ -65,17 +67,18 @@ docker run --rm jrottenberg/ffmpeg:4.1-alpine -r 30 -f lavfi -i testsrc -vf scal
2. View at: http://localhost:8080
-## RTMPS
+## ~~RTMPS~~
+
+~~1. Test via:~~
-1. Test via:
```
docker run --rm jrottenberg/ffmpeg:4.1-alpine -r 30 -f lavfi -i testsrc -vf scale=1280:960 -vcodec libx264 -profile:v baseline -pix_fmt yuv420p -f flv rtmps://{IP_ADDRESS}:1936/live/test
```
-2. View at: http://localhost:8080
+~~2. View at: http://localhost:8080~~
# More Information
Per-stream stats can be viewed at `http://localhost:8080/stats`. The `.xml` provided by this endpoint could,
-for example, be used as a programmatic way to verify a stream has started / stopped.
+for example, be used as a programmatic way to verify a stream has started / stopped.
\ No newline at end of file