Use Flutter to implement GaiaControl BLE OTA upgrade function, supporting Android/IOS

The code is basically transplanted from the official GaiaControl Demo.
Supports RWCP breakpoint resume setting Bluetooth mtu. protocol. Here we mainly analyze the process, agreements, etc. of GAIA CSR ble ota. I hope it will be helpful to you. We won’t talk about Bluetooth service feature subscription here. Readers can find out for themselves.

Gaia is an upper-layer usage protocol developed by CSR. It is based on RFCOMM in BR/EDR and can be understood as a special UUID SPP; the UUID 0X1107 used by classic Bluetooth. iOS only supports BLE, so focus on SPP upgrade UUID 0X1101
Gaia's UUID is 00001107-D102-11E1-9B23-00025B00A5A5;
SPP's UUID is 00001101-0000-1000-8000-00805F9B34FB;
the main UUIDs are as follows
Insert image description here

//升级服务UUID
Uuid otaUUID = Uuid.parse("00001100-d102-11e1-9b23-00025b00a5a5");
//升级服务订阅的特性
Uuid notifyUUID = Uuid.parse("00001102-d102-11e1-9b23-00025b00a5a5");
//升级服务写入特性
Uuid writeUUID = Uuid.parse("00001101-d102-11e1-9b23-00025b00a5a5");
//RWCP 更快的传输特性 不需要回包
Uuid writeNoResUUID = Uuid.parse("00001103-d102-11e1-9b23-00025b00a5a5");

The GAIA protocol process is very cumbersome, so we analyze it from the beginning.
First subscribe to the upgrade notification channel 00001102-d102-11e1-9b23-00025b00a5a5
and write the command 0x000A400112 to 00001101-d102-11e1-9b23-00025b00a5a5 to indicate the subscription to the upgrade notification.
The 000A400112 group package format here is as follows

000A 4001 12

Among them, 0x000A is the fixed character vendorId. 0x4001 two bytes represent the commandId. 10x2 represents the subscription upgrade notification.
UUID 0x1102 is returned if the subscription is successful.

000AC0010012

You can now start sending upgrade requests

  void sendUpgradeConnect() async {
    GaiaPacketBLE packet = GaiaPacketBLE(GAIA.COMMAND_VM_UPGRADE_CONNECT);
    writeMsg(packet.getBytes());
  }

That is, send the 0x0640 command

000A0640

Receiving the reply packet 000A864000 indicates that the request is successful. At this time, the file MD5 information is prepared for verification, and the last 4 bytes of the file MD5 are taken. The command is as follows

000A0642130004F891C66F

The group package format here is vendorId + commandId + operator + data, where the data contains length + real data

000A 0642 13 0004 F891C66F

F891C66F is file MD5;

At this time, two data packets will be received

000A0642010000 //升级开始
000A40031214000600F891C66F03 //MD5接收完成 并携带上次传输步骤信息 其中0x03 表示正在升级并且上次传输步骤为0x00 
 void receiveSyncCFM(VMUPacket? packet) {
    List<int> data = packet?.mData ?? [];
    if (data.length >= 6) {
      int step = data[0];
      addLog("上次传输步骤 step $step");
      if (step == ResumePoints.IN_PROGRESS) {
        setResumePoint(step);
      } else {
        mResumePoint = step;
      }
    } else {
      mResumePoint = ResumePoints.DATA_TRANSFER;
    }
    sendStartReq();
  }
 void sendStartReq() {
    VMUPacket packet = VMUPacket.get(OpCodes.UPGRADE_START_REQ);
    sendVMUPacket(packet, false);
  }

The next step is to package and send the data.

  void sendStartDataReq() {
    setResumePoint(ResumePoints.DATA_TRANSFER);
    VMUPacket packet = VMUPacket.get(OpCodes.UPGRADE_START_DATA_REQ);
    sendVMUPacket(packet, false);
  }
   000A0642150000
void receiveDataBytesREQ(VMUPacket? packet) {
    List<int> data = packet?.mData ?? [];

    // Checking the data has the good length
    if (data.length == OpCodes.DATA_LENGTH) {
      // retrieving information from the received packet
      //REC 120300080000002400000000
      //SEND 000A064204000D0000030000FFFF0001FFFF0002
      var lengthByte = [data[0], data[1], data[2], data[3]];
      var fileByte = [data[4], data[5], data[6], data[7]];
      mBytesToSend = int.parse(StringUtils.byteToHexString(lengthByte), radix: 16);
      int fileOffset = int.parse(StringUtils.byteToHexString(fileByte), radix: 16);

      addLog(StringUtils.byteToHexString(data) + "本次发包: $fileOffset $mBytesToSend");
      // we check the value for the offset
      mStartOffset += (fileOffset > 0 && fileOffset + mStartOffset < (mBytesFile?.length ?? 0)) ? fileOffset : 0;

      // if the asked length doesn't fit with possibilities we use the maximum length we can use.
      mBytesToSend = (mBytesToSend > 0) ? mBytesToSend : 0;
      // if the requested length will look for bytes out of the array we reduce it to the remaining length.
      int remainingLength = mBytesFile?.length ?? 0 - mStartOffset;
      mBytesToSend = (mBytesToSend < remainingLength) ? mBytesToSend : remainingLength;
      if (mIsRWCPEnabled.value) {
        while (mBytesToSend > 0) {
          sendNextDataPacket();
        }
      } else {
        addLog("receiveDataBytesREQ: sendNextDataPacket");
        sendNextDataPacket();
      }
    } else {
      addLog("UpgradeError 数据传输失败");
      abortUpgrade();
    }
  }

This is also the logic of resumed downloading. Read fileOffset for file offset

The last packet data format is 0x01 + data sending completed

void sendData(bool lastPacket, List<int> data) {
    List<int> dataToSend = [];
    dataToSend.add(lastPacket ? 0x01 : 0x00);
    dataToSend.addAll(data);
    sendPkgCount++;
    VMUPacket packet = VMUPacket.get(OpCodes.UPGRADE_DATA, data: dataToSend);
    sendVMUPacket(packet, true);
  }

I will not ask you if you want to submit or not.

 static const UPGRADE_COMMIT_REQ = 0x0F;
 void askForConfirmation(int type) {
    int code = -1;
    switch (type) {
      case ConfirmationType.COMMIT:
        {
          code = OpCodes.UPGRADE_COMMIT_CFM;
        }
        break;
      case ConfirmationType.IN_PROGRESS:
        {
          code = OpCodes.UPGRADE_IN_PROGRESS_RES;
        }
        break;
      case ConfirmationType.TRANSFER_COMPLETE:
        {
          code = OpCodes.UPGRADE_TRANSFER_COMPLETE_RES;
        }
        break;
      case ConfirmationType.BATTERY_LOW_ON_DEVICE:
        {
          sendSyncReq();
        }
        return;
      case ConfirmationType.WARNING_FILE_IS_DIFFERENT:
        {
          stopUpgrade();
        }
        return;
    }
    addLog("askForConfirmation ConfirmationType type $type $code");
    VMUPacket packet = VMUPacket.get(code, data: [0]);
    sendVMUPacket(packet, false);
  }

After the submission is completed, Bluetooth will automatically disconnect. After an interval of 1 second, it will reconnect and resubscribe to the notification to send the upgrade request and file md5 again. If the data is normal, you will receive instructions whether to install it or not.

 static const int COMMIT = 0x04

Regarding RWCP transmission, it is based on the past writing data in 0x1103 without waiting for return notification, which can greatly increase the transmission rate.

The next article will sort out the RWCP transmission process

Insert image description here
Insert image description here

Don’t know how to welcome communication and discussion
https://github.com/Liberations/Flutter-GAIAControl

Guess you like

Origin blog.csdn.net/qq910689331/article/details/128820679