Kaynağa Gözat

Revert "remove: 移除蓝牙支持库nimble"

This reverts commit 7d791847e81123109a235537787fee45088e3c8b.
Wendal Chen 1 yıl önce
ebeveyn
işleme
1db4efc51c

+ 79 - 0
components/nimble/inc/luat_nimble.h

@@ -0,0 +1,79 @@
+
+#ifndef LUAT_NUMBLE_H
+#define LUAT_NUMBLE_H
+#include "luat_base.h"
+#include "luat_msgbus.h"
+
+#include "host/ble_gatt.h"
+#include "host/ble_hs_id.h"
+#include "host/util/util.h"
+#include "host/ble_hs_adv.h"
+#include "host/ble_gap.h"
+#include "host/ble_uuid.h"
+
+#define LUAT_BLE_MAX_CHR (3)
+
+#define MAX_PER_SERV (4)
+
+/** Bluetooth Adapter State */
+typedef enum
+{
+    BT_STATE_OFF,
+    BT_STATE_ON,
+    BT_STATE_CONNECTED,
+    BT_STATE_DISCONNECT,
+} bt_state_t;
+
+
+typedef enum
+{
+    BT_MODE_BLE_SERVER,
+    BT_MODE_BLE_CLIENT,
+    BT_MODE_BLE_BEACON,
+    BT_MODE_BLE_MESH,
+} bt_mode_t;
+
+int luat_nimble_trace_level(int level);
+
+int luat_nimble_init(uint8_t uart_idx, char* name, int mode);
+int luat_nimble_deinit();
+
+int luat_nimble_server_send(int id, char* data, size_t len);
+
+int luat_nimble_server_send_notify(ble_uuid_any_t* srv, ble_uuid_any_t* chr, char* data, size_t len);
+
+int luat_nimble_server_send_indicate(ble_uuid_any_t* srv, ble_uuid_any_t* chr, char* data, size_t len);
+
+
+
+int luat_nimble_server_send2(ble_uuid_any_t* srv, ble_uuid_any_t* chr, char* data, size_t len, int type);
+
+int luat_nimble_blecent_scan(int timeout);
+
+int luat_nimble_blecent_connect(const char* addr);
+int luat_nimble_blecent_disconnect(int id);
+
+// 直接设置标准的ibeacon数据
+int luat_nimble_ibeacon_setup(void *uuid128, uint16_t major,
+                         uint16_t minor, int8_t measured_power);
+
+// 自由设置广播数据, 比ibeacon更自由
+int luat_nimble_set_adv_data(char* buff, size_t len, int flags);
+
+
+int luat_nimble_peripheral_set_chr(int index, ble_uuid_any_t* chr_uuid, int flags);
+
+int luat_nimble_central_disc_srv(int id);
+int luat_nimble_central_disc_chr(int id, struct ble_gatt_svc *service);
+int luat_nimble_central_disc_dsc(int id, struct ble_gatt_svc *service, struct ble_gatt_chr *chr);
+
+int luat_nimble_central_write(int id, struct ble_gatt_chr * chr, char* data, size_t len);
+int luat_nimble_central_read(int id, struct ble_gatt_chr *);
+
+
+int luat_nimble_central_subscribe(int id, struct ble_gatt_chr * chr, int onoff);
+
+int luat_nimble_mac_set(const char* tmac);
+
+#endif
+

+ 1013 - 0
components/nimble/src/luat_lib_nimble.c

@@ -0,0 +1,1013 @@
+/*
+@module  nimble
+@summary 蓝牙BLE库(nimble版)
+@version 1.0
+@date    2022.10.21
+@demo    nimble
+@tag LUAT_USE_NIMBLE
+@usage
+-- 本库当前支持Air101/Air103/ESP32/ESP32C3/ESP32S3
+-- 用法请查阅demo, API函数会归于指定的模式
+
+-- 名称解释:
+-- peripheral 外设模式, 或者成为从机模式, 是被连接的设备
+-- central    中心模式, 或者成为主机模式, 是扫描并连接其他设备
+-- ibeacon    周期性的beacon广播
+
+-- UUID       设备的服务(service)和特征(characteristic)会以UUID作为标识,支持 2字节/4字节/16字节,通常用2字节的缩短版本
+-- chr        设备的服务(service)由多个特征(characteristic)组成, 简称chr
+-- characteristic 特征由UUID和flags组成, 其中UUID做标识, flags代表该特征可以支持的功能
+*/
+
+#include "luat_base.h"
+#include "luat_msgbus.h"
+#include "luat_mem.h"
+#include "luat_spi.h"
+
+
+#include "host/ble_gatt.h"
+#include "host/ble_hs_id.h"
+#include "host/util/util.h"
+#include "host/ble_hs_adv.h"
+#include "host/ble_gap.h"
+
+#include "luat_nimble.h"
+
+#define LUAT_LOG_TAG "nimble"
+#include "luat_log.h"
+
+#define CFG_ADDR_ORDER 1
+
+static uint32_t nimble_mode = 0;
+uint16_t g_ble_state;
+uint16_t g_ble_conn_handle;
+
+// peripheral, 被扫描, 被连接设备的UUID配置
+ble_uuid_any_t ble_peripheral_srv_uuid;
+uint16_t s_chr_flags[LUAT_BLE_MAX_CHR];
+uint16_t s_chr_val_handles[LUAT_BLE_MAX_CHR];
+ble_uuid_any_t s_chr_uuids[LUAT_BLE_MAX_CHR];
+uint8_t s_chr_notify_states[LUAT_BLE_MAX_CHR];
+uint8_t s_chr_indicate_states[LUAT_BLE_MAX_CHR];
+
+#define WM_GATT_SVC_UUID      0x180D
+// #define WM_GATT_SVC_UUID      0xFFF0
+#define WM_GATT_INDICATE_UUID 0xFFF1
+#define WM_GATT_WRITE_UUID    0xFFF2
+#define WM_GATT_NOTIFY_UUID    0xFFF3
+
+
+uint8_t luat_ble_dev_name[32];
+size_t  luat_ble_dev_name_len;
+
+uint8_t adv_buff[128];
+int adv_buff_len = 0;
+struct ble_hs_adv_fields adv_fields;
+struct ble_gap_adv_params adv_params = {0};
+
+static uint8_t ble_uuid_addr_conv = 0; // BLE的地址需要反序, 就蛋疼了
+
+struct ble_gatt_svc *peer_servs[MAX_PER_SERV];
+struct ble_gatt_chr *peer_chrs[MAX_PER_SERV*MAX_PER_SERV];
+
+static int buff2uuid(ble_uuid_any_t* uuid, const char* data, size_t data_len) {
+    if (data_len > 16)
+        return -1;
+    char tmp[16];
+    for (size_t i = 0; i < data_len; i++)
+    {
+        if (ble_uuid_addr_conv == 0)
+            tmp[i] = data[i];
+        else
+            tmp[i] = data[data_len - i - 1];
+    }
+    return ble_uuid_init_from_buf(uuid, tmp, data_len);
+}
+
+
+//              通用API, 适合全部模式
+//--------------------------------------------------
+
+/*
+初始化BLE上下文,开始对外广播/扫描
+@api nimble.init(name)
+@string 蓝牙设备名称,可选,建议填写
+@return bool 成功与否
+@usage
+-- 参考 demo/nimble
+-- 本函数对所有模式都适用
+*/
+static int l_nimble_init(lua_State* L) {
+    int rc = 0;
+    size_t len = 0;
+    const char* name = NULL;
+
+    lua_gc(L, LUA_GCCOLLECT, 0);
+    lua_gc(L, LUA_GCCOLLECT, 0);
+
+    if(lua_isstring(L, 1)) {
+        name = luaL_checklstring(L, 1, &len);
+        if (len > 0) {
+            memcpy(luat_ble_dev_name, name, len);
+            luat_ble_dev_name_len = len;
+        }
+    }
+    LLOGD("init name %s mode %d", name == NULL ? "-" : name, nimble_mode);
+    rc = luat_nimble_init(0xFF, name, nimble_mode);
+    if (rc) {
+        lua_pushboolean(L, 0);
+        lua_pushinteger(L, rc);
+        return 2;
+    }
+    else {
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+}
+
+/*
+关闭BLE上下文
+@api nimble.deinit()
+@return bool 成功与否
+@usage
+-- 仅部分设备支持,当前可能都不支持
+-- 本函数对所有模式都适用
+*/
+static int l_nimble_deinit(lua_State* L) {
+    int rc = 0;
+    rc = luat_nimble_deinit();
+    if (rc) {
+        lua_pushboolean(L, 0);
+        lua_pushinteger(L, rc);
+        return 2;
+    }
+    else {
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+}
+
+static int l_nimble_debug(lua_State* L) {
+    LLOGI("nimble.debug is removed");
+    lua_pushinteger(L, 0);
+    return 1;
+}
+
+static int l_nimble_server_init(lua_State* L) {
+    LLOGI("nimble.server_init is removed");
+    return 0;
+}
+
+static int l_nimble_server_deinit(lua_State* L) {
+    LLOGI("nimble.server_deinit is removed");
+    return 0;
+}
+
+/*
+设置模式
+@api nimble.mode(tp)
+@int 模式, 默认server/peripheral, 可选 client/central模式 nimble.MODE_BLE_CLIENT
+@return bool 成功与否
+@usage
+-- 参考 demo/nimble
+-- 必须在nimble.init()之前调用
+-- nimble.mode(nimble.MODE_BLE_CLIENT) -- 简称从机模式,未完善
+*/
+static int l_nimble_mode(lua_State *L) {
+    if (lua_isinteger(L, 1)) {
+        nimble_mode = lua_tointeger(L, 1);
+    }
+    lua_pushinteger(L, nimble_mode);
+    return 1;
+}
+
+/*
+是否已经建立连接
+@api nimble.connok()
+@return bool 已连接返回true,否则返回false
+@usage
+log.info("ble", "connected?", nimble.connok())
+-- 从机peripheral模式, 设备是否已经被连接
+-- 主机central模式, 是否已经连接到设备
+-- ibeacon模式, 无意义
+*/
+static int l_nimble_connok(lua_State *L) {
+    lua_pushboolean(L, g_ble_state == BT_STATE_CONNECTED ? 1 : 0);
+    return 1;
+}
+
+
+//--------------------------------------------------
+//             从机peripheral模式系列API
+
+/*
+发送信息
+@api nimble.send_msg(conn, handle, data)
+@int 连接id, 当前固定填1
+@int 处理id, 当前固定填0
+@string 数据字符串,可包含不可见字符
+@return bool 成功与否
+@usage
+-- 参考 demo/nimble
+-- 本函数对peripheral/从机模式适用
+*/
+static int l_nimble_send_msg(lua_State *L) {
+    int conn_id = luaL_checkinteger(L, 1);
+    int handle_id = luaL_checkinteger(L, 2);
+    size_t len = 0;
+    const char* data = luaL_checklstring(L, 3, &len);
+    int ret = 0;
+    if (len == 0) {
+        LLOGI("send emtry msg? ignored");
+    }
+    else {
+        ret = luat_nimble_server_send(0, data, len);
+    }
+
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    // lua_pushinteger(L, ret);
+    return 1;
+}
+
+
+/*
+设置server/peripheral的UUID
+@api nimble.setUUID(tp, addr)
+@string 配置字符串,后面的示例有说明
+@string 地址字符串
+@return bool 成功与否
+@usage
+-- 参考 demo/nimble, 2023-02-25之后编译的固件支持本API
+-- 必须在nimble.init()之前调用
+-- 本函数对peripheral/从机模式适用
+
+-- 设置SERVER/Peripheral模式下的UUID, 支持设置3个
+-- 地址支持 2/4/16字节, 需要二进制数据
+-- 2字节地址示例: AABB, 写 string.fromHex("AABB") ,或者 string.char(0xAA, 0xBB)
+-- 4字节地址示例: AABBCCDD , 写 string.fromHex("AABBCCDD") ,或者 string.char(0xAA, 0xBB, 0xCC, 0xDD)
+nimble.setUUID("srv", string.fromHex("380D"))      -- 服务主UUID         ,  默认值 180D
+nimble.setUUID("write", string.fromHex("FF31"))    -- 往本设备写数据的UUID,  默认值 FFF1
+nimble.setUUID("indicate", string.fromHex("FF32")) -- 订阅本设备的数据的UUID,默认值 FFF2
+*/
+static int l_nimble_set_uuid(lua_State *L) {
+    size_t len = 0;
+    ble_uuid_any_t tmp = {0};
+    const char* key = luaL_checkstring(L, 1);
+    const char* uuid = luaL_checklstring(L, 2, &len);
+    int ret = buff2uuid(&tmp, (const void*)uuid, len);
+    if (ret != 0) {
+        LLOGW("invaild UUID, len must be 2/4/16");
+        return 0;
+    }
+    if (!strcmp("srv", key)) {
+        memcpy(&ble_peripheral_srv_uuid, &tmp, sizeof(ble_uuid_any_t));
+    }
+    else if (!strcmp("write", key)) {
+        memcpy(&s_chr_uuids[0], &tmp, sizeof(ble_uuid_any_t));
+    }
+    else if (!strcmp("indicate", key)) {
+        memcpy(&s_chr_uuids[1], &tmp, sizeof(ble_uuid_any_t));
+    }
+    else if (!strcmp("notify", key)) {
+        memcpy(&s_chr_uuids[2], &tmp, sizeof(ble_uuid_any_t));
+    }
+    else {
+        LLOGW("only support srv/write/indicate/notify");
+        return 0;
+    }
+    lua_pushboolean(L, 1);
+    return 1;
+}
+
+/*
+获取蓝牙MAC
+@api nimble.mac(mac)
+@string 待设置的MAC地址, 6字节, 不传就是单获取
+@return string 蓝牙MAC地址,6字节
+@usage
+-- 参考 demo/nimble, 2023-02-25之后编译的固件支持本API
+-- 本函数对所有模式都适用
+local mac = nimble.mac()
+log.info("ble", "mac", mac and mac:toHex() or "Unknwn")
+
+-- 修改MAC地址, 2024.06.05 新增, 当前仅Air601支持, 修改后重启生效
+nimble.mac(string.fromHex("1234567890AB"))
+*/
+static int l_nimble_mac(lua_State *L) {
+    int rc = 0;
+    uint8_t own_addr_type = 0;
+    uint8_t addr_val[6] = {0};
+    if (lua_type(L, 1) == LUA_TSTRING) {
+        size_t len = 0;
+        const char* tmac = luaL_checklstring(L, 1, &len);
+        if (len != 6) {
+            LLOGW("mac len must be 6");
+            return 0;
+        }
+        luat_nimble_mac_set(tmac);
+    }
+    #ifdef TLS_CONFIG_CPU_XT804
+    if (1) {
+        extern int luat_nimble_mac_get(uint8_t* mac);
+        luat_nimble_mac_get(addr_val);
+        lua_pushlstring(L, (const char*)addr_val, 6);
+        return 1;
+    }
+    #endif
+    rc = ble_hs_util_ensure_addr(0);
+    if (rc != 0) {
+        LLOGW("fail to fetch BLE MAC, rc %d", rc);
+        return 0;
+    }
+
+    /* Figure out address to use while advertising (no privacy for now) */
+    rc = ble_hs_id_infer_auto(0, &own_addr_type);
+    if (rc != 0) {
+        LLOGE("error determining address type; rc=%d", rc);
+        return 0;
+    }
+
+    /* Printing ADDR */
+    
+    rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
+    if (rc == 0) {
+        lua_pushlstring(L, (const char*)addr_val, 6);
+        return 1;
+    }
+    LLOGW("fail to fetch BLE MAC, rc %d", rc);
+    return 0;
+}
+
+
+/*
+发送notify
+@api nimble.sendNotify(srv_uuid, chr_uuid, data)
+@string 服务的UUID,预留,当前填nil就行
+@string 特征的UUID,必须填写
+@string 数据, 必填, 跟MTU大小相关, 一般不要超过256字节
+@return bool 成功返回true,否则返回false
+@usage
+-- 本API于 2023.07.31 新增
+-- 本函数对peripheral模式适用
+nimble.sendNotify(nil, string.fromHex("FF01"), string.char(0x31, 0x32, 0x33, 0x34, 0x35))
+*/
+static int l_nimble_send_notify(lua_State *L) {
+    size_t tmp_size = 0;
+    size_t data_len = 0;
+    ble_uuid_any_t chr_uuid;
+    const char* tmp = luaL_checklstring(L, 2, &tmp_size);
+    int ret = buff2uuid(&chr_uuid, tmp, tmp_size);
+    if (ret) {
+        LLOGE("ble_uuid_init_from_buf rc %d", ret);
+        return 0;
+    }
+    const char* data = luaL_checklstring(L, 3, &data_len);
+    ret = luat_nimble_server_send_notify(NULL, &chr_uuid, data, data_len);
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    return 1;
+}
+
+/*
+发送indicate
+@api nimble.sendIndicate(srv_uuid, chr_uuid, data)
+@string 服务的UUID,预留,当前填nil就行
+@string 特征的UUID,必须填写
+@string 数据, 必填, 跟MTU大小相关, 一般不要超过256字节
+@return bool 成功返回true,否则返回false
+@usage
+-- 本API于 2023.07.31 新增
+-- 本函数对peripheral模式适用
+nimble.sendIndicate(nil, string.fromHex("FF01"), string.char(0x31, 0x32, 0x33, 0x34, 0x35))
+*/
+static int l_nimble_send_indicate(lua_State *L) {
+    size_t tmp_size = 0;
+    size_t data_len = 0;
+    ble_uuid_any_t chr_uuid;
+    const char* tmp = luaL_checklstring(L, 2, &tmp_size);
+    int ret = buff2uuid(&chr_uuid, tmp, tmp_size);
+    if (ret) {
+        LLOGE("ble_uuid_init_from_buf rc %d", ret);
+        return 0;
+    }
+    const char* data = luaL_checklstring(L, 3, &data_len);
+    ret = luat_nimble_server_send_indicate(NULL, &chr_uuid, data, data_len);
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    return 1;
+}
+
+
+/*
+设置广播参数
+@api nimble.advParams(conn_mode, disc_mode, itvl_min, itvl_max, channel_map, filter_policy, high_duty_cycle)
+@int 广播模式, 0 - 不可连接, 1 - 定向连接, 2 - 未定向连接, 默认0
+@int 发现模式, 0 - 不可发现, 1 - 限制发现, 3 - 通用发现, 默认0
+@int 最小广播间隔, 0 - 使用默认值, 范围 1 - 65535, 单位0.625ms, 默认0
+@int 最大广播间隔, 0 - 使用默认值, 范围 1 - 65535, 单位0.625ms, 默认0
+@int 广播通道, 默认0, 一般不需要设置
+@int 过滤规则, 默认0, 一般不需要设置
+@int 当广播模式为"定向连接"时,是否使用高占空比模式, 默认0, 可选1
+@return nil 无返回值
+@usage
+-- 当前仅ibeacon模式/peripheral/从机可使用
+-- 例如设置 不可连接 + 限制发现
+-- 需要在nimble.init之前设置好
+nimble.advParams(0, 1)
+-- 注意peripheral模式下自动配置 conn_mode 和 disc_mode
+*/
+static int l_nimble_set_adv_params(lua_State *L) {
+    /** Advertising mode. Can be one of following constants:
+     *  - BLE_GAP_CONN_MODE_NON (non-connectable; 3.C.9.3.2).
+     *  - BLE_GAP_CONN_MODE_DIR (directed-connectable; 3.C.9.3.3).
+     *  - BLE_GAP_CONN_MODE_UND (undirected-connectable; 3.C.9.3.4).
+     */
+    adv_params.conn_mode = luaL_optinteger(L, 1, 0);
+    /** Discoverable mode. Can be one of following constants:
+     *  - BLE_GAP_DISC_MODE_NON  (non-discoverable; 3.C.9.2.2).
+     *  - BLE_GAP_DISC_MODE_LTD (limited-discoverable; 3.C.9.2.3).
+     *  - BLE_GAP_DISC_MODE_GEN (general-discoverable; 3.C.9.2.4).
+     */
+    adv_params.disc_mode = luaL_optinteger(L, 2, 0);
+
+    /** Minimum advertising interval, if 0 stack use sane defaults */
+    adv_params.itvl_min = luaL_optinteger(L, 3, 0);
+    /** Maximum advertising interval, if 0 stack use sane defaults */
+    adv_params.itvl_max = luaL_optinteger(L, 4, 0);
+    /** Advertising channel map , if 0 stack use sane defaults */
+    adv_params.channel_map = luaL_optinteger(L, 5, 0);
+
+    /** Advertising  Filter policy */
+    adv_params.filter_policy = luaL_optinteger(L, 6, 0);
+
+    /** If do High Duty cycle for Directed Advertising */
+    adv_params.high_duty_cycle = luaL_optinteger(L, 7, 0);
+
+    return 0;
+}
+
+/*
+设置chr的特征
+@api nimble.setChr(index, uuid, flags)
+@int chr的索引, 默认0-3
+@int chr的UUID, 可以是2/4/16字节
+@int chr的FLAGS, 请查阅常量表
+@return nil 无返回值
+@usage
+-- 仅peripheral/从机可使用
+nimble.setChr(0, string.fromHex("FF01"), nimble.CHR_F_WRITE_NO_RSP | nimble.CHR_F_NOTIFY)
+nimble.setChr(1, string.fromHex("FF02"), nimble.CHR_F_READ | nimble.CHR_F_NOTIFY)
+nimble.setChr(2, string.fromHex("FF03"), nimble.CHR_F_WRITE_NO_RSP)
+-- 可查阅 demo/nimble/kt6368a
+*/
+static int l_nimble_set_chr(lua_State *L) {
+    size_t tmp_size = 0;
+    // ble_uuid_any_t srv_uuid = {0};
+    ble_uuid_any_t chr_uuid = {0};
+    const char* tmp;
+    int ret = 0;
+    int index = luaL_checkinteger(L, 1);
+    tmp = luaL_checklstring(L, 2, &tmp_size);
+    // LLOGD("chr? %02X%02X %d", tmp[0], tmp[1], tmp_size);
+    ret = buff2uuid(&chr_uuid, tmp, tmp_size);
+    if (ret) {
+        LLOGE("ble_uuid_init_from_buf rc %d", ret);
+        return 0;
+    }
+    int flags = luaL_checkinteger(L, 3);
+
+    luat_nimble_peripheral_set_chr(index, &chr_uuid, flags);
+    return 0;
+}
+
+/*
+设置chr的特征
+@api nimble.config(id, value)
+@int 配置的id,请查阅常量表
+@any 根据配置的不同, 有不同的可选值
+@return nil 无返回值
+@usage
+-- 本函数在任意模式可用
+-- 本API于 2023.07.31 新增
+-- 例如设置地址转换的大小端, 默认是0, 兼容老的代码
+-- 设置成1, 服务UUID和chr的UUID更直观
+nimble.config(nimble.CFG_ADDR_ORDER, 1)
+*/
+static int l_nimble_config(lua_State *L) {
+    int conf = luaL_checkinteger(L, 1);
+    if (conf == CFG_ADDR_ORDER) {
+        if (lua_isboolean(L, 2))
+            ble_uuid_addr_conv = lua_toboolean(L, 2);
+        else if (lua_isinteger(L, 2))
+            ble_uuid_addr_conv = lua_tointeger(L, 2);
+    }
+    return 0;
+}
+
+//-------------------------------------
+//              ibeacon系列API
+
+
+/*
+配置iBeacon的参数,仅iBeacon模式可用
+@api nimble.ibeacon(data, major, minor, measured_power)
+@string 数据, 必须是16字节
+@int 主版本号,默认2, 可选, 范围 0 ~ 65536
+@int 次版本号,默认10,可选, 范围 0 ~ 65536
+@int 名义功率, 默认0, 范围 -126 到 20 
+@return bool 成功返回true,否则返回false
+@usage
+-- 参考 demo/nimble, 2023-02-25之后编译的固件支持本API
+-- 本函数对ibeacon模式适用
+nimble.ibeacon(data, 2, 10, 0)
+nimble.init()
+*/
+static int l_nimble_ibeacon(lua_State *L) {
+    size_t len = 0;
+    const char* data = luaL_checklstring(L, 1, &len);
+    if (len != 16) {
+        LLOGE("ibeacon data MUST 16 bytes, but %d", len);
+        return 0;
+    }
+    uint16_t major = luaL_optinteger(L, 2, 2);
+    uint16_t minor = luaL_optinteger(L, 3, 10);
+    int8_t measured_power = luaL_optinteger(L, 4, 0);
+
+    int rc = luat_nimble_ibeacon_setup(data, major, minor, measured_power);
+    lua_pushboolean(L, rc == 0 ? 1 : 0);
+    return 1;
+}
+
+/*
+配置广播数据,仅iBeacon模式可用
+@api nimble.advData(data, flags)
+@string 广播数据, 当前最高128字节
+@int 广播标识, 可选, 默认值是 0x06,即 不支持传统蓝牙(0x04) + 普通发现模式(0x02)
+@return bool 成功返回true,否则返回false
+@usage
+-- 参考 demo/nimble/adv_free, 2023-03-18之后编译的固件支持本API
+-- 本函数对ibeacon模式适用
+-- 数据来源可以多种多样
+local data = string.fromHex("123487651234876512348765123487651234876512348765")
+-- local data = crypto.trng(25)
+-- local data = string.char(0x11, 0x13, 0xA3, 0x5A, 0x11, 0x13, 0xA3, 0x5A, 0x11, 0x13, 0xA3, 0x5A, 0x11, 0x13, 0xA3, 0x5A)
+nimble.advData(data)
+nimble.init()
+
+-- nimble支持在init之后的任意时刻再次调用, 以实现数据更新
+-- 例如 1分钟变一次
+while 1 do
+    sys.wait(60000)
+    local data = crypto.trng(25)
+    nimble.advData(data)
+end
+*/
+static int l_nimble_set_adv_data(lua_State *L) {
+    size_t len = 0;
+    const char* data = luaL_checklstring(L, 1, &len);
+    int flags = luaL_optinteger(L, 2, BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
+    int rc = luat_nimble_set_adv_data(data, len, flags);
+    lua_pushboolean(L, rc == 0 ? 1 : 0);
+    return 1;
+}
+
+//-----------------------------------------------------
+//              主机central模式API
+
+/*
+扫描从机
+@api nimble.scan(timeout)
+@int 超时时间,单位秒,默认28秒
+@return bool 启动扫描成功与否
+@usage
+-- 参考 demo/nimble/scan
+-- 本函数对central/主机模式适用
+-- 本函数会直接返回, 然后通过异步回调返回结果
+
+-- 调用本函数前, 需要先确保已经nimble.init()
+nimble.scan()
+-- timeout参数于 2023.7.11 添加
+*/
+static int l_nimble_scan(lua_State *L) {
+    int timeout = luaL_optinteger(L, 1, 28);
+    if (timeout < 1)
+        timeout = 1;
+    int ret = luat_nimble_blecent_scan(timeout);
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    // lua_pushinteger(L, ret);
+    return 1;
+}
+
+/*
+连接到从机
+@api nimble.connect(mac)
+@string 设备的MAC地址
+@return bool 启动连接成功与否
+@usage
+-- 本函数对central/主机模式适用
+-- 本函数会直接返回, 然后通过异步回调返回结果
+*/
+static int l_nimble_connect(lua_State *L) {
+    size_t len = 0;
+    const char* addr = luaL_checklstring(L, 1, &len);
+    if (addr == NULL)
+        return 0;
+    luat_nimble_blecent_connect(addr);
+    return 0;
+}
+
+/*
+断开与从机的连接
+@api nimble.disconnect()
+@return nil 无返回值
+@usage
+-- 本函数对central/主机模式适用
+-- 本函数会直接返回
+*/
+static int l_nimble_disconnect(lua_State *L) {
+    int id = luaL_optinteger(L, 1, 0);
+    luat_nimble_blecent_disconnect(id);
+    return 0;
+}
+
+/*
+扫描从机的服务列表
+@api nimble.discSvr()
+@return nil 无返回值
+@usage
+-- 本函数对central/主机模式适用
+-- 本函数会直接返回,然后异步返回结果
+-- 这个API通常不需要调用, 在连接从机完成后,会主动调用一次
+*/
+static int l_nimble_disc_svr(lua_State *L) {
+    int id = luaL_optinteger(L, 1, 0);
+    luat_nimble_central_disc_srv(id);
+    return 0;
+}
+
+/*
+获取从机的服务列表
+@api nimble.listSvr()
+@return table 服务UUID的数组
+@usage
+-- 本函数对central/主机模式适用
+*/
+static int l_nimble_list_svr(lua_State *L) {
+    lua_newtable(L);
+    char buff[64];
+    size_t i;
+    for (i = 0; i < MAX_PER_SERV; i++)
+    {
+        if (peer_servs[i] == NULL)
+            break;
+        lua_pushstring(L, ble_uuid_to_str(&peer_servs[i]->uuid, buff));
+        lua_seti(L, -2, i+1);
+    }
+    return 1;
+}
+
+/*
+扫描从机的指定服务的特征值
+@api nimble.discChr(svr_uuid)
+@string 指定服务的UUID值
+@return boolean 成功启动扫描与否
+@usage
+-- 本函数对central/主机模式适用
+*/
+static int l_nimble_disc_chr(lua_State *L) {
+    size_t tmp_size = 0;
+    size_t data_len = 0;
+    ble_uuid_any_t svr_uuid;
+    const char* tmp = luaL_checklstring(L, 1, &tmp_size);
+    int ret = buff2uuid(&svr_uuid, tmp, tmp_size);
+    if (ret) {
+        return 0;
+    }
+    size_t i;
+    char buff[64];
+    for (i = 0; i < MAX_PER_SERV; i++)
+    {
+        if (peer_servs[i] == NULL)
+            break;
+        if (0 == ble_uuid_cmp(&peer_servs[i]->uuid, &svr_uuid)) {
+            // LLOGD("找到匹配的UUID, 查询其特征值");
+            lua_pushboolean(L, 1);
+            luat_nimble_central_disc_chr(0, peer_servs[i]);
+            return 1;
+        }
+        // LLOGD("期望的服务id %s", ble_uuid_to_str(&svr_uuid, buff));
+        // LLOGD("实际的服务id %s", ble_uuid_to_str(&peer_servs[i]->uuid, buff));
+    }
+    return 0;
+}
+
+/*
+获取从机的指定服务的特征值列表
+@api nimble.listChr(svr_uuid)
+@string 指定服务的UUID值
+@return table 特征值列表,包含UUID和flags
+@usage
+-- 本函数对central/主机模式适用
+*/
+static int l_nimble_list_chr(lua_State *L) {
+    size_t tmp_size = 0;
+    size_t data_len = 0;
+    ble_uuid_any_t svr_uuid;
+    const char* tmp = luaL_checklstring(L, 1, &tmp_size);
+    int ret = buff2uuid(&svr_uuid, tmp, tmp_size);
+    if (ret) {
+        return 0;
+    }
+    size_t i;
+    char buff[64];
+    lua_newtable(L);
+    for (i = 0; i < MAX_PER_SERV; i++)
+    {
+        if (peer_servs[i] == NULL)
+            continue;
+        if (0 == ble_uuid_cmp(&peer_servs[i]->uuid, &svr_uuid)) {
+            for (size_t j = 0; j < MAX_PER_SERV; j++)
+            {
+                if (peer_chrs[i*MAX_PER_SERV + j] == NULL)
+                    break;
+                lua_newtable(L);
+                lua_pushstring(L, ble_uuid_to_str(&(peer_chrs[i*MAX_PER_SERV+j]->uuid), buff));
+                lua_setfield(L, -2, "uuid");
+                lua_pushinteger(L, peer_chrs[i*MAX_PER_SERV+j]->properties);
+                lua_setfield(L, -2, "flags");
+
+                lua_seti(L, -2, j + 1);
+            }
+            return 1;
+        }
+    }
+    return 1;
+}
+
+static int find_chr(lua_State *L, struct ble_gatt_svc **svc, struct ble_gatt_chr **chr) {
+    size_t tmp_size = 0;
+    int32_t ret = 0;
+    const char* tmp;
+    ble_uuid_any_t svr_uuid;
+    ble_uuid_any_t chr_uuid;
+    // 服务的UUID
+    tmp = luaL_checklstring(L, 1, &tmp_size);
+    ret = buff2uuid(&svr_uuid, tmp, tmp_size);
+    if (ret) {
+        return -1;
+    }
+    // 特征的UUUID
+    tmp = luaL_checklstring(L, 2, &tmp_size);
+    ret = buff2uuid(&chr_uuid, tmp, tmp_size);
+    if (ret) {
+        return -1;
+    }
+    for (size_t i = 0; i < MAX_PER_SERV; i++)
+    {
+        if (peer_servs[i] == NULL)
+            continue;
+        if (0 == ble_uuid_cmp(&peer_servs[i]->uuid, &svr_uuid)) {
+            *svc = peer_servs[i];
+            for (size_t j = 0; j < MAX_PER_SERV; j++)
+            {
+                if (peer_chrs[i*MAX_PER_SERV + j] == NULL)
+                    break;
+                if (0 == ble_uuid_cmp(&peer_chrs[i*MAX_PER_SERV + j]->uuid, &chr_uuid)) {
+                    *chr = peer_chrs[i*MAX_PER_SERV + j];
+                    return 0;
+                }
+            }
+        }
+    }
+    return -1;
+}
+
+/*
+扫描从机的指定服务的特征值的其他属性
+@api nimble.discDsc(svr_uuid, chr_uuid)
+@string 指定服务的UUID值
+@string 特征值的UUID值
+@return boolean 成功启动扫描与否
+@usage
+-- 本函数对central/主机模式适用
+*/
+static int l_nimble_disc_dsc(lua_State *L) {
+    int ret;
+    struct ble_gatt_svc *svc;
+    struct ble_gatt_chr *chr;
+    ret = find_chr(L, &svc, &chr);
+    if (ret) {
+        LLOGW("bad svr/chr UUID");
+        return 0;
+    }
+    ret = luat_nimble_central_disc_dsc(0, svc, chr);
+    if (ret == 0) {
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+    return 0;
+}
+
+
+/*
+往指定的服务的指定特征值写入数据
+@api nimble.writeChr(svr_uuid, chr_uuid, data)
+@string 指定服务的UUID值
+@string 指定特征值的UUID值
+@string 待写入的数据
+@return boolean 成功启动写入与否
+@usage
+-- 本函数对central/主机模式适用
+*/
+static int l_nimble_write_chr(lua_State *L) {
+    size_t tmp_size = 0;
+    int32_t ret = 0;
+    const char* tmp;
+    struct ble_gatt_svc *svc;
+    struct ble_gatt_chr *chr;
+    ret = find_chr(L, &svc, &chr);
+    if (ret) {
+        LLOGW("bad svr/chr UUID");
+        return 0;
+    }
+    // 数据
+    tmp = luaL_checklstring(L, 3, &tmp_size);
+    ret = luat_nimble_central_write(0, chr, tmp, tmp_size);
+    if (ret == 0) {
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+    return 0;
+}
+
+/*
+从指定的服务的指定特征值读取数据(异步)
+@api nimble.readChr(svr_uuid, chr_uuid)
+@string 指定服务的UUID值
+@string 指定特征值的UUID值
+@return boolean 成功启动写入与否
+@usage
+-- 本函数对central/主机模式适用
+-- 详细用法请参数 demo/nimble/central
+*/
+static int l_nimble_read_chr(lua_State *L) {
+    int32_t ret = 0;
+    struct ble_gatt_svc *svc;
+    struct ble_gatt_chr *chr;
+    ret = find_chr(L, &svc, &chr);
+    if (ret) {
+        LLOGW("bad svr/chr UUID");
+        return 0;
+    }
+    ret = luat_nimble_central_read(0, chr);
+    if (ret == 0) {
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+    return 0;
+}
+
+/*
+订阅指定的服务的指定特征值
+@api nimble.subChr(svr_uuid, chr_uuid)
+@string 指定服务的UUID值
+@string 指定特征值的UUID值
+@return boolean 成功启动订阅与否
+@usage
+-- 本函数对central/主机模式适用
+-- 详细用法请参数 demo/nimble/central
+*/
+static int l_nimble_subscribe_chr(lua_State *L) {
+    int32_t ret = 0;
+    struct ble_gatt_svc *svc;
+    struct ble_gatt_chr *chr;
+    ret = find_chr(L, &svc, &chr);
+    if (ret) {
+        LLOGW("bad svr/chr UUID");
+        return 0;
+    }
+    ret = luat_nimble_central_subscribe(0, chr, 1);
+    if (ret == 0) {
+        LLOGD("订阅成功");
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+    LLOGD("订阅失败 %d", ret);
+    return 0;
+}
+
+/*
+取消订阅指定的服务的指定特征值
+@api nimble.unsubChr(svr_uuid, chr_uuid)
+@string 指定服务的UUID值
+@string 指定特征值的UUID值
+@return boolean 成功启动取消订阅与否
+@usage
+-- 本函数对central/主机模式适用
+-- 详细用法请参数 demo/nimble/central
+*/
+static int l_nimble_unsubscribe_chr(lua_State *L) {
+    int32_t ret = 0;
+    struct ble_gatt_svc *svc;
+    struct ble_gatt_chr *chr;
+    ret = find_chr(L, &svc, &chr);
+    if (ret) {
+        LLOGW("bad svr/chr UUID");
+        return 0;
+    }
+    ret = luat_nimble_central_subscribe(0, chr, 0);
+    if (ret == 0) {
+        lua_pushboolean(L, 1);
+        return 1;
+    }
+    return 0;
+}
+
+#include "rotable2.h"
+static const rotable_Reg_t reg_nimble[] =
+{
+	{ "init",           ROREG_FUNC(l_nimble_init)},
+    { "deinit",         ROREG_FUNC(l_nimble_deinit)},
+    { "debug",          ROREG_FUNC(l_nimble_debug)},
+    { "mode",           ROREG_FUNC(l_nimble_mode)},
+    { "connok",         ROREG_FUNC(l_nimble_connok)},
+    { "config",         ROREG_FUNC(l_nimble_config)},
+
+    // 外设模式, 广播并等待连接
+    { "send_msg",       ROREG_FUNC(l_nimble_send_msg)},
+    { "sendNotify",     ROREG_FUNC(l_nimble_send_notify)},
+    { "sendIndicate",   ROREG_FUNC(l_nimble_send_indicate)},
+    { "setUUID",        ROREG_FUNC(l_nimble_set_uuid)},
+    { "setChr",         ROREG_FUNC(l_nimble_set_chr)},
+    { "mac",            ROREG_FUNC(l_nimble_mac)},
+    { "server_init",    ROREG_FUNC(l_nimble_server_init)},
+    { "server_deinit",  ROREG_FUNC(l_nimble_server_deinit)},
+
+    // 中心模式, 扫描并连接外设
+    { "scan",           ROREG_FUNC(l_nimble_scan)},
+    { "connect",        ROREG_FUNC(l_nimble_connect)},
+    { "disconnect",     ROREG_FUNC(l_nimble_disconnect)},
+    { "discSvr",        ROREG_FUNC(l_nimble_disc_svr)},
+    { "discChr",        ROREG_FUNC(l_nimble_disc_chr)},
+    { "discDsc",        ROREG_FUNC(l_nimble_disc_dsc)},
+    { "listSvr",        ROREG_FUNC(l_nimble_list_svr)},
+    { "listChr",        ROREG_FUNC(l_nimble_list_chr)},
+    { "readChr",        ROREG_FUNC(l_nimble_read_chr)},
+    { "writeChr",       ROREG_FUNC(l_nimble_write_chr)},
+    { "subChr",         ROREG_FUNC(l_nimble_subscribe_chr)},
+    { "unsubChr",       ROREG_FUNC(l_nimble_unsubscribe_chr)},
+
+    // ibeacon广播模式
+    { "ibeacon",        ROREG_FUNC(l_nimble_ibeacon)},
+
+    // 广播数据
+    { "advData",        ROREG_FUNC(l_nimble_set_adv_data)},
+    { "advParams",        ROREG_FUNC(l_nimble_set_adv_params)},
+
+    // 放一些常量
+    { "STATE_OFF",           ROREG_INT(BT_STATE_OFF)},
+    { "STATE_ON",            ROREG_INT(BT_STATE_ON)},
+    { "STATE_CONNECTED",     ROREG_INT(BT_STATE_CONNECTED)},
+    { "STATE_DISCONNECT",    ROREG_INT(BT_STATE_DISCONNECT)},
+
+    // 模式
+    { "MODE_BLE_SERVER",           ROREG_INT(BT_MODE_BLE_SERVER)},
+    { "MODE_BLE_CLIENT",           ROREG_INT(BT_MODE_BLE_CLIENT)},
+    { "MODE_BLE_BEACON",           ROREG_INT(BT_MODE_BLE_BEACON)},
+    { "MODE_BLE_MESH",             ROREG_INT(BT_MODE_BLE_MESH)},
+    { "SERVER",                    ROREG_INT(BT_MODE_BLE_SERVER)},
+    { "CLIENT",                    ROREG_INT(BT_MODE_BLE_CLIENT)},
+    { "BEACON",                    ROREG_INT(BT_MODE_BLE_BEACON)},
+    { "MESH",                      ROREG_INT(BT_MODE_BLE_MESH)},
+
+    // FLAGS
+    //@const CHR_F_WRITE number chr的FLAGS值, 可写, 且需要响应
+    {"CHR_F_WRITE",                ROREG_INT(BLE_GATT_CHR_F_WRITE)},
+    //@const CHR_F_READ number chr的FLAGS值, 可读
+    {"CHR_F_READ",                 ROREG_INT(BLE_GATT_CHR_F_READ)},
+    //@const CHR_F_WRITE_NO_RSP number chr的FLAGS值, 可写, 不需要响应
+    {"CHR_F_WRITE_NO_RSP",         ROREG_INT(BLE_GATT_CHR_F_WRITE_NO_RSP)},
+    //@const CHR_F_NOTIFY number chr的FLAGS值, 可订阅, 不需要回复
+    {"CHR_F_NOTIFY",               ROREG_INT(BLE_GATT_CHR_F_NOTIFY)},
+    //@const CHR_F_INDICATE number chr的FLAGS值, 可订阅, 需要回复
+    {"CHR_F_INDICATE",             ROREG_INT(BLE_GATT_CHR_F_INDICATE)},
+
+    // CONFIG
+    //@const CFG_ADDR_ORDER number UUID的转换的大小端, 结合config函数使用, 默认0, 可选0/1
+    {"CFG_ADDR_ORDER",                ROREG_INT(CFG_ADDR_ORDER)},
+
+	{ NULL,             ROREG_INT(0)}
+};
+
+LUAMOD_API int luaopen_nimble( lua_State *L ) {
+    memcpy(&ble_peripheral_srv_uuid, BLE_UUID16_DECLARE(WM_GATT_SVC_UUID), sizeof(ble_uuid16_t));
+    memcpy(&s_chr_uuids[0], BLE_UUID16_DECLARE(WM_GATT_WRITE_UUID), sizeof(ble_uuid16_t));
+    memcpy(&s_chr_uuids[1], BLE_UUID16_DECLARE(WM_GATT_INDICATE_UUID), sizeof(ble_uuid16_t));
+    memcpy(&s_chr_uuids[2], BLE_UUID16_DECLARE(WM_GATT_NOTIFY_UUID), sizeof(ble_uuid16_t));
+
+    s_chr_flags[0] = BLE_GATT_CHR_F_WRITE;
+    s_chr_flags[1] = BLE_GATT_CHR_F_INDICATE;
+    s_chr_flags[2] = BLE_GATT_CHR_F_NOTIFY;
+
+    rotable2_newlib(L, reg_nimble);
+    return 1;
+}
+

+ 991 - 0
components/nimble/src/luat_nimble_mode_central.c

@@ -0,0 +1,991 @@
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "luat_base.h"
+#if (defined(TLS_CONFIG_CPU_XT804))
+#include "FreeRTOS.h"
+#else
+#include "freertos/FreeRTOS.h"
+#endif
+
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include "host/util/util.h"
+#include "services/gap/ble_svc_gap.h"
+#include "services/gatt/ble_svc_gatt.h"
+
+#include "luat_msgbus.h"
+#include "luat_mem.h"
+#include "luat_nimble.h"
+
+/* BLE */
+#include "nimble/nimble_port.h"
+
+// #define LUAT_NIMBLE_DEBUG 0
+// #if LUAT_NIMBLE_DEBUG == 0 
+// #undef LLOGD
+// #define LLOGD(...) 
+// #endif
+
+static size_t ble_gatt_svc_counter = 0;
+static size_t ble_gatt_chr_counter = 0;
+
+struct ble_hs_cfg;
+struct ble_gatt_register_ctxt;
+
+extern struct ble_gatt_svc *peer_servs[];
+extern struct ble_gatt_chr *peer_chrs[];
+
+static uint16_t peer_dscs[MAX_PER_SERV * MAX_PER_SERV];
+
+extern uint16_t g_ble_conn_handle;
+extern uint16_t g_ble_state;
+
+#define LUAT_LOG_TAG "nimble"
+#include "luat_log.h"
+
+static char selfname[32];
+// extern uint16_t g_ble_conn_handle;
+// static uint16_t g_ble_state;
+
+typedef struct luat_nimble_scan_result
+{
+    uint16_t uuids_16[16];
+    uint32_t uuids_32[16];
+    uint8_t uuids_128[16][16];
+    uint8_t mfg_data[128];
+    char name[64];
+    char addr[7]; // 地址类型 + MAC地址
+    uint8_t mfg_data_len;
+    int8_t rssi;
+}luat_nimble_scan_result_t;
+
+void rand_bytes(uint8_t *data, int len);
+
+void print_bytes(const uint8_t *bytes, int len);
+
+void print_addr(const void *addr);
+
+void print_mbuf(const struct os_mbuf *om);
+
+char *addr_str(const void *addr);
+
+void print_uuid(const ble_uuid_t *uuid);
+
+void print_conn_desc(const struct ble_gap_conn_desc *desc);
+
+void print_adv_fields(const struct ble_hs_adv_fields *fields);
+
+
+void ble_store_config_init(void);
+
+static int blecent_gap_event(struct ble_gap_event *event, void *arg);
+static int svc_disced(uint16_t conn_handle,
+                                 const struct ble_gatt_error *error,
+                                 const struct ble_gatt_svc *service,
+                                 void *arg);
+static int chr_disced(uint16_t conn_handle,
+                            const struct ble_gatt_error *error,
+                            const struct ble_gatt_chr *chr, void *arg);
+static int dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
+                uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
+                void *arg);
+
+static void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) {
+    (void)arg;
+    LLOGD("gatt_svr_register_cb op %d", ctxt->op);
+}
+
+/**
+ * Initiates the GAP general discovery procedure.
+ */
+int luat_nimble_blecent_scan(int timeout)
+{
+    uint8_t own_addr_type;
+    struct ble_gap_disc_params disc_params = {0};
+    int rc;
+
+    /* Figure out address to use while advertising (no privacy for now) */
+    rc = ble_hs_id_infer_auto(0, &own_addr_type);
+    if (rc != 0) {
+        LLOGE("error determining address type; rc=%d", rc);
+        return rc;
+    }
+
+    /* Tell the controller to filter duplicates; we don't want to process
+     * repeated advertisements from the same device.
+     */
+    disc_params.filter_duplicates = 1;
+
+    /**
+     * Perform a passive scan.  I.e., don't send follow-up scan requests to
+     * each advertiser.
+     */
+    disc_params.passive = 0;
+
+    /* Use defaults for the rest of the parameters. */
+    disc_params.itvl = 0;
+    disc_params.window = 0;
+    disc_params.filter_policy = 0;
+    disc_params.limited = 0;
+
+    rc = ble_gap_disc(own_addr_type, timeout*1000, &disc_params,
+                      blecent_gap_event, NULL);
+    if (rc != 0) {
+        LLOGE("Error initiating GAP discovery procedure; rc=%d", rc);
+    }
+    return rc;
+}
+
+
+#define ADDR_FMT "%02X%02X%02X%02X%02X%02X"
+#define ADDR_T(addr) addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]
+
+static void
+bleprph_print_conn_desc(struct ble_gap_conn_desc *desc)
+{
+    LLOGI("handle=%d our_ota_addr_type=%d our_ota_addr=" ADDR_FMT, desc->conn_handle, desc->our_ota_addr.type, ADDR_T(desc->our_ota_addr.val));
+    LLOGI(" our_id_addr_type=%d our_id_addr=" ADDR_FMT, desc->our_id_addr.type, ADDR_T(desc->our_id_addr.val));
+    LLOGI(" peer_ota_addr_type=%d peer_ota_addr=" ADDR_FMT, desc->peer_ota_addr.type, ADDR_T(desc->peer_ota_addr.val));
+    LLOGI(" peer_id_addr_type=%d peer_id_addr=" ADDR_FMT, desc->peer_id_addr.type, ADDR_T(desc->peer_id_addr.val));
+    LLOGI(" conn_itvl=%d conn_latency=%d supervision_timeout=%d "
+                "encrypted=%d authenticated=%d bonded=%d",
+                desc->conn_itvl, desc->conn_latency,
+                desc->supervision_timeout,
+                desc->sec_state.encrypted,
+                desc->sec_state.authenticated,
+                desc->sec_state.bonded);
+}
+
+// int luat_nimble_connect(ble_addr_t *addr) {
+int luat_nimble_blecent_connect(const char* _addr){
+    uint8_t own_addr_type;
+    int rc;
+    ble_addr_t *addr;
+    addr = (ble_addr_t *)_addr;
+    for (size_t i = 0; i < ble_gatt_svc_counter; i++)
+    {
+        if (peer_servs[i]) {
+            luat_heap_free(peer_servs[i]);
+            peer_servs[i] = NULL;
+        }
+    }
+    ble_gatt_svc_counter = 0;
+    for (size_t i = 0; i < MAX_PER_SERV*MAX_PER_SERV; i++)
+    {
+        if (peer_chrs[i]) {
+            luat_heap_free(peer_chrs[i]);
+            peer_chrs[i] = NULL;
+        }
+    }
+    ble_gatt_chr_counter = 0;
+    
+
+    // 首先, 停止搜索
+    rc = ble_gap_disc_cancel();
+    LLOGD("ble_gap_disc_cancel %d", rc);
+    rc = ble_hs_id_infer_auto(0, &own_addr_type);
+    LLOGD("ble_hs_id_infer_auto %d", rc);
+    rc = ble_gap_connect(own_addr_type, addr, 30000, NULL, blecent_gap_event, NULL);
+    LLOGD("ble_gap_connect %d", rc);
+    return rc;
+}
+
+
+int luat_nimble_blecent_disconnect(int id) {
+    (void)id;
+    if (0 == g_ble_conn_handle) {
+        return 0;
+    }
+    return ble_gap_terminate(g_ble_conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+}
+
+int luat_nimble_central_disc_srv(int id) {
+    (void)id;
+    if (!g_ble_conn_handle) {
+        LLOGW("尚未建立连接");
+        return -1;
+    }
+    return ble_gattc_disc_all_svcs(g_ble_conn_handle, svc_disced, NULL);
+}
+
+int luat_nimble_status_cb(lua_State*L, void*ptr) {
+    (void)ptr;
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L,"sys_pub");
+    lua_pushliteral(L, "BLE_CONN_STATUS");
+    lua_pushboolean(L, msg->arg1 == 0 ? 1 : 0);
+    lua_pushinteger(L, msg->arg1);
+    lua_pushinteger(L, msg->arg2);
+    lua_call(L, 4, 0);
+    return 0;
+}
+
+int luat_nimble_scan_cb(lua_State*L, void*ptr) {
+    luat_nimble_scan_result_t* res = (luat_nimble_scan_result_t*)ptr;
+    lua_getglobal(L,"sys_pub");
+
+    lua_pushliteral(L, "BLE_SCAN_RESULT");
+
+    lua_pushlstring(L, (const char*)res->addr, 7);
+
+    if (res->name[0]) {
+        lua_pushstring(L, res->name);
+    }
+    else {
+        lua_pushliteral(L, ""); // 设备没有名字
+    }
+    lua_newtable(L);
+    // char buff[64];
+
+    // https://gitee.com/openLuat/LuatOS/issues/I7QSJP
+    // 添加RSSI
+    lua_pushinteger(L, res->rssi);
+    lua_setfield(L, -2, "rssi");
+
+    if (res->uuids_16[0]) {
+        lua_newtable(L);
+        for (size_t i = 0; i < 16; i++)
+        {
+            if (res->uuids_16[i] == 0)
+                break;
+            //lua_pushlstring(L, (const char*)&res->uuids_16[i], 2);
+            lua_pushinteger(L, res->uuids_16[i]);
+            lua_seti(L, -2, i+1);
+        }
+        lua_setfield(L, -2, "uuids16");
+    }
+    // if (res->uuids_32[0]) {
+    //     lua_newtable(L);
+    //     for (size_t i = 0; i < 16; i++)
+    //     {
+    //         if (res->uuids_32[i] == 0)
+    //             break;
+    //         lua_pushlstring(L, (const char*)&res->uuids_32[i], 4);
+    //         lua_pushinteger(L, res->uuids_32[i]);
+    //         lua_seti(L, -2, i+1);
+    //     }
+    //     lua_setfield(L, -2, "uuids32");
+    // }
+    // if (res->uuids_128[0][0]) {
+    //     lua_newtable(L);
+    //     for (size_t i = 0; i < 16; i++)
+    //     {
+    //         if (res->uuids_32[i] == 0)
+    //             break;
+    //         lua_pushlstring(L, (const char*)res->uuids_128[i], 16);
+    //         lua_seti(L, -2, i+1);
+    //     }
+    //     lua_setfield(L, -2, "uuids128");
+    // }
+    if (res->mfg_data_len) {
+        lua_pushlstring(L, (const char*)res->mfg_data, res->mfg_data_len);
+    }
+    else {
+        lua_pushnil(L);
+    }
+    luat_heap_free(res);
+    lua_call(L, 5, 0);
+    return 0;
+}
+
+int luat_nimble_connect_cb(lua_State*L, void*ptr) {
+    (void)ptr;
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L,"sys_pub");
+    lua_pushliteral(L, "BLE_CONN_RESULT");
+    lua_pushboolean(L, msg->arg1 == 0 ? 1 : 0);
+    lua_pushinteger(L, msg->arg1);
+    lua_pushinteger(L, msg->arg2);
+    lua_call(L, 4, 0);
+    return 0;
+}
+
+int luat_nimble_chr_disc_cb(lua_State*L, void*ptr) {
+    (void)ptr;
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L,"sys_pub");
+    lua_pushliteral(L, "BLE_CHR_DISC_RESULT");
+    lua_pushboolean(L, msg->arg1 == 0 ? 1 : 0);
+    lua_pushinteger(L, msg->arg1);
+    lua_pushinteger(L, msg->arg2);
+    lua_call(L, 4, 0);
+    return 0;
+}
+
+static int svc_disced(uint16_t conn_handle,
+                                 const struct ble_gatt_error *error,
+                                 const struct ble_gatt_svc *service,
+                                 void *arg) {
+    (void)conn_handle;
+    (void)arg;
+    rtos_msg_t msg = {.handler=luat_nimble_connect_cb};
+    // LLOGD("svc_disced status %d", error->status);
+    if (error->status == BLE_HS_EDONE) {
+        // LLOGD("service discovery done count %d", ble_gatt_svc_counter);
+        msg.arg1 = 0;
+        msg.arg2 = ble_gatt_svc_counter;
+        luat_msgbus_put(&msg, 0);
+        int ret = ble_gattc_disc_all_chrs(g_ble_conn_handle, service->start_handle, service->end_handle, chr_disced, service);
+        LLOGD("ble_gattc_disc_all_chrs ret %d", ret);
+        return 0;
+    }
+    if (error->status != 0) {
+        msg.arg1 = error->status;
+        luat_msgbus_put(&msg, 0);
+        return error->status;
+    }
+    if (ble_gatt_svc_counter >= MAX_PER_SERV) {
+        return 0;
+    }
+    if (peer_servs[ble_gatt_svc_counter] == NULL) {
+        peer_servs[ble_gatt_svc_counter] = luat_heap_malloc(sizeof(struct ble_gatt_svc));
+    }
+    memcpy(peer_servs[ble_gatt_svc_counter], service, sizeof(struct ble_gatt_svc));
+    
+    // char buff[64] = {0};
+    // LLOGD("service->start_handle %04X", service->start_handle);
+    // LLOGD("service->end_handle %04X",   service->end_handle);
+    // LLOGD("service->uuid %s",         ble_uuid_to_str(&service->uuid, buff));
+    ble_gatt_svc_counter ++;
+    return 0;
+}
+
+static const ble_uuid_t *uuid_ccc =
+                BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16);
+
+static int dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
+                uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
+                void *arg) {
+    // LLOGD("dsc_disced status %d", error->status);
+    rtos_msg_t msg = {.handler=luat_nimble_chr_disc_cb};
+    if (error->status == BLE_HS_EDONE) {
+        msg.arg1 = 0;
+        msg.arg2 = ble_gatt_chr_counter;
+        luat_msgbus_put(&msg, 0);
+        return 0;
+    }
+    if (error->status == 0) {
+        struct ble_gatt_chr *chr = (struct ble_gatt_chr *)(arg);
+        // char buff[64];
+        // LLOGD("dsc_disced %d %d %s", chr_val_handle, dsc->handle, ble_uuid_to_str(&dsc->uuid, buff));
+        for (size_t i = 0; i < MAX_PER_SERV * MAX_PER_SERV; i++)
+        {
+            if (peer_chrs[i] == NULL || peer_chrs[i]->val_handle != chr_val_handle) {
+                continue;
+            }
+            if (0 == ble_uuid_cmp(uuid_ccc, &dsc->uuid)) {
+                LLOGD("设置chr %d dsc handle %d", peer_chrs[i]->val_handle, dsc->handle);
+                peer_dscs[i] = dsc->handle;
+            }
+        }
+    }
+    else {
+        msg.arg1 = error->status;
+        msg.arg2 = ble_gatt_chr_counter;
+        luat_msgbus_put(&msg, 0);
+        return 0;
+    }
+    return 0;
+}
+
+static int chr_disced(uint16_t conn_handle,
+                            const struct ble_gatt_error *error,
+                            const struct ble_gatt_chr *chr, void *arg) {
+    rtos_msg_t msg = {.handler=luat_nimble_chr_disc_cb};
+    struct ble_gatt_svc *service = (struct ble_gatt_svc *)arg;
+    // LLOGD("chr_disced status %d", error->status);
+    if (error->status == BLE_HS_EDONE) {
+        // LLOGD("chr discovery done count %d", ble_gatt_chr_counter);
+        int ret = ble_gattc_disc_all_dscs(g_ble_conn_handle, service->start_handle, service->end_handle, dsc_disced, service);
+        LLOGD("ble_gattc_disc_all_dscs %d", ret);
+        if (ret == 0)
+            return 0;
+        else {
+            msg.arg1 = -80;
+            msg.arg2 = ble_gatt_chr_counter;
+            luat_msgbus_put(&msg, 0);
+            return 0;
+        }
+    }
+    if (error->status != 0) {
+        msg.arg1 = error->status;
+        luat_msgbus_put(&msg, 0);
+        return error->status;
+    }
+    if (ble_gatt_chr_counter >= MAX_PER_SERV) {
+        return 0; // 太多了
+    }
+    // char buff[64];
+    for (size_t i = 0; i < MAX_PER_SERV; i++)
+    {
+        if (peer_servs[i] == NULL)
+            continue;
+        if (peer_servs[i] == arg) {
+            if (peer_chrs[i*MAX_PER_SERV+ble_gatt_chr_counter] == NULL) {
+                peer_chrs[i*MAX_PER_SERV+ble_gatt_chr_counter] = luat_heap_malloc(sizeof(struct ble_gatt_chr));
+            }
+            memcpy(peer_chrs[i*MAX_PER_SERV+ble_gatt_chr_counter], chr, sizeof(struct ble_gatt_chr));
+            // LLOGD("特征值 %s flags %d", ble_uuid_to_str(&chr->uuid, buff), chr->properties);
+            ble_gatt_chr_counter ++;
+        }
+    }
+    return 0;
+}
+
+int luat_nimble_central_disc_chr(int id, struct ble_gatt_svc *service) {
+    (void)id;
+    ble_gatt_chr_counter = 0;
+    return ble_gattc_disc_all_chrs(g_ble_conn_handle, service->start_handle, service->end_handle, chr_disced, service);
+}
+
+int luat_nimble_central_disc_dsc(int id, struct ble_gatt_svc *service, struct ble_gatt_chr *chr) {
+    (void)id;
+    // LLOGD("service %d %d chr %d %d", service->start_handle, service->end_handle, chr->val_handle, chr->def_handle);
+    int ret = ble_gattc_disc_all_dscs(g_ble_conn_handle, service->start_handle, service->end_handle, dsc_disced, chr);
+    LLOGD("ble_gattc_disc_all_dscs %d", ret);
+    return ret;
+    // return 0;
+}
+
+static int l_ble_chr_read_cb(lua_State* L, void* ptr) {
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L, "sys_pub");
+    if (lua_isfunction(L, -1)) {
+        lua_pushstring(L, "BLE_GATT_READ_CHR");
+        if (msg->ptr) {
+            lua_pushlstring(L, msg->ptr, msg->arg2);
+        }
+        else {
+            lua_pushnil(L);
+        }
+        // lua_pushinteger(L, msg->arg1);
+        lua_call(L, 2, 0);
+    }
+    if (ptr)
+        luat_heap_free(ptr);
+    return 0;
+}
+
+static int write_attr_cb(uint16_t conn_handle,
+                             const struct ble_gatt_error *error,
+                             struct ble_gatt_attr *attr,
+                             void *arg) {
+    LLOGD("write_attr_cb %d", error->status);
+    return 0;
+}
+
+
+static int read_attr_cb(uint16_t conn_handle,
+                        const struct ble_gatt_error *error,
+                        struct ble_gatt_attr *attr,
+                        void *arg) {
+    LLOGD("read_attr_cb %d", error->status);
+    const struct os_mbuf *om = attr->om;
+    
+    rtos_msg_t msg = {.handler=l_ble_chr_read_cb};
+    msg.arg1 = error->status;
+    if (error->status == 0) {
+        char* ptr = luat_heap_malloc(1024);
+        size_t act = 0;
+        while(om != NULL && ptr != NULL) {
+            if (om->om_len > 0) {
+                if (act + om->om_len > 1024) {
+                    LLOGE("too many data, skip");
+                    break;
+                }
+                memcpy(ptr + act, om->om_data, om->om_len);
+                act += om->om_len;
+            }
+            om = SLIST_NEXT(om, om_next);
+        }
+        msg.arg2 = act;
+        msg.ptr = ptr;
+    }
+    luat_msgbus_put(&msg, 0);
+    return 0;
+}
+
+int luat_nimble_central_write(int id, struct ble_gatt_chr *chr, char* data, size_t data_len) {
+    struct os_mbuf *om;
+    int rc;
+    om = ble_hs_mbuf_from_flat(data, data_len);
+
+    if(om == NULL) {
+        return BLE_HS_ENOMEM;
+    }
+    if (chr->properties & BLE_GATT_CHR_F_WRITE_NO_RSP)
+        return ble_gattc_write_no_rsp(g_ble_conn_handle, chr->val_handle, om);
+    else
+        return ble_gattc_write(g_ble_conn_handle, chr->val_handle, om, write_attr_cb, NULL);
+}
+
+int luat_nimble_central_read(int id, struct ble_gatt_chr *chr) {
+    return ble_gattc_read(g_ble_conn_handle, chr->val_handle, read_attr_cb, NULL);
+}
+
+int luat_nimble_central_subscribe(int id, struct ble_gatt_chr * chr, int onoff) {
+    char buff[2] = {0, 0};
+    if (onoff) {
+        if (chr->properties & BLE_GATT_CHR_F_NOTIFY) {
+            buff[0] |= 0x01;
+        }
+        if (chr->properties & BLE_GATT_CHR_F_INDICATE) {
+            buff[0] |= 0x02;
+        }
+    }
+    for (size_t i = 0; i < MAX_PER_SERV * MAX_PER_SERV; i++)
+    {
+        if (peer_chrs[i] == NULL || peer_chrs[i] != chr) {
+            continue;
+        }
+        // LLOGD("subscribe %d %d %d", onoff, g_ble_conn_handle, peer_dscs[i]);
+        return ble_gattc_write_flat(g_ble_conn_handle, 13, buff, 2, write_attr_cb, chr);
+    }
+    return -1;
+}
+
+static int luat_blecent_tx_cb(lua_State *L, void* ptr) {
+rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L, "sys_pub");
+    if (lua_isfunction(L, -1)) {
+        lua_pushstring(L, "BLE_GATT_TX_DATA");
+        if (msg->ptr) {
+            lua_pushlstring(L, msg->ptr, msg->arg1);
+        }
+        else {
+            lua_pushnil(L);
+        }
+        // lua_pushinteger(L, msg->arg1);
+        lua_call(L, 2, 0);
+    }
+    if (ptr)
+        luat_heap_free(ptr);
+    return 0;
+}
+
+static int blecent_gap_event(struct ble_gap_event *event, void *arg)
+{
+    struct ble_hs_adv_fields fields;
+    struct ble_gap_conn_desc desc;
+    int rc = 0;
+    int i = 0;
+    rtos_msg_t msg = {0};
+    struct os_mbuf *om;
+    size_t offset = 0;
+    char* ptr;
+
+    // LLOGD("blecent_gap_event %d", event->type);ble_gattc_disc_all_dscs
+
+    switch (event->type) {
+    case BLE_GAP_EVENT_DISC_COMPLETE:
+        LLOGD("ble scan complete");
+        return 0;
+    case BLE_GAP_EVENT_DISC:
+        rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
+                                     event->disc.length_data);
+        if (rc != 0) {
+            LLOGI("ble_hs_adv_parse_fields rc %d", rc);
+            return 0;
+        }
+        // if (event->disc.event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND &&
+        //         event->disc.event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) {
+        //     LLOGI("disc event_type Not ADC or DIR %d", event->disc.event_type);
+        //     return 0;
+        // }
+        luat_nimble_scan_result_t* res = luat_heap_malloc(sizeof(luat_nimble_scan_result_t));
+        if (res == NULL) {
+            LLOGI("out of memory when malloc luat_nimble_scan_result_t");
+            return 0;
+        }
+        memset(res, 0, sizeof(luat_nimble_scan_result_t));
+
+        res->rssi = event->disc.rssi;
+
+        // char tmpbuff[64] = {0};
+
+        for (i = 0; i < fields.num_uuids16 && i < 16; i++) {
+            // LLOGD("uuids_16 %s", ble_uuid_to_str(&fields.uuids16[i], tmpbuff));
+            res->uuids_16[i] = fields.uuids16[i].value;
+        }
+        for (i = 0; i < fields.num_uuids32 && i < 16; i++) {
+            // LLOGD("uuids_32 %s", ble_uuid_to_str(&fields.uuids32[i], tmpbuff));
+            res->uuids_32[i] = fields.uuids32[i].value;
+        }
+        for (i = 0; i < fields.num_uuids128 && i < 16; i++) {
+            // LLOGD("uuids_128 %s", ble_uuid_to_str(&fields.uuids128[i], tmpbuff));
+            // memcpy(res->uuids_128[i], fields.uuids128[i].value, 16);
+        }
+        memcpy(res->addr, &event->disc.addr, 7);
+        memcpy(res->name, fields.name, fields.name_len);
+        if (fields.mfg_data_len) {
+            memcpy(res->mfg_data, fields.mfg_data, fields.mfg_data_len);
+            res->mfg_data_len = fields.mfg_data_len;
+        }
+        // LLOGD("addr %02X%02X%02X%02X%02X%02X", event->disc.addr.val[0], event->disc.addr.val[1], event->disc.addr.val[2], 
+        //                                        event->disc.addr.val[3], event->disc.addr.val[4], event->disc.addr.val[5]);
+        // for (i = 0; i < fields.num_uuids128 && i < 16; i++) {
+        //     res->uuids_128[i] = fields.num_uuids128.value >> 32;
+        // }
+        // LLOGD("uuids 16=%d 32=%d 128=%d", fields.num_uuids16, fields.num_uuids32, fields.num_uuids128);
+        msg.handler=luat_nimble_scan_cb;
+        msg.ptr = res;
+        luat_msgbus_put(&msg, 0);
+
+        /* An advertisment report was received during GAP discovery. */
+        print_adv_fields(&fields);
+
+        /* Try to connect to the advertiser if it looks interesting. */
+        //blecent_connect_if_interesting(&event->disc);
+        return 0;
+    case BLE_GAP_EVENT_CONNECT:
+        /* A new connection was established or a connection attempt failed. */
+        LLOGI("connection %s; status=%d ",
+                    event->connect.status == 0 ? "established" : "failed",
+                    event->connect.status);
+        msg.handler = luat_nimble_status_cb;
+        msg.arg1 = event->connect.status;
+        // msg.arg2 = event->connect.conn_handle;
+        luat_msgbus_put(&msg, 0);
+        if (event->connect.status == 0) {
+            g_ble_conn_handle = event->connect.conn_handle;
+            rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
+            if (rc == 0)
+                bleprph_print_conn_desc(&desc);
+            g_ble_state = BT_STATE_CONNECTED;
+            /* Perform service discovery. */
+            rc = ble_gattc_disc_all_svcs(event->connect.conn_handle, svc_disced, NULL);
+        }
+        else {
+            g_ble_state = BT_STATE_DISCONNECT;
+        }
+        return 0;
+
+    case BLE_GAP_EVENT_DISCONNECT:
+        g_ble_state = BT_STATE_DISCONNECT;
+        LLOGI("disconnect; reason=%d ", event->disconnect.reason);
+        // bleprph_print_conn_desc(&event->disconnect.conn);
+        msg.handler = luat_nimble_status_cb;
+        msg.arg1 = 0xff;
+        // msg.arg2 = event->connect.conn_handle;
+        luat_msgbus_put(&msg, 0);
+        return 0;
+
+    case BLE_GAP_EVENT_CONN_UPDATE:
+        /* The central has updated the connection parameters. */
+        LLOGI("connection updated; status=%d ", event->conn_update.status);
+        // rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
+        // if (rc == 0)
+        //     bleprph_print_conn_desc(&desc);
+        return 0;
+
+    case BLE_GAP_EVENT_ENC_CHANGE:
+        /* Encryption has been enabled or disabled for this connection. */
+        LLOGI("encryption change event; status=%d ", event->enc_change.status);
+        // rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
+        // if (rc == 0)
+        //     bleprph_print_conn_desc(&desc);
+        // LLOGI("");
+        return 0;
+
+    case BLE_GAP_EVENT_SUBSCRIBE:
+        LLOGI("subscribe event; conn_handle=%d attr_handle=%d "
+                    "reason=%d prevn=%d curn=%d previ=%d curi=%d",
+                    event->subscribe.conn_handle,
+                    event->subscribe.attr_handle,
+                    event->subscribe.reason,
+                    event->subscribe.prev_notify,
+                    event->subscribe.cur_notify,
+                    event->subscribe.prev_indicate,
+                    event->subscribe.cur_indicate);
+        return 0;
+
+    case BLE_GAP_EVENT_MTU:
+        LLOGI("mtu update event; conn_handle=%d cid=%d mtu=%d",
+                    event->mtu.conn_handle,
+                    event->mtu.channel_id,
+                    event->mtu.value);
+        return 0;
+
+    case BLE_GAP_EVENT_REPEAT_PAIRING:
+        /* We already have a bond with the peer, but it is attempting to
+         * establish a new secure link.  This app sacrifices security for
+         * convenience: just throw away the old bond and accept the new link.
+         */
+
+        /* Delete the old bond. */
+        rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
+        assert(rc == 0);
+        ble_store_util_delete_peer(&desc.peer_id_addr);
+
+        /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
+         * continue with the pairing operation.
+         */
+        return BLE_GAP_REPEAT_PAIRING_RETRY;
+
+    case BLE_GAP_EVENT_PASSKEY_ACTION:
+        LLOGI("PASSKEY_ACTION_EVENT started");
+        return 0;
+
+    case BLE_GAP_EVENT_NOTIFY_RX :
+        // LLOGD("BLE_GAP_EVENT_NOTIFY_RX");
+        om = event->notify_rx.om;
+        ptr = luat_heap_malloc(1024);
+        if (ptr == NULL) {
+            LLOGE("out of memory when malloc buff");
+            return 0;
+        }
+        while(om != NULL) {
+            if (offset + om->om_len > 1024) {
+                LLOGD("data too big");
+                break;
+            }
+            memcpy(ptr + offset, om->om_data, om->om_len);
+            offset += om->om_len;
+            om = SLIST_NEXT(om, om_next);
+        }
+        msg.handler = luat_blecent_tx_cb;
+        msg.arg1 = offset;
+        msg.ptr = ptr;
+        msg.arg2 = event->notify_rx.attr_handle;
+        // msg.arg2 = event->connect.conn_handle;
+        luat_msgbus_put(&msg, 0);
+        return 0;
+    }
+
+    return 0;
+}
+
+static void
+blecent_on_reset(int reason)
+{
+    g_ble_state = BT_STATE_OFF;
+    LLOGE("Resetting state; reason=%d", reason);
+    //app_adapter_state_changed_callback(WM_BT_STATE_OFF);
+}
+
+
+static void
+blecent_on_sync(void)
+{
+    // int rc;
+
+    /* Make sure we have proper identity address set (public preferred) */
+    ble_hs_util_ensure_addr(0);
+}
+
+
+
+int luat_nimble_init_central(uint8_t uart_idx, char* name, int mode) {
+    // int rc = 0;
+    nimble_port_init();
+
+    if (name == NULL || strlen(name) == 0) {
+        if (selfname[0] == 0) {
+            memcpy(selfname, "LuatOS", strlen("LuatOS") + 1);
+        }
+    }
+    else {
+        memcpy(selfname, name, strlen(name) + 1);
+    }
+
+    /* Set the default device name. */
+    if (strlen(selfname))
+        ble_svc_gap_device_name_set((const char*)selfname);
+
+
+    /* Initialize the NimBLE host configuration. */
+    ble_hs_cfg.reset_cb = blecent_on_reset;
+    ble_hs_cfg.sync_cb = blecent_on_sync;
+    ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
+    ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
+
+    ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
+    ble_hs_cfg.sm_sc = 0;
+
+
+    ble_svc_gap_init();
+    ble_svc_gatt_init();
+
+    // rc = gatt_svr_init();
+    // LLOGD("gatt_svr_init rc %d", rc);
+
+    /* XXX Need to have template for store */
+    ble_store_config_init();
+
+    return 0;
+}
+
+//-----------------------------------
+//            helper
+//-----------------------------------
+
+/**
+ * Utility function to log an array of bytes.
+ */
+void
+print_bytes(const uint8_t *bytes, int len)
+{
+    int i;
+    char buff[256 + 1] = {0};
+
+    for (i = 0; i < len; i++) {
+        sprintf_(buff + strlen(buff), "%02X", bytes[i]);
+        // LLOGD("%s0x%02x", i != 0 ? ":" : "", bytes[i]);
+    }
+    LLOGD("%s", buff);
+}
+
+char *
+addr_str(const void *addr)
+{
+    static char buf[6 * 2 + 5 + 1];
+    const uint8_t *u8p;
+
+    u8p = addr;
+    sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
+            u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
+
+    return buf;
+}
+
+void
+print_uuid(const ble_uuid_t *uuid)
+{
+    char buf[BLE_UUID_STR_LEN];
+
+    LLOGD("%s", ble_uuid_to_str(uuid, buf));
+}
+
+
+/**
+ * Logs information about a connection to the console.
+ */
+void
+print_conn_desc(const struct ble_gap_conn_desc *desc)
+{
+    LLOGD("handle=%d our_ota_addr_type=%d our_ota_addr=%s ",
+                desc->conn_handle, desc->our_ota_addr.type,
+                addr_str(desc->our_ota_addr.val));
+    LLOGD("our_id_addr_type=%d our_id_addr=%s ",
+                desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
+    LLOGD("peer_ota_addr_type=%d peer_ota_addr=%s ",
+                desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
+    LLOGD("peer_id_addr_type=%d peer_id_addr=%s ",
+                desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
+    LLOGD("conn_itvl=%d conn_latency=%d supervision_timeout=%d "
+                "encrypted=%d authenticated=%d bonded=%d",
+                desc->conn_itvl, desc->conn_latency,
+                desc->supervision_timeout,
+                desc->sec_state.encrypted,
+                desc->sec_state.authenticated,
+                desc->sec_state.bonded);
+}
+
+
+void
+print_adv_fields(const struct ble_hs_adv_fields *fields)
+{
+    // char s[BLE_HS_ADV_MAX_SZ];
+    // const uint8_t *u8p;
+    // int i;
+
+    // if (fields->flags != 0) {
+    //     LLOGD("    flags=0x%02x", fields->flags);
+    // }
+
+    // if (fields->uuids16 != NULL) {
+    //     LLOGD("    uuids16(%scomplete)=",
+    //                 fields->uuids16_is_complete ? "" : "in");
+    //     for (i = 0; i < fields->num_uuids16; i++) {
+    //         print_uuid(&fields->uuids16[i].u);
+    //         //LLOGD(" ");
+    //     }
+    // }
+
+    // if (fields->uuids32 != NULL) {
+    //     LLOGD("    uuids32(%scomplete)=",
+    //                 fields->uuids32_is_complete ? "" : "in");
+    //     for (i = 0; i < fields->num_uuids32; i++) {
+    //         print_uuid(&fields->uuids32[i].u);
+    //         //LLOGD(" ");
+    //     }
+    // }
+
+    // if (fields->uuids128 != NULL) {
+    //     LLOGD("    uuids128(%scomplete)=",
+    //                 fields->uuids128_is_complete ? "" : "in");
+    //     for (i = 0; i < fields->num_uuids128; i++) {
+    //         print_uuid(&fields->uuids128[i].u);
+    //         LLOGD(" ");
+    //     }
+    //     //LLOGD("");
+    // }
+
+    // if (fields->name != NULL) {
+    //     assert(fields->name_len < sizeof s - 1);
+    //     memcpy(s, fields->name, fields->name_len);
+    //     s[fields->name_len] = '\0';
+    //     LLOGD("    name(%scomplete)=%s",
+    //                 fields->name_is_complete ? "" : "in", s);
+    // }
+
+    // if (fields->tx_pwr_lvl_is_present) {
+    //     LLOGD("    tx_pwr_lvl=%d", fields->tx_pwr_lvl);
+    // }
+
+    // if (fields->slave_itvl_range != NULL) {
+    //     LLOGD("    slave_itvl_range=");
+    //     print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
+    // }
+
+    // if (fields->svc_data_uuid16 != NULL) {
+    //     LLOGD("    svc_data_uuid16=");
+    //     print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
+    // }
+
+    // if (fields->public_tgt_addr != NULL) {
+    //     LLOGD("    public_tgt_addr=");
+    //     u8p = fields->public_tgt_addr;
+    //     for (i = 0; i < fields->num_public_tgt_addrs; i++) {
+    //         LLOGD("public_tgt_addr=%s ", addr_str(u8p));
+    //         u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
+    //     }
+    //     // LLOGD("");
+    // }
+
+    // if (fields->appearance_is_present) {
+    //     LLOGD("    appearance=0x%04x", fields->appearance);
+    // }
+
+    // if (fields->adv_itvl_is_present) {
+    //     LLOGD("    adv_itvl=0x%04x", fields->adv_itvl);
+    // }
+
+    // if (fields->svc_data_uuid32 != NULL) {
+    //     LLOGD("    svc_data_uuid32=");
+    //     print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
+    //     LLOGD("");
+    // }
+
+    // if (fields->svc_data_uuid128 != NULL) {
+    //     LLOGD("    svc_data_uuid128=");
+    //     print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
+    //     LLOGD("");
+    // }
+
+    // if (fields->uri != NULL) {
+    //     LLOGD("    uri=");
+    //     print_bytes(fields->uri, fields->uri_len);
+    //     LLOGD("");
+    // }
+
+    // if (fields->mfg_data != NULL) {
+    //     LLOGD("    mfg_data=");
+    //     print_bytes(fields->mfg_data, fields->mfg_data_len);
+    //     LLOGD("");
+    // }
+}
+

+ 179 - 0
components/nimble/src/luat_nimble_mode_ibeacon.c

@@ -0,0 +1,179 @@
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "luat_base.h"
+#if (defined(TLS_CONFIG_CPU_XT804))
+#include "FreeRTOS.h"
+#else
+#include "freertos/FreeRTOS.h"
+#endif
+
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include "host/util/util.h"
+#include "services/gap/ble_svc_gap.h"
+#include "services/gatt/ble_svc_gatt.h"
+
+#include "luat_msgbus.h"
+#include "luat_mem.h"
+#include "luat_nimble.h"
+
+#define LUAT_LOG_TAG "nimble"
+#include "luat_log.h"
+
+/* BLE */
+#include "nimble/nimble_port.h"
+// #include "nimble/nimble_port_freertos.h"
+
+#define BLE_IBEACON_MFG_DATA_SIZE       25
+
+#define ADDR_FMT "%02X%02X%02X%02X%02X%02X"
+#define ADDR_T(addr) addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]
+
+static uint8_t ble_use_custom_name;
+static uint8_t own_addr_type;
+static uint8_t ble_ready;
+void ble_store_config_init(void);
+
+
+extern uint8_t luat_ble_dev_name[];
+extern size_t  luat_ble_dev_name_len;
+
+extern uint8_t adv_buff[];
+extern int adv_buff_len;
+extern struct ble_hs_adv_fields adv_fields;
+extern struct ble_gap_adv_params adv_params;
+
+static void ble_app_advertise(void);
+
+int luat_nimble_ibeacon_setup(void *uuid128, uint16_t major,
+                         uint16_t minor, int8_t measured_power) {
+    uint8_t buf[BLE_IBEACON_MFG_DATA_SIZE];
+    int rc;
+    /** Company identifier (Apple). */
+    buf[0] = 0x4c;
+    buf[1] = 0x00;
+    /** iBeacon indicator. */
+    buf[2] = 0x02;
+    buf[3] = 0x15;
+    /** UUID. */
+    memcpy(buf + 4, uuid128, 16);
+    /** Version number. */
+    put_be16(buf + 20, major);
+    put_be16(buf + 22, minor);
+
+    /* Measured Power ranging data (Calibrated tx power at 1 meters). */
+    if(measured_power < -126 || measured_power > 20) {
+        return BLE_HS_EINVAL;
+    }
+
+    buf[24] = measured_power;
+    int flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
+    return luat_nimble_set_adv_data((char* )buf, BLE_IBEACON_MFG_DATA_SIZE, flags);
+}
+
+int luat_nimble_set_adv_data(char* buff, size_t len, int flags) {
+    if (buff == NULL)
+        return -1;
+    int rc = 0;
+    if (len > 128)
+        len = 128;
+    adv_buff_len = len;
+    memcpy(adv_buff, buff, len);
+    memset(&adv_fields, 0, sizeof adv_fields);
+    adv_fields.mfg_data = adv_buff;
+    adv_fields.mfg_data_len = adv_buff_len;
+    adv_fields.flags = flags;
+    if (ble_ready) {
+        rc = ble_gap_adv_set_fields(&adv_fields);
+        LLOGD("ble_gap_adv_set_fields rc %d", rc);
+        return rc;
+    }
+
+    return rc;
+}
+
+static int
+bleprph_gap_event(struct ble_gap_event *event, void *arg)
+{
+    switch (event->type) {
+    case BLE_GAP_EVENT_ADV_COMPLETE:
+        LLOGI("advertise complete; reason=%d", event->adv_complete.reason);
+        return 0;
+    }
+    return 0;
+}
+
+static void
+ble_app_advertise(void)
+{
+    // uint8_t uuid128[16];
+    int rc;
+
+    rc = ble_gap_adv_set_fields(&adv_fields);
+    LLOGD("ble_gap_adv_set_fields rc %d", rc);
+
+    /* Begin advertising. */
+    // adv_params = (struct ble_gap_adv_params){ 0 };
+    rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
+                           &adv_params, bleprph_gap_event, NULL);
+    LLOGD("ble_gap_adv_start rc %d", rc);
+    ble_ready = 1;
+}
+
+static void
+bleprph_on_sync(void)
+{
+    // LLOGD("iBeacon GoGoGo");
+    int rc;
+    ble_hs_id_infer_auto(0, &own_addr_type);
+    /* Printing ADDR */
+    uint8_t addr_val[6] = {0};
+    rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
+
+    LLOGI("Device Address: " ADDR_FMT, ADDR_T(addr_val));
+    if (luat_ble_dev_name_len == 0) {
+        sprintf_((char*)luat_ble_dev_name, "LOS-" ADDR_FMT, ADDR_T(addr_val));
+        LLOGD("BLE name: %s", luat_ble_dev_name);
+        luat_ble_dev_name_len = strlen((const char*)luat_ble_dev_name);
+        rc = ble_svc_gap_device_name_set((const char*)luat_ble_dev_name);
+        
+    }
+
+    /* Advertise indefinitely. */
+    ble_app_advertise();
+}
+
+int luat_nimble_init_ibeacon(uint8_t uart_idx, char* name, int mode) {
+    int rc = 0;
+    nimble_port_init();
+
+    /* Set the default device name. */
+    if (name != NULL && strlen(name)) {
+        rc = ble_svc_gap_device_name_set((const char*)name);
+        ble_use_custom_name = 1;
+    }
+
+    /* Initialize the NimBLE host configuration. */
+    // ble_hs_cfg.reset_cb = bleprph_on_reset;
+    ble_hs_cfg.sync_cb = bleprph_on_sync;
+    // ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
+    // ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
+
+    ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
+    ble_hs_cfg.sm_sc = 0;
+
+    ble_svc_gap_init();
+    // ble_svc_gatt_init();
+
+    // rc = gatt_svr_init();
+    // LLOGD("gatt_svr_init rc %d", rc);
+
+    /* XXX Need to have template for store */
+    ble_store_config_init();
+
+    return 0;
+}
+

+ 828 - 0
components/nimble/src/luat_nimble_mode_peripheral.c

@@ -0,0 +1,828 @@
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "luat_base.h"
+#if (defined(TLS_CONFIG_CPU_XT804))
+#include "FreeRTOS.h"
+#else
+#include "freertos/FreeRTOS.h"
+#endif
+
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include "host/util/util.h"
+#include "services/gap/ble_svc_gap.h"
+#include "services/gatt/ble_svc_gatt.h"
+
+#include "luat_msgbus.h"
+#include "luat_mem.h"
+#include "luat_nimble.h"
+
+/* BLE */
+#include "nimble/nimble_port.h"
+
+typedef void (*TaskFunction_t)( void * );
+
+struct ble_hs_cfg;
+struct ble_gatt_register_ctxt;
+
+static void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
+static int gatt_svr_init(void);
+
+extern uint16_t g_ble_conn_handle;
+extern uint16_t g_ble_state;
+
+extern ble_uuid_any_t ble_peripheral_srv_uuid;
+
+extern uint16_t s_chr_flags[];
+extern ble_uuid_any_t s_chr_uuids[];
+extern uint16_t s_chr_val_handles[];
+extern uint8_t s_chr_notify_states[];
+extern uint8_t s_chr_indicate_states[];
+
+#define LUAT_LOG_TAG "nimble"
+#include "luat_log.h"
+
+extern uint8_t luat_ble_dev_name[];
+extern size_t  luat_ble_dev_name_len;
+extern struct ble_gap_adv_params adv_params;
+
+static uint8_t buff_for_read[256];
+static uint8_t buff_for_read_size;
+
+typedef struct ble_write_msg {
+    // uint16_t conn_handle,
+    // uint16_t attr_handle,
+    ble_uuid_t* uuid;
+    uint16_t len;
+    char buff[1];
+}ble_write_msg_t;
+
+static int
+gatt_svr_chr_access_func(uint16_t conn_handle, uint16_t attr_handle,
+                               struct ble_gatt_access_ctxt *ctxt, void *arg);
+
+
+int luat_nimble_peripheral_set_chr(int index, ble_uuid_any_t* chr_uuid, int flags) {
+    if (index < 0 || index >= LUAT_BLE_MAX_CHR)
+        return -1;
+    char buff[BLE_UUID_STR_LEN + 1];
+    ble_uuid_to_str(chr_uuid, buff);
+    // LLOGD("set chr[%d] %s flags %d", index, buff, flags);
+    ble_uuid_copy(&s_chr_uuids[index], chr_uuid);
+    s_chr_flags[index] = flags;
+    return 0;
+}
+
+static void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
+{
+    char buf[BLE_UUID_STR_LEN];
+    // LLOGD("gatt_svr_register_cb op %d", ctxt->op);
+    switch (ctxt->op) {
+    case BLE_GATT_REGISTER_OP_SVC:
+        // LLOGD("registered service %s with handle=%d",
+        //             ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
+        //             ctxt->svc.handle);
+        break;
+
+    case BLE_GATT_REGISTER_OP_CHR:
+        // LLOGD("registering characteristic %s with "
+        //             "def_handle=%d val_handle=%d",
+        //             ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
+        //             ctxt->chr.def_handle,
+        //             ctxt->chr.val_handle);
+        break;
+
+    case BLE_GATT_REGISTER_OP_DSC:
+        // LLOGD("registering descriptor %s with handle=%d",
+        //             ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
+        //             ctxt->dsc.handle);
+        break;
+
+    default:
+        // assert(0);
+        break;
+    }
+}
+
+static int l_ble_chr_write_cb(lua_State* L, void* ptr) {
+    ble_write_msg_t* wmsg = (ble_write_msg_t*)ptr;
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L, "sys_pub");
+    if (lua_isfunction(L, -1)) {
+        lua_pushstring(L, "BLE_GATT_WRITE_CHR");
+        lua_newtable(L);
+        char buff[BLE_UUID_STR_LEN] = {0};
+        for (size_t i = 0; i < LUAT_BLE_MAX_CHR; i++)
+        {
+            if (s_chr_val_handles[i] == msg->arg2) {
+                ble_uuid_to_str(&s_chr_uuids[i], buff);
+                lua_pushstring(L, buff);
+                lua_setfield(L, -2, "chr_uuid");
+                break;
+            }
+        }
+        lua_pushlstring(L, wmsg->buff, wmsg->len);
+        lua_call(L, 3, 0);
+    }
+    luat_heap_free(wmsg);
+    return 0;
+}
+
+
+
+static int l_ble_chr_read_cb(lua_State* L, void* ptr) {
+    lua_getglobal(L, "sys_pub");
+    if (lua_isfunction(L, -1)) {
+        lua_pushstring(L, "BLE_GATT_READ_CHR");
+        lua_call(L, 1, 0);
+    }
+    return 0;
+}
+
+static int l_ble_state_cb(lua_State* L, void* ptr) {
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_getglobal(L, "sys_pub");
+    if (lua_isfunction(L, -1)) {
+        lua_pushstring(L, "BLE_SERVER_STATE_UPD");
+        lua_pushinteger(L, msg->arg1);
+        lua_call(L, 2, 0);
+    }
+    return 0;
+}
+
+static int
+gatt_svr_chr_access_func(uint16_t conn_handle, uint16_t attr_handle,
+                               struct ble_gatt_access_ctxt *ctxt, void *arg)
+{
+    int rc = 0;
+    struct os_mbuf *om = ctxt->om;
+    ble_write_msg_t* wmsg;
+    rtos_msg_t msg = {0};
+    char buff[BLE_UUID_STR_LEN + 1] = {0};
+    int handle_index = -1;
+    for (size_t i = 0; i < LUAT_BLE_MAX_CHR; i++)
+    {
+        if (attr_handle == s_chr_val_handles[i]) {
+            ble_uuid_to_str(&s_chr_uuids[i], buff);
+            handle_index = i;
+            break;
+        }
+    }
+    
+    LLOGD("gatt_svr_chr_access_func %d %d-%s %d", conn_handle, attr_handle, buff, ctxt->op);
+    switch (ctxt->op) {
+        case BLE_GATT_ACCESS_OP_WRITE_CHR:
+            wmsg = (ble_write_msg_t*)(luat_heap_malloc(sizeof(ble_write_msg_t) + MYNEWT_VAL(BLE_ATT_PREFERRED_MTU) - 1));
+            if (!wmsg) {
+                LLOGW("out of memory when malloc ble_write_msg_t");
+                return 0;
+            }
+            memset(wmsg, 0, sizeof(ble_write_msg_t) + MYNEWT_VAL(BLE_ATT_PREFERRED_MTU) - 1);
+            msg.handler = l_ble_chr_write_cb;
+            msg.ptr = wmsg;
+            msg.arg1 = conn_handle;
+            msg.arg2 = attr_handle;
+            while(om) {
+                memcpy(&wmsg->buff[wmsg->len], om->om_data, om->om_len);
+                wmsg->len += om->om_len;
+                om = SLIST_NEXT(om, om_next);
+            }
+            luat_msgbus_put(&msg, 0);
+            return 0;
+        case BLE_GATT_ACCESS_OP_READ_CHR:
+            LLOGD("gatt svr read size = %d", buff_for_read_size);
+            if (buff_for_read_size) {
+                rc = os_mbuf_append(ctxt->om, buff_for_read, buff_for_read_size);
+                buff_for_read_size = 0;
+                msg.handler = l_ble_chr_read_cb;
+                luat_msgbus_put(&msg, 0);
+                return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+            }
+            return 0;
+        default:
+            // assert(0);
+            return BLE_ATT_ERR_UNLIKELY;
+    }
+}
+
+static  struct ble_gatt_svc_def gatt_svr_svcs[] = {
+        {
+            .type = BLE_GATT_SVC_TYPE_PRIMARY,
+            .uuid = &ble_peripheral_srv_uuid,
+            .characteristics = (struct ble_gatt_chr_def[]) {
+                {0}, {0}, {0}, {0}
+            }
+        },
+        {
+            0, /* No more services */
+        },
+    };
+
+static int gatt_svr_init(void)
+{
+    int rc;
+
+    // ble_svc_gap_init();
+    //ble_gatts_reset();
+    // ble_svc_gatt_init();
+
+    struct ble_gatt_chr_def defs[] =
+            { {
+                    // .uuid = &ble_peripheral_write_uuid,
+                    .uuid = &s_chr_uuids[0],
+                    .val_handle = &s_chr_val_handles[0],
+                    .access_cb = gatt_svr_chr_access_func,
+                    .flags = s_chr_flags[0]
+                }, {
+                    // .uuid = &ble_peripheral_indicate_uuid,
+                    .uuid = &s_chr_uuids[1],
+                    .val_handle = &s_chr_val_handles[1],
+                    .access_cb = gatt_svr_chr_access_func,
+                    .flags = s_chr_flags[1]
+                }, {
+                    // .uuid = &ble_peripheral_notify_uuid,
+                    .uuid = &s_chr_uuids[2],
+                    .val_handle = &s_chr_val_handles[2],
+                    .access_cb = gatt_svr_chr_access_func,
+                    .flags = s_chr_flags[2]
+                }, {
+                    0, /* No more characteristics in this service */
+                },
+            };
+    memcpy(gatt_svr_svcs[0].characteristics, defs, sizeof(defs));
+
+    char buff[BLE_UUID_STR_LEN + 1];
+    ble_uuid_to_str(defs[0].uuid, buff);
+    LLOGD("chr %s flags %d", buff,defs[0].flags);
+    ble_uuid_to_str(defs[1].uuid, buff);
+    LLOGD("chr %s flags %d", buff,defs[1].flags);
+    ble_uuid_to_str(defs[2].uuid, buff);
+    LLOGD("chr %s flags %d", buff,defs[2].flags);
+
+    rc = ble_gatts_count_cfg(gatt_svr_svcs);
+    if (rc != 0) {
+        LLOGE("ble_gatts_count_cfg rc %d", rc);
+        return rc;
+    }
+
+    rc = ble_gatts_add_svcs(gatt_svr_svcs);
+    if (rc != 0) {
+        LLOGE("ble_gatts_add_svcs rc %d", rc);
+        return rc;
+    }
+    return 0;
+}
+
+int luat_nimble_server_send(int id, char* data, size_t data_len) {
+    int rc;
+    struct os_mbuf *om;
+
+    if (g_ble_state != BT_STATE_CONNECTED) {
+        //LLOGI("Not connected yet");
+        return -1;
+    }
+    if (data_len <= 256) {
+        memcpy(buff_for_read, data, data_len);
+        buff_for_read_size = data_len;
+    }
+    else {
+        LLOGW("BLE package limit to 256 bytes");
+        return 1;
+    }
+
+    // 先发indicate, TODO 判断是否有监听
+    om = ble_hs_mbuf_from_flat((const void*)data, (uint16_t)data_len);
+    if (!om) {
+        LLOGE("ble_hs_mbuf_from_flat return NULL!!");
+        return BLE_HS_ENOMEM;
+    }
+    rc = ble_gattc_indicate_custom(g_ble_conn_handle, s_chr_val_handles[1], om);
+    LLOGD("ble_gattc_indicate_custom ret %d", rc);
+
+    // 然后发notify, TODO 判断是否有监听
+    om = ble_hs_mbuf_from_flat((const void*)data, (uint16_t)data_len);
+    if (om) {
+        rc = ble_gattc_notify_custom(g_ble_conn_handle,  s_chr_val_handles[2], om);
+        LLOGD("ble_gattc_notify_custom ret %d", rc);
+    }
+    return rc;
+}
+
+int luat_nimble_server_send_notify(ble_uuid_any_t* srv, ble_uuid_any_t* chr, char* data, size_t data_len) {
+    int rc = 0;
+    struct os_mbuf *om;
+    uint16_t handle = 0;
+    char buff[BLE_UUID_STR_LEN] = {0};
+    ble_uuid_to_str(chr, buff);
+    for (size_t i = 0; i < LUAT_BLE_MAX_CHR; i++)
+    {
+        if (!ble_uuid_cmp(&s_chr_uuids[i], chr)) {
+            if (s_chr_notify_states[i] == 0) {
+                LLOGW("chr notify %s state == 0, skip", buff);
+                return -1;
+            }
+            om = ble_hs_mbuf_from_flat((const void*)data, (uint16_t)data_len);
+            if (!om) {
+                LLOGE("ble_hs_mbuf_from_flat return NULL!!");
+                return BLE_HS_ENOMEM;
+            }
+            rc = ble_gattc_notify_custom(g_ble_conn_handle,  s_chr_val_handles[i], om);
+            LLOGD("ble_gattc_notify %s len %d ret %d", buff, data_len, rc);
+            return 0;
+        }
+    }
+    LLOGD("ble_gattc_notify not such chr %s", buff);
+    return -1;
+}
+
+int luat_nimble_server_send_indicate(ble_uuid_any_t* srv, ble_uuid_any_t* chr, char* data, size_t data_len) {
+    int rc = 0;
+    struct os_mbuf *om;
+    uint16_t handle = 0;
+    for (size_t i = 0; i < LUAT_BLE_MAX_CHR; i++)
+    {
+        if (!ble_uuid_cmp(&s_chr_uuids[i], chr)) {
+            if (s_chr_indicate_states[i] == 0) {
+                LLOGW("chr indicate state == 0, skip");
+                return -1;
+            }
+            om = ble_hs_mbuf_from_flat((const void*)data, (uint16_t)data_len);
+            if (!om) {
+                LLOGE("ble_hs_mbuf_from_flat return NULL!!");
+                return BLE_HS_ENOMEM;
+            }
+            rc = ble_gattc_indicate_custom(g_ble_conn_handle,  s_chr_val_handles[i], om);
+            LLOGD("ble_gattc_indicate_custom ret %d", rc);
+            return 0;
+        }
+    }
+    return -1;
+}
+
+
+// static const char *tag = "NimBLE_BLE_PRPH";
+static int bleprph_gap_event(struct ble_gap_event *event, void *arg);
+#if CONFIG_EXAMPLE_RANDOM_ADDR
+static uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM;
+#else
+static uint8_t own_addr_type;
+#endif
+
+void ble_store_config_init(void);
+
+/**
+ * Logs information about a connection to the console.
+ */
+
+#define ADDR_FMT "%02X%02X%02X%02X%02X%02X"
+#define ADDR_T(addr) addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]
+
+static void
+bleprph_print_conn_desc(struct ble_gap_conn_desc *desc)
+{
+    LLOGI("handle=%d our_ota_addr_type=%d our_ota_addr=" ADDR_FMT, desc->conn_handle, desc->our_ota_addr.type, ADDR_T(desc->our_ota_addr.val));
+    LLOGI(" our_id_addr_type=%d our_id_addr=" ADDR_FMT, desc->our_id_addr.type, ADDR_T(desc->our_id_addr.val));
+    LLOGI(" peer_ota_addr_type=%d peer_ota_addr=" ADDR_FMT, desc->peer_ota_addr.type, ADDR_T(desc->peer_ota_addr.val));
+    LLOGI(" peer_id_addr_type=%d peer_id_addr=" ADDR_FMT, desc->peer_id_addr.type, ADDR_T(desc->peer_id_addr.val));
+    LLOGI(" conn_itvl=%d conn_latency=%d supervision_timeout=%d "
+                "encrypted=%d authenticated=%d bonded=%d",
+                desc->conn_itvl, desc->conn_latency,
+                desc->supervision_timeout,
+                desc->sec_state.encrypted,
+                desc->sec_state.authenticated,
+                desc->sec_state.bonded);
+}
+
+#if CONFIG_EXAMPLE_EXTENDED_ADV
+/**
+ * Enables advertising with the following parameters:
+ *     o General discoverable mode.
+ *     o Undirected connectable mode.
+ */
+static void
+ext_bleprph_advertise(void)
+{
+    struct ble_gap_ext_adv_params params;
+    struct os_mbuf *data;
+    uint8_t instance = 1;
+    int rc;
+
+    /* use defaults for non-set params */
+    memset (&params, 0, sizeof(params));
+
+    /* enable connectable advertising */
+    params.connectable = 1;
+    params.scannable = 1;
+    params.legacy_pdu = 1;
+
+    /* advertise using random addr */
+    params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
+
+    params.primary_phy = BLE_HCI_LE_PHY_1M;
+    params.secondary_phy = BLE_HCI_LE_PHY_2M;
+    //params.tx_power = 127;
+    params.sid = 1;
+
+    params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
+    params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
+
+    /* configure instance 0 */
+    rc = ble_gap_ext_adv_configure(instance, &params, NULL,
+                                   bleprph_gap_event, NULL);
+    assert (rc == 0);
+
+    /* in this case only scan response is allowed */
+
+    /* get mbuf for scan rsp data */
+    data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
+    assert(data);
+
+    /* fill mbuf with scan rsp data */
+    rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
+    assert(rc == 0);
+
+    rc = ble_gap_ext_adv_set_data(instance, data);
+    assert (rc == 0);
+
+    /* start advertising */
+    rc = ble_gap_ext_adv_start(instance, 0, 0);
+    assert (rc == 0);
+}
+#else
+/**
+ * Enables advertising with the following parameters:
+ *     o General discoverable mode.
+ *     o Undirected connectable mode.
+ */
+static void
+bleprph_advertise(void)
+{
+    // struct ble_gap_adv_params adv_params;
+    struct ble_hs_adv_fields fields;
+    const char *name;
+    int rc;
+
+    /**
+     *  Set the advertisement data included in our advertisements:
+     *     o Flags (indicates advertisement type and other general info).
+     *     o Advertising tx power.
+     *     o Device name.
+     *     o 16-bit service UUIDs (alert notifications).
+     */
+
+    memset(&fields, 0, sizeof fields);
+
+    /* Advertise two flags:
+     *     o Discoverability in forthcoming advertisement (general)
+     *     o BLE-only (BR/EDR unsupported).
+     */
+    fields.flags = BLE_HS_ADV_F_DISC_GEN |
+                   BLE_HS_ADV_F_BREDR_UNSUP;
+
+    /* Indicate that the TX power level field should be included; have the
+     * stack fill this value automatically.  This is done by assigning the
+     * special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
+     */
+    fields.tx_pwr_lvl_is_present = 1;
+    fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
+
+    name = ble_svc_gap_device_name();
+    fields.name = (uint8_t *)name;
+    fields.name_len = strlen(name);
+    fields.name_is_complete = 1;
+
+    // fields.uuids16 = (ble_uuid16_t[]) {
+    //     ble_peripheral_srv_uuid
+    // };
+    fields.uuids16 = (const ble_uuid16_t *)&ble_peripheral_srv_uuid;
+    fields.num_uuids16 = 1;
+    fields.uuids16_is_complete = 1;
+
+    rc = ble_gap_adv_set_fields(&fields);
+    if (rc != 0) {
+        LLOGE("error setting advertisement data; rc=%d", rc);
+        return;
+    }
+
+    /* Begin advertising. */
+    // memset(&adv_params, 0, sizeof adv_params);
+    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
+    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
+    rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
+                           &adv_params, bleprph_gap_event, NULL);
+    if (rc != 0) {
+        LLOGE("error enabling advertisement; rc=%d", rc);
+        return;
+    }
+    else {
+        LLOGD("ble_gap_adv start rc=0");
+    }
+}
+#endif
+
+/**
+ * The nimble host executes this callback when a GAP event occurs.  The
+ * application associates a GAP event callback with each connection that forms.
+ * bleprph uses the same callback for all connections.
+ *
+ * @param event                 The type of event being signalled.
+ * @param ctxt                  Various information pertaining to the event.
+ * @param arg                   Application-specified argument; unused by
+ *                                  bleprph.
+ *
+ * @return                      0 if the application successfully handled the
+ *                                  event; nonzero on failure.  The semantics
+ *                                  of the return code is specific to the
+ *                                  particular GAP event being signalled.
+ */
+static int
+bleprph_gap_event(struct ble_gap_event *event, void *arg)
+{
+    struct ble_gap_conn_desc desc;
+    int rc;
+    // LLOGD("gap event->type %d", event->type);
+    char buff[BLE_UUID_STR_LEN + 1] = {0};
+    rtos_msg_t msg = {
+        .handler = l_ble_state_cb
+    };
+    switch (event->type) {
+    case BLE_GAP_EVENT_CONNECT:
+        /* A new connection was established or a connection attempt failed. */
+        LLOGI("connection %s; status=%d ",
+                    event->connect.status == 0 ? "established" : "failed",
+                    event->connect.status);
+        if (event->connect.status == 0) {
+            g_ble_conn_handle = event->connect.conn_handle;
+            rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
+            // if (rc == 0)
+            //     bleprph_print_conn_desc(&desc);
+            g_ble_state = BT_STATE_CONNECTED;
+        }
+        else {
+            g_ble_state = BT_STATE_DISCONNECT;
+        }
+        msg.arg1 = g_ble_state;
+        luat_msgbus_put(&msg, 0);
+        // LLOGI("");
+
+        if (event->connect.status != 0) {
+            /* Connection failed; resume advertising. */
+#if CONFIG_EXAMPLE_EXTENDED_ADV
+            ext_bleprph_advertise();
+#else
+            bleprph_advertise();
+#endif
+        }
+        return 0;
+
+    case BLE_GAP_EVENT_DISCONNECT:
+        g_ble_state = BT_STATE_DISCONNECT;
+        LLOGI("disconnect; reason=%d ", event->disconnect.reason);
+        // bleprph_print_conn_desc(&event->disconnect.conn);
+        for (size_t i = 0; i < LUAT_BLE_MAX_CHR; i++)
+        {
+            s_chr_notify_states[i] = 0;
+            s_chr_indicate_states[i] = 0;
+        }
+        
+        msg.arg1 = g_ble_state;
+        luat_msgbus_put(&msg, 0);
+
+        /* Connection terminated; resume advertising. */
+#if CONFIG_EXAMPLE_EXTENDED_ADV
+        ext_bleprph_advertise();
+#else
+        bleprph_advertise();
+#endif
+        return 0;
+
+    case BLE_GAP_EVENT_CONN_UPDATE:
+        /* The central has updated the connection parameters. */
+        LLOGI("connection updated; status=%d ", event->conn_update.status);
+        rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
+        // if (rc == 0)
+        //     bleprph_print_conn_desc(&desc);
+        // LLOGI("");
+        return 0;
+
+    case BLE_GAP_EVENT_ADV_COMPLETE:
+        LLOGI("advertise complete; reason=%d", event->adv_complete.reason);
+#if !CONFIG_EXAMPLE_EXTENDED_ADV
+        bleprph_advertise();
+#endif
+        return 0;
+
+    case BLE_GAP_EVENT_ENC_CHANGE:
+        /* Encryption has been enabled or disabled for this connection. */
+        LLOGI("encryption change event; status=%d ",
+                    event->enc_change.status);
+        rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
+        if (rc == 0)
+            bleprph_print_conn_desc(&desc);
+        // LLOGI("");
+        return 0;
+
+    case BLE_GAP_EVENT_SUBSCRIBE:
+        // LLOGI("subscribe event; conn_handle=%d attr_handle=%d "
+        //             "reason=%d prevn=%d curn=%d previ=%d curi=%d",
+        //             event->subscribe.conn_handle,
+        //             event->subscribe.attr_handle,
+        //             event->subscribe.reason,
+        //             event->subscribe.prev_notify,
+        //             event->subscribe.cur_notify,
+        //             event->subscribe.prev_indicate,
+        //             event->subscribe.cur_indicate);
+        for (size_t i = 0; i < LUAT_BLE_MAX_CHR; i++)
+        {
+            if (s_chr_val_handles[i] == event->subscribe.attr_handle) {
+                ble_uuid_to_str(&s_chr_uuids[i], buff);
+                LLOGD("subscribe %s notify %d indicate %d", buff, event->subscribe.cur_notify, event->subscribe.cur_indicate);
+                s_chr_notify_states[i] = event->subscribe.cur_notify;
+                s_chr_indicate_states[i] = event->subscribe.cur_indicate;
+                // TODO 发送系统消息
+                return 0;
+            }
+        }
+        LLOGI("subscribe event but chr NOT FOUND");
+        return 0;
+
+    case BLE_GAP_EVENT_MTU:
+        LLOGI("mtu update event; conn_handle=%d cid=%d mtu=%d",
+                    event->mtu.conn_handle,
+                    event->mtu.channel_id,
+                    event->mtu.value);
+        return 0;
+
+    case BLE_GAP_EVENT_REPEAT_PAIRING:
+        /* We already have a bond with the peer, but it is attempting to
+         * establish a new secure link.  This app sacrifices security for
+         * convenience: just throw away the old bond and accept the new link.
+         */
+
+        /* Delete the old bond. */
+        rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
+        // assert(rc == 0);
+        ble_store_util_delete_peer(&desc.peer_id_addr);
+
+        /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
+         * continue with the pairing operation.
+         */
+        return BLE_GAP_REPEAT_PAIRING_RETRY;
+
+    case BLE_GAP_EVENT_PASSKEY_ACTION:
+        LLOGI("PASSKEY_ACTION_EVENT started");
+#if 0
+        struct ble_sm_io pkey = {0};
+        int key = 0;
+
+        if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
+            pkey.action = event->passkey.params.action;
+            pkey.passkey = 123456; // This is the passkey to be entered on peer
+            // LLOGI("Enter passkey %d on the peer side", pkey.passkey);
+            rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+            LLOGI("ble_sm_inject_io BLE_SM_IOACT_DISP result: %d", rc);
+        } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
+            LLOGI("Passkey on device's display: %d", event->passkey.params.numcmp);
+            LLOGI("Accept or reject the passkey through console in this format -> key Y or key N");
+            pkey.action = event->passkey.params.action;
+            // if (scli_receive_key(&key)) {
+            //     pkey.numcmp_accept = key;
+            // } else {
+            //     pkey.numcmp_accept = 0;
+            //     ESP_LOGE(tag, "Timeout! Rejecting the key");
+            // }
+            pkey.numcmp_accept = 1;
+            rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+            LLOGI("ble_sm_inject_io BLE_SM_IOACT_NUMCMP result: %d", rc);
+        } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
+            static uint8_t tem_oob[16] = {0};
+            pkey.action = event->passkey.params.action;
+            for (int i = 0; i < 16; i++) {
+                pkey.oob[i] = tem_oob[i];
+            }
+            rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+            LLOGI("ble_sm_inject_io BLE_SM_IOACT_OOB result: %d", rc);
+        } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
+            LLOGI("Passkey on device's display: %d", event->passkey.params.numcmp);
+            LLOGI("Enter the passkey through console in this format-> key 123456");
+            pkey.action = event->passkey.params.action;
+            // if (scli_receive_key(&key)) {
+            //     pkey.passkey = key;
+            // } else {
+            //     pkey.passkey = 0;
+            //     ESP_LOGE(tag, "Timeout! Passing 0 as the key");
+            // }
+            pkey.passkey = event->passkey.params.numcmp;
+            rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+            LLOGI("ble_sm_inject_io BLE_SM_IOACT_INPUT result: %d", rc);
+        }
+#endif
+        return 0;
+    }
+
+    return 0;
+}
+
+static void
+bleprph_on_reset(int reason)
+{
+    g_ble_state = BT_STATE_OFF;
+    LLOGE("Resetting state; reason=%d", reason);
+    //app_adapter_state_changed_callback(WM_BT_STATE_OFF);
+}
+
+
+static void
+bleprph_on_sync(void)
+{
+    int rc;
+
+#if CONFIG_EXAMPLE_RANDOM_ADDR
+    /* Generate a non-resolvable private address. */
+    ble_app_set_addr();
+#endif
+
+    /* Make sure we have proper identity address set (public preferred) */
+#if CONFIG_EXAMPLE_RANDOM_ADDR
+    rc = ble_hs_util_ensure_addr(1);
+#else
+    rc = ble_hs_util_ensure_addr(0);
+#endif
+    // assert(rc == 0);
+
+    /* Figure out address to use while advertising (no privacy for now) */
+    rc = ble_hs_id_infer_auto(0, &own_addr_type);
+    if (rc != 0) {
+        LLOGE("error determining address type; rc=%d", rc);
+        return;
+    }
+
+    /* Printing ADDR */
+    uint8_t addr_val[6] = {0};
+    rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
+    if (rc) {
+        LLOGE("ble_hs_id_copy_addr rc %d", rc);
+    }
+
+    LLOGI("Device Address: " ADDR_FMT, ADDR_T(addr_val));
+    if (luat_ble_dev_name_len == 0) {
+        sprintf_((char*)luat_ble_dev_name, "LOS-" ADDR_FMT, ADDR_T(addr_val));
+        LLOGD("BLE name: %s", luat_ble_dev_name);
+        luat_ble_dev_name_len = strlen((const char*)luat_ble_dev_name);
+        rc = ble_svc_gap_device_name_set((const char*)luat_ble_dev_name);
+        if (rc) {
+            LLOGE("ble_svc_gap_device_name_set rc %d", rc);
+        }
+    }
+    rc = ble_gatts_start();
+    if (rc) {
+        LLOGE("ble_gatts_start rc %d", rc);
+    }
+
+    /* Begin advertising. */
+#if CONFIG_EXAMPLE_EXTENDED_ADV
+    ext_bleprph_advertise();
+#else
+    bleprph_advertise();
+#endif
+
+    if (g_ble_state == BT_STATE_OFF)
+        g_ble_state = BT_STATE_ON;
+}
+
+
+
+int luat_nimble_init_peripheral(uint8_t uart_idx, char* name, int mode) {
+    int rc = 0;
+    nimble_port_init();
+
+    /* Set the default device name. */
+    if (name != NULL && strlen(name)) {
+        rc = ble_svc_gap_device_name_set((const char*)name);
+    }
+
+    /* Initialize the NimBLE host configuration. */
+    ble_hs_cfg.reset_cb = bleprph_on_reset;
+    ble_hs_cfg.sync_cb = bleprph_on_sync;
+    ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
+    ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
+
+    ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
+    ble_hs_cfg.sm_sc = 0;
+
+    ble_svc_gap_init();
+    ble_svc_gatt_init();
+
+    rc = gatt_svr_init();
+    if (rc)
+        LLOGD("gatt_svr_init rc %d", rc);
+
+    /* XXX Need to have template for store */
+    ble_store_config_init();
+
+    return 0;
+}
+