基于树莓派4B的智能家居系统设计-1
https://developer.aliyun.com/article/1508008
五、智能家居设计框架
该智能家居由两个工厂和若干个设备组成,分别是产品工厂和指令工厂。包含功能如下:
1.产品工厂
该工厂主要是用来生产一些供人们使用的设备,如各种灯设备、OLED屏幕显示设备、环境数据检测设备、检测安全设备等等。
创建产品工厂的类,即产品工厂的生产线:
- 设备名称
- 设备状态
- 设备引脚
- 设备数据存储区
- 设备功能
- 打开
- 关闭
- 设备初始化
- 读状态位
- 改变状态位
- 读数据
- 链表链接节点,使对应的设备上线成为商品
controlDevices.h
struct Devices { char devicesName[128]; // 设备名称 int status; // 设备状态 int pinNum; // 设备引脚 unsigned long databuf; // 设备数据存储区 /* 设备功能 */ int (*open)(int pinNum); int (*close)(int pinNum); int (*devicesInit)(int pinNum); int (*readStatus)(int pinNum); int (*changeStatus)(int status); int (*readData)(struct Devices *devices,int pinNum); struct Devices *next; // 链表链接节点 };
具体产品设备有:
- 一楼灯设备
- 二楼灯设备
- 餐厅灯设备
- 卧室灯设备
- OLED屏幕显示设备
- 风扇设备
- 锁设备
- 警报器设备
- 地震监测设备
- 火灾监测设备
- 烟雾监测设备
- 温湿度监测设备
四盏灯设备代码示例:代码基本相同,只列举一个
#include "controlDevices.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 = { .devicesName = "bathroom", .pinNum = 21, .open = bathroomLightOpen, .close = bathroomLightClose, .devicesInit = bathroomLightInit, .next = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct Devices* addBathroomLightToDevicesLink(struct Devices *phead) { if (phead == NULL) { phead = &bathroomLight; return phead; } else { bathroomLight.next = phead; phead = &bathroomLight; return phead; } }
风扇设备代码示例:
#include "controlDevices.h" int fanOpen(int pinNum) { digitalWrite(27,LOW); digitalWrite(28,HIGH); } int fanClose(int pinNum) { digitalWrite(27,LOW); digitalWrite(28,LOW); } int fanInit(int pinNum) { pinMode(27,OUTPUT); pinMode(28,OUTPUT); digitalWrite(27,LOW); digitalWrite(28,LOW); } struct Devices fan = { .devicesName = "fan", .pinNum = 27, .open = fanOpen, .close = fanClose, .devicesInit = fanInit, .next = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct Devices* addfanToDevicesLink(struct Devices *phead) { if (phead == NULL) { phead = &fan; return phead; } else { fan.next = phead; phead = &fan; return phead; } }
火灾、地震、烟雾监测设备代码示例:代码基本相同,只列举一个
#include "controlDevices.h" int fireInit(int pinNum) { pinMode(pinNum,INPUT); digitalWrite(pinNum,HIGH); } /* 检测火灾发生情况 */ int fireReadStatus(int pinNum) { return digitalRead(pinNum); } struct Devices fire = { .devicesName = "fire", .pinNum = 0, .devicesInit = fireInit, .readStatus = fireReadStatus, .next = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct Devices* addfireToDevicesLink(struct Devices *phead) { if (phead == NULL) { phead = &fire; return phead; } else { fire.next = phead; phead = &fire; return phead; } }
温湿度监测代码示例:
#include "controlDevices.h" int sensorInit(int pinNum) { pinMode(pinNum,OUTPUT); digitalWrite(pinNum,HIGH); } /* 获取温度传感器的数据 */ int sensorReadData(struct Devices *sensorMsg,int pinNum) { char crc; char i; unsigned long databuf; // 温湿度存储地址 pinMode(pinNum, OUTPUT); digitalWrite(pinNum, 0); delay(25); digitalWrite(pinNum, 1); pinMode(pinNum, INPUT); pullUpDnControl(pinNum, PUD_UP); // 启用上拉电阻,引脚电平拉倒3.3v delayMicroseconds(27); if (digitalRead(pinNum) == 0) { while (!digitalRead(pinNum)); for (i = 0; i < 32; i++) { while (digitalRead(pinNum)); while (!digitalRead(pinNum)); delayMicroseconds(32); databuf *= 2; if (digitalRead(pinNum) == 1) { databuf++; } } for (i = 0; i < 8; i++) { while (digitalRead(pinNum)); while (!digitalRead(pinNum)); delayMicroseconds(32); crc *= 2; if (digitalRead(pinNum) == 1) { crc++; } } sensorMsg->databuf = databuf; return 1; } else { return 0; } } /* 继承产品工厂的类,生产温度检测设备 */ struct Devices sensor = { .devicesName = "sensor", .pinNum = 7, .devicesInit = sensorInit, .readData = sensorReadData, .next = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct Devices* addsensorToDevicesLink(struct Devices *phead) { if(phead == NULL){ phead = &sensor; return phead; }else{ sensor.next = phead; phead = &sensor; return phead; } }
2.指令工厂
该工厂主要是用来生产一些供人们发出指令控制的设备,如语音设备、蓝牙设备、server设备、人脸识别设备等等,这些设备可以控制产品工厂的设备做出一些控制行为。
创建指令工厂的类,即指令工厂的生产线:
- 指令内容
- 指令名称
- 日志
- 语言模块或蓝牙模块
- 文件的fd
- 设备名称
- 网络通信
- server的fd
- 端口号
- IP地址
- 人脸识别
- 指向主人的人脸图片
- 指向临时的人脸图片
- 人脸识别匹配度
- 设备功能
- 指令设备初始化
- 获取指令设备
- 处理函数
- 链表链接节点,使对应的设备上线成为商品
nputCmd.h
struct InputCmd { int fd; //文件的fd char deviceName[128]; //设备名称 char cmd[64]; //指令内容 char cmdName[128]; //指令名称 char log[1024]; //日志 int sfd; //server的fd char port[12]; //端口号 char ipAress[32]; //IP地址 char *img_buf; //指向主人的人脸图片数据存储区的指针 char *camera_buf; //指向临时的人脸图片数据存储区的指针 float face_data; //人脸识别匹配度 /* 设备功能 */ int (*Init)(struct InputCmd *voicer,char *ipAdress,char *port); int (*getCmd)(struct InputCmd *voicer); int (*ReadHandle)(void *ptr, size_t size, size_t nmemb, void *stream); struct InputCmd *next; //链表链接节点 };
具体指令设备有:
- 语音设备
- 蓝牙设备
- server设备
- 人脸识别设备
树莓派串口通信详细介绍可看我之前写过的文章,链接如下:树莓派串口通信
语音设备、蓝牙设备代码示例:代码基本相同,只列举一个。
#include "inputCmd.h" /* 语音控制设备初始化,启用串口 */ int voiceInit(struct InputCmd *voicer,char *ipAdress,char *port) { int fd; if((fd = serialOpen(voicer->deviceName,9600))==-1){ //打开串口 exit(-1); } voicer->fd = fd; return fd; } /* 读取来自语音控制设备的命令 */ int voiceGetCmd(struct InputCmd *voicer) { char tmp[128]; int nread; memset(tmp,'\0',sizeof(tmp)); nread = read(voicer->fd,tmp,sizeof(tmp)); // 一切皆文件,串口也是文件,读串口数据 if(nread == 0){ printf("usart for voice read overtime\n"); }else if(nread > 0){ memset(voicer->cmd,'\0',sizeof(voicer->cmd)); /* 对串口数据进行优化 */ if(strstr(tmp,"open ba") != NULL){ strcat(voicer->cmd,"open bathroom light"); } if(strstr(tmp,"close ba") != NULL){ strcat(voicer->cmd,"close bathroom light"); } if(strstr(tmp,"open li") != NULL){ strcat(voicer->cmd,"open livingroom light"); } if(strstr(tmp,"close li") != NULL){ strcat(voicer->cmd,"close livingroom light"); } if(strstr(tmp,"open re") != NULL){ strcat(voicer->cmd,"open restaurant light"); } if(strstr(tmp,"close re") != NULL){ strcat(voicer->cmd,"close restaurant light"); } if(strstr(tmp,"open se") != NULL){ strcat(voicer->cmd,"open secondfloor light"); } if(strstr(tmp,"close se") != NULL){ strcat(voicer->cmd,"close secondfloor light"); } if(strstr(tmp,"open bu") != NULL){ strcat(voicer->cmd,"open buzzer light"); } if(strstr(tmp,"close bu") != NULL){ strcat(voicer->cmd,"close buzzer light"); } if(strstr(tmp,"open fn") != NULL){ strcat(voicer->cmd,"open fan light"); } if(strstr(tmp,"close fn") != NULL){ strcat(voicer->cmd,"close fan light"); } if(strstr(tmp,"open fac") != NULL){ strcat(voicer->cmd,"open face light"); } if(strstr(tmp,"open lo") != NULL){ strcat(voicer->cmd,"open lock light"); } if(strstr(tmp,"close lo") != NULL){ strcat(voicer->cmd,"close lock light"); } return nread; }else{ printf("read is fali\n"); } } /* 继承指令工厂的类,生产语音控制设备 */ struct InputCmd voiceControl = { .cmdName = "voice", .deviceName = "/dev/ttyAMA0", .cmd = {'\0'}, .Init = voiceInit, .getCmd = voiceGetCmd, .next = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct InputCmd* addvoiceControlToInputCmdLink(struct InputCmd *phead) { if(phead == NULL){ phead = &voiceControl; return phead; }else{ voiceControl.next = phead; phead = &voiceControl; return phead; } }
网络通信详细介绍和编程可看我之前写过的文章,链接如下:
server设备代码示例:
#include "inputCmd.h" /* server控制设备初始化,为套接字绑定端口号和IP地址 */ int serverInit(struct InputCmd *serverMsg,char *ipAdress,char *port) { int s_fd; struct sockaddr_in s_addr; memset(&s_addr,0,sizeof(struct sockaddr_in)); s_fd = socket(AF_INET,SOCK_STREAM,0); if(s_fd == -1){ perror("socket"); exit(-1); } s_addr.sin_family = AF_INET; s_addr.sin_port = htons(atoi(serverMsg->port)); inet_aton(serverMsg->ipAress,&s_addr.sin_addr); bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in)); listen(s_fd,10); serverMsg->sfd = s_fd; return s_fd; } /* 继承指令工厂的类,生产server控制设备 */ struct InputCmd serverControl = { .cmdName = "server", .cmd = {'\0'}, .log = {'\0'}, .Init = serverInit, .port = "8888", .ipAress = "192.168.137.115", // 树莓派ip地址 .next = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct InputCmd* addserverControlToInputCmdLink(struct InputCmd *phead) { if(phead == NULL){ phead = &serverControl; return phead; }else{ serverControl.next = phead; phead = &serverControl; return phead; } }
人脸识别设备代码示例:该设备通过使用 curl 第三方库访问网站,调用祥云人脸识别平台进行人脸识别。
#include "inputCmd.h" char post_info[500]; /* 人脸识别设备初始化 */ int cameraInit(struct InputCmd *camera,char *ipAdress,char *port) { int fd; int imgSize=0; char cmd[30]; memset(post_info,'\0',500); curl_global_init(CURL_GLOBAL_ALL); //curl库初始化 /* 获取主人的人脸图片的base64编码 */ fd = open("imgfile.txt",O_RDWR,0666); //打开用来存放主人的人脸图片的base64编码的文件 imgSize = lseek(fd,0,SEEK_END); camera->img_buf = (char *)malloc(imgSize + 8); memset(camera->img_buf,'\0',imgSize + 8); lseek(fd,0,SEEK_SET); read(fd,camera->img_buf,imgSize); //将主人的人脸图片的base64编码读到img_buf数据存储区 close(fd); system("raspistill -q 5 -o haozi.jpg"); //调用树莓派摄像头拍摄图片,-q 5为降低画质 while(access("./haozi.jpg",F_OK) != 0); //等待拍摄完成 sprintf(cmd,"base64 %s >camerafile.txt","haozi.jpg"); //字符串拼接 if(system(cmd) == 256){ //生成摄像头拍摄图片的base64编码的图像地址 return -1; } /* 获取临时拍摄的人脸图片的base64编码 */ fd = open("camerafile.txt",O_RDWR,0666); //打开用来存放临时拍摄的人脸图片的base64编码的文件 imgSize = lseek(fd,0,SEEK_END); camera->camera_buf = (char *)malloc(imgSize + 8); memset(camera->camera_buf,'\0',imgSize + 8); lseek(fd,0,SEEK_SET); read(fd,camera->camera_buf,imgSize); //将临时拍摄的人脸图片的base64编码读到camera_buf数据存储区 close(fd); system("rm camerafile.txt"); system("rm haozi.jpg"); if((camera->img_buf == NULL)||(camera->camera_buf == NULL)){ printf("img dont exist!\n"); return -1; } return 0; } /* 获取祥云平台的人脸识别结果数据 */ int cameraReadHandle( void *ptr, size_t size, size_t nmemb, void *stream) { int buf_size=size*nmemb; char *buf=malloc(buf_size+1); memset(buf,'\0',buf_size+1); strncpy(buf,ptr,buf_size); //获取祥云平台的人脸识别结果 memset(post_info,'\0',sizeof(post_info)); strcat(post_info,buf); free(buf); return buf_size; } /* 调用祥云平台进行人脸识别 */ int cameraGetCmd(struct InputCmd *camera) { CURL *curl; CURLcode res; char *PostString; /* 祥云平台的个人基本信息 */ char *key="6M9M9rFAa3DuG33Awu9hKq"; char *secret="db56882c3a0b4a3dbeeaed2af79f026c"; int typeId=21; char *format="xml"; char *result_str = NULL; float result_data; int len = strlen(camera->img_buf)+strlen(camera->camera_buf)+strlen(key)+strlen(secret)+sizeof(typeId)+strlen(format) + 24; PostString=malloc(len); sprintf(PostString,"&img1=%s&img2=%s&key=%s&secret=%s&typeId=%d&format=%s",camera->img_buf,camera->camera_buf,key,secret,typeId,format); //设置需要发送给祥云平台的数据格式 curl = curl_easy_init(); //获取句柄 if (curl){ curl_easy_setopt(curl, CURLOPT_URL, "https://netocr.com/api/faceliu.do"); //设置访问的URL,即祥云人脸识别的接口地址 curl_easy_setopt(curl, CURLOPT_POSTFIELDS,PostString); //向祥云平台发送数据,调用人脸识别功能 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, camera->ReadHandle); //设置回调函数,该函数用来接收人脸识别的结果 res = curl_easy_perform(curl);//错误检测,0没有错误,非0出现错误 curl_easy_cleanup(curl); /* 处理人脸识别的结果数据 */ result_str = strstr(post_info,"CDATA"); if(result_str != NULL){ result_str += 6; result_str = strtok(result_str,"]"); result_data = atof(result_str); }else{ printf("get result error!\n"); } }else{ printf("get result error!\n"); } camera->face_data = result_data; return result_data; } /* 继承指令工厂的类,生产人脸识别设备 */ struct InputCmd cameraControl = { .cmdName = "camera", .cmd = {'\0'}, .log = {'\0'}, .Init = cameraInit, .getCmd = cameraGetCmd, .ReadHandle = cameraReadHandle, .next = NULL, .img_buf = NULL, .camera_buf = NULL, }; /* 头插法加入链表,提供购买渠道,以供客户购买 */ struct InputCmd* addcameraControlToInputCmdLink(struct InputCmd *phead) { if(phead == NULL){ phead = &cameraControl; return phead; }else{ cameraControl.next = phead; phead = &cameraControl; return phead; } }
完整代码我后续再上传到资源
编译时要链接 wiringPi 库跟线程库:gcc *.c *.h -lwiringPi -lpthread
编译调试图示:
六、项目总结
总体来说,只要掌握好C语言、裸机外设和Linux,整个项目做下来其实难度并不大,从这个项目中我们可以学到面向对象的编程思想和练习对多个外设模块进行控制。
该项目还有很多可以扩展的地方,后续我打算用IMX6ULL 加 MQTT 重新实现一下智能家居,再用QT作为上位机控制下位机(选择 IMX6ULL),使用 OpenCV 进行人脸识别从而进行开关锁,计划在暑假实现全部扩展功能,让不同的开发板也可以完美实现该项目。