发送一系列命令并等待响应

2024-04-19

我必须更新连接到串行端口的设备上的固件和设置。 由于这是通过一系列命令完成的,因此我发送命令并等待收到答案。在答案(多行)中,我搜索一个字符串,该字符串指示操作是否成功完成。

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

所以我为这个阻塞读/写方法创建了一个新线程。在线程内部,我使用 waitForX() 函数。 如果我调用 watiForKeyword() 它将调用 readLines() 直到检测到关键字或超时

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() 读取所有可用内容并将其分成行,每行都放置在 QStringList 中,为了获取字符串,我调用 getLine() 它返回列表中的第一个字符串并将其删除。

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;
}

问题是,如果我通过终端发送文件来测试应用程序 readLines() 只会读取文件的一小部分(5 行左右)。由于这些行不包含关键字。该函数将再次运行,但这次不会等待超时,readLines 会立即返回 false。 怎么了 ? 另外,我不确定这是否是正确的方法......有谁知道如何发送命令序列并每次等待响应?


让我们使用QStateMachine让这变得简单。让我们回想一下您希望这样的代码是什么样子的:

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

让我们把它放在一个类中,该类对于程序员可能处于的每个状态都有显式的状态成员。我们还将有动作生成器send, expect等等,将给定的操作附加到状态。

// 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; }
};

对于程序员来说,这是功能齐全、完整的代码!完全异步、非阻塞,并且它还可以处理超时。

可以拥有即时生成状态的基础设施,这样您就不必手动创建所有状态。如果您有明确的状态,代码会小得多,恕我直言,更容易理解。只有对于具有 50-100 多个状态的复杂通信协议,摆脱显式命名状态才有意义。

The AppPipe是一个简单的进程内双向管道,可以用作真实串行端口的替代品:

// 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 {
  [...]
};

The StatefulObject拥有一个状态机,一些用于监视状态机进度的基本信号,以及connectSignals用于连接信号与状态的方法:

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(); }
};

The State and FinalState是 Qt 3 风格的简单命名状态包装器。它们允许我们一次性声明状态并为其命名。

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;

动作生成器也非常简单。动作生成器的含义是“当进入给定状态时做某事”。要执行操作的状态始终作为第一个参数给出。第二个和后续参数特定于给定操作。有时,一个动作可能也需要一个目标状态,例如如果成功或失败。

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);
}

The hasLine测试只是检查可以从给定针的设备读取的所有行。这对于这个简单的通信协议来说效果很好。如果您的通信更加复杂,您将需要更复杂的机器。即使您找到了针,也有必要阅读所有行。那是因为这个测试是从readyRead信号,并且在该信号中您必须读取满足所选标准的所有数据。这里,标准是数据形成一条完整的线。

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;
}

使用默认 API 向状态添加受保护的转换有点麻烦,因此我们将对其进行包装以使其更易于使用,并保持上面的操作生成器的可读性:

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;
}

就是这样 - 如果您有一个真正的设备,这就是您所需要的。由于我没有您的设备,我将创建另一个StatefulObject模拟假定的设备行为:

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; }
};

现在让我们把这一切都很好地可视化。我们将有一个带有文本浏览器的窗口,显示通信内容。下面是启动/停止编程器或设备的按钮,以及指示模拟设备和编程器状态的标签:

int main(int argc, char ** argv) {
   using Q = QObject;
   QApplication app{argc, argv};
   Device dev;
   Programmer prog;

   QWidget w;
   QGridLayout grid{&w};
   QTextBrowser comms;
   QPushButton devStart{"Start Device"}, devStop{"Stop Device"},
               progStart{"Start Programmer"};
   QLabel devState, progState;
   grid.addWidget(&comms, 0, 0, 1, 3);
   grid.addWidget(&devState, 1, 0, 1, 2);
   grid.addWidget(&progState, 1, 2);
   grid.addWidget(&devStart, 2, 0);
   grid.addWidget(&devStop, 2, 1);
   grid.addWidget(&progStart, 2, 2);
   devStop.setDisabled(true);
   w.show();

我们将连接设备和程序员的AppPipes。我们还将可视化程序员发送和接收的内容:

   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));
   });

最后,我们将连接按钮和标签:

   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"

The Programmer and Device可以存在于任何线程中。我将它们留在主线程中,因为没有理由将它们移出,但是您可以将它们放入专用线程中,或者将它们放入自己的线程中,或者放入与其他对象共享的线程中,等等。它是完全透明的,因为AppPipe支持跨线程通信。如果QSerialPort被用来代替AppPipe。重要的是每个实例QIODevice仅从一个线程使用。其他一切都是通过信号/槽连接发生的。

例如。如果你想要Programmer要生活在专用线程中,您需要在以下位置添加以下内容main:

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

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

一个小助手格式化数据以使其更易于阅读:

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);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

发送一系列命令并等待响应 的相关文章

  • 如何在 Unity 中从 RenderTexture 访问原始数据

    问题的简短版本 我正在尝试访问 Unity 中 RenderTexture 的内容 我一直在使用 Graphics Blit 使用自己的材质进行绘制 Graphics Blit null renderTexture material 我的材
  • C++ 求二维数组每一行的最大值

    我已经设法用这个找到我的二维数组的每一行的最小值 void findLowest int A Cm int n int m int min A 0 0 for int i 0 i lt n i for int j 0 j lt m j if
  • fgets() 和 Ctrl+D,三次才能结束?

    I don t understand why I need press Ctrl D for three times to send the EOF In addition if I press Enter then it only too
  • Cygwin 下使用 CMake 编译库

    我一直在尝试使用 CMake 来编译 TinyXML 作为一种迷你项目 尝试学习 CMake 作为补充 我试图将其编译成动态库并自行安装 以便它可以工作 到目前为止 我已经设法编译和安装它 但它编译成 dll 和 dll a 让它工作的唯一
  • 使用 Microsoft Graph API 订阅 Outlook 推送通知时出现 400 错误请求错误

    我正在尝试使用 Microsoft Graph API 创建订阅以通过推送通知获取 Outlook 电子邮件 mentions 我在用本文档 https learn microsoft com en us graph api subscri
  • C# 中可空类型是什么?

    当我们必须使用nullable输入 C net 任何人都可以举例说明 可空类型 何时使用可空类型 https web archive org web http broadcast oreilly com 2010 11 understand
  • 使用 C# 在 WinRT 中获取可用磁盘空间

    DllImport kernel32 dll SetLastError true static extern bool GetDiskFreeSpaceEx string lpDirectoryName out ulong lpFreeBy
  • 使用 Google Analytics API 在 C# 中显示信息

    我一整天都在寻找一个好的解决方案 但谷歌发展得太快了 我找不到有效的解决方案 我想做的是 我有一个 Web 应用程序 它有一个管理部分 用户需要登录才能查看信息 在本节中 我想显示来自 GA 的一些数据 例如某些特定网址的综合浏览量 因为我
  • C# 用数组封送结构体

    假设我有一个类似于 public struct MyStruct public float a 我想用一些自定义数组大小实例化一个这样的结构 在本例中假设为 2 然后我将其封送到字节数组中 MyStruct s new MyStruct s
  • c# Asp.NET MVC 使用FileStreamResult下载excel文件

    我需要构建一个方法 它将接收模型 从中构建excel 构建和接收部分完成没有问题 然后使用内存流导出 让用户下载它 不将其保存在服务器上 我是 ASP NET 和 MVC 的新手 所以我找到了指南并将其构建为教程项目 public File
  • .Net Core / 控制台应用程序 / 配置 / XML

    我第一次尝试使用新的 ConfigurationBuilder 和选项模式进入 Net Core 库 这里有很多很好的例子 https docs asp net en latest fundamentals configuration ht
  • 在 ASP.Net Core 2.0 中导出到 Excel

    我曾经使用下面的代码在 ASP NET MVC 中将数据导出到 Excel Response AppendHeader content disposition attachment filename ExportedHtml xls Res
  • 是否有比 lex/flex 更好(更现代)的工具来生成 C++ 分词器?

    我最近将源文件解析添加到现有工具中 该工具从复杂的命令行参数生成输出文件 命令行参数变得如此复杂 以至于我们开始允许它们作为一个文件提供 该文件被解析为一个非常大的命令行 但语法仍然很尴尬 因此我添加了使用更合理的语法解析源文件的功能 我使
  • 我的 strlcpy 版本

    海湾合作委员会 4 4 4 c89 我的程序做了很多字符串处理 我不想使用 strncpy 因为它不会终止 我不能使用 strlcpy 因为它不可移植 只是几个问题 我怎样才能让我的函数正常运行 以确保它完全安全稳定 单元测试 这对于生产来
  • C 中的位移位

    如果与有符号整数对应的位模式右移 则 1 vacant bit will be filled by the sign bit 2 vacant bit will be filled by 0 3 The outcome is impleme
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • GDK3/GTK3窗口更新的精确定时

    我有一个使用 GTK 用 C 语言编写的应用程序 尽管该语言对于这个问题可能并不重要 这个应用程序有全屏gtk window与单个gtk drawing area 对于绘图区域 我已经通过注册了一个刻度回调gtk widget add ti
  • 在Linux中使用C/C++获取机器序列号和CPU ID

    在Linux系统中如何获取机器序列号和CPU ID 示例代码受到高度赞赏 Here http lxr linux no linux v2 6 39 arch x86 include asm processor h L173Linux 内核似
  • C++ 成员函数中的“if (!this)”有多糟糕?

    如果我遇到旧代码if this return 在应用程序中 这种风险有多严重 它是一个危险的定时炸弹 需要立即在应用程序范围内进行搜索和销毁工作 还是更像是一种可以悄悄留在原处的代码气味 我不打算writing当然 执行此操作的代码 相反
  • 如何连接字符串和常量字符?

    我需要将 hello world 放入c中 我怎样才能做到这一点 string a hello const char b world const char C string a hello const char b world a b co

随机推荐

  • 使用 iText 段落之间的图像

    我正在使用 iText 生成自定义 pdf 文档 我尝试了很多 但无法获得包含图像的文本的所需设计 我需要如下所示的输出 我尝试过 Chunk 类和 Paragraph 类 但我无法获得所需的结果 有任何想法吗 你有 至少 两个选择 Use
  • MySQL select for update 返回空集,即使存在一行

    我发现 MySQL 的 选择更新 有一个奇怪的问题 我使用的是5 1 45版本 我有两张桌子 mysql gt show create table tag Tabl
  • 在 Mac 上打开 CSV 文件时出现错误 53

    当我尝试打开 CSV 文件时 我得到 错误 53 找不到文件 我在第四行收到错误 Open FilePath For Input As 1我究竟做错了什么 这是我第一次打开 CSV 请宽容我的代码 Sub opentextfile Dim
  • “Android”中的所见即所得视图编辑器?

    复制 有适用于 Google Android 的表单设计器吗 https stackoverflow com questions 1755860 我想移动一个复选框 以便它显示在与 main xml 内绝对布局下的左上角不同的位置 对于 A
  • 这个文件格式叫什么

    我需要解析以下格式的文件 General Description Some Text Version 4 ProjType 1 Configurations Mice BuildOutputs BuildProject OutputFile
  • 更改背景颜色

    好吧 我对 vim 还很陌生 我不知道如何更改背景颜色 我正在编辑 vimrc 文件来设置这些颜色 但找不到任何背景颜色 我正在使用一个配色方案 我只需要知道如何覆盖它或者要查找什么 以便我可以在我的 color theme vim 文件中
  • 如何让 NSView 不裁剪其边界区域?

    我在 Xcode 上为 OS X 创建了一个空的 Cocoa 应用程序 并添加了 void applicationDidFinishLaunching NSNotification aNotification self view NSVie
  • Android Studio 布局编辑器无法渲染自定义视图

    在 Android Studio 中 布局编辑器无法预览 xml 中的自定义视图 非常简单的例子 public class MyCustomView extends FrameLayout public MyCustomView Conte
  • 使用 Lucene 进行精确短语搜索?

    我正在使用 SpanTerm Query 在 lucene 中搜索确切的短语 但这似乎不起作用 这是我的代码 Indexing IndexWriter writer new IndexWriter dir new StandardAnaly
  • 预计结构位于 的左侧。或 .* 但它是一个结构

    我收到编译错误structure required on left side of or on chest contents 0 but chest是一个结构 class Item public int id int dmg class C
  • 将JSON键值对绑定到polymer dart中的表模板

    如何以聚合物表示法绑定到 json 对象内的键 值对 我有模板重复 jsonarray中的对象 我想布置一个表格 假设每个对象有 1 一 2 二 3 三 就像是
  • Python 线程模块导入失败

    我正在尝试导入线程模块 但是 我似乎只是无缘无故地收到了错误 这是我的代码 import threading class TheThread threading Thread def run self print Insert some t
  • 是否可以引用 styles.xml 文件中的属性?

    我想让用户能够切换整个应用程序的颜色皮肤 我的意思是当用户按下屏幕上的按钮时动态切换应用程序的某些自定义视图的样式 我知道如果你打电话Activity setTheme before onCreate 方法 您可以动态更改应用程序的主题 但
  • 循环调用lambdaify,避免显式调用

    我有这个代码 var a b c arr np array 1 2 3 4 5 6 7 8 9 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 y np hsplit arr len var newdict for
  • Django,如果使用原始 SQL,我应该采取哪些步骤来避免 SQL 注入攻击?

    我读到 ORM 应该最大限度地减少 SQL 注入攻击的可能性 然而在 Django 中 有时 ORM 受到一定限制 我需要使用原始 SQL 我应该采取哪些步骤来避免 SQL 注入攻击 目前我知道检查查询字符串中的分号 但除此之外就不知道了
  • 通过ARM模板提供经典云服务

    在我们的一个项目中 我们正在尝试在 Azure 上自动部署云组件 对于大多数组件 基本上所有 ARM 组件 如 Redis 服务总线 应用服务等 我们能够使用 ARM 模板和 Powershell 脚本来实现它 然而 我们却陷入了困境云服务
  • List.filter 中的下划线

    为什么这不起作用 List true false filter size 错误说
  • Ruby:未初始化常量 Log4r::DEBUG (NameError) 问题

    使用时log4r在 Ruby 中 我编写了一个类似于以下内容的配置文件 require rubygems require log4r require log4r outputter datefileoutputter SERVICE LOG
  • 单例模式 - 早期绑定(涉及静态变量)是否会减少互斥锁的需要?

    他们说早期绑定解决了同步问题 我无法理解 如何 这是 Java 的特殊之处还是 C 也同样适用 那么 使用这种方法我们实际上不需要互斥锁 JVM 确保每个类都已完全加载 然后才允许通过其他线程对其进行任何访问 这意味着所有静态变量 包括un
  • 发送一系列命令并等待响应

    我必须更新连接到串行端口的设备上的固件和设置 由于这是通过一系列命令完成的 因此我发送命令并等待收到答案 在答案 多行 中 我搜索一个字符串 该字符串指示操作是否成功完成 Serial gt write boot 1000 Serial g