Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Implemented proper ROS service behavior and modified Auto Dig and Auto Offload logic #298

Draft
wants to merge 62 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5c98c98
Initial Commit of the Drivetrain Refactor
Isopod00 Sep 21, 2024
9382a9c
Merge branch 'main' into Refactor-swerve-into-tank
Isopod00 Sep 22, 2024
a976131
refactored read_serial.py to not use absolute encoders per task #275…
kznhq Sep 24, 2024
3ef8cc7
Removed instantiated PID controllers
andrewtomfohrde Sep 25, 2024
2f2f232
Merge branch 'Refactor-swerve-into-tank' of https://github.com/GOFIRS…
andrewtomfohrde Sep 25, 2024
9e64914
deleted all absolute encoders from the arduino code
umnrobotics Sep 25, 2024
846b2fe
Merge branch 'Refactor-swerve-into-tank' of https://github.com/GOFIRS…
umnrobotics Sep 25, 2024
0c1b0b8
refactored drivetrain_node to arcade drive from swerve
ashtondemmer Sep 25, 2024
d81054b
Merge branch 'Refactor-swerve-into-tank' of https://github.com/GOFIRS…
ashtondemmer Sep 25, 2024
d502c20
Merge branch 'main' into Refactor-swerve-into-tank
Isopod00 Sep 25, 2024
b4c1b05
Autoformat the drivetrain_node
Isopod00 Sep 25, 2024
d0ea543
Fixed number of bytes to read in read_serial
Isopod00 Sep 25, 2024
1cf4c28
Minor Whitespace Fixes
Isopod00 Sep 25, 2024
ad4e84c
Fixed the TODO for the Gazebo publishers
Isopod00 Sep 25, 2024
6609ef2
Refactor all usages of the drive ROS service
Isopod00 Sep 26, 2024
c027945
Merge branch 'main' into Refactor-swerve-into-tank
Isopod00 Sep 27, 2024
466a705
Remove remaining mentions of absolute encoders
Isopod00 Sep 29, 2024
e984b9a
Merge branch 'main' into Refactor-swerve-into-tank
Isopod00 Sep 29, 2024
912db1a
changed swerve joints "type" from continuous to fixed
kznhq Oct 1, 2024
c2a1bc4
Fixed Nav2 for tank drive
cparece1 Oct 1, 2024
f1e6d55
Updated autodig to do in-place digging
cparece1 Oct 2, 2024
62f99ef
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 2, 2024
3a2056d
small update to autodig to align with new robot design and prepare fo…
cparece1 Oct 3, 2024
7a2fb6c
finally commiting this garbage. Scuffed fix for Skimmer node, decent …
cparece1 Oct 4, 2024
dfc0719
Fixed the motor_control_node (it got messed up in the previous commit)
Isopod00 Oct 4, 2024
bd70e1b
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 4, 2024
2e93187
Made set_position calls wait to return until position has been reached
cparece1 Oct 9, 2024
cb6d18e
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 9, 2024
a058008
Fixed up set position and set velocity so they dont return til they'r…
cparece1 Oct 11, 2024
413be1e
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 11, 2024
3656b70
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 12, 2024
1b4fb4a
Removed the async node class, and adapted the digger node, and the A…
cparece1 Oct 16, 2024
8b3fa30
Changed logic for extend/retract dumper, reread AutoDig, AutoOff, Dum…
cparece1 Oct 18, 2024
dfff57b
TargTach was private so not accessable, added a getter to work around…
cparece1 Oct 18, 2024
74f651c
Motor_control_node now builds
cparece1 Oct 18, 2024
b0d67cc
Merge branch 'AutoDig-Improvements' of https://github.com/GOFIRST-Rob…
cparece1 Oct 18, 2024
297e80e
Merge remote-tracking branch 'origin/main' into AutoDig-Improvements
Isopod00 Oct 22, 2024
cc8ee93
Digger_node should be good, motor control has thresholds, but still r…
cparece1 Oct 23, 2024
f5e5957
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 23, 2024
f7c39f7
Auto Formatting
Isopod00 Oct 23, 2024
48fe448
velocityThreshold and tachThreshold cleanup
Isopod00 Oct 23, 2024
847ba94
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 25, 2024
c71be3b
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 29, 2024
b17b67e
Add async await syntax for set_position
jmblixt3 Oct 30, 2024
6cf106c
Merge branch 'main' into AutoDig-Improvements
Isopod00 Oct 31, 2024
9de17c8
Merge branch 'main' into AutoDig-Improvements
Isopod00 Nov 5, 2024
fa0d28e
Merge branch 'main' into AutoDig-Improvements
Isopod00 Nov 8, 2024
b52316c
Re-Added Async_sleep via asyncnode to control time spent digging and …
cparece1 Nov 13, 2024
3623d28
Added workaround to make dumping berm cancellable (dump is now a seri…
cparece1 Nov 15, 2024
b6fd434
Switched method used for raising lift
cparece1 Nov 15, 2024
3741a42
dumper and digger now use events for limit switches
cparece1 Nov 15, 2024
82c01be
fixed event import and instantiation
cparece1 Nov 15, 2024
e05dd76
made stop also set events
cparece1 Nov 15, 2024
ed4f5e8
Merge branch 'main' into AutoDig-Improvements
Isopod00 Nov 19, 2024
e95ff0f
switched to future from event
cparece1 Nov 22, 2024
67c4146
trying smthn new with MultiThreadedExecutor
cparece1 Nov 22, 2024
413388a
trying to fix import
cparece1 Nov 22, 2024
d1d9f34
fixed running to be lift running
umnrobotics Nov 22, 2024
570e9ce
Digger node with futures to control limit switch behavior
jmblixt3 Nov 22, 2024
114bb19
fixed zero callback
cparece1 Nov 27, 2024
610e507
Cleaned up
cparece1 Nov 27, 2024
d9512aa
Merge branch 'main' into AutoDig-Improvements
Isopod00 Dec 3, 2024
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
55 changes: 20 additions & 35 deletions src/digger/digger/digger_node.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# This ROS 2 node contains code for the digger subsystem of the robot.
# Original Author: Anthony Brogni <[email protected]> in Fall 2023
# Maintainer: Anthony Brogni <brogn002@umn.edu>
# Last Updated: November 2023
# Maintainer: Charlie Parece <parec020@umn.edu>
# Last Updated: October 2024

# Import the ROS 2 Python module
import rclpy
from rclpy.node import Node

# Import ROS 2 formatted message types
from std_msgs.msg import Bool

# Import custom ROS 2 interfaces
from rovr_interfaces.srv import MotorCommandSet, MotorCommandGet
Expand All @@ -33,16 +31,13 @@ def __init__(self):
self.srv_lift_stop = self.create_service(Stop, "lift/stop", self.stop_lift_callback)
self.srv_lift_set_power = self.create_service(SetPower, "lift/setPower", self.lift_set_power_callback)
self.srv_zero_lift = self.create_service(Stop, "lift/zero", self.zero_lift_callback)
self.srv_lower_lift = self.create_service(Stop, "lift/lower", self.lower_lift_callback)

# Define publishers here
self.publisher_goal_reached = self.create_publisher(Bool, "digger/goal_reached", 10)

# Define subscribers here
self.limit_switch_sub = self.create_subscription(LimitSwitches, "limitSwitches", self.limit_switch_callback, 10)

# Define timers here
self.timer = self.create_timer(0.1, self.timer_callback)

# Define default values for our ROS parameters below #
self.declare_parameter("DIGGER_BELT_MOTOR", 2)
self.declare_parameter("DIGGER_LIFT_MOTOR", 1)
Expand All @@ -59,14 +54,8 @@ def __init__(self):

# Current state of the digger belt
self.running = False
# Current goal position (in degrees)
self.current_goal_position = 0
# Current position of the lift motor in degrees
self.current_position_degrees = 0 # Relative encoders always initialize to 0
# Goal Threshold
# if abs(self.current_goal_position - ACTUAL VALUE) <= self.goal_threshold,
# then we should publish True to /digger/goal_reached
self.goal_threshold = 320 # in degrees of the motor # TODO: Tune this threshold if needed
# Current state of the lift system
self.lift_running = False

Expand Down Expand Up @@ -101,17 +90,15 @@ def toggle(self, digger_belt_power: float) -> None:
else:
self.set_power(digger_belt_power)

# TODO: This method can probably be deleted during the implementation of ticket #257
def set_position(self, position: int) -> None:
"""This method sets the position (in degrees) of the digger."""
self.current_goal_position = position # goal position should be in degrees
self.cli_motor_set.call_async(
jmblixt3 marked this conversation as resolved.
Show resolved Hide resolved
MotorCommandSet.Request(
jmblixt3 marked this conversation as resolved.
Show resolved Hide resolved
type="position",
can_id=self.DIGGER_LIFT_MOTOR,
value=float(self.current_goal_position + self.lift_encoder_offset),
value=float(position + self.lift_encoder_offset),
)
)
).result()
jmblixt3 marked this conversation as resolved.
Show resolved Hide resolved
jmblixt3 marked this conversation as resolved.
Show resolved Hide resolved

def stop_lift(self) -> None:
"""This method stops the lift."""
Expand Down Expand Up @@ -139,6 +126,9 @@ def zero_lift(self) -> None:
"""This method zeros the lift system by slowly raising it until the top limit switch is pressed."""
self.lift_set_power(0.05)

def lower_lift(self) -> None:
self.lift_set_power(-0.05)

# Define service callback methods here
def set_power_callback(self, request, response):
"""This service request sets power to the digger belt."""
Expand All @@ -158,12 +148,10 @@ def toggle_callback(self, request, response):
response.success = 0 # indicates success
return response

# TODO: This method needs to be modified during the implementation of ticket #257
# to return a proper future indicating when the goal position has been reached
# so that rclpy.spin_until_future_complete() can be used to wait for the goal.
def set_position_callback(self, request, response):
"""This service request sets the position of the lift."""
self.set_position(request.position)
# ^ this should already wait due to vesc set position not returning until done
response.success = 0 # indicates success
return response

Expand All @@ -182,23 +170,20 @@ def lift_set_power_callback(self, request, response):
def zero_lift_callback(self, request, response):
"""This service request zeros the lift system."""
self.zero_lift()
cparece1 marked this conversation as resolved.
Show resolved Hide resolved
while not self.top_limit_pressed:
pass
response.success = 0 # indicates success
return response

# Define timer callback methods here
def timer_callback(self):
"""Publishes whether or not the current goal position has been reached."""
# This service call will return a future object, that will eventually contain the position in degrees
future = self.cli_motor_get.call_async(MotorCommandGet.Request(type="position", can_id=self.DIGGER_LIFT_MOTOR))
future.add_done_callback(self.done_callback)

def done_callback(self, future):
self.current_position_degrees = future.result().data
goal_reached_msg = Bool(
data=abs(self.current_goal_position + self.lift_encoder_offset - self.current_position_degrees)
<= self.goal_threshold
)
self.publisher_goal_reached.publish(goal_reached_msg)
def lower_lift_callback(self, request, response):
"""This service request reverse-zeros the lift system, putting it at the lowest point"""
self.lower_lift()
while not self.bottom_limit_pressed:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is burning cpu cycles, create an event that will trigger instead of spinning a while loop constantly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain more about how you might go about this? Would take more research (Me, Anthony, and Alex dont know)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a python event and trigger it. So you create a global event in the node then wait for it here and trigger it when the limit switch is pressed.
there is something similar here, but better examples can be found online
https://github.com/GOFIRST-Robotics/Lunabotics/blob/main/src/gstreamer/gstreamer/client_gstreamer.py#L82

response.success = 0 # indicates success
return response

# No more timer callback because setPos works

# Define subscriber callback methods here
def limit_switch_callback(self, limit_switches_msg):
Expand Down
6 changes: 4 additions & 2 deletions src/dumper/dumper/dumper_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ def toggle_callback(self, request, response):
return response

def extend_dumper(self) -> None:
self.set_power(self.DUMPER_POWER)
while not self.top_limit_pressed:
self.set_power(self.DUMPER_POWER)
pass
self.stop()

def extend_callback(self, request, response):
Expand All @@ -106,8 +107,9 @@ def extend_callback(self, request, response):
return response

def retract_dumper(self) -> None:
self.set_power(-self.DUMPER_POWER)
while not self.bottom_limit_pressed:
self.set_power(-self.DUMPER_POWER)
pass
self.stop()

def retract_callback(self, request, response):
Expand Down
17 changes: 16 additions & 1 deletion src/motor_control/src/motor_control_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <string>
#include <tuple> // for tuples
#include <vector>
#include <cmath>

using namespace std::chrono_literals;
using std::placeholders::_1;
Expand Down Expand Up @@ -141,6 +142,9 @@ class PIDController {
bool isContinuousInputEnabled() {
return this->continuous;
}
int32_t getTargTach() {
return this->targTach;
}
};

class MotorControlNode : public rclcpp::Node {
Expand Down Expand Up @@ -185,9 +189,13 @@ class MotorControlNode : public rclcpp::Node {
this->pid_controllers[id]->isActive = false;
}
int32_t data = rpm;

send_can(id + 0x00000300, data); // ID must be modified to signify this is a RPM command
this->current_msg[id] = std::make_tuple(id + 0x00000300, data); // update the hashmap
while(abs(rpm-this->can_data[id].velocity - rpm) > this->velocityThreshold)
{
continue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again waiting in while loops like this is quite cpu intensive, try something event driven

}
RCLCPP_DEBUG(this->get_logger(), "Setting the velocity of CAN ID: %u to %d RPM", id, rpm); // Print Statement
}

Expand All @@ -196,6 +204,10 @@ class MotorControlNode : public rclcpp::Node {
if (this->pid_controllers[id]) {
this->pid_controllers[id]->setRotation(position);
}
while(abs(this->can_data[id].tachometer - this->pid_controllers[id]->getTargTach()) > this->tachThreshold)
{
continue;
}
Comment on lines +243 to +246
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you do if you want to stop the motor before it reaches its final position

RCLCPP_DEBUG(this->get_logger(), "Setting the position of CAN ID: %u to %d degrees", id, position); // Print Statement
}

Expand Down Expand Up @@ -267,6 +279,9 @@ class MotorControlNode : public rclcpp::Node {
}
}

int velocityThreshold = 50; // TODO: Tune this value
int tachThreshold = 100; // TODO: Tune this value

// Listen for CAN status frames sent by our VESC motor controllers
void CAN_callback(const can_msgs::msg::Frame::SharedPtr can_msg) {
uint32_t motorId = can_msg->id & 0xFF;
Expand Down
68 changes: 22 additions & 46 deletions src/rovr_control/rovr_control/auto_dig_server.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer

from std_msgs.msg import Bool

from rovr_interfaces.action import AutoDig
from rovr_interfaces.srv import Drive, Stop, SetPosition, SetPower
from rclpy.action.server import ServerGoalHandle, CancelResponse

from rovr_control.node_util import AsyncNode
from asyncio import sleep
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncio sleep doesn't work, the asyncnode sleep existed for that reason



class AutoDigServer(AsyncNode):
class AutoDigServer(Node):
def __init__(self):
super().__init__("auto_dig_server")
self._action_server = ActionServer(
self, AutoDig, "auto_dig", self.execute_callback, cancel_callback=self.cancel_callback
)

# TODO: This should not be needed anymore after ticket #257 is implemented!
self.digger_goal_subscription = self.create_subscription(
Bool, "/digger/goal_reached", self.digger_goal_callback, 10
)

self.cli_drivetrain_drive = self.create_client(Drive, "drivetrain/drive")
self.cli_drivetrain_stop = self.create_client(Stop, "drivetrain/stop")

self.cli_lift_zero = self.create_client(Stop, "lift/zero")
self.cli_lift_lower = self.create_client(Stop, "lift/lower")
Isopod00 marked this conversation as resolved.
Show resolved Hide resolved
self.cli_lift_setPosition = self.create_client(SetPosition, "lift/setPosition")
self.cli_lift_set_power = self.create_client(SetPower, "lift/setPower")
self.cli_lift_stop = self.create_client(Stop, "lift/stop")
Expand Down Expand Up @@ -72,43 +67,26 @@ async def execute_callback(self, goal_handle: ServerGoalHandle):
return result

# Zero the digger
self.cli_lift_zero.call_async(Stop.Request())
# Wait for the lift goal to be reached
await self.digger_sleep()

# Lower the digger onto the ground
self.cli_lift_setPosition.call_async(
SetPosition.Request(position=goal_handle.request.lift_digging_start_position)
)
# Wait for the lift goal to be reached
await self.digger_sleep()
await self.cli_lift_zero.call_async(Stop.Reqest())

# Start the digger belt
self.cli_digger_setPower.call_async(SetPower.Request(power=goal_handle.request.digger_belt_power))

# Drive forward while digging
start_time = self.get_clock().now().nanoseconds
elapsed = self.get_clock().now().nanoseconds - start_time
# accelerate for 2 seconds
# TODO: completing ticket #298 can replace this while loop with a motor ramp up service
while elapsed < 2e9:
self.cli_lift_set_power.call_async(SetPower.Request(power=-0.05e-9 * (elapsed)))
self.cli_drivetrain_drive.call_async(Drive.Request(forward_power=0.25e-9 * (elapsed), turning_power=0.0))
self.get_logger().info("Accelerating lift and drive train")
elapsed = self.get_clock().now().nanoseconds - start_time
await self.async_sleep(0.1) # Allows for task to be canceled

self.get_logger().info("Auto Driving")
await self.async_sleep(12) # Allows for task to be canceled
self.get_logger().info("Done Driving")

# Stop driving and skimming
await self.cli_drivetrain_stop.call_async(Stop.Request())
await self.cli_digger_setPower.call_async(SetPower.Request(power=goal_handle.request.digger_belt_power))

# Lower the digger towards the ground slowly
await self.cli_lift_lower.call_async(Stop.Reqest())

self.get_logger().info("Start of Auto Digging in Place")
await sleep(3) # Stay at lowest pos for 3 seconds while digging
self.get_logger().info("Done Digging in Place")

# Stop skimming
await self.cli_digger_stop.call_async(Stop.Request())

self.cli_lift_setPosition.call_async(SetPosition.Request(position=goal_handle.request.lift_dumping_position))
await self.cli_lift_setPosition.call_async(
SetPosition.Request(position=goal_handle.request.lift_dumping_position)
)
# raise lift to dumping position
# Wait for the lift goal to be reached
await self.digger_sleep()

self.get_logger().info("Autonomous Digging Procedure Complete!")
goal_handle.succeed()
Expand All @@ -117,11 +95,9 @@ async def execute_callback(self, goal_handle: ServerGoalHandle):
def cancel_callback(self, cancel_request: ServerGoalHandle):
"""This method is called when the action is canceled."""
self.get_logger().info("Goal is cancelling")
if not self.digger_goal_reached.done():
self.cli_drivetrain_stop.call_async(Stop.Request())
if super().cancel_callback(cancel_request) == CancelResponse.ACCEPT:
self.cli_drivetrain_stop.call_async(Stop.Request())
self.cli_digger_stop.call_async(Stop.Request())
self.cli_drivetrain_stop.call_async(Stop.Request())
self.cli_digger_stop.call_async(Stop.Request())
self.cli_lift_stop.call_async(Stop.Request())
return CancelResponse.ACCEPT


Expand Down
Loading