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;
}
内存泄漏
内存泄漏是指在计算机程序中,动态分配的内存未被正确释放,导致这些内存块不能被重新分配和使用。随着程序的运行,未释放的内存不断累积,最终可能耗尽可用内存资源,导致系统性能下降,甚至使程序或系统崩溃。
内存泄漏的成因
- 未释放动态分配的内存:
程序在使用 malloc、calloc 或 realloc 分配内存后,没有使用 free 函数释放内存。
- 循环或递归调用中未释放内存:
在循环或递归调用中,动态分配的内存未被释放。
- 异常退出或提前返回:
程序在处理错误或异常时,没有正确释放已经分配的内存。
- 丢失指针引用:
动态分配的内存地址被覆盖或丢失,没有其他指针指向该内存块。
栈上分配和堆上分配
栈上分配
栈上分配的内存(例如局部变量)在函数调用结束后会自动释放。
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 选项,可以在客户端异常断开连接时检测到失效连接,并在必要时关闭它。它会自动发生在操作系统内核层面。
- KeepAlive 探测: 当启用了 TCP KeepAlive 并设置了相关参数后,操作系统内核会在连接空闲指定时间(TCP_KEEPIDLE)后开始发送 KeepAlive 探测包。
- 检测响应: 内核会等待客户端对探测包的响应。如果在指定的次数(TCP_KEEPCNT)内没有收到客户端的响应,内核会认为该连接已经失效。
- 关闭连接: 当探测包没有得到响应时,内核会自动关闭该连接,并通知应用程序。此时,你在应用程序中的 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;
}