mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-05-09 14:18:00 +08:00
MAVLab Course2026 Updates (p1) (#3632)
* 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>
This commit is contained in:
committed by
GitHub
parent
84adc6f1e2
commit
cf7c8b3797
@@ -0,0 +1,62 @@
|
||||
<joystick>
|
||||
|
||||
<!--
|
||||
RadioMaster TX16S (OpenTX/EdgeTX) — Paparazzi UAV joystick configuration
|
||||
|
||||
Axis mapping (verify with: ./sw/ground_segment/joystick/test_stick):
|
||||
Axis 0 : Roll — right stick X
|
||||
Axis 1 : Pitch — right stick Y
|
||||
Axis 2 : Throttle — left stick Y
|
||||
Axis 3 : Yaw — left stick X
|
||||
Axis 5 : Mode switch — 3-position (SA/SB): low / mid / high
|
||||
Axis 6 : Arm switch — 2-position (SF/SE): 0 = disarm, ~1024 = arm
|
||||
|
||||
TX16S switch axes output 0 / ~1024 / ~2047 (unsigned), which input2ivy
|
||||
scales to -127 / 0 / +127 internally.
|
||||
|
||||
Pitch and throttle are inverted relative to Paparazzi convention — hence
|
||||
the minus signs in the RC_UP field values below.
|
||||
-->
|
||||
|
||||
<input>
|
||||
<!-- deadband: eliminates stick drift at centre (range 0-127) -->
|
||||
<!-- exponent: finer control near centre, faster at extremes (range 0-1.0) -->
|
||||
<axis index="0" name="roll" deadband="10" exponent="0.5" limit="1.0" trim="0"/>
|
||||
<axis index="1" name="pitch" deadband="10" exponent="0.5" limit="1.0" trim="0"/>
|
||||
<axis index="2" name="throttle" deadband="5" exponent="0.0" limit="1.0" trim="0"/>
|
||||
<axis index="3" name="yaw" deadband="10" exponent="0.5" limit="1.0" trim="0"/>
|
||||
<axis index="5" name="mode_sw"/>
|
||||
<axis index="6" name="arm_sw"/>
|
||||
</input>
|
||||
|
||||
<variables>
|
||||
<var name="mode" default="0"/>
|
||||
</variables>
|
||||
|
||||
<messages period="0.05">
|
||||
|
||||
<message class="datalink" name="RC_UP" send_always="true">
|
||||
<field name="channels"
|
||||
value="roll;
|
||||
-pitch;
|
||||
yaw;
|
||||
Fit(-throttle,-127,127,0,127);
|
||||
Fit(mode_sw,-127,127,0,2)"/>
|
||||
</message>
|
||||
|
||||
<!-- Arm switch: arm_sw > -60 = armed, arm_sw < -60 = disarmed/killed -->
|
||||
<message class="ground" name="DL_SETTING"
|
||||
on_event="arm_sw > -60">
|
||||
<field name="index" value="IndexOfSetting('autopilot.kill_throttle')"/>
|
||||
<field name="value" value="0"/>
|
||||
</message>
|
||||
|
||||
<message class="ground" name="DL_SETTING"
|
||||
on_event="arm_sw < -60">
|
||||
<field name="index" value="IndexOfSetting('autopilot.kill_throttle')"/>
|
||||
<field name="value" value="1"/>
|
||||
</message>
|
||||
|
||||
</messages>
|
||||
|
||||
</joystick>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!--
|
||||
RadioMaster pocket Transmitter - Used as Bluetooth Joystick
|
||||
|
||||
The RadioMaster Pocket is a versatile 32CH radio transmitter with Bluetooth capability.
|
||||
This configuration assumes no modification were done with jstest-gtk
|
||||
|
||||
In this file we will use it as a 6CH joystick to control a UAS.
|
||||
- The left stick vertical axis will be used for throttle
|
||||
- The right stick horizontal axis will be used for roll
|
||||
- The right stick vertical axis will be used for pitch
|
||||
- The left stick horizontal axis will be used for yaw
|
||||
- The VRA axis will be used for arming (left top switch)
|
||||
- The VRC axis will be used for mode switch (right top switch)
|
||||
|
||||
If you want to fly your UAS via the joystick add this to your session:
|
||||
|
||||
/home/username/paparazzi/sw/ground_segment/joystick/input2ivy -d 0 -ac yourairframe_name radiomaster_pocket_ble.xml
|
||||
|
||||
Where -d 0 must be -d 1 if you have a laptop with accelerometer installed
|
||||
|
||||
The basis of steering is the standard signs of aerospace convention
|
||||
-->
|
||||
|
||||
<joystick>
|
||||
<input>
|
||||
<axis index="4" name="LeftStickHorizontal"/>
|
||||
<axis index="3" name="LeftStickVertical"/>
|
||||
<axis index="0" name="RightStickHorizontal"/>
|
||||
<axis index="1" name="RightStickVertical"/>
|
||||
<!-- VRA is SA and VRC is SC switch -->
|
||||
<axis index="2" name="VRA"/>
|
||||
<axis index="6" name="VRC"/>
|
||||
</input>
|
||||
|
||||
<!-- Follow the order of rc_datalink.h -->
|
||||
<messages period="0.05">
|
||||
<message class="datalink" name="RC_UP" send_always="true">
|
||||
<field name="channels" value="RightStickHorizontal;-RightStickVertical;LeftStickHorizontal;Fit(LeftStickVertical,-127,127,0,127);VRC;VRA"/>
|
||||
</message>
|
||||
</messages>
|
||||
|
||||
</joystick>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!--
|
||||
RadioMaster pocket Transmitter - Used as USB Joystick
|
||||
|
||||
The RadioMaster Pocket is a versatile 32CH radio transmitter.
|
||||
This configuration assumes no modification were done with jstest-gtk
|
||||
|
||||
In this file we will use it as a 6CH joystick to control a UAS.
|
||||
- The left stick vertical axis will be used for throttle
|
||||
- The right stick horizontal axis will be used for roll
|
||||
- The right stick vertical axis will be used for pitch
|
||||
- The left stick horizontal axis will be used for yaw
|
||||
- The VRA axis will be used for arming (left top switch)
|
||||
- The VRC axis will be used for mode switch (right top switch)
|
||||
|
||||
If you want to fly your UAS via the joystick add this to your session:
|
||||
|
||||
/home/username/paparazzi/sw/ground_segment/joystick/input2ivy -d 0 -ac yourairframe_name radiomaster_pocket_usb.xml
|
||||
|
||||
Where -d 0 must be -d 1 if you have a laptop with accelerometer installed
|
||||
|
||||
The basis of steering is the standard signs of aerospace convention
|
||||
-->
|
||||
|
||||
<joystick>
|
||||
<input>
|
||||
<axis index="3" name="LeftStickHorizontal"/>
|
||||
<axis index="2" name="LeftStickVertical"/>
|
||||
<axis index="0" name="RightStickHorizontal"/>
|
||||
<axis index="1" name="RightStickVertical"/>
|
||||
<!-- VRA is SA and VRC is SC switch -->
|
||||
<axis index="4" name="VRA"/>
|
||||
<axis index="6" name="VRC"/>
|
||||
</input>
|
||||
|
||||
<!-- Follow the order of rc_datalink.h -->
|
||||
<messages period="0.05">
|
||||
<message class="datalink" name="RC_UP" send_always="true">
|
||||
<field name="channels" value="RightStickHorizontal;-RightStickVertical;LeftStickHorizontal;Fit(LeftStickVertical,-127,127,0,127);VRC;VRA"/>
|
||||
</message>
|
||||
</messages>
|
||||
|
||||
</joystick>
|
||||
+96
-20
@@ -6,9 +6,9 @@
|
||||
Compile and control the settings of the Bebop front and bottom cameras.
|
||||
</description>
|
||||
<section name="Front camera" prefix="MT9F002_">
|
||||
<define name="RESOLUTION" value="0" description="Preset image resolutions, 0 = VGA, 1 = 720p, 2 = 720p 4:3 aspect ratio, 3 = 1080p, 4 = 1080p 4:3 aspect ratio, 5 = 2048*2048, this is the max dimention allowable with isp"/>
|
||||
<define name="RESOLUTION" value="0" description="Preset image resolutions, 0 = VGA, 1 = 720p, 2 = 720p 4:3 aspect ratio, 3 = 1080p, 4 = 1080p 4:3 aspect ratio, 5 = 2048*2048, this is the max dimension allowable with isp"/>
|
||||
<define name="OUTPUT_WIDTH" value="640" description="Image horizontal resolution used if MT9F002_RESOLUTION not defined"/>
|
||||
<define name="OUTPUT_HEIGHT" value="640" description="Image vertical resolution used if MT9F002_RESOLUTION not defined"/>
|
||||
<define name="OUTPUT_HEIGHT" value="480" description="Image vertical resolution used if MT9F002_RESOLUTION not defined"/>
|
||||
<define name="OFFSET_X" value="0" description="Signed fractional offset from centre of image of original sensor [-0.5,0.5]"/>
|
||||
<define name="OFFSET_Y" value="0" description="Signed fractional offset from centre of image of original sensor [-0.5,0.5]"/>
|
||||
<define name="ZOOM" value="1" description="Zoom factor of image"/>
|
||||
@@ -18,8 +18,8 @@
|
||||
<define name="GAIN_GREEN2" value="2" description="Green gain"/>
|
||||
<define name="GAIN_RED" value="1.4" description="Red gain"/>
|
||||
<define name="GAIN_BLUE" value="2.7" description="Blue gain"/>
|
||||
<define name="FOCAL_X" value="0.48686" description="Focal length in the x-direction in pixels"/>
|
||||
<define name="OFFSET_X" value="0.48908" description="Focal length in the y-direction in pixels"/>
|
||||
<define name="FOCAL_X" value="0.48686" description="Normalized focal length in x-direction (fraction of image width)"/>
|
||||
<define name="FOCAL_Y" value="0.48908" description="Normalized focal length in y-direction (fraction of image width)"/>
|
||||
<define name="CENTER_X" value="0.51015" description="Center image coordinate in the x-direction"/>
|
||||
<define name="CENTER_Y" value="0.51015" description="Center image coordinate in the y-direction"/>
|
||||
<define name="DHANE_K" value="1.25" description="(Un)Distortion parameter for a fish-eye lens"/>
|
||||
@@ -27,60 +27,136 @@
|
||||
|
||||
<section name="Bottom camera" prefix="MT9V117_">
|
||||
<define name="TARGET_FPS" value="30" description="Desired frame rate"/>
|
||||
<define name="FOCAL_X" value="0.48686" description="Focal length in the x-direction in pixels"/>
|
||||
<define name="OFFSET_X" value="0.48908" description="Focal length in the y-direction in pixels"/>
|
||||
<define name="FOCAL_X" value="0.48686" description="Normalized focal length in x-direction (fraction of image width)"/>
|
||||
<define name="FOCAL_Y" value="0.48908" description="Normalized focal length in y-direction (fraction of image width)"/>
|
||||
<define name="CENTER_X" value="0.51015" description="Center image coordinate in the x-direction"/>
|
||||
<define name="CENTER_Y" value="0.51015" description="Center image coordinate in the y-direction"/>
|
||||
<define name="DHANE_K" value="1.25" description="(Un)Distortion parameter for a fish-eye lens"/>
|
||||
</section>
|
||||
</doc>
|
||||
|
||||
<!-- ================= Camera runtime settings ================= -->
|
||||
<!-- These settings appear in the Paparazzi Ground Control Station
|
||||
and allow adjusting camera parameters during flight. -->
|
||||
<settings>
|
||||
<dl_settings NAME="Bebop camera control">
|
||||
|
||||
<!-- Top-level group shown in the settings tree -->
|
||||
<dl_settings name="Bebop camera control">
|
||||
|
||||
<!-- Settings for the Bebop FRONT camera (MT9F002 sensor) -->
|
||||
<dl_settings name="Front camera">
|
||||
<dl_setting var="mt9f002.set_zoom" min="1." step="0.05" max="2.5" shortname="zoom" param="MT9F002_ZOOM"/>
|
||||
<dl_setting var="mt9f002.set_offset_x" min="-0.5" step="0.05" max="0.5" shortname="offset_x" param="MT9F002_OFFSET_X"/>
|
||||
<dl_setting var="mt9f002.set_offset_y" min="-0.5" step="0.05" max="0.5" shortname="offset_y" param="MT9F002_OFFSET_Y"/>
|
||||
<dl_setting var="mt9f002_send_resolution" min="1" step="1" max="1" values="SEND" shortname="update_resolution" module="boards/bebop/mt9f002" handler="setting_update_resolution"/>
|
||||
<dl_setting var="mt9f002.gain_green1" min="1" step="0.1" max="60" shortname="green_1" param="MT9F002_GAIN_GREEN1"/>
|
||||
<dl_setting var="mt9f002.gain_green2" min="1" step="0.1" max="60" shortname="green_2" param="MT9F002_GAIN_GREEN2"/>
|
||||
<dl_setting var="mt9f002.gain_blue" min="1" step="0.1" max="60" shortname="blue" param="MT9F002_GAIN_BLUE"/>
|
||||
<dl_setting var="mt9f002.gain_red" min="1" step="0.1" max="60" shortname="red" param="MT9F002_GAIN_RED"/>
|
||||
<dl_setting var="mt9f002_send_color" min="1" step="1" max="1" values="SEND" shortname="update_color" module="boards/bebop/mt9f002" handler="setting_update_color"/>
|
||||
<dl_setting var="mt9f002.target_exposure " min="0.1" step="0.1" max="80" shortname="exposure" param="MT9F002_TARGET_EXPOSURE"/>
|
||||
<dl_setting var="mt9f002_send_exposure" min="1" step="1" max="1" values="SEND" shortname="update_exposure" module="boards/bebop/mt9f002" handler="setting_update_exposure"/>
|
||||
|
||||
<!-- Digital zoom factor applied by the ISP -->
|
||||
<dl_setting var="mt9f002.set_zoom" min="1." step="0.05" max="2.5"
|
||||
shortname="zoom" param="MT9F002_ZOOM"/>
|
||||
|
||||
<!-- Horizontal crop offset relative to image center
|
||||
Range [-0.5, 0.5] corresponds to fraction of sensor width -->
|
||||
<dl_setting var="mt9f002.set_offset_x" min="-0.5" step="0.05" max="0.5"
|
||||
shortname="offset_x" param="MT9F002_OFFSET_X"/>
|
||||
|
||||
<!-- Vertical crop offset relative to image center -->
|
||||
<dl_setting var="mt9f002.set_offset_y" min="-0.5" step="0.05" max="0.5"
|
||||
shortname="offset_y" param="MT9F002_OFFSET_Y"/>
|
||||
|
||||
<!-- Trigger command to send updated resolution to the camera driver -->
|
||||
<dl_setting var="mt9f002_send_resolution" min="1" step="1" max="1"
|
||||
values="SEND" shortname="update_resolution"
|
||||
module="boards/bebop/mt9f002"
|
||||
handler="setting_update_resolution"/>
|
||||
|
||||
<!-- White balance gains for Bayer channels -->
|
||||
<dl_setting var="mt9f002.gain_green1" min="1" step="0.1" max="60"
|
||||
shortname="green_1" param="MT9F002_GAIN_GREEN1"/>
|
||||
|
||||
<dl_setting var="mt9f002.gain_green2" min="1" step="0.1" max="60"
|
||||
shortname="green_2" param="MT9F002_GAIN_GREEN2"/>
|
||||
|
||||
<dl_setting var="mt9f002.gain_blue" min="1" step="0.1" max="60"
|
||||
shortname="blue" param="MT9F002_GAIN_BLUE"/>
|
||||
|
||||
<dl_setting var="mt9f002.gain_red" min="1" step="0.1" max="60"
|
||||
shortname="red" param="MT9F002_GAIN_RED"/>
|
||||
|
||||
<!-- Send updated color gain values to the camera hardware -->
|
||||
<dl_setting var="mt9f002_send_color" min="1" step="1" max="1"
|
||||
values="SEND" shortname="update_color"
|
||||
module="boards/bebop/mt9f002"
|
||||
handler="setting_update_color"/>
|
||||
|
||||
<!-- Target exposure level used by the automatic exposure control -->
|
||||
<dl_setting var="mt9f002.target_exposure" min="0.1" step="0.1" max="80"
|
||||
shortname="exposure" param="MT9F002_TARGET_EXPOSURE"/>
|
||||
|
||||
<!-- Apply the new exposure setting to the camera -->
|
||||
<dl_setting var="mt9f002_send_exposure" min="1" step="1" max="1"
|
||||
values="SEND" shortname="update_exposure"
|
||||
module="boards/bebop/mt9f002"
|
||||
handler="setting_update_exposure"/>
|
||||
|
||||
</dl_settings>
|
||||
</dl_settings>
|
||||
</settings>
|
||||
|
||||
|
||||
<!-- ================= Module dependencies ================= -->
|
||||
<!-- Requires I2C communication for the sensors and the
|
||||
video processing thread for image streaming -->
|
||||
<dep>
|
||||
<depends>i2c,video_thread</depends>
|
||||
</dep>
|
||||
|
||||
|
||||
<!-- ================= Header files ================= -->
|
||||
<!-- Camera driver interfaces used by this module -->
|
||||
<header>
|
||||
<file name="mt9v117.h"/>
|
||||
<file name="mt9f002.h"/>
|
||||
<file name="mt9v117.h"/> <!-- bottom camera sensor -->
|
||||
<file name="mt9f002.h"/> <!-- front camera sensor -->
|
||||
</header>
|
||||
|
||||
|
||||
<!-- ================= Module initialization ================= -->
|
||||
<!-- Called during autopilot startup to initialize sensors -->
|
||||
<init fun="mt9v117_init(PTR(mt9v117))"/>
|
||||
<init fun="mt9f002_init(PTR(mt9f002))"/>
|
||||
|
||||
|
||||
<!-- ================= Build rules for autopilot target ================= -->
|
||||
<makefile target="ap">
|
||||
|
||||
<!-- Camera driver implementations -->
|
||||
<file name="mt9v117.c"/>
|
||||
<file name="mt9f002.c"/>
|
||||
|
||||
<!-- Bebop image signal processor (ISP) library -->
|
||||
<file name="libisp.c" dir="boards/bebop/isp"/>
|
||||
|
||||
<!-- Size of I2C communication buffer -->
|
||||
<define name="I2C_BUF_LEN" value="56"/>
|
||||
|
||||
<!-- Use Bebop I2C bus 0 -->
|
||||
<define name="USE_I2C0"/>
|
||||
|
||||
<!-- Also define for test builds -->
|
||||
<test>
|
||||
<define name="USE_I2C0"/>
|
||||
</test>
|
||||
|
||||
</makefile>
|
||||
|
||||
|
||||
<!-- ================= Build rules for NPS simulator ================= -->
|
||||
<makefile target="nps">
|
||||
|
||||
<!-- Simulated camera drivers -->
|
||||
<file name="mt9v117_nps.c"/>
|
||||
<file name="mt9f002_nps.c"/>
|
||||
|
||||
<!-- Same ISP library used for simulation -->
|
||||
<file name="libisp.c" dir="boards/bebop/isp"/>
|
||||
|
||||
<define name="I2C_BUF_LEN" value="56"/>
|
||||
<define name="USE_I2C0"/>
|
||||
|
||||
</makefile>
|
||||
</module>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<program name="Joystick" command="sw/ground_segment/joystick/input2ivy">
|
||||
<arg flag="-ac" constant="@AIRCRAFT"/>
|
||||
<arg flag="sm600.xml"/>
|
||||
<arg flag="-d 0"/>
|
||||
<arg flag="-d" constant="0"/>
|
||||
</program>
|
||||
<program name="Gazebo"/>
|
||||
<program name="RtpViewer">
|
||||
@@ -65,7 +65,7 @@
|
||||
<program name="Joystick" command="sw/ground_segment/joystick/input2ivy">
|
||||
<arg flag="-ac" constant="@AIRCRAFT"/>
|
||||
<arg flag="sm600.xml"/>
|
||||
<arg flag="-d 0"/>
|
||||
<arg flag="-d" constant="0"/>
|
||||
</program>
|
||||
<!--program name="NatNet3">
|
||||
<arg flag="-ac 9999" constant="@AC_ID"/>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This directory contains libraries and tools in external git repositories used in paparazzi.
|
||||
@@ -0,0 +1,214 @@
|
||||
# External Libraries and Tools
|
||||
|
||||
This directory contains git submodules referencing external libraries and tools used by Paparazzi. These modules provide essential functionality for communications, flight control, simulation, cryptography, and more.
|
||||
|
||||
## Table of Contents
|
||||
- [Quick Start](#quick-start)
|
||||
- [Installing All Modules](#installing-all-modules)
|
||||
- [Installing Individual Modules](#installing-individual-modules)
|
||||
- [Available Modules](#available-modules)
|
||||
- [OpenCV Bebop Setup](#opencv-bebop-setup)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
Initialize and build all external modules:
|
||||
|
||||
```bash
|
||||
make -C sw/ext
|
||||
```
|
||||
|
||||
This will checkout and compile all dependencies automatically.
|
||||
|
||||
---
|
||||
|
||||
## Installing All Modules
|
||||
|
||||
To install and build all external modules at once:
|
||||
|
||||
```bash
|
||||
make -C sw/ext
|
||||
```
|
||||
|
||||
This runs the complete build chain for all modules that require compilation (chibios, fatfs, libsbp, TRICAL, hacl-c, key_generator, rustlink, ecl, matrix, mavlink, dronecan, and unifiedmocaprouter).
|
||||
|
||||
---
|
||||
|
||||
## Installing Individual Modules
|
||||
|
||||
To install or update only a specific module, run:
|
||||
|
||||
```bash
|
||||
make -C sw/ext <module-name>
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
make -C sw/ext chibios
|
||||
make -C sw/ext mavlink
|
||||
```
|
||||
|
||||
Each module target will sync and update the git submodule, then build if required.
|
||||
|
||||
---
|
||||
|
||||
## Available Modules
|
||||
|
||||
See the main [Makefile in this directory](Makefile) for the complete list of modules and their build targets. Common modules include:
|
||||
|
||||
- **chibios** — Operating system for embedded systems
|
||||
- **fatfs** — FAT filesystem library
|
||||
- **mavlink** — MAVLink message definitions and code generation
|
||||
- **pprzlink** — Paparazzi-specific communications protocol
|
||||
- **libsbp** — Swift Binary Protocol for GNSS/RTK
|
||||
- **TRICAL** — Magnetometer automatic calibration
|
||||
- **opencv_bebop** — Computer vision library (see detailed instructions below)
|
||||
- **dronecan** — DroneCAN protocol support
|
||||
- **And many more...** — See [Makefile](Makefile) for details
|
||||
|
||||
---
|
||||
|
||||
## OpenCV Bebop Setup
|
||||
|
||||
This module provides advanced computer vision capabilities for Bebop drones.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
This guide has been tested on **Ubuntu 22.04**. For Ubuntu 20.04 instructions, see: [`conf/modules/cv_opencvdemo.xml`](../../conf/modules/cv_opencvdemo.xml)
|
||||
|
||||
### Building OpenCV Bebop
|
||||
|
||||
From the paparazzi source directory:
|
||||
|
||||
```bash
|
||||
cd sw/ext
|
||||
make opencv_bebop
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
|
||||
After a successful build, you should have these directories:
|
||||
|
||||
```
|
||||
sw/ext/opencv_bebop/build_arm/
|
||||
sw/ext/opencv_bebop/build_pc/
|
||||
sw/ext/opencv_bebop/install_arm/
|
||||
sw/ext/opencv_bebop/install_pc/
|
||||
```
|
||||
|
||||
These contain compiled binaries for ARM (autopilot) and PC (simulation) targets.
|
||||
|
||||
### Configuring Your Airframe Module
|
||||
|
||||
To use OpenCV in your airframe, configure your module XML with build flags for both autopilot (`ap`) and simulation (`nps`) targets.
|
||||
|
||||
**Example airframe module configuration:**
|
||||
|
||||
```xml
|
||||
<makefile target="ap">
|
||||
<file name="opencv_example.cpp"/>
|
||||
<file name="opencv_image_functions.cpp"/>
|
||||
<flag name="CXXFLAGS" value="I$(PAPARAZZI_SRC)/sw/ext/opencv_bebop/install_arm/include/opencv4"/>
|
||||
|
||||
<flag name="LDFLAGS" value="L$(PAPARAZZI_HOME)/sw/ext/opencv_bebop/install_arm/lib"/>
|
||||
<flag name="LDFLAGS" value="lopencv_world"/>
|
||||
<flag name="LDFLAGS" value="L$(PAPARAZZI_HOME)/sw/ext/opencv_bebop/install_arm/lib/opencv4/3rdparty"/>
|
||||
<flag name="LDFLAGS" value="llibprotobuf"/>
|
||||
<flag name="LDFLAGS" value="llibjpeg-turbo"/>
|
||||
<flag name="LDFLAGS" value="llibpng"/>
|
||||
<flag name="LDFLAGS" value="llibtiff"/>
|
||||
<flag name="LDFLAGS" value="llibopenjp2"/>
|
||||
<flag name="LDFLAGS" value="lzlib"/>
|
||||
<flag name="LDFLAGS" value="lade"/>
|
||||
<flag name="LDFLAGS" value="ldl"/>
|
||||
<flag name="LDFLAGS" value="lm"/>
|
||||
<flag name="LDFLAGS" value="lpthread"/>
|
||||
<flag name="LDFLAGS" value="lrt"/>
|
||||
</makefile>
|
||||
<makefile target="nps">
|
||||
<file name="opencv_example.cpp"/>
|
||||
<file name="opencv_image_functions.cpp"/>
|
||||
|
||||
<flag name="CXXFLAGS" value="I$(PAPARAZZI_SRC)/sw/ext/opencv_bebop/install_pc/include/opencv4"/>
|
||||
|
||||
<flag name="LDFLAGS" value="L$(PAPARAZZI_HOME)/sw/ext/opencv_bebop/install_pc/lib"/>
|
||||
<flag name="LDFLAGS" value="lopencv_world"/>
|
||||
<flag name="LDFLAGS" value="L$(PAPARAZZI_HOME)/sw/ext/opencv_bebop/install_pc/lib/opencv4/3rdparty"/>
|
||||
<flag name="LDFLAGS" value="llibprotobuf"/>
|
||||
<flag name="LDFLAGS" value="lade"/>
|
||||
<flag name="LDFLAGS" value="L/usr/lib/x86_64-linux-gnu"/>
|
||||
<flag name="LDFLAGS" value="ljpeg"/>
|
||||
<flag name="LDFLAGS" value="lpng"/>
|
||||
<flag name="LDFLAGS" value="ltiff"/>
|
||||
<flag name="LDFLAGS" value="lopenjp2"/>
|
||||
<flag name="LDFLAGS" value="L/usr/lib/x86_64-linux-gnu/hdf5/serial"/>
|
||||
<flag name="LDFLAGS" value="lhdf5"/>
|
||||
<flag name="LDFLAGS" value="lcrypto"/>
|
||||
<flag name="LDFLAGS" value="lcurl"/>
|
||||
<flag name="LDFLAGS" value="lpthread"/>
|
||||
<flag name="LDFLAGS" value="lsz"/>
|
||||
<flag name="LDFLAGS" value="lz"/>
|
||||
<flag name="LDFLAGS" value="ldl"/>
|
||||
<flag name="LDFLAGS" value="lm"/>
|
||||
<flag name="LDFLAGS" value="lfreetype"/>
|
||||
<flag name="LDFLAGS" value="lharfbuzz"/>
|
||||
<flag name="LDFLAGS" value="lrt"/>
|
||||
|
||||
</makefile>
|
||||
</module>
|
||||
```
|
||||
|
||||
### Using OpenCV in C++ Code
|
||||
|
||||
Include the OpenCV headers in your C++ files:
|
||||
|
||||
```cpp
|
||||
#include "opencv_image_functions.h"
|
||||
|
||||
using namespace std;
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
using namespace cv;
|
||||
```
|
||||
|
||||
You can then use provided wrapper functions like `grayscale_opencv_to_yuv422()` in your code:
|
||||
|
||||
```cpp
|
||||
// Convert and process images
|
||||
Mat image = imread("image.jpg");
|
||||
Mat gray;
|
||||
cvtColor(image, gray, COLOR_BGR2GRAY);
|
||||
// ... your vision processing
|
||||
```
|
||||
|
||||
### Team Consistency
|
||||
|
||||
**Important:** Ensure all team members use the same build environment and configuration. Inconsistent builds can lead to compatibility issues during testing and deployment, leading to high dependency on one laptop/person.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Failures
|
||||
|
||||
- **Module not found:** Ensure you're running `make` from the paparazzi root or using `make -C sw/ext`
|
||||
- **Dependency errors:** Check that required packages are installed (see individual module documentation)
|
||||
- **Python build issues:** Some modules (mavlink, dronecan) require Python packages. See [Makefile](Makefile) for details on `MY_MAVLINKTOOLS` and `MY_DRONECANTOOLS`
|
||||
|
||||
### Submodule Issues
|
||||
|
||||
If a submodule is in a bad state, reinitialize it:
|
||||
|
||||
```bash
|
||||
# Sync and reset a specific module
|
||||
cd <paparazzi-root>
|
||||
git submodule sync sw/ext/<module-name>
|
||||
git submodule update --init --recursive sw/ext/<module-name>
|
||||
|
||||
# Or reset all modules
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
---
|
||||
Submodule sw/ext/tudelft_gazebo_models updated: c865ea02fb...2becbf6fdf
+136
-69
@@ -1,20 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/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
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
# if PAPARAZZI_HOME not set, then assume the tree containing this
|
||||
# file is a reasonable substitute
|
||||
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
|
||||
@@ -24,103 +50,144 @@ def on_press(event):
|
||||
plt.close()
|
||||
|
||||
|
||||
# This is a callback function that gets connected to the NatNet client. It is called once per rigid body per frame
|
||||
def receiveRigidBodyFrame(rigidBodyList, stamp):
|
||||
# print(rigidBodyList)
|
||||
for (id, position, quat, valid) in rigidBodyList:
|
||||
# print( "Received frame for rigid body", id )
|
||||
global pos_x, pos_y, pos_z
|
||||
global track_id
|
||||
if track_id and id != track_id:
|
||||
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
|
||||
|
||||
# print( "Received frame for rigid body", id )
|
||||
pos_x = position[0]
|
||||
pos_y = position[1]
|
||||
pos_z = position[2]
|
||||
pos_x = rb.pos[0]
|
||||
pos_y = rb.pos[1]
|
||||
pos_z = rb.pos[2]
|
||||
|
||||
|
||||
def main(args):
|
||||
global track_id
|
||||
track_id = args.id
|
||||
|
||||
global pos_x, pos_y, pos_z
|
||||
pos_x, pos_y, pos_z = 0.0, 0.0, 0.0
|
||||
# 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])
|
||||
|
||||
# add key press event
|
||||
fig.canvas.mpl_connect('key_press_event', on_press)
|
||||
|
||||
# title
|
||||
plt.title('Press r to start/stop recording \n press q to quit')
|
||||
|
||||
# This will create a new NatNet client
|
||||
streamingClient = NatNetClient(
|
||||
server=args.server,
|
||||
multicast=args.multicast,
|
||||
commandPort=args.commandPort,
|
||||
dataPort=args.dataPort,
|
||||
rigidBodyListListener=receiveRigidBodyFrame,
|
||||
version=(3,0,0,0))
|
||||
# Start up the streaming client now that the callbacks are set up.
|
||||
# This will run perpetually, and operate on a separate thread.
|
||||
streamingClient.run()
|
||||
|
||||
time.sleep(2)
|
||||
print('Start tracking')
|
||||
if args.outputfile:
|
||||
file = open(args.outputfile, 'w')
|
||||
file.write('time, distance, x, y, z, recording \n')
|
||||
|
||||
old_z = pos_z
|
||||
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)
|
||||
|
||||
h = math.hypot(pos_z - old_z, pos_x - old_x)
|
||||
|
||||
if h > 0.10:
|
||||
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_z = pos_z
|
||||
old_x = pos_x
|
||||
if time.time() - pre_time > .1:
|
||||
current_time = time.time() - start_time
|
||||
print(f'distance: {distance}; time: {current_time:.2f}; recording: {recording}')
|
||||
pre_time = time.time()
|
||||
old_z = pos_z
|
||||
|
||||
if args.outputfile:
|
||||
# data = '{}, {}, {}, {}, {}, {} \n'.format(time.time() - start_time, distance, pos_x, pos_y, pos_z, recording)
|
||||
data = f'{current_time}, {distance}, {pos_x}, {pos_y}, {pos_z}, {recording} \n'
|
||||
file.write(data)
|
||||
# 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')
|
||||
plt.title('Press r to start/stop recording \n press q to quit \n RECORDING')
|
||||
else:
|
||||
plt.plot(pos_z, pos_x, 'bo')
|
||||
plt.title('Press r to start/stop recording \n press q to quit \n NOT RECORDING')
|
||||
|
||||
plt.pause(0.001)
|
||||
# time.sleep(0.01)
|
||||
|
||||
streamingClient.stop()
|
||||
if args.outputfile:
|
||||
file.close()
|
||||
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()
|
||||
parser.add_argument('--server', default="127.0.0.1")
|
||||
parser.add_argument('--multicast', default="239.255.42.99")
|
||||
parser.add_argument('--commandPort', type=int, default=1510)
|
||||
parser.add_argument('--dataPort', type=int, default=1511)
|
||||
parser.add_argument('--id', type=int, default=None)
|
||||
parser.add_argument('--outputfile', type=str, default=None)
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Plot summary of a dist.py recording: distance over time, recording regions, totals."""
|
||||
import csv
|
||||
import argparse
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
|
||||
def main(csvfile):
|
||||
times = []
|
||||
distances = []
|
||||
recording = []
|
||||
|
||||
with open(csvfile) as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader) # skip header
|
||||
for row in reader:
|
||||
times.append(float(row[0]))
|
||||
distances.append(float(row[1]))
|
||||
recording.append(row[5].strip() == 'True')
|
||||
|
||||
# Compute total distance and total time spent recording
|
||||
total_distance = distances[-1] if distances else 0.0
|
||||
total_rec_time = 0.0
|
||||
for i in range(1, len(times)):
|
||||
if recording[i]:
|
||||
total_rec_time += times[i] - times[i - 1]
|
||||
|
||||
print(f"File: {csvfile}")
|
||||
print(f"Total distance: {total_distance:.2f} m")
|
||||
print(f"Total recording time: {total_rec_time:.1f} s")
|
||||
print(f"Total session time: {times[-1]:.1f} s")
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(times, distances, 'b-', linewidth=1.5)
|
||||
ax.set_xlabel('Time (s)')
|
||||
ax.set_ylabel('Distance (m)')
|
||||
ax.set_title(f'{csvfile}\nTotal distance: {total_distance:.2f} m | Recording time: {total_rec_time:.1f} s')
|
||||
|
||||
# Shade recording regions
|
||||
in_region = False
|
||||
region_start = 0
|
||||
for i, rec in enumerate(recording):
|
||||
if rec and not in_region:
|
||||
region_start = times[i]
|
||||
in_region = True
|
||||
elif not rec and in_region:
|
||||
ax.axvspan(region_start, times[i], alpha=0.2, color='red')
|
||||
in_region = False
|
||||
if in_region:
|
||||
ax.axvspan(region_start, times[-1], alpha=0.2, color='red')
|
||||
|
||||
rec_patch = mpatches.Patch(color='red', alpha=0.2, label='Recording active')
|
||||
ax.legend(handles=[rec_patch])
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Plot summary of a dist.py recording")
|
||||
parser.add_argument('csvfile', help="CSV file from dist.py")
|
||||
args = parser.parse_args()
|
||||
main(args.csvfile)
|
||||
@@ -34,7 +34,7 @@
|
||||
int main()
|
||||
{
|
||||
note("running algebra math tests");
|
||||
plan(3);
|
||||
plan(6); // number of tests in this file to run
|
||||
|
||||
/* test int32_vect2_normalize */
|
||||
struct Int32Vect2 v = {2300, -4200};
|
||||
@@ -57,6 +57,24 @@ int main()
|
||||
ok((fabs(quat_zxy.qi - 0.9266) < 0.01 && fabs(quat_zxy.qx - -0.2317) < 0.01 && fabs(quat_zxy.qy - 0.1165) < 0.01) && fabs(quat_zxy.qz - 0.2722),
|
||||
"float_quat_of_eulers_zxy(float_eulers_of_quat_zxy(0.9266, -0.2317, 0.1165, 0.2722)) returned [%f, %f, %f, %f]", quat_zxy.qi, quat_zxy.qx, quat_zxy.qy, quat_zxy.qz);
|
||||
|
||||
/* test int32_quat_normalize */
|
||||
struct Int32Quat q = {32768, 0, 0, 0}; // 1.0 in Q15 format
|
||||
q.qx = 16384; // 0.5 in Q15
|
||||
q.qy = 0;
|
||||
q.qz = 0;
|
||||
int32_quat_normalize(&q);
|
||||
// After normalization, the quaternion should be unit length (|q| = 1.0 in Q15)
|
||||
// For [1, 0.5, 0, 0], norm = sqrt(1^2 + 0.5^2) = sqrt(1.25) ≈ 1.118
|
||||
// Normalized: [1/1.118, 0.5/1.118, 0, 0] ≈ [0.894, 0.447, 0, 0]
|
||||
// In Q15: 0.894*32768 ≈ 29300, 0.447*32768 ≈ 14650
|
||||
ok((q.qi >= 29295 && q.qi <= 29315) && (q.qx >= 14645 && q.qx <= 14655), // allowing a small margin of error due to integer rounding
|
||||
"int32_quat_normalize([32768,16384,0,0]) returned [%d, %d, %d, %d] (expected approx [29300,14650,0,0] +/- 15)", q.qi, q.qx, q.qy, q.qz);
|
||||
|
||||
uint32_t sqrt_100 = int32_sqrt(100);
|
||||
ok(sqrt_100 == 10, "int32_sqrt(100) == %d (expected 10)", sqrt_100);
|
||||
|
||||
uint32_t sqrt_12345 = int32_sqrt(12345);
|
||||
ok(sqrt_12345 >= 111 && sqrt_12345 <= 112, "int32_sqrt(12345) == %d (expected approx 111)", sqrt_12345);
|
||||
|
||||
done_testing();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user