Skip to content

Commit 2cbf1b6

Browse files
authored
Python SDK: Stream Demo Cleanup (#788)
* Clean up console for preview stream * Fix Macos 15 wifi
1 parent 729a3ea commit 2cbf1b6

File tree

2 files changed

+69
-46
lines changed

2 files changed

+69
-46
lines changed

demos/python/sdk_wireless_camera_control/open_gopro/demos/gui/preview_stream.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55

66
import argparse
77
import asyncio
8+
import logging
89

10+
from returns.pipeline import is_successful
911
from rich.console import Console
1012

1113
from open_gopro import WirelessGoPro
1214
from open_gopro.demos.gui.video_display import BufferlessVideoCapture
1315
from open_gopro.models import streaming
1416
from open_gopro.util import add_cli_args_and_parse
15-
from open_gopro.util.logger import setup_logging
17+
from open_gopro.util.logger import set_stream_logging_level, setup_logging
18+
from open_gopro.util.util import ainput
1619

1720
console = Console()
1821

@@ -21,22 +24,36 @@ async def main(args: argparse.Namespace) -> None:
2124
setup_logging(__name__, args.log)
2225

2326
async with WirelessGoPro(args.identifier) as gopro:
24-
console.print("Starting preview stream...")
25-
await gopro.streaming.start_stream(
26-
streaming.StreamType.PREVIEW,
27-
streaming.PreviewStreamOptions(port=args.port),
27+
set_stream_logging_level(logging.WARNING)
28+
29+
with console.status("Starting preview stream..."):
30+
if not is_successful(
31+
result := (
32+
await gopro.streaming.start_stream(
33+
stream_type=streaming.StreamType.PREVIEW,
34+
options=streaming.PreviewStreamOptions(port=args.port),
35+
)
36+
)
37+
):
38+
console.print(f"[red]Failed to start preview stream: {result.failure()}")
39+
return
40+
assert gopro.streaming.url, "Preview stream URL should not be empty after starting the stream"
41+
42+
console.print("Preview stream started. It can be viewed in VLC at [yellow]udp://@:8554")
43+
console.print(
44+
"Press Enter to view the preview stream using Python CV2. Once started, it won't be viewable in VLC."
2845
)
46+
await ainput("")
2947

30-
console.print("Displaying the preview stream...")
31-
assert gopro.streaming.url
32-
BufferlessVideoCapture(
33-
source=gopro.streaming.url,
34-
protocol=BufferlessVideoCapture.Protocol.TS,
35-
printer=console.print,
36-
).display_blocking()
48+
with console.status("Displaying the preview stream..."):
49+
BufferlessVideoCapture(
50+
source=gopro.streaming.url,
51+
protocol=BufferlessVideoCapture.Protocol.TS,
52+
printer=console.print,
53+
).display_blocking()
3754

38-
console.print("Stopping preview stream...")
39-
await gopro.streaming.stop_active_stream()
55+
with console.status("Stopping preview stream..."):
56+
await gopro.streaming.stop_active_stream()
4057

4158

4259
def parse_arguments() -> argparse.Namespace:

demos/python/sdk_wireless_camera_control/open_gopro/network/wifi/adapters/wireless.py

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ async def disconnect(self) -> bool: # type: ignore
185185
"""
186186

187187
@pass_through_to_driver
188-
def current(self) -> tuple[Optional[str], SsidState]: # type: ignore
188+
def current(self) -> tuple[str | None, SsidState]: # type: ignore
189189
"""Return the SSID and state of the current network.
190190
191191
Returns:
192-
tuple[Optional[str], SsidState]: Tuple of SSID str and state. If SSID is None,
192+
tuple[str | None, SsidState]: Tuple of SSID str and state. If SSID is None,
193193
there is no current connection.
194194
"""
195195

@@ -222,13 +222,13 @@ def interface(self) -> str:
222222
return self._driver.interface
223223

224224
@interface.setter
225-
def interface(self, interface: Optional[str]) -> None:
225+
def interface(self, interface: str | None) -> None:
226226
"""Set the Wifi interface.
227227
228228
If None is passed, interface will attempt to be auto-detected
229229
230230
Args:
231-
interface (Optional[str]): interface (or None)
231+
interface (str | None): interface (or None)
232232
"""
233233
self._driver.interface = interface # type: ignore
234234

@@ -245,7 +245,7 @@ def is_on(self) -> bool:
245245
class NmcliWireless(WifiController):
246246
"""Linux nmcli Driver < 0.9.9.0."""
247247

248-
def __init__(self, password: str | None, interface: Optional[str] = None) -> None:
248+
def __init__(self, password: str | None, interface: str | None = None) -> None:
249249
WifiController.__init__(self, interface=interface, password=password)
250250

251251
def _clean(self, partial: str) -> None:
@@ -350,11 +350,11 @@ async def disconnect(self) -> bool:
350350
"""
351351
return False
352352

353-
def current(self) -> tuple[Optional[str], SsidState]:
353+
def current(self) -> tuple[str | None, SsidState]:
354354
"""[summary].
355355
356356
Returns:
357-
tuple[Optional[str], SsidState]: [description]
357+
tuple[str | None, SsidState]: [description]
358358
"""
359359
# list active connections for all interfaces
360360
response = cmd(f'{self.sudo} nmcli con status | grep "{self.interface}"')
@@ -415,7 +415,7 @@ def power(self, power: bool) -> bool:
415415
class Nmcli0990Wireless(WifiController):
416416
"""Linux nmcli Driver >= 0.9.9.0."""
417417

418-
def __init__(self, password: str | None, interface: Optional[str] = None) -> None:
418+
def __init__(self, password: str | None, interface: str | None = None) -> None:
419419
WifiController.__init__(self, interface=interface, password=password)
420420

421421
def _clean(self, partial: str) -> None:
@@ -516,11 +516,11 @@ async def disconnect(self) -> bool:
516516
"""
517517
return False
518518

519-
def current(self) -> tuple[Optional[str], SsidState]:
519+
def current(self) -> tuple[str | None, SsidState]:
520520
"""[summary].
521521
522522
Returns:
523-
tuple[Optional[str], SsidState]: [description]
523+
tuple[str | None, SsidState]: [description]
524524
"""
525525
# list active connections for all interfaces
526526
response = cmd(f'{self.sudo} nmcli con | grep "{self.interface}"')
@@ -583,7 +583,7 @@ class WpasupplicantWireless(WifiController):
583583

584584
_file = "/tmp/wpa_supplicant.conf"
585585

586-
def __init__(self, password: str | None, interface: Optional[str] = None) -> None:
586+
def __init__(self, password: str | None, interface: str | None = None) -> None:
587587
WifiController.__init__(self, interface=interface, password=password)
588588

589589
async def connect(self, ssid: str, password: str, timeout: float = 15) -> bool:
@@ -641,11 +641,11 @@ async def disconnect(self) -> bool:
641641
"""
642642
return False
643643

644-
def current(self) -> tuple[Optional[str], SsidState]:
644+
def current(self) -> tuple[str | None, SsidState]:
645645
"""[summary].
646646
647647
Returns:
648-
tuple[Optional[str], SsidState]: [description]
648+
tuple[str | None, SsidState]: [description]
649649
"""
650650
# get interface status
651651
response = cmd(f'{self.sudo} iwconfig "{self.interface}"')
@@ -705,7 +705,7 @@ def power(self, power: bool) -> bool:
705705
class NetworksetupWireless(WifiController):
706706
"""OS X networksetup Driver."""
707707

708-
def __init__(self, interface: Optional[str] = None) -> None:
708+
def __init__(self, interface: str | None = None) -> None:
709709
WifiController.__init__(self, interface)
710710

711711
async def connect(self, ssid: str, password: str, timeout: float = 15) -> bool:
@@ -779,20 +779,26 @@ async def disconnect(self) -> bool:
779779
"""
780780
return False
781781

782-
def current(self) -> tuple[Optional[str], SsidState]:
783-
"""[summary].
782+
def current(self) -> tuple[str | None, SsidState]:
783+
"""Get the currently connected SSID if there is one.
784784
785785
Returns:
786-
tuple[Optional[str], SsidState]: [description]
786+
tuple[str | None, SsidState]: (SSID or None if not connected, SSID state)
787787
"""
788788
# attempt to get current network
789-
response = cmd(f"networksetup -getairportnetwork '{self.interface}'")
790-
791-
# parse response
792-
phrase = "Current Wi-Fi Network: "
793-
if phrase in response:
794-
return (response.replace("Current Wi-Fi Network: ", "").strip(), SsidState.CONNECTED)
795-
return (None, SsidState.DISCONNECTED)
789+
ssid: str | None = None
790+
# On MacOS <= 14...
791+
if int(platform.mac_ver()[0].split(".")[0]) <= 14:
792+
try:
793+
if "Current Wi-Fi Network: " in (output := cmd(f"networksetup -getairportnetwork {self.interface}")):
794+
ssid = output.replace("Current Wi-Fi Network: ", "").strip()
795+
except Exception as e:
796+
pass
797+
# For current MacOs versions or if above failed
798+
if match := re.search(r"\n\s+SSID : ([\x20-\x7E]{1,32})", cmd(f"ipconfig getsummary {self.interface}")):
799+
ssid = match.group(1)
800+
801+
return (ssid, SsidState.CONNECTED) if ssid else (None, SsidState.DISCONNECTED)
796802

797803
def available_interfaces(self) -> list[str]:
798804
"""Return a list of available Wifi Interface strings
@@ -875,9 +881,9 @@ class NetshWireless(WifiController):
875881
</MacRandomization>
876882
</WLANProfile>"""
877883

878-
def __init__(self, interface: Optional[str] = None) -> None:
884+
def __init__(self, interface: str | None = None) -> None:
879885
WifiController.__init__(self, interface)
880-
self.ssid: Optional[str] = None
886+
self.ssid: str | None = None
881887

882888
def __del__(self) -> None:
883889
self._clean(self.ssid)
@@ -957,7 +963,7 @@ async def disconnect(self) -> bool:
957963

958964
return bool("completed successfully" in response.lower())
959965

960-
def current(self) -> tuple[Optional[str], SsidState]:
966+
def current(self) -> tuple[str | None, SsidState]:
961967
"""Get the current network SSID and state.
962968
963969
# Here is an example of what we are parsing (i.e. to find FunHouse SSID):
@@ -969,7 +975,7 @@ def current(self) -> tuple[Optional[str], SsidState]:
969975
# SSID : FunHouse
970976
971977
Returns:
972-
tuple[Optional[str], SsidState]: Tuple of (ssid, network_state)
978+
tuple[str | None, SsidState]: Tuple of (ssid, network_state)
973979
"""
974980

975981
class ParseState(Enum):
@@ -981,8 +987,8 @@ class ParseState(Enum):
981987

982988
response = cmd("netsh wlan show interfaces")
983989
parse_state = ParseState.PARSE_INTERFACE
984-
ssid: Optional[str] = None
985-
network_state: Optional[str] = None
990+
ssid: str | None = None
991+
network_state: str | None = None
986992
for field in response.split("\r\n"):
987993
if parse_state is ParseState.PARSE_INTERFACE:
988994
if "Name" in field and self.interface in field:
@@ -1053,11 +1059,11 @@ def power(self, power: bool) -> bool:
10531059
return "not exist" not in response
10541060

10551061
@staticmethod
1056-
def _clean(ssid: Optional[str]) -> None:
1062+
def _clean(ssid: str | None) -> None:
10571063
"""Disconnect and delete SSID profile.
10581064
10591065
Args:
1060-
ssid (Optional[str]): name of SSID
1066+
ssid (str | None): name of SSID
10611067
"""
10621068
cmd("netsh wlan disconnect")
10631069
if ssid is not None:

0 commit comments

Comments
 (0)