2019年智能家居安装与维护"A卷"Qt 嵌入式部分

Qt部分样例学习必备知识存储过程

0、C++基础知识

0.1 explicit 显式转换

explicit 显式的构造函数。如果没有他的话,默认是隐式转换,

这篇文章讲的很清楚:https://zhuanlan.zhihu.com/p/52152355

0.2 Include 中<>和""

include 中<>和""的区别
<>:如果用#include语句包含文件,编译程序将首先到C:\COMPILER\INCLUDE目录下寻找文件;如果未找到,则到S:\SOURCE\HEADERS目录下继续寻找;如果还未找到,则到当前目录下继续寻找。

"" :如果用#include“file”语句包含文件,编译程序将首先到当前目录下寻找文件;如果未找到,则到C:\COMPILER\INCLUDE目录下继续寻找;如果还未找到,则到S:\SOURCE\HEADERS目录下继续寻找。
这个讲的很好https://blog.csdn.net/vegetable_bird_001/article/details/50905244

0.3 public、private、protected

1.类的一个特征就是封装,public和private作用就是实现这一目的。所以:

用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问。

2.类的另一个特征就是继承,protected的作用就是实现这一目的。所以:

protected成员可以被派生类对象访问,不能被用户代码(类外)访问。

0.4 定义成员,并且实例化

隐式创建

CEmployee cEmployee1; //隐式创建并调用无参构造器
CEmployee cEmployee2(2); //隐式创建并调用有参构造器

这种创建方式在进程虚拟地址空间中的栈中分配内存,它的分配和释放由系统决定,函数内局部变量的存储单元可以在栈上创建,函数执行完毕,系统会自动释放这些存储单元。

显式创建

CEmployee cEmployee1 = CEmployee; //显式创建并调用无参构造器
CEmployee cEmployee2 = CEmployee(2); //显式创建并调用无参构造器

这种创建方式和第一种一样,在进程虚拟地址空间中的栈中分配内存,它的分配和释放由系统决定,函数内局部变量的存储单元可以在栈上创建,函数执行完毕,系统会自动释放这些存储单元。

显式new创建

CEmployee *cEmployee1 = new CEmployee; //显式new创建并调用无参构造器
CEmployee *cEmployee2 = new CEmployee(2); //显式new创建并调用无参构造器

这种方式使用了new关键字,在堆中分配了内存,堆上的内存分配,亦称动态内存分配。程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。

new的注意:

0.5 C++堆和栈

C++中,内存分为5个区:堆、栈、*存储区、全局/静态存储区和常量存储区。

栈:是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。
堆:是由new分配的内存块,由程序员释放(编译器不管),一般一个new与一个delete对应,一个new[]与一个delete[]对应。如果程序员没有释放掉, 资源将由操作系统在程序结束后自动回收。
*存储区:是由malloc等分配的内存块,和堆十分相似,用free来释放。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这一区分)。
常量存储区:这是一块特殊存储区,里边存放常量,不允许修改。
(注意:堆和*存储区其实不过是同一块区域,new底层实现代码中调用了malloc,new可以看成是malloc智能化的高级版本)

这个解释到位:https://blog.csdn.net/yangyong0717/article/details/78001609

0.6垃圾回收

如果子对象是动态分配内存空间的,new

  • 指定父对象
  • 直接或者间接继承与Qobject的

不需要手动释放(delete),QObject中的析构对象树会自动释放。

0.7 运算符

常用的位运算

<<: 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
>>: 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

https://www.runoob.com/cplusplus/cpp-operators.html

0.8 unsigned

无符号的,体现在二进制的最高位没有。二进制的最高为,0为正数,1为负数。

0.9内存和寄存器

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

上图所示: 我们的系统文件是保存在硬盘中的,每次开机,就会把系统加载到内存中 ,就算这样还是赶不上CPU的运算速度,所以在CPU中设计了一个单元叫寄存器,主要负责在运算时保存数据,为了保证CPU的运算速度,就又在CPU中加入了高速缓冲单元,主要负责将CPU中的数据进行预读。如果CPU下次的运算需要的数据正好在告诉缓存中,叫Catch命中,否则就是Catch未命中,需要高速缓存又去读取一次内存

寄存器:x86平台主要有

  • 四个数据寄存器(EAX,EBX,ECX,EDX),每个数据寄存器为32位长度,其中低16位又被称为某x寄存器,操作低位寄存器将不影响高位的值。其他的数据寄存器ESI、EDI
  • 两个指针寄存器ESP、EBP。指针寄存器会分别使用ESP保存当前栈底的地址,而EBP保存当前栈顶的地址,当数据寄存器不够用,也直接拿这两个指针寄存器当作数据寄存器来用。
  • 指令指针寄存器EIP(存储下一句指针的地址),标志寄存器EFlags(计算结果的状态)。这两个无法当成数据寄存器来使用。

0.10 volatile

深入理解:https://blog.csdn.net/m0_37506254/article/details/81408781

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

如果一个基本变量被volatile修饰,编译器将不会把它保存到寄存器中,而是每一次都去访问内存中实际保存该变量的位置上。这一点就避免了没有volatile修饰的变量在多线程的读写中所产生的由于编译器优化所导致的灾难性问题。所以多线程中必须要共享的基本变量一定要加上volatile修饰符。当然了,volatile还能让你在编译时期捕捉到非线程安全的代码。

1、线程部分

Qt中使用QThread模块来管理线程。

在Qt4.7及以后版本推荐使用以下的工作方式。其主要特点就是利用Qt的事件驱动特性,将需要在次线程中处理的业务放在独立的模块(类)中,由主线程创建完该对象后,将其移交给指定的线程,且可以将多个类似的对象移交给同一个线程。

下面的案例,CustTimer类自定义了定时器的功能,然后我们将这个定时器的模块放入到此线程中,运行,更新dialog类界面的数码管。

01//CustTimer 头文件
#ifndef CUSTTIMER_H
#define CUSTTIMER_H

#include <QObject>
#include <QThread>
class CustTimer : public QThread
{
    Q_OBJECT //这个是使用信号与槽的宏对象,如果没有他的话,程序将报错
public:
    
    explicit CustTimer(QObject *parent = 0);
    void setFlag(bool flag=true);//设置跳出循环的flag,默认值是true。
signals:
    void sendSignal();//定义信号发送给dialog。
public slots:
    void myTimer();//定时器模块的槽函数,也可以当成正常的函数来使用
private:
    bool isStop; // 是否跳出循环的标志
protected:
};
#endif // CUSTTIMER_H
/////////////////////////////////////////////////////////////////////////////////////////
02//CustTimer.cpp文件
#include "custtimer.h"
#include <QThread>
#include <QDebug>
#include <QObject>
CustTimer::CustTimer(QObject *parent) :
    QThread(parent)
{
    isStop=false;
}
void CustTimer::myTimer()
{
    while(!isStop)
    {
        //qt4.7中sleep是static protected成员,必须继承使用。
        //qt5 中sleep是静态的公共成员变量
        QThread::sleep(1);
        emit sendSignal();
        qDebug()<<"子线程信号:"<< QThread::currentThread();
        if(isStop){
                break;
            }
    }

}

void CustTimer::setFlag(bool flag)
{
    isStop=flag;
}
/////////////////////////////////////////////////////////////////////////////////////////
03//dialog.h文件
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include "custtimer.h"
#include <QThread>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
    Q_OBJECT    
public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();
signals:
    void startThread(); //发送启动线程的信号   
private slots:
    void dealSendSignal(); //当接收到CustTime发来的信号,处理的槽函数
    void on_btn_Start_clicked();//启动线程的槽函数
    void on_btn_Stop_clicked();//关闭线程的槽函数
private:
    Ui::Dialog *ui;
    CustTimer *myTimerr;//CustTimer类的指针对象
    QThread   *myThread;//QThread类的指针对象

};
endif // DIALOG_H
/////////////////////////////////////////////////////////////////////////////////////////
04//Dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include <QThread>
#include <QDebug>
#include "custtimer.h"
Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    ui->lcdNumber->display(2);
    myTimerr=new CustTimer(); //动态分配内存控件
    myThread=new QThread(this);//动态分配内存空间,指定父对象,用完自动回收
    qDebug() << "主线程号:" << QThread::currentThread();
    myTimerr->moveToThread(myThread);//将定时器模块对象转移到次线程当中
    connect(this,SIGNAL(startThread()),myTimerr,SLOT(myTimer()));//发送信号,启动次线程
    connect(myTimerr,SIGNAL(sendSignal()),this,SLOT(dealSendSignal()));//接收信号,处理显示
        //窗口销毁
    connect(this, SIGNAL(destroyed()), this, SLOT(dealClose));
    
}

Dialog::~Dialog()
{
    delete ui;
}

void Dialog::dealSendSignal()
{
    //数码管+1
    static int i=0;
    i++;
    ui->lcdNumber->display(i);
    qDebug()<<QString("%1").arg(i);
}

void Dialog::on_btn_Start_clicked()
{
    if(myThread->isRunning() == true)//判断线程是否在运行
       {
           return;
       }

    myThread->start();//启动线程,但是没有启动线程处理函数
    myTimerr->setFlag(false);//设置定时器模块启动
     //不能直接调用线程处理函数,
    //直接调用,导致,线程处理函数和主线程是在同一个线程
    //myTimerr->myTimer();
    //只能通过 signal - slot 方式调用
    emit startThread();//发送启动信号
}

void Dialog::on_btn_Stop_clicked()
{
    if(myThread->isRunning() == false)
       {
           return;
       }
    myTimerr->setFlag(true);//跳出循环

    myThread->quit();//先退出
     myThread->wait();//在等待
    //如果顺序颠倒,则不出现暂停的功能。
}
 void Dialog::dealClose()
{
    on_btn_Stop_clicked();//退出线程
    delete myTimerr;//释放空间。
}   

我们也可以单独写一个线程类,重写虚函数run。实例化调用它,start启动。

1.1 使用注意事项

  • 线程当中不能更新UI界面,否则会报错,(只能通过信号与槽)。
AssERt failure in QWidget:"widgets must be ceate in the GUIThread"
  • 如果使用MoveToThread 那么移动到线程中的类不能被指定父对象,那么就需要注意回收内存空间
Canot move object with a parent

2、数据库

使用提供好的sql.h。会自动创建数据库。表格的内容

tb_deviceInfo

CREATE TABLE tb_deviceInfo(boardId integer,shortAddress varchar(4),deviceType varchar(4),mac varchar(16));

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

tb_gatewayInfo

CREATE TABLE tb_gatewayInfo(id integer primary key autoincrement,username varchar,passwd varchar,ip varchar(15),gateway varchar(15),mask varchar(15),mac varchar(20),dns varchar(15),serverIp varchar(15));

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

tb_zigbeeInfo

CREATE TABLE tb_zigbeeInfo(sensorType varchar(8),boardId integer);

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

2.1 注册功能

2.1.1 创建用户表。

--sql语句
Create table if not exists tb_userinfo(_id integer primary key autoincrement,username varchar,password varchar)

使用sql.h中自带的sqlQueryTable查询是否存在这张表。

/*函数功能:查询数据表是否在数据库中存在
    函数参数:tb:表名
    函数返回:若操作成功则返回ture,否则返回false*/
    bool sqlQueryTable(QString tb);
Sql sql;
 if(!sql.sqlQueryTable("tb_userInfo")){
           tabc=sql.sqlExec("create table if not exists tb_userInfo(_id integer primary key autoincrement,username varchar,password varchar)");
           qDebug(tabc?"tb_userInfo Create Success":"tb_userInfo Create failure");
        }

2.1.2密码由数字和密码的组合

使用QRegExp正则表达式判断。

是否存在空格 :QRegExp("\\s")// \是转义 \s是空格
是否有字母	:QRegExp("[a-z]+") //[代表所有小写字母],+多个匹配
是否有数字    :QRegExp("[0-9]+") //[代表所有数字],+贪婪匹配。
是否值数字和字母的组合 : QRegExp("^[a-z]+$") //^整个字符的开始,&的结束,都只能是字母。

RegExp 在线测试的网站:https://www.runoob.com/try/try.php?filename=tryjsref_regexp2

RegExp 教学网站:https://www.runoob.com/regexp/regexp-rule.html

写好的Reg 公式 网站

if(password.contains(QRegExp("[a-z]+")) &&password.contains(QRegExp("[0-9]+"))){
    userinfo.insert("userName",username);
    userinfo.insert("password",cpwd);
}else{
    QMessageBox::warning(NULL,"密码有误","密码必须由字母和数字组合!");
    return userinfo;
}

2.1.3 插入用户数据

insert into tb_userInfo username,password values('','')

通过返回的键值对的长度,看看是否成功赋值。

tabc=false;
tabc=sql.sqlExec(QString("insert into tb_userInfo(username,password) values('%1','%2')").arg(userinfo1.value("userName")).arg(userinfo1.value("password")));
QMessageBox::information(this,tabc?"注册成功":"注册失败","请返回主界面登录");

2.2 注册提示框问题

在弹出提示框,关闭的时候,会退出整个页面,这个和Messbox有关系,解决的办法:

https://blog.csdn.net/wwkaven/article/details/37735329

QMessageBox弹出前加一句:
QApplication::setQuitOnLastWindowClosed(false);

2.3 登录查询用户数据

select password from tb_userInfo where username=?

借助sql.h提供的sqlQuery功能

/*函数功能:执行数据库的查询操作
    函数参数:sql:sql语句
    函数返回:若操作成功则返回QVariant类型数据,可将其转为需要的类型*/
    QVariant sqlQuery(QString sql);
QString pwd=sql.sqlQuery(QString("select password from tb_userInfo where username='%1'").arg(username)).toString();//这里返回的是一个QVarint值,需要转换。

3、 串口通信(QextSerialPort)

QextseialPort使用介绍https://www.jianshu.com/p/047d1b95afa6

QextSerialPort 使用手册https://qextserialport.github.io/1.2/qextserialport-members.html

3.1同步通信和异步通信。

同步通信: 通信放双按照统一的节拍工作,所以配合很好(两个人约定,每五分钟就通话一次,不到规定的节拍就不会工作)。一般需要发送方给接收方发送信息同步时发送时钟信号,接收方根据发送方给它的时钟信号安排自己的节奏。但是专门需要一根时间信号

使用场景: 用于通信双方信息交换频率固定,或者经常通信。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

异步通信:又叫异步通知,在双方通信的频率不固定时(有时3ms一次,有时候3填才收发一次),那就不适合使用同步通信,而适合异步通信。工作流程异步通信时接收方不必一直在意发送方,发送方需要发送信息时会首先给接收方一个信息开始的起始信号,接收方接收到起始信号就认为后面紧跟着的就是有效信息,才会开始注意接收信息,直接收到发送方发送过来的结束标志。(没有时间信号线了,只有通知了)

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

https://www.bilibili.com/video/BV1i7411J7Dx?from=search&seid=12586377103511307997

3.2 波特率bandrate

指的是串口通信的速率。也就是串口通信时每秒钟可以传输多少个二进制位。譬如每秒钟可以传输9600个二进制位(传输一个二进制位的需要的的时间就是1/9600s=104us(微秒))

串口通信的波特率是不能随意设定,而应该在一些值中去选择,一般最常见的波特率是9600或115200(低端的单片机常用9600,高端的单片机与嵌入式Soc一般用115200

为什么不能自定义波特率?

  • 通信双方必须事先设定相同的波特率这样才能通信成功,如果发送方和接收方按照不同的波特率通信则根本收不到。因此波特率最好是大家熟知的而不是随便指定的。
  • 常用的波特率经过长久发展,大家形成了共识,就是9600和115200

3.3 自动流控FlowControl

在以前两台计算机通信只有三根串口线,一根收数据,一根发数据,一根参考GND电频。串口通信是异步通信。 (如果发送方一秒钟发送15个数据包,接收方一秒钟只能接13个,另外2个就会丢掉),为了解决这个问题,在通信的串口线上加入了流控线,工作原理,发送方拉高流控线,表示发送数据,接收方就拉低流控线,来处理,直到处理完,再把流控线拉高,这样发送方就知道接收方接受完了,再接着发第二帧数据。

为什么要流控

让串口通信非常的可靠。在发送方速率比接收方快的时候流控可以保证发送和恶接收不会丢包。

为什么现在不用流控

现在计算机之间有更好更高级(usb、internet)的通信方式,串口已经基本废弃,现在串口的用途更多是Soc用来输出调试信息。由于调试信息不是关键性信息,而且由于硬件发展串口本身的速度很慢,所以硬件都能协调发送和接收的速率,因此流控失去意义了。

3.4 起始位、数据位、校验位、停止位

在信号线上共有两种状态,分别是逻辑1(高电平)和逻辑0(低电平)来区分。
起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。
数据位:可以选择的值有5,6,7,8这四个值,可以传输这么多个值为0或者1的bit位。这个参数最好为8,因为如果此值为其他的值时当你传输的是ASCII值时一般解析肯定会出问题。理由很简单,一个ASCII字符值为8位,如果一帧的数据位为7,那么还有一位就是不确定的值,这样就会出错。
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。就比如传输“A”(01000001)为例。
1、当为奇数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为1才能满足1的个数为奇数(奇校验)。图-1的波形就是这种情况。
2、当为偶数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为0才能满足1的个数为偶数(偶校验)。
此位还可以去除,即不需要奇偶校验位。
停止位:它是一帧数据的结束标志。可以是1bit、1.5bit、2bit的空闲电平。可能大家会觉得很奇怪,怎么会有1.5位~没错,确实有的。所以我在生产此uart信号时用两个波形点来表示一个bit。这个可以不必深究。。。
空闲位:没有数据传输时线路上的电平状态。为逻辑1。
传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输。比如传输“A”如果是MSB那么就是01000001(如图-2),如果是LSB那么就是10000010(如下图的图-4)
uart传输数据的顺序就是:刚开始传输一个起始位,接着传输数据位,接着传输校验位(可不需要此位),最后传输停止位。这样一帧的数据就传输完了。接下来接着像这样一直传送。在这里还要说一个参数。
帧间隔:即传送数据的帧与帧之间的间隔大小,可以以位为计量也可以用时间(知道波特率那么位数和时间可以换算)。比如传送”A”完后,这为一帧数据,再传”B”,那么A与B之间的间隔即为帧间隔。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

https://www.cnblogs.com/zzq888/p/10830215.html

3.5 ReadyRead信号

该信号是QextSerialport继承于QIODevice

每次可从设备读取新数据时,都会发出一次该信号。 它只会在新数据可用时再次发出,例如当网络数据的新有效负载到达您的网络套接字时,或者当新的数据块已附加到您的设备时。readyRead() 不会递归发出; 如果您重新进入事件循环或在连接到 readyRead() 信号的插槽内调用 waitForReadyRead(),则不会重新发送该信号(尽管 waitForReadyRead() 可能仍会返回 true)。实现从 QIODevice 派生的类的开发人员的注意事项:当新数据到达时,您应该始终发出 readyRead()(不要仅仅因为缓冲区中仍有数据要读取而发出它)。 不要在其他条件下发出 readyRead()。

flush()

将所有挂起的 I/O 刷新到串行端口。 如果与类关联的串行端口当前未打开,则此函数无效。

write()

将 byteArray 的内容写入设备。 返回实际写入的字节数,如果发生错误,则返回 -1。

close()

关闭串行端口。 如果与类关联的串行端口当前未打开,则此功能无效.首先发出 aboutToClose(),然后关闭设备并将其 OpenMode 设置为 NotOpen。 错误字符串也被重置。

4、采集数据

4.1 硬件连接

qt的数据采集是基于串口通信的。拿到数据的过程如下

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

硬件连接: 1、需要安装FT232串口通信的驱动。2、将串口连接到Ubuntu中(查看ttyusb0)此时的协调器已经通电,等待子结点连接。3、样板间通电,连接协调器组网。

注意:先通协调器的电,在给子结点上电。如果顺序颠倒,子结点发送的连接信号,协调器收到不到,这样就无法连接了。

4.2 代码部分

代码实现逻辑图:

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

通过同步通信的方式打开串口,以可读可写的方式通信。

4.3 打开串口的线程

    /*-----------------01打开端口(EventDriven同步读写,按照同一频率发送和接收)-----------------------------*/
    myCOM=new QextSerialPort(serialportInfo.port_id,QextSerialPort::EventDriven);
    /*------------------02设置波特率115200----------------------------*/
    myCOM->setBaudRate((BaudRateType)serialportInfo.port_baud.toInt());
    /*-------------------03设置流量控制(默认)---------------------------*/
    myCOM->setFlowControl(FLOW_OFF);//
    /*-------------------04设置校验(奇偶校验,可以默认)ODD奇数,EVEN偶数,NONE没有---------------------------*/
    ParityType a=serialportInfo.port_Check=="None"?PAR_NONE:(serialportInfo.port_Check=="Odd"?PAR_ODD:PAR_EVEN);
    //qDebug()<<a;
    myCOM->setParity(a);
    /*-------------------05设置数据位(8bit)---------------------------*/
    myCOM->setDataBits((DataBitsType)serialportInfo.port_Databit.toInt());
    /*--------------------06设置停止位--------------------------*/
    myCOM->setStopBits(STOP_1);
    /*----------以IO设备读写的方式打开串口,如果成功则返回true,当串口已经打开,此函数就无效了-----------------*/
    if(myCOM->open(QIODevice::ReadWrite)==true){
        /*----------每次从设备读取新的数据,都会发出一次readyRead信号,他只会在新数据可用发出-----------------*/
        connect(myCOM,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
        /*----------------动态分配SerialPortReceive内存空间,用于处理传过来的数据-------------*/
        portReceivedHandle=new SerialPortReceive();
        /*------------------dataHandle处理完成以后,发送byteFinish信号-------------*/
        connect(portReceivedHandle,SIGNAL(byteFinish(QByteArray)),this,SIGNAL(evt_serial_receive(QByteArray)));
        qDebug("打开串口成功");
    }else {
        qDebug("打开串口失败");
    }
    this->exec();
//注意两次的信号与槽函数
 connect(myCOM,SIGNAL(readyRead()/*只要有数据更新*/),this,SLOT(onReadyRead()/*读取全部*/)); 
connect(portReceivedHandle,SIGNAL(byteFinish(QByteArray)/*处理数据*/),this,SIGNAL(evt_serial_receive(QByteArray)/*转发处理好的信号*/));

4.4 主界面处理传过来的数据

信号与槽

 /*---------------------------接受串口发送来的信号信号,再二次处理数据--------------------*/
connect(portThread,SIGNAL(evt_serial_receive(QByteArray)),this,SLOT(dealShowReceive(QByteArray)));
 /*---------------------------接收二次处理好的数据,更新界面-------------------------*/
connect(portdataHandle,SIGNAL(evt_deviceState(QString,QString,QString)),this,SLOT(slotToUpload(QString,QString,QString)));
if(ui->open_port_btn->text()=="打开串口")
    {
        ui->open_port_btn->setText("关闭串口");
        //启动打开串口的线程
        //导入三个头文件,2个源文件,开始写线程类。
        /*---------------------------调用构造方法初始化串口线程-------------------------*/
        portThread=new serialportThread(ui->port_cb->currentText(),ui->Baud_cb->currentText(),ui->check_cb->currentText()
                                          ,ui->data_cb->currentText());
        portThread->start();
         /*---------------------------接受串口发送来的信号信号,再二次处理数据------------------------*/
        connect(portThread,SIGNAL(evt_serial_receive(QByteArray)),this,SLOT(dealShowReceive(QByteArray)));
        portdataHandle=new SerialDataHandle(this);
         /*---------------------------接收二次处理好的数据,更新界面-------------------------*/
        connect(portdataHandle,SIGNAL(evt_deviceState(QString,QString,QString)),this,SLOT(slotToUpload(QString,QString,QString)));
//        timer1 = new QTimer();
//        timer1->start(500);
//        connect(timer1,SIGNAL(timeout()),this,SLOT(readNodeNum()));

    }else {
        if(portThread==NULL){
            return;
          }
        //关闭串口
        portThread->portClose();
        portThread->quit();
        portThread->wait();
        while(portThread->isRunning()){
        }
        delete portThread;
        portThread=NULL;
        ui->open_port_btn->setText("打开串口");
    }
}

void MainDialog::dealShowReceive(QByteArray byte)
{
    /*---------------导入数据处理的文件,开始处理发送过来的数据--------------*/
      portdataHandle->receive(byte);
    //qDebug()<<byte.toHex();
      //ui->show_port->setText(byte);
}

5、控制设备

5.1 控制指令

unsigned int pCmd[9];
pCmd[0] = 0; //固定为0
pCmd[1] = 0;//固定为0
pCmd[2] = 0; //设备类型定义,如继电器:0x01 如红外转发0x0B 门禁0X0A
pCmd[3] = 0xee;// 固定为0xee
pCmd[4] = 0x03; //固定为0x03
pCmd[5] = 0; 	//固定为0
pCmd[6] = 0x01; //命令码cmdCode 命令类型 是开关 固定为0x01 SWITCH
pCmd[7] = 0xXX; //命令cmd 

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

5.2 设备信息

/******************************串口通信部分要用到的定义**************************************************/
/************************************************
设备类型定义
 ***********************************************/
#define RELAY           0XEE01          //继电器
#define BODYINFRARED    0XEE02          //人体红外
#define SMOKE           0XEE03          //烟雾
#define CO2             0XEE04          //二氧化碳
#define GAS             0XEE05          //燃气
#define PRESSURE        0XEE06          //气压
#define THI             0XEE07          //温湿度
#define ILL             0XEE08          //光照
#define PM25            0XEE09          //PM2.5
#define RFID            0XEE0A          //门禁
#define INFRARED        0XEE0B          //红外转发
/**************************************************/
/************************************************
 操作码定义
 ***********************************************/
#define CONTROL             0X06    //控制设备
#define GETSTATE            0X07    //获取状态
#define READNODE            0X0B    //读取节点
#define DEVICETEST          0XFF    //握手测试
/**************************************************/
//cmdCode 定义
#define SWITCH              0x01  //开关
#define INFRAREDSTUDY       0x02  //红外学习
#define INFRAREDEXIT        0x00  //红外退出学习
#define RFID_APP            00x01  //app开门
#define RFID_CARD           0x02  //刷卡开门
#define RFID_KEY            0x04  //外部按键开门

//cmd 定义
#define SWITCH_ON           0x01  //开
#define SWITCH_OFF          0x00  //关

//设备通道定义
#define CHANNEL_1                    0x01   //通道1
#define CHANNEL_2                    0x02   //通道2
#define CHANNEL_3                    0x04   //通道3
#define CHANNEL_4                    0x08   //通道4
#define CHANNEL_ALL                  0x07   //所有通道
/**************************************模式定义**********************************************/
#define ILL_MODE    0
#define TEMP_MODE   1
#define SAFE_MODE   2
#define OUT_MODE    3
#define SLEEP_MODE  4
#define WAKE_MODE   5
/*********************************与服务器通信部分定义********************************************/
#define TEMP_SERVE             "0"           //温度
#define HUM_SERVE              "1"           //湿度
#define ILL_SERVE              "2"           //光照
#define SMK_SERVE              "3"           //烟雾
#define GAS_SERVE              "4"           //燃气
#define PM25_SERVE             "5"           //PM2.5
#define CO2_SERVE              "6"           //二氧化碳
#define AP_SERVE               "7"           //气压
#define HI_SERVE               "8"           //人体红外
#define FAN_SERVE              "9"           //风扇
#define LAMP_SERVE             "10"          //射灯
#define WL_SERVE               "11"          //报警灯
#define CT_SERVE               "12"          //窗帘
#define INFRARED_1_SERVE       "13"          //红外发射1
#define INFRARED_2_SERVE       "14"          //红外发射2
#define DC_SERVE               "15"          //门禁控制
#define SWITCH_SERVE           "16"          //插座控制
//上传状态
#define ON                "1"
#define OFF               "0"
#define STOP              "2"
#define CLOSE             "4"

/****************************************************************************************/
#endif // VARIABLEDEFINITION_H

5.3 控制流程

设备的控制简单一点,就是通过串口线程serialportThread里面的sender方法向协调器发送控制数据。也是就。我只需要把按照上面的内容定义包装好,直接调用。子结点会把控制结果通过触发evt_deviceState信号,把结果返回。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

//封装控制功能函数
void MainDialog::deviceControl(unsigned int board_number, unsigned int *pCmd, unsigned int len)
{
    QByteArray ba = portdataHandle->sendingDataHandle(CONTROL,board_number,pCmd,len);
    portThread->send(ba);
}
// 封装控制指令函数
void MainDialog::slotBtnInfraredClicked(unsigned int command)
{
    unsigned int pCmd[9] ;
    pCmd[0] = 0 ;
    pCmd[1] = 0 ;
    pCmd[2] = 0x0b ;
    pCmd[3] = 0xee ;
    pCmd[4] = 0x03 ;
    pCmd[5] = 0 ;
    pCmd[6] = SWITCH ;
    // command=3 这里的运算步骤就是 0000 0011 & 0000 1111=3 位与运算
    pCmd[7] = (command&0xf);
    // command=3 这里的运算步骤就是 (0000 0011>>8)=0000 0000 & 0000 1111=0 位与运算
    pCmd[8] = (command>>8)&0xf;
    /**********************红外控制,除了0号通道=(cmd)1有效,其他的以外(ALL、1、2、4)通道号=(cmd)2都无效*********/
    //pCmd[8] = CHANNEL_ALL;
    //qDebug()<<command;
    deviceControl(portdataHandle->sensorTypeToBoardIdMap["13"],pCmd,9);
}

6 图表显示

根据数据库中,单个传感器的10条数据进行动态绘制。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

6.1 建立每个传感器的数据表

//建立表
void MainDialog::varInit()
{
    //1、建立传感器数据库表
    tabSensorName<<"tb_temperature"<<"tb_humidity"<<"tb_illumination"<<"tb_somke"<<"tb_airpressure"
                <<"tb_pm"<<"tb_co"<<"tb_gas";
    //qDebug()<<tabSensorName;
    //SensorDataTb="tb_SensorData";
    qDebug()<<QString("传感器数据库表:%1张").arg(tabSensorName.size());
    /*-------------------------------建立8个传感器的数据表---------------------*/
     result=new QSqlTableModel(this);
     QString create_tb;

     for(int i=0;i<tabSensorName.size();i++){
            //qDebug()<<tabSensorName.at(i);
            if(!sql.sqlQueryTable(tabSensorName.at(i))){
 reate_tb=QString("create table %1(_id integer primary key autoincrement,sensorvalue float(7,2))").arg(tabSensorName.at(i));
 qDebug()<<QString(sql.sqlExec(create_tb)?QString("创建%1表成功").arg(tabSensorName.at(i)):QString("创建%1表失败").arg(tabSensorName.at(i)));
                sleep(0.1);
            }else
            {
                /*-------- 只要查到一张表,直接退出循环-----*/
                break;
            }
            }
}

6.2 插入数据及删除数据

void MainDialog::addSensorDataToTab(QString tabName, QString values)
{
    QString selectCount=QString("select count(*) from %1").arg(tabName);

//LIMIT offset , count;//原文出自【易百教程】https://www.yiibai.com/mysql/limit.html
//与排序使用
//select _id from %1 order by _id limit 0,1) 第一行0,总个数1条    
     QString deleteRecode=QString("delete from %1 where _id in(select _id from %1 order by _id limit 0,1)").arg(tabName);
     int count=sql.sqlQuery(selectCount).toInt();
     if(count==10){
         qDebug()<<QString(sql.sqlExec(deleteRecode)?QString("删除数据%1表成功").arg(tabName):QString("删除数据到%1表失败").arg(tabName));
     }
     QString insertSql=QString("insert into %1 (sensorvalue) values(%2)").arg(tabName).arg(values);
     qDebug()<<QString(sql.sqlExec(insertSql)?QString("添加数据到%1表成功").arg(tabName):QString("添加数据到%1表失败").arg(tabName));

}

6.3 绘制坐标

6.3.1 初始化数据

void chartDialog::initCoord()
{
//    if(flagCoord){
//       return;
//    }
     xLength=width();//窗口的宽
     yLetgth=height();//窗口的高
     xSpace=40;//x轴的间距
     xScale=(xLength-50)/xSpace;//x轴一共有多少刻度
     ySpace=40;//y轴的间距
     yScale=(yLetgth-80)/ySpace;//y轴一共有多少刻度
     yText=(yAxisMaxValue+100)/(yScale);//y轴刻度显示的文本
     yRatio=(((float)ySpace)/((float)yText));//等比计算出最大值间距与y轴间距的比值
     qDebug()<<QString::number(yRatio,'f',2);
     interSectionPoint=QPoint(xLength-(xLength-50),yLetgth-80);//x轴、y轴的交点
     xPoint=QPoint(0,interSectionPoint.y());//x轴开始的点
     yPoint=QPoint(interSectionPoint.x(),0);//y轴开始的点
     flagCoord=true;
}

6.3.2 绘制坐标系

void chartDialog::drawAxis(QPainter *p)
{
	//x轴
    p->drawLine(xPoint.x(),xPoint.y(),xPoint.x()+xLength,xPoint.y());
    //y轴
    p->drawLine(yPoint.x(),yPoint.y(),yPoint.x(),yPoint.y()+yLetgth);
    for(int i=1;i<xScale;++i)
    {	//矩形显示文本
        QRect rect(yPoint.x()+i*xSpace,xPoint.y(),textWidth,textHight);
        p->drawText(rect,QString::number(i));
    }
    for(int i=1;i<=yScale;++i)
    {
        QRect rect(xPoint.x()+20,xPoint.y()-i*xSpace-20,textWidth,textHight);
        //qDebug()<<QString::number(yText*i);
        p->drawText(rect,QString::number(yText*i));
    }
    QRect rect(yPoint.x(),xPoint.y()+20,textWidth,textHight);
    p->drawText(rect,QString::number(0));
    //qDebug()<<QString::number(yText);
}

6.3.3 画点连线

void chartDialog::drawValueLine(QPainter *p, float *value, unsigned int length)
{
    //点的数组
    QPointF pointp[length];
    for(int i=0;i<length;i++){
        int x=interSectionPoint.x()+(i+1)*xSpace;
        int y=interSectionPoint.y()-(float)(value[i]*yRatio);
        pointp[i]=QPointF(x,y);
        //画数值
        p->drawText(pointp[i],QString::number(value[i]));
    }
    QPen linePen;
    linePen.setWidth(3);
    linePen.setColor(Qt::green);
    p->setPen(linePen);
    //根据数组直接绘制根据点连线
    //arg:PointF点的数组,arg:PointF的size
    p->drawPolyline(pointp,length);
    QPen mypen;
    mypen.setWidth(5);
    mypen.setColor(Qt::gray);
    p->setPen(mypen);
    //根据数组直接绘制根据画点
    //arg:PointF点的数组,arg:PointF的size
    p->drawPoints(pointp,length);
}

6.4 定时器查询数据

6.4.1 Qt中三种定时器的使用

1、QObject内部定时器

使用startTimer开启定时器,使用killTimer(int id)接口来关闭指定的定时器。
启动定时器后会在对应间隔时间触发timerEvent事件。

class Object : public QObject {
    Q_OBJECT
public:
    Object()
    {
        startTimer(1000);
    }

protected:
    void timerEvent(QTimerEvent *event) { }
};
2、QBasicTimer

QBasicTimer类为对象提供定时器事件。
QBasicTimer特点快速轻量级低级类。对于需要降低使用多个定时器开销的应用程序,QBasicTimer可能是一个不错的选择。如果是一般使用情况建议使用更高级别的QTimer类而不是此类。
使用start接口来设置定时时间与定时事件的接收对象。

class Object : public QObject {
    Q_OBJECT
public:
    Object() { }

protected:
    void timerEvent(QTimerEvent *event) { }
}

Object object;
QBasicTimer basicTimer;
basicTimer.start(500, &object);
3、QTimer

QTimer类提供重复单次定时器。
QTimer类为定时器提供高级编程接口。创建一个QTimer实例,将其timeout()信号连接到对应的槽中,然后调用start()开启定时器,每隔一段时间会发出timeout()信号。

class Object : public QObject {
    Q_OBJECT
public:
    Object()
    {
        connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
        m_timer.start(1000);
    }

private slots:
    void onTimeout() { }

private:
    QTimer m_timer;
};
4、定时器小知识
  • 需要不同时间精度的定时器,可以指定定时器的TimerType类型。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

特别地Qt::VeryCoarseTimer非常粗略的意思是精度为±500ms。例如,10500ms的间隔将四舍五入为11000ms,而10400ms会置为10000ms。

  • 上述定时器例子都为循环触发,需要停止定时器请使用stopkillTimer,而想使用单次定时器最好使用QTimer::singleShot接口。
  • 使用QObject::timerEvent捕获定时器事件,如果存在多个定时器源,可以使用timerId来判断确定那个定时器事件。
  • 如果系统忙或无法提供请求的准确性,所有定时器类型都有可能会比预期的时间晚超时。在这种晚超时的情况下,虽然是多个超时已经过期,但是只发出一次超时事件。
  • QTimer的remainingTime接口可以获得距离触发定时器事件的剩余时间
  • 使用QObject的startTimer需要注意的是每调用一次会新增一个定时器并返回一个定时器ID。
id1 = startTimer(1000); // 开启一个1秒定时器,返回其定时器ID
id2 = startTimer(2000); // 开启一个2秒定时器,返回其定时器ID
id3 = startTimer(3000); // 开启一个3秒定时器,返回其定时器ID

6.4.2 定时器绘制图形

主界面发送信号signalTable(QString tabName) 绘制图形界面接收Slot。

connect(parent,SIGNAL(signalTab(QString)),this,SLOT(getSensorPoint(QString)));

1、定时器QTime触发timeout信号
t=new QTimer(this);
connect(t,SIGNAL(timeout()),this,SLOT(updataForCoord()));//getSensorPoint(QString)
t->start(3000);
2、SqlTableModel查询函数
void chartDialog::getSensorPoint(QString tab_Name)
{
    tab_Name_Time=tab_Name;//赋值表单
    qDebug()<<tab_Name;
    if(tab_Name.isEmpty()){
        return;
    }
    int row;
    model= sql.sqlTableMode(tab_Name);//模式查询
    //如果可以从数据库中读取更多行,则返回 true。 这只会影响不报告查询大小的数据库
    while(model->canFetchMore())
    //从数据库中获取更多行。 这只会影响不报告查询大小的数据库.
    model->fetchMore();//
    row=model->rowCount();//拿到总行数
    yAxisMaxValue=0;
    for(int i=0;i<row;i++){
        float v=model->record(i).value("sensorvalue").toFloat();
        value[i]=v;
        yAxisMaxValue=v>yAxisMaxValue?v:yAxisMaxValue;
        qDebug()<<QString::number(value[i]);
    }
    initCoord();
    //qDebug()<<QString::number(yAxisMaxValue);
    update();
}
3、关闭窗口,关闭定时器。

#include <QCloseEvent> CloseEvnt

void chartDialog::closeEvent(QCloseEvent *)
{
    qDebug("出发");
    if(t->isActive()){
        t->stop();
    }
}

6.5 定时器与线程的区别

定时器是运行在主线程当中的一种中断程序,每隔多少秒就阻塞主程序,执行定时器中的代码。这些代码执行的完成时间都是很短,轻量级的定时器代码量让我们感觉像开启了一个线程。

线程 是完全运行在另外一个线程当中的程序,相比qt的界面主线程, 其他线程可以看成是一种数据处理的服务。

就算线程中有耗时的操作也并不会影响主界面,而定时器如果处理耗时的操作,主界面也会受影响。

7、联动模式

提到联动模式,想到的实现方式就是一个线程加一个循环,这样做的好处就是不会影响主线程,而在qt中有一个模块叫QEalpsedTime,这个模块可以计算程序的执行时间。

QElapsedTimer timer;
timer.start();
 
slowOperation1();//执行函数
 
qDebug() << "The slow operation took" << timer.elapsed() << "milliseconds";

有了这个我们可以判断联动执行一次,所消耗的时间,是否会影响我们的主界面,正常的只要不超过500us。就不会影响主界面,如果程序不超过500us,就一直把这个操作丢给QCoreApplication::processEvents()加上这句话就不会影响主界面响应了,我们可以理解为,把联动执行程序丢到了另一个线程中,执行完就自动退出这个线程。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

联动模块代码

 /*-------------比较传感器数值是否大于设置的阈值--------------------*/
 while(stateFlag){//循环开始停止的标志
switch(ui->cb_SensorType->currentIndex()){
    case 0:
        resultFlag=tep_value>setValue?true:false;
        break;
    case 1:
        resultFlag=hum_value>setValue?true:false;
        break;
    case 2:
        resultFlag=ill_value>setValue?true:false;
        break;
}
/*---------大于号:true,小于号:false---------------*/
if(symbolFlag==resultFlag){
    if(ui->rab_lamp->isChecked())
         slotBtnLampClicked(SWITCH_ON);
}
else{
    if(ui->rab_lamp->isChecked())
        slotBtnLampClicked(SWITCH_OFF);
}
while(t.elapsed()<5000)
    QCoreApplication::processEvents();
 }
}

7.1 迭代器 Const_iterator

https://www.cnblogs.com/linuxAndMcu/p/11027937.html

自从Qt2.0发布就可以使用STL风格的迭代器了,它们适用于Qt和STL的泛型算法,并且对速度作了优化。

对于每个容器类,有两种STL风格的迭代器类型:只读的和可读写的。尽可能使用只读的迭代器,因为它们比可读写的迭代器要快。

容器 只读迭代器 可读写的迭代器
QList, QQueue QList::const_iterator QList::iterator
QLinkedList QLinkedList::const_iterator QLinkedList::iterator
QVector, QStack QVector::const_iterator QVector::iterator
QSet QSet::const_iterator QSet::iterator
QMap<Key, T>, QMultiMap<Key, T> QMap<Key, T>::const_iterator QMap<Key, T>::iterator
QHash<Key, T>, QMultiHash<Key, T> QHash<Key, T>::const_iterator QHash<Key, T>::iterator

STL迭代器的API是以数组中的指针为模型的,比如++运算符将迭代器前移到下一项,*运算符返回迭代器所指的那一项。事实上,对于QVector和QStack,它们的项在内存中存储在相邻的位置,迭代器类型正是T *,const迭代器类型正是const T *。

QList示例

下面是一个典型例子,按顺序循环正序遍历QList中的所有元素,并将它们转为小写:

QList<QString> list;
list << "A" << "B" << "C" << "D";
 
QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
    *i = (*i).toLower();

不同于Java风格的迭代器,STL风格的迭代器直接指向每一项。begin()函数返回指向容器中第一项的迭代器。end()函数返回指向容器中最后一项后面一个位置的迭代器,end()标记着一个无效的位置,不可以被解引用,主要用在循环的break条件。如果list是空的,begin()等于end(),所以我们永远不会执行循环。

下图展示了一个包含4个元素的vector的所有有效迭代器位置,用红色箭头标出:

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

倒序遍历需要我们在获得项之前减少迭代器,这需要一个while循环:

QList<QString> list;
list << "A" << "B" << "C" << "D";
 
QList<QString>::iterator i = list.end();
while (i != list.begin()) 
{
    --i;//倒序
    *i = (*i).toLower();
}

如果是只读的,你可以使用const_iterator、constBegin()和constEnd(),比如:

QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
    qDebug() << *i;

下面的表概括了STL风格迭代器的API:

表达式 用途
*i 返回当前项
++i 将迭代器指向下一项
i += n 迭代器向前移动n项
--i 将迭代器指向上一项
i -= n 将迭代器你向后移动n项
i - j 返回迭代器i和j之间项的数目

7.2 foreach 关键字

如果你想要按顺序遍历容器中的所有项,你可以使用Qt的foreach关键字。这个关键字是Qt特有的,与C++语言无关,并且使用了预处理器实现。

在QMap和QHash中,foreach可以获得键值对中值的部分。如果你遍历既想获得键又想获得值,则可以使用迭代器(这样是最快的),或者你可以这样写:

QMap<QString, int> map;
...
foreach (const QString &str, map.keys())
    qDebug() << str << ":" << map.value(str);

对于一个多值的(multi-valued)map:

QMultiMap<QString, int> map;
...
foreach (const QString &str, map.uniqueKeys()) 
{
    foreach (int i, map.values(str))
        qDebug() << str << ":" << i;
}

7.3 智能联动模式

实现的方法就是每执行一次联动,就把联动的内容作为key,执行的内容执行的次数作为value,这样最后我们通过values就知道哪个执行的最多次。

 str_s.append("当");
str_s.append(sensor_s);
str_s.append(symbol_s);
str_s.append(value_s);
str_s.append("执行开");
str_s.append(control_s);
if(s_map.contains(str_s)){//添加键值对
    s_map[str_s]+=1;//如果已经存在该键,则values+1
}else{
    s_map[str_s]=1;//如果不存在该键,则values=1
}
int max_value= 0;
QString max_key;
QMap<QString,int>::const_iterator i;//const_iterator迭代器
for(i=s_map.begin();i!=s_map.end();++i){
    if(max_value<i.value()){//比较values
        max_value=i.value();
        max_key=i.key();
//如果两个value相同,就比较最后一次联动的内容key与当前保存的key返回的最后一个索引位置和当前Qmap中的key的最后一个索引位置,谁大就用谁。
    }else if(max_value==i.value()){
        if(str_s.lastIndexOf(max_key)<str_s.lastIndexOf(i.key())){
            max_value=i.value();
            max_key=i.key();
        }
    }
}
ui->txt_LinkShow->append(time_s+str_s);
ui->lab_linkShow->setText(max_key);
ui->lab_times->setText(QString("该条件被执行:%1,次").arg(max_value));
str_s.clear();

8、监听端口

这里监听端口的意思起始就是,qt网关作为服务器,网关配置工具作为客户端。起始就是监听各个配置工具的消息是否有变动。

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

void MainDialog::on_listener_port_btn_clicked()
{
    if(!pSever->listen(QHostAddress::Any,6001)){
        qDebug("管理服务开启监听失败");
        return;
    }
    connect(pSever,SIGNAL(evt_configBoardId()),this,SLOT(slot_InitBoardId()));
}

void MainDialog::slot_InitBoardId()
{
    portdataHandle->init();//初始化板号
    qDebug()<<"1111111";
}
//更新信息会自动调用tcpServer中的socket_back_State()方法.Success Failure

9 连接到服务器以及监听android 信息

这时网关作为一个客户端连接到Window7服务器。把数据发给服务器,这时候网关作为监听对象,服务器作为监听者,把这个消息转发给其他的客户端,这里要注意,网关连接服务器的的端口是6006,android连接服务器的端口也是6006.

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

 pClient=new TcpClientThread();
 pClient->serverIP="18.1.5.2";//指定服务器ip,或者通过网关配置工具配置ip,这边读取数据库
// serverIp = sql.sqlQuery(QString("select serverIp from tb_gatewayInfo")).toString();
 connect(pClient,SIGNAL(evt_socket_receive(QString,uint,uint,uint)),this,SLOT(handleAndroidCmd(QString,uint,uint,uint)));
void MainDialog::on_con_server_btn_clicked()
{
    if(ui->con_server_btn->text()=="链接服务器"){
            pClient->start();
            ui->con_server_btn->setText("断开服务器");
    }else{
        ui->con_server_btn->setText("链接服务器");
        if(!pClient->isRunning()){
           return;
        }
        pClient->quit();
        pClient->wait();
        delete pClient;
    }
void MainDialog::handleAndroidCmd(QString type, unsigned int command, unsigned int cmdCode, unsigned int channel)
{
     slotBtnLampClicked(ui->lab_lamp->text().contains("OFF")?SWITCH_ON:SWITCH_OFF);
}

10 烧写到网关

10.1 制作镜像文件

步骤

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

10.1.1 qt编译

  • 修改.pro文件中的LIBS(把x86的库改为arm库)

    LIBS += lib-SmartHomeGateway-ARM-V4.so // x86是在ubuntu中跑的库,arm是在网关上跑的库
    
  • 点击项目,修改编译器

    qt的版本修改为  : Qt 4.7.0在PATH(系统)
    工具链  : GCCE(如果没有,就添加一个,编译器路径为/opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-g++)
    构建路径 : 选择ARM的文件夹(需要把库文件lib-SmartHomeGateway-ARM-V4.so放进来)
    
  • 重新编译生成可执行文件

    按小锤子或者ctrl+B即可。

    如果下次重新编译,需要把ARM中除了库文件以外的其他文件,全部清除。否则容易出错。

10.1.2 拷贝到6410文件夹

由于6410文件夹的访问权限是系统管理员,而我们的当前是访客,所以拷贝就需要用系统管理员命令来完成了。

# 先删除原有的可执行文件
rm /6410/rootfs_qtopia_qt4/mnt/zdd/可执行文件
# 先删除原有的库文件夹
rm /6410/lib-SmartHomeGateway-ARM-V4.so
# 删除原有的img文件
rm /6410/img文件
# 拷贝可执行文件到 /6410/rootfs_qtopia_qt4/mnt/zdd/
cp  /可执行文件路径/可执行文件夹名  /6410/rootfs_qtopia_qt4/mnt/zdd/
# 拷贝库文件到 /6410/
cp  /可执行文件路径/lib-SmartHomeGateway-ARM-V4.so  /6410/

10.1.3 修改编译文件

# 修改rcs文件
gedit /6410/rootfs_qtpia_qt4/etc/init.d/rcS
在倒数第二行上面插入一行,写入:
qt4
# 修改qt4文件
gedit /6410/rootfs_qtopia_qt4/bin/qt4
在文件的最后一行填写可这执行文件的路径,和读写权限
mnt/zdd/项目名称(大小写区分) -qws

10.1.4 开始编译img文件

# 切换到编译文件夹的路径
cd /6410/
# 开始编译
/usr/sbin/mkyaffs2image-128M  rootfs_qtpoia_qt4/ mySmartHome.img(名字可以自定义)
#修改img文件的权限
chmod 777 mySmartHome.img(777是给文件夹最高的权限,任何用户可读可写)

至此镜像就制作完成了。

10.2 烧写镜像到网关

拿到我们烧写好的img文件烧写到A8网关.

2019年智能家居安装与维护"A卷"Qt 嵌入式部分

10.2.1 制作USB启动卡

  • 插入SD卡到电脑,格式化SD卡

  • 打开SD-Flasher.exe,Machine Type选择Mini210/Tiny210

    2019年智能家居安装与维护"A卷"Qt 嵌入式部分

  • 制作完成后,将images文件夹拷贝到SD卡中。

    Linux : zImage
    FriendARM.ini : 烧写配置文件 里面最重要的一句话USBMode=yes
    Superboot210.bin :镜像
    

    FriendARM.ini文件内容

    #This line cannot be removed. by FriendlyARM(www.arm9.net)
    
    CheckOneButton=No
    Action = Install
    OS = Linux 
    
    LCD-Mode = No
    LCD-Type = S70
    
    LowFormat = No 
    VerifyNandWrite = No
    CheckCRC32=No
    USBMode = yes
    
    StatusType = Beeper | LED
    
    ################### Linux ####################
    Linux-BootLoader = Superboot210.bin
    Linux-Kernel = Linux/zImage
    Linux-CommandLine = root=/dev/mtdblock4 rootfstype=yaffs2 console=ttySAC0,115200 init=/linuxrc skipcali=yes ctp=2 ethmac=1C:6F:65:34:51:7E
    

    启动SD卡就制作好了。

10.2.2 A8 进入USB模式

A8关机状态下,插入制作好的SD卡,打到USB模式,数据线连接电脑,启动A8.

10.2.3 MiniTools烧写镜像

10.2.3.1 安装驱动

window10系统不能直接使用提供好的MiniTools工具,需要安装fastboot驱动,而安装驱动需要禁止数字签名。

进入系统设置,点击更新与安全——恢复——高级启动(立即重启)——疑难解答——高级选项——启动设置——7就可以了。

https://jingyan.baidu.com/article/ff411625520aa552e582377a.html

安装Minitools v1.6的软件。

此时我们打开软件就能看到A8连接到电脑了。

10.2.3.2 烧写镜像

1、linux

2、勾选Linux BootLoader 然后选择我提供的Superboot210.bin

3、勾选Linux Kernel 然后选择我提供的zImage

4、勾选Kernel CommandLine 然后输入:root=/dev/mtdblock4 rootfstype=yaffs2 console=ttySAC0,115200 init=/linuxrc skipcali=yes ctp=2

5、勾选Linux RootFs 选择要烧写的.img镜像。

至此镜像烧写完成。

10.3 注意的一些问题

1、从虚拟机拷贝镜像到主机,可能会出现蓝屏的现象,解决的办法:

  • 最好U盘进入ubuntu来拷贝。
  • 虚拟机设置共享的文件夹,然后``mount -t vmhgfs .host:/ /mnt/hgfs` 挂在这个分区

最好做拷贝之前,最好保存工作。

2、镜像在Ubuntu上跑可以,在A8上部分功能可以?

首先确定一点,只要在Ubuntu上能跑通的代码,那么在A8中肯定也能跑通,所以不需要考虑是代码的问题。

然后,你要看看UI的布局文件,可能是因为图片的尺寸超过了设定了UI的尺寸。A8不能自身适应图片的大小。

最后,我个人烧写了30遍,才得出了这个结论。告诉自己谨记。

3、Qt4中不提供软键盘功能,如果要输入怎么办?

外接键盘是可以的

4、串口通信和网线都连接在A8上吗?

是的。

11 其他需要的软件

vnc:可以查看局域网内远程桌面

nativcat :查看服务器数据库。 密码root,数据库名称smartXXX,表名 dataXXX。

12 注意问题

1、界面的数据打开不能更新?

回头串口通信的部分,因为当协调器和节点板连接的步骤。正常的流程:

  • 先开启协调器,再开启样板间,这样节点板发送的连接信息,协调器肯定能接收到。
  • 如果这个顺序颠倒了,那么协调器肯定收不到信息,那么连接就无效了。

一定要注意这个问题。

上一篇:QT软件开发之入门基础--1.6 字符串类介绍


下一篇:UML建模之活动图介绍(Activity Diagram)