NRF52832学习笔记(16)——GAP主机端扫描

一、背景

1.1 蓝牙协议栈

链路层(LL)控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。

广播 为广播数据包,而 扫描 则是监听广播。

GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)

大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。

也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。

1.2 扫描概念

扫描是一个在一定范围内用来寻址其他低功耗蓝牙设备广播的过程。扫描者设备在扫描过程中会使用广播信道。与广播过程不同的是,扫描过程没有严格的时间定义和信道规则。扫描过程应该按照由 Host 层所设定扫描定时参数还运行。

1.2.1 被动扫描

被动扫描:在被动扫描中,扫描设备应该仅仅去监听广播包,而不向广播设备发送任何数据。


一旦扫描参数设置完成,主机就可以在协议栈中发送命令启动扫描。扫描过程中,如果控制器接收到符合过滤策略或其他规则的广播数据包,则发送一个广播报告事件给主机。报告事件除了有广播者的设备地址外,还包括广播数据包中的数据,以及接收广播数据包时的信号接收强度。我们可以利用该信号强度以及位于广播包中的发射功率,共同确定信号的路径损失,从而给出大致的范围,这个应用就是防丢器和蓝牙定位。

1.2.2 主动扫描

主动扫描:不仅可以捕获到从端设备的广播数据包,还可以捕获扫描响应包,并区分它们。


控制器收到扫描数据后将向主机发送一个广播报告事件(adv_report),该事件包括了链路层数据包的广播类型。因此,主机能够判断从端设备是否可以连接或扫描,并且区分出广播数据包和扫描响应包。

二、配置扫描参数

2.1 扫描参数变量

在 main.c 中

static ble_gap_scan_params_t m_scan_param =                                         /**< Scan parameters requested for scanning and connection. */
{
    .active        = 0x01,                                                          // 主动扫描
    .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,                                    // 扫描间隔
    .window        = NRF_BLE_SCAN_SCAN_WINDOW,                                      // 扫描窗口
    .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,
    .timeout       = NRF_BLE_SCAN_SCAN_DURATION,                                    // 扫描超时
    .scan_phys     = BLE_GAP_PHY_1MBPS,
    .extended      = true,
};
  • active:是否主动扫描,配置为1则是主动扫描,0则是被动扫描
  • interval:扫描间隔,控制器间隔多长时间扫描一次,也就是两个连续的扫描窗口开始时间的时间间隔。在 NRF 上设置为 0x0004 and 0x4000 in 0.625ms units(2.5ms 到 10.24s)
  • window:扫描窗口,每次扫描所持续的时间,在持续时间内,扫描设备一直在广播信道上运行。在 NRF 上设置为 0x0004 and 0x4000 in 0.625ms units(2.5ms 到 10.24s)
  • filter_policy:扫描筛选策略,也就死说接受任何广播数据或者仅仅接受白名单设备的广播数据包。实际上就死决定是否使用白名单过滤广播数据包。这里注意一点,如果定向广播数据包中的目的地址并非是自己的,那么该数据必须抛弃,即使广播数据包的发送者在自己的白名单中。
  • timeout:扫描超时,超过指定的时间后,没有扫描到设备将停止扫描。在 NRF 上设置为 0x0001 and 0xFFFF in seconds,设置为 0 则认为没有 timeout
  • scan_phys:扫描的物理层速度

注意:扫描窗口和扫描间隔两个参数非常重要。扫描窗口的设置要小于或等于扫描间隔,并且都要是 0.625ms 的整倍数。这两个参数决定了主机控制器的扫描占空比。比如,如果设置扫描间隔为 100 ms,扫描窗口为 10ms ,那么主机控制器的扫描占空比就死 10%。特别注意可以捕获的定向数据包的最低占空比为 0.4%,即每一秒中扫描时间为 3.75ms,这些时间设置在任何蓝牙 4.x 处理器中都是一致的,不仅仅限于 NRF 处理器。

如果把时间间隔设置为相同的大小,那么控制器会进行连续扫描,每个间隔会改变扫描频率,也就死切换扫描信道。

2.2 扫描过滤相关宏

/**@defgroup BLE_GAP_SCAN_FILTER_POLICIES GAP Scanner filter policies
 * @{ */
#define BLE_GAP_SCAN_FP_ACCEPT_ALL                      0x00  /**< Accept all advertising packets except directed advertising packets
                                                                   not addressed to this device. */
#define BLE_GAP_SCAN_FP_WHITELIST                       0x01  /**< Accept advertising packets from devices in the whitelist except directed
                                                                   packets not addressed to this device. */
#define BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED       0x02  /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_ACCEPT_ALL.
                                                                   In addition, accept directed advertising packets, where the advertiser's
                                                                   address is a resolvable private address that cannot be resolved. */
#define BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED 0x03  /**< Accept all advertising packets specified in @ref BLE_GAP_SCAN_FP_WHITELIST.
                                                                   In addition, accept directed advertising packets, where the advertiser's
                                                                   address is a resolvable private address that cannot be resolved. */
/**@} */
  • BLE_GAP_SCAN_FP_ACCEPT_ALL:接收所有的广播包,除去广播地址不是指向该设备的定向广播。
  • BLE_GAP_SCAN_FP_WHITELIST:接收在白名单里所有的广播,除去广播地址不是指向该设备的定向广播。
  • BLE_GAP_SCAN_FP_ALL_NOT_RESOLVED_DIRECTED:接收所有的广播包,包含定向广播包。这里如果广播MAC地址是私密地址,这里是无法被解析的。
  • BLE_GAP_SCAN_FP_WHITELIST_NOT_RESOLVED_DIRECTED:接收白名单里所有的广播包,包含定向广播包。这里如果广播MAC地址是私密地址,这里是无法被解析的。

2.3 定义扫描模块

NRF_BLE_SCAN_DEF(m_scan);                                                           /**< Scanning module instance. */

2.4 初始化扫描参数

/**@brief Function for initialization the scanning and setting the filters.
 */
static void scan_init(void)
{
    ret_code_t          err_code;
    nrf_ble_scan_init_t init_scan;

    memset(&init_scan, 0, sizeof(init_scan));

    init_scan.p_scan_param = &m_scan_param;

    err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
    APP_ERROR_CHECK(err_code);

    if(strlen(m_target_periph_name) != 0)
    {
        err_code = nrf_ble_scan_filter_set(&m_scan, 
                                           SCAN_NAME_FILTER, 
                                           m_target_periph_name);
        APP_ERROR_CHECK(err_code);
    }

    err_code = nrf_ble_scan_filter_set(&m_scan, 
                                       SCAN_UUID_FILTER, 
                                       &m_adv_uuids[HART_RATE_SERVICE_UUID_IDX]);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_scan_filter_set(&m_scan, 
                                       SCAN_UUID_FILTER, 
                                       &m_adv_uuids[RSCS_SERVICE_UUID_IDX]);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_scan_filters_enable(&m_scan, 
                                           NRF_BLE_SCAN_ALL_FILTER, 
                                           false);
    APP_ERROR_CHECK(err_code);
}

在 main 函数中,执行 scan_init() 进行初始化扫描参数

/**@brief Function for initializing the application main entry.
 */
int main(void)
{
    bool erase_bonds;

    /*-------------------------- 外设驱初始化 ---------------------------*/
    // Initialize.
    log_init();                                 // 日志驱动初始化

    /*-------------------------- 蓝牙协议栈初始化 ---------------------------*/
    power_management_init();                    // 能量初始化
    ble_stack_init();                           // 协议栈初始化
    scan_init();                                // 扫描初始化
    gap_params_init();
    gatt_init();
    conn_params_init();                         // 连接参数初始化
    db_discovery_init();
    peer_manager_init();
    services_init();                            // 服务初始化
    advertising_init();                         // 广播初始化
    
    /*-------------------------- 开启应用 ---------------------------*/
    // Start execution.
    NRF_LOG_INFO("Relay example started.");

    if(erase_bonds == true)
    {
        NRF_LOG_INFO("delete_bonds");
        // Scanning and advertising is done upon PM_EVT_PEERS_DELETE_SUCCEEDED event.
        delete_bonds();
    }
    else
    {
        NRF_LOG_INFO("adv_scan_start"); 
        adv_scan_start();
    }

    // Enter main loop.
    for(;;)
    {
        idle_state_handle();
    }
}

三、开启扫描

包含头文件

#include "nrf_ble_scan.h"

开启扫描函数

/**@brief Function for initializing the scanning.
 */
void scan_start(void)
{
    ret_code_t err_code;

    err_code = nrf_ble_scan_start(&m_scan);
    APP_ERROR_CHECK(err_code);
}

四、关闭扫描

包含头文件

#include "nrf_ble_scan.h"

关闭扫描函数

nrf_ble_scan_stop();

五、广播报告

控制器收到广播数据包后向主机发送一个广播报告事件。

5.1 扫描事件处理

在 main.c 中

/**@brief   Function for handling BLE events from the central application.
 *
 * @details This function parses scanning reports and initiates a connection to peripherals when a
 *          target UUID is found. It updates the status of LEDs used to report the central application
 *          activity.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 */
static void on_ble_central_evt(ble_evt_t const *p_ble_evt)
{
    ret_code_t           err_code;
    ble_gap_evt_t const *p_gap_evt = &p_ble_evt->evt.gap_evt;

    switch(p_ble_evt->header.evt_id)
    {
        ···
        case BLE_GAP_EVT_ADV_REPORT:
            GetAdvReport(p_gap_evt->params.adv_report);
            break;
        ···

5.2 获取广播报告

/**
 @brief 获取广播数据
 @param advReport - 广播报告结构体
 @return 无
*/
void GetAdvReport(ble_gap_evt_adv_report_t advReport)
{
    // 在这里加入对广播报告的处理    
}

5.2.1 ble_gap_evt_adv_report_t

/**@brief Event structure for @ref BLE_GAP_EVT_ADV_REPORT.
 *
 * @note If @ref ble_gap_adv_report_type_t::status is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA,
 *       not all fields in the advertising report may be available.
 *
 * @note When ble_gap_adv_report_type_t::status is not set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA,
 *       scanning will be paused. To continue scanning, call @ref sd_ble_gap_scan_start.
 */
typedef struct
{
  ble_gap_adv_report_type_t type;                  /**< Advertising report type. See @ref ble_gap_adv_report_type_t. */
  ble_gap_addr_t            peer_addr;             /**< Bluetooth address of the peer device. If the peer_addr is resolved:
                                                        @ref ble_gap_addr_t::addr_id_peer is set to 1 and the address is the
                                                        peer's identity address. */
  ble_gap_addr_t            direct_addr;           /**< Contains the target address of the advertising event if
                                                        @ref ble_gap_adv_report_type_t::directed is set to 1. If the
                                                        SoftDevice was able to resolve the address,
                                                        @ref ble_gap_addr_t::addr_id_peer is set to 1 and the direct_addr
                                                        contains the local identity address. If the target address of the
                                                        advertising event is @ref BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE,
                                                        and the SoftDevice was unable to resolve it, the application may try
                                                        to resolve this address to find out if the advertising event was
                                                        directed to us. */
  uint8_t                   primary_phy;           /**< Indicates the PHY on which the primary advertising packet was received.
                                                        See @ref BLE_GAP_PHYS. */
  uint8_t                   secondary_phy;         /**< Indicates the PHY on which the secondary advertising packet was received.
                                                        See @ref BLE_GAP_PHYS. This field is set to @ref BLE_GAP_PHY_NOT_SET if no packets
                                                        were received on a secondary advertising channel. */
  int8_t                    tx_power;              /**< TX Power reported by the advertiser in the last packet header received.
                                                        This field is set to @ref BLE_GAP_POWER_LEVEL_INVALID if the
                                                        last received packet did not contain the Tx Power field.
                                                        @note TX Power is only included in extended advertising packets. */
  int8_t                    rssi;                  /**< Received Signal Strength Indication in dBm of the last packet received. */
  uint8_t                   ch_index;              /**< Channel Index on which the last advertising packet is received (0-39). */
  uint8_t                   set_id;                /**< Set ID of the received advertising data. Set ID is not present
                                                        if set to @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */
  uint16_t                  data_id:12;            /**< The advertising data ID of the received advertising data. Data ID
                                                        is not present if @ref ble_gap_evt_adv_report_t::set_id is set to
                                                        @ref BLE_GAP_ADV_REPORT_SET_ID_NOT_AVAILABLE. */
  ble_data_t                data;                  /**< Received advertising or scan response data. If
                                                        @ref ble_gap_adv_report_type_t::status is not set to
                                                        @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA, the data buffer provided
                                                        in @ref sd_ble_gap_scan_start is now released. */
  ble_gap_aux_pointer_t     aux_pointer;           /**< The offset and PHY of the next advertising packet in this extended advertising
                                                        event. @note This field is only set if @ref ble_gap_adv_report_type_t::status
                                                        is set to @ref BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA. */
} ble_gap_evt_adv_report_t;
  • type:广播报告类型
  • peer_addr:扫描设备的MAC地址。如果peer_addr被解析,那么参数ble_gap_addr_t::addr_id_peer被设置为1,同时该地址是对等方的身份地址
  • direct_addr:扫描定向广播的MAC地址。当参数ble_gap_adv_report_type_t::directed被设置为1,包含目的地址的广播事件。如果协议栈能够解析地址,参数ble_gap_addr_t::addr_id_peer被设置为1,direct_addr包含本地的认证地址。如果广播事件的目的地址是BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE类型,协议栈是服务解析的,应用程序可以尝试解析这个地址,以查明广播是否指向我们
  • primary_phy:主广播包的物理层速度
  • secondry_phy:第二扩展广播包的物理层速度。看参数BLE_GAP_PHYS。如果在二级广播频道上没有收到信息包,则此字段为0
  • tx_power:TX功率报告由广播客户端在最后收到的包头。如果最后收到的数据包不包含TX字段,则这个字段被设置为BLE_GAP_POWER_LEVEL_INVALID
  • rssi:接收的信号强度
  • ch_index:接收到最后一个广播包的频道(0-39)
  • set_id:设置接收广播数据的ID
  • data_id:接收的广播数据的广播数据ID
  • data:接收的广播数据包或者扫描响应包。如果参数ble_gap_adv_report_type_t::status没有被设置为参数BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA,参数sd_ble_gap_scan_start中提供的数据缓存立即释放
  • aux_pointer:在此扩展广播事件中,下一个广播包的偏移量。注意:只有当ble_gap_adv_report_type_t::status被设置为BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA时,该字段才会被设置

5.2.2 ble_gap_adv_report_type_t

/**@brief Advertising report type. */
typedef struct
{
  uint16_t connectable   : 1; /**< Connectable advertising event type. */
  uint16_t scannable     : 1; /**< Scannable advertising event type. */
  uint16_t directed      : 1; /**< Directed advertising event type. */
  uint16_t scan_response : 1; /**< Received a scan response. */
  uint16_t extended_pdu  : 1; /**< Received an extended advertising set. */
  uint16_t status        : 2; /**< Data status. See @ref BLE_GAP_ADV_DATA_STATUS. */
  uint16_t reserved      : 9; /**< Reserved for future use. */
} ble_gap_adv_report_type_t;
  • connectable:可连接广播事件类型
  • scannable:可扫描广播事件类型
  • directed:定向广播事件类型
  • scan_response:收到扫描响应
  • extended_pdu:收到加长广播
  • status:数据状态,参考参数BLE_GAP_ADV_DATA_STATUS
/**@defgroup BLE_GAP_ADV_DATA_STATUS GAP Advertising data status
 * @{ */
// 广播包的所有数据已收到
#define BLE_GAP_ADV_DATA_STATUS_COMPLETE             0x00 /**< All data in the advertising event have been received. */
// 需要接收更多的数据
#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA 0x01 /**< More data to be received.
                                                               @note This value will only be used if
                                                               @ref ble_gap_scan_params_t::report_incomplete_evts and
                                                               @ref ble_gap_adv_report_type_t::extended_pdu are set to true. */
// 不完整的数据,缓冲区大小不足以接收更多
#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_TRUNCATED 0x02 /**< Incomplete data. Buffer size insufficient to receive more.
                                                               @note This value will only be used if
                                                               @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */
// 未能接收剩余的数据
#define BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MISSED    0x03 /**< Failed to receive the remaining data.
                                                               @note This value will only be used if
                                                               @ref ble_gap_adv_report_type_t::extended_pdu is set to true. */
/**@} */
  • reserved:保留部分

5.2.3 ble_data_t

/**@brief Data structure. */
typedef struct
{
  uint8_t     *p_data;  /**< Pointer to the data buffer provided to/from the application. */
  uint16_t     len;     /**< Length of the data buffer, in bytes. */
} ble_data_t;
  • p_data:广播或扫描响应的数据
  • len:广播或扫描响应的数据长度

六、提取广播内容

/**
 @brief 获取广播数据或扫描应答数据中adType对应的数据
 @param adType -[in] 数据类型
 @param pAdvData -[in] 广播包或扫描应答包结构体
 @param pTypeData -[out] 对应的adType类型数据结构体
 @return 0 - 成功;5 - 未找到
*/
uint32_t GetAdtypeData(uint8_t adType, uint8_array_t *pAdvData, uint8_array_t *pTypeData)
{
    uint32_t index = 0;
    uint8_t *pData;

    pData = pAdvData->p_data;

    while(index < pAdvData->size)                               // 判断当前指针是否还未到包尾
    {
        uint8_t fieldLength = pData[index];
        uint8_t fieldType = pData[index + 1];

        if(fieldType == adType)                                 // 如果找到了adType
        {
            pTypeData->p_data = &pData[index + 2];              // 数据段内容
            pTypeData->size = fieldLength - 1;                  // 数据段长度   
                    
            return NRF_SUCCESS;                                 // 找到了adType
        }
        index += fieldLength + 1;                               // 没找到adType则指向下一个数据段                      
    }
    
    return NRF_ERROR_NOT_FOUND;                                 // 本数据串中没有找到对应adType
}

6.1 提取名字

uint32_t errCode;           
    
// 获取名字全称
errCode = GetAdtypeData(BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, pAdvData, pTypeData);
// 没有,则获取名字缩写
if(errCode != NRF_SUCCESS)
{
    errCode = GetAdtypeData(BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME, pAdvData, pTypeData); 
}

6.2 提取UUID

uint32_t errCode;           
    
// 获取部分服务UUID列表
errCode = GetAdtypeData(BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE, pAdvData, pTypeData);
// 没有,则获取全部服务UUID列表
if(errCode != NRF_SUCCESS)
{
    errCode = GetAdtypeData(BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE, pAdvData, pTypeData);
}

6.2 提取自定义数据

GetAdtypeData(BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA, pAdvData, pTypeData);

• 由 Leung 写于 2020 年 3 月 14 日

• 参考:青风电子社区

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容