一、项目整体设计
二、项目代码的前期准备
三、实现语音监听接口
四、实现socket监听接口
五、实现烟雾报警监听接口
六、实现设备节点代码
七、实现接收消息处理接口
一、项目整体设计
整体的软件框架大致如下:
整个项目开启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);
二、项目代码的前期准备
之前讲过智能分类的项目,因为会用到语音模块、OLED显示、网络模块、这些代码都可以从智能分类的项目中直接拷贝过来使用,另外添加之前准备好的人脸识别的代码 。 另外根据《项目整体设计》。再定义gdevice.h和control.h的头文件。整个目录结构如下:
pg@pg-Default-string:~/smarthome$ tree -I 3rd/ #3rd目录直接从garbage工程拷贝过来, 主要是一些依赖库和头文件, 这里就不显示
.
├── inc
│ ├── control.h
│ ├── face.h
│ ├── gdevice.h
│ ├── myoled.h
│ ├── socket.h
│ └── uartTool.h
├── Makefile
└── src
├── face.c
├── face.py
├── myoled.c
├── socket.c
└── uartTool.c
其中 control.h代码如下:
#ifndef __CONTROL__H
#define __CONTROL__H
#include <stdlib.h>
struct control
{
char control_name[128];
int (*init)(void);
void (*final)(void);
void *(*get)(void *arg);
void *(*set)(void *arg);
struct control *next;
};
//头插法,用于control类链表的创建
struct control *add_device_to_ctrl_list(struct control *phead, struct control *device);
#endif
// /dev/ttyS5 115200 ip port buffer pin /dev/I2C-3
control.c 代码如下:
#include "control.h"
//头插法
struct control *add_device_to_ctrl_list(struct control *phead, struct control *device)
{
struct control *pcontrol;
if(NULL == phead){
pcontrol = device;
return pcontrol;
}else{
device->next = phead;
phead = device;
return phead;
}
}
gdevice.h 代码如下:
#ifndef __GDEVICE_H
#define __GDEVICE_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;
};
#endif
gdevice.c 代码如下:
#include <wiringPi.h>
#include "gdevice.h"
//根据key值(buffer[2])查找设备节点
struct gdevice *find_gdevice_by_key(struct gdevice *pdev, unsigned char key)
{
struct gdevice *p = NULL;
if (NULL == pdev)
{
return NULL;
}
p = pdev;
while (NULL != p)
{
if(p->key == key)
{
return p;
}
p = p->next;
}
return NULL;
}
//设置GPIO引脚状态,输入输出和高低电平
int set_gpio_gdevice_status(struct gdevice *pdev)
{
if (NULL == pdev)
{
return -1;
}
if (-1 != pdev->gpio_pin)
{
if (-1 != pdev->gpio_mode)
{
pinMode(pdev->gpio_pin, pdev->gpio_mode);
}
if (-1 != pdev->gpio_status)
{
digitalWrite(pdev->gpio_pin, pdev->gpio_status);
}
}
return 0;
}
//链表头插法
struct gdevice *add_device_to_gdevice_list(struct gdevice *phead, struct gdevice *device)
{
struct gdevice *pgdevice;
if(NULL == phead){
pgdevice = device;
return pgdevice;
}else{
device->next = phead;
phead = device;
return phead;
}
}
Makefile 修改后内容如下:
CC := aarch64-linux-gnu-gcc
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
OBJ := $(subst src/,obj/,$(SRC:.c=.o))
TARGET=obj/smarthome
CFLAGS := $(foreach item, $(INC),-I$(item)) # -I./inc -I./3rd/usr/local/include
LIBS_PATH := ./3rd/usr/local/lib \
./3rd/lib/aarch64-linux-gnu \
./3rd/usr/lib/aarch64-linux-gnu \
./3rd/usr/lib/python3.10 \
#L
LDFLAGS := $(foreach item, $(LIBS_PATH),-L$(item)) # -L./3rd/usr/local/libs
LIBS := -lwiringPi -lpython3.10 -pthread -lexpat -lz -lcrypt
obj/%.o:src/%.c
mkdir -p obj
$(CC) -o $@ -c $< $(CFLAGS)
$(TARGET) :$(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS)
compile : $(TARGET)
clean:
rm $(TARGET) obj $(OBJ) -rf
debug:
echo $(CC)
echo $(SRC)
echo $(INC)
echo $(OBJ)
echo $(TARGET)
echo $(CFLAGS)
echo $(LDFLAGS)
echo $(LIBS)
.PHONY: clean compile debug
三、实现语音监听接口
语音监听模块会借助消息队列进行消息的传递,因此先实现消息队列的接口 msg_queque.c:
#include <stdio.h>
#include "msg_queue.h"
#define QUEQUE_NAME "/mq_queue"
mqd_t msg_queue_create(void)
{
//创建消息队列
mqd_t mqd = -1;
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 256;
attr.mq_curmsgs = 0;
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_final(mqd_t mqd)
{
if (-1 != mqd){
mq_close(mqd);
mq_unlink(QUEQUE_NAME);
mqd = -1;
}
}
int send_message(mqd_t mqd, void *msg, int msg_len)
{
int byte_send = -1;
byte_send = mq_send(mqd, (char *)msg, msg_len, 0);
return byte_send;
}
msg_queue.h 头文件定义:
#ifndef __MSG_QUEQUE_H
#define __MSG_QUEQUE_H
#include <mqueue.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
mqd_t msg_queue_create(void);
void msg_queue_final(mqd_t mqd);
int send_message(mqd_t mqd, void *msg, int msg_len);
#endif
根据control.h头文件的定义,实现语音监听接口
首先定义全局变量用于mqd句柄和struct control链表的传递 global.h 代码:
#ifndef __GLOBAL__H
#define __GLOBAL__H
typedef struct {
mqd_t mqd;
struct control *ctrl_phead;
}ctrl_info_t;
#endif
紧接着语音监听接口 voice_interface.c 代码:
#if 0
struct control
{
char control_name[128]; //监听模块名称
int (*init)(void); //初始化函数
void (*final)(void);//结束释放函数
void *(*get)(void *arg);//监听函数,如语音监听
void *(*set)(void *arg); //设置函数,如语音播报
struct control *next;
};
#endif
#include <pthread.h>
#include <stdio.h>
#include "voice_interface.h"
#include "uartTool.h"
#include "msg_queue.h"
#include "global.h"
static int serial_fd = -1;
static int voice_init(void)
{
serial_fd = myserialOpen (SERIAL_DEV, BAUD);
printf("%s|%s|%d:serial_fd=%d\n", __FILE__, __func__, __LINE__, serial_fd);
return serial_fd;
}
static void voice_final(void)
{
if (-1 != serial_fd)
{
close(serial_fd);
serial_fd = -1;
}
}
//接收语音指令
static void *voice_get(void *arg) // mqd应该来自于arg传参
{
unsigned char buffer[6] = {0x00, 0x00, 0x00, 0x00, 0X00, 0x00};
int len = 0;
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: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:len=%d\n", __FILE__, __func__, __LINE__, len);
if (len > 0)
{
if(buffer[0] == 0xAA && buffer[1] == 0x55
&& buffer[5] == 0xAA && buffer[4] == 0x55)
{
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]);
send_message(mqd, buffer, len);//注意,不要用strlen去计算实际的长度
}
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_ctrl_list(struct control *phead)
{//头插法
return add_interface_to_ctrl_list(phead, &voice_control);
};
voice_interface.h代码:
#ifndef ___VOICE_INTERFACE_H___
#define ___VOICE_INTERFACE_H___
#include "control.h"
struct control *add_voice_to_ctrl_list(struct control *phead);
#endif
四、实现socket监听接口
参考voice接口实现socket 接口socket_interface.c代码:
#include <pthread.h>
#include "socket.h"
#include "control.h"
#include "socket_interface.h"
#include "msg_queue.h"
#include "global.h"
static int s_fd = -1;
static int tcpsocket_init(void)
{
s_fd = socket_init(IPADDR, IPPORT);
return -1;
}
static void tcpsocket_final(void)
{
close(s_fd);
s_fd = -1;
}
static void* tcpsocket_get(void *arg)
{
int c_fd = -1;
int ret = -1;
struct sockaddr_in c_addr;
unsigned char buffer[BUF_SIZE];
mqd_t mqd = -1;
ctrl_info_t *ctrl_info= NULL;
int keepalive = 1; // 开启TCP_KEEPALIVE选项
int keepidle = 10; // 设置探测时间间隔为10秒
int keepinterval = 5; // 设置探测包发送间隔为5秒
int keepcount = 3; // 设置探测包发送次数为3次
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("tcpsocket_init failed\n");
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));
//4. accept
int clen = 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;
}
ret = setsockopt(c_fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)); // 设置TCP_KEEPALIVE选项
if (ret == -1) { // 如果设置失败,打印错误信息并跳出循环
perror("setsockopt");
break;
}
ret = setsockopt(c_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)); // 设置探测时间间隔选项
if (ret == -1) { // 如果设置失败,打印错误信息并跳出循环
perror("setsockopt");
break;
}
ret = setsockopt(c_fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepinterval,
sizeof(keepinterval)); // 设置探测包发送间隔选项
if (ret == -1) { // 如果设置失败,打印错误信息并跳出循环
perror("setsockopt");
break;
}
ret = setsockopt(c_fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcount,
sizeof(keepcount)); // 设置探测包发送次数选项
if (ret == -1) { // 如果设置失败,打印错误信息并跳出循环
perror("setsockopt");
break;
}
printf("Accepted a connection from %s:%d\n", inet_ntoa(c_addr.sin_addr),
ntohs(c_addr.sin_port)); // 打印客户端的IP地址和端口号
while (1)
{
memset(buffer, 0, BUF_SIZE);
ret = recv(c_fd, buffer, BUF_SIZE, 0);
printf("%s|%s|%d: 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]);
if (ret > 0)
{
if(buffer[0] == 0xAA && buffer[1] == 0x55
&& buffer[5] == 0xAA && buffer[4] == 0x55)
{
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]);
send_message(mqd, buffer, ret);//注意,不要用strlen去计算实际的长度
}
}
else if ( -1 == ret || 0 == ret)
{
break;
}
}
}
pthread_exit(0);
}
struct control tcpsocket_control = {
.control_name = "tcpsocket",
.init = tcpsocket_init,
.final = tcpsocket_final,
.get = tcpsocket_get,
.set = NULL,
.next = NULL
};
struct control *add_tcpsocket_to_ctrl_list(struct control *phead)
{//头插法
return add_interface_to_ctrl_list(phead, &tcpsocket_control);
};
socket.h 代码:
#ifndef ___SOCKET_INTERFACE_H___
#define ___SOCKET_INTERFACE_H___
#include "control.h"
struct control *add_tcpsocket_to_ctrl_list(struct control *phead);
#endif
五、实现烟雾报警监听接口
同样参考voice接口实现smoke 接口smoke_interface.c代码:
#include <pthread.h>
#include <wiringPi.h>
#include <stdio.h>
#include "control.h"
#include "smoke_interface.h"
#include "msg_queue.h"
#include "global.h"
#define SMOKE_PIN 6
#define SMOKE_MODE INPUT
static int smoke_init(void)
{
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
pinMode(SMOKE_PIN, SMOKE_MODE);
return 0;
}
static void smoke_final(void)
{
//do nothing;
}
static void* smoke_get(void *arg)
{
int status = HIGH;
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: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]);
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: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]);
byte_send = mq_send(mqd, buffer, 6, 0);
if (-1 == byte_send)
{
continue;
}
}
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_ctrl_list(struct control *phead)
{//头插法
return add_interface_to_ctrl_list(phead, &smoke_control);
};
smoke_interface.h 代码:
#ifndef ___SMOKE_INTERFACE_H___
#define ___SMOKE_INTERFACE_H___
#include "control.h"
struct control *add_smoke_to_ctrl_list(struct control *phead);
#endif
六、实现设备节点代码
- 客厅灯设备节点
由于消息接收处理线程需要处理各设备类外设,因此先根据gdevice.h定义,实现客厅灯设备节点代码
lrled_gdevice.c:
#include "gdevice.h"
struct gdevice lrled_gdev = {
.dev_name = "LV led",
.key = 0x41,
.gpio_pin = 2,
.gpio_mode = OUTPUT,
.gpio_status = HIGH,
.check_face_status = 0,
.voice_set_status = 0,
};
struct gdevice *add_lrled_to_gdevice_list(struct gdevice *pgdevhead)
{//头插法
return add_device_to_gdevice_list(pgdevhead, &lrled_gdev);
};
lrled_gdevice.h 代码如下:
#ifndef __LRLED_GDEVICE_H
#define __LRLED_GDEVICE_H
struct gdevice *add_lrled_to_gdevice_list(struct gdevice *pgdevhead);
#endif
- 卧室灯设备节点
卧室灯设备节点代码bled_gdevice.c:
#include "gdevice.h"
struct gdevice bled_gdev = {
.dev_name = "BR led",
.key = 0x42,
.gpio_pin = 5,
.gpio_mode = OUTPUT,
.gpio_status = HIGH,
.check_face_status = 0,
.voice_set_status = 0,
};
struct gdevice *add_bled_to_gdevice_list(struct gdevice *pgdevhead)
{//头插法
return add_device_to_gdevice_list(pgdevhead, &bled_gdev);
};
bled_gdevice.h 代码如下:
#ifndef __BLED_GDEVICE_H
#define __BLED_GDEVICE_H
struct gdevice *add_bled_to_gdevice_list(struct gdevice *pgdevhead);
#endif
- 实现风扇设备节点代码
实现风扇设备节点代码fan_gdevice.c:
#include "gdevice.h"
struct gdevice gdevice_fan = {
.dev_name = "fan",
.key = 0x43,
.gpio_pin = 7,
.gpio_mode = OUTPUT,
.gpio_status = LOW,
.check_face_status = 0,
.voice_set_status = 0,
.next = NULL
};
struct gdevice *add_fan_to_gdevice_list(struct gdevice *phead)
{
return add_device_to_gdevice_list(phead, &gdevice_fan);
}
fan_gdevice.h 代码如下:
#ifndef __FAN_GDEVICE_H
#define __FAN_GDEVICE_H
struct gdevice *add_fan_to_gdevice_list(struct gdevice *pgdevhead);
#endif
- 蜂鸣器设备节点
蜂鸣器设备节点代码beep_gdevice.c:
#include "gdevice.h"
struct gdevice beep_gdev = {
.dev_name = "beep",
.key = 0x45,
.gpio_pin = 9,
.gpio_mode = OUTPUT,
.gpio_status = HIGH,
.check_face_status = 0,
.voice_set_status = 1,
};
struct gdevice *add_beep_to_gdevice_list(struct gdevice *pgdevhead)
{//头插法
return add_device_to_gdevice_list(pgdevhead, &beep_gdev);
};
beep_gdevice.h 代码如下:
#ifndef __BEEP_GDEVICE_H
#define __BEEP_GDEVICE_H
struct gdevice *add_beep_to_gdevice_list(struct gdevice *pgdevhead);
#endif
七、实现接收消息处理接口
同样参考voice接口实现receive 接口receive_interface.c代码:
#include "gdevice.h"
struct gdevice lock_gdev = {
.dev_name = "lock",
.key = 0x44,
.gpio_pin = 8,
.gpio_mode = OUTPUT,
.gpio_status = HIGH,
.check_face_status = 1,
.voice_set_status = 1,
};
struct gdevice *add_lock_to_gdevice_list(struct gdevice *pgdevhead)
{//头插法
return add_device_to_gdevice_list(pgdevhead, &lock_gdev);
};
receive.h 头文件代码:
#ifndef ___RECEIVE_INTERFACE_H___
#define ___RECEIVE_INTERFACE_H___
#include "control.h"
struct control *add_receive_to_ctrl_list(struct control *phead);
#endif