关于“新型IOT智慧农业——产粮优化改进措施”软硬件开发环境普及&相关技术开源分享

·项目背景及国家政策

        随着全球农业现代化进程的加快,以及物联网、人工智能等先进技术的发展与应用,智慧农业已经成为现代农业发展的新趋势。基于精准感知、智能控制和远程管理的智慧农业系统能够显著提升农作物生产效率,降低资源消耗,实现环境友好型可持续农业生产。

·创新背景

        在当前背景下,我国正大力推进数字乡村建设,智慧农业管理系统作为其中的重要组成部分,对于提高农业生产精细化管理水平,解决传统农业中信息获取不及时、人工管理成本高、决策缺乏科学依据等问题具有重要作用。

整体而言,对比全球市场,我国智慧农业起步较晚,农业机械化、作业智能化程度远低于其他领先国家,依旧处于成长初期,市场空间广阔。得益于我国全方位政策红利支持,财政投入不断增加推动智慧农业市场快速发展,地域之间普及水平有所差异,市场竞争格局分散,但细分市场稳步发展。具体如下:

(1)智慧农业处于成长初期,全方位政策与财政资金支持,市场空间广阔

(2)经济发展不均衡引致区域性差异,各地区积极探索差异化发展路径

(3)智慧农业细分领域众多,行业竞争格局分散

(4)物联网及设备、大数据服务及管理系统等细分领域快速发展

·项目简介

项目文化主旨:推动新一代信息技术与农业生产经营深度融合,建立农业农村大数据体系,实现农业生产的智能化和精准化

特色亮点:

1、精准农业:通过物联网技术实现精准播种溉和施肥,提高农作物产量和质量

2、远程控制:使农民能够通过智能手机或电脑远程控制农业机械和设备

3、数据分析:收集农业生产数据,通过大数据分析优化农业生产决策

4、智慧物流:运用物联网技术优化农产品的储存、运输和销售过程

5、农村信息服务:提供天气预报、市场价格信息等服务,帮助农民做出更好的经营决策

6、农业电商平台:建立线上销售平台,拓宽农产品销售渠道,提高农民收入

7、环境保护:通过物联网技术监测和管理农业生产过程中的资源使用和废物排放

8、乡村教育与培训:利用数字技术提供农业技术和管理知识的在线教育和培训。

·产品介绍 

        本设计开发一套基于Cortex-M系的嵌入式主控芯片的智慧农业管理系统,并通过单独设计的的控制模块拓展口(采用MQTT协议)来与传统农机进行联网控制,并且通过大数据平台和传感器所获取的数据进行自动化处理,以此来提高农作物的质量与产量。 同时,采用NBIoT通信技术将采集到的数据上传至云端,利用EMQX开源MQTT服务器框架部署于华为云ECS服务器上的MQTT服务器,实现数据的远程展示与处理。未来会根据不同的设备进行进一步的设备改造优化。

        我们按照初步的设计理念进行了设计开发一套基于STM32F103RCT6主控芯片的智慧农业管理系统,通过集成DHT11温湿度传感器、BH1750光照强度传感器以及土壤湿度检测传感器,实时监测农田环境和作物生长状态,并在超出阈值时通过蜂鸣器报警,提醒管理人员进行灌溉、施肥等操作。并通过采用NBIoT通信技术(BC26模块)将采集到的数据上传至云端,实现小型的智慧农场环境建设,以此来证明项目的可行性。

·产品特色

        我们计划设计一套完整的联网控制拓展接口和联网拓展发射器,我们分别将其命名为P-NCEI(联网控制拓展接口)和P-NET(联网拓展发射器),设计原理是通过改造原传统农业机器的控制主板,将联网控制拓展口接入操作面板上,当客户将联网控制发射器接入时,原本的传统农业机器则可以完成自动化生产的功能。

        例如将P-NCEI(联网控制拓展接口)和P-NET(联网拓展发射器)接入耕地车中,可以通过自动驾驶技术、机器学习算法和传感器实时上传数据的结合,能够根据土壤条件和植物生长情况实现智能的播种和收割,这一革新显著提高了生产效率和作物产量。

        借助智能化的土壤传感器和数据分析,农机能够实现精准施肥。不同地块和不同时期,农机根据土壤养分情况和植物需求量,调整施肥量,最大程度地提高作物的质量和产量。

        再例如,在原基础的灌溉系统基础上加装我们的P-NCEI(联网控制拓展接口)和P-NET(联网拓展发射器),便可以和我们所搭建的产品管理系统具有水肥一体机系统进行连接,同时将P-NET所具备的实时数据显示、时间设置、触摸控制、实时控制、分区灌溉、轮灌模式、系统参数设置等功能在原传统灌溉系统上实现。

        只需要登入我们的移动终端APP或产品官方网站即可实现设备的控制,能够更好的帮助农户管理农田,查看更详细、准确的农田数据,能有效提高经济效益。

(https://zlyjf.my.canva.site/tech-website-in-dark-green-light-green-gradients-style)

·程序设计

注:本程序仅限于参考,不具备复制使用功能性!⚠️

注:本程序仅限于参考,不具备复制使用功能性!⚠️

注:本程序仅限于参考,不具备复制使用功能性!⚠️

·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定义成员,并且实例化

显式new创建

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

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

new的注意:

  • new创建类对象需要指针接收,一处初始化,多处使用

  • new创建类对象使用完需delete销毁

  • new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间

  • new对象指针用途广泛,比如作为函数返回值、函数参数等

  • 频繁调用场合并不适合new,就像new申请和释放内存一样

  • 0.5 C++堆和栈

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

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

    这个解释到位:C++内存分区详解

    0.6垃圾回收

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

  • 指定父对象
  • 直接或者间接继承与Qobject的
  • 不需要手动释放(delete),QObject中的析构对象树会自动释放。

    0.7 运算符

    常用的位运算

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

    0.8 unsigned

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

    0.9内存和寄存器

    上图所示: 我们的系统文件是保存在硬盘中的,每次开机,就会把系统加载到内存中 ,就算这样还是赶不上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

    深入理解:深入理解volatile关键字的作用(一)-CSDN博客

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

    ·正文

            本程序使用QT4.6进行编写,涉及到数据库调用串口线程、控制程序、窗口设计等,后续会继续拓展与Android Studio所开发的界面控制程序连接,实现移动端—>客户端—>网关—>服务器—>硬件,五大类所结合的控制生态系统环境。

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

    serialportthread.h(串口.h文件开发代码)

    #ifndef SERIALPORTTHREAD_H
    #define SERIALPORTTHREAD_H
    
    #include <QThread>
    #include "qextserialport.h"
    #include "serialportreceive.h"
    #include "systemConfig.h"
    
    class SerialPortThread : public QThread
    {
        Q_OBJECT
    public:
        explicit SerialPortThread(QObject *parent = 0);
        int send(QByteArray ba); //串口发送
    private:
        QextSerialPort *myCom;  // 串口对象
        SerialPortReceive *portReciveHandle; //串口数据接受处理
        void run(); //虚函数,线程运行
    signals:
        void evt_serial_receive(const QByteArray &); //数据接收完成信号
    public slots:
         void onReadyRead(); //数据读取
    };
    
    #endif // SERIALPORTRECEIVETHREAD_H

    serialportthread.cpp(串口.cpp文件开发代码)

    #include "serialportthread.h"
    #include <QDebug>
    
    SerialPortThread::SerialPortThread(QObject *parent) :
        QThread(parent)
    {
        moveToThread(this);
    }
    
    void SerialPortThread::run()
    {
    
        myCom = new QextSerialPort("/dev/ttyUSB0",QextSerialPort::EventDriven);//电脑测试USB0 驱动     ARM网关写SAC1驱动
        myCom->setBaudRate(BAUD115200);
        myCom->setFlowControl(FLOW_OFF);
        myCom->setParity(PAR_NONE);
        myCom->setDataBits(DATA_8);
        myCom->setStopBits(STOP_1);
        if(myCom->open(QIODevice::ReadWrite) == true)
        {
                connect(myCom,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
                portReciveHandle = new SerialPortReceive();
                connect(portReciveHandle,SIGNAL(byteFinish(QByteArray)),this,SIGNAL(evt_serial_receive(QByteArray)));
                qDebug("成功");
         }
        else
         {
                qDebug("失败");
         }
        this->exec();
    }
    
    void SerialPortThread::onReadyRead()
    {
       QByteArray temp = myCom->readAll();
    
       while(portReciveHandle->dataHandleFlag == 1)
       {
            msleep(1);
       }
       portReciveHandle->dataHandle(temp);
    }
    
    int SerialPortThread::send(QByteArray ba)
    {
        int bytes = -1;
        myCom->flush();
        bytes = myCom->write(ba);
        return bytes;
    }
    

    串口通信(QextSerialPort)

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

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

    一、同步通信和异步通信。

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

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

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

    二、 波特率bandrate

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

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

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

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

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

    为什么要流控

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

    为什么现在不用流控

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

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

    在信号线上共有两种状态,分别是逻辑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之间的间隔即为帧间隔。

    五、ReadyRead信号

    该信号是QextSerialport继承于QIODevice

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

    flush()

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

    write()

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

    close()

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

    ·硬件控制程序代码

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

    dialog.h(控制程序.h程序)

    此headers主要功能为此后会用到的sources创建调用函数类,以及定时器的创建

    1、QObject内部定时器

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

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

  • 上述定时器例子都为循环触发,需要停止定时器请使用stopkillTimer,而想使用单次定时器最好使用QTimer::singleShot接口。
  • 使用QObject::timerEvent捕获定时器事件,如果存在多个定时器源,可以使用timerId来判断确定那个定时器事件。
  • 如果系统忙或无法提供请求的准确性,所有定时器类型都有可能会比预期的时间晚超时。在这种晚超时的情况下,虽然是多个超时已经过期,但是只发出一次超时事件。
  • QTimer的remainingTime接口可以获得距离触发定时器事件的剩余时间
  • 使用QObject的startTimer需要注意的是每调用一次会新增一个定时器并返回一个定时器ID。
  • #ifndef DIALOG_H
    #define DIALOG_H
    
    #include <QDialog>
    
    //头文件都要加载哦
    #include "tcpclientthread.h"
    #include "tcpserver.h"
    #include "serialdatahandle.h"
    #include "serialportreceive.h"
    #include "serialportthread.h"
    #include "sql.h"
    #include "VariableDefinition.h"
    
    namespace Ui {
    class Dialog;
    }
    
    class Dialog : public QDialog
    {
        Q_OBJECT
        
    public:
        explicit Dialog(QWidget *parent = 0);
        ~Dialog();
        
    private:
        Ui::Dialog *ui;
    
    private:
        //下面是变量
        TcpClientThread *pClient;  //socket客户端线程
        TcpServer *pServer;        //socket服务线程
        SerialPortThread *pSerial_port;   //串口线程
        SerialDataHandle *pSerialDataHandle; //串口命令处理
    
        int client_timer;  //客户端的定时器
        int serial_send_delay_timer;   //串口延迟发送的定时器
    
        int read_node_timer;   //读取继电器等节点的定时器
        int read_node_times;   //每次读取的时间
        int read_node_num;     //继电器等节点的最大上限
    
        unsigned int boardIdNodeDelay; //延时后需要控制的继电器板号,用于产生控制窗帘的脉冲信号
    
        int  time_timer;//时间的定时器
        int  qingjing_timer;//情景模式的定时器
    private:
        //下面是函数功能
    
    
        void deviceControl(unsigned int board_number, unsigned int *pCmd ,unsigned int len);
        //deviceControl设备控制   函数参数: board_number:板号;*pCmd:代发送数据 ; len: pcmd的长度
    
    private slots:
        //信号槽
    
        void slot_control_device(unsigned int boardId,QString type,unsigned int cmd,unsigned int cmdCode,unsigned int channel);
        //slot_control_device收到服务器数据包的控制命令    函数参数:type: 传感器类型;command:控制命令;clientIp:客户端的 ip,用于服务器返回给对应的安卓app
    
        void slot_serial_receive(const QByteArray &  ba);
          //slot_serial_receive 收到串口数据的控制命令    函数参数:ba:收到的串口数据
    
       void slot_upload(const QString & boardId,const QString & type,const QString & cmdCode,const QString & value);
       //slot_upload  上传给服务器数据     参数:key:json字段中的键;value:传感器设备当前值或状态
    
        void slot_initBoardId();
        //slot_initBoardId 初始化板号
    
    
        void on_pushButton_clicked();
    
        void on_pushButton_2_clicked();
    
        void on_pushButton_3_clicked();
    
        void on_pushButton_4_clicked();
    
        void on_pushButton_6_clicked();
    
        void on_pushButton_5_clicked();
    
        void on_pushButton_7_clicked();
    
        void on_pushButton_8_clicked();
    
        void on_pushButton_9_clicked();
    
        void on_pushButton_10_clicked();
    
        void on_btn_chuanglianopen_clicked();
    
        void on_btn_chuanglianzanting_clicked();
    
        void on_btn_chuanglianclose_clicked();
    
        void on_pushButton_12_clicked();
    
    protected:
        //被保护的权限
    
        virtual void timerEvent( QTimerEvent *event);
        //timerEvent是整个软件的所有定时器项目,用来时时刻刻监控更新 温度、光照、各种传感器,还有监控服务端、客户端等。
    };
    
    #endif // DIALOG_H

    dialog.cpp(控制程序.cpp程序)

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

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

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

    联动模式

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

    #include "dialog.h"
    #include "ui_dialog.h"
    #include <QDateTime> //时间获取的头文件
    
    volatile unsigned int alarm_state;         //报警灯状态
    volatile unsigned int light_state;         //射灯状态
    volatile unsigned int fan_state;         //风扇状态
    volatile unsigned int dvd_state;         //风扇状态
    volatile unsigned int door_state;         //门禁的状态
    
    
    volatile unsigned int wendu_state;         //温度的值
    volatile unsigned int guangzhao_state;     //光的值
    volatile unsigned int smoke_state;        //烟雾的值
    volatile unsigned int gas_state;     //燃气的值
    volatile unsigned int pm_state;     //pm的值
    volatile unsigned int co2_state;     //二氧化碳的值
    volatile unsigned int renti_state;     //人体红外的值
    volatile unsigned int qiya_state;     //气压的值
    
    volatile unsigned int qingjing_state;     //情景模式的状态 开还是关
    
    
    Dialog::Dialog(QWidget *parent) :
        QDialog(parent),
        ui(new Ui::Dialog)
    {
        ui->setupUi(this);
    
        this->setWindowFlags(Qt::FramelessWindowHint);//窗口全屏
    
    
        alarm_state=0;
        light_state=0;
        fan_state=1;
        qingjing_state=0;
        dvd_state=0;
    
        time_timer=startTimer(1000);//启动屏幕时间刷新,每1秒刷新一次。
    
        Sql sql; //数据库SQL
    
    
        ui->stackedWidget->setCurrentIndex(0);
    
    
        /****1、定时器初始化****/
        client_timer = 0;               //初始化客户端定时器,设为0
        read_node_timer = 0;            //初始化节点读取定时器,设为0
        serial_send_delay_timer = 0;      //初始化串口延迟发送定时器,设为0
    
    
        /****2、服务端开启监听****/
        pServer = new TcpServer(this);
        if(!pServer->listen(QHostAddress::Any,6001))
                qDebug("开启监听失败");
        connect(pServer,SIGNAL(evt_configBoardId()),this,SLOT(slot_initBoardId()));
    
    
         /****3、客户端开启监听****/
        pClient = new TcpClientThread();
        pClient->serverIP = "192.168.1.5"; //具体IP根据你们配置的服务器IP来定
        pClient->start();
        connect(pClient,SIGNAL(evt_socket_receive(uint,QString,uint,uint,uint)),this,SLOT(slot_control_device(uint,QString,uint,uint,uint)));
        client_timer = startTimer(1000);// 设置为1000毫秒 刷新一次,并且上传数据到服务器
    
    
         /****4、串口启动****/
          pSerial_port = new SerialPortThread();
          pSerial_port->start();
          connect(pSerial_port,SIGNAL(evt_serial_receive(QByteArray)),this,SLOT(slot_serial_receive(QByteArray)));
    
    
         /****5、串口数据包处理****/
          pSerialDataHandle = new SerialDataHandle();
          connect(pSerialDataHandle,SIGNAL(evt_deviceState(QString,QString,QString,QString)),this,SLOT(slot_upload(QString,QString,QString,QString)));
    
    
         /****6、读取节点初始化****/
         read_node_num = 0;
         read_node_times = 0;
         read_node_timer = startTimer(500);  // 设置为500毫秒 读取一次继电器等节点
    
    
    }
    
    //下面全是具体的函数功能实现
    
    /***实现板号初始化***/
    void Dialog::slot_initBoardId()
    {
        pSerialDataHandle->init();
    }
    
    /***收到服务器数据后,自动执行的控制某些设备***/
    void Dialog::slot_control_device(unsigned int boardId,QString type, unsigned int cmd,unsigned int cmdCode,unsigned int channel)
    {
        unsigned int pCmd[9] ;
        if(type == INFRARED_SERVE)
        {
            pSerialDataHandle->cmdArryHandle(pCmd,INFRARED,channel&0xf,(channel>>8)&0xf);
        }else if(type == DC_SERVE)
        {
            pSerialDataHandle->cmdArryHandle(pCmd,RFID,cmd,channel);
        }else if(type == RELAY_SERVE)
        {
            pSerialDataHandle->cmdArryHandle(pCmd,RELAY,cmd,channel);
            if(cmdCode == NODE_RELAY)
            {
                boardIdNodeDelay = boardId;
                serial_send_delay_timer = startTimer(500);
            }
        }
        deviceControl(boardId,pCmd,9);
    }
    
    /***设备控制的函数***/
    void Dialog::deviceControl(unsigned int board_number, unsigned int *pCmd ,unsigned int len)
    {
        QByteArray ba = pSerialDataHandle->sendingDataHandle(CONTROL,board_number,pCmd,len);
        int bytes = pSerial_port->send(ba);
        if(bytes < 0)
        {
            pSerial_port->send(ba);
        }
    }
    
    /***串口接收的函数***/
    void  Dialog::slot_serial_receive(const QByteArray &  ba)
    {
        pSerialDataHandle->receive(ba);
    }
    
    
    /***上传服务器***/
    void Dialog::slot_upload(const QString & boardId,const QString & type,const QString & cmdCode,const QString & value)
    {
        if(pClient != NULL)
        {
            if(pClient->isRunning() && pClient->getTcpClientFlag())
                pClient->upload(boardId,type,cmdCode ,value);
         }
    
    
        int id = boardId.toInt();
    
        if(type == TEMP_SERVE && id == 1)
        {
            //温度
            ui->Lwendu->setText(value);
    
            //temp_value = value.toFloat();
        }
        if(type == HUM_SERVE && id == 1)
        {
            //湿度
            ui->Lshidu->setText(value+"%");
    
        }
        else if(type == SMK_SERVE && id ==3)
        {
            ui->Lyanwu->setText(value+"ppm");
       }
        else if(type == HI_SERVE && id ==8)
        {
            if(value=="0")
                ui->Lyanwu->setText("无人");
            else
                 ui->Lyanwu->setText("有人");
       }
        else if(type == CO2_SERVE && id ==6)
        {
                ui->Lco2->setText(value+"ppm");
    
       }
        else if(type == PM25_SERVE && id ==5)
        {
                ui->Lpm->setText(value+"up^3");
    
       }
        else if(type == ILL_SERVE && id ==2)
        {
                ui->Lguangzhao->setText(value+"lux");
    
       }
        else if(type == GAS_SERVE && id ==4)
        {
                ui->Lranqi->setText(value+"nm3");
    
       }
        else if(type == AP_SERVE && id ==7)
        {
                ui->Lqiya->setText(value+"pa");
    
       }
        else if(type == SMK_SERVE && id ==16)
        {
                ui->label_34->setText(value+"ppm");
    
       }
        else if(type == GAS_SERVE && id ==17)
        {
                ui->label_34->setText(value+"nm3");
    
       }
    }
    
    
    void Dialog::timerEvent(QTimerEvent *event)
    {
    
        if(event->timerId() == time_timer)
        {   //更新软件界面上的时间
            QDateTime curTime = QDateTime::currentDateTime();
            QString t_str = curTime.toString("yyyy-MM-dd hh:mm:ss");
            ui->label->setText(t_str);
        }
        else if(event->timerId() == client_timer)
        {
    
            if(pClient != NULL)
            {
                if(!pClient->isRunning())
                {
                    pClient->start();
                }
            }
    
        }else if(event->timerId() == read_node_timer)
        {
            read_node_num ++;
            if(read_node_num <= MAX_BOARDID_NUM)
            {
                unsigned int pCmd[3] ;
                pCmd[0] = 0;
                pCmd[1] = 0;
                pCmd[2] = read_node_num ;
                QByteArray ba = pSerialDataHandle->sendingDataHandle(READNODE,0,pCmd,3);
                pSerial_port->send(ba);
            }
            else
            {
                read_node_num = 0;
                read_node_times ++;
            }
            if(read_node_times == 1)
            {
                killTimer(read_node_timer);
                read_node_timer = 0;
            }
        }
        else if(event->timerId() == serial_send_delay_timer)
        {
            unsigned int pCmd[9] ;
            pSerialDataHandle->cmdArryHandle(pCmd,RELAY,SWITCH_OFF,CHANNEL_ALL);
            deviceControl(boardIdNodeDelay,pCmd,9);
            killTimer(serial_send_delay_timer);
            serial_send_delay_timer = 0;
        }
        else if(event->timerId() == qingjing_timer)
        {
    //情景模式
        }
    
    }
    
    
    Dialog::~Dialog()
    {
        delete ui;
    }
    
    void Dialog::on_pushButton_clicked()
    {
        //传感器页面
        ui->stackedWidget->setCurrentIndex(0);
    }
    
    void Dialog::on_pushButton_2_clicked()
    {
        //智能控制页面
         ui->stackedWidget->setCurrentIndex(1);
    }
    
    void Dialog::on_pushButton_3_clicked()
    {
    
        //情景模式页面
             ui->stackedWidget->setCurrentIndex(2);
    }
    
    void Dialog::on_pushButton_4_clicked()
    {
        //报警灯
        unsigned int pCmd[9] ;
    
      pSerialDataHandle->cmdArryHandle(pCmd,RELAY,SWITCH_OFF,CHANNEL_ALL);
      alarm_state =! alarm_state;
      deviceControl(11,pCmd,9);
    }
    
    void Dialog::on_pushButton_6_clicked()
    {
           //射灯
        unsigned int pCmd[9] ;
        light_state =!light_state;
         pSerialDataHandle->cmdArryHandle(pCmd,RELAY,light_state,CHANNEL_ALL);
           deviceControl(9,pCmd,9);
    }
    
    void Dialog::on_pushButton_5_clicked()
    {
        //风扇
    
        unsigned int pCmd[9] ;
    
      fan_state=!fan_state;
      pSerialDataHandle->cmdArryHandle(pCmd,RELAY,fan_state,CHANNEL_ALL);
      deviceControl(10,pCmd,9);
    
    
    }
    
    void Dialog::on_pushButton_7_clicked()
    {
        //DVD
        unsigned int pCmd[9] ;
        dvd_state!=dvd_state;
    
           pSerialDataHandle->cmdArryHandle(pCmd,INFRARED,CHANNEL_1&0xf,(CHANNEL_1>>8)&0xf);
           deviceControl(13,pCmd,9);
    }
    
    void Dialog::on_pushButton_8_clicked()
    {
        //TV
        unsigned int pCmd[9] ;
    
        pSerialDataHandle->cmdArryHandle(pCmd,INFRARED,CHANNEL_2&0xf,(CHANNEL_2>>8)&0xf);
           deviceControl(13,pCmd,9);
    }
    
    void Dialog::on_pushButton_9_clicked()
    {
        //空调
        unsigned int pCmd[9] ;
    
           pSerialDataHandle->cmdArryHandle(pCmd,INFRARED,CHANNEL_1&0xf,(CHANNEL_1>>8)&0xf);
           deviceControl(14,pCmd,9);
    }
    
    void Dialog::on_pushButton_10_clicked()
    {
    //门禁
            unsigned int pCmd[9] ;
            pSerialDataHandle->cmdArryHandle(pCmd,RFID,SWITCH_ON,CHANNEL_1);
            deviceControl(15,pCmd,9);
    }
    
    void Dialog::on_btn_chuanglianopen_clicked()
    {
        //窗帘开
        unsigned int pCmd[9] ;
           pSerialDataHandle->cmdArryHandle(pCmd,RELAY,SWITCH_ON,CHANNEL_1);
           deviceControl(12,pCmd,9);
           boardIdNodeDelay = 12;
           serial_send_delay_timer = startTimer(500);
    }
    
    void Dialog::on_btn_chuanglianzanting_clicked()
    {
        //窗帘暂停
        unsigned int pCmd[9] ;
            pSerialDataHandle->cmdArryHandle(pCmd,RELAY,SWITCH_ON,CHANNEL_2);
            deviceControl(12,pCmd,9);
            boardIdNodeDelay = 12;
            serial_send_delay_timer = startTimer(500);
    }
    
    void Dialog::on_btn_chuanglianclose_clicked()
    {
        //窗帘关
        unsigned int pCmd[9] ;
           pSerialDataHandle->cmdArryHandle(pCmd,RELAY,SWITCH_ON,CHANNEL_3);
           deviceControl(12,pCmd,9);
           boardIdNodeDelay = 12;
           serial_send_delay_timer = startTimer(500);
    }
    
    void Dialog::on_pushButton_12_clicked()
    {
        //情景模式开启
    
    
            if(qingjing_state == 0)
                {
                    qingjing_timer = startTimer(2000);
                    ui->pushButton_12->setStyleSheet("border-image: url(:/new/img/images/checkout_bai.png);");
                    qingjing_state = 1;
                }
              else
                {
                    killTimer(qingjing_timer);
                    ui->pushButton_12->setStyleSheet("border-image: url(:/new/img/images/checkout_lan.png);");
                    qingjing_timer = -1;
                    qingjing_state = 0;
                }
    }
    

    main.cpp(sql数据库连接)

    #include <QtGui/QApplication>
    #include "dialog.h"
    
    #include "dialog.h"    //头文件记得
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
    
    
            //数据库Sql连接
            Sql sql;
           sql.sqlConnect("db_modelRoom.db");
    
    
        Dialog w;
        w.show();
        
        return a.exec();
    }
    

    ·智能联动模式

            实现的方法就是每执行一次联动,就把联动的内容作为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();

    作者:siially

    物联沃分享整理
    物联沃-IOTWORD物联网 » 关于“新型IOT智慧农业——产粮优化改进措施”软硬件开发环境普及&相关技术开源分享

    发表回复