使用orangePi进行智能家居开发
目录
一.电路连接示意图
1.OrangePi硬件连接方案
2.继电器装置接线规范
二.语音功能模块设置
1.GPIO引脚参数配置
2.自定义语音指令设定
三.系统验证流程
1.运用gpio指令检测烟雾传感器物理连接状态
2.开发测试程序验证其余外围设备线路完整性
四.面部识别技术实现路径
1.初始化人脸识别搜索服务接口
2.进入产品管理平台导入人脸特征样本数据集
3.在香橙派终端执行命令获取阿里云SDK软件包
4.SDK安装完成后进行环境变量配置操作
5.进入指定工作目录运行人脸识别示例程序
6.将Python识别代码封装为函数模块并提取最高置信度值作为匹配结果
7.C语言程序调用Python阿里云API接口
五.POSIX通信队列机制
1.基础API操作指南
2.mq_notiy通知函数应用方法
六.智能居住系统构建方案
1.系统架构设计概述:
2.启动四个独立监听线程:
3.工程文件组织结构说明:
3.1 从香橙派设备通过apt download获取依赖库头文件与动态链接库,并复制至宿主计算机:
3.2 解压至/home/mi/Desktop/smarthome/3rd/目标路径下:
3.3 编写编译控制文件Makefile:
3.4 核心功能代码实现清单:
face.py
face.c
face.h
msg_queue.c
msg_queue.h
myoled.c
myoled.h
socket.c
socket.h
control.c
control.h
gdevice.c
gdevice.h
global.h
voice_interface.c
voice_interface.h
smoke_interface.c
smoke_interface.h
socket_interface.c
socket_interface.h
receive_interface.c
receive_interface.h
4.INI配置文件解析库说明
定义设备控制参数配置文件gdevice.ini
5.Makefile构建脚本编写
一.接线图
1.orangePi接线

2.继电器接线

二.语音模块的配置
访问所属厂商的官方网站 http://www.smartpi.cn/#/ ,对相关术语库条目及语音识别结果对应的串行通信端口数据传输协议参数进行系统化设置
1.pin脚的配置

2.命令词自定义信息



三.测试
1.通过gpio指令测试烟雾检测器是否正确连接
sudo gpio mode 6 input 将第6号引脚配置为输入模式
sudo gpio readall 检测到第6号引脚处于高电平状态(此时无烟)
sudo gpio readall 监测发现第6号引脚电平已降至低电平(此时有烟)
2.编写脚本测试其他模组接线是否正常
gpio mode 2 out #卧室灯
gpio mode 5 out #课厅灯
gpio mode 7 out #电磁锁
gpio mode 8 out #风扇
gpio mode 9 out #蜂鸣器
#全部拉高,继电器断开
for i in 2 5 7 8 9
do
gpio write $i 1
done
for i in 2 5 7 8 9
do
gpio write $i 0
sleep 3
gpio write $i 1
done
四.人脸识别方案
本项目所选用的核心实施路径为阿里云所提供的面部特征识别技术体系通义实验室视觉智能开放平台
1.首先开通人脸搜索识别服务

2. 点击产品控制台,向人脸数据库中添加人脸样本数据



3.去香橙派终端执行此命令,下载阿里云SDK包


4.下载完成后,配置环境变量
于**~/.bashrc与/etc/profile**这两个配置文件的结尾位置处添加以下两行内容(请将其中的阿里云AccessKey修改为个人专属的密钥信息)
export ALIBABA_CLOUD_ACCESS_KEY_ID=<access_key_id>
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<access_key_secret>
5.切换至下列目录,点击人脸搜索示例代码


# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230
import os
from alibabacloud_facebody20191230.models import SearchFaceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_facebody20191230.client import Client
from alibabacloud_tea_util.models import RuntimeOptions
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()
search_face_request = SearchFaceRequest(
db_name='Face1',
image_url='http://viapi-test.oss-cn-shanghai.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace/SearchFace1.png',
limit=2
)
try:
# 初始化Client
client = Client(config)
response = client.search_face_with_options(search_face_request, runtime_option)
# 获取整体结果
print(response.body)
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
# tips: 可通过error.__dict__查看属性名称
6.封装python代码为一个函数,并且取返回结果中最大的score值作为最终比对结果
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230
# face.py
import os
import io
from urllib.request import urlopen
from alibabacloud_facebody20191230.client import Client
from alibabacloud_facebody20191230.models import SearchFaceAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions
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'
)
def alibaba_face():
search_face_request = SearchFaceAdvanceRequest()
# 场景一:文件在本地
stream0 = open(r'/tmp/SearchFace.jpg', 'rb')
search_face_request.image_url_object = stream0
#场景二:使用任意可访问的url
#url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace1.png'
#img = urlopen(url).read()
#search_face_request.image_url_object = io.BytesIO(img)
# 阿里云人脸数据库的名称为'default'
search_face_request.db_name = 'default'
search_face_request.limit = 5
runtime_option = RuntimeOptions()
try:
# 初始化Client
client = Client(config)
response = client.search_face_advance(search_face_request, runtime_option)
print(response.body)
match_list = response.body.to_map()['Data']['MatchList']
Scores = [item['Score'] for item in match_list[0]['FaceItems']] #set集合,无序不重复的数据集合
max_score = max(Scores)
# 获取整体结果
value = round(max_score, 2)
return value
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
return 0.0
# tips: 可通过error.__dict__查看属性名称
#关闭流
#stream0.close()
if __name__ == "__main__":
alibaba_face()
实验执行后的数据输出情况

7.C语言与Python跨平台调用实现阿里云接口交互
face.c
#include <Python.h>
#define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O /tmp/SearchFace.jpg"
#define SEARCHFACE_FILE "/tmp/SearchFace.jpg"
void face_init(void){
// 初始化Python解释器
Py_Initialize();
// 导入 sys 模块
PyObject *sys = PyImport_ImportModule("sys");
// 获取 sys 模块的 path 属性
PyObject *path = PyObject_GetAttrString(sys, "path");
// 将当前路径添加到 sys.path 中
PyList_Append(path, PyUnicode_FromString("."));
Py_DECREF(sys); // 释放 sys 引用
Py_DECREF(path); // 释放 path 引用
}
void face_final(void){
// 关闭Python解释器
Py_Finalize();
}
double face_category(void)
{
double result = 0.0;
// A.使用 wget 命令从本地服务器下载一张快照图片
system(WGET_CMD);
// B.检查刚才下载的图片文件是否存在
if (0 != access(SEARCHFACE_FILE, F_OK))
{
return result;
}
// C.导入 Python 模块
PyObject *pModule = PyImport_ImportModule("face"); //face.py
if (!pModule)
{
PyErr_Print(); // 如果导入失败,使用 PyErr_Print() 打印错误信息
printf("Error: failed to load face.py\n");
goto FAILED_MODULE; // 通过 goto 跳转到 FAILED_MODULE 标签,进行清理工作并退出
}
// D.从模块中获取 alibaba_face 函数
PyObject *pFunc = PyObject_GetAttrString(pModule, "alibaba_face");
if (!pFunc)
{
PyErr_Print(); // 如果导入失败,使用 PyErr_Print() 打印错误信息
printf("Error: failed to load alibaba_face\n");
goto FAILED_FUNC; // 通过 goto 跳转到 FAILED_FUNC 标签,进行清理工作并退出
}
// E.调用alibaba_face函数
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
// F.解析调用alibaba_face函数的返回值,转行成c语言格式
if (!PyArg_Parse(pValue, "d", &result))
{
PyErr_Print();
printf("Error: parse failed");
goto FAILED_VALUE;
}
printf("result=%0.2lf\n", result);
FAILED_VALUE:
Py_DECREF(pValue);
FAILED_FUNC:
Py_DECREF(pFunc);
FAILED_MODULE:
Py_DECREF(pModule);
return result;
}
五.POSIX消息队列
1.基本API用法
#include <mqueue.h>
//创建或打开消息队列
mqd_t mq_open(const char *name, int oflag,mode_t mode, struct mq_attr attr );
//关闭消息队列
int mq_close(mqd_t mqdes);
//删除消息队列
int mq_unlink(const char *name);
//发送消息
int mq_send(mqd_t mqdes, const char *ptr, size_tlen, unsigned int prio);
//接受消息
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_tlen, unsigned int *prio);
//消息队列的属性
struct mq_attr{
long mq_flags;//阻塞标志,0(阻塞)或O_NONBLOCK
long mq_maxmsg;//最大消息数
long mq_msgsize;//每个消息最大大小
long mq_curmsgs;//当前消息数
};
#endif
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
// 消息队列的名字
#define QUEUE_NAME "/test_queue"
// 要发送的消息
#define MESSAGE "hello,world"
void *sender_thread(void *arg) {
sleep(10);
mqd_t mqd = *(mqd_t *)arg;
// 发送消息
char message[] = MESSAGE;
printf("sender_thread message is: %s, mqd = %d\n", message, (int)mqd);
if (mq_send(mqd, message, strlen(message) + 1, 0) == -1) {
switch (errno) {
case EAGAIN:
// 消息队列已满,且设置了 O_NONBLOCK 标志
printf("Error: The message queue is full.\n");
break;
case EBADF:
// 描述符无效或未打开为写
printf("Error: Invalid message queue descriptor or not open for writing.\n");
break;
}
perror("mq_send");
} else {
// 消息发送成功的处理
printf("Message sent successfully.\n");
}
return NULL;
}
void *receiver_thread(void *arg) {
sleep(10);
// 获取消息队列描述符
mqd_t mqd = *(mqd_t *)arg;
// 定义合理大小的消息缓冲区
char message[256];
// 接受消息
ssize_t size = mq_receive(mqd, message, sizeof(message), NULL);
// 错误检查
if (size >= 0) {
printf("Message receive successfully.\n");
printf("receiver_thread message is: %s, size is: %zd, mqd = %d\n", message, size, (int)mqd);
} else {
// 根据错误码进行不同的处理
switch (errno) {
case EAGAIN: //消息队列已经满
fprintf(stderr, "mq_receive error: The queue is empty and O_NONBLOCK was set.\n");
break;
case EBADF: //检查mqd是否成功打开
fprintf(stderr, "mq_receive error: Invalid descriptor or not opened for reading.\n");
break;
}
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t sender, receiver;
mqd_t mqd = -1;
struct mq_attr attr = {0}; // 初始化结构体
attr.mq_flags = 0; // 阻塞模式
attr.mq_maxmsg = 10; // 最大消息数为10条
attr.mq_msgsize = 256; // 每个消息的最大大小
attr.mq_curmsgs = 0; // 当前消息数
// 创建消息队列
mqd = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
if (mqd == (mqd_t)-1) {
perror("mq_open");
return -1;
}
// 创建线程
if (pthread_create(&sender, NULL, sender_thread, (void *)&mqd) != 0) {
perror("Create sender_thread failed!\n");
return -1;
}
if (pthread_create(&receiver, NULL, receiver_thread, (void *)&mqd) != 0) {
perror("Create receiver_thread failed!\n");
return -1;
}
// 线程等待
pthread_join(sender, NULL);
pthread_join(receiver, NULL);
// 关闭消息队列
mq_close(mqd);
// 删除消息队列
mq_unlink(QUEUE_NAME);
return 0;
}
执行完毕后,可借助执行 ls -al /dev/mqueue/ 命令来查阅当前系统内存在的消息队列信息

通过执行命令cat /dev/mqueue/<消息队列名称>可查阅该消息队列的相关参数
例如,在向目标队列投递数据之前的数据长度显示为零;当数据成功入队但尚未被消费时其存储空间占用量呈现非零状态;而一旦完成接收操作后其容量值又会恢复至初始零值

2. mq_notiy函数的使用
mq_notify 函数旨在当消息队列状态由空转为非空之际,通过异步触发通知机制向指定进程发送提示。具体而言,在消息队列接收到新数据项时,已完成注册登记的进程将接收到信号触发或执行相应的回调处理程序。
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
struct mq_attr {
long mq_flags; // 阻塞标志:0 表示阻塞模式,O_NONBLOCK 表示非阻塞模式
long mq_maxmsg; // 最大消息数:消息队列中允许的最大消息数量
long mq_msgsize; // 每个消息的最大大小:消息队列中单个消息的最大字节数
long mq_curmsgs; // 当前消息数:消息队列中当前存储的消息数量
};
union sigval {
int sival_int; // 整数值
void *sival_ptr; // 指针值
};
struct sigevent {
int sigev_notify; // 通知方式
int sigev_signo; // 通知信号(当通知方式为信号时使用)
union sigval sigev_value; // 传递给通知方法的数据
void (*sigev_notify_function)(union sigval); // 当通知方式为 SIGEV_THREAD 时调用的函数
void *sigev_notify_attributes; // 线程通知的属性(当通知方式为 SIGEV_THREAD 时使用)
pid_t sigev_notify_thread_id; // 发送信号的线程 ID(Linux 特有,SIGEV_THREAD_ID)
};
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h> // 添加 signal.h 以包含 SIGEV_THREAD
#define QUEUE_NAME "/test_queue"
#define MESSAGE "hello,world"
void notify_thread(union sigval arg) {
mqd_t mqd = *(mqd_t *)arg.sival_ptr;
char buffer[256];
ssize_t receive_size = -1;
memset(buffer, 0, sizeof(buffer));
printf("receive_thread start\n");
receive_size = mq_receive(mqd, buffer, sizeof(buffer), NULL);
printf("receive_thread end\n");
if (receive_size >= 0) {
printf("Message received successfully.\n");
printf("Received message is: %s, size is: %zd, mqd = %d\n", buffer, receive_size, (int)mqd);
} else {
switch (errno) {
case EAGAIN:
fprintf(stderr, "mq_receive error: The queue is empty and O_NONBLOCK was set.\n");
break;
case EBADF:
fprintf(stderr, "mq_receive error: Invalid descriptor or not opened for reading.\n");
break;
default:
perror("mq_receive");
}
}
// 重新注册通知机制
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_value.sival_ptr = &mqd;
sev.sigev_notify_function = notify_thread;
sev.sigev_notify_attributes = NULL;
if (mq_notify(mqd, &sev) == -1) {
perror("mq_notify");
}
}
void *sender_thread(void *arg) {
mqd_t mqd = *(mqd_t *)arg;
char message[] = MESSAGE;
printf("sender_thread message is: %s, mqd = %d\n", message, (int)mqd);
if (mq_send(mqd, message, strlen(message) + 1, 0) == -1) {
switch (errno) {
case EAGAIN:
printf("Error: The message queue is full.\n");
break;
case EBADF:
printf("Error: Invalid message queue descriptor or not open for writing.\n");
break;
default:
perror("mq_send");
}
} else {
printf("Message sent successfully.\n");
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t sender;
mqd_t mqd = -1;
struct mq_attr attr = {0};
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 256;
attr.mq_curmsgs = 0;
mqd = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
if (mqd == (mqd_t)-1) {
perror("mq_open");
return -1;
}
struct sigevent sev;
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_value.sival_ptr = &mqd;
sev.sigev_notify_function = notify_thread;
sev.sigev_notify_attributes = NULL;
if (mq_notify(mqd, &sev) == -1) {
perror("mq_notify");
mq_close(mqd);
mq_unlink(QUEUE_NAME);
return -1;
}
if (pthread_create(&sender, NULL, sender_thread, (void *)&mqd) != 0) {
perror("Create sender_thread failed!\n");
mq_close(mqd);
mq_unlink(QUEUE_NAME);
return -1;
}
sleep(15); //-----------------------------防止发送线程结束后,信号未来得及接受
pthread_join(sender, NULL);
mq_close(mqd);
mq_unlink(QUEUE_NAME);
return 0;
}
六.智能家居的实现
1.项目的整体框架大致如下:

2.整个项目开启4个监听线程,分别是:
1. 语音接收子程序:负责捕捉音频控制信号,在接收到相应音频输入后,借助消息队列机制将操作命令传递至任务调度模块
2. 网络监测子程序:承担网络控制信号的捕获工作,在获取网络传输数据后,通过消息队列通道向任务调度模块转发控制信息
3. 危险预警子程序:当检测到燃气浓度异常或火情发生时,自动触发报警信号并传输至任务调度模块进行响应处理
4. 任务协调子程序:负责整合前三个子程序传送的操作命令,并依据具体指示执行以下操作:设置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);
3.项目结构大致如下
├──3rd
│
├──ini
│
└──gdevice.ini
||
├──inc
│
├──garbage.h
│
├──myoled.h
│
├──pwm.h
│
├──socket.h
│
└──uartTool.h
||
├──src
│
├──garbage.c
│
├──garbage.py
│
├──main.c
│
├──myoled.c
│
├──pwm.c
│
├──socket.c
│
├──uartTool.c
||
└──Makefile
||
├──obj
│
├──Makefile
│
3.1 第一步需在香橙派设备中执行 apt download 命令以获取所需依赖组件的开发头文件及动态链接库,并将这些软件资源迁移至目标宿主机系统内:
apt download zlib1g zlib1g-dev libpython3.10 libpython3.10-dev libexpat1 libexpat1-dev libcrypt1 libcrypt-dev
scp *deb mi@192.168.57.50:/home/mi
3.2 将压缩包展开至目标路径 /home/mi/Desktop/smarthome/3rd/
dpkg -x libcrypt1_1%3a4.4.27-1_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x libcrypt-dev_1%3a4.4.27-1_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x libexpat1_2.4.7-1ubuntu0.3_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x libexpat1-dev_2.4.7-1ubuntu0.3_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x libpython3.10_3.10.12-1~22.04.3_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x libpython3.10-dev_3.10.12-1~22.04.3_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x zlib1g_1%3a1.2.11.dfsg-2ubuntu9.2_arm64.deb /home/mi/Desktop/Class/3rd/
dpkg -x zlib1g-dev_1%3a1.2.11.dfsg-2ubuntu9.2_arm64.deb /home/mi/Desktop/Class/3rd/
3.2 制定用于自动化构建的配置文件 Makefile 的步骤
CC := aarch64-none-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 \
#根据 SRC 变量中的 .c 文件生成对应的 .o 目标文件
OBJ := $(subst src/,obj/,$(SRC:.c=.o))
#最终生成的目标文件
TARGET := obj/smarthome
#将 INC 中包含的路径添加到编译器的头文件搜索路径中
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
#将 LIBS_PATH 中包含的路径添加到链接器的库文件搜索路径中
LDFLAGS := $(foreach item, $(LIBS_PATH), -L$(item))
#指定需要链接的库文件
LIBS := -lwiringPi -lpython3.10 -pthread -lexpat -lz -lcrypt
obj/%.o: src/%.c
mkdir -p obj
$(CC) -o $@ -c $< $(CFLAGS)
#将所有的目标文件链接在一起生成 garbage 文件
$(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
3.4 智能家居系统实际代码开发过程
人脸识别功能对应的核心模块源码文件(face.py)
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230
# face.py
import os
import io
from urllib.request import urlopen
from alibabacloud_facebody20191230.client import Client
from alibabacloud_facebody20191230.models import SearchFaceAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions
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'
)
def alibaba_face():
search_face_request = SearchFaceAdvanceRequest()
# 场景一:文件在本地
stream0 = open(r'/tmp/SearchFace.jpg', 'rb')
search_face_request.image_url_object = stream0
#场景二:使用任意可访问的url
#url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace1.png'
#img = urlopen(url).read()
#search_face_request.image_url_object = io.BytesIO(img)
# 阿里云人脸数据库的名称为'default'
search_face_request.db_name = 'default'
search_face_request.limit = 5
runtime_option = RuntimeOptions()
try:
# 初始化Client
client = Client(config)
response = client.search_face_advance(search_face_request, runtime_option)
print(response.body)
match_list = response.body.to_map()['Data']['MatchList']
Scores = [item['Score'] for item in match_list[0]['FaceItems']] #set集合,无序不重复的数据集合
max_score = max(Scores)
# 获取整体结果
value = round(max_score, 2)
return value
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
return 0.0
# tips: 可通过error.__dict__查看属性名称
#关闭流
#stream0.close()
if __name__ == "__main__":
alibaba_face()
face.c
#include <Python.h>
#define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O /tmp/SearchFace.jpg"
#define SEARCHFACE_FILE "/tmp/SearchFace.jpg"
void face_init(void){
// 初始化Python解释器
Py_Initialize();
// 导入 sys 模块
PyObject *sys = PyImport_ImportModule("sys");
// 获取 sys 模块的 path 属性
PyObject *path = PyObject_GetAttrString(sys, "path");
// 将当前路径添加到 sys.path 中
PyList_Append(path, PyUnicode_FromString("."));
Py_DECREF(sys); // 释放 sys 引用
Py_DECREF(path); // 释放 path 引用
}
void face_final(void){
// 关闭Python解释器
Py_Finalize();
}
double face_category(void)
{
double result = 0.0;
// A.使用 wget 命令从本地服务器下载一张快照图片
system(WGET_CMD);
// B.检查刚才下载的图片文件是否存在
if (0 != access(SEARCHFACE_FILE, F_OK))
{
return result;
}
// C.导入 Python 模块
PyObject *pModule = PyImport_ImportModule("face"); //face.py
if (!pModule)
{
PyErr_Print(); // 如果导入失败,使用 PyErr_Print() 打印错误信息
printf("Error: failed to load face.py\n");
goto FAILED_MODULE; // 通过 goto 跳转到 FAILED_MODULE 标签,进行清理工作并退出
}
// D.从模块中获取 alibaba_face 函数
PyObject *pFunc = PyObject_GetAttrString(pModule, "alibaba_face");
if (!pFunc)
{
PyErr_Print(); // 如果导入失败,使用 PyErr_Print() 打印错误信息
printf("Error: failed to load alibaba_face\n");
goto FAILED_FUNC; // 通过 goto 跳转到 FAILED_FUNC 标签,进行清理工作并退出
}
// E.调用alibaba_face函数
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
// F.解析调用alibaba_face函数的返回值,转行成c语言格式
if (!PyArg_Parse(pValue, "d", &result))
{
PyErr_Print();
printf("Error: parse failed");
goto FAILED_VALUE;
}
printf("result=%0.2lf\n", result);
FAILED_VALUE:
Py_DECREF(pValue);
FAILED_FUNC:
Py_DECREF(pFunc);
FAILED_MODULE:
Py_DECREF(pModule);
return result;
}
用于面部信息处理的对应标头文件
#ifndef __FACE__H
#define __FACE__H
void face_init(void);
void face_final(void);
double face_category(void);
#endif
用于实现消息队列功能的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; // 最大消息数为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_QUEUE__H
#define __MSG_QUEUE__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
myoled.c
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include "oled.h"
#include "font.h"
//包含头文件
#include "myoled.h"
#define FILENAME "/dev/i2c-3"
static struct display_info disp;
// oled设备显示内容(显示内容通过参数传递)
int oled_show(void *arg)
{
unsigned char *buffer = (unsigned char *)arg;
if (NULL != buffer)
{
oled_putstrto(&disp, 0, 9+1, buffer);
}
#if 0
oled_putstrto(&disp, 0, 9+1, "This garbage is:");
disp.font = font2;
switch(buffer[2])
{
case 0x41:
oled_putstrto(&disp, 0, 20, "dry waste");
break;
case 0x42:
oled_putstrto(&disp, 0, 20, "wet waste");
break;
case 0x43:
oled_putstrto(&disp, 0, 20, "recyclable waste");
break;
case 0x44:
oled_putstrto(&disp, 0, 20, "hazardous waste");
break;
case 0x45:
oled_putstrto(&disp, 0, 20, "recognition failed");
break;
}
#endif
disp.font = font2;
// 将显示缓冲区内容发送到OLED显示器进行渲染
oled_send_buffer(&disp);
return 0;
}
// oled设备初始化(a.打开/dev/i2c-3 b.初始化oled设备)
int myoled_init(void)
{
int e;
disp.address = OLED_I2C_ADDR;
disp.font = font2;
e = oled_open(&disp, FILENAME);
e = oled_init(&disp);
oled_clear(&disp);
return e;
}
专有OLED模块的头文件
#ifndef __MYOLED__H
#define __MYOLED__H
int myoled_init(void);
int oled_show(void *arg);
#endif
socket.c
#include "socket.h"
// socket网络初始化
int socket_init(const char *ipaddr, const char *port)
{
int s_fd = -1;
int ret = -1;
struct sockaddr_in s_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
// 1. 创建socket网络套接字
s_fd = socket(AF_INET, SOCK_STREAM, 0); //网络类型
//数据协议
//一般为0
if(s_fd == -1){
perror("socket");
return -1;
}
/** *
struct sockaddr_in{
uint16 sin_family; 网络类型,如IPV4区域网
uint16 sin_port; 端口号
uint32 sin_addr.s_addr; IP地址
unsigned char sin_zero[8]; 结构体占用内存大小
};
*/
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(port));
inet_aton(ipaddr,&s_addr.sin_addr);
// 2. 将给定的网络地址及端口号绑定到指定的socket套接字上
ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
if (-1 == ret)
{
perror("bind");
return -1;
}
// 3. 将socket套接字变为监听套接字,准备接受客户端的连接
ret = listen(s_fd,1); //只监听1个连接,排队扔垃圾
if (-1 == ret)
{
perror("listen");
return -1;
}
return s_fd;
}
// 检测客户端是否断开连接的函数
int is_disconnect(int c_fd)
{
char buf[BUF_SIZE];
int ret = recv(c_fd, buf, BUF_SIZE, MSG_PEEK); // 使用MSG_PEEK选项查看缓冲区中的数据
if (ret == 0)
{ // 如果返回0,说明客户端正常关闭了连接
printf("Client closed the connection.\n");
return 1;
}
else if (ret == -1)
{ // 如果返回-1,说明出现了错误
if (errno == ETIMEDOUT)
{ // 如果错误码是ETIMEDOUT,说明客户端超时断开了连接
printf("Client timed out.\n");
return 1;
}
else
{ // 如果是其他错误,打印错误信息
perror("recv");
return -1;
}
}
else
{ // 如果返回正数,说明缓冲区中有数据可读,客户端还在连接中
return 0;
}
}
用于实现网络通信功能的标准C语言库头文件
#ifndef __SOCKET__H
#define __SOCKET__H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#define IPADDR "192.168.96.202"
#define IPPORT "8192"
#define BUF_SIZE 6
int socket_init(const char *ipaddr, const char *port);
int is_disconnect(int c_fd);
#endif
control.c
#include <stdio.h>
#include "control.h"
struct control *add_interface_to_ctrl_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_ctrl_list(struct control *phead, struct control *control_interface);
#endif
gdevice.c
#include "gdevice.h"
//根据key值寻找设备
struct gdevice *find_device_by_key(struct gdevice *pgdevhead, int key)
{
struct gdevice *p = NULL;
if (NULL == pgdevhead)
{
return NULL;
}
p = pgdevhead;
while (NULL != p)
{
if (p->key == key)
{
return p;
}
p = p->next;
}
return NULL;
}
//设置设备的状态
int set_gpio_gdevice_status(struct gdevice *pgdev)
{
if (NULL == pgdev)
{
return -1;
}
if(-1 != pgdev->gpio_pin)
{
if(-1 != pgdev->gpio_mode)
{
pinMode(pgdev->gpio_pin, pgdev->gpio_mode); //配置引脚的输入输出模式
}
if (-1 != pgdev->gpio_status)
{
digitalWrite(pgdev->gpio_pin, pgdev->gpio_status);//当配置为输出模式时, 引脚的高低状态
}
}
return 0;
}
图形设备接口定义头文件
#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引脚 6 7 8 9 -1
int gpio_mode; //输入输出模式 INPUT OUPUT -1
int gpio_status; //高低电平状态 LOW HIGH -1
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
系统级公共变量定义头文件
#ifndef __GLOBAL__H
#define __GLOBAL__H
typedef struct {
mqd_t mqd;
struct control *ctrl_phead;
}ctrl_info_t;
#endif
语音交互模块对应的C语言源代码实现
#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;
//语音模块的初始化(打开uart5串口)
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
烟雾检测接口实现源码.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);
//引脚为低电平,代表检测到烟雾,发送AA 55 45 00 55 AA到消息队列
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;
}
}
//引脚为高电平且switch_status为1,代表着火灾险情解除,发送AA 55 45 01 55 AA到消息队列
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
Socket通信接口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;
// socket网络初始化————1.创建网络套接字 2.绑定IP地址和端口号 3.套接字变为监听套接字准备接受客户端的连接
static int tcpsocket_init(void)
{
s_fd = socket_init(IPADDR, IPPORT);
return -1;
}
// socket网络终止————关闭网络套接字
static void tcpsocket_final(void)
{
close(s_fd);
s_fd = -1;
}
// socket网络监听线程函数————
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);
// 1.如果 s_fd 没有被初始化,调用 tcpsocket_init 函数来初始化
if (-1 == s_fd)
{
s_fd = tcpsocket_init();
if (-1 == s_fd)
{
printf("tcpsocket_init failed\n");
pthread_exit(0);
}
}
// 2.从传入的参数获取 ctrl_info
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));
int clen = sizeof(struct sockaddr_in);
printf("%s thread start\n", __func__);
// 3.连接客户机并接受客户机消息发送到消息队列
while (1)
{
// 3.1等待并接受客户端连接
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if (-1 == c_fd)
{
continue;
}
// 3.2设置 SO_KEEPALIVE、TCP_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT 选项,使TCP连接能够定时发送探测包,保持活跃状态
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;
}
// 3.3打印客户端的IP地址和端口号
printf("Accepted a connection from %s:%d\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
// 3.4连接上客户机后循环不断检测接受数据
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);
};
网络通信协议实现中的套接字接口定义头文件
#ifndef ___SOCKET_INTERFACE_H___
#define ___SOCKET_INTERFACE_H___
#include "control.h"
struct control *add_tcpsocket_to_ctrl_list(struct control *phead);
#endif
接收接口模块的核心实现代码文件
#include <pthread.h>
#include <mqueue.h>
#include <stdlib.h>
#include <stdio.h>
#include "wiringPi.h"
#include "control.h"
#include "receive_interface.h"
#include "msg_queue.h"
#include "global.h"
#include "face.h"
#include "myoled.h"
#include "ini.h"
#include "gdevice.h"
typedef struct {
int msg_len;
unsigned char *buffer;
ctrl_info_t *ctrl_info;
}recv_msg_t;
static int oled_fd = -1;
static struct gdevice *pdevhead = NULL;
//用于同时检查 section 和 s 是否相同,name 和 n 是否相同
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
/** * ---------------------------------------------------------------
* 用于解析设备的配置并将其存储在gdevice结构体中
* --------------------------------------------------------------
*/
//section:beep name:key value:0x45
//section:beep name:gpio_pin value:9
//section:beep name:gpio_mode value:OUTPUT
//每当调用一次回调函数,传递一个设备的其中一个属性
static int handler_gdevice(void* user, const char* section, const char* name,
const char* value)
{
struct gdevice *pdev = NULL;
// 1.若设备链表为空,则为结点分配内存,然后将传入的 section 字符串复制到设备节点的 dev_name 字段中
if (NULL == pdevhead)
{
pdevhead = (struct gdevice *)malloc(sizeof(struct gdevice));
pdevhead->next = NULL;
memset(pdevhead, 0, sizeof(struct gdevice));
strcpy(pdevhead->dev_name, section);
}
// 若当前的 section(设备名称)与链表头节点(pdevhead)的设备名称不相同,头插法插入当前结点
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)
{
//若pdevhead->dev_name == section,且 "key" == name
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; //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; //OUTPUT
}
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;
}
/** * ----------------------------初始化函数-------------------------
* 解析设备配置文件/etc/gdevice.ini并调用handler_gdevice处理。
* 初始化OLED显示设备(myoled_init)和人脸识别模块(face_init)。
* --------------------------------------------------------------
*/
static int receive_init(void)
{
// 打开并解析给定文件名对应的 .INI 文件
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;
}
static void receive_final(void)
{
face_final();
if(oled_fd != -1)
{
close(oled_fd);
oled_fd = -1;
}
}
/** * -------------------------------核心函数,在独立线程中执行-------------------------------
* 从传入的参数中提取消息,并根据消息中的键值查找对应的设备。
* 如果设备存在,根据不同的控制需求(如人脸识别、GPIO设置)执行相应的操作。
* 如果需要,调用pcontrol->set函数进行语音设置或其他控制操作。
* 在OLED屏幕上显示操作结果。如果是人脸识别模块,还会自动关闭门锁。
* --------------------------------------------------------------------------------------
*/
static void *handle_device(void *arg)
{
recv_msg_t *recv_msg = NULL;
struct gdevice *cur_gdev = NULL;
char success_or_failed[20] = "success";
int ret = -1;
pthread_t tid = -1;
int smoke_status = 0;
double face_result = 0.0;
pthread_detach(pthread_self());
// 1.从传入的参数中提取消息并打印出
if (NULL != arg)
{
recv_msg = (recv_msg_t *)arg;
printf("recv_msg->msg_len = %d\n", recv_msg->msg_len);
printf("%s|%s|%d:hanle 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]);
}
// 2.根据键值buffer[2]进行查找设备,并赋值给指针cur_gdev
if (NULL != recv_msg && NULL != recv_msg->buffer)
{
cur_gdev = find_device_by_key(pdevhead, recv_msg->buffer[2]);
}
// 3.设备控制逻辑
if (NULL != cur_gdev)
{
// 3.1如果成功找到设备,根据buffer[3]的值决定GPIO引脚是置低(LOW)还是置高(HIGH)
cur_gdev->gpio_status = recv_msg->buffer[3] == 0 ? LOW : HIGH;
// special for lock
printf("%s|%s|%d:cur_gdev->check_face_status=%d\n", __FILE__, __func__, __LINE__, cur_gdev->check_face_status);
// 3.2人脸识别逻辑
if (1 == cur_gdev->check_face_status)
{
//如果设备要求检查人脸识别(check_face_status == 1),调用face_category()函数获取人脸识别结果。
face_result = face_category();
printf("%s|%s|%d:face_result=%f\n", __FILE__, __func__, __LINE__, face_result);
//如果人脸识别成功(结果大于0.6),执行GPIO状态设置,并将消息中的指令改为成功码(0x47)
if (face_result > 0.6)
{
ret = set_gpio_gdevice_status(cur_gdev);
recv_msg->buffer[2] = 0x47;
}//如果失败,则返回失败码(0x46),并将返回值设为-1
else
{
recv_msg->buffer[2] = 0x46;
ret = -1;
}
}
// 3.3如果设备不需要人脸识别,直接设置GPIO状态。
else if (0 == cur_gdev->check_face_status)
{
ret = set_gpio_gdevice_status(cur_gdev);
}
// 3.4如果设备的语音设置状态为启用,进行语音播报
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)
{
// 查找控制名称中包含"voice"的控制
if (strstr(pcontrol->control_name, "voice"))
{
// 如果指令码为0x45且GPIO状态为0,标记smoke_status = 1,主要是为了后续进行Oled显示
if (0x45 == recv_msg->buffer[2] && 0 == recv_msg->buffer[3])
{
smoke_status = 1;
}
// 创建一个新线程,执行语音设置函数
pthread_create(&tid, NULL, pcontrol->set, (void *)recv_msg->buffer);
// 找到对应的控制后,退出循环
break;
}
// 移动到链表中的下一个控制节点
pcontrol = pcontrol->next;
}
}
}
// 3.5检查操作是否失败
if (-1 == ret)
{
// 将 success_or_failed 字符串清空
memset(success_or_failed, '\0', sizeof(success_or_failed));
// 将 "failed" 字符串复制到 success_or_failed 变量中
strncpy(success_or_failed, "failed", 6);
}
// OLED 屏显示部分
char oled_msg[512]; // 定义一个 512 字节的字符数组用于存储 OLED 显示信息
memset(oled_msg, 0, sizeof(oled_msg)); // 将数组清零
// 根据 GPIO 状态设置操作描述信息 ("Open" 或 "Close")
char *change_status = cur_gdev->gpio_status == LOW ? "Open" : "Close";
// 格式化生成 OLED 显示的消息,包括设备状态、设备名称和操作结果
sprintf(oled_msg, "%s %s %s!\n", change_status, cur_gdev->dev_name, success_or_failed);
// 针对烟雾报警的特殊处理
if(smoke_status == 1)
{
// 如果检测到烟雾报警,清空 OLED 消息并设置为 "A risk of fire!\n"
memset(oled_msg, 0, sizeof(oled_msg));
strcpy(oled_msg, "A risk of fire!\n");
}
// 打印 OLED 显示的信息到控制台(用于调试)
printf("oled_msg=%s\n", oled_msg);
// 调用函数显示 OLED 消息
oled_show(oled_msg);
// 特殊处理:如果设备是锁且识别人脸成功
if (1 == cur_gdev->check_face_status && 0 == ret && face_result > 0.6)
{
sleep(5); // 等待 5 秒钟
// 将 GPIO 状态设置为 HIGH(关闭锁)
cur_gdev->gpio_status = HIGH;
// 调用函数更新设备的 GPIO 状态
set_gpio_gdevice_status(cur_gdev);
}
}
pthread_exit(0);
}
/** * -------------------------------消息接受函数,在独立线程中执行-------------------------------
* 从消息队列中接收数据,将其封装到recv_msg_t结构体中。
* 检查消息的有效性,然后启动一个新的线程调用handle_device处理该消息
* -----------------------------------------------------------------------------------------
*/
static void* receive_get(void *arg)
{
recv_msg_t *recv_msg = NULL;
ssize_t read_len = -1;
pthread_t tid = -1;
char *buffer = NULL;
struct mq_attr attr;
// A.接受传参获取到消息队列的信息
if (NULL != arg)
{
recv_msg = (recv_msg_t *)malloc(sizeof(recv_msg_t));
recv_msg->ctrl_info = (ctrl_info_t *)arg; //获取到mqd 和phead (struct control 链表的头结点)
recv_msg->msg_len = -1;
recv_msg->buffer = NULL;
}
else
{
pthread_exit(0);
}
// B.获取消息队列属性
if(mq_getattr(recv_msg->ctrl_info->mqd, &attr) == -1)
{
pthread_exit(0);
}
/** * struct mq_attr attr;
* attr.mq_flags = 0; // 阻塞模式
* attr.mq_maxmsg = 10; // 最大消息数为10条
* attr.mq_msgsize = 256; // 每个消息的最大大小
* attr.mq_curmsgs = 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());
// C.持续接收消息的循环
while(1)
{
// C.1从消息队列中接收消息
read_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:read_len=%ld\n", __FILE__, __func__, __LINE__, read_len);
if (-1 == read_len)
{
if(errno == EAGAIN)
{
printf("queue is empyt\n");
}
else
{
break;
}
}
// C.2检查接收到的消息内容是否符合特定格式
else if(buffer[0] == 0xAA && buffer[1] == 0x55
&& buffer[5] == 0xAA && buffer[4] == 0x55)
{
recv_msg->msg_len = read_len;
// 将接收到的消息复制到结构体中
memcpy(recv_msg->buffer, buffer, read_len);
// 创建新线程处理接收到的消息
pthread_create(&(tid), NULL, handle_device, (void *)recv_msg);
}
}
pthread_exit(0);
}
struct control receive_control = {
.control_name = "receive",
.init = receive_init,
.final = receive_final,
.get = receive_get,
.set = NULL,
.next = NULL
};
struct control *add_receive_to_ctrl_list(struct control *phead)
{//头插法
return add_interface_to_ctrl_list(phead, &receive_control);
};
通信接口接收模块定义头文件
#ifndef ___RECEIVE_INTERFACE_H___
#define ___RECEIVE_INTERFACE_H___
#include "control.h"
struct control *add_receive_to_ctrl_list(struct control *phead);
#endif
4.iniH配置文件解析库概述
iniH作为一款体积小巧的C语言开发工具包,专为处理标准INI格式配置文档而设计。
4.1. 获取libinih1原始代码文件
apt source libinih1
4.2. 需将libinih-53目录下的源文件ini.c与头文件ini.h复制至项目工程目录内
4.3. 在调用inih库功能时应先引用其头文件声明,在程序中通过执行ini_parse()函数实现INI格式解析操作。该函数在运行过程中需要接收三项输入参数:目标解析对象所对应的完整路径名称、用于处理解析结果的回调函数实体及传递给回调函数的数据指针变量
定义设备控制配置文件gdevice.ini
[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
[BR led]
key=0x42
gpio_pin=5
gpio_mode=OUTPUT
gpio_status=HIGH
check_face_status=0
voice_set_status=0
[LV led]
key=0x41
gpio_pin=2
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
- 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)
scp obj/smarthome src/face.py ini/gdevice.ini orangepi@192.168.96.202:/home/orangepi
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
运行make compile 命令可实现将所有文件传输至目标主机的操作,在完成该步骤后需进一步实施以下操作指令:首先应将ini格式的配置文档复制至系统目录中的etc路径下
sudo cp gdevice.ini /etc/
随后输入下列操作指令即可启动程序
sudo -E ./smarthome
