【嵌入式Linux】基于ARM-Linux的zero2平台的智慧楼宇管理系统项目
目录
- 1. 需求及项目准备(此项目对于虚拟机和香橙派的配置基于上一个垃圾分类项目,如初次开发,两个平台的环境变量,阿里云接入,摄像头配置可参考垃圾分类项目)
- 1.1 系统框图
- 1.2 硬件接线
- 1.3 语音模块配置
- 1.4 模块测试
- 2. 阿里云人脸识别方案
- 2.1 接入阿里云
- 2.2 C语言调用阿里云人脸识别接口
- 2.3 POSIX消息队列
- 3. 智能家居项目的软件实现
- 3.1 项目整体设计
- 3.2 项目代码的前期准备
- 3.2 项目各文件代码
- 4. 代码优化
- 4.1设备类节点直接通过文件配置
- 4.2 接收处理代码重新实现
- 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路继电器接线图
4. 面包板接线
1.3 语音模块配置
- pin脚配置
- 命令词自定义基本信息
- 命令词自定控制详情
1.4 模块测试
使用以下下脚本可分别测试继电器控制的客厅灯、卧室灯、风扇、烟雾报装置是否正常连接。会依次触发灯的亮灭、电磁锁通断、风扇开关、蜂鸣器的播听及最后读取两次gpio的引进状态。 可通过查看pin6最终确定烟雾报警模块在有烟雾的情况下的状态是否变为0。
#!/bin/bash#if [ $# -ne 1 ]
#then
# echo $#
# echo "usage: ./gpiotest.shh 0/1"
# exit 0
#fiif ! which gpio > /dev/null 2>&1; thenecho "please install wiringOP first"
figpio mode 2 out #livingroom
gpio mode 5 out #bedroom light
gpio mode 7 out #fan
gpio mode 8 out #lock
gpio mode 9 out #beepfor i in 2 5 7 8 9
dogpio write $i 1
donefor i in 2 5 7 8 9
dogpio write $i 0sleep 3gpio write $i 1
donegpio mode 6 in #smokegpio readall
sleep 5
gpio readall
运行脚本,观察硬件反应
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字节的输出,如下
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方案,通过提前在阿里云人脸数据库里存储人脸照片后,输入单张已授权人脸图像,与人脸库中人脸图片进行对比,最终获取比对结果。
官网地址如下:
https://vision.aliyun.com/
点击“人脸搜索1:N”
点击"立即开通"
使用阿里云APP/支付宝/钉钉扫码登录
购买“人脸搜索1:N”能力,第一次购买,可以有5000次的免费使用
开通完后, 在”工作台->开发能力->人脸人体->人脸数据库管理 " 添加人脸照片样本
上传数据库后,安装阿里云人脸识别SDK
pip install alibabacloud_facebody20191230
导入ALIBABA_CLOUD_ACCESS_KEY_ID和 ALIBABA_CLOUD_ACCESS_KEY_SECRET环境变量
vi ~/.bashrc #最后的结尾添加, 在垃圾分类的项目里如果已经添加过就不需要添加了
export ALIBABA_CLOUD_ACCESS_KEY_ID="你的KEY_ID"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="你的KEY_SECRECT"
可以拿同一人的照片和不同人的照片用官方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。例如下面是比对成功的数据。
{'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'}
比对失败的数据则如下所示
{'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来判断是否比对成功。
返回数据的说明:
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语言调用
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230import 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 = Truedef get_jpeg_stream(image_path):try:with Image.open(image_path) as img:# 强制转换为RGB JPEGif 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 bufexcept Exception as e:print(f"图片处理错误: {str(e)}")return Noneconfig = 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',# 访问的域名对应的regionregion_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 = stream0else: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 = 5runtime_option = RuntimeOptions()try:# 初始化Clientclient = 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_scoreexcept 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’]] 的解释:
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属性结构体
struct mq_attr
{long mq_flags;//阻塞标志, 0(阻塞)或O_NONBLOCKlong mq_maxmsg;//最大消息数long mq_msgsize;//每个消息最大大小long mq_curmsgs;//当前消息数
};
- mq_notiy函数的使用注意事项:
a. 注册撤销:当通知被发送给它的注册进程时,其注册会被撤销。这意味着,如果希望继续接收通知,进程必须再次调用 mq_notify 以重新注册。
b. 空队列与数据到来:消息机制触发条件是,在消息队列为空的情况下有数据到来才会触发。当消息队列不为空时,即使有新的数据到来也不会触发通知。
c. 阻塞与通知:只有当没有任何线程阻塞在该队列的 mq_receive 调用的前提下,通知才会发出。这意味着,如果有线程正在等待接收消息,通知可能不会被发送。 - struct sigevent和sigval_t sigev_val 的定义如下:
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 threadnotification (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:使用阻塞方式读写
#include <mqueue.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>#if 0mqd_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_tlen, unsigned int prio);ssize_t mq_receive(mqd_t mqdes, char *ptr, size_tlen, unsigned int *prio);int mq_notify(mqd_t mqdes, const struct sigevent*notification);struct mq_attr{long mq_flags;//阻塞标志, 0(阻塞)或O_NONBLOCKlong 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 0if(pthread_create(&sender,NULL,sender_thread,(void *)&mqd) != 0){perror("pthread_create sender");return -1;}#endifif(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异步通知的方式实现
#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_NONBLOCKlong 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 threadnotification (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类型并赋值给mqdmqd = *((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文件代码:
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.10OBJ := $(subst src/,obj/,$(SRC:.c=.o))TARGET = obj/smarthomeCFLAGS := $(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.10LDFLAGS := $(foreach item,$(LIBS_PATH),-L $(item))LIBS := -lwiringPi -lpython3.10 -lpthread -lexpat -lz -lcryptobj/%.o:src/%.cmkdir -p obj$(CC) -o $@ -c $< $(CFLAGS)$(TARGET) : $(OBJ)$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS)scp obj/smarthome src/face.py orangepi@192.168.0.10:/home/orangepicompile: $(TARGET)clean: rm $(TARGET) obj $(OBJ) -rfdebug:echo $(CC)echo $(SRC)echo $(INC)echo $(OBJ)echo $(TARGET)echo $(CFLAGS)echo $(LDFLAGS)echo $(LIBS).PHONY: clean compile debug
beep_gdevice.h:
#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:
#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:
#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:
#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:
#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:
#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:
#ifndef __FACE__H
#define __FACE__Hvoid 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:
#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;
}
face.py:
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_facebody20191230import 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 = Truedef get_jpeg_stream(image_path):try:with Image.open(image_path) as img:# 强制转换为RGB JPEGif 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 bufexcept Exception as e:print(f"图片处理错误: {str(e)}")return Noneconfig = 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',# 访问的域名对应的regionregion_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 = stream0else: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 = 5runtime_option = RuntimeOptions()try:# 初始化Clientclient = 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_scoreexcept Exception as error:# 获取整体报错信息print(error)# 获取单个字段print(error.code)# tips: 可通过error.__dict__查看属性名称#关闭流#stream0.close()if __name__ == "__main__":alibaba_face()
fan_gdevice.h:
#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:
#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
#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:
#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:
#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:
#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:
#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:
#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:
#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:
#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:
#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:
#ifndef __MYOLED__H
#define __MYOLED__Hint myoled_init(void);
int oled_show(void *arg);#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;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;}#endifdisp.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:
#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:
#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); //添加电磁锁设备节点//初始化oledoled_fd = myoled_init();//初始化人脸识别face_init();return 0;
}//接收结束函数
static void receive_final(void)
{//结束人脸识别face_final();//关闭oledif(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 somethingif(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:
#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:
#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:
#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:
#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:
#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 6int socket_init(const char *ipaddr, const char *port);#endif
socket.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.sockets_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建socketif (s_fd == -1){perror("socket"); // 打印错误信息return -1;}s_addr.sin_family = AF_INET; // 设置地址族为IPv4s_addr.sin_port = htons(atoi(port)); // 将端口号转换为网络字节序inet_aton(ipaddr, &s_addr.sin_addr); // 将IP地址转换为网络字节序// 2. bindret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in)); // 绑定socketif (-1 == ret){perror("bind"); // 打印错误信息return -1;}// 3. listenret = listen(s_fd, 1); // 只监听1个连接,排队扔垃圾if (-1 == ret){perror("listen"); // 打印错误信息return -1;}return s_fd; // 返回socket文件描述符
}
uartTool.h:
#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:
#include "uartTool.h"// 打开串口
int mySerialOpen (const char *device, const int baud)
{struct termios options ;speed_t myBaud ;int status, fd ;// 根据波特率设置myBaudswitch (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) ; // 10mSreturn 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:
#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:
#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:
#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:
[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()
函数接受三个参数:要解析的文件名、一个回调函数以及一个用户数据指针。每当找到一个新的键值对时,都会调用回调函数。例如,以下是一个简单的回调函数示例:
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()函数:
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
[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源代码
apt source libinih1
该命令会下载libinih1d源码, 将libinih-53目录中的ini.c和ini.h拷贝到项目工程中,同时移除设备类信息文件。
目录结构如下:
4.2 接收处理代码重新实现
修改receive_interface.c代码,实现利用libinih1解析库解析ini文件获取设备类节点:
#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;}//初始化oledoled_fd = myoled_init();//初始化人脸识别face_init();return oled_fd;
}//接收结束函数
static void receive_final(void)
{//结束人脸识别face_final();//关闭oledif(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 somethingif(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文件目录下:
//清除以前编译
make cleam
//交叉编译文件
make
//上传可执行文件文件到香橙派
scp ./smarthome ./face.py orangepi@192.168.x.x:/home/orangepi
在香橙派上运行程序:
sudo -E ./smarthome
注:默认情况下,sudo 会重置环境变量(如 PATH, HOME, LD_LIBRARY_PATH 等)为 root 用户的默认值。-E 选项让 sudo 继承当前用户的环境变量,避免因环境变量丢失导致程序运行出错。