Skip to content

Commit 8f5758b

Browse files
committed
fix status indicators
1 parent fcf028f commit 8f5758b

3 files changed

Lines changed: 178 additions & 8 deletions

File tree

src/gui/gui_main.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
#include "amrl_msgs/msg/ackermann_curvature_drive_msg.hpp"
5252
#include "ut_automata/msg/car_status_msg.hpp"
53+
#include "std_msgs/msg/int32.hpp"
5354
#include "gui_mainwindow.h"
5455
#include "shared/util/timer.h"
5556

@@ -59,6 +60,7 @@ using sensor_msgs::msg::LaserScan;
5960
using sensor_msgs::msg::Image;
6061
using sensor_msgs::msg::Joy;
6162
using sensor_msgs::msg::Imu;
63+
using std_msgs::msg::Int32;
6264

6365
namespace {
6466
ut_automata_gui::MainWindow* main_window_ = nullptr;
@@ -71,6 +73,7 @@ std::atomic<float> battery_voltage_{0.0f};
7173
std::atomic_int drive_mode_{0};
7274
std::atomic<float> throttle_{0.0f};
7375
std::atomic<float> steering_{0.0f};
76+
std::atomic_bool is_recording_{false};
7477
// Track last joystick message time for timeout detection
7578
std::atomic<std::chrono::steady_clock::time_point> last_joystick_time_{std::chrono::steady_clock::time_point{}};
7679
// Track last IMU message time for timeout detection
@@ -83,6 +86,7 @@ rclcpp::Subscription<AckermannCurvatureDriveMsg>::SharedPtr drive_sub_ = nullptr
8386
rclcpp::Subscription<Image>::SharedPtr camera_sub_ = nullptr;
8487
rclcpp::Subscription<Joy>::SharedPtr joystick_sub_ = nullptr;
8588
rclcpp::Subscription<Imu>::SharedPtr imu_sub_ = nullptr;
89+
rclcpp::Subscription<Int32>::SharedPtr recording_sub_ = nullptr;
8690
std::mutex cleanup_mutex_;
8791
int lock_fd_ = -1; // File descriptor for the lock file
8892
} // namespace
@@ -190,6 +194,15 @@ void ImuCallback(const Imu::SharedPtr msg) {
190194
imu_okay_.store(true);
191195
}
192196

197+
// Recording status callback
198+
void RecordingCallback(const Int32::SharedPtr msg) {
199+
std::lock_guard<std::mutex> lock(cleanup_mutex_);
200+
if (!run_.load() || main_window_ == nullptr || !rclcpp::ok()) return;
201+
202+
bool recording = (msg->data == 1);
203+
is_recording_.store(recording);
204+
}
205+
193206
void* RosThread(void* arg) {
194207
// Don't detach - we need to properly join the thread
195208
// pthread_detach(pthread_self());
@@ -209,6 +222,8 @@ void* RosThread(void* arg) {
209222
"joystick", 10u, &JoystickCallback);
210223
imu_sub_ = ros_node_->create_subscription<Imu>(
211224
"/imu", 10u, &ImuCallback);
225+
recording_sub_ = ros_node_->create_subscription<Int32>(
226+
"/recording", 10u, &RecordingCallback);
212227

213228
RateLoop loop(5.0);
214229
while (rclcpp::ok() && run_.load()) {
@@ -263,6 +278,7 @@ void* RosThread(void* arg) {
263278
imu_okay_.load(),
264279
throttle_.load(),
265280
steering_.load());
281+
main_window_->UpdateRecordingStatus(is_recording_.load());
266282
}
267283
loop.Sleep();
268284
}
@@ -302,6 +318,10 @@ void* RosThread(void* arg) {
302318
imu_sub_.reset();
303319
printf("IMU subscription cleaned up\n");
304320
}
321+
if (recording_sub_) {
322+
recording_sub_.reset();
323+
printf("Recording subscription cleaned up\n");
324+
}
305325
} catch (const std::exception& e) {
306326
printf("Exception during subscription cleanup: %s\n", e.what());
307327
}
@@ -327,6 +347,7 @@ void* RosThread(void* arg) {
327347
camera_sub_.reset();
328348
joystick_sub_.reset();
329349
imu_sub_.reset();
350+
recording_sub_.reset();
330351
ros_node_.reset();
331352
}
332353
}

src/gui/gui_mainwindow.cc

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ void CameraDisplay::resizeEvent(QResizeEvent* event) {
154154
}
155155
}
156156

157-
StatusLed::StatusLed(QString name) : led_(nullptr) {
157+
StatusLed::StatusLed(QString name, bool is_recording_led) : led_(nullptr) {
158158
QFont font("Arial");
159159
font.setPointSize(15);
160160
QLabel* label = new QLabel(name);
161161
label->setFont(font);
162-
led_ = new Led();
162+
led_ = new Led(is_recording_led);
163163
led_->setFixedSize(30, 30);
164164
QHBoxLayout* layout = new QHBoxLayout();
165165
layout->addWidget(label);
@@ -316,7 +316,8 @@ MainWindow::MainWindow(QWidget* parent) :
316316
display_(nullptr),
317317
status_label_(nullptr),
318318
disk_space_bar_(nullptr),
319-
stop_config_button_(nullptr) {
319+
stop_config_button_(nullptr),
320+
recording_led_(nullptr) {
320321
this->setWindowTitle("UT AUTOmataGUI");
321322

322323
// Ensure window takes full screen space
@@ -336,8 +337,15 @@ MainWindow::MainWindow(QWidget* parent) :
336337
status_label_ = new QLabel("Mode: Autonomous\nBattery: 0V");
337338
status_label_->setFont(font);
338339
status_label_->setAlignment(Qt::AlignHCenter);
340+
341+
// Add recording indicator
342+
recording_led_ = new StatusLed("REC", true); // true = use grey when off
343+
recording_led_->setFixedHeight(60);
344+
339345
top_bar->addWidget(ipaddr_label_);
340346
top_bar->addStretch();
347+
top_bar->addWidget(recording_led_);
348+
top_bar->addStretch();
341349
top_bar->addWidget(status_label_);
342350
top_bar->addStretch();
343351
top_bar->addWidget(close_button);
@@ -437,6 +445,67 @@ MainWindow::MainWindow(QWidget* parent) :
437445

438446
tab_widget_->addTab(main_widget, "Main");
439447
tab_widget_->addTab(ros_group, tr("Configurations"));
448+
449+
// Create Recorder Tab
450+
QWidget* recorder_widget = new QWidget();
451+
QVBoxLayout* recorder_layout = new QVBoxLayout();
452+
453+
QLabel* recorder_title = new QLabel("Recording Topics");
454+
recorder_title->setFont(font);
455+
recorder_layout->addWidget(recorder_title);
456+
457+
// Create scroll area for topics
458+
QWidget* topics_container = new QWidget();
459+
QVBoxLayout* topics_layout = new QVBoxLayout();
460+
topics_container->setLayout(topics_layout);
461+
462+
// Initialize default topics
463+
selected_topics_ = {
464+
"/scan",
465+
"/camera_0/image_raw",
466+
"/ackermann_curvature_drive",
467+
"/car_status",
468+
"/joystick",
469+
"/imu",
470+
"/odom"
471+
};
472+
473+
available_topics_ = {
474+
"/scan",
475+
"/camera_0/image_raw",
476+
"/ackermann_curvature_drive",
477+
"/car_status",
478+
"/joystick",
479+
"/imu",
480+
"/odom",
481+
"/tf",
482+
"/tf_static"
483+
};
484+
485+
topic_buttons_.clear();
486+
487+
for (const auto& topic : available_topics_) {
488+
QPushButton* topic_btn = new QPushButton(QString::fromStdString(topic));
489+
topic_btn->setFont(font);
490+
topic_btn->setCheckable(true);
491+
492+
// Check if topic is in selected list
493+
bool is_selected = std::find(selected_topics_.begin(),
494+
selected_topics_.end(),
495+
topic) != selected_topics_.end();
496+
topic_btn->setChecked(is_selected);
497+
498+
connect(topic_btn, SIGNAL(clicked()), this, SLOT(ToggleTopicRecording()));
499+
topic_buttons_.push_back(topic_btn);
500+
topics_layout->addWidget(topic_btn);
501+
}
502+
503+
topics_layout->addStretch();
504+
505+
recorder_layout->addWidget(topics_container);
506+
recorder_widget->setLayout(recorder_layout);
507+
508+
tab_widget_->addTab(recorder_widget, tr("Recorder"));
440509
}
441510

442511
main_layout_ = new QVBoxLayout(this);
@@ -464,6 +533,10 @@ MainWindow::MainWindow(QWidget* parent) :
464533
connect(this,
465534
SIGNAL(UpdateCameraSignal(const QPixmap&)),
466535
SLOT(UpdateCameraSlot(const QPixmap&)));
536+
537+
connect(this,
538+
SIGNAL(UpdateRecordingStatusSignal(bool)),
539+
SLOT(UpdateRecordingStatusSlot(bool)));
467540
}
468541

469542
MainWindow::~MainWindow() {
@@ -737,4 +810,54 @@ void MainWindow::StopTmuxConfiguration() {
737810
}
738811
}
739812

813+
void MainWindow::UpdateRecordingStatus(bool recording) {
814+
UpdateRecordingStatusSignal(recording);
815+
}
816+
817+
void MainWindow::UpdateRecordingStatusSlot(bool recording) {
818+
if (recording_led_) {
819+
recording_led_->SetStatus(recording);
820+
}
821+
}
822+
823+
void MainWindow::ToggleTopicRecording() {
824+
QPushButton* button = qobject_cast<QPushButton*>(sender());
825+
if (!button) return;
826+
827+
// Find which topic was toggled
828+
for (size_t i = 0; i < topic_buttons_.size(); ++i) {
829+
if (topic_buttons_[i] == button) {
830+
std::string topic = available_topics_[i];
831+
832+
// Update selected topics list
833+
auto it = std::find(selected_topics_.begin(), selected_topics_.end(), topic);
834+
if (button->isChecked()) {
835+
if (it == selected_topics_.end()) {
836+
selected_topics_.push_back(topic);
837+
}
838+
} else {
839+
if (it != selected_topics_.end()) {
840+
selected_topics_.erase(it);
841+
}
842+
}
843+
844+
// TODO: Publish updated topic list to recorder node
845+
UpdateTopicsToRecord();
846+
break;
847+
}
848+
}
849+
}
850+
851+
void MainWindow::UpdateTopicsToRecord() {
852+
// This will be called when topics are toggled
853+
// For now, we'll log the change
854+
std::string topics_str = "Selected topics: ";
855+
for (const auto& topic : selected_topics_) {
856+
topics_str += topic + ", ";
857+
}
858+
printf("%s\n", topics_str.c_str());
859+
860+
// TODO: Publish to /recorder/set_topics topic
861+
}
862+
740863
} // namespace ut_automata_gui

src/gui/gui_mainwindow.h

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class Led : public QWidget {
6868
Q_OBJECT
6969

7070
public:
71-
Led() : status_on_(false) {}
71+
Led(bool is_recording_led = false) : status_on_(false), is_recording_led_(is_recording_led) {}
7272
void SetStatus(bool value) {
7373
if (status_on_ == value) return;
7474
status_on_ = value;
@@ -79,25 +79,38 @@ class Led : public QWidget {
7979
void paintEvent(QPaintEvent *event) override {
8080
static const QBrush kGreenBrush = QBrush(QColor(0, 225, 0));
8181
static const QBrush kRedBrush = QBrush(QColor(255, 0, 0));
82+
static const QBrush kGreyBrush = QBrush(QColor(128, 128, 128));
8283
QPainter painter;
8384
painter.begin(this);
84-
if (status_on_) {
85-
painter.fillRect(QRectF(0, 0, width(), height()), kGreenBrush);
85+
86+
if (is_recording_led_) {
87+
// Recording LED: Red when recording (on), Grey when not recording (off)
88+
if (status_on_) {
89+
painter.fillRect(QRectF(0, 0, width(), height()), kRedBrush);
90+
} else {
91+
painter.fillRect(QRectF(0, 0, width(), height()), kGreyBrush);
92+
}
8693
} else {
87-
painter.fillRect(QRectF(0, 0, width(), height()), kRedBrush);
94+
// Normal status LED: Green when okay (on), Red when not okay (off)
95+
if (status_on_) {
96+
painter.fillRect(QRectF(0, 0, width(), height()), kGreenBrush);
97+
} else {
98+
painter.fillRect(QRectF(0, 0, width(), height()), kRedBrush);
99+
}
88100
}
89101
painter.end();
90102
}
91103

92104
private:
93105
bool status_on_;
106+
bool is_recording_led_;
94107
};
95108

96109
class StatusLed : public QFrame {
97110
Q_OBJECT
98111

99112
public:
100-
explicit StatusLed(QString name);
113+
explicit StatusLed(QString name, bool is_recording_led = false);
101114
void SetStatus(bool value);
102115

103116
private:
@@ -156,6 +169,7 @@ class MainWindow : public QWidget {
156169
float throttle,
157170
float steering);
158171
void UpdateCamera(const QPixmap& image);
172+
void UpdateRecordingStatus(bool recording);
159173

160174
public slots:
161175
void closeWindow();
@@ -175,6 +189,9 @@ public slots:
175189
void UpdateTmuxConfigurations();
176190
void StartTmuxConfiguration();
177191
void StopTmuxConfiguration();
192+
void UpdateRecordingStatusSlot(bool recording);
193+
void UpdateTopicsToRecord();
194+
void ToggleTopicRecording();
178195

179196
signals:
180197
void UpdateQuestion(std::string question,
@@ -189,6 +206,7 @@ public slots:
189206
float throttle,
190207
float steering);
191208
void UpdateCameraSignal(const QPixmap& image);
209+
void UpdateRecordingStatusSignal(bool recording);
192210

193211
private:
194212

@@ -221,6 +239,14 @@ public slots:
221239
QPushButton* stop_config_button_;
222240
std::vector<std::string> tmux_config_names_;
223241

242+
// Recording status
243+
StatusLed* recording_led_;
244+
245+
// Recording topic selection
246+
std::vector<std::string> available_topics_;
247+
std::vector<std::string> selected_topics_;
248+
std::vector<QPushButton*> topic_buttons_;
249+
224250
// Helper functions for GUI-aware tmux configuration
225251
bool IsGuiNodeRunning();
226252
std::string CreateTmuxConfigWithoutGui(const std::string& config_name);

0 commit comments

Comments
 (0)