最效果展示

演示视频链接:基于树莓派实现的智能家居_哔哩哔哩_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) ; // 10mS
	return 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 = 23
    SPI_PORT = 0
    SPI_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 = 23
    SPI_PORT = 0
    SPI_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 = 23
	SPI_PORT = 0
	SPI_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.width
	height = disp.height
	image = 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__H
 
void 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_facebody20191230

import 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 RuntimeOptions


def 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',
    # 访问的域名对应的region
    region_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 = streamA
    
    streamB = open(r'/home/pi/mjm_code/smart_home/face.png', 'rb') #待测试的照片
    compare_face_request.image_urlbobject = streamB


    try:
        # 初始化Client
        client = 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 score
    except 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是为了留出空间给\0
    memset(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__H
 
void 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 4


int dht_start()
{
	pinMode(dht, OUTPUT); //起始拉高电平
	digitalWrite(dht, 1); 
	delay(1000);			
		
	pinMode(dht, OUTPUT);  //拉低超过18ms
	digitalWrite(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 5


int 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,所以不用使用atoi
	inet_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 70

int 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){
			//accept
			conn_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 cmd
				if(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; //fifo
	char 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));
 
	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}
 
	//connect
	server_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");
	}
 
	//fifo
	if(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST)
	{
		perror("fifo");
	}
 
	//fork
	fork_return = fork();
 
	if(fork_return > 0){//father keeps writing msg
		while(1){
			//write
			memset(&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);
			}
 
			//read
			memset(&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 进程编号
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐