1010#include < iomanip>
1111#include < unistd.h>
1212#include < regex>
13+ #include < ctime>
1314
1415#include " boost/bind/bind.hpp"
1516#include " gflags/gflags.h"
@@ -87,6 +88,10 @@ VescDriver::VescDriver(rclcpp::Node::SharedPtr nh,
8788 t_last_command_ (0 ),
8889 t_last_joystick_ (0 ),
8990 last_smooth_speed_ (0 ),
91+ last_dpad_x_ (0 ),
92+ last_dpad_y_ (0 ),
93+ steering_offset_trim_ (0 ),
94+ speed_offset_trim_ (0 ),
9095 imu_available_ (false ) {
9196 // Load config. Ensure car.lua exists; if it doesn't, create it using
9297 // the hostname. Hostnames like "orin07" will produce car_name = "car07".
@@ -187,6 +192,13 @@ VescDriver::VescDriver(rclcpp::Node::SharedPtr nh,
187192 wheelbase_ / tan (max_steering_angle_));
188193 RCLCPP_INFO (nh_->get_logger (), " ===================================" );
189194
195+ // Initialize trim offsets from config values
196+ steering_offset_trim_ = steering_to_servo_offset_;
197+ speed_offset_trim_ = speed_to_erpm_offset_;
198+
199+
200+ state_msg_.header .frame_id = " base_link" ;
201+
190202 state_msg_.header .frame_id = " base_link" ;
191203 car_status_msg_.header = state_msg_.header ;
192204
@@ -351,6 +363,39 @@ void VescDriver::joystickCallback(const sensor_msgs::msg::Joy::SharedPtr msg) {
351363 static const size_t kAutonomousDriveButton = 5 ;
352364 static const size_t kAutonomousDriveToggleButton = 7 ;
353365 if (msg->buttons .size () < 6 ) return ;
366+
367+ // Trim logic using D-pad (axes 6 and 7)
368+ if (msg->axes .size () >= 8 ) {
369+ int dpad_x = static_cast <int >(msg->axes [6 ]);
370+ int dpad_y = static_cast <int >(msg->axes [7 ]);
371+
372+ // Steering trim (Left/Right)
373+ if (dpad_x != 0 && dpad_x != last_dpad_x_) {
374+ // Left (-1) decreases offset, Right (+1) increases offset
375+ float step = 0 .01f ;
376+ steering_offset_trim_ += dpad_x * step;
377+ saveTrimOffsetsToConfig ();
378+ RCLCPP_INFO (nh_->get_logger (), " Steering Trim: %.3f (delta from config: %.3f)" ,
379+ steering_offset_trim_, steering_offset_trim_ - steering_to_servo_offset_);
380+ }
381+
382+ // Speed trim (Up/Down)
383+ if (dpad_y != 0 && dpad_y != last_dpad_y_) {
384+ // Up (+1) increases offset, Down (-1) decreases offset
385+ // ERPM values are typically in thousands, so 100 is a small adjustment
386+ float step = 100 .0f ;
387+ speed_offset_trim_ += dpad_y * step;
388+ saveTrimOffsetsToConfig ();
389+ RCLCPP_INFO (nh_->get_logger (), " Speed Trim: %.2f (delta from config: %.2f)" ,
390+ speed_offset_trim_, speed_offset_trim_ - speed_to_erpm_offset_);
391+ }
392+
393+ last_dpad_x_ = dpad_x;
394+ last_dpad_y_ = dpad_y;
395+ }
396+
397+
398+
354399 t_last_joystick_ = rclcpp::Clock (RCL_ROS_TIME ).now ().seconds ();
355400 int toggle = toggleState (msg->buttons [kAutonomousDriveToggleButton ]);
356401
@@ -509,11 +554,11 @@ void VescDriver::sendDriveCommands() {
509554 mux_drive_speed_, smooth_speed, mux_steering_angle_);
510555 }
511556 const float erpm =
512- speed_to_erpm_gain_ * smooth_speed + speed_to_erpm_offset_ ;
557+ speed_to_erpm_gain_ * smooth_speed + speed_offset_trim_ ;
513558
514559 // calc steering angle (servo)
515560 const float servo = steering_to_servo_gain_ * mux_steering_angle_ +
516- steering_to_servo_offset_ ;
561+ steering_offset_trim_ ;
517562
518563 // Set speed command.
519564 const float erpm_clipped = Clip (erpm, -erpm_speed_limit_, erpm_speed_limit_, " erpm" );
@@ -522,11 +567,11 @@ void VescDriver::sendDriveCommands() {
522567 // Set servo position command.
523568 const float clipped_servo = Clip (servo, servo_min_, servo_max_, " servo" );
524569 vesc_.setServo (clipped_servo);
525- mux_steering_angle_ = (clipped_servo - steering_to_servo_offset_ )
570+ mux_steering_angle_ = (clipped_servo - steering_offset_trim_ )
526571 / steering_to_servo_gain_;
527572 last_steering_angle_ = mux_steering_angle_;
528573
529- const float clipped_speed = (erpm_clipped - speed_to_erpm_offset_ ) / speed_to_erpm_gain_;
574+ const float clipped_speed = (erpm_clipped - speed_offset_trim_ ) / speed_to_erpm_gain_;
530575 drive_pub_->publish (CalculateDriveCmd (clipped_speed, mux_steering_angle_));
531576}
532577
@@ -701,7 +746,7 @@ void VescDriver::updateOdometry(float rpm, float tachometer, float steering_angl
701746 } else {
702747 // Standard odometry without EKF fusion
703748 // Calcuate linear velocity
704- lin_vel = (rpm - speed_to_erpm_offset_ ) / speed_to_erpm_gain_;
749+ lin_vel = (rpm - speed_offset_trim_ ) / speed_to_erpm_gain_;
705750 // Clamp velocity to zero for minuscule values - a VESC drift issue.
706751 if (fabs (lin_vel) < 0.01 ) {
707752 lin_vel = 0.0 ;
@@ -833,6 +878,78 @@ void VescDriver::ackermannCurvatureCallback(
833878}
834879
835880
881+ void VescDriver::saveTrimOffsetsToConfig () {
882+ std::string car_path = FLAGS_config_dir + " /car.lua" ;
883+
884+ // Read existing content
885+ std::ifstream file_in (car_path);
886+ std::string content;
887+ if (file_in.good ()) {
888+ std::stringstream buffer;
889+ buffer << file_in.rdbuf ();
890+ content = buffer.str ();
891+ file_in.close ();
892+
893+ // Create backup
894+ auto now = std::chrono::system_clock::now ();
895+ auto in_time_t = std::chrono::system_clock::to_time_t (now);
896+ std::stringstream ss;
897+ ss << std::put_time (std::localtime (&in_time_t ), " %Y-%m-%d-%H-%M-%S" );
898+ std::string backup_path = car_path + " .bk-" + ss.str ();
899+
900+ std::ofstream backup_out (backup_path);
901+ if (backup_out.good ()) {
902+ backup_out << content;
903+ backup_out.close ();
904+ RCLCPP_INFO (nh_->get_logger (), " Created backup config: %s" , backup_path.c_str ());
905+ } else {
906+ RCLCPP_WARN (nh_->get_logger (), " Failed to create backup config: %s" , backup_path.c_str ());
907+ }
908+ }
909+
910+ // Prepare the offset lines with current absolute values
911+ std::string steering_line = " steering_angle_to_servo_offset = " +
912+ std::to_string (steering_offset_trim_) + " ;\n " ;
913+ std::string speed_line = " speed_to_erpm_offset = " +
914+ std::to_string (speed_offset_trim_) + " ;\n " ;
915+
916+ // Check if values already exist and update them, or append if not
917+ // Regex to match existing assignments. Note: values can be negative.
918+ std::regex steering_regex (R"( steering_angle_to_servo_offset\s*=\s*[-+]?[0-9]*\.?[0-9]+;)" );
919+ std::regex speed_regex (R"( speed_to_erpm_offset\s*=\s*[-+]?[0-9]*\.?[0-9]+;)" );
920+
921+ bool has_steering = std::regex_search (content, steering_regex);
922+ bool has_speed = std::regex_search (content, speed_regex);
923+
924+ if (has_steering) {
925+ content = std::regex_replace (content, steering_regex,
926+ " steering_angle_to_servo_offset = " +
927+ std::to_string (steering_offset_trim_) + " ;" );
928+ } else {
929+ content += steering_line;
930+ }
931+
932+ if (has_speed) {
933+ content = std::regex_replace (content, speed_regex,
934+ " speed_to_erpm_offset = " +
935+ std::to_string (speed_offset_trim_) + " ;" );
936+ } else {
937+ content += speed_line;
938+ }
939+
940+ // Write back to file
941+ std::ofstream file_out (car_path);
942+ if (file_out.good ()) {
943+ file_out << content;
944+ file_out.close ();
945+ RCLCPP_INFO (nh_->get_logger (),
946+ " Saved offsets to %s: steering=%.3f, speed=%.2f" ,
947+ car_path.c_str (), steering_offset_trim_, speed_offset_trim_);
948+ } else {
949+ RCLCPP_ERROR (nh_->get_logger (), " Failed to write offsets to %s" , car_path.c_str ());
950+ }
951+ }
952+
836953bool VescDriver::isAutonomous (){
837954 return drive_mode_ == kAutonomousDrive || drive_mode_ == kAutonomousContinuousDrive ;
838955}
0 commit comments