在QT中添加消息队列,实现与其他应用程序的通信
概要
问题的来源:想用QT显示一下Im6xull上采集到的传感器信息,比如说板载的AP3216C,正点原子的QT应用手册上给出了一个显示板载AP3216C数据的例程,这个例程是将之前AP3216C的应用程序部分,和QT的相关业务逻辑都融合在一起。
整体架构流程
- 上述例程存在的问题
这个例程可以实现在板载屏幕上显示AP3216C数据的功能,但我认为它比较冗长,耦合度较高,不方便移植;
- 程序优化的思路
首先在应用程序的功能架构上,传感器的数据(比如AP3216C)可以划分为一个输入任务;板载的屏幕可以划分为人机交互任务;
在一个测控系统中,输入任务可能有多个,人机交互任务可能有一个,也可能有若干的控制类任务(或者叫输出任务),为了降低系统中业务逻辑间的耦合性,可以将任务独立为一个进程,任务间使用进程间通信机制,进行通信,系统框图如下所示:
- 进程间通信机制,这里选择使用消息队列,消息队列自带阻塞机制,阻塞是个好东西
前置准备
- 配置好了AP3216C的驱动程序和应用程序(正点原子教程)
- 配置好了imx6ull上触摸屏的各种设置,触摸屏可以正常使用(正点原子教程)
- 配置好了imx6ull上QT的开发环境,可以交叉编译QT程序到板子上执行(正点原子教程)
- 了解基本的QT的控件的使用
- 基本了解消息队列的相关API。可以看这个贴子消息队列相关细节
技术细节
下面开始移植消息队列到QT,整体包括传感器输入端程序、 QT端;
- 首先是输入任务的程序,工程结构如下图所示,主要注意comm.h和comm.c
- comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
struct msgbuf
{
long mtype;
//char mtext[1024];
//这个数组还是大一点比较好,大一点没有问题
unsigned short mtext[30];
};
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
int commMsgQueue(int flags,int prj_id);
int createMsgQueue(int prj_id);
int getMsgQueue(int prj_id);
int destoryMsgQueue(int msg_id);
int sendMsgQueue_short_arry(int msg_id, int who, short* msg, int arrysize);
int recvMsgQueue_short_arry(int msg_id, int recvType, unsigned short *databuf, int date_cnt);
#endif
- comm.c
#include "comm.h"
int commMsgQueue(int flags, int prj_id)
{
key_t key = ftok("/tmp", prj_id);
if (key < 0)
{
perror("ftok");
return -1;
}
int msg_id = msgget(key, flags);
if (msg_id < 0)
{
perror("msgget");
}
return msg_id;
}
int createMsgQueue(int prj_id)
{
// return commMsgQueue(IPC_CREAT|IPC_EXCL|0666, prj_id);
return commMsgQueue(IPC_CREAT | 0666, prj_id);
}
int getMsgQueue(int prj_id)
{
return commMsgQueue(IPC_CREAT, prj_id);
}
int destoryMsgQueue(int msg_id)
{
if (msgctl(msg_id, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sendMsgQueue_short_arry(int msg_id, int who, short *msg, int arrysize)
{
struct msgbuf buf;
buf.mtype = who;
int i;
for (i = 0; i < arrysize; i++)
buf.mtext[i] = msg[i];
if (msgsnd(msg_id, (void *)&buf, sizeof(buf.mtext), 0) < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsgQueue_short_arry(int msg_id, int recvType, unsigned short *databuf, int date_cnt)
{
struct msgbuf buf;
// buf.mtype = recvType;
int i;
if (msgrcv(msg_id, (void *)&buf, sizeof(buf.mtext), recvType, 0) < 0)
{
perror("msgrcv");
return -1;
}
for (i = 0; i < date_cnt; i++)
{
databuf[i] = buf.mtext[i];
}
return 0;
}
- sem_ap3216app.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include "comm.h"
#define date_cnt 1
int ir_id = 5;
int als_id = 6;
int main(int argc, char *argv[])
{
int fd;
char *filename;
// unsigned short databuf[date_cnt];
unsigned short databuf[3];
unsigned short ir, als, ps;
unsigned short ir_databuf[1];
unsigned short als_databuf[1];
int ret = 0;
// 创建消息队列
int ir_msgid = createMsgQueue(ir_id);
int als_msgid = createMsgQueue(als_id);
pid_t pid = getpid();
printf("pid = %d\r\n", pid);
printf("ir_msgid = %d\r\n", ir_msgid);
printf("als_msgid = %d\r\n", als_msgid);
printf("我吐了啊\r\n");
if (argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
// 手动执行进程
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("can't open file %s\r\n", filename);
return -1;
}
while (1)
{
ret = read(fd, databuf, sizeof(databuf));
if (ret == 0)
{
// 看一下这样行不行,这样可以
ir_databuf[0] = databuf[0];
als_databuf[0] = databuf[1];
if (sendMsgQueue_short_arry(ir_msgid, SERVER_TYPE, databuf, date_cnt) < 0)
{
printf("sendMsgQueue_short_arry failed\r\n");
return -1;
}
usleep(20);
if (sendMsgQueue_short_arry(als_msgid, SERVER_TYPE, als_databuf, date_cnt) < 0)
{
printf("sendMsgQueue_short_arry failed\r\n");
return -1;
}
}
usleep(200000); /*200ms向队列发一次 */
}
close(fd);
return 0;
}
- 移植完毕后的QT工程的文件目录如下图所示,
- QT这里的基本思路是,我在comm.h里声明了一个定时器槽函数timer_timeout();以及一个自定义的信号ap3216cDataChanged();
在comm.cpp中,设置定时器200ms触发一次定时器槽函数;在定时器槽函数中,发射ap3216cDataChanged()信号去读取消息队列的数据。
comm.h
#ifndef COMM_H
#define COMM_H
#include <QObject>
#include <QTimer>
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
class comm : public QObject
{
Q_OBJECT
public:
explicit comm(QObject *parent = nullptr);
Q_INVOKABLE void setcapture(bool str);
int commMsgQueue(int flags,int prj_id);
int createMsgQueue(int prj_id);
int getMsgQueue(int prj_id);
int destoryMsgQueue(int msg_id);
int sendMsgQueue_short_arry(int msg_id, int who, unsigned short* msg, int arrysize);
int recvMsgQueue_short_arry(int msg_id, int recvType, unsigned short *databuf, int date_cnt);
public slots:
void timer_timeout();
signals:
void ap3216cDataChanged();
};
#endif // COMM_H
- comm.cpp
#include "comm.h"
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <QDebug>
#include "unistd.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//#define _GNU_SOURCE
struct msgbuf
{
long mtype;
//char mtext[1024];
//这个数组要偏大一点
unsigned short mtext[30];
};
//定义一个计时器,200ms发射一个ap3216cDataChanged()信号,
//这样就可以200ms读一次消息队列
QTimer* timer = new QTimer;
comm::comm(QObject *parent) : QObject(parent)
{
//QTimer* timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(timer_timeout()));
}
void comm::timer_timeout(){
//这里暂时不做处理
emit ap3216cDataChanged();
}
void comm::setcapture(bool str){
if(str){
timer -> start(200);
}else{
timer -> stop();
}
}
//开始定义相关的消息队列函数
int comm::commMsgQueue(int flags, int prj_id)
{
key_t key = ftok("/tmp", prj_id);
if(key < 0)
{
perror("ftok");
return -1;
}
int msg_id = msgget(key, flags);
if(msg_id < 0)
{
perror("msgget");
}
return msg_id;
}
int comm::createMsgQueue(int prj_id)
{
// return commMsgQueue(IPC_CREAT|IPC_EXCL|0666, prj_id);
return commMsgQueue(IPC_CREAT|0666, prj_id);
}
int comm::getMsgQueue(int prj_id)
{
return commMsgQueue(IPC_CREAT, prj_id);
}
int comm::destoryMsgQueue(int msg_id)
{
if(msgctl(msg_id, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int comm::sendMsgQueue_short_arry(int msg_id, int who, unsigned short *msg, int arrysize){
struct msgbuf buf;
buf.mtype = who;
int i;
for(i = 0; i < arrysize; i++) buf.mtext[i] = msg[i];
if(msgsnd(msg_id, (void*)&buf, sizeof(buf.mtext), 0) < 0){
perror("msgsnd");
return -1;
}
return 0;
}
int comm::recvMsgQueue_short_arry(int msg_id, int recvType, unsigned short *databuf, int date_cnt){
struct msgbuf buf;
//buf.mtype = recvType;
int i;
if(msgrcv(msg_id, (void*)&buf, sizeof(buf.mtext), recvType, 0) < 0){
perror("msgrcv");
return -1;
}
for(i = 0; i < date_cnt; i++){
databuf[i] = buf.mtext[i];
}
return 0;
}
- mainwidow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QObject>
#include <QThread>
#include <QTimer>
#include <QDebug>
#include<iostream>
using namespace std;
//QT作为消息队列的接受者
#define __arm__ 1
bool str = 1;
//接收消息的队列
#define date_cnt 1
int pro_id = 5;
int als_id = 6;
//定义消息队列
unsigned short ir_databuf[date_cnt];
unsigned short als_databuf[date_cnt];
unsigned short ir, als, ps;
int ret = 0;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ap3216c = new comm(this);
#if __arm__//开启定时器,200毫秒接收一次
ap3216c->setcapture(str);
#endif
connect(ap3216c, SIGNAL(ap3216cDataChanged()),this, SLOT(getAp3216cData()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::getAp3216cData(){
int ir_msgid = ap3216c->getMsgQueue(pro_id);
int als_msgid = ap3216c->getMsgQueue(als_id);
cout<<"读取了一次队列"<<endl;
if(ap3216c->recvMsgQueue_short_arry(ir_msgid, SERVER_TYPE, ir_databuf, date_cnt) < 0 ){
cout<<"ir recvMsgQueue_short_arry is fail"<<endl;
//return -1;
}
//usleep(20);
QThread::msleep(20);
if(ap3216c->recvMsgQueue_short_arry(als_msgid, SERVER_TYPE, als_databuf, date_cnt) < 0 ){
cout<<"als recvMsgQueue_short_arry is fail"<<endl;
//return -1;
}
cout<<"ir is :"<<ir_databuf[0]<<endl;
cout<<"als is :"<<als_databuf[0]<<endl;
QString ir_str = QString::number(ir_databuf[0]);
QString als_str = QString::number(als_databuf[0]);
//想办法,把得到的值打印出来
ui->ir_databuf_text->setText(ir_str);
ui->als_databuf_text->setText(als_str);
}
-
QT 的UI文件的设计(也很简单)
用了两个 Qlabe 和Qlinedit
-
然后是交叉编译AP3216C和QT的应用程序,这里注意要先运行 QT(也就是消息队列接收程序),再运行AP3216C的,不然读到的数据具有滞后性,具体原因,在于消息队列的内部机制,这里可不做赘述
-
实验效果如下:
先运行AP3216C的应用程序,在终端上打印出了新建的队列号
开发板端的现象
小结
ps:如果发现qt里面报struct msgbuf结构体的重定义错误,去修改,features.h文件,注释一个宏定义