7

シリアル ポート経由でデバイスにバイトを送信 (書き込み) しています。QSerialPort ( http://qt-project.org/wiki/QtSerialPort ) モジュールを使用して、デバイス IO サポートをインスタンス化しています。メッセージを INSTEON モデム (シリアル) に送信すると、メッセージを読み取ると、デバイスはメッセージのコピー + 0x06 (ACK バイト) とそれに続くステータス メッセージを返信します。

DockLight ( http://www.docklight.de/ ) を使用してメッセージをテストしました。次のメッセージを送信して、デバイスの状態を照会します。

    02 62 1D E9 4B 05 19 00

Docklight を使用すると、次の応答が返されます。

    02 62 1D E9 4B 05 19 00 06 02 50 20 CB CF 1E DA F7 21 00 FF

返されたメッセージは、デバイスがオンになっていることを正確に示しています。オフの場合、デバイスがオフの場合、モデムは最後のバイト位置で 0x00 を返します。さて、私の問題-応答バイトを送信してから受信するために、関数を適切にセットアップしてはいけません。私は多くの異なる例と構成を試しましたが、現在は以下を使用しています:

シグナルスロット接続をセットアップします。

QObject::connect(&thread, SIGNAL(sendResponse(QByteArray)), 
    this, SLOT(handleResponse(QByteArray)));
QObject::connect(&thread, SIGNAL(error(QString)), 
    this, SLOT(processError(QString)));
QObject::connect(&thread, SIGNAL(timeout(QString)), 
    this, SLOT(processTimeout(QString)));

デバイスの QList を反復処理するために使用される関数。デバイスが目的のタイプ (「ライト」) の場合、デバイス ID を目的の QByteArray メッセージ構造にフォーマットします。メッセージをスレッドに渡して送信します。(QSerialPort のBlockingMaster例から変更されたスレッド。

void Device::currentStatus(QList<Device *> * deviceList){
    QString devID, updateQry;
    int devStatus, updateStatus;
    updateStatus=0;
    QSqlQuery query;
    for(int i=0; i<deviceList->size(); i++){
        if(deviceList->at(i)->type == "Light"){
            devStatus = deviceList->at(i)->status;
            devID = deviceList->at(i)->deviceID;
            QByteArray msg;
            bool msgStatus;
            msg.resize(8);

            msg[0] = 0x02;
            msg[1] = 0x62;
            msg[2] = 0x00;
            msg[3] = 0x00;
            msg[4] = 0x00;
            msg[5] = 0x05;
            msg[6] = 0x19;
            msg[7] = 0x00;
            msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
            qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";
            //send(msg,&msgStatus, &updateStatus);
            //msg.clear();
            thread.setupPort("COM3",500,msg);
            if(devStatus!=updateStatus){
                qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
                updateStatus = !updateStatus;
            }
        }
    }
}

SetupThreadローカルスレッド変数を設定し、スレッドを実行 (実行) するために使用される関数。

void serialThread::setupPort(const QString &portName, int waitTimeout, const QByteArray &msg){
    qDebug() << "Send Message " << msg.toHex();
    QMutexLocker locker(&mutex);
    this->portName = portName;
    this->waitTimeout = waitTimeout;
    this->msg = msg;
    if(!isRunning())
        start();
    else
        cond.wakeOne();
}

Run機能 - 送受信の処理

void serialThread::run(){
    bool currentPortNameChanged = false;
    qDebug() << "Thread executed";
    mutex.lock();
    QString currentPortName;
    if(currentPortName != portName){
        currentPortName = portName;
        currentPortNameChanged = true;
    }

    int currentWaitTimeout = waitTimeout;
    QByteArray sendMsg = msg;
    mutex.unlock();
    QSerialPort serial;

    while(!quit){
        if(currentPortNameChanged){
            serial.close();
            serial.setPortName("COM3");

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

            if (!serial.setBaudRate(QSerialPort::Baud19200)) {
                emit error(tr("Can't set baud rate 9600 baud to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setDataBits(QSerialPort::Data8)) {
                emit error(tr("Can't set 8 data bits to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setParity(QSerialPort::NoParity)) {
                emit error(tr("Can't set no patity to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setStopBits(QSerialPort::OneStop)) {
                emit error(tr("Can't set 1 stop bit to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
                emit error(tr("Can't set no flow control to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }
        }

        //write request
        serial.write(msg);
        if (serial.waitForBytesWritten(waitTimeout)) {
            //! [8] //! [10]
            // read response
            if (serial.waitForReadyRead(currentWaitTimeout)) {
                QByteArray responseData = serial.readAll();
                while (serial.waitForReadyRead(10)){
                    responseData += serial.readAll();
                }

                QByteArray response = responseData;
                //! [12]
                emit this->sendResponse(response);
                //! [10] //! [11] //! [12]
            } else {
                emit this->timeout(tr("Wait read response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }
            //! [9] //! [11]
        } else {
            emit timeout(tr("Wait write request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }
        mutex.lock();
        cond.wait(&mutex);
        if (currentPortName != portName) {
            currentPortName = portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = waitTimeout;
        sendMsg = msg;
        mutex.unlock();
    }
    serial.close();
}

handleResponse関数、応答信号を受信する SLOT

void Device::handleResponse(const QByteArray &msg){
    qDebug() << "Read: " << msg.toHex();
}

次の出力が表示されます。

Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Thread executed 
Read:  "026220cbcf05190006" 
Polling for changes... 
Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Read:  "025020cbcf1edaf721000002621de94b05190006" 
Polling for changes... 
Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Read:  "02501de94b1edaf72100ff02621de94b05190006" 

ここで2つの問題。

  1. 2 番目のデバイス (Bedroom Light) に関する応答がありません。これが 2 番目に送信されたメッセージです。送信がブロックされているようです。最初の送信で応答が受信された後に送信するように、送信をどのようにフォーマットすることをお勧めしますか? 送受信に使用できる COM ポートは 1 つだけです。デバイス 1 にメッセージを送信し、デバイス 1 の応答を受信し、デバイス 2 に送信し、デバイス 2 を受信する必要があると思います。デバイス 2 の通信プロセスを実行する前に、デバイス 1 の通信プロセスが終了するのを待ちますか?

  2. 最初の読み取りには、受信の適切な前半が含まれます。Read: "026220cbcf05190006"2 番目の受信には、最初の応答の後半と 2 番目の応答の前半が含まれます。 Read 2 -Read: "025020cbcf1edaf721000002621de94b05190006"適切な完全な応答は次のとおりです (完全な応答の例では、デバイス 2 の ID に02621DE94B05190006 025020CBCF1EDAF72100FF 注意してください)。20CBCF

シリアルポートからデータを受信する方法を修正する必要がありますか? ありがとうございました!

4

2 に答える 2

3

私の問題はこの質問の範囲から外れたと思います。Kuzulisの助けを借りて、シリアルメッセージを一貫して正常に送受信するための書き込み/読み取り機能を実装しました。Kuzulisは、同期ブロッキング通信パターンの使用を推奨しましたが、後で、非同期非ブロッキング方式が私のアプリケーションに最適であると判断されました。

私の実装は、QSerialPortソースファイルで提供される「マスター」の例に厳密に従っています。

CurrentStatusはオブジェクトのQListを反復処理するために使用しDeviceます。デバイスリストのライトごとに、8バイトのメッセージをフォーマットして、デバイスの現在のステータス(ON / OFF)を照会します。

void Device::currentStatus(QList<Device *> * deviceList){
    QString devID, updateQry;
    int devStatus, updateStatus;
    updateStatus=0;
    QSqlQuery query;
    for(int i=0; i<deviceList->size(); i++){
        if(deviceList->at(i)->type == "Light"){
            devStatus = deviceList->at(i)->status;
            devID = deviceList->at(i)->deviceID;
            QByteArray msg;
            msg.resize(8);

            msg[0] = 0x02;
            msg[1] = 0x62;
            msg[2] = 0x00;
            msg[3] = 0x00;
            msg[4] = 0x00;
            msg[5] = 0x05;
            msg[6] = 0x19;
            msg[7] = 0x00;
            msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
            qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";

            emit writeRequest(msg);

            if(devStatus!=updateStatus){
                qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
                updateStatus = !updateStatus;
            }
        }
    }
}

Deviceクラスのコンストラクターで、シグナルとスロットを接続します。

Device::Device(){

    serialTimer.setSingleShot(true);
    QObject::connect(&serial, SIGNAL(readyRead()),
                     this, SLOT(handleResponse()));
    QObject::connect(&serialTimer, SIGNAL(timeout()),
                     this, SLOT(processTimeout()));
    QObject::connect(this, SIGNAL(writeRequest(QByteArray)),
                     this, SLOT(writeSerial(QByteArray)));
}

送信するメッセージcurrentStatusが準備された後、emit writeRequest(msg);が呼び出されます。これにより、スロットに接続されている信号がディスパッチされwriteRequestます。writeRequestは、メッセージをセットアップして実際にシリアルポートに書き込むために使用されます。

void Device::writeSerial(const QByteArray &msg){
    if (serial.portName() != "COM3") {
        serial.close();
        serial.setPortName("COM3");

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

        if (!serial.setBaudRate(QSerialPort::Baud19200)) {
            processError(tr("Can't set rate 19200 baud to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setDataBits(QSerialPort::Data8)) {
            processError(tr("Can't set 8 data bits to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setParity(QSerialPort::NoParity)) {
            processError(tr("Can't set no patity to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setStopBits(QSerialPort::OneStop)) {
            processError(tr("Can't set 1 stop bit to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
            processError(tr("Can't set no flow control to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }
    }
    qDebug() << "Message written";
    this->msgRequest = msg;
    serial.write(msgRequest);
    serialTimer.start(400);
}

シリアルポートを設定した後、現在のメッセージをに保存しますmsgRequest。エラーが発生した場合、これを使用してメッセージを再送信する必要がある場合があります。呼び出された後serial.write()、400msのタイマーを設定します。このタイマーが切れたら、シリアルポートから読み取ったものを確認します。

handleResponse()QSerialPortがreadyRead()信号を発信するたびに呼び出されるスロットです。readyRead()使用可能なデータをQByteArrayに追加しますresponse

void Device::handleResponse(){
    response.append(serial.readAll());  
}

400ms後、serialTimer(ワンショットタイマー)がtimeout()信号を発します。serialTimer要求されたメッセージをシリアルポートに書き込んだ直後に開始されました。processTimeout()ここで、メッセージを送信した後、PowerLincモデムから受信した応答を最終的に確認します。メッセージがINSTEONPowerLincモデム(PLM)に送信されると、PLMはメッセージをエコーバックし、0x06(正のACK)または0x15(NACK)のいずれかを追加します。私はprocessTimeout()、受信した最後のバイトがACKバイトであることを確認します。そうでない場合は、最初に要求されたメッセージを再送信します。

void Device::processTimeout(){
    qDebug() << "Read: " << response.toHex();
    int msgLength = this->msgRequest.length();
    if(response.at(msgLength)!=0x06){
        qDebug() << "Error, resend.";
        emit writeRequest(msgRequest);
    }
    response.clear();
}

シリアルポートモニター4.0(Eltima Software)を使用して、シリアルポートでの書き込みと読み取りのトランザクションを確認しました。以下に、1つのサンプルトランザクションのログ印刷出力を示します。

20:44:30:666 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00   <--- Send
20:44:30:669 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00 06   <--- Receive
20:44:30:875 STATUS_SUCCESS 02  <--- Receive
20:44:30:881 STATUS_SUCCESS 50 1d e9 4b 1e da f7 21 00 ff   <--- Receive

20回の送信で、同じ応答を受け取りました。したがって、一貫性のないデータ到着に関する問題は解決されたと言っても過言ではありません。現在、複数の書き込み要求に苦労していますが、それは別の質問であると思います。皆様のご支援に感謝申し上げます。

于 2013-03-08T03:00:58.047 に答える
3
  1. リポジトリで BlockingMaster の例を参照し、ブロッキング I/O に関するドキュメントを読んでください。また、ブロッキング I/O を不必要に使用しないでください。

  2. 完全な応答パッケージをすぐに受け取るわけではないため、bytesAvailable() を使用して、読み取り可能なデータの数を取得します。

于 2013-02-27T07:50:26.100 に答える