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: +

    +
  1. Create the COHN certificate
  2. +
  3. Get the COHN certificate
  4. +
  5. Get Basic auth credentials
  6. +
  7. Connect the camera to an access point
  8. +
+

+ +### 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: + + + + + + + + + + + + + + + +
ProtocolVLC Network URL
TSudp://@:{PORT}
RTSPrtsp://{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 +
+ + + + + + + + + + + + + + + + + + +
ParameterDefault Value
res12 (1080p)
fovLast-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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CameraProtocolDefault PortSupports User-Defined Port?
HERO12 BlackTS8554
RTSP554
HERO11 BlackTS8554
HERO10 BlackTS8554
HERO9 BlackTS8554
+ ### 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 + + + + + + + + + + + + + + + + + + +
CommandResponse FormatDescription
/GoProRootCA.crtTextGet COHN cert
/gopro/cohn/statusJSONGet 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