目录:
- 一、刷机和系统启动
- 1、TF 卡格式化:
- 2、镜像烧录:
- 3、登录系统:
- 二、基于官方外设开发
- 1、wiringPi 外设 SDK 安装:
- 2、C 文件编译:
- 3、基于官方外设的应用开发:
- ① GPIO 输入输出:
- ② 超声波测距:
- ③ Linux 定时器:
- ④ PWM 输出:
- ⑤ OLED 显示屏应用——IIC协议:
- ⑥ 串口开发:
- ⑦ Linux 原生串口开发:
- ⑧ 香橙派摄像头的使用:
- 4、Linux 的热拔插 UDEV 机制
- ① 守护进程:
- ② 守护进程开发:
- ③ 守护进程应用:
- ④ UDEV 的配置文件:
- ⑤ UDEV 自动挂载 U 盘:
- 三、智能垃圾分类垃圾桶项目
- 1、功能需求:
- 2、安装 python 环境(需要安装 python3.9 或以上版本):
- 3、C 语言调用 Python:
- ① 安装 libpython3-dev 依赖包:
- ② 直接调用 python 语句:
- ③ 调用无参 python 函数:
- 一般调用的流程如下:
- ④ 调用有参 python 函数:
- 一般调用的流程如下:
- 4、阿里云垃圾分类识别测试:
- 5、C 语言调用阿里云 python 接口:
- ① 添加永久环境变量:
- ② python 接口返回值类型检查:
- ③ 示例:
- 6、项目示例:
- ① 环境准备:
- ② 补充知识点:
- ③ 代码示例:
一、刷机和系统启动
1、TF 卡格式化:
可以使用SD Card Formatter
软件格式化TF
卡
2、镜像烧录:
使用win32diskimager
软件将准备好的镜像烧录到TF
卡中
镜像下载网址🔗点击这里 点击Orange Pi Zero2
官方镜像的ubuntu
镜像,跳转至百度网盘,下载3.0.6
版本:
此镜像可能默认关闭uart5
,需要使用uart5
时配置一下串口uart5
,打开配置文件:
sudo vi /boot/orangepiEnv.txt
加入以下字段,开启uart5
和i2c3
:
overlays=uart5 i2c3
如图所示:
3、登录系统:
利用
MobaXterm
进行串口登录或SSH
方式登录,初次登录应用串口登录或利用USB
转HTML
接入屏幕后登录,默认用户如下:
- 用户名:
orangepi
,密码:orangepi
- 用户名:
root
,密码:orangepi
修改
orangepi
用户密码:sudo passwd orangepi
① 串口登录:
使用TTL 转 USB
模块连接开发板串口(电脑需要安装ch340
驱动),如图所示,然后接入电脑,使用MobaXterm
软件进行串口登录,波特率默认为115200
,MobaXterm
连接上串口后,插入开发板电源线,查看串口打印数据,检验刷机是否成功
② 重启及关机:
- 重启:
sudo reboot
- 关机:
sudo poweroff
③ 网络配置:
- 扫描周围的
WIFI
热点:nmcli dev wifi
- 接入网络:
nmcli dev wifi connect user password xxxxxxxx
// 比如接入用户名为user
的wifi
,密码为xxxxxxxx
- 查看
IP
地址:ip addr show wlan0
或ifconfig
④ SSH 登录开发板:
镜像自带SSH
服务器,只要通过MobaXterm
登陆即可
二、基于官方外设开发
1、wiringPi 外设 SDK 安装:
git clone https://github.com/orangepi-xunlong/wiringOP -b master
// 下载源码cd wiringOP
// 进入文件夹sudo ./build clean
// 清除编译信息sudo ./build
// 编译
或者通过 windows 浏览器打开 https://github.com/orangepi-xunlong/wiringOP,下载压缩包,通过 MobaXterm 把压缩包传到开发板:- 解压:
unzip xxx.zip
cd xxx
sudo ./build
- 验证是否安装成功:
gpio readall
,如下图所示
上述1~26
物理端口,以如图所示方向的引脚对应:
2、C 文件编译:
- 使用
wiringPi
库,编译的时候需要链接:gcc xxx.c -o -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt
- 可以制作简单的
shell
脚本便于编译:vi bulid.sh
// 编写 shell 文件- 加入以下代码:
gcc $1 -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt
// $1 为参数- 保存文件后,为 shell 文件添加可执行权限:
chmod +x build.sh
- 运行时 wiringPi 需要访问底层驱动程序,使用超级用户权限运行:
sudo ./a.out
3、基于官方外设的应用开发:
① GPIO 输入输出:
#include <stdio.h>
#include <wiringPi.h>
#include <unistd.h>#define GPIO1 0
#define GPIO2 2int main (void)
{wiringPiSetup(); // 初始化 wiringPi 库pinMode (GPIO1, OUTPUT); // 设置 IO 口为输出模式pinMode (GPIO2, INPUT); // 设置 IO 口为输入模式while(1){sleep(1); // 延时 1 秒;// usleep(); 延时微秒digitalWrite (GPIO1, HIGH); // IO 口输出高电平sleep(1);digitalWrite (GPIO1, LOW); // IO 口输出低电平if(digitalRead(GPIO2)){ // 检测 IO 口电平printf("high level\n");}else{printf("low level\n");}}return 0;
}
② 超声波测距:
时间函数:
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);struct timeval{time_t tv_sec; /* seconds(秒)*/suseconds_t tv_usec; /* microseconds(微秒)*/
};struct timezone{int tz_minuteswest; /* minutes west of Greenwich (格林威治时间往西方的时差) */int tz_dsttime; /* type of DST correction (DST 时间的修正方式) */
};
- 获取自
1970-01-01 00:00:00
到调用gettimeofday()
函数所经历的秒数,存放在tv
中,精确到微秒,在超声波测距应用中,我们只关心时间差值- 获取时区信息,存放到
tz
中,不关心时置NULL
例程:
#include <stdio.h>
#include <sys/time.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <unistd.h>#define Trig 5
#define Echo 7double getDistance()
{double dis;struct timeval start;struct timeval end;pinMode(Trig, OUTPUT);pinMode(Echo, INPUT);digitalWrite(Trig ,LOW);usleep(5);digitalWrite(Trig ,HIGH); /* 向 Trig 口发送 10 微秒 TTL 脉冲 */usleep(10);digitalWrite(Trig ,LOW);while(!digitalRead(Echo)); // 等待 Echo 口高电平gettimeofday(&start,NULL); // 获取时间while(digitalRead(Echo)); // 等待 Echo 口低电平gettimeofday(&end,NULL); // 获取时间long diffTime = 1000000*(end.tv_sec-start.tv_sec)+(end.tv_usec - start.tv_usec); // 计算时间差值,单位:微秒dis = (double)diffTime/1000000 * 34000 / 2; // 计算距离,音速为 340 m/s/*diffTime/1000000 转化为秒340(m/s) * 100 转化为厘米每秒(cm/s)往返为两段路程,需要 /2*/return dis;
}int main()
{double dis;if(wiringPiSetup() == -1){fprintf(stderr,"%s","initWringPi error");exit(-1);}while(1){dis = getDistance();printf("dis = %lf\n",dis);usleep(500000);}return 0;
}
③ Linux 定时器:
- 实现定时器,通过
itimerval
结构体配置以及函数setitimer()
产生的信号,系统随之使用signal
信号处理函数来处理产生的定时信号,从而实现定时器
struct itimerval
{/* Value to put into `it_value' when the timer expires. */struct timeval it_interval;/* Time to the next timer expiration. */struct timeval it_value;
};struct timeval
{__time_t tv_sec; /* Seconds. */__suseconds_t tv_usec; /* Microseconds. */
};
it_interval : | 计时器的初始值,一般基于这个初始值加或减,基于控制函数的参数配置 |
---|---|
it_value : | 当程序运行到此,间隔多久启动定时器 |
tv_sec : | 秒 |
tv_usec | 微秒(μs)(10 -6s) |
int setitimer (__itimer_which_t __which,const struct itimerval *__restrict __new,struct itimerval *__restrict __old);
返回值: | 成功返回 0,失败返回 -1 |
---|---|
__which 参数: | ITIMER_REAL // 数值为 0,计时器的值实时递减,发送的信号是 SIGALRMITIMER_VIRTUAL // 数值为 1,进程执行时递减计时器的值,发送的信号是 SIGVTALRMITIMER_PROF // 数值为 2,进程和系统执行时都递减计时器的值,发送的信号是 SIGPROF |
__new 参数: | 设定定时器相关设置 |
__old 参数: | 保存先前__new 的值,常设为NULL |
- 函数的第一个参数,我们使用
ITIMER_REAL
,那么显然,我们需要捕获对应的信号进行逻辑相关处理,捕获SIGALRM
信号:signal(SIGALRM, signal_handler);
- 该方法一个进程只能创建一个定时器
例程:
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>static int i;
void signal_handler(int signum) // 每隔 2000 * 500 μs,即 1s 打印 "hello\n"
{i++;if(i == 2000){printf("hello\n");i = 0;}
}
int main()
{struct itimerval itv;// 设定定时时间itv.it_interval.tv_sec = 0;itv.it_interval.tv_usec = 500; // 500 微秒// 定时时间设定完成后,间隔多久启动定时器itv.it_value.tv_sec = 1;itv.it_value.tv_usec = 0;// 间隔 1 秒启动定时器// 设定定时方式if( -1 == setitimer(ITIMER_REAL, &itv, NULL)){perror("error");exit(-1);}//信号处理signal(SIGALRM, signal_handler);while(1);return 0;
}
④ PWM 输出:
- 输出高电平持续时间为
1.0 ms
,低电平持续时间为19 ms
的PWM
信号,即占空比为5%
,周期为20 ms
,频率为1 / 0.02s = 50 Hz
例程:
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>
#include <wiringPi.h>#define PWM 0static int i = 0;
void signal_handler(int signum)
{if(i <= 2){digitalWrite(PWM, HIGH);}else{digitalWrite(PWM, LOW);}if(i == 40){i = 0;}i++;
}int main()
{struct itimerval itv;wiringPiSetup();pinMode(PWM, OUTPUT);itv.it_interval.tv_sec = 0;itv.it_interval.tv_usec = 500; // 定时时间 500 微秒itv.it_value.tv_sec = 1;itv.it_value.tv_usec = 0;if( -1 == setitimer(ITIMER_REAL, &itv, NULL)){perror("error");exit(-1);}signal(SIGALRM, signal_handler);while(1);return 0;
}
⑤ OLED 显示屏应用——IIC协议:
- 由
Orange Pi Zero 2
的26pin
原理图可知,可用的i2c
为i2c3
- 启动
linux
系统,确认/dev
下存在i2c-3
的设备节点,可以观察到系统支持I2C-3
和I2C-5
的驱动,而H616
的外设只有一个IIC
接口,用的是IIC-3
- 开始测试
IIC
,首先安装i2c-tools
:
sudo apt-get install i2c-tools
- 接好线后,终端输入指令
sudo i2cdetect -y 3
,可以看到终端打印信息,如下图,表明已经通过i2c-3
驱动扫描设备成功
下面基于wiringPi
库中的例程进行开发,路径:.../wiringOP-next/examples/oled_demo.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"int oled_demo(struct display_info *disp) {int i;char buf[100];disp->font = font1; // 设置字体类型// void oled_putstrto(struct display_info *disp, uint8_t x, uint8_t y, char *str);oled_putstrto(disp, 0, 9+1, "hello world"); // 将字符信息写入缓存disp->font = font2; // 设置字体类型,目测 font1 与 font2 区别不大oled_putstrto(disp, 0, 18+2, "hello world"); // 将字符信息写入缓存disp->font = font3; // 设置字体类型,font3 是小字oled_putstrto(disp, 0, 27+3, "hello world"); // 将字符信息写入缓存oled_send_buffer(disp); // 将缓存发送到物理屏幕上return 0;
}void show_error(int err, int add) {//const gchar* errmsg;//errmsg = g_strerror(errno);printf("\nERROR: %i, %i\n\n", err, add);//printf("\nERROR\n");
}void show_usage(char *progname) {printf("\nUsage:\n%s <I2C bus device node >\n", progname);
}int main(int argc, char **argv) {char filename[32];struct display_info disp;if (argc < 2) {show_usage(argv[0]);return -1;}memset(&disp, 0, sizeof(disp));sprintf(filename, "%s", argv[1]); // argv[1] 要传入需要使用的 IIC 驱动文件路径disp.address = OLED_I2C_ADDR;disp.font = font2;oled_open(&disp, filename); // 与打开 IIC 驱动文件相关的函数oled_init(&disp);oled_demo(&disp); // 显示内容处理函数return 0;
}
⑥ 串口开发:
- 查看
/dev
可以看到串口驱动文件有:ttyS0
、ttyS1
、ttyS5
,ttyS0
默认作为系统调试信息的串口,也可自行配置为通信串口使用。由Orange Pi Zero 2
的26pin
原理图可知,我们想要使用ttyS5
串口
例程:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <stdlib.h>
#include <unistd.h>int fd;
void* Sendhandler() // 向串口发送数据
{char *sendBuf;sendBuf = (char *)malloc(32*sizeof(32));while(1){memset(sendBuf, '\0', 32);scanf("%s", sendBuf);while(*sendBuf){ // 检测 '\0'serialPutchar(fd, *sendBuf++); // 向串口发送一个字符}}
}void* Revhandler() // 读取串口数据
{while(1){while (serialDataAvail(fd)){printf("%c", serialGetchar(fd)); // 读取串口一个字符并打印fflush(stdout) ; // 清空输出缓冲区之类的操作}}
}int main ()
{pthread_t idSend;pthread_t idRev;if ((fd = serialOpen ("/dev/ttyS5", 115200)) < 0){fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;return 1 ;}// 为了全双工地使用串口,我们分别创建了收和发的线程pthread_create(&idSend, NULL, Sendhandler, NULL);pthread_create(&idRev, NULL, Revhandler, NULL);if (wiringPiSetup () == -1){fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ;return 1 ;}while(1){sleep(10);}return 0 ;
}
⑦ Linux 原生串口开发:
可以参考wiringPi
库的串口源码wiringSerial.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 "wiringSerial.h"/** serialOpen:* Open and initialise the serial port, setting all the right* port parameters - or as many as are required - hopefully!**********************************************************************************/int serialOpen (const char *device, const int baud)
{struct termios options ;speed_t myBaud ;int status, fd ;switch (baud){case 50: myBaud = B50 ; break ;case 75: myBaud = B75 ; break ;case 110: myBaud = B110 ; break ;case 134: myBaud = B134 ; break ;case 150: myBaud = B150 ; break ;case 200: myBaud = B200 ; break ;case 300: myBaud = B300 ; break ;case 600: myBaud = B600 ; break ;case 1200: myBaud = B1200 ; break ;case 1800: myBaud = B1800 ; break ;case 2400: myBaud = B2400 ; break ;case 4800: myBaud = B4800 ; break ;case 9600: myBaud = B9600 ; break ;case 19200: myBaud = B19200 ; break ;case 38400: myBaud = B38400 ; break ;case 57600: myBaud = B57600 ; break ;case 115200: myBaud = B115200 ; break ;case 230400: myBaud = B230400 ; break ;case 460800: myBaud = B460800 ; break ;case 500000: myBaud = B500000 ; break ;case 576000: myBaud = B576000 ; break ;case 921600: myBaud = B921600 ; break ;case 1000000: myBaud = B1000000 ; break ;case 1152000: myBaud = B1152000 ; break ;case 1500000: myBaud = B1500000 ; break ;case 2000000: myBaud = B2000000 ; break ;case 2500000: myBaud = B2500000 ; break ;case 3000000: myBaud = B3000000 ; break ;case 3500000: myBaud = B3500000 ; break ;case 4000000: myBaud = B4000000 ; break ;default:return -2 ;}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) ; // 10mSreturn fd ;
}/** serialFlush:* Flush the serial buffers (both tx & rx)**********************************************************************************/void serialFlush (const int fd)
{tcflush (fd, TCIOFLUSH) ;
}/** serialClose:* Release the serial port**********************************************************************************/void serialClose (const int fd)
{close (fd) ;
}/** serialPutchar:* Send a single character to the serial port**********************************************************************************/void serialPutchar (const int fd, const unsigned char c)
{int ret;ret = write (fd, &c, 1) ;if (ret < 0)printf("Serial Putchar Error\n");
}/** serialPuts:* Send a string to the serial port**********************************************************************************/void serialPuts (const int fd, const char *s)
{ int ret;ret = write (fd, s, strlen (s));if (ret < 0)printf("Serial Puts Error\n");
}/** serialPrintf:* Printf over Serial**********************************************************************************/void serialPrintf (const int fd, const char *message, ...)
{va_list argp ;char buffer [1024] ;va_start (argp, message) ;vsnprintf (buffer, 1023, message, argp) ;va_end (argp) ;serialPuts (fd, buffer) ;
}/** serialDataAvail:* Return the number of bytes of data avalable to be read in the serial port**********************************************************************************/int serialDataAvail (const int fd)
{int result ;if (ioctl (fd, FIONREAD, &result) == -1)return -1 ;return result ;
}/** serialGetchar:* Get a single character from the serial device.* Note: Zero is a valid character and this function will time-out after* 10 seconds.**********************************************************************************/int serialGetchar (const int fd)
{uint8_t x ;if (read (fd, &x, 1) != 1)return -1 ;return ((int)x) & 0xFF ;
}
参考wiringSerial.c
撰写自己的串口通信源码:
serialTool.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) ; // 10mSreturn fd ;
}/* 向串口写入一个字符串 */
void serialSendstring (const int fd, const char *s)
{int ret;ret = write (fd, s, strlen (s));if (ret < 0)printf("Serial Puts Error\n");
}/* 读取串口 32 个字节的字符串 */
int serialGetstring (const int fd, char *buffer)
{int n_read;n_read = read(fd, buffer,32);return n_read;
}
serialTool.h
int myserialOpen (const char *device, const int baud);
void serialSendstring (const int fd, const char *s);
int serialGetstring (const int fd, char *buffer);
测试代码可自行编写,参考⑥ 串口开发的例程,编译时无需再连接wiringPi
库。
⑧ 香橙派摄像头的使用:
[1] 将 USB 摄像头插入到 OrangePi 开发板的 USB 接口中,然后通过lsmod
命令可以看到内核自动加载了uvcvideo
模块:
lsmod | grep uvcvideo | grep -v grep
[2] 通过v4l2-ctl
命令可以看到 USB 摄像头的设备节点信息为/dev/videox
(x 有可能是 0、1 或 2 等数字):
sudo apt update
sudo apt install -y v4l-utilsv4l2-ctl --list-devices
如图:
[3] 使用fswebcam
测试摄像头:
sudo apt update
sudo apt-get install -y fswebcamsudo fswebcam -d /dev/video1 --no-banner -r 1280x720 -S 5 ./image.jpg # 注意这里的 video1 要根据实际的情况修改
解析:
-d | 用于指定 USB 摄像头的设备节点 |
---|---|
--no-banner | 用于去除照片的水印 |
-r | 用于指定照片的分辨率 |
-S | 用于设置跳过前面的帧数 |
./image.jpg | 用于设置生成的照片的名字和路径 |
[4] 使用mjpg-streamer
测试 USB 摄像头:
安装mjpg-streamer
:
git clone https://github.com/jacksonliam/mjpg-streamer # Github 的下载地址
或
git clone https://gitee.com/leeboby/mjpg-streamer # Gitee 的镜像下载地址(推荐)
安装依赖的软件包,Ubuntu 系统使用此命令:
sudo apt-get install -y cmake libjpeg8-dev
编译安装mjpg-streamer
:
cd mjpg-streamer/mjpg-streamer-experimentalmake -j4
sudo make install
在mjpg-streamer/mjpg-streamer-experimental
目录下,打开start.sh
,修改前排的相应语句,注意是否为video1
,改为:
./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -u -f 30" -o "./output_http.so -w ./www"
保存并执行./start.sh
,启动mjpg
服务;
然后在和开发板同一局域网的 Ubuntu PC、Windows PC 或手机的浏览器中输入【开发板的 IP地址:8080】192.168.xx.xxx:8080
就能看到摄像头输出的视频了。拍照命令:
wget http://192.168.xx.xxx:8080/?action=snapshot -O /存放路径/image.jpg
[5] 让mjpg-streamer
服务开机自启动:
在工作目录中创建一个mjpg.sh
可执行文件,添加以下启动脚本:
#!/bin/bashcd /home/orangepi/mjpg-streamer/mjpg-streamer-experimental
./start.sh
添加可执行权限:chmod +x mjpg.sh
然后配置开机启动,先进入文件夹:
cd /etc/xdg/autostart
随便复制一个已有的文件(可以确保权限等符合要求),命名为mjpg.desktop
,比如im-launch.desktop
文件:
sudo cp im-launch.desktop mjpg.desktop
mjpg.desktop
内容为:
[Desktop Entry]
Name=mjpg
Exec=/home/orangepi/mjpg.sh
Type=Application
NoDisplay=true
重启 OrangePi 开发板
4、Linux 的热拔插 UDEV 机制
- 当一个新的设备接入开发板的
USB
时,dmesg
能够查看到接入的设备信息,我们使用adb
工具却无法访问到新设备,输入adb devices
会出现提醒:dinsufficient permissions for device: user in plugdev group; are your udev rules wrong?
(使用adb
前请先安装adb
工具:sudo apt-get install adb
);此时,我们要配置文件以支持USB
设备的热拔插、支持UDEV
的机制:在/etc/udev/rules.d
文件夹下创建规则文件,sudo vim xxx.rules
,在文件中添加内容SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0666"
,再重新拔插USB
设备(如果是手机还需要打开手机的开发者模式,打开USB
调试,在手机上确认手机调试模式)udev
是一个设备管理工具,udev
以守护进程的形式运行,通过侦听内核发出来的uevent
来管理/dev
目录下的设备文件。udev
在用户空间运行,而不在内核空间运行。它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev
目录下。使用udev
后,在/dev
目录下就只包含系统中真正存在的设备
① 守护进程:
Linux Daemon
(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux
系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd
、web
服务器httpd
、邮件服务器sendmail
和数据库服务器mysqld
等。守护进程的名称通常以d
结尾UDEV
守护进程,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等- 守护进程的基本特点:① 生存周期长,一般操作系统启动的时候就启动,关闭的时候关闭;② 守护进程和终端无关联,也就是他们没有控制终端,所以当控制终端退出,也不会导致守护进程退出;③ 守护进程是在后台运行,不会占着终端,终端可以执行其他命令;④ 一个守护进程的父进程是
init
进程,因为它真正的父进程在fork
出子进程后就先于子进程exit
退出了,所以它是一个由init
继承的孤儿进程linux
操作系统本身是有很多的守护进程在默默执行,维持着系统的日常活动。大概 30-50 个
在 Linux 系统中,可以通过命令ps -elf
或ps -efj
来查看守护进程:
ppid = 0
:内核进程,跟随系统启动而启动,生命周期贯穿整个系统;- 在
CMD
列中名字带[]
的,叫内核守护进程- 在
CMD
列中名字不带[]
的普通守护进程(用户集守护进程)init
进程:也是系统守护进程,它负责启动各运行层次特定的系统服务,所以很多进程的PPID
是init
,也负责收养孤儿进程
② 守护进程开发:
#include <unistd.h>int daemon(int nochdir, int noclose);
返回值: | 成功返回 0,失败返回 -1 |
---|---|
nochdir 参数: | 为0 时表示将当前目录更改至/ |
noclose 参数: | 为0 时表示将标准输入、标准输出、标准错误重定向至/dev/null |
解析:由于守护进程跟随系统启动而启动,它不依托于终端,它的工作目录、启动者可能是root
用户,不与应用层普通用户产生联系,所以要将工作目录切换至根目录。且因为有此特点,所以不便于操作守护进程,此时可以使用信号的方式来操控该进程
例程timedaemon.c
:
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdbool.h>/*#include <time.h>char *asctime(const struct tm *timeptr); 将 timeptr 结构体表示的时间用字符串的形式表示struct tm *localtime(const time_t *timer); 使用 timer 的值来填充 tm 结构,timer 的值被分解为 tm 结构,并用本地时区表示time_t time(time_t *tloc); 返回自 Unix 纪元 ( January 1 1970 00:00:00 GMT ) 起的当前时间的秒数struct tm{int tm_sec; 秒,范围从 0 到 59int tm_min; 分,范围从 0 到 59int tm_hour; 小时,范围从 0 到 23int tm_mday; 一月中的第几天,范围从 1 到 31int tm_mon; 月份,范围从 0 到 11int tm_year; 自 1900 起的年数int tm_wday; 一周中的第几天,范围从 0 到 6int tm_yday; 一年中的第几天,范围从 0 到 365int tm_isdst; 夏令时
};*/static bool flag = true;void handler(int sig) // 信号处理函数
{printf("I got a signal %d\nI'm quitting.\n", sig);flag = false;
}int main()
{time_t t;int fd;// 创建守护进程if(-1 == daemon(0, 0)){printf("daemon error\n");exit(1);}// 设置信号处理函数struct sigaction act;act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;if(sigaction(SIGQUIT, &act, NULL)){printf("sigaction error.\n");exit(0);}// 进程工作内容while(flag){fd = open("/home/orangepi/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);if(fd == -1){printf("open error\n");}t = time(0); // 获取时间char *buf = asctime(localtime(&t)); // 翻译时间结构体write(fd, buf, strlen(buf));close(fd);sleep(10);}return 0;
}
可以查询到我们创建的守护进程:
设置开机自启动,在/etc/rc.local
文件中添加执行路径:
sudo vi /etc/rc.local
sudo reboot
重启开发板,可以看到我们的守护进程:
③ 守护进程应用:
守护进程使某程序一直运行,防止应用程序崩溃意外退出,例程:
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdbool.h>static bool flag = true;void handler(int sig)
{printf("I got a signal %d\nI'm quitting.\n", sig);flag = false;
}int judMent() // 判断需要守护的进程 xxxx 是否正在运行
{FILE *file;char buffer[128] = {'\0'};char *cmd = "ps -elf |grep xxxx|grep -v grep"; // 查找 xxxx 进程切忽略 grep xxxx 进程file = popen(cmd, "r"); // 保存终端输出fgets(buffer, 128, file); // 获取终端输出if(strstr(buffer, "xxxx") != NULL){return 0;}else{return -1;}printf("BUFFER:%s\n",buffer);
}int main()
{time_t t;int fd;//创建守护进程if(-1 == daemon(0, 0)){printf("daemon error\n");exit(1);}//设置信号处理函数struct sigaction act;act.sa_handler = handler;sigemptyset(&act.sa_mask);act.sa_flags = 0;if(sigaction(SIGQUIT, &act, NULL)){printf("sigaction error.\n");exit(0);}//进程工作内容while(flag){if( judMent() == -1){system("/home/orangepi/xxxx &");// 启动程序,& 号表示运行为后台进程}sleep(2);}return 0;
}
并设置开机启动:
编辑/etc/rc.local
文件:sudo vi /etc/rc.local
添加以下字段:
/home/orangepi/xDaemon
/home/orangepi/xxxx &exit 0
④ UDEV 的配置文件:
- 规则文件是
udev
里最重要的部分,默认是存放在/etc/udev/rules.d/
下,所有的规则文件必须以rules
为后缀名- 例如下面一个简单的规则:
KERNEL=="sda", NAME="my_root_disk", MODE="0660"
,KERNEL 是匹配键,NAME 和 MODE 是赋值键。这条规则的意思是:如果有一个设备的内核名称为sda
,则该条件生效,并执行后面的赋值:在/dev
下产生一个名为my_root_disk
的设备文件,并把设备文件的权限设为0660
- 当我们接入 USB 设备后,可以通过
dmesg
来查看相应的信息,可以看到new high-speed USB device
下的number 28
:
相应地,在/dev/bus/
总线下的usb
设备下的001
生成了028
:
我们可以使用udevadm info --attribute-walk --name=/dev/设备名字
指令查看更多消息:udevadm info --attribute-walk --name=/dev/bus/usb/001/028
,我们可以通过这些信息来进行规则匹配,如:
我们可以将 4、Linux 的热拔插 UDEV 机制 的概述中在/etc/udev/rules.d/
下创建的规则文件的字段改写为:
SUBSYSTEM=="usb", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="107e", MODE="0666"
,依然适用
udev
规则的匹配键:
ACTION
: 事件(uevent)的行为,例如:add(添加设备)、remove(删除设备);
KERNEL
: 内核设备名称,例如:sda,cdrom;
DEVPATH
: 设备的 devpath 路径;
SUBSYSTEM
: 设备的子系统名称,例如:sda 的系统为 block;
BUS
: 设备在 devpath 里的总线名称,例如:usb;
DRIVER
: 设备在 devpath 的设备驱动名称,例如:ide-cdrom;
ID
: 设备在 devpath 里的识别号;
SYSFS{filename}
: 设备的 devpath 路径下,设备的属性文件 “filename” 里的内容;
ENV{key}
: 环境变量,在一条规则中,可以设定最多五条环境变量的 匹配键;
PROGRAM
: 调用外部命令;
RESULT
: 外部命令 PROGRAM 的返回结果
⑤ UDEV 自动挂载 U 盘:
在/etc/udev/rules.d/
添加规则文件usbflashdisk.rules
,内容如下:
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", RUN{program}+="/bin/mkdir /media/%k", RUN{program}+="/usr/bin/systemd-mount --no-block --collect $devnode /media/%k"
使用dmesg
查看,挂载到的目录(/media/sda/
或/media/sdb
下):
可以看到,挂载到了/media/sda1
下
三、智能垃圾分类垃圾桶项目
1、功能需求:
- 语音控制开启垃圾分类识别,并触发垃圾桶的开关盖
Sockect
发送指令远程控制开启垃圾分类识别,并触发垃圾桶的开关盖- 图像识别垃圾分类功能
- 语音播报垃圾类型
OLED
显示垃圾物品类型- 根据垃圾类型开关不同类型垃圾桶
- 图像处理使用阿里
SDK
,支持Python
和Java
接口
2、安装 python 环境(需要安装 python3.9 或以上版本):
3、C 语言调用 Python:
① 安装 libpython3-dev 依赖包:
先检查是否安装了libpython3
的dev
依赖包:
dpkg -l | grep libpython3 | grep dev
更新软件包,并安装libpython3.10
的dev
依赖包:
sudo apt-get update
sudo apt install libpython3.10-dev
确认安装是否完成:
dpkg -l | grep libpython3 | grep dev
② 直接调用 python 语句:
一个简单的simple.c
例子:
#include "Python.h"int main()
{Py_Initialize(); // 初始化PyRun_SimpleString("print ('funny')");Py_Finalize(); //释放资源return 0;
}
编译:
gcc simple.c -o simple -I /usr/include/python3.10 -l python3.10
解析:
Python.h
头文件,是包含 Python API 的头文件,用于访问 Python 对象和函数- 程序开始时使用
Py_Initialize()
函数初始化 Python 解释器,这样可以在 C 程序中执行 Python 代码int PyRun_SimpleString(const char *command);
函数说明:
传递一个字符串作为参数,表示要执行的 Python 代码,如print ('funny')
这是针对下面PyRun_SimpleStringFlags()
的简化版接口,将PyCompilerFlags*
参数设为NULL
,传参就是 python 执行语句- 在程序结束时使用
Py_Finalize()
函数关闭 Python 解释器,并释放资源
③ 调用无参 python 函数:
一般调用的流程如下:
1、包含Python.h
头文件,以便使用 Python API
2、使用void Py_Initialize()
初始化 Python 解释器
3、使用PyObject *PyImport_ImportModule(const char *name)
和PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)
获取sys.path
对象,并利用int PyList_Append(PyObject *list, PyObject *item)
将当前路径.
添加到sys.path
中,以便加载当前的 Python 模块 ( Python 文件即 python 模块)
4、使用PyObject *PyImport_ImportModule(const char *name)
函数导入 Python 模块,并判断是否有出错
5、使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)
函数获取 Python 函数对象,并判断是否有出错
6、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)
函数调用 Python 函数,并获取返回值
7、使用void Py_DECREF(PyObject *o)
函数释放所有引用的 Python 对象
8、结束时调用void Py_Finalize()
函数关闭 Python 解释器
相关的函数参数说明参考网站,🔗点击这里
欲要调用以下nopara.py
模块的say_funny
无参无返回值函数:
def say_funny():print('funny')
示例:
#include <Python.h>int main()
{Py_Initialize(); // 初始化// 将当前路径添加到 sys.path 中PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString(".")); // PyUnicode_FromString() 将 c 语言的字符串转为 python 的字符串// 导入 nopara 模块PyObject *pModule = PyImport_ImportModule("nopara");if (!pModule){PyErr_Print(); // python 中打印错误信息printf("ERROR: failed to load nopara.py\n");return 1;}// 获取 say_funny 函数对象PyObject *pFunc = PyObject_GetAttrString(pModule, "say_funny");if (!pFunc || !PyCallable_Check(pFunc)){PyErr_Print(); // python 中打印错误信息printf("ERROR: function say_funny not found or not callable\n");return 1;}// 调用 say_funny 函数并获取返回值PyObject *pValue = PyObject_CallObject(pFunc, NULL); // 无参,置 NULLif (!pValue){PyErr_Print(); // python 中打印错误信息printf("ERROR: function call failed\n");return 1;}// 释放所有引用的 Python 对象,最后获取的 pValue 最先释放Py_DECREF(pValue);Py_DECREF(pFunc);Py_DECREF(pModule);// 关闭Python解释器Py_Finalize();return 0;
}
编译:
gcc nopara.c -o nopara -I /usr/include/python3.10 -l python3.10
运行结果:
funny
④ 调用有参 python 函数:
一般调用的流程如下:
1、包含Python.h
头文件,以便使用 Python API
2、使用void Py_Initialize()
初始化 Python 解释器
3、使用PyObject *PyImport_ImportModule(const char *name)
和PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)
获取sys.path
对象,并利用int PyList_Append(PyObject *list, PyObject *item)
将当前路径.
添加到sys.path
中,以便加载当前的 Python 模块 ( Python 文件即 python 模块)
4、使用PyObject *PyImport_ImportModule(const char *name)
函数导入 Python 模块,并判断是否有出错
5、使用PyObject *PyObject_GetAttrString(PyObject *o, const char *attr_name)
函数获取 Python 函数对象,并判断是否有出错
6、使用PyObject *Py_BuildValue(const char *format, ...)
函数创建一个 python 元组,将 C 类型的数据结构转换成 Python 对象,作为 Python 函数的参数,没有参数不需要调用
参数format
对应的Python
、C/C++
类型如下:
下面PyArg_Parse()
函数的format
参数同理
7、使用PyObject *PyObject_CallObject(PyObject *callable, PyObject *args)
函数调用 Python 函数,并获取返回值
8、使用int PyArg_Parse(PyObject *args, const char *format, ...)
函数将返回值转换为 C 类型,并判断是否有出错,没有返回值时不需要调用
9、使用void Py_DECREF(PyObject *o)
函数释放所有引用的 Python 对象
10、结束时调用void Py_Finalize()
函数关闭 Python 解释器
相关的函数参数说明参考网站,🔗点击这里
欲要调用以下para.py
模块的say_funny
有参有返回值函数:
def say_funny(s):print(s, 'is funny')return s
示例:
#include <Python.h>int main()
{Py_Initialize(); // 初始化// 将当前路径添加到 sys.path 中PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString(".")); // PyUnicode_FromString() 将 c 语言的字符串转为 python 的字符串// 导入 nopara 模块PyObject *pModule = PyImport_ImportModule("para");if (!pModule){PyErr_Print(); // python 中打印错误信息printf("ERROR: failed to load para.py\n");return 1;}// 获取 say_funny 函数对象PyObject *pFunc = PyObject_GetAttrString(pModule, "say_funny");if (!pFunc || !PyCallable_Check(pFunc)){PyErr_Print(); // python 中打印错误信息printf("ERROR: function say_funny not found or not callable\n");return 1;}// 将 C 类型的数据结构转换成 Python 对象char *str = "Embedded";PyObject *pArgs = Py_BuildValue("(s)", str); // () 代表元组// 调用 say_funny 函数并获取返回值PyObject *pValue = PyObject_CallObject(pFunc, pArgs); // 同时传参if (!pValue){PyErr_Print(); // python 中打印错误信息printf("ERROR: function call failed\n");return 1;}// 将返回值转换为 C 类型char *result = NULL;if( !PyArg_Parse(pValue, "s", &result) ){PyErr_Print(); // python 中打印错误信息printf("ERROR: parse failed\n");return 1;}printf("result = %s\n", result);// 释放所有引用的 Python 对象,最后获取的 pValue 最先释放Py_DECREF(pValue);Py_DECREF(pFunc);Py_DECREF(pModule);// 关闭Python解释器Py_Finalize();return 0;
}
编译:
gcc para.c -o para -I /usr/include/python3.10 -l python3.10
运行结果:
Embedded is funny
result = Embedded
4、阿里云垃圾分类识别测试:
① 根据 阿里云官网垃圾分类识别🔗点击这里 的接入指引确认好已完成准备工作,并且在OrangePi
上完成Python SDK
的安装,我们需要安装 图像识别 的SDK
包,根据官网上的命令(注意该命令时效性):
pip install alibabacloud_imagerecog20190930
前提是已经安装了python3-pip
,安装命令:
sudo apt install python3-pip
② 继续根据官网指示,配置环境变量,在<>
位置输入自己的阿里云AccessKey ID
和AccessKey Secret
(注意使用双引号""
,不使用尖括号<>
):
export ALIBABA_CLOUD_ACCESS_KEY_ID=<access_key_id>
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<access_key_secret>
③ 🔗点击这里 拷贝 “文件在本地或可访问的URL” 的示例代码,我们使用场景一的代码部分,注释场景二的部分代码,还要修改本地图片的索引路径:
# -*- 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 RuntimeOptionsconfig = 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',# 访问的域名对应的regionregion_id='cn-shanghai'
)# 场景一:文件在本地
img = open(r'/tmp/ClassifyingRubbish1.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:# 初始化Clientclient = Client(config)response = client.classifying_rubbish_advance(classifying_rubbish_request, runtime)# 获取整体结果print(response.body)
except Exception as error:# 获取整体报错信息print(error)# 获取单个字段print(error.code)
准备好图片放在相应路径,运行:
python3 garbage.py
运行结果:
{'Data': {'Elements': [{'Category': '干垃圾', 'CategoryScore': 0.6612, 'Rubbish': '塑料袋', 'RubbishScore': 0.6612}], 'Sensitive': False}, 'RequestId': '3AD473CA-F6FA-56E6-AA5F-62FD70CB6661'}
5、C 语言调用阿里云 python 接口:
① 添加永久环境变量:
打开:
vi ~/.bashrc
在文件末尾加入添加环境变量的语句:
export ALIBABA_CLOUD_ACCESS_KEY_ID=<access_key_id>
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<access_key_secret>
② python 接口返回值类型检查:
从上面运行的结果我们可以看到,接口返回的结果是一个多层嵌套的字典格式,但是返回的类型是否的dict
类型的数据呢?
我们可以做一个测试,对接口返回的数据类型进行判断,代码修改:
运行结果:
<class 'alibabacloud_imagerecog20190930.models.ClassifyingRubbishResponseBody'>
发现接口返回结果是一个ClassifyingRubbishResponseBody
类型,而不是dict
类型,而是一个类
所以我们接下来需要将ClassifyingRubbishResponseBody
数据类型转化为dict
类型:
- 第一步,我们需要查看安装的 图像识别
Python3 SDK
包安装的路径,我们曾经使用这个安装命令:pip install alibabacloud_imagerecog20190930
,现在我们再执行一次,可以捕捉到:
我们进入该目录cd /home/orangepi/.local/lib/python3.10/site-packages
,然后搜索类型grep -r ClassifyingRubbishResponseBody
:
可以看到,在alibabacloud_imagerecog20190930/models.py
模块中找到了该类,我们进入该模块vi alibabacloud_imagerecog20190930/models.py
,我们发现有一个to_map()
函数(Python 中的Map
(映射)通常指的是字典(dict
)数据类型),我们可以尝试使用该函数,修改代码:
运行结果:<class 'dict'>
- 可以看到,已经成功将
ClassifyingRubbishResponseBody
类型转换为dict
类型
③ 示例:
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 RuntimeOptionsconfig = 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',# 访问的域名对应的regionregion_id='cn-shanghai'
)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 = imgruntime = RuntimeOptions()try:# 初始化Clientclient = 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 '获取失败'if __name__ == "__main__":alibaba_garbage()
garbage.c
文件:
#include <Python.h>int main()
{Py_Initialize(); // 初始化// 将当前路径添加到 sys.path 中PyObject *sys = PyImport_ImportModule("sys");PyObject *path = PyObject_GetAttrString(sys, "path");PyList_Append(path, PyUnicode_FromString(".")); // PyUnicode_FromString() 将 c 语言的字符串转为 python 的字符串// 导入 garbage 模块PyObject *pModule = PyImport_ImportModule("garbage");if (!pModule){PyErr_Print(); // python 中打印错误信息printf("ERROR: failed to load garbage.py\n");return 1;}// 获取 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");return 1;}// 调用 alibaba_garbage 函数并获取返回值PyObject *pValue = PyObject_CallObject(pFunc, NULL); // 无参if (!pValue){PyErr_Print(); // python 中打印错误信息printf("ERROR: function call failed\n");return 1;}// 将返回值转换为 C 类型char *result = NULL;if( !PyArg_Parse(pValue, "s", &result) ){PyErr_Print(); // python 中打印错误信息printf("ERROR: parse failed\n");}printf("result = %s\n", result);// 释放所有引用的 Python 对象,最后获取的 pValue 最先释放Py_DECREF(pValue);Py_DECREF(pFunc);Py_DECREF(pModule);// 关闭Python解释器Py_Finalize();return 0;
}
编译:
gcc garbage.c -o garbage -I /usr/include/python3.10 -l python3.10
运行结果:
干垃圾
result = 干垃圾
6、项目示例:
① 环境准备:
[1] 将语音模块连接在UART5
的位置,参考gpio readall
的引脚说明接线
[2] 在orangepi3.0.6
上确认已经配置开启了uart5
:overlays=uart5
,查看配置文件cat /boot/orangepiEnv.txt
:
[3] 将 USB 摄像头接到香橙派开发板上,同时确认mjpg
服务已经开启
[4] 保留orangepi
用户的环境变量:
方法一:
将/dev/ttyS5
设置为777
权限:
sudo chmod 777 /dev/ttyS5
理由是:代码运行需要打开根目录下的串口驱动文件/dev/ttyS5
,需要sudo
来运行,而我们安装的阿里云SDK
是安装在orangepi
用户下的.local
目录下的,如果我们用sudo
来运行代码,则是使用root
用户运行,无法调用阿里云SDK
,并且会提示我们没有安装阿里云SDK
,所以,我们将/dev/ttyS5
的权限增加,运行代码时则不需要再使用sudo
权限
方法二(推荐):
sudo -E ./main
运行时使用-E
设置,意思为保留当前用户的环境变量
② 补充知识点:
一、TCP 心跳机制解决 Soket 异常断开问题:
- Socket 客户端的断开无非就两种情况:
1、客户端能够发送状态给服务器;正常断开,强制关闭客户端等,客户端能够做出反应。
2、客户端不能发送状态给服务器;突然断网,断电,客户端卡死等,客户端根本没机会做出反应,服务器更不了解客户端状态,导致服务器异常等待。二、为了解决上述问题,引入 TCP 心跳包机制:
- 心跳包的实现,心跳包就是服务器定时向客户端发送查询信息,如果客户端有回应就代表连接正常,类似于 linux 系统的看门狗机制。心跳包的机制有一种方法就是采用
TCP_KEEPALIVE
机制,它是一种用于检测 TCP 连接是否存活的机制,它的原理是:在一定时间内没有数据往来时,发送探测包给对方,如果对方没有响应,就认为连接已经断开。TCP_KEEPALIVE
机制可以通过设置一些参数来调整,如探测时间间隔、探测次数等。- Linux 内核提供了通过
sysctl
命令查看和配置TCP KeepAlive
参数的方法。
查看当前系统的TCP KeepAlive
参数:
sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_probes
sysctl net.ipv4.tcp_keepalive_intvl
修改TCP KeepAlive
参数:
sysctl net.ipv4.tcp_keepalive_time=5
sysctl net.ipv4.tcp_keepalive_probes=3
sysctl net.ipv4.tcp_keepalive_intvl=3
- 但是,如果我们直接修改了操作系统上的这几个参数时间,会影响系统上的其他服务。所以我们不应该修改操作系统的参数,应该在程序代码中实现
三、C 语言实现 TCP KeepAlive 功能:
- 对于 Socket 而言,可以在程序中通过 socket 选项开启
TCP KeepAlive
功能,并配置参数。对应的 Socket 选项分别为SO_KEEPALIVE
、TCP_KEEPIDLE
、TCP_KEEPCNT
、TCP_KEEPINTVL
:
int keepalive = 1;
// 开启 TCP KeepAlive 功能
int keepidle = 5;
// tcp_keepalive_time 5s 内没收到数据开始发送心跳包
int keepcnt = 3;
// tcp_keepalive_probes 发送心跳包最大次数
int keepintvl = 3;
// tcp_keepalive_intvl 每 3s 发送一次心跳包
setsockopt(client_socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof (keepidle));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof (keepcnt));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof (keepintvl));
③ 代码示例:
见文章:香橙派 “智能垃圾分类识别垃圾桶” 代码示例 🔗点此打开
编译:gcc -o garbage *.c -I /usr/include/python3.10 -l python3.10 -lwiringPi -lwiringPiDev -lpthread -lm -lcrypt -lrt