Просмотр исходного кода

add: 新增ble蓝牙配网demo

王文中 5 месяцев назад
Родитель
Сommit
173f886813

+ 117 - 0
module/Air8000/demo/config_wifi_network/ble_config_wifi/ble_config_wifi.lua

@@ -0,0 +1,117 @@
+--[[
+@module  ble_config_wifi
+@summary ble_config_wifi 主应用功能模块(支持STA配网 + SoftAP热点配置)
+@version 1.1
+@date    2025.08.11
+@author  拓毅恒
+@usage
+用法实例(支持双模式):
+    本模块实现了基于 BLE 的 Wi-Fi 配网功能,支持 STA 配网和 SoftAP 热点配置两种模式,使用方法如下:
+    1. 模块加载:在 main.lua 中使用 `require "ble_config_wifi"` 即可加载并运行本模块。
+    2. 本demo中 手机端APP 下载地址:
+        安卓测试APP下载地址1:https://github.com/EspressifApp/EspBlufiForAndroid/releases(如果打不开就用下面的)
+        安卓测试APP下载地址2:https://docs.openluat.com/cdn2/apk/blufi-1.6.5-31.apk
+    3. STA 配网流程:
+        - 系统会初始化蓝牙设备并启动 espblufi 配网功能。
+        - 通过手机APP端与设备进行 BLE 连接,配置 STA 的 SSID 和密码(APP端打开如果没有搜到蓝牙就在APP内多次下拉刷新,并且重启一下模组)。
+        - 当收到 "STA_CONNED" 消息时,系统会发起 HTTP 请求验证网络可用性。
+        - 当收到 "STA_DISCONNED" 消息时,系统会主动断开 STA 连接并禁用多网融合代理。
+    4. SoftAP 配置流程:
+        - 手机APP端通过 BLE 配置 SoftAP 的 SSID、密码、信道和最大连接数。
+        - espblufi 模块收到配置后,调用 `wlan.createAP()` 创建热点。
+        - 当收到 "AP_CONNED" 消息时,系统会发起 SoftAP 创建请求。
+        - 同时调用 `exnetif.setproxy()` 启用多网融合,使连接热点的设备可通过 4G 或 STA 上网。
+    5. 消息处理:
+        - 定义 `network_event_handler` 函数监听网络消息,处理 STA 连接、断开及 AP 创建等事件。
+        - 定义 `espblufi_callback` 回调函数,处理 STA 和 SoftAP 的信息事件并打印配置信息。
+    6. 任务管理:
+        - 初始化 `ble_wifi_config_task` 任务负责蓝牙设备和 espblufi 模块的初始化及配网功能启动。
+        - 初始化 `network_event_handler` 任务持续监听网络消息,保持系统持续运行。
+本文件没有对外接口,直接在 main.lua 中 require "ble_config_wifi" 即可加载运行。
+]]
+
+-- 加载espblufi应用功能模块
+local espblufi = require("espblufi")
+local exnetif = require("exnetif")
+local taskName = "config_wifi"
+
+-- 定义网络测试函数
+local function network_event_handler()
+    while true do
+        -- 等待指定任务的消息,不设置超时时间
+        msg = sysplus.waitMsg(taskName, nil)
+        -- 检查接收到的消息是否为表类型
+        if type(msg) == 'table' then
+            log.info("MSG:", msg[1])
+            -- 检查消息的第一个元素是否为 "STA_CONNED",即 STA 连接成功消息
+            if msg[1] == "STA_CONNED" then
+                -- 打印 STA 连接成功日志
+                log.info("STA:", "STA CONNED OK!")
+                sys.wait(3000)
+                -- 发起一个 GET 请求,请求 https://httpbin.air32.cn/bytes/2048 地址,使用 STA 适配器,超时时间为 5000 毫秒
+                local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil, {adapter = socket.LWIP_STA,timeout = 5000,debug = false}).wait()
+                -- 打印 HTTP 请求执行结果,包含状态码、响应头和响应体长度
+                log.info("http执行结果", code, headers, body and #body)
+            end
+            -- 检查消息的第一个元素是否为 "STA_DISCONNED",即 STA 断开连接消息
+            if msg[1] == "STA_DISCONNED" then
+                -- 打印 STA 断开连接日志
+                log.info("STA:", "STA DISCONNED!")
+            end
+            -- 检查消息的第一个元素是否为 "AP_CONNED",即 AP 创建连接消息
+            if msg[1] == "AP_CONNED" then
+                log.info("AP:", "接收到AP创建消息,开始启动AP")
+                -- 调用 exnetif.setproxy 函数,设置 AP 网卡与 4G 网卡的多网融合代理
+                exnetif.setproxy(socket.LWIP_AP, socket.LWIP_GP, {
+                    ssid = msg[2], -- WiFi名称(string),网卡包含wifi时填写
+                    password = msg[3], -- WiFi密码(string),网卡包含wifi时填写
+                    adapter_addr = "192.168.5.1",    -- adapter网卡的ip地址
+                    adapter_gw= {192, 168, 5, 1},   -- adapter网卡的网关地址
+                    ap_opts={                        -- AP模式下配置项
+                        hidden = false,                  -- 是否隐藏SSID, 默认false,不隐藏
+                        max_conn = msg[4]                  -- 最大客户端数量
+                    },
+                    channel=msg[5]                        -- AP建立的通道
+                })
+                log.info("AP:", "AP创建成功")
+            end
+        end
+        sys.wait(10)
+    end
+end
+
+-- 定义 espblufi 回调函数,用于处理不同类型的事件,如 EVENT_STA_INFO、EVENT_SOFTAP_INFO 等。
+-- EVENT_STA_INFO 事件:当收到 APP 下发的 STA 连接信息时触发,会打印出设备连接 WiFi STA 所输入的键值对,其中 i 为配置项名称(如 "ssid"、"passwd"),v 为对应的值。
+-- EVENT_SOFTAP_INFO 事件:当收到 APP 下发的 SoftAP 配置信息时触发,会打印出 SoftAP 配置的键值对,其中 i 为配置项名称(如 "ssid"、"passwd"、"channel" 等),v 为对应的值。
+local function espblufi_callback(event, data)
+    -- 检查事件类型是否为 STA 信息事件
+    if event == espblufi.EVENT_STA_INFO then
+        -- 遍历 STA 信息数据
+        for i, v in pairs(data) do
+            -- 打印 STA 信息数据的键值对
+            log.info("STA:", i, v)
+        end
+        -- 检查事件类型是否为 SoftAP 信息事件
+    elseif event == espblufi.EVENT_SOFTAP_INFO then
+        -- 遍历 SoftAP 信息数据
+        for i, v in pairs(data) do
+            -- 打印 SoftAP 信息数据的键值对
+            log.info("SoftAP:", i, v)
+        end
+    end
+end
+
+-- 定义网络配置任务函数
+local function ble_wifi_config_task()
+    -- 初始化蓝牙设备,并将初始化结果存储在 bluetooth_device 变量中
+    local bluetooth_device = bluetooth.init()
+    -- 初始化 espblufi 模块,传入蓝牙设备实例和回调函数
+    espblufi.init(bluetooth_device, espblufi_callback)
+    -- 启动 espblufi 配网功能
+    espblufi.start()
+end
+
+-- 初始化网络配置任务
+sysplus.taskInitEx(ble_wifi_config_task, "ble_wifi_config_task")
+-- 初始化网络测试任务
+sysplus.taskInitEx(network_event_handler, taskName)

+ 39 - 0
module/Air8000/demo/config_wifi_network/ble_config_wifi/check_wifi.lua

@@ -0,0 +1,39 @@
+--[[
+@module  check_wifi
+@summary 用于检查当前模组中WiFi是否是最新版本,使用蓝牙配网功能需要WIFI版本≥14。
+            如果模组中WiFi版本<14,则需要打开此功能启动升级。
+@version 1.1
+@date    2025.08.11
+@author  拓毅恒
+@usage
+本文件为WIFI固件升级功能模块,核心业务逻辑为:
+1、判断网络是否正常;
+2、执行WiFi升级操作;
+3、反馈升级结果;
+
+本文件没有对外接口,直接在main.lua中require "check_wifi"就可以加载运行;
+]]
+local exfotawifi = require("exfotawifi")
+
+local function wifi_fota_task_func()
+    local result = exfotawifi.request()
+    if result then
+        log.info("exfotawifi", "升级任务执行成功")
+    else
+        log.info("exfotawifi", "升级任务执行失败")
+    end
+end
+
+-- 判断网络是否正常
+local function wait_ip_ready()
+    local result, ip, adapter = sys.waitUntil("IP_READY", 30000)
+    if result then
+        log.info("exfotawifi", "开始执行升级任务")
+        sys.taskInit(wifi_fota_task_func)
+    else
+        log.error("当前正在升级WIFI&蓝牙固件,请插入可以上网的SIM卡")
+    end
+end
+
+-- 在设备启动时检查网络状态
+sys.taskInit(wait_ip_ready)

+ 718 - 0
module/Air8000/demo/config_wifi_network/ble_config_wifi/espblufi.lua

@@ -0,0 +1,718 @@
+--[[
+@module espblufi
+@summary espblufi esp blufi 蓝牙配网(注意:初版暂不支持加密功能!!!!!!!!)
+@version 1.1
+@date    2025.08.07
+@author  拓毅恒
+@usage
+-- 此为Blufi 配网库
+-- BluFi 配网指南:https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_networking_user_guide_cn.pdf
+
+-- 安卓测试APP下载地址:https://github.com/EspressifApp/EspBlufiForAndroid/releases
+-- 安卓APP源码下载地址:https://github.com/EspressifApp/EspBlufiForAndroid
+
+-- 小程序测试:微信搜索小程序:ESP Config
+-- 小程序源码下载地址:https://github.com/EspressifApps/ESP-Config-WeChat
+
+-- 注意:初版暂不支持加密功能!!!!!!!!
+
+-- 业务逻辑总结:
+-- 1. 此函数库需跟APP搭配使用,调用前首先需要初始化蓝牙。
+-- 2. 初始化 espblufi 模块,并传入蓝牙设备实例和回调函数。
+-- 3. 启动 espblufi 配网功能来进行APP配网。
+-- 4. 在APP端点击配网后,会根据配的是station还是softap发布相应的消息 "STA_CONNED"、"STA_DISCONNED"、"AP_CONNED",用户只需要根据消息设置自己的逻辑即可。
+-- 具体业务逻辑实现可以参考下面实例代码。
+
+-- 用法实例
+local espblufi = require("espblufi")
+local taskName = "config_wifi"
+
+local function network_event_handler()
+    while true do
+        msg = sysplus.waitMsg(taskName, nil)
+        if type(msg) == 'table' then
+            -- 检查消息的第一个元素是否为 "STA_CONNED",即 STA 连接成功消息
+            if msg[1] == "STA_CONNED" then
+                ...
+                写入自己的逻辑函数
+                ...
+            end
+            -- 检查消息的第一个元素是否为 "STA_DISCONNED",即 STA 断开连接消息
+            if msg[1] == "STA_DISCONNED" then
+                ...
+                写入自己的逻辑函数
+                ...
+            end
+            -- 检查消息的第一个元素是否为 "AP_CONNED",即 AP 创建连接消息
+            if msg[1] == "AP_CONNED" then
+                ...
+                写入自己的逻辑函数
+                ...
+            end
+        end
+        sys.wait(10)
+    end
+end
+
+-- 定义 espblufi 回调函数,用于处理不同类型的事件,如EVENT_STA_INFO、EVENT_SOFTAP_INFO等
+-- 详情可以在espblufi.lua中查看,demo中演示收到APP下发的连接消息后,打印输入的ssid和passwd
+local function espblufi_callback(event, data)
+    if event == espblufi.EVENT_STA_INFO then
+        for i, v in pairs(data) do
+            print("STA:", i, v)
+        end
+    elseif event == espblufi.EVENT_SOFTAP_INFO then
+        for i, v in pairs(data) do
+            print("SoftAP:", i, v)
+        end
+    end
+end
+
+-- 定义网络配置任务函数
+local function ble_wifi_config_task()
+    local bluetooth_device = bluetooth.init()
+    espblufi.init(bluetooth_device, espblufi_callback)
+    espblufi.start()
+end
+
+-- 初始化网络配置任务
+sysplus.taskInitEx(ble_wifi_config_task, "ble_wifi_config_task")
+-- 初始化网络测试任务
+sysplus.taskInitEx(network_event_handler, taskName)
+
+]]
+
+local espblufi = {}
+
+local sys = require "sys"
+local sysplus = require "sysplus"
+
+local BLUFI_TOPIC = "espblufi"
+local taskName = "config_wifi"
+
+local BTC_BLUFI_GREAT_VER = 0x01 -- Version + Subversion
+local BTC_BLUFI_SUB_VER = 0x03 -- Version + Subversion
+local BTC_BLUFI_VERSION = ((BTC_BLUFI_GREAT_VER << 8) | BTC_BLUFI_SUB_VER) -- Version + Subversion
+
+-- packet type
+local BLUFI_TYPE_MASK = 0x03
+local BLUFI_TYPE_SHIFT = 0
+local BLUFI_SUBTYPE_MASK = 0xFC
+local BLUFI_SUBTYPE_SHIFT = 2
+
+local function BLUFI_GET_TYPE(type)
+    return ((type) & BLUFI_TYPE_MASK)
+end
+local function BLUFI_GET_SUBTYPE(type)
+    return (((type) & BLUFI_SUBTYPE_MASK) >> BLUFI_SUBTYPE_SHIFT)
+end
+local function BLUFI_BUILD_TYPE(type, subtype)
+    return (((type) & BLUFI_TYPE_MASK) | ((subtype) << BLUFI_SUBTYPE_SHIFT))
+end
+
+local BLUFI_TYPE_CTRL = 0x0
+local BLUFI_TYPE_CTRL_SUBTYPE_ACK = 0x00
+local BLUFI_TYPE_CTRL_SUBTYPE_SET_SEC_MODE = 0x01
+local BLUFI_TYPE_CTRL_SUBTYPE_SET_WIFI_OPMODE = 0x02
+local BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP = 0x03
+local BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP = 0x04
+local BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS = 0x05
+local BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA = 0x06
+local BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION = 0x07
+local BLUFI_TYPE_CTRL_SUBTYPE_DISCONNECT_BLE = 0x08
+local BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_LIST = 0x09
+
+local BLUFI_TYPE_DATA = 0x1
+local BLUFI_TYPE_DATA_SUBTYPE_NEG = 0x00
+local BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID = 0x01
+local BLUFI_TYPE_DATA_SUBTYPE_STA_SSID = 0x02
+local BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD = 0x03
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID = 0x04
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD = 0x05
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM = 0x06
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE = 0x07
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL = 0x08
+local BLUFI_TYPE_DATA_SUBTYPE_USERNAME = 0x09
+local BLUFI_TYPE_DATA_SUBTYPE_CA = 0x0a
+local BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT = 0x0b
+local BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT = 0x0c
+local BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY = 0x0d
+local BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY = 0x0e
+local BLUFI_TYPE_DATA_SUBTYPE_WIFI_REP = 0x0f
+local BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION = 0x10
+local BLUFI_TYPE_DATA_SUBTYPE_WIFI_LIST = 0x11
+local BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO = 0x12
+local BLUFI_TYPE_DATA_SUBTYPE_CUSTOM_DATA = 0x13
+local BLUFI_TYPE_DATA_SUBTYPE_STA_MAX_CONN_RETRY = 0x14
+local BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_END_REASON = 0x15
+local BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_RSSI = 0x16
+
+local function BLUFI_TYPE_IS_CTRL(type)
+    return (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_CTRL)
+end
+local function BLUFI_TYPE_IS_DATA(type)
+    return (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_DATA)
+end
+
+-- packet frame control
+local BLUFI_FC_ENC_MASK = 0x01
+local BLUFI_FC_CHECK_MASK = 0x02
+local BLUFI_FC_DIR_MASK = 0x04
+local BLUFI_FC_REQ_ACK_MASK = 0x08
+local BLUFI_FC_FRAG_MASK = 0x10
+
+local BLUFI_FC_ENC = 0x01
+local BLUFI_FC_CHECK = 0x02
+local BLUFI_FC_DIR_P2E = 0x00
+local BLUFI_FC_DIR_E2P = 0x04
+local BLUFI_FC_REQ_ACK = 0x08
+local BLUFI_FC_FRAG = 0x10
+
+local BLUFI_SEQUENCE_ERROR = 0x00
+local BLUFI_CHECKSUM_ERROR = 0x01
+local BLUFI_DECRYPT_ERROR = 0x02
+local BLUFI_ENCRYPT_ERROR = 0x03
+local BLUFI_INIT_SECURITY_ERROR = 0x04
+local BLUFI_DH_MALLOC_ERROR = 0x05
+local BLUFI_DH_PARAM_ERROR = 0x06
+local BLUFI_READ_PARAM_ERROR = 0x07
+local BLUFI_MAKE_PUBLIC_ERROR = 0x08
+local BLUFI_DATA_FORMAT_ERROR = 0x09
+local BLUFI_CALC_MD5_ERROR = 0x0a
+local BLUFI_WIFI_SCAN_FAIL = 0x0b
+local BLUFI_MSG_STATE_ERROR = 0x0c
+
+local BLUFI_OPMODE_NULL = 0x00
+local BLUFI_OPMODE_STA = 0x01
+local BLUFI_OPMODE_SOFTAP = 0x02
+local BLUFI_OPMODE_SOFTAPSTA = 0x03
+
+local BLUFI_STA_CONN_SUCCESS = 0x00
+local BLUFI_STA_CONN_FAIL = 0x01
+local BLUFI_STA_CONNECTING = 0x02
+local BLUFI_STA_NO_IP = 0x03
+
+local BLUFI_BLE_STATE_DISCONN = 0x00
+local BLUFI_BLE_STATE_CONNED = 0x01
+
+local BLUFI_WLAN_STATE_DISCONN = 0x00
+local BLUFI_WLAN_STATE_CONNING = 0x01
+local BLUFI_WLAN_STATE_CONNED = 0x02
+
+local BLUFI_SEQUENCE_ERROR = 0x00
+local BLUFI_CHECKSUM_ERROR = 0x01
+local BLUFI_DECRYPT_ERROR = 0x02
+local BLUFI_ENCRYPT_ERROR = 0x03
+local BLUFI_INIT_SECURITY_ERROR = 0x04
+local BLUFI_DH_MALLOC_ERROR = 0x05
+local BLUFI_DH_PARAM_ERROR = 0x06
+local BLUFI_READ_PARAM_ERROR = 0x07
+local BLUFI_MAKE_PUBLIC_ERROR = 0x08
+local BLUFI_DATA_FORMAT_ERROR = 0x09
+local BLUFI_CALC_MD5_ERROR = 0x0a
+local BLUFI_WIFI_SCAN_FAIL = 0x0b
+local BLUFI_MSG_STATE_ERROR = 0x0c
+
+local function BLUFI_FC_IS_ENC(fc)
+    return ((fc) & BLUFI_FC_ENC_MASK) ~= 0
+end
+local function BLUFI_FC_IS_CHECK(fc)
+    return ((fc) & BLUFI_FC_CHECK_MASK) ~= 0
+end
+local function BLUFI_FC_IS_REQ_ACK(fc)
+    return ((fc) & BLUFI_FC_REQ_ACK_MASK) ~= 0
+end
+local function BLUFI_FC_IS_FRAG(fc)
+    return ((fc) & BLUFI_FC_FRAG_MASK) ~= 0
+end
+
+local BLUFI_PROTOCOL_DATA = "BLUFI_PROTOCOL_DATA"
+local BLUFI_TASK_EXIT = "BLUFI_TASK_EXIT"
+
+local espblufi_uuid_service = "0xFFFF"
+local espblufi_uuid2device = "0xFF01"
+local espblufi_uuid2mobile = "0xFF02"
+
+local espblufi_att_db = {string.fromHex(espblufi_uuid_service), {string.fromHex(espblufi_uuid2device), ble.WRITE},
+                         {string.fromHex(espblufi_uuid2mobile), ble.NOTIFY | ble.READ}}
+
+local blufi_env = {
+    ble_device = nil,
+    callback = nil,
+    isfrag = nil,
+    ble_state = BLUFI_BLE_STATE_DISCONN,
+    wlan_state = BLUFI_WLAN_STATE_DISCONN,
+    opmode = BLUFI_OPMODE_NULL,
+    recv_seq = 0,
+    send_seq = 0,
+    sec_mode = 0,
+    softap_conn_num = 0,
+    softap_auth_mode = 0,
+    softap_max_conn_num = 0,
+    softap_channel = nil,
+    sta_max_conn_retry = nil,
+    sta_conn_end_reason = nil,
+    sta_ssid = nil,
+    sta_passwd = nil,
+    softap_ssid = nil,
+    softap_passwd = nil
+}
+
+local blufi_hdr = {
+    type = nil,
+    fc = nil,
+    seq = nil,
+    data_len = nil,
+    data = nil
+}
+
+espblufi.EVENT_STA_INFO = 0x01
+espblufi.EVENT_SOFTAP_INFO = 0x02
+espblufi.EVENT_CUSTOM_DATA = 0x03
+
+sys.subscribe("WLAN_AP_INC", function(state, mac)
+    if state == "CONNECTED" then
+        blufi_env.softap_conn_num = blufi_env.softap_conn_num + 1
+    elseif state == "DISCONNECTED" and blufi_env.softap_conn_num > 0 then
+        blufi_env.softap_conn_num = blufi_env.softap_conn_num - 1
+    end
+end)
+
+local function blufi_crc_checksum(data)
+    return crypto.crc16("IBM", data)
+end
+
+local function blufi_send_encap(blufi_hdr_send)
+    local send_data = string.char(blufi_hdr_send.type, blufi_hdr_send.fc, blufi_hdr_send.seq, #blufi_hdr_send.data) ..
+                          blufi_hdr_send.data
+    if BLUFI_TYPE_IS_CTRL(blufi_hdr_send.type) then
+        blufi_hdr_send.fc = blufi_hdr_send.fc | BLUFI_FC_CHECK
+        send_data = send_data .. blufi_crc_checksum(send_data)
+    end
+    blufi_env.send_seq = blufi_env.send_seq + 1
+    blufi_env.ble_device:write_notify({
+        uuid_service = string.fromHex(espblufi_uuid_service),
+        uuid_characteristic = string.fromHex(espblufi_uuid2mobile)
+    }, send_data)
+end
+
+local function btc_blufi_send_encap(type, data)
+    if blufi_env.ble_state == BLUFI_BLE_STATE_DISCONN then
+        return
+    end
+    local blufi_hdr_send = {
+        type = type,
+        fc = 0,
+        seq = blufi_env.send_seq,
+        data = data
+    }
+    blufi_send_encap(blufi_hdr_send)
+end
+
+local function btc_blufi_wifi_conn_report(sta_conn_state)
+    local data = string.char(blufi_env.opmode, blufi_env.wlan_state, blufi_env.softap_conn_num)
+    local wlan_info = wlan.getInfo()
+    if wlan_info then
+        if wlan_info.bssid then
+            data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID, 6) .. string.fromHex(wlan_info.bssid)
+        end
+        if wlan_info.rssi then
+            data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_RSSI, 1, wlan_info.rssi % 256)
+        end
+    end
+    if blufi_env.sta_ssid then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_SSID, #blufi_env.sta_ssid) .. blufi_env.sta_ssid
+    end
+    if blufi_env.sta_passwd then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD, #blufi_env.sta_passwd) .. blufi_env.sta_passwd
+    end
+    if blufi_env.softap_ssid then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID, #blufi_env.softap_ssid) .. blufi_env.softap_ssid
+    end
+    if blufi_env.softap_passwd then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD, #blufi_env.softap_passwd) ..
+                   blufi_env.softap_passwd
+    end
+    if blufi_env.softap_authmode then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE, 1) .. blufi_env.softap_authmode
+    end
+    if blufi_env.softap_max_conn_num then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM, 1) .. blufi_env.softap_max_conn_num
+    end
+    if blufi_env.softap_channel then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL, 1) .. blufi_env.softap_channel
+    end
+    if blufi_env.sta_max_conn_retry then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_MAX_CONN_RETRY, 1) .. blufi_env.sta_max_conn_retry
+    end
+    if blufi_env.sta_conn_end_reason then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_END_REASON, 1) .. blufi_env.sta_conn_end_reason
+    end
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_WIFI_REP), data);
+end
+
+local function btc_blufi_send_wifi_list(results)
+    local data = ""
+    for k, v in pairs(results) do
+        data = data .. string.char(#v["ssid"] + 1, v["rssi"] % 256) .. v["ssid"]
+    end
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_WIFI_LIST), data)
+end
+
+local function btc_blufi_send_ack(seq)
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_CTRL, BLUFI_TYPE_CTRL_SUBTYPE_ACK), string.char(seq))
+end
+
+local function btc_blufi_send_error_info(state)
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO), string.char(state))
+end
+
+local function btc_blufi_send_custom_data(data)
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_CUSTOM_DATA), data)
+end
+
+local SEC_TYPE_DH_PARAM_LEN = 0x00
+local SEC_TYPE_DH_PARAM_DATA = 0x01
+local SEC_TYPE_DH_P = 0x02
+local SEC_TYPE_DH_G = 0x03
+local SEC_TYPE_DH_PUBLIC = 0x04
+
+local blufi_sec = {
+    dh_param_len = 0
+}
+
+local function blufi_dh_negotiate_data_handler(data)
+    if #data < 3 then
+        btc_blufi_send_error_info(BLUFI_DATA_FORMAT_ERROR)
+    end
+
+    local type = data:byte(1)
+    if type == SEC_TYPE_DH_PARAM_LEN then
+        blufi_sec.dh_param_len = ((data:byte(2) << 8) | data:byte(3));
+        -- print("dh_param_len",blufi_sec.dh_param_len)
+    elseif type == SEC_TYPE_DH_PARAM_DATA then
+        -- print("SEC_TYPE_DH_PARAM_DATA")
+        if #data < (blufi_sec.dh_param_len + 1) then
+            btc_blufi_send_error_info(BLUFI_DH_PARAM_ERROR);
+            return;
+        end
+
+        -- 秘钥待实现,功能需要mbedtls引出c接口
+        btc_blufi_send_error_info(BLUFI_INIT_SECURITY_ERROR);
+
+    elseif type == SEC_TYPE_DH_P then
+        print("SEC_TYPE_DH_P")
+    elseif type == SEC_TYPE_DH_G then
+        print("SEC_TYPE_DH_G")
+    elseif type == SEC_TYPE_DH_PUBLIC then
+        print("SEC_TYPE_DH_PUBLIC")
+    end
+
+end
+
+local function btc_blufi_protocol_handler(parse_data)
+    local target_data_len = 0
+    local parse_data_len = #parse_data
+
+    if parse_data_len < 4 then
+        return
+    end
+
+    blufi_hdr.type, blufi_hdr.fc, blufi_hdr.seq, blufi_hdr.data_len = string.unpack('<BBBB', parse_data)
+
+    if BLUFI_FC_IS_CHECK(blufi_hdr.fc) then
+        target_data_len = blufi_hdr.data_len + 4 + 2 -- // Data + (Type + Frame Control + Sequence Number + Data Length) + Checksum
+    else
+        target_data_len = blufi_hdr.data_len + 4 -- Data + (Type + Frame Control + Sequence Number + Data Length)
+    end
+    -- print("target_data_len",target_data_len,"parse_data_len",parse_data_len)
+    if target_data_len ~= parse_data_len then
+        return
+    end
+
+    if blufi_hdr.seq ~= blufi_env.recv_seq then
+        return
+    end
+    blufi_env.recv_seq = blufi_env.recv_seq + 1
+
+    if BLUFI_FC_IS_ENC(blufi_hdr.fc) then
+        -- 解密功能需要mbedtls引出c接口
+    end
+    if BLUFI_FC_IS_CHECK(blufi_hdr.fc) and crypto then
+        -- 需要app配合调试,暂时不强制校验
+        local checksum = blufi_crc_checksum()
+    end
+
+    if BLUFI_FC_IS_REQ_ACK(blufi_hdr.fc) then
+        btc_blufi_send_ack(blufi_hdr.seq)
+    end
+
+    if blufi_hdr.data_len and blufi_hdr.data_len > 0 then
+        if blufi_env.isfrag then
+            blufi_hdr.data = blufi_hdr.data .. parse_data:sub(5)
+        else
+            blufi_hdr.data = parse_data:sub(5)
+        end
+    end
+
+    if BLUFI_FC_IS_FRAG(blufi_hdr.fc) then
+        blufi_env.isfrag = true
+        return
+    else
+        blufi_env.isfrag = false
+    end
+
+    -- print(blufi_hdr.type,blufi_hdr.fc,blufi_hdr.seq,blufi_hdr.data_len)
+    -- print(blufi_hdr.data,blufi_hdr.data:toHex())
+
+    -- 从 blufi_hdr.type 中提取数据包的类型,使用 BLUFI_GET_TYPE 函数
+    local blufi_type = BLUFI_GET_TYPE(blufi_hdr.type)
+    -- 从 blufi_hdr.type 中提取数据包的子类型,使用 BLUFI_GET_SUBTYPE 函数
+    local blufi_subtype = BLUFI_GET_SUBTYPE(blufi_hdr.type)
+
+    -- 检查数据包类型是否为控制类型
+    if blufi_type == BLUFI_TYPE_CTRL then
+        -- 检查子类型是否为确认(ACK)类型
+        if blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_ACK then
+            -- 此处为空,未实现具体逻辑
+            -- 检查子类型是否为设置安全模式类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_SET_SEC_MODE then
+            -- 将接收到的数据赋值给环境变量中的安全模式字段
+            blufi_env.sec_mode = blufi_hdr.data;
+            -- 检查子类型是否为设置WiFi工作模式类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_SET_WIFI_OPMODE then
+            -- 从接收到的数据中提取第一个字节,将其作为WiFi工作模式并更新到环境变量中
+            blufi_env.opmode = blufi_hdr.data:byte(1)
+            -- 检查子类型是否为连接到AP的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP then
+            -- 调用回调函数,传递STA信息事件类型和包含SSID和密码的信息表
+            blufi_env.callback(espblufi.EVENT_STA_INFO, {
+                ssid = blufi_env.sta_ssid,
+                password = blufi_env.sta_passwd
+            })
+            -- 使用之前存储的SSID和密码连接到WiFi
+            wlan.connect(blufi_env.sta_ssid, blufi_env.sta_passwd)
+            -- 创建一个DHCP服务器,适配器使用STA模式
+            dhcpsrv.create({adapter = socket.LWIP_STA})
+            -- 将WiFi连接状态更新为正在连接
+            blufi_env.wlan_state = BLUFI_WLAN_STATE_CONNING
+            -- 初始化计数器为0,用于后续等待IP地址获取的循环计数
+            local count = 0
+            -- 初始化IP地址变量为nil,用于存储获取到的IPv4地址
+            local ip = nil
+            local rdy = false
+            -- 等待1秒,防止切换连接时IP地址未清理
+            sys.wait(1000)
+            -- 最多等待30s
+            while count < 300 do
+                -- 获取STA模式下的IPv4地址
+                ip = netdrv.ipv4(socket.LWIP_STA)
+                rdy = wlan.ready()
+                -- 如果获取到有效的IP地址
+                if ip and ip ~= "0.0.0.0" then
+                    if rdy then
+                        -- 将WiFi连接状态更新为已连接
+                        blufi_env.wlan_state = BLUFI_WLAN_STATE_CONNED
+                        -- 发送STA已连接的消息
+                        sysplus.sendMsg(taskName, "STA_CONNED")
+                        -- 跳出循环
+                        break
+                    end
+                end
+                sys.wait(100)
+                count = count + 1
+            end
+            -- 如果未获取到有效IP地址
+            if not ip or ip == "0.0.0.0" then
+                -- 将WiFi连接状态更新为已断开
+                blufi_env.wlan_state = BLUFI_WLAN_STATE_DISCONN
+                -- 发送STA已断开的消息
+                sysplus.sendMsg(taskName, "STA_DISCONNED")
+                -- 断开 STA 连接
+                wlan.disconnect()
+            end
+            -- 发送WiFi连接状态报告
+            btc_blufi_wifi_conn_report()
+            -- 检查子类型是否为断开与AP连接的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP then
+            -- 断开当前的WiFi连接
+            wlan.disconnect()
+            -- 检查子类型是否为获取WiFi状态的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS then
+            -- 发送WiFi连接状态报告
+            btc_blufi_wifi_conn_report()
+            -- 检查子类型是否为使STA认证失效的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA then
+            -- 注释掉的打印语句,可用于调试
+            -- print("BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA")
+            -- 检查子类型是否为获取版本号的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION then
+            -- 构建一个数据类型的数据包,子类型为回复版本号,内容为大版本号和子版本号
+            btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION),
+                string.char(BTC_BLUFI_GREAT_VER, BTC_BLUFI_SUB_VER))
+            -- 检查子类型是否为断开BLE连接的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_DISCONNECT_BLE then
+            -- 断开当前的BLE连接
+            blufi_env.ble_device:disconnect()
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_LIST then
+            wlan.scan()
+            sys.waitUntil("WLAN_SCAN_DONE", 15000)
+            local results = wlan.scanResult()
+            if results and #results > 0 then
+                btc_blufi_send_wifi_list(results)
+            else
+                btc_blufi_send_error_info(BLUFI_WIFI_SCAN_FAIL)
+            end
+        end
+    elseif blufi_type == BLUFI_TYPE_DATA then
+        if blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_NEG then
+            local data = blufi_dh_negotiate_data_handler(blufi_hdr.data)
+            if data then
+                btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_NEG), data)
+            end
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID then
+            blufi_env.sta_bssid = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_STA_SSID then
+            blufi_env.sta_ssid = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD then
+            blufi_env.sta_passwd = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID then
+            blufi_env.softap_ssid = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD then
+            blufi_env.softap_passwd = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM then
+            blufi_env.softap_max_conn_num = blufi_hdr.data:byte(1)
+            -- 检查当前子类型是否为设置SoftAP认证模式的类型
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE then
+            -- 从接收到的数据中提取第一个字节,将其作为SoftAP的认证模式,并更新到环境变量中
+            blufi_env.softap_auth_mode = blufi_hdr.data:byte(1)
+            -- 调用回调函数,传递SoftAP信息事件类型和包含SSID和密码的信息表
+            blufi_env.callback(espblufi.EVENT_SOFTAP_INFO, {
+                ssid = blufi_env.softap_ssid,
+                password = blufi_env.softap_passwd,
+                conn_num = blufi_env.softap_max_conn_num,
+                channel = blufi_env.softap_channel
+            })
+            sysplus.sendMsg(taskName, "AP_CONNED", blufi_env.softap_ssid, blufi_env.softap_passwd, blufi_env.softap_max_conn_num, blufi_env.softap_channel)
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL then
+            blufi_env.softap_max_channel = blufi_hdr.data:byte(1)
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_USERNAME then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_USERNAME")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CA then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_CA")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CUSTOM_DATA then
+            blufi_env.callback(espblufi.EVENT_CUSTOM_DATA, blufi_hdr.data)
+        end
+    else
+        return
+    end
+end
+
+local function espblufi_task()
+    while true do
+        local result, event, data = sys.waitUntil(BLUFI_TOPIC)
+        if result then
+            if event == BLUFI_PROTOCOL_DATA then
+                btc_blufi_protocol_handler(data)
+            elseif event == BLUFI_TASK_EXIT then
+                break
+            end
+        end
+    end
+
+end
+
+local function espblufi_ble_callback(ble_device, ble_event, ble_param)
+    if ble_event == ble.EVENT_CONN then
+        blufi_env.ble_state = BLUFI_BLE_STATE_CONNED
+    elseif ble_event == ble.EVENT_DISCONN then
+        blufi_env.ble_state = BLUFI_BLE_STATE_DISCONN
+        ble_device:adv_start()
+    elseif ble_event == ble.EVENT_WRITE then
+        sys.publish(BLUFI_TOPIC, BLUFI_PROTOCOL_DATA, ble_param.data)
+    end
+end
+
+--[[
+初始化espblufi
+@api espblufi.init(bluetooth_device,espblufi_callback,local_name)
+@userdata bluetooth_device 蓝牙设备对象
+@function 事件回调函数
+@number 蓝牙名,可选,默认为"BLUFI_xxx",xxx为设备型号(因为esp的配网测试app默认过滤蓝牙名称为BLUFI_开头的设备进行显示,可手动修改)
+@usage
+espblufi.init(espblufi_callback)
+]]
+function espblufi.init(bluetooth_device, espblufi_callback, local_name)
+    if not bluetooth or not ble or not wlan then
+        log.error("need bluetooth ble and wlan")
+        return
+    end
+    if bluetooth_device == nil or type(bluetooth_device) ~= "userdata" then
+        log.error("bluetooth_device is nil")
+        return
+    end
+    if not espblufi_callback then
+        log.error("espblufi_callback is nil")
+        return
+    else
+        blufi_env.callback = espblufi_callback
+    end
+    if not local_name then
+        local_name = "BLUFI_" .. rtos.bsp()
+    end
+    wlan.init()
+    local ble_device = bluetooth_device:ble(espblufi_ble_callback)
+    ble_device:gatt_create(espblufi_att_db)
+    ble_device:adv_create({
+        adv_data = {{ble.FLAGS, string.char(0x06)}, {ble.COMPLETE_LOCAL_NAME, local_name}}
+    })
+    blufi_env.ble_device = ble_device
+    return
+end
+
+--[[
+开始配网
+@api espblufi.start()
+@usage
+espblufi.start()
+]]
+function espblufi.start()
+    sys.taskInit(espblufi_task)
+    blufi_env.ble_device:adv_start()
+end
+
+--[[
+停止配网
+@api espblufi.stop()
+@usage
+espblufi.stop()
+]]
+function espblufi.stop()
+    blufi_env.ble_device:adv_stop()
+    sys.publish(BLUFI_TOPIC, BLUFI_TASK_EXIT)
+end
+
+function espblufi.deinit()
+
+end
+
+--[[
+发送自定义数据,一般用于接收到客户端发送的自定义命令后进行回复
+@api espblufi.send_custom_data(data)
+@string 回复的数据包内容
+@usage
+espblufi.send_custom_data(data)
+]]
+function espblufi.send_custom_data(data)
+    btc_blufi_send_custom_data(data)
+end
+
+return espblufi

+ 91 - 0
module/Air8000/demo/config_wifi_network/ble_config_wifi/main.lua

@@ -0,0 +1,91 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.07.17
+@author  拓毅恒
+@usage
+演示功能概述
+1.1 蓝牙配网是什么
+蓝牙配网是一种利用蓝牙低功耗(BLE)链路,在未联网设备与手机之间建立本地安全通道,把 Wi-Fi 的 SSID、密码及其他网络参数传递给设备,使其独立完成 STA 或 SOFTAP 联网的技术方案。
+1.3 蓝牙配网原理
+设备在上电后进入配网模式,作为 BLE Peripheral 持续广播自定义的配网服务 UUID;手机 APP 作为 Central 扫描并建立 GATT 连接,随后通过加密特征值把网络参数下发给设备。设备收到参数后,启用 Wi-Fi 并执行联网流程。
+1.4 蓝牙配网流程:
+1) 广播
+设备以固定间隔广播配网服务,等待手机连接。
+2) 连接
+手机 APP 扫描 → 选择目标设备 → 建立 BLE 连接。
+3) 选择配网方式
+在 APP 界面选择:
+station 模式:设备直接作为 Station 连接路由器。
+softap 模式:设备通过 4G 开 AP 热点,用于其他设备连接。
+
+蓝牙配网就是让Air8000工作在蓝牙配网模式下,手机app通过蓝牙连接Air8000,通过app内界面实现配网功能。
+
+本示例基于合宙 Air8000 模组,演示 “STA + SoftAP 双模式 BLE 配网” 的完整流程。手机通过 BLE 下发 Wi-Fi 账号/密码或热点参数,模组自动完成 STA 连接或 SoftAP 创建,并验证网络可用性。
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "BLE_CONIFG_WIFI"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+-- 加载升级WIFI固件功能模块(Air8000系列可用)
+require "check_wifi"
+
+-- 加载 ble_config_wifi 主应用功能模块
+require "ble_config_wifi"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 137 - 0
module/Air8000/demo/config_wifi_network/ble_config_wifi/readme.md

@@ -0,0 +1,137 @@
+## 演示功能概述
+
+1.1 蓝牙配网是什么
+蓝牙配网是一种利用蓝牙低功耗(BLE)链路,在未联网设备与手机之间建立本地安全通道,把 Wi-Fi 的 SSID、密码及其他网络参数传递给设备,使其独立完成 STA 或 SOFTAP 联网的技术方案。
+
+1.3 蓝牙配网原理
+设备在上电后进入配网模式,作为 BLE Peripheral 持续广播自定义的配网服务 UUID;手机 APP 作为 Central 扫描并建立 GATT 连接,随后通过加密特征值把网络参数下发给设备。设备收到参数后,启用 Wi-Fi 并执行联网流程。
+
+1.4 蓝牙配网流程:
+> 1\. 广播
+设备以固定间隔广播配网服务,等待手机连接。
+>
+> 2\.  连接
+手机 APP 扫描 → 选择目标设备 → 建立 BLE 连接。
+>
+> 3\. 选择配网方式
+在 APP 界面选择:
+station 模式:设备直接作为 Station 连接路由器。
+softap 模式:设备通过 4G 开 AP 热点,用于其他设备连接。
+
+蓝牙配网就是让Air8000工作在蓝牙配网模式下,手机app通过蓝牙连接Air8000,通过app内界面实现配网功能。
+
+本示例基于合宙 Air8000 模组,演示 **“STA + SoftAP 双模式 BLE 配网”** 的完整流程。手机通过 BLE 下发 Wi-Fi 账号/密码或热点参数,模组自动完成 STA 连接或 SoftAP 创建,并验证网络可用性。核心流程如下:
+
+---
+
+#### 1、初始化蓝牙协议栈
+
+调用 `bluetooth.init()` 完成 BLE 协议栈加载。
+
+---
+
+#### 2、启动 BLE 配网服务
+
+- 加载 `espblufi` 模块,注册回调 `espblufi_callback`
+
+- 广播自定义名称 **BLUFI\_xxx**(xxx = 模组型号)
+
+- 手机端使用 **ESP Blufi** 官方 App 连接并配置参数
+
+---
+
+#### 3、STA 配网流程
+
+1. App 通过 BLE 发送 **SSID + Password**
+
+2. 模组收到后调用 `wlan.connect()` 连接目标路由器
+
+3. 轮询 `netdrv.ipv4()` 直至拿到有效 IP(30 s 超时)
+
+4. 成功后通过 HTTP GET `https://httpbin.air32.cn/bytes/2048` 验证外网
+
+5. 若连接失败,主动断开并上报 `STA_DISCONNED` 事件
+
+---
+
+#### 4、SoftAP 创建流程
+
+1. App 通过 BLE 发送 **AP_SSID / AP_Password / Channel / MaxConn**
+
+2. 模组调用 `wlan.createAP()` 创建 2.4 GHz 热点
+
+3. 设置静态 IP:`192.168.4.1/24`
+
+4. 启动 DHCP:`dhcpsrv.create()` 为终端分配 192.168.4.100–200
+
+5. 启用 DNS 代理:`dnsproxy.setup()`,把终端 DNS 请求转发到模组已有出口(蜂窝或 STA),实现零配置上网
+
+6. 订阅 `WLAN_AP_INC` 实时打印终端连接/断开日志
+
+---
+
+#### 5、多网融合(可选)
+
+通过 `exnetif.setproxy()` 把 **4G/STA/以太网** 设为数据出口,供热点终端共享上网。
+
+---
+
+#### 6、运行效果
+
+- **STA 配网成功**:日志打印 `STA CONNED OK!`,并输出 HTTP 200 结果
+
+- **SoftAP 创建成功**:手机搜索到指定热点,连接后即可直接访问互联网
+
+- **实时日志**:终端连接/断开、IP 分配、HTTP 测试结果全程可见
+
+---
+
+#### 7、快速上手
+
+1. 烧录固件后上电,模组自动进入 BLE 广播
+
+2. 手机安装 **ESP Blufi**(或微信搜索“ESP Config”小程序)
+
+3. 选择 **“BLUFI\_xxx”** 设备 → 连接 → 配网 → 选择 **“STA 模式”** 或 **“AP 模式”** → 填写参数 → 一键下发
+
+4. 观察串口日志即可确认结果
+
+## 演示硬件环境
+
+1、Air8000核心板/开发板一块
+
+2、配套天线一套
+
+3、TYPE-C USB数据线一根
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8000 V2010-1版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V2009-1固件对比验证)
+
+3、确保WIFI版本为V14以上版本,否则可能会导致配网失败(可以通过在日志中搜索`AIRLINK_READY`来确认当前WIFI版本),如果当前WIFI不是最新版,可以插卡后等待WIFI自动更新为最新版本。
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、在main.lua文件中选择好要使用的功能,通过Luatools将demo与固件烧录到核心板中
+
+3、烧录好后,板子开机同时在luatools上查看日志,确认WiFi是否为最新版本:
+
+4、下载手机端APP:
+
+- [安卓测试APP下载地址1](https://github.com/EspressifApp/EspBlufiForAndroid/releases)(如果打不开就用下面的)
+
+- [安卓测试APP下载地址2](https://docs.openluat.com/cdn2/apk/blufi-1.6.5-31.apk)
+
+5、确认APP 下载好后,使用APP 连接核心板的蓝牙,APP会自动搜索到**BLUFI_xxx**(xxx = 模组型号) 本篇demo 的设备名应该为 BLUFI_Air8000(app端可能存在问题,有时候搜索不到设备蓝牙,但是用手机可以搜到。**APP如果搜索不到,就重启一下模组,并且在APP端下拉刷新一下**)
+
+6、点击**BLUFI_Air8000**设备,进入配网页面。
+
+7、在配网页面中,依次点击**设备** → **连接** → **配网** → 选择 **“STA 模式”** 或 **“AP 模式”** → 填写参数 → 一键下发
+
+8、如果是STA模式,连接上配置的WIFI热点后,会进行`HTTP GET`测试。
+
+9、如果是AP模式,会根据在APP端配置的SSID、passwd、channel、maxconn来创建一个WIFI热点,供设备使用。

+ 95 - 0
module/Air8101/demo/config_wifi_network/ble_config_wifi/ble_config_wifi.lua

@@ -0,0 +1,95 @@
+--[[
+@module  ble_config_wifi
+@summary ble_config_wifi 主应用功能模块(支持STA配网 + SoftAP热点配置)
+@version 1.1
+@date    2025.08.11
+@author  拓毅恒
+@usage
+用法实例(支持双模式):
+    本模块实现了基于 BLE 的 Wi-Fi 配网功能,支持 STA 配网和 SoftAP 热点(Air8101暂不支持)配置两种模式,使用方法如下:
+    1. 模块加载:在 main.lua 中使用 `require "ble_config_wifi"` 即可加载并运行本模块。
+    2. 本demo中 手机端APP 下载地址:
+        安卓测试APP下载地址1:https://github.com/EspressifApp/EspBlufiForAndroid/releases(如果打不开就用下面的)
+        安卓测试APP下载地址2:https://docs.openluat.com/cdn2/apk/blufi-1.6.5-31.apk
+    3. STA 配网流程:
+        - 系统会初始化蓝牙设备并启动 espblufi 配网功能。
+        - 通过手机APP端与设备进行 BLE 连接,配置 STA 的 SSID 和密码(APP端打开如果没有搜到蓝牙就在APP内多次下拉刷新,并且重启一下模组)。
+        - 当收到 "STA_CONNED" 消息时,系统会发起 HTTP 请求验证网络可用性。
+        - 当收到 "STA_DISCONNED" 消息时,系统会主动断开 STA 连接并禁用多网融合代理。
+    4. 注:Air8101本身没有4G,所以暂时不支持SoftAP 功能!!!
+    5. 消息处理:
+        - 定义 `network_event_handler` 函数监听网络消息,处理 STA 连接、断开等事件。
+        - 定义 `espblufi_callback` 回调函数,处理 STA 的信息事件并打印配置信息。
+    6. 任务管理:
+        - 初始化 `ble_wifi_config_task` 任务负责蓝牙设备和 espblufi 模块的初始化及配网功能启动。
+        - 初始化 `network_event_handler` 任务持续监听网络消息,保持系统持续运行。
+本文件没有对外接口,直接在 main.lua 中 require "ble_config_wifi" 即可加载运行。
+]]
+
+-- 加载espblufi应用功能模块
+local espblufi = require("espblufi")
+local exnetif = require("exnetif")
+local taskName = "config_wifi"
+
+-- 定义网络测试函数
+local function network_event_handler()
+    while true do
+        -- 等待指定任务的消息,不设置超时时间
+        msg = sysplus.waitMsg(taskName, nil)
+        -- 检查接收到的消息是否为表类型
+        if type(msg) == 'table' then
+            log.info("MSG:", msg[1])
+            -- 检查消息的第一个元素是否为 "STA_CONNED",即 STA 连接成功消息
+            if msg[1] == "STA_CONNED" then
+                -- 打印 STA 连接成功日志
+                log.info("STA:", "STA CONNED OK!")
+                sys.wait(3000)
+                -- 发起一个 GET 请求,请求 https://httpbin.air32.cn/bytes/2048 地址,使用 STA 适配器,超时时间为 5000 毫秒
+                local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil, {adapter = socket.LWIP_STA,timeout = 5000,debug = false}).wait()
+                -- 打印 HTTP 请求执行结果,包含状态码、响应头和响应体长度
+                log.info("http执行结果", code, headers, body and #body)
+            end
+            -- 检查消息的第一个元素是否为 "STA_DISCONNED",即 STA 断开连接消息
+            if msg[1] == "STA_DISCONNED" then
+                -- 打印 STA 断开连接日志
+                log.info("STA:", "STA DISCONNED!")
+            end
+            -- 检查消息的第一个元素是否为 "AP_CONNED",即 AP 创建连接消息
+            if msg[1] == "AP_CONNED" then
+                log.info("AP:", "Air8101本身没有4G")
+                log.info("AP:", "暂不支持配置AP功能")
+                log.info("AP:", "请使用STA配网功能")
+            end
+        end
+        sys.wait(10)
+    end
+end
+
+-- 定义 espblufi 回调函数,用于处理不同类型的事件,如 EVENT_STA_INFO、EVENT_SOFTAP_INFO 等。
+-- EVENT_STA_INFO 事件:当收到 APP 下发的 STA 连接信息时触发,会打印出设备连接 WiFi STA 所输入的键值对,其中 i 为配置项名称(如 "ssid"、"passwd"),v 为对应的值。
+local function espblufi_callback(event, data)
+    -- 检查事件类型是否为 STA 信息事件
+    if event == espblufi.EVENT_STA_INFO then
+        -- 遍历 STA 信息数据
+        for i, v in pairs(data) do
+            -- 打印 STA 信息数据的键值对
+            log.info("STA:", i, v)
+        end
+    end
+end
+
+-- 定义网络配置任务函数
+local function ble_wifi_config_task()
+    -- 初始化蓝牙设备,并将初始化结果存储在 bluetooth_device 变量中
+    local bluetooth_device = bluetooth.init()
+    -- 初始化 espblufi 模块,传入蓝牙设备实例和回调函数
+    espblufi.init(bluetooth_device, espblufi_callback)
+    -- 启动 espblufi 配网功能
+    log.info("espblufi_InIt","启动espblufi配网功能")
+    espblufi.start()
+end
+
+-- 初始化网络配置任务
+sysplus.taskInitEx(ble_wifi_config_task, "ble_wifi_config_task")
+-- 初始化网络测试任务
+sysplus.taskInitEx(network_event_handler, taskName)

+ 716 - 0
module/Air8101/demo/config_wifi_network/ble_config_wifi/espblufi.lua

@@ -0,0 +1,716 @@
+--[[
+@module espblufi
+@summary espblufi esp blufi 蓝牙配网(注意:初版暂不支持加密功能!!!!!!!!)
+@version 1.1
+@date    2025.08.07
+@author  拓毅恒
+@usage
+-- 此为Blufi 配网库
+-- BluFi 配网指南:https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_networking_user_guide_cn.pdf
+
+-- 安卓测试APP下载地址:https://github.com/EspressifApp/EspBlufiForAndroid/releases
+-- 安卓APP源码下载地址:https://github.com/EspressifApp/EspBlufiForAndroid
+
+-- 小程序测试:微信搜索小程序:ESP Config
+-- 小程序源码下载地址:https://github.com/EspressifApps/ESP-Config-WeChat
+
+-- 注意:初版暂不支持加密功能!!!!!!!!
+
+-- 业务逻辑总结:
+-- 1. 此函数库需跟APP搭配使用,调用前首先需要初始化蓝牙。
+-- 2. 初始化 espblufi 模块,并传入蓝牙设备实例和回调函数。
+-- 3. 启动 espblufi 配网功能来进行APP配网。
+-- 4. 在APP端点击配网后,会根据配的是station还是softap发布相应的消息 "STA_CONNED"、"STA_DISCONNED"、"AP_CONNED",用户只需要根据消息设置自己的逻辑即可。
+-- 具体业务逻辑实现可以参考下面实例代码。
+
+-- 用法实例
+local espblufi = require("espblufi")
+local taskName = "config_wifi"
+
+local function network_event_handler()
+    while true do
+        msg = sysplus.waitMsg(taskName, nil)
+        if type(msg) == 'table' then
+            -- 检查消息的第一个元素是否为 "STA_CONNED",即 STA 连接成功消息
+            if msg[1] == "STA_CONNED" then
+                ...
+                写入自己的逻辑函数
+                ...
+            end
+            -- 检查消息的第一个元素是否为 "STA_DISCONNED",即 STA 断开连接消息
+            if msg[1] == "STA_DISCONNED" then
+                ...
+                写入自己的逻辑函数
+                ...
+            end
+            -- 检查消息的第一个元素是否为 "AP_CONNED",即 AP 创建连接消息
+            if msg[1] == "AP_CONNED" then
+                ...
+                写入自己的逻辑函数
+                ...
+            end
+        end
+        sys.wait(10)
+    end
+end
+
+-- 定义 espblufi 回调函数,用于处理不同类型的事件,如EVENT_STA_INFO、EVENT_SOFTAP_INFO等
+-- 详情可以在espblufi.lua中查看,demo中演示收到APP下发的连接消息后,打印输入的ssid和passwd
+local function espblufi_callback(event, data)
+    if event == espblufi.EVENT_STA_INFO then
+        for i, v in pairs(data) do
+            print("STA:", i, v)
+        end
+    elseif event == espblufi.EVENT_SOFTAP_INFO then
+        for i, v in pairs(data) do
+            print("SoftAP:", i, v)
+        end
+    end
+end
+
+-- 定义网络配置任务函数
+local function ble_wifi_config_task()
+    local bluetooth_device = bluetooth.init()
+    espblufi.init(bluetooth_device, espblufi_callback)
+    espblufi.start()
+end
+
+-- 初始化网络配置任务
+sysplus.taskInitEx(ble_wifi_config_task, "ble_wifi_config_task")
+-- 初始化网络测试任务
+sysplus.taskInitEx(network_event_handler, taskName)
+
+]]
+
+local espblufi = {}
+
+local sys = require "sys"
+local sysplus = require "sysplus"
+
+local BLUFI_TOPIC = "espblufi"
+local taskName = "config_wifi"
+
+local BTC_BLUFI_GREAT_VER = 0x01 -- Version + Subversion
+local BTC_BLUFI_SUB_VER = 0x03 -- Version + Subversion
+local BTC_BLUFI_VERSION = ((BTC_BLUFI_GREAT_VER << 8) | BTC_BLUFI_SUB_VER) -- Version + Subversion
+
+-- packet type
+local BLUFI_TYPE_MASK = 0x03
+local BLUFI_TYPE_SHIFT = 0
+local BLUFI_SUBTYPE_MASK = 0xFC
+local BLUFI_SUBTYPE_SHIFT = 2
+
+local function BLUFI_GET_TYPE(type)
+    return ((type) & BLUFI_TYPE_MASK)
+end
+local function BLUFI_GET_SUBTYPE(type)
+    return (((type) & BLUFI_SUBTYPE_MASK) >> BLUFI_SUBTYPE_SHIFT)
+end
+local function BLUFI_BUILD_TYPE(type, subtype)
+    return (((type) & BLUFI_TYPE_MASK) | ((subtype) << BLUFI_SUBTYPE_SHIFT))
+end
+
+local BLUFI_TYPE_CTRL = 0x0
+local BLUFI_TYPE_CTRL_SUBTYPE_ACK = 0x00
+local BLUFI_TYPE_CTRL_SUBTYPE_SET_SEC_MODE = 0x01
+local BLUFI_TYPE_CTRL_SUBTYPE_SET_WIFI_OPMODE = 0x02
+local BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP = 0x03
+local BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP = 0x04
+local BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS = 0x05
+local BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA = 0x06
+local BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION = 0x07
+local BLUFI_TYPE_CTRL_SUBTYPE_DISCONNECT_BLE = 0x08
+local BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_LIST = 0x09
+
+local BLUFI_TYPE_DATA = 0x1
+local BLUFI_TYPE_DATA_SUBTYPE_NEG = 0x00
+local BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID = 0x01
+local BLUFI_TYPE_DATA_SUBTYPE_STA_SSID = 0x02
+local BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD = 0x03
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID = 0x04
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD = 0x05
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM = 0x06
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE = 0x07
+local BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL = 0x08
+local BLUFI_TYPE_DATA_SUBTYPE_USERNAME = 0x09
+local BLUFI_TYPE_DATA_SUBTYPE_CA = 0x0a
+local BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT = 0x0b
+local BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT = 0x0c
+local BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY = 0x0d
+local BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY = 0x0e
+local BLUFI_TYPE_DATA_SUBTYPE_WIFI_REP = 0x0f
+local BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION = 0x10
+local BLUFI_TYPE_DATA_SUBTYPE_WIFI_LIST = 0x11
+local BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO = 0x12
+local BLUFI_TYPE_DATA_SUBTYPE_CUSTOM_DATA = 0x13
+local BLUFI_TYPE_DATA_SUBTYPE_STA_MAX_CONN_RETRY = 0x14
+local BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_END_REASON = 0x15
+local BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_RSSI = 0x16
+
+local function BLUFI_TYPE_IS_CTRL(type)
+    return (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_CTRL)
+end
+local function BLUFI_TYPE_IS_DATA(type)
+    return (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_DATA)
+end
+
+-- packet frame control
+local BLUFI_FC_ENC_MASK = 0x01
+local BLUFI_FC_CHECK_MASK = 0x02
+local BLUFI_FC_DIR_MASK = 0x04
+local BLUFI_FC_REQ_ACK_MASK = 0x08
+local BLUFI_FC_FRAG_MASK = 0x10
+
+local BLUFI_FC_ENC = 0x01
+local BLUFI_FC_CHECK = 0x02
+local BLUFI_FC_DIR_P2E = 0x00
+local BLUFI_FC_DIR_E2P = 0x04
+local BLUFI_FC_REQ_ACK = 0x08
+local BLUFI_FC_FRAG = 0x10
+
+local BLUFI_SEQUENCE_ERROR = 0x00
+local BLUFI_CHECKSUM_ERROR = 0x01
+local BLUFI_DECRYPT_ERROR = 0x02
+local BLUFI_ENCRYPT_ERROR = 0x03
+local BLUFI_INIT_SECURITY_ERROR = 0x04
+local BLUFI_DH_MALLOC_ERROR = 0x05
+local BLUFI_DH_PARAM_ERROR = 0x06
+local BLUFI_READ_PARAM_ERROR = 0x07
+local BLUFI_MAKE_PUBLIC_ERROR = 0x08
+local BLUFI_DATA_FORMAT_ERROR = 0x09
+local BLUFI_CALC_MD5_ERROR = 0x0a
+local BLUFI_WIFI_SCAN_FAIL = 0x0b
+local BLUFI_MSG_STATE_ERROR = 0x0c
+
+local BLUFI_OPMODE_NULL = 0x00
+local BLUFI_OPMODE_STA = 0x01
+local BLUFI_OPMODE_SOFTAP = 0x02
+local BLUFI_OPMODE_SOFTAPSTA = 0x03
+
+local BLUFI_STA_CONN_SUCCESS = 0x00
+local BLUFI_STA_CONN_FAIL = 0x01
+local BLUFI_STA_CONNECTING = 0x02
+local BLUFI_STA_NO_IP = 0x03
+
+local BLUFI_BLE_STATE_DISCONN = 0x00
+local BLUFI_BLE_STATE_CONNED = 0x01
+
+local BLUFI_WLAN_STATE_DISCONN = 0x00
+local BLUFI_WLAN_STATE_CONNING = 0x01
+local BLUFI_WLAN_STATE_CONNED = 0x02
+
+local BLUFI_SEQUENCE_ERROR = 0x00
+local BLUFI_CHECKSUM_ERROR = 0x01
+local BLUFI_DECRYPT_ERROR = 0x02
+local BLUFI_ENCRYPT_ERROR = 0x03
+local BLUFI_INIT_SECURITY_ERROR = 0x04
+local BLUFI_DH_MALLOC_ERROR = 0x05
+local BLUFI_DH_PARAM_ERROR = 0x06
+local BLUFI_READ_PARAM_ERROR = 0x07
+local BLUFI_MAKE_PUBLIC_ERROR = 0x08
+local BLUFI_DATA_FORMAT_ERROR = 0x09
+local BLUFI_CALC_MD5_ERROR = 0x0a
+local BLUFI_WIFI_SCAN_FAIL = 0x0b
+local BLUFI_MSG_STATE_ERROR = 0x0c
+
+local function BLUFI_FC_IS_ENC(fc)
+    return ((fc) & BLUFI_FC_ENC_MASK) ~= 0
+end
+local function BLUFI_FC_IS_CHECK(fc)
+    return ((fc) & BLUFI_FC_CHECK_MASK) ~= 0
+end
+local function BLUFI_FC_IS_REQ_ACK(fc)
+    return ((fc) & BLUFI_FC_REQ_ACK_MASK) ~= 0
+end
+local function BLUFI_FC_IS_FRAG(fc)
+    return ((fc) & BLUFI_FC_FRAG_MASK) ~= 0
+end
+
+local BLUFI_PROTOCOL_DATA = "BLUFI_PROTOCOL_DATA"
+local BLUFI_TASK_EXIT = "BLUFI_TASK_EXIT"
+
+local espblufi_uuid_service = "0xFFFF"
+local espblufi_uuid2device = "0xFF01"
+local espblufi_uuid2mobile = "0xFF02"
+
+local espblufi_att_db = {string.fromHex(espblufi_uuid_service), {string.fromHex(espblufi_uuid2device), ble.WRITE},
+                         {string.fromHex(espblufi_uuid2mobile), ble.NOTIFY | ble.READ}}
+
+local blufi_env = {
+    ble_device = nil,
+    callback = nil,
+    isfrag = nil,
+    ble_state = BLUFI_BLE_STATE_DISCONN,
+    wlan_state = BLUFI_WLAN_STATE_DISCONN,
+    opmode = BLUFI_OPMODE_NULL,
+    recv_seq = 0,
+    send_seq = 0,
+    sec_mode = 0,
+    softap_conn_num = 0,
+    softap_auth_mode = 0,
+    softap_max_conn_num = 0,
+    softap_channel = nil,
+    sta_max_conn_retry = nil,
+    sta_conn_end_reason = nil,
+    sta_ssid = nil,
+    sta_passwd = nil,
+    softap_ssid = nil,
+    softap_passwd = nil
+}
+
+local blufi_hdr = {
+    type = nil,
+    fc = nil,
+    seq = nil,
+    data_len = nil,
+    data = nil
+}
+
+espblufi.EVENT_STA_INFO = 0x01
+espblufi.EVENT_SOFTAP_INFO = 0x02
+espblufi.EVENT_CUSTOM_DATA = 0x03
+
+sys.subscribe("WLAN_AP_INC", function(state, mac)
+    if state == "CONNECTED" then
+        blufi_env.softap_conn_num = blufi_env.softap_conn_num + 1
+    elseif state == "DISCONNECTED" and blufi_env.softap_conn_num > 0 then
+        blufi_env.softap_conn_num = blufi_env.softap_conn_num - 1
+    end
+end)
+
+local function blufi_crc_checksum(data)
+    return crypto.crc16("IBM", data)
+end
+
+local function blufi_send_encap(blufi_hdr_send)
+    local send_data = string.char(blufi_hdr_send.type, blufi_hdr_send.fc, blufi_hdr_send.seq, #blufi_hdr_send.data) ..
+                          blufi_hdr_send.data
+    if BLUFI_TYPE_IS_CTRL(blufi_hdr_send.type) then
+        blufi_hdr_send.fc = blufi_hdr_send.fc | BLUFI_FC_CHECK
+        send_data = send_data .. blufi_crc_checksum(send_data)
+    end
+    blufi_env.send_seq = blufi_env.send_seq + 1
+    blufi_env.ble_device:write_notify({
+        uuid_service = string.fromHex(espblufi_uuid_service),
+        uuid_characteristic = string.fromHex(espblufi_uuid2mobile)
+    }, send_data)
+end
+
+local function btc_blufi_send_encap(type, data)
+    if blufi_env.ble_state == BLUFI_BLE_STATE_DISCONN then
+        return
+    end
+    local blufi_hdr_send = {
+        type = type,
+        fc = 0,
+        seq = blufi_env.send_seq,
+        data = data
+    }
+    blufi_send_encap(blufi_hdr_send)
+end
+
+local function btc_blufi_wifi_conn_report(sta_conn_state)
+    local data = string.char(blufi_env.opmode, blufi_env.wlan_state, blufi_env.softap_conn_num)
+    local wlan_info = wlan.getInfo()
+    if wlan_info then
+        if wlan_info.bssid then
+            data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID, 6) .. string.fromHex(wlan_info.bssid)
+        end
+        if wlan_info.rssi then
+            data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_RSSI, 1, wlan_info.rssi % 256)
+        end
+    end
+    if blufi_env.sta_ssid then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_SSID, #blufi_env.sta_ssid) .. blufi_env.sta_ssid
+    end
+    if blufi_env.sta_passwd then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD, #blufi_env.sta_passwd) .. blufi_env.sta_passwd
+    end
+    if blufi_env.softap_ssid then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID, #blufi_env.softap_ssid) .. blufi_env.softap_ssid
+    end
+    if blufi_env.softap_passwd then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD, #blufi_env.softap_passwd) ..
+                   blufi_env.softap_passwd
+    end
+    if blufi_env.softap_authmode then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE, 1) .. blufi_env.softap_authmode
+    end
+    if blufi_env.softap_max_conn_num then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM, 1) .. blufi_env.softap_max_conn_num
+    end
+    if blufi_env.softap_channel then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL, 1) .. blufi_env.softap_channel
+    end
+    if blufi_env.sta_max_conn_retry then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_MAX_CONN_RETRY, 1) .. blufi_env.sta_max_conn_retry
+    end
+    if blufi_env.sta_conn_end_reason then
+        data = data .. string.char(BLUFI_TYPE_DATA_SUBTYPE_STA_CONN_END_REASON, 1) .. blufi_env.sta_conn_end_reason
+    end
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_WIFI_REP), data);
+end
+
+local function btc_blufi_send_wifi_list(results)
+    local data = ""
+    for k, v in pairs(results) do
+        data = data .. string.char(#v["ssid"] + 1, v["rssi"] % 256) .. v["ssid"]
+    end
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_WIFI_LIST), data)
+end
+
+local function btc_blufi_send_ack(seq)
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_CTRL, BLUFI_TYPE_CTRL_SUBTYPE_ACK), string.char(seq))
+end
+
+local function btc_blufi_send_error_info(state)
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO), string.char(state))
+end
+
+local function btc_blufi_send_custom_data(data)
+    btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_CUSTOM_DATA), data)
+end
+
+local SEC_TYPE_DH_PARAM_LEN = 0x00
+local SEC_TYPE_DH_PARAM_DATA = 0x01
+local SEC_TYPE_DH_P = 0x02
+local SEC_TYPE_DH_G = 0x03
+local SEC_TYPE_DH_PUBLIC = 0x04
+
+local blufi_sec = {
+    dh_param_len = 0
+}
+
+local function blufi_dh_negotiate_data_handler(data)
+    if #data < 3 then
+        btc_blufi_send_error_info(BLUFI_DATA_FORMAT_ERROR)
+    end
+
+    local type = data:byte(1)
+    if type == SEC_TYPE_DH_PARAM_LEN then
+        blufi_sec.dh_param_len = ((data:byte(2) << 8) | data:byte(3));
+        -- print("dh_param_len",blufi_sec.dh_param_len)
+    elseif type == SEC_TYPE_DH_PARAM_DATA then
+        -- print("SEC_TYPE_DH_PARAM_DATA")
+        if #data < (blufi_sec.dh_param_len + 1) then
+            btc_blufi_send_error_info(BLUFI_DH_PARAM_ERROR);
+            return;
+        end
+
+        -- 秘钥待实现,功能需要mbedtls引出c接口
+        btc_blufi_send_error_info(BLUFI_INIT_SECURITY_ERROR);
+
+    elseif type == SEC_TYPE_DH_P then
+        print("SEC_TYPE_DH_P")
+    elseif type == SEC_TYPE_DH_G then
+        print("SEC_TYPE_DH_G")
+    elseif type == SEC_TYPE_DH_PUBLIC then
+        print("SEC_TYPE_DH_PUBLIC")
+    end
+
+end
+
+local function btc_blufi_protocol_handler(parse_data)
+    local target_data_len = 0
+    local parse_data_len = #parse_data
+
+    if parse_data_len < 4 then
+        return
+    end
+
+    blufi_hdr.type, blufi_hdr.fc, blufi_hdr.seq, blufi_hdr.data_len = string.unpack('<BBBB', parse_data)
+
+    if BLUFI_FC_IS_CHECK(blufi_hdr.fc) then
+        target_data_len = blufi_hdr.data_len + 4 + 2 -- // Data + (Type + Frame Control + Sequence Number + Data Length) + Checksum
+    else
+        target_data_len = blufi_hdr.data_len + 4 -- Data + (Type + Frame Control + Sequence Number + Data Length)
+    end
+    -- print("target_data_len",target_data_len,"parse_data_len",parse_data_len)
+    if target_data_len ~= parse_data_len then
+        return
+    end
+
+    if blufi_hdr.seq ~= blufi_env.recv_seq then
+        return
+    end
+    blufi_env.recv_seq = blufi_env.recv_seq + 1
+
+    if BLUFI_FC_IS_ENC(blufi_hdr.fc) then
+        -- 解密功能需要mbedtls引出c接口
+    end
+    if BLUFI_FC_IS_CHECK(blufi_hdr.fc) and crypto then
+        -- 需要app配合调试,暂时不强制校验
+        local checksum = blufi_crc_checksum()
+    end
+
+    if BLUFI_FC_IS_REQ_ACK(blufi_hdr.fc) then
+        btc_blufi_send_ack(blufi_hdr.seq)
+    end
+
+    if blufi_hdr.data_len and blufi_hdr.data_len > 0 then
+        if blufi_env.isfrag then
+            blufi_hdr.data = blufi_hdr.data .. parse_data:sub(5)
+        else
+            blufi_hdr.data = parse_data:sub(5)
+        end
+    end
+
+    if BLUFI_FC_IS_FRAG(blufi_hdr.fc) then
+        blufi_env.isfrag = true
+        return
+    else
+        blufi_env.isfrag = false
+    end
+
+    -- print(blufi_hdr.type,blufi_hdr.fc,blufi_hdr.seq,blufi_hdr.data_len)
+    -- print(blufi_hdr.data,blufi_hdr.data:toHex())
+
+    -- 从 blufi_hdr.type 中提取数据包的类型,使用 BLUFI_GET_TYPE 函数
+    local blufi_type = BLUFI_GET_TYPE(blufi_hdr.type)
+    -- 从 blufi_hdr.type 中提取数据包的子类型,使用 BLUFI_GET_SUBTYPE 函数
+    local blufi_subtype = BLUFI_GET_SUBTYPE(blufi_hdr.type)
+
+    -- 检查数据包类型是否为控制类型
+    if blufi_type == BLUFI_TYPE_CTRL then
+        -- 检查子类型是否为确认(ACK)类型
+        if blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_ACK then
+            -- 此处为空,未实现具体逻辑
+            -- 检查子类型是否为设置安全模式类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_SET_SEC_MODE then
+            -- 将接收到的数据赋值给环境变量中的安全模式字段
+            blufi_env.sec_mode = blufi_hdr.data;
+            -- 检查子类型是否为设置WiFi工作模式类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_SET_WIFI_OPMODE then
+            -- 从接收到的数据中提取第一个字节,将其作为WiFi工作模式并更新到环境变量中
+            blufi_env.opmode = blufi_hdr.data:byte(1)
+            -- 检查子类型是否为连接到AP的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP then
+            -- 调用回调函数,传递STA信息事件类型和包含SSID和密码的信息表
+            blufi_env.callback(espblufi.EVENT_STA_INFO, {
+                ssid = blufi_env.sta_ssid,
+                password = blufi_env.sta_passwd
+            })
+            -- 使用之前存储的SSID和密码连接到WiFi
+            wlan.connect(blufi_env.sta_ssid, blufi_env.sta_passwd)
+            -- 创建一个DHCP服务器,适配器使用STA模式
+            dhcpsrv.create({adapter = socket.LWIP_STA})
+            -- 将WiFi连接状态更新为正在连接
+            blufi_env.wlan_state = BLUFI_WLAN_STATE_CONNING
+            -- 初始化计数器为0,用于后续等待IP地址获取的循环计数
+            local count = 0
+            -- 初始化IP地址变量为nil,用于存储获取到的IPv4地址
+            local ip = nil
+            local rdy = false
+            -- 等待1秒,防止切换连接时IP地址未清理
+            sys.wait(1000)
+            -- 最多等待30s
+            while count < 300 do
+                -- 获取STA模式下的IPv4地址
+                ip = netdrv.ipv4(socket.LWIP_STA)
+                rdy = wlan.ready()
+                -- 如果获取到有效的IP地址
+                if ip and ip ~= "0.0.0.0" then
+                    if rdy then
+                        -- 将WiFi连接状态更新为已连接
+                        blufi_env.wlan_state = BLUFI_WLAN_STATE_CONNED
+                        -- 发送STA已连接的消息
+                        sysplus.sendMsg(taskName, "STA_CONNED")
+                        -- 跳出循环
+                        break
+                    end
+                end
+                sys.wait(100)
+                count = count + 1
+            end
+            -- 如果未获取到有效IP地址
+            if not ip or ip == "0.0.0.0" then
+                -- 将WiFi连接状态更新为已断开
+                blufi_env.wlan_state = BLUFI_WLAN_STATE_DISCONN
+                -- 发送STA已断开的消息
+                sysplus.sendMsg(taskName, "STA_DISCONNED")
+                -- 断开 STA 连接
+                wlan.disconnect()
+            end
+            -- 发送WiFi连接状态报告
+            btc_blufi_wifi_conn_report()
+            -- 检查子类型是否为断开与AP连接的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP then
+            -- 断开当前的WiFi连接
+            wlan.disconnect()
+            -- 检查子类型是否为获取WiFi状态的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS then
+            -- 发送WiFi连接状态报告
+            btc_blufi_wifi_conn_report()
+            -- 检查子类型是否为使STA认证失效的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA then
+            -- 注释掉的打印语句,可用于调试
+            -- print("BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA")
+            -- 检查子类型是否为获取版本号的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION then
+            -- 构建一个数据类型的数据包,子类型为回复版本号,内容为大版本号和子版本号
+            btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION),
+                string.char(BTC_BLUFI_GREAT_VER, BTC_BLUFI_SUB_VER))
+            -- 检查子类型是否为断开BLE连接的类型
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_DISCONNECT_BLE then
+            -- 断开当前的BLE连接
+            blufi_env.ble_device:disconnect()
+        elseif blufi_subtype == BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_LIST then
+            wlan.scan()
+            sys.waitUntil("WLAN_SCAN_DONE", 15000)
+            local results = wlan.scanResult()
+            if results and #results > 0 then
+                btc_blufi_send_wifi_list(results)
+            else
+                btc_blufi_send_error_info(BLUFI_WIFI_SCAN_FAIL)
+            end
+        end
+    elseif blufi_type == BLUFI_TYPE_DATA then
+        if blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_NEG then
+            local data = blufi_dh_negotiate_data_handler(blufi_hdr.data)
+            if data then
+                btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_NEG), data)
+            end
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID then
+            blufi_env.sta_bssid = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_STA_SSID then
+            blufi_env.sta_ssid = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD then
+            blufi_env.sta_passwd = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID then
+            blufi_env.softap_ssid = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD then
+            blufi_env.softap_passwd = blufi_hdr.data
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM then
+            blufi_env.softap_max_conn_num = blufi_hdr.data:byte(1)
+            -- 检查当前子类型是否为设置SoftAP认证模式的类型
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE then
+            -- 从接收到的数据中提取第一个字节,将其作为SoftAP的认证模式,并更新到环境变量中
+            blufi_env.softap_auth_mode = blufi_hdr.data:byte(1)
+            -- 调用回调函数,传递SoftAP信息事件类型和包含SSID和密码的信息表
+            blufi_env.callback(espblufi.EVENT_SOFTAP_INFO, {
+                ssid = blufi_env.softap_ssid,
+                password = blufi_env.softap_passwd
+            })
+            sysplus.sendMsg(taskName, "AP_CONNED")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL then
+            blufi_env.softap_max_channel = blufi_hdr.data:byte(1)
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_USERNAME then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_USERNAME")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CA then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_CA")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY then
+            -- print("BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY")
+        elseif blufi_subtype == BLUFI_TYPE_DATA_SUBTYPE_CUSTOM_DATA then
+            blufi_env.callback(espblufi.EVENT_CUSTOM_DATA, blufi_hdr.data)
+        end
+    else
+        return
+    end
+end
+
+local function espblufi_task()
+    while true do
+        local result, event, data = sys.waitUntil(BLUFI_TOPIC)
+        if result then
+            if event == BLUFI_PROTOCOL_DATA then
+                btc_blufi_protocol_handler(data)
+            elseif event == BLUFI_TASK_EXIT then
+                break
+            end
+        end
+    end
+
+end
+
+local function espblufi_ble_callback(ble_device, ble_event, ble_param)
+    if ble_event == ble.EVENT_CONN then
+        blufi_env.ble_state = BLUFI_BLE_STATE_CONNED
+    elseif ble_event == ble.EVENT_DISCONN then
+        blufi_env.ble_state = BLUFI_BLE_STATE_DISCONN
+        ble_device:adv_start()
+    elseif ble_event == ble.EVENT_WRITE then
+        sys.publish(BLUFI_TOPIC, BLUFI_PROTOCOL_DATA, ble_param.data)
+    end
+end
+
+--[[
+初始化espblufi
+@api espblufi.init(bluetooth_device,espblufi_callback,local_name)
+@userdata bluetooth_device 蓝牙设备对象
+@function 事件回调函数
+@number 蓝牙名,可选,默认为"BLUFI_xxx",xxx为设备型号(因为esp的配网测试app默认过滤蓝牙名称为BLUFI_开头的设备进行显示,可手动修改)
+@usage
+espblufi.init(espblufi_callback)
+]]
+function espblufi.init(bluetooth_device, espblufi_callback, local_name)
+    if not bluetooth or not ble or not wlan then
+        log.error("need bluetooth ble and wlan")
+        return
+    end
+    if bluetooth_device == nil or type(bluetooth_device) ~= "userdata" then
+        log.error("bluetooth_device is nil")
+        return
+    end
+    if not espblufi_callback then
+        log.error("espblufi_callback is nil")
+        return
+    else
+        blufi_env.callback = espblufi_callback
+    end
+    if not local_name then
+        local_name = "BLUFI_" .. rtos.bsp()
+    end
+    wlan.init()
+    local ble_device = bluetooth_device:ble(espblufi_ble_callback)
+    ble_device:gatt_create(espblufi_att_db)
+    ble_device:adv_create({
+        adv_data = {{ble.FLAGS, string.char(0x06)}, {ble.COMPLETE_LOCAL_NAME, local_name}}
+    })
+    blufi_env.ble_device = ble_device
+    return
+end
+
+--[[
+开始配网
+@api espblufi.start()
+@usage
+espblufi.start()
+]]
+function espblufi.start()
+    sys.taskInit(espblufi_task)
+    blufi_env.ble_device:adv_start()
+end
+
+--[[
+停止配网
+@api espblufi.stop()
+@usage
+espblufi.stop()
+]]
+function espblufi.stop()
+    blufi_env.ble_device:adv_stop()
+    sys.publish(BLUFI_TOPIC, BLUFI_TASK_EXIT)
+end
+
+function espblufi.deinit()
+
+end
+
+--[[
+发送自定义数据,一般用于接收到客户端发送的自定义命令后进行回复
+@api espblufi.send_custom_data(data)
+@string 回复的数据包内容
+@usage
+espblufi.send_custom_data(data)
+]]
+function espblufi.send_custom_data(data)
+    btc_blufi_send_custom_data(data)
+end
+
+return espblufi

+ 97 - 0
module/Air8101/demo/config_wifi_network/ble_config_wifi/main.lua

@@ -0,0 +1,97 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.07.17
+@author  拓毅恒
+@usage
+演示功能概述
+1.1 蓝牙配网是什么
+蓝牙配网是一种利用蓝牙低功耗(BLE)链路,在未联网设备与手机之间建立本地安全通道,把 Wi-Fi 的 SSID、密码及其他网络参数传递给设备,使其独立完成 STA 或 SOFTAP 联网的技术方案。
+1.3 蓝牙配网原理
+设备在上电后进入配网模式,作为 BLE Peripheral 持续广播自定义的配网服务 UUID;手机 APP 作为 Central 扫描并建立 GATT 连接,随后通过加密特征值把网络参数下发给设备。设备收到参数后,启用 Wi-Fi 并执行联网流程。
+1.4 蓝牙配网流程:
+1) 广播
+设备以固定间隔广播配网服务,等待手机连接。
+2) 连接
+手机 APP 扫描 → 选择目标设备 → 建立 BLE 连接。
+3) 选择配网方式
+在 APP 界面选择:
+station 模式:设备直接作为 Station 连接路由器。
+softap 模式:设备通过 4G 开 AP 热点,用于其他设备连接(由于Air8101本身内部没有4G,所以暂时不支持配置AP功能)。
+
+蓝牙配网就是让Air8101工作在蓝牙配网模式下,手机app通过蓝牙连接Air8101,通过app内界面实现配网功能。
+
+本示例基于合宙 Air8101 模组,演示 “STA + SoftAP 双模式 BLE 配网” 的完整流程。手机通过 BLE 下发 Wi-Fi 账号/密码或热点参数,模组自动完成 STA 连接或 SoftAP 创建,并验证网络可用性。
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "BLE_CONIFG_WIFI"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+-- 加载espblufi应用功能模块
+local espblufi = require("espblufi")
+
+-- 加载dnsproxy应用功能模块
+dnsproxy = require("dnsproxy")
+
+-- 加载dhcp应用功能模块
+dhcpsrv = require("dhcpsrv")
+
+-- 加载 ble_config_wifi 主应用功能模块
+require "ble_config_wifi"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 135 - 0
module/Air8101/demo/config_wifi_network/ble_config_wifi/readme.md

@@ -0,0 +1,135 @@
+## 演示功能概述
+
+1.1 蓝牙配网是什么
+蓝牙配网是一种利用蓝牙低功耗(BLE)链路,在未联网设备与手机之间建立本地安全通道,把 Wi-Fi 的 SSID、密码及其他网络参数传递给设备,使其独立完成 STA 或 SOFTAP 联网的技术方案。
+
+1.3 蓝牙配网原理
+设备在上电后进入配网模式,作为 BLE Peripheral 持续广播自定义的配网服务 UUID;手机 APP 作为 Central 扫描并建立 GATT 连接,随后通过加密特征值把网络参数下发给设备。设备收到参数后,启用 Wi-Fi 并执行联网流程。
+
+1.4 蓝牙配网流程:
+> 1\. 广播
+设备以固定间隔广播配网服务,等待手机连接。
+>
+> 2\.  连接
+手机 APP 扫描 → 选择目标设备 → 建立 BLE 连接。
+>
+> 3\. 选择配网方式
+在 APP 界面选择:
+station 模式:设备直接作为 Station 连接路由器。
+softap 模式:设备通过 4G 开 AP 热点,用于其他设备连接(由于Air8101本身内部没有4G,所以暂时不支持配置AP功能)。
+
+蓝牙配网就是让Air8101工作在蓝牙配网模式下,手机app通过蓝牙连接Air8101,通过app内界面实现配网功能。
+
+本示例基于合宙 Air8101 模组,演示 **“STA + SoftAP 双模式 BLE 配网”** 的完整流程。手机通过 BLE 下发 Wi-Fi 账号/密码或热点参数,模组自动完成 STA 连接或 SoftAP 创建,并验证网络可用性。核心流程如下:
+
+---
+
+#### 1、初始化蓝牙协议栈
+
+调用 `bluetooth.init()` 完成 BLE 协议栈加载。
+
+---
+
+#### 2、启动 BLE 配网服务
+
+- 加载 `espblufi` 模块,注册回调 `espblufi_callback`
+
+- 广播自定义名称 **BLUFI\_xxx**(xxx = 模组型号)
+
+- 手机端使用 **ESP Blufi** 官方 App 连接并配置参数
+
+---
+
+#### 3、STA 配网流程
+
+1. App 通过 BLE 发送 **SSID + Password**
+
+2. 模组收到后调用 `wlan.connect()` 连接目标路由器
+
+3. 轮询 `netdrv.ipv4()` 直至拿到有效 IP(30 s 超时)
+
+4. 成功后通过 HTTP GET `https://httpbin.air32.cn/bytes/2048` 验证外网
+
+5. 若连接失败,主动断开并上报 `STA_DISCONNED` 事件
+
+---
+
+#### 4、SoftAP 创建流程(Air8101暂不支持)
+
+1. App 通过 BLE 发送 **AP_SSID / AP_Password / Channel / MaxConn**
+
+2. 模组调用 `wlan.createAP()` 创建 2.4 GHz 热点
+
+3. 设置静态 IP:`192.168.4.1/24`
+
+4. 启动 DHCP:`dhcpsrv.create()` 为终端分配 192.168.4.100–200
+
+5. 启用 DNS 代理:`dnsproxy.setup()`,把终端 DNS 请求转发到模组已有出口(蜂窝或 STA),实现零配置上网
+
+6. 订阅 `WLAN_AP_INC` 实时打印终端连接/断开日志
+
+---
+
+#### 5、多网融合(可选)
+
+通过 `exnetif.setproxy()` 把 **4G/STA/以太网** 设为数据出口,供热点终端共享上网。
+
+---
+
+#### 6、运行效果
+
+- **STA 配网成功**:日志打印 `STA CONNED OK!`,并输出 HTTP 200 结果
+
+- **SoftAP 创建成功**:手机搜索到指定热点,连接后即可直接访问互联网
+
+- **实时日志**:终端连接/断开、IP 分配、HTTP 测试结果全程可见
+
+---
+
+#### 7、快速上手
+
+1. 烧录固件后上电,模组自动进入 BLE 广播
+
+2. 手机安装 **ESP Blufi**(或微信搜索“ESP Config”小程序)
+
+3. 选择 **“BLUFI\_xxx”** 设备 → 连接 → 配网 → 选择 **“STA 模式”** 或 **“AP 模式”(Air8101暂不支持)** → 填写参数 → 一键下发
+
+4. 观察串口日志即可确认结果
+
+## 演示硬件环境
+
+1、Air8101核心板/开发板一块
+
+2、配套天线一套
+
+3、TYPE-C USB数据线一根
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8101 V1005版本固件](https://docs.openluat.com/air8101/luatos/firmware/)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V1005固件对比验证)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、在main.lua文件中选择好要使用的功能,通过Luatools将demo与固件烧录到核心板中
+
+3、烧录好后,板子开机同时在luatools上查看日志,确认WiFi是否为最新版本:
+
+4、下载手机端APP:
+
+- [安卓测试APP下载地址1](https://github.com/EspressifApp/EspBlufiForAndroid/releases)(如果打不开就用下面的)
+
+- [安卓测试APP下载地址2](https://docs.openluat.com/cdn2/apk/blufi-1.6.5-31.apk)
+
+5、确认APP 下载好后,使用APP 连接核心板的蓝牙,APP会自动搜索到**BLUFI_xxx**(xxx = 模组型号) 本篇demo 的设备名应该为 BLUFI_Air8101(app端可能存在问题,有时候搜索不到设备蓝牙,但是用手机可以搜到。**如果搜索不到,就重启一下模组,并且在APP端下拉刷新一下**)
+
+6、点击**BLUFI_Air8101**设备,进入配网页面。
+
+7、在配网页面中,依次点击**设备** → **连接** → **配网** → 选择 **“STA 模式”** 或 **“AP 模式”(Air8101暂不支持)** → 填写参数 → 一键下发
+
+8、如果是STA模式,连接上配置的WIFI热点后,会进行`HTTP GET`测试。
+
+9、(Air8101暂不支持)如果是AP模式,会根据在APP端配置的SSID、passwd、channel、maxconn来创建一个WIFI热点,供设备使用。