当前位置: 首页 > news >正文

YModem在Android上的实现

(一)参考文献


【安卓相关】蓝牙基于Ymodem协议发送bin文件,对硬件设备进行升级。 - 简书
当Android BLE遇上YModem - 简书

(二)收发机制

基于我们具体的需求,在原有的基础上加了一下前后的处理。

 * MY YMODEM IMPLEMTATION* *SENDER: ANDROID APP *------------------------------------------* RECEIVER: BLE DEVICE** HELLO BOOTLOADER ---------------------------------------------->** <---------------------------------------------------------------* C* SOH 00 FF filename0fileSizeInByte0MD5[90] ZERO[38] CRC CRC----->** <---------------------------------------------------------------* ACK C* STX 01 FE data[1024] CRC CRC ---------------------------------->** <---------------------------------------------------------------* ACK* STX 02 FF data[1024] CRC CRC ---------------------------------->** <---------------------------------------------------------------* ACK* ...* ...* <p>* STX 08 F7 data[1000] CPMEOF[24] CRC CRC ----------------------->** <---------------------------------------------------------------* ACK* EOT ----------------------------------------------------------->** <---------------------------------------------------------------* ACK* SOH 00 FF ZERO[128] ------------------------------------------->** <---------------------------------------------------------------* ACK* <---------------------------------------------------------------* MD5_OK

(三)核心代码模块

首先梳理一下它应该具有哪些模块:

  • 协议的核心实现
    主要是负责数据传输过程中有关协议的部分,如在数据包上加入头,CRC,验证返回的正确性以及超时重发等。
  • 一个协议工具类,封装包数据的提供
  • 一个文件数据的读取模块:它是耗时任务,应该在子线程进行。
  • 各种执行状态的监听

3.1协议的核心实现

/*** Created by leonxtp on 2017/9/16.* Modified by leonxtp on 2017/9/16*/public class Ymodem implements FileStreamThread.DataRaderListener {private static final int STEP_HELLO = 0x00;private static final int STEP_FILE_NAME = 0x01;private static final int STEP_FILE_BODY = 0x02;private static final int STEP_EOT = 0x03;private static final int STEP_END = 0x04;private static int CURR_STEP = STEP_HELLO;private static final byte ACK = 0x06; /* ACKnowlege */private static final byte NAK = 0x15; /* Negative AcKnowlege */private static final byte CAN = 0x18; /* CANcel character */private static final byte ST_C = 'C';private static final String MD5_OK = "MD5_OK";private static final String MD5_ERR = "MD5_ERR";private Context mContext;private String filePath;private String fileNameString = "LPK001_Android";private String fileMd5String = "63e7bb6eed1de3cece411a7e3e8e763b";private YModemListener listener;private TimeOutHelper timerHelper = new TimeOutHelper();private FileStreamThread streamThread;//bytes has been sent of this transmissionprivate int bytesSent = 0;//package data of current sending, used for int case of failprivate byte[] currSending = null;private int packageErrorTimes = 0;private static final int MAX_PACKAGE_SEND_ERROR_TIMES = 5;//the timeout interval for a single packageprivate static final int PACKAGE_TIME_OUT = 6000;/*** Construct of the YModemBLE,you may don't need the fileMD5 checking,remove it** @param filePath       absolute path of the file* @param fileNameString file name for sending to the terminal* @param fileMd5String  md5 for terminal checking after transmission finished* @param listener*/public Ymodem(Context context, String filePath,String fileNameString, String fileMd5String,YModemListener listener) {this.filePath = filePath;this.fileNameString = fileNameString;this.fileMd5String = fileMd5String;this.mContext = context;this.listener = listener;}/*** Start the transmission*/public void start() {sayHello();}/*** Stop the transmission when you don't need it or shut it down in accident*/public void stop() {bytesSent = 0;currSending = null;packageErrorTimes = 0;if (streamThread != null) {streamThread.release();}timerHelper.stopTimer();}/*** Method for the outer caller when received data from the terminal*/public void onReceiveData(byte[] respData) {//Stop the package timertimerHelper.stopTimer();if (respData != null && respData.length > 0) {switch (CURR_STEP) {case STEP_HELLO:handleHello(respData);break;case STEP_FILE_NAME:handleFileName(respData);break;case STEP_FILE_BODY:handleFileBody(respData[0]);break;case STEP_EOT:handleEOT(respData);break;case STEP_END:handleEnd(respData);break;default:break;}} else {L.f("The terminal do responsed something, but received nothing??");}}/*** ==============================================================================* Methods for sending data begin* ==============================================================================*/private void sayHello() {streamThread = new FileStreamThread(mContext, filePath, this);CURR_STEP = STEP_HELLO;L.f("sayHello!!!");byte[] hello = YModemUtil.getYModelHello();if (listener != null) {listener.onDataReady(hello);}}private void sendFileName() {CURR_STEP = STEP_FILE_NAME;L.f("sendFileName");try {int fileByteSize = streamThread.getFileByteSize();byte[] hello = YModemUtil.getFileNamePackage(fileNameString, fileByteSize, fileMd5String);if (listener != null) {listener.onDataReady(hello);}} catch (IOException e) {e.printStackTrace();}}private void startSendFileData() {CURR_STEP = STEP_FILE_BODY;L.f("startSendFileData");streamThread.start();}//Callback from the data reading thread when a data package is ready@Overridepublic void onDataReady(byte[] data) {if (listener != null) {currSending = data;//Start the timer, it will be cancelled when reponse received,// or trigger the timeout and resend the current package datatimerHelper.startTimer(timeoutListener, PACKAGE_TIME_OUT);listener.onDataReady(data);}}private void sendEOT() {CURR_STEP = STEP_EOT;L.f("sendEOT");if (listener != null) {listener.onDataReady(YModemUtil.getEOT());}}private void sendEND() {CURR_STEP = STEP_END;L.f("sendEND");if (listener != null) {try {listener.onDataReady(YModemUtil.getEnd());} catch (IOException e) {e.printStackTrace();}}}/*** ==============================================================================* Method for handling the response of a package* ==============================================================================*/private void handleHello(byte[] value) {int character = value[0];if (character == ST_C) {//Receive "C" for "HELLO"packageErrorTimes = 0;sendFileName();} else {handleOthers(character);}}//The file name package was responsedprivate void handleFileName(byte[] value) {if (value.length == 2 && value[0] == ACK && value[1] == ST_C) {//Receive 'ACK C' for file namepackageErrorTimes = 0;startSendFileData();} else if (value[0] == ST_C) {//Receive 'C' for file name, this package should be resenthandlePackageFail();} else {handleOthers(value[0]);}}private void handleFileBody(int character) {if (character == ACK) {//Receive ACK for file datapackageErrorTimes = 0;bytesSent += currSending.length;try {if (listener != null) {listener.onProgress(bytesSent, streamThread.getFileByteSize());}} catch (IOException e) {e.printStackTrace();}streamThread.keepReading();} else if (character == ST_C) {//Receive C for file data, the ymodem cannot handle this circumstance, transmission failed...if (listener != null) {listener.onFailed();}} else {handleOthers(character);}}private void handleEOT(byte[] value) {if (value[0] == ACK) {packageErrorTimes = 0;sendEND();} else if (value[0] == ST_C) {//As we haven't received ACK, we should resend EOThandlePackageFail();} else {handleOthers(value[0]);}}private void handleEnd(byte[] character) {if (character[0] == ACK) {//The last ACK represents that the transmission has been finished, but we should validate the filepackageErrorTimes = 0;} else if ((new String(character)).equals(MD5_OK)) {//The file data has been checked,Well Done!stop();if (listener != null) {listener.onSuccess();}} else if ((new String(character)).equals(MD5_ERR)) {//Oops...Transmission Failed...stop();if (listener != null) {listener.onFailed();}} else {handleOthers(character[0]);}}private void handleOthers(int character) {if (character == NAK) {//We need to resend this package as the terminal failed when checking the crchandlePackageFail();} else if (character == CAN) {//Some big problem occurred, transmission failed...stop();}}//Handle a failed package data ,resend it up to MAX_PACKAGE_SEND_ERROR_TIMES times.//If still failed, then the transmission failed.private void handlePackageFail() {packageErrorTimes++;if (packageErrorTimes < MAX_PACKAGE_SEND_ERROR_TIMES) {if (listener != null) {listener.onDataReady(currSending);}} else {//Still, we stop the transmission, release the resourcesstop();if (listener != null) {listener.onFailed();}}}/* The InputStream data reading thread was done */@Overridepublic void onFinish() {sendEOT();}//The timeout listenerprivate TimeOutHelper.ITimeOut timeoutListener = new TimeOutHelper.ITimeOut() {@Overridepublic void onTimeOut() {if (currSending != null) {handlePackageFail();}}};public static class Builder {private Context context;private String filePath;private String fileNameString;private String fileMd5String;private YModemListener listener;public Builder with(Context context) {this.context = context;return this;}public Builder filePath(String filePath) {this.filePath = filePath;return this;}public Builder fileName(String fileName) {this.fileNameString = fileName;return this;}public Builder checkMd5(String fileMd5String) {this.fileMd5String = fileMd5String;return this;}public Builder callback(YModemListener listener) {this.listener = listener;return this;}public Ymodem build() {return new Ymodem(context, filePath, fileNameString, fileMd5String, listener);}}}

该代码实现了一个Ymodem类,用于通过Ymodem协议传输文件。以下是代码的简要总结:

  1. 常量定义:定义了传输步骤(HELLO、FILE_NAME、FILE_BODY、EOT、END)和一些控制字符(ACK、NAK、CAN、ST_C)以及MD5校验相关的字符串。

  2. 成员变量:包括上下文(Context)、文件路径、文件名、文件MD5值、传输监听器(YModemListener)、计时器助手(TimeOutHelper)、文件流线程(FileStreamThread)、已发送字节数、当前发送的数据包、错误计数等。

  3. 构造函数:初始化Ymodem对象,接受文件路径、文件名、文件MD5值和监听器作为参数。

  4. 传输控制方法

    • start(): 开始传输,调用sayHello()方法。
    • stop(): 停止传输,重置相关变量,释放资源。
  5. 接收数据方法

    • onReceiveData(byte[] respData): 处理从终端接收的数据,根据当前传输步骤调用相应的处理方法。
  6. 数据发送方法

    • sayHello(): 发送HELLO包。
    • sendFileName(): 发送文件名包。
    • startSendFileData(): 开始发送文件数据包。
    • sendEOT(): 发送EOT包。
    • sendEND(): 发送END包。
  7. 响应处理方法

    • handleHello(byte[] value): 处理HELLO包的响应。
    • handleFileName(byte[] value): 处理文件名包的响应。
    • handleFileBody(int character): 处理文件数据包的响应。
    • handleEOT(byte[] value): 处理EOT包的响应。
    • handleEnd(byte[] character): 处理END包的响应。
    • handleOthers(int character): 处理其他响应(如NAK、CAN)。
  8. 失败处理方法

    • handlePackageFail(): 处理数据包发送失败,重试发送,超过最大重试次数则停止传输。
  9. 构建器类Builder类用于方便地创建Ymodem对象,支持链式调用设置参数。

整体来说,该代码实现了一个Ymodem文件传输协议的客户端,通过分步骤发送文件数据,并处理接收端的各种响应,确保文件能够可靠地传输和校验。

3.2协议包工具类

/*** Util for encapsulating data package of ymodem protocol* <p>* Created by leonxtp on 2017/9/16.* Modified by leonxtp on 2017/9/16*/public class YModemUtil {/*This is my concrete ymodem start signal, customise it to your needs*/private static final String HELLO = "HELLO BOOTLOADER";private static final byte SOH = 0x01; /* Start Of Header with data size :128*/private static final byte STX = 0x02; /* Start Of Header with data size : 1024*/private static final byte EOT = 0x04; /* End Of Transmission */private static final byte CPMEOF = 0x1A;/* Fill the last package if not long enough */private static CRC16 crc16 = new CRC16();/*** Get the first package data for hello with a terminal*/public static byte[] getYModelHello() {return HELLO.getBytes();}/*** Get the file name package data** @param fileNameString file name in String* @param fileByteSize   file byte size of int value* @param fileMd5String  the md5 of the file in String*/public static byte[] getFileNamePackage(String fileNameString,int fileByteSize,String fileMd5String) throws IOException {byte seperator = 0x0;String fileSize = fileByteSize + "";byte[] byteFileSize = fileSize.getBytes();byte[] fileNameBytes1 = concat(fileNameString.getBytes(),new byte[]{seperator},byteFileSize);byte[] fileNameBytes2 = Arrays.copyOf(concat(fileNameBytes1,new byte[]{seperator},fileMd5String.getBytes()), 128);byte seq = 0x00;return getDataPackage(fileNameBytes2, 128, seq);}/*** Get a encapsulated package data block** @param block      byte data array* @param dataLength the actual content length in the block without 0 filled in it.* @param sequence   the package serial number* @return a encapsulated package data block*/public static byte[] getDataPackage(byte[] block, int dataLength, byte sequence) throws IOException {byte[] header = getDataHeader(sequence, block.length == 1024 ? STX : SOH);//The last package, fill CPMEOF if the dataLength is not sufficientif (dataLength < block.length) {int startFil = dataLength;while (startFil < block.length) {block[startFil] = CPMEOF;startFil++;}}//We should use short size when writing into the data package as it only needs 2 bytesshort crc = (short) crc16.calcCRC(block);ByteArrayOutputStream baos = new ByteArrayOutputStream();DataOutputStream dos = new DataOutputStream(baos);dos.writeShort(crc);dos.close();byte[] crcBytes = baos.toByteArray();return concat(header, block, crcBytes);}/*** Get the EOT package*/public static byte[] getEOT() {return new byte[]{EOT};}/*** Get the Last package*/public static byte[] getEnd() throws IOException {byte seq = 0x00;return getDataPackage(new byte[128], 128, seq);}/*** Get InputStream from Assets, you can customize it from the other sources** @param fileAbsolutePath absolute path of the file in asstes*/public static InputStream getInputStream(Context context, String fileAbsolutePath) throws IOException {return new InputStreamSource().getStream(context, fileAbsolutePath);}private static byte[] getDataHeader(byte sequence, byte start) {//The serial number of the package increases Cyclically up to 256byte modSequence = (byte) (sequence % 0x256);byte complementSeq = (byte) ~modSequence;return concat(new byte[]{start},new byte[]{modSequence},new byte[]{complementSeq});}private static byte[] concat(byte[] a, byte[] b, byte[] c) {int aLen = a.length;int bLen = b.length;int cLen = c.length;byte[] concated = new byte[aLen + bLen + cLen];System.arraycopy(a, 0, concated, 0, aLen);System.arraycopy(b, 0, concated, aLen, bLen);System.arraycopy(c, 0, concated, aLen + bLen, cLen);return concated;}
}

这段代码实现了YModem协议的数据打包工具,主要功能包括:

  1. 定义常量

    • HELLO: 自定义的启动信号字符串。
    • SOH: 表示128字节数据包的头部标志。
    • STX: 表示1024字节数据包的头部标志。
    • EOT: 传输结束标志。
    • CPMEOF: 用于填充未满数据包的字节。
  2. 计算CRC16校验

    • 使用CRC16类来计算数据包的CRC校验值。
  3. 生成数据包

    • getYModelHello(): 获取启动信号的字节数组。
    • getFileNamePackage(): 生成包含文件名、文件大小和文件MD5值的数据包。
    • getDataPackage(): 生成带有头部、数据块和CRC校验的数据包,并填充不足部分。
    • getEOT(): 获取传输结束数据包。
    • getEnd(): 获取最后一个填充128字节的数据包。
    • getInputStream(): 从资源文件中获取输入流(可定制其他来源)。
  4. 私有辅助方法

    • getDataHeader(): 生成数据包头部,包括起始字节、序列号及其补码。
    • concat(): 连接多个字节数组。

该工具类主要用于在YModem协议传输过程中打包和封装数据。

3.3文件数据读取类

/*** Thread for reading input Stream and encapsulating into a ymodem package* <p>* Created by leonxtp on 2017/9/16.* Modified by leonxtp on 2017/9/16*/public class FileStreamThread extends Thread {private Context mContext;private InputStream inputStream = null;private DataRaderListener listener;private String filePath;private AtomicBoolean isDataAcknowledged = new AtomicBoolean(false);private boolean isKeepRunning = false;private int fileByteSize = 0;public FileStreamThread(Context mContext, String filePath, DataRaderListener listener) {this.mContext = mContext;this.filePath = filePath;this.listener = listener;}public int getFileByteSize() throws IOException {if (fileByteSize == 0 || inputStream == null) {initStream();}return fileByteSize;}@Overridepublic void run() {try {prepareData();} catch (IOException e) {e.printStackTrace();}}private void prepareData() throws IOException {initStream();byte[] block = new byte[1024];int dataLength;byte blockSequence = 1;//The data package of a file is actually started from 1isDataAcknowledged.set(true);isKeepRunning = true;while (isKeepRunning) {if (!isDataAcknowledged.get()) {try {//We need to sleep for a while as the sending 1024 bytes data from ble would take several seconds//In my circumstances, this can be up to 3 seconds.Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}continue;}if ((dataLength = inputStream.read(block)) == -1) {L.f("The file data has all been read...");if (listener != null) {onStop();listener.onFinish();}break;}byte[] packige = YModemUtil.getDataPackage(block, dataLength, blockSequence);if (listener != null) {listener.onDataReady(packige);}blockSequence++;isDataAcknowledged.set(false);}}/*** When received response from the terminal ,we should keep the thread keep going*/public void keepReading() {isDataAcknowledged.set(true);}public void release() {onStop();listener = null;}private void onStop() {isKeepRunning = false;isDataAcknowledged.set(false);fileByteSize = 0;onReadFinished();}private void initStream() {if (inputStream == null) {try {inputStream = YModemUtil.getInputStream(mContext, filePath);fileByteSize = inputStream.available();} catch (IOException e) {e.printStackTrace();}}}private void onReadFinished() {if (inputStream != null) {try {inputStream.close();inputStream = null;} catch (IOException e) {e.printStackTrace();}}}public interface DataRaderListener {void onDataReady(byte[] data);void onFinish();}}

这段代码定义了一个名为`FileStreamThread`的类,该类继承自`Thread`,用于读取输入流并将其封装成 Ymodem 数据包。主要功能如下:

1. **构造函数**:初始化线程,接受`Context`、文件路径和`DataRaderListener`作为参数。
2. **获取文件大小**:通过`getFileByteSize`方法获取文件的字节大小。
3. **线程运行**:重写`run`方法,在`run`方法中调用`prepareData`方法读取文件数据并封装成 Ymodem 数据包。
4. **准备数据**:`prepareData`方法中:
   - 初始化输入流。
   - 读取文件数据,按块读取,并封装成 Ymodem 数据包。
   - 调用监听器`listener`的方法将封装好的数据包发送出去。
5. **继续读取**:当收到终端响应时,调用`keepReading`方法继续读取数据。
6. **释放资源**:`release`方法停止线程,释放资源。
7. **初始化流**:`initStream`方法初始化输入流并获取文件大小。
8. **读取完成**:`onReadFinished`方法关闭输入流并清理资源。
9. **监听器接口**:`DataRaderListener`接口用于处理数据包准备好和读取完成的事件。

总的来说,该类用于读取文件数据并将其按块封装成 Ymodem 数据包,通过监听器接口将数据包传递给外部处理。

3.4各种状态监听接口

/*** Listener of the transmission process*/
public interface YModemListener {/* the data package has been encapsulated */void onDataReady(byte[] data);/*just the file data progress*/void onProgress(int currentSent, int total);/* the file has been correctly sent to the terminal */void onSuccess();/* the task has failed with several remedial measures like retrying some times*/void onFailed();}

(四)具体使用步骤:
 

初始化

        ymodem = new Ymodem.Builder().with(this).filePath("assets://demo.bin").fileName("demo.bin").checkMd5("lsfjlhoiiw121241l241lgljaf").callback(new YModemListener() {@Overridepublic void onDataReady(byte[] data) {//send this data[] to your ble component here...}@Overridepublic void onProgress(int currentSent, int total) {//the progress of the file data has transmitted}@Overridepublic void onSuccess() {//we are well done with md5 checked}@Overridepublic void onFailed() {//the task has failed for several times of trying}}).build();ymodem.start();

开始传输

ymodem.start();

当接收到设备响应

ymodem.onReceiveData(data);

停止

ymodem.stop();

相关文章:

YModem在Android上的实现

&#xff08;一&#xff09;参考文献 【安卓相关】蓝牙基于Ymodem协议发送bin文件&#xff0c;对硬件设备进行升级。 - 简书当Android BLE遇上YModem - 简书 &#xff08;二&#xff09;收发机制 基于我们具体的需求&#xff0c;在原有的基础上加了一下前后的处理。 * MY YMO…...

循环练习题

代码&#xff1a; public static void main(String[] args) { for (char c1a;c1<z;c1){System.out.print(" "c1); }System.out.println();for (char c2Z;c2>A;c2--){System.out.print(" "c2);}} 结果为&#xff1a;...

Seata解决分布式事务

我举的例子是&#xff1a;在网上购物时&#xff0c;我们支付后&#xff0c;订单微服务会更新订单状态&#xff0c;同时会远程调用购物车微服务清空购物车&#xff0c;和调用商品微服务完成商品库存减一。 我们曾经说的事务是只能在本微服务完成回滚&#xff0c;意思就是如果过…...

C语言编译报错error: expected specifier-qualifier-list before

C语言编译报错 error: storage class specified for parameter error: expected specifier-qualifier-list before 原因&#xff1a; 报错信息 "expected specifier-qualifier-list" 通常表示编译器期望在某个地方出现类型指定列表&#xff0c;但却没有找到。这通常…...

无缝协作:如何实现VMware与Ubuntu虚拟机的剪切板共享!

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 剪贴板共享 📒📝 VMware设置📝 安装VMware Tools或open-vm-tools📝 验证剪贴板共享功能⚓️ 相关链接 🚓️📖 介绍 📖 无缝的剪贴板共享是提高工作效率的关键。在VMware和Ubuntu虚拟机的协同工作中,能够直接在宿…...

linux 进程堆栈分析

1.进程pid jsp -l | grep appName 或 ps -ef | grep appName 2.查看cpu top -c pidps -mp pid-o THREAD,tid,time / top -H -p pid #打印出进程对应的线程id及运行时间timeprintf %x\n 线程id3.查看gc jstat -gcutil | grep pid 500jstat -class pid4.查看进程日志 jsta…...

【续集】Java之父的退休之旅:从软件殿堂到多彩人生的探索

Java之父的退休之旅&#xff1a;从软件殿堂到多彩人生的探索-CSDN博客 四、科技领袖退休后的行业影响 4.1 传承与启迪 Gosling等科技领袖的退休&#xff0c;为行业内部年轻一代提供了更多的发展机会和成长空间。他们的退休不仅意味着权力和责任的交接&#xff0c;更是一种精…...

LVS+Nginx高可用集群---Nginx进阶与实战

1.Nginx中解决跨域问题 两个站点的域名不一样&#xff0c;就会有一个跨域问题。 跨域问题&#xff1a;了解同源策略&#xff1a;协议&#xff0c;域名&#xff0c;端口号都相同&#xff0c;只要有一个不相同那么就是非同源。 CORS全称Cross-Origin Resource Sharing&#xff…...

Appium环境搭建,华为nova8鸿蒙系统(包括环境安装,环境配置)(一)

1.安装代码工具包 appium python client pip install appium-python-client 2.安装JDK 参考链接&#xff1a; antjmeterjenkins从0实现持续集成&#xff08;Windows&#xff09;-CSDN博客 3.下载并安卓SDK 下载地址&#xff1a;AndroidDevTools - Android开发工具 Android…...

【React】React18 Hooks 之 useReducer

目录 useReducer案例1&#xff1a;useReducer不带初始化函数案例2&#xff1a;useReducer带初始化函数注意事项1&#xff1a;dispatch函数不会改变正在运行的代码的状态注意事项2&#xff1a;获取dispatch函数触发后 JavaScript 变量的值注意事项3&#xff1a;触发了reducer&am…...

【cocos creator】2.4.x实现简单3d功能,点击选中,旋转,材质修改,透明材质

demo下载:(待审核) https://download.csdn.net/download/K86338236/89527924 const {ccclass, property } = cc._decorator;const enum box_color {NORMAL = 0,DASHED_LINE = 1,//虚线TRANSLUCENT = 2,//半透明 }@ccclass export default class main extends cc.Component {…...

Android EditText+ListPopupWindow实现可编辑的下拉列表

Android EditTextListPopupWindow实现可编辑的下拉列表 &#x1f4d6;1. 可编辑的下拉列表✅步骤一&#xff1a;准备视图✅步骤二&#xff1a;封装显示方法✅步骤三&#xff1a;获取视图并监听 &#x1f4d6;2. 扩展上下箭头✅步骤一&#xff1a;准备上下箭头icon图标✅步骤二&…...

dify/api/models/task.py文件中的数据表

源码位置&#xff1a;dify/api/models/task.py CeleryTask 表结构 字段英文名数据类型字段中文名字备注idIntegerID自增主键&#xff0c;任务ID序列task_idString任务ID唯一任务标识statusString状态默认值为 PENDINGresultPickleType结果可为空date_doneDateTime完成日期默认…...

hdu物联网硬件实验3 按键和中断

学院 班级 学号 姓名 日期 成绩 实验题目 按键和中断 实验目的 实现闪灯功能转换 硬件原理 无 关键代码及注释 /* Button Turns on and off a light emitting diode(LED) connected to digital pin 13, when pressing a pushbutton attached…...

pytorch通过 tensorboardX 调用 Tensorboard 进行可视化

示例 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transformsfrom tensorboardX import SummaryWriter# 定义神经网络模型 class SimpleCNN(nn.Module):def __init__(self):…...

linux查看目录下的文件夹命令,find 查找某个目录,但是不包括这个目录本身?

linux查看目录下的文件夹命令&#xff0c;find 查找某个目录&#xff0c;但是不包括这个目录本身&#xff1f; Linux中查看目录下的文件夹的命令是使用ls命令。ls命令用于列出指定目录中的文件和文件夹。通过不同的选项可以实现显示详细信息、按照不同的排序方式以及使用不同的…...

单一设备上的 2 级自动驾驶:深入探究 Openpilot 的奥秘

Level 2 Autonomous Driving on a Single Device: Diving into the Devils of Openpilot 单一设备上的 2 级自动驾驶&#xff1a;深入探究 Openpilot 的奥秘 Abstract Equipped with a wide span of sensors, predominant autonomous driving solutions are becoming more m…...

向github远程仓库中push,要求使用token登录

Support for password authentication was removed on August 13, 2021. Please use a personal access token instead. 如上&#xff0c;当向github远程仓库push时&#xff0c;输入github的用户名和密码出现如上错误&#xff0c;要求使用token登录&#xff0c;此时只需要用户…...

最全windows提权总结(建议收藏)

当以低权用户进去一个陌生的windows机器后&#xff0c;无论是提权还是后续做什么&#xff0c;第一步肯定要尽可能的搜集信息。知己知彼&#xff0c;才百战不殆。 常规信息搜集 systeminfo 查询系统信息hostname 主机名net user 查看用户信息netstat -ano|find "3389&quo…...

Could not find Chrome (ver.xxxxx). This can occur if either\n

文章目录 错误解决方法 错误 Could not find Chrome (ver. 119.0.6045.105). This can occur if either\n 1. you did not perform an installation before running the script (e.g. npx puppeteer browsers install chrome) or\n 2. your cache path is incorrectly configu…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...