使用香橙派并基于Linux实现最终版带图像识别的可回收垃圾桶 — 下_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1bc411o7zS/?spm_id_from=333.999.0.0&vd_source=16380169fa415d2d056dd877a8d8c1b7
项目需求
如何使用C语言执行Python代码
ubuntu22.04系统重装
遇到的一些问题
环境搭建
C语言执行一小段python代码
python_test.c:
编译语句:
实现结果:
C语言执行python函数
代码示例:
python_function_test.py:
python_function_test.c:
编译语句:
实现效果:
阿里云的垃圾识别方案
编辑大体步骤:
账号创建 & 开启服务
获取AccessKey ID & Secret
安装SDK & 环境配置
图像识别代码试运行
aliyun_classify.py:
使用C语言调用阿里云识别
aliyun_classify.py:
classify.c:
classify.h: (固定格式)
ali_test.c:
香橙派的摄像头接入
首先,将USB摄像头插入,我使用的是一枚720P的广角USB摄像头:
然后运行以下指令可以看到内核自动加载了下面的模块:
然后通过以下命令可以看到 USB 摄像头的设备节点信息
使用 fswebcam 测试 USB 摄像头拍照
使用 mjpg-streamer 测试 USB 摄像头视频流
修改/home/orangepi/mjpg-streamer/mjpg-streamer-experimental/下的start.sh:
将视频拍照并保存本地:
编写脚本使得mjpg-streamer可以在香橙派开机时自动后台启动:
需求8 --- 使用第三方库结合python实现图像识别垃圾分类
实现思路
新的线程统计图:
新的程序运行图:
新的接线图:
语音模块的重新烧写
引脚设置:
唤醒词:
命令设置:
控制详情:编辑
设置免唤醒:
其他配置:
实现效果
代码的重新编写
注意事项
v6_server.c:
编译语句:
运行语句:
回顾需求:
项目需求
- 靠近时,垃圾桶开启2秒,2秒后关闭
- 垃圾桶开启带滴滴声
- 垃圾桶开启超过10秒,滴滴声警报
- 实现Sockect客户端发送指令远程打开/关闭垃圾桶,并显示垃圾桶状态
- 语音控制垃圾桶开关盖
- 记录一段时间内垃圾桶开关盖动作及开关盖指令来源并记录在文件中
- 统计一段时间内垃圾桶开关盖次数及开关盖指令来源并记录在数据库中
- 图像识别垃圾分类功能
在上节已经完成了对于前7个功能的编写,本节完成最后一个需求!
很无语的是,我目前香橙派上安装的UBUNTU18.04的python3和阿里云并不兼容,也就意味着,我需要先重装系统,安装更高版本的ubuntu,使用更高版本的python3.10来完成和阿里云的交互。
由于之前有过装系统的经验,这里就不再次详细演示了,注意重装前的重要资料备份!!
要将新烧写的系统恢复成我目前的状态,以下是要做的事:
(Orangepi Zero2 全志H616 的初识_mjmmm的博客-CSDN博客)
(香橙派使用外设驱动库wiringOP来驱动蜂鸣器_mjmmm的博客-CSDN博客)
(香橙派配合IIC驱动OLED & 使用SourceInsight解读源码_mjmmm的博客-CSDN博客)
(基于香橙派和SU-03T 使用Linux实现语音控制刷抖音-CSDN博客)
(使用香橙派学习Linux udev的rules 并实现U盘的自动挂载-CSDN博客)
(使用香橙派学习 嵌入式数据库---SQLite-CSDN博客)
安装完后发现,这一次香橙派的IP变为了192.168.2.23
遇到的一些问题
第二次安装后,运行“ls /dev”:
发现,i2c-3和uart5并没有被开启,经过查证,这是内核版本的问题,输入“uname -r” :
可见,内核版本是5.16.17,而这个版本并不会自动开启12c-3,只有老版本4.9才会!
解决办法:
输入“sudo vim /boot/orangepiEnv.txt” ; 并加入一句“overlays=i2c3 uart5”:
保存退出后,reboot,然后再次运行“ls /dev”:
问题解决 !
如果想要在C语言中调用python的代码,需要安装libpython3的dev依赖库:
通过以下命令查看是否已经存在依赖包:
dpkg -l | grep libpython3
可见并不存在后缀为“-dev” 的库,因此需要执行以下指令安装:
sudo apt install libpython3.10-dev
安装完成后再次查看就可以看到“libpython3.10-dev”了:
#include "Python.h" //Python API的头文件,用于访问Python对象和函数
int main()
{
Py_Initialize(); //初始化Python解释器,这样可以在C程序中执行Python代码
PyRun_SimpleString("print('funny')"); //执行一段简单的Python代码
Py_Finalize(); //关闭Python解释器,并释放资源
}
gcc python_test.c -I /usr/include/python3.10 -l python3.10
//“-I”指定python头文件路径;“-l”指定python库文件路径
步骤大体如下:
以上出现的这些粉色的函数可以通过这个网站来查询详细的说明:
(网站左上角输入函数名搜索即可)
导入模块 — Python 3.12.0 文档
关于第三步,一般是这样写:
PyObject *sys = PyImport_ImportModule("sys"); PyObject *path = PyObject_GetAttrString(sys, "path"); PyList_Append(path, PyUnicode_FromString("."));
关于第六步,Py_BuildValue(const char *format, ...)函数创建的是一个Python元组或者对象,作为Python函数的参数。这个函数的第一个参数是类型转换,C对应的Python的数据类型转换对应的格式如下:
首先,单独写一个python文件,其中定义一个带参数和返回值的函数:
def say_something(something):
print(something)
return something
然后在写一个C程序来调用它:
#include
int main()
{
Py_Initialize();
// 将当前路径添加到sys.path中
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
// 导入para模块
PyObject *pModule = PyImport_ImportModule("python_function_test");
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load module\n");
}
//获取say_funny函数对象
PyObject *pFunc = PyObject_GetAttrString(pModule, "say_something");
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load function\n");
}
//创建一个字符串作为参数
char *something = "mjm hahahaha";
PyObject *pArgs = Py_BuildValue("(s)", something); //(s)代表有一个字符串元素的元组
//调用函数并获取返回值
PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
}
//将返回值转换为C类型
char *result = NULL;
if (!PyArg_Parse(pValue, "s", &result))
{
PyErr_Print();
printf("Error: parse failed\n");
}
//打印返回值
printf("pValue=%s\n", result);
//释放所有引用的Python对象
Py_DECREF(pValue);
Py_DECREF(pFunc);
Py_DECREF(pModule);
//释放所有引用的Python对象
Py_Finalize();
return 0;
}
gcc python_function_test.c -I /usr/include/python3.10 -l python3.10
可见,C程序成功调用了python函数并传入了参数,同时也成功获得了python函数的返回值!
在学习了C语言调用python的方法,现在就可以开始了解阿里云提供的算法,从而使得我写的C代码可以将数据发送给阿里云处理并从阿里云获得结果:
开通后就会出现以下画面,点击“进入控制台” :
工作台地址:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
在控制台中,就可以获得Acess Token了,第一次点击需要创建AccessKey:
在获取完AccessKey的ID和Secret之后,就可以回到香橙派,安装阿里云的SDK了:
sudo apt install python3-pip
pip3 install alibabacloud_imagerecog20190930
安装完成后,根据自己实际的ACCESS_KEY_ID 和 ACCESS_KEY_SECRET,将以下内容写入到家目录下的.bashrc中:
export ALIBABA_CLOUD_ACCESS_KEY_ID="XXX" #根据自己实际的ID填写
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="XXXX" #根据自己实际的SECRET填写
vi ~/.bashrc
#然后在末尾输入上面两行后保存
bashrc :
然后输入“export”,可以看到我的ID和Secret已经存在:
示例代码地址:
垃圾分类识别常用语言和示例有哪些_视觉智能开放平台-阿里云帮助中心
找到“文件在本地或文件不在同一地域OSS”的示例代码,将它拷贝到香橙派的smart_bin下,将场景一打开,场景二注释
# -*- 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'/home/orangepi/smart_bin/pic/1.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)
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
其中,“/home/orangepi/smart_bin/pic/1.png” 就是我在香橙派本地存储的一张照片:
然后运行“python3 aliyun_classify.py”:
可见,识别正确,的确是塑料饮料瓶,并且成功分类到了可回收垃圾!
修改aliyun_classify.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 ali_classify():
#场景一:文件在本地
img = open(r'/home/orangepi/smart_bin/pic/1.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)
return response.body.to_map()['Data']['Elements'][0]['Category']
#response.body的类型是class,所以需要先转换成字典在进行提取,此处的to_map函数是阿里云SDK在实现这个class时封装的一个函数(models.py中),作用就是转化成dict
except Exception as error:
# 获取整体报错信息
print(error)
# 获取单个字段
print(error.code)
return '获取失败'
然后写一个C程序封装调用上面python代码可能需要的函数:
#include
#include
#include
#include
#include
#include "classify.h"
void classify_init(void)
{
Py_Initialize();
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyUnicode_FromString("."));
}
void classify_final(void)
{
Py_Finalize();
}
char *classify_category(char *category)
{
PyObject *pModule = PyImport_ImportModule("aliyun_classify");
if (!pModule)
{
PyErr_Print();
printf("Error: failed to load module\n");
goto FAILED_MODULE; //goto的意思就是如果运行到这里就直接跳转到FAILED_MODULE
}
PyObject *pFunc = PyObject_GetAttrString(pModule, "ali_classify");
if (!pFunc)
{
PyErr_Print();
printf("Error: failed to load function\n");
goto FAILED_FUNC;
}
PyObject *pValue = PyObject_CallObject(pFunc, NULL);
if (!pValue)
{
PyErr_Print();
printf("Error: function call failed\n");
goto FAILED_VALUE;
}
char *result = NULL;
if (!PyArg_Parse(pValue, "s", &result))
{
PyErr_Print();
printf("Error: parse failed");
goto FAILED_RESULT;
}
category = (char *)malloc(sizeof(char) * (strlen(result) + 1) ); //开辟一个新的字符串常量。+1是为了留出空间给\0
memset(category, 0, (strlen(result) + 1)); //初始化字符串
strncpy(category, result, (strlen(result) + 1)); //将result的结果复制给新的字符串
FAILED_RESULT:
Py_DECREF(pValue);
FAILED_VALUE:
Py_DECREF(pFunc);
FAILED_FUNC:
Py_DECREF(pModule);
FAILED_MODULE:
return category;
}
此处注意到,result的值是pValue赋予的,且他们都是字符串指针变量,而pValue在最后返回前就会被free掉,pValue的值一旦被free,result的值也会被free,所以必须在pValue被free之前再定义一个字符串,使用strcpy来保存需要返回的结果!
#ifndef __CLASSIFY__H
#define __CLASSIFY__H
void classify_init(void);
void classify_final(void);
char *classify_category(char *category);
#endif
#ifndef __XXX__H #define __XXX__H #endif
这个格式的使用是为了避免重复调用
最后利用刚刚写的h文件作为头文件,写一个调用阿里云接口的C程序:
#include
#include
#include "classify.h"
int main()
{
char *category = NULL;
classify_init();
category = classify_category(category);
printf("category=%s\n", category);
classify_final();
free(category);
return 0;
}
编译:
gcc -o ali_test ali_test.c classify.c classify.h -I /usr/include/python3.10/ -l python3.10
运行:
调用成功!!
详细可参考《OrangePi_Zero2_H616用户手册v4.0.pdf》 中的3.13.6 USB摄像头测试章节
lsmod | grep uvcvideo | grep -v grep
v4l2-ctl --list-devices
此处我提示没有识别这个命令,所以要先运行以下命令进行安装:
sudo apt update sudo apt install -y v4l-utils
后经过测试,可以使用的驱动应为“/dev/video1”
还是先安装fswebcam:
sudo apt-get install -y fswebcam
安装完成后,运行以下代码让接入的摄像头拍照:
sudo fswebcam -d /dev/video1 --no-banner -r 1280x720 -S 5 ./image.jpg
// -d 选项用于指定 USB 摄像头的设备节点
// --no-banner 用于去除照片的水印
// -r 选项用于指定照片的分辨率
// -S 选项用设置于跳过前面的帧数
// ./image.jpg 用于设置生成的照片的名字和路径
然后在当前目录下打开image.jpg查看:
可见,拍照成功!
还是先下载mjpg-streamer:
git clone https://github.com/jacksonliam/mjpg-streamer //github下载地址 git clone https://github.com/jacksonliam/mjpg-streamer //gitee下载地址
然后下载UBUNTU对应的依赖包:
sudo apt-get install -y cmake libjpeg8-dev
最后编译安装 mjpg-streamer:
cd mjpg-streamer/mjpg-streamer-experimental make -j4 sudo make install
安装完成后,运行以下命令启动mjpg_streamer:
export LD_LIBRARY_PATH=.
sudo ./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -u -f 30" -o "./output_http.so -w ./www"
然后在和开发板同一局域网的 Ubuntu PC 或者 Windows PC 或者手机的浏览orange Pi器中输入 【开发板的 IP地址:8080】就能看到摄像头输出的视频了,此处我使用了同一局域网的windowsPC, 地址是:192.168.2.23:8080,进入后点击“Stream”:
成功看到了摄像头拍到的实时影像!
先打开start.sh,可见这是一个自动开启摄像头的脚本:
将下面一句修改为:
./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -u -f 30" -o "./output_http.so -w ./www"
此时,运行start.sh就可以直接运行摄像头了:
在使用mjpg-streamer摄像头开启后,另开一个终端,可以使用以下命令在视频流中拍照并保存本地:
wget http://192.168.2.23:8080/?action=snapshot -O /home/orangepi/smart_bin/pic/garbage.jpg
此时,照片的路径为:/home/orangepi/smart_bin/pic/garbage.jpg
为了让流程进一步简化,不需要每一次需要时都运行start.sh然后再开一个终端,可以将start.sh写成开机自启且后台运行的形式:
在家目录创建脚本文件:
touch mjpg.sh
内容:
#!/bin/bash
cd /home/orangepi/mjpg-streamer/mjpg-streamer-experimental/
./start.sh
保存退出后,赋予权限:
chmod +x mjpg.sh
然后CD到开启自启的程序目录下:
cd /etc/xdg/autostart/
创建一个名为“mjpg.desktop” 的文件,内容如下:
[Desktop Entry]
Name=mjpg
Exec=/home/orangepi/mjpg.sh
Type=Application
NoDisplay=true
保存退出后,reboot重启香橙派,然后再进入192.168.2.23:8080查看:
可见,此时mjpg-streamer已经后台开机自启了!
start.sh:在当前路径下开启mjpg-streamer服务的脚本
mjpg.sh:在其他路径,运行start.sh脚本的脚本
mjpg.desktop:将mjpg设置为开机自启动的程序
Q:为什么不用之前学习守护进程的知识来解决这个问题,即将mjpg.sh的路径或start.sh的路径写在/etc/rc.local下,并加上“&”后台运行呢?
(使用香橙派学习 Linux的守护进程_mjmmm的博客-CSDN博客)
A:我的理解是,写在/etc/rc.local下的路径对应的只能是可执行文件,不能是脚本,通过观察start.sh里的内容可以发现,mjpg-streamer的运行需要在可执行文件后加上很多后缀,在之前还要export,所以也不能直接把可执行文件写在这个路径下。综上,使用了上文的这种方法实现了开机后台自启动。
在上文的学习中,我们学习了:
- 封装了使用C语言调用阿里云图像识别的代码:aliyun_classify.py; classify.c; classify.h
- 学会了使用“wget”指令来通过mjpg-streamer来对视频拍照并保存到本地
所以,思路就是首先通过system函数调用wget指令,将一张实时拍下的照片存到本地,然后通过调用classify.c中封装的函数来对这个本地的照片进行处理,发送到阿里云识别并得到最终结果!
同时,因为增加了图像识别模块,功能是先识别物品类型在开关盖,而之前实现的功能都是直接收到指令或距离接近直接就开盖,所以需要对于代码的整体结构做出调整
(注意!!只要涉及到串口输入或GPIO输入,就必须下载固件而不是SDK,烧录固件文件里的“jx_su_03t_release_update.bin”!!!!)
由于代码思路的调整,语音模块也需要相应的重新烧写,具体烧写步骤见我以前的博文,此处只展示关键的指令设计:
(有时候就想直接开关盖,如果此时还必须来一句“你好小智”进行唤醒就会很鸡肋)
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
详见:线程_mjmmm的博客-CSDN博客
详见:C语言:access函数的用法_access 函数_R-QWERT的博客-CSDN博客
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mjm_uart_tool.h"
#include
#include "classify.h"
#define PWM 5
#define BEEP 2
#define Trig 9
#define Echo 10
#define A25 6
#define A26 7
#define A27 8
#define B2 13
int angle;
double dist;
static int i;
int sockfd;
int conn_sockfd;
int len = sizeof(struct sockaddr_in);
int ret;
char readbuf[128];
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
static int conn_flag;
char hist[128] = {0}; //用于存放一条历史记录,注意这个变量一定不能在create_hist里赋值,因为局部指针变量作为函数的返回值没有意义,会报错; 且作为strcat的对象必须是字符串变量
char hist_whole[10000] = {0}; //用于存放所有历史记录; 且作为strcat的对象必须是字符串变量
sqlite3 *db;
char *zErrMsg = 0;
int dist_open_count = 0;
int dist_close_count = 0;
int sock_open_count = 0;
int sock_close_count = 0;
int soun_open_count = 0;
int soun_close_count = 0;
int request = 0;
int request_is = 0;
//unsigned char buffer[5] = {0xAA, 0x55, 0x00, 0x55, 0xAA};
int io_fd;
pthread_mutex_t mutex;
pthread_cond_t cond;
int callback(void *arg, int column_size, char *column_value[], char *column_name[]) //数据库exec函数对应的callback函数
{
int j;
//printf("arg=%s\n",(char *)arg);
for(j=0;j 40 && angle == 3){//当距离大于且盖子打开
pthread_mutex_lock(&mutex);
request = 2; //关盖请求
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
pthread_exit(NULL);
}
void *thread2(void *arg) //负责检测开盖时间,超过10s就报警的线程
{
struct timeval startTime;
struct timeval stopTime;
double diffTime;
while(1){
while(angle == 1);//程序会卡在这里直到盖子打开
gettimeofday(&startTime,NULL);
while(angle == 3){
gettimeofday(&stopTime,NULL);
diffTime = (stopTime.tv_sec - startTime.tv_sec) + 1/1000000 *(stopTime.tv_usec - startTime.tv_usec);
if(diffTime > 10){ //盖子打开超过10秒
digitalWrite (BEEP, LOW) ; //蜂鸣器响
delay (1000) ; //一秒长鸣警报
digitalWrite (BEEP, HIGH) ; //蜂鸣器不响
break;
}
}
}
pthread_exit(NULL);
}
void *thread3(void *arg) //负责不断等待socket客户端接入的线程
{
while(1){
//accept
conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);
if(conn_sockfd == -1){
perror("accept");
pthread_exit(NULL);;
}else{
conn_flag = 1; //保证连接成功后才开始接收
printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));
fflush(stdout);
}
}
pthread_exit(NULL);
}
void *thread4(void *arg) //负责socket客户端接入后接收处理客户端指令的线程
{
while(1){ //切不可直接“while(conn_flag == 1)”,这样在没有接入时会直接退出,要不停的循环查看flag的值
while(conn_flag == 1){
//read
memset(&readbuf,0,sizeof(readbuf));
ret = recv(conn_sockfd, &readbuf, sizeof(readbuf), 0);
if(ret == 0){ //如果recv函数返回0表示连接已经断开
printf("client has quit\n");
fflush(stdout);
close(conn_sockfd);
break;
}else if(ret == -1){
perror("recv");
conn_flag = 0; //此时打印一遍错误信息就会结束,如果不把flag置1,在一个客户端退出另一个客户端还未接入时就会不停的打印错误信息
//pthread_exit(NULL); //此处不能退出,因为因为这样如果有一个客户端接入并退出后这个线程就会退出,为了保证一个客户端退出后,另一个客户端还可以接入并正常工作,此处仅显示错误信息而不退出
}
//pthread_mutex_lock(&mutex);//对angle值修改需要先上锁
cmd_handler(conn_sockfd, readbuf); //对客户端发来的消息进行判断的总函数
//pthread_mutex_unlock(&mutex);
printf("\nclient: %s\n",readbuf);
fflush(stdout);
}
}
pthread_exit(NULL);
}
void *thread5(void *arg) //负责通过串口接收语音模块指令的线程
{
char io_readbuf[32] = {'\0'};
while(1){
while(serialDataAvail (*((int *)arg))){
serialGetstring (*((int *)arg),io_readbuf) ;
printf("-> %s\n",io_readbuf);
fflush(stdout);
if(strcmp(io_readbuf,"open") == 0 && angle == 1){ //当收到open指令且盖子关闭时
pthread_mutex_lock(&mutex);
request = 5; //开盖请求
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}else if(strcmp(io_readbuf,"close") == 0 && angle == 3){//当收到close指令且盖子打开时
pthread_mutex_lock(&mutex);
request = 6; //关盖请求
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}else if(strcmp(io_readbuf,"open") == 0 && angle == 3){
printf("already open\n");
fflush(stdout);
}else if(strcmp(io_readbuf,"close") == 0 && angle == 1){
printf("already close\n");
fflush(stdout);
}else{
printf("unkown command\n");
fflush(stdout);
}
memset(io_readbuf,'\0',32);
}
}
pthread_exit(NULL);
}
void *thread6(void *arg) //负责每隔一段时间向文件和数据库写入历史记录的线程
{
struct timeval startTime;
struct timeval stopTime;
double diffTime;
while(1){
gettimeofday(&startTime,NULL);
while(1){
gettimeofday(&stopTime,NULL);
diffTime = (stopTime.tv_sec - startTime.tv_sec) + 1/1000000 *(stopTime.tv_usec - startTime.tv_usec); //单位为秒
if(diffTime > 60){//如果时间是1分钟,由于线程的竞争机制,不一定会非常精确,所以使用>号
write2file();//将历史记录写入文件
write2sql();//将历史记录写入数据库
break;
}
}
}
pthread_exit(NULL);
}
void *thread7(void *arg) //阿里云交互,统一处理开关盖请求的线程
{
char *category = NULL;
//char buffer[5] = {0xAA, 0x55, 0x00, 0X55, 0xAA};
pthread_t p_openclose;
pthread_t p_sayres;
int request_action = 0;
while(1){
//printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
pthread_mutex_lock(&mutex);
// while(request!=1 && request!=2 && request!=3){
pthread_cond_wait(&cond, &mutex);
request_is = request; //保存request的值
request = 0;
// }
pthread_mutex_unlock(&mutex);
if(request_is == 1 || request_is == 3 || request_is == 5){//若是开盖指令
system("wget http://192.168.2.23:8080/?action=snapshot -O /home/orangepi/smart_bin/pic/garbage.jpg"); //拍照
delay(10);//给一点时间让照片拍出来
if(0 == access("/home/orangepi/smart_bin/pic/garbage.jpg", F_OK)){ //如果照片成功拍到了
//printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
category = classify_category(category); //阿里云识别
printf("category:%s\n",category);
fflush(stdout);
if(strcmp(category,"干垃圾") == 0){
digitalWrite (A25, HIGH) ;
delay(30);
}else if(strcmp(category,"湿垃圾") == 0){
digitalWrite (A26, HIGH) ;
delay(30);
}else if(strcmp(category,"获取失败") == 0){
digitalWrite (B2, HIGH) ;
delay(30);
}else if(strcmp(category,"可回收垃圾") == 0){ //开盖指令收到且为可回收垃圾
digitalWrite (A27, HIGH) ;
delay(30);
angle = 3; //开盖
digitalWrite (BEEP, LOW) ; //蜂鸣器响
delay (100) ;
digitalWrite (BEEP, HIGH) ; //蜂鸣器不响
delay (100) ;
digitalWrite (BEEP, LOW) ; //蜂鸣器响
delay (100) ;
digitalWrite (BEEP, HIGH) ; //蜂鸣器不响
delay(2000);//延时2秒
if(request_is == 1){ //距离导致的开盖
dist_open_count++;
create_hist(1,1);//构建一条历史记录
strcat(hist_whole,hist);//在总的历史记录中添加刚刚构建的历史记录
}else if(request_is == 3){ //客户端指令导致的开盖
sock_open_count++;
create_hist(2,1);//构建一条历史记录
strcat(hist_whole,hist);//在总的历史记录中添加刚刚构建的历史记录
}else if(request_is == 5){ //语音模块导致的开盖
soun_open_count++;
create_hist(3,1);//构建一条历史记录
strcat(hist_whole,hist);//在总的历史记录中添加刚刚构建的历史记录
}
}
}else{ //拍照不成功
digitalWrite (B2, HIGH) ; //算作获取失败
delay(30);
}
ret = remove("/home/orangepi/smart_bin/pic/garbage.jpg"); //删除照片
if(ret != 0){
printf("pic remove fail!\n");
fflush(stdout);
}
}else if(request_is == 2 || request_is == 4 || request_is == 6){ //若是关盖指令
angle = 1; //关盖
//delay(2000);//延时2秒
if(request_is == 2){ //距离导致的关盖
dist_close_count++;
create_hist(1,2);//构建一条历史记录
strcat(hist_whole,hist);//在总的历史记录中添加刚刚构建的历史记录
}else if(request_is == 4){ //客户端指令导致的关盖
sock_close_count++;
create_hist(2,2);//构建一条历史记录
strcat(hist_whole,hist);//在总的历史记录中添加刚刚构建的历史记录
}else if(request_is == 6){ //语音模块导致的关盖
soun_close_count++;
create_hist(3,2);//构建一条历史记录
strcat(hist_whole,hist);//在总的历史记录中添加刚刚构建的历史记录
}
}
request_is = 0;
digitalWrite (A25, LOW) ;
digitalWrite (A26, LOW) ;
digitalWrite (A27, LOW) ;
digitalWrite (B2, LOW) ;
}
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
struct itimerval itv;
//int io_fd;
char *create_table_sql;
pthread_t t1_id;
pthread_t t2_id;
pthread_t t3_id;
pthread_t t4_id;
pthread_t t5_id;
pthread_t t6_id;
pthread_t t7_id;
memset(&my_addr,0,sizeof(struct sockaddr_in));
memset(&client_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("param error! add ip address & port num\n");
return 1;
}
wiringPiSetup () ;
pinMode (PWM, OUTPUT);
pinMode (Trig, OUTPUT);
pinMode (Echo, INPUT);
pinMode (BEEP, OUTPUT);
pinMode (A25, OUTPUT);
pinMode (A26, OUTPUT);
pinMode (A27, OUTPUT);
pinMode (B2, OUTPUT);
digitalWrite (Trig, LOW) ;
digitalWrite (Echo, LOW) ;
digitalWrite (BEEP, HIGH) ;
digitalWrite (B2, LOW) ;
digitalWrite (A25, LOW) ;
digitalWrite (A26, LOW) ;
digitalWrite (A27, LOW) ;
//阿里云服务初始化
classify_init();
//设定定时时间
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 500;
//设定开始生效,启动定时器的时间
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;
//设定定时方式
if( -1 == setitimer(ITIMER_REAL, &itv, NULL)){
perror("error");
exit(-1);
}
//信号处理
signal(SIGALRM,signal_handler);
angle = 1;//初始化角度为关盖
//打开串口驱动文件,配置波特率
if ((io_fd = myserialOpen ("/dev/ttyS5", 115200)) < 0) {
fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;
return 1 ;
}
//printf("main:io_fd = %d\n",io_fd);
//打开数据库
ret = sqlite3_open("history.db", &db);
if(ret != SQLITE_OK){
printf("Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}else{
printf("Open database successfully\n");
}
//创建表格
//create_table_sql = "create table history(cause char,open Integer,close Integer);";
create_table_sql = "CREATE TABLE HISTORY(" \
"CAUSE CHAR(30) PRIMARY KEY NOT NULL," \
"OPEN INT NOT NULL," \
"CLOSE INT NOT NULL );" ;
ret = sqlite3_exec(db, create_table_sql, callback, 0, &zErrMsg);
if(ret != SQLITE_OK){
printf("Can't create table: %s\n", sqlite3_errmsg(db));
//exit(0);
}else{
printf("Table create successfully\n");
}
//初始化表格数据
init_table();
//socket
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
return 1;
}else{
printf("socket success, sockfd = %d\n",sockfd);
}
//bind
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)
inet_aton(argv[1],&my_addr.sin_addr); //char* format -> net format
ret = bind(sockfd, (struct sockaddr *)&my_addr, len);
if(ret == -1){
perror("bind");
return 1;
}else{
printf("bind success\n");
}
//listen
ret = listen(sockfd,10);
if(ret == -1){
perror("listen");
return 1;
}else{
printf("listening...\n");
}
ret = pthread_mutex_init(&mutex, NULL);
if(ret != 0){
printf("mutex create error\n");
}
ret = pthread_cond_init(&cond, NULL);
if(ret != 0){
printf("cond create error\n");
}
ret = pthread_create(&t1_id,NULL,thread1,NULL);
if(ret != 0){
printf("thread1 create error\n");
}
ret = pthread_create(&t2_id,NULL,thread2,NULL);
if(ret != 0){
printf("thread2 create error\n");
}
ret = pthread_create(&t3_id,NULL,thread3,NULL);
if(ret != 0){
printf("thread3 create error\n");
}
ret = pthread_create(&t4_id,NULL,thread4,NULL);
if(ret != 0){
printf("thread4 create error\n");
}
ret = pthread_create(&t5_id,NULL,thread5,(void *)&io_fd);
if(ret != 0){
printf("thread5 create error\n");
}
ret = pthread_create(&t6_id,NULL,thread6,NULL);
if(ret != 0){
printf("thread6 create error\n");
}
ret = pthread_create(&t7_id,NULL,thread7,NULL);
if(ret != 0){
printf("thread7 create error\n");
}
pthread_join(t1_id,NULL);
pthread_join(t2_id,NULL);
pthread_join(t3_id,NULL);
pthread_join(t4_id,NULL);
pthread_join(t5_id,NULL);
pthread_join(t6_id,NULL);
pthread_join(t7_id,NULL);
pthread_mutex_destroy(&mutex);//摧毁互斥量
pthread_cond_destroy(&cond);//摧毁条件变量
close(io_fd);//关闭串口驱动文件的套接字
classify_final();//关闭阿里云服务
return 0;
}
gcc -o v6_server classify.c classify.h mjm_uart_tool.c mjm_uart_tool.h v6_server.c -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt -lsqlite3 -I /usr/include/python3.10/ -l python3.10
server端:
sudo -E ./v6_server 192.168.2.23 8888
client端:
./v2_client 192.168.2.23 8888