2015-09-09 18 views
5

Tôi phải cập nhật chương trình cơ sở và cài đặt trên thiết bị được kết nối với cổng nối tiếp. Vì điều này được thực hiện bằng một chuỗi lệnh, tôi gửi một lệnh và chờ cho đến khi tôi đọc lại câu trả lời. Bên trong phần trả lời (nhiều dòng) tôi tìm kiếm một chuỗi cho biết hoạt động đã kết thúc thành công chưa.Gửi chuỗi lệnh và chờ phản hồi

Serial->write(“boot”, 1000); 
Serial->waitForKeyword(“boot successful”); 
Serial->sendFile(“image.dat”); 
… 

Vì vậy, tôi đã tạo một Chủ đề mới cho phương pháp đọc/ghi chặn này. Bên trong thread, tôi sử dụng các hàm waitForX(). Nếu tôi gọi watiForKeyword() nó sẽ gọi readlines() cho đến khi nó phát hiện các từ khóa hoặc timesout

bool waitForKeyword(const QString &keyword) 
{ 
    QString str; 

    // read all lines 
    while(serial->readLines(10000)) 
    { 
     // check each line 
     while((str = serial->getLine()) != "") 
     { 
      // found! 
      if(str.contains(keyword)) 
       return true; 
     } 
    } 
    // timeout 
    return false; 
} 

readlines() đọc tất cả mọi thứ có sẵn và có thể chia nó thành dòng, mỗi dòng được đặt bên trong một QStringList và để có được một string Tôi gọi hàm getLine() trả về chuỗi đầu tiên trong danh sách và xóa nó.

bool SerialPort::readLines(int waitTimeout) 
{ 
if(!waitForReadyRead(waitTimeout)) 
{ 
    qDebug() << "Timeout reading" << endl; 
    return false; 
} 

QByteArray data = readAll(); 
while (waitForReadyRead(100)) 
    data += readAll(); 

char* begin = data.data(); 
char* ptr = strstr(data, "\r\n"); 

while(ptr != NULL) 
{ 
    ptr+=2; 
    buffer.append(begin, ptr - begin); 
    emit readyReadLine(buffer); 
    lineBuffer.append(QString(buffer)); // store line in Qstringlist 
    buffer.clear(); 

    begin = ptr; 
    ptr = strstr(begin, "\r\n"); 
} 
// rest 
buffer.append(begin, -1); 
return true; 
} 

Vấn đề là nếu tôi gửi tệp qua thiết bị đầu cuối để kiểm tra ứng dụng readLines() sẽ chỉ đọc phần nhỏ của tệp (5 dòng hoặc hơn). Vì những dòng này không chứa từ khóa. các chức năng sẽ chạy một lần nữa, nhưng lần này nó không chờ đợi thời gian chờ, readLines chỉ trả về false ngay lập tức. Có gì không ổn? Ngoài ra tôi không shure nếu đây là cách tiếp cận đúng ... Có ai biết làm thế nào để gửi một sequenze lệnh và chờ đợi một phản ứng mỗi lần?

+0

Mà không biết những gì các lớp Serial là, câu hỏi ban đầu của bạn tại sao nó đã bỏ qua phần còn lại của tập tin không thể được trả lời. Tuy nhiên, lưu ý rằng trong các thiết bị cổng nối tiếp Linux không hoạt động như ổ cắm liên quan đến IO không chặn, vì vậy đây có thể là lý do tại sao. (Về cơ bản bạn không thể sử dụng I/O không bị chặn với các cổng nối tiếp, đó là lý do tại sao lớp QSerialPort chính thức được thêm vào trong Qt 5.1, mô phỏng giao tiếp không đồng bộ với một luồng.) – MuchToLearn

Trả lời

9

Hãy sử dụng QStateMachine để thực hiện việc này đơn giản. Chúng ta hãy nhớ lại cách bạn muốn mã như vậy sẽ trông:.

Serial->write(“boot”, 1000); 
Serial->waitForKeyword(“boot successful”); 
Serial->sendFile(“image.dat”); 

Hãy đặt nó trong một lớp học có các thành viên nhà nước rõ ràng cho mỗi tiểu bang các lập trình viên có thể ở Chúng tôi cũng sẽ phải phát hành send, expect vv đính kèm các hành động cụ thể cho các trạng thái.

// https://github.com/KubaO/stackoverflown/tree/master/questions/comm-commands-32486198 
#include <QtWidgets> 
#include <private/qringbuffer_p.h> 
#include <type_traits> 

[...] 

class Programmer : public StatefulObject { 
    Q_OBJECT 
    AppPipe m_port { nullptr, QIODevice::ReadWrite, this }; 
    State  s_boot { &m_mach, "s_boot" }, 
       s_send { &m_mach, "s_send" }; 
    FinalState s_ok  { &m_mach, "s_ok" }, 
       s_failed { &m_mach, "s_failed" }; 
public: 
    Programmer(QObject * parent = 0) : StatefulObject(parent) { 
     connectSignals(); 
     m_mach.setInitialState(&s_boot); 
     send (&s_boot, &m_port, "boot\n"); 
     expect(&s_boot, &m_port, "boot successful", &s_send, 1000, &s_failed); 
     send (&s_send, &m_port, ":HULLOTHERE\n:00000001FF\n"); 
     expect(&s_send, &m_port, "load successful", &s_ok, 1000, &s_failed); 
    } 
    AppPipe & pipe() { return m_port; } 
}; 

Đây là mã đầy đủ chức năng, đầy đủ cho lập trình viên! Hoàn toàn không đồng bộ, không chặn và cũng xử lý hết thời gian chờ.

Có thể có cơ sở hạ tầng tạo trạng thái nhanh chóng để bạn không phải tạo thủ công tất cả các trạng thái. Mã nhỏ hơn nhiều và IMHO dễ dàng hơn để comperehend nếu bạn có trạng thái rõ ràng. Chỉ cho các giao thức truyền thông phức tạp với 50-100 + tiểu bang sẽ có ý nghĩa để loại bỏ các trạng thái được đặt tên rõ ràng.

Các AppPipe là một đơn giản trong nội bộ quá trình ống hai chiều có thể được sử dụng như là một stand-in cho một cổng nối tiếp thực:

// See http://stackoverflow.com/a/32317276/1329652 
/// A simple point-to-point intra-process pipe. The other endpoint can live in any 
/// thread. 
class AppPipe : public QIODevice { 
    [...] 
}; 

Các StatefulObject giữ một máy nhà nước, một số tín hiệu cơ bản để theo dõi khả tiến bộ máy nhà nước, và connectSignals phương pháp sử dụng để kết nối tín hiệu với các tiểu bang:

class StatefulObject : public QObject { 
    Q_OBJECT 
    Q_PROPERTY (bool running READ isRunning NOTIFY runningChanged) 
protected: 
    QStateMachine m_mach { this }; 
    StatefulObject(QObject * parent = 0) : QObject(parent) {} 
    void connectSignals() { 
     connect(&m_mach, &QStateMachine::runningChanged, this, &StatefulObject::runningChanged); 
     for (auto state : m_mach.findChildren<QAbstractState*>()) 
     QObject::connect(state, &QState::entered, this, [this, state]{ 
      emit stateChanged(state->objectName()); 
     }); 
    } 
public: 
    Q_SLOT void start() { m_mach.start(); } 
    Q_SIGNAL void runningChanged(bool); 
    Q_SIGNAL void stateChanged(const QString &); 
    bool isRunning() const { return m_mach.isRunning(); } 
}; 

các StateFinalState là wrappers nhà nước đặt tên đơn giản theo phong cách của Qt 3. Họ cho phép chúng tôi tuyên bố nhà nước và đặt tên cho nó một lần.

template <class S> struct NamedState : S { 
    NamedState(QState * parent, const char * name) : S(parent) { 
     this->setObjectName(QLatin1String(name)); 
    } 
}; 
typedef NamedState<QState> State; 
typedef NamedState<QFinalState> FinalState; 

Trình tạo tác vụ cũng khá đơn giản. Ý nghĩa của một bộ tạo hành động là "làm điều gì đó khi một trạng thái đã cho được nhập". Nhà nước để hành động luôn luôn được đưa ra như là đối số đầu tiên. Các đối số thứ hai và tiếp theo là cụ thể cho hành động đã cho. Đôi khi, một hành động cũng có thể cần một trạng thái đích, ví dụ: nếu nó thành công hay thất bại.

void send(QAbstractState * src, QIODevice * dev, const QByteArray & data) { 
    QObject::connect(src, &QState::entered, dev, [dev, data]{ 
     dev->write(data); 
    }); 
} 

QTimer * delay(QState * src, int ms, QAbstractState * dst) { 
    auto timer = new QTimer(src); 
    timer->setSingleShot(true); 
    timer->setInterval(ms); 
    QObject::connect(src, &QState::entered, timer, static_cast<void (QTimer::*)()>(&QTimer::start)); 
    QObject::connect(src, &QState::exited, timer, &QTimer::stop); 
    src->addTransition(timer, SIGNAL(timeout()), dst); 
    return timer; 
} 

void expect(QState * src, QIODevice * dev, const QByteArray & data, QAbstractState * dst, 
      int timeout = 0, QAbstractState * dstTimeout = nullptr) 
{ 
    addTransition(src, dst, dev, SIGNAL(readyRead()), [dev, data]{ 
     return hasLine(dev, data); 
    }); 
    if (timeout) delay(src, timeout, dstTimeout); 
} 

Bài kiểm tra hasLine chỉ cần kiểm tra tất cả các dòng có thể đọc từ thiết bị của kim đã cho. Điều này làm việc tốt cho giao thức truyền thông đơn giản này. Bạn sẽ cần máy móc phức tạp hơn nếu thông tin liên lạc của bạn có liên quan nhiều hơn. Nó là cần thiết để đọc tất cả các dòng, ngay cả khi bạn tìm thấy kim của bạn. Đó là vì thử nghiệm này được gọi từ tín hiệu readyRead và trong tín hiệu đó, bạn phải đọc tất cả dữ liệu đáp ứng tiêu chí đã chọn. Ở đây, tiêu chí là dữ liệu tạo thành một dòng đầy đủ.

static bool hasLine(QIODevice * dev, const QByteArray & needle) { 
    auto result = false; 
    while (dev->canReadLine()) { 
     auto line = dev->readLine(); 
     if (line.contains(needle)) result = true; 
    } 
    return result; 
} 

Thêm hiệu ứng chuyển tiếp bảo vệ cho các quốc gia là một chút rườm rà với các API mặc định, vì vậy chúng tôi sẽ quấn nó để làm cho nó dễ dàng hơn để sử dụng, và để giữ cho máy phát điện hoạt động trên có thể đọc được:

template <typename F> 
class GuardedSignalTransition : public QSignalTransition { 
    F m_guard; 
protected: 
    bool eventTest(QEvent * ev) Q_DECL_OVERRIDE { 
     return QSignalTransition::eventTest(ev) && m_guard(); 
    } 
public: 
    GuardedSignalTransition(const QObject * sender, const char * signal, F && guard) : 
     QSignalTransition(sender, signal), m_guard(std::move(guard)) {} 
    GuardedSignalTransition(const QObject * sender, const char * signal, const F & guard) : 
     QSignalTransition(sender, signal), m_guard(guard) {} 
}; 

template <typename F> static GuardedSignalTransition<F> * 
addTransition(QState * src, QAbstractState *target, 
       const QObject * sender, const char * signal, F && guard) { 
    auto t = new GuardedSignalTransition<typename std::decay<F>::type> 
     (sender, signal, std::forward<F>(guard)); 
    t->setTargetState(target); 
    src->addTransition(t); 
    return t; 
} 

Đó là về nó - nếu bạn có một thiết bị thực sự, đó là tất cả những gì bạn cần. Vì tôi không có điện thoại, tôi sẽ tạo ra một StatefulObject bắt chước những hành vi thiết bị coi:

class Device : public StatefulObject { 
    Q_OBJECT 
    AppPipe m_dev { nullptr, QIODevice::ReadWrite, this }; 
    State  s_init  { &m_mach, "s_init" }, 
       s_booting { &m_mach, "s_booting" }, 
       s_firmware { &m_mach, "s_firmware" }; 
    FinalState s_loaded { &m_mach, "s_loaded" }; 
public: 
    Device(QObject * parent = 0) : StatefulObject(parent) { 
     connectSignals(); 
     m_mach.setInitialState(&s_init); 
     expect(&s_init, &m_dev, "boot", &s_booting); 
     delay (&s_booting, 500, &s_firmware); 
     send (&s_firmware, &m_dev, "boot successful\n"); 
     expect(&s_firmware, &m_dev, ":00000001FF", &s_loaded); 
     send (&s_loaded, &m_dev, "load successful\n"); 
    } 
    Q_SLOT void stop() { m_mach.stop(); } 
    AppPipe & pipe() { return m_dev; } 
}; 

Bây giờ chúng ta hãy làm cho nó tất cả độc đáo hình dung. Chúng tôi sẽ có một cửa sổ với một trình duyệt văn bản hiển thị nội dung của các liên lạc. Dưới đây nó sẽ được nút để bắt đầu/dừng các lập trình viên hoặc thiết bị, và nhãn ghi rõ tình trạng của thiết bị mô phỏng và các lập trình viên:

screenshot

Chúng tôi sẽ kết nối của thiết bị và lập trình viên của AppPipe s .Chúng tôi cũng sẽ hình dung những gì các lập trình viên được gửi và nhận:

dev.pipe().addOther(&prog.pipe()); 
    prog.pipe().addOther(&dev.pipe()); 
    Q::connect(&prog.pipe(), &AppPipe::hasOutgoing, &comms, [&](const QByteArray & data){ 
     comms.append(formatData("&gt;", "blue", data)); 
    }); 
    Q::connect(&prog.pipe(), &AppPipe::hasIncoming, &comms, [&](const QByteArray & data){ 
     comms.append(formatData("&lt;", "green", data)); 
    }); 

Cuối cùng, chúng tôi sẽ kết nối các nút và nhãn:

Q::connect(&devStart, &QPushButton::clicked, &dev, &Device::start); 
    Q::connect(&devStop, &QPushButton::clicked, &dev, &Device::stop); 
    Q::connect(&dev, &Device::runningChanged, &devStart, &QPushButton::setDisabled); 
    Q::connect(&dev, &Device::runningChanged, &devStop, &QPushButton::setEnabled); 
    Q::connect(&dev, &Device::stateChanged, &devState, &QLabel::setText); 
    Q::connect(&progStart, &QPushButton::clicked, &prog, &Programmer::start); 
    Q::connect(&prog, &Programmer::runningChanged, &progStart, &QPushButton::setDisabled); 
    Q::connect(&prog, &Programmer::stateChanged, &progState, &QLabel::setText); 
    return app.exec(); 
} 

#include "main.moc" 

Các ProgrammerDevice thể sống trong bất kỳ chủ đề. Tôi đã để chúng trong chủ đề chính vì không có lý do để di chuyển chúng ra, nhưng bạn có thể đặt cả hai vào một chủ đề chuyên dụng, hoặc mỗi chủ đề riêng của nó, hoặc vào các chủ đề được chia sẻ với các đối tượng khác, v.v. Nó hoàn toàn minh bạch kể từ AppPipe hỗ trợ truyền thông qua các chủ đề. Đây cũng là trường hợp nếu QSerialPort được sử dụng thay vì AppPipe. Tất cả những điều quan trọng là mỗi trường hợp của một QIODevice chỉ được sử dụng từ một chủ đề. Mọi thứ khác xảy ra thông qua kết nối tín hiệu/khe.

Ví dụ: nếu bạn muốn Programmer sống trong một thread chuyên dụng, bạn muốn thêm một nơi nào đó sau trong main:

// fix QThread brokenness 
    struct Thread : QThread { ~Thread() { quit(); wait(); } }; 

    Thread progThread; 
    prog.moveToThread(&progThread); 
    progThread.start(); 

Một helper ít định dạng dữ liệu để làm cho nó dễ dàng hơn để đọc:

static QString formatData(const char * prefix, const char * color, const QByteArray & data) { 
    auto text = QString::fromLatin1(data).toHtmlEscaped(); 
    if (text.endsWith('\n')) text.truncate(text.size() - 1); 
    text.replace(QLatin1Char('\n'), QString::fromLatin1("<br/>%1 ").arg(QLatin1String(prefix))); 
    return QString::fromLatin1("<font color=\"%1\">%2 %3</font><br/>") 
     .arg(QLatin1String(color)).arg(QLatin1String(prefix)).arg(text); 
} 
+1

Điều này phải là 100+ câu trả lời, tôi không thể dừng bản thân mình từ bình luận :) – Mike

1

Tôi không chắc chắn đây có phải là phương pháp phù hợp hay không.

Bạn đang bỏ phiếu với waitForReadyRead(). Nhưng kể từ khi cổng nối tiếp là QIODevice, nó sẽ phát ra một dấu hiệu void QIODevice::readyRead() khi một cái gì đó sẽ đến trên cổng nối tiếp. Tại sao không kết nối tín hiệu này với mã phân tích đầu vào của bạn? Không cần waitForReadyRead().

Ngoài ra/mặt khác: "... lần này nó không chờ thời gian chờ, readLines chỉ trả về false ngay lập tức. Có gì sai?"

Trích dẫn tài liệu:

Nếu waitForReadyRead() trả về false, kết nối đã được đóng hoặc một lỗi đã xảy ra.

(tôi nhấn mạnh) Từ kinh nghiệm của tôi như là một nhà phát triển nhúng, nó không phải là bất khả thi mà bạn đặt thiết bị vào loại một chế độ "nâng cấp firmware", và rằng bằng cách làm như vậy các thiết bị khởi động lại vào một khởi động đặc biệt chế độ (không chạy chương trình cơ sở bạn sắp cập nhật) và do đó đóng kết nối. Không có cách nào để nói trừ khi nó được tài liệu/bạn có liên hệ với các nhà phát triển thiết bị. Không rõ ràng để kiểm tra bằng cách sử dụng một thiết bị đầu cuối nối tiếp để gõ lệnh của bạn và chứng kiến ​​rằng, tôi sử dụng minicom hàng ngày kết nối với các thiết bị của tôi và nó khá đàn hồi trên khởi động lại - tốt cho tôi.

+0

Trong khi thử nghiệm chương trình tôi sử dụng hai cổng com ảo . Một kết nối với ứng dụng và một ứng dụng khác để thực hiện. Tôi gửi các tập tin qua realterm và kiểm tra những gì các ứng dụng nào. Nếu tôi kết nối mã readLine trực tiếp với tín hiệu readyRead() thì eventloop không có cơ hội cập nhật các sự kiện, vì tôi chỉ xoay bên trong waitForKeyword(). Có cách nào để buộc tổ chức sự kiện cập nhật không? Nếu vậy làm thế nào để tôi thực hiện điều đó trong các chức năng của tôi? – silversircel

+1

@silversircel Không viết mã đồng bộ giả. Bạn không nên sử dụng bất kỳ phương thức 'waitForXxxx' nào cả. Không ai. Hãy quên rằng họ tồn tại, và nghĩ về cách giải quyết nó sau đó. Tất cả những gì bạn có thể làm và cần làm là kết nối với tín hiệu 'readyRead'. Và tất cả sẽ hoạt động, vì bạn không chặn nữa với các cuộc gọi phương thức 'wait'. –

+0

@KubaOber Nhưng làm thế nào để tôi chờ đợi nếu tất cả mọi thứ là không đồng bộ? Đó chính xác là điểm tôi đang gặp rắc rối. Nếu tôi gửi lệnh và nhận phản hồi, dẫn đến một vị trí. Tôi có nên kiểm tra từng từ khóa có thể trong một trường hợp chuyển đổi lớn và quyết định phải làm gì tiếp theo không? Phải có một số kiểu thiết kế. Tôi cần một cái gì đó giống như một máy nhà nước ... làm bước 1 sau đó làm bước 2 sau đó ... Làm thế nào để thực hiện một cái gì đó như thế với tín hiệu và khe cắm? – silversircel

Các vấn đề liên quan