【嵌入式Linux】基于ARM-Linux的zero2平台的智慧楼宇管理系统项目
目录
- 1. 需求及项目准备(此项目对于虚拟机和香橙派的配置基于上一个垃圾分类项目,如初次开发,两个平台的环境变量,阿里云接入,摄像头配置可参考垃圾分类项目)
-
- 1.1 系统框图
- 1.2 硬件接线
- 1.3 语音模块配置
- 1.4 模块测试
- 2. 阿里云人脸识别方案
- 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; then echo \"please install wiringOP first\"figpio mode 2 out #livingroomgpio mode 5 out #bedroom lightgpio mode 7 out #fangpio mode 8 out #lockgpio mode 9 out #beepfor i in 2 5 7 8 9do gpio write $i 1donefor i in 2 5 7 8 9do gpio write $i 0 sleep 3 gpio write $i 1donegpio mode 6 in #smokegpio readallsleep 5gpio 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 -> 170Out: 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.pyimport osimport iofrom urllib.request import urlopenfrom alibabacloud_facebody20191230.client import Clientfrom alibabacloud_facebody20191230.models import SearchFaceAdvanceRequestfrom alibabacloud_tea_openapi.models import Configfrom alibabacloud_tea_util.models import RuntimeOptionsconfig = 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\')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 = 5runtime_option = RuntimeOptions()try:# 初始化Clientclient = 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 osimport iofrom urllib.request import urlopenfrom alibabacloud_facebody20191230.client import Clientfrom alibabacloud_facebody20191230.models import SearchFaceAdvanceRequestfrom alibabacloud_tea_openapi.models import Configfrom alibabacloud_tea_util.models import RuntimeOptionsfrom PIL import Image, ImageFileimport io# 允许加载截断的图片(兼容性更强)ImageFile.LOAD_TRUNCATED_IMAGES = Truedef 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 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\', # 访问的域名对应的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’]] 的解释:
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 #include #include #include #include #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异步通知的方式实现
#include #include #include #include #include #include #include #include #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_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文件代码:
CC := aarch64-linux-gnu-gccSRC := $(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 #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 01、包含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 #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 osimport iofrom urllib.request import urlopenfrom alibabacloud_facebody20191230.client import Clientfrom alibabacloud_facebody20191230.models import SearchFaceAdvanceRequestfrom alibabacloud_tea_openapi.models import Configfrom alibabacloud_tea_util.models import RuntimeOptionsfrom PIL import Image, ImageFileimport io# 允许加载截断的图片(兼容性更强)ImageFile.LOAD_TRUNCATED_IMAGES = Truedef 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 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\', # 访问的域名对应的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:
#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 #include 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 #include #include #include 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 #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 #include #include #include #include #include #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:
#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 #include #include #include #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:
#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 #include #include #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 #include \"socket.h\"#include \"msg_queue.h\"#include \"global.h\"#include \"socket_interface.h\"static int s_fd;// 初始化TCP socketstatic int tcpsocket_init(void){ s_fd = socket_init(IPADDR, IPPORT); return -1;}// 关闭TCP socketstatic void tcpsocket_final(void){ close(s_fd); s_fd = -1;}// 获取TCP socketstatic 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 #include #include #include /* See NOTES */#include #include #include #include #include #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\"// 初始化socketint 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:
#ifndef __UARTTOOL_H#define __UARTTOOL_H#include #include #include #include #include #include #include #include #include #include #include #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 0struct control{ char control_name[128]; // 监听模块名称 int (*init)(void); // 初始化函数 void (*final)(void); // 结束释放函数 void *(*get)(void *arg); // 监听函数,如语音监听 void *(*set)(void *arg); // 设置函数,如语音播报 struct control *next;};#endif#include #include #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 #include #include #include #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 SchedulerDocumentation=man:cupsd(8)After=network.target nss-user-lookup.target nslcd.serviceRequires=cups.socket[Service]ExecStart=/usr/sbin/cupsd -lType=notifyRestart=on-failure[Install]Also=cups.socket cups.pathWantedBy=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=0x44gpio_pin=8gpio_mode=OUTPUTgpio_status=HIGHcheck_face_status=1voice_set_status=1[beep]key=0x45gpio_pin=9gpio_mode=OUTPUTgpio_status=HIGHcheck_face_status=0voice_set_status=1[BR led]key=0x42gpio_pin=5gpio_mode=OUTPUTgpio_status=HIGHcheck_face_status=0voice_set_status=0[LV led]key=0x41gpio_pin=2gpio_mode=OUTPUTgpio_status=HIGHcheck_face_status=0voice_set_status=0[fan]key=0x43gpio_pin=7gpio_mode=OUTPUTgpio_status=HIGHcheck_face_status=0voice_set_status=0
- 下载libinih1源代码
apt source libinih1
该命令会下载libinih1d源码, 将libinih-53目录中的ini.c和ini.h拷贝到项目工程中,同时移除设备类信息文件。
目录结构如下:
4.2 接收处理代码重新实现
修改receive_interface.c代码,实现利用libinih1解析库解析ini文件获取设备类节点:
#include #include #include #include #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文件目录下:
//清除以前编译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 继承当前用户的环境变量,避免因环境变量丢失导致程序运行出错。