QCC300x学习笔记:自定义HFP AT command

为了方便大家学习,现与我爱蓝牙网联合推出【QCC300x/CSR867x/QCC30xx/QCC51xx开发板】。

技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠独家学习资料)
——————————正文分割线———————————–

1. 引言

最近在做一个蓝牙发射器+接收器的项目,客户提了几个定制需求:

  • 接收器能同时连接一个蓝牙发射器和一个普通手机
  • 有两个按键,按键一呼叫蓝牙发射器,按键二呼叫手机
  • 蓝牙发射器和手机与接收器的连接次序不固定

一般地,在确定连接次序的前提下,按键一对应事件[Initiate voice dial 1],可呼叫第一个连接的设备,按键二对应事件[Initiate voice dial 2],可呼叫第二个连接的设备。若不确定连接次序,可能第一个连接的设备是手机,这就导致按键一无法呼叫蓝牙发射机。

为了让按键一总能呼叫蓝牙发射器,有必要在发射器和接收器之间建立匹配校验机制。当发射器连接上任何设备后,其发送匹配校验请求,如对方返回正确的匹配响应,即彼此都知晓对方是匹配的设备类型,接收器可以指定按键一关联此设备,从而不用考虑连接次序,总能呼叫到发射器。

发射器 接收器 蓝牙连接建立 匹配校验请求? 设备连接关联到按键一 匹配校验响应 匹配校验通过 发射器 接收器

匹配校验消息的传输通道可以是HFP、GATT、SPP等协议。本项目没有GATT、SPP协议的使用场景,用既有的HFP协议传输自定义的AT command是一个省力又可靠的解决方案。

2. 什么是HFP AT command

AT command是HFP协议的一部分,用于在AG(Audio Gateway)和HF(Hands-Free Unit)两者之间通过RFCOMM通道传输控制信令。
QCC300x学习笔记:自定义HFP AT command_第1张图片
QCC300x学习笔记:自定义HFP AT command_第2张图片
举一个简单的例子来说明AT command的用途。某些场景下,AG的回声消除和噪声抑制功能需要被关闭,此时HF会发送特定的AT command给AG,流程图如下:
QCC300x学习笔记:自定义HFP AT command_第3张图片
可以看到HF发送AT+NREC=0给AG,AG根据自身情况返回OK或ERROR给HF。这里HFP给出了关于AT command的几个约定:

  • HF发送给AG的command,要有“AT+”,0代表关闭,1代表使能
  • NREC是HFP协议原生支持的命令
  • AG发送给HFP的command可以没有“AT+”

参考HFP V1.7全文,没有找到可用于匹配校验的AT command,于是需要我们自定义一个AT command来完成匹配校验。

相类似的做法可参考苹果设备的AT+BVRA命令,可在手机端(AG)发起与耳机端(HF)的SCO连接:

The accessory should expect the following command sequence:
· The iOS device sends a +BVRA:1 event to the accessory.
· The iOS device launches a Siri session and creates a SCO connection for the
audio.
· When the Siri session is finished, the iOS device sends a +BVRA:0 result code
to the accessory.
· The iOS device disconnects the SCO connection.
QCC300x学习笔记:自定义HFP AT command_第4张图片

3. 自定义AT command

3.1. 自定义AT command交互时序图

发射器VM 发射器AGHFP lib 接收器HFP lib 接收器VM AGHFP_INTERNAL_DEVICE_ROLE_SET "+DERL: 1" AGHFP_DEVICE_ROLE_SET_CFM success HFP_UNRECOGNISED_AT_CMD_IND sinkAtCommandsCheckDeviceRole HFP_INTERNAL_AT_CMD_REQ “+DERL: 0” AGHFP_UNRECOGNISED_AT_CMD_IND sinkAtCommandsCheckDeviceRole 发射器VM 发射器AGHFP lib 接收器HFP lib 接收器VM

3.2. AG端程序示例

VM层发消息给AGHFP lib:

void aghfp_device_role_ind(bool role)
{
    uint16 index = 0;
    aghfpInstance *inst = theSource->aghfp_data.inst;
        
    if (inst != NULL)
    {
        for_all_aghfp_instance(index)
        {
            if (aghfp_is_connected(inst->aghfp_state))
            {
                /* send voice recognition to remote side */
                AghfpDeviceRoleSet(theSource->aghfp_data.aghfp, role);
            }
            inst++;
        }
    }
}

void AghfpDeviceRoleSet(AGHFP *aghfp, bool role)
{
	MAKE_AGHFP_MESSAGE(AGHFP_INTERNAL_DEVICE_ROLE_SET);
	message->role = role;
	MessageSend(&aghfp->task, AGHFP_INTERNAL_DEVICE_ROLE_SET, message);
}

AGHFP lib调用aghfpSendAtCmd发送自定义AT command:

void aghfpHandleDeviceRoleSet(AGHFP *aghfp, bool role)
{
	/* Send AT cmd to HF */
	const char AgMessage[] = "+DERL: 1";
	const char HfMessage[] = "+DERL: 0";
	if (role)
	{
		aghfpSendAtCmd(aghfp, AgMessage);
	}
	else
	{
		aghfpSendAtCmd(aghfp, HfMessage);
	}

	/* Send confirm to app */
	aghfpSendCommonCfmMessageToApp(AGHFP_DEVICE_ROLE_SET_CFM, aghfp, aghfp_success);
}

3.3. HF端程序示例

HF在收到AT command时,会尝试用默认支持的AT command去匹配,如匹配不上,会把AT command的字符串用AGHFP_UNRECOGNISED_AT_CMD_IND消息发送给VM来解析。

    case HFP_UNRECOGNISED_AT_CMD_IND:
    {
        sinkHandleUnrecognisedATCmd( (HFP_UNRECOGNISED_AT_CMD_IND_T*)message ) ;
    }
    break ;

我们在sinkHandleUnrecognisedATCmd中插入匹配校验处理函数。

    AT_DEBUG(("AT command = %s\n", pData));

    sinkAtCommandsCheckDeviceRole(ind, pData); // 匹配校验处理函数

    sinkAtCommandsCheckAndProcessProductionTestCommands(ind, pData);

当匹配成功后,调用HfpAtCmdRequest函数,返回预定的AT command “+DERL: 0"”。

static const char * const device_role_ag_string	            = "+DERL: 1";
static const char * const device_role_ag_string_res_success = "+DERL: 0";

static void sinkAtCommandsCheckDeviceRole(HFP_UNRECOGNISED_AT_CMD_IND_T *ind,
                                                            const char * const command_string)
{
    if(strncmp(command_string, device_role_ag_string, strlen(device_role_ag_string)) == 0)
    {
        AT_DEBUG(("Handle Device Role Ag\n"));
        
        /* Send the success response AT command */
        AT_DEBUG(("Response %s\n", device_role_ag_string_res_success));
        HfpAtCmdRequest(ind->priority, device_role_ag_string_res_success);
    }
}

在获取到的HFP_UNRECOGNISED_AT_CMD_IND_T句柄中,可以得到其对应的hfp_link_priority:

typedef struct
{
    /*! The priority of the link. */
    hfp_link_priority   priority;
    /*! The number of bytes pointed to by data.*/
    uint16    size_data;
    /*! The data that could not be parsed. The client should not attempt to
      free this pointer, the memory will be freed when the message is
      destroyed. If the client needs access to this data after the message has
      been destroyed it is the client's responsibility to copy it. */
    uint8    data[1];
} HFP_UNRECOGNISED_AT_CMD_IND_T;

/*!
    @brief Link priority is used to identify different links to
    AG devices using the order in which the devices were connected.
*/
typedef enum
{
    /*! Invalid Link. */
    hfp_invalid_link,
    /*! The link that was connected first. */
    hfp_primary_link,
    /*! The link that was connected second. */
    hfp_secondary_link
} hfp_link_priority;

有了priority,可以直接调用sinkInitiateVoiceDial(priority)向指定的设备发起呼叫。

4. 总结

自定义AT command的方法适用于仅需少量数据交互的场景。使用时注意不要影响到HFP协议自带的AT command。

5. 参考资料

  • SIG: HFP V1.7.0
  • QUALCOMM: CS-330103-UG (Audio Sink Application Custom AT Commands User Guide)
  • APPLE: BluetoothDesignGuideLines

你可能感兴趣的:(CSR8670蓝牙芯片软件开发)