522 lines
14 KiB
JavaScript
522 lines
14 KiB
JavaScript
![]() |
// subpackage/train/bluetooth/train.js
|
||
|
const utils = require("../../../utils/utils");
|
||
|
|
||
|
// 常量定义
|
||
|
const DEVICE_TYPES = {
|
||
|
YD_O1: 'YD-O1', // 设备类型1
|
||
|
YD_JUMP: 'YD_Jump' // 设备类型2
|
||
|
};
|
||
|
|
||
|
const COMMAND_TYPES = {
|
||
|
START: 1, // 开始命令
|
||
|
PAUSE: 4, // 暂停命令
|
||
|
RESUME: 5, // 恢复命令
|
||
|
STOP: 6 // 停止命令
|
||
|
};
|
||
|
|
||
|
const COMMAND_STRINGS = {
|
||
|
CLEAN: 'Clean Count', // 清零计数
|
||
|
START: 'Start Count', // 开始计数
|
||
|
STOP: 'Stop Count', // 停止计数
|
||
|
GET_CT: 'Get CT' // 获取计数
|
||
|
};
|
||
|
|
||
|
let audioCtx = null;
|
||
|
let audioCtxBg = null;
|
||
|
Page({
|
||
|
data: {
|
||
|
// 界面状态
|
||
|
counts: 0, // 跳绳次数
|
||
|
sportTime: 0, // 运动时长(秒)
|
||
|
times: 0, // 剩余时间/次数
|
||
|
timeStr: '00:00', // 格式化时间
|
||
|
totalTime:0,//总时长
|
||
|
isRunning: false, // 是否在运动中
|
||
|
showRipple: false, // 是否显示水波纹效果
|
||
|
isStart: true, // 是否未开始
|
||
|
isStarting: false, // 是否显示倒计时
|
||
|
isOver: false, // 是否结束
|
||
|
countdown: 3, // 倒计时秒数
|
||
|
|
||
|
// 蓝牙相关
|
||
|
tabName: 'time', // 训练模式(time/num)
|
||
|
device_name: '', // 设备名称
|
||
|
device_id: '', // 设备ID
|
||
|
serviceId: '', // 服务ID
|
||
|
deviceServices: [], // 设备服务列表
|
||
|
deviceCharacteristics: [], // 设备特征列表
|
||
|
canSport:true, //是否可以继续运动
|
||
|
|
||
|
// 计时器
|
||
|
timerId: null, // 运动计时器ID
|
||
|
intervalId: null, // 获取计数定时器ID
|
||
|
timeout:3,
|
||
|
music:'',
|
||
|
isPlay:false,
|
||
|
nowGroup: 1,
|
||
|
},
|
||
|
|
||
|
// 页面加载
|
||
|
onLoad(options) {
|
||
|
this.initDeviceData(options);
|
||
|
this.getDeviceServices(this.data.device_id);
|
||
|
if(options.timeout && (options.timeout == 3 || options.timeout == 5 || options.timeout == 10)){
|
||
|
this.setData({
|
||
|
timeout:options.timeout
|
||
|
})
|
||
|
}
|
||
|
this.loadAudio("https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/bg_music/count_down_3.mp3",options.music)
|
||
|
if (options.homeWorkId) { //作业
|
||
|
this.setData({
|
||
|
homeWorkId: options.homeWorkId,
|
||
|
groupNumber: options.groupNumber
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 页面卸载
|
||
|
onUnload() {
|
||
|
console.log('卸载卸载卸载')
|
||
|
this.cleanupTimers();
|
||
|
if(audioCtx){
|
||
|
audioCtx.stop();
|
||
|
if(audioCtx.destory){
|
||
|
audioCtx.destory();
|
||
|
}
|
||
|
}
|
||
|
if (this.data.isRunning) {
|
||
|
this.stop();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 初始化设备数据
|
||
|
initDeviceData(options) {
|
||
|
const name = wx.getStorageSync('device_name');
|
||
|
const device_id = wx.getStorageSync('device_id');
|
||
|
|
||
|
this.setData({
|
||
|
tabName: options.type,
|
||
|
device_name: name,
|
||
|
device_id: device_id
|
||
|
});
|
||
|
|
||
|
if (options.type === "time") {
|
||
|
this.setData({
|
||
|
times: options.time,
|
||
|
timeStr: utils.formatTimeStr(Number(options.time)),
|
||
|
type:options.type,
|
||
|
totalTime:options.time
|
||
|
});
|
||
|
} else if (options.type === "num") {
|
||
|
this.setData({
|
||
|
number: Number(options.number),
|
||
|
type:options.type
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if(options.theme){
|
||
|
this.setData({
|
||
|
theme:options.theme
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if(options.taskId){
|
||
|
this.setData({
|
||
|
taskId:options.taskId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if(options.music){
|
||
|
this.setData({
|
||
|
music:options.music
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 清理计时器
|
||
|
cleanupTimers() {
|
||
|
if (this.data.timerId) clearInterval(this.data.timerId);
|
||
|
if (this.data.intervalId) clearInterval(this.data.intervalId);
|
||
|
this.setData({ timerId: null, intervalId: null });
|
||
|
},
|
||
|
|
||
|
// 开始倒计时
|
||
|
beginCountdown() {
|
||
|
this.setData({
|
||
|
isStarting: true,
|
||
|
countdown: this.data.timeout
|
||
|
});
|
||
|
|
||
|
audioCtxBg.play()
|
||
|
|
||
|
const countdownInterval = setInterval(() => {
|
||
|
if (this.data.countdown > 0) {
|
||
|
this.setData({ countdown: this.data.countdown - 1 });
|
||
|
} else {
|
||
|
clearInterval(countdownInterval);
|
||
|
this.startSport(this.data.isStart ? 1 : 2);
|
||
|
this.btnCilck();
|
||
|
this.setData({
|
||
|
isRunning: true,
|
||
|
isStart: false,
|
||
|
isStarting: false
|
||
|
});
|
||
|
}
|
||
|
}, 1000);
|
||
|
},
|
||
|
|
||
|
// 开始运动 (type:1开始 2继续)
|
||
|
startSport(type) {
|
||
|
this.startNotify(this.data.device_id, this.data.serviceId);
|
||
|
// 根据不同设备类型发送不同命令
|
||
|
console.log('type=============>',type)
|
||
|
console.log('type=============>',this.data.groupNumber)
|
||
|
if (this.data.device_name == DEVICE_TYPES.YD_O1) {
|
||
|
this.startFreeJumpRope(this.data.device_id, this.data.serviceId,
|
||
|
type == 1 || (this.data.groupNumber && this.data.counts >= this.data.number) ? COMMAND_TYPES.START : COMMAND_TYPES.RESUME);
|
||
|
} else if (this.data.device_name == DEVICE_TYPES.YD_JUMP) {
|
||
|
if (type == 1 || (this.data.groupNumber && this.data.counts >= this.data.number)) {
|
||
|
this.sendCommandJump(COMMAND_STRINGS.CLEAN);
|
||
|
}
|
||
|
this.sendCommandJump(COMMAND_STRINGS.START);
|
||
|
this.sendCommandJump(COMMAND_STRINGS.GET_CT);
|
||
|
}
|
||
|
|
||
|
if(this.data.groupNumber && this.data.counts >= this.data.number){
|
||
|
this.setData({
|
||
|
counts:0
|
||
|
},()=>{
|
||
|
this.startSportTimer();
|
||
|
})
|
||
|
}else {
|
||
|
this.startSportTimer();
|
||
|
}
|
||
|
|
||
|
},
|
||
|
|
||
|
// 启动运动计时器
|
||
|
startSportTimer() {
|
||
|
const starTimer = setInterval(() => {
|
||
|
// 检查是否达到目标
|
||
|
if ((this.data.tabName === "time" && this.data.times <= 0) ||
|
||
|
(this.data.tabName === 'num' && this.data.counts >= this.data.number)
|
||
|
) {
|
||
|
this.stop();
|
||
|
// this.setData({
|
||
|
// canSport:false
|
||
|
// })
|
||
|
if (this.data.homeWorkId && this.data.nowGroup < this.data.groupNumber) {
|
||
|
///判断是定时计数 || 定数计时
|
||
|
if(this.data.tabName == 'time'){
|
||
|
console.log(this.data.totalTime)
|
||
|
this.setData({
|
||
|
nowGroup: this.data.nowGroup + 1,
|
||
|
times:this.data.totalTime,
|
||
|
timeStr:utils.formatTimeStr(Number(this.data.totalTime))
|
||
|
})
|
||
|
}else{
|
||
|
this.setData({
|
||
|
nowGroup: this.data.nowGroup + 1,
|
||
|
// counts:0
|
||
|
})
|
||
|
}
|
||
|
} else {
|
||
|
this.setData({
|
||
|
canSport: false,
|
||
|
nowGroup: this.data.nowGroup + 1
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}else{
|
||
|
this.updateTimer();
|
||
|
}
|
||
|
|
||
|
|
||
|
}, 1000);
|
||
|
|
||
|
this.setData({ timerId: starTimer });
|
||
|
},
|
||
|
|
||
|
// 更新计时器
|
||
|
updateTimer() {
|
||
|
const newTime = this.data.tabName !== "time" ? 1 : -1;
|
||
|
const newTimes = Number(newTime) + Number(this.data.times);
|
||
|
|
||
|
this.setData({
|
||
|
times: newTimes,
|
||
|
sportTime: this.data.sportTime + 1,
|
||
|
timeStr: utils.formatTimeStr(newTimes)
|
||
|
});
|
||
|
},
|
||
|
|
||
|
//加载背景音乐
|
||
|
loadAudio( bgurl,url) {
|
||
|
audioCtx = wx.createInnerAudioContext();
|
||
|
audioCtx.src = url
|
||
|
audioCtx.loop = true
|
||
|
|
||
|
audioCtxBg = wx.createInnerAudioContext();
|
||
|
audioCtxBg.src = bgurl
|
||
|
audioCtxBg.loop = false
|
||
|
// audioCtx.onCanplay(() => {
|
||
|
// audioCtx.duration
|
||
|
// audioCtx.currentTime
|
||
|
// console.log(audioCtx.currentTime)
|
||
|
// console.log(audioCtx.duration)
|
||
|
// console.log(audioCtx.currentTime)
|
||
|
// })
|
||
|
|
||
|
audioCtx.onTimeUpdate(() => {
|
||
|
console.log('在播放咯')
|
||
|
})
|
||
|
},
|
||
|
|
||
|
//触发背景音乐状态变更
|
||
|
btnCilck(){
|
||
|
// audioCtx.destory()
|
||
|
if(this.data.isPlay){
|
||
|
audioCtx.pause()
|
||
|
this.setData({
|
||
|
isPlay:false
|
||
|
})
|
||
|
}else{
|
||
|
audioCtx.play()
|
||
|
this.setData({
|
||
|
isPlay:true
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 暂停运动
|
||
|
stop() {
|
||
|
this.cleanupTimers();
|
||
|
this.btnCilck();
|
||
|
if (this.data.device_name === DEVICE_TYPES.YD_O1) {
|
||
|
this.startFreeJumpRope(this.data.device_id, this.data.serviceId, COMMAND_TYPES.PAUSE);
|
||
|
} else if (this.data.device_name === DEVICE_TYPES.YD_JUMP) {
|
||
|
this.sendCommandJump(COMMAND_STRINGS.STOP);
|
||
|
}
|
||
|
|
||
|
this.setData({
|
||
|
isRunning: false,
|
||
|
timeStr: utils.formatTimeStr(this.data.times)
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 结束运动
|
||
|
over() {
|
||
|
// if (this.data.device_name === DEVICE_TYPES.YD_O1) {
|
||
|
// this.startFreeJumpRope(this.data.device_id, this.data.serviceId, COMMAND_TYPES.STOP);
|
||
|
// } else {
|
||
|
// this.sendCommandJump(COMMAND_STRINGS.STOP);
|
||
|
// }
|
||
|
// this.setData({ isOver: true });
|
||
|
wx.showModal({
|
||
|
title: '确定结束训练吗',
|
||
|
complete: (res) => {
|
||
|
if (res.cancel) {
|
||
|
|
||
|
}
|
||
|
|
||
|
if (res.confirm) {
|
||
|
let params = `?type=${this.data.type}&count=${this.data.counts}&time=${this.data.sportTime}&theme=${this.data.theme}&isAi=false&sportType=跳绳`
|
||
|
if(this.data.homeWorkId){
|
||
|
params += `&homeWorkId=${this.data.homeWorkId}`
|
||
|
}
|
||
|
if(this.data.taskId){
|
||
|
params += `&taskId=${this.data.taskId}`
|
||
|
}
|
||
|
wx.redirectTo({
|
||
|
url: `/subpackage/train/result/result`+params,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
|
||
|
},
|
||
|
|
||
|
// 界面交互处理
|
||
|
handleLongPressEnd() {
|
||
|
this.triggerRipple();
|
||
|
this.over();
|
||
|
},
|
||
|
|
||
|
handleTouchStart() {
|
||
|
this.setData({ showRipple: true });
|
||
|
},
|
||
|
|
||
|
handleTouchEnd() {
|
||
|
this.setData({ showRipple: false });
|
||
|
},
|
||
|
|
||
|
// 触发水波纹效果
|
||
|
triggerRipple() {
|
||
|
this.setData({ showRipple: true }, () => {
|
||
|
setTimeout(() => {
|
||
|
this.setData({ showRipple: false });
|
||
|
}, 600);
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 蓝牙设备服务发现
|
||
|
getDeviceServices(deviceId) {
|
||
|
wx.getBLEDeviceServices({
|
||
|
deviceId,
|
||
|
success: (res) => {
|
||
|
if (res.services.length > 0) {
|
||
|
const serviceIndex = this.data.device_name === DEVICE_TYPES.YD_O1 ? 0 : 1;
|
||
|
const serviceId = res.services[serviceIndex].uuid;
|
||
|
|
||
|
this.setData({
|
||
|
deviceServices: res.services,
|
||
|
serviceId
|
||
|
});
|
||
|
|
||
|
this.getDeviceCharacteristics(deviceId, serviceId);
|
||
|
}
|
||
|
},
|
||
|
fail: console.error
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 获取设备特征
|
||
|
getDeviceCharacteristics(deviceId, serviceId) {
|
||
|
wx.getBLEDeviceCharacteristics({
|
||
|
deviceId,
|
||
|
serviceId,
|
||
|
success: (res) => {
|
||
|
this.setData({ deviceCharacteristics: res.characteristics });
|
||
|
},
|
||
|
fail: console.error
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 开启通知
|
||
|
startNotify(deviceId, serviceId) {
|
||
|
const { deviceCharacteristics } = this.data;
|
||
|
|
||
|
deviceCharacteristics.forEach(characteristic => {
|
||
|
if (characteristic.properties.notify) {
|
||
|
wx.notifyBLECharacteristicValueChange({
|
||
|
deviceId,
|
||
|
serviceId,
|
||
|
characteristicId: characteristic.uuid,
|
||
|
state: true,
|
||
|
success: () => this.setupDataListener(deviceId, characteristic.uuid),
|
||
|
fail: console.error
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// 定时获取计数(仅YD_Jump设备需要)
|
||
|
if (this.data.device_name === DEVICE_TYPES.YD_JUMP) {
|
||
|
this.setData({
|
||
|
intervalId: setInterval(() => {
|
||
|
this.sendCommandJump(COMMAND_STRINGS.GET_CT);
|
||
|
}, 1200)
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 设置数据监听
|
||
|
setupDataListener(deviceId, characteristicId) {
|
||
|
wx.onBLECharacteristicValueChange((res) => {
|
||
|
if (res.deviceId === deviceId && res.characteristicId === characteristicId) {
|
||
|
if (this.data.device_name === DEVICE_TYPES.YD_O1) {
|
||
|
this.handleReceivedData(res.value);
|
||
|
} else if (this.data.device_name === DEVICE_TYPES.YD_JUMP) {
|
||
|
this.handleReceivedDataJump(res.value);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 发送自由跳绳命令(YD-O1设备)
|
||
|
startFreeJumpRope(deviceId, serviceId, type) {
|
||
|
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, type);
|
||
|
dataView.setUint8(2, 0x00);
|
||
|
dataView.setUint8(3, 0x00);
|
||
|
|
||
|
wx.writeBLECharacteristicValue({
|
||
|
deviceId,
|
||
|
serviceId,
|
||
|
characteristicId: characteristic.uuid,
|
||
|
value: commandData,
|
||
|
success: () => console.log("命令发送成功"),
|
||
|
fail: console.error
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 发送跳绳命令(YD_Jump设备)
|
||
|
sendCommandJump(command) {
|
||
|
const characteristic = this.data.deviceCharacteristics.find(c => c.properties.write);
|
||
|
if (!characteristic) {
|
||
|
console.error('未找到可写特征');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const commandData = this.stringToArrayBuffer(`$${command}!`);
|
||
|
|
||
|
wx.writeBLECharacteristicValue({
|
||
|
deviceId: this.data.device_id,
|
||
|
serviceId: this.data.serviceId,
|
||
|
characteristicId: characteristic.uuid,
|
||
|
value: commandData,
|
||
|
success: () => console.log(`${command} 命令已发送`),
|
||
|
fail: console.error
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// 数据处理方法
|
||
|
handleReceivedData(arrayBuffer) {
|
||
|
const int16Array = new Int16Array(arrayBuffer);
|
||
|
if (int16Array.length < 3 || !this.data.isRunning) return;
|
||
|
this.setData({
|
||
|
counts:int16Array[2] ,
|
||
|
speed: int16Array[2] == 0 ? 0 : (int16Array[2]/this.data.sportTime || 0).toFixed(0)
|
||
|
});
|
||
|
},
|
||
|
|
||
|
handleReceivedDataJump(arrayBuffer) {
|
||
|
const resultString = this.arrayBufferToString(arrayBuffer);
|
||
|
const value = this.extractNumber(resultString);
|
||
|
if(value){
|
||
|
if(!this.data.isRunning) return
|
||
|
this.setData({
|
||
|
counts: value,
|
||
|
speed: value == 0 ? 0 : (value/this.data.sportTime || 0).toFixed(0)
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// ArrayBuffer转字符串
|
||
|
arrayBufferToString(buffer) {
|
||
|
return String.fromCharCode.apply(null, new Uint8Array(buffer));
|
||
|
},
|
||
|
|
||
|
// 从字符串提取数字
|
||
|
extractNumber(input) {
|
||
|
const match = input.match(/^\$(0*)(\d+),/);
|
||
|
return match ? match[2].replace(/^0+/, '') : null;
|
||
|
},
|
||
|
|
||
|
// 字符串转ArrayBuffer
|
||
|
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);
|
||
|
}
|
||
|
return buffer;
|
||
|
}
|
||
|
});
|