2.功能模块说明

2.1 基础功能

备注

1.下列中的数据模型可以通过SDK查看

2.本文档只展示部分重要数据模型

  • 获取系统/状态信息模型
    • JLModel_Device() 系统信息变更/状态获取,都是通过该模型获取

  • 闹钟相关模型
    • JLModel_Ring() 闹钟铃声模型

    • JLModel_AlarmSetting() 闹钟设置模型

    • RTC_RingInfo() 闹钟铃声详情模型,部分设备不支持,需要考虑过固件实际版本

    • JLModel_RTC() 闹钟RTC模型

  • 设备文件模型
    • JLModel_File() 文件模型,此功能用于文件浏览/播放等一系列的文件操作相关功能

  • 设备FM模型
    • JLModel_FM() FM模型

  • TWS耳机参数模型
    • JLModel_Headset() TWS耳机参数模型

  • 蓝牙相关参数模型
    • JLModel_BT() 蓝牙相关参数模型

  • 均衡调试器模型
    • JLModel_EQ() 均衡调试器模型

  • 设备降噪模型
    • JLModel_ANC() 设备降噪模型

2.1.1 请求设备信息

获取设备的基础信息,包括设备电量、设备各个模块状态、所处模式等。

示例代码:

 /*--- 获取设备信息 ---*/
[self.mBleEntityM.mCmdManager cmdTargetFeatureResult:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
    JL_CMDStatus st = status;
    if (st == JL_CMDStatusSuccess) {
        JLModel_Device *device = [elf.mBleEntityM.mCmdManager outputDeviceModel];
        NSLog(@"设备信息:%@", device);
    }
}];

2.1.2 查询设备系统信息

获取设备系统的信息内容,为后续操作提供基础信息。

示例代码:

/*--- 查询设备系统信息 ---*/
[wSelf.mBleEntityM.mCmdManager cmdGetSystemInfo:JL_FunctionCodeCOMMON
                                         Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data)
 {
    JL_CMDStatus st = status;
    if (st == JL_CMDStatusSuccess) {
        JLModel_SystemInfo *systemInfo = [elf.mBleEntityM.mCmdManager outputSystemInfoModel];
        NSLog(@"设备系统信息:%@", systemInfo);
    }
}];

2.1.3 监听设备状态信息

对于设备的状态信息变化,可通过KVO监听设备的状态信息变化,并获取设备的状态信息。

示例代码:

/*--- 监听设备状态信息 ---*/
-(void)addNote{
    [JLModel_Device observeModelProperty:@"currentFunc" Action:@selector(noteCurrentFunction:) Own:self];
}

-(void)noteCurrentFunction:(NSNotification*)note{
    BOOL isOK = [JL_RunSDK isCurrentDeviceCmd:note];
    if (isOK == NO) return;

    JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
    JLModel_Device *devel = [entity.mCmdManager outputDeviceModel];
    if (devel.currentFunc == JL_FunctionCodeBT) {

    }else{
        NSLog(@"关闭手机所有音乐.");

    }

}

2.1.4 切换设备模式

示例代码:

 /*--- 切换设备模式 ---*/
-(void)changeDeviceMode:(JL_FunctionCode)code{
 JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
 [entity.mCmdManager cmdFunction:JL_FunctionCodeCOMMON Command:code Extend:0x00 Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {

         if (code == JL_FunctionCodeBT) {

             LocalMusicVC *vc = [[LocalMusicVC alloc] init];
             vc.modalPresentationStyle = UIModalPresentationFullScreen;
             [self presentViewController:vc animated:YES completion:nil];
         }
         if (code == JL_FunctionCodeFM) {
             [entity.mCmdManager cmdGetSystemInfo:JL_FunctionCodeFM Result:nil];
         }
         if (code == JL_FunctionCodeFMTX) {
             [entity.mCmdManager cmdGetSystemInfo:JL_FunctionCodeFMTX Result:nil];
         }
     }];
 }

2.2 音量控制功能

2.2.1 设备音量设置

2.2.2 示例代码

 JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
[entity.mCmdManager.mSystemVolume cmdSetSystemVolume:10 Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
    JL_CMDStatus state = status;
    if(state == JL_CMDStatusFail){
         //[DFUITools showText:kJL_TXT("settings_failed") onView:self delay:1.0];
    }
 }];

2.3 设备音乐控制

2.3.1 功能描述

当设备处于播放SD卡或U盘/TF卡时,手机端进行控制时用到

2.3.2 示例代码

 JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
[entity.mCmdManager.mMusicControlManager cmdFastPlay:JL_FCmdMusicFastBack
                                         Second:(uint16_t)fabsf((pg * f_tott - f_curt))
                                         Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data)  {
     //progress_sec = (int)(f_tott*pg);
     //NSLog(@"---------------> To Progress Second: %d",progress_sec);

     if (sv_tott > 60*60) {
         [JL_Tools delay:0.6 Task:^{
             NSLog(@"----> delay get music progess 0");
             [self getDeviceMusicProgress];
         }];
     }else{
         [self getDeviceMusicProgress];
     }
}];

2.3.3 相关接口

typedef NS_ENUM(UInt8, JL_FCmdMusic) {
    JL_FCmdMusicPP                  = 0x01, //PP按钮
    JL_FCmdMusicPREV                = 0x02, //上一曲
    JL_FCmdMusicNEXT                = 0x03, //下一曲
    JL_FCmdMusicMODE                = 0x04, //切换播放模式
    JL_FCmdMusicEQ                  = 0x05, //EQ
    JL_FCmdMusicFastBack            = 0x06, //快退
    JL_FCmdMusicFastPlay            = 0x07, //快进
};


NS_ASSUME_NONNULL_BEGIN

@interface JL_MusicControlManager : JL_FunctionBaseManager

#pragma mark ---> 设置播放模式
/**
 @param mode 模式
 0x01:全部循环; 0x02:设备循环; 0x03:单曲循环; 0x04:随机播放; 0x05:文件夹循环
 */
-(void)cmdSetSystemPlayMode:(JL_MusicMode)mode;

#pragma mark ---> 快进快退
/**
 @param cmd 快进或者快退枚举
 @param sec 时间
 @param result 返回结果
 */
-(void)cmdFastPlay:(JL_FCmdMusic)cmd
            Second:(uint16_t)sec
            Result:(JL_CMD_RESPOND __nullable)result;

@end

为推行更方便使用的API,当前类将会被废弃,使用新的类: JLDevPlayerCtrl 来接替; 区别在于当前类是存在于 JL_ManagerM 类初始化生成而, JLDevPlayerCtrl 则是基于实例类,在开发者有需要时即时生成。

/// 播放器回调
@protocol JLDevPlayerCtrlDelegate<NSObject>

/// 播放状态
/// - Parameters:
///   - ctrl: 播放器
///   - status: 播放模式
-(void)jlDevPlayerCtrl:(JLDevPlayerCtrl *)ctrl playMode:(uint8_t)playMode;

/// 播放状态回调
/// - Parameters:
///   - ctrl: 播放器
///   - status: 播放状态
///   - card: 当前播放的设备
///   - time: 当前播放的时间
///   - total: 总时长
-(void)jlDevPlayerCtrl:(JLDevPlayerCtrl *)ctrl playStatus:(uint8_t)status currentCard:(uint8_t)card currentTime:(uint32_t)time tolalTime:(uint32_t)total;

/// 播放文件回调
/// - Parameters:
///   - ctrl: 播放器
///   - name: 文件名
///   - clus: 文件簇号
-(void)jlDevPlayerCtrl:(JLDevPlayerCtrl *)ctrl fileName:(NSString *)name currentClus:(uint32_t)clus;

@end


/// 设备播放器管理类
@interface JLDevPlayerCtrl : JLCmdBasic

/// 播放状态
@property(nonatomic,assign)uint8_t playStatus;

/// 当前播放的设备
/// 0x00 : USB
/// 0x01 : SD_0
/// 0x02 : SD_1
/// 0x03 : FLASH
/// 0x04 : LineIn
/// 0x05 : FLASH2
@property(nonatomic,assign)uint8_t currentCard;

/// 当前播放时间
@property(nonatomic,assign)uint32_t currentTime;

/// 歌曲总时长
@property(nonatomic,assign)uint32_t tolalTime;

/// 当前播放文件名
@property(nonatomic,strong)NSString *fileName;

/// 当前播放文件的簇号
@property(nonatomic,assign)uint32_t currentClus;

/// 当前播放模式
///  0x01:全部循环;
///  0x02:设备循环;
///  0x03:单曲循环;
///  0x04:随机播放;
///  0x05:文件夹循环
@property (nonatomic,assign)uint8_t playMode;

/// 代理
@property(nonatomic,weak)id<JLDevPlayerCtrlDelegate> delegate;

/// 操控设备播放器设置
/// - Parameters:
///   - cmd: 操作命令
///    0x01:PP按钮
///    0x02:上一曲
///    0x03:下一曲
///    0x04:切换播放模式(当前播放模式:不可指定,设备播放模式自增)
///    0x05:EQ
///    0x06:快退
///    0x07:快进
///   - sec: 时间(当快进/快退时需要,其他功能时值为0)
///   - manager: 设备对象
///   - result: 返回结果
-(void)cmdPlayerCtrl:(uint8_t)cmd
            Second:(uint16_t)sec
            Manager:(JL_ManagerM *)manager
            Result:(JL_CMD_RESPOND __nullable)result;

2.4 均衡器调试功能

2.4.1 功能描述

均衡器调试功能,对设备的EQ进行设置

2.4.2 示例代码

2.4.2.1 系统EQ调试

 /// EQ参数值
 /// (只适用于EQ Mode == CUSTOM情况)
 @property (strong,  nonatomic) NSArray *eqArray;

 /// 自定义 EQ数组
 @property (strong,  nonatomic) NSArray *eqCustomArray;

 /// EQ频率
 @property (strong,  nonatomic) NSArray *eqFrequencyArray;

 /// EQ的预设值数组
 @property (strong,nonatomic) NSArray <JLModel_EQ*> *eqDefaultArray;
/**
设置系统EQ
@param eqMode EQ模式
@param params EQ参数(10个参数,仅适用于JL_EQModeCUSTOM情况)
*/
+(void)cmdSetSystemEQ:(JL_EQMode)eqMode Params:(NSArray* __nullable)params;

/// 查询系统EQ内容
-(void)cmdGetSystemEQ:(JLSystemEQResult)result;

///  当前EQ模式
 @property (assign,nonatomic) JL_EQMode eqMode;

 /// EQ段数类型
 @property (assign,nonatomic) JL_EQType eqType;

-(void)test{
  NSArray *eqArray = [@(2),@(3),@(2),@(2),@(8)];
    [bleSDK.mBleEntityM.mCmdManager.mSystemEQ cmdSetSystemEQ:JL_EQModeCUSTOM Params:eqArray];
}

2.4.2.2 设置混响/限幅

设置混响以及限幅值时需要先判断设备是否支持。

JLModel_Device *model = [bleSDK.mBleEntityM.mCmdManager outputDeviceModel];
int type = -1; //0:支持混响和限幅器 1:只支持混响 2:只支持限幅器
if(model.reverberationTypes.count==2  && [model.reverberationTypes containsObject:@(JL_ReverberationAndDynamicType)] //支持混响和限幅器
   && [model.reverberationTypes containsObject:@(1)]){
    type = 0;
}
if(model.reverberationTypes.count==1  && [model.reverberationTypes containsObject:@(JL_OnlyReverberationType)]) {//只支持混响
    type = 1;
}
if(model.reverberationTypes.count==1  && [model.reverberationTypes containsObject:@(JL_OnlyDynamicLimiterType)]) {//只支持限幅器
    type = 2;
}

JL_BinChargeManager 使用的接口如下:

/// 设置混响值
/// @param depthValue 深度 0-100
/// @param intensityValue 强度 0-100
/// @param dynamicLimiterValue 动态限幅值 -60 - 0
/// @param reverOn 是否开启
/// @param type 混响类型
-(void)cmdSetReverberation:(int)depthValue
            IntensityValue:(int)intensityValue
    DynamicLimiterValue:(int)dynamicLimiterValue
        SwtichReverState:(int)reverOn
                FunType:(JL_ReverberationFunType)type;

2.5 时钟功能

2.5.1 功能描述

同步闹钟设备、管理闹钟包括:读取、修改、删除、

2.5.2 使用demo

2.5.2.1 同步时间

//在JLManagerM -> JL_SystemTime -> cmdSetSystemTime
 /*--- 同步时间戳 ----*/
NSLog(@"--->(2) SET Device time.");
NSDate *date = [NSDate new];
JL_SystemTime *systemTime = self.mBleEntityM.mCmdManager.mSystemTime;
[systemTime cmdSetSystemTime:date];

2.5.2.2 读取闹钟

JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
[entity.mCmdManager cmdGetSystemInfo:JL_FunctionCodeRTC SelectionBit:0xF2 Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
    //TODO: do something...
    //拿到的itemArray去做闹钟列表的数据展示
}];

2.5.2.3 修改闹钟

JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];

//新建一个闹钟
JLModel_RTC *rtcmodel = [JLModel_RTC new];
rtcmodel.rtcName = kJL_TXT("闹钟");
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = @"yyyy:MM:DD:HH:mm:ss";
NSString *nowStr = [formatter stringFromDate:[NSDate new]];
NSArray *timeArr = [nowStr componentsSeparatedByString:@":"];
rtcmodel.rtcYear = [timeArr[0] intValue];
rtcmodel.rtcMonth = [timeArr[1] intValue];
rtcmodel.rtcDay = [timeArr[2] intValue];
rtcmodel.rtcHour = [timeArr[3] intValue];
rtcmodel.rtcMin = [timeArr[4] intValue];
rtcmodel.rtcSec = [timeArr[5] intValue];
rtcmodel.rtcMode = 0x00;//响铃类型
rtcmodel.rtcEnable = YES;
rtcmodel.rtcIndex = 0;//当前闹钟编号

//一般而言可以通过上述的读取闹钟方法,获取到设备当前所有闹钟,然后根据对应的rtcIndex进行设置
//JLModel_RTC *rtcmodel = // cmdGetSystemInfo: SelectionBit: Result:

JLModel_Device *device = [entity.mCmdManager outputDeviceModel];//获取设备属性详情
if (device.rtcDfRings.count>0) {//当设备支持自定义闹铃时
    JLModel_Ring *ring = device.rtcDfRings[0];//设备所带默认闹铃声音
    rtcmodel.ringInfo = [RTC_RingInfo new];
    rtcmodel.ringInfo.type = 0;//闹铃类型:默认或自定义
    rtcmodel.ringInfo.dev = 0;//铃声存放位置,详情可参考
    rtcmodel.ringInfo.clust = 0;//自定义闹铃文件簇号
    rtcmodel.ringInfo.data = [ring.name dataUsingEncoding:NSUTF8StringEncoding];//铃声名字
    rtcmodel.ringInfo.len = (uint8_t)self.rtcmodel.ringInfo.data.length;//铃声的名字文件长度
        }
        //当前闹铃设置包含新增或修改一个/多个的闹铃
   [entity.mCmdManager.mAlarmClockManager cmdRtcSetArray:@[rtcmodel] Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
        //TODO: 返回当前设备的闹铃个数,to do something...
   }];

2.5.2.4 删除闹钟

JLModel_RTC *rtcmodel = //.... 通过获取设备闹钟得到某个闹钟
//JLModel_RTC
int rtcIndex = rtcmodel.rtcIndex;//闹钟序号
  JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
    [entity.mCmdManager.mAlarmClockManager cmdRtcDeleteIndexArray:@[@(rtcIndex)] Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data){
        JL_CMDStatus state = (UInt8)[array[0] intValue];
        if(state == JL_CMDStatusSuccess){
            //TODO: do something...
        }
        if(state == JL_CMDStatusFail){
            //ERR: delete failed
        }
    }];

2.5.2.5 获取默认铃声选择列表

JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
JLModel_Device *model = [entity.mCmdManager outputDeviceModel];
NSArray *defaultRings =  model.rtcDfRings;

2.5.2.6 闹钟铃声试听

JLModel_RTC *rtcmodel = [JLModel_RTC new];//构建或者从设备获取
JLModel_Ring *ring = //闹铃对象,通过获取默认或新建;
rtcModel.ringInfo.type = 0;//类型
/*
typedef NS_ENUM(UInt8, JL_CardType) {
    JL_CardTypeUSB                  = 0, //USB
    JL_CardTypeSD_0                 = 1, //SD_0
    JL_CardTypeSD_1                 = 2, //SD_1
    JL_CardTypeFLASH                = 3, //FLASH
    JL_CardTypeLineIn               = 4, //LineIn
    JL_CardTypeFLASH2               = 5, //FLASH2
};
*/
rtcModel.ringInfo.dev = JL_CardTypeUSB;//响铃内容来自哪儿
rtcModel.ringInfo.clust = ring.index;//文件簇号
rtcModel.ringInfo.data = [ring.name dataUsingEncoding:NSUTF8StringEncoding];//文件名
rtcModel.ringInfo.len = rtcModel.ringInfo.data.length;//文件长度

JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
[entity.mCmdManager.mAlarmClockManager cmdRtcAudition:rtcModel Option:YES result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
}];

2.5.2.7 停止铃声试听

JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
[entity.mCmdManager.mAlarmClockManager cmdRtcStopResult:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
}];

2.5.2.8 获取闹铃模式设置

@interface JLModel_AlarmSetting : NSObject
@property(assign,nonatomic)uint8_t index;       //Index of the alarm clock
@property(assign,nonatomic)uint8_t isCount;     //Whether the [alarm times] can be set
@property(assign,nonatomic)uint8_t count;       //The alarm number
@property(assign,nonatomic)uint8_t isInterval;  //Whether the [time interval] can be set
@property(assign,nonatomic)uint8_t interval;    //The time interval
@property(assign,nonatomic)uint8_t isTime;      //Whether the [time length] can be set
@property(assign,nonatomic)uint8_t time;        //Length of time
-(NSData*)dataModel;
@end

 -(void)getAlarmModelSetting{
    JL_RunSDK *bleSDK = [JL_RunSDK sharedMe];
    JL_ManagerM *mCmdManager = bleSDK.mBleEntityM.mCmdManager;
    JLModel_Device *model = [mCmdManager outputDeviceModel];

    // 是否支持闹钟设置
    if (model.rtcAlarmType == YES) {
      //itemArray是指从读取闹钟里获取到闹钟列表
      JLModel_RTC *rtcModel = itemArray[0];
      uint8_t bit = 0x01;
      uint8_t bit_index = bit << rtcModel.rtcIndex;
/**
 @param operate 0x00:读取 0x01:设置
 @param index  掩码
//Bit0:闹钟0
//Bit1:闹钟1
//Bit1:闹钟2
//Bit1:闹钟3
//Bit1:闹钟4
如下所示:
0b0000 0001:设置闹钟0
0b0000 0011:设置闹钟0和闹钟1(其他同理)
当如要设置0 3 4闹钟,则index= 0x19
如要设置1 2 闹钟,则index = 0x06
 @param setting 设置选项,读取时无需传入
 @param result 回复
 */
[mCmdManager.mAlarmClockManager cmdRtcOperate:JL_FlashOperateFlagRead Index:bit_index Setting:nil
                            Result:^(NSArray<JLModel_AlarmSetting *> * _Nullable array, uint8_t flag) {
                }];
    }
}

2.5.2.9 设置闹铃模式

@interface JLModel_RTC : NSObject
@property (assign,nonatomic) uint16_t       rtcYear; //年
@property (assign,nonatomic) uint8_t        rtcMonth;//月
@property (assign,nonatomic) uint8_t        rtcDay; //日
@property (assign,nonatomic) uint8_t        rtcHour;//时
@property (assign,nonatomic) uint8_t        rtcMin; //分
@property (assign,nonatomic) uint8_t        rtcSec; //秒
@property (assign,nonatomic) BOOL           rtcEnable; //开启关闭
//模式:
/*
情况1:
mode=0:只响一次
情况2:
Bit0:每天
Bit1:星期一
Bit2:星期二
Bit3:星期三
Bit4:星期四
Bit5:星期五
Bit6:星期六
Bit7:星期天
*/
@property (assign,nonatomic) uint8_t        rtcMode;
@property (assign,nonatomic) uint8_t        rtcIndex; //序号
@property (copy  ,nonatomic) NSString       *rtcName; //名称
@property (strong,nonatomic) RTC_RingInfo   *ringInfo;//详情
@property (strong,nonatomic) NSData         *RingData;//响铃数据
@end

@interface RTC_RingInfo : NSObject
//类型:0 :默认  1:外置
@property (assign,nonatomic) uint8_t type;
//存放位置
/*
typedef NS_ENUM(UInt8, JL_CardType) {
    JL_CardTypeUSB                  = 0, //USB
    JL_CardTypeSD_0                 = 1, //SD_0
    JL_CardTypeSD_1                 = 2, //SD_1
    JL_CardTypeFLASH                = 3, //FLASH
    JL_CardTypeLineIn               = 4, //LineIn
    JL_CardTypeFLASH2               = 5, //FLASH2
};
*/
@property (assign,nonatomic) uint8_t  dev;
//文件簇号
@property (assign,nonatomic) uint32_t clust;
//铃声名字长度
@property (assign,nonatomic) uint8_t len;
//铃声名字内容
@property (strong,nonatomic) NSData *data;
@end

//响铃周期模式设置
-(uint8_t)rtcmode{
NSArray *array = @[@1,@3,@5];
uint8_t mode = 0x00;
    if (array.count > 0) {
        for (NSString *num in array) {
            uint8_t tmp = 0x01;
            int n = [num intValue];
            uint8_t tmp_n = tmp<<n;
            mode = mode|tmp_n;
        }
    }else{
        mode = 0x01;
    }
    return mode;
}
//新建闹钟
JLModel_RTC *rtcmodel = [JLModel_RTC new];
//响铃周期模式设置
rtcmodel.rtcMode = [self rtcmode];
//接口声明
#pragma mark ---> 闹铃设置
/**
 @param operate 0x00:读取 0x01:设置
 @param index 掩码
//Bit0:闹钟0
//Bit1:闹钟1
//Bit1:闹钟2
//Bit1:闹钟3
//Bit1:闹钟4
如下所示:
0b0000 0001:设置闹钟0
0b0000 0011:设置闹钟0和闹钟1(其他同理)
当如要设置0 3 4闹钟,则index= 0x19
如要设置1 2 闹钟,则index = 0x06
 @param setting 设置选项,读取时无需传入
 @param result 回复
 */
-(void)cmdRtcOperate:(JL_RtcOperate)operate
               Index:(uint8_t)index
               Setting:(JLModel_AlarmSetting* __nullable)setting
               Result:(JL_RTC_ALARM_BK __nullable)result;

-(void)setRingModel{
    JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];

    //可以新建
    JLModel_AlarmSetting *setting = [JLModel_AlarmSetting new];
    setting.index = index;
    setting.isCount = 1;
    setting.count = mCount;
    setting.isInterval = 1;
    setting.interval = mInterval;
    setting.isTime = 1;
    setting.time = mTime;

    //也可以通过上面的方式获取闹铃模式后,使用该模式进行设置

    NSLog(@"闹钟Index ---> %d count:%d interval:%d time:%d",index,mCount,mInterval,mTime);
    [entity.mCmdManager.mAlarmClockManager cmdRtcOperate:JL_FlashOperateFlagWrite Index:index Setting:setting
                               Result:^(NSArray<JLModel_AlarmSetting *> * _Nullable array, uint8_t flag)
    {
        if (flag == 0) NSLog(@"设置闹钟成功");
    }];
}

2.5.2.10 闹钟正在响或则闹钟停止响

extern NSString *kJL_RTC_RINGING;       //闹钟正在响
extern NSString *kJL_RTC_RINGSTOP;      //闹钟停止响

2.5.2.11 停止闹钟响声回调

/**
停止闹钟响声
@param result 回复
*/
+(void)cmdRtcStopResult:(JL_CMD_BK)result;

2.6 外接设备控制功能

2.6.1 功能描述

切换到Linein模式,以及LineIn模式下的操作

2.6.2 示例代码

[JL_Manager cmdFunction:JL_FunctionCodeCOMMON Command:JL_FunctionCodeLINEIN Extend:0x00 Result:nil]; //切换到Linein模式
[JL_Manager cmdGetSystemInfo:JL_FunctionCodeLINEIN Result:nil]; //获取Linein模式下的信息
//设置Linein下的播放和暂停
[JL_Manager cmdFunction:JL_FunctionCodeLINEIN Command:JL_FCmdLineInPP Extend:0 Result:nil];

2.6.3 注意事项

获取LineIn的状态,通过JL_ManagerMoutputDeviceModel方法,其中的属性

/— LineIn INFO —/

@property (assign,nonatomic) JL_LineInStatus lineInStatus; //LineIn状态

2.7 FM控制功能

2.7.1 功能描述

控制固件收音机模块的相关操作FM相关操作

2.7.2 示例代码

 ///下一个节点
- (void)btn_lastPoint {
    JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
    JLModel_Device *model = [entity.mCmdManager outputDeviceModel];
    if (model.currentFunc == JL_FunctionCodeFM) {
        if(self->imageGif.hidden == NO){
            [DFUITools showText:kJL_TXT("searching") onView:self delay:1.0];
            return;
        }
        [entity.mCmdManager.mFmManager cmdFm:JL_FCmdFMChannelBefore
                   Saerch:0x00 Channel:0x00
                Frequency:0x00 Result:nil];
    }
}

2.7.3 相关接口

 typedef NS_ENUM(UInt8, JL_FCmdFM) {
    JL_FCmdFMPP                     = 0x01, //FM 暂停/播放
    JL_FCmdFMPonitBefore            = 0x02, //上一个频点
    JL_FCmdFMPonitNext              = 0x03, //下一个频点
    JL_FCmdFMChannelBefore          = 0x04, //上一个频道
    JL_FCmdFMChannelNext            = 0x05, //下一个频道
    JL_FCmdFMSearch                 = 0x06, //扫描
    JL_FCmdFMChannelSelect          = 0x07, //选择频道
    JL_FCmdFMChannelDelete          = 0x08, //删除频道
    JL_FCmdFMFrequencySelect        = 0x09, //选择频点
    JL_FCmdFMFrequencyDelete        = 0x0a, //删除频点
};

typedef NS_ENUM(UInt8, JL_FMSearch) {
    JL_FMSearchALL                  = 0x00, //FM 暂停/播放
    JL_FMSearchForward              = 0x01, //向前搜索
    JL_FMSearchBackward             = 0x02, //向后搜索
    JL_FMSearchStop                 = 0x03, //停止搜索
};

NS_ASSUME_NONNULL_BEGIN

@interface JL_FmManager : JL_FunctionBaseManager
 ///Fm状态
 @property (assign,nonatomic) JL_FMStatus        fmStatus;

 ///Fm 频段范围
 ///76.5-108.0Mhz
 ///87.5-108.0Mhz
 @property (assign,nonatomic) JL_FMMode          fmMode;

 ///当前fm
 @property (strong,nonatomic) JLModel_FM          *currentFm;

 ///Fm列表
 @property (strong,nonatomic) NSArray            *fmArray;

 #pragma mark ---> FM相关操作
 /**
 @param cmd FM功能
 @param search FM搜索
 @param channel FM频道
 @param frequency FM频点
 @param result 返回结果
 */
 -(void)cmdFm:(JL_FCmdFM)cmd
     Saerch:(JL_FMSearch)search
     Channel:(uint8_t)channel
 Frequency:(uint16_t)frequency
     Result:(JL_CMD_RESPOND __nullable)result;
 @end

2.8 灯光控制功能

2.8.1 功能描述

设备设置灯光相关的内容

2.8.2 示例代码

#pragma mark App---->固件 发送命令给固件
-(void)sendMessageLightState:(int )lightState withLightMode:(int) lightMode{

   COLOR_HSL hsl = {360*(sliderSewen_0.value),100*(sliderSewen_1.value),100*(sliderSewen_2.value)};
   COLOR_RGB rgb = {0,0,0};
   HSLtoRGB(&hsl, &rgb);

   btnColor.backgroundColor = kDF_RGBA(rgb.red, rgb.green, rgb.blue, 1.0);
   addColor = kDF_RGBA(rgb.red, rgb.green, rgb.blue, 1.0);

   float hue = 360*(sliderSewen_0.value);
   float saturation = 100*(sliderSewen_1.value);
   float lightness = 100*(sliderSewen_2.value);
   [bleSDK.mBleEntityM.mCmdManager.mLightManager cmdSetState:lightState Mode:lightMode
                                           Red:rgb.red Green:rgb.green Blue:rgb.blue
                                     FlashInex:flashIndex FlashFreq:freqenyIndex SceneIndex:sceneIndex
                                           Hue:hue Saturation:saturation Lightness:lightness];
}

2.8.3 相关接口

NS_ASSUME_NONNULL_BEGIN

@interface JL_LightManager : JL_FunctionBaseManager

#pragma mark ---> 设置灯光
/**
 *  设置灯光
 *  @param lightState 灯光状态
 *  @param lightMode 灯光模式
 *  @param red 红色色值
 *  @param green 绿色色值
 *  @param blue 蓝色色值
 *  @param flashIndex 闪烁模式index
 *  @param flashFreqIndex 闪烁频率
 *  @param sceneIndex 情景模式
 *  @param hue 色调,范围0-360
 *  @param saturation 饱和度,0-100
 *  @param lightness 亮度,0-100
 */
-(void)cmdSetState:(JL_LightState)lightState
              Mode:(JL_LightMode)lightMode
               Red:(uint8_t)red
             Green:(uint8_t)green
              Blue:(uint8_t)blue
         FlashInex:(JL_LightFlashModeIndex)flashIndex
         FlashFreq:(JL_LightFlashModeFrequency)flashFreqIndex
        SceneIndex:(JL_LightSceneMode)sceneIndex
               Hue:(uint16_t)hue
        Saturation:(uint8_t)saturation
         Lightness:(uint8_t)lightness;

@end

2.9 ID3信息控制功能

设备通过ID3向APP发送在播歌曲的相关信息,APP这边可通过以下接口进行控制

2.9.1 控制类相关接口

//播放/暂停
+(void)cmdID3_PP;

//上一曲
+(void)cmdID3_Before;

//下一曲
+(void)cmdID3_Next;

// 开启/暂停 音乐信息推送
+(void)cmdID3_PushEnable:(BOOL)enable;

//主动设置ID3播放状态
+(void)setID3_Status:(uint8_t)st;

2.9.2 监听以及使用方法

ID3 是手机其他的播放器在播放音乐时,向手机发送的一些信息,手机可以通过以下接口监听到这些信息,注意的是:需要开发者主动定时查询变化

-(void)addNote{
    //监听ID3播放状态
    [JL_Tools add:kUI_JL_SHOW_ID3 Action:@selector(noteShowId3:) Own:self];
}
-(void)noteShowId3:(NSNotification*)note{
    //第三方音乐,关闭设备音乐的定时器.
    [DFAction timingStop:myTimer];
    NSLog(@"----------> showID3UI 1111");
    [self showID3UI:YES];

    JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
    JLModel_Device *model = [entity.mCmdManager outputDeviceModel];
    NSString *name_0 = [NSString stringWithFormat:@"%@",model.ID3_Title];
    NSString *name_1 = [NSString stringWithFormat:@"%@",model.ID3_Artist];
    NSString *name_2 = [NSString stringWithFormat:@"%@",model.ID3_AlBum];
}

2.9.3 设备LRC歌词显示

2.9.3.1 功能描述

对固件传输的歌词进行显示

2.9.3.2 示例代码

 -(void)speexTest {
    JL_ManagerM *manager =  [[JL_ManagerM alloc] init];
    [manager.mLrcManager cmdLrcMonitorResult:^(NSString * _Nullable lrc, JL_LRCType type) {
       //TODO:
    }];
}

2.10 TWS功能

2.10.1 功能描述

对TWS耳机操作的相关接口

2.10.2 示例代码

2.10.2.1 智能充电仓相关

智能充电仓操作,通知固件App的信息,开启设备蓝⽛扫描、返回结果,其他操作等

  • 示例代码

// 通知固件App的信息
// @param flag  未知
+(void)cmdSetAppInfo:(uint8_t)flag;

// 设置通讯MTU
// @param mtu app请求mtu⼤⼩
// @param result 实际设置的Mtu⼤⼩
+(void)cmdSetMTU:(uint16_t)mtu Result:(JL_CMD_VALUE_BK)result;

// 开启蓝⽛扫描
// @param timeout 超时时间
// @param result  0:成功 1:失败
+(void)cmdBTScanStartTimeout:(uint16_t)timeout Result:(JL_CMD_VALUE_BK)result;

// 推送蓝⽛扫描结果
// 返回【蓝⽛数据结构】数组
// @see JLBTModel
extern NSString *kJL_BT_LIST_RESULT;

// 停⽌蓝⽛扫描(APP-->固件)
// @param reason  0:超时结束  1:打断结束  2:开启扫描失败  3:正在扫描
// @param result  0:成功  1:失败
+(void)cmdBTScanStopReason:(uint8_t)reason Result:(JL_CMD_VALUE_BK)result;

// 停⽌蓝⽛扫描(固件-->APP)
// 0:超时结束  1:打断结束  2:开启扫描失败  3:正在扫描
extern NSString *kJL_BT_SCAN_STOP_NOTE;

// 通知固件连接指定的蓝⽛设备
// @param addr 蓝⽛设备地址
// @param result  0:成功  1:失败
+(void)cmdBTConnectAddress:(NSData*)addr Result:(JL_CMD_VALUE_BK)result;

//文件传输 【固件-->APP】
//1.监听文件数据
+(void)cmdFileDataMonitorResult:(JL_FILE_DATA_BK)result;

//2.允许传输文件数据
+(void)cmdAllowFileData;

//3.拒绝传输文件数据
+(void)cmdRejectFileData;

//4.停止传输文件数据
+(void)cmdStopFileData;

//文件传输 【APP-->固件】
//5.请求传输文件给设备
+(void)cmdFileDataSize:(uint8_t)size
             SavePath:(NSString*)path;

//6.推送文件数据给设备
+(void)cmdPushFileData:(NSData*)data;

2.10.2.2 获取设备的图片

/**
获取设备的图片。
@param vid 设备vid
@param pid 设备pid
@param result 图片数据
*/
+(void)cmdRequestDeviceImageVid:(NSString*)vid
Pid:(NSString*)pid
Result:(JL_IMAGE_RT __nullable)result;

2.10.2.3 设置EDR名字

//对耳相关API
/**
设置EDR名字
@param name
*/
+(void)cmdHeatsetEdrName:(NSData*)name;

2.10.2.4 按键设置(对耳)

/**
按键设置(对耳)
@param key 左耳0x01 右耳0x02
@param act 单击0x01 双击0x02
@param fuc 0x00    无作用
0x01    开机
0x02    关机
0x03    上一曲
0x04    下一曲
0x05    播放/暂停
0x06    接听/挂断
0x07    拒听
0x08    拍照
*/
+(void)cmdHeatsetKeySettingKey:(uint8_t)key
Action:(uint8_t)act
Function:(uint8_t)fuc;

2.10.2.5 LED设置(对耳)

/**
LED设置(对耳)
@param scene 未配对     0x01
未连接     0x02
连接       0x03
@param effect  0x00    全灭
0x01    红灯常亮
0x02    蓝灯常亮
0x03    红灯呼吸
0x04    蓝灯呼吸
0x05    红蓝交替快闪
0x06    红蓝交替慢闪
*/
+(void)cmdHeatsetLedSettingScene:(uint8_t)scene
Effect:(uint8_t)effect;

2.10.2.6 MIC设置(耳机)

/**
MIC设置(耳机)
@param mode 0: 仅左耳
1: 仅右耳
2: 自动选择
*/
+(void)cmdHeatsetMicSettingMode:(uint8_t)mode;

2.10.2.7 工作模式(耳机)

/**
工作模式(耳机)
@param mode 0: 普通模式
1: 游戏模式
*/
+(void)cmdHeatsetWorkSettingMode:(uint8_t)mode;

2.10.2.8 同步时间戳(耳机)

/**
 同步时间戳(耳机)
 @param date  当前系统时间
*/
+(void)cmdHeatsetTimeSetting:(NSDate*)date;

2.10.2.9 获取设备信息(耳机)

/**
获取设备信息(耳机)
@param flag BIT0    小机电量获取 格式为3个字节 参考广播包格式
BIT1    Edr 名称
BIT2    按键功能
BIT3    LED 显示状态
BIT4    MIC 模式
BIT5    工作模式

@param result 返回字典:
@"ISCHARGING_L"
@"ISCHARGING_R"
@"ISCHARGING_C"
@"POWER_L"
@"POWER_R"
@"POWER_C"
@"EDR"
@"KEY_LR"
@"KEY_ACTION"
@"KEY_FUNCTION"
@"LED_SCENE"
@"LED_EFFECT"
@"MIC_MODE"
@"WORK_MODE"
*/
+(void)cmdHeatsetGetAdvFlag:(uint32_t)flag
Result:(JL_HEADSET_BK __nullable)result;

2.10.2.10 设备广播通知(耳机)

/**
设备广播通知(耳机)
@{@"JLID": 杰理ID,
@"VID": ,
@"PID":  ,
@"EDR": ,
@"SCENE": ,
@"ISCHARGING_L": ,
@"ISCHARGING_R": ,
@"ISCHARGING_C": ,
@"POWER_L": ,
@"POWER_R": ,
@"POWER_C": ,
@"CHIP_TYPE": ,
@"PROTOCOL_TYPE": ,
@"SEQ": };
*/
extern NSString *kJL_HEADSET_ADV;

2.10.2.11 关闭或开启设备广播(耳机)

/**
关闭或开启设备广播(耳机)
@param enable 使能位
*/
+(void)cmdHeatsetAdvEnable:(BOOL)enable;

2.10.2.12 用于ADV设置同步后需要主机操作的行为。

/**
 用于ADV设置同步后需要主机操作的行为。
  1:更新配置信息,需要重启生效。
 */
extern NSString *kJL_HEADSET_TIPS;

2.10.2.13 对挂脖耳机类型支持

挂脖耳机的功能和前面所提及的对耳、TWS耳机功能类似,只是产品造型方式不一致,APP这边就按照TWS对耳的方式来进行解析。

详见JL_EntityM类的以下属性:

/**
    JL_DeviceTypeSoundBox           = 0,     //AI音箱类型
    JL_DeviceTypeChargingBin        = 1,     //充电仓类型
    JL_DeviceTypeTWS                = 2,     //TWS耳机类型
    JL_DeviceTypeHeadset            = 3,     //普通耳机类型
    JL_DeviceTypeSoundCard          = 4,     //声卡类型
    JL_DeviceTypeWatch              = 5,     //手表类型
    JL_DeviceTypeTradition          = -1,    //传统设备类型
 */
@property(assign,nonatomic) JL_DeviceType   mType; //这里标记为:TWS耳机类型

@property(assign,nonatomic) BOOL            isCharging_L; //左耳状态显示为整个耳机的电量
@property(assign,nonatomic) BOOL            isCharging_R;//为零或无参考意义
@property(assign,nonatomic) BOOL            isCharging_C;//为零或无参考意义
@property(assign,nonatomic) uint8_t         mPower;  //左耳电量显示为整个耳机的电量
@property(assign,nonatomic) uint8_t         mPower_L; //左耳电量显示为整个耳机的电量
@property(assign,nonatomic) uint8_t         mPower_R; //为零或无参考意义
@property(assign,nonatomic) uint8_t         mPower_C;//为零或无参考意义

@property(assign,nonatomic) uint8_t         mProtocolType; //当为挂脖耳机时,此属性显示值为0x03 ,当设备是普通TWS耳机是0x02

其他的属性与设置和TWS功能属性保持一致

2.10.2.14 辅听耳机验配功能

辅听功能,是作用于TWS耳机设置的内容,其他设备不支持。实现需要依赖于JL_EntityM类的以下属性:mCmdManager属性。

  • 判断当前设备是否支持辅听功能:

    当前是是否支持辅听功能,可以通过JLModel_Device类的以下属性:isSupportDhaFitting属性来判断。

    JLModel_Device类对象是通过,JL_EntityM类的以下属性:mCmdManager属性的,outputDeviceModel方法来获取。

/// 是否支持辅听设置
@property (assign,nonatomic) BOOL               isSupportDhaFitting;
///验配信息交互:版本、通道数、通道频率
@property (strong,nonatomic) DhaFittingInfo     *dhaFitInfo;
/// 验配中断/开启的对象,仅限于监听
@property (strong,nonatomic) DhaFittingSwitch   *dhaFitSwitch;
  • 获取设备当前辅听的基础信息:版本、通道数、通道频率

[JLDhaFitting auxiGetInfo:^(DhaFittingInfo * _Nonnull info, NSArray<NSNumber *> * _Nonnull gains) {
    if (info) {
        if (info.ch_num != 0) {
            [self.navigationController pushViewController:vc animated:true];
        }else{
            Dialog()
            //位置
                .wToastPositionSet(DialogToastBottom)
                .wTypeSet(DialogTypeToast)
                .wMessageSet(kJL_TXT("msg_read_file_err_reading"))
            //调整宽度
                .wMainOffsetXSet(30)
                .wStart();
        }
    }else{
        Dialog()
        //位置
            .wToastPositionSet(DialogToastBottom)
            .wTypeSet(DialogTypeToast)
            .wMessageSet(kJL_TXT("msg_read_file_err_reading"))
        //调整宽度
            .wMainOffsetXSet(30)
            .wStart();
    }
} Manager:[[JL_RunSDK sharedMe] mBleEntityM].mCmdManager];
  • 进行辅听验配:

dhaManager = [[JLDhaFitting alloc] init];
JL_ManagerM *cmd = [[JL_RunSDK sharedMe] mBleEntityM].mCmdManager;
 DhaFittingData *dataInfo = [DhaFittingData new];
dataInfo.leftOn = NO;//左耳是否开启
dataInfo.rightOn = NO;//右耳是否开启
dataInfo.freq = 0;//频率
dataInfo.gain = 0.0;//增益
[dhaManager auxiCheckByStep:dataInfo Manager:cmd Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
    NSLog(@"验配:channecl:%d freq:%d gains:%.2f",dataInfo.channel,dataInfo.freq,dataInfo.gain);

}];
  • 保存验配设置:

警告

这里需要注意的是,保存验配设置,需要把前面验配好的N条DhaFittingData数据,重新整理发送到设备。

NSMutableArray *newArray = [NSMutableArray new];
for (FittingMgr *item in self.results) {
    for (DhaFittingData *item2 in item.dhaList) {
        [newArray addObject:item2];
    }
}
JL_ManagerM *mgr = [[JL_RunSDK sharedMe] mBleEntityM].mCmdManager;

[fitting auxiSaveGainsList:newArray Manager:mgr Type:[self getType] Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
    //保存成功或失败
}];
  • 退出辅听验配:

JL_ManagerM *cmd = [[JL_RunSDK sharedMe] mBleEntityM].mCmdManager;
[[JLDhaFitting new] auxiCloseManager:cmd Result:^(JL_CMDStatus status, uint8_t sn, NSData * _Nullable data) {
    NSLog(@"退出验配");
}];

警告

当前协议版本,只针对辅听开关只通过固件按键进行设置,所以设备会在辅听开关开启的时候,自动进入辅听验配状态。每次只要请求设备信息,回复正确时,设备就会进入辅听验配状态。 在此特定条件下,需要注意及时执行退出辅听验配的操作。

  • 监听设备推出辅听验配状态:

使用KVO的方式监听设备推出辅听验配状态,当设备推出辅听验配状态时,会触发该回调。

@interface FittingBasicVC (){
    JLModel_Device *devModel;
}
@end

@implementation FittingBasicVC
- (void)viewDidLoad {
    static void *dhaFitSwitchContext = &dhaFitSwitchContext;

    devModel = [[[JL_RunSDK sharedMe] mBleEntityM].mCmdManager getDeviceModel];
    [devModel addObserver:self forKeyPath:@"dhaFitSwitch" options:NSKeyValueObservingOptionNew context:dhaFitSwitchContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{

    if (context == dhaFitSwitchContext) {
        if ([change objectForKey:@"new"]) {

            JL_ManagerM *manager = [[JL_RunSDK sharedMe] mBleEntityM].mCmdManager;
            TwsElectricity *electricity = manager.mTwsManager.electricity;
            DhaFittingSwitch *sw = [change objectForKey:@"new"];
            if (electricity.powerLeft > 0 && electricity.powerRight>0) {
                //双耳
                if (sw.rightOn == NO || sw.leftOn == NO) {
                    [self goBackToRoot];
                }
            }
            if (electricity.powerLeft == 0 && electricity.powerRight>0) {
                //右耳
                if (sw.rightOn == NO) {
                    [self goBackToRoot];
                }

            }
            if (electricity.powerRight == 0 && electricity.powerLeft>0) {
                //左耳
                if (sw.leftOn == NO) {
                    [self goBackToRoot];
                }
            }

        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}
-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [devModel removeObserver:self forKeyPath:@"dhaFitSwitch" context:dhaFitSwitchContext];
}
-(void)goBackToRoot{
    [self.navigationController popToRootViewControllerAnimated:YES];
}
@end

2.10.2.15 一拖二耳机功能支持

一拖二功能的支持可以通过 JL_TwsManager 类的扩展功能支持属性 TwsSupportFuncs *supports;,下的 isSupportDragWithMore; 来判断是否支持;

  • 获取设备已连接的手机名

NSMutableArray *itemList = [NSMutableArray new];

JL_TwsManager *tws = [[JL_RunSDK sharedMe] mBleEntityM].mCmdManager.mTwsManager;

[tws cmdGetDeviceInfoListResult:^(JL_CMDStatus status, NSArray<JLTWSAddrNameInfo *> * _Nullable phoneInfos) {
    if (status == JL_CMDStatusSuccess){

        [self->itemList setArray:phoneInfos];
        for (JLTWSAddrNameInfo *info in phoneInfos){
            [info logProperties];
        }

        NSData *addr = [[NSUserDefaults standardUserDefaults] valueForKey:PhoneEdrAddr];

        if (phoneInfos.count == 1){
            JLTWSAddrNameInfo *info = phoneInfos.firstObject;
            [[NSUserDefaults standardUserDefaults] setValue:info.phoneEdrAddr forKey:PhoneEdrAddr];
            [[NSUserDefaults standardUserDefaults] setValue:info.phoneName forKey:PhoneName];
            [[NSUserDefaults standardUserDefaults] synchronize];
            //绑定
            [tws cmdBindDeviceInfo:info.phoneEdrAddr phone:info.phoneName result:^(JL_CMDStatus status, NSArray<JLTWSAddrNameInfo *> * _Nullable phoneInfos) {
                [self->subTable reloadData];
            }];
        }
        if (phoneInfos.count == 2){
            for (JLTWSAddrNameInfo *info in phoneInfos) {
                if (info.isBind){
                    if ([info.phoneEdrAddr isEqualToData:addr]){
                        [[NSUserDefaults standardUserDefaults] setValue:info.phoneName forKey:PhoneName];
                        [[NSUserDefaults standardUserDefaults] synchronize];
                        //绑定
                        [tws cmdBindDeviceInfo:info.phoneEdrAddr phone:info.phoneName result:^(JL_CMDStatus status, NSArray<JLTWSAddrNameInfo *> * _Nullable phoneInfos) {
                            [self->subTable reloadData];
                        }];

                    }
                }else{
                    if ([info.phoneEdrAddr isEqualToData:addr]){
                        [[NSUserDefaults standardUserDefaults] setValue:info.phoneEdrAddr forKey:PhoneEdrAddr];
                        [[NSUserDefaults standardUserDefaults] setValue:info.phoneName forKey:PhoneName];
                        [[NSUserDefaults standardUserDefaults] synchronize];
                        //绑定
                        [tws cmdBindDeviceInfo:info.phoneEdrAddr phone:info.phoneName result:^(JL_CMDStatus status, NSArray<JLTWSAddrNameInfo *> * _Nullable phoneInfos) {
                            [self->subTable reloadData];
                        }];
                    }
                }
            }
        }
    }
    [self->subTable reloadData];
}];

以上逻辑是一个判断本机对应的蓝牙地址的判断:

警告

  1. 当设备返回只有一台手机信息的时候,手机本地需要更新并记录该名字与地址(无论本身是否已经记录过),如果手机本地记录(名字或地址)有更改,请绑定后重新发送C031;

  2. 当设备返回两台手机信息都没有绑定的时候,手机不做任何操作;

  3. 当设备返回两台手机信息其中一台有绑定、另外一台没有绑定时:

  4. 如果绑定的地址与手机本地的相符,查看是否有更新,如果有更新则重新发送C031命令,没有更新则不作处理;

  5. 如果绑定的地址与手机本地的不相符(包括手机本地),则没有绑定的手机信息是当前手机的信息,绑定后重新发送C031命令。

一拖二设备列表信息也能通过以下通知获得:

/// 设备上传一拖二设备信息列表
extern NSString* kJL_MULIT_NAME_LIST;
  • 通知设备一连上手机地址和绑定信息

    此接口需要配合上述的获取设备已连接手机信息接口使用,具体绑定规则如上述注意点所述

/// 通知设备一连上手机地址和绑定信息
/// - Parameters:
///   - addr: 手机EDR地址
///   - name: 手机蓝牙名称
///   - result: 结果
-(void)cmdBindDeviceInfo:(NSData *)addr phone:(NSString *)name result:(JL_MulitLinksInfo_BK)result
  • 设备开启/关闭多连接手机接口

/// 一拖二开关
/// @param dragWithMore 开关状态
/// @param addr 手机的经典蓝牙地址(通过cmdGetDeviceInfoList 获取)
/// @param result   结果回调
-(void)setDragWithMore:(BOOL)dragWithMore phoneBleAddr:(NSData *) addr result:(JL_CMD_RESPOND)result

2.11 ANC设置功能

2.11.1 功能说明

当前接口只针对具备降噪功能的耳机起作用,属于公共接口,在 JL_Manager.h 类的 JLDeviceModel 对象里面的相关字段属性为

2.11.2 示例代码

@property (assign,nonatomic) uint8_t            mAncMode;       //耳机降噪模式
@property (strong,nonatomic) NSArray            *mAncModeTypes;  //耳机主动降噪所支持模式
  • 目前分为两个接口,一个是用来设置耳机变成什么降噪模式的,一个是设置耳机可支持多少种降噪模式。

/// 耳机主动降噪ANC
/// @param mode 模式 (0x01:普通模式 0x02:降噪模式 0x03:通透模式)
+(void)cmdSetAncMode:(uint8_t)mode;

/// 耳机主动降噪ANC (模式使能)
/// @param modeTypes 支持的模式 @[@(JL_ANCType_Normal),@(JL_ANCType_NoiseReduction).....]
/// JL_ANCType_Normal                      = 0,    //普通模式
/// JL_ANCType_NoiseReduction        = 1,    //降噪模式
/// JL_ANCType_Transparent              = 2,    //通透模式
+(void)cmdSetAncModeTypes:(NSArray *)modeTypes;

2.11.3 耳机SDK添加ANC(主动降噪)

  • 耳机主动降噪支持模式

@property (copy  ,nonatomic) NSMutableArray     *mAncModeArray;         //ANC模式数组
  • 耳机主动降噪当前的模式

@property (copy  ,nonatomic) JLModel_ANC        *mAncModeCurrent;       //当前ANC的模式
  • 对耳机主动降噪进行设置

@interface JLModel_ANC : NSObject
@property(assign,nonatomic)JL_AncMode       mAncMode;               //耳机降噪模式
@property(assign,nonatomic) uint16_t        mAncMax_L;              //左耳最大增益
@property(assign,nonatomic) uint16_t        mAncCurrent_L;          //左耳当前增益
@property(assign,nonatomic) uint16_t        mAncMax_R;              //右耳最大增益
@property(assign,nonatomic) uint16_t        mAncCurrent_R;          //右耳当前增益
-(NSData*)dataModel;

#pragma mark ---> 耳机主动降噪ANC设置
-(void)cmdSetANC:(JLModel_ANC*)model;

2.12 声卡功能配置

2.12.1 功能描述

对声卡设备的处理

2.12.2 示例代码

/// 声卡功能
@interface JLSoundCardIndexValue : NSObject

@property (assign,nonatomic)uint8_t index;

@property (assign,nonatomic)uint16_t value;

@end

@protocol JLSoundCardMgrDelegate <NSObject>

/// 声卡控制回调
/// - Parameters:
///   - mask: 掩码
///   - items: 掩码对应的值
-(void)jlsoundCardMask:(uint64_t)mask values:(NSArray <JLSoundCardIndexValue *> *)items;

/// 声卡控制回调
/// - Parameter frequencyArray: 频率数组
-(void)jlsoundCardMicFrequency:(NSArray*)frequencyArray;

/// 声卡控制回调
/// - Parameter eqArray: EQ数组
-(void)jlsoundCardMicEQ:(NSArray*)eqArray;

@end


/// 卡拉OK(声卡控制相关)
@interface JL_SoundCardManager : NSObject

/// 卡拉OK 组件索引
@property (assign,nonatomic)long index;

/// 卡拉OK 组件的值
@property (assign,nonatomic)long value;

/// 卡拉OK 固件返回的掩码
@property (assign,nonatomic)uint64_t mask;

/// 声卡功能参数列表
@property (strong,nonatomic)NSArray <JLSoundCardIndexValue *>* iVitems;

/// 卡拉OK 频率数组
@property (strong,nonatomic)NSArray *micFrequencyArray;

/// 卡拉OK EQ数组
@property (strong,nonatomic)NSArray *micEQArray;

/// 代理
@property (weak,nonatomic)id<JLSoundCardMgrDelegate> delegate;

2.12.3 声卡效果设置

声卡设置的效果是根据以下json的 id 接送来设置相关内容的

{
"hasEq": "true",
"function": [
{
    "id": 0,
    "title": {
    "zh": "变声",
    "en": "Voice"
    },
    "type": "select",
    "icon_url": "icon_voice_nol.png",
    "column": 3,
    "paging": false,
    "list": [
    {
        "title": {
        "zh": "正常",
        "en": "Normal"
        },
        "index": 0
    },
    {
        "title": {
        "zh": "男声",
        "en": "Boy"
        },
        "index": 1
    },
    {
        "title": {
        "zh": "女声",
        "en": "Girl"
        },
        "index": 2
    },
    {
        "title": {
        "zh": "童音",
        "en": "Children"
        },
        "index": 3
    },
    {
        "title": {
        "zh": "魔音",
        "en": "Magic"
        },
        "index": 4
    },
    {
        "title": {
        "zh": "电音",
        "en": "EM"
        },
        "group": true,
        "list": [
        {
            "title": {
            "zh": "A大调",
            "en": "A major"
            },
            "index": 5
        },
        {
            "title": {
            "zh": "升A大调",
            "en": "L A major"
            },
            "index": 6
        },
        {
            "title": {
            "zh": "B大调",
            "en": "B major"
            },
            "index": 7
        },
        {
            "title": {
            "zh": "C大调",
            "en": "C major"
            },
        "index": 8
        },
        {
            "title": {
            "zh": "升C大调",
            "en": "L C major"
            },
        "index": 9
        },
        {
            "title": {
            "zh": "D大调",
            "en": "D major"
            },
            "index": 10
        },
        {
            "title": {
            "zh": "升D大调",
            "en": "L D major"
            },
            "index": 11
        },
        {
            "title": {
            "zh": "E大调",
            "en": "E major"
            },
            "index": 12
        },
        {
            "title": {
            "zh": "F大调",
            "en": "F major"
            },
            "index": 13
        },
        {
            "title": {
            "zh": "升F大调",
            "en": "L F major"
            },
            "index": 14
        },
        {
            "title": {
            "zh": "G大调",
            "en": "G major"
            },
            "index": 15
        },
        {
            "title": {
            "zh": "升G大调",
            "en": "L G major"
            },
            "index": 16
        }
      ]
    }
  ]
},
{
    "title": {
    "zh": "其他",
    "en": "Other"
    },
    "id": 3,
    "icon_url": "icon_others_nol.png",
    "type": "select",
    "column": 3,
    "paging": false,
    "list": [
    {
        "title": {
        "zh": "爆音",
        "en": "Popping"
        },
        "index": 36
    },
    {
        "title": {
        "zh": "喊麦",
        "en": "Shout"
        },
        "index": 37
    },
    {
        "title": {
        "zh": "闪避",
        "en": "Dodge"
        },
        "index": 38
    }
  ]
},
{
    "title": {
    "zh": "气氛",
    "en": "AtmosPhere"
    },
    "id": "1",
    "icon_url": "icon_effect_nol.png",
    "type": "img_select",
    "column": 4,
    "paging": true,
    "row": 2,
    "list": [
    {
        "title": {
        "zh": "欢呼",
        "en": "Cheers"
        },
        "index": 17,
        "icon_url": "img_cheer.png"
    },
    {
        "title": {
        "zh": "尴尬",
        "en": "Awkward"
        },
        "index": 18,
        "icon_url": "img_awkward.png"
    },
    {
        "title": {
        "zh": "枪声",
        "en": "Gunfire"
        },
        "index": 19,
        "icon_url": "img_gun.png"
    },
    {
        "title": {
        "zh": "鄙视",
        "en": "Despise"
        },
        "index": 20,
        "icon_url": "img_despise.png"
    },
    {
        "title": {
        "zh": "开场",
        "en": "Stage"
        },
        "index": 21,
        "icon_url": "img_stage.png"
    },
    {
        "title": {
        "zh": "飞吻",
        "en": "Kiss"
        },
        "index": 22,
        "icon_url": "img_kiss.png"
    },
    {
        "title": {
        "zh": "笑声",
        "en": "Laugh"
        },
        "index": 23,
        "icon_url": "img_laugh.png"
    },
    {
        "title": {
        "zh": "掌声",
        "en": "Applause"
        },
        "index": 24,
        "icon_url": "img_applause.png"
    },
    {
        "title": {
        "zh": "请关注",
        "en": "Attention"
        },
        "index": 25,
        "icon_url": "img_follow.png"
    },
    {
        "title": {
        "zh": "么么哒",
        "en": "Mua"
        },
        "index": 26,
        "icon_url": "img_memeda.png"
    },
    {
        "title": {
         "zh": "贼啦啦",
        "en": "Song01"
        },
        "index": 27,
        "icon_url": "img_song_01.png"
    },
    {
        "title": {
        "zh": "非诚勿扰",
        "en": "Song02"
        },
        "index": 28,
        "icon_url": "img_song_02.png"
    }
  ]
},
{
    "title": {
    "zh": "Mic参数",
    "en": "Mic Args"
    },
    "id": 2,
    "icon_url": "icon_settle_nol.png",
    "type": "slider",
    "list": [
     {
        "title": {
        "zh": "麦音量",
        "en": "Mic Vol"
        },
        "index": 29,
        "max": 30,
        "enable": true
    },
    {
        "title": {
        "zh": "录音音量",
        "en": "Record Vol"
        },
        "index": 30,
        "enable": true,
        "max": 30
    },
    {
        "title": {
        "zh": "混响",
        "en": "Reverberation"
        },
        "index": 31,
        "enable": true,
        "max": 30
    },
    {
        "title": {
        "zh": "高音",
        "en": "High"
        },
        "index": 32,
        "enable": true,
        "max": 10
    },
    {
        "title": {
        "zh": "低音",
        "en": "Bass"
        },
        "index": 33,
        "enable": true,
        "max": 10
    },
    {
        "title": {
        "zh": "伴奏音量",
        "en": "Accomp Vol"
        },
        "index": 34,
        "enable": true,
        "max": 30
    },
    {
        "title": {
        "zh": "监听音量",
        "en": "Monitor Vol"
        },
        "index": 35,
        "enable": true,
        "max": 30
        }
    ]
    }
]
}

2.13 查找设备功能

2.13.1 功能描述

接收到设备的命令后,通过播放手机的音频,从而使用户找到手机,或手机通过发送命令到设备,设备发出声响。

2.13.2 示例代码

设备查找手机的通知,携带了响铃时长

extern NSString *kJL_BT_FIND_PHONE;

查找设备命令

/// 查找设备命令
/// @param isVoice 是否发声
/// @param timeout 超时时间
/// @param isIphone 是否设备查找手机(默认是手机找设备)
+(void)cmdFindDevice:(BOOL)isVoice timeOut:(uint16_t)timeout findIphone:(BOOL)isIphone;

2.14 用户自定义命令功能

2.14.1 功能描述

通过此接口可以收发自定义数据。

2.14.2 示例代码

//发送自定义数据
NSData *data = [NSData new];
[self.mBleEntityM.mCmdManager.mCustomManager cmdCustomData:data
                                                    Result:^(JL_CMDStatus status,
                                                             uint8_t sn, NSData * _Nullable data) {
    if (status == JL_CMDStatusSuccess) {
        NSLog(@"发数成功...");
    }else{
        NSLog(@"发数失败~");
    }
}];

//接收自定义数据
[JL_Tools add:kJL_MANAGER_CUSTOM_DATA Action:@selector(noteCustomData:) Own:self];

2.14.3 注意事项

发数成功会有JL_CMDStatusSuccess回调。

2.15 OTA

2.15.1 功能描述

对设备进行升级,详细版参考:【杰理OTA升级(iOS)开发说明】

2.15.2 使用demo

2.15.2.1 OTA升级文件下载

若使用杰理提供的服务器资源,则协商后可使用以下接口进行升级文件下载。

/**
OTA升级文件下载
@param key 授权key
@param code 授权code
@param result 回复
*/
+(void)cmdGetOtaFileKey:(NSString*)key
Code:(NSString*)code
Result:(JL_OTA_URL __nullable)result;

/**
OTA升级文件下载【MD5】
@param key 授权key
@param code 授权code
@param hash  MD5值
@param result 回复
*/
+(void)cmdGetOtaFileKey:(NSString*)key
                   Code:(NSString*)code
                   hash:(NSString*)hash
                 Result:(JL_OTA_URL __nullable)result;

2.15.2.2 OTA升级设备

/**
OTA升级设备
@param data 升级数据
@param result 升级结果
*/
+(void)cmdOTAData:(NSData*)data
Result:(JL_OTA_RT __nullable)result;

2.15.2.3 OTA的升级状态

JL_OtaStatusNormal              = 0,    //正常升级
JL_OtaStatusForce               = 1,    //强制升级

2.15.2.4 OTA的返回结果

JL_OTAResultSuccess             = 0x00, //OTA升级成功
JL_OTAResultFail                = 0x01, //OTA升级失败
JL_OTAResultDataIsNull          = 0x02, //OTA升级数据为空
JL_OTAResultCommandFail         = 0x03, //OTA指令失败
JL_OTAResultSeekFail            = 0x04, //OTA标示偏移查找失败
JL_OTAResultInfoFail            = 0x05, //OTA升级固件信息错误
JL_OTAResultLowPower            = 0x06, //OTA升级设备电压低
JL_OTAResultEnterFail           = 0x07, //未能进入OTA升级模式
JL_OTAResultUpgrading           = 0x08, //OTA升级中
JL_OTAResultReconnect           = 0x09, //OTA需重连设备
JL_OTAResultReboot              = 0x0a, //OTA需设备重启
JL_OTAResultPreparing           = 0x0b, //OTA准备中
JL_OTAResultPrepared            = 0x0f, //OTA准备完成
JL_OTAResultFailVerification    = 0xf1, //升级数据校验失败
JL_OTAResultFailCompletely      = 0xf2, //升级失败
JL_OTAResultFailKey             = 0xf3, //升级数据校验失败
JL_OTAResultFailErrorFile       = 0xf4, //升级文件出错
JL_OTAResultFailUboot           = 0xf5, //uboot不匹配
JL_OTAResultFailLenght          = 0xf6, //升级过程长度出错
JL_OTAResultFailFlash           = 0xf7, //升级过程flash读写失败
JL_OTAResultFailCmdTimeout      = 0xf8, //升级过程指令超时
JL_OTAResultFailSameVersion     = 0xf9, //相同版本
JL_OTAResultFailTWSDisconnect   = 0xfa, //TWS耳机未连接
JL_OTAResultFailNotInBin        = 0xfb, //耳机未在充电仓
JL_OTAResultUnknown,                    //OTA未知错误

2.15.2.5 获取MD5的数据

#pragma mark 获取MD5数据
+(void)cmdGetMD5_Result:(JL_CMD_BK __nullable)result;

2.15.2.6 版本校对,并获取升级文件

-(void)checkVersion{

#if (LT==0)

//    //有新版本
//    self->shouldUp = YES;
//    self->downloadUrl = [JL_Tools find:@"update_yx_hp_93.ufw"];
//    savePath = [JL_Tools find:@"update_yx_hp_93.ufw"];
//    dispatch_async(dispatch_get_main_queue(), ^{
//        [self->upgradeView initWithNews:@"1.0.0.0" tips:@"升级测试"];
//        [self.view addSubview:self->upgradeView];
//    });
//    [self.upgradeTable reloadData];
//    return;

    JLModel_Device *model = [self.otaEntity.mCmdManager outputDeviceModel];
    if (model.md5Type == YES) {
        /*---- OTA升级使用MD5校验 ----*/
        [self.otaEntity.mCmdManager cmdGetMD5_Result:^(NSArray * _Nullable array) {
            if (array.count >= 3) {
                NSData *data_md5 = array[2];
                NSString *str_md5 = [[NSString alloc] initWithData:data_md5 encoding:NSUTF8StringEncoding];
                NSLog(@"MD5 ----> %@",str_md5);
                //NSString* test = @"eb5eaa7e89664adc2c840230fc494656";
                [self.otaEntity.mCmdManager cmdGetOtaFileKey:model.authKey Code:model.proCode hash:str_md5
                                      Result:^(JL_OTAUrlResult result,
                                               NSString * _Nullable version,
                                               NSString * _Nullable url,
                                               NSString * _Nullable explain) {
                    [self updateWithOTAResult:result Version:version Url:url Explain:explain];
                }];
            }
        }];
    }else{
        /*--- 传统OTA升级 ---*/
        NSString *authKey = @"";
        NSString *proCode = @"";
        if ([model.authKey isEqualToString:@""] || [model.proCode isEqualToString:@""] ) {

            DeviceModel *m1 = [[SqliteManager sharedInstance] checkoutDeviceModelBy:self.otaEntity.mUUID];
            authKey = m1.authKey;
            proCode = m1.proCode;
        }else{
            authKey = model.authKey;
            proCode = model.proCode;
        }
        [self.otaEntity.mCmdManager cmdGetOtaFileKey:authKey Code:proCode
                              Result:^(JL_OTAUrlResult result,
                                       NSString * _Nullable version,
                                       NSString * _Nullable url,
                                       NSString * _Nullable explain) {
            [self updateWithOTAResult:result Version:version Url:url Explain:explain];
        }];
    }
#else
    JLModel_Device *model = [self.otaEntity.mCmdManager outputDeviceModel];
    NSString *authKey = @"";
    NSString *proCode = @"";
    if ([model.authKey isEqualToString:@""] || [model.proCode isEqualToString:@""] ) {

        DeviceModel *m1 = [[SqliteManager sharedInstance] checkoutDeviceModelBy:self.otaEntity.mUUID];
        authKey = m1.authKey;
        proCode = m1.proCode;
    }else{
        authKey = model.authKey;
        proCode = model.proCode;
    }
    //有新版本
    shouldUp = YES;
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    path = [path stringByAppendingPathComponent:@"update.ufw"];
    savePath = path;

    [upgradeView initWithNews:@"Max" tips:@"无限制升级"];
    [self.view addSubview:upgradeView];


#endif

}

2.15.2.7 执行OTA升级

[self.otaEntity.mCmdManager cmdOTAData:data Result:^(JL_OTAResult result, float progress) {
    if (result == JL_OTAResultSuccess) {
        [self->transportView update:1.0 Text:nil];
        self->transportView.alpha = 0.0;

        [[JLUI_Cache sharedInstance] setOtaUUID:nil];

        [weakSelf upgradeFinish];
    }
    if (result == JL_OTAResultFail) {
        [weakSelf failedWithAction:kJL_TXT("OTA升级失败")];
    }
    if (result == JL_OTAResultDataIsNull) {
        [weakSelf failedWithAction:kJL_TXT("OTA升级数据为空!")];
    }
    if (result == JL_OTAResultCommandFail) {
        [weakSelf failedWithAction:kJL_TXT("OTA指令失败!")];
    }
    if (result == JL_OTAResultSeekFail) {
        [weakSelf failedWithAction:kJL_TXT("OTA标示偏移查找失败!")];
    }
    if (result == JL_OTAResultInfoFail) {
        [weakSelf failedWithAction:kJL_TXT("OTA升级固件信息错误!")];
    }
    if (result == JL_OTAResultLowPower) {
        [weakSelf failedWithAction:kJL_TXT("OTA升级设备电压低!")];
    }
    if (result == JL_OTAResultEnterFail) {
        [weakSelf failedWithAction:kJL_TXT("未能进入OTA升级模式!")];
    }
    if (result == JL_OTAResultUnknown) {
        [weakSelf failedWithAction:kJL_TXT("OTA未知错误!")];
    }
    if (result == JL_OTAResultFailSameVersion) {
        [weakSelf failedWithAction:kJL_TXT("相同版本!")];
    }
    if (result == JL_OTAResultFailTWSDisconnect) {
        [weakSelf failedWithAction:kJL_TXT("TWS耳机未连接")];
    }
    if (result == JL_OTAResultFailNotInBin) {
        [weakSelf failedWithAction:kJL_TXT("耳机未在充电仓")];
    }

    if (result == JL_OTAResultPreparing ||
        result == JL_OTAResultUpgrading)
    {
        if (result == JL_OTAResultUpgrading) [self->transportView update:progress Text:kJL_TXT("正在升级")];
        if (result == JL_OTAResultPreparing) [self->transportView update:progress Text:@"检验文件"];
        [self otaTimeCheck];//增加超时检测
    }

    if (result == JL_OTAResultPrepared) {
        [self otaTimeCheck];//增加超时检测
    }
    if (result == JL_OTAResultReconnect) {
        [self otaTimeCheck];//增加超时检测

        NSLog(@"---> OTA正在回连设备... %@",self.otaEntity.mItem);
        [self->bleSDK.mBleMultiple connectEntity:self.otaEntity Result:^(JL_EntityM_Status status) {
            if (status != JL_EntityM_StatusPaired) {
                [weakSelf failedWithAction:kJL_TXT("OTA升级超时")];
            }
        }];
    }
}];