香橙派 “智能垃圾分类识别垃圾桶” 代码示例

文章:OrangePi Zero2 全志H616 开发初探 🔗点此打开 智能垃圾分类识别垃圾桶代码示例

main.c 主函数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>

#include "serial.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"
#include "socket.h"

int serial_fd = -1;
pthread_cond_t cond;
pthread_mutex_t mutex;

/* 检查 process_name 进程是否存在、运行 */
static int detect_process(const char *process_name)
{
	int n = -1;
	FILE *strm;
	char buf[128]={0};
	
	sprintf(buf,"ps -ax | grep %s|grep -v grep", process_name);
	// 构造 ps -ax | grep process_name|grep -v grep 终端命令
	
	if((strm = popen(buf, "r")) != NULL)		// 使用 popen 执行 buf 命令
	{											// 返回值不为 -1 表明存在该进程
		if(fgets(buf, sizeof(buf), strm) != NULL)	// 取出输出的信息存放到 buf
		{
			printf("buf=%s\n",buf);		// 将信息打印出来
			n = atoi(buf);				// 取出进程 ID 号,因为输出的信息最前方就是进程 ID 号
			printf("n=%d\n",n);			// 打印进程 ID 号
		}
	}
	else		// 进程不存在
	{
		return -1;
	}
	
	pclose(strm);		// 关闭文件流
	return n;			// 返回进程 ID 号,不为 -1
}

/* 语音指令接收线程 */
void *pget_voice(void *arg)
{
	unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};	// 初始化接收串口信息的内存空间
	int len = 0;
	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);		// 调试信息
	
	if (-1 == serial_fd)		// 串口打开失败
	{
		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);		// 获取串口信息,语音模块发送的信息
		printf("%s|%s|%d, len=%d\n", __FILE__, __func__, __LINE__,len);		// 调试信息
		
		/* 语音模块识别到 “开始识别垃圾”,会向串口发送 0xAA 0x55 0x46 0x00 0x55 0xAA 的数据 */
		if (len > 0 && buffer[2] == 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());
	
	unsigned char *buffer = (unsigned char *)arg;
	
	if (-1 == serial_fd)		// 串口打开失败
	{
		printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
		pthread_exit(0);
	}
	
	if (NULL != buffer)		// 传递过来的参数不为空
	{
		serialSendstring(serial_fd, buffer, 6);
		// 向串口发送 buffer 数据,语音模块根据数据进行播报
	}
	
	pthread_exit(0);	// 线程退出
}

/* 垃圾桶开关控制线程 */
void *popen_trash_can(void *arg)
{
	// 线程脱离,如果阿里云线程等待该线程退出,那么将失去创建该线程的意义
	pthread_detach(pthread_self());
	
	unsigned char *buffer = (unsigned char *)arg;
	
	if (buffer[2] == 0x41 || buffer[2] == 0x42 || buffer[2] == 0x43)
	{	// 识别为干垃圾、湿垃圾、可回收垃圾,由于硬件模块数量有限,此处打开同一个垃圾桶盖

		printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
		
		pwm_write(PWM_RECOVERABLE_GARBAGE);		// 打开垃圾桶
		delay(2000);							// 延时两秒
		pwm_stop(PWM_RECOVERABLE_GARBAGE);		// 关闭垃圾桶
	}
	else if (buffer[2] != 0x44)		// 识别为有害垃圾
	{

		printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
		
		pwm_write(PWM_GARBAGE);		// 打开垃圾桶
		delay(2000);				// 延时两秒
		pwm_stop(PWM_GARBAGE);		// 关闭垃圾桶
	}
	
	pthread_exit(0);	// 线程退出
}

/* OLED 显示线程 */
void *poled_show(void *arg)
{
	// 线程脱离,如果阿里云线程等待该线程退出,那么将失去创建该线程的意义
	pthread_detach(pthread_self());
	
	myoled_init();		// OLED 初始化
	oled_show(arg);		// OLED 根据 arg 参数显示
	
	pthread_exit(0);	// 线程退出
}

/* 阿里云垃圾分类识别线程 */
void *pcategory(void *arg)
{
	unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};		// 初始化发送缓存
	char *category = NULL;
	pthread_t send_voice_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;		// 初始化发送缓存
		system(WGET_CMD);		// 系统调用,mjpg 拍照指令
		if (0 == access(GARBAGE_FILE, F_OK))		// 检查是否存在 .jpg 文件
		{
			category = garbage_category(category);		// 启动识别,garbage.c
			if (strstr(category, "干垃圾"))
			{
				buffer[2] = 0x41;		// 识别为干垃圾,串口发送缓存 buffer[2] 置 0x41,下同
			}
			else if (strstr(category, "湿垃圾"))
			{
				buffer[2] = 0x42;		// 湿垃圾 buffer[2] 置 0x42
			}
			else if (strstr(category, "可回收垃圾"))
			{
				buffer[2] = 0x43;		// 可回收垃圾 buffer[2] 置 0x43
			}
			else if (strstr(category, "有害垃圾"))
			{
				buffer[2] = 0x44;		// 有害垃圾 buffer[2] 置 0x44
			}
			else
			{
				buffer[2] = 0x45;		// 识别不出垃圾类型 buffer[2] 置 0x45
			}
		}
		else
		{
			buffer[2] = 0x45;		// 拍照不成功 buffer[2] 置 0x45
		}
		
		/*
			由于垃圾桶开盖会持续几秒,且调用阿里云识别本身就需要时间
			为保证垃圾桶运作流畅性,识别成功后,开启垃圾桶开盖控制线程、语音模块播报线程、oled 显示线程
			使阿里云识别线程马上进入下一轮等待条件
		*/

		// 创建语音播报线程,传参:buffer
		pthread_create(&trash_tid, NULL, psend_voice, (void *)buffer);
		
		// 创建垃圾桶开关控制线程,传参:buffer
		pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);
		
		// 创建 OLED 显示线程,传参:buffer
		pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);
		
		// buffer[2] = 0x00;		// 上述三个线程需要判断 buffer[2],此处先不作初始化
		remove(GARBAGE_FILE);		// 删除摄下的垃圾照片
	}

	pthread_exit(0);		// 阿里云线程退出
}

/* 网络指令接收线程 */
void *pget_socket(void *arg)
{
	int s_fd = -1;
	int c_fd = -1;
	char buffer[6];
	int nread = -1;
	struct sockaddr_in c_addr;
	memset(&c_addr,0,sizeof(struct sockaddr_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(struct sockaddr_in);
	while(1)
	{
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);		// 接受连接
		
		// TCP 心跳包机制
		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("%s|%s|%d: Accept a connection from %s:%d\n", __FILE__, __func__, __LINE__,
				inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
		
		if(c_fd == -1)		// 接受连接失败
		{
			perror("accept");
			continue;		// 下一个循环,重新等待接收请求
		}
		
		while(1)
		{
			memset(buffer, 0, sizeof(buffer));
			nread = recv(c_fd, buffer, sizeof(buffer), 0);		// 接收网络数据

			// 调试信息
			printf("%s|%s|%d:nread=%d, buffer=%s\n", __FILE__, __func__, __LINE__,
					nread, buffer);
			
			if (nread > 0)		// 与客户端连接正常
			{
				if (strstr(buffer, "open"))		// 接收到客户端发送的数据为字符串 "open"
				{	/* 
						与阿里云线程、语音指令接收线程的互斥条件
						一次只能通过一种方式(语音或网络)命令垃圾桶开启一次识别
						与网络线程互斥、与阿里云线程互为条件变量
					*/
					pthread_mutex_lock(&mutex);
					pthread_cond_signal(&cond);// 触发条件,唤醒 “阿里云垃圾分类识别” 线程
					pthread_mutex_unlock(&mutex);
				}
			}
			else if(0 == nread || -1 == nread)		// 与客户端正常断开连接
			{
				break;
			}
		}
		
		close(c_fd);		// 关闭套接字
	}

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

int main(int argc, char *argv[])
{
	int len = 0;
	int ret = -1;
	char *category = NULL;
	pthread_t get_voice_tid, category_tid, get_socket_tid;
	
	wiringPiSetup();	// wiringPi 库初始化
	garbage_init();		// 阿里云垃圾识别相关初始化
	
	ret = detect_process("mjpg_streamer");		// 检查 mjpg 服务是否已运行
	if ( -1 == ret)
	{
		printf("detect process failed\n");
		goto END;
	}
	
	serial_fd = myserialOpen(SERIAL_DEV, BAUD);		// 打开串口(语音模块)
	if (-1 == serial_fd)
	{
		printf("open serial failed\n");
		goto END;
	}
	
	// 创建语音指令接收线程
	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);		// 调试信息
	pthread_create(&get_voice_tid, NULL, pget_voice, NULL);

	// 创建网络指令接收线程
	pthread_create(&get_socket_tid, NULL, pget_socket, NULL);

	// 创建阿里云交互线程
	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);		// 调试信息
	pthread_create(&category_tid, NULL, pcategory, NULL);
	
	// 等待线程退出
	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_clear();
	return 0;
}

serial.h

#ifndef __SERIAL__H
#define __SERIAL__H

int myserialOpen (const char *device, const int baud);
void serialSendstring (const int fd, const unsigned char *s, int len);
int serialGetstring (const int fd, unsigned char *buffer);

#define SERIAL_DEV "/dev/ttyS5"
#define BAUD 115200

#endif

serial.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 ;
    }

	/* 打开串口驱动文件 */
    if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)
        return -1 ;

	/* 下面为标准操作,设置波特率,奇偶校验位,停止位,数据位等 */
    fcntl (fd, F_SETFL, O_RDWR) ;
    // Get and modify current options:
    tcgetattr (fd, &options) ;
    cfmakeraw (&options) ;
    cfsetispeed (&options, myBaud) ;
    cfsetospeed (&options, myBaud) ;
    options.c_cflag |= (CLOCAL | CREAD) ;
    options.c_cflag &= ~PARENB ;
    options.c_cflag &= ~CSTOPB ;
    options.c_cflag &= ~CSIZE ;
    options.c_cflag |= CS8 ;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;
    options.c_oflag &= ~OPOST ;
    options.c_cc [VMIN] = 0 ;
    options.c_cc [VTIME] = 100 ; // Ten seconds (100 deciseconds)
    tcsetattr (fd, TCSANOW, &options) ;
    ioctl (fd, TIOCMGET, &status);
    status |= TIOCM_DTR ;
    status |= TIOCM_RTS ;
    ioctl (fd, TIOCMSET, &status);
    usleep (10000) ; // 10mS
    
    return fd ;
}

/* 向串口写入数据,fd 通过 myserialOpen() 获取 */
void serialSendstring (const int fd, const unsigned char *s, int len)
{
    int ret;
    ret = write (fd, s, len);
    if (ret < 0)
        printf("Serial Puts Error\n");
}

/* 读取串口 32 个字节的字符串,存放到 buffer,fd 通过 myserialOpen() 获取  */
int serialGetstring (const int fd, unsigned char *buffer)
{
    int n_read;
    n_read = read(fd, buffer, 32);
    return n_read;
}

socket.h

#ifndef __SOCKET__H
#define __SOCKET__H

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

#define IPADDR "192.168.31.219"     // 香橙派的 ip 地址
#define IPPORT "8188"       // 随意设置一个合理范围的端口号,但不能是 8080,8080 用于 mjpg 服务

#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 = -1;
	int ret = -1;
	struct sockaddr_in s_addr;
	memset(&s_addr,0,sizeof(struct sockaddr_in));
	
    // socket
	s_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(s_fd == -1){
		perror("socket");
		return -1;
	}
	
    s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(port));
	inet_aton(ipaddr,&s_addr.sin_addr);
    // bind
	ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	if (-1 == ret)
	{
		perror("bind");
		return -1;
	}
	
    // listen
	ret = listen(s_fd,1);       // 只监听 1 个连接,排队扔垃圾
	if (-1 == ret)
	{
		perror("listen");
		return -1;
	}
	
    return s_fd;
}

garbage.h

#ifndef __GARBAGE_H
#define __GARBAGE_H

void garbage_init();
void garbage_clear();
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

garbage.c 阿里云垃圾分类识别相关

#include <Python.h>

void garbage_init()
{
    Py_Initialize();    // 初始化

    // 将当前路径添加到 sys.path 中
    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(path, PyUnicode_FromString("."));     // PyUnicode_FromString() 将 c 语言的字符串转为 python 的字符串
}

void garbage_clear()
{
    // 关闭Python解释器
    Py_Finalize();
}

char *garbage_category(char *category)
{
    // 导入 garbage 模块
    PyObject *pModule = PyImport_ImportModule("garbage");
    if (!pModule)
    {
        PyErr_Print();      // python 中打印错误信息
        printf("ERROR: failed to load garbage.py\n");
        goto FAILED_MODULE;
    }

    // 获取 alibaba_garbage() 函数对象
    PyObject *pFunc = PyObject_GetAttrString(pModule, "alibaba_garbage");
    if (!pFunc || !PyCallable_Check(pFunc))
    {
        PyErr_Print();      // python 中打印错误信息
        printf("ERROR: function alibaba_garbage not found or not callable\n");
        goto FAILED_FUNC;
    }

    // 调用 alibaba_garbage 函数并获取返回值
    PyObject *pValue = PyObject_CallObject(pFunc, NULL);        // 无参
    if (!pValue)
    {
        PyErr_Print();      // python 中打印错误信息
        printf("ERROR: function call failed\n");
        goto FAILED_VALUE;
    }

    // 将返回值转换为 C 类型
    char *result = NULL;
    if( !PyArg_Parse(pValue, "s", &result) ){
        PyErr_Print();      // python 中打印错误信息
        printf("ERROR: parse failed\n");
		goto FAILED_RESULT;
    }
    printf("result = %s\n", result);

	// 拷贝分类结果,因为释放时 result 指向的内存空间会被清空
	category = (char*)malloc(strlen(result)+1);
	memset(category, 0, strlen(result)+1);
	strncpy(category, result, strlen(result)+1);

    // 释放所有引用的 Python 对象,最后获取的 pValue 最先释放
FAILED_RESULT:
	Py_DECREF(pValue);
FAILED_VALUE:
	Py_DECREF(pFunc);
FAILED_FUNC:
	Py_DECREF(pModule);
FAILED_MODULE:
	// 错位跳转的原因在于:调用失败后对应的 PyObject 对象是没有申请到空间的

    return category;
}


pwm.h

#ifndef __PWM__H
#define __PWM__H

#define PWM_GARBAGE 7
#define PWM_RECOVERABLE_GARBAGE 5

void pwm_write(int pwm_pin);
void pwm_stop(int pwm_pin);

#endif

pwm.c 垃圾桶开关舵机相关

#include <wiringPi.h>
#include <softPwm.h>

/*
	舵机控制垃圾桶开关盖
	根据 PWM 频率公式:PWMfreq = 1 x 10^6 / (100 x range)
	要得到 PWM 频率为 50Hz,则 range 为 200,即周期分为 200 步,控制精度相比硬件 PWM 较低
*/

void pwm_write(int pwm_pin)     // 舵机控制垃圾桶开盖
{
	pinMode(pwm_pin, OUTPUT);
	softPwmCreate(pwm_pin,0,200);   // range 设置周期分为 200 步, 周期 20ms
	softPwmWrite(pwm_pin,10);       //1ms,表示舵机角度 45°
	delay(1000);
	softPwmStop(pwm_pin);
}

void pwm_stop(int pwm_pin)      // 舵机控制垃圾桶关盖
{
	pinMode(pwm_pin, OUTPUT);
	softPwmCreate(pwm_pin,0,200);   // range 设置周期分为 200 步, 周期 20ms
	softPwmWrite(pwm_pin,5);        //0.5ms,表示舵机角度 0°
	delay(1000);
	softPwmStop(pwm_pin);
}

myoled.h

#ifndef __MYOLED__H
#define __MYOLED__H

int myoled_init(void);
int oled_show(void *arg);

#endif

myoled.c 显示屏 OLED 显示相关

#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, 9+1, "This garbage is:");
	disp.font = font2;
	
	switch(buffer[2])
	{
		case 0x41:
			oled_putstrto(&disp, 0, 20, "dry garbage");
			break;
		case 0x42:
			oled_putstrto(&disp, 0, 20, "wet garbage");
			break;
		case 0x43:
			oled_putstrto(&disp, 0, 20, "recyclable trash");
			break;
		case 0x44:
			oled_putstrto(&disp, 0, 20, "hazardous waste");
			break;
		case 0x45:
			oled_putstrto(&disp, 0, 20, "recognition failure");
			break;
	}
	
	disp.font = font2;
	oled_send_buffer(&disp);
	return 0;
}

int myoled_init(void)
{
	int e;
	disp.address = OLED_I2C_ADDR;
	disp.font = font2;
	e = oled_open(&disp, FILENAME);
	e = oled_init(&disp);
	return e;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~莘莘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值