2929#include < mutex>
3030#include < errno.h>
3131#include < time.h>
32+ #include < sys/file.h>
33+ #include < unistd.h>
34+ #include < fcntl.h>
3235#include < QApplication>
3336#include < QPushButton>
3437#include < QMetaType>
38+ #include < QMessageBox>
3539#include < memory>
3640#include < atomic>
3741
3842#include " rclcpp/rclcpp.hpp"
3943#include " sensor_msgs/msg/laser_scan.hpp"
4044#include " sensor_msgs/msg/image.hpp"
4145#include " sensor_msgs/msg/joy.hpp"
46+ #include " sensor_msgs/msg/imu.hpp"
4247#include " cv_bridge/cv_bridge.h"
4348#include < opencv2/opencv.hpp>
4449#include < QPixmap>
@@ -53,29 +58,77 @@ using ut_automata::msg::CarStatusMsg;
5358using sensor_msgs::msg::LaserScan;
5459using sensor_msgs::msg::Image;
5560using sensor_msgs::msg::Joy;
61+ using sensor_msgs::msg::Imu;
5662
5763namespace {
5864ut_automata_gui::MainWindow* main_window_ = nullptr ;
5965std::atomic_bool run_{true };
6066std::atomic_bool lidar_okay_{false };
6167std::atomic_bool joystick_okay_{false };
6268std::atomic_bool vesc_okay_{false };
69+ std::atomic_bool imu_okay_{false };
6370std::atomic<float > battery_voltage_{0 .0f };
6471std::atomic_int drive_mode_{0 };
6572std::atomic<float > throttle_{0 .0f };
6673std::atomic<float > steering_{0 .0f };
6774// Track last joystick message time for timeout detection
6875std::atomic<std::chrono::steady_clock::time_point> last_joystick_time_{std::chrono::steady_clock::time_point{}};
76+ // Track last IMU message time for timeout detection
77+ std::atomic<std::chrono::steady_clock::time_point> last_imu_time_{std::chrono::steady_clock::time_point{}};
6978// ROS node and subscriptions for proper cleanup
7079std::shared_ptr<rclcpp::Node> ros_node_ = nullptr ;
7180rclcpp::Subscription<CarStatusMsg>::SharedPtr status_sub_ = nullptr ;
7281rclcpp::Subscription<LaserScan>::SharedPtr lidar_sub_ = nullptr ;
7382rclcpp::Subscription<AckermannCurvatureDriveMsg>::SharedPtr drive_sub_ = nullptr ;
7483rclcpp::Subscription<Image>::SharedPtr camera_sub_ = nullptr ;
7584rclcpp::Subscription<Joy>::SharedPtr joystick_sub_ = nullptr ;
85+ rclcpp::Subscription<Imu>::SharedPtr imu_sub_ = nullptr ;
7686std::mutex cleanup_mutex_;
87+ int lock_fd_ = -1 ; // File descriptor for the lock file
7788} // namespace
7889
90+ // Function to check if another instance is already running
91+ bool IsAnotherInstanceRunning () {
92+ const char * lock_file_path = " /tmp/ut_automata_gui.lock" ;
93+
94+ lock_fd_ = open (lock_file_path, O_CREAT | O_RDWR , 0666 );
95+ if (lock_fd_ == -1 ) {
96+ printf (" Error: Could not create lock file\n " );
97+ return false ; // Assume no other instance if we can't create lock file
98+ }
99+
100+ // Try to acquire an exclusive lock (non-blocking)
101+ if (flock (lock_fd_, LOCK_EX | LOCK_NB ) == -1 ) {
102+ if (errno == EWOULDBLOCK ) {
103+ // printf("Another instance of the GUI is already running\n");
104+ close (lock_fd_);
105+ lock_fd_ = -1 ;
106+ return true ;
107+ } else {
108+ printf (" Error acquiring lock: %s\n " , strerror (errno));
109+ close (lock_fd_);
110+ lock_fd_ = -1 ;
111+ return false ; // Assume no other instance on error
112+ }
113+ }
114+
115+ // Write PID to lock file
116+ char pid_str[32 ];
117+ snprintf (pid_str, sizeof (pid_str), " %d\n " , getpid ());
118+ write (lock_fd_, pid_str, strlen (pid_str));
119+
120+ return false ; // No other instance running
121+ }
122+
123+ // Function to release the instance lock
124+ void ReleaseInstanceLock () {
125+ if (lock_fd_ != -1 ) {
126+ close (lock_fd_); // This automatically releases the flock
127+ unlink (" /tmp/ut_automata_gui.lock" ); // Remove the lock file
128+ lock_fd_ = -1 ;
129+ }
130+ }
131+
79132void StatusCallback (const CarStatusMsg::SharedPtr msg) {
80133 std::lock_guard<std::mutex> lock (cleanup_mutex_);
81134 if (!run_.load () || main_window_ == nullptr || !rclcpp::ok ()) return ;
@@ -127,6 +180,16 @@ void JoystickCallback(const Joy::SharedPtr msg) {
127180 joystick_okay_.store (true );
128181}
129182
183+ // IMU callback - marks IMU as okay when messages are received
184+ void ImuCallback (const Imu::SharedPtr msg) {
185+ std::lock_guard<std::mutex> lock (cleanup_mutex_);
186+ if (!run_.load () || !rclcpp::ok ()) return ;
187+
188+ // Update the last IMU message timestamp
189+ last_imu_time_.store (std::chrono::steady_clock::now ());
190+ imu_okay_.store (true );
191+ }
192+
130193void * RosThread (void * arg) {
131194 // Don't detach - we need to properly join the thread
132195 // pthread_detach(pthread_self());
@@ -144,6 +207,8 @@ void* RosThread(void* arg) {
144207 " /camera_0/image_raw" , 10u , &CameraCallback);
145208 joystick_sub_ = ros_node_->create_subscription <Joy>(
146209 " joystick" , 10u , &JoystickCallback);
210+ imu_sub_ = ros_node_->create_subscription <Imu>(
211+ " /imu" , 10u , &ImuCallback);
147212
148213 RateLoop loop (5.0 );
149214 while (rclcpp::ok () && run_.load ()) {
@@ -168,6 +233,19 @@ void* RosThread(void* arg) {
168233 // If we received a message within the timeout, joystick_okay_ is already set to true in the callback
169234 }
170235
236+ // Check IMU timeout - fail if no message received in the last 1 second
237+ auto last_imu = last_imu_time_.load ();
238+ if (last_imu.time_since_epoch ().count () == 0 ) {
239+ // No IMU message ever received
240+ imu_okay_.store (false );
241+ } else {
242+ auto time_since_last_imu = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_imu).count ();
243+ if (time_since_last_imu > 1000 ) { // 1 second timeout
244+ imu_okay_.store (false );
245+ }
246+ // If we received a message within the timeout, imu_okay_ is already set to true in the callback
247+ }
248+
171249 try {
172250 rclcpp::spin_some (ros_node_);
173251 } catch (const std::exception& e) {
@@ -182,6 +260,7 @@ void* RosThread(void* arg) {
182260 vesc_okay_.load (),
183261 lidar_okay_.load (),
184262 joystick_okay_.load (),
263+ imu_okay_.load (),
185264 throttle_.load (),
186265 steering_.load ());
187266 }
@@ -219,6 +298,10 @@ void* RosThread(void* arg) {
219298 joystick_sub_.reset ();
220299 printf (" Joystick subscription cleaned up\n " );
221300 }
301+ if (imu_sub_) {
302+ imu_sub_.reset ();
303+ printf (" IMU subscription cleaned up\n " );
304+ }
222305 } catch (const std::exception& e) {
223306 printf (" Exception during subscription cleanup: %s\n " , e.what ());
224307 }
@@ -243,6 +326,7 @@ void* RosThread(void* arg) {
243326 drive_sub_.reset ();
244327 camera_sub_.reset ();
245328 joystick_sub_.reset ();
329+ imu_sub_.reset ();
246330 ros_node_.reset ();
247331 }
248332 }
@@ -257,6 +341,9 @@ void SignalHandler(int num) {
257341 printf (" \n Received signal %d, shutting down gracefully...\n " , num);
258342 run_.store (false );
259343
344+ // Release the instance lock
345+ ReleaseInstanceLock ();
346+
260347 // Give the ROS thread time to process the shutdown signal
261348 std::this_thread::sleep_for (std::chrono::milliseconds (200 ));
262349
@@ -269,6 +356,18 @@ void SignalHandler(int num) {
269356}
270357
271358int main (int argc, char *argv[]) {
359+ // Check if another instance is already running
360+ if (IsAnotherInstanceRunning ()) {
361+ // // Create a minimal QApplication to show the message box
362+ // QApplication app(argc, argv);
363+ // QMessageBox::warning(nullptr,
364+ // "UT Automata GUI",
365+ // "Another instance of the GUI is already running.\n"
366+ // "Only one instance is allowed at a time.",
367+ // QMessageBox::Ok);
368+ return 1 ;
369+ }
370+
272371 rclcpp::init (argc, argv);
273372 signal (SIGINT , &SignalHandler);
274373 qRegisterMetaType<std::vector<std::string> >(" std::vector<std::string>" );
@@ -326,5 +425,8 @@ int main(int argc, char *argv[]) {
326425 printf (" rclcpp already shut down.\n " );
327426 }
328427
428+ // Release the instance lock before exiting
429+ ReleaseInstanceLock ();
430+
329431 return retval;
330432}
0 commit comments