目录
- [1. 需求及项目准备(此项目对于虚拟机和香橙派的配置基于上一个垃圾分类项目,如初次开发,两个平台的环境变量,阿里云接入,摄像头配置可参考垃圾分类项目)](#1. 需求及项目准备(此项目对于虚拟机和香橙派的配置基于上一个垃圾分类项目,如初次开发,两个平台的环境变量,阿里云接入,摄像头配置可参考垃圾分类项目))
-
- [1.1 系统框图](#1.1 系统框图)
- [1.2 硬件接线](#1.2 硬件接线)
- [1.3 语音模块配置](#1.3 语音模块配置)
- [1.4 模块测试](#1.4 模块测试)
- [2. 阿里云人脸识别方案](#2. 阿里云人脸识别方案)
-
- [2.1 接入阿里云](#2.1 接入阿里云)
- [2.2 C语言调用阿里云人脸识别接口](#2.2 C语言调用阿里云人脸识别接口)
- [2.3 POSIX消息队列](#2.3 POSIX消息队列)
- [3. 智能家居项目的软件实现](#3. 智能家居项目的软件实现)
-
- [3.1 项目整体设计](#3.1 项目整体设计)
- [3.2 项目代码的前期准备](#3.2 项目代码的前期准备)
- [3.2 项目各文件代码](#3.2 项目各文件代码)
- [4. 代码优化](#4. 代码优化)
-
- 4.1设备类节点直接通过文件配置
- [4.2 接收处理代码重新实现](#4.2 接收处理代码重新实现)
- [5. 代码编译运行](#5. 代码编译运行)
1. 需求及项目准备(此项目对于虚拟机和香橙派的配置基于上一个垃圾分类项目,如初次开发,两个平台的环境变量,阿里云接入,摄像头配置可参考垃圾分类项目)
- 语音接入控制各类家电,如客厅灯、卧室灯、风扇
- 回顾Socket编程,实现Sockect发送指令远程控制各类家电
- 烟雾警报监测, 实时检查是否存在煤气泄漏或者火灾警情,当存在警情时及时触发蜂鸣器报警及语音播报
- 控制人脸识别打开房门功能,并语音播报识别成功或者失败
- 局域网实时视频监控
- OLED屏实时显示当前主板温度、警情信息及控制指令信息
人脸识别使用阿里SDK支持Python和Java接口,目的是复习巩固C语言的Python调用,此接口是人工智能接口,阿里云识别模型是通过训练后的模型,精准度取决于训练程度,人工智能范畴。在常规嵌入式设备负责执行居多,说白的嵌入式设备负责数据采集,然后转发给人工智能识别后,拿到结果进行执行器动作。
1.1 系统框图

1.2 硬件接线
- 硬件准备
USB充电头(当前实测可用:5V/2.5A)x1、USB转TYPE-Cx1、SU-03Tx1、烟雾报警模块x1、4路继电器x1、 OLEDx1、 电磁锁x1(5V吸合开锁)、 蜂鸣器x1、小风扇+电机x1(需要自行购买)、面包板x1、 5号1.5V电池x6 、 2节电池盒x1、4节电池盒x1、带3路led灯小房子(3.3V可驱动, 需自行购买搭建) - 香橙派的引脚接线信息(注意硬件不要接错了)
- 4路继电器接线图

- 面包板接线
1.3 语音模块配置
- pin脚配置
- 命令词自定义基本信息

- 命令词自定控制详情


1.4 模块测试
使用以下下脚本可分别测试继电器控制的客厅灯、卧室灯、风扇、烟雾报装置是否正常连接。会依次触发灯的亮灭、电磁锁通断、风扇开关、蜂鸣器的播听及最后读取两次gpio的引进状态。 可通过查看pin6最终确定烟雾报警模块在有烟雾的情况下的状态是否变为0。
bash
#!/bin/bash
#if [ $# -ne 1 ]
#then
# echo $#
# echo "usage: ./gpiotest.shh 0/1"
# exit 0
#fi
if ! which gpio > /dev/null 2>&1; then
echo "please install wiringOP first"
fi
gpio mode 2 out #livingroom
gpio mode 5 out #bedroom light
gpio mode 7 out #fan
gpio mode 8 out #lock
gpio mode 9 out #beep
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
gpio mode 6 in #smoke
gpio readall
sleep 5
gpio readall
运行脚本,观察硬件反应
bash
orangepi@orangepizero2:~$ bash -x ./gpiotest.sh
+ which gpio
+ 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
+ gpio write 2 1
+ for i in 2 5 7 8 9
+ gpio write 5 1
+ for i in 2 5 7 8 9
+ gpio write 7 1
+ for i in 2 5 7 8 9
+ gpio write 8 1
+ for i in 2 5 7 8 9
+ gpio write 9 1
+ for i in 2 5 7 8 9
+ gpio write 2 0
+ sleep 3
+ gpio write 2 1
+ for i in 2 5 7 8 9
+ gpio write 5 0
+ sleep 3
+ gpio write 5 1
+ for i in 2 5 7 8 9
+ gpio write 7 0
+ sleep 3
+ gpio write 7 1
+ for i in 2 5 7 8 9
+ gpio write 8 0
+ sleep 3
+ gpio write 8 1
+ for i in 2 5 7 8 9
+ gpio write 9 0
+ sleep 3
+ gpio write 9 1
+ gpio mode 6 in
+ gpio readall
+------+-----+----------+------+---+ H616 +---+------+----------+-----+------+
| GPIO | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | GPIO |
+------+-----+----------+------+---+----++----+---+------+----------+-----+------+
| | | 3.3V | | | 1 || 2 | | | 5V | | |
| 229 | 0 | SDA.3 | ALT5 | 0 | 3 || 4 | | | 5V | | |
| 228 | 1 | SCL.3 | ALT5 | 0 | 5 || 6 | | | GND | | |
| 73 | 2 | PC9 | OUT | 1 | 7 || 8 | 0 | ALT2 | TXD.5 | 3 | 226 |
| | | GND | | | 9 || 10 | 0 | ALT2 | RXD.5 | 4 | 227 |
| 70 | 5 | PC6 | OUT | 1 | 11 || 12 | 1 | IN | PC11 | 6 | 75 |
| 69 | 7 | PC5 | OUT | 1 | 13 || 14 | | | GND | | |
| 72 | 8 | PC8 | OUT | 1 | 15 || 16 | 1 | OUT | PC15 | 9 | 79 |
| | | 3.3V | | | 17 || 18 | 0 | OFF | PC14 | 10 | 78 |
| 231 | 11 | MOSI.1 | OFF | 0 | 19 || 20 | | | GND | | |
| 232 | 12 | MISO.1 | OFF | 0 | 21 || 22 | 0 | OFF | PC7 | 13 | 71 |
| 230 | 14 | SCLK.1 | OFF | 0 | 23 || 24 | 0 | OFF | CE.1 | 15 | 233 |
| | | GND | | | 25 || 26 | 0 | OFF | PC10 | 16 | 74 |
| 65 | 17 | PC1 | OFF | 0 | 27 || 28 | | | | | |
| 272 | 18 | PI16 | ALT2 | 0 | 29 || 30 | | | | | |
| 262 | 19 | PI6 | OFF | 0 | 31 || 32 | | | | | |
| 234 | 20 | PH10 | ALT3 | 0 | 33 || 34 | | | | | |
+------+-----+----------+------+---+----++----+---+------+----------+-----+------+
| GPIO | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | GPIO |
+------+-----+----------+------+---+ H616 +---+------+----------+-----+------+
+ sleep 5
+ gpio readall
+------+-----+----------+------+---+ H616 +---+------+----------+-----+------+
| GPIO | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | GPIO |
+------+-----+----------+------+---+----++----+---+------+----------+-----+------+
| | | 3.3V | | | 1 || 2 | | | 5V | | |
| 229 | 0 | SDA.3 | ALT5 | 0 | 3 || 4 | | | 5V | | |
| 228 | 1 | SCL.3 | ALT5 | 0 | 5 || 6 | | | GND | | |
| 73 | 2 | PC9 | OUT | 1 | 7 || 8 | 0 | ALT2 | TXD.5 | 3 | 226 |
| | | GND | | | 9 || 10 | 0 | ALT2 | RXD.5 | 4 | 227 |
| 70 | 5 | PC6 | OUT | 1 | 11 || 12 | 1 | IN | PC11 | 6 | 75 |
| 69 | 7 | PC5 | OUT | 1 | 13 || 14 | | | GND | | |
| 72 | 8 | PC8 | OUT | 1 | 15 || 16 | 1 | OUT | PC15 | 9 | 79 |
| | | 3.3V | | | 17 || 18 | 0 | OFF | PC14 | 10 | 78 |
| 231 | 11 | MOSI.1 | OFF | 0 | 19 || 20 | | | GND | | |
| 232 | 12 | MISO.1 | OFF | 0 | 21 || 22 | 0 | OFF | PC7 | 13 | 71 |
| 230 | 14 | SCLK.1 | OFF | 0 | 23 || 24 | 0 | OFF | CE.1 | 15 | 233 |
| | | GND | | | 25 || 26 | 0 | OFF | PC10 | 16 | 74 |
| 65 | 17 | PC1 | OFF | 0 | 27 || 28 | | | | | |
| 272 | 18 | PI16 | ALT2 | 0 | 29 || 30 | | | | | |
| 262 | 19 | PI6 | OFF | 0 | 31 || 32 | | | | | |
| 234 | 20 | PH10 | ALT3 | 0 | 33 || 34 | | | | | |
+------+-----+----------+------+---+----++----+---+------+----------+-----+------+
| GPIO | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | GPIO |
+------+-----+----------+------+---+ H616 +---+------+----------+-----+------+
I2C模块测试模块可以运行wiringOP中的oled_demo程序

串口模块可先通过串口助手验证每个指令的准确性, 然后运行wiringOP中的serialTest程序(需
把/dev/ttyS2改成/dev/ttyS5)

然后语音接收到指令后(比如喊你好美)会有6字节的输出,如下
bash
test@test:~/wiringOP-master/examples$ make serialTest
[CC] serialTest.c
[link]
test@test:~/wiringOP-master/examples$
test@test:~/wiringOP-master/examples$ sudo ./serialTest
[sudo] password for orangepi:
Out: 0:
Out: 1:
Out: 2:
Out: 3:
Out: 4:
Out: 5: -> 170 -> 85 -> 64 -> 0 -> 85 -> 170
Out: 6:
Out: 7:
Out: 8:
Out: 9:
Out: 10:
Out: 11:
Out: 12:
Out: 13:
2. 阿里云人脸识别方案
2.1 接入阿里云
本项目将采用人脸搜索1:N方案,通过提前在阿里云人脸数据库里存储人脸照片后,输入单张已授权人脸图像,与人脸库中人脸图片进行对比,最终获取比对结果。
官网地址如下:
c
https://vision.aliyun.com/
点击"人脸搜索1:N"

点击"立即开通"

使用阿里云APP/支付宝/钉钉扫码登录


购买"人脸搜索1:N"能力,第一次购买,可以有5000次的免费使用


开通完后, 在"工作台->开发能力->人脸人体->人脸数据库管理 " 添加人脸照片样本


上传数据库后,安装阿里云人脸识别SDK
c
pip install alibabacloud_facebody20191230
导入ALIBABA_CLOUD_ACCESS_KEY_ID和 ALIBABA_CLOUD_ACCESS_KEY_SECRET环境变量
c
vi ~/.bashrc #最后的结尾添加, 在垃圾分类的项目里如果已经添加过就不需要添加了
export ALIBABA_CLOUD_ACCESS_KEY_ID="你的KEY_ID"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="你的KEY_SECRECT"
可以拿同一人的照片和不同人的照片用官方python代码进行对比
python
# -*- 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'
)
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)
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)
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
# tips: 可通过error.__dict__查看属性名称
#关闭流
#stream0.close()
一般比对成功的Python字典数据里的score会有大于0.6的值,而比对失败score普遍低于0.1。例如下面是比对成功的数据。
c
{'Data': {'MatchList': [{'FaceItems': [{'Confidence': 80.54945, 'DbName':
'default', 'EntityId': 'sfms', 'FaceId': '88665949', 'Score':
0.7572572231292725}, {'Confidence': 77.51004, 'DbName': 'default', 'EntityId':
'sfms', 'FaceId': '88665951', 'Score': 0.7193253040313721}, {'Confidence':
74.420425, 'DbName': 'default', 'EntityId': 'sfms', 'FaceId': '88665946',
'Score': 0.6665557622909546}, {'Confidence': 11.461451, 'DbName': 'default',
'EntityId': 'lyf', 'FaceId': '88657431', 'Score': 0.0663260966539383},
{'Confidence': 5.28706, 'DbName': 'default', 'EntityId': 'lyf', 'FaceId':
'88657429', 'Score': 0.030595608055591583}], 'Location': {'Height': 527, 'Width':
405, 'X': 136, 'Y': 123}, 'QualitieScore': 99.3521}]}, 'RequestId': '6DE302BB-
130A-5D3C-B83D-0937D5A257FD'}
比对失败的数据则如下所示
c
{'Data': {'MatchList': [{'FaceItems': [{'Confidence': 6.137868, 'DbName':
'default', 'EntityId': 'lyf', 'FaceId': '88657429', 'Score':
0.03551913797855377}, {'Confidence': 2.9869182, 'DbName': 'default', 'EntityId':
'lyf', 'FaceId': '88657433', 'Score': 0.017284952104091644}, {'Confidence':
2.0808065, 'DbName': 'default', 'EntityId': 'lyf', 'FaceId': '88657431', 'Score':
0.01204138807952404}, {'Confidence': 0.71279377, 'DbName': 'default', 'EntityId':
'lyf', 'FaceId': '88657430', 'Score': 0.004124855622649193}, {'Confidence': 0.0,
'DbName': 'default', 'EntityId': 'sfms', 'FaceId': '88665951', 'Score':
-0.09112970530986786}], 'Location': {'Height': 257, 'Width': 173, 'X': 156, 'Y':
42}, 'QualitieScore': 99.673065}]}, 'RequestId': '62C20100-CCAC-5FE2-9BA6-
AE583F0056EF'}
因此,就可以利用获取的最大score的值判断是否大于0.6来判断是否比对成功。
返回数据的说明:
c
Data:这是一个对象,其中包含了匹配列表的信息。
MatchList:这是一个数组,其中包含了匹配的结果。每个元素都是一个对象,代表一个匹配项。
FaceItems:这是一个数组,其中包含了匹配项中所有人脸的信息。每个元素都是一个对象,包含了一些关于
该人脸的信息,如自信度(Confidence)、数据库名(DbName)、实体ID(EntityId)、面部ID
(FaceId)和分数(Score)。
Location:这是一个对象,包含了人脸在原始图像中的位置信息,包括宽度(Width)、高度(Height)、
左上角的x坐标(X)和y坐标(Y)。
QualitieScore:这是一个浮点数,表示了整个匹配过程的质量得分。
2.2 C语言调用阿里云人脸识别接口
修改垃圾分类项目的 face.py 代码,将其中的代码封装成函数,并获取其中字典里score的最大值,以备C语言调用
c
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230
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
from PIL import Image, ImageFile
import io
# 允许加载截断的图片(兼容性更强)
ImageFile.LOAD_TRUNCATED_IMAGES = True
def get_jpeg_stream(image_path):
try:
with Image.open(image_path) as img:
# 强制转换为RGB JPEG
if img.mode != 'RGB':
img = img.convert('RGB')
# 保存到内存缓冲区
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=95)
buf.seek(0)
# 验证JPEG头
if buf.read(2) != b'\xff\xd8':
raise ValueError("生成的JPEG无效")
buf.seek(0)
return buf
except Exception as e:
print(f"图片处理错误: {str(e)}")
return None
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')
stream0 = get_jpeg_stream(r'/tmp/SearchFace.jpg')
if stream0:
search_face_request.image_url_object = stream0
else:
print("无法处理图片,请检查文件")
search_face_request.image_url_object = stream0
#场景二:使用任意可访问的url
#url = 'http://viapi-test.oss-cn-shanghai.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace/SearchFace1.png'
#img = urlopen(url).read()
#search_face_request.image_url_object = io.BytesIO(img)
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 max_score
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
# tips: 可通过error.__dict__查看属性名称
#关闭流
#stream0.close()
if __name__ == "__main__":
alibaba_face()
这里面对scores = [item['Score'] for item in match_list[0]['FaceItems']] 的解释:
c
match_list[0]['FaceItems']]输入内容为:
[{'Confidence': 12.260886, 'DbName': 'default', 'EntityId': 'sfms', 'FaceId':
'88665949', 'Score': 0.07095234096050262}, {'Confidence': 9.446312, 'DbName':
'default', 'EntityId': 'sfms', 'FaceId': '88665946', 'Score':
0.054664719849824905}, {'Confidence': 1.2030103, 'DbName': 'default', 'EntityId':
'sfms', 'FaceId': '88665951', 'Score': 0.006961682811379433}, {'Confidence': 0.0,
'DbName': 'default', 'EntityId': 'lyf', 'FaceId': '88657431', 'Score':
-0.03559441864490509}, {'Confidence': 0.0, 'DbName': 'default', 'EntityId':
'lyf', 'FaceId': '88657429', 'Score': -0.04274216294288635}]
那么[item['Score'] for item in match_list[0]['FaceItems']是一个 Python 列表推导式),
用于从嵌套的字典中提取特定的值。
具体来说,match_list 是一个包含字典的列表。每个字典里都有很多键值对,其中一个键是
'FaceItems'。'FaceItems' 对应的值是一个字典列表,每个字典都代表一个面部信息,并且都有一个
'Score' 键。
这个列表推导式的目的是从 data 的第一个元素(即第一个字典)中的 'FaceItems' 键对应的字典列表中
提取所有 'Score' 键的值,并将这些值存储在一个新的列表 scores 中。
分解一下这个列表推导式:
for item in data[0]['FaceItems']:这部分代码遍历 match_list 的第一个元素中的
'FaceItems' 键对应的字典列表。在每次循环中,item 被赋予列表中的下一个字典。
item['Score']:这部分代码获取当前 item(即一个包含面部信息的字典)中 'Score' 键对应的值。
[item['Score'] for item in data[0]['FaceItems']]:整体而言,这个列表推导式创建一个新的列
表 scores,该列表包含 data 中第一个元素的 'FaceItems' 键对应的所有字典的 'Score' 键的值。
最终,scores 将是一个包含所有 'Score' 值的列表,你可以对这个列表进行进一步的操作和分析,比如找
出最大值。
2.3 POSIX消息队列
在后面的项目中会用POSIX消息队列, 它原来学的System V消息队列(msgget、msgsnd, msgrcv)类似,都是用以队列的形式传递消息。接口主要有以下几个:




其他说明:
-
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr) 中oflag和
mode 参数说明
参数oflag:同int open(const char *pathname, int flags, mode_t mode);函数的的oflag类似有O_RDONLY、O_RDWR, O_WRONLY,除此之外还有 O_CREAT、O_EXCL(如果 O_CREAT 指定,但name 不存在,就返回错误),O_NONBLOCK(以非阻塞方式打开消息队列,在正常情况下mq_receive和mq_send 函数会阻塞的地方,使用该标志打开的消息队列会返回 EAGAIN 错误)。
参数mode:同int open(const char *pathname, int flags, mode_t mode);函数的mode参数,用于指定权限位, 比如0644权限 -
关于 struct mq_attr属性结构体
c
struct mq_attr
{
long mq_flags;//阻塞标志, 0(阻塞)或O_NONBLOCK
long mq_maxmsg;//最大消息数
long mq_msgsize;//每个消息最大大小
long mq_curmsgs;//当前消息数
};
- mq_notiy函数的使用注意事项:
a. 注册撤销:当通知被发送给它的注册进程时,其注册会被撤销。这意味着,如果希望继续接收通知,进程必须再次调用 mq_notify 以重新注册。
b. 空队列与数据到来:消息机制触发条件是,在消息队列为空的情况下有数据到来才会触发。当消息队列不为空时,即使有新的数据到来也不会触发通知。
c. 阻塞与通知:只有当没有任何线程阻塞在该队列的 mq_receive 调用的前提下,通知才会发出。这意味着,如果有线程正在等待接收消息,通知可能不会被发送。 - struct sigevent和sigval_t sigev_val 的定义如下:
c
union sigval { /* Data passed with notification */
int sival_int; /* Integer value */
void *sival_ptr; /* Pointer value */
};
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value;
/* Data passed with notification */
void (*sigev_notify_function) (union sigval);
/* Function used for thread
notification (SIGEV_THREAD) */
void *sigev_notify_attributes;
/* Attributes for notification thread
(SIGEV_THREAD) */
pid_t sigev_notify_thread_id;
/* ID of thread to signal
(SIGEV_THREAD_ID); Linux-specific */
};
a. sigev_notify取值:
SIGEV_NONE:这个值表示不需要任何通知。当sigev_notify被设置为这个值时,即使事件发生了,也不会有任何通知发送到进程。
SIGEV_SIGNAL:事件发生时,将sigev_signo指定的信号发送给指定的进程。
SIGEV_THREAD:事件发生时,内核会(在此进程内)以sigev_notify_attributes为线程属性创建一个线程,并让其执行sigev_notify_function,并以sigev_value为其参数。
b. sigev_signo: 在sigev_notify=SIGEV_SIGNAL时使用,指定信号类别, 例如SIGUSR1、SIGUSR2等。
c.sigev_value: sigev_notify=SIGEV_SIGEV_THREAD时使用,作为sigev_notify_function的参数, 当发送信号时,这个值会传递给信号处理函数。
示例1:使用阻塞方式读写
c
#include <mqueue.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#if 0
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_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, struct mq_attr *attr,
struct mq_attr *oattr);
int mq_send(mqd_t mqdes, const char *ptr, size_t
len, unsigned int prio);
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t
len, unsigned int *prio);
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;//当前消息数
};
#endif
#define QUEUE_NAME "/test_queue"
#define MESSAGE "mqueue,test!"
void *sender_thread(void *arg)
{
//发送消息
mqd_t mqd = *(mqd_t *)arg;
char message[] = MESSAGE;
int send_size = -1;
send_size = mq_send(mqd,message,strlen(message)+1,0);
printf("sender thread message=%s,mqd=%d\n",message,mqd);
if(-1 == send_size){
if(errno == EAGAIN){
printf("message queue is full\n");
}else{
perror("mq_send");
}
}
}
void *receiver_thread(void *arg)
{
//接收消息
char buffer[256];
mqd_t mqd = *(mqd_t *)arg;
ssize_t receiver_size = -1;
printf("Receive thread start\n");
receiver_size = mq_receive(mqd,buffer,sizeof(buffer),NULL);
printf("receiver thread message=%s,mqd=%d,receiver_size=%ld\n",buffer,mqd,receiver_size);
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__,receiver_size);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t sender,receiver;
//创建消息队列
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(QUEUE_NAME,O_CREAT | O_RDWR,0666,&attr);
if(mqd == -1){
perror("mq_open");
return -1;
}
#if 0
if(pthread_create(&sender,NULL,sender_thread,(void *)&mqd) != 0){
perror("pthread_create sender");
return -1;
}
#endif
if(pthread_create(&receiver,NULL,receiver_thread,(void *)&mqd) != 0){
perror("pthread_create receiver");
return -1;
}
//pthread_join(sender,NULL);
pthread_join(receiver,NULL);
mq_close(mqd);
//mq_unlink(QUEUE_NAME);
return 0;
}
示例2: 使用mq_notify sigev_notify = SIGEV_THREAD异步通知的方式实现
c
#include <mqueue.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#if 0
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_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, struct mq_attr *attr,
struct mq_attr *oattr);
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);
ssize_t mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *prio);
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
{ /* Data passed with notification */
int sival_int; /* Integer value */
void *sival_ptr; /* Pointer value */
};
struct sigevent
{
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value;
/* Data passed with notification */
void (*sigev_notify_function)(union sigval);
/* Function used for thread
notification (SIGEV_THREAD) */
void *sigev_notify_attributes;
/* Attributes for notification thread
(SIGEV_THREAD) */
pid_t sigev_notify_thread_id;
/* ID of thread to signal
(SIGEV_THREAD_ID); Linux-specific */
};
#endif
#define QUEUE_NAME "/test_queue"
#define MESSAGE "mqueue,test!"
void *sender_thread(void *arg)
{
// 发送消息
mqd_t mqd = *(mqd_t *)arg;
char message[] = MESSAGE;
int send_size = -1;
// 发送消息到消息队列
send_size = mq_send(mqd, message, strlen(message) + 1, 0);
printf("sender thread message=%s,mqd=%d\n", message, mqd);
if (-1 == send_size)
{
// 如果消息队列已满,则打印提示信息
if (errno == EAGAIN)
{
printf("message queue is full\n");
}
// 否则,打印错误信息
else
{
perror("mq_send");
}
}
}
void notify_thread(union sigval arg)
{
// 定义消息队列描述符
mqd_t mqd = -1;
// 将arg.sival_ptr转换为mqd_t类型并赋值给mqd
mqd = *((mqd_t*)arg.sival_ptr);
// 定义缓冲区
char buffer[256];
// 定义接收消息的长度
ssize_t recv_size = -1;
// 定义sigevent结构体
struct sigevent sev;
// 将缓冲区清零
memset(buffer,0,sizeof(buffer));
// 打印线程开始信息
printf("notify_thread start,mqd=%d\n",mqd);
// 从消息队列中接收消息
recv_size = mq_receive(mqd,buffer,sizeof(buffer),NULL);
// 打印接收到的消息
printf("notify_thread recv_size=%ld,buffer=%s\n",recv_size,buffer);
// 如果接收消息失败
if (recv_size == -1){
// 如果错误码为EAGAIN,表示消息队列为空
if (errno == EAGAIN)
{
// 打印消息队列为空的信息
printf("message queue is empty\n");
// 退出程序
exit(1);
}else{
// 打印错误信息
perror("mq_receive");
// 退出程序
exit(1);
}
}
// 设置通知方式为线程
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");
// 退出程序
exit(1);
}
}
int main(int argc, char *argv[])
{
pthread_t sender, receiver;
// 创建消息队列
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(QUEUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
if (mqd == -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");
exit(1);
}
if (pthread_create(&sender, NULL, sender_thread, (void *)&mqd) != 0)
{
perror("pthread_create sender");
return -1;
}
pthread_join(sender, NULL);
sleep(5);
mq_close(mqd);
mq_unlink(QUEUE_NAME);
return 0;
}
3. 智能家居项目的软件实现
3.1 项目整体设计
整体的软件框架大致如下:

整个项目开启4个监听线程, 分别是:
- 语音监听线程:用于监听语音指令, 当有语音指令过来后, 通过消息队列的方式给消息处理线程发送指令
- 网络监听线程:用于监听网络指令,当有网络指令过来后, 通过消息队列的方式给消息处理线程发送指令
- 火灾检测线程:当存在煤气泄漏或者火灾闲情时, 发送警报指令给消息处理线程
- 消息监听线程: 用于处理以上3个线程发过来的指令,并根据指令要求配置GPIO引脚状态,OLED屏显示、语音播报,还有人脸识别开门
上述四个线程采用统一个对外接口接口,同时添加到监听链表中。
整个项目文件的目录如下:


3.2 项目代码的前期准备
语音模块、OLED显示、网络模块、这些代码都可以从智能垃圾分类系统的项目中直接拷贝过来使用,另外添加之前准备好的人脸识别的代码 。再定义gdevice.h和control.h的头文件。3rd目录直接从智能垃圾分类系统工程中拷贝过来, 主要是一些依赖库和头文件。Makefile文件也来自于上一个垃圾分类系统工程,只改目标文件。
3.2 项目各文件代码
Makefile文件代码:
bash
CC := aarch64-linux-gnu-gcc
SRC := $(shell find src -name "*.c")
INC := ./inc \
./3rd/usr/local/include \
./3rd/usr/include \
./3rd/usr/include/aarch64-linux-gnu/python3.10 \
./3rd/usr/include/aarch64-linux-gnu \
./3rd/usr/include/python3.10
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 -lpthread -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 [email protected]:/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
beep_gdevice.h:
c
#ifndef __BEEP_GDEVICE_H__
#define __BEEP_GDEVICE_H__
struct gdevice *add_beep_to_device_list(struct gdevice *pdevhead);
#endif /* __BEEP_GDEVICE_H__ */
beep_gdevice.c:
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_device_list(struct gdevice *pdevhead)
{
return add_interface_to_device_list(pdevhead, &beep_gdev);
}
bled_gdevice.h:
c
#ifndef __BLED_GDEVICE_H__
#define __BLED_GDEVICE_H__
struct gdevice *add_bled_to_device_list(struct gdevice *pdevhead);
#endif /* __BLED_GDEVICE_H__ */
bled_gdevice.c:
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_device_list(struct gdevice *pdevhead)
{
return add_interface_to_device_list(pdevhead, &bled_gdev);
}
control.h:
c
#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 /* __CONTROL_H__ */
control.c:
c
#include <stdio.h>
#include "control.h"
struct control *add_interface_to_control_list(struct control *phead,struct control *control_interface)
{
// 如果控制列表为空,则将smoke_control设置为头节点
if (NULL == phead)
{
phead = control_interface;
}
else
{
// 否则,将smoke_control添加到控制列表的头部
control_interface->next = phead;
phead = control_interface;
}
return phead;
}
face.h:
c
#ifndef __FACE__H
#define __FACE__H
void face_init(void);
double face_category(void);
void face_final(void);
#define WGET_CMD "wget http://192.168.0.10:8080/?action=snapshot -O /tmp/SearchFace.jpg"
#define SEARCHFACE_FILE "/tmp/SearchFace.jpg"
#endif
face.c:
c
#if 0
1、包含Python.h头文件,以便使用Python API。
2、使用void Py_Initialize()初始化Python解释器,
3、使用PyObject *PyImport_ImportModule(const char *name)和PyObject
*PyObject_GetAttrString(PyObject *o, const char *attr_name)获取sys.path对象,并利用
int PyList_Append(PyObject *list, PyObject *item)将当前路径.添加到sys.path中,以便加载
当前的Python模块(Python文件即python模块)。
4、使用PyObject *PyImport_ImportModule(const char *name)函数导入Python模块,并检查是否
有错误。
5、使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)函数获取
Python函数对象,并检查是否可调用。
6、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函数调用
Python函数,并获取返回值。
7、使用void Py_DECREF(PyObject *o)函数释放所有引用的Python对象。
8、结束时调用void Py_Finalize()函数关闭Python解释器。
相关的函数参数说明参考网站(网站左上角输入函数名即可开始搜索):
https://docs.python.org/zh-cn/3/c-api/import.html
#endif
#include <Python.h>
#include "face.h"
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("."));
}
void face_final(void)
{
// 关闭Python解释器
Py_Finalize();
}
double face_category()
{
double result = 0.0;
// 执行wget命令
system(WGET_CMD);
// 检查SEARCHFACE_FILE文件是否存在
if (0 != access(SEARCHFACE_FILE, F_OK)){
return result;
}
PyObject *pModule = PyImport_ImportModule("face"); //导入人脸识别python模块
if(!pModule){
PyErr_Print();
printf("Error: failed to load face.py\n");
goto FAILED_MODULE;
}
PyObject *pFun = PyObject_GetAttrString(pModule,"alibaba_face"); //获取人脸识别函数指针
if(!pFun){
PyErr_Print();
printf("Error: failed to load alibaba_face\n");
goto FAILED_FUNC;
}
PyObject *pValue = PyObject_CallObject(pFun,NULL); //通过指针调用人脸识别函数
if(!pValue){
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
if(!PyArg_Parse(pValue,"d",&result)){ //解析函数调用结果为C语言格式
PyErr_Print();
printf("Error: parse failed\n");
goto FAILED_RESULT;
}
printf("result=%0.2lf\n",result);
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFun);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
return result;
}
python
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230
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
from PIL import Image, ImageFile
import io
# 允许加载截断的图片(兼容性更强)
ImageFile.LOAD_TRUNCATED_IMAGES = True
def get_jpeg_stream(image_path):
try:
with Image.open(image_path) as img:
# 强制转换为RGB JPEG
if img.mode != 'RGB':
img = img.convert('RGB')
# 保存到内存缓冲区
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=95)
buf.seek(0)
# 验证JPEG头
if buf.read(2) != b'\xff\xd8':
raise ValueError("生成的JPEG无效")
buf.seek(0)
return buf
except Exception as e:
print(f"图片处理错误: {str(e)}")
return None
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')
stream0 = get_jpeg_stream(r'/tmp/SearchFace.jpg')
if stream0:
search_face_request.image_url_object = stream0
else:
print("无法处理图片,请检查文件")
search_face_request.image_url_object = stream0
#场景二:使用任意可访问的url
#url = 'http://viapi-test.oss-cn-shanghai.aliyuncs.com/viapi-3.0domepic/facebody/SearchFace/SearchFace1.png'
#img = urlopen(url).read()
#search_face_request.image_url_object = io.BytesIO(img)
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 max_score
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
# tips: 可通过error.__dict__查看属性名称
#关闭流
#stream0.close()
if __name__ == "__main__":
alibaba_face()
fan_gdevice.h:
c
#ifndef __FAN_GDEVICE_H__
#define __FAN_GDEVICE_H__
struct gdevice *add_fan_to_device_list(struct gdevice *pdevhead);
#endif /* __FAN_GDEVICE_H__ */
fan_gdevice.c:
c
#include "gdevice.h"
struct gdevice fan_gdev = {
.dev_name = "fan",
.key = 0x43,
.gpio_pin = 7,
.gpio_mode = OUTPUT,
.gpio_status = HIGH,
.check_face_status = 0,
.voice_set_status = 0,
};
struct gdevice *add_fan_to_device_list(struct gdevice *pdevhead)
{
return add_interface_to_device_list(pdevhead, &fan_gdev);
}
gdevice.h
c
#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 *add_interface_to_device_list(struct gdevice *pdevhead,struct gdevice *device_interface);
struct gdevice *find_device_by_key(struct gdevice *pdevhead,int key);
int set_gpio_gdevice_status(struct gdevice *pdev);
#endif // __GDEVICE_H__
gdevice.c:
c
#include "gdevice.h"
// 将接口添加到设备列表中
struct gdevice *add_interface_to_device_list(struct gdevice *pdevhead,struct gdevice *device_interface)
{
// 如果控制列表为空,则将smoke_control设置为头节点
if (NULL == pdevhead)
{
pdevhead = device_interface;
}else{
// 否则,将smoke_control添加到控制列表的头部
device_interface->next = pdevhead;
pdevhead = device_interface;
}
return pdevhead;
}
// 根据key查找设备
struct gdevice *find_device_by_key(struct gdevice *pdevhead,int key)
{
struct gdevice *p = pdevhead;
if(pdevhead == NULL){
return NULL;
}
while(p != NULL){
if(p->key == key){
return p;
}
p = p->next;
}
}
// 设置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;
}
global.h:
c
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
#include "msg_queue.h"
//控制节点结构体
typedef struct{
mqd_t mqd;
struct control *control_phead;
}control_info_t;
#endif
lock_gdevice.h:
c
#ifndef __LOCK_GDEVICE_H__
#define __LOCK_GDEVICE_H__
struct gdevice *add_lock_to_device_list(struct gdevice *pdevhead);
#endif /* __LOCK_GDEVICE_H__ */
lock_gdevice.c:
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_device_list(struct gdevice *pdevhead)
{
return add_interface_to_device_list(pdevhead, &lock_gdev);
}
lred_gdevice.h:
c
#ifndef __LRLED_GDEVICE_H__
#define __LRLED_GDEVICE_H__
struct gdevice *add_lrled_to_device_list(struct gdevice *pdevhead);
#endif /* __LRLED_GDEVICE_H__ */
lred_gdevice.c:
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_device_list(struct gdevice *pdevhead)
{
return add_interface_to_device_list(pdevhead, &lrled_gdev);
}
msg_queue.h:
c
#ifndef __MSG__QUEUE__H__
#define __MSG__QUEUE__H__
#include <mqueue.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
mqd_t msg_queue_create(void);
int send_message(mqd_t mqd, void *msg,int msg_len);
void msg_queue_final(mqd_t mqd);
#endif
msg_queue.c:
c
#include "msg_queue.h"
#include <stdio.h>
#define QUEUE_NAME "/test_queue"
//创建消息队列
mqd_t msg_queue_create()
{
pthread_t sender,receiver;
//创建消息队列
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(QUEUE_NAME,O_CREAT | O_RDWR,0666,&attr);
printf("%s| %s|%d: mqd=%d\n",__FILE__,__FUNCTION__,__LINE__,mqd);
return mqd;
}
//发送消息
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;
}
//关闭消息队列
void msg_queue_final(mqd_t mqd)
{
if(-1 != mqd){
mq_close(mqd);
}
mq_unlink(QUEUE_NAME);
mqd = -1;
}
myoled.h:
c
#ifndef __MYOLED__H
#define __MYOLED__H
int myoled_init(void);
int oled_show(void *arg);
#endif
myoled.c:
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;
int oled_show(void *arg)
{
unsigned char *buffer = (unsigned char *)arg;
if(buffer != NULL){
oled_putstrto(&disp, 0, 9 + 1, buffer);
}
#if 0
// 打印字符串
oled_putstrto(&disp, 0, 9 + 1, "This garbage is:");
disp.font = font2;
// 根据buffer[2]的值打印不同的字符串
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_send_buffer(&disp);
return 0;
}
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);
oled_show("Welcome go home\n");
return e;
}
receive_interface.h:
c
#ifndef __RECEIVE_INTERFACE_H__
#define __RECEIVE_INTERFACE_H__
#include "control.h"
struct control* add_receive_to_control_list(struct control *phead);
#endif
receive_interface.c:
c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include "msg_queue.h"
#include "global.h"
#include "receive_interface.h"
#include "myoled.h"
#include "face.h"
#include "lrled_gdevice.h"
#include "bled_gdevice.h"
#include "fan_gdevice.h"
#include "beep_gdevice.h"
#include "lock_gdevice.h"
#include "gdevice.h"
#include "ini.h"
//定义接收消息结构体
typedef struct {
int msg_len;
unsigned char *buffer;
control_info_t *control_info;
}recv_msg_t;
//定义oled文件描述符和设备类链表指针
static int oled_fd = -1;
static struct gdevice *pdevhead = NULL;
//接收初始化函数
static int receive_init(void)
{
//设备类链表添加
pdevhead = add_lrled_to_device_list(pdevhead); //添加客厅灯设备节点
pdevhead = add_bled_to_device_list(pdevhead); //添加卧室灯设备节点
pdevhead = add_fan_to_device_list(pdevhead); //添加风扇设备节点
pdevhead = add_beep_to_device_list(pdevhead); //添加蜂鸣器设备节点
pdevhead = add_lock_to_device_list(pdevhead); //添加电磁锁设备节点
//初始化oled
oled_fd = myoled_init();
//初始化人脸识别
face_init();
return 0;
}
//接收结束函数
static void receive_final(void)
{
//结束人脸识别
face_final();
//关闭oled
if(oled_fd != -1){
close(oled_fd);
}
}
//设备处理函数
static void *handler_device(void *arg)
{
recv_msg_t *recv_msg = NULL;
struct gdevice *cur_gdev = NULL;
int ret = -1;
pthread_t tid = -1;
int smoke_falg = -1;
char success_or_failed[20] = "success";
double face_result = 0.0;
pthread_detach(pthread_self());
//do something
if(arg != NULL){
recv_msg = (recv_msg_t *)arg;
printf("recv_msg->msg_len = %d\n",recv_msg->msg_len);
printf("%s|%s|%d:handler_device: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(recv_msg != NULL && recv_msg->buffer != NULL){
cur_gdev = find_device_by_key(pdevhead, recv_msg->buffer[2]);
}
// //若cur_gdev为空,则退出线程,防止程序段错误,比如语音唤醒的命令对应的设备不存在
// if(cur_gdev == NULL) {
// pthread_exit(NULL);
// }
//设置设备状态
if(cur_gdev != NULL){
cur_gdev->gpio_status = recv_msg->buffer[3] == 0 ? LOW : HIGH;
//对开锁做特殊处理
if(cur_gdev->check_face_status == 1){
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(cur_gdev->check_face_status == 0){
ret = set_gpio_gdevice_status(cur_gdev); //写入设备状态
}
//语音播报
if(cur_gdev->voice_set_status == 1){
if(recv_msg != NULL && recv_msg->control_info != NULL && recv_msg->control_info->control_phead != NULL){
struct control *pcontrol = recv_msg->control_info->control_phead;
while(pcontrol != NULL){
if(strstr(pcontrol->control_name, "voice")){
if(recv_msg->buffer[2] == 0x45 && recv_msg->buffer[3] == 0){
smoke_falg = 1;
}
pthread_create(&tid,NULL,pcontrol->set,(void *)recv_msg->buffer);
break;
}
pcontrol = pcontrol->next;
}
}
}
//oled显示
if(ret == -1){
memset(success_or_failed, '\0', sizeof(success_or_failed));
strncpy(success_or_failed, "failed",6);
}
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", cur_gdev->dev_name, change_status, success_or_failed);
//火灾显示特殊处理
if(smoke_falg == 1){
memset(oled_msg, 0, sizeof(oled_msg));
strcpy(oled_msg, "A risk of fire!\n");
}
oled_show(oled_msg);
printf("oled_msg=%s\n", oled_msg);
if(cur_gdev->gpio_status == HIGH){
sleep(3);
oled_show("Welcome go home\n");
}
//开门后一段时间关锁
if(cur_gdev->check_face_status == 1 && ret == 0 && face_result > 0.6){
sleep(5);
cur_gdev->gpio_status = HIGH;
set_gpio_gdevice_status(cur_gdev);
}
}
pthread_exit(0);
}
//接收函数
static void *receive_get(void *arg)
{
struct mq_attr attr;
recv_msg_t *recv_msg = NULL;
ssize_t read_len = -1;
char *buffer = NULL;
pthread_t tid = -1;
if(arg !=NULL){
recv_msg = (recv_msg_t *)malloc(sizeof(recv_msg_t));
recv_msg->control_info = (control_info_t*)arg;
recv_msg->msg_len = -1;
recv_msg->buffer = NULL;
}else{
pthread_exit(0);
}
//获取消息队列属性
if(mq_getattr(recv_msg->control_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){
//接收消息
read_len = mq_receive(recv_msg->control_info->mqd, buffer, attr.mq_msgsize, NULL);
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:read_len=%ld\n", __FILE__, __func__, __LINE__, read_len);
if(read_len == -1){
if(errno == EAGAIN){
printf("queue is empty\n");
}else{
break;
}
}else if(buffer[0] == 0xAA && buffer[1] == 0x55 && buffer[4] == 0x55 && buffer[5] == 0xAA){
//如果消息头尾正确,则处理消息
recv_msg->msg_len = read_len;
memcpy(recv_msg->buffer, buffer, read_len);
pthread_create(&tid, NULL, handler_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_control_list(struct control *phead)
{
return add_interface_to_control_list(phead, &receive_control);
}
smoke_interface.h:
c
#ifndef __SMOKE_INTERFACE_H__
#define __SMOKE_INTERFACE_H__
#include "control.h"
struct control* add_smoke_to_control_list(struct control *phead);
#endif
smoke_interface.c:
c
#include <unistd.h>
#include <wiringPi.h>
#include <stdio.h>
#include "msg_queue.h"
#include "global.h"
#include "smoke_interface.h"
#include "control.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;
control_info_t *control_info = NULL;
if(NULL != arg){
control_info = (control_info_t *)arg;
}
// 如果控制信息不为空,则获取消息队列
if(NULL != control_info){
mqd = control_info->mqd;
}
// 如果消息队列未打开,则退出线程
if(-1 == mqd){
pthread_exit(0);
}
// 分离线程
pthread_detach(pthread_self());
printf("%s thread start\n", __func__);
while (1){
status = digitalRead(SMOKE_PIN);
if(status == LOW){
switch_status = 1;
buffer[2] = 0x45;
buffer[3] = 0x00;
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]);
byte_send = mq_send(mqd, buffer, 6, 0);
if(-1 == byte_send){
continue;
}
}else if(status == HIGH && switch_status == 1){ // 如果烟雾传感器未检测到烟雾且之前状态为检测到烟雾则只发一次消息
switch_status = 0;
buffer[2] = 0x45;
buffer[3] = 0x01;
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]);
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_control_list(struct control *phead)
{
return add_interface_to_control_list(phead, &smoke_control);
}
socket_interface.h:
c
#ifndef __SOCKET_INTERFACE_H__
#define __SOCKET_INTERFACE_H__
#include "control.h"
struct control* add_tcpsocket_to_control_list(struct control *phead);
#endif
socket_interface.c:
c
#include <unistd.h>
#include "socket.h"
#include "msg_queue.h"
#include "global.h"
#include "socket_interface.h"
static int s_fd;
// 初始化TCP socket
static int tcpsocket_init(void)
{
s_fd = socket_init(IPADDR, IPPORT);
return -1;
}
// 关闭TCP socket
static void tcpsocket_final(void)
{
close(s_fd);
s_fd = -1;
}
// 获取TCP socket
static void *tcpsocket_get(void *arg)
{
int c_fd = -1;
char buffer[6];
int nread = -1;
struct sockaddr_in c_addr;
int keepalive = 1; // 开启TCP KeepAlive功能
int keepidle = 5; // tcp_keepalive_time 3s内没收到数据开始发送心跳包
int keepcnt = 3; // tcp_keepalive_probes 发送3次
int keepintvl = 3; // tcp_keepalive_intvl 每3s发送一次心跳包
mqd_t mqd = -1;
control_info_t *control_info = NULL;
if(NULL != arg){
control_info = (control_info_t *)arg;
}
pthread_detach(pthread_self());
printf("%s|%s|%d: socket fd=%d\n", __FILE__, __func__, __LINE__, s_fd);
if (s_fd == -1)
{
s_fd = socket_init(IPADDR, IPPORT);
if (s_fd == -1)
{
printf("%s|%s|%d: socket init failed\n", __FILE__, __func__, __LINE__);
pthread_exit(0);
}
}
// 如果控制信息不为空,则获取消息队列
if(NULL != control_info){
mqd = control_info->mqd;
}
// 如果消息队列未打开,则退出线程
if(-1 == mqd){
pthread_exit(0);
}
memset(&c_addr, 0, sizeof(struct sockaddr_in));
sleep(3);
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 (c_fd == -1)
{
perror("accept");
continue;
}
setsockopt(c_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
setsockopt(c_fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepidle, sizeof(keepidle));
setsockopt(c_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof(keepcnt));
setsockopt(c_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof(keepintvl));
printf("%s|%s|%d: Accept a connection from %s:%d\n", __FILE__, __func__, __LINE__, inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
while (1)
{
memset(buffer, 0, sizeof(buffer));
nread = recv(c_fd, buffer, sizeof(buffer), 0); // n_read = read(c_fd,buffer, sizeof(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]);
if (nread > 0)
{
// 如果接收到的数据符合协议,则发送消息
if (buffer[0] == 0xAA && buffer[1] == 0x55 && buffer[4] == 0x55 && buffer[5] == 0xAA)
{
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, nread);
}
}
else if (0 == nread || -1 == nread)
{
break;
}
}
close(c_fd);
}
pthread_exit(0);
}
struct control tcpsocket_control = {
.control_name = "tcpsocket",
.init = tcpsocket_init,
.final = tcpsocket_final,
.get = tcpsocket_get,
.set = NULL,
.next = NULL};
// 将TCP socket添加到控制列表中
struct control *add_tcpsocket_to_control_list(struct control *phead)
{
return add_interface_to_control_list(phead, &tcpsocket_control);
}
socket.h:
c
#ifndef __SOCKET__H
#define __SOCKET__H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#define IPADDR "192.168.0.10" //填写自己实际的ip地址
#define IPPORT "8192"
#define BUF_SIZE 6
int socket_init(const char *ipaddr, const char *port);
#endif
socket.c:
c
#include "socket.h"
// 初始化socket
int socket_init(const char *ipaddr, const char *port)
{
int s_fd = -1; // socket文件描述符
int ret = -1; // 返回值
struct sockaddr_in s_addr; // socket地址结构体
memset(&s_addr, 0, sizeof(struct sockaddr_in)); // 将s_addr结构体清零
// 1.socket
s_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建socket
if (s_fd == -1)
{
perror("socket"); // 打印错误信息
return -1;
}
s_addr.sin_family = AF_INET; // 设置地址族为IPv4
s_addr.sin_port = htons(atoi(port)); // 将端口号转换为网络字节序
inet_aton(ipaddr, &s_addr.sin_addr); // 将IP地址转换为网络字节序
// 2. bind
ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in)); // 绑定socket
if (-1 == ret)
{
perror("bind"); // 打印错误信息
return -1;
}
// 3. listen
ret = listen(s_fd, 1); // 只监听1个连接,排队扔垃圾
if (-1 == ret)
{
perror("listen"); // 打印错误信息
return -1;
}
return s_fd; // 返回socket文件描述符
}
uartTool.h:
c
#ifndef __UARTTOOL_H
#define __UARTTOOL_H
#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);
void serialSendString (const int fd, const unsigned char *s,int len);
int serialGetString (const int fd,unsigned char *buffer);
#define SERIAL_DEV "/dev/ttyS5"
#define BAUD 115200
#endif
uartTool.c:
c
#include "uartTool.h"
// 打开串口
int mySerialOpen (const char *device, const int baud)
{
struct termios options ;
speed_t myBaud ;
int status, fd ;
// 根据波特率设置myBaud
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 ;
}
void serialSendString (const int fd, const unsigned char *s,int len)
{
int ret;
ret = write (fd, s, len);
if (ret < 0)
printf("Serial Puts Error\n");
}
int serialGetString (const int fd,unsigned char *buffer)
{
int n_read;
n_read = read(fd,buffer,32);
return n_read;
}
voice_interface.h:
c
#ifndef __VOICE_INTERFACE_H__
#define __VOICE_INTERFACE_H__
#include "control.h"
struct control* add_voice_to_control_list(struct control *phead);
#endif
voice_interface.c:
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 "msg_queue.h"
#include "uartTool.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)
{
unsigned char buffer[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
int len = 0;
mqd_t mqd = -1;
control_info_t *control_info = NULL;
if(NULL != arg){
control_info = (control_info_t *)arg;
}
// 如果串口未打开,则打开串口
if (serial_fd == -1)
{
serial_fd = voice_init();
if(-1 == serial_fd){
pthread_exit(0);
}
}
// 如果控制信息不为空,则获取消息队列
if(NULL != control_info){
mqd = control_info->mqd;
}
// 如果消息队列未打开,则退出线程
if(-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[4] == 0x55 && buffer[5] == 0xAA){
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);
}
// 清空缓冲区
memset(buffer,0,sizeof(buffer));
}
}
pthread_exit(0);
}
//语音播报
static void *voice_set(void *arg)
{
unsigned char *buffer = (unsigned char *)arg;
pthread_detach(pthread_self()); // 忽略线程等待,自己释放资源
if (serial_fd == -1)
{
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);
}
main.c:
c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <wiringPi.h>
#include "voice_interface.h"
#include "msg_queue.h"
#include "control.h"
#include "global.h"
#include "socket_interface.h"
#include "smoke_interface.h"
#include "receive_interface.h"
int main(int argc, char *argv[])
{
pthread_t thread_id;
control_info_t *control_info = (control_info_t *)malloc(sizeof(control_info_t));
struct control *pointer = NULL;
int node_num = 0;
control_info->control_phead = NULL;
control_info->mqd = -1;
//初始化wiringPi库
if(wiringPiSetup() == -1){
return -1;
}
//创建消息队列
control_info->mqd = msg_queue_create();
if(control_info->mqd == -1){
printf("%s| %s| %d,control_info->mqd=%d\n",__FILE__, __func__, __LINE__, control_info->mqd);
return -1;
}
//插入各个控制节点
control_info->control_phead = add_voice_to_control_list(control_info->control_phead);
control_info->control_phead = add_tcpsocket_to_control_list(control_info->control_phead);
control_info->control_phead = add_smoke_to_control_list(control_info->control_phead);
control_info->control_phead = add_receive_to_control_list(control_info->control_phead);
//初始化控制节点
pointer = control_info->control_phead;
while(pointer != NULL){
if(pointer->init != NULL){
pointer->init();
pointer = pointer->next;
node_num++;
}
}
//定义控制节点线程数组
pthread_t *tid = malloc(sizeof(int) * node_num);
//创建控制节点线程
pointer = control_info->control_phead;
for(int i = 0; i < node_num; i++){
pthread_create(&tid[i], NULL, (void *)pointer->get, (void *)control_info);
pointer = pointer->next;
}
//主线程等待控制节点线程结束
for(int i = 0; i < node_num; i++){
pthread_join(tid[i], NULL);
}
//控制节点释放
for(int i = 0; i < node_num; i++){
if(pointer->final != NULL){
pointer->final(); //不等待子线程退出提前释放串口可能会造成主程序退出!!!
pointer = pointer->next;
}
}
//销毁消息队列
msg_queue_final(control_info->mqd);
//释放内存
if(control_info != NULL){
free(control_info);
}
if(tid != NULL){
free(tid);
}
return 0;
}
4. 代码优化
上面设备类的代码都是重复设备信息配置, 因此选择非常的冗余,其实这些信息完全可以利用配置文件进行配置,这样就不需要如此多的设备类节点代码, 也方便后期的添加维护。
4.1设备类节点直接通过文件配置
- 什么是.ini文件
ini文件通常以纯文本形式存在,并且包含了一个或多个节(sections)以及每个节下的键值对(key-value pairs)。这些键值对用来指定应用程序的各种设置。比如Linux系统里就有非常多这类格式的文件,如Linux下的打印机服务程序启动配置文件/lib/systemd/system/cups.service:
bash
[Unit]
Description=CUPS Scheduler
Documentation=man:cupsd(8)
After=network.target nss-user-lookup.target nslcd.service
Requires=cups.socket
[Service]
ExecStart=/usr/sbin/cupsd -l
Type=notify
Restart=on-failure
[Install]
Also=cups.socket cups.path
WantedBy=printer.target multi-user.target
- inih解析库介绍
inih是一个轻量级的C库,用于解析INI格式的配置文件。这个库由Ben Hoyt开发,并在GitHub上提供源代码(https://github.com/benhoyt/inih)。 inih 库的设计目标是简单易用,同时保持最小的依赖性。
以下是关于inih库的一些特点:
跨平台:inih库是跨平台的,可以在多种操作系统和编译器环境下使用。
体积小:inih库只有几个C文件,非常适合嵌入到其他项目中。
可定制:用户可以通过回调函数来处理读取到的键值对,使得处理方式非常灵活。
易于集成:只需要将ini.c和ini.h两个文件添加到你的项目中即可开始使用。
支持注释:inih库可以正确地处理以分号或哈希字符开头的行作为注释。
错误处理:如果在解析过程中遇到错误,ini_parse()函数会返回一个负数。
要使用inih库,你需要在你的代码中包含ini.h头文件,并调用ini_parse()函数来解析INI文件ini_parse()
函数接受三个参数:要解析的文件名、一个回调函数以及一个用户数据指针。每当找到一个新的键值对时,都会调用回调函数。例如,以下是一个简单的回调函数示例:
c
static int handler(void* user, const char* section,
const char* name, const char* value)
{
printf("Section: '%s', Name: '%s', Value: '%s'\n", section, name, value);
return 1; /* 成功 */
}
然后,你可以像这样调用ini_parse()函数:
c
int error = ini_parse("config.ini", handler, NULL);
if (error < 0) {
printf("Can't load 'config.ini'\n");
exit(1);
}
如果你需要更复杂的处理逻辑,你可以在回调函数中实现它。注意,inih库并不直接提供设置的持久化功能,因此你需要自己负责将修改后的设置写回INI文件。
- 首先定义设备控制ini文件gdevice.ini
bash
[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
- 下载libinih1源代码
bash
apt source libinih1
该命令会下载libinih1d源码, 将libinih-53目录中的ini.c和ini.h拷贝到项目工程中,同时移除设备类信息文件。
目录结构如下:

4.2 接收处理代码重新实现
修改receive_interface.c代码,实现利用libinih1解析库解析ini文件获取设备类节点:
c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include "msg_queue.h"
#include "global.h"
#include "receive_interface.h"
#include "myoled.h"
#include "face.h"
#include "gdevice.h"
#include "ini.h"
//定义接收消息结构体
typedef struct {
int msg_len;
unsigned char *buffer;
control_info_t *control_info;
}recv_msg_t;
//定义oled文件描述符和设备类链表指针
static int oled_fd = -1;
static struct gdevice *pdevhead = NULL;
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
//ini文件解析函数
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(strcmp(pdevhead->dev_name,section) != 0){
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")){
pdevhead->gpio_mode = INPUT;
}
}else if(MATCH(pdevhead->dev_name, "gpio_status")){
if(strcmp(value,"LOW") == 0){
pdevhead->gpio_status = LOW;
}else if(strcmp(value,"HIGH")){
pdevhead->gpio_status = 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);
}
}
printf("section=%s,name=%s,value=%s\n",section,name,value);
return 1;
}
//接收初始化函数
static int receive_init(void)
{
//ini设备类链表添加
if (ini_parse("gdevice.ini", handler_gdevice, NULL) < 0) {
printf("Can't load 'gdevice.ini'\n");
return 1;
}
//测试
struct gdevice *pdev = pdevhead;
while(pdev != NULL){
printf("pdev->dev_name=%s\n",pdev->dev_name);
printf("pdev->key=%x\n",pdev->key);
printf("pdev->gpio_pin=%d\n",pdev->gpio_pin);
printf("pdev->gpio_mode=%d\n",pdev->gpio_mode);
printf("pdev->gpio_status=%d\n",pdev->gpio_status);
printf("pdev->check_face_status=%d\n",pdev->check_face_status);
printf("pdev->voice_set_status=%d\n",pdev->voice_set_status);
pdev = pdev->next;
}
//初始化oled
oled_fd = myoled_init();
//初始化人脸识别
face_init();
return oled_fd;
}
//接收结束函数
static void receive_final(void)
{
//结束人脸识别
face_final();
//关闭oled
if(oled_fd != -1){
close(oled_fd);
}
}
//设备处理函数
static void *handler_device(void *arg)
{
recv_msg_t *recv_msg = NULL;
struct gdevice *cur_gdev = NULL;
int ret = -1;
pthread_t tid = -1;
int smoke_falg = -1;
char success_or_failed[20] = "success";
double face_result = 0.0;
pthread_detach(pthread_self());
//do something
if(arg != NULL){
recv_msg = (recv_msg_t *)arg;
printf("recv_msg->msg_len = %d\n",recv_msg->msg_len);
printf("%s|%s|%d:handler_device: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(recv_msg != NULL && recv_msg->buffer != NULL){
cur_gdev = find_device_by_key(pdevhead, recv_msg->buffer[2]);
}
// //若cur_gdev为空,则退出线程,防止程序段错误,比如语音唤醒的命令对应的设备不存在
// if(cur_gdev == NULL) {
// pthread_exit(NULL);
// }
//设置设备状态
if(cur_gdev != NULL){
cur_gdev->gpio_status = recv_msg->buffer[3] == 0 ? LOW : HIGH;
//对开锁做特殊处理
if(cur_gdev->check_face_status == 1){
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(cur_gdev->check_face_status == 0){
ret = set_gpio_gdevice_status(cur_gdev); //写入设备状态
}
//语音播报
if(cur_gdev->voice_set_status == 1){
if(recv_msg != NULL && recv_msg->control_info != NULL && recv_msg->control_info->control_phead != NULL){
struct control *pcontrol = recv_msg->control_info->control_phead;
while(pcontrol != NULL){
if(strstr(pcontrol->control_name, "voice")){
if(recv_msg->buffer[2] == 0x45 && recv_msg->buffer[3] == 0){
smoke_falg = 1;
}
pthread_create(&tid,NULL,pcontrol->set,(void *)recv_msg->buffer);
break;
}
pcontrol = pcontrol->next;
}
}
}
//oled显示
if(ret == -1){
memset(success_or_failed, '\0', sizeof(success_or_failed));
strncpy(success_or_failed, "failed",6);
}
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", cur_gdev->dev_name, change_status, success_or_failed);
//火灾显示特殊处理
if(smoke_falg == 1){
memset(oled_msg, 0, sizeof(oled_msg));
strcpy(oled_msg, "A risk of fire!\n");
}
oled_show(oled_msg);
printf("oled_msg=%s\n", oled_msg);
if(cur_gdev->gpio_status == HIGH){
sleep(3);
oled_show("Welcome go home\n");
}
//开门后一段时间关锁
if(cur_gdev->check_face_status == 1 && ret == 0 && face_result > 0.6){
sleep(5);
cur_gdev->gpio_status = HIGH;
set_gpio_gdevice_status(cur_gdev);
}
}
pthread_exit(0);
}
//接收函数
static void *receive_get(void *arg)
{
struct mq_attr attr;
recv_msg_t *recv_msg = NULL;
ssize_t read_len = -1;
char *buffer = NULL;
pthread_t tid = -1;
if(arg !=NULL){
recv_msg = (recv_msg_t *)malloc(sizeof(recv_msg_t));
recv_msg->control_info = (control_info_t*)arg;
recv_msg->msg_len = -1;
recv_msg->buffer = NULL;
}else{
pthread_exit(0);
}
//获取消息队列属性
if(mq_getattr(recv_msg->control_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){
//接收消息
read_len = mq_receive(recv_msg->control_info->mqd, buffer, attr.mq_msgsize, NULL);
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:read_len=%ld\n", __FILE__, __func__, __LINE__, read_len);
if(read_len == -1){
if(errno == EAGAIN){
printf("queue is empty\n");
}else{
break;
}
}else if(buffer[0] == 0xAA && buffer[1] == 0x55 && buffer[4] == 0x55 && buffer[5] == 0xAA){
//如果消息头尾正确,则处理消息
recv_msg->msg_len = read_len;
memcpy(recv_msg->buffer, buffer, read_len);
pthread_create(&tid, NULL, handler_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_control_list(struct control *phead)
{
return add_interface_to_control_list(phead, &receive_control);
}
5. 代码编译运行
在smarthome2.0文件目录下:
bash
//清除以前编译
make cleam
//交叉编译文件
make
//上传可执行文件文件到香橙派
scp ./smarthome ./face.py [email protected]:/home/orangepi
在香橙派上运行程序:
bash
sudo -E ./smarthome
注:默认情况下,sudo 会重置环境变量(如 PATH, HOME, LD_LIBRARY_PATH 等)为 root 用户的默认值。-E 选项让 sudo 继承当前用户的环境变量,避免因环境变量丢失导致程序运行出错。