From 8bb540298cc4e56ee8e2ece805dc7e33bb7be113 Mon Sep 17 00:00:00 2001 From: akeildev Date: Fri, 26 Jun 2026 16:45:12 -0700 Subject: [PATCH] fix(python-sdk): pass CommandFactory (not invoked coroutine) to observables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `GoproObserverDistinctInitial.start()` calls `self._register_command_factory()`, and the type is `CommandFactory = Callable[[], Coroutine[...]]` — i.e. a factory that *returns* a coroutine. Several call sites instead passed an already-invoked coroutine, so `start()` tried to call a coroutine object: TypeError: 'coroutine' object is not callable This breaks `AccessPointFeature.connect()` (and therefore COHN provisioning, which depends on it) deterministically, as well as `LiveStreamController`'s status observable. Fix the call sites to pass factories, matching the correct pattern already used elsewhere (e.g. cohn_feature.py, api/builders.py): - access_point_feature.py: wrap `scan_wifi_networks()` in a lambda; build the wifi-connect command with functools.partial. - livestream.py: wrap register/unregister status commands in lambdas. Tested against a HERO12 Black: `access_point.connect()` now completes the wifi scan and returns a normal Result instead of raising. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_017S8amPPZwqFt7hqdNk3yJh --- .../open_gopro/features/access_point_feature.py | 9 ++++++--- .../open_gopro/features/streaming/livestream.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/features/access_point_feature.py b/demos/python/sdk_wireless_camera_control/open_gopro/features/access_point_feature.py index 9c5be56ea..35ac02df9 100644 --- a/demos/python/sdk_wireless_camera_control/open_gopro/features/access_point_feature.py +++ b/demos/python/sdk_wireless_camera_control/open_gopro/features/access_point_feature.py @@ -5,6 +5,7 @@ import asyncio import logging +from functools import partial from returns.pipeline import is_successful from returns.result import Result @@ -58,7 +59,7 @@ async def scan_wifi_networks(self, timeout: int = 60) -> Result[proto.ResponseGe async with GoproObserverDistinctInitial[ResponseStartScanning, NotifStartScanning]( gopro=self._gopro, update=ProtobufId(FeatureId.NETWORK_MANAGEMENT, ActionId.NOTIF_START_SCAN), - register_command=self._gopro.ble_command.scan_wifi_networks(), + register_command=lambda: self._gopro.ble_command.scan_wifi_networks(), ) as observable, asyncio.timeout(timeout): if observable.initial_response.result != EnumResultGeneric.RESULT_SUCCESS: return Result.from_failure(GoProError("Failed to start scanning.")) @@ -90,10 +91,12 @@ async def connect( # Are we already provisioned? if entry.scan_entry_flags & EnumScanEntryFlags.SCAN_FLAG_CONFIGURED: logger.info(f"Connecting to already provisioned network {ssid}...") - command = self._gopro.ble_command.request_wifi_connect(ssid=ssid) + command = partial(self._gopro.ble_command.request_wifi_connect, ssid=ssid) else: logger.info(f"Provisioning new network {ssid}...") - command = self._gopro.ble_command.request_wifi_connect_new(ssid=ssid, password=password) + command = partial( + self._gopro.ble_command.request_wifi_connect_new, ssid=ssid, password=password + ) async with GoproObserverDistinctInitial[ ResponseConnect | ResponseConnectNew, diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/features/streaming/livestream.py b/demos/python/sdk_wireless_camera_control/open_gopro/features/streaming/livestream.py index 354968369..4512c24e0 100644 --- a/demos/python/sdk_wireless_camera_control/open_gopro/features/streaming/livestream.py +++ b/demos/python/sdk_wireless_camera_control/open_gopro/features/streaming/livestream.py @@ -79,10 +79,10 @@ async def get_livestream_status_observable( """ return await GoProObservable[NotifyLiveStreamStatus]( gopro=self.gopro, - register_command=self.gopro.ble_command.register_livestream_status( + register_command=lambda: self.gopro.ble_command.register_livestream_status( register=[EnumRegisterLiveStreamStatus.REGISTER_LIVE_STREAM_STATUS_STATUS] ), - unregister_command=self.gopro.ble_command.register_livestream_status( + unregister_command=lambda: self.gopro.ble_command.register_livestream_status( unregister=[EnumRegisterLiveStreamStatus.REGISTER_LIVE_STREAM_STATUS_STATUS] ), update=ProtobufId(FeatureId.QUERY, ActionId.LIVESTREAM_STATUS_NOTIF),