目录:
文章: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;
}