2025-06-06 15:17:30 +08:00

525 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_${options.timeout || 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,
times: 0,
timeStr: utils.formatTimeStr(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,
totalCount:this.data.counts,
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;
}
});