当前位置:   article > 正文

蓝牙BLE OTA 升级 CRC校验_uniapp 蓝牙ota加crc验证信息

uniapp 蓝牙ota加crc验证信息

CRC

循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。

应用场景

项目中涉及到OTA 蓝牙升级模块,由于BLE 每次发包只能发20字节,多余的需要自己分包处理。我们的上传的文件有100k

所以 CRC使用非常有必要。

内部协议

协议
SOH包序号序号补码数据区CRC
Byte 1Byte 2Byte 3Byte 4-18Byte 19-20

客户端与服务端的通信:

  1. 客户端发送OTA 申请服务端进入文件升级模式
  2. 服务端返回字符C 允许文件上传
  3. 客户端发送第一个包
  4. 服务端返回ACK 表示已经 CRC 校验成功
  5. 服务端返回NAK 表示 CRC 校验失败
  6. 客户端收到ACK 继续传递下一个包
  7. 客户端收到NAK 重新传输失败的包
  8. 在客户端3秒后没有收到 服务端的ACK 或NAK 响应 默认为超时 需要重新发送包
  9. 带客户端计算检测所有包发送完毕后,发送EOT 告诉服务端文件已上传完毕
  10. 服务端收到EOT后 开始CRC校验整个文件,校验通过后返回OK
  11. 客户端收到ok 后 提示用户文件升级成功 退出OTA 模式。

通讯过程

java 业务代码

发送OTA字符:

  1. 打开Android 内部的资源文件 获取文件流 计算传递文件的大小。
  2. 添加包头 OTA 转字节
  3. 添加4字节大小的文件长度
  4. 添加4字节 文件长度反码
  5. 添加2字节 crc 值(文件为参数计算)
  6. 添加2字节的 crc 反码

下面是发送OTA 的代码

  1. /**
  2. * 开始联机 OTA 初始化 获取文件信息属性 更新progress.
  3. */
  4. private void updateUIandSendOTAcomend() {
  5. try {
  6. mInputStream = getActivity().getResources().getAssets().open(filePath == null ? Constance.odd_filename : filePath);
  7. sendInputStream = getActivity().getResources().getAssets().open(filePath == null ? Constance.odd_filename : filePath);
  8. mLength = mInputStream.available();
  9. byte[] totle_bins = readBytes(mInputStream);
  10. mPb_update.setMax(mLength);
  11. String header_ota = GattBluetoothType.OTA.BLE_RESPOND_OTA;
  12. mBytes.clear();
  13. mBytes.add(header_ota.getBytes()); //OTA 字节包
  14. int filesize = mLength;
  15. Log.i(TAG, "updateUIandSendOTAcomend: totlesize=" + mLength);
  16. byte[] filesize_byte = intToByteArray1(filesize);
  17. mBytes.add(filesize_byte); //文件大小
  18. int filesize_contrary = ~(filesize);
  19. byte[] filesize_complement = intToByteArray1(filesize_contrary);
  20. mBytes.add(filesize_complement); //文件大小反码
  21. byte[] crc16_rerify = CodecUtil.crc16Bytes(totle_bins); //crc
  22. byte[] crc16_complement = {(byte) ~crc16_rerify[0], (byte) ~crc16_rerify[1]};
  23. mBytes.add(crc16_rerify); //CRC
  24. mBytes.add(crc16_complement); //CRC 反码
  25. mHead_content_bytes = sysCopy(mBytes);
  26. mPb_update.setProgress(0);
  27. writeData(mHead_content_bytes); //写入BLE
  28. int size = mLength / 1024;
  29. tvAPkSize.setText("apk :" + size + "k");
  30. tvProgress.setText("0%");
  31. Log.i(TAG, "updateUIandSendOTAcomend: ");
  32. /* for (byte[] aByte : mBytes) {
  33. Log.i(TAG, "updateUIandSendOTAcomend: 长度="+aByte.length+" tostring= "+DataSwitchUtils.bytesToHexString(aByte));
  34. }*/
  35. } catch (IOException e) {
  36. e.printStackTrace();
  37. }
  38. }

发送文件:

由于包头包尾 已经占据了5个字节(包头01,包序号,包序号反码,crc 2个字节),所以每次发包内容只是从文件中读取15个字节,100k 计算估计1万次 10分钟左右。(后面再说优化方案4分钟)

bytenum 是15 ,for循环是检测长度不够15的最后一包,没有内容字节数组做0xff处理。例如最后一包只剩下06一个字节了,那15的数组里面的内容就是06 FF FF FF FF FF FF FF FF FF FF FF FF FF FF。如果包序号是00 反码就是11,比如CRC 计算结果为C507 
最后一包:01 00 11 06 FF FF FF FF FF FF FF FF FF FF FF FF FF FF C5 07

下面是发送文件的方法

  1. private boolean setUpdatePackageACK(boolean interruptUPGRADE) {
  2. try {
  3. byte[] send_package = new byte[byteNums];
  4. int n = sendInputStream.read(send_package, 0, byteNums);
  5. if (n > 0) {
  6. if ((mLength - counter) < byteNums) { //最后一包 空至0XFF
  7. int num = mLength - counter;
  8. for (int i = 0; i < send_package.length; i++) {
  9. if (i >= num) {
  10. send_package[i] = (byte) 0xff;
  11. }
  12. }
  13. }
  14. data = send_package.length;
  15. byte[] header_byte = {0x01};
  16. byte[] order = null;
  17. if (interruptUPGRADE) {
  18. order = new byte[]{order_index--};
  19. } else {
  20. order = new byte[]{order_index++};
  21. }
  22. Log.d(TAG, "--------------order_index = " + order_index);
  23. byte[] complement = {(byte) ~order[0]};
  24. byte[] crc16_rerify = CodecUtil.crc16Bytes(send_package);
  25. mBytes.clear();
  26. mBytes.add(header_byte);
  27. mBytes.add(order);
  28. mBytes.add(complement);
  29. mBytes.add(send_package);
  30. mBytes.add(crc16_rerify);
  31. content_bytes = null;
  32. content_bytes = sysCopy(mBytes);
  33. writeData(content_bytes);
  34. mPb_update.setProgress(counter);
  35. Log.i(TAG, "setUpdatePackageACK: ");
  36. } else {
  37. Log.i(TAG, "setUpdatePackageACK: false inputstream");
  38. }
  39. } catch (Exception e) {
  40. e.printStackTrace();
  41. }
  42. return false;
  43. }

后面贴出发送过程中涉及到的方法代码:

  1. public static byte[] sysCopy(List<byte[]> srcArrays) {
  2. int len = 0;
  3. for (byte[] srcArray : srcArrays) {
  4. len += srcArray.length;
  5. }
  6. byte[] destArray = new byte[len];
  7. int destLen = 0;
  8. for (byte[] srcArray : srcArrays) {
  9. System.arraycopy(srcArray, 0, destArray, destLen, srcArray.length);
  10. destLen += srcArray.length;
  11. }
  12. return destArray;
  13. }
  14. // int 转字节
  15. public static byte[] intToByteArray1(int i) {
  16. byte[] result = new byte[4];
  17. result[0] = (byte) ((i >> 24) & 0xFF);
  18. result[1] = (byte) ((i >> 16) & 0xFF);
  19. result[2] = (byte) ((i >> 8) & 0xFF);
  20. result[3] = (byte) (i & 0xFF);
  21. return result;
  22. }
  23. // 流转换为字节数组的方法
  24. public byte[] readBytes(InputStream in) throws IOException {
  25. byte[] temp = new byte[in.available()];
  26. byte[] result = new byte[0];
  27. int size = 0;
  28. while ((size = in.read(temp)) != -1) {
  29. byte[] readBytes = new byte[size];
  30. System.arraycopy(temp, 0, readBytes, 0, size);
  31. result = Tools.mergeArray(result, readBytes);
  32. }
  33. return result;
  34. }

后面贴出的事 CRC16 计算代码

  1. package com.rrioo.sateliteone.util;
  2. /**
  3. * @version :2015年1月28日 下午2:03:54
  4. *
  5. * 类说明 :byte\short\crc转换
  6. */
  7. public class CodecUtil {
  8. static CRC16 crc16 = new CRC16();
  9. private CodecUtil() {
  10. }
  11. public static byte[] short2bytes(short s) {
  12. byte[] bytes = new byte[2];
  13. for (int i = 1; i >= 0; i--) {
  14. bytes[i] = (byte)(s % 256);
  15. s >>= 8;
  16. }
  17. return bytes;
  18. }
  19. public static short bytes2short(byte[] bytes) {
  20. short s = (short)(bytes[1] & 0xFF);
  21. s |= (bytes[0] << 8) & 0xFF00;
  22. return s;
  23. }
  24. /*
  25. * 获取crc校验的byte形式
  26. */
  27. public static byte[] crc16Bytes(byte[] data) {
  28. return short2bytes(crc16Short(data));
  29. }
  30. /*
  31. * 获取crc校验的short形式
  32. */
  33. public static short crc16Short(byte[] data) {
  34. return crc16.getCrc(data);
  35. }
  36. }
  1. package com.rrioo.sateliteone.util;
  2. /**
  3. * @version :2015年1月28日 下午2:03:54
  4. *
  5. * 类说明 :获取crc校验码
  6. */
  7. public class CRC16 {
  8. private short[] crcTable = new short[256];
  9. private int gPloy = 0x1021; // 生成多项式
  10. public CRC16() {
  11. computeCrcTable();
  12. }
  13. private short getCrcOfByte(int aByte) {
  14. int value = aByte << 8;
  15. for (int count = 7; count >= 0; count--) {
  16. if ((value & 0x8000) != 0) { // 高第16位为1,可以按位异或
  17. value = (value << 1) ^ gPloy;
  18. } else {
  19. value = value << 1; // 首位为0,左移
  20. }
  21. }
  22. value = value & 0xFFFF; // 取低16位的值
  23. return (short)value;
  24. }
  25. /*
  26. * 生成0 - 255对应的CRC16校验码
  27. */
  28. private void computeCrcTable() {
  29. for (int i = 0; i < 256; i++) {
  30. crcTable[i] = getCrcOfByte(i);
  31. }
  32. }
  33. public short getCrc(byte[] data) {
  34. int crc = 0;
  35. int length = data.length;
  36. for (int i = 0; i < length; i++) {
  37. crc = ((crc & 0xFF) << 8) ^ crcTable[(((crc & 0xFF00) >> 8) ^ data[i]) & 0xFF];
  38. }
  39. crc = crc & 0xFFFF;
  40. return (short)crc;
  41. }
  42. }

优化方案:

BLE空中传输20字节是不可变的,致使你打开了最大传输字节的MTU 也就是23个字节的样子。

我的做法是 减少ACK 的校验。没发送8个20 字节包后再 ACK 校验一次 ,即一次可以发送文件内容为155个字节。服务端做好丢包检测就好,一旦发现1S之内没有收到客户端的包 就扔掉所有数据。
列如:客户端发送160个字节 结果有20 字节丢包了,服务端只检测到140个字节,并不反回ACK ,移动端检测ACk 超时重发2秒之后。此时服务端已经扔掉了不完整的包,清楚buf 继续等待下一次160字节的到来。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/911714
推荐阅读
相关标签
  

闽ICP备14008679号