124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Probe — does the G1 audio multicast actually deliver packets?
|
|
|
|
Run on the Jetson:
|
|
/home/unitree/miniconda3/envs/gemini_sdk/bin/python ~/Marcus/Voice/_probe_multicast.py
|
|
|
|
This is a STRIPPED-DOWN version of Voice/audio_io.py::BuiltinMic.start()
|
|
plus a 5-second packet counter. Tells us in plain numbers whether the G1
|
|
audio service is publishing on 239.168.123.161:5555 — independent of any
|
|
Marcus state. Prints:
|
|
|
|
interfaces with 192.168.123.x: [list]
|
|
chosen local_ip: x.x.x.x
|
|
joined multicast 239.168.123.161:5555 ✓
|
|
packets received in 5s: N
|
|
total bytes received: M
|
|
first packet bytes (hex preview): ...
|
|
|
|
If packets=0 → G1 audio service isn't publishing OR Jetson can't reach
|
|
the multicast group. Either way Marcus's voice can never work until
|
|
THIS probe shows packets > 0.
|
|
"""
|
|
import socket
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
|
|
GROUP = "239.168.123.161"
|
|
PORT = 5555
|
|
|
|
|
|
def find_g1_ips() -> list:
|
|
"""Return all local IPs in 192.168.123.0/24 (the G1 internal subnet)."""
|
|
out = subprocess.run(
|
|
["ip", "-4", "-o", "addr"], capture_output=True, text=True,
|
|
).stdout
|
|
found = []
|
|
for line in out.splitlines():
|
|
for tok in line.split():
|
|
if tok.startswith("192.168.123."):
|
|
found.append(tok.split("/")[0])
|
|
return found
|
|
|
|
|
|
def main() -> int:
|
|
ips = find_g1_ips()
|
|
print(f" interfaces with 192.168.123.x: {ips or '(none — Jetson NOT on G1 subnet)'}")
|
|
if not ips:
|
|
print(" ✗ Cannot proceed — no 192.168.123.x interface on this host.")
|
|
print(" The G1 audio multicast only delivers to that subnet.")
|
|
print(" Check `ip addr show` and reconnect to the G1's network.")
|
|
return 1
|
|
|
|
local_ip = ips[0]
|
|
print(f" chosen local_ip: {local_ip}")
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
try:
|
|
sock.bind(("", PORT))
|
|
except Exception as e:
|
|
print(f" ✗ bind on UDP port {PORT} failed: {e}")
|
|
return 2
|
|
|
|
mreq = struct.pack(
|
|
"4s4s",
|
|
socket.inet_aton(GROUP),
|
|
socket.inet_aton(local_ip),
|
|
)
|
|
try:
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
except Exception as e:
|
|
print(f" ✗ multicast join failed: {e}")
|
|
return 3
|
|
sock.settimeout(0.5)
|
|
print(f" joined multicast {GROUP}:{PORT} ✓")
|
|
print(f" listening for 5 seconds...")
|
|
|
|
deadline = time.time() + 5.0
|
|
pkt_count = 0
|
|
byte_count = 0
|
|
first_pkt = None
|
|
while time.time() < deadline:
|
|
try:
|
|
data, addr = sock.recvfrom(4096)
|
|
except socket.timeout:
|
|
continue
|
|
except Exception as e:
|
|
print(f" ✗ recvfrom error: {e}")
|
|
break
|
|
pkt_count += 1
|
|
byte_count += len(data)
|
|
if first_pkt is None:
|
|
first_pkt = data
|
|
|
|
sock.close()
|
|
|
|
print(f" packets received in 5s: {pkt_count}")
|
|
print(f" total bytes received: {byte_count}")
|
|
if first_pkt is not None:
|
|
preview = first_pkt[:24].hex(" ")
|
|
print(f" first packet bytes (hex preview): {preview} ({len(first_pkt)} bytes total)")
|
|
|
|
if pkt_count == 0:
|
|
print()
|
|
print(" ✗ ZERO packets received. Diagnosis:")
|
|
print(" - G1's audio service is not publishing, OR")
|
|
print(" - This Jetson is on the right subnet but a firewall or")
|
|
print(" switch is blocking multicast (rare on G1's setup).")
|
|
print(" Fix: restart the G1 audio service or reboot the robot.")
|
|
return 4
|
|
|
|
print()
|
|
print(f" ✓ Multicast IS delivering ({pkt_count} packets / {byte_count} bytes in 5s).")
|
|
print(f" Expected for 16 kHz mono int16: ~{int(5*16000*2/byte_count*pkt_count)} packets/s if continuous")
|
|
print(f" If audio is non-zero too → the issue is somewhere in the runner; not the network.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|