Skip to content
ReactNative蓝牙扫描监听写入特征值示例代码

页面代码

js
import { View, StyleSheet, Text, PermissionsAndroid, Platform, Dimensions, TouchableOpacity } from 'react-native';
import theme from '../../config/theme.json';
import { BleManager } from 'react-native-ble-plx';
import { getBLEDeviceServices, getHistoryData, setUtc } from '../../utils/BLEConnect';
const manager = new BleManager();
const { width, height } = Dimensions.get('window');
const getWidth = (e) => {
    return width * e / 2000 // 设备的宽度大小 * 原型图的元素宽度 / 原型图固定宽度
}
const getHeight = (e) => {
    return height * e / 1000 // 设备的宽度大小 * 原型图的元素宽度 / 原型图固定宽度
}
export const SyncData = () => {
    // 开始同步
    const startSync = async () => {
        let permissions;
        if (Platform.OS === 'android') {
            if (Platform.Version >= 31) { // Android 12 及以上
                permissions = [
                    'android.permission.BLUETOOTH_SCAN',
                    'android.permission.BLUETOOTH_CONNECT',
                    'android.permission.BLUETOOTH_ADVERTISE'
                ];
            } else { // Android 12 以下
                permissions = ['android.permission.ACCESS_FINE_LOCATION'];
            }
        }

        // 检查权限
        for (const permission of permissions) {
            const check = await PermissionsAndroid.check(permission);
            console.log(`permission ${permission} check ${check}`);
            if (!check) {
                await PermissionsAndroid.request(permission);
            }
        }
        // 开始扫描
        manager.startDeviceScan(null, null, async (error, device) => {
            if (error) {
                // 扫描出错,一般是权限问题
                console.error(error);
                // 停止扫描
                manager.stopDeviceScan()
                console.log('蓝牙扫描出错了~');
                return;
            }

            // 扫如果点开了同步,代表要去同步设备,否则就是扫描信号
            if (device.name == null) return
            console.log('device.name', device.name);
            // 描到设备
            if (device.name == 'Take-73576717') {
                // 连接设备,并获取是否连接成功,拿到连接成功后返回的信息
                getBLEDeviceServices(device.id, manager, (name, id) => {
                    console.log('当前连接的蓝牙名称和id值为:', name, id);
                })
            } else {
                // 全部同步完成
            }
        });
    }

    return (
        <View
            style={styles.oncontainer}>
                <TouchableOpacity style={styles.kstb} activeOpacity={1} onPress={startSync}>
                <Text style={styles.kstbText}>开始扫描并连接连接后断开</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.kstb} activeOpacity={1} onPress={() => {
                getHistoryData()
            }}>
                <Text style={styles.kstbText}>获取历史数据</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.kstb} activeOpacity={1} onPress={() => {
                setUtc()
            }}>
                <Text style={styles.kstbText}>设置UTC</Text>
            </TouchableOpacity>
        </View>
    )
}

const styles = StyleSheet.create({
    oncontainer: {
        flex: 1,
        backgroundColor: theme.generalBackground,
        flexDirection:'row',
        justifyContent:'center',
        alignItems:'center'
    },
    kstb: {
        paddingLeft: getWidth(10),
        paddingRight:getWidth(10),
        height: getHeight(56),
        backgroundColor: '#EF6234',
        borderRadius: getWidth(8),
        justifyContent: 'center',
        alignItems: 'center',
        marginRight: getWidth(10),
    },
    kstbText: {
        fontSize: getWidth(24),
        color: '#ffffff',
        fontWeight: '600',
        lineHeight: getHeight(56),
    }
})

工具包js代码

js
import { Buffer } from 'buffer';
// 公共协议UUID
var UUID_COMMON_POWER = "00002a19-0000-1000-8000-00805f9b34fb"; //电量
var UUID_COMMON_FIRMVERSION = "00002a26-0000-1000-8000-00805f9b34fb"; //固件版本
var UUID_COMMON_HARDVERSION = "00002a27-0000-1000-8000-00805f9b34fb"; //硬件版本
var UUID_COMMON_SOFTVERSION = "00002a28-0000-1000-8000-00805f9b34fb"; //软件版本
var UUID_COMMON_WRITE = "0000fd1a-0000-1000-8000-00805f9b34fb"; //写命令的特征值id,现阶段发送指令都用这一个
var UUID_COMMON_NOTIFY = "0000fd19-0000-1000-8000-00805f9b34fb"; //公共收命令特征值
var UUID_COMMON_SERVICEUUID = "0000FD00-0000-1000-8000-00805F9B34FB"; //公共服务UUID

//私有协议,例如 设置心率振动阈值
var UUID_COMMON_WRITE0A = "0000fd0a-0000-1000-8000-00805f9b34fb";
//未用到
var UUID_COMMON_NOTIFY09 = "0000fd09-0000-1000-8000-00805f9b34fb"; //公共通知09
var UUID_HEARTRATE_NOTIFY = "00002a37-0000-1000-8000-00805f9b34fb"; //心率数据

let headArray = [];
/** 解析训练时间与卡路里 */
var caloriesArray = [];
/** 解析心率历史数据 */
var heartRateArray = [];
/** 解析步数的历史数据 */
var stepArray = [];
var step15Array = [];

// 协议版本号
var protocolVersion = 1;

let device = {
  deviceId: null,
  serviceId: "0000FD00-0000-1000-8000-00805F9B34FB",
  name: null,
  devices: null,
  services: null,
  connected: false,
  stopDiscovery: true, //停止扫码状态,默认停止
  sending: false, //正在发送数据
  headerNumber: 0,//指令序号,从0开始,断开置为0
};

/** 蓝牙监听 */
//获取在蓝牙模块生效期间所有已发现的蓝牙设备。包括已经和本机处于连接状态的设备
export const getBLEDeviceServices = async (deviceId, manager, callback) => {
  manager.stopDeviceScan() // 扫描到设备就停止扫描
  const devices = await manager.connectToDevice(deviceId); // 连接设备
  callback(devices.name, devices.id) // 将当前设备名和id回调出去给上一个页面使用
  device.deviceId = devices.id; // 给全局device赋值
  device.name = devices.name;
  device.devices = devices;
  const services = await devices.discoverAllServicesAndCharacteristics(); // 获取所有的服务和特征
  const aaa = await services.services(); // 仅获取所有的服务
  const arr = aaa.filter(item => item.uuid == UUID_COMMON_SERVICEUUID.toLowerCase()); // 过滤一下,找到当前需要的自定义的服务
  device.services = arr[0]; // 因为就一个自定义服务,将服务的数组的0号元素赋值给全局services

  // 因为前面加了过滤才是只有一个,正常来说服务是有多个,需要循环一下
  for (let i = 0; i < arr.length; i++) {
    // 判断是否是主服务
    if (arr[i].isPrimary) { 
      getBLEDeviceCharacteristics(devices, arr[i], manager); // 获取当前服务的特征
    }
  }
}

const getBLEDeviceCharacteristics = async (device, service, manager) => {

  try {
    // 获取服务下的特征值
    const serviceitem = await service.characteristics(); // 获取到当前服务下的所有特征
    // 循环特征
    for (const item of serviceitem) {
      try {
        if (item.isReadable) { // 判断当前特征是否支持读(里面逻辑不用看,这里我是打印下特征值做下测试使用)
          const serviceitem2 = await item.read();
          const newValue = serviceitem2.value;
          console.log('newValue', newValue); // 获取当前服务的特征值
        }
        //启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。注意:必须设备的特征值支持 isNotifiable 或者 isIndicatable 才可以成功调用。
        //另外,必须先启用 notifyBLECharacteristicValueChange 才能监听到设备 onBleCharacteristicValueChange 事件
        // isNotifiable:是否允许通知  
        // isIndicatable:是否支持指示
        if (item.isNotifiable || item.isIndicatable) {
          // 操作之前先监听,保证第一时间获取数据, 如果是断开重连,则是已经创建过监听
          // 开启特征值监听
          onBLECharacteristicValueChange(device, service, item.uuid);
        }
      } catch (error) {
        console.error('Read error:', error);
      }
    }

  } catch (error) {
    console.error('Error getting characteristics:', error);
  }
}

// 监听特征值变化
const onBLECharacteristicValueChange = (device, service, characteristicId) => {

  // 监听服务特征值
  service.monitorCharacteristic(characteristicId, (error, characteristic) => {
    if (error) {
      console.error('Error monitoring characteristic:', error);
    } else {
      //解析数据(characteristic.value是base64类型的,将其转为buffer类型进行解析)
      const buffer = base64ToArrayBuffer(characteristic.value);
      // 数据处理
      analysisBleData(service, characteristic.uuid, buffer, device);
    }
  })
}
//ble 数据解析value,返回 {type, data} 
function analysisBleData(service, uuid, value, device) {
  var bleByte = new Uint8Array(value);
  if (uuid.toLowerCase() == UUID_COMMON_NOTIFY09) { //震动阈值设置
    var sendsetShock = byte2hexStr(bleByte); //设置成功一般会返回:D2050901E1
    console.log("[analysisBleData]心率阈值设置、获取、关闭回复结果 = " + sendsetShock);
    var first = bleByte[0] & 0xFF;
    var keyMark = bleByte[2] & 0xFF;
    if (first == 210) {
      if (keyMark == 9) { //设置
        var setResultValue = bleByte[3] & 0xFF;
        if (setResultValue == 1) { //设置成功
          //回调函数
          connectDateListener(11, 'success');
        } else {
          connectDateListener(11, 'failure');
        }
      } else if (keyMark == 10) { //获取
        var switchMark = bleByte[3] & 0xFF;
        var shockNumber = bleByte[4] & 0xFF;
        var result = {
          switchMark: switchMark,
          shockNumber: shockNumber
        };
        //回调函数
        connectDateListener(12, result);
      } else if (keyMark == 11) { //关闭
        var closeResultValue = bleByte[3] & 0xFF;
        if (closeResultValue == 1) { //关闭成功
          //回调函数
          connectDateListener(13, 'success');
        } else {
          connectDateListener(13, 'failure');
        }
      }
    }
    return;
  }

  if (uuid.toLowerCase() == UUID_COMMON_POWER) { //电量数据
    //回调函数
    connectDateListener(0, bleByte[0] & 0xFF);
    return;
  }
  if (uuid.toLowerCase() == UUID_COMMON_FIRMVERSION) { //固件版本
    //回调函数
    connectDateListener(8, hex2str(bleByte));
    return;
  }
  if (uuid.toLowerCase() == UUID_COMMON_HARDVERSION) { //硬件版本
    //回调函数
    connectDateListener(6, hex2str(bleByte));
    return;
  }
  if (uuid.toLowerCase() == UUID_COMMON_SOFTVERSION) { //软件版本
    //回调函数
    connectDateListener(7, hex2str(bleByte));
    return;
  }

  if (uuid.toLowerCase() == UUID_HEARTRATE_NOTIFY) { //心率数据
    var result = getHeartRateData(bleByte);
    connectDateListener(9, result);
    return;
  }
  if (!checkByte(bleByte)) { //接收数据无效
    console.log("无效数据")
    return;
  }
  var byte1 = bleByte[1] & 0xFF;
  if (byte1 == 32 && bleByte.length >= 5) {
    var byte3 = bleByte[3] & 0xFF;
    var byte4 = bleByte[4] & 0xFF;
    var headerNumber = (byte3 << 8) + byte4;
    connectDateListener(10, headerNumber);
    //处理回复结果回调
    return;
  }
  console.log('第二个转换', JSON.stringify(new Uint8Array(value)));
  var valueByte = getCompleteValueByte(value); //获取完整数据
  console.log('第三个转换', JSON.stringify(new Uint8Array(valueByte)));

  if (valueByte == "") {
    console.log("数据接收未结束");
    return; //非完整数据,不处理
  }
  dowithValueByte(service,valueByte, device);
  return;
  //} 
}
/**
 * 获取bit字节
 */
function getIntBit(dataNumber, startBit, endBit) {
  if (startBit < 0 || startBit > 15) {
    return 0;
  }
  if (endBit < 0 || endBit > 15) {
    return 0;
  }
  if (startBit > endBit) {
    return 0;
  }
  return (dataNumber & (0xFFFF >> (15 - endBit))) >> startBit;
}
/** 校验数据 */
function checkByte(bleByte) {
  if (!bleByte || bleByte.length < 5) {
    return false;
  }
  var lastNumber = bleByte[bleByte.length - 1] & 0xFF;
  var checkNumber = 0;
  for (var i = 0; i < bleByte.length - 1; i++) {
    checkNumber = checkNumber + bleByte[i] & 0xFF;
  }
  if (lastNumber != checkNumber % 256) {
    return false;
  }
  return true;
}
/**
 * 解析心率数据
 */
function getHeartRateData(bleByte) {
  var length = bleByte[0] & 0xFF;
  var heartRate = bleByte[1] & 0xFF;
  var intervalArray = [];
  if (bleByte.length > 2 && bleByte.length % 2 == 0) {
    for (var i = 2; i < bleByte.length; i++) {
      if (i % 2 == 0) {
        var lowByte = bleByte[i] & 0xFF;
        var hiByte = bleByte[i + 1] & 0xFF;
        var interval = (hiByte << 8) + lowByte;
        intervalArray.push(interval);
      }
    }
  }
  var result = {
    heartRate: heartRate,
    dataLength: length,
    interval: intervalArray
  };
  return result;
}
/** bufferArray hex -> str */
function hex2str(hex) {
  var hexStr = "";
  for (var i = 0; i < hex.length; i++) {
    hexStr += String.fromCharCode(hex[i]);
  }
  return hexStr;
}
/**
 * 获取完整的命令值
 */
function getCompleteValueByte(value) {
  var bleByte = new Uint8Array(value);
  var byte1 = bleByte[1] & 0xFF;
  var isMore = getIntBit(byte1, 7, 7);
  var isEnd = getIntBit(byte1, 6, 6);
  var bagNo = getIntBit(byte1, 0, 3);
  //获取要处理的数据,最后一位checkSum,第一位固定值,第二位标识符,第三位包长度,都去掉
  var valueByte = new ArrayBuffer(bleByte.length - 4);
  var valuebyteView = new Uint8Array(valueByte);
  //从第三位开始截取
  for (var i = 3; i < bleByte.length - 1; i++) {
    valuebyteView[i - 3] = bleByte[i];
  }
  if (isMore == 0) { //单个包数据,直接返回
    return valueByte;
  }
  //多包发送
  if (isEnd == 0 && bagNo == 0) {
    byteArray = [];
    byteArray.push(valueByte);
    return ""; //多包没有处理完成,返回空
  }
  if (isEnd == 0 && bagNo != 0) {
    byteArray.push(valueByte);
    return ""; //多包没有处理完成,返回空
  }
  //最后一个包,处理数据
  byteArray.push(valueByte);
  var valueLong = 0;
  for (var i = 0; i < byteArray.length; i++) {
    valueLong = valueLong + byteArray[i].byteLength;
  }
  var resultRow = 0;
  var resultByte = new ArrayBuffer(valueLong);
  var resultByteView = new Uint8Array(resultByte);
  for (var i = 0; i < byteArray.length; i++) {
    var listByte = byteArray[i];
    var listByteView = new Uint8Array(listByte);
    for (var j = 0; j < listByteView.length; j++) {
      resultByteView[resultRow] = listByteView[j];
      resultRow = resultRow + 1;
    }
  }
  if (byteArray.length != bagNo + 1) {
    return ""; //丢包了,返回空
  }
  byteArray = []; //变量初始化为空
  return resultByte;
}
/**
 * int 转byte
 */
function int2byte(num) {
  return num & 0xFF; // 确保返回值是一个 8 位的无符号字节
}

/** 回复命令 **/
function getAckCmdValue(sortNumber) {
  var checkNumber = 255 + 32 + 6 + (sortNumber / 256) + (sortNumber % 256);
  var cmdByte = new ArrayBuffer(6);
  var bufView = new Uint8Array(cmdByte);
  bufView[0] = int2byte(255);
  bufView[1] = int2byte(32);
  bufView[2] = int2byte(6);
  bufView[3] = int2byte(sortNumber / 256);
  bufView[4] = int2byte(sortNumber % 256);
  bufView[5] = int2byte(checkNumber % 256);
  return cmdByte;
}

// base64 转 ArrayBuffer
function base64ToArrayBuffer(base64) {
  // 解码Base64字符串
  // 解码 Base64 字符串
  const buffer = Buffer.from(base64, 'base64');
  // 将 Buffer 转换为 ArrayBuffer
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
  // 返回ArrayBuffer
  return arrayBuffer;
}

// 将 ArrayBuffer 转换为 Base64 字符串
function arrayBufferToBase64(buffer) {
  return Buffer.from(buffer).toString('base64');
}


/** 处理接收的数据 */
function dowithValueByte(service,value, device) {
  var valueByte = new Uint8Array(value);
  var byte0 = valueByte[0] & 0xFF;
  var byte1 = valueByte[1] & 0xFF;
  console.log('byte0', JSON.stringify(byte0));
  console.log('byte1', JSON.stringify(byte1));
  var sortNum = (byte0 << 8) + byte1;
  console.log('sortNum', JSON.stringify(sortNum));
  var cmdId = valueByte[2] & 0xFF; //命令
  var keyMark = valueByte[4] & 0xFF;
  var valueLong = valueByte[6] & 0xFF;
  var byteNum = 7;
  console.log("sortNum=", sortNum);
  var ackBuffer = getAckCmdValue(sortNum); //获取ack命令值
  var ackStr = arrayBufferToBase64(ackBuffer);
  console.log('ackStr', ackStr);

  sendAckCmd(service, ackStr, UUID_COMMON_WRITE.toUpperCase(), device); //发送指令
  if (headArray.indexOf(sortNum) >= 0) {
    console.log("已处理过的数据");
    return;
  }
  headArray.push(sortNum);
  if (headArray.length > 20) {
    headArray.splice(5);
  }

  for (var i = 0; i < 32; i++) {
    var resultByteBuffer = new ArrayBuffer(valueLong + 1);
    var resultByte = new Uint8Array(resultByteBuffer);
    resultByte[0] = int2byte(keyMark);
    for (var j = byteNum; j < valueLong + byteNum; j++) {
      resultByte[1 + j - byteNum] = valueByte[j];
    }
    var result;
    console.log('cmdId', cmdId);

    switch (cmdId) {
      case 1: {
        console.log('实时数据');
        result = realtimeData(resultByte);
        break;
      }
      case 3: {
        console.log('自定义数据上传');
        result = customData(resultByte);
        break;
      }
      case 4: {
        analyticalRecord(resultByte);
        break;
      }
      case 5: { //版本号
        if (keyMark == 1 && resultByte.length == 2) {
          result = resultByte[1] & 0xFF;
        }
        if (keyMark == 0) {
          result = customData(resultByte);
        }
        break;
      }
      default: {
        break;
      }
    }
    //第一次读完三个字节,即已经处理完成,第二次不再继续
    if (valueLong + byteNum == valueByte.byteLength) {
      if (cmdId == 4) { //历史数据一次处理不完整
        result = analyticalComplete();
      }
      console.log('cmdId, result', cmdId, result);
      connectDateListener(cmdId, result);
      //break;
    }
    keyMark = resultByte[byteNum + valueLong] & 0xFF;
    valueLong = resultByte[byteNum + valueLong + 2] & 0xFF;
    //ackType = resultByte[byteNum + valueLong + 1] & 0xFF;
    byteNum = byteNum + valueLong + 3;
  }
}
/** 蓝牙连接返回数据监听 
 *  1:实时数据,4:历史数据,5:版本号, 6硬件版本, 7软件版本,8固件版本,0电量
 * 5:版本号, 6硬件版本, 7软件版本,8固件版本,0电量 连接上就自动读取, 10:设备接收指令的结果
 * 11:心率震动阈值设置,12:心率震动阈值获取,13,心率震动阈值关闭
 */
const connectDateListener = (cmdKey, data) => {
  if (parseInt(cmdKey) == 0) {
    console.log("电量:" + data);
    return;
  }
  if (parseInt(cmdKey) == 5) {
    console.log("设备版本号:version=" + data);
    return;
  }
  if (parseInt(cmdKey) == 6) {
    console.log("硬件版本:" + data);
    return;
  }
  if (parseInt(cmdKey) == 7) {
    console.log("软件版本:" + data);
    return;
  }
  if (parseInt(cmdKey) == 8) {
    console.log("固件版本:" + data);
    return;
  }
  if (parseInt(cmdKey) == 9) { //实时心率
    console.log("心率数据:heartRate=" + data.heartRate + ",dataLength=" + data.dataLength + ",interval=" + data.interval.length);
    return;
  }
  if (parseInt(cmdKey) == 10) { //设备接受指令结果
    console.log("设备接收到指令回调=" + data);
    return;
  }
  if (parseInt(cmdKey) == 11) { //心率震动阈值设置结果
    //data:success 或 failure
    console.log("震动阈值设置结果=" + data);
    return;
  }
  if (parseInt(cmdKey) == 12) { //心率震动阈值获取结果
    //switchMark : 是否开启震动 0-为开启 1-开启
    //shockNumber : 震动阀值
    console.log("震动阈值获取结果 shockNumber =" + data.shockNumber + ",switchMark = " + data.switchMark);
    return;
  }
  if (parseInt(cmdKey) == 13) { //心率震动阈值关闭结果
    //data:success 或 failure
    console.log("震动阈值关闭结果=" + data);
    return;
  }

  if (parseInt(cmdKey) == 1 || parseInt(cmdKey) == 2) { //步数,步频,卡路里
    if ("step" == data.type) { //实时步数、卡路里
      console.log("实时步数、卡路里: steps=" + data.steps + ",frequency=" + data.frequency + ",calorie=" + data.calorie);
    } else if ("custom" == data.type) { //自定义数据
      console.log("自定义数据: " + data.value);
    }
    return;
  }
  if (parseInt(cmdKey) == 3) { //步数,步频,卡路里
    if ("custom" == data.type) { //自定义数据
      console.log("自定义数据: " + data.value);
    }
    return;
  }
  console.log('cmdKey', cmdKey, data);

  if (parseInt(cmdKey) == 4) {
    if (data.type == 'step') {
      console.log("步数历史数据:" + data.steps.length + "组");
      for (let i = 0; i < data.steps.length; i++) {
        console.log("步数历史第" + i + "组时间:" + data.steps[i].createTime);
      }
    } else if (data.type == 'rate') {
      console.log("心率历史数据:" + data.heartRate.length + "组");
      for (let i = 0; i < data.heartRate.length; i++) {
        console.log("心率历史第" + i + "组时间:" + data.heartRate[i].createTime);
      }
    } else if (data.type == 'step15') {
      console.log("15分钟步数历史数据:" + data.steps.length + "组");
      for (let i = 0; i < data.steps.length; i++) {
        console.log("15分钟步数历史第" + i + "组时间:" + data.steps[i].createTime);
      }
    } else if (data.type == 'calorie') {
      console.log("训练卡路里数据:" + data.calories.length + "组");
    }
    return;
  }
}
/** 解析实时数据 */
function realtimeData(byteData) {
  if (byteData.length > 0) {
    var keyMark = byteData[0] & 0xFF;
    if (keyMark == 2 && byteData.length >= 5) { //步频
      var stepHi = byteData[1] & 0xFF;
      var stepMin = byteData[2] & 0xFF;
      var stepLow = byteData[3] & 0xFF;
      var steps = (stepHi << 16) + (stepMin << 8) + stepLow; //步数
      var frequency = byteData[4] & 0xFF; //步频
      //卡路里计算
      var calorie = "0";
      if (byteData.length >= 8) {
        var calHi = byteData[5] & 0xFF;
        var calpMin = byteData[6] & 0xFF;
        var calLow = byteData[7] & 0xFF;
        calorie = (calHi << 16) + (calpMin << 8) + calLow;
      }
      //处理数据
      return {
        type: "step",
        steps: steps,
        frequency: frequency,
        calorie: calorie
      };
    }
  }
}
/** 解析历史数据 */
function analyticalRecord(recordByte) {
  var keyMark = recordByte[0] & 0xFF;
  //var keyMark = getIntBit(byte0, 0, 3);
  if (keyMark == 1) { //一分钟的步数数据
    analysisStepRecordMark1(recordByte);
  } else if (keyMark == 8) { //心率数据
    analysisHeartRateRecord(recordByte);
  } else if (keyMark == 9) { //15分钟的步数数据
    analysisStepRecordMark9(recordByte);
  } else if (keyMark == 12) { //训练时间及卡路里数据
    analysisCaloriesRecord(recordByte);
  }
}
function analysisStepRecordMark1(recordByte) {
  var offSet = 0;
  while (offSet < recordByte.length - 1) {
    var dataLength = recordByte[offSet + 1] & 0xFF;
    offSet = offSet + 1;

    var utc1 = recordByte[offSet + 1] & 0xFF;
    var utc2 = recordByte[offSet + 2] & 0xFF;
    var utc3 = recordByte[offSet + 3] & 0xFF;
    var utc4 = recordByte[offSet + 4] & 0xFF;
    var myUtc = (utc1 << 24) + (utc2 << 16) + (utc3 << 8) + utc4 + new Date().getTimezoneOffset() * 60;
    offSet = offSet + 4;

    for (var i = 0; i < dataLength / 2; i++) {
      var steps = recordByte[offSet + 1] & 0xFF;
      var calories = recordByte[offSet + 2] & 0xFF;
      offSet = offSet + 2;

      var timeStamps = myUtc * 1000;
      var date = new Date(timeStamps);
      var timeString = formatTime(date);

      var data = {
        timeStamps: myUtc,
        createTime: timeString,
        steps: steps,
        calories: calories
      };
      stepArray.push(data);
      myUtc = myUtc + 60;
    }
  }
}

function analysisHeartRateRecord(recordByte) {
  var offSet = 0;
  while (offSet < recordByte.length - 1) {
    var dataLength = recordByte[offSet + 1] & 0xFF;
    offSet = offSet + 1;

    var utc1 = recordByte[offSet + 1] & 0xFF;
    var utc2 = recordByte[offSet + 2] & 0xFF;
    var utc3 = recordByte[offSet + 3] & 0xFF;
    var utc4 = recordByte[offSet + 4] & 0xFF;
    var myUtc = (utc1 << 24) + (utc2 << 16) + (utc3 << 8) + utc4 + new Date().getTimezoneOffset() * 60;
    offSet = offSet + 4;

    for (var i = 0; i < dataLength; i++) {
      var heartRateNum = recordByte[offSet + 1] & 0xFF;
      offSet = offSet + 1;

      var timeStamps = myUtc * 1000;
      var date = new Date(timeStamps);
      var timeString = formatTime(date);

      var data = {
        timeStamps: myUtc,
        createTime: timeString,
        heartRateNum: heartRateNum
      };
      heartRateArray.push(data);
      myUtc = myUtc + 2;
    }
  }
}

function analysisCaloriesRecord() {
  var offSet = 0;
  while (offSet < recordByte.length - 1) {
    var calorie1 = recordByte[offSet + 1] & 0xFF;
    var calorie2 = recordByte[offSet + 2] & 0xFF;
    var calorie3 = recordByte[offSet + 3] & 0xFF;
    var calorie4 = recordByte[offSet + 4] & 0xFF;
    //计算卡路里
    var calorie = (calorie1 << 24) + (calorie2 << 16) + (calorie3 << 8) + calorie4;
    offSet = offSet + 4;
    var startUtc1 = recordByte[offSet + 1] & 0xFF;
    var startUtc2 = recordByte[offSet + 2] & 0xFF;
    var startUtc3 = recordByte[offSet + 3] & 0xFF;
    var startUtc4 = recordByte[offSet + 4] & 0xFF;
    var startUtc = (startUtc1 << 24) + (startUtc2 << 16) + (startUtc3 << 8) + startUtc4 + new Date().getTimezoneOffset() * 60;
    offSet = offSet + 4;
    var endUtc1 = recordByte[offSet + 1] & 0xFF;
    var endUtc2 = recordByte[offSet + 2] & 0xFF;
    var endUtc3 = recordByte[offSet + 3] & 0xFF;
    var endUtc4 = recordByte[offSet + 4] & 0xFF;
    var endUtc = (endUtc1 << 24) + (endUtc2 << 16) + (endUtc3 << 8) + endUtc4 + new Date().getTimezoneOffset() * 60;
    offSet = offSet + 4;

    var data = {
      startUtc: startUtc,
      endUtc: endUtc,
      calorie: calorie
    };
    caloriesArray.push(data);
  }
}

function analysisStepRecordMark9(recordByte) {
  var offSet = 0;
  while (offSet < recordByte.length - 1) {
    var dataLength = recordByte[offSet + 1] & 0xFF;
    offSet = offSet + 1;
    var utc1 = recordByte[offSet + 1] & 0xFF;
    var utc2 = recordByte[offSet + 2] & 0xFF;
    var utc3 = recordByte[offSet + 3] & 0xFF;
    var utc4 = recordByte[offSet + 4] & 0xFF;
    var myUtc = (utc1 << 24) + (utc2 << 16) + (utc3 << 8) + utc4 + new Date().getTimezoneOffset() * 60;
    offSet = offSet + 4;
    var timeCount = recordByte[offSet + 1] & 0xFF;
    for (var i = 0; i < dataLength / 2; i++) {
      var stepHi = recordByte[offSet + 1] & 0xFF;
      var stepLow = recordByte[offSet + 2] & 0xFF;
      var steps = (stepHi << 8) + stepLow;
      offSet = offSet + 2;
      var timeStamps = myUtc * 1000;
      var date = new Date(timeStamps);
      var timeString = formatTime(date);
      var spendCount = 15;
      if (i == dataLength / 2 - 1) {
        spendCount = timeCount;
      }
      var data = {
        timeStamps: myUtc,
        createTime: timeString,
        steps: steps,
        spendCount: spendCount
      };
      step15Array.push(data);
      myUtc = myUtc + 900;
    }
  }
}
function formatTime(date) {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const hour = date.getHours();
  const minute = date.getMinutes();
  const second = date.getSeconds();
  return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':');
}
function formatNumber(n) {
  n = n.toString();
  return n[1] ? n : '0' + n;
}
/** 解析自定义数据 */
function customData(byteData) {
  var value = "";
  if (byteData.length > 0) {
    var byte0 = byteData[0] & 0xFF;
    var keyMark = getIntBit(byte0, 0, 3);
    if (keyMark == 254 && byteData.length >= 2) {
      value = byteData[1] & 0xFF;
    }
  }
  var result = {
    type: "custom",
    value: value
  };
  return result;
}
/** 发送ack回复*/
function sendAckCmd(service, cmdValue, cmdCharacteristicId, devices) {
  try {
    console.log('写入的数据为: ', cmdValue);

    service.writeCharacteristicWithResponse(cmdCharacteristicId, cmdValue).then(res => {
      console.log('write sucess');
      device.sending = false;
    }).catch(err => {
      console.log('write fail', JSON.stringify(err));
      device.sending = false;
    });
  } catch (error) {
    console.log('sendAckCmd error', error);
    device.sending = false;
  }

}
/**
 * 解析数据结束,返回结果
 */
function analyticalComplete() {
  var result = {};
  if (stepArray.length > 0) {
    result.type = 'step';
    result.steps = stepArray;
    stepArray = [];
  }
  if (step15Array.length > 0) {
    result.type = 'step15';
    result.steps = step15Array;
    step15Array = [];
  }
  if (heartRateArray.length > 0) {
    result.type = 'rate';
    result.heartRate = heartRateArray;
    heartRateArray = [];
  }
  if (caloriesArray.length > 0) {
    result.type = 'calorie';
    result.calories = caloriesArray;
    caloriesArray = [];
  }
  return result;
}

// 获取历史数据
export function getHistoryData() {
  console.log('开始获取历史数据');
  var headNum = sendTotalRecordCmd('history');
  console.log('headNum', headNum);
}

/** 发送指令回调 */
function sendCmdCallBackListener(res) {
  console.log("发送指令回调:", res);
}

/**
 * 发送获取历史数据指令
 */
function sendTotalRecordCmd(type) {
  var cmdBuffer = getHistoryRecordCmdValue();
  var headNum = device.headerNumber;
  var valueBuffer = packageSendCmd(headNum, cmdBuffer);
  console.log('valueBuffer', JSON.stringify(new Uint8Array(valueBuffer)));
  console.log('valueBuffer', valueBuffer.length);

  sendBLECmdValue(valueBuffer, type);
  return headNum;
}
//向蓝牙设备发送16进制数据,
//单通道,只能一条指令执行完成返回后,再进行下一条指令的发送
function sendBLECmdValue(sendCmd, type) {
  if (device.sending) {
    console.log('on sending');
    return;
  }
  device.sending = true;
  var cmdCharacteristicId = UUID_COMMON_WRITE.toUpperCase(); //写命令uuid
  device.headerNumber += 1;
  var sendIndex = 0;
  //一次只能发送一条指令,上条指令未收到处理成功时,不进行下一条发送
  console.log('sendCmd', sendCmd.length);

  do {
    var sendCmdValue = sendCmd[sendIndex];
    console.log('sendCmdValue', sendCmdValue);

    var delay = 1000;
    if (sendIndex == 0) {
      delay = 0;
    }
    //多包发送设置延迟发送
    setTimeout(function () {
      var ackStr = arrayBufferToBase64(sendCmdValue);
      console.log('ackStr', ackStr);
      wwriteBLECharacteristicCmdValue(cmdCharacteristicId, ackStr, type);
    }, delay);
  } while (!device.sending)
}
/**获取所有历史数据**/
function getHistoryRecordCmdValue() {
  var cmdId = 4;
  var version = protocolVersion * 16 + 0 * 8;
  var keyMark = 0;
  var ackType = 2;
  var valueLong = 0;

  var cmdByte = new ArrayBuffer(5);
  var cmdView = new Uint8Array(cmdByte);
  cmdView[0] = int2byte(cmdId);
  cmdView[1] = int2byte(version);
  cmdView[2] = int2byte(keyMark);
  cmdView[3] = int2byte(ackType);
  cmdView[4] = int2byte(valueLong);

  console.log('第一个转换', JSON.stringify(new Uint8Array(cmdByte)));
  return cmdByte;
}
/**
 * 封住发送的完整指令
 */
function packageSendCmd(headerNumber, value) {
  var cmdValue = new Uint8Array(value);
  var sortHi = headerNumber / 256;
  var sortLow = headerNumber % 256;
  var valueNum = 2;
  var addValue = new ArrayBuffer(cmdValue.length + 2);
  var bufView = new Uint8Array(addValue);
  bufView[0] = int2byte(sortHi);
  bufView[1] = int2byte(sortLow); //添加发送序号,占两位
  for (var i = 0; i < cmdValue.length; i++) {
    bufView[valueNum + i] = cmdValue[i];
  }
  var m_cmdArray = [];
  if (bufView.length <= 16) {
    var lenght = bufView.length + 4;
    var checkNum = 255 + lenght;
    var myCmd = new ArrayBuffer(lenght);
    var myCmdView = new Uint8Array(myCmd);
    myCmdView[0] = int2byte(255);
    myCmdView[1] = int2byte(0);
    myCmdView[2] = int2byte(lenght);
    for (var i = 0; i < bufView.length; i++) {
      myCmdView[3 + i] = bufView[i];
      checkNum = checkNum + parseInt(bufView[i]);
    }
    myCmdView[lenght - 1] = int2byte(checkNum % 256);
    m_cmdArray.push(myCmd);
  } else {
    var cmdNum = parseInt(bufView.length / 16);
    if (bufView.length % 16 != 0) {
      cmdNum = cmdNum + 1;
    }
    for (var i = 0; i < cmdNum; i++) {
      if (i == cmdNum - 1) {
        var mark = 128 + 64 + i;
        var lenght = bufView.length - i * 16 + 4;
        var checkNum = 255 + mark + lenght;
        var myCmd = new ArrayBuffer(lenght);
        var myCmdView = new Uint8Array(myCmd);
        myCmdView[0] = int2byte(255);
        myCmdView[1] = int2byte(mark);
        myCmdView[2] = int2byte(lenght);
        for (var j = i * 16; j < bufView.length; j++) {
          myCmdView[3 + j - (i * 16)] = bufView[j];
          checkNum = checkNum + parseInt(bufView[j]);
        }
        myCmdView[lenght - 1] = int2byte(checkNum % 256);
        m_cmdArray.push(myCmd);
      } else {
        var mark = 128 + i;
        var lenght = 20;
        var checkNum = 255 + mark + lenght;
        var myCmd = new ArrayBuffer(lenght);
        var myCmdView = new Uint8Array(myCmd);
        myCmdView[0] = int2byte(255);
        myCmdView[1] = int2byte(mark);
        myCmdView[2] = int2byte(lenght);
        for (var j = i * 16; j < i * 16 + 16; j++) {
          myCmdView[3 + j - (i * 16)] = bufView[j];
          checkNum = checkNum + parseInt(bufView[j]);
        }
        myCmdView[lenght - 1] = int2byte(checkNum % 256);
        m_cmdArray.push(myCmd);
      }
    }
  }
  return m_cmdArray;
}

/** 向蓝牙设备发送指令*/
function wwriteBLECharacteristicCmdValue(cmdCharacteristicId, cmdValue, type) {
  var ss = device.deviceId + "-" + device.serviceId + '-' + cmdCharacteristicId + '--' + byte2hexStr(cmdValue);
  console.log("向蓝牙设备发送指令:" + ss);
  sendCmdCallBackListener({ msg: '发送成功', type: type });

  sendAckCmd(device.services, cmdValue, cmdCharacteristicId, device.devices);
}

/**
 * byte转16进制字符
 */
function byte2hexStr(byteBuffer) {
  var str = "";
  var arrBytes = new Uint8Array(byteBuffer);
  for (var i = 0; i < arrBytes.length; i++) {
    var tmp;
    var num = arrBytes[i];
    if (num < 0) {
      //此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理
      tmp = (255 + num + 1).toString(16);
    } else {
      tmp = num.toString(16);
    }
    if (tmp.length == 1) {
      tmp = "0" + tmp;
    }
    if (i > 0) {
      str += ":" + tmp;
    } else {
      str += tmp;
    }
  }
  return str;
}

//设置utc
export function setUtc() {
  var date = new Date();
  var date = formatTime(date);
  var headNum = sendUtcCmd(date, 'utc');
  console.log('headNum',headNum);
  
  return;
}

/**
* 发送设置Utc指令
*/
function sendUtcCmd(date, type, callback) {
  var cmdBuffer = getUtcCmdValue(date);
  var headNum = device.headerNumber;
  var valueBuffer = packageSendCmd(headNum, cmdBuffer);
  console.log('valueBuffer2', valueBuffer);
  sendBLECmdValue(valueBuffer, type, callback);
  return headNum;
}

/**设置时间**/
function getUtcCmdValue(timeDate) {
  var cmdId = 1;
  var version = protocolVersion * 16 + 0 * 8;
  var keyMark = 8;
  var ackType = 1;
  var valueLong = 4;
  var myUtc = (new Date(timeDate).getTime()) / 1000 - (new Date().getTimezoneOffset()) * 60;
  var time4 = myUtc / (256 * 256 * 256);
  var time3 = (myUtc % (256 * 256 * 256)) / 65536;
  var time2 = (myUtc % 65536) / 256;
  var time1 = myUtc % 256;
  var cmdByte = new ArrayBuffer(9);
  var cmdView = new Uint8Array(cmdByte);
  cmdView[0] = int2byte(cmdId);
  cmdView[1] = int2byte(version);
  cmdView[2] = int2byte(keyMark);
  cmdView[3] = int2byte(ackType);
  cmdView[4] = int2byte(valueLong);
  cmdView[5] = int2byte(time4);
  cmdView[6] = int2byte(time3);
  cmdView[7] = int2byte(time2);
  cmdView[8] = int2byte(time1);
  return cmdByte;
}