|
|
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; |