ESP32Cam人工智能教学20

news2025/6/30 21:20:15

ESP32Cam人工智能教学20

ESP32Cam专用APP

这次我们专门为ESP32Cam量身定制一个手机APP。手机APP是客户端,利用Socket连接ESP32Cam,ESP32Cam成了服务器,实现Socket全双工的数据传输模式,还可以一边显示摄像头图像,一边传送自定义的数据,功能非常齐全。

这一课涉及的内容非常多,涉及的知识非常广,又非常的难。为了避免吓跑了大家,我准备从后面倒着往前讲,由易到难逐层推进。

  • 使用效果展示

我们按照上节课的内容(第十九课 UDP Socket服务器),把ESP32Cam设计成一个视频及数据收发的服务器,通电后开启192.168.1.180:8080端口服务。手机安装了专用的APP,连接ESP32Cam的服务端口,成为客户端。(这时候手机和ESP32Cam在同一个网络中,也就是内网中。当然如果你利用星空隧道或者花生壳进行内网穿透FRP,可以把这个ESP32Cam的服务器端口推到公网中,实现用户的异地查看与操控)。

手机APP界面上面有一个连接按钮和两个文本框,用于连接到ESP32Cam,或者断开连接。一个图片控件,显示摄像头的图片。一个文本框和发送按钮,用于发送文本消息到ESP32Cam。一个文本显示标签控件,用于显示从ESP32Cam发送过来的消息。

我们对这个程序进行了一些优化。当ESP32Cam通电开机后,不断地检查客户端是否连接,堵塞并等待客户端的连接,也就是执行在主程序Loop的循环中。当检测到手机APP已连接在线的时候,会进入一个新的循环while (wclient.connected())当中,这时候,只要手机APP在线,就会一直执行这个收发模式的循环。在这个工作状态的循环体中,如果接收到了来自APP的数据(字符串hello),那么就把接收到的字符串前面加上“MSG”的标志,然后返回给手机APP(返回MSGhello)。同时每隔一定的延时,就发送一张摄像头的图片到手机APP中。

当然,如果我们按动手机APP上面的断开按钮,ESP32Cam会退出工作状态的while (wclient.connected())循环,串口显示“client out”,并重新返回到检测客户端连接的主循环Loop中,等待客户端的下一次连接成功。有了这样的机制后,我们可以随时断开手机APP的连接、或者重连,都不会造成程序崩溃。(当然这个ESP32Cam只能有一个客户端连接,因为这个开发板还是比较弱的,所以我们就设置了只允许一个客户端连接。如果需要允许多个客户端同时连接的话,就需要使用线程了,开发出一个主线程,然后给每个连接的客户端都开发出一个新的独立的工作线程。)

#include <WiFi.h>
#include <WiFiClient.h> 
#include <WiFiServer.h>
#include "esp_camera.h"

// 摄像头引脚 CAMERA_MODEL_WROVER_KIT
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    21
#define SIOD_GPIO_NUM    26
#define SIOC_GPIO_NUM    27

#define Y9_GPIO_NUM      35
#define Y8_GPIO_NUM      34
#define Y7_GPIO_NUM      39
#define Y6_GPIO_NUM      36
#define Y5_GPIO_NUM      19
#define Y4_GPIO_NUM      18
#define Y3_GPIO_NUM       5
#define Y2_GPIO_NUM       4
#define VSYNC_GPIO_NUM   25
#define HREF_GPIO_NUM    23
#define PCLK_GPIO_NUM    22

const char* ssid = "ChinaNet-xxVP";
const char* password = "123456789";

#define CLIENTS_MAX_NUMS  1
WiFiServer server(8080);
WiFiClient serverClients[CLIENTS_MAX_NUMS];

int ac=0;

void setup() {
    Serial.begin(115200);//开启串口

    camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  //config.frame_size = FRAMESIZE_QVGA;   //320 * 240
  config.frame_size = FRAMESIZE_HQVGA;   //240 * 176
  config.pixel_format = PIXFORMAT_JPEG;  // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 10;
  config.fb_count = 1;

  if (psramFound()) {
    config.jpeg_quality = 10;
    config.fb_count = 2;
    config.grab_mode = CAMERA_GRAB_LATEST;

  } else {
    // Limit the frame size when PSRAM is not available
    config.frame_size = FRAMESIZE_HQVGA;
    config.fb_location = CAMERA_FB_IN_DRAM;
  }


  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  WiFi.begin(ssid, password);
  WiFi.setSleep(false);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println(WiFi.localIP());
  delay(500);
  Serial.println("Start tcp server...");
  server.begin();
  server.setNoDelay(true);

}

void loop() {
    if (server.hasClient())
    {
        if (!serverClients[0] || !serverClients[0].connected())
        {
            if (serverClients[0])
            {
                serverClients[0].stop();
            }
            serverClients[0] = server.available();
            ac=0;
        }
    }
    if (serverClients[0] && serverClients[0].connected())
    {  //程序堵塞,等待客户端连接
        WiFiClient wclient = serverClients[0];
        camera_fb_t *fb = NULL;
        size_t _jpg_buf_len = 0;
        uint8_t *_jpg_buf = NULL;
        int bs, bss;
        Serial.println("clinet in");
        while (wclient.connected())
        {
            if (wclient.available())
            {  //当接收到来自客户端的数据时
                while (wclient.available()) 
                {
                    String recv_data = wclient.readStringUntil('\r');
                    Serial.println(recv_data);
                    recv_data = "MSG" + recv_data;
                    wclient.println(recv_data);
                }
            }
            
            fb = esp_camera_fb_get();
            if (fb)
            {  // 从摄像头获取图片的数据
                _jpg_buf_len = fb->len;
                _jpg_buf = fb->buf;
                bs = _jpg_buf_len / 1024;
                bss = _jpg_buf_len % 1024;
                //分成几个数据包进行发送
                for(int j=0; j<bs; j++)
                {
                    wclient.write(_jpg_buf, 1024);
                    for(int i=0; i<1024; i++)
                    {
                        _jpg_buf++;
                    }
                    delay(40); 
                }
                wclient.write(_jpg_buf, bss);
               
                Serial.println(_jpg_buf_len);
            }

            if (fb)
            {
                esp_camera_fb_return(fb);
                fb = NULL;
                _jpg_buf = NULL;
            }
            else if (_jpg_buf)
            {
                free(_jpg_buf);
                _jpg_buf = NULL;
            }
            //这里做个延时,根据你的网络速度调整,在调试时可以延时三四秒发一张
            delay(5000); 
        }
        Serial.println("clinet out");

    }
    delay(1000);
}

  • APPInventor制作手机APP

APPInventor是一款适合小学生学习的手机APP制作软件,采用可视化的积木块程序编辑模式,可以在线的方式制作自己的手机APP。网上提供APPInventor服务的服务器有华南理工、广州服务器、上海服务器等,现在好像只剩下广州服务器是免费的,其他两个都已经VIP收费使用了。

我们访问APPInventor广州服务器官网,用QQ账号注册,登录后就可以免费使用了。

这个程序需要两个自定义的组件支持,其中一个网络连接的组件是我自己编写的,另外一个BASE64编码的组件,是我花了40大洋从上海服务器购买的。为了方便大家,我把这个程序的源代码打包到百度网盘,大家可以去下载

链接:https://pan.baidu.com/s/1_PsfLAwq-NRnC5LCCkBJMw?pwd=k4h9

下载到电脑中的是一个aia后缀名的文件。接下来我们可以登录APPInventor广州服务器,点击顶端的“项目——导入项目”菜单,把这个aia文件上传,就能看到这个APP的源代码了。

手机APP的设计分为前端和后台两个部分,组件设计是前端,是和用户之间面对面的。可以看到,我们把一些文本输入框、按钮控件、图片控件、文字显示控件等,按照一定的顺序在手机屏幕中进行排列。这样就给用户提供了操作的方便,这个前端也就是手机中打开APP看到的样子了。

如果说前端的组件设计提供了漂亮的皮囊,那么后台的程序就是提供了有趣的灵魂。点击窗口右上角的“逻辑设计”可以切换到后台的程序设计界面。在这里我们可以看到按钮1是“连接 / 断开”按钮,通过自定义组件SocketClient1,进行网络连接或端口。右边的三个是当APP接收到来自摄像头的图像时,经过自定义组件进行BASE64解码,并发送到图片框进行显示。当接收到文本信息时(文本信息以MSG这三个字符开头为标志),则显示在文本标签中。当按动按钮2发送消息的按钮时,则调用自定义组件的发送消息程序块。

这时候,我们可以点击顶端的“打包APK——显示二维码”菜单,程序会进行编译,编译结束后,会显示一个二维码。我们用手机中的微信扫一扫,然后用浏览器打开,就能下载到一个APK安装文件,接着会自动安装,安装完成后,我们就可以在手机的桌面上看到这个ESP32Cam的专用APP了。打开这个APP,就能进行前面第一步的ESP32Cam的连接测试了。

我们可以看到,有了APPInventor,制作一个手机APP都是积木化的编程操作了。所以说小学生也能制作自己的手机APP,这下你应该相信了吧。不过我们从这个程序设计界面可以看到,所有的复杂的程序代码,都已经并编译成自定义组件,编程一个个可以拖动拼接的程序积木块了。

  • 打包自定义组件

有些人可能会好奇,究竟这样的程序块,后面隐藏着怎样复杂的程序代码呢?我们要怎样才能制作自己的自定义组件呢。前面程序中,负责网络连接的那个sockclient自定义组件就是由我自己编写代码,并打包完成的。

接下来,我记录一下自己是怎样制作和打包自定义模块的学习过程吧,怕有一天要用了,忘得一干二净了呢。这里很多都参考了下面博主“作业乃身外之物”的这篇博客

App Inventor插件开发(一)配置与测试_app inventor 插件 树树-CSDN博客

(一).  下载、安装、配置环境变量

想要制作和打包自己的自定义模块,需要在电脑中至少安装四个东西:

GIT   我下的是2.26.2

JDK   版本不要超过8,所以我下载的是7u79

ANT  我下的是1.9.14

appinventor   这个是好像是inventor的离线环境吧

note  我是用这个编辑插件的java代码的。

我的电脑是64位win7的(我怕这些软件版本低了,在高版本的Windows会不兼容出错),我会把相关的软件放在百度网盘中,需要的也可以自行去下载。

链接:https://pan.baidu.com/s/1pY9NsuAbPzORsm8Tm7knEQ?pwd=7tp9

https://p.ananas.chaoxing.com/star3/origin/7d19d0404bb69ff361a9b91e86bdc4c6.jpg

    

把GIT安装在C盘默认目录中

JDK安装在C盘默认目录(我怕装在其他盘配置会出问题),装完后,要配置系统环境变量:

新建JAVA_HOME为安装目录如

C:\Program Files\Java\jdk1.7.0_79

新建CLASSPATH为  

      .;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/lib/tools.jar

Path末尾添加(如果原来的末尾没有分号,要补上)

      %JAVA_HOME%/bin;%JAVA_HOME%/jre/bin;

检验JAVA安装是否正确:cmd输入java -version,输出版本信息即为成功。

https://p.ananas.chaoxing.com/star3/origin/75bee947afed9ce8b88a5f17df03aa16.jpg

Ant环境变量配置

新建ANT_HOME  为安装(解压)位置如

      C:\Program Files\ant\apache-ant-1.9.14

CLASSPATH末尾添加   ;%ANT_HOME%\lib;

Path中添加   ;%ANT_HOME%\bin;C:\Program Files\Git\cmd

检验:cmd输入ant,输出如下则为配置成功

https://p.ananas.chaoxing.com/star3/origin/ef7ea04fee6c71e980fcdb62eeb17937.jpg

把appinventor解压到D盘。

(二).  打包 aix  插件

我提供的安装程序中,有一个测试插件,把里面的cn文件夹解压到appinventor的安装文件夹下面的指定文件夹中。

解压出来的就是两个java后缀名的文件,可以用记事本打开查看。

大家千万要把文件的路径做对了,否则出错是没商量的。我们可以看到,这两个文件夹的路径中,src文件夹下面的路径,和这个 java文件的第一行中的路径是一致的。

https://p.ananas.chaoxing.com/star3/origin/c036c286fa331fbedb92270566349db6.jpg

我们在appinventor文件夹中,按住键盘的shift,用鼠标在空白的地方右击,选“在此处打开命令窗口”

然后在打开的cmd命令窗口中,输入ant extensions,按回车。

https://p.ananas.chaoxing.com/star3/origin/b9573a167f71f3b1016d26e0d4a74162.jpg

程序就开始执行打包了,如果出现后面的画面,就说明打包成功了。我们可以在里面的显示的路径下面,找到了已经打包好的 aix 的文件了。

https://p.ananas.chaoxing.com/star3/origin/32d66eb7d309f878a0aef9c643bdc025.jpg

这样,就有了自己的自定义组件了,也就可以把他导入到网上的APP项目中进行使用了。我们在组件前端的编辑界面,选择窗口左侧的积木仓库的底部,选择导入自定义组件按钮,然后选择刚才打包得到的aix文件,这样这个socketclient自定义组件就会显示在仓库的底部了。我么可以把他们拖入到APP的界面中了进行使用了。

四.  编辑 java  程序代码

刚才展示的仅仅是自定义组件的打包过程而已,是已经提供了程序的代码,在此基础上完成打包的动作。

当然,你如果你会 java代码,可以用note这个小程序打开文件进行程序代码的编写了。这样,我们就可以编写专属于自己的自定义模块了。这就是开源设计的优势,要怎样的功能插件都可以自己编写、打包、上传、分享、使用。

https://p.ananas.chaoxing.com/star3/origin/a7abee0349f9211d60827c1fe2115414.jpg


    百度网盘中下载到的是我两年前的JAVA程序代码,主要是对博主“作业乃身外之物”的原来的插件进行修改而成的。他原来的客户端插件中,客户端只有发送,没有从服务端接收的功能块。所以这次我着重修改、增加了客户端的接收部分。

这里有许多的知识点稍微提一下:

(1)在完成Socket连接后,创建一个线程,用于无限循环地监听,是否接收到来自服务器端的(ESP32Cam)的消息。

(2)如果接收到了新的消息(字符串或二进制数据),则转换成字符串,然后利用Message消息的方式,传递给Handle,用于修改UI的内容。这个是JAVA的运行机制,UI界面的一些属性修改,是放在主线程里进行的;而接收监听的子线程,是无法直修改UI的内容,需要把字符串传递给Handle进行修改。

(3)我们对接收到来自ESP32Cam的数据进行识别。接收到的数据无非只有两种,要么是字符串,要么是JPG图片。所以我们在这里做了个界定,ESP32Cam发送过来的字符串,都必须以“MSG”这三个字母为开头作为标志,APP接收到一组数据后,先检测是否MSG开头,如果是则由getMessage函数把字符发送到文本标签中显示。

JPG图片的数据比较大,所以我们采用分段发送(前一课的内容),每次发送1024个字节。JPG图片数据都是以0xFF  0xD8为开头,以0xFF  0xD9为结束。我们在接收数据的时候,会一组一组地接收,并把数据串接在一起。

(4)当图片数据结束时(检测到了结束标志),我们需要把图片的二进制数据byte数组,经过BASE64,转换成一个非常长的字符串,把这个字符串通过getJpg函数传递出来。

(5)经过我的这个自定义网络连接组件,可以负责APP与ESP32Cam之间的连接、数据传递收发。但是也存在一个问题,那就是我这个自定义组件接收到的摄像头图片,最后转化成了一个BASE64字符串。这个字符串是无法之间给图片控件显示的。

经过网上查找,我发现了上海的APPInventor服务器fun123.cn,可以提供BASE64字符串转换成图片的自定义组件。于是我就花了40大洋,注册了一个月的VIP用户,下载到了这个组件。

(7)其实网上也有人提供了一些其他的方法,就是当你接收到了图片数据后,把这些数据写入到手机的临时文件中(图片文件)。然后让图片控件去读取手机临时的图片文件并显示。这种方法是每收到一张图片,就要写一次的临时图片文件。这样频繁的存储操作是很低效的,也是我很反感的。

我虽然已经花钱得到了那个BASE64转图片的组件,但是我也看不到这个组件的源代码,我想这个组件应该不会使用存储临时文件这种拙劣的方法吧,否则怎么对得起我花的钱呢?

package cn.roger.socket;

import com.google.appinventor.components.annotations.*;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.util.*;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import android.graphics.drawable.GradientDrawable;
import android.graphics.Color;
import android.content.res.ColorStateList;
import android.view.View;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.Drawable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;

import java.util.Base64;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

@DesignerComponent(version = 1,
    description = "by Roger Young",
    category = ComponentCategory.EXTENSION,
    nonVisible = true,
    iconName = "images/extension.png")

@SimpleObject(external = true)

public class SocketClient extends AndroidNonvisibleComponent {
    Socket socket = null;
    OutputStream ou = null;
    String buffer = "";
    String geted1;
	Message msg;
    final int CONNECT = 100001;
    final int SENDMESSAGE = 100002;
    final int CLOSE = 100003;
    public SocketClient(ComponentContainer container) {
        super(container.$form());
    }
    public Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
			if(msg.what == 1){
				GetMessage(msg.obj.toString());
			}else if(msg.what == 2){
				GetJpg(msg.obj.toString());
			}
        }
 
    };
    @SimpleFunction(description = "start")
    public void closeConnect(){
        if(socket != null){
            try {
                ou.close();
                socket.close();
                socket = null;
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "关闭";
                myHandler.sendMessage(msg);
            }catch (IOException e) {
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "未知错误";
                myHandler.sendMessage(msg);
            }
        }else{
            GetMessage("连接未创建!");
        }
    }
    @SimpleFunction(description = "start")
    public void sendMessage(String s){
        if(socket != null){
            try {
                ou.write(s.getBytes("utf-8"));
                ou.write("\n".getBytes("utf-8"));
                ou.flush();
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "发送完毕";
                myHandler.sendMessage(msg);
            }catch (IOException e) {
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "未知错误";
                myHandler.sendMessage(msg);
            }
        }else{
            GetMessage("连接未创建!");
        }
    }
    @SimpleFunction(description = "start")
    public void connect(String ip, String port){
        if(socket == null){
			try {
				int po = Integer.valueOf(port);
                socket = new Socket();
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "开始连接";
                myHandler.sendMessage(msg);
                socket.connect(new InetSocketAddress(ip, po), 3000);
                ou = socket.getOutputStream();
				Thread mt = new Thread(){
					public void run() {
                        try {
							InputStream inst = null;
							byte[] buf = new byte[1060];
							byte[] buffer = new byte[30720];
							String msgstr = null;
							int len = 0;
							int pdd = 0;
							
							inst = socket.getInputStream();
                            while(true){
								if(!socket.isInputShutdown()) {
									buf = new byte[1060];
									len = inst.read(buf);
									
									if(buf[0] == 0x4D && buf[1] == 0x53 && buf[2] == 0x47){
										msgstr = null; 
										msgstr = new String(buf, 0, len);
                                        if(msgstr != null){
                                            msg = myHandler.obtainMessage();
											msg.what = 1; // 消息标识
                                            msg.obj = msgstr;
                                            myHandler.sendMessage(msg);
                                        }
									}else if((buf[0] & 0xFF)== 0xFF && (buf[1] & 0xFF) == 0xD8){
									    buffer = new byte[30720];
                                        for(int i=0; i<1024; i++){
                                            buffer[i]=buf[i];    
                                        }
                                		pdd = 1024;								
									}else if((buf[len-2] & 0xFF) == 0xFF && (buf[len-1] & 0xFF) == 0xD9){
                                        for(int i=0; i<len; i++){
                                            buffer[pdd + i]=buf[i];  										
                                        }
										pdd += len;
										//msgstr = String.format("jpg size  %d", pdd); 
                                        String jpgstr = Base64.getEncoder().encodeToString(buffer);
                                        msg = myHandler.obtainMessage();
										msg.what = 2; // 消息标识
                                        msg.obj = jpgstr;
                                        myHandler.sendMessage(msg);								
									}else if(len == 1024){
                                        for(int i=0; i<1024; i++){
                                            buffer[pdd + i]=buf[i];  										
                                        }
										pdd += 1024;
									}
                                } 
                            }
	                    } catch (IOException e) {
                            msg = myHandler.obtainMessage();
							msg.what = 1; // 消息标识
                            msg.obj = "他好像不见了";
                            myHandler.sendMessage(msg);
                            try{socket.close();}catch(Exception e1){}
	                    }
                    }
				};
				mt.start();
                msg = myHandler.obtainMessage();
                msg.obj = "连接成功";
                myHandler.sendMessage(msg);
            } catch (SocketTimeoutException aa) {
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "连接超时";
                myHandler.sendMessage(msg);
                socket = null;
            } catch (IOException e) {
                msg = myHandler.obtainMessage();
				msg.what = 1; // 消息标识
                msg.obj = "未知错误";
                myHandler.sendMessage(msg);
                socket = null;
            }
        }else{
            GetMessage("连接已创建!");
        }
    }

    @SimpleEvent
    public void GetMessage(String s){
        EventDispatcher.dispatchEvent(this, "GetMessage", "\n"+s);
    }

    @SimpleEvent
    public void GetJpg(String jpgstr){
        EventDispatcher.dispatchEvent(this, "GetJpg", "\n"+jpgstr);
    }
	
}

这样几百行的代码,看起来是否有密集恐惧症呢,这些没有一定的基础,确实是读起来很费劲,写起来就更难了,出了BUG调试那就难上加难了。程序猿就是这样一点一点地扣的。

为什么我会修改这段代码呢?因为我以前用eclipse做过手机APP,这些代码其实都是一样一样的。我这段时间也修改了eclipse做的用于连接开发板的手机客户端APP做了修改,也在之前的一些篇章中拿出来用过的。其实也没什么秘密武器,到最后都一点一点的拿出来了。

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

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

相关文章

【Canvas与诗词】北岛诗《献给遇罗克》节选(以太阳的名义...)

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>以太阳的名义</title><style type"text/css">…

基类没有虚析构,即使派生类使用智能指针也一定会内存泄漏

实验 定义一个基类和一个派生类 class Base { public:virtual ~Base() default; };class Derive :public Base { public:std::shared_ptr<int> sp{new int{0},[](int *p){delete p;std::cout << "删除器" << endl;},}; };main 函数执行如下代码…

作业08.21

服务器&#xff1a; #include <myhead.h>#define SER_PORT 6666 #define SER_IP "127.0.0.1"int find_client(int *client_arr, int len, int client) {for(int i0; i<len; i){if(client_arr[i] client){return i;}}return -1; }void remove_client(int *…

Mac 使用vscode 创建vue项目后修改文件提示:权限不足,以超级用户身份重试

项目场景&#xff1a; Mac 安装了全局 vue-cli 插件后&#xff0c;使用webpack 创建vue项目&#xff0c;打开项目&#xff0c;选择信任所有文件夹&#xff0c;然后正常编写代码&#xff0c;并对项目中的文件进行修改&#xff0c;点击保存的时候提示&#xff1a;保存“webpack.…

Vue3+Ts封装类似el-dialog的对话框组件

提供11个字段对dialog组件进行控制&#xff1a; modelValue: 对话框显示隐藏控制, width: 控制对话框的宽度, height&#xff1a;控制对话框的高度, top: 控制对话框个距离顶部的距离, title: 控制对话框的标题, appendToBody: 是否将对话框添加至body, closeOnClickModa…

GX Works2的使用方法

目录&#xff1a; 1、概述 2、硬件连接 3、录入与修改程序 1&#xff09;进入编辑按F2或点击“写入模式”图标 2&#xff09;修改部分元件 3&#xff09;注释 4&#xff09;改变显示触点数 4、软仿真与在线仿真 1&#xff09;软仿真 2&#xff09;在线仿真 5、P…

Linux源码阅读笔记-USB设备驱动架构

总线速度及主机控制器 USB系统架构 USB系统主机端提供为4个引脚的A型接口&#xff0c;USB外围设备通过4个引脚的B型接口和主机端连接。那4个引脚&#xff08;一条电压线VBUS、一条地线GND、一条正方向传输数据的D和一条反方向传输数据的D-线。&#xff09;USB主机和USB设备收发…

2024年翻译神器:探索四款好用的翻译工具!

因为有了一些翻译工具的存在&#xff0c;语言障碍已经渐渐不成问题。接下来就为大家推荐几款好用的翻译工具&#xff01; 福昕在线翻译 链接&#xff1a; https://fanyi.pdf365.cn/ 福昕在线翻译以其简洁的界面和强大的翻译能力&#xff0c;成为用户跨越语言障碍的首选。它…

独立站PrestaShop安装

独立站PrestaShop安装 独立站PrestaShop安装系统需求下载PrestaShop浏览器下载命令行下载 解压PrestaShop创建数据库移动PrestaShop源码到web目录composer安装依赖包nginx配置访问域名进入安装页面选择语言许可协议系统兼容性店铺信息Content of your store系统配置数据库店铺安…

金矢之lian,非你莫蜀:金矢留学携手16所英国大学共襄成都盛会

碧海蓝天的东海岸&#xff0c;盛夏的热烈未尽&#xff0c;草书云山如锦绣的天府之国&#xff0c;初秋的凉意渐起。近一年的忙碌与等待之后&#xff0c;2024年英国秋季入学申请已近尾声&#xff0c;如愿以偿拿到了录取的同学们&#xff0c;欢欣鼓舞的进入申请签证甚至预定行程机…

nginx实例

nginx的由来 Nginx是由1994年毕业于俄罗斯国立莫斯科鲍曼科技大学的同学为俄罗斯rambler.ru公司开发的&#xff0c;开发工作最早从2002年开始&#xff0c;第一次公开发布时间是2004年10月4日&#xff0c;版本号是0.1.0。2019年3月11日F5 与 NGINX达成协议,F5 将收购 NGINX 的所…

NSSCTF联系记录:[SWPUCTF 2021 新生赛]crypto7

题目&#xff1a; 一共有32个字符&#xff0c;且只有数字和字母&#xff0c;可能为md5加密 得到答案

Linux shell编程学习笔记74:sed命令——沧海横流任我行(中)

0 前言 自 60 年代末以来&#xff0c;sed 一直是 Unix 标准工具箱的一部分。 Sed在以下三种情况下特别有用&#xff1a; 编辑太大的文件&#xff0c;无法进行舒适的交互式编辑&#xff1b; 当编辑命令序列过于复杂而无法在交互模式下轻松键入时&#xff0c;可以编辑任何大小的…

【学习笔记】Day 20

一、进度概述 1、机器学习常识12-18&#xff0c;以及相关代码复现 二、详情 12、SVM&#xff08;support vector machines&#xff0c;支持向量机&#xff09; 实际上&#xff0c;支持向量机是一种二分类模型&#xff0c;它将实例的特征向量映射为空间中的一些点&#xff0c;…

[C语言]-基础知识点梳理-文件管理

前言 各位师傅们好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解文件管理的相关知识&#xff0c;也就是常见的 读取&#xff0c;删除一类的操作 文件 为什么要使用文件&#xff1f; 程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&…

基于django的学生作业提交与管理系统,有管理后台,可作为课设使用

在本项目中&#xff0c;我们设计并实现了一个基于Django框架的学生作业提交与管理系统&#xff0c;旨在为教师和学生提供一个高效、便捷的作业管理平台。Django作为一个高效的Web框架&#xff0c;因其强大的功能和灵活的架构&#xff0c;使得本系统能够快速开发并扩展。 系统功…

KeyShot 2024.2:卓越的Mac与Windows 3D渲染与动画制作软件

KeyShot 2024.2作为一款专为Mac和Windows用户设计的3D渲染与动画制作软件&#xff0c;凭借其出色的性能和丰富的功能&#xff0c;在业界树立了新的标杆。这款软件不仅继承了KeyShot系列一贯的实时渲染和动画优势&#xff0c;还在多个方面进行了全面升级和优化&#xff0c;为3D设…

C++:二叉搜索树(binary search tree)

目录 1&#xff1a; 二叉搜索树概念 2&#xff1a;二叉搜索树操作 3&#xff1a;二叉树的模拟实现 4&#xff1a;二叉搜索树的应用 5&#xff1a;二叉搜索树的性能分析 6&#xff1a;复习前中后序遍历 7&#xff1a;二叉树进阶面试题 1&#xff1a; 二叉搜索树概念 二叉搜…

基于协同过滤算法的体育商品推荐系统_t81xg

TOC springboot618基于协同过滤算法的体育商品推荐系统_t81xg--论文 绪 论 近年来&#xff0c;随着互联网科技的进步和发展&#xff0c;人们的生活水平得到了极大的提高&#xff0c;图书的数量也在快速增加&#xff0c;以至于体育商品推荐的数量不断扩大&#xff0c;管理个性…