2026-07-01 15:10:17 +08:00

599 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Page({
data: {
devices: [],
isSearching: false,
status: '未连接',
connected: false,
skipCount: 0,
connectedDeviceId: null,
deviceServices: [],
deviceCharacteristics: [],
blueIsOk: false,
battery: null,
batteryLoading: false,
deviceName: '',
nameInput: '',
nameLoading: false
},
// 1. 初始化蓝牙模块
startBluetooth() {
const that = this
wx.closeBluetoothAdapter({
success:()=>{
wx.openBluetoothAdapter({
success: () => {
// console.log('蓝牙初始化成功');
this.setData({
blueIsOk: true
})
that.searchDevices() //开始搜索设备
// wx.showToast({ title: '蓝牙已开启', icon: 'success' });
},
fail: (res) => {
console.error('蓝牙初始化失败', res);
wx.showModal({
title: '提示',
content: '请打开手机蓝牙并授权小程序使用',
showCancel: false,
});
}
});
}
})
},
onLoad(options) {
this.startBluetooth()
console.log(options.params)
if (options.params) {
this.setData({
params: options.params.replaceAll('*', '=').replaceAll('@', '&')
})
console.log(this.data.params)
}
},
// 2. 搜索设备
searchDevices() {
const that = this
if (!this.data.blueIsOk) return wx.showModal({
title: '提示',
content: '请打开手机蓝牙并授权小程序使用',
showCancel: false,
});
this.setData({
isSearching: true,
devices: []
});
const last_device_id = wx.getStorageSync('device_id')
const uuid = wx.getStorageSync('uuid')
// if (uuid != '' && device_id != '') {
// wx.getConnectedBluetoothDevices({
// services: [uuid],
// success: ress => {
// console.log('aa', ress)
// console.log('看看有没有', ress)
// console.log('看看有没有', ress)
// console.log('看看有没有', ress)
// console.log('看看有没有', ress)
// if (ress.devices.length > 0) {
// for (let item of ress.devices) {
// if (item.deviceId == device_id) {
// that.setData({
// connectedDeviceId: device_id,
// devices: [item, ...that.data.devices]
// }, () => {
// console.log('nowDevice===========>', that.data.devices)
// console.log('nowDevice===========>', that.data.devices)
// console.log('nowDevice===========>', that.data.devices)
// console.log('nowDevice===========>', that.data.devices)
// })
// }
// }
// } else {
// //尝试链接原来的蓝牙
// wx.closeBLEConnection()
// return
// wx.showModal({
// title: '是否连接上次的设备?',
// content: '',
// complete: (res) => {
// if (res.cancel) {
// }
// if (res.confirm) {
// that.connectDevice({
// currentTarget: {
// dataset: {
// device: {
// deviceId: device_id
// }
// }
// }
// })
// }
// }
// })
// // that.connectDevice({
// // currentTarget:{
// // dataset:{
// // device:{deviceId:device_id}
// // }
// // }
// // })
// }
// }
// })
// }
// 启动搜索
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: () => {
console.log('开始搜索设备');
// 监听发现设备事件
wx.onBluetoothDeviceFound((res) => {
console.log('发现新设备', res.devices);
res.devices.forEach(device => {
if (device.name == 'YD-Jump' || device.name == 'YD_Jump' || device.name == 'YD-O1' || device.localName == 'YD-O1' || device.localName == 'YD-Jump' || device.localName == 'YD_Jump') {
const exists = this.data.devices.find(d => d.deviceId === device.deviceId);
if (!exists) {
if (device.deviceId == last_device_id) {
that.connectDevice({
currentTarget: {
dataset: {
device: device
}
}
})
}
this.setData({
devices: [...this.data.devices, device]
});
}
}
});
});
},
fail: (err) => {
console.error('搜索设备失败', err);
wx.showToast({
title: '搜索失败',
icon: 'error'
});
this.setData({
isSearching: false
});
}
});
// 设置超时自动停止
setTimeout(() => {
wx.stopBluetoothDevicesDiscovery();
this.setData({
isSearching: false
});
console.log('停止搜索');
}, 9000); // 10秒后停止
},
// 3. 连接设备
connectDevice(e) {
if (this.data.connectedDeviceId) { //如果已经链接先断开连接
this.disconnectDevice()
}
const device = e.currentTarget.dataset.device;
console.log('尝试连接设备', device);
wx.showLoading({
title: '连接中...',
})
wx.createBLEConnection({
deviceId: device.deviceId,
success: () => {
console.log('连接成功', device.deviceId);
console.log('连接成功的设备信息:', JSON.stringify(device));
wx.showToast({
title: '连接成功',
icon: 'success'
});
this.setData({
status: '已连接',
connected: true,
connectedDeviceId: device.deviceId
});
wx.setStorageSync('device_id', device.deviceId)
wx.setStorageSync('device_name', device.name)
if (device.name == 'YD-O1') {
const serviceId = device.advertisServiceUUIDs[0];
wx.setStorageSync('uuid', serviceId)
this.setupWriteAndNotify(device.deviceId, serviceId);
} else {
// 获取设备的服务
wx.getBLEDeviceServices({
deviceId: device.deviceId,
success: (res) => {
console.log('设备服务', res.services);
// YD_Jump(下划线)使用 services[1],YD-Jump(短横线)使用 services[0]
const serviceIndex = device.name === 'YD_Jump' ? 1 : 0;
const serviceId = res.services[serviceIndex].uuid;
wx.setStorageSync('uuid', serviceId)
this.setupWriteAndNotify(device.deviceId, serviceId);
},
fail: (err) => {
console.error('获取服务失败', err);
}
});
}
if (this.data.params) {
wx.redirectTo({
url: '/subpackage/train/bluetooth/train?' + this.data.params,
})
}
},
fail: (err) => {
console.error('连接失败', err);
wx.showToast({
title: '连接失败',
icon: 'error'
});
},
complete: () => {
wx.hideLoading()
}
});
},
// 获取设备的服务
getDeviceServices(deviceId) {
wx.getBLEDeviceServices({
deviceId: deviceId,
success: (res) => {
console.log('设备服务', res.services);
this.setData({
deviceServices: res.services
});
wx.getConnectedBluetoothDevices({
services: [res.services[0].uuid],
success: res => {
console.log('aaaaa333333===>', res)
}
})
wx.getConnectedBluetoothDevices({
services: [res.services[1].uuid],
success: res => {
console.log('aaaaa444444===>', res)
}
})
// 获取第一个服务的特征值
// if (res.services.length > 0) {
// this.getDeviceCharacteristics(deviceId, res.services[0].uuid);
// }
},
fail: (err) => {
console.error('获取服务失败', err);
}
});
},
// 获取第一个服务的特征值
getDeviceCharacteristics(deviceId, serviceId) {
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: (res) => {
console.log('服务特征值', res.characteristics);
this.setData({
deviceCharacteristics: res.characteristics
});
// 启动接收数据通知
this.startNotify(deviceId, serviceId);
// 发送启动自由跳绳的命令
this.startFreeJumpRope(deviceId, serviceId);
},
fail: (err) => {
console.error('获取特征值失败', err);
}
});
},
// 启动接收数据通知
startNotify(deviceId, serviceId) {
const characteristics = this.data.deviceCharacteristics;
characteristics.forEach(characteristic => {
if (characteristic.properties.notify) {
wx.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristic.uuid,
state: true,
success: () => {
console.log('启动数据通知');
},
fail: (err) => {
console.error('启动通知失败', err);
}
});
// 接收数据
wx.onBLECharacteristicValueChange((res) => {
console.log('接收到数据', res.value);
if (res.deviceId === deviceId && res.characteristicId === characteristic.uuid) {
console.log('接收到数据', res.value);
this.handleReceivedData(res.value);
}
});
}
});
},
// 发送启动自由跳绳命令
startFreeJumpRope(deviceId, serviceId) {
const command = 3; // 启动自由跳绳的命令
const code = 1;
const parameter = 0; // 启动自由跳绳,参数为0
// 获取蓝牙设备的特征值(特征值应是你已经连接的蓝牙设备的写入特征值)
const characteristic = this.data.deviceCharacteristics.find(c => c.properties.write);
if (!characteristic) {
console.error('没有可写的特征值');
return;
}
// 构建命令数据
const commandData = new ArrayBuffer(4);
const dataView = new DataView(commandData);
dataView.setUint8(0, 0x03);
dataView.setUint8(1, 0x01);
dataView.setUint8(2, 0x00);
dataView.setUint8(3, 0x00);
// 发送命令到设备
wx.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristic.uuid,
value: commandData,
success: () => {
console.log("自由跳绳已启动!");
},
fail: (error) => {
console.error("启动自由跳绳失败", error);
}
});
},
// 处理接收到的数据
handleReceivedData(arrayBuffer) {
// 1. 创建 Int16Array 视图(从 ArrayBuffer 读取)
const int16Array = new Int16Array(arrayBuffer);
// 2. 检查长度是否足够(至少 3 个元素)
if (int16Array.length < 3) {
console.error("Int16Array length is too short!");
return;
}
// 3. 读取索引 2 的值
const value = int16Array[2];
// 4. 更新数据
this.setData({
skipCount: value
});
},
// 连接成功后获取特征值、存储写特征并建立通知监听
setupWriteAndNotify(deviceId, serviceId) {
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) => {
const writeChar = res.characteristics.find(c => c.properties.write || c.properties.writeNoResponse);
if (writeChar) {
wx.setStorageSync('writeCharacteristic', writeChar.uuid);
}
const notifyChar = res.characteristics.find(c => c.properties.notify);
if (notifyChar) {
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId: notifyChar.uuid,
state: true,
});
wx.onBLECharacteristicValueChange((result) => {
const deviceName = wx.getStorageSync('device_name');
if (deviceName === 'YD-O1') {
// YD-O1: 二进制协议解析电量响应
const int16Array = new Int16Array(result.value);
if (int16Array.length > 0) {
const battery = int16Array[0]; // 根据实际协议调整字段索引
if (battery >= 0 && battery <= 100) {
this.setData({ battery, batteryLoading: false });
wx.hideLoading();
}
}
} else {
// YD_Jump / YD-Jump: 字符串协议解析 $Batt,xx!
const bytes = new Uint8Array(result.value);
let str = '';
for (let i = 0; i < bytes.length; i++) {
str += String.fromCharCode(bytes[i]);
}
console.log('收到蓝牙数据:', str);
// 解析电量响应 $Batt,xx!
const battMatch = str.match(/\$Batt,(\d+)!/);
if (battMatch) {
this.setData({ battery: parseInt(battMatch[1], 10), batteryLoading: false });
wx.hideLoading();
}
// 解析获取名称响应 $My Name:xxxxxxxx!
const nameMatch = str.match(/\$My Name:(.+)!/);
if (nameMatch) {
this.setData({ deviceName: nameMatch[1].trim(), nameLoading: false });
wx.hideLoading();
}
// 解析修改名称响应 $Set Name OK!
if (str.includes('$Set Name OK!')) {
wx.hideLoading();
wx.showToast({ title: '名称修改成功', icon: 'success' });
}
}
});
}
},
fail: (err) => console.error('获取特征值失败', err)
});
},
// 4. 断开连接
disconnectDevice(id) {
wx.closeBLEConnection({
deviceId: id || this.data.connectedDeviceId,
success: () => {
console.log('断开连接');
this.setData({
status: '未连接',
connected: false,
battery: null,
batteryLoading: false,
deviceName: '',
nameInput: '',
nameLoading: false
});
}
});
},
// 5. 获取设备电量(按设备类型发送不同协议命令)
getBattery() {
if (!this.data.connected || !this.data.connectedDeviceId) {
wx.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
wx.showToast({ title: '特征值未就绪,请重新连接', icon: 'none' });
return;
}
const deviceId = this.data.connectedDeviceId;
const serviceId = wx.getStorageSync('uuid');
const deviceName = wx.getStorageSync('device_name');
wx.showLoading({ title: '获取电量中...' });
this.setData({ batteryLoading: true });
let commandData;
if (deviceName === 'YD-O1') {
// YD-O1: 二进制协议获取电量
commandData = new ArrayBuffer(4);
const dataView = new DataView(commandData);
dataView.setUint8(0, 0x07); // 获取电量命令字节
dataView.setUint8(1, 0x00);
dataView.setUint8(2, 0x00);
dataView.setUint8(3, 0x00);
} else {
// YD_Jump / YD-Jump: 字符串协议获取电量
commandData = this.stringToArrayBuffer('$Get Batt!');
}
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: commandData,
success: () => {
console.log(`${deviceName} 发送获取电量命令成功`);
},
fail: (err) => {
console.error('发送命令失败', err);
wx.hideLoading();
this.setData({ batteryLoading: false });
wx.showToast({ title: '发送命令失败', icon: 'none' });
}
});
},
// 获取设备名称
getDeviceName() {
if (!this.data.connected || !this.data.connectedDeviceId) {
wx.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
wx.showToast({ title: '特征值未就绪,请重新连接', icon: 'none' });
return;
}
const deviceId = this.data.connectedDeviceId;
const serviceId = wx.getStorageSync('uuid');
wx.showLoading({ title: '获取名称中...' });
this.setData({ nameLoading: true });
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: this.stringToArrayBuffer('$Get Name!'),
success: () => console.log('发送获取名称命令成功'),
fail: (err) => {
console.error('发送命令失败', err);
wx.hideLoading();
this.setData({ nameLoading: false });
wx.showToast({ title: '发送命令失败', icon: 'none' });
}
});
},
// 修改设备名称(名称不足8位用空格填充,超过8位截断)
setDeviceName() {
if (!this.data.connected || !this.data.connectedDeviceId) {
wx.showToast({ title: '请先连接设备', icon: 'none' });
return;
}
const nameInput = this.data.nameInput.trim();
if (!nameInput) {
wx.showToast({ title: '请输入新名称', icon: 'none' });
return;
}
const characteristic = wx.getStorageSync('writeCharacteristic');
if (!characteristic) {
wx.showToast({ title: '特征值未就绪,请重新连接', icon: 'none' });
return;
}
const deviceId = this.data.connectedDeviceId;
const serviceId = wx.getStorageSync('uuid');
// 名称固定8字节:不足补空格,超过截断
const paddedName = nameInput.padEnd(8, ' ').substring(0, 8);
const cmd = `$Set Name:${paddedName}!`;
console.log('发送修改名称命令:', cmd);
wx.showLoading({ title: '修改名称中...' });
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: characteristic,
value: this.stringToArrayBuffer(cmd),
success: () => console.log('修改名称命令已发送:', cmd),
fail: (err) => {
console.error('发送命令失败', err);
wx.hideLoading();
wx.showToast({ title: '发送命令失败', icon: 'none' });
}
});
},
// 名称输入框变化
onNameInput(e) {
this.setData({ nameInput: e.detail.value });
},
// 字符串转 ArrayBuffer(与 bluetoothServe.js 保持一致)
stringToArrayBuffer(str) {
const buffer = new ArrayBuffer(str.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
view[i] = str.charCodeAt(i) & 0xFF;
}
return buffer;
}
});