一、项目概述
•项目功能
通过语音控制客厅灯、卧室灯、风扇、人脸识别开门等,可以进行火灾险情监测,可以并且实现Sockect发送指令远程控制各类家电等
•项目描述
全志H616通过串口连接各模块硬件,检测语音的识别结果,分析语音识别的结果来对家电设备进行控制。摄像头拍摄到人脸之后通过阿里云平台人脸识别方案对比阿里云人脸数据库来进行人脸识别开锁。该项目采用设计模式中的简单工厂模式,让设备端和控制端都采用链表管理,提高了项目代码的稳定性和拓展性。
二、系统框图
三、硬件搭建
语音模块及人脸识别方案配置参考: 语音模块配置 人脸识别方案
四、软件框架
整个项目开启4个监听线程, 分别是:
1. 语音监听线程:用于监听语音指令, 当有语音指令过来后, 通过消息队列的方式给消息处理线程发送指令
2. 网络监听线程:用于监听网络指令,当有网络指令过来后, 通过消息队列的方式给消息处理线程发送指令
3. 火灾检测线程:当存在煤气泄漏或者火灾闲情时, 发送警报指令给消息处理线程
4. 消息监听线程: 用于处理以上3个线程发过来的指令,并根据指令要求配置GPIO引脚状态,OLED屏显示、语音播报,还有人脸识别开门
上述四个线程采用统一个对外接口接口,同时添加到监听链表中。
统一的监听模块接口如下:
struct control
{
char control_name[128]; //监听模块名称
int (*init)(void); //初始化函数
void (*final)(void);//结束释放函数
void *(*get)(void *arg);//监听函数,如语音监听
void *(*set)(void *arg); //设置函数,如语音播报
struct control *next;
};
struct control *add_device_to_ctrl_list(struct control *phead, struct control *device);
统一的设备类接口如下:
struct gdevice
{
char dev_name[128]; //设备名称
int key; //key值,用于匹配控制指令的值
int gpio_pin; //控制的gpio引脚
int gpio_mode; //输入输出模式
int gpio_status; //高低电平状态
int check_face_status; //是否进行人脸检测状态
int voice_set_status; //是否语音语音播报
struct gdevice *next;
};
struct gdevice *add_device_to_gdevice_list(struct gdevice *phead, struct gdevice *device);
struct gdevice *find_gdevice_by_key(struct gdevice *pdev, unsigned char key);
int set_gpio_gdevice_status(struct gdevice *pdev);
五、项目代码
本项目代码量较大,仅展示部分源代码,项目开源地址:https://gitee.com/GeekerGao/smart-home
(一)
监听模块接口加入链表
control.c
#include <stdio.h>
#include "control.h"
struct control *add_interface_to_control_list(struct control *phead,struct control *control_interface)
{
//头插法添加节点
if(NULL == phead)
{
phead = control_interface;
}
else
{
control_interface->next = phead;
phead = control_interface;
}
return phead;
};
control.h
#ifndef __CONTROL_H__
#define __CONTROL_H__
struct control
{
char control_name[128]; //监听模块名称
int (*init)(void); //初始化函数
void (*final)(void);//结束释放函数
void *(*get)(void *arg);//监听函数,如语音监听
void *(*set)(void *arg); //设置函数,如语音播报
struct control *next;
};
struct control *add_interface_to_control_list(struct control *phead,struct control *control_interface);
#endif
消息队列的接口
msg_queue.c
#include <stdio.h>
#include "msg_queue.h"
// 定义消息队列的名称
#define QUEQUE_NAME "/test_queue"
// 创建一个新的消息队列
mqd_t msg_queue_create(void)
{
// 初始化mqd_t变量mqd为-1,表示未成功打开消息队列
mqd_t mqd = -1;
// 定义并初始化消息队列的属性结构体
struct mq_attr attr;
// 设置消息队列属性:默认为阻塞模式
attr.mq_flags = 0;
// 消息队列最大消息数量为10
attr.mq_maxmsg = 10;
// 每条消息的最大字节数为256
attr.mq_msgsize = 256;
// 当前消息队列中消息数量初始化为0
attr.mq_curmsgs = 0;
// 使用mq_open函数创建消息队列,如果不存在则根据属性创建(O_CREAT),并设置读写权限(O_RDWR)
// 第三个参数是权限位,0666表示所有用户都有读写权限,实际权限还需参考umask值
// 第四个参数是指向消息队列属性的指针
mqd = mq_open(QUEQUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
// 打印消息队列描述符,用于调试
printf("%s | %s | %d:mqd = %d\n", __FILE__,__func__,__LINE__,mqd);
// 返回创建的消息队列描述符
return mqd;
}
// 清理并关闭消息队列
void msg_queue_fianl(mqd_t mqd)
{
// 如果消息队列描述符有效,则关闭消息队列
if(-1 != mqd)
mq_close(mqd);
// 删除消息队列
mq_unlink(QUEQUE_NAME);
// 保险起见,再次将mqd设为-1
mqd = -1;
}
// 向消息队列发送消息
int send_message(mqd_t mqd,void *msg,int msg_len)
{
int byte_send = -1;
// 使用mq_send函数发送消息到消息队列,第四个参数0为消息的优先级,默认情况下优先级不影响消息的发送顺序
byte_send = mq_send(mqd, (char *)msg, msg_len, 0);
// 返回发送的字节数,成功时应等于msg_len,失败时返回-1
return byte_send;
}
msg_queue.h
#ifndef __MSG_QUEUE_H__
#define __MSG_QUEUE_H__
#include <mqueue.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
mqd_t msg_queue_create(void);
void msg_queue_fianl(mqd_t mqd);
int send_message(mqd_t mqd,void *msg,int msg_len);
#endif
语音监听接口
global.h
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
typedef struct {
mqd_t mqd;
struct control *ctrl_phead;
}ctrl_info_t;
#endif
voice_interface.c
#include <stdio.h>
#include <pthread.h> // 线程库
#include "voice_interface.h" // 语音接口头文件
#include "uartTool.h" // 串口操作工具库
#include "msg_queue.h" // 消息队列操作库
#include "global.h" // 全局变量和宏定义头文件
// 串口文件描述符,初始设为-1
static int serial_fd = -1;
// 初始化语音模块
static int voice_init(void)
{
// 打开指定的串口设备,BAUD为波特率宏定义
serial_fd = myserialOpen(SERIAL_DEV, BAUD);
// 打印日志,显示串口文件描述符
printf("%s|%s|%d:serial_fd = %d\n", __FILE__, __FUNCTION__, __LINE__, serial_fd);
return serial_fd;
}
// 释放语音模块资源
static void voice_final(void)
{
if(-1 != serial_fd) // 如果串口已打开
{
// 关闭串口
close(serial_fd);
// 重置串口文件描述符为-1
serial_fd = -1;
}
}
// 线程函数,用于接收语音指令
static void *voice_get(void *arg)
{
unsigned char buffer[6] = {0}; // 初始化缓冲区
int len; // 接收长度
mqd_t mqd = -1; // 消息队列描述符
ctrl_info_t *ctrl_info = NULL; // 控制信息指针
// 检查传入参数并转换类型
if (NULL != arg)
ctrl_info = (ctrl_info_t *)arg;
// 如果串口未初始化,尝试初始化
if (-1 == serial_fd)
{
serial_fd = voice_init();
if (-1 == serial_fd) // 初始化失败则退出线程
pthread_exit(0);
}
// 获取消息队列描述符
if(NULL != ctrl_info)
mqd = ctrl_info->mqd;
// 若消息队列描述符无效,退出线程
if((mqd_t) -1 == mqd)
pthread_exit(0);
// 设置线程为分离状态,进程结束后自动清理资源
pthread_detach(pthread_self());
// 打印日志,表示线程启动
printf("%s thread start\n", __func__);
// 循环监听语音模块串口输出
while(1)
{
len = serialGetstring(serial_fd, buffer); // 读取串口数据
// 打印接收到的数据
printf("%s|%s|%d:数据内容...\n",__FILE__,__FUNCTION__,__LINE__);
printf("%s|%s|%d:接收到的字节数=%d\n",__FILE__,__FUNCTION__,__LINE__,len);
if(len > 0) // 如果有数据接收
{
// 校验数据头部和尾部
if (buffer[0] == 0xAA && buffer[1] == 0x55 && buffer[5] == 0xAA && buffer[4] == 0x55)
{
// 打印并发送有效数据到消息队列
printf("%s|%s|%d:有效数据,发送...\n",__FILE__,__FUNCTION__,__LINE__);
send_message(mqd, buffer, len);
}
// 清空缓冲区准备下一次接收
memset(buffer, 0, sizeof(buffer));
}
}
// 理论上不会执行到这里,但保持规范
pthread_exit(0);
}
// 线程函数,用于语音播报结果
static void *voice_set(void *arg)
{
pthread_detach(pthread_self()); // 设置线程为分离状态
unsigned char *buffer = (unsigned char*)arg; // 接收的缓冲区指针
// 串口未初始化则尝试初始化
if (-1 == serial_fd)
{
serial_fd = voice_init();
if (-1 == serial_fd)
pthread_exit(0); // 初始化失败则退出线程
}
// 如果有数据,发送回语音模块进行播报
if(NULL != buffer)
{
serialSendstring(serial_fd, buffer, 6); // 发送数据到串口
}
pthread_exit(0); // 结束线程
}
// 定义控制结构体,包含语音模块的初始化、清理、获取和设置函数
struct control voice_control = {
.control_name = "voice", // 控制名称
.init = voice_init, // 初始化函数指针
.final = voice_final, // 清理函数指针
.get = voice_get, // 获取函数指针(接收语音指令)
.set = voice_set, // 设置函数指针(语音播报)
.next = NULL // 下一个控制结构体指针,用于链表
};
// 将语音控制模块添加到控制列表的函数
struct control *add_voice_to_control_list(struct control *phead)
{
// 使用头插法将语音控制模块插入到控制列表中
return add_interface_to_control_list(phead, &voice_control);
}
voice_interface.h
#ifndef __VOICE_INTERFACE_H__
#define __VOICE_INTERFACE_H__
#include "control.h"
struct control *add_voice_to_control_list(struct control *phead);
#endif
Socket网络监听接口
socket_interface.c
#include <pthread.h> // 多线程库
#include "socket.h" // 自定义的套接字操作库
#include "control.h" // 控制接口相关定义
#include "socket_interface.h" // 套接字接口层
#include "msg_queue.h" // 消息队列操作库
#include "global.h" // 全局变量与常量定义
// 套接字文件描述符,初始化为-1
static int s_fd = -1;
// 初始化TCP套接字
static int tcpsocket_init(void)
{
// 使用自定义的socket初始化函数,IPADDR和IPPORT为全局定义的服务器IP地址和端口
s_fd = socket_init(IPADDR, IPPORT);
// 注意:这里返回-1可能是个笔误,应当根据实际情况调整返回值以反映成功或失败
return -1;
}
// 清理TCP套接字资源
static void tcpsocket_final(void)
{
// 关闭套接字文件描述符
close(s_fd);
// 重置文件描述符为-1
s_fd = -1;
}
// 线程函数,用于接收TCP连接并处理数据
static void* tcpsocket_get(void* arg)
{
int c_fd = -1; // 客户端连接的文件描述符
int ret; // 函数返回值
unsigned char buffer[BUF_SIZE]; // 缓冲区用于存储接收的数据
mqd_t mqd = -1; // 消息队列描述符
ctrl_info_t *ctrl_info = NULL; // 控制信息结构体指针
struct sockaddr_in c_addr; // 客户端地址结构
socklen_t clen = sizeof(struct sockaddr_in); // 地址长度
// 将当前线程设置为脱离线程,即线程结束后自动释放资源
pthread_detach(pthread_self());
// 打印当前套接字文件描述符状态
printf("%s|%s|%d:s_fd=%d\n", __FILE__, __func__, __LINE__, s_fd);
// 检查并初始化套接字
if(-1 == s_fd)
{
s_fd = tcpsocket_init();
if(-1 == s_fd)
{
// 初始化失败,打印错误信息并退出线程
printf("%s|%s|%d:init socket fail\n", __FILE__, __func__, __LINE__ );
pthread_exit(0);
}
}
// 获取消息队列描述符
if(NULL != arg)
ctrl_info = (ctrl_info_t *)arg;
if(NULL != ctrl_info)
mqd = ctrl_info->mqd;
if((mqd_t) -1 == mqd)
pthread_exit(0); // 消息队列描述符无效,退出线程
// 清零客户端地址结构
memset(&c_addr, 0, sizeof(struct sockaddr_in));
// 打印线程启动信息
printf("%s thread start\n",__func__);
// 循环等待接受客户端连接
while(1)
{
// 接受新的连接请求
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if(-1 == c_fd)
{
// 接受失败,继续循环
continue;
}
// 开启TCP KeepAlive功能并设置其参数
ret = setsockopt(c_fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
if(-1 == ret)
perror("setsockopt SO_KEEPALIVE");
ret = setsockopt(c_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
if(-1 == ret)
perror("setsockopt TCP_KEEPIDLE");
ret = setsockopt(c_fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
if(-1 == ret)
perror("setsockopt TCP_KEEPINTVL");
ret = setsockopt(c_fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
if(-1 == ret)
perror("setsockopt TCP_KEEPCNT");
// 打印已建立连接的客户端信息
printf("Accepted a connection from %s:%d\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
// 循环处理来自该客户端的数据
while(1)
{
// 清空接收缓冲区
memset(buffer, 0, BUF_SIZE);
// 接收数据
ret = recv(c_fd, buffer, BUF_SIZE, 0);
// 打印接收到的数据
printf("%s|%s|%d: Data received...\n",__FILE__,__func__,__LINE__);
if(ret > 0)
{
// 校验数据头尾,若匹配则发送至消息队列
if(buffer[0] == 0xAA && buffer[1] == 0x55 && buffer[5] == 0xAA && buffer[4] == 0x55)
{
printf("%s|%s|%d: Sending data to message queue...\n",__FILE__,__func__,__LINE__);
send_message(mqd, buffer, ret); // 发送数据到消息队列
}
}
else if(-1 == ret || 0 == ret)
{
// 接收失败或客户端断开连接,跳出内循环
break;
}
}
}
// 线程结束
pthread_exit(0);
}
// 定义控制结构体,包含TCP套接字的控制接口
struct control tcpsocket_control = {
.control_name = "tcpsocket", // 控制器名称
.init = tcpsocket_init, // 初始化函数指针
.final = tcpsocket_final, // 清理函数指针
.get = tcpsocket_get, // 获取数据函数指针
.set = NULL, // 设置数据函数指针(此处未定义)
.next = NULL // 指向下一个控制结构体的指针
};
// 将TCP套接字控制模块添加到控制列表中的函数
struct control *add_tcpsocket_to_control_list(struct control *phead)
{
// 使用头插法将TCP套接字控制模块添加到控制列表
return add_interface_to_control_list(phead, &tcpsocket_control);
}
socket_interface.h
#ifndef __SOCKET_INTERFACE_H__
#define __SOCKET_INTERFACE_H__
#include "control.h"
struct control *add_tcpsocket_to_control_list(struct control *phead);
#endif
烟雾报警监听接口
smoke_interface.c
#include <pthread.h> // 引入线程库
#include <wiringPi.h> // 引入WiringPi库,用于树莓派等硬件的GPIO操作
#include <stdio.h> // 标准输入输出库
#include "control.h" // 控制器接口定义
#include "smoke_interface.h" // 烟雾检测接口定义
#include "msg_queue.h" // 消息队列操作库
#include "global.h" // 全局变量和宏定义
// 烟雾传感器连接的GPIO引脚号
#define SMOKE_PIN 6
// 设置GPIO为输入模式
#define SMOKE_MODE INPUT
// 初始化烟雾传感器
static int smoke_init(void)
{
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__); // 打印函数调用信息
pinMode(SMOKE_PIN, SMOKE_MODE); // 设置SMOKE_PIN为输入模式
return 0; // 初始化成功返回0
}
// 烟雾传感器清理函数,当前无操作
static void smoke_final(void)
{
// 目前不需要执行任何操作
}
// 线程函数,用于持续监测烟雾传感器状态并发送消息
static void* smoke_get(void *arg)
{
int status = HIGH; // GPIO初始状态设为高电平
int switch_status = 0; // 状态切换标志,初始为未触发报警
unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA}; // 定义消息缓冲区,含特殊标记头尾
ssize_t byte_send = -1; // 发送消息的返回值
mqd_t mqd = -1; // 消息队列描述符
ctrl_info_t *ctrl_info = NULL; // 控制信息结构体指针
// 解析传入的参数
if (NULL != arg)
ctrl_info = (ctrl_info_t *)arg;
if(NULL != ctrl_info)
mqd = ctrl_info->mqd; // 获取消息队列描述符
if ((mqd_t)-1 == mqd)
pthread_exit(0); // 如果消息队列描述符无效,退出线程
// 将当前线程设置为脱离线程,线程结束时自动回收资源
pthread_detach(pthread_self());
printf("%s thread start\n", __func__); // 打印线程启动信息
// 持续监测烟雾传感器
while(1)
{
status = digitalRead(SMOKE_PIN); // 读取烟雾传感器状态
if (LOW == status) // 烟雾报警触发(低电平)
{
buffer[2] = 0x45; // 设定消息体,表示烟雾报警
buffer[3] = 0x00; // 具体信息,这里可能表示报警状态
switch_status = 1; // 更新状态为报警已发送
printf("%s|%s|%d: 发送烟雾报警消息...\n", __FILE__,__func__, __LINE__); // 打印消息发送记录
byte_send = mq_send(mqd, buffer, 6, 0); // 向消息队列发送报警消息
if (-1 == byte_send) // 发送失败则跳过本次循环
continue;
}
else if (HIGH == status && 1 == switch_status) // 烟雾恢复正常且之前已发送过报警
{
buffer[2] = 0x45; // 保留消息头
buffer[3] = 0x01; // 表示烟雾恢复正常
switch_status = 0; // 重置状态标志
printf("%s|%s|%d: 发送烟雾恢复消息...\n", __FILE__,__func__, __LINE__); // 打印消息发送记录
byte_send = mq_send(mqd, buffer, 6, 0); // 发送恢复消息到消息队列
if (-1 == byte_send) // 发送失败则跳过本次循环
continue;
}
// 每5秒检测一次,避免频繁读取
sleep(5);
}
pthread_exit(0); // 正常情况下不会执行到这,线程结束
}
// 定义烟雾控制结构体
struct control smoke_control = {
.control_name = "smoke", // 控制器名称
.init = smoke_init, // 初始化函数指针
.final = smoke_final, // 清理函数指针
.get = smoke_get, // 获取数据函数指针
.set = NULL, // 设置数据函数指针,这里未定义
.next = NULL // 指向下一个控制结构体的指针
};
// 向控制列表添加烟雾控制器的函数
struct control *add_smoke_to_control_list(struct control *phead)
{
// 使用头插法将烟雾控制模块添加到控制列表中
return add_interface_to_control_list(phead, &smoke_control);
}
smoke_interface.h
#ifndef ___SMOKE_INTERFACE_H___
#define ___SMOKE_INTERFACE_H___
#include "control.h"
struct control *add_smoke_to_control_list(struct control *phead);
#endif
消息接收处理监听接口
receive_interface.c
#include <stdio.h>
#include <pthread.h>
#include <mqueue.h>
#include <stdlib.h>
#include <wiringPi.h>
#include "ini.h"
#include "control.h"
#include "receive_interface.h"
#include "msg_queue.h"
#include "global.h"
#include "myoled.h"
#include "face.h"
#include "gdevice.h"
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
// 定义接收消息结构体
typedef struct {
int msg_len;
unsigned char *buffer;
ctrl_info_t *ctrl_info;
}recv_msg_t;
// 初始化设备链表头指针和OLED显示文件描述符
static int oled_fd = -1;
static struct gdevice *pdevhead = NULL;
// 配置文件解析回调函数,根据section和name匹配,填充gdevice结构体
static int handler_gdevice(void* user, const char* section, const char* name,const char* value)
{
struct gdevice *pdev = NULL;
if(NULL == pdevhead)
{
pdevhead = (struct gdevice *)malloc(sizeof(struct gdevice));
pdevhead ->next = NULL;
memset(pdevhead, 0, sizeof(struct gdevice));
strcpy(pdevhead->dev_name,section);
}
else if(0 != strcmp(section,pdevhead->dev_name))
{
pdev = (struct gdevice *)malloc(sizeof(struct gdevice));
memset(pdev, 0, sizeof(struct gdevice));
strcpy(pdev->dev_name,section);
pdev->next = pdevhead;
pdevhead = pdev;
}
if(NULL != pdevhead)
{
if(MATCH(pdevhead->dev_name,"key"))
{
sscanf(value,"%x",&pdevhead->key);
printf("%d|pdevhead->key = %x\n",__LINE__,pdevhead->key);
}
else if(MATCH(pdevhead->dev_name,"gpio_pin"))
{
pdevhead->gpio_pin = atoi(value);
}
else if(MATCH(pdevhead->dev_name,"gpio_mode"))
{
if(strcmp(value,"OUTPUT") == 0)
{
pdevhead->gpio_mode = OUTPUT;
}
else if(strcmp(value,"INPUT") == 0)
{
pdevhead->gpio_mode = INPUT;
}
}
else if(MATCH(pdevhead->dev_name,"gpio_status"))
{
if(strcmp(value,"LOW") == 0)
{
pdevhead->gpio_mode = LOW;
}
else if(strcmp(value,"HIGH") == 0)
{
pdevhead->gpio_mode = HIGH;
}
}
else if(MATCH(pdevhead->dev_name,"check_face_status"))
{
pdevhead->check_face_status = atoi(value);
}
else if(MATCH(pdevhead->dev_name,"voice_set_status"))
{
pdevhead->voice_set_status = atoi(value);
}
}
return 1;
}
// 初始化函数,加载配置文件、初始化OLED和面部识别模块
static int receive_init(void)
{
if (ini_parse("/etc/gdevice.ini", handler_gdevice, NULL) < 0)
{
printf("Can't load 'gdevice.ini'\n");
return 1;
}
oled_fd = myoled_init();
face_init();
return oled_fd;
}
// 清理函数,释放OLED资源
static void receive_final(void)
{
face_final();
if (oled_fd != -1)
{
close(oled_fd);
oled_fd = -1;
}
}
// 设备控制处理线程,根据消息内容控制设备、更新状态并显示到OLED
static void* handle_device(void *arg)
{
recv_msg_t *recv_msg = NULL;
struct gdevice *cur_gdev = NULL;
char success_or_fail[20] = "success";
int ret = -1;
pthread_t tid = -1;
int smoke_status = 0;
double face_result = 0.0;
pthread_detach(pthread_self());
if(NULL != arg)
{
recv_msg = (recv_msg_t *)arg;
printf("recv_msg->msg_len=%d\n",recv_msg->msg_len);
printf("%s|%s|%d:handle 0x%x, 0x%x,0x%x, 0x%x, 0x%x,0x%x\n",__FILE__, __func__, __LINE__, recv_msg->buffer[0], recv_msg->buffer[1], recv_msg->buffer[2], recv_msg->buffer[3],recv_msg->buffer[4],recv_msg->buffer[5]);
}
if(NULL != recv_msg && NULL != recv_msg->buffer)
{
cur_gdev = find_device_by_key(pdevhead,recv_msg->buffer[2]);
}
if(NULL != cur_gdev)
{
cur_gdev->gpio_status = recv_msg->buffer[3] == 0 ? LOW : HIGH;
//special for lock
if(1 == cur_gdev->check_face_status)
{
face_result = face_category();
if(face_result > 0.6)
{
ret = set_gpio_gdevice_status(cur_gdev);
recv_msg->buffer[2] = 0x47;
}
else
{
recv_msg->buffer[2] = 0x46;
ret = -1;
}
}
else if(0 == cur_gdev->check_face_status)
{
ret = set_gpio_gdevice_status(cur_gdev);
}
if(1 == cur_gdev->voice_set_status)
{
if(NULL != recv_msg && NULL != recv_msg->ctrl_info && NULL != recv_msg->ctrl_info->ctrl_phead)
{
struct control *pcontrol = recv_msg->ctrl_info->ctrl_phead;
while(NULL != pcontrol)
{
if(strstr(pcontrol->control_name,"voice"))
{
if(0x45 == recv_msg->buffer[2] && 0x00 == recv_msg->buffer[3])
{
smoke_status = 1;
}
pthread_create(&tid,NULL,pcontrol->set,(void *)recv_msg->buffer);
break;
}
pcontrol = pcontrol->next;
}
}
}
if(-1 == ret)
{
memset(success_or_fail,'\0',sizeof(success_or_fail));
strncpy(success_or_fail,"failed",6);
}
//OLED 显示
char oled_msg[512];
memset(oled_msg,0,sizeof(oled_msg));
char *change_status = cur_gdev->gpio_status == LOW ? "Open" : "Close";
sprintf(oled_msg,"%s %s %s!\n",change_status,cur_gdev->dev_name,success_or_fail);
//special for smoke
if(1 == smoke_status)
{
memset(oled_msg,0,sizeof(oled_msg));
strcpy(oled_msg,"Fire Detected!\n");
}
oled_show(oled_msg);
//special for lock ,close lock
if(1 == cur_gdev->check_face_status && 0 == ret && face_result > 0.6)
{
sleep(5);
cur_gdev->gpio_status = HIGH;
set_gpio_gdevice_status(cur_gdev);
}
}
pthread_exit(0);
}
// 消息队列监听线程,循环接收消息并分发给handle_device处理
static void* receive_get(void *arg)
{
recv_msg_t *recv_msg = NULL;
ssize_t receive_len = -1;
pthread_t tid = -1;
char *buffer = NULL;
struct mq_attr attr;
if(NULL != arg)
{
recv_msg = (recv_msg_t *)malloc(sizeof(recv_msg_t));
recv_msg->ctrl_info = (ctrl_info_t *)arg; //获取到mqd 和 (struct control)链表的头节点
recv_msg->msg_len = -1;
recv_msg->buffer = NULL;
}
else
{
pthread_exit(0);
}
if (mq_getattr(recv_msg->ctrl_info->mqd,&attr) == -1)
{
pthread_exit(0);
}
recv_msg->buffer = (unsigned char *)malloc(attr.mq_msgsize);
buffer = (unsigned char *)malloc(attr.mq_msgsize);
memset(recv_msg->buffer,0,attr.mq_msgsize);
memset(buffer,0,attr.mq_msgsize);
pthread_detach(pthread_self());
while(1)
{
receive_len = mq_receive(recv_msg->ctrl_info->mqd,buffer,attr.mq_msgsize,NULL);
printf("%s|%s|%d:send 0x%x, 0x%x,0x%x, 0x%x, 0x%x,0x%x\n",__FILE__, __func__, __LINE__, buffer[0], buffer[1], buffer[2], buffer[3],buffer[4],buffer[5]);
printf("%s|%s|%d:receive_len=%ld\n",__FILE__, __func__, __LINE__, receive_len);
if(-1 == receive_len)
{
if(errno == EAGAIN)
{
printf("queue empty\n");
}
else
{
break;
}
}
else if(buffer[0] == 0xAA && buffer[1] == 0x55 && buffer[5] == 0xAA && buffer[4] == 0x55)
{
recv_msg->msg_len = receive_len;
memcpy(recv_msg->buffer,buffer,receive_len);
pthread_create(&tid,NULL,handle_device,(void *)recv_msg);
}
}
pthread_exit(0);
}
// 定义控制接口结构体实例,描述receive接口行为
struct control receive_control = {
.control_name = "receive",
.init = receive_init,
.final = receive_final,
.get = receive_get,
.set = NULL,
.next = NULL
};
// 向控制接口链表添加receive_control接口
struct control *add_receive_to_control_list(struct control *phead)
{
// 使用头插法将receive_control添加到控制接口链表中
return add_interface_to_control_list(phead,&receive_control);
}
receive_interface.h
#ifndef __RECEIVE_INTERFACE_H__
#define __RECEIVE_INTERFACE_H__
#include "control.h"
struct control *add_receive_to_control_list(struct control *phead);
#endif
(二)
设备类节点直接通过ini文件配置
gdevice.ini
[lv led] //客厅灯
key=0x41
gpio_pin=2
gpio_mode=OUTPUT
gpio_status=HIGH
check_face_status=0
voice_set_status=0
[br led] //卧室灯
key=0x42
gpio_pin=5
gpio_mode=OUTPUT
gpio_status=HIGH
check_face_status=0
voice_set_status=0
[fan] //风扇
key=0x43
gpio_pin=7
gpio_mode=OUTPUT
gpio_status=HIGH
check_face_status=0
voice_set_status=0
[lock] //锁
key=0x44
gpio_pin=8
gpio_mode=OUTPUT
gpio_status=HIGH
check_face_status=1
voice_set_status=1
[beep] //蜂鸣器(烟雾报警器)
key=0x45
gpio_pin=9
gpio_mode=OUTPUT
gpio_status=HIGH
check_face_status=0
voice_set_status=1
然后,下载libinih1源代码
apt source libinih-dev
再将libinih-53目录中的ini.c和ini.h拷贝到项目工程中,同时移除设备类信息文件
pg@pg-Default-string:~/libinih-53$ cp ini.h ../smarthome/inc/
pg@pg-Default-string:~/libinih-53$ cp ini.c ../smarthome/src/
gdevice.c
#include "gdevice.h"
// 根据给定的键值查找设备链表中的设备
struct gdevice *find_device_by_key(struct gdevice *pgdevhead, int key)
{
struct gdevice *p = pgdevhead;
// 如果链表为空,则直接返回NULL
if(NULL == pgdevhead)
{
return NULL;
}
// 从链表头开始遍历
p = pgdevhead;
// 遍历链表直到找到键值匹配的设备或遍历完链表
while(NULL != p)
{
if(p->key == key)
{
// 找到匹配的设备,返回该设备指针
return p;
}
p = p->next; // 移动到下一个设备
}
// 遍历完未找到,返回NULL
return NULL;
}
// 设置设备的GPIO状态
int set_gpio_gdevice_status(struct gdevice *pgdev)
{
// 首先检查设备指针是否有效
if(NULL == pgdev)
{
return -1;
}
// 如果设备的GPIO引脚已设定
if(-1 != pgdev->gpio_pin)
{
// 并且如果设置了引脚的模式(输入/输出)
if(-1 != pgdev->gpio_mode)
{
pinMode(pgdev->gpio_pin, pgdev->gpio_mode); // 配置GPIO引脚的模式
}
// 如果设备的GPIO状态已设定
if(-1 != pgdev->gpio_status)
{
digitalWrite(pgdev->gpio_pin, pgdev->gpio_status); // 设置GPIO引脚的状态(高/低)
}
}
// 成功完成设置返回0
return 0;
}
gdevice.h
#ifndef __GDEVICE_H__
#define __GDEVICE_H__
#include <stdio.h>
#include <wiringPi.h>
struct gdevice
{
char dev_name[128]; //设备名称
int key; //key值,用于匹配控制指令的值
int gpio_pin; //控制的gpio引脚
int gpio_mode; //输入输出模式
int gpio_status; //高低电平状态
int check_face_status; //是否进行人脸检测状态
int voice_set_status; //是否语音语音播报
struct gdevice *next;
};
struct gdevice *find_device_by_key(struct gdevice *pgdevhead,int key);
int set_gpio_gdevice_status(struct gdevice *pgdev);
#endif
(三)
主函数
main.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <wiringPi.h>
#include "voice_interface.h"
#include "socket_interface.h"
#include "smoke_interface.h"
#include "receive_interface.h"
#include "msg_queue.h"
#include "control.h"
#include "global.h"
int main(int argc, char *argv[])
{
pthread_t thread_id;
struct control *control_phead = NULL;
struct control *pointer = NULL;
ctrl_info_t *ctrl_info = NULL;
ctrl_info = (ctrl_info_t *)malloc(sizeof(ctrl_info_t));
ctrl_info->ctrl_phead = NULL;
ctrl_info->mqd = -1;
int node_num = 0;
if(-1 == wiringPiSetup())
{
return -1;
}
ctrl_info->mqd = msg_queue_create();
if(ctrl_info->mqd == -1)
{
printf("%s|%s|%d,ctrl_info->mqd=%d\n",__FILE__,__func__,__LINE__,ctrl_info->mqd);
return -1;
}
ctrl_info->ctrl_phead = add_voice_to_control_list(ctrl_info->ctrl_phead);
ctrl_info->ctrl_phead = add_tcpsocket_to_control_list(ctrl_info->ctrl_phead);
ctrl_info->ctrl_phead = add_smoke_to_control_list(ctrl_info->ctrl_phead);
ctrl_info->ctrl_phead = add_receive_to_control_list(ctrl_info->ctrl_phead);
//init
pointer = ctrl_info->ctrl_phead;
while(NULL != pointer)
{
if(NULL != pointer->init)
{
pointer->init();
}
pointer = pointer->next;
node_num++;
}
pthread_t *tid = malloc(sizeof(int) * node_num);
//get
pointer = ctrl_info->ctrl_phead;
for(int i = 0; i < node_num; i++)
{
if(NULL != pointer->get)
{
pthread_create(&tid[i], NULL,(void *)pointer->get, (void *)ctrl_info);
}
pointer = pointer->next;
}
//join
for(int i = 0; i < node_num; i++)
{
pthread_join(tid[i],NULL);
}
//final
for(int i = 0; i < node_num; i++)
{
if(NULL != pointer->final)
pointer->final();
pointer = pointer->next;
}
msg_queue_fianl(ctrl_info->mqd);
return 0;
}
Makefile
# 设置编译器为针对aarch64架构的GCC
CC := aarch64-linux-gnu-gcc
# 自动查找源代码目录下的所有.c文件
SRC := $(shell find src -name "*.c")
# 指定头文件搜索路径
INC := ./inc \
./3rd/usr/local/include \
./3rd/usr/include \
./3rd/usr/include/python3.10 \
./3rd/usr/include/aarch64-linux-gnu/python3.10 \
./3rd/usr/include/aarch64-linux-gnu
# 将源文件路径转换为对象文件路径,同时将.c扩展名替换为.o
OBJ := $(subst src/,obj/,$(SRC:.c=.o))
# 目标可执行文件
TARGET=obj/smarthome
# 编译选项,包含指定的所有头文件路径
CFLAGS := $(foreach item,$(INC),-I$(item))
# 库文件路径
LIBS_PATH := ./3rd/usr/local/lib \
./3rd/lib/aarch64-linux-gnu \
./3rd/usr/lib/aarch64-linux-gnu \
./3rd/usr/lib/python3.10/
# 链接选项,指定库文件路径
LDFLAGS := $(foreach item,$(LIBS_PATH),-L$(item))
# 需要链接的库
LIBS := -lwiringPi -lpython3.10 -pthread -lexpat -lz -lcrypt
# 规则:如何从.c文件生成对应.o文件
obj/%.o:src/%.c
# 确保obj目录存在
mkdir -p obj
# 编译生成.o文件
$(CC) -o $@ -c $< $(CFLAGS)
# 规则:如何生成最终的可执行文件
$(TARGET) : $(OBJ)
# 链接生成可执行文件
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS)
# 将生成的可执行文件、face.py和配置文件复制到远程服务器
scp obj/smarthome src/face.py ini/gdevice.ini orangepi@192.168.0.113:/home/orangepi/smarthome/
# 编译目标
compile: $(TARGET)
# 清理规则,删除可执行文件及中间文件
clean:
rm -rf $(TARGET) obj $(OBJ)
# 打印调试信息
debug:
echo $(CC) # 打印使用的编译器
echo $(SRC) # 打印所有源文件路径
echo $(INC) # 打印头文件包含路径
echo $(OBJ) # 打印所有目标文件路径
echo $(TARGET) # 打印目标可执行文件名
echo $(CFLAGS) # 打印编译选项
echo $(LDFLAGS) # 打印链接选项
echo $(LIBS) # 打印链接库
# 标记,表示clean和compile总是需要执行
.PHONY: clean compile debug
六、项目实现
(实现视频正在剪辑ing)