OrangePi Zero2 全志H616 开发初探

目录:

  • 一、刷机和系统启动
      • 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

加入以下字段,开启uart5i2c3

overlays=uart5 i2c3

如图所示:

在这里插入图片描述

3、登录系统:

利用MobaXterm进行串口登录或SSH方式登录,初次登录应用串口登录或利用USBHTML接入屏幕后登录,默认用户如下:

  • 用户名:orangepi,密码:orangepi
  • 用户名:root,密码:orangepi

修改orangepi用户密码:sudo passwd orangepi

① 串口登录:

使用TTL 转 USB模块连接开发板串口(电脑需要安装ch340驱动),如图所示,然后接入电脑,使用MobaXterm软件进行串口登录,波特率默认为115200MobaXterm连接上串口后,插入开发板电源线,查看串口打印数据,检验刷机是否成功

在这里插入图片描述
② 重启及关机:

  • 重启:sudo reboot
  • 关机:sudo poweroff

③ 网络配置:

  • 扫描周围的WIFI热点:nmcli dev wifi
  • 接入网络:nmcli dev wifi connect user password xxxxxxxx    // 比如接入用户名为userwifi,密码为xxxxxxxx
  • 查看IP地址:ip addr show wlan0ifconfig

④ 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,计时器的值实时递减,发送的信号是 SIGALRM
ITIMER_VIRTUAL    // 数值为 1,进程执行时递减计时器的值,发送的信号是 SIGVTALRM
ITIMER_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 msPWM信号,即占空比为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 226pin原理图可知,可用的i2ci2c3
  • 启动linux系统,确认/dev下存在i2c-3的设备节点,可以观察到系统支持I2C-3I2C-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可以看到串口驱动文件有:ttyS0ttyS1ttyS5ttyS0默认作为系统调试信息的串口,也可自行配置为通信串口使用。由Orange Pi Zero 226pin原理图可知,我们想要使用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 PCWindows 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系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogdweb服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。守护进程的名称通常以d结尾
  • UDEV守护进程,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等
  • 守护进程的基本特点:① 生存周期长,一般操作系统启动的时候就启动,关闭的时候关闭;② 守护进程和终端无关联,也就是他们没有控制终端,所以当控制终端退出,也不会导致守护进程退出;③ 守护进程是在后台运行,不会占着终端,终端可以执行其他命令;④ 一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程
  • linux操作系统本身是有很多的守护进程在默默执行,维持着系统的日常活动。大概 30-50 个

在 Linux 系统中,可以通过命令ps -elfps -efj来查看守护进程:

在这里插入图片描述

  • ppid = 0:内核进程,跟随系统启动而启动,生命周期贯穿整个系统;
  • CMD列中名字带[]的,叫内核守护进程
  • CMD列中名字不带[]的普通守护进程(用户集守护进程)
  • init进程:也是系统守护进程,它负责启动各运行层次特定的系统服务,所以很多进程的PPIDinit,也负责收养孤儿进程
② 守护进程开发:
#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 是匹配键,NAMEMODE 是赋值键。这条规则的意思是:如果有一个设备的内核名称为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: 内核设备名称,例如:sdacdrom
    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,支持PythonJava接口

2、安装 python 环境(需要安装 python3.9 或以上版本):

3、C 语言调用 Python:

① 安装 libpython3-dev 依赖包:

先检查是否安装了libpython3dev依赖包:

dpkg -l | grep libpython3 | grep dev

更新软件包,并安装libpython3.10dev依赖包:

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对应的PythonC/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 IDAccessKey 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上确认已经配置开启了uart5overlays=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_KEEPALIVETCP_KEEPIDLETCP_KEEPCNTTCP_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/50070.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【个人亲试最新】WSL2中的Ubuntu 22.04安装Docker

文章目录 Wsl2中的Ubuntu22.04安装Docker其他问题wsl中执行Ubuntu 报错&#xff1a;System has not been booted with systemd as init system (PID 1). Can‘t operate. 参考博客 &#x1f60a;点此到文末惊喜↩︎ Wsl2中的Ubuntu22.04安装Docker 确定为wsl2ubuntu22.04&#…

vue接入google map自定义marker教程

需求背景 由于客户需求&#xff0c;原来系统接入的高德地图&#xff0c;他们不接受&#xff0c;需要换成google地图。然后就各种百度&#xff0c;各种Google&#xff0c;却不能实现。----无语&#xff0c;就连google地图官方的api也是一坨S-H-I。所以才出现这篇文章。 google地…

【Python实战因果推断】56_因果推理概论6

目录 Causal Quantities: An Example Bias Causal Quantities: An Example 让我们看看在我们的商业问题中&#xff0c;你如何定义这些量。首先&#xff0c;你要注意到&#xff0c;你永远无法知道价格削减&#xff08;即促销活动&#xff09;对某个特定商家的确切影响&#xf…

AMEsim液压阀伯德图绘制方法

之前也在液压圈论坛里面发过类似的贴子&#xff0c;具体可以看这个网址&#x1f6aa;&#x1f449;&#xff1a;如何得出说明书里面的伯德图曲线&#xff1f;&#xff0c;回复的人还是比较少&#xff0c;这个方法重要信息是参考百度文库这篇文章&#x1f6aa;&#x1f449;&…

【系统架构设计师】计算机组成与体系结构 ⑯ ( 奇偶校验码 | CRC 循环冗余码 | 海明码 | 模 2 除法 )

文章目录 一、校验码1、校验码由来2、奇偶校验码3、CRC 循环冗余码 ( 重点考点 )4、海明码校验 ( 软考不经常考到 ) 二、CRC 循环冗余码 ( 重点考点 )1、模 2 除法概念2、模 2 除法步骤3、模 2 除法示例4、CRC 循环冗余码示例 15、CRC 循环冗余码示例 2 参考之前的博客 : 【计…

Webshell管理工具:AntSword(中国蚁剑)

中国蚁剑是一款开源的跨平台网站管理工具&#xff0c;它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。 通俗的讲&#xff1a;中国蚁剑是 一 款比菜刀还牛的shell控制端软件。 一、中国蚁剑下载 1. 下载 AntSword-Loader https://github.com/AntSwordP…

面试前端实习常问的关于【ES6新特性】的问题

ES6新特性 日常前端代码开发中&#xff0c;有哪些值得用 ES6 去改进的编程优化或者规范? 常用箭头函数来取代有this指向的函数常用 let 取代 var 命令常用数组/对象的结构赋值来命名变量&#xff08;结构更清晰&#xff0c;语义更明确&#xff0c;可读性更好&#xff09;在长字…

【C语言报错已解决】“Undefined Reference”

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言&#xff1a; 在开发过程中&#xff0c;我们经常会遇到各种编译错误或运行时错误。其中&#xff0c;“Undefined Referenc…

Axure Web端元件库:从Quick UI到500+组件的飞跃

在快速变化的数字世界中&#xff0c;产品设计不仅仅是功能的堆砌&#xff0c;更是用户体验的精心雕琢。原型设计作为产品开发过程中的关键环节&#xff0c;其重要性不言而喻。Axure&#xff0c;作为业界领先的原型设计工具&#xff0c;凭借其强大的交互设计和丰富的功能&#x…

Rce漏洞复习(ctfshow29-50)

Rce漏洞简介思维导图 Web29 代码审计&#xff1a; if(!preg_match("/flag/i", $c)){ eval($c); 传参没有flag&#xff08;大小写都没有出现&#xff09; Payload&#xff1a; ?csystem("ls"); ?csystem("tac *lag.php"); Web30 代码…

文件上传漏洞(ctfshow web151-161)

Web151 F12修改源代码 exts后面png改为php 这样就可以上传php的文件了 Web152&#xff1a; 考点&#xff1a;后端不能单一校验 就是要传图片格式&#xff0c;抓个包传个png的图片 然后bp抓包修改php后缀解析 然后放包 Web153-web156 在php代码中可以使用“{}”代替“[]” …

WPF项目实战视频《二》(主要为prism框架)

14.prism框架知识&#xff08;1&#xff09; 使用在多个平台的MVVM框架 新建WPF项目prismDemo 项目中&#xff1a;工具-NuGet包管理&#xff1a;安装Prism.DryIoc框架 在git中能看Prism的结构和源代码&#xff1a;git链接地址 例如&#xff1a;Prism/src/Wpf/Prism.DryIoc.Wpf…

机器学习 | 回归算法原理——随机梯度下降法

Hi&#xff0c;大家好&#xff0c;我是半亩花海。接着上次的多重回归继续更新《白话机器学习的数学》这本书的学习笔记&#xff0c;在此分享随机梯度下降法这一回归算法原理。本章的回归算法原理还是基于《基于广告费预测点击量》项目&#xff0c;欢迎大家交流学习&#xff01;…

uniapp中出现图片过小会与盒子偏离

结论&#xff1a;在image的父盒子中加上display: flex&#xff0c;原因不清楚 出问题的代码和图片如下&#xff1a; <template><view style" background-color: greenyellow; height: 10rpx;width: 10rpx;"><image :src"imgSrc.seatnull" …

嵌入式C++、MQTT、数据库、Grafana、机器学习( Scikit-learn):智能建筑大数据管理平台(代码示例)

项目概述 智能建筑管理系统&#xff08;Intelligent Building Management System, IBMS&#xff09;是一个集成多种技术的复杂系统&#xff0c;旨在通过智能化手段提升建筑的管理效率、节能效果和居住舒适度。该系统涉及嵌入式系统、物联网&#xff08;IoT&#xff09;、大数据…

光明乳业:以科技赋能品质,引领乳业绿色新未来

近日&#xff0c;光明乳业再次成为行业焦点&#xff0c;其在科技创新与绿色发展方面的卓越表现赢得了广泛赞誉。作为中国乳制品行业的领军企业&#xff0c;光明乳业始终坚守品质至上的原则&#xff0c;不断探索科技创新之路&#xff0c;致力于为消费者提供更高品质、更健康的乳…

vdb:虚拟数据库

将文件虚拟成数据库&#xff0c;序列化写入、反序列化读取、直接读取。

Adobe正通过数字体验改变世界

在当今这个数字化飞速发展的时代&#xff0c;Adobe公司正以其创新的技术和卓越的产品引领着创意设计领域的变革。从Adobe发布的生成式AI工具&#xff08;Adobe Firefly&#xff09;&#xff0c;到Illustrator和Photoshop的新AI功能&#xff0c;再到广受认可的Adobe国际认证&…

GLSL教程 第5章:光照和材质

目录 5.1 光照模型基础 5.2 Phong光照模型 5.3 Blinn-Phong光照模型 5.4 Cook-Torrance光照模型 5.5 Lambert光照模型 5.6 材质属性的深入讲解 小结 光照和材质是计算机图形学中至关重要的元素&#xff0c;它们共同决定了渲染图像的视觉效果。光照模型用于模拟光源与物体…

政安晨【零基础玩转各类开源AI项目】基于Ubuntu系统部署LivePortrait :通过缝合和重定向控制实现高效的肖像动画制作

目录 项目论文介绍 论文中实际开展的工作 非扩散性的肖像动画 基于扩散的肖像动画 方法论 基于Ubuntu的部署实践开始 1. 克隆代码并准备环境 2. 下载预训练权重 3. 推理 快速上手 驱动视频自动裁剪 运动模板制作 4. Gradio 界面 5. 推理速度评估 社区资源 政安…