12. 健康功能
同步实时健康数据以及健康数据文件
12.1. 获取实时健康数据
//主动获取实时健康数据
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//注册RCSP事件监听器
healthOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
@Override
public void onHealthDataChange(BluetoothDevice device, HealthData data) {
//此处将回调健康数据
switch (data.type) {//根据类型不同分类
case AttrAndFunCode.HEALTH_DATA_TYPE_HEART_RATE: //心率
HeartRate heartRate = (HeartRate) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_AIR_PRESSURE: //气压
AirPressure airPressure = (AirPressure) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_ALTITUDE: //海拔高度
Altitude altitude = (Altitude) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_STEP: //运动步数
SportsSteps sportsSteps = (SportsSteps) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_PRESSUER: //压力检测
PressureDetection pressureDetection = (PressureDetection) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_BLOOD_OXYGEN: //血氧饱和度
OxygenSaturation oxygenSaturation = (OxygenSaturation) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_TRAINING_LOAD: //训练负荷
TrainingLoad trainingLoad = (TrainingLoad) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_MAX_OXYGEN_UPTAKE: //最大摄氧量
MaxOxygenUptake maxOxygenUptake = (MaxOxygenUptake) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_SPORT_RECOVERY_TIME://运动恢复时间
ExerciseRecoveryTime recoveryTime = (ExerciseRecoveryTime) data;
break;
}
}
});
//通过掩码获取实时数据,相关掩码:AttrAndFunCode.HEALTH_DATA_TYPE_XXX
//举例:获取实时心率,步数,距离,热量和血氧饱和度
int mask = 0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_HEART_RATE | (0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_STEP) | (0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_BLOOD_OXYGEN);
//从低位到高位排序
byte[] subMask = new byte[]{0x01, 0x07, 0x01};
//当前数据版本:0
byte version = 0;
//执行读取健康数据功能并且等待结果回调
healthOp.readHealthData(healthOp.getConnectedDevice(), new HealthDataQuery(version, mask, subMask), new OnOperationCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
//成功回调
//结果将在OnRcspEventListener#onHealthDataChange回调
}
@Override
public void onFailed(BaseError error) {
//失败回调
//error - 错误信息
}
});
12.2. 监听实时健康数据
//监听实时健康数据
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//注册RCSP事件监听器
watchManager.registerOnRcspEventListener(new RcspEventListenerManager() {
@Override
public void onHealthDataChange(BluetoothDevice device, HealthData data) {
//此处将回调健康数据
switch (data.type) {//根据类型不同分类
case AttrAndFunCode.HEALTH_DATA_TYPE_HEART_RATE: //心率
HeartRate heartRate = (HeartRate) data;
// heartRate.getRealTimeValue(); //实时心率
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_AIR_PRESSURE: //气压
AirPressure airPressure = (AirPressure) data;
// airPressure.getRealTimeValue(); //实时气压
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_ALTITUDE: //海拔高度
Altitude altitude = (Altitude) data;
// altitude.getRealTimeValue(); //实时高度
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_STEP: //运动步数
SportsSteps sportsSteps = (SportsSteps) data;
// sportsSteps.getStepNum(); //步数
// sportsSteps.getDistance(); //距离
// sportsSteps.getCalorie(); //热量
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_PRESSUER: //压力检测
PressureDetection pressureDetection = (PressureDetection) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_BLOOD_OXYGEN: //血氧饱和度
OxygenSaturation oxygenSaturation = (OxygenSaturation) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_TRAINING_LOAD: //训练负荷
TrainingLoad trainingLoad = (TrainingLoad) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_MAX_OXYGEN_UPTAKE: //最大摄氧量
MaxOxygenUptake maxOxygenUptake = (MaxOxygenUptake) data;
break;
case AttrAndFunCode.HEALTH_DATA_TYPE_SPORT_RECOVERY_TIME://运动恢复时间
ExerciseRecoveryTime recoveryTime = (ExerciseRecoveryTime) data;
// recoveryTime.getHour(); //时
// recoveryTime.getMin(); //分
break;
}
}
});
12.3. 健康数据对照表
名称 |
类型 |
数据模型 |
选项 |
|---|---|---|---|
心率 |
0x00 |
HeartRate |
心率范围: 0~220
Bit0 => realTimeValue: 实时心率
Bit1 => restingValue: 静息心率
Bit2 => maxValue:最大心率
|
气压 |
0x01 |
AirPressure |
气压范围: 300~1100 hPa
Bit0 => realTimeValue: 实时气压
Bit1 => minValue: 最低气压
Bit2 => maxValue: 最高气压
|
海拔高度 |
0x02 |
Altitude |
海拔高度范围: 0~65536 m
Bit0 => realTimeValue: 实时高度
Bit1 => minValue: 最低高度
Bit2 => maxValue: 最高高度
|
运动步数 |
0x03 |
SportsSteps |
Bit0 => stepNum: 运动步数, 范围: 0~200000 步
Bit1 => distance: 运动距离, 单位: 0.01 km
Bit2 => calorie: 热量, 单位: kcal
|
压力检测 |
0x04 |
PressureDetection |
Bit0 => detectionValue: 压力测试值, 范围: 0%~100% |
血氧饱和度 |
0x05 |
OxygenSaturation |
Bit0 => percent: 血氧饱和度, 范围: 0%~100% |
训练负荷 |
0x06 |
TrainingLoad |
Bit0 => value: 负荷值 |
最大摄氧量 |
0x07 |
MaxOxygenUptake |
Bit0 => value: 摄氧量 |
运动恢复时间 |
0x08 |
ExerciseRecoveryTime |
Bit0 => time: 时间, hour, min |
Important
健康数据的基类: HealthData
12.3.1. HealthData
健康数据的基类
public class HealthData {
public int type; //功能类型
public int version; //版本
public byte subMask; //功能掩码
public byte[] data; //有效数据
...
}
12.3.2. 掩码生成规则
subMask的生成, subMask结构为1Byte
//举例-查询实时心率,运动步数,运动距离,热量
int mask = 0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_HEART_RATE | (0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_STEP);
//从低位到高位排序
byte[] subMask = new byte[]{0x01, 0x07};
//type : 0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_HEART_RATE, subMask : 0x01 => 0000 0001 => 查询实时心率
//type : 0x01 << AttrAndFunCode.HEALTH_DATA_TYPE_STEP, subMask : 0x07 => 0000 0111 => 查询运动步数,运动距离,热量
public class AttrAndFunCode {
...
//健康数据类型
public final static int HEALTH_DATA_TYPE_HEART_RATE = 0x00;//心率
public final static int HEALTH_DATA_TYPE_AIR_PRESSURE = 0x01;//气压
public final static int HEALTH_DATA_TYPE_ALTITUDE = 0x02;//海拔高度
public final static int HEALTH_DATA_TYPE_STEP = 0x03;//运动步数
public final static int HEALTH_DATA_TYPE_PRESSURE = 0x04;//压力检测
public final static int HEALTH_DATA_TYPE_BLOOD_OXYGEN = 0x05;//血氧饱和度
public final static int HEALTH_DATA_TYPE_TRAINING_LOAD = 0x06;//训练负荷
public final static int HEALTH_DATA_TYPE_MAX_OXYGEN_UPTAKE = 0x07;//最大摄氧量
/**
* 运动恢复时间
* Deprecated 从运动结束数据中返回,不再单独获取
*/
@Deprecated
public final static int HEALTH_DATA_TYPE_SPORT_RECOVERY_TIME = 0x08;//运动恢复时间
...
}
12.4. 获取健康统计数据
12.4.1. 获取流程
12.4.2. 通用获取的实现类
public abstract class HealthFileSyncDemo {
final byte type;
final WatchManager mWatchManager;
/**
*
* @param type 文件类型:
* QueryFileTask.TYPE_HEART_RATE;
* QueryFileTask.TYPE_BLOOD_OXYGEN;
* QueryFileTask.TYPE_SLEEP;
* QueryFileTask.TYPE_STEP;
*/
public HealthFileSyncDemo(byte type) {
this.type = type;
mWatchManager = WatchManager.getInstance();
}
public void start() {
//获取小文件列表
QueryFileTask task = new QueryFileTask(mWatchManager, new QueryFileTask.Param(type));
task.setListener(new SimpleTaskListener() {
@Override
public void onFinish() {
super.onFinish();
List<QueryFileTask.File> list = task.getFiles();
Collections.reverse(list);//倒序,从后面往回读
readFileInfoRecursion(list);
}
@Override
public void onError(int code, String msg) {
super.onError(code, msg);
//获取文件列表失败 结束
}
});
task.start();
}
/**
* 递归读取文小文件
* @param list
*/
private void readFileInfoRecursion(List<QueryFileTask.File> list) {
if (list.size() < 1) {
//没有文件,结束
return;
}
readFileHeader(list.get(0), new SimpleTaskListener() {
@Override
public void onFinish() {
list.remove(0);
readFileInfoRecursion(list);
}
@Override
public void onError(int code, String msg) {
//获取文件内容失败,不应该继续读取,结束
}
});
}
/**
*读取小文件头内容
*/
private void readFileHeader(QueryFileTask.File file, final SimpleTaskListener simpleTaskListener) {
int size = 30;
ReadFileTask readFileTask = new ReadFileTask(mWatchManager, new ReadFileTask.Param(file.type, file.id, size, 0));
readFileTask.setListener(new SimpleTaskListener() {
@Override
public void onError(int code, String msg) {
super.onError(code, msg);
//获取文件头失败
simpleTaskListener.onError(code, msg);
}
@Override
public void onFinish() {
//todo 和本地数据库比对
byte[] data = readFileTask.getReadData();
if (isInLocal(data)) {
//一个文件读取结束
simpleTaskListener.onFinish();
} else {
readFileData(file, simpleTaskListener);
}
}
});
readFileTask.start();
}
/**
*读取小文件内容
*/
private void readFileData(QueryFileTask.File file, SimpleTaskListener simpleTaskListener) {
ReadFileTask readFileTask = new ReadFileTask(mWatchManager, new ReadFileTask.Param(file.type, file.id, file.size, 0));
readFileTask.setListener(new SimpleTaskListener() {
@Override
public void onError(int code, String msg) {
simpleTaskListener.onError(code, msg);
}
@Override
public void onFinish() {
byte[] data = readFileTask.getReadData();
// todo 保存到数据库
saveToDb(data);
simpleTaskListener.onFinish();//一个文件读取结束
}
});
readFileTask.start();
}
/**
* 和本地数据库比对
*/
protected boolean isInLocal(byte[] data) {
//todo 判断文件是否已经读取过
//1.获取data 内的文件crc
//2. 在本地查找是否有和该文件一样的crc文件,如有则读取过
return false;
}
/**
* 保存到数据
*/
protected void saveToDb(byte[] data) {
//todo 保存文件到本地
}
}
12.4.3. 通用获取的实现类的使用
HealthFileSyncDemo healthFileSyncDemo = new HealthFileSyncDemo(QueryFileTask.TYPE_SLEEP);
healthFileSyncDemo.start();
12.5. 统计数据文件结构
12.5.1. 小文件格式
文件类型(1Byte) |
日期(年月日,4Bytes) |
文件校验码(2Bytes) |
版本号(1Byte) |
存储间隔(1Byte) |
保留位(2Bytes) |
N * (数据格式) |
|---|---|---|---|---|---|---|
参考 类型对照表 |
yyyyMMdd
年: 2Bytes
月: 1Byte
日: 1Byte
|
CRC校验码 |
当前版本号:0 |
数据存储的时间间隔
范围: 1~60min
当该值为0xFF时为睡眠类型, 不读
|
保留位 |
参考 数据格式 |
12.5.2. 类型对照表
类型 |
类型值 |
备注 |
|---|---|---|
联系人 |
0x01 |
SmallFileTransferCmd.TYPE_CONTACTS |
运动记录 |
0x02 |
SmallFileTransferCmd.TYPE_SPORTS_RECORD
(不按照小文件格式, 参考 运动记录文件结构 )
|
心率全天数据 |
0x03 |
SmallFileTransferCmd.TYPE_HEART_RATE |
血氧全天数据 |
0x04 |
SmallFileTransferCmd.TYPE_BLOOD_OXYGEN |
睡眠全天数据 |
0x05 |
SmallFileTransferCmd.TYPE_SLEEP |
消息数据 |
0x06 |
SmallFileTransferCmd.TYPE_MESSAGE_SYNC |
天气数据 |
0x07 |
SmallFileTransferCmd.TYPE_WEATHER |
通话记录文件 |
0x08 |
SmallFileTransferCmd.TYPE_CALL_LOG |
步数全天数据 |
0x09 |
SmallFileTransferCmd.TYPE_STEP (包含 距离和热量) |
12.5.3. 数据格式
时间(时分)(2Bytes) |
长度(2Bytes) |
数据内容(n bytes) |
|---|---|---|
HHmm (时: 1Byte,分: 1Byte) |
数据内容的长度 |
参考 :数据内容 |
12.5.4. 数据内容
数据内容类型 |
数据内容 |
备注 |
|---|---|---|
睡眠 |
n * (睡眠类型(1Byte), 时长(1Byte, 单位是分钟)) |
|
心率 |
n * 数值(1Byte, 单位是次) |
静息心率在文件头部的保留位的低地址, 例如: 0x4000 64 |
血氧 |
n * 数值(1Byte, 单位是次) |
|
全天步数 |
n * (步数(2Bytes, 单位是步),距离(2Bytes, 单位是10米),热量(2Bytes, 单位是千卡)) * 柱增量 |
Note
睡眠得分(1Byte) |
深睡比例(1Byte) |
浅睡比例(1Byte) |
rem比例(1Byte) |
时长评价(1Byte) |
深睡连续性得分(1Byte) |
夜间醒来次数(1Byte) |
|---|---|---|---|---|---|---|
0~100 |
0~100 |
0~100 |
0~100 |
值范围:0 – 未更新
1 – 偏低 2 – 正常
3 – 偏高
总时长评价: 0~1Bit
深睡评价: 2~3Bit
浅睡评价: 4~5Bit
rem评价: 6~7Bit
|
0~100 |
0~127 |
12.5.4.1. 睡眠类型
- 前提条件:
睡眠类型的日期是以睡醒的时间作为记录日期
睡眠类型判断逻辑:
当检测到佩戴者的睡眠的开始时间或结束时间落在零点到六点范围内的都认为是夜间睡眠
当检测到佩戴者睡觉开始时间和结束时间均落在其他睡眠时间(非零点到六点)范围内都认为是零星小睡
睡眠日期时分的区分策略:
当收到一段夜间睡眠数据解析时, 00:00会作为分界线, 区分了前后一天的数据, 当睡眠类型为零星小睡时只作为当天的睡眠数据
类型 |
类型值 |
备注 |
|---|---|---|
清醒 |
0xFF |
|
浅睡 |
0x01 |
|
深睡 |
0x02 |
|
REM(快速眼动) |
0x03 |
|
零星小睡 |
0x04 |
不计入图表展示 |