BLE Mesh蓝牙组网技术详细解析之Bearer Layer承载层(二)

目录

一、什么是 BLE Mesh Bearer Layer?

二、ADV Bearer Layer

2.1 广播承载的特点

2.2 adv bearer layer是如何工作的?

2.3 广播承载层的数据包格式

2.4 如何实现Mesh Message的接收和解析? 

三、GATT Bearer Layer

3.1 GATT承载的特点

3.2 Proxy PDU

3.3 Proxy filtering

3.4 Proxy Server/Client

 3.5 如何建立GATT连接

四、资料获取


一、什么是 BLE Mesh Bearer Layer?

蓝牙Mesh协议中负责数据传输的层,建立在BLE的物理层和链路层之上;

提供了两种承载方式:广播承载和GATT承载。

二、ADV Bearer Layer

2.1 广播承载的特点

利用BLE的广播和扫描功能,在3个广播信道上发送和接收Mesh消息;

可以实现多对多的通信,不需要建立连接,也不需要维护路由表;

数据传输速率较低,且受到广播间隔和扫描窗口的限制。

2.2 adv bearer layer是如何工作的?

adv bearer layer是蓝牙mesh网络中的一种承载层,它使用广播通道进行数据传输。adv bearer layer的工作原理如下:

  • adv bearer layer接收来自网络层的数据,将其封装为adv bearer layer数据包,包括长度,AD Type和AD Data三个字段。
  • adv bearer layer数据包的AD Type用于区分数据包的类型,有Mesh Message,Mesh Beacon和PB-ADV三种。
  • adv bearer layer数据包的AD Data用于存储网络层的数据,如Network PDU,Mesh Beacon或Provisioning PDU等。
  • adv bearer layer通过广播方式发送数据包,使用不可连接,不可扫描,不可定向的广播类型。
  • adv bearer layer通过扫描方式接收数据包,使用尽可能全窗的扫描事件,以确保不丢失数据。
  • adv bearer layer收到数据包后,根据AD Type和消息缓存判断是否需要处理,如果是,就将AD Data解封装并传递给网络层。

2.3 广播承载层的数据包格式

Filed

Size(octs)

Notes

Length

1

当前数据包总长度

AD Type

1

当前数据包为Mesh数据包

NetworkPDU

18-29

Network层的PDU

广播包种类

广播包类型(AD Type)

功能说明

PB-ADV

0x29

用于传输通用配网PDU,用于设备配网期间的通信。数据长度为24字节。

Mesh Message

0x2A

用于传输网络层的数据,包括控制和应用消息,数据长度为18-29字节。

Mesh Beacon

0x2B

用于传输安全网络信标,用于节点的认证和密钥更新。数据长度为21字节。

注:广告都是不可连接和不可扫描的非定向广告事件。如果节点在可连接或可扫描的通告事件中接收到广告,则忽略该消息。

1、PB-ADV 是一种配网承载,用于通过广播通道传输配网过程中的数据包。要发送 PB-ADV 代码,您需要以下几个步骤:

  • 构造 Generic Provisioning PDU,包含 Provisioning PDU 和 Transaction Number。
  • 将 Generic Provisioning PDU 封装到 BLE 广播包中,设置 AD Type 为 0x29,表示 Mesh Provisioning Service。
  • 调用 BLE 协议栈的接口,设置广播参数和数据,启动广播。

下面是一个基于 ESP-IDF 的示例代码,用于发送 PB-ADV 代码

// 定义 Generic Provisioning PDU 的数据结构
typedef struct
{
    uint8_t transaction_number; // Transaction Number
    uint8_t pdu_type; // Provisioning PDU Type
    uint8_t pdu_data[]; // Provisioning PDU Data
} generic_provisioning_pdu_t;

// 定义广播数据的缓冲区
static uint8_t m_adv_data_buffer[BEARER_ADV_DATA_BUFFER_SIZE];

// 定义广播参数
static const ble_gap_adv_params_t m_adv_params =
{
    .properties.type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED, // 广播类型
    .p_peer_addr = NULL, // 不指定对方地址
    .interval = UNIT_0_625_MS(BEACON_INTERVAL_MS), // 广播间隔
    .duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限时长
    .max_adv_evts = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限次数
    .channel_mask = {0, 0, 0, 0, 0}, // 不屏蔽任何信道
    .filter_policy = BLE_GAP_ADV_FP_ANY, // 不过滤任何设备
    .primary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
    .secondary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
    .set_id = 0, // 广播集合ID
    .scan_req_notification = 0, // 不通知扫描请求
};

// 定义广播数据
static ble_data_t m_adv_data =
{
    .p_data = m_adv_data_buffer, // 指向缓冲区
    .len = 0 // 初始长度为0
};

// 构造 Generic Provisioning PDU 的数据
static void generic_provisioning_pdu_build(generic_provisioning_pdu_t * p_pdu)
{
    // 设置 Transaction Number,每发送一个 Provisioning PDU 该值累加
    static uint8_t transaction_number = 0;
    p_pdu->transaction_number = transaction_number++;

    // 设置 Provisioning PDU Type,这里假设是 Invite PDU
    p_pdu->pdu_type = PROV_INVITE;

    // 设置 Provisioning PDU Data,这里假设是 Attention Duration
    p_pdu->pdu_data[0] = 0x05;
}

// 启动广播
static void adv_start(void)
{
    // 创建一个 Generic Provisioning PDU 的实例
    generic_provisioning_pdu_t pdu;
    // 构造 PDU 的数据
    generic_provisioning_pdu_build(&pdu);

    // 设置 AD Type 为 Mesh Provisioning Service
    m_adv_data_buffer[0] = AD_TYPE_MESH_PROV; // AD Type

    // 拷贝 PDU 的数据到缓冲区
    memcpy(&m_adv_data_buffer[1], &pdu, sizeof(generic_provisioning_pdu_t));

    // 设置广播数据的长度
    m_adv_data.len = 1 + sizeof(generic_provisioning_pdu_t);

    // 调用 BLE 协议栈的接口,设置广播数据
    uint32_t status = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params);
    NRF_MESH_ASSERT(status == NRF_SUCCESS);

    // 调用 BLE 协议栈的接口,启动广播
    status = sd_ble_gap_adv_start(m_adv_handle, BLE_CONN_CFG_TAG_DEFAULT);
    NRF_MESH_ASSERT(status == NRF_SUCCESS);
}

2、Mesh Message是一种用于在Mesh网络中传输应用层数据的广播承载,它可以实现多对多的通信,也是Mesh网络中最常用的数据传输方式。要发送Mesh Message代码,您需要以下几个步骤:

  • 构造Access Payload,包含Opcode、Parameters和TransMIC。
  • 将Access Payload封装到Upper Transport PDU中,添加AppKey、AID和SegO/AK。
  • 将Upper Transport PDU封装到Lower Transport PDU中,添加SZMIC、SeqZero、SegN和Segment。
  • 将Lower Transport PDU封装到Network PDU中,添加IVI、NID、CTL、TTL、SEQ、SRC、DST和NetMIC。
  • 将Network PDU封装到BLE广播包中,设置AD Type为0x2B,表示Mesh Message。

下面是一个基于Zephyr的示例代码,用于发送Mesh Message代码:

// 定义Access Payload的数据结构
struct bt_mesh_model_op {
    const uint32_t opcode; // Opcode
    const size_t min_len; // Minimum length of the parameters
    void (*const func)(struct bt_mesh_model *model,
               struct bt_mesh_msg_ctx *ctx,
               struct net_buf_simple *buf); // Function to be called
};

// 定义Generic OnOff Set消息的Opcode和Parameters
#define BT_MESH_MODEL_OP_GEN_ONOFF_SET  BT_MESH_MODEL_OP_2(0x82, 0x02)
#define BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK BT_MESH_MODEL_OP_2(0x82, 0x03)
struct bt_mesh_gen_onoff_set {
    uint8_t onoff; // OnOff state
    uint8_t tid; // Transaction ID
    uint8_t trans_time; // Transition time (optional)
    uint8_t delay; // Delay (optional)
};

// 定义Generic OnOff Client模型的操作
static const struct bt_mesh_model_op gen_onoff_cli_op[] = {
    { BT_MESH_MODEL_OP_GEN_ONOFF_SET, 2, gen_onoff_set },
    { BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2, gen_onoff_set_unack },
    BT_MESH_MODEL_OP_END,
};

// 定义Generic OnOff Client模型的结构体
struct bt_mesh_gen_onoff_cli {
    struct bt_mesh_model *model;
    struct bt_mesh_model_pub pub;
    struct bt_mesh_model_ack_ctx ack_ctx;
};

// 定义Generic OnOff Client模型的实例
static struct bt_mesh_gen_onoff_cli gen_onoff_cli;

// 定义Generic OnOff Set消息的发送函数
static int gen_onoff_cli_send(struct bt_mesh_model *model,
                  struct bt_mesh_msg_ctx *ctx,
                  bool ack,
                  uint8_t onoff,
                  uint8_t tid,
                  uint8_t trans_time,
                  uint8_t delay)
{
    // 创建一个Access Payload的缓冲区
    BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_MODEL_OP_GEN_ONOFF_SET,
                 BT_MESH_GEN_ONOFF_MSG_MAXLEN_SET);

    // 设置Opcode
    bt_mesh_model_msg_init(&msg, ack ? BT_MESH_MODEL_OP_GEN_ONOFF_SET :
                       BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK);

    // 设置Parameters
    net_buf_simple_add_u8(&msg, onoff);
    net_buf_simple_add_u8(&msg, tid);

    if (trans_time || delay) {
        net_buf_simple_add_u8(&msg, trans_time);
        net_buf_simple_add_u8(&msg, delay);
    }

    // 调用Mesh协议栈的接口,发送Access Payload
    return bt_mesh_model_send(model, ctx, &msg, NULL, NULL);
}

3、Mesh beacon是一种特殊的广播承载,它用来传输Mesh网络的安全和配置信息,有两种类型:Unprovisioned Device beacon和Secure Network beacon。前者用于未入网的设备广播自己的UUID和OOB信息,以便被Provisioner发现和配置;后者用于已入网的设备广播自己的子网ID和安全状态,以便更新密钥和IV Index

发送Mesh beacon的代码取决于具体的BLE协议栈和Mesh协议栈的实现,不同的厂商或平台可能有不同的API和函数。一般来说,需要先构造Mesh beacon的数据格式,然后调用BLE协议栈的接口,设置广播参数和数据,最后启动广播。

下面是一个基于Nordic nRF5 SDK for Mesh的示例代码,用于发送Unprovisioned Device beacon

// 定义Unprovisioned Device beacon的数据结构
typedef struct
{
    uint8_t beacon_type; // Beacon Type,固定为0x00
    uint8_t uuid[NRF_MESH_UUID_SIZE]; // Device UUID
    uint16_t oob_info; // OOB Information
    uint32_t uri_hash; // URI Hash,可选
} unprov_beacon_t;

// 定义广播数据的缓冲区
static uint8_t m_adv_data_buffer[BEARER_ADV_DATA_BUFFER_SIZE];

// 定义广播参数
static const ble_gap_adv_params_t m_adv_params =
{
    .properties.type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED, // 广播类型
    .p_peer_addr = NULL, // 不指定对方地址
    .interval = UNIT_0_625_MS(BEACON_INTERVAL_MS), // 广播间隔
    .duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限时长
    .max_adv_evts = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限次数
    .channel_mask = {0, 0, 0, 0, 0}, // 不屏蔽任何信道
    .filter_policy = BLE_GAP_ADV_FP_ANY, // 不过滤任何设备
    .primary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
    .secondary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
    .set_id = 0, // 广播集合ID
    .scan_req_notification = 0, // 不通知扫描请求
};

// 定义广播数据
static ble_data_t m_adv_data =
{
    .p_data = m_adv_data_buffer, // 指向缓冲区
    .len = 0 // 初始长度为0
};

// 构造Unprovisioned Device beacon的数据
static void unprov_beacon_build(unprov_beacon_t * p_beacon)
{
    // 获取设备的UUID
    const uint8_t * p_uuid = nrf_mesh_configure_device_uuid_get();
    memcpy(p_beacon->uuid, p_uuid, NRF_MESH_UUID_SIZE);

    // 设置OOB信息,这里假设使用静态OOB
    p_beacon->oob_info = NRF_MESH_PROV_OOB_INFO_SOURCE_STATIC;

    // 设置URI Hash,这里假设不使用URI
    p_beacon->uri_hash = 0;
}

// 启动广播
static void adv_start(void)
{
    // 创建一个Unprovisioned Device beacon的实例
    unprov_beacon_t beacon;
    // 构造beacon的数据
    unprov_beacon_build(&beacon);

    // 设置beacon的类型为Mesh Beacon
    m_adv_data_buffer[0] = AD_TYPE_MESH_BEACON; // AD Type
    m_adv_data_buffer[1] = BEACON_TYPE_UNPROVISIONED_DEVICE; // Beacon Type

    // 拷贝beacon的数据到缓冲区
    memcpy(&m_adv_data_buffer[2], &beacon, sizeof(unprov_beacon_t));

    // 设置广播数据的长度
    m_adv_data.len = 2 + sizeof(unprov_beacon_t);

    // 调用BLE协议栈的接口,设置广播数据
    uint32_t status = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params);
    NRF_MESH_ASSERT(status == NRF_SUCCESS);

    // 调用BLE协议栈的接口,启动广播
    status = sd_ble_gap_adv_start(m_adv_handle, BLE_CONN_CFG_TAG_DEFAULT);
    NRF_MESH_ASSERT(status == NRF_SUCCESS);
}

2.4 如何实现Mesh Message的接收和解析? 

要接收和解析Mesh Message,您需要以下几个步骤:

  • 从BLE广播包中提取Network PDU,检查AD Type是否为0x2B,表示Mesh Message。
  • 使用网络密钥(NetKey)派生的解密密钥(EncryptionKey)和隐私密钥(PrivacyKey)对Network PDU进行解密和反混淆,得到网络层的消息。
  • 检查网络层消息的目的地址(DST)是否与本地节点的单播地址或订阅地址匹配,如果匹配则继续处理,否则丢弃或中继。
  • 使用应用密钥(AppKey)或设备密钥(DevKey)派生的解密密钥(EncryptionKey)对网络层消息的有效载荷(Payload)进行解密和完整性校验,得到上层传输层的消息。
  • 检查上层传输层消息的类型(Access Message或Control Message),如果是Access Message,则继续处理,否则丢弃或转发。
  • 从Access Message中提取Access Payload,包括Opcode和Parameters,根据Opcode确定消息的模型类型和功能,根据Parameters解析消息的具体内容。
  • 根据消息的模型类型和功能,调用相应的模型回调函数,处理消息的内容,如改变状态、发送响应等。

三、GATT Bearer Layer

3.1 GATT承载的特点

利用BLE的连接和GATT服务,在37个数据信道上发送和接收Mesh消息;

可以实现高速的数据传输,且可以兼容不支持Mesh的BLE设备;

需要建立连接,且只能实现一对一的通信。

3.2 Proxy PDU

Filed

Size(bits)

Notes

SAR

2

0b00:代表该消息为完整消息

0b01:代表该消息为分段消息的第一个分段

0b10:代表该消息为分段消息的中间某一段

0b11:代表该消息为分段消息的最后一个分段

Message Type

6

0x00:Network PDU

0x01:Mesh Beacon

0x02:Proxy Configuration

0x03:Provisioning PDU

0x04-0x3F:RFU

Data

variable

Message

3.3 Proxy filtering

GATT Bearer layer Proxy filtering是一种用于优化代理服务器和代理客户端之间的数据传输的机制。它可以让代理客户端指定自己感兴趣或不感兴趣的目的地址,从而减少不必要的网络流量和电量消耗。

GATT Bearer layer Proxy filtering有两种类型,分别是:

  • 白名单过滤器:只允许白名单中的目的地址通过,其他的目的地址都被拦截。白名单过滤器适用于代理客户端只关注少数目的地址的情况。
  • 黑名单过滤器:只拦截黑名单中的目的地址,其他的目的地址都允许通过。黑名单过滤器适用于代理客户端只排除少数目的地址的情况。

代理客户端可以通过发送Proxy Configuration消息来配置代理服务器的过滤器类型和地址列表。Proxy Configuration消息的格式如下:

Field

Size (octets)

Notes

Opcode

1

表示Proxy Configuration消息的操作码,有六种取值:

0x00(Set Filter Type)

0x01(Add Addresses to Filter)

0x02(Remove Addresses from Filter)

0x03(Filter Status)

0x04(Reserved for Future Use)

0x05-0x3F(Reserved for Vendor Use)

Parameters

variable

表示Proxy Configuration消息的参数,根据Opcode的不同而不同。

3.4 Proxy Server/Client

Proxy Client:只能通过GATT连接进行通信,而无法使用ADV进行mesh通信的节点。

Proxy Server:能够在ADV Bearer和GATT Bearer之间进行转换的节点。

 3.5 如何建立GATT连接

GATT bearer可以使那些不支持广播承载的设备也能加入到Mesh网络中,GATT bearer使用Proxy protocol通过GATT连接在设备之间转发、接收Proxy PDUs 。要建立GATT连接,您需要以下几个步骤:

  • 在设备端,实现Mesh Proxy Service,包括两个特征:Mesh Proxy Data In和Mesh Proxy Data Out,用于接收和发送Proxy PDUs。
  • 在设备端,启动广播,发送Unprovisioned Device beacon或Secure Network beacon,携带Mesh UUID和Network ID,用于被Provisioner或Proxy Client发现。
  • 在Provisioner或Proxy Client端,扫描广播,根据Mesh UUID或Network ID识别Mesh设备,发起GATT连接请求。
  • 在设备端,接受GATT连接请求,建立GATT连接,启用Mesh Proxy Data In和Mesh Proxy Data Out的通知。
  • 在Provisioner或Proxy Client端,启用Mesh Proxy Data In和Mesh Proxy Data Out的通知,通过它们发送和接收Proxy PDUs。

下面是一个基于Zephyr的示例代码,用于在设备端实现Mesh Proxy Service和建立GATT连接:

// 定义Mesh Proxy Service的UUID
#define BT_UUID_MESH_PROXY_VAL 0x1828
#define BT_UUID_MESH_PROXY BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_VAL)

// 定义Mesh Proxy Data In特征的UUID
#define BT_UUID_MESH_PROXY_DATA_IN_VAL 0x2ADD
#define BT_UUID_MESH_PROXY_DATA_IN BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_DATA_IN_VAL)

// 定义Mesh Proxy Data Out特征的UUID
#define BT_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ADE
#define BT_UUID_MESH_PROXY_DATA_OUT BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_DATA_OUT_VAL)

// 定义Mesh Proxy Data In特征的值
static uint8_t proxy_in_data[31];

// 定义Mesh Proxy Data Out特征的值
static uint8_t proxy_out_data[31];

// 定义Mesh Proxy Data Out特征的描述符
static struct bt_gatt_ccc_cfg proxy_out_ccc[BT_GATT_CCC_MAX] = {};

// 定义Mesh Proxy Data In特征的写入回调函数
static void proxy_in_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
               const void *buf, uint16_t len, uint16_t offset,
               uint8_t flags)
{
    // 检查写入的数据长度是否有效
    if (len > sizeof(proxy_in_data)) {
        return;
    }

    // 拷贝写入的数据到特征的值
    memcpy(proxy_in_data, buf, len);

    // 调用Mesh协议栈的接口,处理Proxy PDU
    bt_mesh_proxy_recv(conn, proxy_in_data, len);
}

// 定义Mesh Proxy Data Out特征的读取回调函数
static void proxy_out_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
    // 获取当前的GATT连接
    struct bt_conn *conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &proxy_addr);

    // 检查是否启用了通知
    if (value == BT_GATT_CCC_NOTIFY) {
        // 调用Mesh协议栈的接口,启用Proxy功能
        bt_mesh_proxy_connected(conn);
    } else {
        // 调用Mesh协议栈的接口,停用Proxy功能
        bt_mesh_proxy_disconnected(conn);
    }

    // 释放GATT连接的引用
    bt_conn_unref(conn);
}

// 定义Mesh Proxy Service的属性
static struct bt_gatt_attr attrs[] = {
    // Mesh Proxy Service声明
    BT_GATT_PRIMARY_SERVICE(BT_UUID_MESH_PROXY),
    // Mesh Proxy Data In特征声明
    BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_IN,
               BT_GATT_CHRC_WRITE_WITHOUT_RESP,
               BT_GATT_PERM_WRITE,
               NULL, proxy_in_write, NULL),
    // Mesh Proxy Data Out特征声明
    BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_OUT,
               BT_GATT_CHRC_NOTIFY,
               BT_GATT_PERM_NONE,
               NULL, NULL, &proxy_out_data),
    // Mesh Proxy Data Out特征的描述符声明
    BT_GATT_CCC_MANAGED(proxy_out_ccc,
            BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
            NULL, proxy_out_ccc_changed, NULL),
};

// 定义Mesh Proxy Service的服务
static struct bt_gatt_service prov_svc = BT_GATT_SERVICE(attrs);

// 注册Mesh Proxy Service
static int proxy_svc_register(void)
{
    return bt_gatt_service_register(&prov_svc);
}

// 启动广播,发送Unprovisioned Device beacon或Secure Network beacon
static int proxy_adv_start(void)
{
    // 创建一个广播参数的结构体
    struct bt_le_adv_param adv_param = {
        .options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME,
        .interval_min = BT_GAP_ADV_FAST_INT_MIN_2,
        .interval_max = BT_GAP_ADV_FAST_INT_MAX_2,
    };

    // 创建一个广播数据的结构体
    struct bt_data adv_data[2] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA(BT_DATA_SVC_DATA16, NULL, 0),
    };

    // 根据设备的状态,设置广播数据的类型和内容
    if (bt_mesh_is_provisioned()) {
        // 已入网,发送Secure Network beacon
        adv_data[1].data_len = 18;
        adv_data[1].data = net_beacon_data;
    } else {
        // 未入网,发送Unprovisioned Device beacon
        adv_data[1].data_len = 16;
        adv_data[1].data = unprov_beacon_data;
    }

    // 调用BLE协议栈的接口,启动广播
    return bt_le_adv_start(&adv_param, adv_data, ARRAY_SIZE(adv_data),
                   NULL, 0);
}

四、资料获取

通过点击以下链接,您可以获取BLE Mesh模块原理图、源代码以及开发资料。链接地址将为您提供详细的文件资料,以供您进行参考和使用。

如果您在使用过程中遇到任何问题或疑虑,欢迎加我QQ ,一起探讨技术问题,我的QQ号是986571840,加的时候请注明CSDN。

BLE Mesh蓝牙组网模块 - 硬创社 (jlc.com)icon-default.png?t=N7T8https://x.jlc.com/platform/detail/001d23cba7b64b0d9df5b9b69720fadb

感谢各位用户点赞、分享、在看,这些行为让知识得以更加广泛地传播,从而让更多人受益。

请在转载作品时注明出处,严禁抄袭行为。

  • 33
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芯航路IOT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值