基于Linux的云端垃圾分类助手

项目简介

本项目旨在开发一个基于嵌入式系统的智能垃圾分类装置。该装置能够通过串口通信、语音播报、网络通信等多种方式,实现垃圾的自动识别和分类投放。系统采用多线程设计,确保各功能模块高效并行工作。

项目功能

  1. 垃圾分类识别

系统使用摄像头拍摄垃圾图片,并将其上传到阿里云进行分类识别。
识别结果包括干垃圾、湿垃圾、可回收垃圾、有害垃圾等类别。

  1. 语言播报

系统通过串口与语言模块通信,根据垃圾分类结果进行语音播报。
语音模块接收指令并播放相应的提示音。

  1. OLED显示

系统通过OLED屏幕显示当前垃圾分类结果。

4.垃圾桶盖控制

系统根据识别结果控制相应垃圾桶的开关。
使用PWM信号控制舵机开关垃圾桶盖。

5.网络通信

系统支持通过网络接收客户端指令,实现远程控制。
通过TCP/IP协议接收来自客户端的“open”指令,触发垃圾分类操作。

硬件需求

Orange Pi Zero2
摄像头模块
语音播报模块SU-03T
128*64 OLED显示屏
SG90舵机
串口通信模块

运行环境

操作系统:Linux
编程语言:C语言、Python

功能实现

garbage.py

调用阿里云SDK API

# -*- coding: utf-8 -*-
# 引入依赖包
# pip install alibabacloud_imagerecog20190930
//引入必要的Python包,包括阿里云图像识别服务的SDK包。
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

//通过读取环境变量中的AccessKey ID和AccessKey Secret,配置阿里云客户端的认证信息和访问域名。
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'
)

//垃圾分类函数,提供了从本地文件读取和从URL读取,两种方式
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.to_map()['Data']['Elements'][0]['Category'])
      return response.body.to_map()['Data']['Elements'][0]['Category']
    except Exception as error:
      return 'gat failed'

在脚本被直接执行时,调用‘alibaba_garbage’函数。
if __name__ == "__main__":
     alibaba_garbage()

garbage.c

在C语言中嵌入Python解释器以调用Python函数。

#include <Python.h>
#include "garbage.h"

void garbage_init(void)
{	
	//初始化Python解释器
	Py_Initialize();
	//导入‘sys’模块
	PyObject *sys = PyImport_ImportModule("sys");
	//获取‘sys.path’模块
	PyObject *path = PyObject_GetAttrString(sys, "path");
	//将当前目录‘.’添加到'sys.path'中,以便导入当前目录下的Python脚本。
	PyList_Append(path, PyUnicode_FromString("."));
}

void garbage_final(void)
{
	//终止Python解释器。	
	Py_Finalize();
}

char *garbage_category(char *category)
{
	//导入名为‘garbage.py’的python模块。
	//如果导入失败,打印错误信息并跳转到'FAILED_MODULE'表签进行清理。
	PyObject *pModule = PyImport_ImportModule("garbage");
	if(!pModule){
		PyErr_Print();
		printf("ERROR: faild to load garbage.py\n");
		goto FAILED_MODULE;
	}
	//获取'garbage'模块中的'alibaba_garbage'函数。
	//如果获取失败或函数不可调用,打印错误信息并跳转到 FAILED_FUNC 标签进行清理。
	PyObject *pFunc = PyObject_GetAttrString(pModule, "alibaba_garbage");
	if(!pFunc || !PyCallable_Check(pFunc)){
	
		PyErr_Print();
		printf("ERROR: function alibaba_garbage not found or not callable\n");
		goto FAILED_FUNC;
	}
	//调用 alibaba_garbage 函数,不传递参数。
	//如果调用失败,打印错误信息并跳转到 FAILED_VALUE 标签进行清理。
	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\n");
		goto FAILED_RESULT;
	}

	category = (char *)malloc(sizeof(char *)*(strlen(result)+1));
	memset(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;
}

内存泄漏

内存泄漏是指在计算机程序中,动态分配的内存未被正确释放,导致这些内存块不能被重新分配和使用。随着程序的运行,未释放的内存不断累积,最终可能耗尽可用内存资源,导致系统性能下降,甚至使程序或系统崩溃。

内存泄漏的成因
  1. 未释放动态分配的内存:

程序在使用 malloc、calloc 或 realloc 分配内存后,没有使用 free 函数释放内存。

  1. 循环或递归调用中未释放内存:

在循环或递归调用中,动态分配的内存未被释放。

  1. 异常退出或提前返回:

程序在处理错误或异常时,没有正确释放已经分配的内存。

  1. 丢失指针引用:

动态分配的内存地址被覆盖或丢失,没有其他指针指向该内存块。

栈上分配和堆上分配
栈上分配

栈上分配的内存(例如局部变量)在函数调用结束后会自动释放。

void function() {
    int local_variable = 10; // 栈上分配
    // local_variable 在函数结束时自动释放
}

堆上分配

堆上分配的内存需要程序员手动释放,否则会导致内存泄漏。

void function() {
    int *heap_variable = (int *)malloc(sizeof(int) * 10); // 堆上分配
    // 使用完 heap_variable 后需要手动释放内存
    free(heap_variable);
}

garbage.h

#ifndef __GARBAGE__H
#define __GARBAGE__H

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

main.c

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

#include "uartTool.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;
 
 //该函数通过执行 shell 命令 ps 检测特定进程是否正在运行。
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);

    if ((strm = popen(buf, "r")) != NULL) 
    {
        if (fgets(buf, sizeof(buf), strm) != NULL) 
        {
            n = atoi(buf);
        }
    }
    else
    {
        return -1;
    }

    pclose(strm);
    return n;
}

//从串口读取语音命令,并根据特定条件触发条件变量信号。
void *pget_voice(void *arg)
{
    unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0x55, 0xAA};
    int len = 0;

    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);
        if (len > 0 && buffer[2] == 0x46) 
        {
            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);
    }

    pthread_exit(0);
}

//控制垃圾桶打开和关闭。
void *popen_trash_can(void *arg)
{
    pthread_detach(pthread_self());
    unsigned char *buffer = (unsigned char *)arg;

    if (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] != 0x45)
    {
        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_show(arg);

    pthread_exit(0);
}

//等待条件变量信号,执行垃圾分类逻辑,并触发语音、垃圾桶和 OLED 显示。
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;
    
    while(1)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        pthread_mutex_unlock(&mutex);

        buffer[2] = 0x00;
        system(WGET_CMD);

        if (0 == access(GARBAGE_FILE, F_OK))
        {
            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(&trash_tid, NULL, psend_voice, (void *)buffer);

        //开垃圾桶开关
        pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);

        //oled显示线程
        pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);

        remove(GARBAGE_FILE);
    }

}

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);// 接受客户端连接
        int keepalive = 1; // 开启TCP KeepAlive功能
        int keepidle = 5; // 5s内没收到数据开始发送心跳包
        int keepcnt = 3; // 每次发送心跳包的次数
        int keepintvl = 3; // 每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); //n_read = read(c_fd,buffer, 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);
                }
            }
            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();

    garbage_init();
    ret = detect_process("mjpg_streamer"); //判断mjpg_streamer是否已经在运行
    if (-1 == ret)
    {
        goto END;
    }
    else
    {
        printf("已运行mjpg_streamer,可以开始垃圾分类识别\n");
    }
    serial_fd = myserialOpen(SERIAL_DEV, BAUD);

    if (-1 == serial_fd) {
       
        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_final;
    return 0; 
}

关于TCP KeepAlive

通过设置 TCP KeepAlive 选项,可以在客户端异常断开连接时检测到失效连接,并在必要时关闭它。它会自动发生在操作系统内核层面。

  1. KeepAlive 探测: 当启用了 TCP KeepAlive 并设置了相关参数后,操作系统内核会在连接空闲指定时间(TCP_KEEPIDLE)后开始发送 KeepAlive 探测包。
  2. 检测响应: 内核会等待客户端对探测包的响应。如果在指定的次数(TCP_KEEPCNT)内没有收到客户端的响应,内核会认为该连接已经失效。
  3. 关闭连接: 当探测包没有得到响应时,内核会自动关闭该连接,并通知应用程序。此时,你在应用程序中的 recv 调用会返回 -1,表示连接已经断开。

oled.c

这段代码实现了一个 OLED 显示功能,用于在一个指定位置上显示垃圾分类的结果。具体来说,代码分为两个主要部分:oled_show 函数和 myoled_init 函数。

#include <error.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;
	//根据 buffer[2] 的值显示相应的垃圾分类结果
    switch(buffer[2])
    {
        case 0x41:
            oled_putstrto(&disp, 0, 20, "dry waste");
        break;
        case 0x42:
            oled_putstrto(&disp, 0, 20, "wet waste");
        break;
        case 0x43:
            oled_putstrto(&disp, 0, 20, "recyclable waste");
        break;
        case 0x44:
            oled_putstrto(&disp, 0, 20, "hazardous waste");
        break;
        case 0x45:
            oled_putstrto(&disp, 0, 20, "recognition failed");
        break;
    }
	// 再次设置字体
    disp.font = font2;
    //把需要显示的内容刷新到屏幕上
    oled_send_buffer(&disp); 

     return 0;
}

int myoled_init(void)
{
    int e;
    // 设置显示屏地址和字体
    disp.address = OLED_I2C_ADDR;
    disp.font = font2;
	// 打开和初始化 OLED 显示屏
    e = oled_open(&disp, FILENAME);
    e = oled_init(&disp);

    return e;
}
  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值