2 功能说明

所有示例代码, WatchManager对象默认已初始化完成

2.1 文件管理

2.1.1 功能描述

管理设备sd、usb、flash的文件, 可以实现文件的浏览、删除, 文件下载、文件读取、格式化等功能。典型功能: 音乐管理

2.1.2 文件浏览

函数名

参数

返回值

作用说明

addFileObserver

FileObserver: 2.1.2.1.2   目录浏览观察者

void

注册目录浏览观察者

removeFileObserver

FileObserver: 2.1.2.1.2   目录浏览观察者

void

注销目录浏览观察者

setPageSize

int: 每次浏览文件个数,范围:[1, 30]

void

设置每次浏览文件个数

getPageSize

void

int: 每次浏览文件个数

获取每次浏览文件个数

getOnlineStorage

FileStruct: 2.1.2.1.3   文件结构

SDCardBean: 2.1.2.1.4   存储器信息

获取在线存储器

getCurrentReadFile

SDCardBean: 2.1.2.1.4   存储器信息

Folder: 2.1.2.1.5   文件夹

获取当前浏览目录

getCurrentFileStructs

SDCardBean: 2.1.2.1.4   存储器信息

List<FileStruct>: 子文件列表

读取当前目录下的已加载文件

isReading

void

boolean: 是否正在读取文件

是否正在读取文件

isOnline

int: 存储器索引号

boolean: 是否在线

存储器是否在线

getSdCardBeans

void

List<SDCardBean>: 存储器列表

获取存储器信息列表

getOnlineDev

void

List<SDCardBean>: 在线存储器列表

获取在线存储器列表

listFiles

int: 起始偏移

int: 2.1.2.1.1   操作结果码

目录浏览

listFiles

int: 起始偏移
boolean: 是否通知结果

int: 2.1.2.1.1   操作结果码

目录浏览

loadMore

SDCardBean: 2.1.2.1.4   存储器信息

int: 2.1.2.1.1   操作结果码

加载更多文件信息

appenBrowse

int: 2.1.2.1.1   操作结果码

浏览下一级目录

appenBrowse

Folder: 2.1.2.1.5   文件夹

int: 2.1.2.1.1   操作结果码

浏览下一级目录

backBrowse

SDCardBean: 2.1.2.1.4   存储器信息

int: 2.1.2.1.1   操作结果码

返回上一级目录(有回调)

backBrowse

boolean: 是否回调文件数据

int: 2.1.2.1.1   操作结果码

返回上一级目录

deleteFile

List<FileStruct>: 删除文件列表
DeleteCallback: 删除文件回调

int: 2.1.2.1.1   操作结果码

删除文件

deleteFile

List<FileStruct>: 删除文件列表
boolean: 是否需要准备环境
DeleteCallback: 删除文件回调

int: 2.1.2.1.1   操作结果码

删除文件

deleteFile

List<FileStruct>: 删除文件列表
boolean: 是否需要准备环境
DeleteCallback: 删除文件回调

int: 2.1.2.1.1   操作结果码

删除文件

playFile

int: 2.1.2.1.1   操作结果码

播放音乐文件

playFile

RegFile: 2.1.2.1.6   资源文件

int: 2.1.2.1.1   操作结果码

播放音乐文件

formatDevice

OperatCallback: 操作回调

int: 2.1.2.1.1   操作结果码

格式化存储器

cleanCache

void

void

清除所有缓存

cleanCache

BluetoothDevice: 蓝牙设备

void

清除指定设备的所有缓存

cleanCache

SDCardBean: 2.1.2.1.4   存储器信息

void

清除指定存储器的所有缓存

2.1.2.1 数据说明

2.1.2.1.1 操作结果码

名称

码值

描述

FileBrowseConstant#SUCCESS

0(0x0000)

成功结果

FileBrowseConstant#ERR_PARAM

4096(0x1001)

无效参数

FileBrowseConstant#ERR_BUSY

12291(0x3003)

系统繁忙

FileBrowseConstant#ERR_READING

16385(0x4001)

正在目录浏览

FileBrowseConstant#ERR_OFFLINE

16384(0x4000)

存储器下线

FileBrowseConstant#ERR_LOAD_FINISHED

16386(0x4002)

文件列表已加载完毕

FileBrowseConstant#ERR_NO_DATA

16387(0x4003)

丢失文件数据

FileBrowseConstant#ERR_BEYOND_MAX_DEPTH

16388(0x4004)

超过限制目录层级

FileBrowseConstant#ERR_OPERATION_TIMEOUT

4098(0x1003)

操作超时

FileBrowseConstant#ERR_FILE_NOT_IN_STORAGE

16390(0x4006)

文件结构与存储器不一致
说明输入参数冲突
2.1.2.1.2 目录浏览观察者
public interface FileObserver {

     /**
      * 收到目录文件数据后回调
      *
      * @param fileStructs 文件结构列表
      */
     void onFileReceiver(List<FileStruct> fileStructs);

     /**
      * 一次文件读取结束
      *
      * @param isEnd 是否结束
      */
     void onFileReadStop(boolean isEnd);

     /**
      * 文件读取开始
      */
     void onFileReadStart();

     /**
      * 文件读取失败
      *
      * @param reason 错误码
      */
     void onFileReadFailed(int reason);

     /**
      * 设备的存储设备状态变化
      *
      * @param onLineCards 在线设备列表
      */
     void onSdCardStatusChange(List<SDCardBean> onLineCards);

     /**
      * 文件点播成功回调
      */
     void OnFlayCallback(boolean success);

 }
2.1.2.1.3 文件结构
public class FileStruct implements IDataOp, Parcelable {

     /**
      * 是否文件
      *
      * <p>结果说明:
      * true -- 文件
      * false -- 文件夹</p>
      */
     private boolean file;
     /**
      * 是否Unicode编码
      *
      * <p>
      * 结果说明:
      * true -- Unicode编码
      * false -- ASCII编码
      * </p>
      */
     private boolean unicode;
     /**
      * 簇号。唯一标识
      */
     private int cluster = 0;
     /**
      * 文件夹序号
      */
     private short fileNum = 1;
     /**
      * 文件名
      */
     private String name = "";
     /**
      * 存储器类型
      */
     private byte devIndex;
}
2.1.2.1.4 存储器信息
public class SDCardBean implements Parcelable {

 /**
  * SD卡类型
  */
 public static final int SD = 0;
 /**
  * USB类型
  */
 public static final int USB = 1;
 /**
  * Flash类型
  */
 public static final int FLASH = 2;
 /**
  * LineIn类型
  */
 public static final int LINEIN = 3;
 /**
  * Flash2类型
  */
 public static final int FLASH_2 = 4;

 //index
 /**
  * USB索引
  */
 public static final int INDEX_USB = 0;
 /**
  * SD0索引
  */
 public static final int INDEX_SD0 = 1;
 /**
  * SD1类型
  */
 public static final int INDEX_SD1 = 2;
 /**
  * Flash索引
  */
 public static final int INDEX_FLASH = 3;
 /**
  * 输入设备索引
  */
 public static final int INDEX_LINE_IN = 4;
 /**
  * Flash2索引
  */
 public static final int INDEX_FLASH2 = 5;
 /**
  * Flash3索引
  */
 public static final int INDEX_FLASH3 = 6;


 /**
  * 存储器索引
  */
 private int index;
 /**
  * 存储器类型
  */
 private int type;
 /**
  * 存储器名称
  */
 private String name;
 /**
  * 存储器句柄
  */
 private int devHandler = -1;
 /**
  * 是否在线
  */
 private boolean online;
 /**
  * 操作设备
  */
 private BluetoothDevice device;
}
2.1.2.1.5 文件夹
public class Folder extends File {
  /**
   * 文件列表
   */
  private final transient List<File> files = new ArrayList<>();
  /**
   * 是否加载完成
   */
  private boolean isLoadFinish = false;
}
2.1.2.1.6 资源文件
public class RegFile extends File {

}

public abstract class File extends FileStruct {

 /**
  * 父类文件夹
  */
 private transient final Folder parent;
 /**
  * 目录层级
  */
 private final int level;
}

2.1.2.2 目录浏览

FileObserver fileObserver = new FileObserver() {
    @Override
    public void onFileReceiver(List<FileStruct> fileStructs) {
        // 读取到文件列表,仅仅回调本次读取的文件列表
    }

    @Override
    public void onFileReadStop(boolean isEnd) {
        // 文件列表读取结束
    }

    @Override
    public void onFileReadStart() {
        // 开始文件列表读取
    }

    @Override
    public void onFileReadFailed(int reason) {
        // 文件列表读取失败
    }

    @Override
    public void onSdCardStatusChange(List<SDCardBean> onLineCards) {
        //在线设备有变化
    }

    @Override
    public void OnFlayCallback(boolean success) {
        //歌曲点播回调
    }
};
//第一步:注册观察者

// 第2步:获取在线设备列表,可以通过fileObserver处理设备状态变化
FileBrowseManager.getInstance().addFileObserver(fileObserver);

// 第3步:读取当前设备正在读的当前目录
List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if(list.size()<1){
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
Folder currentFolder = FileBrowseManager.getInstance().getCurrentReadFile(sdCardBean);

//第4步:获取当前目录下已经读了但在缓存中的子文件
List<FileStruct> fileStructs = currentFolder.getChildFileStructs();

//第5步:浏览操作
//加载更多
FileBrowseManager.getInstance().loadMore(sdCardBean);
//进入下一级目录
FileStruct fileStruct = currentFolder.getChildFileStructs().get(0);//根据需要获取需要读取的文件夹
FileBrowseManager.getInstance().appenBrowse(fileStruct, sdCardBean);
//返回上一级目录没有列表回调
boolean hasEvent = true;//是否需要FileObserver的事件回调
FileBrowseManager.getInstance().backBrowse(sdCardBean,hasEvent);
//点播文件
FileBrowseManager.getInstance().playFile(fileStruct, sdCardBean);

2.1.2.3 文件删除

List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if(list.size() < 1){
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
List<FileStruct> fileStructs = new ArrayList<>();//注意fileStructs一定要是在sdCardBean中
boolean withEnv = false;//准备环境,一般使用false
FileBrowseManager.getInstance().deleteFile(sdCardBean, fileStructs,withEnv, new DeleteCallback() {
    @Override
    public void onSuccess(FileStruct fileStruct) {
        //成功
    }

    @Override
    public void onError(int code, FileStruct fileStruct) {
        //fileStruct 删除失败
    }

    @Override
    public void onFinish() {
        //删除结束,通过onError判断是否有删除失败的文件
    }
});

2.1.2.4 格式化

 //格式化,仅仅格式化设备
    List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if (list.size() < 1) {
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
FileBrowseManager.getInstance().formatDevice(sdCardBean, new OperatCallback() {
    @Override
    public void onSuccess() {
        //成功
    }

    @Override
    public void onError(int code) {
        //失败
    }
});

2.1.2.5 注意事项

Important

  1. 格式化操作请和固件确认是否需要备份或者准备环境

  2. 删除文件操作请和固件确认是否需要备份或者准备环境

2.1.3 大文件传输

2.1.3.1 文件下载(APP -> 设备)

对应类

功能说明

TransferTask

大文件传输任务(Path方式)

UriTransferTask

大文件传输任务(Uri方式)

示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager watchManager = WatchManager.getInstance();
SDCardBean sdCardBean = DeviceChoseUtil.getTargetDev();//获取设备目标设备,这里需要判断设备是否为空
if(sdCardBean == null) return;
    TransferTask.Param param = new TransferTask.Param();
param.devHandler = sdCardBean.getDevHandler();//设置设备句柄,可以理解为选择文件存储的设备:如sd,usb等,
/**
 * 是否使用其他编码方式。
 * 默认false,编码方式如下:<br/>
 * - 短文件名:  GBK {@link Charset#forName(String)}<br/>
 * - 长文件名: 增加"\\U" + Unicode编码数据 + 结束符, {@link Charset#forName(String)}
 * 若为true, 则{@link #encodeType} 属性生效
 */
param.isOtherEncode = false;
String path = "需要传输的文件路径";
ITask task = new TransferTask(watchManager,path,param);
task.setListener(new TaskListener() {
    @Override
    public void onBegin() {
        //文件传输开始
    }

    @Override
    public void onProgress(int progress) {
        //进度回调
    }

    @Override
    public void onFinish() {
        //传输结束
    }

    @Override
    public void onError(int code, String msg) {
        //传输异常
    }

    @Override
    public void onCancel(int reason) {
        //传输已取消
    }
});
//开始传输文件
if(!task.isRun()){
    task.start();
}else {
    //文件正在传输
}
    // 主动取消传输
    task.cancel((byte) 0x01);

Important

  1. 需要保证文件具有访问权限, 在Android API 28及以上, 部分文件无法通过文件路径读取, 可以使用UriTransferTask替代TransferTask, 两者在使用上仅仅是构造函数不同

ITask task = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
    task = new UriTransferTask(context, watchManager, uri, title, param);
 } else {
    task = new TransferTask(watchManager, path, param);
}
2.1.3.1.1 TransferTask.Param
public static class Param {
    public int devHandler; //设备句柄
    public boolean appHasCrc16 = true; //app是否支持crc16,一般用默认值
    public boolean useFlash = false;  //是否是flash设备

    /**
     * 是否使用其他编码方式
     *
     * <p>
     * 默认false,编码方式如下:<br/>
     * - 短文件名:  GBK {@link Charset#forName(String)}<br/>
     * - 长文件名: 增加"\\U" + Unicode编码数据 + 结束符, {@link Charset#forName(String)}
     * 若为true, 则{@link #encodeType} 属性生效
     * </p>
     */
    public boolean isOtherEncode = false; //是否使用其他编码方式

    /**
     * 编码方式
     *
     * <strong>1. 注意需要{@link  #isOtherEncode} 为 true, 字段才生效</strong>
     */
    public String encodeType = StandardCharsets.UTF_16LE.name(); //默认编码方式
    /**
     * 指定输出目录路径
     * 若为空,则指定输出文件的路径。
     */
    public String outputDirPath;
}

2.1.3.2 文件读取(设备 -> APP)

对应类

功能说明

GetFileByClusterTask

通过文件簇号读取文件任务

GetFileByNameTask

通过文件名读取文件任务

2.1.3.2.1 通过文件簇号读取文件

推荐读取文件方式

示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if (list.size() < 1) {
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
FileStruct fileStruct = null;//注意fileStructs一定要是在sdCardBean中
int devHandle = sdCardBean.getDevHandler();
int cluster = fileStruct.getCluster();
int offset = 0;
String path = "读取内容的保存路径";
GetFileByClusterTask.Param param = new GetFileByClusterTask.Param(devHandle, 0, cluster, path);
GetFileByClusterTask task = new GetFileByClusterTask(watchManager, param);
task.setListener(new TaskListener() {
    @Override
    public void onBegin() {
        //开始
    }

    @Override
    public void onProgress(int progress) {
        //进度回调
    }

    @Override
    public void onFinish() {
        //成功
    }

    @Override
    public void onError(int code, String msg) {
        //失败
    }

    @Override
    public void onCancel(int reason) {
        //取消
    }
});
task.start();
2.1.3.2.2 通过文件名读取文件

不推荐,需要固件端支持

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
String name = "文件名";
String path = "保存路径";
List<SDCardBean> list = FileBrowseManager.getInstance().getOnlineDev();
if (list.size() < 1) {
    //没有在线设备
    return;
}
SDCardBean sdCardBean = list.get(0);//获取设备,如果有多个设备,请根据需求获取相应的设备
int devHandle = sdCardBean.getDevHandler();
boolean unicode = false;//文件名是否支持长文件名(长文件采用unicode编码,短文件名使用8+3结构的ASCII编码)
GetFileByNameTask.Param param = new GetFileByNameTask.Param(devHandle,name,path,unicode);
GetFileByNameTask task = new GetFileByNameTask(watchManager,param);
task.setListener(new TaskListener() {
    @Override
    public void onBegin() {
        //开始
    }

    @Override
    public void onProgress(int progress) {
        //进度回调
    }

    @Override
    public void onFinish() {
        //成功
    }

    @Override
    public void onError(int code, String msg) {
        //失败
    }

    @Override
    public void onCancel(int reason) {
        //取消
    }
});
task.start();

Important

  1. 同一个watchManager实例同一时间只能运行一个文件管理的操作, 如果要连续执行多个操作, 需要做串行限制

  2. OTA过程中, 不允许调用文件操作

2.1.4 特殊操作(需要设备端配置)

使用以下接口时,请与固件端开发人员沟通是否支持。

2.1.4.1 通过文件名删除文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
String name = "文件名";
DeleteFileByNameCmd deleteFileByNameCmd = new DeleteFileByNameCmd(new DeleteFileByNameCmd.Param(name));
watchManager.sendRcspCommand(watchManager.getTargetDevice(), deleteFileByNameCmd, new BooleanRcspActionCallback("DeleteFileByName",
            new OnOperationCallback<Boolean>() {
                @Override
                public void onSuccess(Boolean result) {
                    //成功
                }

                @Override
                public void onFailed(BaseError error) {
                    //失败
                }
            }));

2.1.5 注意事项

Important

  1. 使用文件管理的相关接口时需要先检查存储设备是否上线

  2. 特殊操作需要和固件确认是否支持

  3. OTA过程中, 不允许调用文件操作

2.2 小文件传输

2.2.1 功能描述

小文件传输主要用于传输数据量较小的文件,如:运动记录、健康统计数据、联系人等,功能包括文件列表查询,文件读取,文件删除,文件添加,文件更新

2.2.2 示例代码

2.2.2.1 文件列表查询

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager watchManager = WatchManager.getInstance();
    //小文件类型:QueryFileTask.TYPE_BLOOD_OXYGEN、QueryFileTask.TYPE_SLEEP、QueryFileTask.TYPE_STEP等
    byte type = QueryFileTask.TYPE_HEART_RATE;
    QueryFileTask.Param param = new QueryFileTask.Param(type);
QueryFileTask task = new QueryFileTask(watchManager, param);
task.setListener(new SimpleTaskListener() {
    @Override
    public void onBegin() {
        //开始传输
    }

    @Override
    public void onFinish() {
        //查询成功
        List<QueryFileTask.File> list = task.getFiles();
    }

    @Override
    public void onError(int code, String msg) {
        //查询失败
    }
});
task.start();

2.2.2.2 新增文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager watchManager = WatchManager.getInstance();
    //小文件类型:QueryFileTask.TYPE_BLOOD_OXYGEN、QueryFileTask.TYPE_SLEEP、QueryFileTask.TYPE_STEP等
    byte type = QueryFileTask.TYPE_MESSAGE_SYNC;
    byte [] data = "数据包"
    AddFileTask.Param param = new AddFileTask.Param(type, data);
final AddFileTask task = new AddFileTask(mRcspOp, param);
task.setListener(new SimpleTaskListener() {

    @Override
    public void onBegin() {
        //开始传输
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //添加完成
    }
});
task.start();

2.2.2.3 读取文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
QueryFileTask.File file; //通过查询文件列表获取
byte type = file.type; //文件类型
short id = file.id; //文件id
int size = file.size; //要读取的内容大小
int offset = 0;//开始读取的位置。和size结合可以实现部分读取
ReadFileTask.Param param = new ReadFileTask.Param(type, id, size, offset);
ReadFileTask task = new ReadFileTask(watchManager, param);
task.setListener(new SimpleTaskListener() {

    @Override
    public void onBegin() {
        //开始传输
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //添加完成
    }
});
task.start();

2.2.2.4 修改文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
QueryFileTask.File file = null; //通过查询文件列表获取
byte type = file.type; //文件类型
short id = file.id; //文件id
    byte [] data  = "更新的数据";
UpdateFileTask.Param param = new UpdateFileTask.Param(type, id, data);
UpdateFileTask task = new UpdateFileTask(watchManager, param);
task.setListener(new SimpleTaskListener() {

    @Override
    public void onBegin() {
        //开始传输
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //完成
    }
});
task.start();

2.2.2.5 删除文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
QueryFileTask.File file = null; //通过查询文件列表获取
byte type = file.type; //文件类型
short id = file.id; //文件id
DeleteFileTask.Param param = new DeleteFileTask.Param(type, id);
DeleteFileTask task = new DeleteFileTask(watchManager, param);
task.setListener(new SimpleTaskListener() {

    @Override
    public void onBegin() {
        //开始
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //完成
    }
});
task.start();

2.2.3 注意事项

Important

  1. 每个设备对象不能同时进行小文件操作和文件操作, 只能队列式操作

  2. OTA过程中, 不允许调用小文件传输

2.3 表盘操作

表盘功能管理,包括:遍历文件列表,添加表盘,删除表盘,获取表盘,获取当前表盘信息,切换表盘,设置自定义表盘背景等等

2.3.1 遍历文件列表

2.3.1.1 功能描述

获取表盘相关文件列表

2.3.1.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
watchManager.listWatchList(new OnWatchOpCallback<ArrayList<FatFile>>() {
    @Override
    public void onSuccess(ArrayList<FatFile> result) {
        //成功回调
        //result 是结果,(watch或WATCH)前缀的是表盘文件,(bgp_w或BGP_W)前缀的是自定义背景文件
        //可以过滤获取所有Watch文件
    }

    @Override
    public void onFailed(BaseError error) {
       //失败回调
    }
});

2.3.1.3 FatFile

public class FatFile implements Parcelable {
    //文件大小
    private long size;
    //文件名
    private String name;
    //是否文件夹 (若false,则是文件)
    private boolean isDir;
    //修改时间 [格式: YYYY/MM/DD hh:mm:ss]
    private String modifyTime;
    //绝对路径
    private String path;

    //是否文件
    private boolean file;
    //是否unicode编码
    private boolean unicode;
    //簇号
    private int cluster;
    //文件序号
    private short fileNum;
    //设备驱动索引
    private byte devIndex;
}

2.3.2 插入表盘

2.3.2.1 功能描述

插入新的表盘文件

2.3.2.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//filePath是表盘文件路径,必须存在
//isNoNeedCheck:是否跳过文件校验
// - false: 表盘文件需要文件校验
// - true :  自定义背景文件不需要文件校验,但需要转换工具进行算法转换
//OnFatFileProgressListener:进度监听器
watchManager.createWatchFile(filePath, false, new OnFatFileProgressListener() {
    @Override
    public void onStart(String filePath) {
        //回调开始
    }

    @Override
    public void onProgress(float progress) {
        //回调进度
    }

    @Override
    public void onStop(int result) {
        //回调结束
        //result : 0 --- 成功  非0是错误码,参考WatchError
    }
});

Warning

表盘文件的判断可由固件工程师修改
下面是公版固件SDK的表盘文件判断方式
插入表盘文件的格式说明:
  1. 表盘文件是前缀 WATCH_ 的文件

  2. 表盘自定义背景文件是前缀 BGP_ 的文件

2.3.2.3 注意事项

Important

  1. 文件路径必须存在

  2. 自定义表盘背景必须通过转换工具进行转换, 参考 :2.3.14   图像转换

2.3.3 删除表盘

2.3.3.1 功能描述

删除已存在的表盘文件

2.3.3.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//watchPath: 设备存在的表盘文件路径
//OnFatFileProgressListener:进度监听器
watchManager.deleteWatchFile(watchPath, new OnFatFileProgressListener() {
    @Override
    public void onStart(String filePath) {
        //回调开始
    }

    @Override
    public void onProgress(float progress) {
        //回调进度
    }

    @Override
    public void onStop(int result) {
        //回调结束
        //result : 0 --- 成功  非0是错误码,参考WatchError
    }
});

2.3.3.3 注意事项

Important

  1. 表盘文件路径必须存在于设备

  2. 错误码参考 3.13   错误码定义

2.3.4 获取当前表盘信息

2.3.4.1 功能描述

获取当前使用的表盘信息

2.3.4.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
watchManager.getCurrentWatchInfo(new OnWatchOpCallback<FatFile>() {
    @Override
    public void onSuccess(FatFile result) {
        //成功回调 - FatFile是表盘信息
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
    }
});

2.3.5 获取表盘额外信息

2.3.5.1 功能描述

获取表盘文件的额外信息, 比如版本, 服务器对应的UUID等

2.3.5.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//watchPath:设备表盘文件的路径
watchManager.getWatchMessage(watchPath, new OnWatchOpCallback<String>() {
    @Override
    public void onSuccess(String result) {
        //成功回调,result是表盘额外信息
        //格式:[version],[uuid],
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
    }
});

2.3.6 设置当前使用表盘

2.3.6.1 功能描述

设置当前使用表盘。通过此功能切换表盘

2.3.6.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
// watchPath:必须存在于设备的表盘文件路径
watchManager.setCurrentWatchInfo(watchPath, new OnWatchOpCallback<FatFile>() {
    @Override
    public void onSuccess(FatFile result) {
        //成功回调 - FatFile是表盘信息
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
    }
});

2.3.7 设置当前使用表盘

2.3.7.1 功能描述

获取指定表盘对应的自定义背景信息

2.3.7.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//watchPath:设备表盘文件的路径
watchManager.getCustomWatchBgInfo(watchPath, new OnWatchOpCallback<String>() {
    @Override
    public void onSuccess(String result) {
        //成功回调,result是背景文件的路径
        //如果result是“null”,则是空路径,不存在自定义背景
        //如果result不是“null”,则是背景文件的路径
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
    }
});

2.3.7.3 注意事项

Important

  1. 若结果是 null ,意味着不存在自定义背景

  2. 若结果不是 null , 则是背景文件的路径

2.3.8 使能表盘自定义背景

2.3.8.1 功能描述

为当前使用的表盘使能指定的自定义背景

2.3.8.2 示例代码

 //WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//watchBgPath: 表盘背景路径
//若watchBgPath为“/null”,则视为当前表盘清除自定义背景的绑定
//若watchBgPath不为“/null”,则为当前表盘绑定自定义背景
watchManager.enableCustomWatchBg(watchBgPath, new OnWatchOpCallback<FatFile>() {
    @Override
    public void onSuccess(FatFile result) {
        //成功回调 - FatFile是自定义背景文件信息
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
    }
});

2.3.8.3 注意事项

Important

  1. 若表盘背景路径为 /null ,则视为当前表盘清除自定义背景的绑定

  2. 若表盘背景路径不为 /null ,则为当前表盘绑定自定义背景

2.3.9 资源更新

2.3.9.1 功能描述

更新设备资源文件

2.3.9.2 流程图

../../_images/update_resource_flow.png

2.3.9.3 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//resourcePath: 资源路径 (zip压缩包)
//OnUpdateResourceCallback: 更新资源进度监听器
watchManager.updateWatchResource(resourcePath, new OnUpdateResourceCallback() {
    @Override
    public void onStart(String filePath, int total) {
        //回调开始
        //filePath -- 资源文件路径
        //total -- 更新文件总数
    }

    @Override
    public void onProgress(int index, String filePath, float progress) {
        //回调进度
        //index -- 序号 (从0开始)
        //filePath -- 资源文件路径
        //progress -- 更新进度
    }

    @Override
    public void onStop(String otaFilePath) {
        //回调更新结束
        //otaFilePath: 固件升级文件路径
        //若固件升级文件不为null,则意味着需要进行OTA升级
    }

    @Override
    public void onError(int code, String message) {
        //回调错误事件
        //code -- 错误码 (参考WatchError)
        //message -- 错误信息
    }
});

2.3.9.4 注意事项

Important

  1. 确保压缩包文件正常且完整

  2. 若返回OTA文件路径,则需要执行OTA固件功能。参考 杰理OTA外接库开发文档(Android)

2.3.10 系统恢复

2.3.10.1 功能描述

系统出现异常时,用于恢复系统

2.3.10.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//注册手表事件回调
watchManager.registerOnWatchCallback(new OnWatchCallback() {
    //系统异常回调
    @Override
    public void onWatchSystemException(BluetoothDevice device, int sysStatus) {
        if(sysStatus != 0){ //系统状态不为0,则是系统异常
            //监听到系统异常,才调用此方法恢复系统
            //正常情况,禁止调用此方法
            watchManager.restoreWatchSystem(new OnFatFileProgressListener() {
                @Override
                public void onStart(String filePath) {
                    //回调开始
                }

                @Override
                public void onProgress(float progress) {
                    //回调进度
                }

                @Override
                public void onStop(int result) {
                    //回调结果
                    //result : 0 --- 成功  非0是错误码,参考WatchError
                }
            });
        }
    }
});

2.3.10.3 注意事项

Important

  1. 禁止在系统正常运行的情况下,使用该接口

  2. 错误码参考 3.13   错误码定义

2.3.11 表盘操作流程

2.3.11.1 添加表盘文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//执行插入表盘操作并等待结果回调
watchManager.createWatchFile(watchFilePath, false, new OnFatFileProgressListener() {
    @Override
    public void onStart(String filePath) {
        //回调开始插入表盘
    }

    @Override
    public void onProgress(float progress) {
        //回调插入表盘进度
    }

    @Override
    public void onStop(int result) {
        //回调插入表盘结果
        if (result == 0) { //插入表盘成功
            //设置当前表盘为插入表盘
            watchManager.setCurrentWatchInfo(FatUtil.getFatFilePath(watchFilePath), new OnWatchOpCallback<FatFile>() {
                @Override
                public void onSuccess(FatFile result) {
                    //设置当前表盘成功
                }

                @Override
                public void onFailed(BaseError error) {
                    //操作失败
                }
            });
        } else {
            //插入文件失败
        }
    }
});

2.3.11.2 添加表盘背景文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//执行插入自定义表盘背景文件操作并等待结果回调
watchManager.createWatchFile(bgFilePath, true, new OnFatFileProgressListener() {
    @Override
    public void onStart(String filePath) {
        //回调开始插入表盘背景文件
    }

    @Override
    public void onProgress(float progress) {
        //回调插入表盘背景文件进度
    }

    @Override
    public void onStop(int result) {
        //回调插入表盘背景文件结果
        if (result == 0) { //插入表盘背景文件成功
            //设置表盘背景文件为当前表盘的自定义背景
            watchManager.enableCustomWatchBg(FatUtil.getFatFilePath(bgFilePath), new OnWatchOpCallback<FatFile>() {
                @Override
                public void onSuccess(FatFile result) {
                    //激活自定义背景成功
                }

                @Override
                public void onFailed(BaseError error) {
                    //操作失败
                }
            });
        }else{
            //插入文件失败
        }
    }
});

Note

插入其他文件可以参考 添加表盘背景文件 实现

2.3.11.3 查询当前表盘的所有信息

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//查询当前表盘信息
watchManager.getCurrentWatchInfo(new OnWatchOpCallback<FatFile>() {
    @Override
    public void onSuccess(FatFile result) {
        //回调当前表盘信息
        //查询当前表盘的自定义背景信息
        watchManager.getCustomWatchBgInfo(result.getPath(), new OnWatchOpCallback<String>() {
            @Override
            public void onSuccess(String result) {
                //result 如果是null, 就是默认背景
                //result 若不是null, 则是自定义背景文件的路径
            }

            @Override
            public void onFailed(BaseError error) {
                //操作失败
            }
        });
    }

    @Override
    public void onFailed(BaseError error) {
        //操作失败
    }
});

2.3.12 获取手表系统剩余空间大小

2.3.12.1 功能描述

获取手表系统剩余空间大小

2.3.12.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//获取手表系统剩余空间
watchManager.getWatchSysLeftSize(new OnWatchOpCallback<Long>() {
    @Override
    public void onSuccess(Long result) {
        //手表剩余空间
    }

    @Override
    public void onFailed(BaseError error) {
        //错误信息
    }
});

2.3.12.3 注意事项

Important

  1. 注意调用时机, 必须在手表系统初始化完成之后调用

2.3.13 获取表盘文件的大小

2.3.13.1 功能描述

获取表盘文件的大小

2.3.13.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//fatFilePath : 表盘文件路径(必须存在)
//获取表盘文件的大小
watchManager.getWatchFileSize(fatFilePath, new OnWatchOpCallback<WatchFileContent>() {
    @Override
    public void onSuccess(WatchFileContent result) {
        result.getFileSize(); //表盘文件的大小
        result.getCrc();      //表盘文件的CRC (AC695X的SDK, 此此字段无效)
    }

    @Override
    public void onFailed(BaseError error) {
        //错误信息
    }
});

2.3.13.3 注意事项

Important

  1. 表盘文件路径必须存在设备

  2. CRC字段, 在AC695X的手表SDK无效, 在AC701X的手表SDK有效

2.3.14 图像转换

2.3.14.1 功能描述

普通PNG图片(RGB, ARGB)转换成表盘背景文件

2.3.14.2 示例代码

//1.初始化图片转换对象
BmpConvert bmpConvert = new BmpConvert();
String inPath = "输入文件路径"; //图像格式, png, jpg
String outPath = "输出文件路径"; //输出文件格式。二进制文件
//2.开始图像转换
//用户需要根据设备的芯片和性能来选择算法
int flag = BmpConvert.TYPE_701N_RGB; //JL701N-WATCH-SDK 图像转换算法 - RGB
//        flag = BmpConvert.TYPE_695N_RBG;   //AC695N-WATCH-SDK 图像转换算法 - RGB
//        flag = BmpConvert.TYPE_701N_ARGB; //JL701N-WATCH-SDK 图像转换算法 - ARGB
//        flag = BmpConvert.TYPE_701N_RGB_NO_PACK;   //JL701N-WATCH-SDK 图像转换算法 - RGB & 不打包封装
//        flag = BmpConvert.TYPE_701N_ARGB_NO_PACK;  //JL701N-WATCH-SDK 图像转换算法 - ARGB & 不打包封装
//        flag = BmpConvert.TYPE_707N_RGB;  //AC707N-WATCH-SDK 图像转换算法 - RGB
//        flag = BmpConvert.TYPE_707N_ARGB; //AC707N-WATCH-SDK 图像转换算法 - ARGB
//        flag = BmpConvert.TYPE_707N_RGB_NO_PACK;  //AC707N-WATCH-SDK 图像转换算法 - RGB & 不打包封装
//        flag = BmpConvert.TYPE_707N_ARGB_NO_PACK; //AC707N-WATCH-SDK 图像转换算法 - ARGB & 不打包封装
bmpConvert.bitmapConvert(flag, inPath, outPath, new OnConvertListener() {

    @Override
    public void onStart(String path) {
        //回调转换开始
        //path: 输入文件路径
    }

    @Override
    public void onStop(boolean result, String output) {
        //回调转换结束
        //result: 转换结果
        //output: 输出文件路径
    }
});
//3.不需要使用图片转换功能时,需要释放图片转换对象
//        bmpConvert.release();
  • 获取手表屏幕尺寸

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//获取缓存的手表系统信息
ExternalFlashMsgResponse watchSysInfo = watchManager.getExternalFlashMsg(watchManager.getConnectedDevice());
if(null == watchSysInfo) return;
int screenWidth = watchSysInfo.getScreenWidth();    //手表屏幕的宽度
int screenHeight = watchSysInfo.getScreenHeight();  //手表屏幕的高度

2.3.14.3 注意事项

Important

  1. 图像转换功能需要导入库:BmpConvert_Vxxx-release.aar

  2. 输入图像的尺寸不能超过手表的屏幕尺寸

  3. 输入图像的大小尽量小点

  4. 根据固件SDK选择合适的算法

  5. 输出的RGB图像为 RGB565 格式,输出的ARGB图像为 ARGB8565 格式

2.3.14.4 算法说明

芯片系列

旧算法名称

新算法名称

算法说明

AC695N

TYPE_BR_23

TYPE_695N_RBG

AC695N-WATCH-SDK 图像转换算法 - RGB

JL701N

TYPE_BR_28

TYPE_701N_RGB

JL701N-WATCH-SDK 图像转换算法 - RGB

TYPE_BR_28_ALPHA

TYPE_701N_ARGB

JL701N-WATCH-SDK 图像转换算法 - ARGB

TYPE_BR_28_RAW

TYPE_701N_RGB_NO_PACK

JL701N-WATCH-SDK 图像转换算法 - RGB & 不打包封装

TYPE_BR_28_ALPHA_RAW

TYPE_701N_ARGB_NO_PACK

JL701N-WATCH-SDK 图像转换算法 - ARGB & 不打包封装

AC707N

TYPE_707N_RGB

AC707N-WATCH-SDK 图像转换算法 - RGB

TYPE_707N_ARGB

AC707N-WATCH-SDK 图像转换算法 - ARGB

TYPE_707N_RGB_NO_PACK

AC707N-WATCH-SDK 图像转换算法 - RGB & 不打包封装

TYPE_707N_ARGB_NO_PACK

AC707N-WATCH-SDK 图像转换算法 - ARGB & 不打包封装

2.4 消息同步

监听目标第三方软件的消息通知,同步消息到设备端

2.4.1 推送消息

2.4.1.1 功能描述

监听到目标第三方软件的消息通知后,推送消息到设备端

2.4.1.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//模拟收到微信消息
String appName = HealthConstant.PACKAGE_NAME_WECHAT;
NotificationMsg msg = new NotificationMsg()
        .setAppName(appName)
        .setFlag(NotificationHelper.getNotificationFlag(appName))
        .setContent("帅小伙:[1条] 你好,欢迎使用健康助手,乐享运动,助力健康!")
        .setTitle("测试消息")
        .setTime(time)
        .setOp(NotificationMsg.OP_PUSH);
watchManager.pushMessageInfo(msg, new OnWatchOpCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
            //回调成功
    }

    @Override
    public void onFailed(BaseError error) {
        //回调失败
        //error: 错误信息
    }
});

2.4.1.3 注意事项

Important

  1. 需要获取监听应用通知的权限

  2. 需要判断应用是否打开通知功能

2.4.1.4 信息说明

信息类型表

Type

name

Value

0

时间戳

时间戳(4Bytes, 参考运动记录的时间戳格式 2.9.3.2   时间结构(4Bytes))

1

包名

最大限制范围: 31 Bytes

2

应用标识

Flag(1 Byte):
0: 默认图标
1: 短信图标
2: 微信图标
3: QQ图标
4: 钉钉图标

3

标题

最大限制范围: 36 Bytes

4

内容

最大限制范围: 133 ~ 439 Bytes, 由固件决定

2.4.2 删除消息

2.4.2.1 功能描述

监听到系统清除消息通知后,通知设备端删除该消息

2.4.2.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//模拟撤销微信消息
String appName = HealthConstant.PACKAGE_NAME_WECHAT;
NotificationMsg msg = new NotificationMsg()
        .setAppName(appName)
        .setFlag(NotificationHelper.getNotificationFlag(appName))
        .setTime(time)
        .setOp(NotificationMsg.OP_REMOVE);
watchManager.removeMessageInfo(msg, new OnWatchOpCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //回调成功
    }

    @Override
    public void onFailed(BaseError error) {
        //回调失败
        //error: 错误信息
    }
});

2.4.2.3 注意事项

Important

  1. 需要获取监听应用通知的权限

2.5 联系人管理

2.5.1 功能描述

管理设备上的联系人数据,支持读取和更新操作

2.5.2 示例代码

2.5.2.1 读取联系人

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
ReadContactsTask task = new ReadContactsTask(watchManager,context);
task.setListener(new SimpleTaskListener(){
    @Override
    public void onBegin() {
        //开始传输
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //完成
        List<DeviceContacts> contacts = task.getContacts();
    }
});
task.start();

2.5.2.2 更新联系人

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager watchManager = WatchManager.getInstance();
List<DeviceContacts> contacts = "修改后的联系人数据";
UpdateContactsTask task = new UpdateContactsTask(watchManager,context,contacts);
task.setListener(new SimpleTaskListener(){
    @Override
    public void onBegin() {
        //开始传输
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //完成
    }
});
task.start();

2.5.3 注意事项

Important

  1. 公版最多支持10个联系人, 在开发过程中需要和固件协商支持的联系人数量

  2. 联系人与大文件传输和小文件传输不能同一时刻进行

  3. 联系人传输需要依赖固件有存储介质,所以在使用该接口时需要判断设备环境

  4. 联系人姓名长度必须小于20字节

2.6 设备设置

2.6.1 功能描述

管理设备的设置功能,具体设置项有:传感器设置、久坐提醒、连续测量心率、运动心率提醒、睡眠检测、跌倒检测、抬腕检测、个人信息、蓝牙断开提醒

2.6.2 示例代码

2.6.2.1 读取设置状态

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//注册RCSP事件监听器
healthOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onHealthSettingChange(BluetoothDevice device, HealthSettingInfo healthSettingInfo) {
        //此处将会回调健康设置信息
        int funcFlag = healthSettingInfo.getFuncFlag(); //当前功能码标志
        switch (funcFlag){
            case AttrAndFunCode.HEALTH_SETTING_TYPE_SENSOR://传感器设置开关
                SensorInfo sensorInfo = healthSettingInfo.getSensorInfo();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_SEDENTARY_REMINDER://久坐提醒
                SedentaryReminder sedentaryReminder = healthSettingInfo.getSedentaryReminder();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_HEART_RATE_MEASURE://心率连续测量
                HeartRateMeasure heartRateMeasure = healthSettingInfo.getHeartRateMeasure();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_EXERCISE_HEART_RATE_REMINDER: //运动心率测量
                ExerciseHeartRateReminder exerciseHeartRateReminder = healthSettingInfo.getExerciseHeartRateReminder();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_AUTOMATIC_PRESSURE_DETECTION://压力自动检测
                AutomaticPressureDetection automaticPressureDetection = healthSettingInfo.getAutomaticPressureDetection();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_SLEEP_DETECTION: //睡眠检测
                SleepDetection sleepDetection = healthSettingInfo.getSleepDetection();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_FALL_DETECTION: //跌倒检测
                FallDetection fallDetection = healthSettingInfo.getFallDetection();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_LIFT_WRIST_DETECTION: //抬腕检测
                LiftWristDetection liftWristDetection = healthSettingInfo.getLiftWristDetection();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_USER_INFO: //个人信息
                UserInfo userInfo = healthSettingInfo.getUserInfo();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_DISCONNECT_REMINDER: //蓝牙断开提醒
                DisconnectReminder disconnectReminder = healthSettingInfo.getDisconnectReminder();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_BLOOD_OXYGEN_MEASUREMENT_ALERT:  //血氧测量提醒
                BloodOxygenMeasurementAlert bloodOxygenMeasurementAlert = healthSettingInfo.getBloodOxygenMeasurementAlert();
                break;
            case AttrAndFunCode.HEALTH_SETTING_TYPE_EMERGENCY_CONTACT: //紧急联系人
                EmergencyContact emergencyContact = healthSettingInfo.getEmergencyContact();
                break;
        }
    }
});
// mask 设置功能对应的掩码
// 1.获取全部设置
//int mask = 0xffffffff;

// 2.获取部分设置 类型值为:AttrAndFunCode.HEALTH_SETTING_TYPE_XXX,XXX是对应的类型,如获取传感器和睡眠检测,
//查询传感器设置和睡眠检测设置
//3. 不同的功能对应不同的Bit位,对应Bit置1,表示查询该功能
int mask = 0x01 << AttrAndFunCode.HEALTH_SETTING_TYPE_SENSOR
            | 0x01 << AttrAndFunCode.HEALTH_SETTING_TYPE_SLEEP_DETECTION;
//执行读取健康设置信息并等待结果回调
healthOp.readHealthSettings(healthOp.getConnectedDevice(), mask, new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会在OnRcspEventListener#onHealthSettingChange回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});
//获取缓存的设置状态,使用缓存状态需要先主动获取状态,避免状态不一致,sdk不会自动获取设备的设置状态
HealthSettingInfo healthSettingInfo = healthOp.getRcspOp().getDeviceInfo().getHealthSettingInfo();

2.6.2.2 修改设置状态

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//获取缓存的设置状态,使用缓存状态需要先主动获取状态,避免状态不一致,sdk不会自动获取设备的设置状态
HealthSettingInfo healthSettingInfo = watchManager.getDeviceInfo().getHealthSettingInfo();
//举例: 修改蓝牙断开提醒的设置状态
final DisconnectReminder disconnectReminder = healthSettingInfo.getDisconnectReminder();
//进行修改操作
disconnectReminder.setEnable(false);
//执行配置健康设置信息功能并等待结果回调
healthOp.configHealthSettings(healthOp.getConnectedDevice(), disconnectReminder, new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //修改成功后,可以重新读取健康设置信息
        //int mask = 1 << disconnectReminder.toAttr().getType();
        //healthOp.readHealthSettings(healthOp.getConnectedDevice(), mask, null);
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});

2.6.3 设置项说明

设置

标识

数据模型

可设置项

传感器功能

0x01

SensorInfo

计步传感器、心率传感器、血氧传感器、海拔气压传感器
通过SensorInfo的getXXX() 方法获取传感器实例<
开关状态enable: false:关闭 、true:打开
记录开关状态recordEnable: false:关闭 、true:打开

久坐提醒

0x02

SedentaryReminder

开关状态enable: false:关闭 、true:打开
提醒模式mode: 0x00:亮屏、 0x01:震动
午休免打扰freeLunchBreak: 关闭/打开(午休时段为12:00-14:00)<
开始时间:startHour, startMin
结束时间:endHour, endMin

连续测量心率

0x03

HeartRateMeasure

开关状态enable: false:关闭 、true:打开
提醒模式mode: 0x00:智能、 0x01: 实时

运动心率提醒

0x04

ExerciseHeartRateReminder

开关状态enable: false:关闭 、true:打开
上限心率max:(0-255)
心率区间划分方式spaceMode: 0x00:最大心率百分比、0x01:存储心率百分比

压力自动检测

0x05

AutomaticPressureDetection

开关状态enable: false:关闭 、true:打开
模式mode: 0 : 放松、1: 正常、2: 中等、3: 偏高

睡眠检测

0x06

SleepDetection

状态status: 0x00:关闭、0x01:全天开启、0x02 自定义时间段
开始时间:startHour, startMin
结束时间:endHour, endMin

跌倒检测

0x07

FallDetection

开关状态enable: false:关闭 、true:打开
提醒模式mode: 0x00:亮屏、 0x01:震动 、 0x02:电话呼叫
电话号码: mobile

抬腕检测

0x08

LiftWristDetection

状态status: 0x00:关闭、0x01:全天开启、0x02 自定义时间段<
提醒模式mode: 0x00:亮屏、 0x01:震动
开始时间:startHour, startMin
结束时间:endHour, endMin

个人信息

0x09

UserInfo

身高:height, 单位: cm, 取整
体重:weight, 单位: kg, 取整
出生年:birthYear
出生月:birthMonth
出生日:birthDay
性别sex: 0x00:女 0x01:男

蓝牙断开提醒

0x0A

DisconnectReminder

状态status: 0x00:关闭、0x01:开启
提醒模式mode: 0x00:亮屏、 0x01:震动

血氧测量提醒

0x0B

BloodOxygenMeasurementAlert

状态status: 0x00:关闭、0x01:开启
模式mode: 0: 智能模式 1: 定时模式
血氧下限数值limitingValue: 0-100

紧急联系人

0x0C

EmergencyContact

电话号码长度phoneLen
电话号码number

引用常量

public class AttrAndFunCode {
    ...
    //健康设备设置
    public final static int HEALTH_SETTING_TYPE_SENSOR = 0x01;//传感器功能
    public final static int HEALTH_SETTING_TYPE_SEDENTARY_REMINDER = 0x02;//久坐提醒
    public final static int HEALTH_SETTING_TYPE_HEART_RATE_MEASURE = 0x03;//连续测量心率
    public final static int HEALTH_SETTING_TYPE_EXERCISE_HEART_RATE_REMINDER = 0x04;//运动心率提醒
    public final static int HEALTH_SETTING_TYPE_AUTOMATIC_PRESSURE_DETECTION = 0x05;//压力自动检测
    public final static int HEALTH_SETTING_TYPE_SLEEP_DETECTION = 0x06;//睡眠检测
    public final static int HEALTH_SETTING_TYPE_FALL_DETECTION = 0x07;//跌倒检测
    public final static int HEALTH_SETTING_TYPE_LIFT_WRIST_DETECTION = 0x08;//抬腕检测
    public final static int HEALTH_SETTING_TYPE_USER_INFO = 0x09;//个人信息
    public final static int HEALTH_SETTING_TYPE_DISCONNECT_REMINDER = 0x0A;//蓝牙断开提醒
    public final static int HEALTH_SETTING_TYPE_BLOOD_OXYGEN_MEASUREMENT_ALERT = 0x0B; //血氧测量提醒
    public final static int HEALTH_SETTING_TYPE_EMERGENCY_CONTACT = 0x0C; //紧急联系人
    ...
}

2.7 天气同步

2.7.1 功能描述

同步天气信息给设备,目前仅仅支持实时天气同步,暂不支持天气预告

2.7.2 示例代码

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
String province = "省份";
String city = "城市";
byte weatherCode = 0;//天气编码,参考天气编码表
byte temperature = 0;//温度
byte humidity = 0;   //湿度
byte windPower = 0;  //风力等级
byte windDirectionCode = 0; //方向编码,参考方向编码表
long time = System.currentTimeMillis(); //时间
//构建天气信息
PushInfoDataToDeviceCmd.Weather weather = new PushInfoDataToDeviceCmd.Weather(province, city,
        weatherCode, temperature,
        humidity, windPower, windDirectionCode,
        time
);
//执行同步天气信息功能并等待结果回调
watchManager.syncWeatherInfo(weather, new OnWatchOpCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //天气同步成功
    }

    @Override
    public void onFailed(BaseError error) {
        //天气同步失败
        //error - 错误信息
    }
});

2.7.3 天气编码表

码值

说明

码值

说明

码值

说明

0

11

阵雨

22

极端降雨

1

少云

12

雷阵雨

23

雨夹雪/阵雨夹雪/冻雨/雨雪天气

2

晴间多云

13

雷阵雨并伴有冰雹

24

3

多云

14

雨/小雨/毛毛雨/细雨/小雨-中雨

25

阵雪

4

15

中雨/中雨-大雨

26

小雪/小雪-中雪

5

有风/和风/清风/微风

16

大雨/大雨-暴雨

27

中雪/中雪-大雪

6

平静

17

暴雨/暴雨-大暴雨

28

大雪/大雪-暴雪

7

大风/强风/劲风/疾风

18

大暴雨/大暴雨-特大暴雨

29

暴雪

8

飓风/狂暴风

19

特大暴雨

30

浮尘

9

热带风暴/风暴

20

强阵雨

31

扬尘

10

霾/中度霾/重度霾/严重霾

21

强雷阵雨

32

沙尘暴


33

强沙尘暴

34

龙卷风

35

雾/轻雾/浓雾/强浓雾/特强浓雾

36

37

38

未知

2.7.4 方向编码表

code

0

1

2

3

4

5

6

7

8

9

说明

无风向

西

东南

东北

西北

西南

旋转不定

2.8 闹钟管理

2.8.1 功能描述

同步手机时间到设备、管理闹钟包括:读取、修改、删除

2.8.2 示例代码

2.8.2.1 时间同步

//同步手机时间到设备
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//执行同步时间功能并等待结果回调
rtcOp.syncTime(rtcOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.2 读取闹钟

//读取闹钟
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//注册Rcsp事件监听器
rtcOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onAlarmListChange(BluetoothDevice device, AlarmListInfo alarmListInfo) {
        //此处将会回调闹钟列表信息
    }
});
//执行读取闹钟列表功能并等待结果回调
rtcOp.readAlarmList(rtcOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会在OnRcspEventListener#onAlarmListChange处回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.3 修改闹钟

//修改闹钟
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//注册Rcsp事件监听器
rtcOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onAlarmListChange(BluetoothDevice device, AlarmListInfo alarmListInfo) {
        //此处将会回调闹钟列表信息
    }
});
//        AlarmBean alarmBean = "通过读取闹钟得到";
AlarmBean alarmBean = new AlarmBean();
//执行创建或修改闹钟功能并等待结果回调
rtcOp.addOrModifyAlarm(rtcOp.getConnectedDevice(), alarmBean, new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调,成功设置后重新获取闹钟列表,更新UI
        //rtcOp.readAlarmList(rtcOp.getConnectedDevice(), null);
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.4 删除闹钟

//删除闹钟
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//注册Rcsp事件监听器
rtcOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onAlarmListChange(BluetoothDevice device, AlarmListInfo alarmListInfo) {
        //此处将会回调闹钟列表信息
    }
});
//        AlarmBean alarmBean = "通过读取闹钟得到";
AlarmBean alarmBean = new AlarmBean();
//执行删除闹钟功能并等待结果回调
rtcOp.deleteAlarm(rtcOp.getConnectedDevice(), alarmBean, new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调,删除成功后重新获取闹钟列表,更新UI
        //rtcOp.readAlarmList(rtcOp.getConnectedDevice(), null);
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.5 获取默认铃声选择列表

//读取闹钟默认铃声列表,用于设置闹钟铃声
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//注册Rcsp事件监听器
rtcOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onAlarmDefaultBellListChange(BluetoothDevice device, List<DefaultAlarmBell> bells) {
        //此处将会回调闹钟默认铃声列表信息
    }
});
//执行读取默认闹钟铃声列表功能并等待结果回调
rtcOp.readAlarmDefaultBellList(rtcOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会在OnRcspEventListener#onAlarmDefaultBellListChange处回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.6 铃声试听

//读取闹钟默认铃声列表,用于设置闹钟铃声
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//注册Rcsp事件监听器
rtcOp.getRcspOp().registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onAlarmDefaultBellListChange(BluetoothDevice device, List<DefaultAlarmBell> bells) {
        //此处将会回调闹钟默认铃声列表信息
    }
});
//执行读取默认闹钟铃声列表功能并等待结果回调
rtcOp.readAlarmDefaultBellList(rtcOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会在OnRcspEventListener#onAlarmDefaultBellListChange处回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
         //error -- 错误信息
    }
});

2.8.2.7 停止铃声试听

//停止铃声试听
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//执行停止试听铃声功能并等待结果回调
rtcOp.stopAlarmBell(rtcOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //设备将会暂停播放铃声
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.8 获取闹铃模式设置

//获取闹铃模式设置
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
boolean enable = watchManager.getDeviceInfo().getAlarmExpandFlag() == 0x01;
if (enable) {
    //不支持闹铃模式设置
    return;
}
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//      AlarmBean alarmBean = "通过读取闹钟得到";
AlarmBean alarmBean = new AlarmBean();
int mask = 0x01 << alarmBean.getIndex();//可以一次获取多个闹钟的闹铃模式设置状态
//执行读取闹钟铃声参数功能并等待结果回调
rtcOp.readAlarmBellArgs(rtcOp.getConnectedDevice(), (byte) mask, new OnOperationCallback<List<AlarmExpandCmd.BellArg>>() {
    @Override
    public void onSuccess(List<AlarmExpandCmd.BellArg> result) {
        //成功回调
        //result - 闹钟铃声参数
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error -- 错误信息
    }
});

2.8.2.9 设置闹铃模式

//设置闹铃模式
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
boolean enable = watchManager.getDeviceInfo().getAlarmExpandFlag() == 0x01;
if (enable) {
    //不支持闹铃模式设置
    return;
}
//初始化时钟功能实现类
RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
//        AlarmExpandCmd.BellArg bellArg = "通过获取闹铃模式设置得到";
AlarmExpandCmd.BellArg bellArg = null;
//执行设置闹钟铃声参数功能并等待结果回调
rtcOp.setAlarmBellArg(rtcOp.getConnectedDevice(), bellArg, new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
    }
});

2.8.2.10 监听闹钟铃声

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
watchManager.registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onAlarmNotify(BluetoothDevice device, AlarmBean alarmBean) {
        super.onAlarmNotify(device, alarmBean);
        //闹钟闹铃

        //初始化时钟功能实现类
        RTCOpImpl rtcOp = new RTCOpImpl(watchManager);
        //执行停止闹钟闹铃功能并等待结果回调
        rtcOp.stopAlarmBell(device, new OnOperationCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean result) {

            }

            @Override
            public void onFailed(BaseError error) {

            }
        });
    }

    @Override
    public void onAlarmStop(BluetoothDevice device, AlarmBean alarmBean) {
        super.onAlarmStop(device, alarmBean);
        //闹钟闹铃已停止
    }
});

2.8.3 注意事项

Important

  1. 最大闹钟个数: 5个

  2. 新增闹钟: 索引index支持: 0-4

    • 新增操作应根据闹钟列表的索引决定当前新建闹钟的index。
      比如, 列表中有索引: 1和3的两个闹钟, 新增闹钟的index应该为2
  3. 闹铃模式可以在固件短配置,所以在使用时需要先检测固件是否支持该功能

2.9 运动功能

2.9.1 功能描述

控制和同步设备的运动状态

2.9.2 示例代码

2.9.2.1 同步运动状态

//同步状态
//使用场景:1.App连接成功  2.app接收到开始运动命令 3.app主动发送开始运动命令
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//执行读取运动信息功能并等待结果回调
healthOp.readSportsInfo(healthOp.getConnectedDevice(), new OnOperationCallback<SportsInfo>() {
    @Override
    public void onSuccess(SportsInfo result) {
        //成功回调
        //result.getMode();//运动类型
        //result.getState(); //运动状态
        //result.getId(); //运动id
        //RcspUtil.intToTime(result.getId()); //运动开始时间
        //result.getReadRealTimeDataInterval(); //同步运动实时数据的时间间隔
        //result.getHeartRateMode();//运动的心率模式
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});

2.9.2.2 开始运动

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//注册RCSP事件监听器
watchManager.registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onSportsState(BluetoothDevice device, int state) {
        //此处将会回调运动状态变化
        if (state == StateCode.SPORT_STATE_RUNNING) { //正在运动
            //同步设备状态
            healthOp.readSportsInfo(device, null);
        }
    }
});
int mode = SportsInfoStatusSyncCmd.SPORTS_TYPE_OUTDOOR & 0xff; //户外运动模式
//执行开始运动功能并等待结果回调
healthOp.startSports(healthOp.getConnectedDevice(), mode, new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会OnRcspEventListener#onSportsState回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});

2.9.2.3 暂停运动

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//注册RCSP事件监听器
watchManager.registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onSportsState(BluetoothDevice device, int state) {
        //此处将会回调运动状态变化
        if (state == StateCode.SPORT_STATE_PAUSE) { //运动暂停
            //更新运动状态
        }
    }
});
//执行暂停运动功能并等待结果回调
healthOp.pauseSports(healthOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会OnRcspEventListener#onSportsState回调
    }

     @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});

2.9.2.4 继续运动

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//注册RCSP事件监听器
watchManager.registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onSportsState(BluetoothDevice device, int state) {
        //此处将会回调运动状态变化
        if (state == StateCode.SPORT_STATE_RESUME) {//继续运动
            //同步设备状态
            //healthOp.readSportsInfo(device, null);
        }
    }
});
//执行继续运动功能并等待结果回调
healthOp.resumeSports(healthOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会OnRcspEventListener#onSportsState回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});

2.9.2.5 停止运动

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//注册RCSP事件监听器
watchManager.registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onSportsState(BluetoothDevice device, int state) {
        //此处将会回调运动状态变化
        if (state == StateCode.SPORT_STATE_NONE) { //运动结束
            //处理运动信息
            //1.获取运动信息,有两种方法
            //1) 等待onSportInfoChange回调
            //2) 获取缓存运动信息
//               SportsInfo sportsInfo = watchManager.getDeviceInfo(device).getSportsInfo();
//               sportsInfo.getEndTime(); //结束时间
//               sportsInfo.getRecoveryTime(); //运动恢复时间
//               sportsInfo.getRecordFileId(); //运动记录文件ID
//               sportsInfo.getRecordFileSize(); //运动记录文件大小
//               sportsInfo.getExerciseIntensityState(); //运动强度状态
            //2.获取运动记录文件
//               QueryFileTask.File file = new QueryFileTask.File(QueryFileTask.TYPE_SPORTS_RECORD, sportsInfo.getRecordFileId(), sportsInfo.getRecordFileSize());
//               ReadFileTask.Param param = new ReadFileTask.Param(QueryFileTask.TYPE_SPORTS_RECORD, (short) file.id, file.size, 0);
                //详细参考文件传输功能说明
        }
    }

    @Override
    public void onSportInfoChange(BluetoothDevice device, SportsInfo sportsInfo) {
        //此处将会回调改变的运动信息

    }
});
//执行停止运动功能并等待结果回调
healthOp.stopSports(healthOp.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //成功回调
        //结果将会OnRcspEventListener#onSportsState回调
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
         //error - 错误信息
    }
});

2.9.2.6 同步实时运动数据

//同步运动实时数据
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//初始化健康功能实现
HealthOpImpl healthOp = new HealthOpImpl(watchManager);
//执行读取运动实时数据功能并等待结果回调
healthOp.readRealTimeSportsData(healthOp.getConnectedDevice(), new OnOperationCallback<RealTimeSportsData>() {
    @Override
    public void onSuccess(RealTimeSportsData result) {
        if(result == null){
            onFailed(new BaseError(RcspErrorCode.ERR_RESPONSE_BAD_RESULT, "Real time data is null."));
            return;
        }
        //成功回调
//        result.getVersion();        //版本
//        result.getStep();           //运动步数
//        result.getDistance();       //运动距离, 单位:0.01 km
//        result.getDuration();       //运动时长, 单位:秒
//        result.getSpeed();          //速度,单位:km/h
//        result.getPace();           //配速,单位:s/km
//        result.getCalorie();        //热量,单位:kcal
//        result.getStepFreq();       //步频,单位:step/min
//        result.getStride();         //步幅,单位:cm
//        result.getExerciseStatus(); //运动强度状态:最大心率模式={0非运动、1热身、2燃脂、3有氧耐力、4无氧耐力、5极限}<br/>储备心率模式={0非运动、1有氧基础、2有氧进阶、3乳酸阈值、4无氧基础、5无氧进阶}
//        result.getHeartRate();      //实时心率
    }

    @Override
    public void onFailed(BaseError error) {
        //失败回调
        //error - 错误信息
    }
});

2.9.2.7 监听设备的运动状态变化

//监听设备的运动状态变化
//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//注册RCSP事件监听器
watchManager.registerOnRcspEventListener(new OnRcspEventListener() {
    @Override
    public void onSportsState(BluetoothDevice device, int state) {
        //此处回调运动状态
        switch (state) {
            case StateCode.SPORT_STATE_NONE:     //运动结束
                //参考结束运动功能处理
                break;
            case StateCode.SPORT_STATE_RUNNING:  //正在运动
                //参考开始运动功能处理
                break;
            case StateCode.SPORT_STATE_PAUSE:    //运动暂停
                break;
            case StateCode.SPORT_STATE_RESUME:   //继续运动
                break;
        }
    }

    @Override
    public void onSportInfoChange(BluetoothDevice device, SportsInfo sportsInfo) {
        //此处回调运动信息
    }
});

2.9.2.8 读取运动记录文件

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
QueryFileTask.File file; //通过运动结束命令或者查询文件列表获取到
byte type = file.type; //文件类型
short id = file.id; //文件id
int size = file.size; //要读取的内容大小
//注意:如果运动结束返回的file.id和file.size为0,则认为运动距离过短,设备没有保存运动记录
ReadFileTask.Param param = new ReadFileTask.Param(type, id, size, 0);
ReadFileTask task = new ReadFileTask(watchManager, param);
task.setListener(new SimpleTaskListener() {

    @Override
    public void onBegin() {
        //开始
    }

    @Override
    public void onError(int code, String msg) {
        //异常
    }

    @Override
    public void onFinish() {
        //读取完成
        byte [] data =  task.getReadData(); //获取文件内容
        SportRecord sportRecord = SportRecord.from(data);//解析运动数据内容
    }
});
task.start();

2.9.3 运动记录文件结构

运功记录文件通过小文件传输方式获取, 注意:运动记录的文件数据是以小端格式存储

偏移

长度

属性名

备注

Byte 0

1

运动模式

运动模式:参考 2.9.3.1   运动模式

Byte 1

1

版本号

有效范围: 0~255

Byte 2

1

间隔

有效范围: 1~180 , 单位是秒

Byte 3-12

10

保留位

格式如下:
Byte0 : mask — 检查校验位(0xee: 检查位完整 0xe0: 数据被破坏)
Byte1-4 : 数据信息
- Bit0-14 : block — 数据块(代表有多少个数据格式)
- Bit15-31 : size — 文件大小
Byte5-9 : reserved — 保留位

Byte 13-n

n

数据区域

数据格式: [flag(1Byte)+len(1Byte)+data] * n
heart rate: 心率, 有效范围: 0~220
speed: 速度, 有效范围: 0~8000, 单位是0.01公里/小时
pace: 配速, 单位:秒
n : 第n公里
flag: 标志位
- 0: 开始时间包: 参考 2.9.3.2   时间结构(4Bytes)
- 1: 基础包: heart(1Byte)+step_freq(2Bytes)+speed(2Bytes)
- 2: 暂停包: 参考 2.9.3.2   时间结构(4Bytes)
- 3: 每公里配速包: pace(2Bytes)+n(1Byte)
- 0xff: 结束包: 参考 2.9.3.2   时间结构(4Bytes)

Byte(n+1)-(n+2)

2

时长

有效范围: 1-28800, 单位是秒

Byte(n+3)-(n+6)

4

保留位

保留位

Byte(n+7)-(n+8)

2

距离

有效范围: 1-65535, 单位是0.01公里

Byte(n+9)-(n+10)

2

热量

有效范围: 1-65535, 单位是千卡, Kcal

Byte(n+11)-(n+14)

4

步数

有效范围: 0-200000, 单位是步

Byte(n+15)-(n+16)

2

恢复时间

Byte(n+15): 时, 单位是1小时, 有效范围: 0~168
Byte(n+16): 分, 单位是1分钟, 有效范围: 0~60

Byte(n+17)

1

心率模式

0x00: 最大心率模式 0x01: 存储心率模式

Byte(n+18)-(n+38)

20

运动强度状态站比时长

(4Bytes为一组, 从模式1开始), 单位是秒

2.9.3.1 运动模式

模式名称

模式值

非运动模式

0x00

室外跑步

0x01

室内跑步

0x02

2.9.3.2 时间结构(4Bytes)

意义

位置

Bit31-26

Bit25-22

Bit21-17

Bit16-12

Bit11-6

Bit5-0

长度(bit)

6

4

5

5

6

6

备注

起始时间:2010

1~12

1 ~ 31

0 ~ 24

0 ~ 59

0 ~ 59

举例:
2021-08-02 10:10:10
2E04A28A
00 1011   1000  00010     01010    001010    001010
                                      

2.10 健康功能

2.10.1 功能描述

同步实时健康数据以及健康数据文件

2.10.2 示例代码

2.10.2.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 - 错误信息
    }
});

2.10.2.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;
        }
    }
});

2.10.2.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

public class HealthData {
    public int type;       //功能类型
    public int version;    //版本
    public byte subMask;   //功能掩码
    public byte[] data;    //有效数据
    ...
}

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;//运动恢复时间
    ...
}

2.10.3 获取健康统计数据

2.10.3.1 获取流程

../../_images/obation_health_statistics_data_flow.png

2.10.3.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 保存文件到本地
    }
}

2.10.3.3 通用获取的实现类的使用

HealthFileSyncDemo healthFileSyncDemo = new HealthFileSyncDemo(QueryFileTask.TYPE_SLEEP);
healthFileSyncDemo.start();

2.10.4 统计数据文件结构

2.10.4.1 小文件格式

文件类型(1Byte)

日期(年月日,4Bytes)

文件校验码(2Bytes)

版本号(1Byte)

存储间隔(1Byte)

保留位(2Bytes)

N * (数据格式)

参考 2.10.4.1.1   类型对照表

yyyyMMdd
年: 2Bytes
月: 1Byte
日: 1Byte

CRC校验码

当前版本号:0

数据存储的时间间隔
范围: 1~60min
当该值为0xFF时为睡眠类型, 不读

保留位

参考 2.10.4.2   数据格式

2.10.4.1.1 类型对照表

类型

类型值

备注

联系人

0x01

SmallFileTransferCmd.TYPE_CONTACTS

运动记录

0x02

SmallFileTransferCmd.TYPE_SPORTS_RECORD
(不按照小文件格式, 参考 2.9.3   运动记录文件结构 )

心率全天数据

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 (包含 距离和热量)

2.10.4.2 数据格式

时间(时分)(2Bytes)

长度(2Bytes)

数据内容(n bytes)

HHmm (时: 1Byte,分: 1Byte)

数据内容的长度

参考 :2.10.4.3   数据内容

2.10.4.3 数据内容

数据内容类型

数据内容

备注

睡眠

n * (睡眠类型(1Byte), 时长(1Byte, 单位是分钟))

心率

n * 数值(1Byte, 单位是次)

静息心率在文件头部的保留位的低地址, 例如: 0x4000 64

血氧

n * 数值(1Byte, 单位是次)

全天步数

n * (步数(2Bytes, 单位是步),距离(2Bytes, 单位是10米),热量(2Bytes, 单位是千卡)) * 柱增量

Note

当数据格式是睡眠内容时,
如果时间为0xFFFF, 则说明该段数据格式为分析数据内容, 应使用以下方法解析:

睡眠得分(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

2.10.4.4 睡眠类型

前提条件:

睡眠类型的日期是以睡醒的时间作为记录日期

睡眠类型判断逻辑:

  1. 当检测到佩戴者的睡眠的开始时间或结束时间落在零点到六点范围内的都认为是夜间睡眠

  2. 当检测到佩戴者睡觉开始时间和结束时间均落在其他睡眠时间(非零点到六点)范围内都认为是零星小睡

睡眠日期时分的区分策略:

  1. 当收到一段夜间睡眠数据解析时, 00:00会作为分界线, 区分了前后一天的数据, 当睡眠类型为零星小睡时只作为当天的睡眠数据

类型

类型值

备注

清醒

0xFF

浅睡

0x01

深睡

0x02

REM(快速眼动)

0x03

零星小睡

0x04

不计入图表展示

2.11 找手机/设备

2.11.1 功能描述

找手机: 接收到设备的命令后,通过播放手机的音频,从而使用户找到手机
找设备: SDK发送命令给设备后, 通过设备播放指定音频,从而让用户找到设备

2.11.2 示例代码

2.11.2.1 找手机

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//铃声管理器
final RingHandler mRingHandler = RingHandler.getInstance();
//注册手表事件回调
watchManager.registerOnWatchCallback(new OnWatchCallback() {
    //监听设备主动推送的命令
    @Override
    public void onRcspCommand(BluetoothDevice device, CommandBase command) {
        if (command.getId() == Command.CMD_SEARCH_DEVICE) { //处理查找设备命令
            SearchDevCmd searchDevCmd = (SearchDevCmd) command;
            SearchDevParam param = searchDevCmd.getParam();
            if(param == null) return;
            if (param.getOp() == RcspConstant.RING_OP_OPEN) { //铃声打开
                //播放铃声
                //type: 播放方式
                //play timeout: 播放超时
                mRingHandler.playAlarmRing(param.getType(), param.getTimeoutSec() * 1000);
                isRingPlay = true;
            } else { //铃声关闭
                mRingHandler.stopAlarmRing();
                isRingPlay = false;
            }
            //回复设备操作成功
            searchDevCmd.setStatus(StateCode.STATUS_SUCCESS);
            searchDevCmd.setParam(new SearchDevParam.SearchDevResultParam(0));
            watchManager.sendCommandResponse(device, searchDevCmd, null);
        }
    }
});

2.11.2.2 找设备

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//构建查找设备命令
//op      --- 查找设备
//timeout --- 超时时间
//ringWay --- 全部响铃
//player  --- 设备播放
CommandBase searchDevCmd = CommandBuilder.buildSearchDevCmd(RcspConstant.SEARCH_TYPE_DEVICE, 60,
            RcspConstant.RING_WAY_ALL, RcspConstant.RING_PLAYER_DEVICE);
watchManager.sendRcspCommand(watchManager.getConnectedDevice(), searchDevCmd, new RcspCommandCallback<SearchDevCmd>() {
    @Override
    public void onCommandResponse(BluetoothDevice device, SearchDevCmd cmd) {
        if (cmd.getStatus() != StateCode.STATUS_SUCCESS) {
            onErrCode(device, RcspErrorCode.buildJsonError(cmd.getId(), RcspErrorCode.ERR_RESPONSE_BAD_STATUS, cmd.getStatus(), null));
            return;
        }
        SearchDevResponse response = cmd.getResponse();
        if (null == response) {
           onErrCode(device, new BaseError(RcspErrorCode.ERR_PARSE_DATA, RcspErrorCode.getErrorDesc(RcspErrorCode.ERR_PARSE_DATA)));
            return;
        }
        if (response.getResult() == 0) {
            //请求成功, 说明设备在响铃
        } else {
            onErrCode(device, RcspErrorCode.buildJsonError(cmd.getId(), RcspErrorCode.ERR_RESPONSE_BAD_RESULT, response.getResult(), null));
        }
    }

    @Override
    public void onErrCode(BluetoothDevice device, BaseError error) {
        //处理错误事件
    }
});

2.11.2.3 停止查找

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
final WatchManager watchManager = WatchManager.getInstance();
//构建停止查找设备命令
watchManager.sendRcspCommand(CommandBuilder.buildSearchDevCmd(RcspConstant.RING_OP_CLOSE, 0), new RcspCommandCallback<SearchDevCmd>() {
    @Override
    public void onCommandResponse(BluetoothDevice device, SearchDevCmd cmd) {
        if (cmd.getStatus() != StateCode.STATUS_SUCCESS) {
            onErrCode(device, RcspErrorCode.buildJsonError(cmd.getId(), RcspErrorCode.ERR_RESPONSE_BAD_STATUS, cmd.getStatus(), null));
            return;
        }
        SearchDevResponse response = cmd.getResponse();
        if (null == response) {
            onErrCode(device, new BaseError(RcspErrorCode.ERR_PARSE_DATA, RcspErrorCode.getErrorDesc(RcspErrorCode.ERR_PARSE_DATA)));
            return;
        }
        if (response.getResult() == 0) {
            //请求成功, 说明设备已经停止响铃
        } else {
            onErrCode(device, RcspErrorCode.buildJsonError(cmd.getId(), RcspErrorCode.ERR_RESPONSE_BAD_RESULT, response.getResult(), null));
        }
    }

    @Override
    public void onErrCode(BluetoothDevice device, BaseError error) {
        //处理错误事件
    }
});

2.11.3 注意事项

Important

  1. 铃声播放最好是播放闹钟,其他音频播放,有可能因为手机静音而没有声音。

  2. 手机不要设置成静音模式

2.12 自定义命令

2.12.1 功能描述

方便客户拓展功能

2.12.2 示例代码

2.12.2.1 发送自定义命令

public void sendCustomCommand(byte[] data) {
    //WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager manager = WatchManager.getInstance();
    //build the custom command
    CommandBase customCmd = CommandBuilder.buildCustomCmd(data); //carries custom data
    final boolean isNoResponse = false; //设置自定义命令是否不需要回复, 不建议使用命令不回复
    if (isNoResponse) {
       customCmd = CommandBuilder.buildCustomCmdWithoutResponse(data);//Setting does not require reply
    }
    //Send custom command and waiting for the result callback
    manager.sendRcspCommand(manager.getTargetDevice(), customCmd, new RcspCommandCallback<CustomCmd>() {
        @Override
        public void onCommandResponse(BluetoothDevice device, CustomCmd cmd) {
            if (cmd.getStatus() != StateCode.STATUS_SUCCESS) {
                onErrCode(device, RcspErrorCode.buildJsonError(cmd.getId(), RcspErrorCode.ERR_RESPONSE_BAD_STATUS, cmd.getStatus(), null));
                return;
            }
            boolean hasResponse = cmd.getType() == CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE
                    || cmd.getType() == CommandBase.FLAG_NO_PARAMETER_HAVE_RESPONSE;
            if (hasResponse) { //有回复
                CustomResponse response = cmd.getResponse();
                if (null == response) {
                    onErrCode(device, new BaseError(RcspErrorCode.ERR_PARSE_DATA, RcspErrorCode.getErrorDesc(RcspErrorCode.ERR_PARSE_DATA)));
                    return;
                }
                byte[] data = response.getData();
                //parse data
            }
        }

        @Override
        public void onErrCode(BluetoothDevice device, BaseError error) {
            //callback error event
        }
    });
}

2.12.2.2 接收自定义命令

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager manager = WatchManager.getInstance();
//add Rcsp event callback
manager.registerOnRcspCallback(new OnRcspCallback() {
    @Override
    public void onRcspCommand(BluetoothDevice device, CommandBase command) {
        //receive rcsp command
            if (command.getId() != Command.CMD_EXTRA_CUSTOM) return; //filter other command
            CustomCmd customCmd = (CustomCmd) command;
            //Determine whether to reply the command
            boolean hasResponse = customCmd.getType() == CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE
                    || customCmd.getType() == CommandBase.FLAG_NO_PARAMETER_HAVE_RESPONSE;
            CustomParam param = customCmd.getParam();
            if (param == null) {
                if (hasResponse) { //需要回复
                    ////you can set up the custom response data if desired
                    //byte[] responseData = new byte[0]; //custom response data
                    //param.setData(responseData);
                    //customCmd.setParam(param);
                    customCmd.setParam(null);
                    customCmd.setStatus(StateCode.STATUS_SUCCESS);
                    manager.sendCommandResponse(device, customCmd, null);
                }
                return;
            }
            byte[] data = param.getData(); //the custom data from device
            //parse data
            if (hasResponse) { //需要回复
                //doing some thing and reply a success result.
                customCmd.setStatus(StateCode.STATUS_SUCCESS);
                ////you can set up the custom response data if desired
                //byte[] responseData = new byte[0]; //custom response data
                //param.setData(responseData);
                //customCmd.setParam(param);
                customCmd.setParam(null);
                manager.sendCommandResponse(device, customCmd, null);
            }
    }
});

2.12.3 注意事项

1. 自定义命令的长度限制
public void checkRCSPProtocolMTU(BluetoothDevice device) {
    //最大发送MTU
    int protocolMtu = DeviceStatusManager.getInstance().getMaxCommunicationMtu(device);
    //自定义命令的大小 = 最大发送MTU - 协议包长度
    int customDataLimit = protocolMtu - 23;
    //建议与固件协商好,不建议发最大数据
}
2. 建议与固件协商好最大发送数值, 不要超过协议MTU

2.13 获取异常信息

2.13.1 功能描述

当设备发送异常后, 重新连接上可以获取设备异常信息

2.13.2 流程图

../../_images/obtain_exception_message_flow.jpg

Important

  1. 用户可以保存获取的异常信息, 也可以上传到服务器

  2. 具体实现参考 com.jieli.healthaide.tool.watch.synctask.DeviceLogcatSyncTask

2.13.3 示例代码

//创建任务
final ReadLogcatTask task = new ReadLogcatTask(mWatchManager);
//设置监听器
task.setListener(new TaskListener() {
    @Override
    public void onBegin() {
        //回调任务开始
    }

    @Override
    public void onProgress(int progress) {
        //回调进度
    }

    @Override
    public void onFinish() {
        //回调任务完成
        JL_Log.i(tag, "ReadLogcatTask: onFinish : read logcat size = " + task.getResult().length);
        uploadLogFile(task.getResult());
    }

    @Override
    public void onError(int code, String msg) {
        //回调任务异常信息
        JL_Log.w(tag, "ReadLogcatTask: onError : " + code + ", " + msg);
        if (null != finishListener) finishListener.onFinish();
    }

    @Override
    public void onCancel(int reason) {
        //回调任务被取消, 该任务暂不支持取消操作
    }
});
//执行任务
task.start();

2.13.4 错误码

码值

对应的常量

描述

WatchError#ERR_REMOTE_NOT_CONNECT

TaskBase#ERR_DEVICE_NOT_CONNECT

设备未连接

WatchError#ERR_FUNC_NOT_SUPPORT

TaskBase#ERR_FUNC_NOT_SUPPORT

功能不支持

WatchError#ERR_CRC_CHECK

TaskBase#ERR_CRC_CHECK

CRC校验失败

WatchError#ERR_IO_EXCEPTION

TaskBase#ERR_IO_EXCEPTION

IO异常

2.14 AI相关功能

目前已开发的AI相关功能有:AI云服务、AI表盘

Important

公版发出去的项目工程,编译是无法正常使用AI相关功能的。需要先前往科大讯飞注册开通相关功能。在对应位置进行配置【APPID】【APISecret】【APIKey】。

2.14.1 AI云服务

Note

为兼容AI表盘功能,若App用到AI云服务功能的话,需要使用优化后的AI云服务功能(AIManager),不能使用优化前的AI云服务功能(AIServeManager)。如何区分AI云服务功能,是优化前还是优化后?优化后的AI云服务功能是集成在 AIManager.class 内,优化前是单独在 AIServeManager.class 实现

2.14.1.1 功能描述

固件对应的功能:【科大讯飞AI】

App对应的功能:【设备】->【AI云服务】。

  • 初始化设备时,设备端告诉App是否支持AI云服务功能。

  • 设备端开始录音时,发送RCSP命令告诉App端开始录音。然后就开始传输录音数据。

  • 设备端停止录音,发送RCSP命令告诉App端停止录音,是否需要传输录音识别结果,是否需要传输语义识别结果,是否需要播放TTS。

  • App收到开始录音后,开始把接受到的实时录音数据进行Opus转码成PCM数据,转码PCM结果发送给科大讯飞服务器。App收到停止录音后,发送录音结束命令给科大讯飞服务器。科大讯飞先返回语言识别结果,再返回语音识别结果,再返回语义识别结果,App根据语义识别结果进行TTS转换。

  • App端把语音识别结果、语义识别结果,TTS播放状态发送给设备端。

该功能的时序图如下:

../../_images/ai_serve_single_timing_diagram.jpg

2.14.1.2 科大讯飞配置

如不使用科大讯飞的SDK,改用其他平台的AISDK。可跳过这一步。

公版发出去的HealthAide项目,编译后是无法正常使用AI云服务功能。需要先前往科大讯飞注册开通【星火认知大模型-星火大模型V1.5】、【语音识别】、【语音合成】功能。

IflytekChatWrapper.class IflytekIatWrapper.class IflytekTtsWrapper.class 对应位置配置【APPID】【APISecret】【APIKey】。

//IflytekChatWrapper.class

public IflytekChatWrapper(Context context) {
    ......
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId,appKey,apiSecret。否则会闪退
        final AiHelper.Params.Builder params = AiHelper.Params.builder()
                .appId("ffffffff"/**APPID**/)
                .apiKey("fffffffffffffffffffffffffffffff"/**APIKey**/)
                .apiSecret("ffffffffffffffffffffffffffffffff"/**APISecret**/)
                .workDir(rootFile.getPath());
        ......
    }
}
//IflytekIatWrapper.class

public IflytekIatWrapper(Context context) {
    //开log
    Setting.setShowLog(true);
    //初始化即创建语音配置对象
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId。否则会闪退
        SpeechUtility.createUtility(context, SpeechConstant.APPID + "=" + "ffffffff"/**APPID**/);
    }
    //初始化识别无UI识别对象
    mSpeechRecognizer = SpeechRecognizer.createRecognizer(context, mIatInitListener);
}
//IflytekTtsWrapper.class

public IflytekTtsWrapper(Context context) {
    //开log
    Setting.setShowLog(true);
    //初始化即创建语音配置对象
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId。否则会闪退
        SpeechUtility.createUtility(context, SpeechConstant.APPID + "=" + "ffffffff"/**APPID**/);
    }
    // 初始化合成对象
    mTts = SpeechSynthesizer.createSynthesizer(context, mTtsInitListener);
}

2.14.1.3 使用示例

/**
* @ClassName: AICloudServerDemo
* @Description: AI云服务功能示例
* @Author: ZhangHuanMing
* @CreateDate: 2023/12/28 11:23
*/
public class AICloudServerDemo {
    private AIManager mAIManager;
    private final Observer<SessionInfo> mRecordStatusObserver = sessionInfo -> {
        if (sessionInfo != null) {
            switch (sessionInfo.getStatus()) {
                case SessionInfo.STATE_RECORD_START://开始录音
                case SessionInfo.STATE_RECORDING://录音中
                case SessionInfo.STATE_IDLE://默认状态
                case SessionInfo.STATE_RECORD_END://录音结束
                case SessionInfo.STATE_IAT_END://语音识别结束
                case SessionInfo.STATE_NLP_END://语义识别结束
                case SessionInfo.STATE_FAIL://异常
                    //获取当前对话消息
                    List<AICloudMessage>  aiCloudMessageList=  sessionInfo.getSessionMessageList();
                    for (AICloudMessage aiCloudMessage:aiCloudMessageList) {
                        //获取对话的类型。0;用户,1;AI
                        int itemType =  aiCloudMessage.getItemType();
                        //获取消息的数据实体
                        AICloudMessageEntity entity =  aiCloudMessage.getEntity();
                        entity.getAiCloudState();//此消息的语音识别状态
                        entity.getDevMac();//设备mac
                        entity.getId();//消息的唯一id
                        entity.getRevId();//消息对应的回复消息的id
                        entity.getRole();//消息的角色,0;用户,1;AI
                        entity.getText();//消息的文本
                        entity.getTime();//消息的时间
                        entity.getUid();//用户的唯一id
                    }
                    break;
            }
        }
    };
    /**
    * 初始化AIManager
    */
    @Test
    public void init(Context context){
        //Step1. 初始化
        AIManager.init(context, WatchManager.getInstance());
        mAIManager = AIManager.getInstance();
        //Step2. 监听对话状态。请根据使用场景选择observeForever或observe
        mAIManager.getAICloudServe().currentSessionMessageMLD.observeForever(mRecordStatusObserver);
    }

    /**
    * 获取录音开始时间
    */
    @Test
    public long getStartRecordTime(){
        return mAIManager.getAICloudServe().getStartRecordTime();
    }


    /**
    * 停止语音合成
    */
    @Test
    public void stopTTS(){
        mAIManager.getAICloudServe().stopTTS();
    }
}

2.14.1.4 接入其他AI平台

请参考 IflytekAICloudServeHandler.classIflytekAIIatHandler.class 调用 BaseAICloudServeHandlerBaseAIIatHandler 的方法

//BaseAIIatHandler.class
//基础语音识别处理
public abstract class BaseAIIatHandler extends BaseAIHandler {
    /**
    * 需要实现
    */
    //设备断开
    abstract void onDeviceDisconnect();
    // 开始录音
    abstract void onRecordStart();
    // 收到录音数据(已解码成PCM格式)
    abstract void onRecordData(byte[] recordData);
    // 录音结束
    abstract void onRecordStop();
    // 录音取消
    abstract void onRecordCancel();


    /**
    * 需要调用
    */
    //语音识别异常标志位
    void setRecognizerFail(boolean recognizerFail);
    //网络异常标志位
    void setNetworkWrong(boolean networkWrong);
    //识别结果
    void onRecognizerResult(String iatString);
    //识别失败
    void onRecognizerFail();
}
//BaseAICloudServeHandler.class
//AI云服务处理流程
public abstract class BaseAICloudServeHandler extends BaseAIChatHandler {
    /**
    * 需要实现
    */
    /**
    * 停止TTS合成播放
    */
    abstract void onStopTTS();

    /**
    * 开始TTS合成播放
    */
    abstract void onStartTTS(String ttsText);

    /**
    * 开始对话
    *
    * @param userText 用户文本
    */
    abstract void onStartChat(String userText);

    /**
    * 处理对话状态
    */
    abstract void onHandlerSessionStatus(int status);

    /**
    * 需要调用
    */
    //添加语义文本对话内容
    protected void addNlpTTSMessage(String nlpText);
    //开始语义对话
    protected void onStartChat();
    //播放tts失败
    protected void onPlayTTSFail();
    //播放tts结束
    protected void onPlayTTSStop();
}

2.14.1.5 是否支持AI云服务

如何判断设备是否支持AI云服务功能

WatchConfigure configure  mWatchManager.getWatchConfigure(device);
boolean isSupportAICloud = configure == null || configure.getFunctionOption() != null && configure.getFunctionOption().isSupportAICloud();;

2.14.2 AI表盘

2.14.2.1 功能描述

固件对应的功能:【AI表盘】

App对应的功能:【设备】->【AI表盘】。

  • 初始化设备时,设备端告诉App是否支持AI表盘功能。

  • 设备进入AI表盘界面,发送RCSP命令告诉App端在使用AI表盘功能。

  • 设备端开始录音时,发送RCSP命令告诉App端开始录音。然后就开始传输录音数据。

  • 设备端停止录音,发送RCSP命令告诉App端停止录音。

  • App收到开始录音后,开始把接受到的实时录音数据进行Opus转码成PCM数据,转码PCM结果发送给科大讯飞服务器。App收到停止录音后,发送录音结束命令给科大讯飞服务器。科大讯飞先返回语音识别结果,再返回语音识别结果。

  • App同步语音识别结果给设备,设备显示语音识别结果。用户确认生成AI表盘

  • App调用文生图接口生成图片,根据屏幕尺寸裁剪生成缩略图发送给设备。用户确认安装表盘

  • App根据图片生成自定义表盘,给设备安装

该功能的时序图如下:

../../_images/ai_dial_single_timing_diagram.jpg

2.14.2.2 科大讯飞配置

如不使用科大讯飞的SDK,改用其他平台的AISDK。可跳过这一步。**

公版发出去的HealthAide项目,编译后是无法正常使用AI表盘功能。需要先前往科大讯飞注册开通【星火认知大模型-图片生成】、【语音识别】、【语音合成】功能。

IflytekChatWrapper.class IflytekIatWrapper.class IflytekTextToImageWrapper.class 对应位置配置【APPID】【APISecret】【APIKey】。

//IflytekChatWrapper.class

public IflytekChatWrapper(Context context) {
    ......
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId,appKey,apiSecret。否则会闪退
        final AiHelper.Params.Builder params = AiHelper.Params.builder()
                .appId("ffffffff"/**APPID**/)
                .apiKey("fffffffffffffffffffffffffffffff"/**APIKey**/)
                .apiSecret("ffffffffffffffffffffffffffffffff"/**APISecret**/)
                .workDir(rootFile.getPath());
        ......
    }
}
//IflytekIatWrapper.class

public IflytekIatWrapper(Context context) {
    //开log
    Setting.setShowLog(true);
    //初始化即创建语音配置对象
    if (!TextUtils.isEmpty(BuildConfig.IFLYTEK_APP_ID)) {//TODO 需要注释掉这一行
        //TODO 请先在科大讯飞平台申请,请填写正确appId。否则会闪退
        SpeechUtility.createUtility(context, SpeechConstant.APPID + "=" + "ffffffff"/**APPID**/);
    }
    //初始化识别无UI识别对象
    mSpeechRecognizer = SpeechRecognizer.createRecognizer(context, mIatInitListener);
}
//IflytekTextToImageWrapper.class

public void startChat(String usrInputText, TextToImageCallback callback) throws Exception {
        String appid = "ffffffff";/**APPID**/
        String apiSecret = "fffffffffffffffffffffffffffffff";/**APIKey**/
        String apiKey = "ffffffffffffffffffffffffffffffff";/**APISecret**/
        ......
}

2.14.2.3 使用示例

/**
* @ClassName: AIDialDemo
* @Description: AI表盘功能示例
* @Author: ZhangHuanMing
* @CreateDate: 2023/12/28 11:39
*/
public class AIDialDemo {
    private AIManager mAIManager;
    /**
    * 初始化AIManager
    */
    @Test
    public void init(Context context){
        //Step1. 初始化
        AIManager.init(context, WatchManager.getInstance());
        mAIManager = AIManager.getInstance();
    }

    /**
    * 设置AI表盘风格
    */
    @Test
    public void setStyle(String style){
        mAIManager.getAIDial().setCurrentAIDialStyle(style);
    }
}

2.14.2.4 接入其他AI平台

请参考 IflytekAIDialHandler.classIflytekAIIatHandler.class 调用 BaseAIDialHandlerBaseAIIatHandler 的方法

//BaseAIIatHandler.class
//基础语音识别处理
public abstract class BaseAIIatHandler extends BaseAIHandler {
    /**
    * 需要实现
    */
    //设备断开
    abstract void onDeviceDisconnect();
    // 开始录音
    abstract void onRecordStart();
    // 收到录音数据(已解码成PCM格式)
    abstract void onRecordData(byte[] recordData);
    // 录音结束
    abstract void onRecordStop();
    // 录音取消
    abstract void onRecordCancel();


    /**
    * 需要调用
    */
    //语音识别异常标志位
    void setRecognizerFail(boolean recognizerFail);
    //网络异常标志位
    void setNetworkWrong(boolean networkWrong);
    //识别结果
    void onRecognizerResult(String iatString);
    //识别失败
    void onRecognizerFail();
}
//BaseAIDialHandler.class
//AI表盘处理流程
public abstract class BaseAIDialHandler extends BaseAIChatHandler {
    /**
    * 开始对话(文生图),需要实现
    */
    abstract void onStartAIChat(String userText);
    /**
    * 处理AI图片(文生图),需要调用
    * @param srcImagePath 本地图片路径
    */
    protected void handleAIChatImage(String srcImagePath);
}

2.14.2.5 是否支持AI表盘

如何判断设备是否支持AI表盘功能

WatchConfigure configure  mWatchManager.getWatchConfigure(device);
boolean isSupportAIDial = configure == null || configure.getFunctionOption() != null && configure.getFunctionOption().isSupportAIDial();;

2.14.2.6 本地测试AI表盘

在测试过程中为避免浪费服务器的文生图资源,AI表盘有本地测试功能。在发布正式版时,请关闭。

在打开App时,将assert/aidial 的资源拷贝至/Android/data/com.jieli.healthaide/files/aidial。(从1到10轮询选择一张图片进行AI表盘生成)

关闭本地测试功能:

//HealthConstant.class
public class HealthConstant {
    .....
    //测试AI表盘功能
    public final static boolean TEST_AI_DIAL_FUNCTION = false;
    .....
}

2.14.2.7 AI表盘缩略图

目前AI表盘的缩略图文件大小默认为240*240,设备端可在进入AI表盘界面时推送缩略图大小。如需修改默认缩略图大小,请修改 AIDialWrapper.java 的thumbHeight和thumbWidth属性。

//AIDialWrapper.class
private final OnWatchCallback mWatchCallback = new OnWatchCallback() {
    @Override
    public void onRcspCommand(BluetoothDevice device, CommandBase command) {
        super.onRcspCommand(device, command);
        if (command.getId() == Command.CMD_AI_OPERATE) {
            AIOperateCmd aiOperateCmd = (AIOperateCmd) command;
            AIOperateParam param = aiOperateCmd.getParam();
            if (param.getOp() == AttrAndFunCode.AI_OP_AI_DIAL) {
                switch (param.getFlag()) {
                    case AttrAndFunCode.AI_DIAL_OP_UI://设备通知AppAI表盘界面变化
                        Integer state = param.getAiDialFunUIState();
                        Integer scaleZoomHeight = param.getScaleZoomHeight();
                        Integer scaleZoomWidth = param.getScaleZoomWidth();
                        if (state != null) {
                            onDevNotifyAIDialUIChange(state);
                        }
                        if (scaleZoomHeight != null) {
                            thumbHeight = scaleZoomHeight;
                        } else {
                            thumbHeight = 240;
                        }
                        if (scaleZoomWidth != null) {
                            thumbWidth = scaleZoomWidth;
                        } else {
                            thumbWidth = 240;
                        }
                        break;

                }
                aiOperateCmd.setParam(new AIOperateParam.AIOperateResultParam(0));
                aiOperateCmd.setStatus(StateCode.STATUS_SUCCESS);
                mWatchManager.sendCommandResponse(mWatchManager.getConnectedDevice(), aiOperateCmd, null);
            }
        }
    }
};

2.14.3 代码架构

../../_images/ai_code_architecture.png


2.15 录音功能

开始录音可由设备端发起,也可由App端发起。结束录音可由设备端发起,也可由App端发起。

2.15.1 示例代码

/**
* @ClassName: RecordDemo
* @Description: 录音功能示例
* @Author: ZhangHuanMing
* @CreateDate: 2023/12/28 15:52
*/
public class RecordDemo {
    private RecordOpImpl mRecordOp;
    private OnRecordStateCallback mOnRecordStateCallback = (bluetoothDevice, recordState) -> {
        switch (recordState.getState()) {
            case RecordState.RECORD_STATE_START:   /* 录音状态 -- 开始状态*/
                //录音参数,音频类型 采样率  断句方
                RecordParam recordParam = recordState.getRecordParam();
                recordParam.getSampleRate();//采样率
                recordParam.getVoiceType();//音频编码格式
                recordParam.getVadWay();//断句方
                break;
            case RecordState.RECORD_STATE_WORKING: //录音数据回传
                recordState.getVoiceDataBlock();//录音分包数据

                break;
            case RecordState.RECORD_STATE_IDLE: /* 录音状态 -- 空闲(结束)状态*/
                if (recordState.getReason() >= 0) {//结束原因,大于0则是正常
                    //非AI云服务功能,不需要处理以下状态
                    recordState.isPlayTTS();//是否需要播放TTS
                    recordState.isSyncIatText();//是否需要传输语音识别文本
                    recordState.isSyncNlpText();//是否需要传输语义识别文本
                }
                recordState.getVoiceData();//录音总包数据
                break;
        }
    };
    /**
    * 初始化
    */
    @Test
    public void init(){
        mRecordOp = new RecordOpImpl(WatchManager.getInstance());
        mRecordOp.addOnRecordStateCallback(mOnRecordStateCallback);
    }
    /**
    * 释放
    */
    @Test
    public void release(){
        if (mRecordOp != null) {
            mRecordOp.removeOnRecordStateCallback(mOnRecordStateCallback);
            mRecordOp.release();
            mRecordOp = null;
        }
    }

    /**
    * 获取录音状态
    */
    @Test
    public RecordState getRecordState(){
        return mRecordOp.getRecordState();
    }
    /**
    * App端开始录音
    */
    @Test
    public void startRecord(BluetoothDevice device){
        int voiceType = RecordParam.VOICE_TYPE_OPUS;//录音格式-opus
        int sampleRate = RecordParam.SAMPLE_RATE_8K;//采样率-8k
        int vadWay = RecordParam.VAD_WAY_DEVICE;//断句方-设备
        RecordParam param = new RecordParam(voiceType,sampleRate,vadWay);
        OnOperationCallback<Boolean> callback = new OnOperationCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean result) {
                if (result){
                    //开始成功
                }
            }
            @Override
            public void onFailed(BaseError error) {
            }
        };
        mRecordOp.startRecord(device,param,callback);
    }
    /**
    * App端结束录音
    */
    @Test
    public void stopRecord(BluetoothDevice device){
        int reason = REASON_NORMAL;//0:正常结束,1取消
        boolean isSyncIatText= true;//是否同步语音识别文本(会同步更新设置)
        boolean isSyncNlpText= true;//是否同步AI语义识别文本(会同步更新设置)
        boolean isPlayTTS= true;//是否播放AI语义识别的TTS(会同步更新设置)
        OnOperationCallback<Boolean> callback = new OnOperationCallback<Boolean>() {
            @Override
            public void onSuccess(Boolean result) {
                if (result){
                    //停止成功
                }
            }
            @Override
            public void onFailed(BaseError error) {
            }
        };
        mRecordOp.stopRecord(device,reason,isSyncIatText,isSyncNlpText,isPlayTTS,callback);
    }
}

2.16 大数据传输功能

为了方便不操作文件,只是大数据交互而设置的大数据传输功能。有接收设备端大数据、发送大数据和读取大数据等子功能。

2.16.1 接收设备端的大数据

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager mWatchManager = WatchManager.getInstance();
final OnWatchCallback onWatchCallback = new OnWatchCallback() {
    /**
     * 回调接收到的大数据
     * @param device 设备对象
     * @param type   数据类型
     * @param data   大数据
     */
    @Override
    public void onReceiveBigData(BluetoothDevice device, int type, byte[] data) {
        super.onReceiveBigData(device, type, data);
    }

    /**
     * 接收大数据发生异常
     *
     * @param device 设备对象
     * @param error  错误事件
     */
    @Override
    public void onBigDataError(BluetoothDevice device, BaseError error) {
        super.onBigDataError(device, error);
    }
};
//Step1. 注册事件监听器
mWatchManager.registerOnWatchCallback(onWatchCallback);

//StepN. 不需要监听大数据时,记得移除监听器
mWatchManager.unregisterOnWatchCallback(onWatchCallback);

2.16.2 发送大数据

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager mWatchManager = WatchManager.getInstance();
//Step1. 构建发送参数
int type = RcspConstant.TYPE_RAW_DATA; //裸数据
int version = 0; //数据协议
int sendLimit = 4 * 1024; //每次发送数据的最大值
int recvLimit = 4 * 1024; //每次接收数据的最大值
byte[] data = new byte[1024]; //发送的大数据
SendParams param = new SendParams(type, version, sendLimit, recvLimit, data);
//Step2. 调用发送大数据接口,并等待回调。
//注意: 大数据接口不能多次调用,需要等待回调结束,才能继续调用
mWatchManager.sendLargeData(param, new OnDataEventCallback() {
    /**
     * 回调操作开始
     * @param way 传输方式
     *            <p>{@link RcspConstant#WAY_SEND_DATA} -- 发送数据
     *            {@link RcspConstant#WAY_READ_DATA} -- 读取数据</p>
     */
    @Override
    public void onBegin(int way) {

    }

    /**
     * 回调进度
     * @param progress 进度
     */
    @Override
    public void onProgress(float progress) {

    }

    /**
     * 回调操作结束
     * @param type 数据类型
     * @param data 数据
     */
    @Override
    public void onStop(int type, byte[] data) {

    }

    /**
     * 回调发生异常
     * @param error 错误信息
     */
    @Override
    public void onError(BaseError error) {

    }
});

2.17 设备配置信息

设备配置信息,是为了动态适配设备功能。

2.17.1 获取缓存的配置信息

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//Step1. 获取缓存配置信息
WatchConfigure configure = watchManager.getWatchConfigure(watchManager.getConnectedDevice());
WatchConfigure.NecessaryFunc necessaryFunc = configure.getNecessaryFunc(); //必要功能配置
WatchConfigure.SystemSetup systemSetup = configure.getSystemSetup();  //系统配置
WatchConfigure.FunctionOption functionOption = configure.getFunctionOption(); //功能配置
SportHealthConfigure sportHealthConfigure = configure.getSportHealthConfigure(); //运动健康配置

2.17.2 请求设备配置信息

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//Step1. 调用请求设备信息接口,并等待结果返回
watchManager.requestDeviceConfigure(new OnWatchOpCallback<WatchConfigure>() {
    @Override
    public void onSuccess(WatchConfigure result) {
        //回调成功
        WatchConfigure.NecessaryFunc necessaryFunc = result.getNecessaryFunc(); //必要功能配置
        WatchConfigure.SystemSetup systemSetup = result.getSystemSetup();  //系统配置
        WatchConfigure.FunctionOption functionOption = result.getFunctionOption(); //功能配置
        SportHealthConfigure sportHealthConfigure = result.getSportHealthConfigure(); //运动健康配置
    }

    @Override
    public void onFailed(BaseError error) {
        //回调失败信息
    }
});

2.17.2.1 NecessaryFunc

public static class NecessaryFunc {

     /**
      * 是否支持OTA功能
      */
     private boolean isSupportOTA;
     /**
      * 是否支持资源更新功能
      */
     private boolean isSupportResourceUpdate;
     /**
      * 是否支持表盘操作
      */
     private boolean isSupportDialOp;
     /**
      * 是否支持表盘切换
      */
     private boolean isSupportDialSwitch;
     /**
      * 是否支持表盘预览
      */
     private boolean isSupportDialBrowse;
}

2.17.2.2 SystemSetup

public static class SystemSetup {

    /**
     * 是否支持屏幕设置
     */
    private boolean isSupportScreenSetting;
    /**
     * 是否支持震动强度设置
     */
    private boolean isSupportVibrationSetting;
    /**
     * 是否支持勿扰模式
     */
    private boolean isSupportDNDMode;
    /**
     * 是否支持锻炼设置
     */
    private boolean isSupportExerciseSetting;
    /**
     * 是否支持蓝牙断开提醒
     */
    private boolean isSupportBtDisconnectSetting;

}

2.17.2.3 FunctionOption

public static class FunctionOption {

    /**
     * 是否支持常用联系人
     */
    private boolean isSupportContacts;
    /**
     * 是否文件浏览
     */
    private boolean isSupportFileBrowse;
    /**
     * 是否支持音乐文件传输
     */
    private boolean isSupportMusicTransfer;
    /**
     * 是否支持闹钟设置
     */
    private boolean isSupportAlarmSetting;
    /**
     * 是否支持信息同步
     */
    private boolean isSupportMessageSync;
    /**
     * 是否支持天气同步
     */
    private boolean isSupportWeatherSync;
    /**
     * 是否支持查找设备/手机
     */
    private boolean isSupportSearchDevice;
    /**
     * 是否支持AI云功能
     */
    private boolean isSupportAICloud;
    /**
     * 是否支持AI表盘
     */
    private boolean isSupportAIDial;
    /**
     * 是否支持4G网络模块
     */
    private boolean isSupportNetworkModule;
    /**
     * 是否支持设备表盘拓展信息
     */
    private boolean isSupportDialExpandInfo;

}

2.17.2.4 SportHealthConfigure

public class SportHealthConfigure {
 public static final int CONFIGURE_TYPE_GSENSOR = 0;
 public static final int CONFIGURE_TYPE_RATE = 1;
 public static final int CONFIGURE_TYPE_BLOOD_OXYGEN = 2;
 public static final int CONFIGURE_TYPE_ALTITUDE = 3;
 public static final int CONFIGURE_TYPE_GPS = 4;

 public static final int CONFIGURE_TYPE_SPORT_MODE = 0xfe;
 public static final int CONFIGURE_TYPE_COMBINE_FUNC = 0xff;

 private GSensorFunc gSensorFunc;             //计步功能
 private RateFunc rateFunc;                   //心率功能
 private BloodOxygenFunc bloodOxygenFunc;     //血氧功能
 private AltitudeFunc altitudeFunc;           //海拔功能
 private GPSFunc gpsFunc;                     //GPS功能
 private SportModeFunc sportModeFunc;         //运动模式
 private CombineFunc combineFunc;             //综合功能
}

2.18 网络模块操作

网络模块信息、网络模块升级等操作

2.18.1 是否支持网络模块功能

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//Step1. 获取缓存的手表配置信息
WatchConfigure configure = watchManager.getWatchConfigure(watchManager.getConnectedDevice());
if (null == configure) {
    //可以尝试向设备请求一次
    watchManager.requestDeviceConfigure(new OnWatchOpCallback<WatchConfigure>() {
        @Override
        public void onSuccess(WatchConfigure result) {
            //获取手表配置信息成功
        }

        @Override
        public void onFailed(BaseError error) {
            //获取手表配置信息失败
            //error -- 错误信息
        }
    });
    return;
}
configure.getFunctionOption().isSupportNetworkModule(); //是否支持网络模块

2.18.2 查询网络模块基础信息

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//Step1. 初始化网络模块升级操作对象
NetworkOpImpl networkOp = NetworkOpImpl.instance(watchManager);
//Step2. 注册网络事件监听器
final OnNetworkListener listener = new OnNetworkListener() {
    @Override
    public void onNetworkInfo(BluetoothDevice device, NetworkInfo info) {
        //回调网络模块基础信息
//        info.getVid();         //4G模块厂商ID
//        info.isMandatoryOTA(); //是否需要强制升级, 升级失败后触发
//        info.getVersion();     //4G模块版本号
    }

    @Override
    public void onNetworkOTAState(BluetoothDevice device, NetworkOTAState state) {
        //回调网络模块升级状态
    }
};
networkOp.addOnNetworkListener(listener);
//Step3. 执行查询网络信息接口,并等待结果回调
networkOp.queryNetworkInfo(watchManager.getConnectedDevice(), new OnOperationCallback<Boolean>() {
    @Override
    public void onSuccess(Boolean result) {
        //回调操作成功结果
    }

    @Override
    public void onFailed(BaseError error) {
        //回调操作失败
        //error -- 错误描述
    }
});
//StepN. 不需要监听时移除监听器
//    networkOp.removeOnNetworkListener(listener);

获取缓存的网络模块信息

//WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
final DeviceInfo deviceInfo = watchManager.getDeviceInfo();
if (null == deviceInfo) return; //设备未初始化完成
//获取网络模块信息
final NetworkInfo networkInfo = deviceInfo.getNetworkInfo();
if(null == networkInfo){//设备不支持网络模块功能
    //可能还没更新,也可以按照上面接口查询下
    return;
}

2.18.3 开始网络模块OTA

//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//Step1. 初始化网络模块升级操作对象
NetworkOpImpl networkOp = NetworkOpImpl.instance(watchManager);
if (networkOp.isNetworkOTA()) {
    //正在OTA
    return;
}
//执行开始网络OTA
//filePath --- 4G模块升级文件路径
networkOp.startNetworkOTA(watchManager.getConnectedDevice(), new OTAParam(filePath), new OnNetworkOTACallback() {
    @Override
    public void onStart() {
        //回调OTA开始
    }

    @Override
    public void onProgress(int progress) {
        //回调OTA进度
    }

    @Override
    public void onCancel() {
        //回调OTA被取消
    }

    @Override
    public void onStop() {
        //回调OTA成功
    }

    @Override
    public void onError(int code, String message) {
        //回调OTA异常
        //code --- 错误码
        //message --- 错误描述
    }
});

Important

  1. 必须设备支持4G模块才能进行功能操作

  2. 升级过程中不能执行其他命令操作

2.18.4 网络模块OTA异常回调

//Step1. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
 WatchManager watchManager = WatchManager.getInstance();
 //Step2. 注册手表事件监听器
 final OnWatchCallback watchCallback = new OnWatchCallback() {
     @Override
     public void onNetworkModuleException(BluetoothDevice device, NetworkInfo info) {
         //回调升级模块升级异常,需要强制升级
         //info --- 网络模块基本信息
     }
 };
 watchManager.registerOnWatchCallback(watchCallback);

 //StepN. 不需要监听时注销监听器
 //        watchManager.unregisterOnWatchCallback(watchCallback);

2.18.5 网络模块OTA接口流程图

网络模块OTA接口流程图

2.18.6 网络模块升级包差分升级策略

网络模块差分升级策略

sdk_map.json

  {
      "cursdk": {
        "version": "gx318l_h6_v0022_p7",
        "vendor": "归芯科技",
        "chip": "BR28"
    },
    "map": [
        {
            "version": "gx318l_h6_v0022_p5",
            "pakage": "gx318l_h6_v0022_p5_to_gx318l_h6_v0022_p7.diff"
        },
  {
            "version": "gx318l_h6_v0022_p6",
            "pakage": "gx318l_h6_v0022_p6_to_gx318l_h6_v0022_p7.diff"
        }
    ]
}

字段说明

参数

含义

cursdk

包含当前包信息,例如:版本号、供应商信息等

map

映射规则,里面已数组形式提供

2.19 表盘拓展参数

表盘拓展参数包括形状、边角半径、背景颜色(ARGB)等

2.19.1 表盘拓展参数

public class DialExpandInfo extends SettingFunction {
     /**
      * 圆形
      */
     public static final int SHAPE_CIRCULAR = 1;
     /**
      * 矩形
      */
     public static final int SHAPE_RECTANGLE = 2;
     /**
      * 圆角矩形
      */
     public static final int SHAPE_ROUNDED_RECTANGLE = 3;

     /**
      * 形状
      */
     private int shape;
     /**
      * 边角半径
      */
     private int radius;
     /**
     * 背景颜色(ARGB)
     */
     private int color;
 }

2.19.2 获取表盘拓展参数

final WatchManager watchManager = WatchManager.getInstance();
final WatchConfigure watchConfigure = watchManager.getWatchConfigure(watchManager.getConnectedDevice());
if (null == watchConfigure) return null;
DialExpandInfo dialExpandInfo = watchConfigure.getDialExpandInfo();