uniapp蓝牙封装及使用(工厂模式封装,多设备切换,OTA升级,重发)

301 阅读14分钟

uniapp蓝牙门锁功能(开门,录入指纹,密码,门卡,OTA升级,门锁设置音量语言等) ,蓝牙连接,发送应答,OTA升级分包,重发机制,蓝牙中断

当时第一次弄蓝牙,一开始以为特别简单,但是后面发现 与嵌入式工程师联调,因为嵌入式和前端常用的数据结构不同,导致遇到了很多问题,沟通也不畅通,所以记录一下

因为有多种多个类型的蓝牙设备,里面的加密方式,通信方式都不同,所以考虑采用工厂模式

  • 创建一个蓝牙基础类BluetoothManager
  • 创建不同的 智能锁设备类LockDevice,采集器类等继承BluetoothManager进行扩展或重写
  • bluetoothUtils 蓝牙通信相关的工具函数(进制转换,加密解密,累加和计算)
uniapp插件市场 ext.dcloud.net.cn/plugin?id=2…

蓝牙连接流程图以及代码实现

在这里插入图片描述

下面是一些具体的代码封装实现

BluetoothManager.js


/**
 * 蓝牙管理基础类
 * 处理蓝牙连接、状态管理等基础功能
 */
export default class BluetoothManager {
  constructor() {
    this.bluetoothState = 0 // 蓝牙连接状态 0断开 1连接 2连接中
    this.deviceId = '' // 蓝牙设备ID
    this.serviceId = '' // 服务ID
    this.writeuuid = '' // 写入特征值
    this.notifyuuid = '' // 通知特征值
  }

  /**
   * 打开蓝牙适配器
   */
  openBluetoothAdapter = () => {
    return new Promise((resolve, reject) => {
      uni.openBluetoothAdapter({
        success: () => {
          console.log('蓝牙初始化成功');
						resolve();
        },
        fail: (err) => {
					
          if (err.errMsg === 'openBluetoothAdapter:fail auth deny') {
						uni.showModal({
							title: '温馨提示',
							content: `获取蓝牙权限失败,是否进入设置页开启您的权限?`,
							confirmText: '开启授权',
							success: (showModalRes) => {
								if (showModalRes.confirm) {
									uni.openSetting({
										success: (openSettingRes) => {
											// 蓝牙权限是否已开启
                    if (openSettingRes.authSetting['scope.bluetooth']) {
                      // 如果权限已开启,重新调用 openBluetoothAdapter
                      this.openBluetoothAdapter()
                        .then(resolve)
                        .catch(reject);
                    } else {
                      // 如果权限仍未开启,显示错误信息
                      uni.showToast({
                        icon: 'none',
                        title: '蓝牙权限未开启,请手动开启',
                        duration: 2000
                      });
                      reject(err);
                    }
										},
										fail: (openSettingErr) => {
											console.error('打开设置页失败', openSettingErr);
											reject(openSettingErr);
										}

									});
								}else{
									// 用户取消授权
									uni.showToast({
										icon: 'none',
										title: '蓝牙权限未开启',
										duration: 2000
									});
									reject(err);
								}
							},
	
						})
					} else if (err.errCode == 10001) {
						uni.showToast({
							icon: 'none',
							title: '请查看手机蓝牙是否打开',
							duration: 2000
						});
						reject(err);	
					} else {
						uni.showToast({
							icon: 'none',
							title: '蓝牙打开失败,' + err.errMsg,
							duration: 2000
						});
						reject(err);	
					}

					
        },
      });
    });
  }
  /**
   * 获取搜索到的蓝牙设备信息
   */
  getBluetoothDevices = () => {
    return new Promise((resolve, reject) => {
      uni.getBluetoothDevices({
        success(res) {
          console.log('获取搜索到的蓝牙设备信息', res.devices);
          // // 过滤掉name为空或者未知设备的设备
          // let devices = res.devices.filter(obj=> {
          //   return obj.name !== '' && obj.name !== '未知设备';
          // });
          // console.log('有名称的蓝牙列表', devices);
          // bluetoothArr = devices;
          resolve(res.devices);
        },
        fail(err) {
          console.log('获取搜索到的蓝牙设备信息失败');
          reject(err);
        },
      });
    });
  }
  /**
   * 搜索蓝牙设备
   */
startDiscoveryBluetooth = () => {
	return new Promise((resolve, reject) => {
		uni.startBluetoothDevicesDiscovery({
			success(res) {
				console.log('开始搜寻附近的蓝牙外围设备成功', res);
				resolve();
			},
			fail(err) {
				console.log('开始搜寻附近的蓝牙外围设备失败', err);
				isSearch = false;
				if (err.errMsg ===
					'startBluetoothDevicesDiscovery.fail:location permission is denled') {
					let locationEnabled = uni.getSystemSetting().locationEnabled
					//获取微信APP授权设置
					let locationAuthorized = uni.getAppAuthorizeSetting().locationAuthorized
					if (!locationEnabled) {
						uni.showModal({
							content: '请开启手机位置信息重试',
							showCancel: false,
							success: function(res) {
								console.log(res);
							}
						});

					}
					if (locationAuthorized !== 'authorized') {
						uni.showModal({
							title: '提示',
							content: '请进入授权设置开启定位权限重试',
							showCancel: true,
							confirmText: '开启授权',
							success: function(res) {
								if (res.confirm) {
									console.log('用户点击确定')
									wx.openAppAuthorizeSetting({
										success(res) {

											// console.log('系统微信授权管理页',
											// 	res)
										}
									})
								}
							}
						})
					}
				}
				reject(err);
			},
		});
	});
}

/**
 * 停止搜索蓝牙设备
 */
stopDiscoveryBluetooth = () => {
	return new Promise((resolve, reject) => {
		uni.stopBluetoothDevicesDiscovery({
			success(res) {
				console.log('停止搜寻附近的蓝牙外围设备成功', res);
				resolve();
			},
			fail(err) {
				console.log('停止搜寻附近的蓝牙外围设备失败', err);
				reject(err);
			},
		})
	})
}
// 不能封装成promise
// /**
//  * 监听搜索到新设备的事件
//  */
// onBluetoothDeviceFound = () => {
// 	return new Promise((resolve, reject) => {
// 		uni.onBluetoothDeviceFound((res) => {
//       resolve(res);
// 			console.log('有名称的蓝牙列表', res);
// 		})
// 	})
// }

/**
 * 连接蓝牙设备
 */
connectBluetoothDevice = () => {
	return new Promise((resolve, reject) => {
		uni.createBLEConnection({
			deviceId:this.deviceId,
			success(res) {
				console.log('连接蓝牙设备成功', res);
				resolve();
			},
			fail(err) {
				console.log('连接蓝牙设备失败', err);

				if (err.errCode == 10001) {
					uni.showToast({
						icon: 'none',
						title: '蓝牙重连失败,请查看手机蓝牙是否打开',
						duration: 2000
					});

				} else {
					// 如果重连失败,就重新搜索
					this.deviceId = null
					uni.showToast({
						title: `蓝牙连接失败,${err.errMsg}`,
						icon: "none",
					});
				}
				reject(err);
			},
		})
	})
}

/**
 * 获取蓝牙设备所有服务(service)
 */
getBLEDeviceServices = () => {
	return new Promise((resolve, reject) => {
		uni.getBLEDeviceServices({
			deviceId:this.deviceId,
			success:res => {
				console.log('获取蓝牙设备所有服务(service)成功', res);
        this.serviceId = res.services[0].uuid;
				resolve(res);
			},
			fail:err => {
				console.log('获取蓝牙设备所有服务(service)失败', err);
				reject(err);
			}
		})
	})
}

/**
 * 获取蓝牙设备所有 characteristic(特征值)
 */
getBLEDeviceCharacteristics = () => {
	return new Promise((resolve, reject) => {
		uni.getBLEDeviceCharacteristics({
			deviceId:this.deviceId,
			serviceId:this.serviceId,
			success:res => {
				console.log('获取蓝牙设备所有 characteristic(特征值)成功', res.characteristics);
				
        for (var i = 0; i < res.characteristics.length; i++) {
					var model = res.characteristics[i];
					if (model.properties.write) {
						this.writeuuid = model.uuid; //特征值
					}
					if (model.properties.notify) {
						this.notifyuuid = model.uuid; //特征值
					}
				}
				resolve(res);
			},
			fail:err => {
				reject(err)
				console.log('获取蓝牙设备所有 characteristic(特征值)失败', err);
			}
		})
	})
}

/**
 * 设备特征值变化时的notify功能,订阅特征值
 * 
 */
notifyBLECharacteristicValueChange = () => {
	return new Promise((resolve, reject) => {
		uni.notifyBLECharacteristicValueChange({
			deviceId:this.deviceId,
			serviceId:this.serviceId,
			characteristicId:this.notifyuuid,
			state: true,
			success(res) {
				console.log('设置蓝牙接受函数成功', res);
				resolve(res);
			},
			fail(err) {
				resolve(err)
			}
		})
	})
}


// 不能封装成promise
// /**
//  * 监听蓝牙设备特征值的变化
//  * 接收数据
//  */
// onBLECharacteristicValueChange = () => {
// 	return new Promise((resolve, reject) => {
// 		uni.onBLECharacteristicValueChange((res) => {
// 			console.log('监听蓝牙设备特征值的变化', res);
// 			resolve(res);
// 		})
// 	})
// }

/**
 * 蓝牙连接状态的改变事件
 */
onBLEConnectionStateChange = () => {
	return new Promise((resolve, reject) => {
		uni.onBLEConnectionStateChange((res) => {
			console.log('蓝牙连接状态的改变事件', res);
			resolve(res);
		})
	})
}






}



LockDevice.js

继承了BluetoothManager 如果有多种类型设备,可以继续新增

import CryptoJS from 'crypto-js'
import SparkMD5 from "spark-md5"
import BluetoothManager from './BluetoothManager'



/**
 * 智能锁设备类
 * 处理与智能锁相关的具体业务逻辑
 */
export default class LockDevice extends BluetoothManager {
  constructor(deviceNum) {
    super()
    this.deviceNum = deviceNum   //设备号
    this.devCrc16 = calculateCRC16Modbus(strToHexCharCode(this.deviceNum)).padStart(4, 0).toUpperCase()
    this.secretkey = '123456'  // 设备秘钥-加密解密
    this.second = 0   //重发次数
    this.repeatFailTimer = null  //重发定时器 
    this.bluetoothState = 0 // 蓝牙连接状态 0未连接 1已连接 2连接中
    this.bt_info = null // 蓝牙设备信息

    this.serialNumber =  null   //采集器序列号
    this.fingerData =  ''      // 指纹数据
  }

  /**
   * 采集器录入指纹
   * @param {*录入目标} state 1开始 2结束
   */
  msg_collect_finger_print(state) {
    this.clear_fp_data() 
    console.log('采集器开始录入指纹');
    let msg_cmd = "21";
    let msg_type = "00";
    let msg_step = state === 1 ? '0x00' : '0xFE';
    let msg_ts_start = 'FFFFFFFF'; // 时间戳先写死
    let msg_ts_end = 'FFFFFFFF'; // 时间戳先写死
    let msg_data = msg_type + msg_step + msg_ts_start + msg_ts_end;
    // this.msg_collect_finger_callback = callback;
    this.sendBluetoothMessage(msg_cmd, msg_data);
  }

  /**
   * 采集器录入指纹反馈  
   */
  res_collect_finger_print_hardler(data, len) {
    let step = hex2int(data.slice(2, 4))  //录入阶段
    console.log('采集器录入指纹阶段', step)
    this.msg_collect_finger_callback && this.msg_collect_finger_callback(step,this.fingerData)
  }

  //采集器上发指纹数据反馈 
  rev_send_finger_print_hardler(data, len) {
    console.log("###  rev_send_finger_print_hardler采集器上发指纹数据反馈 ")

    // 包序号
    let serialNumber = data.slice(2, 4)
    // 指纹数据  128字节 256位 
    let fingerprintData = data.slice(4, 260)
    this.serialNumber = serialNumber
    console.log('采集器录入数据接收--', '包序号:' + serialNumber, '数据:' + fingerprintData)
    this.fingerData = this.fingerData + fingerprintData
    console.log('总数据', this.fingerData)
    // 接受成功再应答
    this.msg_response_finger_print()
  }

  clear_fp_data() {
		this.fingerData = '' //采集器指纹包数据
		this.serialNumber = null //采集器当前包序号

	}
  // 采集器上发指纹数据应答
  msg_response_finger_print() {
    //合成数据包
    let msg_cmd = "22"
    let msg_type = "00"
    let msg_data = msg_type + this.serialNumber
    let msg_len = get_msg_len(msg_data)
    let msg0 = msg_head + msg_ext + msg_cmd + msg_len + msg_data
    console.log('采集器数据接收应答', msg_data)
    this.sendBluetoothMessage(msg_cmd, msg_data,1);

  }

  /**
   * 手动设置密钥  采集器
   * 用于蓝牙通信加密
   */
  setSecretkey(dev_key) {
    this.secretkey = hex2String(get_MD5Hash(dev_key))
  }



  /**
 * 处理不同命令类型
 * @param {string} cmd - 命令类型
 * @param {string} dataInput - 数据内容
 * @param {number} dataLen - 数据长度
 */
  handleCommand(cmd, dataInput, dataLen) {
    // 清除定时器
    this.clear_Interval();

    // 命令处理映射表
    const commandHandlers = {
      '21': () => this.res_collect_finger_print_hardler(dataInput, dataLen), // 采集器指纹录入反馈
      '22': () => this.rev_send_finger_print_hardler(dataInput, dataLen), // 采集器指纹数据反馈
    };

    // 执行对应的命令处理函数
    const handler = commandHandlers[cmd];
    if (handler) {
      handler();
    } else {
      console.warn(`未知的命令类型: ${cmd}`);
    }
  }
  /**
  *  开始蓝牙连接
  */
  async startBluetooth() {
    try {
      this.setBLEState(2)
      await this.openBluetoothAdapter()
      const devices = await this.getBluetoothDevices() || []
      let flag = true
      devices.forEach(item => {
        if (item["localName"] === "123_E5B") {
          if (item["advertisData"] === this.devCrc16) {
            console.log('******** 开始连接 ')
            flag = false
            this.bt_info = item
            this.deviceId = item.deviceId
            this.connectToDevice()
          }
        }
        if (this.deviceNum === 'DC' && item["localName"] === "DC") {
          flag = false
          this.deviceId = item.deviceId
          this.bt_info = item
          this.connectToDevice()
        }
      })

      if (flag) this.startBluetoothDeviceDiscovery()
    } catch (error) {
      console.error('连接蓝牙失败', error)
      this.setBLEState(0)
    }
  }

  /**
   * 搜索蓝牙设备
   */
  async startBluetoothDeviceDiscovery() {
    try {
      uni.showLoading({
        title: "蓝牙搜索中...",
        mask: true
      });
      await this.startDiscoveryBluetooth()
      this.searchBTFail()
      uni.onBluetoothDeviceFound((res) => {
        const [device] = res.devices;
        const { localName, advertisData } = device;
        console.log('发现新设备', localName, device);
        if (localName === "123_E5B") {
          if (!advertisData) return;
          //判断是否是这个设备 
          if (advertisData === this.devCrc16) {
            console.log('******** 开始连接', crc);
            uni.hideLoading();
            console.log(`找到设备 ${localName}`);
            this.bt_info = device;
            this.deviceId = device.deviceId;
            this.connectToDevice();
          }
        }
        //  采集设备处理
        if (this.deviceNum === 'DC' && localName === "DC") {
          console.log('******** 开始连接 DC');
          uni.hideLoading();
          console.log(`找到设备 ${localName}`);
          this.bt_info = device;
          this.deviceId = device.deviceId;
          this.connectToDevice();
        }
      });
    } catch (e) {
      this.setBLEState(0)
      console.error('搜索设备失败', error)

    }


  }



  /**
   * 连接蓝牙设备
   */
  async connectToDevice() {
    try {
      uni.showLoading({
        title: "正在连接蓝牙...",
        mask: true
      });
      this.stopBluetoothDeviceDiscovery()
      console.log('连接蓝牙设备', this.deviceId)
      await this.connectBluetoothDevice()
      await this.getBLEDeviceServices()
      await this.getBLEDeviceCharacteristics()
      await this.notifyBLECharacteristicValueChange()

      uni.onBLECharacteristicValueChange(res => {
        console.log('onBLECharacteristicValueChange', res)
        this.listenValueChange(res)
      })

      uni.hideLoading()
      this.setBLEState(1)
      uni.showToast({
        title: this.bt_info.name + "蓝牙连接成功!",
        icon: "none",
      });
      this.BLEStateChange()

    } catch (error) {
      uni, hideLoading()
      this.setBLEState(0)
      console.error('连接设备失败', error)
    }
  }



  /**
   * 设置搜索蓝牙超时定时器
   */
  searchBTFail() {
    this.searchTimer = setTimeout(() => {
      uni.hideLoading()
      this.stopDiscoveryBluetooth(); // 停止搜索蓝牙
      this.setBLEState(0)
      uni.showToast({
        title: "蓝牙搜索超时,请靠近设备",
        icon: "error",
        duration: 2000
      })
      // clearTimeout(this.searchTimer)
      this.searchTimer = null
    }, 20 * 1000)
  }



  /**
   * 重连设备
   */
  reconnectBlue(){
    if(!this.deviceId){
      this.startBluetooth()
    }else{
      this.connectToDevice()
      
    }

  }



  /**
   * 停止搜索设备
   */
  stopBluetoothDeviceDiscovery() {
    if (this.searchTimer) clearTimeout(this.searchTimer)
    this.searchTimer = null
    this.stopDiscoveryBluetooth()
  }


  /**
    *  发送蓝牙消息的公共函数
    * @param {*数据类型} msg_cmd 
    * @param {*数据内容} msg_data 
    * @param {*重发次数} maxAttempts 
    * @param {*间隔时间} interval 
    * @returns 
    */
  sendBluetoothMessage(msg_cmd, msg_data,maxAttempts = 3,interval = 1500) {
    //公共部分
    let msg_head = "1234567";
    let msg_ext = "152";
    let msg_len = get_msg_len(msg_data);
    let msg0 = msg_head + msg_ext + msg_cmd + msg_len + msg_data;
    console.debug("发送蓝牙消息", msg0);
    this.repeat_send_bt_msg(msg0,maxAttempts,interval);
  }

  /**
    * 发送数据  
    * @param {*数据内容} msg0 
    * @param {*重发次数} maxAttempts 
    * @param {*间隔时间} interval 
    * @returns 
    */
  repeat_send_bt_msg(msg0, maxAttempts, interval) {
    if (!this.writeuuid) {
      console.log('UUID为空', this.writeuuid);
      return;
    }
    //数据加密处理 
    const msg0_hex = hex2String(msg0);
    const md5 = SparkMD5.hashBinary(msg0_hex).toUpperCase();
    const sum_data = get_sum(msg0 + md5);
    const msg = msg0 + md5 + sum_data;
    console.log('#######发送的完整信息', msg);
    const data = hex2String(msg);
    this.clear_Interval();
    let attempt = 0;
    const sendAttempt = () => {
      if (attempt >= maxAttempts) {
        console.log('重发数据失败');
        this.clear_Interval();
        if(maxAttempts === 1)return
        uni.showToast({
          title: '操作失败,请重试',
          icon: 'none'
        });
        return;
      }
      attempt += 1;
      console.log(`发送第${attempt}次数据...`);
      this.write(data)
        // .then(() => {
        //   console.log('数据发送成功');
        //   //          this.clear_Interval();
        //   //          // 执行发送成功的回调
        //   //          this.onSendSuccess();
        // })
        // .catch(err => {
        //   console.log('write写入失败', err);
        //   // 可以在这里增加更多的错误处理逻辑
        // });
    };
    // 第一次立即执行发送
    sendAttempt();
    // 如果重发次数大于1,则设置定时器
    if (maxAttempts > 1) {
      this.repeatFailTimer = setInterval(sendAttempt, interval);
    }
  }



  /**
   * 清除重发定时器
   */
  clear_Interval() {
    this.second = 0;
    if (this.repeatFailTimer) clearInterval(this.repeatFailTimer);
    this.repeatFailTimer = null;
  }


  /**
   * 
   * @param {*数据内容} hexadecimal 
   * @returns 发送蓝牙数据
   */
  write(hexadecimal) {
    const buf = hex2ArrayBuffer(hexadecimal);

    return new Promise((resolve, reject) => {
      const byte_cnt = 20;
      const index = Math.ceil(buf.byteLength / byte_cnt);
      let offset = 0;

      for (let i = 0; i < index; i++) {
        const buf_tmp = buf.slice(offset, offset + byte_cnt);
        offset += byte_cnt;

        uni.writeBLECharacteristicValue({
          deviceId: this.deviceId,
          serviceId: this.serviceId,
          characteristicId: this.writeuuid,
          value: buf_tmp,
          success: resolve,
          fail: (err) => {
            console.log('write写入失败', err);
            reject(err);
          }
        });
      }
    });
  }



  /**
 * 数据接收处理函数
 * @param {Object} res - 蓝牙数据响应对象
 */
  listenValueChange(res) {
    try {
      // 1. 数据解密和转换
      const hexData = arrayBuffer2Hex(res.value);
      const decryptedData = data_decrypt(hex2String(hexData), this.secretkey).out;
      const data = string2Hex(decryptedData);
      console.log("接收的明文数据:", data);

      // 2. 数据完整性校验
      if (!this.validateDataIntegrity(data)) {
        console.error("数据完整性校验失败");
        return;
      }

      // 3. 解析数据包
      // const version = data.slice(4, 6);
      const cmd = data.slice(6, 8);
      const dataLen = hex2int(data.slice(8, 12));
      const dataInput = data.slice(12);

      // 4. 根据命令类型处理数据
      this.handleCommand(cmd, dataInput, dataLen);

    } catch (err) {
      console.error("数据接收处理错误:", err);
    }
  }


  /**
 * 校验数据完整性
 * @param {string} data - 十六进制数据字符串
 * @returns {boolean} 校验结果
 */
  validateDataIntegrity(data) {
    // 1. 校验数据头
    const head = data.slice(0, 4);
    if (head != "1234567") return false;
    // 2. 解析数据包
    // const version = data.slice(4, 6);
    const cmd = data.slice(6, 8);
    const dataLen = hex2int(data.slice(8, 12));
    const dataInput = data.slice(12);
    // 3. 计算并验证MD5
    const dataCal = data.slice(0, 12 + dataLen * 2);
    const md5Cal = SparkMD5.hashBinary(hex2String(dataCal)).toString();
    const totalData = dataCal + md5Cal;
    const sumCal = get_sum(totalData).padStart(2, '0').toLowerCase();

    const md5 = dataInput.slice(dataLen * 2, -2);
    const sum = dataInput.slice((dataLen) * 2 + 32);

    return md5Cal === md5 && sumCal === sum;
  }



  /**
   * 蓝牙连接状态监听
   */
  BLEStateChange() {
    this.onBLEConnectionStateChange().then(res => {
      console.log('蓝牙连接状态改变', res);
      if (!res.connected) {
        this.setBLEState(0)
        uni.showToast({
          title: '蓝牙已断开',
          icon: 'none',
          mask: true,
          duration: 2000
        })

      }

    })


  }


  /**
   * 设置蓝牙连接状态
   */
  setBLEState(state) {
    this.bluetoothState = state
    store.commit('setBluetoothState', state)

  }




} 

bluetoothUtils工具函数

/**
 * @file bluetoothUtils.js
 * 
 * 本文件提供了蓝牙通信相关的工具函数,主要用于处理数据加密、解密、校验码计算以及数据格式转换。
 * 具体功能包括但不限于:
 * - 数据加密与解密:支持HMAC-SHA1、Base64、SHA256等算法;
 * - CRC校验码计算:实现MODBUS协议的CRC-16校验码计算;
 * - 数据格式转换:提供字符串与16进制字符串、ArrayBuffer之间的相互转换;
 * - 时间戳处理:获取当前时间戳及其16进制表示,支持带偏移量的时间戳生成;
 * - 消息长度计算:根据输入的消息数据计算其长度的16进制表示;
 * - 累加和计算:对输入的16进制字符串进行累加求和,返回低8位的结果。
 *
 * 注意事项:
 * - 本文件依赖于 `crypto-js` 库,请确保已正确安装并引入;
 * - 使用时请注意各函数的参数类型及返回值格式,确保调用正确。
 */
import crypto from "crypto-js";

/**
 * 使用HMAC-SHA1算法对数据进行加密。
 *
 * @param {string} data - 需要加密的原始数据字符串。
 * @param {string} encryptKey - 用于加密的密钥字符串。
 * @returns {string} - 返回加密后的16进制大写字符串。
 *
 * 注意事项:
 * - 确保 `crypto` 模块已正确引入并可用。
 * - 输入的 `data` 和 `encryptKey` 应为有效的字符串。
 */
export function encryptHMACSHA1(data, encryptKey){
  var result = crypto.HmacSHA1(crypto.enc.Utf8.parse(data), crypto.enc.Utf8.parse(encryptKey));
  return crypto.enc.Hex.stringify(result).toUpperCase();
}

/**
 * 将输入的字符串数据进行Base64加密
 * 
 * @param {string} data - 需要加密的字符串数据
 * @returns {string} 返回加密后的Base64编码字符串
 */
export function encryptBase64(data){
  return crypto.enc.Base64.stringify(crypto.enc.Utf8.parse(data));
}

/**
 * 使用SHA256算法对输入数据进行加密处理。
 * 
 * @param {string} data - 需要加密的原始数据。
 * @returns {string} 返回经过SHA256加密后的字符串结果。
 */
export function encrySHA256(data){
  return crypto.SHA256(data).toString();
}

/**
 * 计算MODBUS协议的CRC校验码
 * 
 * 该函数根据输入的数据数组,按照MODBUS协议的CRC-16算法计算CRC校验值,并返回一个包含两个元素的数组,
 * 数组中的元素为十六进制字符串形式的CRC校验码的低字节和高字节。
 * 
 * @param {Array} data - 需要计算CRC校验的数据数组,数组元素应为0-255之间的整数
 * @returns {Array} 包含两个十六进制字符串的数组,[低字节, 高字节]
 * 
 * 注意:
 * - 输入数据数组为空时,返回的CRC校验码为['00', '00']
 * - CRC校验码的计算过程中,crcValue初始值为0xFFFF
 * - 最终返回的CRC校验码是以十六进制字符串的形式表示,不足两位前面补0
 */
export function MODBUS_CRC(data) {
    let crcValue = 0xFFFF;
    for(let i=0;i<data.length;i++){
        crcValue^=data[i]&0xFFFF
        for(let j=0;j<8;j++){
            if(crcValue&0x0001){
                crcValue>>=1
                crcValue^=0xA001
            }else{
                crcValue>>=1
            }
        }
    }

    crcValue=crcValue.toString(16)
    let crcArr=new Array(2)
    crcArr[0]=crcValue.substring(2,4)
    crcArr[1]=crcValue.substring(0,2)
    return crcArr
}


/**
 * 计算Modbus协议的CRC16校验码
 * 
 * @param {string} dataHexString - 输入的16进制字符串数据,例如 "01020304"
 * @returns {string} - 返回计算后的CRC16校验码的16进制字符串,大写格式
 * 
 * 该函数将输入的16进制字符串转换为字节数组,并使用CRC16算法(多项式 0xA001)进行校验码计算。
 * 最终返回的CRC16值为16进制字符串形式,并且转换为大写。
 */
export function calculateCRC16Modbus(dataHexString) {
  const dataBytes = [];
  for (let i = 0; i < dataHexString.length; i += 2) {
    dataBytes.push(parseInt(dataHexString.substring(i, i + 2), 16));
  }

    let crc = 0xFFFF;
    const polynomial = 0xA001;  // This is the polynomial x^16 + x^15 + x^2 + 1
    
    for (const byte of dataBytes) {
        crc ^= byte;
        for (let i = 0; i < 8; i++) {
            if (crc & 0x0001) {
                crc = ((crc >> 1) ^ polynomial) & 0xFFFF;
            } else {
                crc >>= 1;
            }
        }
    }
    
    return crc.toString(16).toUpperCase();
}

/**
 * 将字符串转换为16进制字符编码字符串
 *
 * @param {string} str - 输入的字符串
 * @returns {string} 返回转换后的16进制字符编码字符串
 */
export function strToHexCharCode(str) {
  if (str === "")
    return "";
  var hexCharCode = [];
  for (var i = 0; i < str.length; i++) {
    hexCharCode.push((str.charCodeAt(i)).toString(16));
  }
  return hexCharCode.join("");
}

// // hex转json字符串,16进制ASCII
// var hextoString2 = function (hex) {
//     var arr = hex.split("")
//     var out = ""
//     for (var i = 0; i < arr.length / 2; i++) {
//         var tmp = "0x" + arr[i * 2] + arr[i * 2 + 1]
//         var charValue = String.fromCharCode(tmp);
//         out += charValue
//     }
//     return out
// };

// // json字符串转hex
// var stringtoHex2 = function (str) {
//     var val = "";
//     for (var i = 0; i < str.length; i++) {
//         if (val == "")
//             val = str.charCodeAt(i).toString(16);
//         else
//             val += str.charCodeAt(i).toString(16);
//     }
//     val += "0a"
//     return val
// }

// 字符串转16进制字符串
export function string2Hex(str) {
  let val = ""
  for (let i = 0; i < str.length; i++) {
  if (val == "")
    val = str.charCodeAt(i).toString(16).slice(-2);
  else
    val += str.charCodeAt(i).toString(16).slice(-2);
  }
  return val
}

// 16进制字符串转ArrayBuffer
export function hex2ArrayBuffer(hex_str) {
      // let hex_str = 'AA5504B10000B5'
      let typedArray = new Uint8Array(hex_str.match(/[\da-f]{2}/gi).map(function (h) {
        return parseInt(h, 16)
      }))
      let buffer = typedArray.buffer
      return buffer
}

/**
 * 将ArrayBuffer转换为16进制字符串
 *
 * @param {ArrayBuffer} buffer - 输入的ArrayBuffer对象
 * @returns {string} 返回转换后的16进制字符串
 */
export function arrayBuffer2Hex(buffer) {
      const hexArr = Array.prototype.map.call(
        new Uint8Array(buffer),
        function (bit) {
          return ('00' + bit.toString(16)).slice(-2)
        }
      )
      return hexArr.join('')
}

/**
 * 将16进制字符串转换为普通字符串
 *
 * @param {string} hex_str - 输入的16进制字符串
 * @returns {string} 返回转换后的普通字符串
 */
export function hex2String(hex_str) {
    
    var move_flag = false
    
    if(hex_str.length % 2 !== 0)
    {
      hex_str = hex_str + "0"
      move_flag = true
    }
  
      let trimedStr = hex_str.trim();
      let rawStr = trimedStr.substr(0,2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr;
      let len = rawStr.length;
    
    /*
      if(len % 2 !== 0) {
        console.log("Illegal Format ASCII Code!");
        return "";
      }
    */
   
      let curCharCode;
      let resultStr = [];
      for(let i = 0; i < len;i = i + 2) {
        curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
        resultStr.push(String.fromCharCode(curCharCode));
      }
    
    if(move_flag == true)
    {
      resultStr = resultStr.slice(0, -1)
    }
    
      return resultStr.join("");
    }

/**
 * 将字符串转换为ArrayBuffer
 *
 * @param {string} str - 输入的字符串
 * @returns {ArrayBuffer} 返回转换后的ArrayBuffer对象
 */
export function string2ArrayBuffer(str) {
      // 首先将字符串转为16进制
      let val = ""
      for (let i = 0; i < str.length; i++) {
        if (val === '') {
          val = str.charCodeAt(i).toString(16)
        } else {
          val += ',' + str.charCodeAt(i).toString(16)
        }
      }
      // 将16进制转化为ArrayBuffer
      return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function (h) {
        return parseInt(h, 16)
      })).buffer
}

/**
 * 将ArrayBuffer转换为普通字符串
 *
 * @param {ArrayBuffer} buffer - 输入的ArrayBuffer对象
 * @returns {string} 返回转换后的普通字符串
 */
export function arrayBuffer2String(buffer) {
      return String.fromCharCode.apply(null, new Uint8Array(buffer))
}

/**
 * 将16进制字符串转换为整数
 *
 * @param {string} hex - 输入的16进制字符串
 * @returns {number} 返回转换后的整数值
 */
export function hex2int(hex) {
    var len = hex.length, a = new Array(len), code;
    for (var i = 0; i < len; i++) {
        code = hex.charCodeAt(i);
        if (48<=code && code < 58) {
            code -= 48;
        } else {
            code = (code & 0xdf) - 65 + 10;
        }
        a[i] = code;
    }
    
    return a.reduce(function(acc, c) {
        acc = 16 * acc + c;
        return acc;
    }, 0);
}




export function get_time_from_ts(ts)
{	
  //var commonTime = new Date(ts*1000).Format("yyyy-MM-dd hh:mm:ss");
  
  var commonTime = new Date(ts*1000).toLocaleString();
    
  return commonTime
}



/**
 * 获取当前时间戳加上指定偏移量后的16进制表示
 *
 * @param {number} val - 时间偏移量(秒)
 * @returns {string} 返回转换后的16进制时间戳字符串
 */
export function get_time_ts(val)
{
  var unixTime = Math.floor(Date.now() / 1000);
  unixTime += val;
  return unixTime.toString(16).toUpperCase();
}

/**
 * 获取消息长度的16进制表示
 *
 * @param {string} msg_data - 输入的消息数据(16进制字符串)
 * @returns {string} 返回消息长度的16进制表示(4位,大写)
 */
export function get_msg_len(msg_data){
  return (msg_data.length/2).toString(16).padStart(4, '0').toUpperCase() 
}

/**
 * 将数字转换为指定长度的16进制字符串,并补足前导零
 *
 * @param {number} msg - 输入的数字
 * @param {number} cnt - 目标16进制字符串的长度
 * @returns {string} 返回转换后的16进制字符串(大写)
 */
export function get_hex_str(msg, cnt){
  return msg.toString(16).padStart(cnt, '0').toUpperCase()
}
/**
 * 
 * 将数字字符串转换为16进制字符串
 * 处理方式:将每个字符视为单独的数字,并将其转换为16进制表示。
 * 每个字符转换后的结果会用padStart(2, '0')确保是两位数(不足两位前面补0),然后转为大写
 * 
 * @param {String} msg - 输入的数字字符串
 * @returns {string} 返回转换后的16进制字符串(大写)
 */
export function get_num2hex(msg)
{
  let ret = ''
  for(let i = 0; i < msg.length; i++) {
    ret += msg[i].toString().padStart(2, '0').toUpperCase()
  }
  return ret
}


// /**
//  * 将二进制字符串转换为16进制字符串
//  *
//  * @returns {string} 返回转换后的16进制字符串(大写)
//  */
// export function get_bin2hex(msg)
// {
//   var ret = ''
//   for(let i = 0; i < msg.length; i++) {
//     ret += string2Hex(msg[i]).padStart(2, 0).toUpperCase()
      
//   }
    
//   return ret
// }

/**
 * 加密函数
 *
 * @param {string} data - 待加密的数据
 * @param {string} key - 加密密钥
 * @returns {Object} 返回加密后的结果对象,包含属性 `out`
 */
export function data_encrypt(data, key) {
    let result = '';
    let tmp = '';
    for(var i = 0; i < data.length; i++) {
        tmp = String.fromCharCode(~(data.charCodeAt(i) ^ key.charCodeAt(i % key.length)))
        result += tmp
    }
    return {"out": result}
}

/**
 * 解密函数
 *
 * @param {string} data - 待解密的数据
 * @param {string} key - 解密密钥
 * @returns {Object} 返回解密后的结果对象,包含属性 `out`
 */
export function data_decrypt(data, key) {
    let result = '';
    let tmp = '';
    for(var i = 0; i < data.length; i++) {
        tmp = String.fromCharCode(~(data.charCodeAt(i) ^ key.charCodeAt(i % key.length)))
        result += tmp
    }
   return {"out": result}
}

/**
 * 计算数据的累加和,并返回结果的低8位
 * 校验和
 *
 * @param {string} data - 输入的16进制字符串
 * @returns {string} 返回累加和的低8位(16进制字符串,大写)
 */
export function get_sum(data) {
  let sum = 0;
  for (let index = 0; index < data.length; index += 2) {
    let str_data = data.slice(index, index + 2);
    sum += parseInt(str_data, 16);
  }
  let result = sum & 0xff;
  return result.toString(16).toUpperCase();
}



/**
 * 计算输入字符串的MD5哈希值,并将其转换为大写字符串。
 *
 * @param {string} input - 需要计算MD5哈希值的输入字符串。
 * @returns {string} 返回计算后的MD5哈希值,以大写十六进制字符串形式表示。
 */
export function get_MD5Hash(input) {
  return crypto.MD5(input).toString().toUpperCase();
}