599 lines
19 KiB
JavaScript
Raw Normal View History

2025-06-06 15:17:30 +08:00
Page({
data: {
devices: [],
isSearching: false,
status: '未连接',
connected: false,
skipCount: 0,
connectedDeviceId: null,
deviceServices: [],
deviceCharacteristics: [],
2026-07-01 15:10:17 +08:00
blueIsOk: false,
battery: null,
batteryLoading: false,
deviceName: '',
nameInput: '',
nameLoading: false
2025-06-06 15:17:30 +08:00
},
// 1. 初始化蓝牙模块
startBluetooth() {
const that = this
2026-04-16 13:38:33 +08:00
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,
});
}
2025-06-06 15:17:30 +08:00
});
}
2026-04-16 13:38:33 +08:00
})
2025-06-06 15:17:30 +08:00
},
2026-04-16 13:38:33 +08:00
onLoad(options) {
2025-06-06 15:17:30 +08:00
this.startBluetooth()
2026-04-16 13:38:33 +08:00
console.log(options.params)
if (options.params) {
this.setData({
params: options.params.replaceAll('*', '=').replaceAll('@', '&')
})
console.log(this.data.params)
}
2025-06-06 15:17:30 +08:00
},
// 2. 搜索设备
searchDevices() {
2026-04-16 13:38:33 +08:00
const that = this
if (!this.data.blueIsOk) return wx.showModal({
2025-06-06 15:17:30 +08:00
title: '提示',
content: '请打开手机蓝牙并授权小程序使用',
showCancel: false,
});
2026-04-16 13:38:33 +08:00
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}
// // }
// // }
// // })
// }
// }
// })
// }
2025-06-06 15:17:30 +08:00
// 启动搜索
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: () => {
console.log('开始搜索设备');
// 监听发现设备事件
wx.onBluetoothDeviceFound((res) => {
console.log('发现新设备', res.devices);
res.devices.forEach(device => {
2026-07-01 15:10:17 +08:00
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') {
2025-06-06 15:17:30 +08:00
const exists = this.data.devices.find(d => d.deviceId === device.deviceId);
if (!exists) {
2026-04-16 13:38:33 +08:00
if (device.deviceId == last_device_id) {
that.connectDevice({
currentTarget: {
dataset: {
device: device
}
}
})
}
2025-06-06 15:17:30 +08:00
this.setData({
devices: [...this.data.devices, device]
});
}
}
});
});
},
fail: (err) => {
console.error('搜索设备失败', err);
2026-04-16 13:38:33 +08:00
wx.showToast({
title: '搜索失败',
icon: 'error'
});
this.setData({
isSearching: false
});
2025-06-06 15:17:30 +08:00
}
});
// 设置超时自动停止
setTimeout(() => {
wx.stopBluetoothDevicesDiscovery();
2026-04-16 13:38:33 +08:00
this.setData({
isSearching: false
});
2025-06-06 15:17:30 +08:00
console.log('停止搜索');
}, 9000); // 10秒后停止
},
// 3. 连接设备
connectDevice(e) {
2026-04-16 13:38:33 +08:00
if (this.data.connectedDeviceId) { //如果已经链接先断开连接
this.disconnectDevice()
}
2025-06-06 15:17:30 +08:00
const device = e.currentTarget.dataset.device;
console.log('尝试连接设备', device);
wx.showLoading({
title: '连接中...',
})
wx.createBLEConnection({
deviceId: device.deviceId,
success: () => {
console.log('连接成功', device.deviceId);
2026-07-01 15:10:17 +08:00
console.log('连接成功的设备信息:', JSON.stringify(device));
2026-04-16 13:38:33 +08:00
wx.showToast({
title: '连接成功',
icon: 'success'
});
2025-06-06 15:17:30 +08:00
this.setData({
status: '已连接',
connected: true,
connectedDeviceId: device.deviceId
});
wx.setStorageSync('device_id', device.deviceId)
wx.setStorageSync('device_name', device.name)
2026-04-16 13:38:33 +08:00
if (device.name == 'YD-O1') {
2026-07-01 15:10:17 +08:00
const serviceId = device.advertisServiceUUIDs[0];
wx.setStorageSync('uuid', serviceId)
this.setupWriteAndNotify(device.deviceId, serviceId);
2026-04-16 13:38:33 +08:00
} else {
2025-06-06 15:17:30 +08:00
// 获取设备的服务
wx.getBLEDeviceServices({
deviceId: device.deviceId,
success: (res) => {
console.log('设备服务', res.services);
2026-07-01 15:10:17 +08:00
// 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);
2025-06-06 15:17:30 +08:00
},
fail: (err) => {
console.error('获取服务失败', err);
}
});
}
2026-04-16 13:38:33 +08:00
if (this.data.params) {
wx.redirectTo({
url: '/subpackage/train/bluetooth/train?' + this.data.params,
})
}
2025-06-06 15:17:30 +08:00
},
fail: (err) => {
console.error('连接失败', err);
2026-04-16 13:38:33 +08:00
wx.showToast({
title: '连接失败',
icon: 'error'
});
2025-06-06 15:17:30 +08:00
},
2026-04-16 13:38:33 +08:00
complete: () => {
2025-06-06 15:17:30 +08:00
wx.hideLoading()
}
});
},
// 获取设备的服务
getDeviceServices(deviceId) {
wx.getBLEDeviceServices({
deviceId: deviceId,
success: (res) => {
console.log('设备服务', res.services);
this.setData({
deviceServices: res.services
});
wx.getConnectedBluetoothDevices({
2026-04-16 13:38:33 +08:00
services: [res.services[0].uuid],
success: res => {
console.log('aaaaa333333===>', res)
2025-06-06 15:17:30 +08:00
}
})
wx.getConnectedBluetoothDevices({
2026-04-16 13:38:33 +08:00
services: [res.services[1].uuid],
success: res => {
console.log('aaaaa444444===>', res)
2025-06-06 15:17:30 +08:00
}
})
// 获取第一个服务的特征值
// 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) {
2026-04-16 13:38:33 +08:00
const command = 3; // 启动自由跳绳的命令
const code = 1;
const parameter = 0; // 启动自由跳绳,参数为0
2025-06-06 15:17:30 +08:00
// 获取蓝牙设备的特征值(特征值应是你已经连接的蓝牙设备的写入特征值)
const characteristic = this.data.deviceCharacteristics.find(c => c.properties.write);
if (!characteristic) {
console.error('没有可写的特征值');
return;
}
// 构建命令数据
2026-04-16 13:38:33 +08:00
const commandData = new ArrayBuffer(4);
2025-06-06 15:17:30 +08:00
const dataView = new DataView(commandData);
2026-04-16 13:38:33 +08:00
dataView.setUint8(0, 0x03);
dataView.setUint8(1, 0x01);
dataView.setUint8(2, 0x00);
dataView.setUint8(3, 0x00);
2025-06-06 15:17:30 +08:00
// 发送命令到设备
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) {
2026-04-16 13:38:33 +08:00
console.error("Int16Array length is too short!");
return;
2025-06-06 15:17:30 +08:00
}
// 3. 读取索引 2 的值
const value = int16Array[2];
// 4. 更新数据
2026-04-16 13:38:33 +08:00
this.setData({
skipCount: value
});
},
2025-06-06 15:17:30 +08:00
2026-07-01 15:10:17 +08:00
// 连接成功后获取特征值、存储写特征并建立通知监听
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)
});
},
2025-06-06 15:17:30 +08:00
// 4. 断开连接
2026-04-16 13:38:33 +08:00
disconnectDevice(id) {
2025-06-06 15:17:30 +08:00
wx.closeBLEConnection({
2026-04-16 13:38:33 +08:00
deviceId: id || this.data.connectedDeviceId,
2025-06-06 15:17:30 +08:00
success: () => {
console.log('断开连接');
this.setData({
status: '未连接',
2026-07-01 15:10:17 +08:00
connected: false,
battery: null,
batteryLoading: false,
deviceName: '',
nameInput: '',
nameLoading: false
2025-06-06 15:17:30 +08:00
});
}
});
2026-07-01 15:10:17 +08:00
},
// 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;
2025-06-06 15:17:30 +08:00
}
2026-04-16 13:38:33 +08:00
});