对profile QPPS的分析理解

今天写的东西就深入一点了,是一个昆天科的定制协议的理解,即QPPS profile。这个profile用于手机与模块的透传,非常的实用,因为我们在BLE上使用最多的就是透传。

首先烧录程序,完成后用light blue查看服务。

在profile中只有一个服务,UUID为128位的UUID,但是可以转换为16位UUID:FEE9,在这个服务下包含有8个特征,其中第一个特征UUID为9600结尾的一个128位UUID,是用于接收APP发送的数据的特征,在APP端显示为可写的一个特征值。后面是7个用于发送数据到APP的特征值,特征值UUID后四位由9601递增至9607,一共有七个特征值。这个特征值的数目实际是可以修改的,可以改为1~7,在usr_config.h中有如下定义,更改QPPS_NOTIFY_NUM的数值就可以改变发送特征值的数目。

///Quintic private profile Role
#define CFG_PRF_QPPS
#define QPPS_NOTIFY_NUM     7
//#define CFG_TASK_QPPS     TASK_PRF1

app向第一个特征值中写入数据,发送,则系统会调用到gatt_write_cmd_ind_handler函数处理APP端的操作命令。

static int gatt_write_cmd_ind_handler(ke_msg_id_t const msgid,
                                      struct gatt_write_cmd_ind const *param,
                                      ke_task_id_t const dest_id,
                                      ke_task_id_t const src_id)
{
    uint8_t status = PRF_ERR_OK;

    if (param->conhdl == qpps_env.conhdl)
    {
        // Client Char. Configuration
		uint8_t char_index = param->handle - (qpps_env.shdl + QPPS_IDX_VAL_NTF_CFG);
        if ((param->handle > (qpps_env.shdl + QPPS_IDX_VAL_CHAR)) && ((char_index % 3) == 0))
        {
            uint16_t value = 0x0000;

            //Extract value before check
            memcpy(&value, &(param->value), sizeof(uint16_t));

            if ((value == PRF_CLI_STOP_NTFIND) || (value == PRF_CLI_START_NTF))
            {
                if (value == PRF_CLI_STOP_NTFIND)
                {
                    qpps_env.features &= ~(QPPS_VALUE_NTF_CFG << (char_index / 3));
                }
                else //PRF_CLI_START_NTF
                {
                    qpps_env.features |= QPPS_VALUE_NTF_CFG << (char_index / 3);
                }
            }
            else
            {
                status = PRF_APP_ERROR;
            }

            if (status == PRF_ERR_OK)
            {
                uint8_t *old_value;
                atts_size_t length;

                attsdb_att_get_value(param->handle, &length, &old_value);
                if (value != co_read16p(old_value))
                {
                    //Update the attribute value
                    attsdb_att_set_value(param->handle, sizeof(uint16_t), (uint8_t *)&value);

                    if(param->last)
                    {
                        //Inform APP of configuration change
                        struct qpps_cfg_indntf_ind * ind = KE_MSG_ALLOC(QPPS_CFG_INDNTF_IND,
                                                                        qpps_env.appid, TASK_QPPS,
                                                                        qpps_cfg_indntf_ind);

                        ind->char_index = (char_index / 3);
                        memcpy(&ind->cfg_val, &value, sizeof(uint16_t));

                        ke_msg_send(ind);
                    }
                }
            }
        }
        else if (param->handle == (qpps_env.shdl + QPPS_IDX_RX_DATA_VAL))
        {
			if (param->length <= QPP_DATA_MAX_LEN)
			{
				//inform APP of configuration change
				struct qpps_data_val_ind * ind = KE_MSG_ALLOC_DYN(QPPS_DAVA_VAL_IND,
																  qpps_env.appid,
																  TASK_QPPS,
																  qpps_data_val_ind, param->length);

				memcpy(&ind->conhdl, &(qpps_env.conhdl), sizeof(uint16_t));
				//Send received data to app value
				ind->length = param->length;
				memcpy(ind->data, param->value, param->length);

				ke_msg_send(ind);
			}
			else
			{
				status = QPPS_ERR_RX_DATA_EXCEED_MAX_LENGTH;
			}
        }
		else
		{
			status = QPPS_ERR_INVALID_PARAM;
		}
    }

    if (param->response)
    {
        //Send write response
        atts_write_rsp_send(qpps_env.conhdl, param->handle, status);
    }

    return (KE_MSG_CONSUMED);
}

由于app对特征值9600的写入操作实际上是对特征中QPPS_IDX_RX_DATA_VAL一值进行了写入操作,所以函数中以下代码就是经过了一系列的接收操作后,我们能在程序中看到的第一入口。程序中构建了一条标识为QPPS_DAVA_VAL_IND的消息,在信息中填入了从app处获取到的写入内容。

if (param->handle == (qpps_env.shdl + QPPS_IDX_RX_DATA_VAL))
        {
			if (param->length <= QPP_DATA_MAX_LEN)
			{
				//inform APP of configuration change
				struct qpps_data_val_ind * ind = KE_MSG_ALLOC_DYN(QPPS_DAVA_VAL_IND,
																  qpps_env.appid,
																  TASK_QPPS,
																  qpps_data_val_ind, param->length);

				memcpy(&ind->conhdl, &(qpps_env.conhdl), sizeof(uint16_t));
				//Send received data to app value
				ind->length = param->length;
				memcpy(ind->data, param->value, param->length);

				ke_msg_send(ind);
			}
			else
			{
				status = QPPS_ERR_RX_DATA_EXCEED_MAX_LENGTH;
			}
        }
		else
		{
			status = QPPS_ERR_INVALID_PARAM;
		}
    }

通过查看QPPS_DAVA_VAL_IND,得知该消息处理函数为app_qpps_data_ind_handler。根据函数描述,app发送数据到开发板后,会在串口中输出数据,打开串口,设置好波特率(9600),可以看到app端发送数据的字节长度和第一个数据的16进制。如果需要取出完整数据,根据param->length把param->data数组的数据都取出来,就是完整的数据了。

int app_qpps_data_ind_handler(ke_msg_id_t const msgid,
                              struct qpps_data_val_ind *param,
                              ke_task_id_t const dest_id,
                              ke_task_id_t const src_id)
{
    if (param->length > 0)
    {
        QPRINTF("len=%d, I%02X", param->length, param->data[0]);
    }
    QPRINTF("\r\n");

    return (KE_MSG_CONSUMED);
}

对于发送数据,昆天科做了一个很有意思的算法,让app在配置完所有的notify后持续发送递增数据到每一个发送特征值中,所以开启这个发送的办法在与打开所有的notify。我们看一下打开notify的操作,这个操作会触发gatt_write_cmd_ind_handler处理函数,代码片如下:

uint8_t char_index = param->handle - (qpps_env.shdl + QPPS_IDX_VAL_NTF_CFG);
        if ((param->handle > (qpps_env.shdl + QPPS_IDX_VAL_CHAR)) && ((char_index % 3) == 0))
        {
            uint16_t value = 0x0000;

            //Extract value before check
            memcpy(&value, &(param->value), sizeof(uint16_t));

            if ((value == PRF_CLI_STOP_NTFIND) || (value == PRF_CLI_START_NTF))
            {
                if (value == PRF_CLI_STOP_NTFIND)
                {
                    qpps_env.features &= ~(QPPS_VALUE_NTF_CFG << (char_index / 3));
                }
                else //PRF_CLI_START_NTF
                {
                    qpps_env.features |= QPPS_VALUE_NTF_CFG << (char_index / 3);
                }
            }
            else
            {
                status = PRF_APP_ERROR;
            }

            if (status == PRF_ERR_OK)
            {
                uint8_t *old_value;
                atts_size_t length;

                attsdb_att_get_value(param->handle, &length, &old_value);
                if (value != co_read16p(old_value))
                {
                    //Update the attribute value
                    attsdb_att_set_value(param->handle, sizeof(uint16_t), (uint8_t *)&value);

                    if(param->last)
                    {
                        //Inform APP of configuration change
                        struct qpps_cfg_indntf_ind * ind = KE_MSG_ALLOC(QPPS_CFG_INDNTF_IND,
                                                                        qpps_env.appid, TASK_QPPS,
                                                                        qpps_cfg_indntf_ind);

                        ind->char_index = (char_index / 3);
                        memcpy(&ind->cfg_val, &value, sizeof(uint16_t));

                        ke_msg_send(ind);
                    }
                }
            }
        }

当app开启notify的时候,会带来一个value,value代表着是开启还是关闭notify,所以首先要进行判断是哪一个特征中开启或者关闭了notify,然后判断是否需要进行开启和关闭操作(检查之前状态是否与当前操作冲突),如果需要开启或者关闭,则会发出一个标识为QPPS_CFG_INDNTF_IND的消息,消息中填入了操作的特征序号和值。
消息QPPS_CFG_INDNTF_IND的处理函数为app_qpps_cfg_indntf_ind_handler,此消息为指示性消息,notify已经在刚才 的处理中开启,所以本函数主要任务是指示了哪一个消息被开启或关闭,在判断所有的notify被开启后,函数调用了另一个函数app_test_send_data(app_qpps_env->tx_char_num - 1);,发送数据到9601~9607七个特征值去。

int app_qpps_cfg_indntf_ind_handler(ke_msg_id_t const msgid,
                                    struct qpps_cfg_indntf_ind *param,
                                    ke_task_id_t const dest_id,
                                    ke_task_id_t const src_id)
{
    if (app_qpps_env->conhdl == param->conhdl)
    {
        if (param->cfg_val == PRF_CLI_START_NTF)
        {
            QPRINTF("param->char_index %d\r\n",param->char_index);
						app_qpps_env->features |= (QPPS_VALUE_NTF_CFG << param->char_index);
            // App send data if all of characteristic have been configured
						QPRINTF("app_qpps_env->features 0x%X : 0x%X \r\n",app_qpps_env->features,app_qpps_env->tx_char_num);
            if (get_bit_num(app_qpps_env->features) == app_qpps_env->tx_char_num)
            {
                app_qpps_env->char_status = app_qpps_env->features;
                app_test_send_data(app_qpps_env->tx_char_num - 1);
            }
        }
        else
        {
            app_qpps_env->features &= ~(QPPS_VALUE_NTF_CFG << param->char_index);
            app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG << param->char_index);
        }
    }

    return (KE_MSG_CONSUMED);
}

函数通过对cnt计数和一些条件判断,对特征值依次发送数据。

static void app_test_send_data(uint8_t max)
{
    uint8_t cnt;

    for (cnt = 0; (max != 0) && cnt < app_qpps_env->tx_char_num; cnt++)
    {
        if ((app_qpps_env->char_status >> cnt) & QPPS_VALUE_NTF_CFG)
        {
            static uint8_t val[] = {0, '0', '1', '2','3','4','5','6','7','8','9','8','7','6','5','4','3','2','1','0'};

            // Increment the first byte for test
            val[0]++;

            max--;
            // Allow next notify until confirmation received in this characteristic
            app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG << cnt);
            app_qpps_data_send(app_qpps_env->conhdl, cnt, sizeof(val), val);
        }
				QPRINTF("send to cnt:%d,max %d\r\n",cnt,max);
    }
		QPRINTF("app_qpps_env->char_status: %X\r\n",app_qpps_env->char_status);
}

发送数据完成,系统会发出QPPS_DATA_SEND_CFM消息作为响应,于是在消息对应的处理函数app_qpps_data_send_cfm_handler中,再次发起消息发送。

int app_qpps_data_send_cfm_handler(ke_msg_id_t const msgid,
                                   struct qpps_data_send_cfm *param,
                                   ke_task_id_t const dest_id,
                                   ke_task_id_t const src_id)
{
    if (app_qpps_env->conhdl == param->conhdl && param->status == PRF_ERR_OK)
    {
        // Allow new notify
        app_qpps_env->char_status |= (1 << param->char_index);
				QPRINTF("app_qpps_env->char_status %X\r\n",app_qpps_env->char_status);

        // Send next group data until current data have been sent
        if (get_bit_num(app_qpps_env->char_status) == (app_qpps_env->tx_char_num - 1))
        {
					QPRINTF("t:app_qpps_env->char_status %X\r\n",app_qpps_env->char_status);
            app_test_send_data(app_qpps_env->tx_char_num - 1);
        }
    }
    else
    {
        QPRINTF("QPPS send error %d.\r\n", param->status);
    }

    return (KE_MSG_CONSUMED);
}

探讨:如果仔细观察,可以发现,从9601到9606第一次发送的数据分别是以01、02、03、…、06开头的,而07却再次回到了9601,接下来08、09、…、0c,0c发送在9607的特征上,9606并没有观察到发送的数据。也就是第一次循环的时候9607没有参与到发送循环中,而第二次发送时,9606没有参与到循环中,如此重复。这是为什么呢?

沙发!

我是定了5个,QPPS和QPPC互连 观察发现其实都是轮流从1到5发送,而在QPPC中代码是收到200个字才打印一次。

作为QPPC来说,接收到的确实是1~5,在用手机连接时,打开所有notify,才会出现我帖子所说的现象,那是从机单方面测试用的。

安安 发表于 2015-5-20 11:38
作为QPPC来说,接收到的确实是1~5,在用手机连接时,打开所有notify,才会出现我帖子所说的现象,那是从机 …

请问楼主手机上装的是什么软件?我用手机找不到QPPC的广播。

我之前没有说清楚,应该是手机连接QPPS,QPPC作为主机不会发出广播啊,只会扫描广播。QPPS在用手机连接,并且打开所有notify的时候会有测试发送命令

怎么感觉和我的代码都不一样啊03.#define QPPS_NOTIFY_NUM 7

都没有

你的代码应该是直接赋值为7的吧,因为之前的版本这样做很难找到这个地方去更改revevie的特征,所以后面版本做成宏了。

楼主我想请教一个问题,ATT的CLIENT要读取SERIVER的数据用什么命令呀,写数据使用GATTC_WRITE_CMD,读数据的呢使用GATTC_READ_CMD吗还是GATTC_DISC_CMD,CLIENT接受要读取数据的命令以后的操作是啥呀,谢谢大神。

huzi741 发表于 2015-7-17 18:59
楼主我想请教一个问题,ATT的CLIENT要读取SERIVER的数据用什么命令呀,写数据使用GATTC_WRITE_CMD,读数据 …

一切命令ATT命令都在app_task.c和app_gatt.c/app_gatt_task.c三个文件中。

请问楼主我用的是昆天科的开发板,烧录qpps程序怎么不能实现透传呢

小米粥 发表于 2015-7-21 09:50
请问楼主我用的是昆天科的开发板,烧录qpps程序怎么不能实现透传呢

如果是FireBLE源码的话,那么按键定义是不一样的,烧录SDK源码应该是可以跑的。来一块FireBLE呗,那就不用修改button_pin了:lol

小米粥 发表于 2015-7-21 09:50
请问楼主我用的是昆天科的开发板,烧录qpps程序怎么不能实现透传呢

nt app_gap_set_mode_req_cmp_evt_handler(ke_msg_id_t const msgid, struct gap_event_common_cmd_complete const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
app_task_msg_hdl(msgid, param);

switch (ke_state_get(dest_id))
{
case APP_INIT:

//#if QN_DEMO_AUTO luhong 2015.8.3
// Created DB should has been finished by each profile service,
// Start Adv mode automatically here
app_gap_adv_start_req(GAP_GEN_DISCOVERABLE|GAP_UND_CONNECTABLE,
app_env.adv_data, app_set_adv_data(GAP_GEN_DISCOVERABLE),
app_env.scanrsp_data, app_set_scan_rsp_data(app_get_local_service_flag()),
GAP_ADV_FAST_INTV1, GAP_ADV_FAST_INTV2);
//#endif

不需要按键照样可以进入广播状态

没有#define QPPS_NOTIFY_NUM 7 这个宏定义的话 要在哪里修改?

卜道翁先生 发表于 2015-9-17 19:37
没有#define QPPS_NOTIFY_NUM 7 这个宏定义的话 要在哪里修改?

同问 之前我改过 后来我写Android写多了 把这个给忘了 现在又得回来找{:3_58:}

请问QPPS写的特征值数能改么

昆天科深入讲解的资料很少,这算很详细了

本帖最后由 安安 于 2015-10-8 09:31 编辑

卜道翁先生 发表于 2015-9-17 19:37
没有#define QPPS_NOTIFY_NUM 7 这个宏定义的话 要在哪里修改?

在函数

void app_create_server_service_DB(void)

中,有如下代码:
旧版本:

#if BLE_QPP_SERVER
    app_qpps_env->tx_char_num = 7;
    app_qpps_create_db(app_qpps_env->tx_char_num);
#endif

新版本:

#if BLE_QPP_SERVER
    qpps_set_service_uuid((uint8_t *)QPP_SVC_PRIVATE_UUID);
    app_qpps_env->tx_char_num = QPPS_NOTIFY_NUM;
    app_qpps_create_db(app_qpps_env->tx_char_num);
#endif

旧版本的例程中是直接给app_qpps_env->tx_char_num赋值的,新版本的是通过定义QPPS_NOTIFY_NUM来给app_qpps_env->tx_char_num赋值的。

卜道翁先生 发表于 2015-10-6 14:59
请问QPPS写的特征值数能改么

可以修改的,具体请看另一篇帖子:http://developer.t-firefly.com/thread-2163-1-2.html