diff --git a/demos/python/sdk_wireless_camera_control/docs/changelog.rst b/demos/python/sdk_wireless_camera_control/docs/changelog.rst
index 3c61987e..3929587f 100644
--- a/demos/python/sdk_wireless_camera_control/docs/changelog.rst
+++ b/demos/python/sdk_wireless_camera_control/docs/changelog.rst
@@ -10,7 +10,7 @@ The format is based on `Keep a Changelog `
and this project adheres to `Semantic Versioning `_.
0.14.0 (September-13-2022)
--------------------------
+--------------------------
* NOTE! This is a major update and includes massive API breaking changes.
* Move to asyncio-based framework
* Add HERO 12 support
diff --git a/demos/python/sdk_wireless_camera_control/noxfile.py b/demos/python/sdk_wireless_camera_control/noxfile.py
index 73b1e430..0b8bc436 100644
--- a/demos/python/sdk_wireless_camera_control/noxfile.py
+++ b/demos/python/sdk_wireless_camera_control/noxfile.py
@@ -78,6 +78,6 @@ def docs(session) -> None:
"autodoc-pydantic",
"darglint",
)
- session.run("sphinx-build", "docs", "docs/build")
+ session.run("sphinx-build", "-W", "docs", "docs/build")
# Clean up for Jekyll consumption
session.run("rm", "-rf", "docs/build/.doctrees", "/docs/build/_sources", "/docs/build/_static/fonts", external=True)
diff --git a/demos/python/sdk_wireless_camera_control/open_gopro/demos/log_battery.py b/demos/python/sdk_wireless_camera_control/open_gopro/demos/log_battery.py
index 78cb29fa..cf3117d9 100644
--- a/demos/python/sdk_wireless_camera_control/open_gopro/demos/log_battery.py
+++ b/demos/python/sdk_wireless_camera_control/open_gopro/demos/log_battery.py
@@ -17,7 +17,7 @@
from open_gopro import WirelessGoPro, types
from open_gopro.constants import StatusId
from open_gopro.logger import set_stream_logging_level, setup_logging
-from open_gopro.util import add_cli_args_and_parse
+from open_gopro.util import add_cli_args_and_parse, ainput
console = Console()
@@ -84,56 +84,55 @@ async def process_battery_notifications(update: types.UpdateType, value: int) ->
async def main(args: argparse.Namespace) -> None:
logger = setup_logging(__name__, args.log)
- global SAMPLE_INDEX
gopro: Optional[WirelessGoPro] = None
try:
async with WirelessGoPro(args.identifier, enable_wifi=False) as gopro:
set_stream_logging_level(logging.ERROR)
- if args.poll:
- with console.status("[bold green]Polling the battery until it dies..."):
- while True:
- SAMPLES.append(
- Sample(
- index=SAMPLE_INDEX,
- percentage=(await gopro.ble_status.int_batt_per.get_value()).data,
- bars=(await gopro.ble_status.batt_level.get_value()).data,
+ async def log_battery() -> None:
+ global SAMPLE_INDEX
+ if args.poll:
+ with console.status("[bold green]Polling the battery until it dies..."):
+ while True:
+ SAMPLES.append(
+ Sample(
+ index=SAMPLE_INDEX,
+ percentage=(await gopro.ble_status.int_batt_per.get_value()).data,
+ bars=(await gopro.ble_status.batt_level.get_value()).data,
+ )
)
- )
- console.print(str(SAMPLES[-1]))
- SAMPLE_INDEX += 1
- await asyncio.sleep(args.poll)
- # Otherwise set up notifications
- else:
- global last_bars
- global last_percentage
-
- console.print("Configuring battery notifications...")
- # Enable notifications of the relevant battery statuses. Also store initial values.
- last_bars = (
- await gopro.ble_status.batt_level.register_value_update(process_battery_notifications)
- ).data
- last_percentage = (
- await gopro.ble_status.int_batt_per.register_value_update(process_battery_notifications)
- ).data
- # Append initial sample
- SAMPLES.append(Sample(index=SAMPLE_INDEX, percentage=last_percentage, bars=last_bars))
- console.print(str(SAMPLES[-1]))
-
- # Start a thread to handle asynchronous battery level notifications
- console.print("[bold green]Receiving battery notifications until it dies...")
- while True:
- await asyncio.sleep(1)
+ console.print(str(SAMPLES[-1]))
+ SAMPLE_INDEX += 1
+ await asyncio.sleep(args.poll)
+ else: # Not polling. Set up notifications
+ global last_bars
+ global last_percentage
+
+ console.print("Configuring battery notifications...")
+ # Enable notifications of the relevant battery statuses. Also store initial values.
+ last_bars = (
+ await gopro.ble_status.batt_level.register_value_update(process_battery_notifications)
+ ).data
+ last_percentage = (
+ await gopro.ble_status.int_batt_per.register_value_update(process_battery_notifications)
+ ).data
+ # Append initial sample
+ SAMPLES.append(Sample(index=SAMPLE_INDEX, percentage=last_percentage, bars=last_bars))
+ console.print(str(SAMPLES[-1]))
+ console.print("[bold green]Receiving battery notifications until it dies...")
+
+ asyncio.create_task(log_battery())
+ await ainput("[purple]Press enter to exit.", console.print)
+ console.print("Exiting...")
except KeyboardInterrupt:
logger.warning("Received keyboard interrupt. Shutting down...")
- if len(SAMPLES) > 0:
+ if SAMPLES:
csv_location = Path(args.log.parent) / "battery_results.csv"
dump_results_as_csv(csv_location)
if gopro:
await gopro.close()
- console.print("Exiting...")
def parse_arguments() -> argparse.Namespace:
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 2ec35932..a10c8dfc 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
@@ -27,7 +27,7 @@
)
from open_gopro.ble import BleakWrapperController, BleUUID
from open_gopro.communicator_interface import GoProWirelessInterface, MessageRules
-from open_gopro.constants import ActionId, GoProUUIDs, QueryCmdId, SettingId, StatusId
+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.response import BleRespBuilder, GoProResp
@@ -538,12 +538,17 @@ async def _update_internal_state(self, update: types.UpdateType, value: int) ->
logger.trace("Control setting encoded started") # type: ignore
self._encoding_started.set()
+ # TODO this needs unit testing
async def _route_response(self, response: GoProResp) -> None:
"""After parsing response, route it to any stakeholders (such as registered listeners)
Args:
response (GoProResp): parsed response
"""
+ # Flatten data if possible
+ 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:
@@ -554,10 +559,10 @@ async def _route_response(self, response: GoProResp) -> None:
# If this wasn't the awaited synchronous response...
if not response_claimed:
logger.info(Logger.build_log_rx_str(response, asynchronous=True))
- if isinstance(response.identifier, QueryCmdId):
+ if response._is_push:
for update_id, value in response.data.items():
await self._notify_listeners(update_id, value)
- elif isinstance(response.identifier, (StatusId, SettingId, ActionId)):
+ elif isinstance(response.identifier, ActionId):
await self._notify_listeners(response.identifier, response.data)
def _notification_handler(self, handle: int, data: bytearray) -> None:
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 df15512e..748a1a91 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
@@ -143,6 +143,28 @@ def ok(self) -> bool:
"""
return self.status in [ErrorCode.SUCCESS, ErrorCode.UNKNOWN]
+ @property
+ def _is_push(self) -> bool:
+ """Was this response an asynchronous push?
+
+ Returns:
+ bool: True if yes, False otherwise
+ """
+ return self.identifier in [
+ QueryCmdId.STATUS_VAL_PUSH,
+ QueryCmdId.SETTING_VAL_PUSH,
+ QueryCmdId.SETTING_CAPABILITY_PUSH,
+ ]
+
+ @property
+ def _is_query(self) -> bool:
+ """Is this response to a settings / status query?
+
+ Returns:
+ bool: True if yes, False otherwise
+ """
+ return isinstance(self.identifier, QueryCmdId)
+
class RespBuilder(ABC, Generic[T]):
"""Common Response Builder Interface"""
@@ -437,12 +459,6 @@ def build(self) -> GoProResp:
# Query (setting get value, status get value, etc.)
if query_type:
- is_multiple = self._identifier in [
- QueryCmdId.GET_CAPABILITIES_VAL,
- QueryCmdId.REG_CAPABILITIES_UPDATE,
- QueryCmdId.SETTING_CAPABILITY_PUSH,
- ]
-
camera_state: types.CameraState = defaultdict(list)
self._status = ErrorCode(buf[0])
buf = buf[1:]
@@ -467,7 +483,11 @@ def build(self) -> GoProResp:
camera_state[param_id] = param_val
continue
# These can be more than 1 value so use a list
- if is_multiple:
+ if self._identifier in [
+ QueryCmdId.GET_CAPABILITIES_VAL,
+ QueryCmdId.REG_CAPABILITIES_UPDATE,
+ QueryCmdId.SETTING_CAPABILITY_PUSH,
+ ]:
# Parse using parser from global map and append
camera_state[param_id].append(parser.parse(param_val))
else:
@@ -479,12 +499,8 @@ def build(self) -> GoProResp:
# isn't functionally critical
logger.warning(f"{param_id} does not contain a value {param_val}")
camera_state[param_id] = param_val
- # Flatten if not multiple
- if is_multiple:
- self._identifier = list(camera_state.keys())[0]
- parsed = list(camera_state.values())[0]
- else:
- parsed = camera_state
+ parsed = camera_state
+
else: # Commands, Protobuf, and direct Reads
if is_cmd := isinstance(self._identifier, CmdId):
# All (non-protobuf) commands have a status
diff --git a/demos/python/sdk_wireless_camera_control/pyproject.toml b/demos/python/sdk_wireless_camera_control/pyproject.toml
index 9c682ce8..ef4462f0 100644
--- a/demos/python/sdk_wireless_camera_control/pyproject.toml
+++ b/demos/python/sdk_wireless_camera_control/pyproject.toml
@@ -183,7 +183,7 @@ log_file_level = "DEBUG"
log_file_format = "%(threadName)13s: %(name)40s:%(lineno)5d %(asctime)s.%(msecs)03d %(levelname)-8s | %(message)s"
log_file_date_format = "%H:%M:%S"
filterwarnings = "ignore::DeprecationWarning"
-# timeout = 10
+timeout = 10
addopts = [
"-s",
"--capture=tee-sys",
diff --git a/demos/python/sdk_wireless_camera_control/tests/conftest.py b/demos/python/sdk_wireless_camera_control/tests/conftest.py
index 75618167..10738c98 100644
--- a/demos/python/sdk_wireless_camera_control/tests/conftest.py
+++ b/demos/python/sdk_wireless_camera_control/tests/conftest.py
@@ -40,6 +40,7 @@
from open_gopro.communicator_interface import GoProBle, GoProWifi
from open_gopro.constants import CmdId, ErrorCode, GoProUUIDs, StatusId
from open_gopro.exceptions import ConnectFailed, FailedToFindDevice
+from open_gopro.gopro_base import GoProBase
from open_gopro.logger import set_logging_level, setup_logging
from open_gopro.models.response import GoProResp
from open_gopro.wifi import SsidState, WifiClient, WifiController
@@ -444,6 +445,7 @@ def close(self) -> None:
@pytest.fixture(params=versions)
async def mock_wireless_gopro_basic(request):
test_client = MockWirelessGoPro(request.param)
+ GoProBase.HTTP_GET_RETRIES = 1
yield test_client
test_client.close()
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_responses.py b/demos/python/sdk_wireless_camera_control/tests/unit/test_responses.py
index f76b5259..d9920de8 100644
--- a/demos/python/sdk_wireless_camera_control/tests/unit/test_responses.py
+++ b/demos/python/sdk_wireless_camera_control/tests/unit/test_responses.py
@@ -35,8 +35,10 @@ def test_push_response_no_parameter_values():
assert builder.is_finished_accumulating
r = builder.build()
assert r.ok
- assert r.identifier == SettingId.RESOLUTION
- assert r.data == []
+ assert r.identifier == QueryCmdId.SETTING_CAPABILITY_PUSH
+ assert r.data[SettingId.RESOLUTION] == []
+ assert r.data[SettingId.FPS] == []
+ assert r.data[SettingId.VIDEO_FOV] == []
test_read_receive = bytearray([0x64, 0x62, 0x32, 0x2D, 0x73, 0x58, 0x56, 0x2D, 0x66, 0x62, 0x38])
diff --git a/demos/python/sdk_wireless_camera_control/tests/unit/test_wireless_gopro.py b/demos/python/sdk_wireless_camera_control/tests/unit/test_wireless_gopro.py
index 5b04e2e2..b56269fc 100644
--- a/demos/python/sdk_wireless_camera_control/tests/unit/test_wireless_gopro.py
+++ b/demos/python/sdk_wireless_camera_control/tests/unit/test_wireless_gopro.py
@@ -131,7 +131,7 @@ async def receive_encoding_status(id: types.UpdateType, value: bool):
event.set()
mock_wireless_gopro_basic.register_update(receive_encoding_status, StatusId.ENCODING)
- not_encoding = bytearray([0x05, 0x13, 0x00, StatusId.ENCODING.value, 0x01, 0x00])
+ not_encoding = bytearray([0x05, 0x93, 0x00, StatusId.ENCODING.value, 0x01, 0x00])
mock_wireless_gopro_basic._notification_handler(0xFF, not_encoding)
await event.wait()
@@ -153,7 +153,7 @@ async def receive_encoding_status(id: types.UpdateType, value: bool):
event.set()
mock_wireless_gopro_basic.register_update(receive_encoding_status, StatusId.ENCODING)
- not_encoding = bytearray([0x05, 0x13, 0x00, StatusId.ENCODING.value, 0x01, 0x00])
+ not_encoding = bytearray([0x05, 0x93, 0x00, StatusId.ENCODING.value, 0x01, 0x00])
mock_wireless_gopro_basic._notification_handler(0xFF, not_encoding)
await event.wait()