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();
}