ROS 2では通常、ros2 runやros2 launchコマンドを用いてノードを起動しますが、QtのGUIボタンからノードのライフサイクルを管理することも可能です。以下では、Qtのシグナル/スロット機構を使ってPythonで記述されたROS 2ノードを起動・終了する実装例を示します。
1. プロセス管理に必要なLinuxコマンド
ノードの死活監視と強制終了に次のコマンドを利用します。
# プロセス一覧をフィルタ
ps aux | grep my_node
# プロセスを名前で終了
pkill -f "python3 my_node.py"
# シグナル指定で強制終了
pkill -9 -f "python3 my_node.py"
2. バックグラウンドプロセス実行クラス
QProcessをラップしたNodeRunnerクラスを用意します。
NodeRunner.h
#ifndef NODERUNNER_H
#define NODERUNNER_H
#include <QObject>
#include <QProcess>
class NodeRunner : public QObject
{
Q_OBJECT
public:
explicit NodeRunner(QObject *parent = nullptr);
~NodeRunner();
public slots:
void start(const QString &cmd);
void halt();
signals:
void stdoutMsg(const QString &text);
void stderrMsg(const QString &text);
void finished(int code);
private slots:
void onReadyReadOut();
void onReadyReadErr();
void onFinished(int exitCode);
private:
QProcess *proc;
};
#endif // NODERUNNER_H
NodeRunner.cpp
#include "noderunner.h"
#include <QDebug>
NodeRunner::NodeRunner(QObject *parent) : QObject(parent), proc(new QProcess(this))
{
connect(proc, &QProcess::readyReadStandardOutput, this, &NodeRunner::onReadyReadOut);
connect(proc, &QProcess::readyReadStandardError, this, &NodeRunner::onReadyReadErr);
connect(proc, QOverload<int>::of(&QProcess::finished), this, &NodeRunner::onFinished);
}
NodeRunner::~NodeRunner()
{
halt();
delete proc;
}
void NodeRunner::start(const QString &cmd)
{
if (proc->state() != QProcess::NotRunning) {
qWarning() << "Already running";
return;
}
QStringList parts = cmd.split(' ');
QString program = parts.takeFirst();
proc->start(program, parts);
}
void NodeRunner::halt()
{
if (proc->state() != QProcess::NotRunning) {
proc->terminate();
if (!proc->waitForFinished(2000))
proc->kill();
}
}
void NodeRunner::onReadyReadOut()
{
emit stdoutMsg(proc->readAllStandardOutput());
}
void NodeRunner::onReadyReadErr()
{
emit stderrMsg(proc->readAllStandardError());
}
void NodeRunner::onFinished(int exitCode)
{
emit finished(exitCode);
}
3. GUI側の統合
メインウィンドウでNodeRunnerを利用し、ボタン押下でノードを制御します。
// MainWindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow), runner(new NodeRunner(this))
{
ui->setupUi(this);
connect(ui->btnStart, &QPushButton::clicked, this, [this](){
runner->start("ros2 run demo_nodes_py talker");
});
connect(ui->btnStop, &QPushButton::clicked, this, [this](){
runner->halt();
QProcess::execute("pkill -f talker");
});
connect(runner, &NodeRunner::stdoutMsg, this, [](const QString &msg){
qDebug() << "[OUT]" << msg;
});
connect(runner, &NodeRunner::stderrMsg, this, [](const QString &msg){
qDebug() << "[ERR]" << msg;
});
}
MainWindow::~MainWindow()
{
runner->halt();
QThread::msleep(500);
rclcpp::shutdown();
delete ui;
}
4. 注意点
pkill実行後に少し待機(QThread::msleep)を入れることで、確実にプロセスが終了するまで待ちます。- 複数ノードを扱う場合は、
NodeRunnerをインスタンス化するか、プロセス名を一意にして管理してください。 - ROS 2コンテキストはアプリケーション終了時に必ず
rclcpp::shutdown()で破棄します。