当前位置: 首页 > news >正文

基于树莓派实现 --- 智能家居

最效果展示

演示视频链接:基于树莓派实现的智能家居_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1Tr421n7BM/?spm_id_from=333.999.0.0

PS:房屋模型的搭建是靠纸板箱和淘宝买的家居模型,户型参考了留学时短租的公寓~) 

前言

到目前为止,对于linux的嵌入式软件开发,从底层到上层都有了一定的认识。这个项目的初衷就是整合知识并以工厂模式的架构开发项目。

功能实现

  1. 实现了socket服务器远程控制卧室,餐厅,厕所,客厅4盏灯的开启和关闭
  2. 实现了语音控制卧室,餐厅,厕所,客厅4盏灯的开启和关闭
  3. 实现了当温度超过阈值的时候进行火灾报警,并且可以语音关闭警报
  4. 实现了进门前结合语音,OLED和摄像头的人脸识别
  5. 实现了实时的远程视频监控
  6. 实现OLED屏幕的实时温湿度显示

开发环境 & 实现思路

  • 开发板:树莓派3B+
  • 开发语言:C
  • 编程工具:Source Insight 3

工厂设计

对于这个项目的实现,采用上节学到的工厂模式来设计,从而提升整体代码的稳定性和可拓展性。

软件设计模式 --- 类,对象和工厂模式的引入-CSDN博客

 阅读功能需求后,结合工厂模式的思路可以先设计两个工厂:指令工厂设备工厂

  • 指令工厂:存储需要使用到的指令
  • 设备工厂:存储需要使用到的设备

工厂模式的主要的考量有两点:

1. 工厂的类
struct device //设备工厂
{char device_name[64]; //设备名称int status;int (*init)(); //初始化函数int (*open)(); //打开设备的函数int (*close)(); //关闭设备的函数int	(*read_status)(); //查看设备状态的函数struct device *next;
};struct cmd //指令工厂
{char cmd_name[64]; //指令名称//char cmd_log[1024]; //指令日志int (*init)(int port, char *IP, char *UART, int BAUD); //初始化函数int (*accept)(int fd); //接受函数int (*cmd_handler)(struct device *phead, int fd); //处理指令的函数struct cmd *next;
};
2. 工厂的对象

实现思路Q&A

Q:如何实现socket服务器的远程控制?

A:使用之前学习的socket知识,创建一个服务端一个客户端,服务端负责创建套接字并绑定,然后阻塞监听;客户端负责建立连接后发送指令。指令在服务端通过指令工厂中socket对象的cmd_handler函数进行分析并作出相关动作。最后在main函数中使用一个线程不断阻塞等待新客户端的加入;使用另一个线程不断阻塞接受客户端传来的指令并分析。

参考我之前的博文:

(👇相关的API讲解)

Linux socket网络编程概述 和 相关API讲解_linux 网络编程-CSDN博客

(👇具体如何使用API的实战,父子进程版)

基于Linux并结合socket网络编程的ftp服务器的实现-CSDN博客

(👇具体如何使用API的实战,多线程版)

使用香橙派并基于Linux实现最终版智能垃圾桶项目 --- 上_linux 打印扔垃圾桶-CSDN博客

Q:如何实现语音控制的操作?

A:使用之前学习的SU-03T,在其官网对指令进行编辑和烧录,然后通过串口来和树莓派进行通信。同样通过指令工厂中语音控制对象的cmd_handler函数来进行分析并作出相关动作。最后在main函数开启一个线程不断通过串口阻塞读取语音模块的指令并分析。

参考我之前的博文:

(👇如何设置官网指令并烧录&通过电平变化来控制语音模块的实例)

语音小车---6 + 最终整合_unioneupdatetool-CSDN博客

(👇关于在多线程环境下通过串口通信来控制语音模块的实例)

使用香橙派并基于Linux实现最终版智能垃圾桶项目 --- 下_香橙派 项目-CSDN博客

Q:如何实现火灾报警?

A:使用之前学习的温湿度传感器DHT11和蜂鸣器,通过阅读DHT11的手册,在设备工厂中实现其激活和读取状态的函数;在指令工厂中,调用刚刚实现的函数结合手册实现温湿度的获取。最后在main函数中开启一个线程不断判断当前的温度来决定是否驱动蜂鸣器。

参考我之前的博文:

(👇DHT11的介绍和如何通过手册驱动DHT11的实例)

温湿度传感器 DHT11_dht11温湿度传感器 库从哪里下载-CSDN博客

(👇别人实现的,通过树莓派驱动DHT11的例程)

树莓派驱动DH11温湿度传感器_如何使用zynq驱动dh11-CSDN博客

Q:如何实现OLED屏幕显示?

参考我之前的博文:

(👇关于树莓派驱动OLED屏幕)

使用树莓派 结合Python Adafruit驱动OLED屏幕 显示实时视频-CSDN博客

Q:如何实现人脸识别?

A:使用一枚之前用过的USB摄像头HBV-W202012HD V33,接入树莓派,在设备工厂为其实现拍照等功能。然后接入阿里云的人脸识别方案,当收到对应的人脸识别语音指令时,在指令工厂的语音模块对象下的cmd_handler函数中添加人脸识别的代码,并根据结果通过串口回传给语音模块播报结果。

参考我之前的博文:

(👇香橙派中驱动摄像头,并调用阿里云物品识别的实例)

使用香橙派并基于Linux实现最终版智能垃圾桶项目 --- 下_香橙派 项目-CSDN博客

(👇树莓派中驱动摄像头,并调用阿里云人脸识别的实例)

基于阿里云平台 通过树莓派实现 1:1人脸识别-CSDN博客

硬件接线

整体的接线情况如下:

注意!如果外设和单片机采用了不同供电,且外设和单片机存在信息交互那么就必须共地!! 

 

预备工作

在有了大概的思路和硬件接线完毕完成后,要进行两个重要的预备工作:

摄像头的接入和mpjg-streamer的自动后台运行

由于这个项目在运行时只要涉及人脸识别就需要用到摄像头拍照,并且需要实现实时的监控画面,所以先将USB摄像头接入并让mpjg-streamer在每次树莓派开机的时候自动运行就很有必要了。

实现其实很简单,可以参考我的这篇博文:

树莓派接入USB摄像头并使用fswebcam和mjpg-streamer进行测试_在树莓派ros2中安装usb摄像头驱动-CSDN博客

语音模块SU-03T的指令编辑和烧写

这一步虽然叫预备工作,但是在实际开发中随着项目的完善肯定要多次修改和烧写,但是为了逻辑清晰所以将这一步归为预备工作,仅展示最后的效果。

同时再次提醒,只要涉及到SU-03T的串口输入输出,就要下载固件而不是SDK!!!

关于网站和具体细节,请移步至上面的相关链接

引脚配置

命令设置

自定义配置

实现效果

1. 开机播报“海豹助手帮你实现智能居住体验"

1. 当说出“你好小豹”可以唤醒模块,模块回复“海豹在”或“有什么可以帮到你

2. 当超过10s没有指令或说出“退下”时,模块会进入休眠模式,并回复“有需要再叫我

3. 当说出“打开/关闭 客厅/卧室/餐厅/厕所 灯”时,模块回复“收到”,并根据当前灯的状态打开/关闭 相应的灯或回复“灯本来就开/关着哦

4. 当说出“打开/关闭 所有灯”时,模块回复“收到”,并打开/关闭所有灯

5. 当说出“关闭警报”时,模块回复“已关闭,但为了您的安全请随时说出‘ 恢复警报 ’来恢复报警功能!”,并关闭警报

6.  当说出“恢复警报”时,模块回复“火灾警报已经恢复工作”,并恢复警报

7.  当说出“人脸识别”时,开始人脸识别,并根据识别结果回复“识别成功”或“识别失败

代码开发

在刚刚提到,代码编写的主要工具是“Source Insight”,所以主要的代码编写就在windows下;写完发送到树莓派测试

①代码预创建

  • 首先创建一个名为“smart_home”文件夹用于保存项目所有相关文件,并在其中创建一个“si”文件夹用于保存source insight工程:

  • 在“smart_home”下创建会使用的.c.h文件:

以下是最终版的结果,实际开发过程中这一步先创建可能会需要的文件,后面随着实现慢慢的添加和修改

 一共23个代码文件

  • 打开source insight,创建一个新工程并将代码全部包含进来同步

(具体步骤见上篇博文)

最终效果:

此时就可以开始正式编程了!

②代码编写

2.1 指令工厂cmd_fac.h和设备工厂dvice_fac.h的编写:

这一步主要是根据上一节工厂模式的思路来实现,具体有哪些函数根据代码的编写再反过来修改

cmd_fac.h:
#ifndef __CMDFAC_H__#define __CMDFAC_H__#include <wiringPi.h>
#include <sys/types.h>     
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>#include "device_fac.h"
#include "find_link.h"
#include "mjm_uart_tool.h"
#include "face_cmp.h"struct cmd
{char cmd_name[64]; //指令名称//char cmd_log[1024]; //指令日志int (*init)(int port, char *IP, char *UART, int BAUD); //初始化函数int (*accept)(int fd); //接受函数int (*cmd_handler)(struct device *phead, int fd);struct cmd *next;
};struct cmd* putSocketInLink(struct cmd *head);
struct cmd* putVoiceInLink(struct cmd *head);
struct cmd* putFireInLink(struct cmd *head);#endif
device_fac.h:
#ifndef __DEVICEFAC_H__#define __DEVICEFAC_H__#include <wiringPi.h>
#include <stddef.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>struct device
{char device_name[64]; //设备名称int status;int (*init)(); //初始化函数int (*open)(); //打开设备的函数int (*close)(); //关闭设备的函数int	(*read_status)(); //查看设备状态的函数struct device *next;
};struct device* putLight_bedroomInLink(struct device *head);
struct device* putLight_diningroomInLink(struct device *head);
struct device* putLight_livingroomInLink(struct device *head);
struct device* putLight_washroomInLink(struct device *head);
struct device* putDhtInLink(struct device *head);
struct device* putBeeperInLink(struct device *head);
struct device* putCameraInLink(struct device *head);#endif
 2.2 串口通讯mjm_uart_tool.c/.h的编写:

串口的代码使用我之前基于wiringPi库自己实现的函数,详见:

树莓派的的串口通信协议-CSDN博客

但是关于“serialSendstring”函数和“serialGetstring”函数需要进行一些修改,其原因就是SU-03T(语音模块)在规定串口输入的时候有固定要求的帧头帧尾格式:

//注意,这个通过串口发送字符串的函数,其中的read函数的第三个参数不能使用strlen
//因为发送给语音模块的数据有固定的帧头帧尾,都是16进制数不包含结束符
//所以如果使用了strlen的话,就无法成功的发送
//所以为这个函数加一个len参数
void serialSendstring (const int fd, const unsigned char *s, int len)
//void serialSendstring (const int fd, const char *s)
{int ret;ret = write (fd, s, len);//ret = write (fd, s, strlen(s));if (ret < 0)printf("Serial Puts Error\n");
}int serialGetstring (const int fd, unsigned char *buffer)
//int serialGetstring (const int fd, char *buffer)
{int n_read;n_read = read(fd, buffer,32);return n_read;
}

主要修改有两点:

  • serialSendstring”函数增加一个参数len,用于指示发送数据的具体长度
  • 两个函数的第二个参数都加上unsigned”,因为不加的话数据长度可能会超出普通char的范围(127)
mjm_uart_tool.c:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "wiringSerial.h"int myserialOpen (const char *device, const int baud)
{struct termios options ;speed_t myBaud ;int status, fd ;switch (baud){case 9600: myBaud = B9600 ; break ;case 115200: myBaud = B115200 ; break ;}if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)return -1 ;fcntl (fd, F_SETFL, O_RDWR) ;// Get and modify current options:tcgetattr (fd, &options) ;cfmakeraw (&options) ;cfsetispeed (&options, myBaud) ;cfsetospeed (&options, myBaud) ;options.c_cflag |= (CLOCAL | CREAD) ;options.c_cflag &= ~PARENB ;options.c_cflag &= ~CSTOPB ;options.c_cflag &= ~CSIZE ;options.c_cflag |= CS8 ;options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;options.c_oflag &= ~OPOST ;options.c_cc [VMIN] = 0 ;options.c_cc [VTIME] = 100 ; // Ten seconds (100 deciseconds)tcsetattr (fd, TCSANOW, &options) ;ioctl (fd, TIOCMGET, &status);status |= TIOCM_DTR ;status |= TIOCM_RTS ;ioctl (fd, TIOCMSET, &status);usleep (10000) ; // 10mSreturn fd ;
}//注意,这个通过串口发送字符串的函数,其中的read函数的第三个参数不能使用strlen
//因为发送给语音模块的数据有固定的帧头帧尾,都是16进制数不包含结束符
//所以如果使用了strlen的话,就无法成功的发送
//所以为这个函数加一个len参数
void serialSendstring (const int fd, const unsigned char *s, int len)
//void serialSendstring (const int fd, const char *s)
{int ret;ret = write (fd, s, len);//ret = write (fd, s, strlen(s));if (ret < 0)printf("Serial Puts Error\n");
}int serialGetstring (const int fd, unsigned char *buffer)
//int serialGetstring (const int fd, char *buffer)
{int n_read;n_read = read(fd, buffer,32);return n_read;
}int serialDataAvail (const int fd)
{int result ;if (ioctl (fd, FIONREAD, &result) == -1)return -1 ;return result ;
}
mjm_uart_tool.h:
#ifndef __UART_H__#define __UART_H__int myserialOpen (const char *device, const int baud);
void serialSendstring (const int fd, const unsigned char *s, int len);
int serialGetstring (const int fd, unsigned char *buffer);
int serialDataAvail (const int fd);#endif
2.3 在工厂链表中查找对象的代码find_link.c/.h的编写:

这部分的代码实现的功能就是封装“在工厂链表中查找特定对象”的函数,其实现思路就是根据对象的名字来在链表中进行遍历

find_link.c:
#include "find_link.h"struct device* findDEVICEinLink(char *name, struct device *phead)
{	struct device *p = phead;	while(p != NULL){		if(strcmp(p->device_name,name)==0){			return p;		}		p = p->next;	}	return NULL;
}struct cmd* findCMDinLink(char *name, struct cmd *phead)
{	struct cmd *p = phead;	while(p != NULL){		if(strcmp(p->cmd_name,name)==0){			return p;		}		p = p->next;	}	return NULL;
}
find_link.h:
#ifndef __FINDLINK_H__#define __FINDLINK_H__#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "device_fac.h"
#include "cmd_fac.h"struct device* findDEVICEinLink(char *name, struct device *phead);
struct cmd* findCMDinLink(char *name, struct cmd *phead);#endif
2.4 OLED显示代码oled_show.c/.h & python代码的编写:

这部分的代码在之前给出的链接里已经大致实现,其核心思路就是先用python调用Adafruit_Python_SSD1306库实现清屏,显示温湿度和显示图片的代码,再用C语言调用python封装这三个函数,最后获得可以清屏,显示温湿度和显示图片的C函数

oled_camera.py:
def init(): # Raspberry Pi pin configuration:RST = 24# Note the following are only used with SPI:DC = 23SPI_PORT = 0SPI_DEVICE = 0# 128x64 display with hardware I2C:disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)# Initialize library.disp.begin()# Clear display.disp.clear()disp.display()def display(): # Raspberry Pi pin configuration:RST = 24# Note the following are only used with SPI:DC = 23SPI_PORT = 0SPI_DEVICE = 0# 128x32 display with hardware I2C:disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)# Initialize library.disp.begin()img = Image.open('/home/pi/mjm_code/smart_home/face.png')img_resized = img.resize((128, 64),Image.LANCZOS)image = img_resized.convert('1')# Display image.disp.image(image)disp.display()def tmphumi(tmp, humi):# Raspberry Pi pin configuration:RST = 24# Note the following are only used with SPI:DC = 23SPI_PORT = 0SPI_DEVICE = 0# 128x32 display with hardware I2C:disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)# Initialize library.disp.begin()# Create blank image for drawing.# Make sure to create image with mode '1' for 1-bit color.width = disp.widthheight = disp.heightimage = Image.new('1', (width, height))# Get drawing object to draw on image.draw = ImageDraw.Draw(image)# Load default font.font = ImageFont.load_default()str1 = f"tmperature: {tmp} C"str2 = f"humidity: {humi} %"# Write two lines of text.draw.text((20,20), str1,  font=font, fill=255)draw.text((20,40), str2, font=font, fill=255)disp.image(image)disp.display()#测试用
if __name__ == '__main__': init()#display()tmphumi(25,50)
oled_show.c:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <Python.h>#include "oled_show.h"void oled_init(void)
{Py_Initialize();PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString("."));
}void oled_final(void)
{Py_Finalize();
}void oled_show_init(void)  //清屏
{PyObject *pModule = PyImport_ImportModule("oled_camera"); if (!pModule){PyErr_Print();printf("Error: failed to load module\n");goto FAILED_MODULE; }PyObject *pFunc = PyObject_GetAttrString(pModule, "init"); if (!pFunc){PyErr_Print();printf("Error: failed to load function\n");goto FAILED_FUNC;}PyObject *pValue = PyObject_CallObject(pFunc, NULL);if (!pValue){PyErr_Print();printf("Error: function call failed\n");goto FAILED_VALUE;}FAILED_RESULT:Py_DECREF(pValue);
FAILED_VALUE:Py_DECREF(pFunc);
FAILED_FUNC:Py_DECREF(pModule);
FAILED_MODULE:
}void oled_show(void) //显示图片
{PyObject *pModule = PyImport_ImportModule("oled_camera"); if (!pModule){PyErr_Print();printf("Error: failed to load module\n");goto FAILED_MODULE; }PyObject *pFunc = PyObject_GetAttrString(pModule, "display"); if (!pFunc){PyErr_Print();printf("Error: failed to load function\n");goto FAILED_FUNC;}PyObject *pValue = PyObject_CallObject(pFunc, NULL);if (!pValue){PyErr_Print();printf("Error: function call failed\n");goto FAILED_VALUE;}FAILED_RESULT:Py_DECREF(pValue);
FAILED_VALUE:Py_DECREF(pFunc);
FAILED_FUNC:Py_DECREF(pModule);
FAILED_MODULE:
}void oled_tmphumi(int tmp, int humi) //显示温湿度
{PyObject *pModule = PyImport_ImportModule("oled_camera"); //加载python文件if (!pModule){PyErr_Print();printf("Error: failed to load module\n");goto FAILED_MODULE; //goto的意思就是如果运行到这里就直接跳转到FAILED_MODULE}PyObject *pFunc = PyObject_GetAttrString(pModule, "tmphumi"); //加载python文件中的对应函数if (!pFunc){PyErr_Print();printf("Error: failed to load function\n");goto FAILED_FUNC;}//创建一个字符串作为参数PyObject *pArgs = Py_BuildValue("(i,i)",tmp,humi); //(i,i)代表有两个int的元组PyObject *pValue = PyObject_CallObject(pFunc, pArgs);if (!pValue){PyErr_Print();printf("Error: function call failed\n");goto FAILED_VALUE;}FAILED_RESULT:Py_DECREF(pValue);
FAILED_VALUE:Py_DECREF(pFunc);
FAILED_FUNC:Py_DECREF(pModule);
FAILED_MODULE:}
oled_show.h:
#ifndef __oled__H
#define __oled__Hvoid oled_init(void);
void oled_final(void);
void oled_show_init(void); //清屏
void oled_show(void); //显示图片
void oled_tmphumi(int tmp, int humi); //显示温湿度#endif

2.5 人脸识别代码face_cmp.c/.h & python代码的编写:

这部分的代码在之前给出的链接里已经大致实现,其核心思路就是先用python调用阿里云的1:1人脸识别,再用C语言调用python,最后获得可以进行人脸识别的C函数

face.py:
# -*- coding: utf-8 -*-
# 引入依赖�?# 最低SDK版本要求:facebody20191230的SDK版本需大于等于4.0.8
# 可以在此仓库地址中引用最新版本SDK:https://pypi.org/project/alibabacloud-facebody20191230/
# pip install alibabacloud_facebody20191230import os
import io
from urllib.request import urlopen
from alibabacloud_facebody20191230.client import Client
from alibabacloud_facebody20191230.models import CompareFaceAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptionsdef face_detect():config = Config(# 创建AccessKey ID和AccessKey Secret,请参考https://help.aliyun.com/document_detail/175144.html�?    # 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考https://help.aliyun.com/document_detail/145025.html�?    # 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量�?    access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),# 访问的域�?    endpoint='facebody.cn-shanghai.aliyuncs.com',# 访问的域名对应的regionregion_id='cn-shanghai')runtime_option = RuntimeOptions()compare_face_request = CompareFaceAdvanceRequest()#场景一:文件在本地streamA = open(r'/home/pi/mjm_code/smart_home/mjm.png', 'rb') #预存的照�?    compare_face_request.image_urlaobject = streamAstreamB = open(r'/home/pi/mjm_code/smart_home/face.png', 'rb') #待测试的照片compare_face_request.image_urlbobject = streamBtry:# 初始化Clientclient = Client(config)response = client.compare_face_advance(compare_face_request, runtime_option)# 获取整体结果#print(response.body)# 单独打印置信�?        confidence = response.body.to_map()['Data']['Confidence'] #to_map()函数很重要,不要忘记score = int(confidence)#print(score)return scoreexcept Exception as error:# 获取整体报错信息print(error)# 获取单个字段print(error.code)# tips: 可通过error.__dict__查看属性名�?# 关闭�?    streamA.close()streamB.close()if __name__ == '__main__':face_detect()
face_cmp.c:
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <Python.h>#include "face_cmp.h"void face_init(void)
{Py_Initialize();PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString("."));
}void face_final(void)
{Py_Finalize();
}int face_score(void) //python下face_detect函数返回的是已经经过提取和取证过的置信度score,是个int型
{PyObject *pModule = PyImport_ImportModule("face"); //加载python文件if (!pModule){PyErr_Print();printf("Error: failed to load module\n");goto FAILED_MODULE; //goto的意思就是如果运行到这里就直接跳转到FAILED_MODULE}PyObject *pFunc = PyObject_GetAttrString(pModule, "face_detect"); //加载python文件中的对应函数if (!pFunc){PyErr_Print();printf("Error: failed to load function\n");goto FAILED_FUNC;}PyObject *pValue = PyObject_CallObject(pFunc, NULL);if (!pValue){PyErr_Print();printf("Error: function call failed\n");goto FAILED_VALUE;}int result = 0;if (!PyArg_Parse(pValue, "i", &result)) //ace_detect函数返回的是已经经过提取和取证过的置信度score,是个int型,用‘i’表示{PyErr_Print();printf("Error: parse failed");goto FAILED_RESULT;}/* 如果函数返回的是字符串,上面的PyArg_Parse则需要用‘s’来表示,且下面注释的代码非常重要,因为字符串名代表了其首地址,所以不能直接复制而是需要使用strncpy函数!!!category = (char *)malloc(sizeof(char) * (strlen(result) + 1) ); //开辟一个新的字符串常量。+1是为了留出空间给\0memset(category, 0, (strlen(result) + 1)); //初始化字符串strncpy(category, result, (strlen(result) + 1)); //将result的结果复制给新的字符串*/FAILED_RESULT:Py_DECREF(pValue);
FAILED_VALUE:Py_DECREF(pFunc);
FAILED_FUNC:Py_DECREF(pModule);
FAILED_MODULE:return result;
}
face_cmp.h:
#ifndef __face__H
#define __face__Hvoid face_init(void);
void face_final(void);
int face_score(void);#endif
2.6 4盏灯的代码light_xxx.c的编写:

作为设备工厂的对象,灯代码的编写就是选择性的实现设备工厂的类,并且4盏灯的代码除了wiringPi对应的引脚和名字不同之外,几乎没有任何差别。

light_XXXX.c:
#include "device_fac.h"#define lightXXXX X //根据硬件接线来int light_XXXX_init()
{pinMode (lightXXXX, OUTPUT); digitalWrite (lightXXXX, HIGH) ; 
}int light_XXXX_open()
{digitalWrite (lightXXXX, LOW) ; 
}int light_XXXX_close()
{digitalWrite (lightXXXX, HIGH) ; 
}int light_XXXX_status()
{return digitalRead(lightXXXX);
}struct device light_XXXX = {.device_name = "light_XXXX",.init = light_XXXX_init,.open = light_XXXX_open,.close = light_XXXX_close,.read_status = light_XXXX_read_status,
};struct device* putLight_XXXXInLink(struct device *head)
{	struct device *p = head;        		if(p == NULL){		head = &light_XXXX;	}else{		light_XXXX.next = head;		head = &light_XXXX;	}		return head; 
}
2.7 温湿度传感器dht11.c的编写:

作为设备工厂的对象,dht11代码的编写就是选择性的实现设备工厂的类:

dht11.c:
#include "device_fac.h"#define dht 4int dht_start()
{pinMode(dht, OUTPUT); //起始拉高电平digitalWrite(dht, 1); delay(1000);			pinMode(dht, OUTPUT);  //拉低超过18msdigitalWrite(dht, 0);delay(21);digitalWrite(dht, 1); //拉高电平,等响应pinMode(dht, INPUT);delayMicroseconds(28);
}int dht_read_status()
{return digitalRead(dht);
}struct device dht11 = {.device_name = "dht",.open = dht_start,.read_status = dht_read_status,
};struct device* putDhtInLink(struct device *head)
{	struct device *p = head;        		if(p == NULL){		head = &dht11;	}else{		dht11.next = head;		head = &dht11;	}		return head; 
}
2.8 蜂鸣器beeper.c的编写:

作为设备工厂的对象,蜂鸣器代码的编写就是选择性的实现设备工厂的类,蜂鸣器的实现和4盏灯极其类似,直接看代码:

beeper.c:
#include "device_fac.h"#define io 5int beep_init()
{pinMode (io, OUTPUT); digitalWrite (io, HIGH) ;
}int beep_open()
{digitalWrite (io, LOW) ;  //蜂鸣器响		
}int beep_close()
{digitalWrite (io, HIGH) ;  //蜂鸣器不响	
}struct device beeper = {.device_name = "beeper",.init = beep_init,.open = beep_open,.close = beep_close,
};struct device* putBeeperInLink(struct device *head)
{	struct device *p = head;        		if(p == NULL){		head = &beeper;	}else{		beeper.next = head;		head = &beeper;	}		return head; 
}
2.9 摄像头camera.c的编写:

作为设备工厂的对象,摄像头代码的编写就是选择性的实现设备工厂的类:

camera.c:
#include "device_fac.h"int camera_takePic() //返回1成功拍到照片,返回0拍照失败
{system("wget http://192.168.2.56:8080/?action=snapshot -O /home/pi/mjm_code/smart_home/face.png"); //拍照delay(10);//给一点时间让照片拍出来if(0 == access("/home/pi/mjm_code/smart_home/face.png", F_OK)){ //如果照片成功拍到了return 1;}else{return 0;}
}int camera_removePic()
{return remove("/home/pi/mjm_code/smart_home/face.png");
}struct device camera = {.device_name = "camera",.open = camera_takePic,.close = camera_removePic,
};struct device* putCameraInLink(struct device *head)
{	struct device *p = head;        		if(p == NULL){		head = &camera;	}else{		camera.next = head;		head = &camera;	}		return head; 
}
2.10 socket控制socket_ctl.c的编写:

作为指令工厂的对象,socket控制就是选择性的实现指令工厂的类:

socket_ctl.c:
#include "cmd_fac.h"char *HELP = "welcome to smart home! Here are some cmd instructions:\n\'oll\'---open livingroom light\n\'cll\'---close livingroom light\n\'odl\'---open diningroom light\n\'cdl\'---close diningroom light\n\'obl\'---open bedroom light\n\'cbl\'---close bedroom light\n\'owl\'---open washroom light\n\'cwl\'---close washroom light\n\'quit\'---disconnect\ntype \'help\' to review all the command\n";
int conn_sockfd;int answer_success(int fd)
{int ret = 0;ret = write(fd,"operation success",18);if(ret == -1){perror("write1");return -1;}else{return 0;}
}int answer_fail(int fd)
{int ret = 0;ret = write(fd,"already open/close",19);if(ret == -1){perror("write2");return -1;}else{return 0;}
}int handler(int fd, char readbuf[128], struct device *phead)
{struct device *device_pfind = NULL;int ret;	int i = 0;	char str[128]; //将读到的数据备份在这里 	strcpy(str,readbuf); //由于字符串的首地址是字符串的名字,所以此时相当于传入的地址,所有对字符串的操作都会影响它,所以需要进行备份,先备份再对备份的数据进行数据处理就不会影响原数据了if(strcmp((char *)str,"obl")==0){ //收到打开卧室灯的指令device_pfind = findDEVICEinLink("light_bedroom",phead);if(device_pfind->read_status()){//如果卧室灯关着device_pfind->open();answer_success(fd);}else{//如果卧室灯开着answer_fail(fd);}}else if(strcmp((char *)str,"cbl")==0){ //收到关闭卧室灯的指令device_pfind = findDEVICEinLink("light_bedroom",phead);if(!device_pfind->read_status()){//如果卧室灯开着device_pfind->close();answer_success(fd);}else{//如果卧室灯关着answer_fail(fd);}}else if(strcmp((char *)str,"odl")==0){ //收到打开厨房灯的指令device_pfind = findDEVICEinLink("light_diningroom",phead);if(device_pfind->read_status()){//如果厨房灯关着device_pfind->open();answer_success(fd);}else{//如果厨房灯开着answer_fail(fd);}}else if(strcmp((char *)str,"cdl")==0){ //收到关闭厨房灯的指令device_pfind = findDEVICEinLink("light_diningroom",phead);if(!device_pfind->read_status()){//如果厨房灯开着device_pfind->close();answer_success(fd);}else{//如果厨房灯关着answer_fail(fd);}}else if(strcmp((char *)str,"oll")==0){ //收到打开客厅灯的指令device_pfind = findDEVICEinLink("light_livingroom",phead);if(device_pfind->read_status()){//如果客厅灯关着device_pfind->open();answer_success(fd);}else{//如果客厅灯开着answer_fail(fd);}}else if(strcmp((char *)str,"cll")==0){ //收到关闭客厅灯的指令device_pfind = findDEVICEinLink("light_livingroom",phead);if(!device_pfind->read_status()){//如果客厅灯开着device_pfind->close();answer_success(fd);}else{//如果客厅灯关着answer_fail(fd);}}else if(strcmp((char *)str,"owl")==0){ //收到打开厕所灯的指令device_pfind = findDEVICEinLink("light_washroom",phead);if(device_pfind->read_status()){//如果厕所灯关着device_pfind->open();answer_success(fd);}else{//如果厕所灯开着answer_fail(fd);}}else if(strcmp((char *)str,"cwl")==0){ //收到关闭厕所灯的指令device_pfind = findDEVICEinLink("light_washroom",phead);if(!device_pfind->read_status()){//如果厕所灯开着device_pfind->close();answer_success(fd);}else{//如果厕所灯关着answer_fail(fd);}}else if(strcmp((char *)str,"quit")==0){ret = write(fd,"Bye",4);if(ret == -1){perror("write5");return -1;}else{return 0;}}else if(strcmp((char *)str,"help")==0){ret = write(fd,HELP,512);if(ret == -1){perror("write4");return -1;}else{return 0;}}else{return -1;}}int socket_init(int port, char *IP, char *UART, int BAUD)
{int sockfd;int ret = 0;int len = sizeof(struct sockaddr_in);struct sockaddr_in my_addr;sockfd = socket(AF_INET,SOCK_STREAM,0);	if(sockfd == -1){		perror("socket");		return -1;	}else{		printf("socket success, sockfd = %d\n",sockfd);}//bind	my_addr.sin_family = AF_INET;	my_addr.sin_port = htons(port);//host to net (2 bytes) //此处原本是atoi(port),但考虑到port本来就是int,所以不用使用atoiinet_aton(IP,&my_addr.sin_addr); //char* format -> net format ret = bind(sockfd, (struct sockaddr *)&my_addr, len);if(ret == -1){		perror("bind");		return -1;	}else{		printf("bind success\n");	}//listen	ret = listen(sockfd,10);	if(ret == -1){		perror("listen");		return -1;	}else{		printf("listening...\n");	}return sockfd;
}int socket_accept(int sockfd) //return 1代表连接成功;return 0代表连接错误
{int ret = 0;int len = sizeof(struct sockaddr_in);struct sockaddr_in client_addr;//accept		conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);if(conn_sockfd == -1){			perror("accept");			return -1;		}else{						printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));			fflush(stdout);ret = write(conn_sockfd,HELP,512);if(ret == -1){perror("write3");return -2;}else{return 1; //return 给main里的conn_flag}}}//socket实现的handler函数(下面这个)没有使用第二个参数fd
//这是因为我发现把conn_sockfd传进来会导致recv函数不认识这个标识符
//但我不太清楚为什么会这样,因为我用这种方法传递其他fd就不会报错
int socket_receiveANDhandle(struct device *phead, int fd)
{int ret;char readbuf[128];memset(&readbuf,0,sizeof(readbuf));			ret = recv(conn_sockfd, &readbuf, sizeof(readbuf), 0);			if(ret == 0){ //如果recv函数返回0表示连接已经断开				printf("client has quit\n");				fflush(stdout);				close(conn_sockfd);				return -1;			}else if(ret == -1){				perror("recv");				return 0; //这个值会return 给 main中的conn_flag。此时打印一遍错误信息就会结束,如果不把conn_flag置0,在一个客户端退出另一个客户端还未接入时就会不停的打印错误信息				//pthread_exit(NULL); //此处不能退出,因为因为这样如果有一个客户端接入并退出后这个线程就会退出,为了保证一个客户端退出后,另一个客户端还可以接入并正常工作,此处仅显示错误信息而不退出			}ret = handler(conn_sockfd, readbuf, phead);if(ret == -1){printf("socket_cmd_handler error!\n");}printf("\nclient: %s\n",readbuf);fflush(stdout);return 1; //这句很重要,正常情况下要保持conn_flag为1
}struct cmd sockt = {.cmd_name = "socket",.init = socket_init,.accept = socket_accept,.cmd_handler = socket_receiveANDhandle,
};struct cmd* putSocketInLink(struct cmd *head)
{struct cmd *p = head;        		if(p == NULL){		head = &sockt;	}else{		sockt.next = head;		head = &sockt;	}		return head; 
}
2.11 火灾控制fire_ctl.c的编写:

作为指令工厂的对象,火灾控制就是选择性的实现指令工厂的类:

fire_ctl.c:
#include "cmd_fac.h"int readDataFromDHT(struct device *phead, int fd) //此处的第二个参数fd用来指示返回的是温度还是湿度
{unsigned char crc, i;unsigned long data = 0;struct device *device_pfind = NULL;device_pfind = findDEVICEinLink("dht",phead);device_pfind->open();if (!device_pfind->read_status()){			//主机接收到从机发送的响应信号(低电平)while(!device_pfind->read_status());		//主机接收到从机发送的响应信号(高电平)for (i = 0; i < 32; i++){while(device_pfind->read_status());	//数据位开始的54us低电平while(!device_pfind->read_status());	//数据位开始的高电平就开始delayMicroseconds(50);			//等50us,此时电平高为1,低为0(data) *= 2;   //进位if (device_pfind->read_status()){(data)++;}}for (i = 0; i < 8; i++){while(device_pfind->read_status());	while(!device_pfind->read_status());			delayMicroseconds(50);			crc *= 2;  if (device_pfind->read_status()){crc++;}}//return 1;}else{//return 0;}if(fd == 0){return ((data >> 8) & 0xff); //将温度的整数位返回}else if(fd == 1){return ((data >> 24) & 0xff); //将湿度的整数位返回}//温度小数位:data & 0xff//湿度小数位:(data >> 16) & 0xff}struct cmd fire = {.cmd_name = "fire",.cmd_handler = readDataFromDHT,
};struct cmd* putFireInLink(struct cmd *head)
{struct cmd *p = head;        		if(p == NULL){		head = &fire;	}else{		fire.next = head;		head = &fire;	}		return head; 
}
2.12 语音控制voice_ctl.c的编写:

作为指令工厂的对象,语音控制就是选择性的实现指令工厂的类:

voice_ctl.c:
#include "cmd_fac.h"#define threhold 70int v_answer(int fd, int cmd)
{unsigned char buffer[6]= {0xAA, 0X55, 0X00, 0X00, 0X55, 0XAA};int ret = 0;if(cmd == 1){ //回复 成功打开buffer[2] = 0X02;buffer[3] = 0X01;}else if(cmd == 2){ //回复 成功关闭buffer[2] = 0X04;buffer[3] = 0X03;}else if(cmd == 3){ //回复 灯本来就开着哦buffer[2] = 0X03;buffer[3] = 0X02;}else if(cmd == 4){ //回复 灯本来就关着哦buffer[2] = 0X05;buffer[3] = 0X04;}else if(cmd == 5){ //回复 识别成功buffer[2] = 0X06;buffer[3] = 0X05;}else if(cmd == 6){ //回复 识别失败buffer[2] = 0X07;buffer[3] = 0X06;}serialSendstring (fd, buffer, 6);	}int voice_init(int port, char *IP, char *UART, int BAUD)
{int serial_fd;serial_fd = myserialOpen (UART, BAUD);if(serial_fd < 0){perror("serial:");return -1;}else{return serial_fd;}
}int voice_accept(int serialfd)
{int ret;ret = serialDataAvail (serialfd);if(ret != -1){return ret;}else{perror("serial_DataAvail:");return -1;}
}int voice_receiveANDhandle(struct device *phead, int fd)
{struct device *device_pfind = NULL;char readbuf[32] = {'\0'};int re;int score;//人脸识别结果int val = 0;int flags = fcntl(fd, F_GETFL, 0);fcntl(fd, F_SETFL, flags | O_NONBLOCK);int len = serialGetstring (fd,readbuf) ;if(len <0){perror("serialGetstring:");}if(strcmp(readbuf,"opli") == 0){ //收到打开客厅灯的指令device_pfind = findDEVICEinLink("light_livingroom",phead);if(device_pfind->read_status()){//如果客厅灯关着device_pfind->open();v_answer(fd,1);}else{//如果客厅灯开着v_answer(fd,3);}}else if(strcmp(readbuf,"clli") == 0){ //收到关闭客厅灯的指令device_pfind = findDEVICEinLink("light_livingroom",phead);if(!device_pfind->read_status()){//如果客厅灯开着device_pfind->close();v_answer(fd,2);}else{//如果客厅灯关着v_answer(fd,4);}}else if(strcmp(readbuf,"opbe") == 0){ //收到打开卧室灯的指令device_pfind = findDEVICEinLink("light_bedroom",phead);if(device_pfind->read_status()){//如果卧室灯关着device_pfind->open();v_answer(fd,1);}else{//如果卧室灯开着v_answer(fd,3);}}else if(strcmp(readbuf,"clbe") == 0){ //收到关闭卧室灯的指令device_pfind = findDEVICEinLink("light_bedroom",phead);if(!device_pfind->read_status()){//如果卧室灯开着device_pfind->close();v_answer(fd,2);}else{//如果卧室灯关着v_answer(fd,4);}}else if(strcmp(readbuf,"opdi") == 0){ //收到打开厨房灯的指令device_pfind = findDEVICEinLink("light_diningroom",phead);if(device_pfind->read_status()){//如果厨房灯关着device_pfind->open();v_answer(fd,1);}else{//如果厨房灯开着v_answer(fd,3);}}else if(strcmp(readbuf,"cldi") == 0){ //收到关闭厨房灯的指令device_pfind = findDEVICEinLink("light_diningroom",phead);if(!device_pfind->read_status()){//如果厨房灯开着device_pfind->close();v_answer(fd,2);}else{//如果厨房灯关着v_answer(fd,4);}}else if(strcmp(readbuf,"opwa") == 0){ //收到打开厕所灯的指令device_pfind = findDEVICEinLink("light_washroom",phead);if(device_pfind->read_status()){//如果厕所灯关着device_pfind->open();v_answer(fd,1);}else{//如果厕所灯开着v_answer(fd,3);}}else if(strcmp(readbuf,"clwa") == 0){ //收到关闭厕所灯的指令device_pfind = findDEVICEinLink("light_washroom",phead);if(!device_pfind->read_status()){//如果厕所灯开着device_pfind->close();v_answer(fd,2);}else{//如果厕所灯关着v_answer(fd,4);}}else if(strcmp(readbuf,"opal") == 0){ //收到打开所有灯的指令device_pfind = findDEVICEinLink("light_washroom",phead);device_pfind->open();device_pfind = findDEVICEinLink("light_diningroom",phead);device_pfind->open();device_pfind = findDEVICEinLink("light_bedroom",phead);device_pfind->open();device_pfind = findDEVICEinLink("light_livingroom",phead);device_pfind->open();}else if(strcmp(readbuf,"clal") == 0){ //收到关闭所有灯的指令device_pfind = findDEVICEinLink("light_washroom",phead);device_pfind->close();device_pfind = findDEVICEinLink("light_diningroom",phead);device_pfind->close();device_pfind = findDEVICEinLink("light_bedroom",phead);device_pfind->close();device_pfind = findDEVICEinLink("light_livingroom",phead);device_pfind->close();}else if(strcmp(readbuf,"gbjb") == 0){ //收到关闭警报指令return 3;}else if(strcmp(readbuf,"hfjb") == 0){ //收到恢复警报指令return 2;}else if(strcmp(readbuf,"rlsb") == 0){ //收到人脸识别指令device_pfind = findDEVICEinLink("camera",phead);re = device_pfind->open(); //拍照if(re == 1){ //拍照成功oled_show_init(); //OLED清屏oled_show(); //显示拍出的照片score = face_score(); //进行人脸识别,获取置信度分数printf("score = %d\n",score);fflush(stdout);if(score >= threhold){//识别成功v_answer(fd,5);val = 4;}else{//识别失败v_answer(fd,6);val = 5;}re = device_pfind->close(); //删除照片if(re != 0){printf("pic remove fail!\n");fflush(stdout);}}else{ //拍照失败v_answer(fd,6);val = 5;}score = 0;return val; //return 4说明	成功,return 5说明失败		}}struct cmd voice = {.cmd_name = "voice",.init = voice_init,.accept = voice_accept,.cmd_handler = voice_receiveANDhandle,
};struct cmd* putVoiceInLink(struct cmd *head)
{struct cmd *p = head;        		if(p == NULL){		head = &voice;	}else{		voice.next = head;		head = &voice;	}		return head; 
}
2.13 main函数的编写:

main函数的核心思路就是利用以上所有代码提供的函数接口来完成项目的总体逻辑

main函数共有4个线程:

  • socket等待连接的线程
  • socket连接成功后接受数据的线程
  • 语音控制&人脸识别线程
  • 火灾报警线程

main.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>#include "device_fac.h"
#include "cmd_fac.h"
#include "find_link.h"
#include "face_cmp.h"
#include "oled_show.h"#define port 8888 //端口号
#define IP "192.168.2.56" //IP地址
#define UART "/dev/ttyAMA0" //串口驱动文件
#define BAUD 115200 //波特率
#define FIRE_TMP 30 //火灾报警温度struct device *device_phead = NULL;
struct cmd *cmd_phead = NULL;
int sockfd;
int serialfd;
//int conn_sockfd;
int conn_flag = 0;
int ret;
//char readbuf[128];
int voice_return_flag;
int tmp;
int humi;pthread_mutex_t mutex;void *thread1(void *arg) //socket等待连接的线程
{struct cmd *cmd_pfind_th1 = NULL;while(1){cmd_pfind_th1 = findCMDinLink("socket",cmd_phead);if(cmd_pfind_th1!=NULL){//acceptconn_flag = cmd_pfind_th1->accept(sockfd); //conn_flag保证了接收连接成功后才可以开始接收数据if(conn_flag != 1 ){printf("s_accept error!\n");fflush(stdout);}}else{printf("thread1:can't find 'socket' in link!\n");fflush(stdout);}}pthread_exit(NULL);
}void *thread2(void *arg) //socket连接成功后接受数据的线程
{	struct cmd *cmd_pfind_th2 = NULL;while(1){while(conn_flag == 1){cmd_pfind_th2 = findCMDinLink("socket",cmd_phead);if(cmd_pfind_th2!=NULL){conn_flag = cmd_pfind_th2->cmd_handler(device_phead,0);//receive msg form client and handle cmdif(conn_flag == -1){break;//说明客户端已退出,退出内层while,等待下一个客户端接入}}else{printf("thread2:can't find 'socket' in link!\n");fflush(stdout);}}}pthread_exit(NULL);
}void *thread3(void *arg) //语音控制&人脸识别线程
{struct cmd *cmd_pfind_th3 = NULL;while(1){cmd_pfind_th3 = findCMDinLink("voice",cmd_phead);if(cmd_pfind_th3!=NULL){while(cmd_pfind_th3->accept(serialfd)){ //当串口接收到信息时,即当语音模块发送信息时pthread_mutex_lock(&mutex); //上锁voice_return_flag = cmd_pfind_th3->cmd_handler(device_phead,serialfd);//voice的cmd_handler函数的返回值://返回2:接收到“恢复警报”指令//返回3:接收到“关闭警报”指令//返回4:接收到“人脸识别”指令且识别成功//返回5:接收到“人脸识别”指令且识别失败pthread_mutex_unlock(&mutex); //解锁}}else{printf("thread3:can't find 'voice' in link!\n");fflush(stdout);}}pthread_exit(NULL);
}void *thread4(void *arg) //火灾报警线程
{struct cmd *cmd_pfind_th4 = NULL;struct device *device_pfind_th4 = NULL;while(1){//delay(1000);//不用delay因为线程间本来就是竞争关系,加上一共有多个线程,哪怕不delay也不会很快速的运行cmd_pfind_th4 = findCMDinLink("fire",cmd_phead);if(cmd_pfind_th4!=NULL){tmp = cmd_pfind_th4->cmd_handler(device_phead,0);//检测温度humi = cmd_pfind_th4->cmd_handler(device_phead,1);//检测湿度printf("current temperature:%d\n",tmp); //不断打印当前的温度,同时充当心跳包fflush(stdout); pthread_mutex_lock(&mutex); //上锁oled_show_init(); //清屏int tmp1 = tmp; //保留tmp的值,至于为什么要保留存疑,如果不保留之后报警就会失效oled_tmphumi(tmp1,humi); //显示在OLED上pthread_mutex_unlock(&mutex); //解锁if(tmp > FIRE_TMP && voice_return_flag!=3){//如果温度大于XX度且用户希望警报打开device_pfind_th4 = findDEVICEinLink("beeper",device_phead);//此处不需要再判断device_pfind是否为空,因为main函数在初始化的时候判断过了device_pfind_th4->open();				}else{ //否则就关闭警报device_pfind_th4 = findDEVICEinLink("beeper",device_phead);//此处不需要再判断device_pfind是否为空,因为main函数在初始化的时候判断过了device_pfind_th4->close();}}else{printf("thread4:can't find 'fire' in link!\n");fflush(stdout);}}pthread_exit(NULL);
}int main()
{pthread_t t1_id;pthread_t t2_id;pthread_t t3_id;pthread_t t4_id;struct device *device_pfind = NULL;struct cmd *cmd_pfind = NULL;wiringPiSetup(); //初始化wiringPi库//指令工厂初始化cmd_phead = putSocketInLink(cmd_phead);cmd_phead = putVoiceInLink(cmd_phead);cmd_phead = putFireInLink(cmd_phead);//设备工厂初始化device_phead = putLight_bedroomInLink(device_phead);device_phead = putLight_diningroomInLink(device_phead);device_phead = putLight_livingroomInLink(device_phead);device_phead = putLight_washroomInLink(device_phead);device_phead = putDhtInLink(device_phead);device_phead = putBeeperInLink(device_phead);device_phead = putCameraInLink(device_phead);device_pfind = findDEVICEinLink("light_livingroom",device_phead);if(device_pfind != NULL){device_pfind->init();}else{printf("main:can't find 'livingroom' in link!\n");}device_pfind = findDEVICEinLink("light_diningroom",device_phead);if(device_pfind != NULL){device_pfind->init();}else{printf("main:can't find 'diningroom' in link!\n");}device_pfind = findDEVICEinLink("light_bedroom",device_phead);if(device_pfind != NULL){device_pfind->init();}else{printf("main:can't find 'bedroom' in link!\n");}device_pfind = findDEVICEinLink("light_washroom",device_phead);if(device_pfind != NULL){device_pfind->init();}else{printf("main:can't find 'washroom' in link!\n");}device_pfind = findDEVICEinLink("beeper",device_phead);if(device_pfind != NULL){device_pfind->init();}else{printf("main:can't find 'beeper' in link!\n");}//对于dht,唯一需要的初始化就是在通电后延时1秒越过不稳定状态delay(1000); //camera不需要初始化//socket初始化cmd_pfind = findCMDinLink("socket",cmd_phead);if(cmd_pfind != NULL){sockfd = cmd_pfind->init(port,IP,NULL,0);if(sockfd == -1){printf("socket init fail!\n");}}else{printf("main:can't find 'socket' in link!\n");}//语音模块初始化cmd_pfind = findCMDinLink("voice",cmd_phead);if(cmd_pfind != NULL){serialfd = cmd_pfind->init(0,NULL,UART,BAUD);if(serialfd == -1){printf("main:voice init fail!\n");}}else{printf("main:can't find 'voice' in link!\n");}//人脸识别初始化face_init();//OLED初始化oled_init();//互斥锁初始化ret = pthread_mutex_init(&mutex, NULL);if(ret != 0){printf("mutex create error\n");}//socket控制线程ret = pthread_create(&t1_id,NULL,thread1,NULL);if(ret != 0){printf("thread1 create error\n");}ret = pthread_create(&t2_id,NULL,thread2,NULL);if(ret != 0){printf("thread2 create error\n");}//语音控制&人脸识别线程ret = pthread_create(&t3_id,NULL,thread3,NULL);if(ret != 0){printf("thread3 create error\n");}//火灾报警线程ret = pthread_create(&t4_id,NULL,thread4,NULL);if(ret != 0){printf("thread4 create error\n");}pthread_join(t1_id,NULL);pthread_join(t2_id,NULL);pthread_join(t3_id,NULL);pthread_join(t4_id,NULL);//释放python解释器face_final();oled_final();return 0;
}

以上所有代码均属于服务端


以下代码属于客户端

2.14 socke客户端 client.c的编写:

客户端的编写大量参考之前写的socket客户端,详见上面的相关链接:

client.c:
#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>#define port 8888
#define IP "192.168.2.56"int main()
{int sockfd;int ret;int n_read;int n_write;char readbuf[512];char msg[128];int fd; //fifochar fifo_readbuf[20] = {0};char *fifo_msg = "quit";pid_t fork_return;/*if(argc != 3){printf("param error!\n");return 1;}*/struct sockaddr_in server_addr;memset(&server_addr,0,sizeof(struct sockaddr_in));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//connectserver_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);//host to net (2 bytes)inet_aton(IP,&server_addr.sin_addr); ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));if(ret == -1){perror("connect");return 1;}else{printf("connect success!\n");}//fifoif(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST){perror("fifo");}//forkfork_return = fork();if(fork_return > 0){//father keeps writing msgwhile(1){//writememset(&msg,0,sizeof(msg));//printf("\ntype msg:");scanf("%s",(char *)msg);n_write = write(sockfd,&msg,strlen(msg));if(msg[0]=='q' && msg[1]=='u' && msg[2]=='i' && msg[3]=='t'){printf("quit detected!\n");fd = open("./fifo",O_WRONLY);write(fd,fifo_msg,strlen(fifo_msg));close(fd);close(sockfd);wait(NULL);break;}if(n_write == -1){perror("write");return 1;}else{printf("%d bytes msg sent\n",n_write);}}}else if(fork_return < 0){perror("fork");return 1;}else{//son keeps reading while(1){fd = open("./fifo",O_RDONLY|O_NONBLOCK);lseek(fd, 0, SEEK_SET);read(fd,&fifo_readbuf,20);//printf("read from fifo:%s\n",fifo_readbuf);if(fifo_readbuf[0]=='q' && fifo_readbuf[1]=='u' && fifo_readbuf[2]=='i' && fifo_readbuf[3]=='t'){exit(1);}//readmemset(&readbuf,0,sizeof(readbuf));n_read = read(sockfd,&readbuf,512);if(n_read == -1){perror("read");return 1;}else{printf("\nserver: %s\n",readbuf);}}}return 0;
}

③注意事项

3.1 .h文件的格式

由于使用工厂模式,涉及到很多头文件的调用,所以为了避免重复调用的错误,在.h文件中使用条件编译非常重要,具体格式如下:

#ifndef __XXXXX_H__#define __XXXXX_H__//头文件内容
#endif
3.2  关于cmd_pfind和device_pfind

cmd_pfind和device_pfind不能设置为全局变量,而应该设置为局部变量

因为如果设置为全局变量,那么在多个线程里都会使用它们来定位需要的函数,如果一个线程刚定义,另一个线程也定义了,可能会造成混乱,所以为了不让它们成为临界资源,要设置为局部变量。

Q:如果设置为全局变量,并且加锁会怎么样?

A:依然不行。在本代码中,socket的accept函数和recv函数;语音模块的serialgetstring函数都会阻塞,这将导致阻塞时永远无法解锁,所以不能用锁。

3.3  关于人脸识别和OLED显示

人脸识别位于语音控制的线程中,如果说出“人脸识别”就会调用人脸识别的程序,同时还会调用OLED的程序来显示照片;而在火灾报警线程中每隔一段时间也会调用OLED的程序来显示温度和湿度。这就导致了:如果在人脸识别调用OLED程序创建PYobject的同时火灾报警线程也正好调用OLED程序来创建PYobject,这就会导致段错误。

为了避免段错误,设置一个互斥锁,使得人脸识别的过程中,暂时让火灾报警程序阻塞,这样就不会造成段错误,而且人脸识别通常只有几秒,所以不会过久的阻塞火灾报警程序,不会影响安全性。

并且,最重要的是,由于使用了锁,在语音控制线程里的cmd_handler里调用的read函数必须更改为非阻塞的模式!否则一旦在上锁后阻塞住就会造成死锁!

//将fd修改为非阻塞的方式int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);//然后再使用fd来read就不会阻塞了

④代码的编译&运行&关闭

 编译语句
gcc *.c -I /usr/include/python3.11/ -l python3.11 -lwiringPi -o smart_home
运行语句 
./smart_home
关闭程序方法
ps -ef|grep smart_home
kill 进程编号

相关文章:

基于树莓派实现 --- 智能家居

最效果展示 演示视频链接&#xff1a;基于树莓派实现的智能家居_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Tr421n7BM/?spm_id_from333.999.0.0 &#xff08;PS&#xff1a;房屋模型的搭建是靠纸板箱和淘宝买的家居模型&#xff0c;户型参考了留学时短租的公寓~&a…...

基于Arduino IDE 野火ESP8266模块 一键配网 的开发

一、配网介绍 ESP8266 一键配网&#xff08;也称为 SmartConfig 或 FastConfig&#xff09;是一种允许用户通过智能手机上的应用程序快速配置 ESP8266 Wi-Fi 模块的方法&#xff0c;而无需手动输入 SSID 和密码。为了实现这一功能&#xff0c;则需要一个支持 SmartConfig 的智能…...

左手医生:医疗 AI 企业的云原生提效降本之路

相信这样的经历对很多人来说并不陌生&#xff1a;为了能到更好的医院治病&#xff0c;不惜路途遥远奔波到大城市&#xff1b;或者只是看个小病&#xff0c;也得排上半天长队。这些由于医疗资源分配不均导致的就医问题已是老生长谈。 云计算、人工智能、大数据等技术的发展和融…...

ceph集群部署

1. 每台服务器各增加2块硬盘(类型最好是相同的) 2. 将三台主机名设为node1.openlab.edu、node2.openlab.edu、node3.openlab.edu 3. 登录所有主机&#xff0c;配置 /etc/hosts 文件 192.168.136.55 ceph1.openlab.edu ceph1 192.168.136.56 ceph2.openlab.edu ceph2 192.168…...

C#WPF控件Label宽度绑定到父控件的宽度

如何将Label的宽度绑定到它所在Grid的宽度。跟随父控件的宽度的改变而改变。 <Window x:Class="WpfApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&q…...

HMI的学习

什么是HMI&#xff1f;了解HMI或人机界面的一些基础知识_哔哩哔哩_bilibili Human Machine Interface 人机界面 在工业中使用HMI来控制和监视设备 常见的HMI是ATM机 通过屏幕和按钮来完成取款或存款 工业中&#xff0c;操作员或维护人员可以从HMI操作和监视设备。 它们可能…...

工业无线网关在汽车制造企业的应用效果和价值-天拓四方

随着智能制造的快速发展&#xff0c;工业无线网关作为关键通信设备&#xff0c;在提升生产效率、优化生产流程、实现设备间的互联互通等方面发挥着越来越重要的作用。以下是一个关于工业无线网关在智能制造行业应用的具体案例&#xff0c;展示了其在实际生产中的应用效果和价值…...

校园app开发流程-uniapp开发-支持APP小程序H5-源码交付-跑腿-二手市场-交友论坛等功能,学校自由选择!

随着科技的不断发展&#xff0c;智慧校园系统和跑腿外卖小程序已经成为当今社会的热门话题。作为未来的重要趋势之一&#xff0c;科技在教育领域中的应用越来越广泛。本文将探讨智慧校园系统和跑腿外卖小程序的开发过程&#xff0c;并阐述如何利用科技“育”见未来 一、智慧校…...

Machine Learning机器学习之K近邻算法(K-Nearest Neighbors,KNN)

目录 前言 背景介绍&#xff1a; 思想&#xff1a; 原理&#xff1a; KNN算法关键问题 一、构建KNN算法 总结&#xff1a; 博主介绍&#xff1a;✌专注于前后端、机器学习、人工智能应用领域开发的优质创作者、秉着互联网精神开源贡献精神&#xff0c;答疑解惑、坚持优质作品共…...

四、在数据库里建库

一、查库 ##1&#xff09;库:一个库就是一个excell文档&#xff0c;库里含有表,一个表就是一个excell的sheet. ##2&#xff09;查看数据库实例中有哪些库 MariaDB [(none)]> show databases; -------------------- | Database | -------------------- | informat…...

蓝桥杯-网络安全比赛(2)基础学习-正则表达式匹配电话号码、HTTP网址、IP地址、密码校验

正则表达式&#xff08;Regular Expression&#xff09;&#xff1a;定义&#xff1a;一种强大的文本处理工具&#xff0c;用于描述、匹配和查找字符串中的特定模式。应用&#xff1a;密码验证、文本搜索和替换、数据清洗等。特点&#xff1a;通过特定的元字符和规则来构建复杂…...

如何创建azure pipeline

Azure Pipelines是一种持续集成和持续交付&#xff08;CI/CD&#xff09;工具&#xff0c;可以帮助开发团队自动化构建、测试和部署应用程序。以下是创建Azure Pipeline的步骤&#xff1a; 登录到Azure DevOps&#xff08;https://dev.azure.com/&#xff09;。在Azure DevOps…...

缓存菜品、套餐、购物车相关功能

一、缓存菜品 通过缓存的方式提高查询性能 1.1问题说明 大量的用户访问导致数据库访问压力增大&#xff0c;造成系统响应慢&#xff0c;用户体验差 1.2 实现思路 优先查询缓存&#xff0c;如果缓存没有再去查询数据库&#xff0c;然后载入缓存 将菜品集合序列化后缓存入red…...

微信小程序的页面交互1

一、page&#xff08;&#xff09;函数 每个页面的s代码全部写入对应的js文件的page&#xff08;&#xff09;函数里面。点击编译&#xff0c;就可以显示js代码的运行效果。注意&#xff0c;每个页面的page&#xff08;&#xff09;函数是唯一的。 page&#xff08;&#xff…...

win10 docker zookeeper和kafka搭建

好久没用参与大数据之类的开发了&#xff0c;近日接触到一个项目中使用到kafka&#xff0c;因此要在本地搭建一个简易的kafka服务。时间比较紧急&#xff0c;之前有使用docker的经验&#xff0c;因此本次就使用docker来完成搭建。在搭建过程中出现的一些问题&#xff0c;及时记…...

【Redis】快速入门 数据类型 常用指令 在Java中操作Redis

文章目录 一、简介二、特点三、下载与安装四、使用4.1 服务器启动4.2 客户端连接命令4.3 修改Redis配置文件4.4 客户端图形化界面 五、数据类型5.1 五种常用数据类型介绍5.2 各种数据类型特点 六、常用命令6.1 字符串操作命令6.2 哈希操作命令6.3 列表操作命令6.4 集合操作命令…...

【tingsboard开源平台】下载数据库,IDEA编译,项目登录

一&#xff0c; PostgreSQL 下载 需要看官网的&#xff1a;点此下载直达地址&#xff1a;点此进行相关学习&#xff1a;PostgreSQL 菜鸟教程 二&#xff0c;PostgreSQL 安装 点击安装包进行安装 出现乱码错误&#xff1a; There has been an error. Error running C:\Wind…...

Web3:探索区块链与物联网的融合

引言 随着科技的不断发展&#xff0c;区块链技术和物联网技术都成为了近年来备受瞩目的前沿技术。而当这两者结合在一起&#xff0c;将产生怎样的化学反应呢&#xff1f;本文将深入探讨Web3时代中区块链与物联网的融合&#xff0c;探索其意义、应用场景以及未来发展趋势。 1. …...

[BT]BUUCTF刷题第9天(3.27)

第9天&#xff08;共2题&#xff09; [护网杯 2018]easy_tornado 打开网站就是三个txt文件 /flag.txt flag in /fllllllllllllag/welcome.txt render/hints.txt md5(cookie_secretmd5(filename))当点进flag.txt时&#xff0c;url变为 http://b9e52e06-e591-46ad-953e-7e8c5f…...

html页面使用@for(){},@if(){},利用jquery 获取当前class在列表中的下标

基于以前的项目进行修改优化&#xff0c;前端代码根据List元素在html里进行遍历显示 原先的代码&#xff1a; 其中&#xff0c;noticeGuide.Id是标识noticeGuide的唯一值&#xff0c;但是不是从0开始的【是数据库自增字段】 但是在页面初始化加载的时候&#xff0c;我们只想…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...