mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-05-09 14:18:00 +08:00
cf7c8b3797
* updated distance measurement script. - automatically shows which ids are available - filter out big jumps - plot_summary.py shows distance over time with recording regions * Adding config file for radiomaster pocket joystick ble/usb (#108) * added radiomaster tx16s xml (#123) Co-authored-by: Wiebe van der Knaap <wkvanderknaap@tudelft.nl> * Fix joystick device argument parsed as single token in control panel sessions (#118) The `-d 0` joystick device flag was passed as a single `flag` attribute, causing the joystick program to receive it as one token instead of two separate arguments. This prevented the device number from being recognized, breaking joystick input in the Simulation - Gazebo and Flight UDP sessions. Fixed by splitting into `<arg flag="-d" constant="0"/>`. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fixed names of variables and resolution bugs, added documentation (#113) Co-authored-by: macoman <macoman@student.tudelft.nl> * Update Gazebo Models: Gate, Plants, Logo * Added some (math) tests (#114) * added a test for paparazzi's math librarie's int sqrt function and int quaternion normalization function * Keep essential tests Reduced the number of tests planned from 9 to 6 and removed tests for int32_sqrt. --------- Co-authored-by: LSSchef <l.s.scheffer@student.tudelft.nl> Co-authored-by: AniketBehura <aniketbehura1023@gmail.com> Co-authored-by: diaa <D.abbasi@student.tudelft.nl> * Feat: readme update for submodule installation (#115) * feat: readme update for submodule installation * Rename README to README.md --------- Co-authored-by: Christophe De Wagter <dewagter@gmail.com> --------- Co-authored-by: robinferede <robinferede@tudelft.nl> Co-authored-by: Robin Euger <robin.euger@gmail.com> Co-authored-by: Wiebe van der Knaap <wkvanderknaap@tudelft.nl> Co-authored-by: EAbbenhuis <113993394+EAbbenhuis@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Mihai Coman <127535163+miki133@users.noreply.github.com> Co-authored-by: macoman <macoman@student.tudelft.nl> Co-authored-by: Swayam Kuckreja <110131770+swayamkuckreja@users.noreply.github.com> Co-authored-by: LSSchef <l.s.scheffer@student.tudelft.nl> Co-authored-by: AniketBehura <aniketbehura1023@gmail.com> Co-authored-by: diaa <D.abbasi@student.tudelft.nl> Co-authored-by: Douwe-Rijs <Douwe@standofl.nl>
194 lines
5.9 KiB
Python
Executable File
194 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import time
|
|
from datetime import datetime
|
|
import sys
|
|
import os
|
|
import math
|
|
import socket
|
|
import struct
|
|
import argparse
|
|
import matplotlib.pyplot as plt
|
|
|
|
PPRZ_HOME = os.getenv("PAPARAZZI_HOME", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../')))
|
|
sys.path.append(PPRZ_HOME + "/sw/ground_segment/python/natnet3.x")
|
|
|
|
from NatNetClient import NatNetClient
|
|
|
|
|
|
def discover_motive(multicast="239.255.42.99", data_port=1511, timeout=3):
|
|
"""Discover a Motive server by listening for NatNet multicast data."""
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
sock.settimeout(timeout)
|
|
sock.bind(('', data_port))
|
|
|
|
# Join the NatNet multicast group
|
|
mreq = struct.pack('4sL', socket.inet_aton(multicast), socket.INADDR_ANY)
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
|
|
try:
|
|
_, addr = sock.recvfrom(4096)
|
|
sock.close()
|
|
return addr[0]
|
|
except socket.timeout:
|
|
sock.close()
|
|
return None
|
|
|
|
# Global state
|
|
recording = False
|
|
pos_x, pos_y, pos_z = 0.0, 0.0, 0.0
|
|
track_id = None
|
|
frame_count = 0
|
|
seen_ids = set()
|
|
|
|
|
|
def on_press(event):
|
|
global recording
|
|
if event.key == 'r':
|
|
recording = not recording
|
|
if event.key == 'q':
|
|
plt.close()
|
|
|
|
|
|
def receive_rigid_body_frame(rigid_body_data, stamp):
|
|
global pos_x, pos_y, pos_z, frame_count, seen_ids
|
|
frame_count += 1
|
|
for rb in rigid_body_data.rigid_body_list:
|
|
seen_ids.add(rb.id_num)
|
|
if track_id is not None and rb.id_num != track_id:
|
|
continue
|
|
pos_x = rb.pos[0]
|
|
pos_y = rb.pos[1]
|
|
pos_z = rb.pos[2]
|
|
|
|
|
|
def main(args):
|
|
global track_id
|
|
|
|
# Discover or use provided server IP
|
|
if args.server:
|
|
server_ip = args.server
|
|
print(f"Using provided server IP: {server_ip}")
|
|
else:
|
|
print("Discovering Motive server on the network...")
|
|
server_ip = discover_motive()
|
|
if server_ip is None:
|
|
print("ERROR: No Motive server found. Check that Motive is running and streaming.")
|
|
print("You can also specify the IP manually with --server <ip>")
|
|
return
|
|
print(f"Found Motive server at {server_ip}")
|
|
|
|
client = NatNetClient()
|
|
client.server_ip_address = server_ip
|
|
client.local_ip_address = "0.0.0.0"
|
|
client.set_print_level(0)
|
|
client.rigid_body_list_listener = receive_rigid_body_frame
|
|
client.run()
|
|
|
|
print("Waiting for rigid body data...")
|
|
time.sleep(3)
|
|
|
|
if frame_count == 0:
|
|
print("ERROR: No data received. Check that Motive is streaming.")
|
|
client.shutdown()
|
|
return
|
|
|
|
print(f"Receiving data: {frame_count} frames, rigid body IDs: {sorted(seen_ids)}")
|
|
|
|
# Pick which rigid body to track
|
|
if len(seen_ids) == 1:
|
|
track_id = list(seen_ids)[0]
|
|
print(f"Auto-selected ID {track_id}")
|
|
else:
|
|
while True:
|
|
choice = input(f"Enter rigid body ID to track {sorted(seen_ids)}: ").strip()
|
|
try:
|
|
choice = int(choice)
|
|
if choice in seen_ids:
|
|
track_id = choice
|
|
break
|
|
print(f"ID {choice} not available.")
|
|
except ValueError:
|
|
print("Please enter a valid integer.")
|
|
|
|
print(f"Tracking ID {track_id}. Press r to record, q to quit.")
|
|
|
|
# Output file with timestamp and rigid body ID
|
|
if args.outputfile:
|
|
output_path = args.outputfile
|
|
else:
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
output_path = f"dist_rb{track_id}_{timestamp}.csv"
|
|
file = open(output_path, 'w')
|
|
file.write('time, distance, x, y, z, recording\n')
|
|
print(f"Writing data to {output_path}")
|
|
|
|
# Setup plot
|
|
fig = plt.figure()
|
|
plt.axis([-6, 6, -6, 6])
|
|
fig.canvas.mpl_connect('key_press_event', on_press)
|
|
|
|
old_x = pos_x
|
|
old_z = pos_z
|
|
distance = 0
|
|
glitch_count = 0
|
|
start_time = time.time()
|
|
pre_time = time.time()
|
|
freq_count = 0
|
|
freq_time = time.time()
|
|
data_freq = 0.0
|
|
|
|
while plt.fignum_exists(fig.number):
|
|
h = math.hypot(pos_x - old_x, pos_z - old_z)
|
|
|
|
if h > 1.0:
|
|
# Glitch: jump too large, ignore but update position
|
|
glitch_count += 1
|
|
old_x = pos_x
|
|
old_z = pos_z
|
|
elif h > 0.10:
|
|
if recording:
|
|
distance += h
|
|
old_x = pos_x
|
|
old_z = pos_z
|
|
|
|
# Track data frequency
|
|
freq_count += 1
|
|
now = time.time()
|
|
if now - freq_time >= 1.0:
|
|
data_freq = freq_count / (now - freq_time)
|
|
freq_count = 0
|
|
freq_time = now
|
|
|
|
if now - pre_time > 0.1:
|
|
current_time = now - start_time
|
|
print(f'distance: {distance:.2f} m; time: {current_time:.2f} s; freq: {data_freq:.0f} Hz; recording: {recording}')
|
|
pre_time = now
|
|
|
|
data = f'{current_time}, {distance}, {pos_x}, {pos_y}, {pos_z}, {recording}\n'
|
|
file.write(data)
|
|
|
|
rec_str = "RECORDING" if recording else "NOT RECORDING"
|
|
plt.title(f'Distance: {distance:.2f} m | {data_freq:.0f} Hz | {rec_str}\nPress r to record, q to quit')
|
|
|
|
if recording:
|
|
plt.plot(pos_z, pos_x, 'ro')
|
|
else:
|
|
plt.plot(pos_z, pos_x, 'bo')
|
|
|
|
plt.pause(0.001)
|
|
|
|
client.shutdown()
|
|
file.close()
|
|
print(f"Total distance: {distance:.2f} m ({glitch_count} glitches ignored)")
|
|
print(f"Data saved to {output_path}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description="Distance counter using Optitrack NatNet stream")
|
|
parser.add_argument('--server', default=None, help="Motive server IP (auto-discovers if not set)")
|
|
parser.add_argument('--outputfile', type=str, default=None, help="CSV output file (default: dist_rb<id>_<timestamp>.csv)")
|
|
args = parser.parse_args()
|
|
|
|
main(args)
|