湘潭做网站 活动磐石网络,wordpress 界面设计,机关网站建设方案,个人网站设计源代码#xff08;一#xff09;参考文献
【安卓相关】蓝牙基于Ymodem协议发送bin文件#xff0c;对硬件设备进行升级。 - 简书当Android BLE遇上YModem - 简书 #xff08;二#xff09;收发机制
基于我们具体的需求#xff0c;在原有的基础上加了一下前后的处理。 * MY YMO…一参考文献
【安卓相关】蓝牙基于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 dont 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 dont 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 readyOverridepublic 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 HELLOpackageErrorTimes 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 havent 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协议传输文件。以下是代码的简要总结 常量定义定义了传输步骤HELLO、FILE_NAME、FILE_BODY、EOT、END和一些控制字符ACK、NAK、CAN、ST_C以及MD5校验相关的字符串。 成员变量包括上下文Context、文件路径、文件名、文件MD5值、传输监听器YModemListener、计时器助手TimeOutHelper、文件流线程FileStreamThread、已发送字节数、当前发送的数据包、错误计数等。 构造函数初始化Ymodem对象接受文件路径、文件名、文件MD5值和监听器作为参数。 传输控制方法 start(): 开始传输调用sayHello()方法。stop(): 停止传输重置相关变量释放资源。 接收数据方法 onReceiveData(byte[] respData): 处理从终端接收的数据根据当前传输步骤调用相应的处理方法。 数据发送方法 sayHello(): 发送HELLO包。sendFileName(): 发送文件名包。startSendFileData(): 开始发送文件数据包。sendEOT(): 发送EOT包。sendEND(): 发送END包。 响应处理方法 handleHello(byte[] value): 处理HELLO包的响应。handleFileName(byte[] value): 处理文件名包的响应。handleFileBody(int character): 处理文件数据包的响应。handleEOT(byte[] value): 处理EOT包的响应。handleEnd(byte[] character): 处理END包的响应。handleOthers(int character): 处理其他响应如NAK、CAN。 失败处理方法 handlePackageFail(): 处理数据包发送失败重试发送超过最大重试次数则停止传输。 构建器类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协议的数据打包工具主要功能包括 定义常量 HELLO: 自定义的启动信号字符串。SOH: 表示128字节数据包的头部标志。STX: 表示1024字节数据包的头部标志。EOT: 传输结束标志。CPMEOF: 用于填充未满数据包的字节。 计算CRC16校验 使用CRC16类来计算数据包的CRC校验值。 生成数据包 getYModelHello(): 获取启动信号的字节数组。getFileNamePackage(): 生成包含文件名、文件大小和文件MD5值的数据包。getDataPackage(): 生成带有头部、数据块和CRC校验的数据包并填充不足部分。getEOT(): 获取传输结束数据包。getEnd(): 获取最后一个填充128字节的数据包。getInputStream(): 从资源文件中获取输入流可定制其他来源。 私有辅助方法 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();