【全志H616】【开源】 ARM-Linux 智能分拣项目:阿里云、网络编程、图像识
文章目录
1、实现功能
- 语音接入控制垃圾分类识别并触发垃圾桶的开关盖,并根据垃圾类型开关不同类型垃圾桶
- 利用
TCP
的Socket
编程,实现Sockect
发送指令远程控制垃圾分类识别,引入心跳包来检测客户端状态,防止客户端异常关闭导致进程堵塞 - 为什么要用 TCP的socket编程 通过网络来控制呢?
- 是为了可以通过远程PC来实现对硬件的控制
- 调用阿里云接口实现 物品的动态识别服务,将识别到的物品类型返回,并最终通过语音模块播报
- 语音播报垃圾物品类型
- OLED显示垃圾物品类型
- 多线程编程 使用多线程编程来解决多并发的问题
看下面的流程图会更清晰
2、软件及所需环境
Ubantu-22.04
、
MoberXterm
、
arach64-linux-gnu
、
VCS-ssh
、全志开发板、SU-03T语音模块、OLED显示屏、USB摄像头、SG90舵机
语音烧入软件&SU-03T语音模块的配置文件:
链接: https://pan.baidu.com/s/1SfNVrNmBR02YqE7lU2LiVQ?pwd=2222 提取码: 2222 复制这段内容后打开百度网盘手机App,操作更方便哦
网络调试助手
链接: https://pan.baidu.com/s/1HkVjyYVYZ9NdhQNwx-qTGQ?pwd=2222 提取码: 2222 复制这段内容后打开百度网盘手机App,操作更方便哦
3、逻辑流程图及简述
3.1 完整逻辑流程图
3.2 硬件接线
- 命令
gpio readall
显示针脚的状态和配置信息。
3.3 功能简述
3.3.1 main()
我们先通过main函数来了解一下整个程序的概况
- 定义线程在被创造
pthread_create
后 ,需要的tid
参数;> 是一个指向 >> pthread_t>
> 类型变量的指针,用于存储新创建线程的标识符。 - 先进行初始化,
wiringPiSetup() & garbage_init();
- 对于witingPi
库和阿里云接口初始化 - 判断
mjpg_streamer
这个进程是否存在;detect_process("mjpg_streamer");
这个函数是我们自己写的,在后文会提供;-mjpg_streamer
是一款开源的流媒体服务器软件,它能够从一系列输入插件(如USB摄像头
)捕获视频数据,并通过网络以MJPG(Motion JPEG)
格式实时传输 - 打开串行接口
serial_fd = myserialOpen(SERIAL_DEV,BAUD);//可代替设备节点 & 波特率 返回值fd
;判断是否打开成功 - 打开语音、网络、阿里云交互线程
pthread_create()
- 等待指定的线程结束其执行
pthread_join()
- 销毁互斥锁
(mutex)
和条件变量(cond
)pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond);
- 关闭串口
close(serial_fd);
//main()intmain(int argc,char*argv[]){printf("adsfasdf\n");int len =0;int ret =-1;//标识char*category =NULL;unsignedchar buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//AA 55 ** 00 55 AApthread_t get_voice_tid, category_tid,get_socket_tid;//初始化wiringPiSetup();//初始化wiringPi库garbage_init();//初始化阿里云接口// 用于检测特定进程是否在运行。// 它通过调用系统命令ps -ax | grep process_name|grep -v grep来查找包含指定进程名的进程,并返回相应的进程号 // mjpg_streamer 是一款开源的流媒体服务器软件,它能够从一系列输入插件(如USB摄像头)捕获视频数据,并通过网络以MJPG(Motion JPEG)格式实时传输
ret =detect_process("mjpg_streamer");if(-1== ret){printf("detect process failed\n");goto END;}//打开设备
serial_fd =myserialOpen(SERIAL_DEV,BAUD);//可代替设备节点 & 波特率 返回值fdif(serial_fd ==-1)//是否打开成功{goto END;}//*pget_voice(void *arg)里面判断//printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);//开语音的线程pthread_create(&get_voice_tid,NULL,pget_voice,NULL);//开网络线程pthread_create(&get_socket_tid,NULL,pget_socket,NULL);//开阿里云交互线程pthread_create(&category_tid,NULL,pcategory,NULL);//get_voice_tid获取语音模块的数据//pthread_join用于等待指定的线程结束其执行pthread_join(get_voice_tid,NULL);pthread_join(category_tid,NULL);pthread_join(get_socket_tid,NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);close(serial_fd);
END:
garbage_final;//释放所有引用的Python对象return0;}
3.3.2 main函数中打开的语音、网络、阿里云交互线程
3.3.2.1 语音线程
在main()函数中,用
pthread_create(&get_voice_tid,NULL,pget_voice,NULL);
来开启语音线程
- 判断该串口是否已经打开,用输入文件|函数|行号的形式来当调试信息
- 开一个 while(1) 循环- 利用串口接收函数
serialGetstring(serial_fd,buffer); //P8 & P10 ==> TXD.5 & RXD.5
//获取(读)传过来的数据> 这个函数来自 >> uartTool.c & uartTool.h>
> 文件- 读取的数据buffer[2] 是否是 我先要的;> 0x46命令表示:语音播报了“识别垃圾类型” 可以在语音模块的配置文件里面修改;- 得到的信号是我想要的话,便执行 上锁pthread_mutex_lock(&mutex)
–buffer[2]复位–发信号(利用条件变量)pthread_cond_signal(&cond)
–关锁pthread_mutex_unlock(&mutex)
> 互斥锁>> (mutex)>
> 和条件变量>> (cond)>
> 是多线程编程中常用的同步机制,用于控制对共享资源的访问和线程间的通信。 - 进程退出
pthread_exit(0);
main.c
里面的函数
//获得 语音线程 pget_voice()void*pget_voice(void*arg){unsignedchar buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//AA 55 ** 00 55 AAint len =0;if(serial_fd ==-1)//是否打开成功{printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号pthread_exit(0);}printf("%s|%s|%d\n",__FILE__,__func__,__LINE__);while(1){//获取(读)传过来的数据
len =serialGetstring(serial_fd,buffer);//P8 & P10 ==> TXD.5 & RXD.5printf("%s|%s|%d, len=%d\n",__FILE__,__func__,__LINE__,len);if(len>0&& buffer[2]==0x46)//0x46命令:识别垃圾类型{printf("%s|%s|%d\n",__FILE__,__func__,__LINE__);//利用 互斥锁(mutex)和条件变量(condition variable)//互斥锁(mutex)和条件变量(cond)是多线程编程中常用的同步机制,用于控制对共享资源的访问和线程间的通信。//上锁pthread_mutex_lock(&mutex);
buffer[2]=0x00;//给阿里云发信号pthread_cond_signal(&cond);//发信号pthread_mutex_unlock(&mutex);}}pthread_exit(0);}
uartTool.h
//uartTool.h#ifndef__UARTTOOL_H#define__UARTTOOL_HintmyserialOpen(constchar*device,constint baud);voidserialSendstring(constint fd,unsignedconstchar*s,int len);//写数据intserialGetstring(constint fd,unsignedchar*buffer);//读数据#defineSERIAL_DEV"/dev/ttyS5"// 可代替设备节点#defineBAUD115200// 代替波特率#endif//防止头文件中的声明反复区声明
uartTool.c
//uartTool.c 中的一个函数intserialGetstring(constint fd,unsignedchar*buffer){int n_read;//read() 用于从文件描述符(file descriptor)读取数据的 POSIX 标准函数之一
n_read =read(fd,buffer,6);//printf("fd = %d\n",fd); printf("BUFFER = %x,%x,%x\n",buffer[0],buffer[1],buffer[2]);return n_read;}
3.3.2.2 网络线程
main函数中 利用
pthread_create(&get_socket_tid,NULL,pget_socket,NULL);
来开启socket网络线程
- 初始化socket,这个就不详细讲解了,在前面的文章,socket网络编程有详细讲解> Linux系统编程8–网络编程-CSDN博客
- 在网络编程文件的基础上,这边还添加了 心跳包功能,- Socket客户端得断开有两种情况:- 1.客户端能够发送状态给服务器;正常断开,强制关闭客户端等,客户端能够做出反应。- 2.客户端不能发送状态给服务器;突然断网,断电,客户端卡死等,客户端根本没机会做出反应, 服务器更不了解客户端状态,导致服务器异常等待。- 因此,为了解决上述问题,引入TCP心跳包机制: 心跳包的实现,心跳包就是服务器定时向客户端发送查询信息,如果客户端有回应就代表连接正常, 类似于linux系统的看门狗机制。心跳包的机制有一种方法就是采用TCP_KEEPALIVE机制,它是一种用于 检测TCP连接是否存活的机制,它的原理是在一定时间内没有数据往来时,发送探测包给对方,如果对方 没有响应,就认为连接已经断开。TCP_KEEPALIVE机制可以通过设置一些参数来调整,如探测时间间 隔、探测次数等。- 查看当前系统的
TCP KeepAlive
参数sysctl net.ipv4.tcp_keepalive_timesysctl net.ipv4.tcp_keepalive_probessysctl net.ipv4.tcp_keepalive_intvl
默认等待时间为7200s,查询9次,每次间隔75s
我们把它修改了,见
main.c
文件里的
void *pget_socket(void *fd)
函数
- while(1)循环- 引入心跳包- 连接成功后,输出调试信息- while(1)- 读数据,如果读取成功,判断读取的数值是否是 “open” - 读取到"open" 便执行 上锁、给阿里云发信号、关锁的操作(和前文语音模块一样)- 没有读取到,或者读取错误,就break;
- 关闭
s_fd
(ivp4 TCP) - 进程退出
pthread_exit(0);
main.c
中的pget_socket
函数
void*pget_socket(void*fd){int s_fd =-1;int c_fd =-1;char buffer[6];int nread =-1;structsockaddr_in c_addr;//用于acceptmemset(&c_addr,0,sizeof(structsockaddr_in));//清零
s_fd =socket_init(IPADDR,IPPORT);printf("%s|%s|%d: s_fd=%d\n",__FILE__,__func__,__LINE__,s_fd);if(-1== s_fd){pthread_exit(0);}sleep(3);int clen =sizeof(structsockaddr_in);while(1){//accep第三个参数要求是指针//不连接,便堵塞,直到与客户端连接
c_fd =accept(s_fd,(structsockaddr*)&c_addr,&clen);// 心包跳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));////连接成功后,输出调试信息printf("--------ok?--------\n");printf("%s|%s|%d: Accept a connection from %s:%d\n",__FILE__,__func__,__LINE__,inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));//最后两个window主机的ip和端口号,可以在网络调试助手设置查看,printf("-----------ok!-----\n");if(c_fd ==-1){perror("accept");//终端打出错误:accept:---continue;}while(1){memset(buffer,0,sizeof(buffer));
nread =recv(c_fd,buffer,sizeof(buffer),0);// == n_read = read(c_fd,readBuf,sizeof(buffer));printf("%s|%s|%d:nread=%d,buffer=%s\n",__FILE__,__func__,__LINE__,nread,buffer);if(nread >0){if(strstr(buffer,"open")){//上锁pthread_mutex_lock(&mutex);//给阿里云发信号pthread_cond_signal(&cond);//发信号pthread_mutex_unlock(&mutex);}}elseif(0==nread ||-1==nread){break;}}close(c_fd);}pthread_exit(0);}
socket.h
socket初始化
//socket.h#ifndef__SOCKET_H#define__SOCKET_H#include<stdio.h>#include<stdlib.h>#include<string.h>//memset包含头文件#include<sys/socket.h>#include<sys/types.h>//#include <linux/in.h>#include<arpa/inet.h>#include<netinet/in.h>//该头文件会与 #include <linux/in.h> 冲突#include<arpa/inet.h>#include<unistd.h>#include<netinet/in.h>#include<netinet/tcp.h>#include<arpa/inet.h>#include<errno.h>intsocket_init(constchar*ipaddr,constchar*ipport);#endif
socket.c
socket的初始化
#include"socket.h"intsocket_init(constchar*ipaddr,constchar*ipport){int s_fd;int ret =-1;//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息structsockaddr_in s_addr;//用于bindmemset(&s_addr,0,sizeof(structsockaddr_in));//进行清空 //1、socket
s_fd =socket(AF_INET,SOCK_STREAM,0);//ivp4,TCPif(-1== s_fd){perror("socket");//将上一个函数错误的原因输出return-1;}
s_addr.sin_family = AF_INET;//ivp4
s_addr.sin_port =htons(atoi(ipport));//htons() 返回网络字节的端口号 //ipport是第二个参数,端口号inet_aton(ipaddr,&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针//2、bind
ret =bind(s_fd,(structsockaddr*)&s_addr,sizeof(structsockaddr_in));if(-1== ret){perror("bind");//将上一个函数错误的原因输出return-1;}//3、listen
ret =listen(s_fd,1);//同时允许一个监听if(-1== ret){perror("listen");//将上一个函数错误的原因输出return-1;}return s_fd;}
3.3.2.3 阿里云交互线程
main()函数中,利用
pthread_create(&category_tid,NULL,pcategory,NULL);
来开阿里云交互线程
- 定义线程在被创造
pthread_create
后 ,需要的tid
参数;> 是一个指向 >> pthread_t>
> 类型变量的指针,用于存储新创建线程的标识符。 - while(1)循环- 线程上锁、等待信号 //
cond
会合、关锁- buffer[2]复位- 拍照指令system(WGET_CMD)
宏定义WGET_CMD
:拍照命令,garbage.h
文件中- 识别垃圾图片是否存在,也就是是否拍摄成功access(GARBAGE_FILE, F_OK)
GARBAGE_FILE 文件位置宏定义- 调用阿里云接口判断垃圾类型category = garbage_category(category);
函数见garbage.c & garbage.py
- 判断 对应的垃圾类型,并对buffer[2] 赋不同的值- 再开三个线程 - 开垃圾桶开盖线程:pthread_create(&trash_tid,NULL,psend_voice,(void*)buffer);
- 开语音播报线程:pthread_create(&send_void_tid,NULL,popen_trash_can,(void*)buffer);
- 开oled
显示线程 :pthread_create(&oled_tid,NULL,poled_show,(void*)buffer);
- 把识别到的垃圾的图片清除掉remove(GARBAGE_FILE);
GARBAGE_FILE 文件文章宏定义,见garbage.h
- 退出进程
pthread_exit(0);
void*pcategory(void*arg){unsignedchar buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//AA 55 ** 00 55 AAchar*category =NULL;pthread_t send_void_tid,trash_tid,oled_tid;printf("%s|%s|%d:\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号while(1){printf("%s|%s|%d:\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号pthread_mutex_lock(&mutex);// 锁住//给阿里云发信号pthread_cond_wait(&cond,&mutex);//等待信号pthread_mutex_unlock(&mutex);printf("%s|%s|%d:\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号
buffer[2]=0x00;//define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O /tmp/garbage.jpg" //抓拍命令system(WGET_CMD);//宏定义:拍照命令,garbage.h 文件中// access函数是一个用于测试文件访问权限的函数,它通常用于检查某个文件是否可以被当前用户读取、写入或执行。// 包括F_OK(检查文件是否存在)、R_OK(检查读权限)、W_OK(检查写权限)和X_OK(检查执行权限)// 该函数返回值为0表示有权限,-1表示无权限或发生错误。if(0==access(GARBAGE_FILE, F_OK))//GARBAGE_FILE 宏定义 识别到的垃圾图片是否存在{
category =garbage_category(category);//调用阿里云接口判断垃圾类型if(strstr(category,"干垃圾")){
buffer[2]=0x41;//赋值}elseif(strstr(category,"湿垃圾")){
buffer[2]=0x42;}elseif(strstr(category,"可回收垃圾")){
buffer[2]=0x43;}elseif(strstr(category,"有害垃圾")){
buffer[2]=0x44;}else{
buffer[2]=0x45;}}else{
buffer[2]=0x45;}//开三个线程//垃圾桶开盖线程pthread_create(&trash_tid,NULL,psend_voice,(void*)buffer);//开语音播报线程pthread_create(&send_void_tid,NULL,popen_trash_can,(void*)buffer);//开oled显示线程pthread_create(&oled_tid,NULL,poled_show,(void*)buffer);// buffer[2]=0x00;//将buffer[2]清零remove(GARBAGE_FILE);//garbage.h 的宏定义 把识别到的垃圾的图片清除掉}pthread_exit(0);}
3.3.2.4 阿里云交互线程
pcategory
开的3个线程
void*psend_voice(void*arg)//写数据,回传给语音模块{pthread_detach(pthread_self());unsignedchar*buffer =(unsignedchar*)arg;if(serial_fd ==-1)//是否打开成功{printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号pthread_exit(0);}if(NULL!= buffer){serialSendstring(serial_fd,buffer,6);//写数据,回传给语音模块}pthread_exit(0);}void*popen_trash_can(void*arg)//满足条件就开舵机{pthread_detach(pthread_self());unsignedchar*buffer =(unsignedchar*)arg;//这意味着线程结束时会自动释放其资源,//而不需要其他线程调用 pthread_join() 来等待其结束并回收资源if(buffer[2]==0x41){pwm_write(PWM_RECOVERABLE_GARBAGE);//对应wPi 5口,接着一个舵机printf("---------%x---------\n",buffer[2]);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}elseif(buffer[2]!=0x45){printf("%s|%s|%d: buffer[2]=0x%x\n",__FILE__,__func__,__LINE__,buffer[2]);printf("start\n");pwm_write(PWM_GARBAGE);//对应wPi 7口,接着另一个舵机delay(2000);pwm_stop(PWM_GARBAGE);}pthread_exit(0);}void*poled_show(void*arg)//oled显示当前识别的垃圾类型{pthread_detach(pthread_self());//这意味着线程结束时会自动释放其资源,myoled_init();oled_show(arg);//bufferpthread_exit(0);}
4、实验程序
4.1
uartTool
串口
- 串口的打开;发送、接收串口信息
//uartTool.h#ifndef__UARTTOOL_H#define__UARTTOOL_HintmyserialOpen(constchar*device,constint baud);voidserialSendstring(constint fd,unsignedconstchar*s,int len);//写数据intserialGetstring(constint fd,unsignedchar*buffer);//读数据#defineSERIAL_DEV"/dev/ttyS5"// 可代替设备节点#defineBAUD115200// 代替波特率#endif//防止头文件中的声明反复区声明
//uartTool.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>#include"uartTool.h"//先到当前文件夹查看#include"wiringSerial.h"intmyserialOpen(constchar*device,constint baud){structtermios options ;//这个结构体speed_t myBaud ;int status, fd ;switch(baud){case9600: myBaud = B9600 ;break;case115200: myBaud = B115200 ;break;default:}if((fd =open(device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK))==-1)return-1;fcntl(fd, F_SETFL, O_RDWR);// Get and modify current options:tcgetattr(fd,&options);//get//标准化的操作 一般都是固定的cfmakeraw(&options);cfsetispeed(&options, myBaud);cfsetospeed(&options, myBaud);//波特率的设置
options.c_cflag |=(CLOCAL | CREAD);
options.c_cflag &=~PARENB ;//奇偶校验位
options.c_cflag &=~CSTOPB ;//ͣ停止位 一般都是一位的
options.c_cflag &=~CSIZE ;
options.c_cflag |= CS8 ;//数据位 基本都是八位的
options.c_lflag &=~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag &=~OPOST ;
options.c_cc [VMIN]=0;
options.c_cc [VTIME]=100;// Ten seconds (100 deciseconds)//标准化的操作tcsetattr(fd, TCSANOW,&options);//写入内核ioctl(fd, TIOCMGET,&status);//获得整型控制字TIOCMGET get出来
status |= TIOCM_DTR ;
status |= TIOCM_RTS ;//控制字加上标志位ioctl(fd, TIOCMSET,&status);//写入内核usleep(10000);// 10mSreturn fd ;}//0XAA 0X55 0X46 0X00 0X55 0XAAvoidserialSendstring(constint fd,constunsignedchar*s,int len)//去写数据{int ret;
ret =write(fd, s, len);if(ret <0)printf("Serial Putchar Error\n");}intserialGetstring(constint fd,unsignedchar*buffer){int n_read;//read() 用于从文件描述符(file descriptor)读取数据的 POSIX 标准函数之一
n_read =read(fd,buffer,6);//printf("fd = %d\n",fd); printf("BUFFER = %x,%x,%x\n",buffer[0],buffer[1],buffer[2]);return n_read;}
4.2
socket
网络
- socket网络线程 + 心跳包
//socket.h#ifndef__SOCKET_H#define__SOCKET_H#include<stdio.h>#include<stdlib.h>#include<string.h>//memset包含头文件#include<sys/socket.h>#include<sys/types.h>//#include <linux/in.h>#include<arpa/inet.h>#include<netinet/in.h>//该头文件会与 #include <linux/in.h> 冲突#include<arpa/inet.h>#include<unistd.h>#include<netinet/in.h>#include<netinet/tcp.h>#include<arpa/inet.h>#include<errno.h>intsocket_init(constchar*ipaddr,constchar*ipport);#endif
//socket.c#include"socket.h"intsocket_init(constchar*ipaddr,constchar*ipport){int s_fd;int ret =-1;//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息structsockaddr_in s_addr;//用于bindmemset(&s_addr,0,sizeof(structsockaddr_in));//进行清空 //1、socket
s_fd =socket(AF_INET,SOCK_STREAM,0);//ivp4,TCPif(-1== s_fd){perror("socket");//将上一个函数错误的原因输出return-1;}
s_addr.sin_family = AF_INET;//ivp4
s_addr.sin_port =htons(atoi(ipport));//htons() 返回网络字节的端口号 //ipport是第二个参数,端口号inet_aton(ipaddr,&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针//2、bind
ret =bind(s_fd,(structsockaddr*)&s_addr,sizeof(structsockaddr_in));if(-1== ret){perror("bind");//将上一个函数错误的原因输出return-1;}//3、listen
ret =listen(s_fd,1);//同时允许一个监听if(-1== ret){perror("listen");//将上一个函数错误的原因输出return-1;}return s_fd;}
4.3 阿里云
garbage
识别
# garbage.py# -*- coding: utf-8 -*-# 引入依赖包# pip install alibabacloud_imagerecog20190930import 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')defalibaba_garbage():# 封装成一个函数,方便其他文件调用#场景一:文件在本地# img = open(r'/home/orangepi/garbage/lj/test2.jpg','rb')
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()) # 打印出body的类型print(response.body.to_map()['Data']['Elements'][0]['Category'])# 打印出body的类型return response.body.to_map()['Data']['Elements'][0]['Category']# 提供调用方便except Exception as error:# 获取整体报错信息# print(error)# 获取单个字段# print(error.code)print("shibai")return'获取失败'if __name__=="__main__":
alibaba_garbage()
// garbage.h#ifndef__GARBAGE__H#define__GARBAGE__Hvoidgarbage_init(void);//第一步做初始化,初始python的编译器包括导入当前路劲到sys.path里面voidgarbage_final(void);char*garbage_category(char*category);//127.0.0.1//通常被称为"本地回环地址"或"localhost";//这个地址经常用于测试网络应用程序和诊断网络问题//因为它确保数据能够在同一台计算机上进行循环传输,而无需真正通过网络传输。#defineWGET_CMD"wget http://127.0.0.1:8080/?action=snapshot -O /tmp/garbage.jpg"//抓拍命令#defineGARBAGE_FILE"/tmp/garbage.jpg"#endif
//garbage.c// C语言引用Python文件#if01、包含Python.h头文件,以便使用Python API。
2、使用voidPy_Initialize()初始化Python解释器,
3、使用PyObject *PyImport_ImportModule(constchar*name)和PyObject
*PyObject_GetAttrString(PyObject *o,constchar*attr_name)获取sys.path对象,并利用
intPyList_Append(PyObject *list, PyObject *item)将当前路径.添加到sys.path中,以便加载
当前的Python模块(Python文件即python模块)。
4、使用PyObject *PyImport_ImportModule(constchar*name)函数导入Python模块,并检查是否
有错误。
5、使用PyObject *PyObject_GetAttrString(PyObject *o,constchar*attr_name)函数获取
Python函数对象,并检查是否可调用。
+6、使用PyObject *Py_BuildValue(constchar*format,...)函数将C类型的数据结构转换成
Python对象,作为Python函数的参数,没有参数不需要调用
7、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)函数调用
Python函数,并获取返回值。
+8、使用intPyArg_Parse(PyObject *args,constchar*format,...)函数将返回值转换为C类
型,并检查是否有错误,没有返回值时不需要调用。
9、使用voidPy_DECREF(PyObject *o)函数释放所有引用的Python对象。
10、结束时调用voidPy_Finalize()函数关闭Python解释器。
相关的函数参数说明参考网站(网站左上角输入函数名即可开始搜索):
https://docs.python.org/zh-cn/3/c-api/import.html#endif#include<Python.h>#include"garbage.h"voidgarbage_init(void){Py_Initialize();// 将当前路径添加到sys.path中
PyObject *sys =PyImport_ImportModule("sys");//初始化python解释器
PyObject *path =PyObject_GetAttrString(sys,"path");PyList_Append(path,PyUnicode_FromString("."));}voidgarbage_final(void){//释放所有引用的Python对象Py_Finalize();}char*garbage_category(char*category){// 导入para模块
PyObject *pModule =PyImport_ImportModule("garbage");//引用的是本目录底下的garbage.py文件if(!pModule){PyErr_Print();printf("Error: failed to load para.py\n");goto FAILED_MODULE;}//获取 alibaba_garbage 函数对象
PyObject *pFunc =PyObject_GetAttrString(pModule,"alibaba_garbage");//garbage.py文件里的这个函数 alibaba_garbageif(!pFunc){PyErr_Print();printf("Error: failed to load say_funny\n");goto FAILED_FUNC;}//调用alibaba_garbage函数并获取返回值
PyObject *pValue =PyObject_CallObject(pFunc,NULL);if(!pValue){PyErr_Print();printf("Error: function call failed\n");goto FAILED_VALUE;}//将返回值转换为C类型char*result =NULL;//pValue(py) --> result(c)if(!PyArg_Parse(pValue,"s",&result))//PyArg_Parse根据指定的格式字符串将 Python 对象转换为 C 变量{PyErr_Print();printf("Error: parse failed\n");goto FAILED_RESULT;}//打印返回值printf("pValue=%s\n", result);
category =(char*)malloc(sizeof(char)*(strlen(result)+1));//要留一个字节给 \0memset(category,0,(strlen(result)+1));strncpy(category, result,(strlen(result)+1));//复制字符串//会用来标识某个测试案例、任务或计划未能通过或达到预期的标准或目标;失败后跳转
FAILED_RESULT:Py_DECREF(pValue);
FAILED_VALUE:Py_DECREF(pFunc);
FAILED_FUNC:Py_DECREF(pModule);
FAILED_MODULE:return category;}
4.4
PWM
sg90舵机
//pwm.h#ifndef__PWM__H#define__PWM__H#definePWM_GARBAGE7#definePWM_RECOVERABLE_GARBAGE5voidpwm_write(int pwm_pin);// 控制PWM ==> 开盖voidpwm_stop(int pwm_pin);// 控制PWM ==> 关盖#endif
//pwm.c#include<wiringPi.h>#include<softPwm.h>// 根据公式PWMfreq = (1 × 10^6 )/(100 × range) 1000000/20000=50 Hz ==> 周期为 1/50 = 0.02s = 20ms// 要得到PWM频率为50Hz,则range为200,即周期为200步,控制精度相比硬件PWM较低voidpwm_write(int pwm_pin){pinMode(pwm_pin, OUTPUT);//把引脚设置为输出// 软件模拟PWM函数softPwmCreate(pwm_pin,0,200);// range设置周期分为200步,周期为20mssoftPwmWrite(pwm_pin,15);// 200步里面有多少步是高电平 10/200 = 0.05// 15/200 = 0.075// 1.0ms--------------45度; 5.0% 占空比// 1.5ms------------90度; 7.5% 占空比delay(1000);//延时1ssoftPwmStop(pwm_pin);//停止,不输出PWM波了}voidpwm_stop(int pwm_pin){pinMode(pwm_pin, OUTPUT);softPwmCreate(pwm_pin,0,200);softPwmWrite(pwm_pin,5);// 200步里面有多少步是高电平 5/200 = 0.025// 0.5ms-------------0度; 2.5% 占空比delay(1000);softPwmStop(pwm_pin);}
4.5 OLED` 显示
//myoled.h#ifndef__MYOLED__H#define__MYOLED__Hintmyoled_init(void);intoled_show(void*arg);#endif
//myoled.c#include<errno.h>#include<string.h>#include<stdio.h>#include<stdlib.h>#include<time.h>#include<stdint.h>#include"oled.h"#include"font.h"//包含头文件#include"myoled.h"#defineFILENAME"/dev/i2c-3"staticstructdisplay_info disp;intoled_show(void*arg){unsignedchar*buffer =(unsignedchar*)arg;oled_putstrto(&disp,0,9+1,"This garbage is:");
disp.font = font2;//设置字体switch(buffer[2]){case0x41:oled_putstrto(&disp,0,20,"dry waste");break;case0x42:oled_putstrto(&disp,0,20,"wet waste");break;case0x43:oled_putstrto(&disp,0,20,"recyclable waste");break;case0x44:oled_putstrto(&disp,0,20,"hazardous waste");break;case0x45:oled_putstrto(&disp,0,20,"recognition failed");break;}
disp.font = font2;oled_send_buffer(&disp);return0;}intmyoled_init(void){int e;
disp.address = OLED_I2C_ADDR;
disp.font = font2;
e =oled_open(&disp, FILENAME);
e =oled_init(&disp);return e;}
4.6
main.c
- 定义
mutex
(互斥锁) &cond
(条件变量) //main.c 中包含的函数detect_process(constchar*);//用于检测特定进程是否在运行。pget_voice(void*);//获取(读)串口中的数据 0x46 //P8 & P10 ==> TXD.5 & RXD.5psend_voice(void*);//发送数据(写),回传给串口(语音模块) TX RXpopen_trash_can(void*);//通过 buffer[2]的数据,控制舵机,从而开盖poled_show(void*);//调用 myoled 模块,通过buffer[2]的数据在oled上显示不同数据pcategory(void*);//调用阿里云进行识别、通过返回值来对buffer[2] 进行赋值;并且创造3个线程//开垃圾桶开盖popen_trash_can、语音播报psend_voice、`oled`显示线程poled_show pget_socket(void*);//开启socket网络线程 和 心跳包main(int,char*[]);
main.c
//main.c#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<errno.h>#include<wiringPi.h>#include<pthread.h>#include"uartTool.h"#include"garbage.h"#include"pwm.h"#include"myoled.h"#include"socket.h"#defineIPADDR"192.168.1.76"#defineIPPORT"8000"//一定不能是8080,摄像头服务已经使用int serial_fd =-1;//全局变量pthread_cond_t cond;pthread_mutex_t mutex;// 用于检测特定进程是否在运行。// 它通过调用系统命令ps -ax | grep process_name|grep -v grep来查找包含指定进程名的进程,并返回相应的进程号。staticintdetect_process(constchar*process_name){int n =-1;// 初始化进程号为-1
FILE *strm;// 文件流指针char buf[128]={0};// 缓冲区// 构建命令字符串,查找特定进程名的进程sprintf(buf,"ps -ax | grep %s|grep -v grep", process_name);// 执行系统命令,并返回文件指针用于读取命令输出if((strm =popen(buf,"r"))!=NULL){// 读取命令输出到缓冲区中if(fgets(buf,sizeof(buf), strm)!=NULL){printf("buf = %s\n", buf);// 输出读取到的内容
n =atoi(buf);// 将读取到的内容转换为整数,这里假设内容是进程号printf("n=%d\n", n);// 输出转换后的进程号}}else{return-1;// 如果执行popen失败,则返回-1}pclose(strm);// 关闭文件流return n;// 返回进程号,可能为-1(未找到进程或出现错误)}void*pget_voice(void*arg){unsignedchar buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//AA 55 ** 00 55 AAint len =0;if(serial_fd ==-1)//是否打开成功{printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号pthread_exit(0);}printf("%s|%s|%d\n",__FILE__,__func__,__LINE__);while(1){//获取(读)传过来的数据
len =serialGetstring(serial_fd,buffer);//P8 & P10 ==> TXD.5 & RXD.5printf("%s|%s|%d, len=%d\n",__FILE__,__func__,__LINE__,len);if(len>0&& buffer[2]==0x46)//0x46命令:识别垃圾类型{printf("%s|%s|%d\n",__FILE__,__func__,__LINE__);//上锁pthread_mutex_lock(&mutex);
buffer[2]=0x00;//给阿里云发信号pthread_cond_signal(&cond);//发信号pthread_mutex_unlock(&mutex);}}pthread_exit(0);}void*psend_voice(void*arg){pthread_detach(pthread_self());unsignedchar*buffer =(unsignedchar*)arg;if(serial_fd ==-1)//是否打开成功{printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号pthread_exit(0);}if(NULL!= buffer){serialSendstring(serial_fd,buffer,6);//写数据,回传给语音模块}pthread_exit(0);}void*popen_trash_can(void*arg){pthread_detach(pthread_self());unsignedchar*buffer =(unsignedchar*)arg;//这意味着线程结束时会自动释放其资源,//而不需要其他线程调用 pthread_join() 来等待其结束并回收资源if(buffer[2]==0x41){pwm_write(PWM_RECOVERABLE_GARBAGE);//对应wPi 5口,接着一个舵机printf("---------%x---------\n",buffer[2]);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}elseif(buffer[2]!=0x45){printf("%s|%s|%d: buffer[2]=0x%x\n",__FILE__,__func__,__LINE__,buffer[2]);printf("start\n");pwm_write(PWM_GARBAGE);//对应wPi 7口,接着另一个舵机delay(2000);pwm_stop(PWM_GARBAGE);}pthread_exit(0);}void*poled_show(void*arg){pthread_detach(pthread_self());//这意味着线程结束时会自动释放其资源,myoled_init();oled_show(arg);//bufferpthread_exit(0);}void*pcategory(void*arg){unsignedchar buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//AA 55 ** 00 55 AAchar*category =NULL;pthread_t send_void_tid,trash_tid,oled_tid;printf("%s|%s|%d:\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号while(1){printf("%s|%s|%d:\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号pthread_mutex_lock(&mutex);// 锁住//给阿里云发信号pthread_cond_wait(&cond,&mutex);//等待信号pthread_mutex_unlock(&mutex);printf("%s|%s|%d:\n",__FILE__,__func__,__LINE__);//文件名-函数名-行号
buffer[2]=0x00;//define WGET_CMD "wget http://127.0.0.1:8080/?action=snapshot -O /tmp/garbage.jpg" //抓拍命令system(WGET_CMD);//宏定义:拍照命令,garbage.h 文件中// access函数是一个用于测试文件访问权限的函数,它通常用于检查某个文件是否可以被当前用户读取、写入或执行。// 包括F_OK(检查文件是否存在)、R_OK(检查读权限)、W_OK(检查写权限)和X_OK(检查执行权限)// 该函数返回值为0表示有权限,-1表示无权限或发生错误。if(0==access(GARBAGE_FILE, F_OK))//GARBAGE_FILE 宏定义 识别到的垃圾图片是否存在{
category =garbage_category(category);//调用阿里云接口判断垃圾类型if(strstr(category,"干垃圾")){
buffer[2]=0x41;//赋值}elseif(strstr(category,"湿垃圾")){
buffer[2]=0x42;}elseif(strstr(category,"可回收垃圾")){
buffer[2]=0x43;}elseif(strstr(category,"有害垃圾")){
buffer[2]=0x44;}else{
buffer[2]=0x45;}}else{
buffer[2]=0x45;}//开三个线程//垃圾桶开盖线程pthread_create(&trash_tid,NULL,psend_voice,(void*)buffer);//开语音播报线程pthread_create(&send_void_tid,NULL,popen_trash_can,(void*)buffer);pthread_create(&oled_tid,NULL,poled_show,(void*)buffer);// buffer[2]=0x00;//将buffer[2]清零remove(GARBAGE_FILE);//garbage.h 的宏定义 把识别到的垃圾的图片清除掉}pthread_exit(0);}void*pget_socket(void*fd){int s_fd =-1;int c_fd =-1;char buffer[6];int nread =-1;structsockaddr_in c_addr;//用于acceptmemset(&c_addr,0,sizeof(structsockaddr_in));//清零
s_fd =socket_init(IPADDR,IPPORT);printf("%s|%s|%d: s_fd=%d\n",__FILE__,__func__,__LINE__,s_fd);if(-1== s_fd){pthread_exit(0);}sleep(3);int clen =sizeof(structsockaddr_in);while(1){//accep第三个参数要求是指针//不连接,便堵塞,直到与客户端连接
c_fd =accept(s_fd,(structsockaddr*)&c_addr,&clen);// 心包跳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));printf("--------ok?--------\n");printf("%s|%s|%d: Accept a connection from %s:%d\n",__FILE__,__func__,__LINE__,inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));printf("-----------ok!-----\n");if(c_fd ==-1){perror("accept");//终端打出错误:accept:---continue;}while(1){memset(buffer,0,sizeof(buffer));
nread =recv(c_fd,buffer,sizeof(buffer),0);// == n_read = read(c_fd,readBuf,sizeof(buffer));printf("%s|%s|%d:nread=%d,buffer=%s\n",__FILE__,__func__,__LINE__,nread,buffer);if(nread >0){if(strstr(buffer,"open")){//上锁pthread_mutex_lock(&mutex);//给阿里云发信号pthread_cond_signal(&cond);//发信号pthread_mutex_unlock(&mutex);}}elseif(0==nread ||-1==nread){break;}}close(c_fd);}pthread_exit(0);}intmain(int argc,char*argv[]){printf("adsfasdf\n");int len =0;int ret =-1;//标识char*category =NULL;unsignedchar buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//AA 55 ** 00 55 AApthread_t get_voice_tid, category_tid,get_socket_tid;//初始化wiringPiSetup();//初始化wiringPi库garbage_init();//初始化阿里云接口// 用于检测特定进程是否在运行。// 它通过调用系统命令ps -ax | grep process_name|grep -v grep来查找包含指定进程名的进程,并返回相应的进程号 // mjpg_streamer 是一款开源的流媒体服务器软件,它能够从一系列输入插件(如USB摄像头)捕获视频数据,并通过网络以MJPG(Motion JPEG)格式实时传输
ret =detect_process("mjpg_streamer");if(-1== ret){printf("detect process failed\n");goto END;}//打开设备
serial_fd =myserialOpen(SERIAL_DEV,BAUD);//可代替设备节点 & 波特率 返回值fdif(serial_fd ==-1)//是否打开成功{goto END;}//*pget_voice(void *arg)里面判断//printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);//开语音的线程pthread_create(&get_voice_tid,NULL,pget_voice,NULL);//开网络线程pthread_create(&get_socket_tid,NULL,pget_socket,NULL);//开阿里云交互线程pthread_create(&category_tid,NULL,pcategory,NULL);//get_voice_tid获取语音模块的数据//pthread_join用于等待指定的线程结束其执行pthread_join(get_voice_tid,NULL);pthread_join(category_tid,NULL);pthread_join(get_socket_tid,NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);close(serial_fd);
END:
garbage_final;//释放所有引用的Python对象return0;}
5、实验流程和执行结果
5.1 配置客户端和
mjpg_Stream
- 利用网络调试助手
- 查看
Window & linux
主机ip
window IP 查询方法Linux orangepi IP 查询方法
5.2 调试和运行
>>> gcc -o category *.c *.h -I /usr/include/python3.10/ -lpython3.10-lwiringPi
//得到 category 可执行文件
输入命令
sudo -E ./category
125.3 开始识别
一切都准备好了,现在可以开始实验了
识别干垃圾(纸巾)识别结果
识别可回收垃圾识别结果
识别湿垃圾(一个橘子)识别结果
6、总结
- 在这个基于全志H616 ARM-Linux 的智能分拣项目中,我们利用的是网络调试助手来进行
pc
与orangepi
通信,也就是说,我们可以在实现基于 TCP的socket编程 的远程PC控制;只要orangepi
与PC机
的ip
是互通的,便可以进行远程操控; - 当然我们也可以通过蓝牙模块使得
移动端
与orangepi
进行通信,从而实现移动设备对硬件的控制;但是相比 网络通信,蓝牙通信就显得太局限了,无法做到远距离控制; - 我们也可以不用阿里云端识别服务,自己来训练自己的模型,然后写入
orangpi
里面,从而实现自己想要的智能识别功能;
欢迎大家一起交流讨论!
版权归原作者 半条-咸鱼 所有, 如有侵权,请联系我们删除。