uni-app 微信小程序蓝牙模块的解耦封装-持续更新

一、核心代码

core.js

import {
   
	showModal,
	stringToHex,
	sleep,
	uniqueArr,
	arrayBufferToHexString,
	hexStringToArrayBuffer,
	compareVersion
} from './tool.js'
//buffer分包,根据MTU的长度进行分包,蓝牙分发官方建议20个字节一包,但是随着硬件的发展,可设置一次传输的最大单元
//根据MTU的大小,把所发数据进行分包处理
const getBufferArrayByMtu = (data) => {
   
	let writePacketLen = bluetoothCore.mtu;
	let num = 0;
	if (typeof data == "undefined") return [];
	let allBuffer = hexStringToArrayBuffer(data); //16进制字符串转ArrayBuffer
	let bufferLen = allBuffer.byteLength;
	let bufferArray = [];
	while (bufferLen > 0) {
   
		let buffer;
		if (bufferLen > writePacketLen) {
   
			buffer = allBuffer.slice(num, num + writePacketLen);
			num = num + writePacketLen;
			bufferLen -= writePacketLen;
			bufferArray.push(buffer);
		} else {
   
			buffer = allBuffer.slice(num, num + bufferLen);
			num += bufferLen;
			bufferLen -= bufferLen;
			bufferArray.push(buffer);
		}
	}
	return bufferArray;
}



// 3位数是自己定义的,4位数是蓝牙功能确定的,因为页面有交互,故定义一些字段进行交互提示
export const bluetoothStatus = {
   
	'000': '正在搜索蓝牙设备',
	'001': '正在连接',
	'002': '连接成功',
	'003': '正在通讯',
	'004': '通讯完成',
	'005': '请检查设备是否开启蓝牙!',
	'006': '蓝牙已断开',
	"-1": "已经连接",
	"0": "正常",
	"10000": "开启蓝牙后,才可连接设备",
	"10001": "开启蓝牙后,才可连接设备",
	"10002": "没有找到指定设备",
	"10003": "连接失败",
	"10004": "没有找到指定服务",
	"10005": "没有找到指定特征值",
	"10006": "当前连接已断开",
	"10007": "当前特征值不支持此操作",
	"10008": "其余所有系统上报的异常",
	"10009": "Android 系统特有,系统版本低于 4.3 不支持 BLE",
	"10012": "操作超时",
	"10013": "连接 deviceId 为空或者是格式不正确"
}



//核心的重要的bluetoothCore对象
let bluetoothCore = {
   
	bluetoothStatus: {
   }, //有时候需要自己定义通讯的提示文字
	errorCallback: () => {
   }, //通讯过程中出现错误的回调处理方法
	isErrorShowodal: true, //错误消息是否用showModal展示,因为有些错误不需要展示,或者已经展示在了其他的地方
	tip: '', //展示在页面上的信息,包括正式进行的提示和蓝牙反馈的错误提示
	status: '', //需要像页面展示的蓝牙状态
	SERVICEID: "0000FEE7-0000-2222-8000-00805F9B34FB",
	NOTIFYID: "0000FEC1-0000-2222-8000-00805F9B34FB",
	WRITEID: "0000FEC1-0000-2222-8000-00805F9B34FB",
	STARTPACKET: '54424F582C435143542C', //报文回复的开始标志
	ENDPACKET: '2C54424F58454E44', //报文回复的结束标志
	planConnectBluetoothDevice: {
   }, //计划去连接的蓝牙设备
	connectedBluetoothDevice: {
   }, //已经连接上的蓝牙设备
	bluetoothList: [], //展示的蓝牙列表
	isDiscovering: true, //0为未在搜索,1为正在搜索
	isImmediateConnect: true, //是否直接连接
	isImmediateWrite: true, //是否立即写数据
	filterProp: 'name', // 蓝牙过滤的属性名称
	filterPattern: /^\d{
   10}$/, // 过滤蓝牙列表的正则表达式
	isConnected: false, //设备是否连接,实际的连接状态
	sendPacket: '', //当前发送的报文
	respondPacket: '', //监听到的组合报文
	isInit: false, //是否进行了初始化,只进行初始化一次就可以了
	mtu: 20,
	setTip: {
   }, //哪一步不需要设置提示 {'004':false}
	discoveryTimer: null, // 蓝牙搜索停止的计时器
	bluetoothDevicesDiscoveryTimeout: 10000, //多少秒后停止搜索蓝牙
	isStopExe: false, //蓝牙断开后,是否停止执行
	isCancelOparate: false, //是否中途取消操作,取消操作后,在异步方法中停止执行
	isReceivePacketFinished: false, //是否此次接报文完毕,如果接收报文完毕,其他接收的就是日志,此字段表示,在日志开始的时候,可以发送指令,指令正常回复有开始和结束标志,但是日志报文没有
	communicationStatus: 0, //每次通讯前设置为0,通讯成功为1,通讯成功之前就失败为-1,有可能在此次通讯中断开了蓝牙,要把有些状态还原,比如开关状态
	parsePacketCallback: (result) => {
   }, //报文解析完后的回调
	//解析报文的方式,每种蓝牙设备的协议方式不一样,故解析也不一样
	parsePacketMethod: (value) => {
   

	},
	//执行蓝牙的取消操作,由于蓝牙异步操作,在以下几个重要地方做拦截操作,搜索监听中、下发指令中、解析指令中
	cancelOparate: () => {
   
		bluetoothCore.isCancelOparate = true;
		bluetoothCore.status = '000';
		bluetoothCore.tip = '正在搜索蓝牙设备';
	},
	//解析日志报文不需要判断首帧和尾帧,直接解析收到的字符串
	parseLogPacketMethod: () => {
   

	},
	//蓝牙连接后的回调
	bluetoothConnectedCallback: (connected) => {
   

	},
	//下发指令,调用这个方法
	writePacket: (params) => {
   
		if (bluetoothCore.isCancelOparate) return;
		bluetoothCore.setBlueTip('003'); //正在通讯
		bluetoothCore.writeDataToDeviceBySubPackage();
	},
	//蓝牙已断开后,不应该在提示其他异步的操作
	setBlueTip: (status, isError = false) => {
   
		if (!bluetoothCore.isStopExe && !bluetoothCore.isCancelOparate) {
   
			bluetoothCore.status = status;
			let newBluetoothStatus = Object.assign({
   }, bluetoothStatus, bluetoothCore
				.bluetoothStatus); // 有时候要自己定义通讯的提示文字
			let isSetTip = bluetoothCore.setTip[status] + ''; // 如果设置了false,则不显示,默认为显示
			if (isSetTip !== 'false') {
    //有些状态下,不需要告知通讯完成
				bluetoothCore.tip = newBluetoothStatus[status];
			}
			//如果再调用蓝牙API的时候,发生了错误,则看是否提示用户
			if (isError) {
   
				if (bluetoothCore.isErrorShowodal) {
   
					showModal(bluetoothCore.tip);
				}
				//如果再通讯成功之前就失败了,则认为此次通讯是不成功的
				if (bluetoothCore.communicationStatus == 0) {
   
					bluetoothCore.communicationStatus = -1;
				}
				bluetoothCore.errorCallback();
			}
			if (status == '006') {
    // 如果状态等于006,则停止执行,停止显示后面的内容
				bluetoothCore.isStopExe = true;
			}
		}

	},
	//获取在蓝牙模块生效期间所有已发现的蓝牙设备,包括已经和本机处于连接状态的设备
	getBluetoothDevices: () => {
   
		return new Promise((resolve) => {
   
			uni.getBluetoothDevices({
   
				success(res) {
   
					let devicesList = bluetoothCore.filterDevices(res.devices);
					resolve(devicesList);
				}
			})
		})
	},
	//解析报文,判断解析的是否是日志
	parsePacket(val, isLog = false) {
   
		if (bluetoothCore.isCancelOparate) return;
		bluetoothCore.communicationStatus = 1; //通讯成功标志
		bluetoothCore.setBlueTip('004'); // 通讯完成
		if (!isLog) {
   
			console.log(`正常通讯回复的报文数据-`, val);
			let result = bluetoothCore.parsePacketMethod(val);
			bluetoothCore.parsePacketCallback(result)
		} else {
   
			bluetoothCore.parseLogPacketMethod(val)
		}
	},
	async clickCreateBLEConnection() {
   
		let devicesList = await bluetoothCore.getBluetoothDevices();
		if (devicesList.length) {
    //判断已搜索的设备里是否有目标设备,如果有,则直接连接,如果没有,则先搜索在连接
			bluetoothCore.planConnectBluetoothDevice = Object.assign({
   }, bluetoothCore
				.planConnectBluetoothDevice, devicesList[0])

			bluetoothCore.createBLEConnection();
		} else {
   
			bluetoothCore.startBluetoothDevicesDiscovery();
		}
	},
	searchBluetooth(params) {
   
		bluetoothCore = Object.assign(bluetoothCore, params);
		if (!bluetoothCore.isInit) {
    // 如果没有进行初始化,则先进行初始化
			bluetoothCore.init();
		} else {
    //如果进行了初始化,则直接进行搜索
			bluetoothCore.startBluetoothDevicesDiscovery();
		}
	},
	//点击设备连接操作
	operateBluetooth(params) {
   
		console.log('params==', params)
		bluetoothCore.isStopExe = false;
		bluetoothCore.isCancelOparate = false;
		bluetoothCore = Object.assign(bluetoothCore, params);
		//先生成一个报文
		if (!bluetoothCore.isInit) {
    // 如果没有进行初始化,则先进行初始化
			bluetoothCore.init();
		} else {
    // 判断是否连接
			if (bluetoothCore.isConnected) {
    // 判断当前连接的车辆是否是现在的目标车辆
				if (bluetoothCore.connectedBluetoothDevice[bluetoothCore.filterProp] == bluetoothCore
					.planConnectBluetoothDevice[bluetoothCore.filterProp]) {
   
					//如果当前连接的设备是目标设备,则直接写入数据
					bluetoothCore.writePacket();
				} else {
    // 断开蓝牙,再连接目标设备
					bluetoothCore.closeBLEConnection(() => {
   
						bluetoothCore.clickCreateBLEConnection();
					})
				}
			} else {
   
				bluetoothCore.clickCreateBLEConnection();
			}
		}
	},
	// 初始化蓝牙模块
	init() {
   
		// 初始化蓝牙对象
		uni.openBluetoothAdapter({
   
			success(res) {
   
				bluetoothCore.isInit = true;
				bluetoothCore.openBluetoothAdapterCallback();
				console.log("打开蓝牙设配器成功", res)
			},
			fail(res) {
   
				console.log("打开蓝牙设配器失败", res)
				bluetoothCore.setBlueTip(res.errCode, true);
			},
			complete() {
   

			}
		})
	},
	openBluetoothAdapterCallback() {
   
		// 监听蓝牙适配器的变化
		uni.onBluetoothAdapterStateChange((res) => {
   
			console.log('蓝牙适配器状态变化', res);
		});

		// 监听搜索新设备
		uni.onBluetoothDeviceFound((res) => {
   
			//console.log('搜索到新设备', res);
			//1、有可能展示列表,有可能直接连接
			if (bluetoothCore.isCancelOparate) return;
			bluetoothCore.bluetoothDeviceFoundCallback(res.devices);
		});

		// 监听蓝牙连接状态
		uni.onBLEConnectionStateChange((res) => {
   
			console.log('蓝牙连接状态变化', res);
			bluetoothCore.isConnected = res.connected;
			bluetoothCore.bluetoothConnectedCallback(res.connected);
			if (!res.connected) {
   
				bluetoothCore.setBlueTip('006', true); //蓝牙已断开
			}
		});

		// 监听MTU的变化
		uni.onBLEMTUChange((res) => {
   
			console.log('MTU变化', res);
			//获取到的mtu为517,但是设置的时候只能为最大512
			bluetoothCore.mtu = res.mtu - 10;
		});

		bluetoothCore.startBluetoothDevicesDiscovery();
	},

   //过滤搜索到的设备,如果搜索了立马连接,则返回指定设备,否则就只是简单的搜索列表
	filterDevices: (devices) => {
   
		let targetDevices = [];
		targetDevices = devices.filter((item) => {
   
			let name = item.name;
			//console.log("搜索到的设备名称:",name)
			if (bluetoothCore.isImmediateConnect) {
    //返回指定的那一个
				return name.indexOf(bluetoothCore.planConnectBluetoothDevice[bluetoothCore
					.filterProp]) != -1;
			} else {
    //返回列表
				return name != "" && bluetoothCore.filterPattern.test(name);
			}
		});
		return targetDevices;
	},
	//监听搜索到的设备
	bluetoothDeviceFoundCallback(devices) {
   
		let targetDevices = bluetoothCore.filterDevices(devices);
		console.log('搜索到的目标设备', targetDevices)
		if (targetDevices.length) {
   
			if (bluetoothCore.isImmediateConnect) {
   
				//搜索到蓝牙设备后,则停止蓝牙搜索
				//清除蓝牙搜索的定时器
				clearTimeout(bluetoothCore.discoveryTimer);
				bluetoothCore.planConnectBluetoothDevice = Object.assign({
   }, bluetoothCore
					.planConnectBluetoothDevice, targetDevices[0]); //合并参数  
				bluetoothCore.bluetoothList = targetDevices;
				bluetoothCore.stopBluetoothDevicesDiscovery(() => {
   
					bluetoothCore.createBLEConnection();
				})

			} else {
   
				bluetoothCore.bluetoothList = uniqueArr([...bluetoothCore.bluetoothList, ...targetDevices]);
			}
		}
	},
	createBLEConnection() {
   
		bluetoothCore.setBlueTip('001');
		console.log('蓝牙连接')
		uni.createBLEConnection({
   
			deviceId: bluetoothCore.planConnectBluetoothDevice.deviceId,
			success(res) {
   
				bluetoothCore.setBlueTip('002'); //连接成功
				console.log(`与蓝牙设备${
   bluetoothCore.planConnectBluetoothDevice.name}创建连接成功`, res);
				bluetoothCore.isConnected = true;
				bluetoothCore.connectedBluetoothDevice = Object.assign({
   }, bluetoothCore
					.planConnectBluetoothDevice);
				//如果是安卓手机并且安卓系统 5.1 以上版本有效
				uni.getSystemInfo({
   
					success: function(res) {
   
						// res.platform 可以获取手机的操作系统类型,如 "android", "ios"
						// res.system 可以获取手机的操作系统版本,如 "Android 5.1.1"
						console.log("操作系统类型", res.platform);
						console.log("操作系统版本", res.system);
						if (res.platform === 'android' && compareVersion(res.system.split(' ')[
									1]
								.toString(), '5.1.0') >=
							0) {
   
							// 在安卓手机且系统版本大于等于 5.1.0 时执行相应的逻辑
							uni.setBLEMTU({
   
								deviceId: bluetoothCore.connectedBluetoothDevice
									.deviceId,
								mtu: bluetoothCore.mtu,
								success(res) {
   
									console.log('设置mtu成功', res)
								},
								fail(res) {
   
									console.log('设置mtu失败', res)
								},
								complete() {
   
									bluetoothCore.getBLEDeviceServices();
								}
							});
						} else {
   
							console.log('设置mtu不符合条件');
							bluetoothCore.getBLEDeviceServices();
						}
					}
				});
			},
			fail(res) {
   
				console.log('蓝牙连接失败', res);
				bluetoothCore.setBlueTip(res.errCode, true);
			}
		})
	},
	//ios必须要执行
	getBLEDeviceServices() {
   
		uni.getBLEDeviceServices({
   
			deviceId: bluetoothCore.connectedBluetoothDevice.deviceId,
			success(res) {
   
				console.log(`获取${
   bluetoothCore.connectedBluetoothDevice.deviceId}服务成功`, res);
				bluetoothCore.getBLEDeviceCharacteristics();
			}
		})
	},
	//ios必须要执行
	getBLEDeviceCharacteristics() {
   
		uni.getBLEDeviceCharacteristics({
   
			deviceId: bluetoothCore.connectedBluetoothDevice.deviceId,
			serviceId: bluetoothCore.SERVICEID,
			success: function(res) {
   
				console.log(`获取${
   bluetoothCore.connectedBluetoothDevice.deviceId}特征值成功`,
					res);
				bluetoothCore.notifyAndOnBLECharacteristicValueChange();
			},
			fail(res) {
   
				console.log(`获取${
   bluetoothCore.connectedBluetoothDevice.deviceId}特征值失败`,
					res);
			}
		})
	},

	//判断报文,如果字符串中含有帧头,则是命令回复,为了防止有分包的情况,设置isReceivePacketFinished为标识字段,否则为日志标志
	notifyValueCallback(val) {
   
		console.log('设备监听返回的报文', val)
		let start = val.substring(0, bluetoothCore.STARTPACKET.length);
		if (start == bluetoothCore.STARTPACKET) {
    //如果有报文开始帧,重置接收的报文
			bluetoothCore.respondPacket = "";
			bluetoothCore.isReceivePacketFinished = false;
		}
		bluetoothCore.respondPacket = bluetoothCore.respondPacket + val;
		let end = bluetoothCore.respondPacket.substring(bluetoothCore.respondPacket
			.length -
			bluetoothCore.ENDPACKET.length);

		if (bluetoothCore.isReceivePacketFinished) {
    //此段标志为日志报文
			bluetoothCore.parsePacket(val, true)
		}
		if (end == bluetoothCore.ENDPACKET) {
   //如果有报文结束帧,则重置接收的报文,把带有帧头帧尾的报文进行解析
			bluetoothCore.isReceivePacketFinished = true;
			let respondPacket = bluetoothCore.respondPacket;
			bluetoothCore.respondPacket = '';
			bluetoothCore.parsePacket(respondPacket, false);
		}

	},

	notifyAndOnBLECharacteristicValueChange() {
   
		//防止没有更换状态
		uni.notifyBLECharacteristicValueChange({
   
			state: true,
			deviceId: bluetoothCore.connectedBluetoothDevice.deviceId,
			serviceId: bluetoothCore.SERVICEID,
			characteristicId: bluetoothCore.NOTIFYID,
			success(res) {
   
				if (bluetoothCore.isImmediateWrite) {
   //如果notify监听成功,则直接写报文
					bluetoothCore.writePacket();
				}
				console.log("开启notify通知模式成功", res);
				uni.onBLECharacteristicValueChange((res) => {
   
					let val = arrayBufferToHexString(res.value);
					bluetoothCore.notifyValueCallback(val);

				})
			},
			fail(res) {
   }
		})
	},
	writeBLECharacteristicValue(value, isLastPackage = true) {
   
		console.log('写入的数据', arrayBufferToHexString(value))
		uni.writeBLECharacteristicValue({
   
			deviceId: bluetoothCore.connectedBluetoothDevice.deviceId,
			serviceId: bluetoothCore.SERVICEID,
			characteristicId: bluetoothCore.WRITEID,
			value,
			success(res) {
   
				console.log("写入数据成功", res);
				//判断是否是最后一包,如果是最后一包,则开启通讯计时器
			},
			fail(res) {
   
				console.log("写入数据失败", res);
				bluetoothCore.setBlueTip(res.errCode, true);
			}

		})
	},

	//不分包发送数据
	writeDataToDevice() {
   
		let allBuffer = hexStringToArrayBuffer(bluetoothCore.sendPacket);
		bluetoothCore.writeBLECharacteristicValue(allBuffer);
	},
	//分包发送数据
	async writeDataToDeviceBySubPackage() {
   
		bluetoothCore.respondPacket = '';
		let bufferArray = getBufferArrayByMtu(bluetoothCore.sendPacket);
		for (let i = 0; i < bufferArray.length; i++) {
   
			await sleep(10); //同步延迟1ms
			bluetoothCore.writeBLECharacteristicValue(bufferArray[i], i == bufferArray.length - 1);
		}
	},
	startBluetoothDevicesDiscovery() {
   
		bluetoothCore.isDiscovering = true;
		bluetoothCore.setBlueTip('000'); //正在搜索蓝牙设备
		uni.startBluetoothDevicesDiscovery({
   
			services: [], //添加了SERVICEID搜索不到蓝牙
			allowDuplicatesKey: false, //是否允许重复上报同一设备。如果允许重复上报,则 uni.onBlueToothDeviceFound 方法会多次上报同一设备,但是 RSSI 值会有不同。
			powerLevel: 'high',
			success() {
   
				console.log("开始蓝牙搜索成功");
				bluetoothCore.discoveryTimer = setTimeout(() => {
   
					bluetoothCore.stopBluetoothDevicesDiscovery();
				}, bluetoothCore.bluetoothDevicesDiscoveryTimeout)
			},
			fail(res) {
   
				console.log("开始蓝牙搜索失败:", res);
			}
		})
	},
	stopBluetoothDevicesDiscovery(callback = () => {
   }) {
   
		bluetoothCore.isDiscovering = false;
		//如果停止了,都没有搜索到蓝牙设备,则认为没有搜索到蓝牙设备
		if (!bluetoothCore.bluetoothList.length) {
   
			bluetoothCore.setBlueTip('005', true); //请检查设备是否开启蓝牙
		}
		uni.stopBluetoothDevicesDiscovery({
   
			success() {
   
				callback();
				console.log("停止蓝牙搜索成功");
			},
			fail(res) {
   
				console.log("停止蓝牙搜索失败:", res);
			},

		})
	},
	closeBLEConnection(callback = () => {
   }) {
   
		//关闭蓝牙连接
		uni.closeBLEConnection({
   
			deviceId: bluetoothCore.connectedBluetoothDevice.deviceId,
		})
	},
	// 销毁蓝牙模块
	destroy() {
   
		bluetoothCore.isInit = false;
		// 取消搜索监听
		uni.offBluetoothDeviceFound();

		// 取消蓝牙连接状态的监听
		uni.offBLEConnectionStateChange();

		// 取消蓝牙适配器变化的监听
		uni.offBluetoothAdapterStateChange();

		// 取消MTU的监听
		uni.offBLEMTUChange();

		// 取消特征值变化的监听
		uni.offBLECharacteristicValueChange()
		//关闭蓝牙连接
		bluetoothCore.closeBLEConnection();

		//关闭蓝牙模块
		uni.closeBluetoothAdapter()

	}
}


export default bluetoothCore;


二、工具代码

tool.js

const iconv = require('iconv-lite');
//十六进制转字符串
export const hexToStr = function(hex, encoding) {
   
	var trimedStr = hex.trim();
	var rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr;
	var len = rawStr.length;

	var curCharCode;
	var resultStr = [];
	for (var i = 0; i < len; i = i + 2) {
   
		curCharCode = parseInt(rawStr.substr(i, 2), 16);
		resultStr.push(curCharCode);
	}
	var val = "";
	var arr = resultStr;
	for (let i = 0; i < arr.length; i++) {
   
		val += String.fromCharCode(arr[i]);
	}
	return val;
}


//CRC16计算函数
export const calculateCRC16 = (data) => {
   
	// 初始化CRC16校验值
	let crc = 0xFFFF;

	// 遍历输入数据
	for (let i = 0; i < data.length; i++) {
   
		crc ^= (data.charCodeAt(i) & 0xFF);

		for (let j = 0; j < 8; j++) {
   
			if ((crc & 0x0001) !== 0) {
   
				crc >>= 1;
				crc ^= 0xA001;
			} else {
   
				crc >>= 1;
			}
		}
	}

	// 返回计算后的CRC16校验值
	return crc.toString(16).toUpperCase();
}

//十进制转十六进制
export const tenToHex = (num, digit = 4) => {
   
	const hex = num.toString(16);
	return hex.padStart(digit, 0);
}

export const showModal = (content, sureCallback = function() {
   }, showCancel = false,
	confirmText =
	"确定", cancelText = "取消", cancelCallback = function() {
   }) => {
   
	uni.showModal({
   
		title: '提示',
		content: content,
		showCancel: showCancel,
		confirmText: confirmText,
		cancelText: cancelText,
		success(res) {
   
			if (res.confirm) {
   
				sureCallback();
			} else if (res.cancel) {
   
				cancelCallback();
			}
		}
	});
}

//字符串转16进制
export const stringToHex = (str) =>{
   
	var val = "";
	for (var i = 0; i < str.length; i++) {
   
		if (val == "") {
   
			val = str.charCodeAt(i).toString(16); //获取字符的Unicode码然后转16进制
		} else {
   
			val += str.charCodeAt(i).toString(16); //获取字符的Unicode码然后转16进制再拼接,中间用逗号隔开
		}
	}
	return val;
}

export const sleep = (time) => {
   
	return new Promise(resolve => setTimeout(resolve, time));
}

//根据name过滤数据
export const uniqueArr = (arr) => {
   
	// 过滤掉重复的设备名称
	let uniqueArr = arr.filter((device, index, array) => {
   
		return array.findIndex(d => d.name === device.name) === index;
	})
	return uniqueArr;
}

export const arrayBufferToHexString = (buffer) => {
   
	const hexArr = Array.prototype.map.call(
		new Uint8Array(buffer),
		function(bit) {
   
			return ('00' + bit.toString(16)).slice(-2)
		}
	)
	return hexArr.join('').toUpperCase();
}

export let hexStringToArrayBuffer = (str) => {
   
	//十六进制转ArrayBuffer
	return new Uint8Array(str.match(/[\da-f]{
   2}/gi).map(function(h) {
   
		return parseInt(h, 16)
	})).buffer
}

//匹配版本号
export let compareVersion = (version1, version2) => {
   
	const v1 = version1.split('.').map(Number);
	const v2 = version2.split('.').map(Number);

	const len = Math.max(v1.length, v2.length);

	for (let i = 0; i < len; i++) {
   
		const num1 = i < v1.length ? v1[i] : 0;
		const num2 = i < v2.length ? v2[i] : 0;

		if (num1 > num2) {
   
			return 1;
		} else if (num1 < num2) {
   
			return -1;
		}
	}

	return 0;
}

//16进制转GB2312编码,可以转为汉字,编码可自己切换
 export let hexToGb2312 = (hexString)=> {
   
  const buffer = Buffer.from(hexString, 'hex');
  const decodedString = iconv.decode(buffer, 'GB2312');
  return decodedString;
}

三、通讯逻辑代码

util.js

import vm from '@/main.js'

import {
   
	tenToHex,
	calculateCRC16,
	hexToStr,
	hexToGb2312
} from './tool.js'
let packetNo = 0;

//根据状态的自定义描述
export let getBluetoothStatusDesc = (meterEcuid)=> {
   
	return {
   
		'000': '请靠近设备',
		'001': meterEcuid,
		'002': meterEcuid,
		'003': meterEcuid,
		'004': meterEcuid,
		'005': '搜索失败',
		'006': meterEcuid,
		"-1": meterEcuid,
		"0": "正常",
		"10000": "手机蓝牙未打开",
		"10001": "手机蓝牙未打开",
		"10002": meterEcuid,
		"10003": meterEcuid,
		"10004": meterEcuid,
		"10005": meterEcuid,
		"10006": meterEcuid,
		"10007": meterEcuid,
		"10008": meterEcuid,
		"10009": meterEcuid,
		"10012": meterEcuid,
		"10013": meterEcuid
	}
}

//根据状态的图片展示
export let bluetoothStatusLoadingImagesIndex = {
   
		'000': 0,//'正在搜索蓝牙设备',
		'001': 0,//'正在连接',
		'002': 1,//'连接成功',
		'003': 3,//'正在通讯',
		'004':1,// '通讯完成',
		'005':1,// '请检查设备是否开启蓝牙!',
		'006':2,// '蓝牙已断开',
		"-1": 2,//"已经连接",
		"0":0, //"正常",
		"10000": 2,//"开启蓝牙后,才可连接设备",
		"10001":2, //"当前蓝牙适配器不可用",
		"10002":2,// "没有找到指定设备",
		"10003":2,// "连接失败",
		"10004": 2,//"没有找到指定服务",
		"10005":2,// "没有找到指定特征值",
		"10006":2,// "当前连接已断开",
		"10007":2, //"当前特征值不支持此操作",
		"10008":2,// "其余所有系统上报的异常",
		"10009": 2,//"Android 系统特有,系统版本低于 4.3 不支持 BLE",
		"10012": 2,//"操作超时",
		"10013": 2,//"连接 deviceId 为空或者是格式不正确"
}

//获取下发的基本的报文信息
const getBaseCommandPacket = (deviceType, deviceNo, data) => {
   
	packetNo = packetNo + 1;
	let startHex = 'FAC5'; //帧头
	packetNo = packetNo > 32767 ? 1 : packetNo;
	let packetNoHex = tenToHex(packetNo); //数据包编号 //最大1-32767
	let deviceTypeHex = deviceType == 1 ? 'FF' : 'FF'; //设备类型 FF 表示小程序,01表示无限远传摄像水表
	let deviceNoBcd = deviceNo; //设备编号
	let dataLenHex = tenToHex(data.length / 2); //数据域长度 算长度-转16进制-补0成2位字节
	let dataHex = data; //数据域
	let validData = startHex + packetNoHex + deviceTypeHex + deviceNoBcd + dataLenHex + dataHex;
	//let CRC16Hex = calculateCRC16(validData); //校验
	let CRC16Hex = 'FFFF'; //校验
	let endHex = 'FBC6'; //帧尾
	let resultHex = validData + CRC16Hex + endHex

	return resultHex;
}


/*

*/

//获取数据域的报文
export const getDataCommandPacket = (deviceType = 1, deviceNo, commandCode, params) => {
   
	let data = '';
	if (commandCode == 'read-up-report-cycle') {
    //读上报周期
		data = '0604';
	} else if (commandCode == 'take-a-picture') {
    //拍照
		data = '10' + tenToHex(20, 2) + tenToHex(0, 2) + tenToHex(0) + tenToHex(240, 2) + tenToHex(320) + tenToHex(
			1, 2) + tenToHex(0, 2);
	} else if (commandCode == 'get-picture-packetnumber') {
    //获取照片数据包数
		data = '11';
	} else if (commandCode == 'get-picture-data') {
    //获取照片数据,需要传递参数
		data = '12' + tenToHex(params.picturePacketCount, 2) + tenToHex(params.picturePacketNo, 2);
	} else if (commandCode == 'open-debug') {
    //打开调试
		data = '4355';
	} else if (commandCode == 'start-network') {
    //开始联网
		data = '40';
	} else if (commandCode == 'stop-network') {
    //结束联网
		data = '41';
	} else if (commandCode == 'start-read-meterAmount') {
    //开始抄表
		data = '42';
	} else if (commandCode == 'close-debug') {
    //关闭调试
		data = '43AA';
	} else if (commandCode == 'read-meter') {
    //获取设备信息
		data = '01';
	} else if (commandCode == 'read-meter-domain-port') {
    //读域名和端口
		data = '0602';
	} else if (commandCode == 'set-meter-domain-port') {
    // 设置域名和端口 47.115.13.97: 24226
		data = '0502' + stringToHex(params.domainPort);
	}
	return getBaseCommandPacket(deviceType, deviceNo, data)
}

//解析报文工具方法
export const parsePacketMethod = (value) => {
   
	let len = value.length;
	let result = '';
	if (true) {
    //数据包编号和校验数据
		result = value.substring(24, len - 8);
		console.log('数据域的结果', result)
		return result;
	} else {
   
		uni.showModal({
   
			content: '校验不通过',
		});
		return null
	}
}

//不同命令的不同交互显示
const messageList = {
   
	'take-a-picture': '正在拍照',
	'get-picture-packetnumber': '获取图片数据',
	'get-picture-data': '获取图片数据',
	'open-debug': '打开调试',
	'close-debug': '关闭调试',
	'start-network': '开始联网',
	'stop-network': '结束联网',
	'start-read-meterAmount': '开始抄表'
}
let setSetTip = () => {
   
	// webview的堆栈
	const pages = getCurrentPages()
	// 当前页面
	const page = pages[pages.length - 1]; 
	console.log('当前页面',page);
	let fullPath = page.$page.fullPath;
	if (fullPath.indexOf('meterDetail') != -1) {
   
		return {
   
			'004': false
		};
	} else {
   
		return {
   }
	}
}

//下发命令 表号、命令编码,命名接收完毕后的回调,下发指令的参数,执行蓝牙的方法
export const sendCommand = (userOption) => {
   
	let defaultOption = {
   
		meterEcuid: '', //表号
		commandCode: '', //命令编码
		parsePacketCallback: (result) => {
   }, //命名接收完毕后的回调
		commandParams: {
   }, //下发指令的参数
		bluetoothConnectedCallback:(connected)=>{
   },
		errorCallback:()=>{
   },
		communicationStatus:0, // 通讯状态
		operateBluetooth: (params) => {
   } //执行蓝牙的方法
	}
	let option = Object.assign({
   }, defaultOption, userOption)
	console.log('执行的命令-', option.commandCode);
	console.log('命令执行参数', option);
	let sendPacket = getDataCommandPacket(3, option.meterEcuid, option.commandCode, option.commandParams);
	console.log('生成的报文', sendPacket)
	let params = {
   
		planConnectBluetoothDevice: {
   
			meterEcuid: option.meterEcuid
		},
		respondPacket: '',
		isImmediateConnect: true,
		isImmediateWrite: true,
		sendPacket: sendPacket,
		isErrorShowodal: false,
		setTip: setSetTip(),
		bluetoothConnectedCallback:(connected)=>{
   
			option.bluetoothConnectedCallback(connected)
		},
		errorCallback:()=>{
   
			option.errorCallback()
		},
		communicationStatus:0, // 通讯状态
		bluetoothStatus: {
   
			'003': messageList[option.commandCode] ? messageList[option.commandCode] : '正在通讯'
		},
		parsePacketCallback: (result) => {
   
			option.parsePacketCallback(result)
		},
	}; //初始化的参数
	option.operateBluetooth(params);
}

//解析日志报文
export const parseLogPacketMethod = (val) => {
   
	let result = hexToGb2312(val) + '\n';
	console.log('打开调试后解析的字符串', result);
	getApp().globalData.logAllText += result; //处理所有日志
	if (result.indexOf('LogStart') != -1) {
   
		getApp().globalData.isStartLog = true;
	} else if (result.indexOf('LogEnd') != -1) {
   
		getApp().globalData.isStartLog = false;
	}
	if (getApp().globalData.isStartLog && result.indexOf('LogStart') == -1 && result.indexOf(
			'LogEnd') == -1) {
    //处理中文展示
		let logChina = vm.$store.state.logChina;
		logChina += result;
		vm.$store.commit('setLogChina', logChina)
	}


}

四、全局注册

main.js

//默认蓝牙设备数据
Object.assign(bluetoothCore,{
   
	filterPattern: /^Y3[567].*/, //Y35主机、Y36分机、Y37一体式
	filterProp: 'meterEcuid',
	STARTPACKET:'FAC5',
	ENDPACKET:'FBC6',
	bluetoothDevicesDiscoveryTimeout: 5000, //多少秒后停止搜索蓝牙
	parsePacketMethod:parsePacketMethod,
	parseLogPacketMethod:parseLogPacketMethod,
	SERVICEID: "0000FFC0-0000-xxxx-8000-00805F9B34FB",
	NOTIFYID: "0000FFC1-0000-xxxx-00805F9B34FB",
	WRITEID: "0000FFC2-0000-xxxx-8000-00805F9B34FB",
})

五、使用

sendCommandFun('open-debug')
//像蓝牙发送指令
			sendCommandFun(commandCode) {
   
				this.commandCode = commandCode;
				//this.setTitle(commandCode);
				//如果蓝牙未连接,则弹出连接状态弹窗 并且调试未开启
				if (!bluetoothCore.isConnected) {
   
					this.isShowModal = true;
				}
				sendCommand({
   
					meterEcuid: this.meterEcuid,
					commandCode: commandCode,
					bluetoothConnectedCallback: (connected) => {
    //监听到蓝牙连接上,则关闭弹窗
						console.log('蓝牙连接上的回调', connected)
						if (connected) {
   
							this.isShowModal = false;
						}
					},
					operateBluetooth: (params) => {
   
						bluetoothCore.operateBluetooth(params)
					},
					errorCallback: () => {
   
						if ((commandCode == 'open-debug' || commandCode == 'close-debug') && bluetoothCore
							.communicationStatus == -1) {
   
							this.$store.commit('setIsStartLog', !this.isStartLog)
						}
					}
				})
			},

相关推荐

  1. uni-app 程序模块封装-持续更新

    2023-12-21 13:14:03       33 阅读
  2. 程序连接

    2023-12-21 13:14:03       16 阅读
  3. 程序实现连接通讯

    2023-12-21 13:14:03       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-21 13:14:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-21 13:14:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-21 13:14:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-21 13:14:03       18 阅读

热门阅读

  1. 速盾网络:网络安全守护者

    2023-12-21 13:14:03       45 阅读
  2. SpringBoot缓存注解@Cacheable使用姿势介绍

    2023-12-21 13:14:03       42 阅读
  3. 算法:从入门到变通

    2023-12-21 13:14:03       40 阅读
  4. 面试算法63:替换单词

    2023-12-21 13:14:03       39 阅读
  5. 在spring boot项目引入mybatis plus后的的案例实践

    2023-12-21 13:14:03       44 阅读
  6. Rust中Result处理方式

    2023-12-21 13:14:03       33 阅读
  7. 力扣题目学习笔记(OC + Swift)16. 最接近的三数之和

    2023-12-21 13:14:03       41 阅读
  8. Python多任务编程-07多线程版udp聊天程序

    2023-12-21 13:14:03       27 阅读
  9. shell——变量之字符串的截取

    2023-12-21 13:14:03       33 阅读