前言
我國是農(nóng)業(yè)大國,而非農(nóng)業(yè)強國。近30年來農(nóng)業(yè)高產(chǎn)量主要依靠農(nóng)藥化肥的大量投入,大部分化肥和水資源沒有被有效利用而隨地棄置,導致大量養(yǎng)分損失并造成環(huán)境污染。我國農(nóng)業(yè)生產(chǎn)仍然以傳統(tǒng)生產(chǎn)模式為主,傳統(tǒng)耕種只能憑經(jīng)驗施肥灌溉,不僅浪費大量的人力物力,也對環(huán)境保護與水土保持構(gòu)成嚴重威脅,對農(nóng)業(yè)可持續(xù)性發(fā)展帶來嚴峻挑戰(zhàn)
基于I.MX6ULL的Linux C多線程物聯(lián)網(wǎng)網(wǎng)關(guān)+STM32+Qt上位機+Linux C++多線程服務器(含web服務)的多種無線通信系統(tǒng)的智慧農(nóng)場項目
技術(shù)棧+硬件選型
Linux c++應用編程(JS,WEB,HTML什么的我只知道一點點皮毛,只用了其一兩個函數(shù));
Linux socket編程,多線程編程,內(nèi)核驅(qū)動編程,文件I/O
Qt/C++ 客戶端開發(fā);
Mysql 數(shù)據(jù)存儲;
C語言下位機開發(fā);
I.MX6ULL? ? 挺貴,不建議買,我腦抽買了:
光敏模塊? ?模擬天黑天亮;
水泵? ? ? ? ? 抽水;
電機? ? ? ? ?散熱
電機驅(qū)動模塊 *2
土壤濕度檢測傳感器;
ZigBee DL_22 *2? 45r;
HC-06 *2? ? 藍牙模塊;
stm32c8t6 *4 下位機(便宜,夠用,市面價格10r);
RC522(RFID模塊 SPI協(xié)議) 與白卡通信 獲取卡號;
DTH11 溫濕度采集模塊(單總線協(xié)議,市面價格 4r);
sg90 舵機模塊(PWM協(xié)議 市面價9r );
8226 01-s WIFI模塊*2? ?(uart協(xié)議 市面價格5r) 連接 C++ 服務器 和做熱點;
蜂鳴器? RFID注冊提示音
總設(shè)計流程
本系統(tǒng)一共有四個單片機,四個單片機上分別掛載了的不同傳感器結(jié)點和一個通信模塊,通過三種無線通信協(xié)議向物聯(lián)網(wǎng)網(wǎng)關(guān)發(fā)送結(jié)點數(shù)據(jù),物聯(lián)網(wǎng)網(wǎng)關(guān)收到數(shù)據(jù)將數(shù)據(jù)上傳至Linux C++云服務器,服務器監(jiān)聽了5個端口,分別是用于監(jiān)聽物聯(lián)網(wǎng)網(wǎng)關(guān)的消息,web服務的80端口用與發(fā)送HTML給瀏覽器,與JavaScript通信更新HTML網(wǎng)頁的端口,還有物聯(lián)網(wǎng)網(wǎng)關(guān)終端Qt界面,用與對物聯(lián)網(wǎng)網(wǎng)關(guān)的下結(jié)點的整體控制與顯示,最后一個端口給一個Qt移動端的端口,用于智慧農(nóng)場的一系列數(shù)據(jù)顯示,整個物聯(lián)網(wǎng)系統(tǒng)服務于智慧農(nóng)場。兩兩單片機可以說毫無聯(lián)系,但經(jīng)過網(wǎng)關(guān)和服務器的連接,又顯得聯(lián)系緊密恰巧凸顯一個完整的物聯(lián)網(wǎng)控制系統(tǒng)。
STM32C8T6:
除了使用8266模塊的單片機不需要編號因為8266會自動為其局域網(wǎng)下的用戶編號,其他都需要編號,同一個無線傳感器上編號必須不一樣,還有就上傳服務器的設(shè)備名字不能一樣,Mysql將設(shè)備名字設(shè)為主鍵了,唯一。單片機需要每隔2s左右上傳一次傳感器結(jié)點數(shù)據(jù),上傳格式為 “ ID+傳感器設(shè)備名字+#+value+操作符”? 例如?,002舵機#false0 , 001電機#true#LED3#false0?? 這樣為一個數(shù)據(jù)包可一直延申,按格式寫就好了,服務器按格式拆包。因為存在設(shè)備控制,所以需要預留控制接口,比如對收到的數(shù)據(jù)拆包,如果收到id與自身id相同,再判斷傳感器設(shè)備名字,如果傳感器設(shè)備名字相同,在根據(jù)value去改變設(shè)備狀態(tài)。操作符什么的在服務器介紹那邊會講訴清楚,知道有這個事情就可以了。RFID模塊上傳數(shù)據(jù)都很特殊,所以自己寫了幾個操作符專門服務于RFID設(shè)備的注冊,與門禁比對。
I.MX6ULL Linux C 多線程物聯(lián)網(wǎng)網(wǎng)關(guān):
一共有四個通信模塊,物聯(lián)網(wǎng)網(wǎng)關(guān)通過三種無線通信模塊接收數(shù)據(jù)MCU傳來的數(shù)據(jù),然后通過一個無線通信模塊(8266 -01s Sation模式)連接Linux云服務器上傳數(shù)據(jù),所以可以通過三種協(xié)議向物聯(lián)網(wǎng)網(wǎng)關(guān)發(fā)送數(shù)據(jù),無線通信為塊為ESP 8266-01s,HC-06,ZigBee,其中只有HC-06是一對一通信,其他都是可以一對多,三種通信模塊都是串口(UART)驅(qū)動,其中一個ESP 8266-01s配置成AP 模式,AP 模式是指 ESP8266 模塊自身作為一個熱點,然后監(jiān)聽一個8888端口,單片機即可直接與其連接,從而實現(xiàn)物聯(lián)網(wǎng)網(wǎng)關(guān)獲取整個局域網(wǎng)的結(jié)點信息,另外一個ESP 8266-01s 配置成Sation 模式,Sation 模式是 ESP8266 模塊通過路由器連接Linux云服務器,對設(shè)備的遠程控制功就能通過互聯(lián)網(wǎng)實現(xiàn),用于將其他三個模塊收到的消息上傳Linux云服務器。ZigBee是硬件上按鈕配置,配置成相同信道,廣播模式就可以接收同信道的數(shù)據(jù)了。三種都是串口協(xié)議,所以我們需要編寫I.MX6ULL的Linux驅(qū)動程序,這里I.MX6ULL的恩智浦官方已經(jīng)寫好了,但是我不會搞設(shè)備樹那些,好不容易修改的設(shè)備樹去編譯驅(qū)動然后Uboot啟動I.MX6ULL時,內(nèi)核啟動報錯,我也不知道什么原因,用的是原子的的設(shè)備樹和內(nèi)核文件,當時搞不出來,我就中直接用配置寄存器的方式自己寫4個串口驅(qū)動就行了,串口驅(qū)動還是簡單的,照著裸機歷程去配置寄存器就好了,有一些細微的區(qū)別就是定義寄存器地址需要映射出寄存器虛擬地址指針,讀取寄存器修改寄存器的方式也不同。
驅(qū)動程序編寫,最重要的就是文件操作結(jié)構(gòu)體,用戶與內(nèi)核空間信息交互的橋梁,學過STM32都知道串口接收函數(shù)的編寫吧,接收寄存讀到的數(shù)據(jù)一直追加在一個BUF里面,直到當收到\r\n時,代表接收完畢,立一個標志位,用戶空間一直通過read函數(shù)讀取驅(qū)動文件,如果驅(qū)動文件中的標志位為一,則代表收到了一個完整的數(shù)據(jù)包,將數(shù)據(jù)包發(fā)給用戶空間,然后清除標志位。這樣用戶空間獲取到了該通信模塊接收到的數(shù)據(jù),這里不同的是8266的消息接收函數(shù)不一樣!? ?!? ?!? 當時因為接收函數(shù)都寫一樣的這個8266的一直收不到,這個8266收到的消息有前有多個\r\n所以需要對其過濾。
? ?一共3模塊,所以需要開啟三個線程,三個線程打開各自的通信模塊的UART驅(qū)動模塊文件然后按照AT指令集用write函數(shù)將用戶空間的數(shù)據(jù)發(fā)送到內(nèi)核空間然后去發(fā)送AT指令配置模塊,配置完成后就輪詢獲取接收標志位,如果收到數(shù)據(jù),就通過一個?ESP 8266-01s 發(fā)送給Linux云服務器,所以思路就有了,我們創(chuàng)建三個線程,6個全局變量,其中三個為各自的接收數(shù)據(jù)BUF,另外三個為各自的標志位,子線程循環(huán)讀取內(nèi)核模塊傳給用戶空間的消息,當read函數(shù)>0,說明內(nèi)核模塊中的標志位被置一,說明接收到了一個完整的數(shù)據(jù)包,然后將用戶空間的對應標志位置一,主線程循環(huán)判斷三個標志位,當其中一個標志位為一,就將對應的buf寫入 上傳Linux云服務器的那個8266的UART內(nèi)核驅(qū)動文件,內(nèi)核驅(qū)動文件就將其寫入寄存器上傳Linux云服務器,最后標志位賦值為0,一直輪詢下去,然后需要將打開的文件的文件描述符置為全局變量方便主線程收到服務器的消息需要對特定的文件進行操作(寫入寄存器給指定的無線通信模塊發(fā)送數(shù)據(jù))。
一共有三個對外接收消息的模塊嘛,在網(wǎng)關(guān)我們可以知道該數(shù)據(jù)是哪個無線傳感器來的數(shù)據(jù),但是上傳服務器器后,服務器不知道,所以我們將經(jīng)過 8266-01s結(jié)點的消息都加1000 ,ZigBee加2000,HC-06加3000 ,這樣其中單片機id為001經(jīng)過ZigBee發(fā)過來就成了2001,同理藍牙,但是8266不一樣,他熱點模式自動分配IP號,可以直接實現(xiàn)該局域網(wǎng)下點對點通信,所以8266的單片機不需要發(fā)送帶id的數(shù)據(jù)包,這樣就根據(jù)單片機的id值大小范圍我們就知道了他所在結(jié)點區(qū)域,這樣就可以實現(xiàn)服務器對其的控制,同樣收到服務器傳下來的控制數(shù)據(jù)包時,我們需要根據(jù)范圍去判斷該值的是哪個結(jié)點的數(shù)據(jù)包,然后減去該增加的數(shù),最后傳到指定單片機上。
主循環(huán)
while(1)
{
if(WiFi8266Server_Flag==1)
{
WiFi8266Server_handle();
WiFi8266Server_Flag=0;
}
if(UARTServer_Flag==1)
{
TCP_Server_handle();
UARTServer_Flag=0;
}
if(ZigBee_Flag==1)
{
ZigBee_handle();
ZigBee_Flag=0;
}
if(HC06_Flag==1)
{
HC06_handle();
HC06_Flag=0;
}
if(UART_Flag==1)
{
UART_handle();
UART_Flag=0;
}
}
串口3文件驅(qū)動編寫,按照可以去寫其他幾個
#define NEWCHRIOBEE_CNT 1 /* 設(shè)備號個數(shù) */
#define NEWCHRIOBEE_NAME "newchriobee" /* 名字 */
/* 寄存器物理地址 */
#define CCM_CGPR1_BASE (0x020C406C) //uart3 ,uart4
#define CCM_CGPR0_BASE (0x020C4068) //uart2
//3
#define IOMUXC_UART3_TX_DATA_UART3_TX_BASE (0x020E00A4)
#define IOMUXC_UART3_RX_DATA_UART3_RX_BASE (0x020E00A8)
#define PAD_CTL_PAD_UART3_TX_DATA_BASE (0x020E0330)
#define PAD_CTL_PAD_UART3_RX_DATA_BASE (0x020E0334)
#define UART3_UFCR_BASE (0x021EC090)
#define UART3_UBIR_BASE (0x021EC0A4)
#define UART3_UBMR_BASE (0x021EC0A8)
#define UART3_UCR1_BASE (0x021EC080)
#define UART3_UCR2_BASE (0x021EC084)
#define UART3_UCR3_BASE (0x021EC088)
#define UART3_USR2_BASE (0x021EC098)
#define UART3_URXD_BASE (0x021EC000)
#define UART3_UTXD_BASE (0x021EC040)
/* 映射后的寄存器虛擬地址指針 */
static void __iomem * CCM_CGPR1;
static void __iomem * CCM_CGPR0;
static void __iomem *IOMUXC_UART3_TX_DATA_UART3_TX;
static void __iomem *IOMUXC_UART3_RX_DATA_UART3_RX;
static void __iomem *PAD_CTL_PAD_UART3_TX_DATA;
static void __iomem *PAD_CTL_PAD_UART3_RX_DATA;
static void __iomem *UART3_UFCR;
static void __iomem *UART3_UBIR;
static void __iomem *UART3_UBMR;
static void __iomem *UART3_UCR1;
static void __iomem *UART3_UCR2;
static void __iomem *UART3_UCR3;
static void __iomem *UART3_USR2;
static void __iomem *UART3_URXD;
static void __iomem *UART3_UTXD;
void register_init(void);
void uart_init(void);
void uart_io_init(void);
void uart_disable(void);
void uart_enable(void);
void uart_softreset(void);
int getc(void);
void myexit(void);
unsigned char i;
char RECS[100];
void register_init(void)
{
printk("register_init\r\n");
CCM_CGPR1=ioremap(CCM_CGPR1_BASE,4);
CCM_CGPR0=ioremap(CCM_CGPR0_BASE,4);
IOMUXC_UART3_TX_DATA_UART3_TX=ioremap(IOMUXC_UART3_TX_DATA_UART3_TX_BASE, 4);
IOMUXC_UART3_RX_DATA_UART3_RX=ioremap(IOMUXC_UART3_RX_DATA_UART3_RX_BASE,4);
PAD_CTL_PAD_UART3_TX_DATA=ioremap(PAD_CTL_PAD_UART3_TX_DATA_BASE,4);
PAD_CTL_PAD_UART3_RX_DATA=ioremap(PAD_CTL_PAD_UART3_RX_DATA_BASE,4);
UART3_UFCR=ioremap(UART3_UFCR_BASE,4);
UART3_UBIR=ioremap(UART3_UBIR_BASE,4);
UART3_UBMR=ioremap(UART3_UBMR_BASE,4);
UART3_UCR1=ioremap(UART3_UCR1_BASE,4);
UART3_UCR2=ioremap(UART3_UCR2_BASE,4);
UART3_UCR3=ioremap(UART3_UCR3_BASE,4);
UART3_USR2=ioremap(UART3_USR2_BASE,4);
UART3_URXD=ioremap(UART3_URXD_BASE,4);
UART3_UTXD=ioremap(UART3_UTXD_BASE,4);
}
void uart_init(void)
{
__u32 ret;
uart_io_init();
writel(0XFFFFFFFF,CCM_CGPR1);
writel(0XFFFFFFFF,CCM_CGPR0);
uart_disable();
uart_softreset();
writel(0,UART3_UCR1);
ret=readl(UART3_UCR2);
ret|= (1<<14) | (1<<5) | (1<<2) | (1<<1);
writel(ret,UART3_UCR2);
ret=readl(UART3_UCR3);
ret|= 1<<2;
writel(ret,UART3_UCR3);
ret=readl(UART3_UCR1);
ret &= ~(1<<14);
writel(ret,UART3_UCR1);
writel(5<<7,UART3_UFCR);
writel(71,UART3_UBIR);
writel(3124,UART3_UBMR);
uart_enable();
}
void uart_io_init(void)
{
writel(0,IOMUXC_UART3_TX_DATA_UART3_TX);
writel(0,IOMUXC_UART3_RX_DATA_UART3_RX);
writel(0x10B0,PAD_CTL_PAD_UART3_TX_DATA);
writel(0x10B0,PAD_CTL_PAD_UART3_TX_DATA);
}
void uart_disable()
{
__u32 ret;
ret=readl(UART3_UCR1);
ret &= ~(1<<0);
writel(ret,UART3_UCR1);
}
void uart_enable()
{
__u32 ret;
ret=readl(UART3_UCR1);
ret |= (1<<0);
writel(ret,UART3_UCR1);
}
void uart_softreset()
{
__u32 ret;
ret=readl(UART3_UCR2);
ret &= ~(1<<0); /* UCR2的bit0為0,復位UART */
writel(ret,UART3_UCR2);
while((readl(UART3_UCR2) & 0x1) == 0); /* 等待復位完成 */
}
void send_c(unsigned char c)
{
while(((readl(UART3_USR2)>> 3) & 0X01) == 0); /*等待上一次發(fā)送完成*/
writel(c & 0XFF, UART3_UTXD); /*寫入寄存器*/
}
void send_str(char *str)
{
char *p = str;
while(*p)
send_c(*p++);
}
int read_c(void)
{
while((readl(UART3_USR2) & 0x1) == 0);/*等待接收完成*/
RECS[i++]=readl(UART3_URXD);
if((RECS[i-2]=='\r')|(RECS[i-1]=='\n'))
{
RECS[i-2]='\0';
i = 0;
return 1;
}
return 0;
}
void myexit(void)
{
iounmap(CCM_CGPR1);
iounmap(IOMUXC_UART3_TX_DATA_UART3_TX);
iounmap(IOMUXC_UART3_RX_DATA_UART3_RX);
iounmap(PAD_CTL_PAD_UART3_TX_DATA);
iounmap(PAD_CTL_PAD_UART3_RX_DATA);
iounmap(UART3_UFCR);
iounmap(UART3_UBIR);
iounmap(UART3_UBMR);
iounmap(UART3_UCR1);
iounmap(UART3_UCR2);
iounmap(UART3_UCR3);
iounmap(UART3_USR2);
iounmap(UART3_URXD);
iounmap(UART3_UTXD);
}
struct newchriobee_dev{
dev_t devid; /* 設(shè)備號 */
struct cdev cdev; /* cdev */
struct class *class; /* 類 */
struct device *device; /* 設(shè)備 */
int major; /* 主設(shè)備號 */
int minor; /* 次設(shè)備號 */
};
struct newchriobee_dev newchriobee;
static int iobee_open(struct inode *inode, struct file *filp)
{
register_init(); /*初始化寄存器*/
uart_init(); /*初始化uart*/
printk("kernel open!\r\n");
filp->private_data = &newchriobee; /* 設(shè)置私有數(shù)據(jù) */
return 0;
}
static ssize_t iobee_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
if(!read_c()) /*判斷是否接收到數(shù)據(jù)*/
return 0;
printk("kernel recvdata:%s@\r\n",RECS); /*調(diào)試用途,可刪除*/
ret=copy_to_user(buf,RECS,cnt); /*內(nèi)核到用戶空間*/
if(ret<0)
{
printk("kernel read error!\r\n");
}
return 0;
}
static ssize_t iobee_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
char sendbuf[1024]={0};
int ret;
ret=copy_from_user(sendbuf,buf,cnt);
if(ret< 0) {
printk("write error !\r\n");
return 0;
}
printk("kernel sendstring:%s@\r\n",sendbuf); /*調(diào)試用途,可刪除*/
send_str(sendbuf);
return 0;
}
static int iobee_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 設(shè)備操作函數(shù) */
static struct file_operations newchriobee_fops = {
.owner = THIS_MODULE,
.open = iobee_open,
.read = iobee_read,
.write = iobee_write,
.release = iobee_release,
};
static int __init iobee_init(void)
{
/* 注冊字符設(shè)備驅(qū)動 */
/* 1、創(chuàng)建設(shè)備號 */
if (newchriobee.major) { /* 定義了設(shè)備號 */
newchriobee.devid = MKDEV(newchriobee.major, 0);
register_chrdev_region(newchriobee.devid, NEWCHRIOBEE_CNT, NEWCHRIOBEE_NAME);
} else { /* 沒有定義設(shè)備號 */
alloc_chrdev_region(&newchriobee.devid, 0, NEWCHRIOBEE_CNT, NEWCHRIOBEE_NAME); /* 申請設(shè)備號 */
newchriobee.major = MAJOR(newchriobee.devid); /* 獲取分配號的主設(shè)備號 */
newchriobee.minor = MINOR(newchriobee.devid); /* 獲取分配號的次設(shè)備號 */
}
printk("newcheiobee major=%d,minor=%d\r\n",newchriobee.major, newchriobee.minor);
/* 2、初始化cdev */
newchriobee.cdev.owner = THIS_MODULE;
cdev_init(&newchriobee.cdev, &newchriobee_fops);
/* 3、添加一個cdev */
cdev_add(&newchriobee.cdev, newchriobee.devid,NEWCHRIOBEE_CNT);
/* 4、創(chuàng)建類 */
newchriobee.class = class_create(THIS_MODULE,NEWCHRIOBEE_NAME);
if (IS_ERR(newchriobee.class)) {
return PTR_ERR(newchriobee.class);
}
/* 5、創(chuàng)建設(shè)備 */
newchriobee.device = device_create(newchriobee.class, NULL, newchriobee.devid, NULL,NEWCHRIOBEE_NAME);
if (IS_ERR(newchriobee.device)) {
return PTR_ERR(newchriobee.device);
}
return 0;
}
static void __exit iobee_exit(void)
{
myexit();
/* 注銷字符設(shè)備驅(qū)動 */
cdev_del(&newchriobee.cdev);/* 刪除cdev */
unregister_chrdev_region(newchriobee.devid, NEWCHRIOBEE_CNT); /* 注銷設(shè)備號 */
device_destroy(newchriobee.class, newchriobee.devid);
class_destroy(newchriobee.class);
printk("iobee clear !\r\n");
}
module_init(iobee_init);
module_exit(iobee_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YZM very Good");
Linux C++服務器:
咱們物聯(lián)網(wǎng)系統(tǒng)一共有五個設(shè)備需要接入云服務,分別是web,MCU,QT,QT APP,JS。所以創(chuàng)建五個套接字,綁定五個不同端口,實現(xiàn)不同的服務。訪問80端口服務器向客戶發(fā)送一個HTML文件,HTML文件里面有JS,JS連接服務器的另外一個端口,每2秒向服務器拿一次結(jié)點數(shù)據(jù)更新,響應JS數(shù)據(jù)需要自己發(fā)送響應頭和json格式的數(shù)據(jù)模擬WEB服務器。同理其他都差不多,MCU的端口服務是工程量最大的,首先介紹服務器的自動判斷單片機結(jié)點是否連接斷開功能,我規(guī)定單片機每2秒上傳一次傳感器個結(jié)點數(shù)據(jù)并且給每個單片機都固定一個IP,當上傳數(shù)據(jù)時包頭都是ID,我們拆包拿出ID,將IP數(shù)字作為鍵放入一個map容器。初始化鍵的值為true。然后給這個ID開一個線程讓一直while循環(huán)每sleep 5秒判斷一次鍵的值是否為true,如果為true則置為false繼續(xù)循環(huán),如果為false則刪除該數(shù)據(jù)退出線程清理線程。所以單片機每次上傳數(shù)據(jù)都判斷map容器是否存在ID如果存在則讓它置為true,不存在則添加,就像STM32的看門狗一樣沒有及時喂狗就沒有及時將false置為true,線程判斷到時如果還為false就刪除ID當作斷開連接處理,所以需要單片機每一段時間更新一次數(shù)據(jù)去將map容器的值置true。接下來多線程操作數(shù)據(jù)庫的問題,數(shù)據(jù)庫的性能瓶頸往往出現(xiàn)在并發(fā)讀寫上。我對Mysql的查詢優(yōu)化性能什么的挺薄弱的,我想了一個辦法,每次收到單片機的數(shù)據(jù)時我們對數(shù)據(jù)庫庫插入更改后,立馬進行一次查詢,然后將查詢到的數(shù)據(jù)包保存起來,web網(wǎng)頁呀Qt程序呀他們那邊都是定時器定時拿數(shù)據(jù),避免其他線程頻繁對數(shù)據(jù)庫查詢,直接讓他們定時去訪問這個保存起來的數(shù)據(jù)包,因為每來一次單片機數(shù)據(jù)都會更新數(shù)據(jù)庫和數(shù)據(jù)包所以數(shù)據(jù)包時刻都是最新狀態(tài)。
? 接下來就是各個單片機之間的控制系統(tǒng)的熟實現(xiàn),單片機上傳過來的數(shù)據(jù)包最后一為代表他需要的服務器進行的操作,0代表更新數(shù)據(jù),1代表需要控制其他單片機結(jié)點,2代表RFID注冊,3代表RFID卡號與數(shù)據(jù)庫比對,打個比如:我的數(shù)據(jù)包是? 1000name#value0? 這個數(shù)據(jù)包是id1000 設(shè)備名字name 值為value? ? 0為操作符 所以該數(shù)據(jù)是更新服務器數(shù)據(jù),服務器收到此數(shù)據(jù)就去數(shù)據(jù)庫更新數(shù)據(jù), 如果數(shù)據(jù)包為 1000 LED3#true1? ?這個1就是代表該id:1000 的單片機需要服務器將設(shè)備為 LED3的值修改為true 就是開燈的意思,然后服務器就去數(shù)據(jù)庫或者那個數(shù)據(jù)包找到包涵LED3設(shè)備的ID 知道了ID 我們直接組包發(fā)給網(wǎng)關(guān),網(wǎng)關(guān)根據(jù)ID范圍就知道該ID屬于哪個結(jié)點,然后就向該無線通信結(jié)點發(fā)送數(shù)據(jù)包控制該結(jié)點下的單片機的結(jié)點,對應廣播形式的無線傳感器結(jié)點我們單片機需要判斷收到的數(shù)據(jù)包的ID是不是跟本設(shè)備ID匹配,不同則代表不屬于該單片機消息,直接丟包。
通過內(nèi)模板創(chuàng)建線程
int main(void)
{
//數(shù)據(jù)庫初始化
SQLifconfig *sql_typ_MCU=new SQLifconfig;
sql_typ_MCU->SQL_init(host,user,pwd,dbname); /*數(shù)據(jù)庫連接*/
SQLifconfig *sql_typ_MID=new SQLifconfig;
sql_typ_MID->SQL_init(host,user,pwd,dbname); /*數(shù)據(jù)庫連接*/
SQLifconfig *sql_typ_APP=new SQLifconfig;
sql_typ_APP->SQL_init(host,user,pwd,dbname); /*數(shù)據(jù)庫連接*/
SQLifconfig *sql_typ_Web=new SQLifconfig;
sql_typ_Web->SQL_init(host,user,pwd,dbname); /*數(shù)據(jù)庫連接*/
SQLifconfig *sql_typ_JS=new SQLifconfig;
sql_typ_JS->SQL_init(host,user,pwd,dbname); /*數(shù)據(jù)庫連接*/
cout<<"sql select :"<<sql_typ_MCU->Dql_sql("SELECT * FROM node")<<endl;
sql_typ_MCU->Dml_sql("DELETE FROM node");
//server初始化
Myserver<MCUServer> MCU_server(AF_INET,SOCK_STREAM,0,sql_typ_MCU);
MCU_server.server_start(MY_IP,MY_MCUPORT,AF_INET);
Myserver<APPServer> APP_server(AF_INET,SOCK_STREAM,0,sql_typ_APP);
APP_server.server_start(MY_IP,MY_APPPORT,AF_INET);
Myserver<WebServer> Web_server(AF_INET,SOCK_STREAM,0,sql_typ_Web);
Web_server.server_start(MY_IP,MY_WEBPORT,AF_INET);
Myserver<JSServer> js_server(AF_INET,SOCK_STREAM,0,sql_typ_JS);
js_server.server_start(MY_IP,MY_JSPORT,AF_INET);
Myserver<MEEServer> MEE_server(AF_INET,SOCK_STREAM,0,sql_typ_MID);
MEE_server.server_start(MY_IP,MY_MMEPORT,AF_INET);
while (1)
{
//std::cout<<"new connect !!"<<endl;
}
}
Qt物聯(lián)網(wǎng)網(wǎng)關(guān)終端:
終端開一個定時器,定時去服務器拿數(shù)據(jù)顯示,根據(jù)ID范圍判斷哪個無線模塊結(jié)點下的數(shù)據(jù),顯示在特定的容器控件下,首先因為存在不同類型的傳感器節(jié)點所以有不同的操作,比如溫濕度只需要顯示,RFID需要信息顯示和清空注冊,溫度閾值需要設(shè)置閾值,電機需要開關(guān),所以傳過來的數(shù)據(jù)我對器其ID進行子串匹配,存在TH(threshold)代表設(shè)備關(guān)于閾值 (溫度TH),然后給他創(chuàng)建一共自己封裝好的類,然后在根據(jù)id把這個對象放入特定容器布局,同理設(shè)備名字存在RFID,value值等于true或者false的都格外判斷為啟動電機,LED等設(shè)備的標志,這些規(guī)則都需要單片機去遵循。
特殊功能:服務器那邊以STM32看門狗的方式判斷是否斷開連接,我們客戶端也同樣需要。首先一共有四種容器,分別轉(zhuǎn)四個自定義類,每一個設(shè)備類都有兩個槽函數(shù),一個為接收主窗口的信號,一個為數(shù)據(jù)比對函數(shù),每次收到數(shù)據(jù)包都需要根據(jù)上面的分類將其id,name,value與早就存在特定的容器的相同對象進行比對(封裝了一個槽函數(shù)),如果比對成功該類的標志位置一,全部比對完成后,發(fā)送一個所以對象都綁定了的信號,所有對象判斷自身標志位,如果為一則將置0,如果為0,則代表沒有及時喂狗,代表新的數(shù)據(jù)包沒有上次存儲的數(shù)據(jù),代表該結(jié)點以不存在,需要delete清除該自己,同時發(fā)送信號到主窗口,刪除容器中的數(shù)據(jù),這樣可以一直保持最新的數(shù)據(jù)顯示。(這個思路真的復雜,想了好久)
挺亂這算法哈哈
void Widget::Data_Analysis(QStringList Data)
{
bool asd;
if(Data.at(1).contains("TH"))
{
asd=true;
for (int i=0;i<mlist_setth.size();i++) {
if(mlist_setth[i]->Data_compare(Data)){
asd=false;
return ;
}
}
if(asd)
{
setWidget_layout(Data,1);
}
}
else if(Data.at(2)=="true" || Data.at(2)=="false"){
asd=true;
for (int i=0;i<mlist_isbtn.size();i++) {
if(mlist_isbtn[i]->Data_compare(Data))
{
asd=false;
return ;
}
}
if(asd)
{
setWidget_layout(Data,0);
}
}
else if(Data.at(1).contains("RFID")){
asd=true;
for (int i=0;i<mlist_rfid.size();i++) {
if(mlist_rfid[i]->Data_compare(Data))
{
asd=false;
return ;
}
}
if(asd)
{
setWidget_layout(Data,3);
}
}
else
{
asd=true;
for (int i=0;i<mlist_showdata.size();i++) {
if(mlist_showdata[i]->Data_compare(Data))
{
asd=false;
return ;
}
}
if(asd)
{
setWidget_layout(Data,2);
}
}
}
void Widget::setWidget_layout(QStringList Data,int x)
{
int id=Data.at(0).toInt();
if(id>=1000 && id<2000)
{
if(x==0)
{
MyIsbtn* isbtn=new MyIsbtn(this,Data,socket);
connect(this,&Widget::Delete_SING,isbtn,&MyIsbtn::Delete_MyIsbtn);
connect(isbtn,&MyIsbtn::sendindex,this,&Widget::Delete_index);
layout8266->insertWidget(0,isbtn);
mlist_isbtn.append(isbtn);
}else if(x==1){
MySetTH* setTh=new MySetTH(this,Data,socket);
connect(this,&Widget::Delete_SING,setTh,&MySetTH::Delete_MySetTH);
connect(setTh,&MySetTH::sendindex,this,&Widget::Delete_index);
layout8266->insertWidget(0,setTh);
mlist_setth.append(setTh);
}
else if(x==2){
MyShowData* showdata=new MyShowData(this,Data);
connect(this,&Widget::Delete_SING,showdata,&MyShowData::Delete_MyShowData);
connect(showdata,&MyShowData::sendindex,this,&Widget::Delete_index);
layout8266->insertWidget(0,showdata);
mlist_showdata.append(showdata);
}
else if(x==3){
MyRFID* rfid=new MyRFID(this,Data,socket);
connect(this,&Widget::Delete_SING,rfid,&MyRFID::Delete_MyRFID);
connect(rfid,&MyRFID::sendindex,this,&Widget::Delete_index);
layout8266->insertWidget(0,rfid);
mlist_rfid.append(rfid);
}
}
else if(id>=2000 && id<3000)
{
if(x==0)
{
MyIsbtn* isbtn=new MyIsbtn(this,Data,socket);
connect(this,&Widget::Delete_SING,isbtn,&MyIsbtn::Delete_MyIsbtn);
connect(isbtn,&MyIsbtn::sendindex,this,&Widget::Delete_index);
layouthc06->insertWidget(0,isbtn);
mlist_isbtn.append(isbtn);
}else if(x==1){
MySetTH* setTh=new MySetTH(this,Data,socket);
connect(this,&Widget::Delete_SING,setTh,&MySetTH::Delete_MySetTH);
connect(setTh,&MySetTH::sendindex,this,&Widget::Delete_index);
layouthc06->insertWidget(0,setTh);
mlist_setth.append(setTh);
}
else if(x==2){
MyShowData* showdata=new MyShowData(this,Data);
connect(this,&Widget::Delete_SING,showdata,&MyShowData::Delete_MyShowData);
connect(showdata,&MyShowData::sendindex,this,&Widget::Delete_index);
layouthc06->insertWidget(0,showdata);
mlist_showdata.append(showdata);
}
else if(x==3){
MyRFID* rfid=new MyRFID(this,Data,socket);
connect(this,&Widget::Delete_SING,rfid,&MyRFID::Delete_MyRFID);
connect(rfid,&MyRFID::sendindex,this,&Widget::Delete_index);
layouthc06->insertWidget(0,rfid);
mlist_rfid.append(rfid);
}
}
else if(id>=3000 && id<4000)
{
if(x==0)
{
MyIsbtn* isbtn=new MyIsbtn(this,Data,socket);
connect(this,&Widget::Delete_SING,isbtn,&MyIsbtn::Delete_MyIsbtn);
connect(isbtn,&MyIsbtn::sendindex,this,&Widget::Delete_index);
layoutzibge->insertWidget(0,isbtn);
mlist_isbtn.append(isbtn);
}else if(x==1){
MySetTH* setTh=new MySetTH(this,Data,socket);
connect(this,&Widget::Delete_SING,setTh,&MySetTH::Delete_MySetTH);
connect(setTh,&MySetTH::sendindex,this,&Widget::Delete_index);
layoutzibge->insertWidget(0,setTh);
mlist_setth.append(setTh);
}
else if(x==2){
MyShowData* showdata=new MyShowData(this,Data);
connect(this,&Widget::Delete_SING,showdata,&MyShowData::Delete_MyShowData);
connect(showdata,&MyShowData::sendindex,this,&Widget::Delete_index);
layoutzibge->insertWidget(0,showdata);
mlist_showdata.append(showdata);
}
else if(x==3){
MyRFID* rfid=new MyRFID(this,Data,socket);
connect(this,&Widget::Delete_SING,rfid,&MyRFID::Delete_MyRFID);
connect(rfid,&MyRFID::sendindex,this,&Widget::Delete_index);
layoutzibge->insertWidget(0,rfid);
mlist_rfid.append(rfid);
}
}
}
void Widget::Delete_index(int i,QString name)
{
//ui->textEdit->append("銷毀"+QString ::number(i)+":"+name);
if(i==0) //isbtn
{
for (int i=0;i<mlist_isbtn.size();i++) {
if(mlist_isbtn.at(i)->Isname(name))
{
mlist_isbtn.removeAt(i);
return ;
}
}
}
else if(i==1){
for (int i=0;i<mlist_setth.size();i++) {
if(mlist_setth.at(i)->Isname(name))
{
mlist_setth.removeAt(i);
return ;
}
}
}
else if(i==2){
for (int i=0;i<mlist_showdata.size();i++) {
if(mlist_showdata.at(i)->Isname(name))
{
mlist_showdata.removeAt(i);
return ;
}
}
}
else if(i==3){
for (int i=0;i<mlist_rfid.size();i++) {
if(mlist_rfid.at(i)->Isname(name))
{
mlist_rfid.removeAt(i);
return ;
}
}
}
}
Qt? APP:
本項目是服務于智慧農(nóng)場,所以開發(fā)一個app 只顯示一個農(nóng)場的特定的數(shù)據(jù)和特定的操作,使其一些操作和數(shù)據(jù)固化。
JS,WEB,HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>智慧農(nóng)場</title>
<style>
/* 樣式表 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
#header {
background-color: #333;
color: white;
text-align: center;
padding: 10px;
}
#content {
margin: 20px;
padding: 20px;
border: 1px solid #ccc;
}
#footer {
background-color: #333;
color: white;
text-align: center;
padding: 10px;
}
#DJ {
margin-left: 600px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#DJ2 {
margin-left: 600px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#SB {
margin-left: 600px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#deng {
margin-left: 600px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#WD {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#WDHT {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#SD {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#TRSD {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#TRSDTH {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#GM {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
#GMTH {
margin-left: 100px; /* 你可以根據(jù)需要調(diào)整右移的距離 */
}
</style>
</head>
<body>
<div id="header">
<h1>智慧農(nóng)場</h1>
</div>
<div id="content">
<h2 id="WD">溫------------度:
<label id="wendu">等待數(shù)據(jù)傳輸</label>
<label id="DJ">電機:</label>
<label id="dianji">等待數(shù)據(jù)傳輸</label>
</h2>
<h2 id="WDHT">溫--度--閾--值:
<label id="wendu_TH">等待數(shù)據(jù)傳輸</label>
</h2>
<br>
<h2 id="SD">濕------------度:
<label id="shidu">等待數(shù)據(jù)傳輸</label>
<label id="DJ2">舵機:</label>
<label id="duoji">等待數(shù)據(jù)傳輸</label>
</h2>
<br>
<h2 id="TRSD">土--壤--濕--度:
<label id="shidu_2">等待數(shù)據(jù)傳輸</label>
</h2>
<h2 id="TRSDTH">土壤濕度閾值:
<label id="shidu_2_TH">等待數(shù)據(jù)傳輸</label>
<label id="SB">水泵:</label>
<label id="shuibeng">等待數(shù)據(jù)傳輸</label>
</h2>
<br>
<h2 id="GM">光------------敏:
<label id="light">等待數(shù)據(jù)傳輸</label>
</h2>
<h2 id="GMTH">光--敏--閾--值:
<label id="light_TH">等待數(shù)據(jù)傳輸</label>
<label id="deng">LED:</label>
<label id="LED">等待數(shù)據(jù)傳輸</label>
</h2>
<br>
</div>
<div style="margin-top: 160px;"></div>
<div id="footer">
版權(quán)所有 © 2023 YZM系統(tǒng)公司
</div>
</body>
<script>
setInterval(function () {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var ctext = xhr.responseText;
var obj = JSON.parse(ctext);
for(key in obj)
{
if(key == "溫度")
{
document.getElementById('wendu').innerHTML = obj[key];
}
else if(key == "土壤濕度")
{
document.getElementById('shidu_2').innerHTML = obj[key];
}
else if(key == "土壤濕度TH")
{
document.getElementById('shidu_2_TH').innerHTML = obj[key];
}
else if(key == "光敏")
{
document.getElementById('light').innerHTML = obj[key];
}
else if(key == "光敏TH")
{
document.getElementById('light_TH').innerHTML = obj[key];
}
else if(key == "濕度")
{
document.getElementById('shidu').innerHTML = obj[key];
}
else if(key == "溫度TH")
{
document.getElementById('wendu_TH').innerHTML = obj[key];
}
else if(key == "電機")
{
document.getElementById('dianji').innerHTML = obj[key];
}
else if(key == "舵機")
{
document.getElementById('duoji').innerHTML = obj[key];
}
else if(key == "水泵")
{
document.getElementById('shuibeng').innerHTML = obj[key];
}
else if(key == "LED3")
{
document.getElementById('LED').innerHTML = obj[key];
}
}
} else {
console.error("請求失?。? + xhr.status);
}
}
};
var url = "http://110.42.228.65:65000"
xhr.open("GET", url, true);
xhr.send();
}, 2000);
</script>
</html>
本人hello word的水平,不多說? (面向人工智障編程,cv工程師)文章來源:http://www.zghlxwxcb.cn/news/detail-850745.html
總結(jié)
本項目是我寫過最麻煩的項目,調(diào)試花了一周,總時長25天,各種BUG,每個設(shè)備之間的數(shù)據(jù)傳遞問題巨多,先局部后則整體,代碼看了百遍不止,調(diào)麻了,但也只有調(diào)試BUG才能成長和磨練自己,學習階段還是好好自己學扎實。文章來源地址http://www.zghlxwxcb.cn/news/detail-850745.html
到了這里,關(guān)于基于I.MX6ULL的Linux C多線程物聯(lián)網(wǎng)網(wǎng)關(guān)+STM32+Qt上位機+Linux C++多線程服務器(含web)的多種無線通信系統(tǒng)的智慧農(nóng)場的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!