深圳罗湖区网站建设公司,1688成品网站源码下载,杭州商城app开发,代写企业软文目录 1. 需求及项目准备#xff08;此项目对于虚拟机和香橙派的配置基于上一个垃圾分类项目#xff0c;如初次开发#xff0c;两个平台的环境变量#xff0c;阿里云接入#xff0c;摄像头配置可参考垃圾分类项目#xff09;1.1 系统框图1.2 硬件接线1.3 语音模块配置1.4 … 目录 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、 电磁锁x15V吸合开锁、 蜂鸣器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 21; 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运行脚本观察硬件反应
orangepiorangepizero2:~$ bash -x ./gpiotest.shwhich gpiogpio mode 2 outgpio mode 5 outgpio mode 7 outgpio mode 8 outgpio mode 9 outfor i in 2 5 7 8 9gpio write 2 1for i in 2 5 7 8 9gpio write 5 1for i in 2 5 7 8 9gpio write 7 1for i in 2 5 7 8 9gpio write 8 1for i in 2 5 7 8 9gpio write 9 1for i in 2 5 7 8 9gpio write 2 0sleep 3gpio write 2 1for i in 2 5 7 8 9gpio write 5 0sleep 3gpio write 5 1for i in 2 5 7 8 9gpio write 7 0sleep 3gpio write 7 1for i in 2 5 7 8 9gpio write 8 0sleep 3gpio write 8 1for i in 2 5 7 8 9gpio write 9 0sleep 3gpio write 9 1gpio mode 6 ingpio 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 5gpio 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字节的输出如下
testtest:~/wiringOP-master/examples$ make serialTest
[CC] serialTest.c
[link]
testtest:~/wiringOP-master/examples$
testtest:~/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_idos.environ.get(ALIBABA_CLOUD_ACCESS_KEY_ID),
access_key_secretos.environ.get(ALIBABA_CLOUD_ACCESS_KEY_SECRET),
# 访问的域名
endpointfacebody.cn-shanghai.aliyuncs.com,
# 访问的域名对应的region
region_idcn-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、实体IDEntityId、面部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, formatJPEG, quality95)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_idos.environ.get(ALIBABA_CLOUD_ACCESS_KEY_ID),access_key_secretos.environ.get(ALIBABA_CLOUD_ACCESS_KEY_SECRET),# 访问的域名endpointfacebody.cn-shanghai.aliyuncs.com,# 访问的域名对应的regionregion_idcn-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 defaultsearch_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_notifySIGEV_SIGNAL时使用指定信号类别, 例如SIGUSR1、SIGUSR2等。 c.sigev_value: sigev_notifySIGEV_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 orangepi192.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 debugbeep_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.hstruct 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.hstruct 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.hstruct 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/?actionsnapshot -O /tmp/SearchFace.jpg
#define SEARCHFACE_FILE /tmp/SearchFace.jpg#endifface.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.hvoid 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, formatJPEG, quality95)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_idos.environ.get(ALIBABA_CLOUD_ACCESS_KEY_ID),access_key_secretos.environ.get(ALIBABA_CLOUD_ACCESS_KEY_SECRET),# 访问的域名endpointfacebody.cn-shanghai.aliyuncs.com,# 访问的域名对应的regionregion_idcn-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 defaultsearch_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.hstruct 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.hstruct 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;#endiflock_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.hstruct 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.hstruct 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.hmqd_t msg_queue_create(void);
int send_message(mqd_t mqd, void *msg,int msg_len);
void msg_queue_final(mqd_t mqd);#endifmsg_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);#endifmyoled.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-3static 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);#endifreceive_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);#endifsmoke_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);#endifsocket_interface.c:
#include unistd.h
#include socket.h
#include msg_queue.h
#include global.h
#include socket_interface.hstatic 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);#endifsocket.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.hint 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#endifuartTool.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);#endifvoice_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.hstatic 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.hint 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]
DescriptionCUPS Scheduler
Documentationman:cupsd(8)
Afternetwork.target nss-user-lookup.target nslcd.service
Requirescups.socket[Service]
ExecStart/usr/sbin/cupsd -l
Typenotify
Restarton-failure[Install]
Alsocups.socket cups.path
WantedByprinter.target multi-user.targetinih解析库介绍
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(Cant load config.ini\n);exit(1);
}如果你需要更复杂的处理逻辑你可以在回调函数中实现它。注意inih库并不直接提供设置的持久化功能因此你需要自己负责将修改后的设置写回INI文件。
首先定义设备控制ini文件gdevice.ini
[lock]
key0x44
gpio_pin8
gpio_modeOUTPUT
gpio_statusHIGH
check_face_status1
voice_set_status1[beep]
key0x45
gpio_pin9
gpio_modeOUTPUT
gpio_statusHIGH
check_face_status0
voice_set_status1[BR led]
key0x42
gpio_pin5
gpio_modeOUTPUT
gpio_statusHIGH
check_face_status0
voice_set_status0[LV led]
key0x41
gpio_pin2
gpio_modeOUTPUT
gpio_statusHIGH
check_face_status0
voice_set_status0[fan]
key0x43
gpio_pin7
gpio_modeOUTPUT
gpio_statusHIGH
check_face_status0
voice_set_status0下载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(Cant 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 orangepi192.168.x.x:/home/orangepi在香橙派上运行程序
sudo -E ./smarthome注默认情况下sudo 会重置环境变量如 PATH, HOME, LD_LIBRARY_PATH 等为 root 用户的默认值。-E 选项让 sudo 继承当前用户的环境变量避免因环境变量丢失导致程序运行出错。