根据上一节内容的代码框架开始编写代码:
首先编写controlDevices.h
这个头文件里面的代码,这个是设备工厂每一个结点的结构体类型,而且还要在这个头文件里面进行函数的声明,也就是创建的那些设备.c
文件里面的函数(为了将设备添加至设备链表的函数),其中这个头文件里面的结构体内容根据功能提前设定。同样然后再编写inputCommand.h
这个头文件里面的内容,这个是指令工厂里面的头文件,也是指令链表里面的每一个结点的类型。编写完这两个头文件,然后再进行设备工厂设备文件、指令工厂指令文件和main.c文件的编写。
controlDevices.h
是指令工厂头文件代码,结点结构体的声明,这里面的东西不一定够用,可以先写上,等不够的时候在进行添加。#include //包含wiringPi库
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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,波特率115200
printf("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;
}
}
#include
#include
#include
#include
#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)树莓派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请求。在之后的文章里面会有如何使用智能云平台。