Sur cette page

Récepteur bloquant

Explique comment utiliser l'API synchrone de QSerialPort dans un thread non-GUI.

Récepteur bloquant montre comment créer une application pour une interface série à l'aide de l'API synchrone d'QSerialPort dans un thread non-GUI.

Récepteur bloquant pour Qt Serial Port

QSerialPort prend en charge deux approches de programmation générales :

  • L'approche asynchrone (non bloquante). Les opérations sont planifiées et exécutées lorsque le contrôle revient à la boucle d’événements de Qt. QSerialPort émet un signal lorsque l’opération est terminée. Par exemple, QSerialPort::write() renvoie immédiatement un résultat. Lorsque les données sont envoyées vers le port série, QSerialPort émet bytesWritten().
  • L'approche synchrone (bloquante). Dans les applications sans interface graphique et multithread, les fonctions waitFor...() peuvent être appelées (par exemple QSerialPort::waitForReadyRead()) pour suspendre le thread appelant jusqu’à ce que l’opération soit terminée.

Cet exemple illustre l’approche synchrone. L’exemple « Terminal » illustre quant à lui l’approche asynchrone.

Le but de cet exemple est de présenter un modèle que vous pouvez utiliser pour simplifier votre code de programmation série, sans perdre en réactivité au niveau de votre interface utilisateur. L'utilisation de l'API de programmation série bloquante de Qt conduit souvent à un code plus simple, mais en raison de son comportement bloquant, elle ne doit être utilisée que dans des threads non liés à l'interface graphique afin d'éviter que celle-ci ne se fige. Cependant, contrairement à ce que beaucoup pensent, l'utilisation de threads avec QThread n'ajoute pas nécessairement une complexité ingérable à votre application.

Cette application est un « Receiver » (récepteur) qui illustre son fonctionnement en association avec l’application « Blocking Sender »(émetteur blo quant).

L'application « Receiver » reçoit la requête via le port série de la part de l'application « Sender » et lui envoie une réponse.

Nous commencerons par la classe `ReceiverThread`, qui gère le code de programmation série.

class ReceiverThread : public QThread
{
    Q_OBJECT

public:
    explicit ReceiverThread(QObject *parent = nullptr);
    ~ReceiverThread();

    void startReceiver(const QString &portName, int waitTimeout, const QString &response);

signals:
    void request(const QString &s);
    void error(const QString &s);
    void timeout(const QString &s);

private:
    void run() override;

    QString m_portName;
    QString m_response;
    int m_waitTimeout = 0;
    QMutex m_mutex;
    bool m_quit = false;
};

ReceiverThread est une sous-classe de ` QThread ` qui fournit une API permettant de recevoir les requêtes de l’application « Sender » et dispose de signaux pour transmettre les réponses et signaler les erreurs.

Vous devez appeler la méthode `startReceiver()` pour démarrer l’application « Receiver ». Cette méthode transmet à `ReceiverThread` les paramètres souhaités pour la configuration et le démarrage de l’interface série. Lorsque `ReceiverThread` reçoit une requête de l’application « Sender », il émet le signal `request()`. En cas d’erreur, les signaux `error()` ou `timeout()` sont émis.

Il est important de noter que la méthode `startReceiver()` est appelée depuis le thread principal (GUI), mais que les données de réponse et les autres paramètres seront accessibles depuis le thread `ReceiverThread`. Les membres de données de `ReceiverThread` sont lus et écrits simultanément depuis différents threads ; il est donc conseillé d’utiliser ` QMutex ` pour synchroniser les accès.

void ReceiverThread::startReceiver(const QString &portName, int waitTimeout, const QString &response)
{
    const QMutexLocker locker(&m_mutex);
    m_portName = portName;
    m_waitTimeout = waitTimeout;
    m_response = response;
    if (!isRunning())
        start();
}

La fonction `startReceiver()` stocke le nom du port série, le délai d’expiration et les données de réponse, et ` QMutexLocker ` verrouille le mutex pour protéger ces données. Nous lançons ensuite le thread, à moins qu’il ne soit déjà en cours d’exécution. La fonction ` QWaitCondition::wakeOne()` sera abordée plus loin.

void ReceiverThread::run()
{
    bool currentPortNameChanged = false;

    m_mutex.lock();
    QString currentPortName;
    if (currentPortName != m_portName) {
        currentPortName = m_portName;
        currentPortNameChanged = true;
    }

    int currentWaitTimeout = m_waitTimeout;
    QString currentRespone = m_response;
    m_mutex.unlock();

Dans la fonction run(), commencez par acquérir le verrou du mutex, récupérez le nom du port série, le délai d’expiration et les données de réponse à partir des données des membres, puis relâchez à nouveau le verrou. La méthode startReceiver() ne doit en aucun cas être appelée simultanément avec un processus en train de récupérer ces données. La fonction QString est réentrante mais n’est pas sûre pour les threads ; il n’est donc pas recommandé de lire le nom du port série à partir d’un démarrage, d’un appel, d’un délai d’expiration ou de données de réponse provenant d’un autre. ReceiverThread ne peut gérer qu’un seul démarrage à la fois.

L’objet ` QSerialPort ` que nous construisons sur la pile dans la fonction `run()` avant d’entrer dans la boucle :

    QSerialPort serial;

    while (!m_quit) {

Cela nous permet de créer un objet une seule fois, pendant l’exécution de la boucle, et signifie également que toutes les méthodes de l’objet seront exécutées dans le contexte du thread run().

Dans la boucle, vérifiez si le nom du port série du démarrage en cours a changé ou non. Si c’est le cas, rouvrez et reconfigurez le port série.

        if (currentPortName.isEmpty()) {
            emit error(tr("Port not set"));
            return;
        } else if (currentPortNameChanged) {
            serial.close();
            serial.setPortName(currentPortName);

            if (!serial.open(QIODevice::ReadWrite)) {
                emit error(tr("Can't open %1, error code %2")
                           .arg(m_portName).arg(serial.error()));
                return;
            }
        }

        if (serial.waitForReadyRead(currentWaitTimeout)) {

La boucle continuera d’attendre les données de requête :

            // read request
            QByteArray requestData = serial.readAll();
            while (serial.waitForReadyRead(10))
                requestData += serial.readAll();

Avertissement : la méthode waitForReadyRead() doit être utilisée avant chaque appel à read() dans le cadre de l’approche bloquante, car elle traite toutes les routines d’E/S à la place de la boucle d’événements Qt.

Le signal timeout() est émis si une erreur se produit lors de la lecture des données.

        } else {
            emit timeout(tr("Wait read request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }

Une fois la lecture réussie, essayez d’envoyer une réponse et attendez la fin du transfert :

            // write response
            const QByteArray responseData = currentRespone.toUtf8();
            serial.write(responseData);
            if (serial.waitForBytesWritten(m_waitTimeout)) {
                const QString request = QString::fromUtf8(requestData);
                emit this->request(request);

Avertissement : la méthode waitForBytesWritten() doit être utilisée après chaque appel à write() dans le cadre de l'approche bloquante, car elle traite toutes les routines d'E/S à la place de la boucle d'événements Qt.

Le signal timeout() est émis si une erreur survient lors de l’écriture des données.

            } else {
                emit timeout(tr("Wait write response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }

Une fois l'écriture réussie, le signal `request()` contenant les données reçues de l'application expéditrice est émis :

                emit this->request(request);

Ensuite, le thread passe à la lecture des paramètres actuels de l'interface série, car ceux-ci ont peut-être déjà été mis à jour, puis relance la boucle depuis le début.

        m_mutex.lock();
        if (currentPortName != m_portName) {
            currentPortName = m_portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = m_waitTimeout;
        currentRespone = m_response;
        m_mutex.unlock();
    }

Exécution de l’exemple

Vous pouvez exécuter l’exemple à partir de :

Exemple de projet sur code.qt.io

Voir également Terminal série et Émetteur bloquant.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.