7. WIFI_SOUNDBOX 工程说明

7.1. wifi_soundbox工程简介

  • wifi_soundbox工程实现了通过WIFI、蓝牙实现多功能智能音箱的应用

  • WIFI智能音箱应用于连接蓝牙进行蓝牙播放或连接WIFI进行网络资源播放,也可进行FLASH、SD卡和U盘播放

  • 该工程具有的功能有TWS、蓝牙EDR功能、BLE功能、音频播放功能、录音功能、配网功能和接入第三方SDK功能,具体为:

  • 蓝牙功能:
    • 蓝牙媒体音频

    • 蓝牙通话

    • 蓝牙串口SPP通信

  • BLE功能:
    • 数传ble

    • 通用HID功能

    • 主机多机BLE通讯

  • 音频播放功能:
    • 本地播放
      • FLASH播放

      • SD卡播放

      • U盘播放

    • 网络播放

    • LINEIN播放

    • PC播放

    • 蓝牙播放

    • tws

  • 录音功能

  • 配网功能
    • 声波配网

    • BLE配网

    • AIRKISS配网

    • WSC配网

7.2. wifi_soundbox工程操作说明

  • 查看快速入门文档中的开发准备说明、开发环境安装说明、SDK工程编译与下载和SDK应用开发说明

  • 工程打开路径: apps\wifi_soundbox\board\wl83\AC792N_WIFI_SOUNDBOX.cbp

  • 打开工程后SDK中选择开发板对应的板级: Headers/apps/wifi_soundbox/board/wl83/board_config.h 中选择对应的板级宏打开,如下图以打开7926A开源学习板级为例

//芯片型号
// #define CONFIG_BOARD_AC7925A
// #define CONFIG_BOARD_AC7925B
#define CONFIG_BOARD_AC7926A
// #define CONFIG_BOARD_AC7922C
  • 在app_config.h中选择需要使用的功能和平台将其宏打开

  • 进行编译烧录使用

  • 按键操作说明:
    • K1(KEY_POWER):短按进入语音监听模式;

      长按进行电话操作,如接听/挂断/回拨(在连接了蓝牙设备前提下); 双击唤醒手机语音助手如siri(蓝牙连接情况下);

    • K2(KEY_MODE): 短按切换下一个模式;

      长按进入配网模式,通过微信小程序进行声波、ble等配网; 双击开关蓝牙连接; 三击开关tws蓝牙配对

    • K3(KEY_DOWN): 短按减小音量;

      长按上一首(在播放音乐前提下); 调低音调(linein_music/pc模式下);

    • K4(KEY_UP): 短按增大音量;

      长按下一首(在播放音乐前提下);调高音调(linein_music/pc模式下);

    • K5(KEY_OK): 短按暂停/播放;

      长按开关录音

  • 按KEY_MODE切换下一模式时,会从local_music到recorder再回到local_music循环切换

// 可选择的模式列表
static const app_mode_table_t app_mode_table[] = {
#if TCFG_APP_BT_EN
    { "bt",             APP_MSG_BT_BOTTOM,              APP_MSG_TWS_TOP,            APP_MODE_BT },
#endif
#if TCFG_APP_NET_MUSIC_EN
    { "net_music",      APP_MSG_NET_MUSIC_BOTTOM,       APP_MSG_NET_MUSIC_TOP,      APP_MODE_NET },
#endif
#if TCFG_APP_MUSIC_EN
    { "local_music",    APP_MSG_LOCAL_MUSIC_BOTTOM,     APP_MSG_LOCAL_MUSIC_TOP,    APP_MODE_LOCAL },
#endif
#if TCFG_APP_LINEIN_EN
    { "linein_music",   APP_MSG_LINEIN_MUSIC_BOTTOM,    APP_MSG_LINEIN_MUSIC_TOP,   APP_MODE_LINEIN },
#endif
#if TCFG_APP_PC_EN
    { "pc_music",       APP_MSG_PC_MUSIC_BOTTOM,        APP_MSG_PC_MUSIC_TOP,       APP_MODE_PC },
#endif
#if TCFG_APP_RECORD_EN
    { "recorder",       APP_MSG_RECORDER_BOTTOM,        APP_MSG_RECORDER_TOP,       APP_MODE_RECORDER },
#endif
#if TCFG_LOCAL_TWS_ENABLE
    { "sink_music",     APP_MSG_SINK_MUSIC_BOTTOM,      APP_MSG_SINK_MUSIC_TOP,     APP_MODE_SINK },
#endif
};
  • 模式切换函数:

void app_mode_change_next(void)
{
    struct intent it;
    init_intent(&it);

    app_mode_t curr_mode = get_current_app_mode();

    if (curr_mode > APP_MODE_BT) {
        it.action = ACTION_REPLACE;
    }
    do {
        if (++curr_mode == APP_MODE_SINK || curr_mode < APP_MODE_LOCAL) { //设定模式从APP_MODE_LOCAL到recorder
            curr_mode = APP_MODE_LOCAL;
        }

        for (int i = 0; i < ARRAY_SIZE(app_mode_table); ++i) {
            if (curr_mode == app_mode_table[i].app_mode) {
                it.name = app_mode_table[i].app_name;
                //清掉上一个app的恢复断点
                app_msg_handler(NULL, APP_MSG_STOP);
                if (0 == start_app(&it)) {
                    return;
                }
            }
        }
    } while (1);
}
  • 提醒:AD按键配置的电阻值需对应板卡电阻。如在board_develop_AC7926A.h中已配置

//*********************************************************************************//
//                                  AD按键配置                                     //
//*********************************************************************************//
#define TCFG_ADKEY_ENABLE                   1              //AD按键
#define TCFG_PRESS_LONG_KEY_POWERON_ENABLE  TCFG_ADKEY_ENABLE       //长按开关机功能
#define TCFG_ADKEY_INPUT_IO                 IO_PORTD_00
#define TCFG_ADKEY_INPUT_CHANNEL            ADC_IO_CH_PD00

#define ADKEY_UPLOAD_R                      22             //上拉电阻值
#define TCFG_ADC_LEVEL_09                   0x3FF
#define TCFG_ADC_LEVEL_08                   0x3FF
#define TCFG_ADC_LEVEL_07                   0x3FF
#define TCFG_ADC_LEVEL_06                   0x3FF
#define TCFG_ADC_LEVEL_05                   0x3FF
#define TCFG_ADC_LEVEL_04                   (ADC_VDDIO * 100 / (100 + ADKEY_UPLOAD_R))
#define TCFG_ADC_LEVEL_03                   (ADC_VDDIO * 33  / (33  + ADKEY_UPLOAD_R))
#define TCFG_ADC_LEVEL_02                   (ADC_VDDIO * 15  / (15  + ADKEY_UPLOAD_R))
#define TCFG_ADC_LEVEL_01                   (ADC_VDDIO * 51  / (51  + ADKEY_UPLOAD_R * 10))
#define TCFG_ADC_LEVEL_00                   (0)

7.3. wifi_soundbox工程代码流程重点讲解

  • 1.app_mian.c:app_main()入口:通过play_tone_file_callback创建player播放器对象,注册播放提示音结束回调函数poweron_tone_play_end_callback并播放开机提示音,随后调用回调函数poweron_tone_play_end_callback进入蓝牙模式,若开机提示音播放失败直接调用poweron_tone_play_end_callback进入蓝牙模式。

static int poweron_tone_play_end_callback(void *priv, enum stream_event event)
{
    if (event == STREAM_EVENT_STOP) {
        app_mode_change(APP_MODE_BT);
    }

    return 0;
}

/*
* 应用程序主函数
*/
void app_main(void)
{
    puts("------------- wifi_soundbox app main-------------\n");

#ifdef USE_LVGL_V8_UI_DEMO
    int lvgl_main_task_init(void);
    lvgl_main_task_init();
#endif

    int ret = play_tone_file_callback(get_tone_files()->power_on, NULL, poweron_tone_play_end_callback);//播放开机提示音
    if (ret) {
        poweron_tone_play_end_callback(NULL, STREAM_EVENT_STOP);
    }
}
  • 2.各模式app,音箱工程可主要分为:local_music本地音乐模式、net_music网络音乐模式、linein_music线路输入模式、pc模式、tws模式、蓝牙模式、录音模式,每个模式都有注册对应的APP

    如net_music.c:

static const struct application_operation net_music_ops = {
    .state_machine  = net_music_state_machine,
    .event_handler  = net_music_event_handler,
    .msg_handler    = net_music_msg_handler,
};

REGISTER_APPLICATION(net_music) = {
    .name   = "net_music",
    .ops    = &net_music_ops,
    .state  = APP_STA_DESTROY,
};
  • 通过REGISTER_APPLICATION注册net_music app,每个app有各自的net_music_state_machine状态机、net_music_event_handler事件处理器、net_music_msg_handler消息处理器。

static int net_music_key_click(struct key_event *key)
{
    int ret = FALSE;

    switch (key->value) {
    case KEY_OK:
        app_send_message(APP_MSG_NET_MUSIC_PP, 1, 1);
        break;
//...........
}
  • 按键事件通过app_send_message通过消息队列向app_core任务发送消息,app_core会调用对应的net_music_msg_handler执行相应的操作。

  • 通过REGISTER_APP_EVENT_HANDLER注册单个事件处理函数,如net_app_cfg.c:

REGISTER_APP_EVENT_HANDLER(net_cfg_key_event) = {
.event      = SYS_KEY_EVENT,
.from       = KEY_EVENT_FROM_KEY,
.handler    = net_cfg_key_event_handler,
};

7.4. wifi_soundbox工程功能配置说明

  • 1.音频播放部分:编解码流程可参考“音频部分”文档中“音频编码”和“音频解码”
    • 1.1本地播放可选择FLASH、SD卡和U盘之间切换,通过扫描文件和搜索音频格式进行选择解码播放。

    /*local_music.c*/
    //设备切换
        case APP_MSG_LOCAL_MUSIC_CHANGE_DEV:
            log_info("change play dev");
            __this->mount_play_logo = NULL;
            // 获取下一个设备
            u8 auto_next_dev = ((msg[0] == APP_MSG_LOCAL_MUSIC_AUTO_NEXT_DEV) ? 1 : 0);
            logo = music_player_get_dev_next(__this->player_hd, auto_next_dev);
            if (logo == NULL) { //若找不到下一个设备,不响应设备切换
                break;
            }
            __this->suspend_flag = 0;
            log_info("next dev is %s", logo);
            //切换设备前先保存当前的设备断点信息(解码信息和文件信息)
            if (music_player_get_playing_breakpoint(__this->player_hd, __this->breakpoint, 1) == TRUE) {
                music_player_stop(__this->player_hd, 0); //先停止当前播放,防止下一步操作VM卡顿
                breakpoint_vm_write(__this->breakpoint, dev_manager_get_logo(__this->player_hd->dev)); // 保存断点信息
            }
    #if (MUSIC_DEVICE_TONE_EN)
            if (0 == music_device_tone_play(logo)) { //播放下一个设备的提示音
                break;
            }
    #endif
            // 播放下一设备
            if (TRUE == breakpoint_vm_read(__this->breakpoint, logo)) { //读取设备断点信息
                err = music_player_play_by_breakpoint(__this->player_hd, logo, __this->breakpoint); //从断点处继续播放
            } else {
                err = music_player_play_first_file(__this->player_hd, logo); //从第一个文件开始播放
            }
            break;
    
    /*local_player.c*/
    //从断点处播放
    int music_player_play_by_breakpoint(struct music_player *player_hd, const char *logo, breakpoint_t *bp)
    {
        u32 bp_flag = 1;
        if (bp == NULL) {
            return music_player_play_first_file(player_hd, logo); // 无断点则从头播放
        }
        if (logo == NULL) {
            music_player_stop(player_hd, 0);
            if (dev_manager_online_check(player_hd->dev, 1) == 0) {
                return MUSIC_PLAYER_ERR_DEV_OFFLINE; // 设备离线错误
            }
        } else {
            music_player_stop(player_hd, 1); // 停止当前播放
            // 查找指定设备
            player_hd->dev = dev_manager_find_spec(logo, 1);
            if (player_hd->dev == NULL) {
                return MUSIC_PLAYER_ERR_DEV_NOFOUND;
            }
            //验证断点有效性
            if (strcmp(logo, "sdfile")) {
                bp_flag = 0;
                set_bp_info(bp->sclust, bp->fsize, &bp_flag); //断点若有效把bp_flag置1,注意后面要用put_bp_info释放资源
            }
            //设备文件扫描扫盘,获取文件列表
            player_hd->fsn = file_manager_scan_disk(player_hd->dev, NULL, scan_parm, cycle_mode, (scan_callback_t *)player_hd->parm.scan_cb);
        }
        //错误检查
        if (player_hd->fsn == NULL) {
            put_bp_info();
            return MUSIC_PLAYER_ERR_FSCAN;
        }
        if (!bp_flag) { //断点无效
            put_bp_info();
            return MUSIC_PLAYER_ERR_PARM;
        }
        //查找文件
        player_hd->file = file_manager_select(player_hd->dev, player_hd->fsn, FSEL_BY_SCLUST, bp->sclust, (scan_callback_t *)player_hd->parm.scan_cb);//根据文件簇号查找断点文件
        put_bp_info();
        if (player_hd->file == NULL) {
            return MUSIC_PLAYER_ERR_FILE_NOFOUND;
        }
        //文件有效性验证
        struct vfs_attr attr = {0};
        fget_attrs(player_hd->file, &attr);
        if (bp->fsize != attr.fsize) {
            return MUSIC_PLAYER_ERR_PARM;
        }
    
        //启动解码器
        int err = music_player_decode_start(player_hd, player_hd->file, &(bp->dbp));
        if (err == MUSIC_PLAYER_SUCC) {
            //选定新设备播放成功后,需要激活当前设备
            dev_manager_set_active(player_hd->dev);
            log_info("%s ok", __FUNCTION__);
        }
        return err;
    }
    
    • 1.2网络播歌:可进行网络音频资源播放或使用DLNA功能:通过ai_server平台服务器进行DLNA投播,在同一局域网可通过手机音乐app的dlna功能在设备上投播。通过将网络文件数据缓存在网络缓存区后进行读取播放。

      • 注意:代码首次下载到设备不会自动连接wifi,需长按KEY_MODE键进入配网模式,手机连接上wifi后通过杰理智能机器人微信小程序对设备进行配网(声波配网、蓝牙配网等),连接wifi后会将wifi信息保存,下次开机自动连接。

    • wifi_app_task.c:wifi初始化及wifi事件处理

    static void wifi_app_task(void *priv)  //主要是create wifi 线程的
    {
        wifi_set_store_ssid_cnt(NETWORK_SSID_INFO_CNT); //设置WiFi存储的SSID数量
    
    #ifdef CONFIG_DUER_SDK_ENABLE
        u8 airkiss_aes_key[16] = {
            0x65, 0x31, 0x63, 0x33, 0x36, 0x31, 0x63, 0x63,
            0x32, 0x39, 0x65, 0x34, 0x33, 0x66, 0x62, 0x38
        };
        wifi_set_airkiss_key(airkiss_aes_key);
    #endif
    
    #if 0
        wifi_set_smp_cfg_scan_all_channel(1);
        wifi_set_smp_cfg_airkiss_recv_ssid(1);
    #endif
    
        wifi_set_sta_connect_timeout(CONNECT_TIMEOUT_SEC); //WiFi连接超时时间
    
        wifi_set_event_callback(wifi_event_callback); //WiFi 事件回调
    
    #if !IP_NAPT_EXT
        struct wifi_mode_info info;
        if (0 == wifi_get_mode_stored_info(&info)) { //若成功读取到保存的wifi数据信息
            wifi_and_network_on(); //启用 WiFi 和网络功能
            rf_coexistence_scene_enter(RF_COEXISTENCE_SCENE_WIFI_FAST_CONNECT, 5000);
        }
    #endif
    }
    
    static int wifi_event_callback(void *network_ctx, enum WIFI_EVENT event)
    {
        int ret = 0;
        struct net_event net = {0};
    
        switch (event) {
    
        case WIFI_EVENT_MODULE_INIT:// WiFi模块初始化完成
            wifi_event_module_init();
            break;
        case WIFI_EVENT_MODULE_START:// WiFi模块启动
            log_info("network_user_callback->WIFI_EVENT_MODULE_START");
            wifi_event_module_start();
            break;
        ..........
        ..........
        case WIFI_EVENT_STA_CONNECT_SUCC:// 成功连接目标wifi
            log_info("network_user_callback->WIFI_STA_CONNECT_SUCC, ch=%d", wifi_get_channel());
            ret = wifi_event_sta_connect_succ();
            break;
        case WIFI_EVENT_STA_CONNECT_TIMEOUT_NOT_FOUND_SSID:// 超时未找到指定SSID
            log_info("network_user_callback->WIFI_STA_CONNECT_TIMEOUT_NOT_FOUND_SSID");
            net.event = NET_CONNECT_TIMEOUT_NOT_FOUND_SSID;
            net_event_notify(NET_EVENT_FROM_WIFI, &net);
            break;
        case WIFI_EVENT_STA_CONNECT_ASSOCIAT_FAIL:// 关联失败
            log_info("network_user_callback->WIFI_STA_CONNECT_ASSOCIAT_FAIL");
            net.event = NET_CONNECT_ASSOCIAT_FAIL;
            net_event_notify(NET_EVENT_FROM_WIFI, &net);
            break;
        case WIFI_EVENT_STA_CONNECT_ASSOCIAT_TIMEOUT:// 关联超时
            log_info("network_user_callback->WIFI_STA_CONNECT_ASSOCIAT_TIMEOUT");
            break;
        case WIFI_EVENT_STA_NETWORK_STACK_DHCP_SUCC:// DHCP获取IP成功
            log_info("network_user_callback->WIFI_EVENT_STA_NETWPRK_STACK_DHCP_SUCC");
            wifi_event_sta_network_stack_dhcp_succ();
            break;
        case WIFI_EVENT_STA_DISCONNECT: //断开连接
            log_info("network_user_callback->WIFI_STA_DISCONNECT");
            wifi_event_sta_disconnect();
            break;
        case WIFI_EVENT_STA_NETWORK_STACK_DHCP_TIMEOUT:// DHCP超时
            log_info("network_user_callback->WIFI_EVENT_STA_NETWPRK_STACK_DHCP_TIMEOUT");
            break;
        ..........
        ..........
        case WIFI_EVENT_SMP_CFG_TIMEOUT: // 智能配网超时
            log_info("network_user_callback->WIFI_EVENT_SMP_CFG_TIMEOUT");
            net.event = NET_EVENT_SMP_CFG_TIMEOUT;
            net_event_notify(NET_EVENT_FROM_WIFI, &net);
            break;
        case WIFI_EVENT_SMP_CFG_COMPLETED:// 智能配网完成
            log_info("network_user_callback->WIFI_EVENT_SMP_CFG_COMPLETED");
            net.event = NET_SMP_CFG_COMPLETED;
            net_event_notify(NET_EVENT_FROM_WIFI, &net);
            break;
        ..........
        ..........
        case WIFI_EVENT_AP_ON_ASSOC:// 有客户端连接到本设备热点
            wifi_event_ap_on_assoc(network_ctx);
            break;
        case WIFI_EVENT_AP_ON_DISCONNECTED:// 客户端从本设备热点断开
            wifi_event_ap_on_disconnected(network_ctx);
            break;
        default:
            break;
        }
    
        return ret;
    }
    
    • net_app_cfg.c:wifi配网相关

    static int net_smp_cfg_event_handler(void *evt) //接收wifi信息后启动wifi连接
    {
        struct net_event *event = (struct net_event *)evt;
    
        switch (event->event) {
        case NET_EVENT_SMP_CFG_FINISH:
            log_info("NET_EVENT_SMP_CFG_FINISH");
            if (is_in_config_network_state()) {// 检查当前处于“网络配置状态”
                play_tone_file(get_tone_files()->net_ssid_recv);// 1. 播放“接收SSID”提示音
                config_network_stop();// 2. 停止配网模式(退出监听状态,避免重复接收)
                config_network_connect();// 3. 启动网络连接(使用接收的SSID和密码连接WiFi)
    #if TCFG_SMART_VOICE_ENABLE
                audio_smart_voice_detect_open(JL_KWS_COMMAND_KEYWORD);
    #endif
            } else {// 若未处于配置状态,仅播放提示音并连接网络
                play_tone_file(get_tone_files()->net_ssid_recv);
                config_network_connect();
            }
            __this->recv_ssid_flag = 1;
            __this->request_connect_flag = 1;
            break;
        default:
            break;
        }
    
        return FALSE;
    }
    
    • ai.c:连接ai云服务器相关

    static int ai_net_event_handler(void *evt)
    {
        struct net_event *event = (struct net_event *)evt;
    
        switch (event->event) {
        case NET_EVENT_CONNECTED:
            log_info("AI_NET_EVENT_CONNECTED");
            ai_server_connect(); // 连接AI云服务
            break;
        case NET_EVENT_DISCONNECTED:
            log_info("AI_NET_EVENT_DISCONNECTED");
            ai_server_disconnect(); // 断开AI服务
            break;
        default:
            break;
        }
    
        return FALSE;
    }
    
    • net_download接口:

    • 1.3tws模式:实现source端、sink端设备协同
      • 在sdk_config.h中选择配对方式(按键发起配对、开机自动配对、测试盒/充电仓配对) 及声道选择:

    /*sdk_config.h*/
    #define TCFG_BT_TWS_PAIR_MODE CONFIG_TWS_PAIR_BY_CLICK // 配对方式
    #define TCFG_BT_TWS_CHANNEL_SELECT CONFIG_TWS_MASTER_AS_LEFT // 声道选择
    
    
    /*app_config.h*/
    /* 配对方式选择 */
    #define CONFIG_TWS_PAIR_BY_CLICK                0   //按键发起配对
    #define CONFIG_TWS_PAIR_BY_AUTO                 1   //开机自动配对
    #define CONFIG_TWS_PAIR_BY_BOX                  2   //测试盒/充电仓配对
    
    /* 声道确定方式选择 */
    #define CONFIG_TWS_MASTER_AS_LEFT               0   //主机作为左耳
    #define CONFIG_TWS_MASTER_AS_RIGHT              1   //主机作为右耳
    #define CONFIG_TWS_AS_LEFT                      2   //固定左耳
    #define CONFIG_TWS_AS_RIGHT                     3   //固定右耳
    #define CONFIG_TWS_START_PAIR_AS_LEFT           4   //双击发起配对的耳机做左耳
    #define CONFIG_TWS_START_PAIR_AS_RIGHT          5   //双击发起配对的耳机做右耳
    #define CONFIG_TWS_EXTERN_UP_AS_LEFT            6   //外部有上拉电阻作为左耳
    #define CONFIG_TWS_EXTERN_DOWN_AS_LEFT          7   //外部有下拉电阻作为左耳
    #define CONFIG_TWS_EXTERN_UP_AS_RIGHT           8   //外部有上拉电阻作为右耳
    #define CONFIG_TWS_EXTERN_DOWN_AS_RIGHT         9   //外部有下拉电阻作为右耳
    #define CONFIG_TWS_CHANNEL_SELECT_BY_BOX        10  //充电仓/测试盒决定左右耳
    
    • source/sink角色决策:

    /*local_tws.c*/
    static int bt_local_tws_cmd_handler(void *evt)
    {
     ..............
     ..............
        case CMD_TWS_CONNECT_MODE_REPORT:
            if (cmd[1] == APP_MODE_BT && !current_app_in_mode(APP_MODE_BT)) {   //当前仅考虑了除蓝牙模式其他模式都为Source的情况
                log_info("CMD_TWS_CONNECT_MODE_REPORT:BECOME TO SOURCE");
                local_tws_become_to_source(get_current_app_mode()); // 本地成为 Source
            } else if (cmd[1] != APP_MODE_BT && current_app_in_mode(APP_MODE_BT)) { //对端是非蓝牙模式,但本地是蓝牙模式
                log_info("CMD_TWS_CONNECT_MODE_REPORT: MODE REPORT");
                local_tws_connect_mode_report(); //// 向对端报告本地模式
            } else if (cmd[1] == APP_MODE_BT && current_app_in_mode(APP_MODE_BT)) { //双方都处于蓝牙模式
                //都处于蓝牙模式不需要操作
                log_info("Both in BT MODE");
            } else {
                //两边都处于非蓝牙模式,有可能一边在音乐模式下播歌,另一边开机切到音乐模式才连上
                bool local_play_status = FALSE;
                struct local_tws_mode_ops *ops;
                list_for_each_local_tws_ops(ops) {
                    if (!strcmp(ops->name, __get_current_app()->name) && ops->get_play_status) {
                        local_play_status = ops->get_play_status();
                    }
                }
                log_info("local_play_status:%d remote_play_status:%d", local_play_status, cmd[2]);
                // 根据播放状态决策
                if (local_play_status == FALSE && cmd[2] == TRUE) { // 本地未播放,对端在播放
                    local_tws_connect_mode_report(); // 同步到对端(成为 Sink)
                } else {
                    local_tws_become_to_source(get_current_app_mode()); // 本地成为 Source
                }
            }
            break;
     ..............
     ..............
    }
    
  • 2.蓝牙播歌和蓝牙通话部分
    • 2.1需在app_conifg中配置开启蓝牙功能宏(选择下图中需要宏打开),进入蓝牙模式自动打开蓝牙,蓝牙连接后可进行蓝牙播歌和通话功能

    • bt_mode_init()入口:调用bt_function_select_init()函数进行蓝牙功能初始化,包括蓝牙支持连接的个数、通话使用msbc或cvsd、电量更新的周期时间、回连搜索时间长度、回连时超时参数设置、简易配对参数等。btstack_init()初始化蓝牙协议栈。

    • 调用REGISTER_APP_EVENT_HANDLER()函数分别注册HCI层事件和蓝牙连接事件回调函数,处理相应事件,包括设备查询完成、蓝牙连接建立完成、请求确认配对、蓝牙第一次连接、第二次连接、初始化完成、断开连接等。

    //*********************************************************************************//
    //                                  EDR和BLE配置                                   //
    //*********************************************************************************//
    #ifdef CONFIG_BT_ENABLE
    
    #define TCFG_BT_MODE                            BT_NORMAL
    
    #define BT_EMITTER_EN                           1
    #define BT_RECEIVER_EN                          2
    
    #define TCFG_POWER_ON_ENABLE_EMITTER            0   //开机自动打开发射器
    #define TCFG_POWER_ON_ENABLE_BT                 0   //开机自动打开经典蓝牙
    #define TCFG_POWER_ON_ENABLE_BLE                0   //开机自动打开BLE
    #define TCFG_USER_BT_CLASSIC_ENABLE             1   //经典蓝牙功能
    #define TCFG_USER_BLE_ENABLE                    1   //BLE功能使能
    #define TCFG_USER_EMITTER_ENABLE                0   //蓝牙发射功能
    #define TCFG_BT_DUAL_CONN_ENABLE                1   //经典蓝牙支持同时连接2台设备
    
    #endif
    
    /*bt.c*/
    static int bt_mode_init(void)
    {
    
        .........
        .........
        bt_function_select_init();
        bredr_handle_register();
        btstack_init(); //蓝牙栈初始化
        .........
        .........
    }
    
     /*bt_event_func.c*/
    static void bt_function_select_init(void)
    {
        /* set_bt_data_rate_acl_3mbs_mode(1); */
    #if TCFG_BT_DUAL_CONN_ENABLE
        bt_set_user_ctrl_conn_num(2); //设置蓝牙支持连接的个数,主要用于控制控制可发现可连接和回连流程
    #else
        bt_set_user_ctrl_conn_num(1);
    #endif
    #if (defined(TCFG_BT_SUPPORT_MSBC) && TCFG_BT_SUPPORT_MSBC)
        bt_set_support_msbc_flag(TCFG_BT_SUPPORT_MSBC); //配置通话使用16k的msbc还是8k的cvsd
    #endif
    
    #if (defined(TCFG_BT_SUPPORT_AAC) && TCFG_BT_SUPPORT_AAC)
        bt_set_support_aac_flag(TCFG_BT_SUPPORT_AAC); //配置协议栈使用支持AAC的信息
        bt_set_aac_bitrate(TCFG_BT_AAC_BITRATE);
    #endif
    
    #if (defined(TCFG_BT_SUPPORT_LHDC) && TCFG_BT_SUPPORT_LHDC)
        bt_set_support_lhdc_flag(TCFG_BT_SUPPORT_LHDC); //配置协议栈使用支持LHDC的信息
    #endif
    
    #if (defined(TCFG_BT_SUPPORT_LHDC_V5) && TCFG_BT_SUPPORT_LHDC_V5)
        bt_set_support_lhdc_v5_flag(TCFG_BT_SUPPORT_LHDC_V5);
    #endif
    
    #if (defined(TCFG_BT_SUPPORT_LDAC) && TCFG_BT_SUPPORT_LDAC)
        bt_set_support_ldac_flag(TCFG_BT_SUPPORT_LDAC);
    #endif
    
    #if TCFG_BT_DISPLAY_BAT_ENABLE
        bt_set_update_battery_time(60); //设置电量显示发送更新的周期时间
    #else
        bt_set_update_battery_time(0); //为0表示关闭电量显示功能
    #endif
        /*回连搜索时间长度设置,可使用该函数注册使用,ms单位,u16*/
        bt_set_page_timeout_value(0);
    
        /*回连时超时参数设置。ms单位。做主机有效*/
        bt_set_super_timeout_value(8000);
    
    #if TCFG_BT_DUAL_CONN_ENABLE
        bt_set_auto_conn_device_num(2);
    #endif
    
    #if TCFG_BT_VOL_SYNC_ENABLE
        vol_sys_tab_init();
    #endif
        /* io_capabilities
        * 0: Display only 1: Display YesNo 2: KeyboardOnly 3: NoInputNoOutput
        *  authentication_requirements: 0:not protect  1 :protect
        */
    #if TCFG_BT_BQB_PROFILE_TEST_ENABLE
        bt_set_simple_pair_param(1, 0, 2); //设置简易配对参数
    #else
        bt_set_simple_pair_param(3, 0, 2);
    #endif
    
    #if 0
        /*测试盒连接获取参数需要的一些接口注册*/
        bt_testbox_ex_info_get_handle_register(TESTBOX_INFO_VBAT_VALUE, get_vbat_value);
        bt_testbox_ex_info_get_handle_register(TESTBOX_INFO_VBAT_PERCENT, get_vbat_percent);
        bt_testbox_ex_info_get_handle_register(TESTBOX_INFO_BURN_CODE, sdfile_get_burn_code);
        bt_testbox_ex_info_get_handle_register(TESTBOX_INFO_SDK_VERSION, bt_get_sdk_ver_info);
    #endif
    
        bt_set_sbc_cap_bitpool(TCFG_BT_SBC_BITPOOL);
    
    #if TCFG_USER_BLE_ENABLE
        u8 tmp_ble_addr[6];
    #if TCFG_BT_BLE_BREDR_SAME_ADDR
        memcpy(tmp_ble_addr, (void *)bt_get_mac_addr(), 6);
    #else
        bt_make_ble_address(tmp_ble_addr, (void *)bt_get_mac_addr());
    #endif
        le_controller_set_mac((void *)tmp_ble_addr);
        log_info("-----edr + ble 's address-----");
        put_buf((void *)bt_get_mac_addr(), 6);
        put_buf((void *)tmp_ble_addr, 6);
    #endif
    
    #if (TCFG_BT_MODE != BT_NORMAL)
        set_bt_enhanced_power_control(1);
    #endif
    
    #if (TCFG_BT_SUPPORT_PROFILE_PBAP==1)
        ////设置蓝牙设备类型
        bt_change_hci_class_type(BD_CLASS_CAR_AUDIO);
    #else
        bt_change_hci_class_type(BD_CLASS_LOUDSPEAKER);
    #endif
    }
    
    static void bredr_handle_register(void)
    {
    #if TCFG_BT_SUPPORT_PROFILE_SPP
    #if APP_ONLINE_DEBUG
        online_spp_init();
    #endif
        bt_spp_data_deal_handle_register(spp_data_handler); //支持串口功能的数据处理接口
    #endif
    
        bt_fast_test_handle_register(bt_fast_test_api);//测试盒快速测试接口
    
    #if TCFG_BT_VOL_SYNC_ENABLE
        bt_music_vol_change_handle_register(bt_set_music_device_volume, bt_get_phone_device_vol);
    #endif
    #if TCFG_BT_DISPLAY_BAT_ENABLE
        bt_get_battery_percent_handle_register(bt_get_battery_value);   /*电量显示获取电量的接口*/
    #endif
    
        //样机进入dut被测试仪器链接上回调
        bt_dut_test_handle_register(bt_dut_api);
    
        //获取远端设备蓝牙名字回调
        bt_read_remote_name_handle_register(bt_read_remote_name);
    
    #if TCFG_BT_MUSIC_INFO_ENABLE
        //获取歌曲信息回调
        bt_music_info_handle_register(user_get_bt_music_info);
    #endif
    }
    
    //hci层事件处理
    static int bt_hci_event_handler(void *evt)
    {
        struct bt_event *bt = (struct bt_event *)evt;
    
        //对应原来的蓝牙连接上断开处理函数  ,bt->value=reason
        log_info("bt_hci_event_handler reason 0x%x 0x%x", bt->event, bt->value);
    
        switch (bt->event) {
        case HCI_EVENT_INQUIRY_COMPLETE: //设备查询完成
            log_info("HCI_EVENT_INQUIRY_COMPLETE");
            bt_hci_event_inquiry(bt);
            break;
        case HCI_EVENT_USER_CONFIRMATION_REQUEST:
            log_info("HCI_EVENT_USER_CONFIRMATION_REQUEST");
            ///<可通过按键来确认是否配对 1:配对   0:取消
            bt_send_pair(1);
            break;
        case HCI_EVENT_USER_PASSKEY_REQUEST:
            log_info("HCI_EVENT_USER_PASSKEY_REQUEST");
            ///<可以开始输入6位passkey
            break;
        case HCI_EVENT_USER_PRESSKEY_NOTIFICATION:
            log_info("HCI_EVENT_USER_PRESSKEY_NOTIFICATION %x", bt->value);
            ///<可用于显示输入passkey位置 value 0:start  1:enrer  2:earse   3:clear  4:complete
            break;
        case HCI_EVENT_PIN_CODE_REQUEST:
            log_info("HCI_EVENT_PIN_CODE_REQUEST");
            break;
        case HCI_EVENT_VENDOR_NO_RECONN_ADDR:
            log_info("HCI_EVENT_VENDOR_NO_RECONN_ADDR");
            bt_hci_event_disconnect(bt);
            break;
        case HCI_EVENT_DISCONNECTION_COMPLETE:
            log_info("HCI_EVENT_DISCONNECTION_COMPLETE");
            if (!__this->enable) {
                break;
            }
            if (bt->value == ERROR_CODE_CONNECTION_TIMEOUT) {
                bt_hci_event_connection_timeout(bt);
            }
            bt_hci_event_disconnect(bt);
            break;
        case BTSTACK_EVENT_HCI_CONNECTIONS_DELETE:
        case HCI_EVENT_CONNECTION_COMPLETE:
            log_info("HCI_EVENT_CONNECTION_COMPLETE");
            switch (bt->value) {
            case ERROR_CODE_SUCCESS:
                log_info("ERROR_CODE_SUCCESS");
                testbox_in_ear_detect_test_flag_set(0);
                break;
            case ERROR_CODE_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED:
            case ERROR_CODE_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES:
            case ERROR_CODE_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR:
            case ERROR_CODE_CONNECTION_ACCEPT_TIMEOUT_EXCEEDED:
            case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
            case ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST:
            case ERROR_CODE_AUTHENTICATION_FAILURE:
                //case CUSTOM_BB_AUTO_CANCEL_PAGE:
                bt_hci_event_disconnect(bt);
                break;
            case ERROR_CODE_CONNECTION_TIMEOUT:
                log_info("ERROR_CODE_CONNECTION_TIMEOUT");
                bt_hci_event_connection_timeout(bt);
                break;
            default:
                break;
            }
            break;
        default:
            break;
        }
    
        return FALSE;
    }
    
    /*
    * 对应原来的状态处理函数,连接,电话状态等
    */
    static int bt_connction_status_event_handler(void *evt)
    {
        struct bt_event *bt = (struct bt_event *)evt;
        u8 a2dp_vol_mac[6];
    
        switch (bt->event) {
        case BT_STATUS_INIT_OK:
            /* 蓝牙初始化完成 */
            log_info("BT_STATUS_INIT_OK");
            bt_status_init_ok();
            break;
        case BT_STATUS_SECOND_CONNECTED:
            bt_clear_current_poweron_memory_search_index(0);
        case BT_STATUS_FIRST_CONNECTED:
            log_info("BT_STATUS_CONNECTED");
    #if TCFG_USER_TWS_ENABLE
            bt_tws_phone_connected();
    #endif
    #if TCFG_BT_SUPPORT_PROFILE_MAP
            bt_cmd_prepare(USER_CTRL_MAP_READ_TIME, 0, NULL);
    #endif
    #if TCFG_BT_VOL_SYNC_ENABLE
            u8 *vol_addr = malloc(6);
            if (vol_addr) {
                memcpy(vol_addr, bt->args, 6);
                __this->vol_sync_timer = sys_timeout_add(vol_addr, bt_connect_vol_sync, 2000);
            }
    #endif
            break;
        case BT_STATUS_FIRST_DISCONNECT:
        case BT_STATUS_SECOND_DISCONNECT:
            log_info("BT_STATUS_DISCONNECT");
            break;
        case BT_STATUS_CONN_A2DP_CH:
            log_info("BT_STATUS_CONN_A2DP_CH");
            memcpy(a2dp_vol_mac, bt->args, 6);
            app_audio_bt_volume_save_mac(a2dp_vol_mac);
            break;
        case BT_STATUS_DISCON_A2DP_CH:
            log_info("BT_STATUS_DISCON_A2DP_CH");
            break;
        case BT_STATUS_AVRCP_INCOME_OPID:
    #define AVC_VOLUME_UP                   0x41
    #define AVC_VOLUME_DOWN                 0x42
    #define AVC_PLAY                            0x44
    #define AVC_PAUSE                           0x46
            log_info("BT_STATUS_AVRCP_INCOME_OPID:0x%x", bt->value);
            if (bt->value == AVC_VOLUME_UP) {
    
            } else if (bt->value == AVC_VOLUME_DOWN) {
            } else if (bt->value == AVC_PLAY) {
                bt_music_player_time_timer_deal(1);
            } else if (bt->value == AVC_PAUSE) {
                bt_music_player_time_timer_deal(0);
            }
            break;
        case BT_STATUS_AVRCP_VOL_CHANGE:
            if (!__this->vol_sync_timer &&
    #if TCFG_USER_TWS_ENABLE
                tws_api_get_role() != TWS_ROLE_SLAVE &&
    #endif
                !a2dp_player_get_btaddr(a2dp_vol_mac)) {
                bt_set_music_device_volume(bt->value);
            }
            break;
        default:
            log_info("BT STATUS DEFAULT : 0x%x", bt->event);
            break;
        }
    
        return FALSE;
    }
    
  • 3.设备配网及使用第三方平台
    • 3.1配网:在app_config.h选择第三方平台,例如使用图灵平台,如下打开宏#define CONFIG_DUER_SDK_ENABLE //使用百度平台

    • 当前仅可通过杰理平台配网,第三方平台需自行移植接通。

    //==============网络配置.json--第三方平台相关配置==========/
    #if TCFG_SERVER_ASSIGN_PROFILE
    #define CONFIG_SERVER_ASSIGN_PROFILE            //第三方平台的profile由杰理服务器分配
    #endif
    #if TCFG_PROFILE_UPDATE
    #define CONFIG_PROFILE_UPDATE                   //每次开机都从杰理服务器获取第三方平台的profile,不读flash
    #endif
    #if TCFG_HTTP_SERVER_ENABLE
    #define CONFIG_HTTP_SERVER_ENABLE               //HTTP服务器
    #endif
    #if TCFG_FTP_SERVER_ENABLE
    #define CONFIG_FTP_SERVER_ENABLE                //FTP服务器
    #endif
    #if TCFG_DLNA_SDK_ENABLE
    #define CONFIG_DLNA_SDK_ENABLE                  //打开DLNA音乐播放功能
    #endif
    #if TCFG_AI_SERVER == TCFG_DUER_ENABLE
    #define CONFIG_DUER_SDK_ENABLE                  //使用百度平台
    #elif TCFG_AI_SERVER == TCFG_AI_SERVER_DISABLE
    // #define CONFIG_TURING_SDK_ENABLE             //使用图灵平台
    // #define CONFIG_DUI_SDK_ENABLE                //使用思必驰DUI平台
    // #define CONFIG_ALI_SDK_ENABLE                //使用天猫精灵平台
    // #define CONFIG_TVS_SDK_ENABLE                //使用腾讯云平台
    // #define CONFIG_TELECOM_SDK_ENABLE            //电信云平台
    // #define CONFIG_JL_CLOUD_SDK_ENABLE           //打开使用杰理云平台
    
    /*************电信云平台配网方式选择*************/
    #ifdef CONFIG_TELECOM_SDK_ENABLE
    #define CONFIG_APLINK_NET_CFG                   //电信AP配网。注意:1.电信AP配网不能与elink无感配网同时使能。2.wifi库需要支持ap
    #ifndef CONFIG_APLINK_NET_CFG
    #define CONFIG_ELINK_QLINK_NET_CFG              //电信elink无感配网。注意:elink无感配网不能与AIRKISS配网同时使能。
    #endif
    #define CONFIG_CTEI_DEVICE_ENABLE               //电信非蜂窝类串码对接设备使用
    //#define CONFIG_MC_DEVICE_ENABLE               //电信mc接口类直连设备使用
    #endif
    #endif
    
    • 未选用第三方平台时,可使用杰理智能机器人配网工具进行配网

    • 通过长按K2进入配网模式,使用声波、蓝牙可进行配置网络,配网时手机与选择配置的网络必须为同一网络。

    • 可在app_config.h选择配网方式

    //==============网络配置.json--配网相关配置==============/
    #if TCFG_WIFI_ENABLE
    // #define CONFIG_AIRKISS_NET_CFG               //AIRKISS配网
    // #define CONFIG_WSC_NET_CFG                   //WSC配网
    #endif
    
    #if TCFG_ACOUSTIC_COMMUNICATION_NODE_ENABLE
    #define CONFIG_ACOUSTIC_COMMUNICATION_ENABLE    //声波配网
    #endif
    
    • 蓝牙配网须在app_config.h配置第三方协议,开启NET_CFG_EN:

    #ifndef THIRD_PARTY_PROTOCOLS_SEL
    #define THIRD_PARTY_PROTOCOLS_SEL               NET_CFG_EN
    #endif
    
    • 对应功能实现:

    /*multi_protocol_main.c*/
    #if (THIRD_PARTY_PROTOCOLS_SEL & NET_CFG_EN) && TCFG_POWER_ON_ENABLE_BLE
        le_net_cfg_all_init();
    #endif