This autonomous hydrofoil is designed for efficient water transit using electric propulsion combined with hydrofoil technology. The system features a large front wing with two electric thrusters, a smaller rear stabilizing wing, and a raised waterproof payload compartment. The design focuses on maintaining stable horizontal flight above water while adapting to wave conditions through active control surfaces.
-
Front Wing Configuration:
WING_FRONT_WIDTH
: 800 mm (0.8 m, as specified)WING_FRONT_NACA_PROFILE
: NACA 2412- Design Rationale: Chosen for good lift generation at low Reynolds numbers (expected at 0.8 m/s) due to its 2% camber located at 40% chord. The 12% thickness-to-chord ratio provides good structural properties and a relatively gentle stall characteristic. Expected Cl_max around 1.4-1.6 (higher with flaps).
WING_FRONT_CHORD_ROOT
: 200 mm- Design Rationale: With a NACA 2412 (12% max thickness), this chord gives the specified 24 mm maximum thickness at the wing root.
WING_FRONT_CHORD_TIP
: 100 mmWING_FRONT_TAPER_RATIO
: 0.5 (Tip Chord / Root Chord)- Design Rationale: A taper ratio of 0.5 helps improve structural efficiency (less material at tips) and promotes a more desirable stall progression (root tends to stall before the tips, maintaining some aileron/flap effectiveness).
WING_FRONT_MAC
: 155.6 mm (Mean Aerodynamic Chord)- Formula: MAC_f = (2/3) * c_root * (1 + λ + λ²) / (1 + λ), where λ is taper ratio.
WING_FRONT_AREA
: 0.12 m²- Formula: S_f = b_f * (c_root + c_tip) / 2
WING_FRONT_ASPECT_RATIO
: 5.33- Formula: AR_f = b_f² / S_f
- Design Rationale: This moderate AR offers a good balance between structural robustness, forgiving stall characteristics, maneuverability, and induced drag. While higher AR reduces induced drag, at 0.8 m/s, structural integrity and stall behavior are prioritized.
WING_FRONT_SWEEP_LE
: ~3.6 degrees (leading edge sweep for a straight quarter-chord line). Can be adjusted up to 10 degrees.- Design Rationale: Minimal sweep is preferred for low-speed applications as it simplifies construction and provides predictable aerodynamics. A slight aft sweep of the leading edge (resulting in a straight quarter-chord line) is common.
WING_FRONT_DIHEDRAL
: 2-3 degrees per side- Design Rationale: Provides inherent roll stability. If one side drops, it experiences a higher effective angle of attack, generating more lift and tending to roll the craft back to level. This complements the pendulum stability from the high payload.
WING_FRONT_THICKNESS_ROOT_MAX
: 24 mm (12% of 200mm root chord)WING_FRONT_THICKNESS_TIP_MAX
: 12 mm (12% of 100mm tip chord)WING_FRONT_COG
: [placeholder, typically ~30-40% chord from LE of MAC, requires detailed CAD and mass distribution analysis of the wing structure itself]WING_FRONT_COB
: [placeholder, for uniform density, similar to CoG, requires detailed CAD analysis of wing volume]WING_FRONT_FLAP_CHORD
: 30-40 mm (approx. 15-20% of local wing chord)- Design Rationale: Standard flap chord sizing for effective lift modulation without excessive drag.
WING_FRONT_FLAP_SPAN_PER_FLAP
: ~300-350 mm (extending over a significant portion of each semi-span, leaving room near root for mast and near tip for winglet effect if any)WING_FRONT_FLAP_AXIS
: [placeholder, e.g., 0 mm from flap leading edge, aligned with wing hinge line]WING_FRONT_ESTIMATED_CL_CRUISE
: ~0.287- Formula: Cl_cruise = Load_on_front_wing / (0.5 * ρ_water * V_cruise² * S_f)
- Assumption: Front wing supports ~75% of total 1.5kg mass (11.04 N load) at 0.8 m/s. ρ_water ≈ 1000 kg/m³.
- Design Rationale: This low operational Cl provides a large margin to stall (Cl_max ~1.4-1.6), ensuring high control authority from flaps and robustness against gusts/waves.
-
Rear Wing Configuration:
WING_REAR_WIDTH
: 400 mm (0.4 m)- Design Rationale: Approximately 50% of front wing span, a common ratio for stabilizers.
WING_REAR_NACA_PROFILE
: NACA 0009 or NACA 0010- Design Rationale: A symmetrical airfoil is crucial for a stabilizer as it needs to generate both positive and negative lift efficiently for pitch trim and control, without inherent pitching moment. Thinner profiles (9-10% thickness) reduce drag.
WING_REAR_CHORD_ROOT
: 70 mm (example, can be adjusted)WING_REAR_CHORD_TIP
: 46 mm (example, for a taper ratio of ~0.65)WING_REAR_CHORD_AVG (if rectangular)
: ~58 mm (to achieve target area with 400mm span)WING_REAR_AREA (S_h)
: ~0.0233 m²WING_REAR_TAIL_VOLUME_COEFFICIENT (V_h)
: ~0.5- Formula: V_h = (S_h * L_h) / (S_f * MAC_f), where L_h is tail arm length.
- Design Rationale: A V_h between 0.3-0.7 is typical for good static longitudinal (pitch) stability. This value assumes L_h ≈ 400mm.
WING_REAR_ASPECT_RATIO (AR_h)
: ~6.87 (for S_h=0.0233m² and b_h=0.4m)- Formula: AR_h = b_h² / S_h
- Design Rationale: A higher AR for the tail is often beneficial for stabilizer effectiveness.
WING_REAR_THICKNESS_MAX
: ~5.2 mm to 7 mm (9-10% of a 58-70mm chord)WING_REAR_COG
: [placeholder, typically ~30-40% chord from LE, requires CAD analysis]WING_REAR_COB
: [placeholder, for uniform density, similar to CoG, requires CAD analysis]WING_REAR_FLAP_CHORD
: 15-20 mm (approx. 25-30% of local wing chord)WING_REAR_FLAP_SPAN
: ~380 mm (if nearly full span flap)WING_REAR_FLAP_AXIS
: [placeholder, e.g., 0 mm from flap leading edge]
-
Mast Structure:
- Two vertical mast assemblies, each 700mm in vertical height before angling.
- Each mast assembly constructed from:
- Two 16mm outer diameter carbon fiber tubes as primary structural members.
- A symmetrical NACA profile fairing moulded over the tubes, aligned with the direction of travel.
MAST_NACA_PROFILE
: e.g., NACA 0018 to NACA 0024- Design Rationale: Symmetrical profile for consistent drag characteristics regardless of small angle of attack changes. A thicker profile (18% to 24% thickness-to-chord ratio) is chosen to comfortably accommodate the two 16mm tubes internally with a reasonable chord length, minimizing wetted area compared to a thinner profile requiring a very long chord for the same internal thickness.
MAST_INTERNAL_TUBE_ARRANGEMENT
: [e.g., Fore-aft with X mm separation, or side-by-side with Y mm separation - this needs to be defined]- Consideration:
- Fore-aft placement (one tube behind the other, possibly slightly offset vertically or horizontally if space inside fairing allows): This is generally good for maximizing stiffness in the plane of the tubes for a given fairing chord. The fairing's maximum thickness will need to accommodate at least one tube diameter plus material thickness.
- Side-by-side placement (tubes separated across the mast's "span" or minor axis): This would require the fairing's maximum thickness to be significantly larger if the tubes are also the widest point, or the chord would need to be very long.
- Let's assume for calculation a fore-aft arrangement where the tubes are effectively defining the minimum internal thickness required.
- Consideration:
MAST_MAX_THICKNESS_FAIRING_INTERNAL_EST
: ~20-25 mm (to accommodate a 16mm tube + some clearance/bonding material, or two tubes closely spaced fore-aft if they are offset to fit within this thickness envelope).MAST_CHORD_EST
:- If using NACA 0018 (18% thick) and needing ~20mm internal thickness: Chord ≈ 20mm / 0.18 ≈ 111 mm.
- If using NACA 0020 (20% thick) and needing ~20mm internal thickness: Chord ≈ 20mm / 0.20 = 100 mm.
- If using NACA 0024 (24% thick) and needing ~20mm internal thickness: Chord ≈ 20mm / 0.24 ≈ 83 mm.
- Choice: Select based on desired balance of chord length, wetted area, and internal space. A shorter chord (thicker profile) might be preferable to reduce wetted area, provided it's structurally sound. E.g., NACA 0020 with a 100mm chord.
MAST_ACTUAL_MAX_THICKNESS_FAIRING
: For NACA 0020 with 100mm chord = 20 mm.MAST_CONSTRUCTION_DETAIL
: The 700mm vertical section transitions to a 45° inward angle at the top to meet at the payload housing.- Critical Consideration: The bend area is a high-stress point. The carbon tubes must be carefully joined (e.g., mitered and overwrapped with carbon fiber) or bent (if feasible with the chosen tubes and bend radius) to maintain structural integrity. Internal bracing or bulkheads within the fairing, especially at the bend and attachment points, is highly recommended.
MAST_SEPARATION
: [placeholder, e.g., 250-350mm] (This refers to the distance between the two complete mast assemblies where they attach to the front wing).- Design Rationale: Affects roll stability input from wing dihedral/anhedral, structural load distribution on the front wing, and clearance for thrusters.
MAST_WIRING_CONDUIT
: One or both 16mm tubes can potentially serve as conduits for thruster/servo wires if properly sealed. Alternatively, space within the fairing alongside the tubes can be used.
-
Payload Housing:
- Torpedo-shaped hydrodynamic design
PAYLOAD_LENGTH
: [placeholder] mmPAYLOAD_DIAMETER
: [placeholder] mmPAYLOAD_VOLUME
: [placeholder] cubic cmPAYLOAD_COG
: [X:placeholder, Y:placeholder, Z:placeholder] mm from reference point- Critical Consideration: The overall system CoG (including payload, wings, masts, thrusters) must be carefully managed relative to the overall Center of Lift / Neutral Point for stability. A high CoG (payload above water) provides pendulum stability in roll but needs careful balance with aerodynamic stability.
- Waterproof rating: IP67 or higher
Further considerations
WING_FRONT_COG / COB
andWING_REAR_COG / COB
: These refer to the center of gravity/buoyancy of the wing itself. For the overall system, thePAYLOAD_COG
and the masses/positions of all other components (masts, thrusters, wings) will determine the total system CoG.- Placeholder Values: Remember to replace other placeholders (like servo models, battery specs, etc.) as you finalize those components.
- Iterative Process: Wing design is often iterative. These dimensions are a strong starting point. CFD analysis and/or model testing might lead to further refinements.
- Stall Speed Calculation:
- Formula: V_stall = sqrt( (2 * Load) / (ρ_water * S_wing * Cl_max) )
- Estimated V_stall for front wing (75% load): ~0.35 m/s (without flaps). This provides a good margin at 0.8 m/s cruise.
-
Thrusters:
- Model: F2838 350KV Underwater Brushless DC Motors
- Specifications:
- Power: 150W each
- Thrust: 2.4kg each
- Voltage: 3-4S (11.1V-16.8V)
- KV rating: 350
- Positioning:
- Mounted 100mm above front wing on each mast
THRUSTER_X_OFFSET
: [placeholder] mm from wing leading edgeTHRUSTER_VECTOR_ANGLE
: [placeholder] degrees relative to horizontal
-
Electronic Speed Controllers (ESCs):
- Type: [placeholder] Waterproof ESCs
- Current rating: [placeholder] A continuous
- BEC output: [placeholder] V / [placeholder] A
- Protocol: DShot600 compatible
-
Front Wing Flaps:
- Two independently controlled flaps
FLAP_FRONT_DEFLECTION_MAX
: ±[placeholder] degrees- Servo model: [placeholder] Waterproof Servos
- Torque: [placeholder] kg-cm
- Speed: [placeholder] sec/60°
- Voltage: [placeholder] V
-
Rear Wing Flap:
- Single flap for pitch stability
FLAP_REAR_DEFLECTION_MAX
: ±[placeholder] degrees- Servo model: [placeholder] Waterproof Servo
- Torque: [placeholder] kg-cm
- Speed: [placeholder] sec/60°
- Voltage: [placeholder] V
-
Battery:
- Chemistry: LiPo 4S
- Capacity: [placeholder] mAh
- C-Rating: [placeholder]C continuous
- Weight: [placeholder] g
- Dimensions: [placeholder] mm
-
Power Distribution:
- Main power rail: 14.8V nominal (4S)
- 5V rail for flight controller and servos: [placeholder] A max
- 3.3V rail for sensors: [placeholder] A max
-
Endurance Calculations:
POWER_CRUISE
: [placeholder] W at [placeholder] m/sPOWER_THRUSTERS
: [placeholder] W at cruise powerPOWER_SERVOS
: [placeholder] W (average during operation)POWER_ELECTRONICS
: [placeholder] W (flight controller, sensors, receivers)- Estimated cruise endurance: [placeholder] minutes
-
Flight Controller:
- Holybro Pixhawk 6C Mini
- Processor: STM32H7
- IMU: [placeholder]
- Memory: [placeholder] Flash, [placeholder] RAM
-
GPS:
- M10 GPS with External QMC5883 Compass
- Interface: I2C and UART
- Update rate: [placeholder] Hz
- Accuracy: [placeholder] m
-
External Compass:
- Model: CAN-L4-3100
- Interface: CAN1
- Update rate: [placeholder] Hz
-
Distance Sensor:
- Model: A02YYUW Waterproof Ultrasonic Sensor
- Range: 0.20m - 7.5m
- Interface: UART (TELEM2)
- Update rate: [placeholder] Hz
- Purpose: Wave detection and altitude control
-
Receiver:
- ELRS (ExpressLRS) Receiver
- Interface: UART (TELEM1)
- Update rate: [placeholder] Hz
- Range: [placeholder] km
Port | Connected Device | Protocol | Speed | Purpose |
---|---|---|---|---|
TELEM1 | ELRS Receiver | UART | 115200 bps | Remote control |
TELEM2 | A02YYUW Ultrasonic Sensor | UART | 9600 bps | Distance measurement |
CAN1 | CAN-L4-3100 Compass | CAN | 1 Mbps | Heading data |
I2C + UART | M10 GPS with QMC5883 | I2C/UART | 400 kHz | Position and heading data |
PWM 1-2 | Front Wing Flap Servos | PWM | 50 Hz | Control surface actuation |
PWM 3 | Rear Wing Flap Servo | PWM | 50 Hz | Control surface actuation |
PWM 4-5 | Thruster ESCs | DShot600 | - | Propulsion control |
Driver | Purpose | Status |
---|---|---|
drivers__distance_sensor__a02yyuw |
Ultrasonic sensor for wave detection | Custom |
drivers__pwm_out |
Servo control | Enabled |
drivers__dshot |
ESC communication | Enabled |
drivers__gps |
Position data | Enabled |
drivers__magnetometer |
Heading data | Enabled |
Module | Purpose |
---|---|
modules__commander |
Flight mode management |
modules__control_allocator |
Control surface allocation |
modules__ekf2 |
Extended Kalman Filter for sensor fusion |
modules__sensors |
Sensor processing |
modules__rc_update |
Remote control input processing |
modules__logger |
System logging |
modules__navigator |
Mission management |
modules__mc_hover_thrust_estimator |
Thrust estimation for altitude control |
Module | Reason |
---|---|
drivers__optical_flow |
Not using optical sensors |
drivers__imu__icm42688p |
Only if not used by Pixhawk 6C Mini |
drivers__uavcan |
Not using UAVCAN devices |
drivers__camera_trigger |
No camera system |
drivers__barometer |
Using ultrasonic for altitude |
modules__simulation |
Not using simulation |
modules__vtol_att_control |
Not a VTOL aircraft |
modules__micrortps_bridge |
Not using ROS2 |
-
Altitude Hold Mode:
- Maintains consistent height above water using ultrasonic sensor
- PID parameters:
AH_P
: [placeholder]AH_I
: [placeholder]AH_D
: [placeholder]
-
Wave Adaptation Mode:
- Actively follows wave contours while maintaining horizontal orientation
- Parameters:
WA_SENSITIVITY
: [placeholder]WA_MAX_CLIMB_RATE
: [placeholder] m/sWA_PREDICTION_HORIZON
: [placeholder] ms
-
Cruise Mode:
- Maintains constant speed and heading
- Parameters:
CRUISE_SPEED
: [placeholder] m/sCRUISE_HEADING_P
: [placeholder]
-
Manual Override Mode:
- Direct control of thrusters and control surfaces
param set SENS_EN_A02YYUW 1 # Enable ultrasonic sensor
param set PWM_MAIN_FUNC1 201 # Left front flap servo
param set PWM_MAIN_FUNC2 202 # Right front flap servo
param set PWM_MAIN_FUNC3 203 # Rear flap servo
param set PWM_MAIN_FUNC4 101 # Left thruster
param set PWM_MAIN_FUNC5 102 # Right thruster
param set SYS_AUTOSTART 1002 # Custom autostart script
param set CA_AIRFRAME 13 # Custom airframe (similar to airboat)
-
Wing Preparation:
- Prepare front and rear wings with servo cutouts
- Install servo mounts and control linkages
- Mount thruster attachments to front wing masts
-
Mast and Payload Housing:
- Fabricate masts with 45° bend at top section
- Create waterproof payload housing with access hatch
- Install masts to front wing and secure to payload housing
-
Electronics Installation:
- Mount Pixhawk 6C Mini in payload housing
- Install ESCs, power distribution board, and battery
- Connect all servos, thrusters, and sensors
- Route cables through waterproof conduits
-
Waterproofing:
- Seal all electronics compartments with appropriate waterproof materials
- Test waterproofing in controlled environment before deployment
-
PX4 Installation:
- Flash PX4 firmware to Pixhawk 6C Mini
- Install custom A02YYUW driver (see code below)
- Configure parameters as listed in configuration section
-
Custom Driver Installation:
- Place A02YYUW driver in
src/drivers/distance_sensor/a02yyuw/
directory - Add
A02YYUW.cpp
,A02YYUW.hpp
,a02yyuw_main.cpp
,CMakeLists.txt
, andKconfig
files - Build PX4 firmware with driver included
- Place A02YYUW driver in
-
Calibration:
- Calibrate sensors (IMU, compass)
- Configure and test control surfaces
- Verify ultrasonic sensor operation
The A02YYUW sensor is critical for wave detection and altitude control. Below is the implementation of the custom driver (see files for complete implementation):
// Add this to drivers/drv_sensor.h or in your driver header
#ifndef DRV_DIST_DEVTYPE_A02YYUW
#define DRV_DIST_DEVTYPE_A02YYUW 0x21 // A02YYUW waterproof ultrasonic sensor
#endif
To optimize memory usage on the Pixhawk 6C Mini:
- Disable all unused sensor drivers
- Reduce logging verbosity
- Optimize parameter count
- Use efficient control algorithms
-
Control Loop Rates:
- Attitude control: 250 Hz
- Position control: 50 Hz
- Wave detection processing: 10 Hz
-
Filter Configuration:
- Low-pass filter on ultrasonic data: cutoff [placeholder] Hz
- Complementary filter for attitude estimation: [placeholder] ratio
-
Static Testing:
- Bench test all electronic systems
- Test waterproofing in controlled environment
- Verify sensor readings and calibration
-
Water Testing:
- Initial testing in calm water
- Wave response testing in controlled conditions
- Full system testing in varying wave conditions
-
Autonomous Navigation:
- Waypoint following capability
- Obstacle avoidance using additional sensors
-
Energy Optimization:
- Dynamic speed control based on wave conditions
- Energy harvesting from wave motion
-
Telemetry and Monitoring:
- Real-time data transmission to shore station
- Performance monitoring and analytics
This project is licensed under [appropriate license].
Special thanks to contributors and open-source projects that made this possible:
- PX4 Autopilot
- [Other acknowledgements]
Replace all [placeholder] values with actual measurements and specifications for your specific implementation.
This document serves both as a project overview and as a comprehensive reference for autonomous hydrofoil development.
Diameter = circumference / 3.14159 (pi)
Front Window Example: Inner circumference: 122 * 2 + 85 * 2 = 244 + 170 = 414mm / " Inner diameter: 414 / 3.14159 = 131.78mm Groove Width: 3.4mm Groove Depth: 2.1mm SIL_Mechanical_Seal_catalogue_2020 O-Ring: BS160N70 - 133.02X2.62 O-RING NBR D70
Reference: https://www.bluetrailengineering.com/post/thrusters-for-asvs-usvs-rovs-auvs
The resistance of the windings of the motor. Resistance means wasted energy, so you want Rm to be as small as possible.
Can be measured by hooking a multimeter up to any two of the three wires coming out of the motor and measuring the resistance.
How much current is required to spin the motor with zero external load on the motor. You want Io to be as low as possible.
Motors have both mechanical friction as well as magnetic effects that require a certain amount of electrical current to make the motor spin even when there is no external load on the motor. The no-load current captures these various effects. It's not really a precise parameter, because the amount of current needed to spin the motor depends a bit on the RPM. Typically manufacturers only one value of Io, often without stating what RPM it was measured at.
Technically, the KV tells you how much back-EMF the motor generates for a given RPM. KV is approximately how fast the motor will spin at a given input voltage, assuming no external load. The units are RPM/volt.
If the motor has a KV of 1000 and you run it at 12 volts, it will spin close to 12,000 RPM (again, assuming no external load). If you put a load on it, it'll spin somewhat slower than that.
Usually you want the KV to be as low as possible. Large, slowly-spinning propellers are more efficient than small, fast-spinning propellers.
Check the rated wattage to make sure it's not run above the recommended rating.
If the rating is signifigantly higher than the running wattage, a smaller motor may be more efficient.
Smaller motors tend to have higher KVs, and we want our KV to be as low as possible. If you want your thruster motor to be compact, there will be a limit to how low the KV can be.
Motors with low KVs tend to have a high Rm. To make a low-KV motor, they put in a large number of loops of thin wire in the windings. A lot of thin wire equates to a lot of resistance.
When selecting find two or three of the lowest-KV motors that will physically fit your application, and pick the one with the lowest Io and Rm values.
Start by assuming the RPM and required power output of the motor, P_out (also known as shaft power).
$ I = Io + P_out / (RPM / KV) $
$ V = RPM / KV + I * Rm $
$ P = I * V $
If you don't know the exact RPM or power output, you can make the following approximations:
$ RPM = KV * Approximate voltage that you want to operate at $
$ P_out = ( The drag on your vehicle (in Newtons) * vehicle speed (in m/s)) / propeller efficiency (assume 0.7 for propeller efficiency) $
Note that none of these equations include the efficiency of the motor controller (ESC). A good ESC might reach about 90% efficiency. If you want to account for the power draw of the ESC, just divide the motor input power by 0.9 to approximate the total system power.
$ total system power = P / 0.9 $
When designing AUV components note that surface is for o-ring and requires finishing to ensure a smooth suface.
When installing seals, apply a small amount of silicone sealant to o-ring. Wear gloves, then rub a small amount between fore-finger and thumb, then lightly rub o-ring between fingers, rotating o-ring until coated. Only apply a small amount.
Only tighten bolts to finger tight. They don't need to be super tight.
Certain foam can handle different depth. Blue ROV make a foam rated to 300m, however this foam is toxic if not sealed. It should not be sculpted after purchase.
Steps to calculate in Fusion360.
- Create a copy of the body.
- Fill all cavities.
- Create a customer sea water material.
- Choose water
- Create a copy and edit
- Edit the advance setting to change density
- Seawater = 1023.6kg / m^3 = 1.0236 g / cm ^3
- Change the material to seawater
- Record the (x,y,z) coordinates for centre of mass
These vary depending on location and magnetic crust movement. The current position and an up-to-date model should be loaded when using. Models are updated every 5 years.
Before installing QGroundControl for the first time:
cd
sudo usermod -a -G dialout $USER
sudo apt-get remove modemmanager -y
sudo apt install gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-gl -y
sudo apt install libqt5gui5 -y
sudo apt install libfuse2 -y
Logout and login again to enable the change to user permissions.
Download and install QGroundControl:
Install (and run) using the terminal commands:
cd Downloads/
chmod +x ./QGroundControl.AppImage
./QGroundControl.AppImage
Discussion on nosedive stability design
Cable braiding can be used to create a finger trap for strain relief.
Parameter | Value |
---|---|
Full Throttle FWD/REV Thrust @ 12 V | 3.71 / 2.92 kg f |
Full Throttle FWD/REV Thrust @ Nominal (16 V) | 5.25 / 4.1 kg f |
Full Throttle FWD/REV Thrust @ Maximum (20 V) | 6.7 / 5.05 kg f |
Minimum Thrust | 0.02 kg f* |
Full Throttle Current (Power) @ 12 V | 17 Amps (205 Watts) |
Full Throttle Current (Power) @ Nominal (16 V) | 24 Amps (390 Watts) |
Full Throttle Current (Power) @ Maximum (20 V) | 32 Amps (645 Watts) |
Operating Voltage | 7–20 volts |
Conductor Gauge | 16 AWG |
Compatible WetLink Penetrator | WLP-M10-6.5MM-LC |
Weight in Air (with 1m cable) | 344 g |
Propeller Diameter | 76 mm |
Mounting Hole Threads | M3 x 0.5 |
Mounting Hole Spacing | 19 mm |
*Values limited by ESC used to drive thruster.
import numpy as np
import matplotlib.pyplot as plt
# Define the AUV parameters
g = 9.80665 # Acceleration due to gravity in m/s^2
mass = 15 # Mass of the AUV in kg
# Volume of the AUV
volume_auv = 0.01 # Volume of the AUV in cubic meters (m^3)
# Water density (can be changed for seawater or freshwater)
density_seawater = 1025 # Density of seawater in kg/m^3
density_freshwater = 999.8 # Density of freshwater in kg/m^3
# Example calculations
buoyant_force_seawater, net_force_seawater = calculate_forces(density_seawater)
I = np.diag([10, 10, 10]) # Moment of inertia matrix (simplified diagonal matrix)
thruster_distance = 0.3 # Distance of each thruster from the origin (center of gravity) in meters
thruster_angle = np.radians(45) # Angle of thrusters (45 degrees inwards)
# Thrust force in kilograms-force
thrust_force_fwd_kgf = 5.25 # Forward thrust in kgf
thrust_force_rev_kgf = 4.1 # Reverse thrust in kgf
# Convert kgf to Newtons
thrust_force_fwd_N = thrust_force_fwd_kgf * 9.80665 # Forward thrust in Newtons
thrust_force_rev_N = thrust_force_rev_kgf * 9.80665 # Reverse thrust in Newtons
# Function to calculate buoyant force and net force
def calculate_forces(density_water):
buoyant_force = density_water * volume_auv * g # Buoyant force
net_force = mass * g - buoyant_force # Net force (weight - buoyant force)
return buoyant_force, net_force
# Simulation parameters
time_step = 0.1 # Time step for the simulation (s)
total_time = 10.0 # Total time for the simulation (s)
# Initialize state variables
# Position (x, y, z), Orientation (roll, pitch, yaw)
# Linear velocity (v_x, v_y, v_z), Angular velocity (omega_x, omega_y, omega_z)
state = {
"position": np.zeros((3, int(total_time/time_step))),
"orientation": np.zeros((3, int(total_time/time_step))),
"linear_velocity": np.zeros((3, int(total_time/time_step))),
"angular_velocity": np.zeros((3, int(total_time/time_step)))
}
# Thruster configuration
# Calculate the position and direction vectors for each thruster
thruster_positions = np.array([
[thruster_distance, 0, 0], # Position of the first thruster
[-thruster_distance, 0, 0] # Position of the second thruster
])
thruster_directions = np.array([
[np.cos(thruster_angle), np.sin(thruster_angle), 0], # Direction of the first thruster
[np.cos(thruster_angle), -np.sin(thruster_angle), 0] # Direction of the second thruster
])
# Simulation loop
for t in range(1, state["position"].shape[1]):
# Calculate forces and torques from thrusters
total_force = np.zeros(3)
total_torque = np.zeros(3)
for i in range(2):
force = thrust_force * thruster_directions[i] # Thrust force vector
torque = np.cross(thruster_positions[i], force) # Torque vector using cross product
total_force += force
total_torque += torque
# Newton's second law for linear and angular motion
linear_acceleration = total_force / mass
angular_acceleration = np.linalg.inv(I).dot(total_torque)
# Update linear and angular velocities
state["linear_velocity"][:, t] = state["linear_velocity"][:, t-1] + linear_acceleration * time_step
state["angular_velocity"][:, t] = state["angular_velocity"][:, t-1] + angular_acceleration * time_step
# Update position and orientation
# Integrating velocity to get position and orientation
state["position"][:, t] = state["position"][:, t-1] + state["linear_velocity"][:, t-1] * time_step
state["orientation"][:, t] = state["orientation"][:, t-1] + state["angular_velocity"][:, t-1] * time_step
# Visualizing the results
plt.figure(figsize=(12, 12))
plt.subplot(3, 1, 1)
plt.plot(state["position"][0], state["position"][1])
plt.title('Position (X vs Y)')
plt.xlabel('X Position (m)')
plt.ylabel('Y Position (m)')
plt.subplot(3, 1, 2)
plt.plot(state["orientation"][0])
plt.title('Roll Orientation Over Time')
plt.xlabel('Time (s)')
plt.ylabel('Roll (radians)')
plt.subplot(3, 1, 3)
plt.plot(state["linear_velocity"][0])
plt.title('X Velocity Over Time')
plt.xlabel('Time (s)')
plt.ylabel('Velocity (m/s)')
plt.tight_layout()
plt.show()
- Wear face shield and heavy duty gloves. Carcinogenic while setting (24 hour set time)
- Clean cables with acetone. This removes the shiny coating on the cables for better adhesion.
- Spray mould with release agent. Make sure to not get this on anything else. Change gloves.
- Please cable in mould. Make sure join is not touching the sides.
- Tighten mould bolts and use bluetack and masking tape to seal around all sides of mould to prevent it leaking. If it does wait until it dries before cleaning.
- Wait 24 hours to set before removing from mould.
- Cut small piece of hard plastic tube to give over cable end (25mm).
- Place heatshrink over tube and cable.
- Heat heatshrink to create a tappered mould.
- Pour in potting agent.
- When dried remove heatshift and hard plastic tube.
from math import exp
import numpy as np
# Formula 1: reflectance = 100 * (measurement - dark_current) / (reference - dark_current)
# reflectance: True reflectance
# measurement: Measured image
# dark_current: Dark current image
# reference: Reference image
def calculate_reflectance(measurement, dark_current, reference):
return 100 * (measurement - dark_current) / (reference - dark_current)
# Formula 2: DC(t) = bias + slope * t
# DC: Dark current
# t: Exposure time
# bias: Bias coefficient
# slope: Slope coefficient
def calculate_DC(t, bias, slope):
return bias + slope * t
# Formula 3: D(λ, t) = L(λ) * C(λ) * t + DC(t)
# D: Camera output
# λ: Spectral channel
# t: Exposure time
# L: Spectral distribution of light intensity
# C: Spectral distribution of sensitivity for the camera
def calculate_D(L, C, t, DC):
return L * C * t + DC
# Formula 4: R(λ) = (D(λ, t) - DC(t)) / t = L(λ) * C(λ)
# R: Normalized measurement of light
# λ: Spectral channel
# t: Exposure time
def calculate_R(D, DC, t, L, C):
return (D - DC) / t # or L * C
# Formula 5: WR(λ) = L(λ) * W(λ) * C(λ)
# WR: Light reflected from the calibration reference
# L: Spectral distribution of light intensity for the illumination source
# W: Spectral distribution of reflectance for the calibration reference
# C: Spectral distribution of sensitivity for the camera
def calculate_WR(L, W, C):
return L * W * C
# Formula 6: MR(λ) = L(λ) * M(λ) * C(λ)
# MR: Light reflected from the unknown material
# M: Material-dependent spectral distribution of reflectance
def calculate_MR(L, M, C):
return L * M * C
# Formula 7: RR(λ) = MR(λ) / WR(λ)
# RR: Relative reflectance in the analyzed surface
def calculate_RR(MR, WR):
return MR / WR
# Formula 8: M(λ) = W(λ) * RR(λ)
# M: Material-dependent reflectance
def calculate_M(W, RR):
return W * RR
# Formula 9: DL(λ) = s * L(λ) * C(λ)
# DL: Spectral distribution of light intensity
# s: Scaling constant
def calculate_DL(s, L, C):
return s * L * C
# Formula 10: WR(λ) / DL(λ) = 1 / s * W(λ)
# WR: Light reflected from the calibration reference
# DL: Spectral distribution of light intensity
# s: Scaling constant
def calculate_W_from_WR_DL(WR, DL, s):
return (WR / DL) * s
# Formula 1: Image Distance z_w
# la: distance between the optical center of the lens and the inner surface of the optical glass
# lg: thickness of the optical glass
# lw: distance between the target plane and the outer surface of the optical glass
# ng: refractive index of the optical glass
# nw: refractive index of the water body
def calculate_zw(la, lg, lw, ng, nw):
return la + (lg / ng) + (lw / nw)
# Formula 2-4: Conjugate Coordinates x'_w, y'_w, z'_w
# f: focal length of the camera
# zw: image distance
# xw, yw, zw: spatial position coordinates on the object plane
def calculate_conjugate_coordinates(f, zw, xw, yw, zw):
factor = -1 * (f / (zw - f))
xw_prime = factor * xw
yw_prime = factor * yw
zw_prime = factor * zw
return xw_prime, yw_prime, zw_prime
# Formula 5: Spectral R_c(λ_c)
# Ic: spectral response value of the target
# Iw: spectral response value of the standard whiteboard
# Rw: spectral reflectance of the standard whiteboard
def calculate_Rc_air(Ic, Iw, Rw):
return (Ic / Iw) * Rw
# Formula 6: Spectral Reflectance in Water R_c(λ_c)
# Ib: spectral response value of the blackboard
# Rb: spectral reflectance of the blackboard
def calculate_Rc_water(Ic, Iw, Ib, Rw, Rb):
return ((Ic - Ib) / (Iw - Ib)) * Rw - ((Iw - Ic) / (Iw - Ib)) * Rb
# Formula 7: Pixel Response I(x,y,λ_c)
# K: mapping relationship from the spectral energy to the pixel response
# R: spectral reflectance of the target
# Es: incident spectral radiation energy at the target position
# Et: stray light entering the image surface
# c: spectral attenuation coefficient of the water body
# lw: water depth
def calculate_pixel_response(K, R, Es, Et, c, lw, lambda_c):
return K * (R * Es * exp(-c * lw) + Et)
# Formula 8: Spectral Reflectance of Underwater Target R_c(λ_c)
# Ic, Iw, Ib: pixel responses of color board, whiteboard, and blackboard, respectively
def calculate_Rc_underwater(Ic, Iw, Ib):
# Assuming K, Es, c, and Et remain unchanged
return Ic # This is considered as the spectral reflectance of the underwater targeted object
# Equation 1: Image intensity at a pixel
# I_c(x) = D_c(x) + B_c(x)
def image_intensity(D_c, B_c):
I_c = D_c + B_c
return I_c
# Equation 2: Light intensity at depth z_2 with integral attenuation
# D(z_2, λ) = D(z_1, λ)e^(−integral{z_2}{z_1}β(z′,λ)dz′)
def light_intensity_integral(D_z1, beta_func, z1, z2):
integral_beta = np.exp(-beta_func(z1, z2))
D_z2 = D_z1 * integral_beta
return D_z2
# Equation 3: Light intensity at depth z_2 with constant attenuation
# D(z_2, λ) = D(z_1, λ)e^(−β(λ)∆z)
def light_intensity_constant(D_z1, beta_lambda, delta_z):
D_z2 = D_z1 * np.exp(-beta_lambda * delta_z)
return D_z2
# Equation 4: Diffuse component of the image
# D_c = 1/κ * integral{}{Λ}S_c(λ)ρ(λ)E(λ)e(−β(λ)z)dλ , c = R, G, B
def diffuse_component(S_c, rho, E, beta, z, kappa):
integral_term = np.trapz(S_c * rho * E * np.exp(-beta * z))
D_c = integral_term / kappa
return D_c
# Equation 5: Image intensity at the surface
# J_c = D_c(z = 0)
def image_intensity_surface(D_c):
return D_c
# Equation 6: Diffuse component at depth z + ∆z
# D_c(z + ∆z) = D_c(z)e(−βc∆z)
def diffuse_component_depth(D_c, beta_c, delta_z):
D_c_new = D_c * np.exp(-beta_c * delta_z)
return D_c_new
# Equation 7: Estimated image intensity
# Jˆ_c = D_ce^(β_cz)
def estimated_image_intensity(D_c, beta_c, z):
J_c_hat = D_c * np.exp(beta_c * z)
return J_c_hat
# Equation 8: Attenuation coefficient from two depths
# βc = ln [(D_c(z))/(Dc_(z + ∆z))]/∆z
def attenuation_coefficient(D_c, D_c_new, delta_z):
beta_c = np.log(D_c / D_c_new) / delta_z
return beta_c
# Equation 9: Attenuation coefficient from spectral integrals
# β_c = ln[(integral S_c(λ)ρ(λ)E(λ)e^(−β(λ)(z))dλ) / (integral S_c(λ)ρ(λ)E(λ)e^(−β(λ)(z+∆z))dλ)]/∆z
def attenuation_coefficient_spectral(S_c, rho, E, beta, z, delta_z):
integral_term1 = np.trapz(S_c * rho * E * np.exp(-beta * z))
integral_term2 = np.trapz(S_c * rho * E * np.exp(-beta * (z + delta_z)))
beta_c = np.log(integral_term1 / integral_term2) / delta_z
return beta_c
# E = I∞(z)ρe^(−β(λ)d) + I∞(z)(1 − e^(−β(λ)d))
def Ambient_Light_Scene_Radiance(I_inf_z, rho, beta_lambda, d):
"""
Calculate the irradiance observed by each pixel of the camera.
Parameters:
- I_inf_z: Ambient light at depth z
- rho: Normalized radiance of a scene point
- beta_lambda: Total beam attenuation coefficient
- d: Distance from the scene point to the camera
Returns:
- E: Irradiance observed by the camera
"""
term1 = I_inf_z * rho * np.exp(-beta_lambda * d)
term2 = I_inf_z * (1 - np.exp(-beta_lambda * d))
E = term1 + term2
return E
# I∞(z) = T * I_0 * e^(−β(λ)z)
def Ambient_Light_Depth(T, I_0, beta_lambda, z):
"""
Calculate the ambient light at depth z.
Parameters:
- T: Transmission coefficient at the water surface
- I_0: Atmospheric intensity at the surface
- beta_lambda: Total beam attenuation coefficient
- z: Depth
Returns:
- I_inf_z: Ambient light at depth z
"""
I_inf_z = T * I_0 * np.exp(-beta_lambda * z)
return I_inf_z
# β(λ) ≥ K^{sw}_{w} * (λ) + b_p * (λ) + a_p * (λ) + a_y * (λ)
def Total_Beam_Attenuation_Coefficient(K_sw_w_lambda, b_p_lambda, a_p_lambda, a_y_lambda):
"""
Calculate the total beam attenuation coefficient in saltwater.
Parameters:
- K_sw_w_lambda: Diffuse attenuation coefficient for the clearest saltwater
- b_p_lambda: Scattering coefficient of particles
- a_p_lambda: Absorption coefficient of particles
- a_y_lambda: Absorption coefficient of dissolved organic material
Returns:
- beta_lambda: Total beam attenuation coefficient
"""
beta_lambda = K_sw_w_lambda + b_p_lambda + a_p_lambda + a_y_lambda
return beta_lambda
# ρ^c ≈ 0, ∀E^c ≤ E^{c}_{1%}
def black_patch_assumption(E_c, E_c_1_percent):
"""
Apply the black patch assumption to estimate normalized radiance.
Parameters:
- E_c: Observed irradiance in color channel c
- E_c_1_percent: 1% threshold for irradiance in color channel c
Returns:
- rho_c: Normalized radiance for color channel c
"""
if E_c <= E_c_1_percent:
return 0
else:
return None # Not applicable
# ρ_c ≤ 1, ∀E^c ≥ E^{c}_{99%}
def white_patch_assumption(E_c, E_c_99_percent):
"""
Apply the white patch assumption to estimate normalized radiance.
Parameters:
- E_c: Observed irradiance in color channel c
- E_c_99_percent: 99% threshold for irradiance in color channel c
Returns:
- rho_c: Normalized radiance for color channel c
"""
if E_c >= E_c_99_percent:
return 1
else:
return None # Not applicable
# E^c = I^{c}_{∞}*(z)(1 − e^(−βcd), ∀E^c ≤ E^{c}_{1%}
def Nonlinear_Estimation(I_c_inf_z, beta_c, d, E_c_1_percent, E_c):
"""
Conditional nonlinear estimation for irradiance in color channel c.
Parameters:
- I_c_inf_z: Ambient light in color channel c at depth z
- beta_c: Attenuation coefficient for color channel c
- d: Distance from the scene point to the camera
- E_c_1_percent: 1% threshold for irradiance in color channel c
- E_c: Observed irradiance in color channel c
Returns:
- E_c: Estimated irradiance for color channel c
"""
if E_c <= E_c_1_percent:
return I_c_inf_z * (1 - np.exp(-beta_c * d))
else:
return None # Not applicable
# ρ^c = 1 + ((E^c / (I^{c}_{∞}(z))) - 1) e^(β^c*d)
def Scene_Radiance_Recovery(E_c, I_c_inf_z, beta_c, d):
"""
Recover the scene radiance for each color channel.
Parameters:
- E_c: Observed irradiance in color channel c
- I_c_inf_z: Ambient light in color channel c at depth z
- beta_c: Attenuation coefficient for color channel c
- d: Distance from the scene point to the camera
Returns:
- rho_c: Recovered scene radiance for color channel c
"""
rho_c = 1 + ((E_c / I_c_inf_z) - 1) * np.exp(beta_c * d)
return rho_c
# α = (∑_c * (β^c - k^c)) / (∑_c1)
def mean_attenuation_coefficient(beta_c, k_c):
"""
Calculate the mean attenuation coefficient.
Parameters:
- beta_c: Dictionary containing attenuation coefficients for each wavelength
- k_c: Dictionary containing mean clear ocean water attenuation coefficients for each wavelength
Returns:
- alpha: Mean attenuation coefficient
"""
total_diff = 0
for wavelength in beta_c.keys():
total_diff += beta_c[wavelength] - k_c[wavelength]
total_wavelengths = len(beta_c)
alpha = total_diff / total_wavelengths
return alpha
# k^c = integral ^{800nm}_{200nm} s^c (λ) ∗ K^{sw}_{w} (λ)dλ
def calculate_k_c(sc_lambda, K_sw_w_lambda):
"""
Calculate the mean clear ocean water attenuation coefficients.
Parameters:
- sc_lambda: Spectral response of the camera sensor for each wavelength
- K_sw_w_lambda: Diffuse attenuation coefficient for the clearest saltwater for each wavelength
Returns:
- k_c: Mean clear ocean water attenuation coefficients
"""
k_c = np.trapz(sc_lambda * K_sw_w_lambda, dx=1) # Integral from 200nm to 800nm
return k_c
# C_vis = (E_max − E_min)/(E_max + E_min)
def human_contrast(E_max, E_min):
"""
Calculate human contrast discrimination.
Parameters:
- E_max: Maximum irradiance
- E_min: Minimum irradiance
Returns:
- C_vis: Human contrast discrimination
"""
C_vis = (E_max - E_min) / (E_max + E_min)
return C_vis
# d_vis = 1 / (min_c(β^c) * log(((1 − C_vis) / (2 * C_vis)) + 1)
def visibility_distance(beta_c, C_vis):
"""
Calculate the visibility distance.
Parameters:
- beta_c: Dictionary containing attenuation coefficients for each wavelength
- C_vis: Human contrast discrimination
Returns:
- d_vis: Visibility distance
"""
d_vis = 1 / (min(beta_c.values()) * np.log(((1 - C_vis) / (2 * C_vis)) + 1))
return d_vis
# z_est = arg_z min (∑_c((γ^c − γ^{c}_{th})^2)
def depth_estimate(gamma_c, gamma_th):
"""
Estimate the depth of the AUV.
Parameters:
- gamma_c: Dictionary containing estimated ambient light chromaticity for each wavelength
- gamma_th: Dictionary containing theoretical chromaticity-depth-dependency in clear ocean water for each wavelength
Returns:
- z_est: Depth estimate of the AUV
"""
z_est = np.argmin(sum((np.array(list(gamma_c.values())) - np.array(list(gamma_th.values()))) ** 2))
return z_est
# γ^c = I^{c}_{0} / (∑_c I^{c}_{0})
def estimated_ambient_chromaticity(I_c_0):
"""
Calculate the estimated ambient light chromaticity.
Parameters:
- I_c_0: Dictionary containing ambient light intensity for each wavelength
Returns:
- gamma_c: Dictionary containing estimated ambient light chromaticity for each wavelength
"""
total_intensity = sum(I_c_0.values())
gamma_c = {wavelength: intensity / total_intensity for wavelength, intensity in I_c_0.items()}
return gamma_c
Razor_IMU_Artemis This instruction set describes how to calibrate the Artemis IMU from Sparkfun (https://www.sparkfun.com/products/16832). It is a bit of a pain and uses an older instruction set based on the Razor IMU (https://medium.com/@leofaf/sparkfun-9dof-razor-imu-m0-with-ros-d9e1c5209f9e) and was based on https://github.com/Razor-AHRS/razor-9dof-ahrs/wiki/Tutorial. There are other pieces of instructions from Sparkfun (https://learn.sparkfun.com/tutorials/9dof-razor-imu-m0-hookup-guide/all)
It is a three step process:
- Program the Artemis to output serial on the USB-C port (using Arduino).
- Using Processing, run the Magnetometer Calibration routine to get the calibration parameters.
- Copy the calibration parameters to the Artemis and change the output serial to SerialLog (RX,TX) and upload using Arduino. Unfortunately, there are very specific requirements for the programs to compile. Some of the latest modules do not work and you have to use older versions.
-
Arduino Version: arduino-1.8.12
-
Arduino Boards: Sparkfun Apollo3 Boards (Version 1.2.1)
-
Arduino Libraries: Sparkfun MPU-9250 9 DOF Breakout (Version 1.0.2) Some other pre-requisites for Arduino
-
SparkFun ICM_20948 - find in manage libraries
-
PString - https://github.com/kachok/arduino-libraries/tree/master Pre-requisties for Processing
-
EJML - https://sourceforge.net/projects/ejml/files/v0.23/ There may be more from the instructions. It was a pain to set up and compile.
Open the IMU Core In Arduino, open the sketch Razor_AHRS. Set the board to "Sparkfun RedBoard Artemis ATP" Comment out the Uart Serial port and Uncomment the USB-C LOG_PORT Open the Processing Sketch "Magnetometer_calibration" and press start. Rotate the IMU to get a good distribution around the sphere. When looking good, press the space bar. In the lower text section of the Processing screen, you will see text similar to this: boolean CALIBRATION__MAGN_USE_EXTENDED = true; float magn_ellipsoid_center[3] = {-85.5321, -215.400, 315.694}; float magn_ellipsoid_transform[3][3] = {{0.959897, 0.00642552, -0.0157447}, {0.00642552, 0.980319, -0.0218634}, {-0.0157447, -0.0218634, 0.961935}}; Copy this calibration output and paste it into the arduino sketch around the area where it says the following. Note that there are old commented out calbrations as well. // Magnetometer (extended calibration mode) // Set to true to use extended magnetometer calibration (compensates hard & soft iron errors) Comment out the USB-C LOG_PORT and uncomment the two Uart serial lines (for outputing to the Uart ports). Reflash the IMU with the updated IMU sketch. NOTES:
It may be the buad rate is different. For floatyboat it should be 57600 You can check the IMU output after calibration by setting the LOG_PORT to USB-C and looking at it in a serial monitor. Just remember to set it back to Uart for use on FloatyBoat.