根据上一节内容的代码框架开始编写代码:
首先编写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,波特率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;
}
}
- 最后进行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请求。在之后的文章里面会有如何使用智能云平台。