import DayConverter from './DayConverter'; import CommonUtil from './CommonUtil.js'; const ET620ParseDataUtil = { /** * @returns {Array} data 心率或血氧数据 * @param {Array} data 手表SDK数据 * @param {Number} dataType 数据类型 1-心率 3-血氧 */ parseHeartRateOrBloodOxygenData(data,dataType){ if (!Array.isArray(data)) { console.error('输入数据必须是数组'); return []; } return ET620ParseDataUtil.get10MinuteAverage(data,dataType); }, /** * * @param {Array} data 手表SDK数据 */ parseBodyTemperatureData(data){ if (!Array.isArray(data)) { console.error('输入数据必须是数组'); return []; } return data .filter(item => this.isValidBodyTemperature(item.bodyTemperature)) .map(item => this.formatBodyTemperatureItem(item)); }, parseBloodPressureData(data){ if (!Array.isArray(data)) { console.error('输入数据必须是数组'); return []; } return data .filter(item => this.isValidBloodPressure(item.bloodPressure)) .map(item => this.formatBloodPressureItem(item)); }, /** * @returns {Object} 返回血压数据 * @param {Number} bp */ isValidBloodPressure(bp) { return bp && bp.bloodPressureHigh > 0 && bp.bloodPressureLow > 0; }, /** * @returns {Object} 返回体温数据 * @param {} bt */ isValidBodyTemperature(bt) { return bt && bt.bodyTemperature > 0.0 ; }, /** * @returns {Object} 返回体温数据 * @param {Number} bt */ isValidStep(step){ return step && step.stepCount > 0 ; }, /** * 组装体温数据 * @param {Object} item */ formatBodyTemperatureItem(item) { let timestamp = this.getTimestamp(item.date) // const tempStr = item.bodyTemperature.bodyTemperature.toFixed(1); // "36.0" const [integerPart, decimalPart] = item.bodyTemperature.bodyTemperature.split('.'); return { type: 4, // 4.体温 oneValue: integerPart, // 体温整数部分 twoValue: decimalPart, // 体温小数部分 time: timestamp, // 11位时间戳 stringTime: item.date }; }, /** * 组装步数数据 * @param {Object} item */ formatStepItem(item,name,userId) { let timestamp = this.getTimestamp(item.date) return { calories: item.step.calorie, distance: item.step.distance, step: item.step.stepCount, startTime: timestamp - (5 * 60), // 开始时间 endTime: timestamp, // 结束时间 createBy: name, // 体温整数部分 createTime: timestamp * 1000, // 体温小数部分 updateTime: timestamp * 1000, // 11位时间戳 userId: userId }; }, /** * 组装血压数据 * @param {Object} item */ formatBloodPressureItem(item) { let timestamp = this.getTimestamp(item.date) return { type: 2, // 2.血压 oneValue: item.bloodPressure.bloodPressureHigh, // 高压 twoValue: item.bloodPressure.bloodPressureLow, // 低压 time: timestamp, // 11位时间戳 stringTime: item.date // 直接使用原始date字段 }; }, /** * 解析心率、血氧数据,心率和血氧开启科学睡眠后,是1分钟采集1次,按每10分钟分组并计算平均值,传给后端 * @param {Array} data - 心率数据数组,格式为[{date: "YYYY-MM-DD-HH-mm", pulseReat: [number...]}, ...] * @returns {Array} - 分组后的结果数组 */ get10MinuteAverage(data,dataType) { // 1. 创建用于分组的Map const groupMap = new Map(); // 2. 遍历每个数据点 for (const item of data) { // 解析日期和时间 const [year, month, day, hour, minute] = item.date.split('-').map(Number); // 计算分组标识 (每10分钟一组) const groupMinute = Math.floor(minute / 10) * 10; const groupKey = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}-${hour.toString().padStart(2, '0')}-${groupMinute.toString().padStart(2, '0')}`; // 提取有效值(大于0) SDK文档有心率字段,实际没有心率字段,与厂家确认,使用脉率 pulseReat const validPulseRates = dataType== 1 ? item.pulseReat.filter(rate => rate > 0) : item.bloodOxygen.oxygens.filter(rate => rate > 0); // 添加到分组 if (!groupMap.has(groupKey)) { groupMap.set(groupKey, []); } groupMap.get(groupKey).push(...validPulseRates); } // 3. 计算每个分组的平均值 const result = []; groupMap.forEach((values, groupKey) => { let average = 0; if (values.length > 0) { // 计算平均值 const sum = values.reduce((total, rate) => total + rate, 0); average = Math.round(sum / values.length);// 四舍五入 } let timestamp = this.getTimestamp(groupKey) if(average > 0){ result.push({ type: dataType, // 1.心率 , 3.血氧 oneValue: average, time: timestamp, // 11位时间戳 stringTime: groupKey // 直接使用原始date字段 }); } }); // 4. 按时间排序(可选) result.sort((a, b) => a.stringTime.localeCompare(b.stringTime)); return result; }, /** * 步数详情 * @param {Array} data 手表SDK数据 * @param {string} name 用户姓名 * @param {Number} userId */ parseStepData(data,name,userId){ if (!Array.isArray(data)) { console.error('输入数据必须是数组'); return []; } return data .filter(item => this.isValidStep(item.step)) .map(item => this.formatStepItem(item,name,userId)); }, /** * 获取11位的时间戳 * @param {Long} date */ getTimestamp(date){ const dateParts = date.split('-'); const year = dateParts[0]; const month = dateParts[1]; const day = dateParts[2]; const hour = dateParts[3]; const minute = dateParts[4]; // 创建Date对象并获取时间戳 const dateObj = new Date( `${year}-${month}-${day}T${hour}:${minute}:00` ); // 单独 +5 分钟:使数据的时间更接近现在的时刻 const timestamp = (dateObj.getTime() / 1000) + (5 * 60); return timestamp; }, /** * 解析睡眠数据 * @param {*} data 睡眠数据 * @param {*} sleepFlagBitType 1-精准睡眠, 2-无睡眠 ,4-精准睡眠,数据结构与1一致,曲线修改为2bit表示,其他-普通睡眠 */ parseSleepData(data,sleepFlagBitType){ let sleepList = []; console.log("data.content ===============================================",data.content) data.content.forEach(item=>{ // 睡眠片段的入睡时间,13位时间戳 let startTimestamp = DayConverter.formatFullDateTime(item.fallAsleepTime); // 计算睡眠曲线 const timeline = item.sleepCurve.map((state, index) => { const currentTimestamp = startTimestamp + index * 60000; // 每分钟 +60,000ms /** * 睡眠:一个字符代表1分钟时长,0深睡,1浅睡,2快速眼动,3失眠,4苏醒 */ // console.log(`minute: ${DayConverter.getMinuteOfDay(currentTimestamp)}, status: ${state}, yyyyMMdd: ${DayConverter.formatDate(currentTimestamp)}`); return { yyyyMMdd: DayConverter.formatDate(currentTimestamp), timestamp: currentTimestamp, minute: DayConverter.getMinuteOfDay(currentTimestamp), status: this.et620SleepStatus(state) }; }); sleepList.push(...timeline); }) console.log("解析后格式:",sleepList.length) let completeList = CommonUtil.completeMissingData(sleepList); console.log("完整的数据:",completeList.length) // 组装数据 let arr = CommonUtil.assembleSleepData(completeList); arr[0].date = DayConverter.getDateByDayName(data.readDay); console.log("睡眠日期:",DayConverter.getDateByDayName(data.readDay)) return arr; }, /** * 普通睡眠 */ calculateNormalSleep(){ }, /** * 精准睡眠 */ calculatePreciseSleep(){ }, /** * 0深睡,1浅睡,2快速眼动,3失眠,4苏醒 * @param {*} status */ et620SleepStatus(status) { switch (status) { case 0: return 2; // 深睡 case 1: return 1; // 浅睡 case 2: return 4; // 快速眼动 default: return 3; // 清醒 } }, } // 导出模块 module.exports = ET620ParseDataUtil;