// 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; } });