Qt GUIでROS 2 Pythonノードを起動・停止する方法

ROS 2では通常、ros2 runros2 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()で破棄します。

タグ: Qt6 ros2 QProcess Pythonノード プロセス管理

7月26日 03:59 投稿