基于Orangepi全志H616智能视觉垃圾分类系统

目录

一、功能需求

二、Python的安装和环境搭建

三、Python基础

3.1 Python的特点:

3.2 Python的基础学习:

3.3 字典的多层嵌套:

四、C语言调用Python

4.1 搭建编译环境:

4.2 C语言执行Python语句:

4.3 C语言调用Python无参函数:

C语言调用Python无参函数代码详解:

逐步分解代码

代码总结

4.4 C语言调用Python有参函数:

C语言调用Python有参函数代码详解:

逐步分解代码

总结代码

五、阿里云垃圾识别方案

5.1 接入阿里云:

5.1.1 开通阿里云账号及图像识别服务:

5.1.2 创建并获取AccessKey ID和Secret:

5.1.3 安装图像识别SDK:

5.1.4 配置环境变量:

5.1.5 根据示例代码进行修改进行垃圾分类识别:

5.2 C语言调用阿里云Python接口:

5.3 封装图像识别接口供项目使用:

六、香橙派使用USB摄像头

6.1 将USB摄像头接入香橙派:

6.2 通过lsmod指令查看内核是否加载USB摄像头模块:

6.3 通过 v4l2-ctl 指令查看USB 摄像头的设备节点信息:

6.4 使用 fswebcam 测试 USB 摄像头:

6.4.1 安装fswebcam:

6.4.2 安装完 fswebcam 后可以使用下面的命令来拍照:

6.5 使用 mjpg-streamer 测试 USB 摄像头:

6.5.1 下载 mjpg-streamer:

6.5.2 安装依赖的软件包:

6.5.3 编译安装 mjpg-streamer:

6.5.4 输入下面命令启动 mjpg_streamer:

6.5.5 局域网设备通过开发板IP地址和端口号查看摄像头:

6.6 使用wget指令拍一张照片:

6.7 设置摄像头服务每次开机自启动:

6.7.1 创建一个mjpg.sh脚本并赋予可执行的权限:

6.7.2 设置开机自启动:

七、SU-03T语音模组配置和烧录

7.1 语音模块交互示意图:

7.2 PIN引脚配置:

7.3 唤醒词定义:

7.4 命令词自定义基础信息和控制详情:

7.5 其他配置:

7.6 固件烧录:

八、基于Orangepi的视觉垃圾分类系统

8.1 语音模组和阿里云图像识别结合:

8.1.1 语音模组和阿里云图像识别流程:

8.1.2 环境准备:

8.1.3 代码实现:

8.2 增加垃圾桶开关盖功能:

8.2.2 硬件接线:

8.2.3 PWM频率的公式:

8.2.4 代码实现:

8.3 项目代码优化:

8.3.1 代码流程:

8.3.2 代码实现:

8.4 写一个shell脚本用于杀死运行的进程:

8.5 增加OLED显示功能:

8.5.1 环境配置:

8.5.2 代码实现:

8.6 增加网络控制功能:

8.6.1 代码流程:

8.6.2 TCP 心跳机制解决Soket异常断开问题:

8.6.3 C语言实现TCP KeepAlive功能:

8.6.4 代码实现:


一、功能需求

  • 语音接入控制垃圾分类识别,并触发垃圾桶的开关盖

  • 回顾二阶段的Socket编程,实现Sockect发送指令远程控制垃圾分类识别,并触发垃圾桶的开关盖

  • 图像识别垃圾分类功能

  • 语音播报垃圾物品类型

  • OLED显示垃圾物品类型

  • 根据垃圾类型开关不同类型垃圾桶

图像处理使用阿里SDK支持Python和Java接口,目的是引入C语言的Python调用,感受大厂做的算法bug

此接口是人工智能接口,阿里云识别模型是通过训练后的模型,精准度取决于训练程度,人工智能范畴在常规嵌入式设备负责执行居多,

说白的嵌入式设备负责数据采集,然后转发给人工智能识别后,拿到结果进行执行器动作

二、Python的安装和环境搭建

环境搭建:(备注:在香橙派 3.0.6版本的镜像里已经默认自带了python3.10的版本,不需要安装,只需要后续安装下python3 dev即可,后续统一采用Orangepizero2_3.0.6_ubuntu_jammy_desktop_xfce_linux5.16.17的系统镜像)

1、查看当前Linux下自带的Python版本
python --version
    
2、更新Linux源
sudo apt update
    
3、安装Python所需要的环境-代码如下(通用代码:树莓派、全志、Linux均适用)
sudo apt install -y build-essential zlib1g-dev \
libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev \
libreadline-dev libffi-dev curl libbz2-dev
    
4、载python3.9源码压缩包
wget \
https://www.python.org/ftp/python/3.9.10/Python-3.9.10.tgz

5、解压包
tar xvf Python-3.9.10.tgz
    
6、进入python文件操作-配置
./configure --enable-optimizations
    
7、查看创建好的Makefile文件
vi Makefile
    
8、编译(大概35分钟)
make -j4
    
9、安装
sudo make install
    
10、查看python安装路径
/usr/local/bin

11、可以查看到其他路径下的python版本
which python

12、可以将python2.7版本删除
sudo rm -f /usr/bin/python

13.建立一个软链接专门指向python3.9
sudo ln -s /usr/local/bin/python3.9 /usr/bin/python

14.更新源
sudo apt install -y python-pip python3-pip

15.更新源(返回主目录下再操作)
mkdir .pip 建立pip工作文件夹

cd .pip

vi pip.conf 添加 pip 服务器配置文件

内容如下:
[global]
timeout = 6000
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host = pypi.tuna.tsinghua.edu.cn

到这里pytnon就安装完成了,安装好之后输入python指令进入python的命令行模式

三、Python基础

Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。

Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构。

  • Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。

  • Python 是交互式语言: 这意味着,您可以在一个 Python 提示符 >>> 后直接执行代码。

  • Python 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。

  • Python 是初学者的语言:Python 对初级程序员而言,是一种伟大的语言,它支持广泛的应用程序开发,从简单的文字处理到 WWW 浏览器再到游戏。

系统学习Python3Python 基础教程 | 菜鸟教程 (runoob.com)]()Python3课程内容 地址如下:

https://www.runoob.com/python/python-tutorial.html

3.1 Python的特点:

Python 是一种广泛使用的高级编程语言,以其简洁、易读和强大的功能而著称。以下是 Python 的一些主要特点:

  1. 易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单。

  2. 易于阅读:Python代码定义的更清晰。

  3. 易于维护:Python的成功在于它的源代码是相当容易维护的。

  4. 一个广泛的标准库:Python的最大的优势之一是丰富的库,跨平台的,在UNIX,Windows和Macintosh兼容很好。

  5. 互动模式:互动模式的支持,您可以从终端输入执行代码并获得结果的语言,互动的测试和调试代码片断。

  6. 可移植:基于其开放源代码的特性,Python已经被移植(也就是使其工作)到许多平台。

  7. 可扩展:如果你需要一段运行很快的关键代码,或者是想要编写一些不愿开放的算法,你可以使用C或C++完成那部分程序,然后从你的Python程序中调用。

  8. 数据库:Python提供所有主要的商业数据库的接口。

  9. GUI编程:Python支持GUI可以创建和移植到许多系统调用。

  10. 可嵌入: 你可以将Python嵌入到C/C++程序,让你的程序的用户获得"脚本化"的能力。

综上所述,Python 是一种功能强大、易于学习且应用广泛的编程语言,它的这些特点使得它成为许多程序员的首选语言。

3.2 Python的基础学习:

Python的基础部分我们单独写了另外一篇文章,可以查看此文章入门Python:

[基于Orangepi全志H616学习Python3-CSDN博客](https://blog.csdn.net/weixin_54859557/article/details/140914836?spm=1001.2014.3001.5502)

https://blog.csdn.net/weixin_54859557/article/details/140914836?spm=1001.2014.3001.5502	

更多的关于Python学习请点击上面链接🔗进入菜鸟教程学习!

3.3 字典的多层嵌套:

dict(字典)的使用,字典(Dictionary)是Python里非常常见的一种数据结 构,如果是在其他语言里,一般称做map。是由键(key)值(value)成对组成,键和值中间以冒 号":“隔开键值对之间用”,“隔开整个字典由大括号”{}"括起来。 格式如下:

dict = {key1 : value1, key2 : value2 }

如之前课程提供的例子:

tinydict = {'name': 'runoob', 'likes': 123, 'url': 'www.runoob.com'}

这里面,键一般是唯一的,如果重复了, 最后的一个键值对(Key:value)会替换前面的。 而且键可以 用数字,字符串或元组充当,用列表不行。而且值就不需要唯一,而且形式多样,比如可以以列表或者 dict的形式出现。

dict的使用非常灵活, 甚至可以和列表组合使用, 列表里能嵌套列表,也能嵌套字典。同样的,字典里 能嵌套字典,字典里也能嵌套列表。 如下面这个例子:

garbage_dict = {'Data': {'Elements': [{'Category': '干垃圾', 'CategoryScore':
0.8855999999999999, 'Rubbish': '', 'RubbishScore': 0.0}], 'Sensitive': False},
'RequestId': '1AB9E813-3781-5CA2-95A0-1EA334E80663'}

这个例子里的dict内容是就是一个嵌套的结构,也就是说,它包含了其他的dict或列表作为值。我们可以 用以下的方式来理解它:

  • 最外层的dict有两个键:‘Data’和’RequestId’。

  • 'Data’对应的值是一个内层的dict,它有两个键:‘Elements’和’Sensitive’。

  • 'Elements’对应的值是一个列表,它包含了一个元素,也就是另一个内层的dict。

  • 这个内层的dict有四个键:‘Category’、‘CategoryScore’、‘Rubbish’和’RubbishScore’。

  • ‘Category’对应的值是一个字符串,表示垃圾分类的类别,例如’干垃圾’。

  • 'CategoryScore’对应的值是一个浮点数,表示垃圾分类的置信度,例如0.8856。

  • ‘Rubbish’对应的值是一个字符串,表示垃圾的具体名称,例如’'(空字符串)。

  • 'RubbishScore’对应的值是一个浮点数,表示垃圾名称的置信度,例如0.0。

  • 'Sensitive’对应的值是一个布尔值,表示是否涉及敏感信息,例如False。

  • ‘RequestId’对应的值是一个字符串,表示请求的唯一标识符,例如’1AB9E813-3781-5CA2-95A0- 1EA334E80663’。

四、C语言调用Python

4.1 搭建编译环境:

通过C语言调用Python代码,需要先安装libpython3dev依赖库(不同的ubuntu版本下,python版本 可能会有差异, 比如ubuntu 22.04里是libpython3.10-dev)。

首先可以通过以下命令验证是否是否已经存在python3的dev包:

dpkg -l | grep libpython3

如果没有, 可以通过apt命令安装相关的dev包:

sudo apt install libpython3.10-dev

但是很不幸的是安装失败了,经过百度查看失败原因:

 然后尝试使用指令更新了一下源,然后就成功的安装了libpython3.10-dev依赖包

sudo apt-get update		//更新镜像源

使用dpkg -l | grep libpython3查看是否已经存在python3的dev包,我们可以看见已经安装成功了

安装成功后我们可以查看我们路径下的Python版本,可以看到我们的版本是Python3.10,并且目录下是我们Python3.10的各种依赖包

ls /usr/include/python

4.2 C语言执行Python语句:

#include "Python.h"												//包含Python.h头文件

int main()
{
	Py_Initialize();											//初始化Python环境
	PyRun_SimpleString("print('Hello, Python!')");				//执行Python代码
	Py_Finalize();												//退出Python环境
	
	return 0;
}

 然要编译和运行这个程序,可以使用以下命令(假设使用的是gcc编译器和Python 3.10版本):

gcc Python_test.c -I /usr/include/python3.10/ -l python3.10		//编译代码
./a.out															//运行程序

代码说明

首先包含Python.h头文件,这是Python API的头文件,用于访问Python对象和函数

其次在程序开始时使用Py_Initialize()函数初始化Python解释器这样可以在C程序中执行Python代码

然后使用PyRun_SimpleString()函数执行一段简单的Python代码,例如打印"Hello, Python!"。需要传递一个字符串作为参数,表示要执行的Python代码,如print ('Hello, Python!')这么一个Python代码字符串。

函数说明:
int PyRun_SimpleString(const char *command)
这是针对下面 PyRun_SimpleStringFlags() 的简化版接口,将 PyCompilerFlags* 参数设为 NULL;传参就是python执行语句。

最后在程序结束时使用Py_Finalize()函数关闭Python解释器,并释放资源

4.3 C语言调用Python无参函数:

C语言调用python函数 , 上一节我们调用了print('Hello, Python!')这条语句,现在把这条语句放到 Python_print.py的文件的函数里, 如下:

#文件name: Python_print.py

def print_Python():                                     # 定义函数 print_Python()
    print("Hello, Python!")                             # 打印字符串 "Hello, Python!"

接下来用C语言进行调用,一般调用的流程是这样子的:

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.htmls

根据上面的流程写出的示例代码如下:

/* 文件name:Python_wucan.c */
#include "Python.h"                                             //包含Python.h文件,该文件包含Python API的头文件

int main()
{
    Py_Initialize();                                            //初始化Python解释器

    //将当前路径添加到sys.path列表中
    PyObject *sys = PyImport_ImportModule("sys");               //导入sys模块
    PyObject *path = PyObject_GetAttrString(sys, "path");       //获取sys.path属性
    PyList_Append(path, PyUnicode_FromString("."));             //添加当前路径到sys.path列表中

    //导入模块
    PyObject *module = PyImport_ImportModule("Python_print");   //导入Python_print模块
    if(module == NULL){
        PyErr_Print();                                          //打印错误信息
        printf("加载Python_print模块失败\n");                    //输出错误信息
        return 1;                                               //返回1表示失败
    }
    
    //获取print_Python对象
    PyObject *pfunc = PyObject_GetAttrString(module, "print_Python");       //获取print_Python对象
    if(pfunc == NULL || !PyCallable_Check(pfunc)){                          //判断print_Python对象是否可调用
        PyErr_Print();                                                      //打印错误信息  
        printf("获取print_Python对象失败\n");                                //输出错误信息    
        return 1;                                                           //返回1表示失败
    }
    
    //调用print_Python函数并获取返回值
    PyObject *pValue = PyObject_CallObject(pfunc, NULL);                    //调用print_Python函数
    if(pValue == NULL){
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_Python函数失败\n");                                //输出错误信息
        return 1;                                                           //返回1表示失败
    }

    //释放所有引用的Python对象
    Py_DECREF(pValue);                                                      //释放返回值引用
    Py_DECREF(pfunc);                                                       //释放函数引用
    Py_DECREF(module);                                                      //释放模块引用

    //关闭Python解释器
    Py_Finalize();                                                          //关闭Python解释器          
    return 0;
}

然要编译和运行这个程序,可以使用以下命令(假设使用的是gcc编译器和Python 3.10版本):

gcc -o wucan Python_wucan.c -I /usr/include/python3.10/ -l python3.10		//编译文件
./wucan																		//运行程序

C语言调用Python无参函数代码详解:

逐步分解代码

  1. 包含头文件

    #include "Python.h"

    这行代码包含了Python.h头文件,该文件包含了Python API的所有定义。

  2. 初始化Python解释器

    Py_Initialize();

    初始化Python解释器,为后续的Python代码执行做准备。

  3. 添加当前路径到sys.path

    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(path, PyUnicode_FromString("."));

    导入sys模块,获取sys.path属性,并将当前路径(".")添加到sys.path列表中,以便Python解释器能够找到并导入当前目录下的模块。

  4. 导入Python模块

    PyObject *module = PyImport_ImportModule("Python_print");
    if(module == NULL){
        PyErr_Print();
        printf("加载Python_print模块失败\n");
        return 1;
    }

    尝试导入名为"Python_print"的Python模块。如果导入失败,打印错误信息并返回1表示失败。

  5. 获取Python函数对象

    PyObject *pfunc = PyObject_GetAttrString(module, "print_Python");
    if(pfunc == NULL || !PyCallable_Check(pfunc)){
        PyErr_Print();
        printf("获取print_Python对象失败\n");
        return 1;
    }

    从导入的模块中获取名为"print_Python"的函数对象。如果获取失败或该对象不可调用,打印错误信息并返回1表示失败。

  6. 调用Python函数

    PyObject *pValue = PyObject_CallObject(pfunc, NULL);
    if(pValue == NULL){
        PyErr_Print();
        printf("调用print_Python函数失败\n");
        return 1;
    }

    调用获取到的"print_Python"函数,并检查调用是否成功。如果调用失败,打印错误信息并返回1表示失败。

  7. 释放Python对象引用

    Py_DECREF(pValue);
    Py_DECREF(pfunc);
    Py_DECREF(module);

    释放所有引用的Python对象,以避免内存泄漏。

  8. 关闭Python解释器

    Py_Finalize();

    关闭Python解释器,结束Python代码的执行。

代码总结

这段代码的主要功能是:

  • 初始化Python解释器。

  • 将当前路径添加到Python的模块搜索路径中。

  • 导入一个名为"Python_print"的Python模块。

  • 从该模块中获取并调用一个名为"print_Python"的函数。

  • 在完成操作后,释放所有Python对象的引用并关闭Python解释器。

通过这些步骤,代码实现了在C语言环境中调用Python函数的功能,展示了如何使用Python C API进行混合编程。

4.4 C语言调用Python有参函数:

我们要实现C语言调用python有参函数,首先定义一个带参数和返回值的函数:

#文件name: Python_printStr.py             

def print_String(str):                  #定义函数print_String,参数str
    print(str)                          #打印参数str
    return str                          #返回参数str  

 接下来用C语言进行调用,调用的流程和无参函数的调用方式几乎是一样的,只不过多了参数和返回值的获取:

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 *Py_BuildValue(const char *format, ...)函数将C类型的数据结构转换成Python对象,作为Python函数的参数,没有参数不需要调用
7、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函数调用Python函数,并获取返回值。
8、使用int PyArg_Parse(PyObject *args, const char *format, ...)函数将返回值转换为C类型,并检查是否有错误,没有返回值时不需要调用。
9、使用void Py_DECREF(PyObject *o)函数释放所有引用的Python对象。
10、结束时调用void Py_Finalize()函数关闭Python解释器。
相关的函数参数说明参考网站(网站左上角输入函数名即可开始搜索):
https://docs.python.org/zh-cn/3/c-api/import.html

C语言调用Python有参函数对比与C语言调用Python无参函数多了两步(第6步:传参 and 第8步:返回值的处理 ):

6、使用PyObject *Py_BuildValue(const char *format, ...)函数创建一个Python元组或者对象,作为Python函数的参数,没有参数势不需要调用

这里要注意的是,Py_BuildValue的第一个参数是类型转换:C对应的Python的数据类型转换对应的格式,如下:

8、使用int PyArg_Parse(PyObject *args, const char *format, ...)函数将返回值转换为C类型,并检查是否有错误,没有返回值时不需要调用。

 C语言调用Python有参函数示例:

#include "Python.h"                                                 //包含Python.h文件,该文件包含Python API的头文件

int main()
{
    Py_Initialize();                                                //初始化Python解释器

    //将当前路径添加到sys.path列表中
    PyObject *sys = PyImport_ImportModule("sys");                   //导入sys模块
    PyObject *path = PyObject_GetAttrString(sys, "path");           //获取sys.path属性
    PyList_Append(path, PyUnicode_FromString("."));                 //添加当前路径到sys.path列表中

    //导入模块
    PyObject *module = PyImport_ImportModule("Python_printStr");    //导入Python_print模块
    if(module == NULL){
        PyErr_Print();                                              //打印错误信息
        printf("加载Python_printStr模块失败\n");                     //输出错误信息
        return 1;                                                   //返回1表示失败
    }
    
    //获取print_Python对象
    PyObject *pfunc = PyObject_GetAttrString(module, "print_String");       //获取print_String函数
    if(pfunc == NULL || !PyCallable_Check(pfunc)){                          //判断print_String对象是否可调用
        PyErr_Print();                                                      //打印错误信息  
        printf("获取print_String对象失败\n");                                //输出错误信息    
        return 1;                                                           //返回1表示失败
    }

    //创建一个字符串作为参数传递给print_String函数
    char *str = "chenlichen handsome";                                       //定义字符串
    PyObject *pArgs = Py_BuildValue("(s)", str);                             //创建字符串参数
    
    //调用print_String函数并获取返回值
    PyObject *pValue = PyObject_CallObject(pfunc, pArgs);                   //调用print_String函数
    if(pValue == NULL){
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数失败\n");                                //输出错误信息
        return 1;                                                           //返回1表示失败
    }

    //将返回值转换成C类型并输出
    char * result = NULL;                                                    //定义C字符串变量

    if(PyArg_Parse(pValue, "s", &result) == 0){                              //将返回值转换成C类型
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数返回值转换失败\n");                        //输出错误信息
        return 1;                                                           //返回1表示失败
    }                                   //将返回值转换成C类型
    printf("调用print_String函数返回值:%s\n", result);                        //输出返回值

    //释放所有引用的Python对象
    Py_DECREF(pValue);                                                      //释放返回值引用
    Py_DECREF(pfunc);                                                       //释放函数引用
    Py_DECREF(module);                                                      //释放模块引用

    //关闭Python解释器
    Py_Finalize();                                                          //关闭Python解释器          
    return 0;
}

然后要编译和运行这个程序,可以使用以下命令(假设使用的是gcc编译器和Python 3.10版本):

gcc Python_youcan.c -o youcan -I /usr/include/python3.10 -l python3.10		//编译文件
./youcan																	//运行程序

C语言调用Python有参函数代码详解:

逐步分解代码

  1. 包含头文件

    #include "Python.h"

    这行代码包含了Python.h头文件,该文件包含了Python API的头文件,使得C代码可以调用Python的函数和对象。

  2. 初始化Python解释器

    Py_Initialize();

    这行代码初始化Python解释器,使得C代码可以与Python解释器进行交互。

  3. 添加当前路径到sys.path

    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(path, PyUnicode_FromString("."));

    这三行代码导入了sys模块,获取了sys.path属性,并将当前路径(".")添加到sys.path列表中,以便Python解释器可以找到并导入当前路径下的模块。

  4. 导入Python模块

    PyObject *module = PyImport_ImportModule("Python_printStr");
    if(module == NULL){
        PyErr_Print();
        printf("加载Python_printStr模块失败\n");
        return 1;
    }

    这段代码尝试导入名为"Python_printStr"的Python模块。如果导入失败,则打印错误信息并返回1表示失败。

  5. 获取Python函数对象

    PyObject *pfunc = PyObject_GetAttrString(module, "print_String");
    if(pfunc == NULL || !PyCallable_Check(pfunc)){
        PyErr_Print();
        printf("获取print_String对象失败\n");
        return 1;
    }

    这段代码从导入的模块中获取名为"print_String"的函数对象。如果获取失败或该对象不可调用,则打印错误信息并返回1表示失败。

  6. 创建参数并调用Python函数

    char *str = "chenlichen handsome";
    ​
    PyObject *pArgs = Py_BuildValue("(s)", str);  
    PyObject *pValue = PyObject_CallObject(pfunc, pArgs);
    if(pValue == NULL){
        PyErr_Print();
        printf("调用print_String函数失败\n");
        return 1;
    }

    这段代码定义了一个字符串参数,并将其转换为Python对象。然后调用"print_String"函数,并检查调用是否成功。

  7. 处理返回值并输出

    char * result = NULL;
    ​
    if(PyArg_Parse(pValue, "s", &result) == 0){
        PyErr_Print();
        printf("调用print_String函数返回值转换失败\n");
        return 1;
    }
    printf("调用print_String函数返回值:%s\n", result);

    这段代码将Python函数的返回值转换为C字符串,并输出该返回值。如果转换失败,则打印错误信息并返回1表示失败。

  8. 释放Python对象并关闭解释器

    Py_DECREF(pValue);
    Py_DECREF(pfunc);
    Py_DECREF(module);
    Py_Finalize();

    这段代码释放所有引用的Python对象,并关闭Python解释器。

总结代码

该代码的主要功能是:

  1. 初始化Python解释器。

  2. 将当前路径添加到Python的模块搜索路径中。

  3. 导入一个名为"Python_printStr"的Python模块。

  4. 从该模块中获取一个名为"print_String"的函数对象。

  5. 创建一个字符串参数并调用该函数。

  6. 处理函数的返回值并输出。

  7. 释放所有引用的Python对象并关闭Python解释器。

通过这些步骤,C代码成功地调用了Python模块中的函数,并处理了其返回值

五、阿里云垃圾识别方案

5.1 接入阿里云:

在垃圾分类的项目中,我们采用阿里云视觉智能开发平台的接口来做垃圾分类的识别方案通过上传本地的拍照下的垃圾图片,通过阿里提供的接口来识别出该垃圾是干垃圾、湿垃圾、回收垃圾还是有害垃圾。

阿里云视觉智能开放平台网址:[通义实验室视觉智能开放平台 (aliyun.com)](https://vision.aliyun.com/)

https://vision.aliyun.com/

也可以通过此网址直接进入该页面,该页面有开通和相关的技术文档:

https://vision.aliyun.com/experience/detail?tagName=imagerecog&children=ClassifyingRubbish

我们先点击“技术文档”查看使用方法:

根据技术文档中的接入指引我们应该怎么做:

  1. 开通阿里云账号及图像识别服务,用自己支付宝即可开通

  2. 创建并获取AccessKey ID和Secret

  3. 在Linux或开发板上安装所需的SDK

  4. 根据示例代码进行修改垃圾分类识别

5.1.1 开通阿里云账号及图像识别服务:

首先点击上面的“立即开通”,然后用自己的支付宝扫描二维码进行开通,然后同意下面的服务协议,然后开始实名认证,实名认证之后就开通成功了,点击进入:控制台

5.1.2 创建并获取AccessKey ID和Secret:

登录完成后,即可在产品控制台->点击获取Acce Token,获取对应的AccessKey IDAccessKey Secret

当然,在第一次获取到AccessKey ID和AccessKey Secret,需要点击创建AccessKey, 然后最好把 AccessKey.csv下载下来备份,不然会找不到AccessKey Secret就需要重新创建。

5.1.3 安装图像识别SDK:
sudo apt install python3-pip
pip3 install alibabacloud_imagerecog20190930

我们可以看到图像识别的SDK安装成功了,其实在这之前的安装过程中是失败的,我们更新了一下源,然后就可以成功安装了

5.1.4 配置环境变量:

  同时配置Linux环境变量,根据自己实际的ACCESS_KEY_IDACCESS_KEY_SECRET,下面的两行写入到家目录下的.bashrc中:

export ALIBABA_CLOUD_ACCESS_KEY_ID=“你的ID” 			#根据自己实际的ID填写
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="你的SECRET" 	#根据自己实际的SECRET填写

vi ~/.bashrc #然后在末尾输入上面两行后保存  

然后退出终端重新登录下,此时再执行export,能看到这两个Key的存在:

export
declare -x ALIBABA_CLOUD_ACCESS_KEY_ID="你的阿里云Key ID"
declare -x ALIBABA_CLOUD_ACCESS_KEY_SECRET="你的阿里云Key Secret"

5.1.5 根据示例代码进行修改进行垃圾分类识别:

文件在本地或可访问的URL:

# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_imagerecog20190930

import os
import io
from urllib.request import urlopen
from alibabacloud_imagerecog20190930.client import Client
from alibabacloud_imagerecog20190930.models import ClassifyingRubbishAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions

config = Config(
  # 创建AccessKey ID和AccessKey Secret,请参考https://help.aliyun.com/document_detail/175144.html。
  # 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考https://help.aliyun.com/document_detail/145025.html
  # 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量。
  access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
  access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
  # 访问的域名
  endpoint='imagerecog.cn-shanghai.aliyuncs.com',
  # 访问的域名对应的region
  region_id='cn-shanghai'
)
#场景一:文件在本地
#img = open(r'/tmp/ClassifyingRubbish1.jpg', 'rb')
#场景二:使用任意可访问的url
url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/imagerecog/ClassifyingRubbish/ClassifyingRubbish1.jpg'
img = io.BytesIO(urlopen(url).read())
classifying_rubbish_request = ClassifyingRubbishAdvanceRequest()
classifying_rubbish_request.image_urlobject = img
runtime = RuntimeOptions()
try:
  # 初始化Client
  client = Client(config)
  response = client.classifying_rubbish_advance(classifying_rubbish_request, runtime)
  # 获取整体结果
  print(response.body)
except Exception as error:
  # 获取整体报错信息
  print(error)
  # 获取单个字段
  print(error.code)

场景一是从本地读取文件场景二是从网址上访问读取文件,将场景二注释,场景一代码打开,并修改输入自己测试图片的路径,如下:

这是测试的照片,我们把它上传到我们的全志开发板上:

 其中“/home/orangepi/GeLi.jpg”为本地测试用图片路径(根据在线文档要求:图像类型:JPEG、JPG、PNG,图像大小:不大于3 MB,图像分辨率:不限制图像分辨率,但图像分辨率太高可能会导致API识别超时,超时时间为5秒)

 经过图像垃圾分类识别,识别的结果为:可回收垃圾,我们换一张图片来测试一下:

经过图像垃圾分类识别,识别的结果为:干垃圾,说明阿里云垃圾分类方案对接成功。

5.2 C语言调用阿里云Python接口:

tuxiangTest.py拷贝一份,改造下tuxiangTest2.py里面的代码,封装成一个函数,供后C程序调用

# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_imagerecog20190930

import os                                                              
import io         
from urllib.request import urlopen
from alibabacloud_imagerecog20190930.client import Client
from alibabacloud_imagerecog20190930.models import ClassifyingRubbishAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions

config = Config(
  # 创建AccessKey ID和AccessKey Secret,请参考https://help.aliyun.com/document_detail/175144.html。
  # 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考https://help.aliyun.com/document_detail/145025.html
  # 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量。
  access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
  access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
  # 访问的域名
  endpoint='imagerecog.cn-shanghai.aliyuncs.com',
  # 访问的域名对应的region
  region_id='cn-shanghai'
)

def alibaba_garbage():                                                        #定义函数
  #场景一:文件在本地
  img = open(r'/home/orangepi/tmp/test.png', 'rb')                            #打开图片文件
  #场景二:使用任意可访问的url
  #url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/imagerecog/ClassifyingRubbish/ClassifyingRubbish1.jpg'
  #img = io.BytesIO(urlopen(url).read())
  classifying_rubbish_request = ClassifyingRubbishAdvanceRequest()
  classifying_rubbish_request.image_urlobject = img
  runtime = RuntimeOptions()
  try:
    # 初始化Client
    client = Client(config)
    response = client.classifying_rubbish_advance(classifying_rubbish_request, runtime)
    # 获取整体结果
    #print(response.body)
    # 获取单个字段
    print(response.body.to_map()['Data']['Elements'][0]['Category'])                        #获取垃圾种类   
    return response.body.to_map()['Data']['Elements'][0]['Category']                        #返回垃圾种类
  except Exception as error:
    # 获取整体报错信息
    #print(error)
    print(type('获取失败'))                                                                   #返回失败信息
    return '获取失败'           
    # 获取单个字段
    #print(error.code)

if __name__ == '__main__':                                                                  #主函数
  alibaba_garbage()                                                                         #调用函数      
/*  Python_youcan.c */
#include "Python.h"                                              //包含Python.h文件,该文件包含Python API的头文件

int main()
{
    Py_Initialize();                                                //初始化Python解释器

    //将当前路径添加到sys.path列表中
    PyObject *sys = PyImport_ImportModule("sys");                   //导入sys模块
    PyObject *path = PyObject_GetAttrString(sys, "path");           //获取sys.path属性
    PyList_Append(path, PyUnicode_FromString("."));                 //添加当前路径到sys.path列表中

    //导入模块
    PyObject *module = PyImport_ImportModule("tuxiangTest2");    //导入tuxiangTest2模块
    if(module == NULL){
        PyErr_Print();                                              //打印错误信息
        printf("加载Python_printStr模块失败\n");                     //输出错误信息
        return 1;                                                   //返回1表示失败
    }
    
    //获取print_Python对象
    PyObject *pfunc = PyObject_GetAttrString(module, "alibaba_garbage");       //获取alibaba_garbage函数
    if(pfunc == NULL || !PyCallable_Check(pfunc)){                          //判断print_String对象是否可调用
        PyErr_Print();                                                      //打印错误信息  
        printf("获取print_String对象失败\n");                                //输出错误信息    
        return 1;                                                           //返回1表示失败
    }
    
    //调用print_String函数并获取返回值
    PyObject *pValue = PyObject_CallObject(pfunc, NULL);                   //调用print_String函数
    if(pValue == NULL){
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数失败\n");                                //输出错误信息
        return 1;                                                           //返回1表示失败
    }

    //将返回值转换成C类型并输出
    char * result = NULL;                                                    //定义C字符串变量

    if(PyArg_Parse(pValue, "s", &result) == 0){                              //将返回值转换成C类型
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数返回值转换失败\n");                        //输出错误信息
        return 1;                                                           //返回1表示失败
    }                                   //将返回值转换成C类型
    printf("return value: %s\n", result);                                    //输出返回值

    //释放所有引用的Python对象
    Py_DECREF(pValue);                                                      //释放返回值引用
    Py_DECREF(pfunc);                                                       //释放函数引用
    Py_DECREF(module);                                                      //释放模块引用

    //关闭Python解释器
    Py_Finalize();                                                          //关闭Python解释器          
    return 0;
}

5.3 封装图像识别接口供项目使用:

参照前面C语言调用Python有参函数的做法,封装实现以下代码:

/* garbage.c */
#include "Python.h"                                              //包含Python.h文件,该文件包含Python API的头文件
#include <stdio.h>                                               //包含stdio.h文件,该文件包含标准输入输出函数
#include <stdlib.h>                                              //包含stdlib.h文件,该文件包含标准函数库
#include <string.h>                                              //包含string.h文件,该文件包含字符串处理函数
#include "garbage.h"

void garbage_init()                                                //初始化函数  
{
    Py_Initialize();                                                //初始化Python解释器

    //将当前路径添加到sys.path列表中
    PyObject *sys = PyImport_ImportModule("sys");                   //导入sys模块
    PyObject *path = PyObject_GetAttrString(sys, "path");           //获取sys.path属性
    PyList_Append(path, PyUnicode_FromString("."));                 //添加当前路径到sys.path列表中
}

void garbage_destroy()                                             //销毁函数
{
    Py_Finalize();                                                  //关闭Python解释器
}

char* garbage_category(char *category)
{
    //导入模块
    PyObject *module = PyImport_ImportModule("tuxiangTest2");    //导入tuxiangTest2模块
    if(module == NULL){
        PyErr_Print();                                              //打印错误信息
        printf("加载Python_printStr模块失败\n");                     //输出错误信息
        goto FAILED_MODULE;                                         //跳转到失败处理
    }
    
    //获取print_Python对象
    PyObject *pfunc = PyObject_GetAttrString(module, "alibaba_garbage");       //获取alibaba_garbage函数
    if(pfunc == NULL || !PyCallable_Check(pfunc)){                          //判断print_String对象是否可调用
        PyErr_Print();                                                      //打印错误信息  
        printf("获取print_String对象失败\n");                                //输出错误信息    
        goto FAILED_FUNC;                                                  //跳转到失败处理
    }
    
    //调用print_String函数并获取返回值
    PyObject *pValue = PyObject_CallObject(pfunc, NULL);                   //调用print_String函数
    if(pValue == NULL){
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数失败\n");                                //输出错误信息
        goto FAILED_VALUE;                                                   //跳转到失败处理
    }

    //将返回值转换成C类型并输出
    char * result = NULL;                                                    //定义C字符串变量

    if(PyArg_Parse(pValue, "s", &result) == 0){                              //将返回值转换成C类型
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数返回值转换失败\n");                        //输出错误信息
        goto FAILED_RESULT;                                                  //跳转到失败处理
    }
    printf("return value: %s\n", result);                                    //输出返回值

    category = (char *)malloc(sizeof(char) * (strlen(result) + 1));        //分配内存空间
    memset(category, 0, strlen(result) + 1);                              //清空内存
    strncpy(category, result, strlen(result));                               //拷贝字符串到category变量

    //释放所有引用的Python对象
FAILED_RESULT:
    Py_DECREF(pValue);                                                      //释放返回值引用
FAILED_VALUE:
    Py_DECREF(pfunc);                                                       //释放函数引用
FAILED_FUNC:
    Py_DECREF(module);                                                      //释放模块引用
FAILED_MODULE:

    return category;
}
/* 头文件garbage.h如下: */
#ifndef __GARBAGE__H
#define __GARBAGE__H

//函数声明
void garbage_init();                                                //初始化函数  
void garbage_destroy();                                             //销毁函数
char* garbage_category(char *category);                             //分类函数

#endif
/* 测试代码:garbageTest.c */
#include <stdio.h>                                                                   // 包含标准输入输出头文件
#include <stdlib.h>                                                                  // 包含标准库头文件    
#include "garbage.h"                                                                 // 包含垃圾分类器头文件                

int main(int argc, char **argv)
{
    char *category = NULL;                                                           // 垃圾类别

    garbage_init();                                                                 // 初始化垃圾分类器
    category = garbage_category(category);                                          // 识别垃圾类别
    printf("垃圾类别: %s\n", category);                                              // 打印识别结果
    garbage_destroy();                                                              // 销毁垃圾分类器

    if(category != NULL){                                                           // 如果识别结果不为空
        free(category);                                                             // 释放内存
    }

    return 0;
}

然后要编译和运行这个程序,可以使用以下命令(假设使用的是gcc编译器和Python 3.10版本):

gcc garbageTest.c -o garbageTest garbage.c garbage.h -I /usr/include/python3.10/ -l python3.10	//编译测试代码
./garbageTest																					//运行程序

六、香橙派使用USB摄像头

详细可参考《OrangePi_Zero2_H616用户手册v4.0.pdf》 中的3.13.6 USB摄像头测试章节。

6.1 将USB摄像头接入香橙派:

6.2 通过lsmod指令查看内核是否加载USB摄像头模块:

lsmod			//列出当前加载的内核模块

  • lsmod 命令用于列出当前加载的内核模块。它会显示已加载模块的信息,包括模块的名称、大小、使用次数等

  • 每一行表示一个加载的模块,其中包括模块的名称、大小、以及使用该模块的其他模块等信息。

6.3 通过 v4l2-ctl 指令查看USB 摄像头的设备节点信息:

sudo apt update
sudo apt install -y v4l-utils
v4l2-ctl --list-devices

USB 2.0 Camera (usb-sunxi-ehci-1): 
               
/dev/video0

通过 v4l2-ctl 命令可以看到 USB 摄像头的设备节点信息为/dev/videox(x有可能是0 1或者2等数 字),所以我们需要测试一下:

6.4 使用 fswebcam 测试 USB 摄像头:

6.4.1 安装fswebcam:
orangepi@orangepi:~$ sudo apt update
orangepi@orangepi:~$ sudo apt-get install -y fswebcam
6.4.2 安装完 fswebcam 后可以使用下面的命令来拍照:
  1. -d 选项用于指定 USB 摄像头的设备节点

  2. --no-banner 用于去除照片的水印

  3. -r 选项用于指定照片的分辨率

  4. -S 选项用设置于跳过前面的帧数

  5. ./image.jpg 用于设置生成的照片的名字和路径

sudo fswebcam -d /dev/video0 --no-banner -r 1280x720 -S 5 ./test.jpg

 在服务器版的 linux 系统中,拍完照后可以直接通过mobaxterm拖到电脑桌面看或者使用 scp 命令将拍好的图片传到Ubuntu PC 上镜像观看

orangepi@orangepi:~$ scp image.jpg pg@192.168.1.55:/home/pg/ #根据自己的实际家目录修改pg

我们可以看到这就是我们的USB摄像头拍摄的一张照片,虽然像素不怎么滴!!!

6.5 使用 mjpg-streamer 测试 USB 摄像头:

6.5.1 下载 mjpg-streamer:
  1. Github 的下载地:

    orangepi@orangepi:~$ git clone https://github.com/jacksonliam/mjpg-streamer
  2. Gitee 的镜像下载地址为:

orangepi@orangepi:~$ git clone https://gitee.com/leeboby/mjpg-streamer

我们使用Gitee 的镜像下载方式。

6.5.2 安装依赖的软件包:
  1. Ubuntu 系统:

    orangepi@orangepi:~$ sudo apt-get install -y cmake libjpeg8-dev
  2. Debian 系统:

orangepi@orangepi:~$ sudo apt-get install -y cmake libjpeg62-turbo-dev
6.5.3 编译安装 mjpg-streamer:
orangepi@orangepi:~$ cd mjpg-streamer/mjpg-streamer-experimental
orangepi@orangepi:~/mjpg-streamer/mjpg-streamer-experimental$ make -j4
orangepi@orangepi:~/mjpg-streamer/mjpg-streamer-experimental$ sudo make install
6.5.4 输入下面命令启动 mjpg_streamer:
注意,video 的序号不一定都是 video0,请以实际看到的为准。
orangepi@orangepi:~/mjpg-streamer/mjpg-streamer-experimental$ export LD_LIBRARY_PATH="$(pwd)"
orangepi@orangepi:~/mjpg-streamer/mjpg-streamer-experimental$ sudo ./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -u -f 30" -o "./output_http.so -w ./www"

同时也可以根据自带的shell脚本来配置:

修改 start.sh脚本,将start.sh里的:

./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so -w ./www"

 字段修改为:

./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -u -f 30" -o "./output_http.so -w ./www" 
/* 注意这里的video1需要根据实际情况修改 */

这样就可以通过执行./start.sh运行摄像头了。

6.5.5 局域网设备通过开发板IP地址和端口号查看摄像头:

在和开发板同一局域网的 Ubuntu PC 或者 Windows PC 或者手机的浏览orange Pi器中输入【开发板的IP地址:8080】就能看到摄像头输出的视频了:

推荐使用 mjpg-streamer 来测试 USB 摄像头,比 motion 流畅很多,使用mjpg-streamer 感觉 不到任何卡顿

6.6 使用wget指令拍一张照片:

sudo fswebcam -d /dev/video1 --no-banner -r 1280x720 -S 5 /home/orangepi/tmp/test.jpg

wget http://192.168.31.249:8080/?action=snapshot -O /home/orangepi/tmp/shiyahaohome.jpg

6.7 设置摄像头服务每次开机自启动:

6.7.1 创建一个mjpg.sh脚本并赋予可执行的权限:
touch mjpg.sh
  • 在这个shell脚本下添加以下内容:

# !/bin/bash													//指定用bash执行						
  	
cd /home/orangepi/mjpg-streamer/mjpg-streamer-experimental		//需要执行文件的路径
./start.sh														//执行

  • 给mjpg.sh赋予可执行的权限:

chmod +x mjpg.sh

6.7.2 设置开机自启动:
  • /etc/xdg/autostart/下创建mjpg.desktop 文件输入以下内容,/etc/xdg/autostart这个文件夹下面的.desktop文件会在开机的时候自动启动

cd /etc/xdg /autostart/
sudo vi mjpg.desktop
[Desktop Entry]
Name=mjpg 					//名字
Exec=/home/orangepi/mjpg.sh //执行命令
Type=Application  			//类型是应用
NoDisplay=true 				//不显示

然后重启我们的系统查看服务有没有启动,由于我全志的ubtunu的版本装错了,所以在/etc/xdg/目录下没有autostart,所以我们在项目中手动的打开USB摄像头的服务。

七、SU-03T语音模组配置和烧录

进入语音模块官网 [智能公元/AI产品零代码平台 (smartpi.cn)](https://smartpi.cn/#/) 配置词条和识别后的串口输入输出指令,如下:

7.1 语音模块交互示意图:

7.2 PIN引脚配置:

7.3 唤醒词定义:

7.4 命令词自定义基础信息和控制详情:

7.5 其他配置:

7.6 固件烧录:

八、基于Orangepi的视觉垃圾分类系统

8.1 语音模组和阿里云图像识别结合:

使用语音模块和摄像头在香橙派上做垃圾智能分类识别:

8.1.1 语音模组和阿里云图像识别流程:

8.1.2 环境准备:
  • 将语音模块接到全志开发板的UART5的位置上,并且将USB摄像头插到全志开发板的USB接口上:

  • 在orange pi 3.0.6上确认已经配置开启了uart5:(overlays=uart5):

cat /boot/orangepiEnv.txt

  • 确认已经运行了mjpg-streamer服务:

ps ax | grep mjpg_streamer | grep -v grep

8.1.3 代码实现:
#代码name:tuxiangTest2.py
# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_imagerecog20190930

import os                                                              
import io         
from urllib.request import urlopen
from alibabacloud_imagerecog20190930.client import Client
from alibabacloud_imagerecog20190930.models import ClassifyingRubbishAdvanceRequest
from alibabacloud_tea_openapi.models import Config
from alibabacloud_tea_util.models import RuntimeOptions

config = Config(
  # 创建AccessKey ID和AccessKey Secret,请参考https://help.aliyun.com/document_detail/175144.html。
  # 如果您用的是RAM用户的AccessKey,还需要为RAM用户授予权限AliyunVIAPIFullAccess,请参考https://help.aliyun.com/document_detail/145025.html
  # 从环境变量读取配置的AccessKey ID和AccessKey Secret。运行代码示例前必须先配置环境变量。
  access_key_id=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID'),
  access_key_secret=os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
  # 访问的域名
  endpoint='imagerecog.cn-shanghai.aliyuncs.com',
  # 访问的域名对应的region
  region_id='cn-shanghai'
)

def alibaba_garbage():                                                        #定义函数
  #场景一:文件在本地
  img = open(r'/tmp/garbage.jpg', 'rb')                                       #打开图片文件
  #场景二:使用任意可访问的url
  #url = 'https://viapi-test-bj.oss-cn-beijing.aliyuncs.com/viapi-3.0domepic/imagerecog/ClassifyingRubbish/ClassifyingRubbish1.jpg'
  #img = io.BytesIO(urlopen(url).read())
  classifying_rubbish_request = ClassifyingRubbishAdvanceRequest()
  classifying_rubbish_request.image_urlobject = img
  runtime = RuntimeOptions()
  try:
    # 初始化Client
    client = Client(config)
    response = client.classifying_rubbish_advance(classifying_rubbish_request, runtime)
    # 获取整体结果
    #print(response.body)
    # 获取单个字段
    print(response.body.to_map()['Data']['Elements'][0]['Category'])                        #获取垃圾种类   
    return response.body.to_map()['Data']['Elements'][0]['Category']                        #返回垃圾种类
  except Exception as error:
    # 获取整体报错信息
    #print(error)
    print(type('获取失败'))                                                                   #返回失败信息
    return '获取失败'           
    # 获取单个字段
    #print(error.code)

if __name__ == '__main__':                                                                  #主函数
  alibaba_garbage()                                                                         #调用函数  
/* 代码name:garbage.c */
#include "Python.h"                                              //包含Python.h文件,该文件包含Python API的头文件
#include <stdio.h>                                               //包含stdio.h文件,该文件包含标准输入输出函数
#include <stdlib.h>                                              //包含stdlib.h文件,该文件包含标准函数库
#include <string.h>                                              //包含string.h文件,该文件包含字符串处理函数
#include "garbage.h"

void garbage_init()                                                //初始化函数  
{
    Py_Initialize();                                                //初始化Python解释器

    //将当前路径添加到sys.path列表中
    PyObject *sys = PyImport_ImportModule("sys");                   //导入sys模块
    PyObject *path = PyObject_GetAttrString(sys, "path");           //获取sys.path属性
    PyList_Append(path, PyUnicode_FromString("."));                 //添加当前路径到sys.path列表中
}

void garbage_destroy()                                             //销毁函数
{
    Py_Finalize();                                                  //关闭Python解释器
}

char* garbage_category(char *category)                               //垃圾分类函数
{
    //导入模块
    PyObject *module = PyImport_ImportModule("tuxiangTest2");    //导入tuxiangTest2模块
    if(module == NULL){
        PyErr_Print();                                              //打印错误信息
        printf("加载Python_printStr模块失败\n");                     //输出错误信息
        goto FAILED_MODULE;                                         //跳转到失败处理
    }
    
    //获取函数
    PyObject *pfunc = PyObject_GetAttrString(module, "alibaba_garbage");       //获取alibaba_garbage函数
    if(pfunc == NULL || !PyCallable_Check(pfunc)){                          //判断print_String对象是否可调用
        PyErr_Print();                                                      //打印错误信息  
        printf("获取print_String对象失败\n");                                //输出错误信息    
        goto FAILED_FUNC;                                                  //跳转到失败处理
    }
    
    //调用print_String函数并获取返回值
    PyObject *pValue = PyObject_CallObject(pfunc, NULL);                   //调用print_String函数
    if(pValue == NULL){
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数失败\n");                                //输出错误信息
        goto FAILED_VALUE;                                                   //跳转到失败处理
    }

    //将返回值转换成C类型并输出
    char * result = NULL;                                                    //定义C字符串变量

    if(PyArg_Parse(pValue, "s", &result) == 0){                              //将返回值转换成C类型
        PyErr_Print();                                                      //打印错误信息
        printf("调用print_String函数返回值转换失败\n");                        //输出错误信息
        goto FAILED_RESULT;                                                  //跳转到失败处理
    }
    printf("return value: %s\n", result);                                    //输出返回值

    category = (char *)malloc(sizeof(char) * (strlen(result) + 1));        //分配内存空间
    memset(category, 0, strlen(result) + 1);                              //清空内存
    strncpy(category, result, strlen(result));                               //拷贝字符串到category变量

    //释放所有引用的Python对象
FAILED_RESULT:
    Py_DECREF(pValue);                                                      //释放返回值引用
FAILED_VALUE:
    Py_DECREF(pfunc);                                                       //释放函数引用
FAILED_FUNC:
    Py_DECREF(module);                                                      //释放模块引用
FAILED_MODULE:

    return category;
}
/* garbage.h */
#ifndef __GARBAGE__H
#define __GARBAGE__H

//函数声明
void garbage_init();                                                //初始化函数  
void garbage_destroy();                                             //销毁函数
char* garbage_category(char *category);                             //分类函数

//增加拍照指令和照片路径宏定义
#define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O /tmp/garbage.jpg"          //拍照指令
#define GARBAGE_FILE "/tmp/garbage.jpg"                                                     //照片路径 

#endif
/* serialTest.c */
#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>

/*打开指定的串口设备,并设置波特率*/
int mySerialOpen(const char *device, const int baud)
{
    struct termios options; // 串口配置参数
    speed_t myBaud;         // 波特率
    int status, fd;         // 状态和文件描述符

    //根据传入的波特率参数设置相应的波特率
    switch(baud){
        case 9600:
            myBaud = B9600;
            break;
        case 115200:
            myBaud = B115200;
            break;
        default:
            printf("不支持的波特率!\n");
            return -2;
    }  

    //打开串口设备
    if( (fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY)) == -1){
        printf("无法打开串口设备\n");
        return -1;
    }

     // 设置文件描述符的标志为读写模式
    fcntl(fd, F_SETFL, O_RDWR);

    // 获取当前串口配置
    tcgetattr(fd, &options);

    // 设置串口为原始模式,无特殊处理
    cfmakeraw(&options);
    // 设置输入波特率
    cfsetispeed(&options, myBaud);
    // 设置输出波特率
    cfsetospeed(&options, myBaud);

    // 清除标志位并设置数据格式
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~PARENB; // 无奇偶校验位
    options.c_cflag &= ~CSTOPB; // 1个停止位
    options.c_cflag &= ~CSIZE; // 清除数据位
    options.c_cflag |= CS8; // 设置为8位数据位
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 非规范模式,不执行输入处理
    options.c_oflag &= ~OPOST; // 输出处理

    // 设置读取时的超时和最小接收字符数
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 100; // 10秒超时

    // 应用串口配置
    tcsetattr(fd, TCSANOW, &options);

    // 使用ioctl设置串口的DTR和RTS信号
    ioctl(fd, TIOCMGET, &status);
    status |= TIOCM_DTR;
    status |= TIOCM_RTS;
    ioctl(fd, TIOCMSET, &status);

    // 短暂延时
    usleep(10000); // 10毫秒延时

    return fd; // 返回文件描述符
}

/*向指定的串口设备发送字符串*/
void mySerialSendString(const int fd, const unsigned char *str, int len)
{
    int ret;

    ret = write(fd, str, len);                    // 发送串口数据
    if(ret == -1){
        printf("串口发送失败!\n");
        exit(-1);                                 // 发送失败,退出程序
    }
}

/*从指定的串口设备读取字符串*/
int mySerialReadString(const int fd, unsigned char *buffer)
{
    int n_read;

    n_read = read(fd, buffer, 32);                // 读取串口数据
    if(n_read == -1){
        printf("串口读取失败!\n");
        exit(-1);                                 // 读取失败,退出程序
    }
}
/* serialTest.h */
#ifndef __SERIALTOOL_H__
#define __SERIALTOOL_H__

/*
* @Author: <NAME> 打开指定的串口设备,并设置波特率
*
* @param device 串口设备名称,如"/dev/ttyUSB0"
* @param baud 波特率,如9600、115200等
* @return 成功返回文件描述符,失败返回-1
*/
int mySerialOpen(const char *device, const int baud);

/*
* @Author: 向指定的串口设备发送字符串
*
* @param fd 串口设备文件描述符
* @param str 要发送的字符串 
* @return 无
*/
void mySerialSendString(const int fd, const unsigned char *str, int len);

/*
* @Author: 从指定的串口设备读取字符串
*
* @param fd 串口设备文件描述符              
* @param buffer 读取到的字符串存放的缓冲区      
* @return 读取到的字符串的长度,失败返回-1
*/
int mySerialReadString(const int fd, unsigned char *buffer);

#define SERIAL_DEV "/dev/ttyS5"           /* 串口设备名称 */
#define SERIAL_BAUD 115200                /* 串口波特率 */

#endif
/* main.c */
#include <stdio.h>          // 包含标准输入输出库,用于基本的输入输出操作
#include <stdlib.h>         // 包含标准库,用于内存分配等操作
#include <string.h>         // 包含字符串操作库,用于字符串处理
#include <unistd.h>         // 包含unistd库,用于系统调用

#include "garbage.h"        // 包含自定义的垃圾回收库,用于内存管理
#include "serialTool.h"     // 包含自定义的串口工具库,用于串口通信

/*判断进程是否在运行*/ 
static int detect_process(const char * process_name)
{
	int n = -1;
	FILE *strm;
	char buf[128] = {0};
	
	// 构造命令字符串,通过ps命令查找进程
	sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
	// 使用popen执行命令并读取输出
	if ((strm = popen(buf, "r")) != NULL) {
		if (fgets(buf, sizeof(buf), strm) != NULL) {
			n = atoi(buf); // 将进程ID字符串转换为整数
		}
	}
	else {
		return -1; // popen失败
	}	
	pclose(strm); // 关闭popen打开的文件流

	return n;
}

int main(int argc, char **argv)
{
    int ret;                                                                // 函数返回值
    int len = 0;                                                            // 串口数据长度
    int serial_fd;                                                          // 串口文件描述符
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};         // 发送数据
    char *category = NULL;                                                  // 垃圾分类结果

    garbage_init();                                                         // 初始化垃圾分类器

    ret = detect_process("mjpg_streamer");                                  // 检测mjpg_streamer是否在运行
    if(ret == -1){              
        printf("USB摄像头未连接或mjpg_streamer未启动\n");
        goto END;                                                           // 未启动则退出
    }

    serial_fd = mySerialOpen(SERIAL_DEV, SERIAL_BAUD);                      // 打开串口
    if(serial_fd == -1){
        printf("打开串口失败\n");
        goto END;                                                           // 打开串口失败则退出
    }

    while(1){
        len = mySerialReadString(serial_fd, buffer);                        // 读取串口数据
        printf("len = %d, buffer[2] = 0x%x\n",len, buffer[2]);              // 打印串口数据
        if(len > 0 && buffer[2] == 0x46){
            buffer[2] = 0x00;                                              // 置0x46为0x00
            printf("使用wget拍照中...\n");
            system(WGET_CMD);                                              // 执行wget命令拍照
            printf("拍照完成\n");

            if(access(GARBAGE_FILE, F_OK) == 0){                           // 判断垃圾分类文件是否存在
                category = garbage_category(category);                      // 分类垃圾
                if(strstr(category, "干垃圾")){
                    buffer[2] = 0x41;                                      // 识别为干垃圾
                }else if(strstr(category, "湿垃圾")){
                    buffer[2] = 0x42;                                      // 识别为湿垃圾
                }else if(strstr(category, "可回收垃圾")){
                    buffer[2] = 0x43;                                      // 识别为可回收垃圾
                }else if(strstr(category, "有害垃圾")){
                    buffer[2] = 0x44;                                      // 识别为有害垃圾
                }else{
                    buffer[2] = 0x45;                                      // 未识别到垃圾类型
                }
            }else{
                buffer[2] = 0x45;                                          //识别失败
            }
            //发送分类结果到串口
            mySerialSendString(serial_fd, buffer, 6);                       // 发送数据
            buffer[2] = 0x00;                                              // 置0x00为0x46  
            remove(GARBAGE_FILE);                                          // 删除垃圾分类文件  
        }
    }

END:
    garbage_destroy();                                      // 销毁垃圾分类器

    return 0;
}
gcc *.c *.h -o test -I /usr/include/python3.10 -l python3.10		//编译代码
./test																//运行程序

8.2 增加垃圾桶开关盖功能:

实现功能:使用语音模块和摄像头在香橙派上做垃圾智能分类识别, 同时根据识别结果开关不同的垃圾桶的盖子。

8.2.2 硬件接线:

在《语音模块和阿里云图像识别结合》搭建环境的基础上, 接上用于开关盖的舵机,当前代码里用了4个舵机用于示例代码的编写,用于区分4垃圾类型,接线位置如下:

 四个垃圾桶的舵机的PWM引脚都接到了全志H616的开发板上了,由于我们的开发板上面的电源引脚有限,刚好我们手里也有自己做的外置DC-DC电源模块,把四个舵机的VCC和GND分别接到电源模块的5V引脚

8.2.3 PWM频率的公式:

8.2.4 代码实现:

增加用于实现开光盖(驱动舵机)的源码文件(pwm.c)、(pwm.h)和增加PWM功能后的main.c文件:

/* pwm.c */
#include <wiringPi.h>
#include <softPwm.h>

void pwm_write(int pwm_pin) 
{
    pinMode(pwm_pin, OUTPUT);                                       // 设置pwm_pin引脚为输出模式
    softPwmCreate(pwm_pin, 0, 200);                                 // 设置pwm_pin的占空比为0,占空比范围为0-200
    softPwmWrite(pwm_pin, 20);                                      // 设置pwm_pin的占空比为10,即5%,1ms,45%
    delay(1000);                                                    
    softPwmStop(pwm_pin);                                            // 停止pwm_pin的占空比输出
}

void pwm_stop(int pwm_pin)
{
    pinMode(pwm_pin, OUTPUT);                                       // 设置pwm_pin引脚为输出模式
    softPwmCreate(pwm_pin, 0, 200);                                 // 设置pwm_pin的占空比为0,占空比范围为0-200
    softPwmWrite(pwm_pin, 5);                                       // 设置pwm_pin的占空比为5,0.5ms,0°
    delay(1000);                                                    
    softPwmStop(pwm_pin);                                            // 停止pwm_pin的占空比输出
}
/* pwm.h */
#ifndef __PWM_H__
#define __PWM_H__

#define GanGarbage_SG90_PIN 2                                           //干垃圾GPIO引脚 
#define ShiGarbage_SG90_PIN 5                                           //湿垃圾GPIO引脚 
#define KeGarbage_SG90_PIN  7                                           //可回收垃圾GPIO引脚
#define YouGarbage_SG90_PIN 8                                           //有害垃圾GPIO引脚

void pwm_write(int pwm_pin);                                            //设置PWM占空比
void pwm_stop(int pwm_pin);                                             //停止PWM占空比

#endif
/* main.c */
#include <stdio.h>          // 包含标准输入输出库,用于基本的输入输出操作
#include <stdlib.h>         // 包含标准库,用于内存分配等操作
#include <string.h>         // 包含字符串操作库,用于字符串处理
#include <unistd.h>         // 包含unistd库,用于系统调用
#include <wiringPi.h>       // 包含wiringPi库,用于GPIO控制

#include "garbage.h"        // 包含自定义的垃圾回收库,用于内存管理
#include "serialTool.h"     // 包含自定义的串口工具库,用于串口通信
#include "pwm.h"            // 包含自定义的PWM库,用于控制舵机

/*判断进程是否在运行*/ 
static int detect_process(const char * process_name)
{
	int n = -1;
	FILE *strm;
	char buf[128] = {0};
	
	// 构造命令字符串,通过ps命令查找进程
	sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
	// 使用popen执行命令并读取输出
	if ((strm = popen(buf, "r")) != NULL) {
		if (fgets(buf, sizeof(buf), strm) != NULL) {
			n = atoi(buf); // 将进程ID字符串转换为整数
		}
	}
	else {
		return -1; // popen失败
	}	
	pclose(strm); // 关闭popen打开的文件流

	return n;
}

int main(int argc, char **argv)
{
    int ret;                                                                // 函数返回值
    int len = 0;                                                            // 串口数据长度
    int serial_fd;                                                          // 串口文件描述符
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};         // 发送数据
    char *category = NULL;                                                  // 垃圾分类结果

    wiringPiSetup();                                                         // 初始化wiringPi库
    garbage_init();                                                         // 初始化垃圾分类器

    ret = detect_process("mjpg_streamer");                                  // 检测mjpg_streamer是否在运行
    if(ret == -1){              
        printf("USB摄像头未连接或mjpg_streamer未启动\n");
        goto END;                                                           // 未启动则退出
    }

    serial_fd = mySerialOpen(SERIAL_DEV, SERIAL_BAUD);                      // 打开串口
    if(serial_fd == -1){
        printf("打开串口失败\n");
        goto END;                                                           // 打开串口失败则退出
    }

    while(1){
        len = mySerialReadString(serial_fd, buffer);                        // 读取串口数据
        printf("len = %d, buffer[2] = 0x%x\n",len, buffer[2]);              // 打印串口数据
        if(len > 0 && buffer[2] == 0x46){
            buffer[2] = 0x00;                                              // 置0x46为0x00
            printf("使用wget拍照中...\n");
            system(WGET_CMD);                                              // 执行wget命令拍照
            printf("拍照完成\n");

            if(access(GARBAGE_FILE, F_OK) == 0){                           // 判断垃圾分类文件是否存在
                category = garbage_category(category);                      // 分类垃圾
                if(strstr(category, "干垃圾")){
                    buffer[2] = 0x41;                                      // 识别为干垃圾
                }else if(strstr(category, "湿垃圾")){
                    buffer[2] = 0x42;                                      // 识别为湿垃圾
                }else if(strstr(category, "可回收垃圾")){
                    buffer[2] = 0x43;                                      // 识别为可回收垃圾
                }else if(strstr(category, "有害垃圾")){
                    buffer[2] = 0x44;                                      // 识别为有害垃圾
                }else{
                    buffer[2] = 0x45;                                      // 未识别到垃圾类型
                }
            }else{
                buffer[2] = 0x45;                                          //识别失败
            }
            //发送分类结果到串口
            mySerialSendString(serial_fd, buffer, 6);                       // 发送数据
            if(buffer[2] == 0x41){                                                      // 识别为干垃圾
                pwm_stop(GanGarbage_SG90_PIN);                                      
                delay(2000);                                                            // 延时2秒
                pwm_write(GanGarbage_SG90_PIN);                                     
            }else if(buffer[2] == 0x42){                                                // 识别为湿垃圾
                pwm_stop(ShiGarbage_SG90_PIN);                                      
                delay(2000);                                                            // 延时2秒
                pwm_write(ShiGarbage_SG90_PIN);                                     
            }else if(buffer[2] == 0x43){                                                // 识别为可回收垃圾
                pwm_stop(KeGarbage_SG90_PIN);                                      
                delay(2000);                                                            // 延时2秒
                pwm_write(KeGarbage_SG90_PIN);                                     
            }else if(buffer[2] == 0x44){                                                // 识别为有害垃圾
                pwm_stop(YouGarbage_SG90_PIN);                                      
                delay(2000);                                                            // 延时2秒
                pwm_write(YouGarbage_SG90_PIN);                                     
            }
            buffer[2] = 0x00;                                              // 置0x00为0x46  
            remove(GARBAGE_FILE);                                          // 删除垃圾分类文件  
        }
    }

END:
    garbage_destroy();                                      // 销毁垃圾分类器

    return 0;
}

8.3 项目代码优化:

在之前实现的代码中, 主函数是单线程执行的, 导致整个代码的可扩展性非常差,比如想加OLED显示 或者添加网络控制变得非常复杂, 而且执行一次识别开关盖的流程非常长。因此,调整下代码架构,增加并发功能、提升代码的可扩展性 和执行效率。

8.3.1 代码流程:

8.3.2 代码实现:

修改main.c代码,调整整体main函数的代码架构,利用多线程实现具体的功能(用到了线程里的条件变量控制线程间的数据同步):

#include <stdio.h>          // 包含标准输入输出库,用于基本的输入输出操作
#include <stdlib.h>         // 包含标准库,用于内存分配等操作
#include <string.h>         // 包含字符串操作库,用于字符串处理
#include <unistd.h>         // 包含unistd库,用于系统调用
#include <wiringPi.h>       // 包含wiringPi库,用于GPIO控制
#include <pthread.h>        // 包含pthread库,用于线程控制

#include "garbage.h"        // 包含自定义的垃圾回收库,用于内存管理
#include "serialTool.h"     // 包含自定义的串口工具库,用于串口通信
#include "pwm.h"            // 包含自定义的PWM库,用于控制舵机

int serial_fd;                                                          // 串口文件描述符
pthread_mutex_t mutex;                                                  // 互斥锁,用于线程之间的互斥访问
pthread_cond_t cond;                                                    // 条件变量,用于线程之间的条件同步

/*判断进程是否在运行*/ 
static int detect_process(const char * process_name)
{
	int n = -1;
	FILE *strm;
	char buf[128] = {0};
	
	// 构造命令字符串,通过ps命令查找进程
	sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
	// 使用popen执行命令并读取输出
	if ((strm = popen(buf, "r")) != NULL) {
		if (fgets(buf, sizeof(buf), strm) != NULL) {
			n = atoi(buf); // 将进程ID字符串转换为整数
		}
	}
	else {
		return -1; // popen失败
	}	
	pclose(strm); // 关闭popen打开的文件流

	return n;
}

/*语音识别线程*/
void* SU03T_func(void* arg)
{   
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};             // 发送数据
    int len = 0;                                                                // 串口数据长度

    if(serial_fd == -1){
        printf("%s|%s|%d:串口未打开\n", __FILE__, __func__, __LINE__);         
        pthread_exit(NULL);                                                     // 线程退出
    }
    while(1){
        len = mySerialReadString(serial_fd, buffer);                            // 读取串口数据 
        printf("len = %d, buffer[2] = 0x%x\n",len, buffer[2]);                  // 打印串口数据
        //检测到垃圾分类数据,唤醒阿里云交互线程
        if(len > 0 && buffer[2] == 0x46){
            pthread_mutex_lock(&mutex);                                         // 加互斥锁
            buffer[2] = 0x00;                                                   // 置0x46为0x00
            pthread_cond_signal(&cond);                                         // 唤醒阿里云交互线程
            pthread_mutex_unlock(&mutex);                                       // 解互斥锁
        }
    }

    pthread_exit(0);                                                            // 线程退出   
}

/* 语音播报线程 */
void* yuyin_func(void* arg)
{
    pthread_detach(pthread_self());                                             // 使线程成为守护线程
    unsigned char *buffer = (unsigned char *)arg;                               // 接收到的数据

    if(serial_fd == -1){
        printf("%s|%s|%d:串口未打开\n", __FILE__, __func__, __LINE__);         
        pthread_exit(NULL);                                                     // 线程退出
    }

    if(buffer != NULL){                                                         // 判断是否接收到数据
        printf("开始播报语音...\n");
        mySerialSendString(serial_fd, buffer, 6);                               // 发送数据
        printf("播报语音结束\n");
    }

    pthread_exit(0);                                                            // 线程退出
}

/* SG90舵机线程 */
void* SG90_func(void* arg)
{
    pthread_detach(pthread_self());                                       //分离线程,使其在退出时能够自动释放资源
    unsigned char *buffer = (unsigned char *)arg;                               // 接收到的数据

    if(buffer[2] == 0x41){                                                      // 识别为干垃圾
        pwm_stop(GanGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(GanGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x42){                                                      // 识别为湿垃圾
        pwm_stop(ShiGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(ShiGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x43){                                                      // 识别为可回收垃圾
        pwm_stop(KeGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(KeGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x44){                                                      // 识别为有害垃圾
        pwm_stop(YouGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(YouGarbage_SG90_PIN);                                     
    }

    pthread_exit(0);                                                            // 线程退出
}

/*阿里云交互线程*/
void* aliyun_func(void* arg)
{
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};             // 发送数据
    char *category = NULL;                                                      // 垃圾分类结果
    pthread_t SG90_thread_id;                                                   // 定义SG90舵机线程ID
    pthread_t yuyin_thread_id;                                                 // 定义播报线程ID

    while(1){
        pthread_mutex_lock(&mutex);                                             // 加互斥锁
        pthread_cond_wait(&cond, &mutex);                                       // 等待SU03T线程发送垃圾分类数据
        pthread_mutex_unlock(&mutex);                                         // 解互斥锁
        
        buffer[2] = 0x00;                                                      // 置0x00为0x46 
        printf("使用wget拍照中...\n");
        system(WGET_CMD);                                                      // 执行wget命令拍照
        printf("拍照完成\n");

        if(access(GARBAGE_FILE, F_OK) == 0){                                    // 判断垃圾分类文件是否存在    
            category = garbage_category(category);                              // 分类垃圾
            if(strstr(category,"干垃圾")){
                buffer[2] = 0x41;                                              // 识别为干垃圾
            }else if(strstr(category, "湿垃圾")){
                buffer[2] = 0x42;                                              // 识别为湿垃圾
            }else if(strstr(category, "可回收垃圾")){
                buffer[2] = 0x43;                                              // 识别为可回收垃圾
            }else if(strstr(category, "有害垃圾")){     
                buffer[2] = 0x44;                                              // 识别为有害垃圾
            }else{
                buffer[2] = 0x45;                                              // 未识别到垃圾类型
            }  
        }else{
            buffer[2] = 0x45;                                                  // 识别失败
        }
        //创建开垃圾桶线程
        pthread_create(&SG90_thread_id, NULL, SG90_func, (void *)buffer);      // 创建SG90舵机线程
        //创建语音播报线程
        pthread_create(&yuyin_thread_id, NULL, yuyin_func, (void *)buffer);     // 创建播报线程
        
        remove(GARBAGE_FILE);                                                   // 删除垃圾分类文件                              
    }
    
    pthread_exit(NULL);                                                   // 线程退出       
}

int main(int argc, char **argv)
{
    int ret;

    pthread_t SU03T_thread_id;                                              // 定义SU03T线程ID
    pthread_t aliyun_thread_id;                                             // 定义阿里云交互线程ID

    wiringPiSetup();                                                         // 初始化wiringPi库
    garbage_init();                                                         // 初始化垃圾分类器

    ret = detect_process("mjpg_streamer");                                  // 检测mjpg_streamer是否在运行
    if(ret == -1){              
        printf("USB摄像头未连接或mjpg_streamer未启动\n");
        goto END;                                                           // 未启动则退出
    }

    serial_fd = mySerialOpen(SERIAL_DEV, SERIAL_BAUD);                      // 打开串口
    if(serial_fd == -1){
        printf("打开串口失败\n");
        goto END;                                                           // 打开串口失败则退出
    }

    //创建语音线程
    pthread_create(&SU03T_thread_id, NULL, SU03T_func, NULL);                // 创建SU03T线程
    //创建阿里云交互线程
    pthread_create(&aliyun_thread_id, NULL, aliyun_func, NULL);              // 创建阿里云交互线程

    pthread_join(SU03T_thread_id, NULL);                                    // 等待SU03T线程结束
    pthread_join(aliyun_thread_id, NULL);                                   // 等待阿里云交互线程结束

    pthread_mutex_destroy(&mutex);                                           // 销毁互斥锁
    pthread_cond_destroy(&cond);                                             // 销毁条件变量

    close(serial_fd);                                                      // 关闭串口

END:
    garbage_destroy();                                                    // 销毁垃圾分类器

    return 0;
}
gcc *.c *.h -o test -I /usr/include/python3.10 -l python3.10 -l pthread -l wiringPi		//编译代码
sudo -E ./test																			//运行程序

8.4 写一个shell脚本用于杀死运行的进程:

当你运行程序时,shell脚本可以使用ps命令查找到特定进程的PID并使用kill命令杀死该进程。 以下是一个简单的Shell脚本示例,假设你的程序名为test

#!/bin/bash

# 查找进程PID
PID=$(ps aux | grep './test' | grep -v grep | awk '{print $2}')

if [ -n "$PID" ]; then
    # 杀死进程
    kill -SIGKILL $PID
    echo "Process ./test (PID $PID) killed."
else
    echo "Process ./test not found."
fi

这个脚本中,ps aux命令获取当前所有进程的信息,grep './test'用于查找包含"./test"的进程,awk '{print $2}'用于提取PID。然后,脚本检查是否找到了进程,如果找到,就使用kill命令杀死进程,并输出相关信息。如果没有找到对应进程,输出相应的提示。

grep -v grep 是为了在使用 ps 命令结合 grep 进行进程查询时,排除掉 grep 进程本身。当你执行 ps aux | grep 'test' 时,该命令本身也会被匹配,因为它包含了关键字 'test'。为了排除掉这个匹配,使用 grep -v grep 来过滤掉含有 'grep' 字符串的行,从而得到准确的进程信息。

将这个脚本保存为kill_test.sh,并添加执行权限:

chmod +x kill_test.sh

然后可以通过运行./kill_test.sh来执行脚本。确保在运行脚本之前你的程序已经启动。

8.5 增加OLED显示功能:

回顾我们之前在全志H616芯片上驱动的OLED屏幕:[Orangepi配合IIC驱动OLED屏幕_orangepi z3 iic-CSDN博客](https://blog.csdn.net/weixin_54859557/article/details/140216221?spm=1001.2014.3001.5501)

8.5.1 环境配置:
cat /boot/orangepiEnv.txt

8.5.2 代码实现:

在原有的代码上增加两个文件:myOLED.cmyOLED.h,然后修改下main.c文件, 增加OLED线程,用于显示识别后的垃圾类型

/* myOLED.c */
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include "oled.h"
#include "font.h"

#include "myOLED.h" 

#define FILENAME "/dev/i2c-3"                                               //设备文件名
static struct display_info disp;                                           //显示信息结构体

int oled_show(void *arg)                                                   //显示垃圾信息
{
    unsigned char *buffer = (unsigned char *)arg;                            //获取垃圾信息
    oled_putstrto(&disp, 0, 0, "Garbage type:");     
    disp.font = font2;

    switch(buffer[2])                                                     //根据垃圾类型显示垃圾名称
    {
        case 0x41:
            oled_putstrto(&disp, 0, 20, "Gan Garbage");                       //干垃圾
            break;
        case 0x42:
            oled_putstrto(&disp, 0, 20, "Shi Garbage");                         //湿垃圾
            break;
        case 0x43:
            oled_putstrto(&disp, 0, 20, "KeHuiShou Garbage");                    //可回收垃圾
            break;
        case 0x44:
            oled_putstrto(&disp, 0, 20, "YouHai Garbage");                      //有害垃圾
   	        break;
        case 0x45:
            oled_putstrto(&disp, 0, 20, "Not Garbage");                         //未识别到垃圾类型
            break;
        
    }
    disp.font = font2;                                                          //选择字体
    oled_send_buffer(&disp);                                                   //发送显示缓冲区     

    return 0;
}

int myoled_init(void)                                                           //初始化OLED屏幕
{
    int e;                                                                       //错误码   

    disp.address = OLED_I2C_ADDR;                                               //设置OLED地址
    disp.font = font2;                                                           //选择字体
    e = oled_open(&disp, FILENAME);                                             //打开OLED设备  
    e = oled_init(&disp);                                                       //初始化OLED屏幕

    return  e;
}
/* myOLED.h*/
#ifndef __MYOLED__H
#define __MYOLED__H

int myoled_init(void);                      //初始化OLED
int oled_show(void *arg);                   //显示OLED

#endif
/* main.c */
#include <stdio.h>          // 包含标准输入输出库,用于基本的输入输出操作
#include <stdlib.h>         // 包含标准库,用于内存分配等操作
#include <string.h>         // 包含字符串操作库,用于字符串处理
#include <unistd.h>         // 包含unistd库,用于系统调用
#include <wiringPi.h>       // 包含wiringPi库,用于GPIO控制
#include <pthread.h>        // 包含pthread库,用于线程控制

#include "garbage.h"        // 包含自定义的垃圾回收库,用于内存管理
#include "serialTool.h"     // 包含自定义的串口工具库,用于串口通信
#include "pwm.h"            // 包含自定义的PWM库,用于控制舵机
#include "myOLED.h"         // 包含自定义的OLED库,用于显示屏控制

int serial_fd;                                                          // 串口文件描述符
pthread_mutex_t mutex;                                                  // 互斥锁,用于线程之间的互斥访问
pthread_cond_t cond;                                                    // 条件变量,用于线程之间的条件同步

/*判断进程是否在运行*/ 
static int detect_process(const char * process_name)
{
	int n = -1;
	FILE *strm;
	char buf[128] = {0};
	
	// 构造命令字符串,通过ps命令查找进程
	sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
	// 使用popen执行命令并读取输出
	if ((strm = popen(buf, "r")) != NULL) {
		if (fgets(buf, sizeof(buf), strm) != NULL) {
			n = atoi(buf); // 将进程ID字符串转换为整数
		}
	}
	else {
		return -1; // popen失败
	}	
	pclose(strm); // 关闭popen打开的文件流

	return n;
}

/*语音识别线程*/
void* SU03T_func(void* arg)
{   
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};             // 发送数据
    int len = 0;                                                                // 串口数据长度

    if(serial_fd == -1){
        printf("%s|%s|%d:串口未打开\n", __FILE__, __func__, __LINE__);         
        pthread_exit(NULL);                                                     // 线程退出
    }
    while(1){
        len = mySerialReadString(serial_fd, buffer);                            // 读取串口数据 
        printf("len = %d, buffer[2] = 0x%x\n",len, buffer[2]);                  // 打印串口数据
        //检测到垃圾分类数据,唤醒阿里云交互线程
        if(len > 0 && buffer[2] == 0x46){
            pthread_mutex_lock(&mutex);                                         // 加互斥锁
            buffer[2] = 0x00;                                                   // 置0x46为0x00
            pthread_cond_signal(&cond);                                         // 唤醒阿里云交互线程
            pthread_mutex_unlock(&mutex);                                       // 解互斥锁
        }
    }

    pthread_exit(0);                                                            // 线程退出   
}

/* 语音播报线程 */
void* yuyin_func(void* arg)
{
    pthread_detach(pthread_self());                                        //分离线程,使其在退出时能够自动释放资源
    unsigned char *buffer = (unsigned char *)arg;                               // 接收到的数据

    if(serial_fd == -1){
        printf("%s|%s|%d:串口未打开\n", __FILE__, __func__, __LINE__);         
        pthread_exit(NULL);                                                     // 线程退出
    }

    if(buffer != NULL){                                                         // 判断是否接收到数据
        printf("开始播报语音...\n");
        mySerialSendString(serial_fd, buffer, 6);                               // 发送数据
        printf("播报语音结束\n");
    }

    pthread_exit(0);                                                            // 线程退出
}

/* SG90舵机线程 */
void* SG90_func(void* arg)
{
    pthread_detach(pthread_self());                                        //分离线程,使其在退出时能够自动释放资源
    unsigned char *buffer = (unsigned char *)arg;                               // 接收到的数据

    if(buffer[2] == 0x41){                                                      // 识别为干垃圾
        pwm_stop(GanGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(GanGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x42){                                                      // 识别为湿垃圾
        pwm_stop(ShiGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(ShiGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x43){                                                      // 识别为可回收垃圾
        pwm_stop(KeGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(KeGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x44){                                                      // 识别为有害垃圾
        pwm_stop(YouGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(YouGarbage_SG90_PIN);                                     
    }

    pthread_exit(0);                                                            // 线程退出
}

/* OLED显示线程 */
void* OLED_func(void* arg)
{
    pthread_detach(pthread_self());                                        //分离线程,使其在退出时能够自动释放资源

    myoled_init();                                                              //OLED初始化

    oled_show(arg);                                                           //OLED显示垃圾信息  
    pthread_exit(0);                                                            // 线程退出
}

/*阿里云交互线程*/
void* aliyun_func(void* arg)
{
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};             // 发送数据
    char *category = NULL;                                                      // 垃圾分类结果
    pthread_t SG90_thread_id;                                                   // 定义SG90舵机线程ID
    pthread_t yuyin_thread_id;                                                 // 定义播报线程ID
    pthread_t OLED_thread_id;                                                  // 定义OLED显示线程ID

    while(1){
        pthread_mutex_lock(&mutex);                                             // 加互斥锁
        pthread_cond_wait(&cond, &mutex);                                       // 等待SU03T线程发送垃圾分类数据
        pthread_mutex_unlock(&mutex);                                         // 解互斥锁
        
        buffer[2] = 0x00;                                                      // 置0x00为0x46 
        printf("使用wget拍照中...\n");
        system(WGET_CMD);                                                      // 执行wget命令拍照
        printf("拍照完成\n");

        if(access(GARBAGE_FILE, F_OK) == 0){                                    // 判断垃圾分类文件是否存在    
            category = garbage_category(category);                              // 分类垃圾
            if(strstr(category,"干垃圾")){
                buffer[2] = 0x41;                                              // 识别为干垃圾
            }else if(strstr(category, "湿垃圾")){
                buffer[2] = 0x42;                                              // 识别为湿垃圾
            }else if(strstr(category, "可回收垃圾")){
                buffer[2] = 0x43;                                              // 识别为可回收垃圾
            }else if(strstr(category, "有害垃圾")){     
                buffer[2] = 0x44;                                              // 识别为有害垃圾
            }else{
                buffer[2] = 0x45;                                              // 未识别到垃圾类型
            }  
        }else{
            buffer[2] = 0x45;                                                  // 识别失败
        }
        //创建开垃圾桶线程
        pthread_create(&SG90_thread_id, NULL, SG90_func, (void *)buffer);      // 创建SG90舵机线程
        //创建语音播报线程
        pthread_create(&yuyin_thread_id, NULL, yuyin_func, (void *)buffer);     // 创建播报线程
        //OLED显示垃圾分类结果线程
        pthread_create(&OLED_thread_id, NULL, OLED_func, (void *)buffer);     // 创建OLED显示线程
        
        remove(GARBAGE_FILE);                                                   // 删除垃圾分类文件                              
    }
    
    pthread_exit(NULL);                                                   // 线程退出       
}

int main(int argc, char **argv)
{
    int ret;

    pthread_t SU03T_thread_id;                                              // 定义SU03T线程ID
    pthread_t aliyun_thread_id;                                             // 定义阿里云交互线程ID

    wiringPiSetup();                                                         // 初始化wiringPi库
    garbage_init();                                                         // 初始化垃圾分类器

    ret = detect_process("mjpg_streamer");                                  // 检测mjpg_streamer是否在运行
    if(ret == -1){              
        printf("USB摄像头未连接或mjpg_streamer未启动\n");
        goto END;                                                           // 未启动则退出
    }

    serial_fd = mySerialOpen(SERIAL_DEV, SERIAL_BAUD);                      // 打开串口
    if(serial_fd == -1){
        printf("打开串口失败\n");
        goto END;                                                           // 打开串口失败则退出
    }

    //创建语音线程
    pthread_create(&SU03T_thread_id, NULL, SU03T_func, NULL);                // 创建SU03T线程
    //创建阿里云交互线程
    pthread_create(&aliyun_thread_id, NULL, aliyun_func, NULL);              // 创建阿里云交互线程

    pthread_join(SU03T_thread_id, NULL);                                    // 等待SU03T线程结束
    pthread_join(aliyun_thread_id, NULL);                                   // 等待阿里云交互线程结束

    pthread_mutex_destroy(&mutex);                                           // 销毁互斥锁
    pthread_cond_destroy(&cond);                                             // 销毁条件变量

    close(serial_fd);                                                      // 关闭串口

END:
    garbage_destroy();                                                    // 销毁垃圾分类器

    return 0;
}

8.6 增加网络控制功能:

实现需求: 回顾下Socket编程,通过Socket远程接入控制垃圾识别功能

[Linux系统编程——网络编程_sendto函数是先发低字节还是高字节-CSDN博客](https://blog.csdn.net/weixin_54859557/article/details/139744676?spm=1001.2014.3001.5502)

8.6.1 代码流程:

8.6.2 TCP 心跳机制解决Soket异常断开问题:
  • Socket客户端得断开情形无非就两种情况:

  1. 客户端能够发送状态给服务器;正常断开,强制关闭客户端等,客户端能够做出反应。

  2. 客户端不能发送状态给服务器;突然断网,断电,客户端卡死等,客户端根本没机会做出反应,服务器更不了解客户端状态,导致服务器异常等待。

  • 为了解决上述问题,引入TCP心跳包机制

心跳包的实现,心跳包就是服务器定时向客户端发送查询信息,如果客户端有回应就代表连接正常,类似于linux系统的看门狗机制。心跳包的机制有一种方法就是采用TCP_KEEPALIVE机制,它是一种用于检测TCP连接是否存活的机制,它的原理是在一定时间内没有数据往来时,发送探测包给对方,如果对方没有响应,就认为连接已经断开TCP_KEEPALIVE机制可以通过设置一些参数来调整,如探测时间间隔、探测次数等。

  • Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。

  • 查看当前系统的TCP KeepAlive参数:

sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_probes
sysctl net.ipv4.tcp_keepalive_intvl
  • 修改TCP KeepAlive参数:

sysctl net.ipv4.tcp_keepalive_time=3600
8.6.3 C语言实现TCP KeepAlive功能:

对于Socket而言,可以在程序中通过socket选项开启TCP KeepAlive功能,并配置参数。对应的Socket选项分别为 SO_KEEPALIVETCP_KEEPIDLETCP_KEEPCNTTCP_KEEPINTVL

int keepalive = 1; 	// 开启TCP KeepAlive功能
int keepidle = 5; 	// tcp_keepalive_time 3s内没收到数据开始发送心跳包
int keepcnt = 3; 	// tcp_keepalive_probes 每次发送心跳包的时间间隔,单位秒
int keepintvl = 3; 	// tcp_keepalive_intvl 每3s发送一次心跳包

setsockopt(client_socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive,sizeof(keepalive));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof(keepidle));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof(keepcnt));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof(keepintvl));
8.6.4 代码实现:
/* socket.h */
#ifndef __SOCKET_H__
#define __SOCKET_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <unistd.h>

#define IPADDR "192.168.31.249"         // IP地址
#define IPPORT "8192"                   // 端口号
#define BUF_SIZE 6                      // 缓冲区大小

int socket_init(const char *ipaddr, const char *port);

#endif
/* socket.c */
#include "socket.h"

int socket_init(const char *ipaddr, const char *port)
{
    int s_fd;                                                                   //套接字文件描述符
    int ret;

    struct sockaddr_in server_addr;                                              //服务器地址结构体

    memset(&server_addr, 0, sizeof(server_addr));                                //清零服务器地址结构体

    s_fd = socket(AF_INET, SOCK_STREAM, 0);                                     //创建TCP/IP套接字
    if(s_fd == -1){
        printf("创建套接字失败!\n");
        perror("socket");
        return -1;
    }

    server_addr.sin_family = AF_INET;                                        //设置服务器地址结构体的地址族为IPv4
    server_addr.sin_port = htons(atoi(port));                                    //设置服务器地址结构体的端口号
    inet_aton(ipaddr, &server_addr.sin_addr);                                   // 设置服务器IP地址

    ret = bind(s_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));    //绑定IP地址和端口号
    if(ret == -1){
        printf("绑定IP地址和端口号失败!\n");   
        perror("bind");
        return -1;  
    }

    ret = listen(s_fd, 1);                                                      //监听套接字
    if(ret == -1){
        printf("监听套接字失败!\n");
        perror("listen");
        return -1;
    }

    return s_fd;                                                               //返回套接字文件描述符
}
/* main.c */
#include <stdio.h>          // 包含标准输入输出库,用于基本的输入输出操作
#include <stdlib.h>         // 包含标准库,用于内存分配等操作
#include <string.h>         // 包含字符串操作库,用于字符串处理
#include <unistd.h>         // 包含unistd库,用于系统调用
#include <wiringPi.h>       // 包含wiringPi库,用于GPIO控制
#include <pthread.h>        // 包含pthread库,用于线程控制

#include "garbage.h"        // 包含自定义的垃圾回收库,用于内存管理
#include "serialTool.h"     // 包含自定义的串口工具库,用于串口通信
#include "pwm.h"            // 包含自定义的PWM库,用于控制舵机
#include "myOLED.h"         // 包含自定义的OLED库,用于显示屏控制
#include "socket.h"         // 包含自定义的socket库,用于网络通信

int serial_fd;                                                          // 串口文件描述符
pthread_mutex_t mutex;                                                  // 互斥锁,用于线程之间的互斥访问
pthread_cond_t cond;                                                    // 条件变量,用于线程之间的条件同步

/*判断进程是否在运行*/ 
static int detect_process(const char * process_name)
{
	int n = -1;
	FILE *strm;
	char buf[128] = {0};
	
	// 构造命令字符串,通过ps命令查找进程
	sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
	// 使用popen执行命令并读取输出
	if ((strm = popen(buf, "r")) != NULL) {
		if (fgets(buf, sizeof(buf), strm) != NULL) {
			n = atoi(buf); // 将进程ID字符串转换为整数
		}
	}
	else {
		return -1; // popen失败
	}	
	pclose(strm); // 关闭popen打开的文件流

	return n;
}

/*语音识别线程*/
void* SU03T_func(void* arg)
{   
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};             // 发送数据
    int len = 0;                                                                // 串口数据长度

    if(serial_fd == -1){
        printf("%s|%s|%d:串口未打开\n", __FILE__, __func__, __LINE__);         
        pthread_exit(NULL);                                                     // 线程退出
    }
    while(1){
        len = mySerialReadString(serial_fd, buffer);                            // 读取串口数据 
        printf("len = %d, buffer[2] = 0x%x\n",len, buffer[2]);                  // 打印串口数据
        //检测到垃圾分类数据,唤醒阿里云交互线程
        if(len > 0 && buffer[2] == 0x46){
            pthread_mutex_lock(&mutex);                                         // 加互斥锁
            buffer[2] = 0x00;                                                   // 置0x46为0x00
            pthread_cond_signal(&cond);                                         // 唤醒阿里云交互线程
            pthread_mutex_unlock(&mutex);                                       // 解互斥锁
        }
    }

    pthread_exit(0);                                                            // 线程退出   
}

/* 语音播报线程 */
void* yuyin_func(void* arg)
{
    pthread_detach(pthread_self());                                        //分离线程,使其在退出时能够自动释放资源
    unsigned char *buffer = (unsigned char *)arg;                               // 接收到的数据

    if(serial_fd == -1){
        printf("%s|%s|%d:串口未打开\n", __FILE__, __func__, __LINE__);         
        pthread_exit(NULL);                                                     // 线程退出
    }

    if(buffer != NULL){                                                         // 判断是否接收到数据
        printf("开始播报语音...\n");
        mySerialSendString(serial_fd, buffer, 6);                               // 发送数据
        printf("播报语音结束\n");
    }

    pthread_exit(0);                                                            // 线程退出
}

/* SG90舵机线程 */
void* SG90_func(void* arg)
{
    pthread_detach(pthread_self());                                        //分离线程,使其在退出时能够自动释放资源
    unsigned char *buffer = (unsigned char *)arg;                               // 接收到的数据

     if(buffer[2] == 0x41){                                                      // 识别为干垃圾
        pwm_stop(GanGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(GanGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x42){                                                      // 识别为湿垃圾
        pwm_stop(ShiGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(ShiGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x43){                                                      // 识别为可回收垃圾
        pwm_stop(KeGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(KeGarbage_SG90_PIN);                                     
    }else if(buffer[2] == 0x44){                                                      // 识别为有害垃圾
        pwm_stop(YouGarbage_SG90_PIN);                                      
        delay(2000);                                                            // 延时2秒
        pwm_write(YouGarbage_SG90_PIN);                                     
    }

    pthread_exit(0);                                                            // 线程退出
}

/* OLED显示线程 */
void* OLED_func(void* arg)
{
    pthread_detach(pthread_self());                                        //分离线程,使其在退出时能够自动释放资源

    myoled_init();                                                              //OLED初始化

    oled_show(arg);                                                           //OLED显示垃圾信息  
    pthread_exit(0);                                                            // 线程退出
}

/*阿里云交互线程*/
void* aliyun_func(void* arg)
{
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};             // 发送数据
    char *category = NULL;                                                      // 垃圾分类结果
    pthread_t SG90_thread_id;                                                   // 定义SG90舵机线程ID
    pthread_t yuyin_thread_id;                                                 // 定义播报线程ID
    pthread_t OLED_thread_id;                                                  // 定义OLED显示线程ID

    while(1){
        pthread_mutex_lock(&mutex);                                             // 加互斥锁
        pthread_cond_wait(&cond, &mutex);                                       // 等待SU03T线程发送垃圾分类数据
        pthread_mutex_unlock(&mutex);                                         // 解互斥锁
        
        buffer[2] = 0x00;                                                      // 置0x00为0x46 
        printf("使用wget拍照中...\n");
        system(WGET_CMD);                                                      // 执行wget命令拍照
        printf("拍照完成\n");

        if(access(GARBAGE_FILE, F_OK) == 0){                                    // 判断垃圾分类文件是否存在    
            category = garbage_category(category);                              // 分类垃圾
            if(strstr(category,"干垃圾")){
                buffer[2] = 0x41;                                              // 识别为干垃圾
            }else if(strstr(category, "湿垃圾")){
                buffer[2] = 0x42;                                              // 识别为湿垃圾
            }else if(strstr(category, "可回收垃圾")){
                buffer[2] = 0x43;                                              // 识别为可回收垃圾
            }else if(strstr(category, "有害垃圾")){     
                buffer[2] = 0x44;                                              // 识别为有害垃圾
            }else{
                buffer[2] = 0x45;                                              // 未识别到垃圾类型
            }  
        }else{
            buffer[2] = 0x45;                                                  // 识别失败
        }
        //创建开垃圾桶线程
        pthread_create(&SG90_thread_id, NULL, SG90_func, (void *)buffer);      // 创建SG90舵机线程
        //创建语音播报线程
        pthread_create(&yuyin_thread_id, NULL, yuyin_func, (void *)buffer);     // 创建播报线程
        //OLED显示垃圾分类结果线程
        pthread_create(&OLED_thread_id, NULL, OLED_func, (void *)buffer);     // 创建OLED显示线程
        
        remove(GARBAGE_FILE);                                                   // 删除垃圾分类文件                              
    }
    
    pthread_exit(NULL);                                                   // 线程退出       
}

/* 网络交互线程 */
void* socket_func(void* arg)
{
    int s_fd;                                                               // 网络通信文件描述符 
    int c_fd;                                                               // 客户端文件描述符
    struct sockaddr_in client_addr;                                          // 客户端地址
    char buffer[6];                                                         // 接收到的数据
    char n_read;                                                            // 读取到的字符 

    memset(&client_addr, 0, sizeof(client_addr));                            // 客户端地址清零

    s_fd = socket_init(IPADDR, IPPORT);                                   // 初始化网络通信
    if(s_fd == -1){
        printf("初始化网络通信失败\n");
        pthread_exit(NULL);                                                 // 线程退出
    }else{
        printf("初始化网络通信成功\n");
    }
    int client_len = sizeof(client_addr);                                    // 客户端地址长度

    while(1){
        c_fd = accept(s_fd, (struct sockaddr *)&client_addr, &client_len);    // 接收客户端连接

        // 配置 TCP KeepAlive 参数
		int keepalive = 1; 			// 开启TCP KeepAlive功能
		int keepidle = 5; 			// tcp_keepalive_time 3s内没收到数据开始发送心跳包
		int keepcnt = 3; 			// tcp_keepalive_probes 每次发送心跳包的时间间隔,单位秒
		int keepintvl = 3;			// tcp_keepalive_intvl 每3s发送一次心跳包

		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));

        if(c_fd == -1){
            printf("接收客户端连接失败\n");
            perror("accept");
            continue;
        }
        printf("接收客户端连接成功:%s:%d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        while(1){
            memset(buffer, 0, sizeof(buffer));                                // 清空接收到的数据
            n_read = read(c_fd, buffer, sizeof(buffer));                      // 读取数据
            if(n_read == -1 || n_read == 0){
                printf("读取数据失败\n");
                perror("read");
                break;
            }else if(n_read > 0){                                             // 读取到数据
                printf("接收到数据:%s\n", buffer);
                if(strstr(buffer, "open")){                                     //检测到open命令
                    pthread_mutex_lock(&mutex);                                 // 加互斥锁
                    pthread_cond_signal(&cond);                                 // 唤醒阿里云交互线程   
                    pthread_mutex_unlock(&mutex);                               // 解互斥锁
                }
            }
        }
        close(c_fd);                                                         // 关闭客户端连接
    }

    pthread_exit(NULL);                                                     // 线程退出
}

int main(int argc, char **argv)
{
    int ret;

    pthread_t SU03T_thread_id;                                              // 定义SU03T线程ID
    pthread_t aliyun_thread_id;                                             // 定义阿里云交互线程ID
    pthread_t socket_thread_id;                                             // 定义网络交互线程ID

    wiringPiSetup();                                                         // 初始化wiringPi库
    garbage_init();                                                         // 初始化垃圾分类器

    ret = detect_process("mjpg_streamer");                                  // 检测mjpg_streamer是否在运行
    if(ret == -1){              
        printf("USB摄像头未连接或mjpg_streamer未启动\n");
        goto END;                                                           // 未启动则退出
    }

    serial_fd = mySerialOpen(SERIAL_DEV, SERIAL_BAUD);                      // 打开串口
    if(serial_fd == -1){
        printf("打开串口失败\n");
        goto END;                                                           // 打开串口失败则退出
    }

    //创建语音线程
    pthread_create(&SU03T_thread_id, NULL, SU03T_func, NULL);                // 创建SU03T线程
    //创建阿里云交互线程
    pthread_create(&aliyun_thread_id, NULL, aliyun_func, NULL);              // 创建阿里云交互线程
    //创建网络交互线程
    pthread_create(&socket_thread_id, NULL, socket_func, NULL);              // 创建网络交互线程

    pthread_join(SU03T_thread_id, NULL);                                    // 等待SU03T线程结束
    pthread_join(aliyun_thread_id, NULL);                                   // 等待阿里云交互线程结束
    pthread_join(socket_thread_id, NULL);                                   // 等待网络交互线程结束

    pthread_mutex_destroy(&mutex);                                           // 销毁互斥锁
    pthread_cond_destroy(&cond);                                             // 销毁条件变量

    close(serial_fd);                                                      // 关闭串口

END:
    garbage_destroy();                                                    // 销毁垃圾分类器

    return 0;
}

 

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值