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 State
và FinalState
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:
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(">", "blue", data));
});
Q::connect(&prog.pipe(), &AppPipe::hasIncoming, &comms, [&](const QByteArray & data){
comms.append(formatData("<", "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 Programmer
và Device
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);
}
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