根據上一節內容的代碼框架開始編寫代碼:
首先編寫controlDevices.h
這個頭文件里面的代碼,這個是設備工廠每一個結點的結構體類型,而且還要在這個頭文件里面進行函數的聲明,也就是創建的那些設備.c
文件里面的函數(為了將設備添加至設備鏈表的函數),其中這個頭文件里面的結構體內容根據功能提前設定。同樣然后再編寫inputCommand.h
這個頭文件里面的內容,這個是指令工廠里面的頭文件,也是指令鏈表里面的每一個結點的類型。編寫完這兩個頭文件,然后再進行設備工廠設備文件、指令工廠指令文件和main.c文件的編寫。
controlDevices.h
是指令工廠頭文件代碼,結點結構體的聲明,這里面的東西不一定夠用,可以先寫上,等不夠的時候在進行添加。
#include<wiringPi.h> //包含wiringPi庫
#include<string.h>
struct Devices
{int status; //表示開關的狀態int pinNum;char devicesName[128]; //存放設備的名稱int (*open)(int pinNum);int (*close)(int pinNum);int (*deviceInit)(int pinNum);int (*readStatus)(int pinNum);int (*changStatus)(int status);struct Devices*next;
};
//以下幾行將設備添加至設備鏈表的函數聲明,便于以后的查找引用
struct Devices* addBathroomLightToDeviceLink(struct Devices* phead);
struct Devices* addSecondFlootLightToDeviceLink(struct Devices* phead);
struct Devices* addRestaurantLightToDeviceLink(struct Devices* phead);
struct Devices* addLivingRoomLightToDeviceLink(struct Devices* phead);
struct Devices* addFireContrlToDeviceLink(struct Devices* phead);
inputCommand.h
是設備工廠頭文件代碼,里面有設備鏈表每一個結點的結構體類型的聲明,和指令工廠頭文件類似。
#include<wiringPi.h>
#include<string.h>
#include<wiringSerial.h>
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
struct InputCommander
{int fd;int socketfd;char port[12]; //端口號char ipAdress[32]; //ip地址char command[32]; //存放指令信息char log[1024]; //存放日志信息char devicesname[128]; //存放串口設備名字char commandName[128];int (*getCommand)(struct InputCommander* voicer); //接收指令函數int (*Init)(struct InputCommander* voicer,char* ipAdress,char* port);struct InputCommander* next;
};
//將指令結點添加至指令鏈表中的函數聲明
struct InputCommander* addVoiceToDeviceLink(struct InputCommander* phead);
struct InputCommander* addSocketToDeviceLink(struct InputCommander* phead);
- 首先編寫設備工廠的設備文件
bathroomLight.c
#include"contrlDevices.h" //包含頭文件
int bathroomLightOpen(int pinNum)
{digitalWrite(pinNum,LOW);//將引腳電平拉低,點亮浴室燈
}
int bathroomLightClose(int pinNum)
{digitalWrite(pinNum,HIGH);//將引腳電平拉高,熄滅浴室燈
}
int bathroomLightInit(int pinNum)
{pinMode(pinNum,OUTPUT);digitalWrite(pinNum,HIGH);//初始化引腳功能
}
struct Devices bathroomLight={.pinNum=22, //浴室燈繼電器控制IO口引腳.devicesName="bathroomLight", //通過這個設備名進行浴室燈結點的查找,然后再進行結構體函數的調用.deviceInit=bathroomLightInit,.open=bathroomLightOpen,.close=bathroomLightClose,
};
struct Devices* addBathroomLightToDeviceLink(struct Devices* phead) //將浴室燈結點插入到設備工廠鏈表里面,采用頭插法
{if (phead==NULL){return &bathroomLight;}else{bathroomLight.next=phead;phead=&bathroomLight;}
}
fire.c
文件代碼,代碼框架和浴室燈代碼框架相同,不同的是這個文件里面要有讀取引腳狀態的函數,同時引腳也要設置為輸入模式,當檢測到或火災是火災傳感器的引腳會被拉為低電平。
#include"contrlDevices.h"
int fireContrlInit(int pinNum)
{pinMode(pinNum,INPUT);digitalWrite(pinNum,HIGH);
}
int fireReadstatus(int pinNum)
{int ret;ret=digitalRead(pinNum);return ret;
}
struct Devices fireContrl={.pinNum=25, //火災報警器輸入IO口.devicesName="fire",.deviceInit=fireContrlInit,.readStatus=fireReadstatus
};
struct Devices* addFireContrlToDeviceLink(struct Devices* phead)
{if (phead==NULL){return &fireContrl;}else{fireContrl.next=phead;phead=&fireContrl;}
}
- 由于其他幾個燈光控制文件里面的代碼和第一個浴室燈控制代碼大同小異,所以這里不再贅述,下面是語音指令輸入文件代碼,這個文件里面的函數就要添加讀取指令函數和初始化函數,所謂的初始化函數就是將串口打開然后設置相應的波特率,讀取指令函數需要注意的是在讀取指令前需要將緩存區初始化防止有亂碼,讀指令函數主要調用
read
函數進行指令的讀取,在沒有指令到來的時候,輸出:usart for voice read over time
,其實代碼框架和設備工廠的框架基本類似,只是文件里面包含的函數有所差異,但都有一個設備結點插入函數。
#include "inputCommand.h"
int voiceInit(struct InputCommander* voicer)//就是對串口的初始化
{int fd;if((fd=serialOpen(voicer->devicesname,115200))==-1){ //open serial,波特率115200printf("usrat open fail\n");exit(-1);}voicer->fd=fd;return fd;
}
int voiceGetCommand(struct InputCommander* voicer)
{int nread=0;memset(voicer->command,'\0',sizeof(voicer->command));nread=read(voicer->fd,voicer->command,sizeof(voicer->command));if(nread==0){printf("usart for voice read over time\n");}else{return nread;}
}
struct InputCommander voiceContrl={.commandName="voice",.command={'\0'},.devicesname="/dev/ttyAMA0",.next=NULL,.getCommand=voiceGetCommand,.Init=voiceInit,.log={'\0'}
};
struct InputCommander* addVoiceToDeviceLink(struct InputCommander* phead)
{if(phead==NULL){return &voiceContrl;}else{voiceContrl.next=phead;phead=&voiceContrl;}
}
- 下面是
socket
指令文件代碼,這個里面不需要getCommmand
這個函數因為在這里寫了,對后面多線程的處理不是特別的方便,計劃的是連接進來一個客戶端然后起一個線程去對接,但是客戶端發送完一條消息后,需要斷開連接然后重新連接,因為代碼里面采用的是點對點的方式。《socket知識補充》
#include "inputCommand.h"
int socketInit(struct InputCommander* socketMes)//就是對socket的初始化
{int socketfd;int bindre;int listenre;int len=sizeof(struct sockaddr_in);struct sockaddr_in IP;memset(&IP,'\0',len);IP.sin_family=AF_INET; //協議IP.sin_port=htons(atoi(socketMes->port));IP.sin_addr.s_addr=inet_addr(socketMes->ipAdress);socketfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//建立套接字if(socketfd==-1){printf("socket create fail\n");perror("socket");exit(-1);}else{printf("socket create successful\n");}bindre=bind(socketfd,(struct sockaddr*)&IP,len); //綁定服務器IP地址和端口號listenre=listen(socketfd,10); //監聽printf("socket server listening.........\n");socketMes->socketfd=socketfd;return socketfd;
}
struct InputCommander socketContrl={.commandName="socketServer",.command={'\0'},.next=NULL,.Init=socketInit, //socket初始化函數,建立套接字,然后綁定、監聽、等待客戶端的連接.log={'\0'},.port="8088", //服務器端口號.ipAdress="192.168.43.136" //服務端IP地址
};
struct InputCommander* addSocketToDeviceLink(struct InputCommander* phead)
{if(phead==NULL){return &socketContrl;}else{socketContrl.next=phead;phead=&socketContrl;}
}
- 最后進行main函數代碼的編寫,main函數里面涉及到設備工廠、指令工廠頭結點的插入和設備文件、指令文件分別插入到設備鏈表和指令鏈表。同時還要有結點查找函數:包括設備結點查找函數、指令結點查找函數,查找后返回結點指針然后對特定結點進行操作即可。同時main函數里面還涉及到線程的創建,socket_thread這個函數里面在有客戶端接入的時候又進行了線程的創建,用來對接接入的客戶端。《線程知識補充》
#include<stdio.h>
#include<string.h>
#include <unistd.h>
#include <pthread.h>
#include "inputCommand.h"
#include"contrlDevices.h"
int newfd;
struct Devices *pdeviceHead=NULL;//將設備鏈表的頭結點設置為全局變量
struct InputCommander* socketHandler=NULL; //這個是查找到的的socket指令結點,將它設為全局變量是因為socket_thread這個函數有用到這個節點//除此之外,socket_thread的子線程read_thread也有用到這個節點指針,雖然可以通過創建線程傳參,但是不建議那么做。
struct InputCommander* pcommandHead=NULL;//將指令鏈表的頭結點設置為全局變量
struct Devices *findDevicesByName(char*name,struct Devices*phead)//查找設備結點函數
{struct Devices *tmp=phead;if(phead==NULL){return NULL;}else{while(tmp!=NULL){if(strcmp(tmp->devicesName,name)==0)return tmp;tmp=tmp->next;}return NULL;}
}
struct InputCommander *findCommandByName(char*name,struct InputCommander*phead)//查找指令結點函數
{struct InputCommander *tmp=phead;if(phead==NULL){return NULL;}else{while(tmp!=NULL){if(strcmp(tmp->commandName,name)==0)return tmp;tmp=tmp->next;}return NULL;}
}
void* read_thread(void *data)//當有新的客戶端接入的時候,創建線程去對接,這個函數就是線程對接函數,用于讀取客戶端指令
{int n_read;memset(socketHandler->command,'\0',sizeof(socketHandler->command));n_read=read(newfd,socketHandler->command,sizeof(socketHandler->command));if(n_read==-1)perror("read");else if(n_read>0){printf("\n get:%d,%s\n",n_read,socketHandler->command);}else{printf("client quit\n");}
}
void* voice_thread(void*data)//語音線程函數,用于等待語音指令,在這里設置為一個包含有while(1)的線程
{int nread;struct InputCommander* voiceHandler;voiceHandler=findCommandByName("voice",pcommandHead);if(voiceHandler==NULL){printf("find voiceHandler error!\n");pthread_exit(NULL); //查找指令工廠語音部分失敗退出當前線程}else{printf("%s find successful\n",voiceHandler->commandName);if(voiceHandler->Init(voiceHandler)<0){printf("voice init error\n");pthread_exit(NULL); //初始化失敗退出當前線程}else{printf("%s init successful!\n",voiceHandler->commandName);}while(1){nread=voiceHandler->getCommand(voiceHandler);if(nread==0){printf("nodata from voice\n");}else{printf("do divece contrl:%s\n",voiceHandler->command);}}}
}
void* socket_thread(void*data)//socket線程,用于與客戶端對接,這個是main函數里面創建線程函數
{pthread_t readthread; int len=sizeof(struct sockaddr_in);struct sockaddr_in CLI;//客戶端信息memset(&CLI,'\0',len);socketHandler=findCommandByName("socketServer",pcommandHead);if(socketHandler==NULL){printf("find socketHandler error!\n");pthread_exit(NULL); //查找指令工廠socket部分失敗退出當前線程}else{printf("%s find successful!\n",socketHandler->commandName);}socketHandler->Init(socketHandler);while(1){newfd=accept(socketHandler->socketfd,(struct sockaddr*)&CLI,&len);pthread_create(&readthread,NULL,read_thread,NULL);}
}
int main()
{if(wiringPiSetup()==-1){return -1;}//初始化樹莓派硬件,這個只需要執行一次所以放在main函數里面即可pthread_t voicetd;pthread_t sockettd;//指令工廠初始化pcommandHead=addVoiceToDeviceLink(pcommandHead); //插入語音指令結點pcommandHead=addSocketToDeviceLink(pcommandHead); //插入socket指令結點//設備控制工廠初始化pdeviceHead=addBathroomLightToDeviceLink(pdeviceHead); //插入浴室燈pdeviceHead=addRestaurantLightToDeviceLink(pdeviceHead); //插入餐廳燈pdeviceHead=addSecondFlootLightToDeviceLink(pdeviceHead); //插入二樓浴室燈pdeviceHead=addLivingRoomLightToDeviceLink(pdeviceHead); //插入客廳燈pdeviceHead=addFireContrlToDeviceLink(pdeviceHead); //插入火災報警器控制//線程池建立、語音線程、socket線程pthread_create(&voicetd,NULL,voice_thread,NULL);pthread_create(&sockettd,NULL,socket_thread,NULL);pthread_join(voicetd,NULL);pthread_join(sockettd,NULL);//等待指定線程退出return 0;
}
代碼編寫完成后通過ftp工具傳輸到樹莓派編譯(我這里用的是FileIlla)
- 使用指令:
gcc *.c -o test -lpthread -lwiringPi
進行編譯,然后執行可以看到下圖:(在語音沒有指令的時候打印nodata from voice)
- 然后進行串口接收信息功能的測試,使用串口前按照這篇博文進行串口的設置,我這里使用的是USB轉ttl連接電腦進行測試測試結果如下:(測試成功)
- 然后進行socket客戶端連接的測試,在電腦端使用網絡調試助手,輸入IP地址和端口號進行連接。下圖是發送和接受的結果顯示。
樹莓派mjpg-streamer監控功能調試:
使用監控功能使用樹莓派現成的庫mjpg-streamer,樹莓派利用pi Camera模塊,通過mjpg-streamer軟件獲取視頻,通過手機端或電腦端瀏覽實時視頻。mjpg-streamer是一個開源的攝像頭媒體流,通過本地獲取攝像頭的數據,再通過http通訊發出來,然后再通過瀏覽器訪問樹莓派的ip地址和對應的端口號就能看到對應的視頻流。mjpg-streamer是一個比較好的軟件框架,他用的是插件的思想,它將相應的功能編譯成相應的.so
庫,然后通過代碼里面的delsy將.so
庫里面的API拿來用。
- 下載前先下載一下幾個工具和庫:
sudo apt-get install libjpeg8-dev
#JPEG支持庫,圖像處理庫
sudo apt-get install imagemagick
sudo apt-get install libv4l-dev
#4l是小寫"L",這個是底層攝像頭驅動的上層的一個應用庫,底層是value for linux,v4表示value 4, l表示linux,這是一個開源的底層視頻設備驅動的一個庫。
sudo apt-get install cmake
#下載編譯工具 git clone https://github.com/jacksonliam/mjpg-streamer.git
下載mjpg-streamer庫,- 下載好在這個庫之后
cd mjpg-streamer/mjpg-streamer-experimental //進入下載目錄后進入左側路徑
,然后使用指令:make all #進行編譯
,出現下圖錯誤,表示樹莓派里面沒有cmake編譯工具,make指令會調用cmake的東西,sudo apt-get install cmake
進行安裝即可。
出現下圖表示編譯成功:
- 然后使用指令:
sudo make install #進行安裝
,結果如圖:
- 然后打開啟動腳本
start.sh
,這里面有啟動腳本,如下圖所示:input_uvc
是使用uvc攝像頭,也就是usb口的攝像頭,output_http
表示使用http輸出。而實際上樹莓派的應該使用input_raspicam.so
,所依要進行修改。將input_uvc.so改為 input_raspicam.so即可 。 - 然后使用指令:
sudo raspi-config
打開設置再將攝像頭打開即可。
- 最后使用指令:
./start.sh
執行腳本即可,然后通過瀏覽器輸入 http://IP地址:8080,回車 顯示如下頁面,點擊頁面左側,Stream欄,顯示監視畫面。
智能家居人臉識別方案:
-
對于人臉識別這個功能的實現我采用人工智能開放平臺——祥云平臺,只要掌握了這一個平臺后臺API的開發,同樣就可以使用其他平臺的方案去開發車牌識別、人臉識別、圖片識別等等功能。下面是翔云平臺的產品:
-
先試用一下人臉識別功能,注冊登錄后開始使用人臉識別功能,比對結果有JSON數據( JSON 是一種輕量級的傳輸數據格式 , 用于數據交互 ,json是一種與語言無關的數據交換的格式.),這種在網頁上點擊進行的識別是進行的BS(browser serve,就是瀏覽器服務)的識別,每一次網頁訪問都是BS模式,這種通用的協議是http的協議,人臉識別就是讓代碼完成剛才點擊的一系列操作,就是讓代碼發起http請求,不一定要掉瀏覽器發起http請求,因為瀏覽器的后臺也是通過http的請求來獲取數據。既然要使用代碼發起http請求就要了解linux如何使用C語言發起http請求。在之后的文章里面會有如何使用智能云平臺。