Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
10a6fe2
Added CV package boilerplate
Nikil-Shyamsunder Nov 23, 2024
af4cb41
Update Docker image with CV commands
Nikil-Shyamsunder Nov 23, 2024
47d9ccd
initial buoy detection webcam code
Nov 23, 2024
119f734
Merge branch 'computer_vision' of https://github.com/CUSail-Navigatio…
Nov 23, 2024
f325c6a
update config file for buoy detection
Nov 23, 2024
ed41309
Initial CV tracker
asun121 Nov 23, 2024
9422595
Merge branch 'computer_vision' of https://github.com/CUSail-Navigatio…
asun121 Nov 23, 2024
6486ba3
Adjust buoy tracking with servo
Feb 22, 2025
62974af
Fix bugs with teensy cpp
Feb 22, 2025
15bca83
added buoy displacement info to telemetry data
nicoleluo7 Feb 22, 2025
7cec45a
Add calculation for buoy z based on angle.
ericcai32 Mar 1, 2025
c743baf
Fix camera node bugs
ericcai32 Mar 11, 2025
aa12d31
Add function to align depth and color image
ericcai32 Mar 15, 2025
ec7be3c
Finalize buoy tracking
ericcai32 Mar 18, 2025
3fcd3bc
Reorder logic
ericcai32 Mar 22, 2025
eb8db71
Include required dependencies/setup
ericcai32 Mar 22, 2025
5355803
Add launch file for testing and fix coordinate logic
ericcai32 Mar 22, 2025
2c61253
Fix bugs in teensy node and fake teensy
ericcai32 Mar 22, 2025
3d36815
change teensy.py to include buoy angle info
nicoleluo7 Mar 22, 2025
2c63acf
Merge remote-tracking branch 'origin/main' into computer_vision
nicoleluo7 Mar 26, 2025
baeeb05
added buoy info to teensy.py and fixed merge conflicts
nicoleluo7 Mar 26, 2025
66911cb
maintain servo angle instead of buoy displacement
nicoleluo7 Apr 12, 2025
50ef169
Only update buoy position when centered
ericcai32 Apr 12, 2025
c288156
code cleanup and minor changes
nicoleluo7 Apr 19, 2025
31e7fd5
remove print statements
nicoleluo7 Apr 19, 2025
3ce231f
cv debugging adjustments
nicoleluo7 May 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11.6
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"C_Cpp.errorSquiggles": "disabled"
}
44 changes: 22 additions & 22 deletions setup/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y \
&& locale-gen en_US.UTF-8 \
&& update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 \
&& rm -rf /var/lib/apt/lists/*
ENV LANG en_US.UTF-8
ENV LANG=en_US.UTF-8

# Install timezone
RUN ln -fs /usr/share/zoneinfo/UTC /etc/localtime \
Expand All @@ -26,25 +26,25 @@ RUN ln -fs /usr/share/zoneinfo/UTC /etc/localtime \
&& rm -rf /var/lib/apt/lists/*

RUN apt-get update && apt-get -y upgrade \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/*

# Install common programs
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
gnupg2 \
lsb-release \
sudo \
software-properties-common \
wget \
&& rm -rf /var/lib/apt/lists/*
curl \
gnupg2 \
lsb-release \
sudo \
software-properties-common \
wget \
&& rm -rf /var/lib/apt/lists/*

# Install ROS2
RUN sudo add-apt-repository universe \
&& curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null \
&& apt-get update && apt-get install -y --no-install-recommends \
ros-humble-ros-base \
python3-argcomplete \
ros-humble-ros-base \
python3-argcomplete \
&& rm -rf /var/lib/apt/lists/*

ENV ROS_DISTRO=humble
Expand Down Expand Up @@ -85,12 +85,12 @@ ARG USER_GID=$USER_UID

# Check if "ubuntu" user exists, delete it if it does, then create the desired user
RUN if getent passwd ubuntu > /dev/null 2>&1; then \
userdel -r ubuntu && \
echo "Deleted existing ubuntu user"; \
fi && \
groupadd --gid $USER_GID $USERNAME && \
useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME && \
echo "Created new user $USERNAME"
userdel -r ubuntu && \
echo "Deleted existing ubuntu user"; \
fi && \
groupadd --gid $USER_GID $USERNAME && \
useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME && \
echo "Created new user $USERNAME"

# Add sudo support for the non-root user
RUN apt-get update && apt-get install -y sudo \
Expand Down Expand Up @@ -137,11 +137,11 @@ RUN chmod +x /home/ros2_user/setup_ros.sh
RUN pip3 install -r /home/ros2_user/ros2_ws/requirements.txt

# Install RosBridge and other WebDev Packages
RUN sudo apt update \
&& sudo apt install curl \
&& sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo tee /usr/share/keyrings/ros-archive-keyring.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null \
&& sudo apt-get -y install ros-humble-rosbridge-server
RUN sudo apt-get -y update \
&& sudo apt-get -y install curl \
&& sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo tee /usr/share/keyrings/ros-archive-keyring.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null \
&& sudo apt-get -y install ros-humble-rosbridge-server

# Set the entry point to run the setup script on container start
ENTRYPOINT ["/home/ros2_user/setup_ros.sh"]
Expand Down
3 changes: 3 additions & 0 deletions setup/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ pyserial==3.5
utm==0.7.0
geopy==2.4.1
rosdoc2
opencv-python==4.8.0.74
pyrealsense2==2.55.1.6486

6 changes: 6 additions & 0 deletions src/sailboat_launch/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ sailbot: #Namespace
ros__parameters:
vectornav_port: "/dev/serial/by-id/usb-FTDI_USB-RS232-WE_AV0LF3XT-if00-port0"
simulated: false
computer_vision:
ros__parameters:
video_source: 0
hsv_lower: [0, 120, 180]
hsv_upper: [10, 160, 255]
detection_threshold: 100
37 changes: 37 additions & 0 deletions src/sailboat_launch/launch/sailboat.launch_cv_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
config = os.path.join(
get_package_share_directory('sailboat_launch'),
'config',
'config_sim.yaml'
)

teensy_cmd = Node(
package='sailboat_main',
executable='teensy',
name='teensy',
namespace='sailbot',
parameters=[config]
)

cv_cmd = Node(
package='sailboat_vision',
executable='buoy_detection',
name='buoy_detection',
namespace='sailbot',
parameters=[config]
)

ld = LaunchDescription()

# Sensors
ld.add_action(teensy_cmd)
ld.add_action(cv_cmd)

return ld
19 changes: 14 additions & 5 deletions src/sailboat_main/sailboat_main/teensy/teensy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TeensyHardware:

START_BYTE = 0xff
END_BYTE = 0xee
PACKET_LENGTH = 5
PACKET_LENGTH = 6

def __init__(self, port):
self.port = port
Expand Down Expand Up @@ -39,6 +39,7 @@ def read_telemetry(self, data):
data["wind_angle"], \
data["sail_angle"], \
data["rudder_angle"], \
data["buoy_angle"], \
data["dropped_packets"] = self._parse_packet(self.buffer)

# clear buffer
Expand Down Expand Up @@ -70,17 +71,24 @@ def _parse_packet(self, packet):
else:
rudder_angle = packet[3]

dropped_packets = packet[4]

return wind_angle, sail_angle, rudder_angle, dropped_packets
if packet[4] >= 128:
buoy_angle = packet[4] - 256
else:
buoy_angle = packet[4]

dropped_packets = packet[5]

return wind_angle, sail_angle, rudder_angle, buoy_angle, dropped_packets


def send_command(self, sail, rudder):
def send_command(self, sail, rudder, servo_angle):
"""
Send a properly formatted command packet to the servo.

:param sail: Sail position (integer)
:param tail: Tail position (integer)
:param servo_angle Servo angle (integer)
"""
try:
# check bounds
Expand All @@ -90,9 +98,10 @@ def send_command(self, sail, rudder):
# convert sail and tail to signed 8-bit integers (bytes)
sail_byte = sail & 0xFF if sail >= 0 else (sail + 256) & 0xFF
rudder_byte = rudder & 0xFF if rudder >= 0 else (rudder + 256) & 0xFF
servo_angle_byte = servo_angle & 0xFF if servo_angle >= 0 else (servo_angle + 256) & 0xFF

# create the packet: [start flag] [sail] [tail] [end flag]
command_packet = bytearray([self.START_BYTE, sail_byte, rudder_byte, self.END_BYTE])
command_packet = bytearray([self.START_BYTE, sail_byte, rudder_byte, servo_angle_byte, self.END_BYTE])

# send the packet over serial
self.serial.write(command_packet)
Expand Down
23 changes: 6 additions & 17 deletions src/sailboat_main/sailboat_main/teensy/teensy_fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def __init__(self, ):
self.wind_step = random.uniform(-5, 5)
self.last_sail = 0
self.last_rudder = 0
self.buoy_angle = 90
self.dropped_packets = 0

print("Simulated TeensyFake initialized")
Expand All @@ -26,6 +27,7 @@ def read_telemetry(self, data):
data["wind_angle"] = self._generate_random_wind()
data["sail_angle"] = self.last_sail
data["rudder_angle"] = self.last_rudder
data["buoy_angle"] = self.buoy_angle
data["dropped_packets"] = self.dropped_packets

return random.randint(0,1)
Expand All @@ -46,28 +48,15 @@ def _generate_random_wind(self):
return int(self.wind_angle)


def send_command(self, sail, rudder):
def send_command(self, sail, rudder, servo_angle):
"""
Send a properly formatted command packet to the servo.

:param sail: Sail position (integer)
:param tail: Tail position (integer)
"""
try:
# check bounds
sail = max(min(sail, 127), -128)
rudder = max(min(rudder, 127), -128)

# convert sail and tail to signed 8-bit integers (bytes)
sail_byte = sail & 0xFF if sail >= 0 else (sail + 256) & 0xFF
rudder_byte = rudder & 0xFF if rudder >= 0 else (rudder + 256) & 0xFF

# create the packet: [start flag] [sail] [tail] [end flag]
command_packet = bytearray([self.START_BYTE, sail_byte, rudder_byte, self.END_BYTE])

# send the packet over serial
return 0
except:
return 1
self.last_sail = sail
self.last_rudder = rudder
command_packet = bytearray([self.START_BYTE, sail, rudder, servo_angle, self.END_BYTE])

print(f'Sent to FakeTeensy: {command_packet}')
36 changes: 31 additions & 5 deletions src/sailboat_main/sailboat_main/teensy/teensy_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from rclpy.node import Node
from sailboat_interface.msg import SailTail
from std_msgs.msg import Int32

from . import teensy
from . import teensy_fake

Expand Down Expand Up @@ -44,11 +43,13 @@ def __init__(self):
# keep track of our desired sail and rudder angles
self.desired_sail = 0
self.desired_rudder = 0
self.servo_angle = 0

# telemetry data publishers
self.wind_angle_pub = self.create_publisher(Int32, 'wind', 10)
self.actual_sail_angle_pub = self.create_publisher(Int32, 'actual_sail_angle', 10)
self.actual_rudder_angle_pub = self.create_publisher(Int32, 'actual_rudder_angle', 10)
self.buoy_angle_pub = self.create_publisher(Int32, 'buoy_angle', 10)
self.dropped_packets_pub = self.create_publisher(Int32, 'dropped_packets', 10)

# callback to read teensy data
Expand All @@ -66,6 +67,13 @@ def __init__(self):
'rudder_angle',
self.rudder_callback,
10)

#subscription for servo angle
self.subscription = self.create_subscription(
Int32,
'/servo_angle',
self.servo_angle_callback,
10)

def check_telemetry(self):
"""
Expand All @@ -87,13 +95,18 @@ def check_telemetry(self):
rudder_angle_msg.data = data["rudder_angle"]
self.actual_rudder_angle_pub.publish(rudder_angle_msg)

buoy_angle_msg = Int32()
buoy_angle_msg.data = data["buoy_angle"]
self.buoy_angle_pub.publish(buoy_angle_msg)

dropped_packets_msg = Int32()
dropped_packets_msg.data = data["dropped_packets"]
self.dropped_packets_pub.publish(dropped_packets_msg)

self.get_logger().info(f"{'Wind angle:':<20} {wind_angle_msg.data}")
self.get_logger().info(f"{'Actual sail angle:':<20} {sail_angle_msg.data}")
self.get_logger().info(f"{'Actual tail angle:':<20} {rudder_angle_msg.data}")
self.get_logger().info(f"{'Buoy angle:':<20} {buoy_angle_msg.data}")
self.get_logger().info(f"{'Dropped packets:':<20} {dropped_packets_msg.data}")
else:
self.get_logger().info("No telemetry received")
Expand All @@ -106,20 +119,33 @@ def sail_callback(self, msg):
self.desired_sail = msg.data
#self.get_logger().info(f"Sail callback-sent to Teensy sail:{self.desired_sail}, rudder: {self.desired_rudder}")

if self.teensy.send_command(self.desired_sail, self.desired_rudder) == 0:
if self.teensy.send_command(self.desired_sail, self.desired_rudder, self.servo_angle) == 0:
self.get_logger().info(f"Message sent to servo")
else:
self.get_logger().warn(f"Message failed to send to servo")

def rudder_callback(self, msg):
"""
Callback function for the 'rudder_angle' topic. Sends the updated rudder
and the previously set sail.
"""
self.desired_rudder = msg.data
self.get_logger().info(f"Rudder callback-sent to Teensy sail:{self.desired_sail}, rudder: {self.desired_rudder}")
self.get_logger().info(f"Rudder position callback-sent to Teensy sail:{self.desired_sail}, rudder: {self.desired_rudder} servo angle: {self.servo_angle}")

if self.teensy.send_command(self.desired_sail, self.desired_rudder, self.servo_angle) == 0:
self.get_logger().info(f"Message sent to servo")
else:
self.get_logger().warn(f"Message failed to send to servo")

def servo_angle_callback(self, msg):
"""
Callback function for the 'servo_angle' topic. Sends the updated servo angle, previously set rudder,
and the previously set sail.
"""
self.servo_angle = msg.data
self.get_logger().info(f"Buoy position callback-sent to Teensy sail:{self.desired_sail}, rudder: {self.desired_rudder} servo angle: {self.servo_angle}")

if self.teensy.send_command(self.desired_sail, self.desired_rudder) == 0:
if self.teensy.send_command(self.desired_sail, self.desired_rudder, self.servo_angle) == 0:
self.get_logger().info(f"Message sent to servo")
else:
self.get_logger().warn(f"Message failed to send to servo")
Expand Down
18 changes: 18 additions & 0 deletions src/sailboat_vision/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>sailboat_vision</name>
<version>0.0.0</version>
<description>Computer Vision</description>
<maintainer email="fz246@cornell.edu">Fiona Zheng</maintainer>
<license>Apache License 2.0</license>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.
Empty file.
Loading