当我按住一个按钮时,我很难让我的程序不断发送字符串“mobile 200”
。我将按钮设置为自动重复,但它仅在按钮释放时发送,而不是在按住时发送。然而,当按住时,计数器会添加应该发送消息的次数。我迷路了。
主窗口。cpp公司
void MainWindow::on_forwardButton_clicked()
{
if(arduino->isWritable()){
arduino->write(command.toStdString().c_str());
qDebug() << i;
}else{
qDebug() << "Couldn't write to serial!";
}
ui->label->setText("Moving");
i++;
}
主窗口。h类
ifndef MAINWINDOW_H
define MAINWINDOW_H
include <QMainWindow>
include <QDialog>
include <QSerialPort>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_forwardButton_clicked();
private:
Ui::MainWindow *ui;
QSerialPort *arduino; //makes arduino a pointer to the SerialPort
bool arduino_is_available;
QString command = "move 200";
bool buttonReleased = false;
};
endif // MAINWINDOW_H
根据@dtech建议添加代码
pButtonTimer = new QTimer;
connect(pButtonTimer, SIGNAL(timeout()), this, SLOT(sendData()));
int i = 0;
void MainWindow::on_forwardButton_pressed()
{
pButtonTimer->start(1000);
ui->label->setText("Moving");
qDebug() << "Button Pushed";
}
void MainWindow::on_forwardButton_released()
{
pButtonTimer->stop();
}
void MainWindow::sendData(){
i++; //used to count how many times the command should have been sent
qDebug() << i << "sendData is running"; //notifies me the function has been called
if(arduino->isWritable()){
arduino->write(command.toStdString().c_str());
qDebug() << i << "arduino is writable with command " << command; //lets me know the Arduino is writable
}
else{qDebug() << "Couldn't write to serial!";}
}
释放按钮后,Arduino中的串行监视器显示发送的所有内容,机器人移动
“盲目”或未经确认的自动重复不是一个好主意,因为Arduino可能需要一些时间才能对命令做出反应。如果默认情况下,任何地方都没有流量控制,那么在USB到串行芯片(如果有)和Arduino中,缓冲区都会溢出。由于您的数据包(行)没有错误检查,您最终会在Arduino上执行垃圾命令,并产生不同的效果。
至少,Arduino应该发送一条消息,指示命令已完成。它可以是一个简单的Serial.println("OK")
。然后,一旦收到成功的回复,您将立即发送下一个命令。
这会稍微减慢速度,因为只有在您完成接收回复和发送命令后才能处理下一个命令。相反,您可以提前预发送一个或多个命令,以便Arduino始终处于忙碌状态。
我们可以利用Qt来简洁地建模它的PC端以及Arduino。
下面是一个完整的示例,以识字编程风格编写。
首先,我们需要一个本地管道来在PC和模型Arduino之间进行通信。这比使用QLocalServer
要容易得多。
// https://github.com/KubaO/stackoverflown/tree/master/questions/comm-button-loop-43695121
#include <QtWidgets>
#include <private/qringbuffer_p.h>
#include <cctype>
class AppPipe; // See https://stackoverflow.com/a/32317276/1329652
为了管理通信,控制器允许在任何给定时间最多两个“飞行中”命令。这是一个非常简单的控制器-在生产代码中,我们应该有一个显式的状态机,允许错误处理等。请参见例如此问题。
class Controller : public QObject {
Q_OBJECT
int m_sent = {}, m_received = {};
QPointer<QIODevice> m_dev;
QByteArray m_command;
QQueue<QByteArray> m_commands;
void sendCommand() {
if (m_command.isEmpty()) return;
while (m_commands.size() < 2) {
m_commands.enqueue(m_command);
m_dev->write(m_command);
m_dev->write("\n");
m_sent ++;
updateStatus();
}
}
Q_SLOT void updateStatus() {
emit statusChanged(m_sent, m_received, m_commands.size());
}
public:
Controller(QIODevice * dev, QObject * parent = {}) : QObject{parent}, m_dev(dev) {
connect(dev, &QIODevice::readyRead, [this]{
if (!m_dev->canReadLine()) return;
auto const replyFor = m_commands.dequeue();
m_received ++;
if (m_dev->readLine() == "OK\n" || m_dev->readLine() == "ERROR\n")
sendCommand();
updateStatus();
Q_UNUSED(replyFor);
});
QMetaObject::invokeMethod(this, "updateStatus", Qt::QueuedConnection);
}
Q_SLOT void setCommand(const QByteArray & cmd) {
m_command = cmd;
sendCommand();
}
Q_SLOT void stop() {
m_command.clear();
}
Q_SIGNAL void statusChanged(int sent, int received, int queueDepth);
};
class Ui : public QWidget {
Q_OBJECT
QFormLayout m_layout{this};
QPushButton m_move{"Move"};
QLabel m_status;
public:
Ui(QWidget * parent = {}) : QWidget{parent} {
setMinimumWidth(300);
m_layout.addWidget(&m_move);
m_layout.addWidget(&m_status);
connect(&m_move, &QPushButton::pressed, this, &Ui::moveActive);
connect(&m_move, &QPushButton::released, this, &Ui::inactive);
}
Q_SIGNAL void moveActive();
Q_SIGNAL void inactive();
Q_SLOT void setStatus(const QString & status) {
m_status.setText(status);
}
};
我们主要是在PC端完成的-测试设置将稍后在main中进行。
现在我们转向Arduino一侧,模拟一个最小的Arduino环境。回想一下,Arduino的“语言”实际上是C 11!我们使用Qt类实现Arduino功能。
#define F(str) str
QElapsedTimer arduinoTimer;
unsigned long millis() {
return arduinoTimer.elapsed();
}
inline bool isSpace(int c) {
return ( isspace (c) == 0 ? false : true);
}
class Print {
public:
virtual size_t write(uint8_t) = 0;
size_t write(const char *str) {
if (str == nullptr) return 0;
return write((const uint8_t *)str, strlen(str));
}
virtual size_t write(const uint8_t *buffer, size_t size) = 0;
size_t write(const char *buffer, size_t size) {
return write((const uint8_t *)buffer, size);
}
size_t print(const char text[]) { return write(text); }
size_t println(const char text[]) { return write(text) + write("\n"); }
size_t println() { return write("\n"); }
};
class Stream : public Print {
public:
virtual int available() = 0;
virtual int read() = 0;
};
class HardwareSerial : public Stream {
QPointer<QIODevice> m_dev;
public:
void setDevice(QIODevice * dev) { m_dev = dev; }
void begin(int) {}
size_t write(uint8_t c) override {
return m_dev->putChar(c) ? 1 : 0;
}
size_t write(const uint8_t * buffer, size_t size) override {
return m_dev->write((const char*)buffer, size);
}
int read() override {
char c;
return m_dev->getChar(&c) ? c : -1;
}
int available() override {
return m_dev->bytesAvailable();
}
} Serial;
我们现在可以编写Arduino代码,就像它在真实的Arduino上显示的那样。LineEditor是我在Arduino中发现的一个类,它提供异步输入标记化,并允许在设置TTY时进行交互式行编辑。在实际的Arduino上运行时,可以调用Line。设置tty(true)
并通过PUTTY或任何其他终端程序连接到Arduino。是-PUTTY是一种可连接到串行端口的通用终端。
template <unsigned int N> class LineEditor {
char m_data[N];
char * m_ptr;
bool m_has : 1; ///< Have we got a complete line yet?
bool m_tty : 1; ///< Are we an interactive application (attached to a terminal)?
LineEditor(const LineEditor &) = delete;
LineEditor & operator=(const LineEditor &) = delete;
public:
LineEditor() : m_tty{false} { clear(); }
void clear() {
m_data[0] = '\0';
m_ptr = m_data;
m_has = false;
}
void input(Stream & str) {
auto const c = str.read();
if (c == '\r' || c == '\n') {
m_has = true;
m_ptr = m_data;
if (m_tty) str.println();
}
else if (m_tty && (c == '\b' || c == 0x7F)) {
if (m_ptr > m_data) {
*--m_ptr = '\0';
str.print(F("\b \b"));
}
}
else if (c >= 32 && c < 127 && m_ptr < m_data+N-1) {
*m_ptr++ = c;
*m_ptr = '\0';
if (m_tty) str.write(c);
}
}
void setTTY(bool tty) { m_tty = tty; }
bool isTTY() const { return m_tty; }
bool ready() const { return m_has; }
char * data() { return m_data; }
unsigned int size() const { return m_ptr-m_data; }
const char * getToken() {
if (!m_has) return nullptr;
char c;
while ((c = *m_ptr) && isSpace(c)) m_ptr++;
auto ret = m_ptr;
while ((c = *m_ptr) && !isSpace(c)) *m_ptr++ = tolower(c);
if (c)
*m_ptr++ = '\0'; // terminate the previous token
return ret;
}
};
LineEditor<32> Line;
void s_input();
void s_moveCommand();
struct {
unsigned long at = {};
void (*handler)() = s_input;
} state ;
void processLine() {
auto const cmd = Line.getToken();
auto const param = Line.getToken();
if (strcmp(cmd, "move") == 0 && param) {
char * end;
auto distance = strtol(param, &end, 10);
if (param != end && distance >= 0 && distance <= 10000) {
// valid move command - pretend that it took some time
state.at = millis() + 1000;
state.handler = s_moveCommand;
}
} else
Serial.println("ERROR");
Line.clear();
}
void s_moveCommand() {
Serial.println("OK");
state.at = {};
state.handler = s_input;
}
void s_input() {
while (Serial.available()) {
Line.input(Serial);
if (Line.ready())
return processLine();
}
}
void setup() {
Serial.begin(9600);
}
void loop() {
if (!state.at || millis() >= state.at)
state.handler();
}
适配器类执行Arduino环境:
class Arduino : public QObject {
QBasicTimer m_loopTimer;
static QPointer<Arduino> m_instance;
void timerEvent(QTimerEvent * event) override {
if (event->timerId() == m_loopTimer.timerId())
loop();
}
public:
Arduino(QObject * parent = {}) : QObject{parent} {
Q_ASSERT(!m_instance);
m_instance = this;
m_loopTimer.start(0, this);
arduinoTimer.start();
setup();
}
};
QPointer<Arduino> Arduino::m_instance;
最后,我们设置测试并连接所有相关组件。Arduino对象在自己的线程中运行。
class SafeThread : public QThread {
using QThread::run;
public:
~SafeThread() { quit(); wait(); }
};
int main(int argc, char ** argv) {
using Q = QObject;
QApplication app{argc, argv};
AppPipe ctlPipe(nullptr, QIODevice::ReadWrite | QIODevice::Text);
AppPipe serialPipe(&ctlPipe, QIODevice::ReadWrite | QIODevice::Text);
ctlPipe.addOther(&serialPipe);
Serial.setDevice(&serialPipe);
Controller ctl(&ctlPipe);
Ui ui;
Arduino arduino;
SafeThread thread;
arduino.moveToThread(&thread);
thread.start(QThread::LowPriority);
Q::connect(&ui, &Ui::moveActive, &ctl, [&]{ ctl.setCommand("move 200"); });
Q::connect(&ui, &Ui::inactive, &ctl, [&]{ ctl.stop(); });
Q::connect(&ctl, &Controller::statusChanged, &ui, [&](int s, int r, int d){
ui.setStatus(QStringLiteral("sent=%1 received=%2 queue depth=%3").arg(s).arg(r).arg(d));
});
ui.show();
return app.exec();
}
#include "main.moc"
示例到此结束。您可以将其复制粘贴到空的主目录中。cpp,或者您可以从github获取完整的项目。
来自文档:
当鼠标、空格键或键盘快捷键激活按钮时,按钮会发出clicked()信号。连接此信号以执行按钮的操作。按钮还提供不太常用的信号,例如,pressed()和released()。
因此,请使用“按下/释放”而不是“单击”。
鼠标单击按钮后,可能在释放按钮后,会发送单击的。我不知道Qt如何“知道”处理单点和双击。
按下的由“按下”操作发送,释放的由释放操作释放。所以只需根据这两个信号相应地设置您的标志。
顺便说一句:您必须在发送函数时使用某种循环,如果文件io变得可写,通常会定期或始终调用。简单地对io开火并不能达到您的期望。
我建议您稍微扩展一下您的设计:
事件只发送一次,因此处理程序将只执行一次,如果您想继续重复它,您将不得不使用计时器或其他事件驱动的方式。您不能使用循环,因为这会阻塞GUI线程并且您的应用程序将停止响应。
当然,您可以使用该按钮的自动重复,并且可以选择调整触发和重复间隔,但是在逻辑和GUI之间划清界限的解决方案更好。您应该真正依赖GUI来存储数据或控制内部逻辑。GUI应该只是前端。
不过,您需要在串行端口上做更多的工作。如果要从GUI线程使用它,则必须使用非阻塞API。这将需要对您的实现进行更多的扩展。关于如何实现这一点,有一个很好的例子,您只需修改它,以便在成功发送之前的有效负载后,能够发送更多的有效负载。在伪代码中:
on button press
start timer
on button release
stop timer
onTimeout
if (can send)
send
can send = false
onBytesWritten
accumulate bytes
if (payload is completed)
can send = true
reset payload byte counter
当然,您还必须进行一些错误检查,您不能期望它正常工作。链接的示例包含基本错误处理。
我正在尝试用Java连接字符串。为什么这不起作用?
我有一个程序正在显示结果的条形图。我想等到用户关闭条形图,继续下一行代码,询问他们是否要为该图输入新信息。 正在发生的是,柱状图的场景将打开,但什么也不会显示,并且JOptionPane立即弹出问题。如果我点击“否”,则显示图表,但程序结束。如果我点击“是”,程序将返回到前面的对话框窗口,但在其他对话框结束之前不会显示图表。 我想等待提示用户,直到他们关闭条形图。在这一点上,我是如此的沮丧,以至于
我有一个varchar字段,存储类似的日期 我需要将这些值更改为
程序描述:人们使用jsliders输入用户分数,然后在游戏结束时点击按钮检查分数,它会告诉你谁得了第一、第二、第三等分数。 我不确定的是如何让程序告诉我得分最高的球员姓名。我把它分类了,它告诉你实际的分数,只是没有名字。 我基本上是在寻找: 代码:
我正在做一个关于模拟正在处理的CPU工作的项目。基本上,用户将输入一个工作,该工作的长度将被处理,优先级键(从-20到19,从-20开始以获得更高的优先级)。到目前为止,我已经让所有这些工作,除了程序正确终止。基本上,当我的优先级队列中的所有作业都被处理完时,我需要程序终止。当每个作业被处理时,它将从长度中减去,直到它为0。当它为0时,它将从优先级队列中移除。当没有剩余的作业(优先级队列是空的)时
在第二个while循环(在用户选择抛硬币模拟器选项之后),当用户选择0时,我遇到了问题,程序没有像我希望的那样返回到主菜单,而是停止了,没有循环回到主菜单,让用户选择另一个选项。 有什么办法解决这个问题吗?我不能使用多个方法,因为这是我正在做的一个项目的要求。我已经被困在这一个部分很长时间了,现在(一个星期),并将感激地感谢任何指针或方向。 下面是当用户运行抛硬币模拟器后选择零时我的程序的样子。