Преглед на файлове

Merge branch 'master' of https://gitee.com/openLuat/LuatOS

alienwalker преди 8 месеца
родител
ревизия
ac4ccb7800
променени са 39 файла, в които са добавени 1408 реда и са изтрити 246 реда
  1. 80 16
      components/airlink/src/exec/luat_airlink_bt_exec_task.c
  2. 156 12
      components/airlink/src/exec/luat_airlink_cmd_exec_bluetooth.c
  3. 0 0
      components/bluetooth/drv/luat_drv_ble_gatt.c
  4. 129 18
      components/bluetooth/drv/luat_drv_ble_port.c
  5. 1 0
      components/bluetooth/drv/luat_drv_bt_port.c
  6. 24 11
      components/bluetooth/include/luat_ble.h
  7. 9 1
      components/bluetooth/include/luat_drv_ble.h
  8. 258 91
      components/bluetooth/src/luat_lib_ble.c
  9. 2 2
      components/bluetooth/src/luat_lib_bluetooth.c
  10. 5 0
      components/ethernet/common/dhcp_client.c
  11. 4 0
      components/network/libhttp/luat_http_client.c
  12. 4 0
      components/wlan/luat_lib_wlan.c
  13. 79 0
      luat/demo/airlink/air8000_ble/master/main.lua
  14. 1 1
      luat/demo/airlink/air8000_ble/peripheral/main.lua
  15. 7 0
      luat/demo/airlink/air8000_ble/scan/main.lua
  16. 40 16
      luat/demo/bluetooth/ble/main.lua
  17. 3 3
      luat/include/luat_debug.h
  18. 0 2
      module/Air780EGH/demo/u8g2/main.lua
  19. BIN
      module/Air780EHM/core/LuatOS-SoC_V2007_Air780EHM.soc
  20. 1 2
      module/Air780EHM/demo/camera/spi_cam/main.lua
  21. 40 0
      module/Air780EHM/demo/fastlz/readme.md
  22. 0 4
      module/Air780EHM/demo/onewire/onewire_single_18b20/main.lua
  23. 0 2
      module/Air780EHM/demo/u8g2/main.lua
  24. 72 0
      module/Air780EHM/demo/ymodem/main.lua
  25. 0 2
      module/Air780EHV/DEMO/u8g2/main.lua
  26. BIN
      module/Air780EPM/core/LuatOS-SoC_V2007_Air780EPM.soc
  27. 0 2
      module/Air780EPM/demo/camera/spi_cam/main.lua
  28. 1 6
      module/Air780EPM/demo/onewire/onewire_single_18b20/main.lua
  29. 0 2
      module/Air780EPM/demo/u8g2/main.lua
  30. 1 2
      module/Air8000/core/固件功能列表.md
  31. 32 7
      module/Air8000/demo/ble/peripheral/main.lua
  32. 36 0
      module/Air8000/demo/ble/peripheral/readme.md
  33. 94 0
      module/Air8000/demo/ble/scan/main.lua
  34. 36 0
      module/Air8000/demo/ble/scan/readme.md
  35. 1 6
      module/Air8000/demo/onewire/main.lua
  36. 1 4
      module/Air8000/project/School_To_Home/code/test.lua
  37. 77 33
      module/Air8000/project/整机开发板出厂工程/user/airble.lua
  38. 1 1
      module/Air8000/project/整机开发板出厂工程/user/main.lua
  39. 213 0
      script/libs/fota_wifi.lua

+ 80 - 16
components/airlink/src/exec/luat_airlink_bt_exec_task.c

@@ -10,9 +10,9 @@
 #include "luat_airlink.h"
 
 #if defined(LUAT_USE_AIRLINK_EXEC_BLUETOOTH) || defined(LUAT_USE_AIRLINK_EXEC_BLUETOOTH_RESP)
-#include "luat_drv_ble.h"
 #include "luat_bluetooth.h"
 #include "luat_ble.h"
+#include "luat_drv_ble.h"
 #endif
 
 #include "luat_mem.h"
@@ -29,7 +29,9 @@ static luat_rtos_task_handle g_task_handle;
 
 static void drv_ble_cb(luat_ble_t* luat_ble, luat_ble_event_t event, luat_ble_param_t* param) {
     // 注意, 这里要代理不同的事件, 转发到airlink
-    LLOGD("drv_ble event %d", event);
+    if (event != LUAT_BLE_EVENT_SCAN_REPORT) {
+        LLOGD("drv_ble event %d", event);
+    }
     uint64_t seq = luat_airlink_get_next_cmd_id();
     airlink_queue_item_t item = {
         .len =  sizeof(luat_airlink_cmd_t) + sizeof(luat_drv_ble_msg_t) + 1024
@@ -45,18 +47,45 @@ static void drv_ble_cb(luat_ble_t* luat_ble, luat_ble_event_t event, luat_ble_pa
     msg->cmd_id = LUAT_DRV_BT_CMD_BLE_EVENT_CB;
 
     uint32_t tmp = event;
+    size_t offset = 0;
+    size_t len = 0;
+    luat_ble_gatt_service_t* gatt = NULL;
     memcpy(ptr, &tmp, 4);
     if (param) {
-        LLOGD("param %p", param);
+        // LLOGD("param %p", param);
+        offset = 4 + sizeof(luat_ble_param_t);
         if (LUAT_BLE_EVENT_SCAN_REPORT == event && param->adv_req.data && param->adv_req.data_len > 0) {
-            memcpy(ptr + 4 + sizeof(luat_ble_param_t), param->adv_req.data, param->adv_req.data_len);
+            memcpy(ptr + offset, param->adv_req.data, param->adv_req.data_len);
         }
         else if (LUAT_BLE_EVENT_READ == event) {
             // 请求读, 这个事件仅能通知lua, .value的数据是要被写入的, 不是被读
         }
+        else if (LUAT_BLE_EVENT_READ_VALUE == event && param->read_req.value && param->read_req.value_len > 0) {
+            LLOGD("read resp value %d %p", param->read_req.value_len, param->read_req.value);
+            memcpy(ptr + offset, param->read_req.value, param->read_req.value_len);
+        }
         else if (LUAT_BLE_EVENT_WRITE == event && param->write_req.value_len && param->write_req.value_len > 0) {
             LLOGD("write req value %d %p", param->write_req.value_len, param->write_req.value);
-            memcpy(ptr + 4 + sizeof(luat_ble_param_t), param->write_req.value, param->write_req.value_len);
+            memcpy(ptr + offset, param->write_req.value, param->write_req.value_len);
+        }
+        else if (LUAT_BLE_EVENT_GATT_DONE == event) {
+            // TODO 这个操作就比较复杂了
+            // 需要将gatt的内容全部拷贝到ptr中
+            // LLOGI("gatt done, gatt len %d, pack now", param->gatt_done_ind.gatt_service_num);
+            for (size_t i = 0; i < param->gatt_done_ind.gatt_service_num; i++)
+            {
+                gatt = param->gatt_done_ind.gatt_service[i];
+                // LLOGD("gatt service %02X%02X", gatt->uuid[0], gatt->uuid[1]);
+                len = 0;
+                luat_ble_gatt_pack(gatt, ptr + offset, &len);
+                if (len == 0) {
+                    LLOGE("gatt pack failed, gatt %p len=0!!!", gatt);
+                    break;
+                }
+                // LLOGD("gatt service pack %d/%d", len, offset);
+                offset += len;
+            }
+            // return;
         }
         memcpy(ptr + 4, param, sizeof(luat_ble_param_t));
     }
@@ -67,11 +96,16 @@ static void drv_ble_cb(luat_ble_t* luat_ble, luat_ble_event_t event, luat_ble_pa
 
 static int drv_gatt_create(luat_drv_ble_msg_t *msg) {
     // 从数据中解析出参数, 重新组装
+    int ret = 0;
+    size_t rlen = 0;
     luat_ble_gatt_service_t* gatt = luat_heap_malloc(sizeof(luat_ble_gatt_service_t));
     if (gatt == NULL) {
         LLOGE("out of memory when malloc gatt");
         return -1;
     }
+    #if 1
+    luat_ble_gatt_unpack(gatt, msg->data, &rlen);
+    #else
     size_t offset = 0;
     uint16_t sizeof_gatt = 0;
     uint16_t sizeof_gatt_chara = 0;
@@ -106,8 +140,9 @@ static int drv_gatt_create(luat_drv_ble_msg_t *msg) {
         }
         offset += gatt->characteristics[i].descriptors_num * sizeof(luat_ble_gatt_descriptor_t);
     }
-
-    return luat_ble_create_gatt(NULL, gatt);
+    #endif
+    ret = luat_ble_create_gatt(NULL, gatt);
+    return ret;
 }
 
 static int drv_adv_create(luat_drv_ble_msg_t *msg) {
@@ -195,15 +230,32 @@ static int drv_ble_write_value(luat_drv_ble_msg_t *msg) {
     
 }
 
-// static int drv_ble_send_read_resp(luat_drv_ble_msg_t *msg) {
-//     // 从数据中解析出参数, 重新组装
-//     luat_ble_rw_req_t write = {0};
-//     uint16_t sizeof_write = 0;
-//     memcpy(&sizeof_write, msg->data, 2);
-//     memcpy(&write, msg->data + 2, sizeof(luat_ble_rw_req_t));
-//     LLOGD("ble send read resp len %d", write.len);
-//     return luat_ble_read_response_value(NULL, write.handle, msg->data + 2 + sizeof_write, write.len);
-// }
+static int drv_ble_connect(luat_drv_ble_msg_t *msg) {
+    // 从数据中解析出参数, 重新组装
+    luat_ble_connect_req_t conn = {0};
+    uint16_t sizeof_conn = 0;
+    memcpy(&sizeof_conn, msg->data, 2);
+    memcpy(&conn, msg->data + 2, sizeof(luat_ble_connect_req_t));
+    return luat_ble_connect(NULL, &conn);
+}
+
+static int drv_ble_read_value(luat_drv_ble_msg_t *msg) {
+    // 从数据中解析出参数, 重新组装
+    luat_ble_rw_req_t write = {0};
+    uint16_t sizeof_write = 0;
+    memcpy(&sizeof_write, msg->data, 2);
+    memcpy(&write, msg->data + 2, sizeof(luat_ble_rw_req_t));
+    LLOGD("ble read len %d", write.len);
+    uint8_t* value = NULL;
+    int ret = 0;
+    if (write.descriptor.uuid_type) {
+        ret = luat_ble_read_value(&write.service, &write.characteristic, &write.descriptor, &value, write.len);
+    }
+    else {
+        ret = luat_ble_read_value(&write.service, &write.characteristic, NULL, &value, write.len);
+    }
+    return ret;
+}
 
 static void drv_bt_task(void *param) {
     luat_drv_ble_msg_t *msg = NULL;
@@ -303,6 +355,18 @@ static void drv_bt_task(void *param) {
             //     ret = drv_ble_send_read_resp(msg);
             //     LLOGD("ble send read resp %d", ret);
             //     break;
+            case LUAT_DRV_BT_CMD_BLE_CONNECT:
+                ret = drv_ble_connect(msg);
+                LLOGD("ble connect %d", ret);
+                break;
+            case LUAT_DRV_BT_CMD_BLE_DISCONNECT:
+                ret = luat_ble_disconnect(NULL);
+                LLOGD("ble disconnect %d", ret);
+                break;
+            case LUAT_DRV_BT_CMD_BLE_READ_VALUE:
+                ret = drv_ble_read_value(msg);
+                LLOGD("ble read value %d", ret);
+                break;
             default:
                 LLOGD("unknow bt cmd %d", msg->cmd_id);
                 break;

+ 156 - 12
components/airlink/src/exec/luat_airlink_cmd_exec_bluetooth.c

@@ -9,6 +9,9 @@
 #define LUAT_LOG_TAG "airlink.bt"
 #include "luat_log.h"
 
+#undef LLOGD
+#define LLOGD(...)
+
 #if defined(LUAT_USE_AIRLINK_EXEC_BLUETOOTH) || defined(LUAT_USE_AIRLINK_EXEC_BLUETOOTH_RESP)
 #include "luat_airlink.h"
 #include "luat_bluetooth.h"
@@ -48,6 +51,7 @@ int luat_airlink_cmd_exec_bt_request(luat_airlink_cmd_t *cmd, void *userdata)
 
 
 extern luat_ble_cb_t g_drv_ble_cb;
+static luat_ble_gatt_service_t* s_gatts[16];
 
 int luat_airlink_cmd_exec_bt_resp_cb(luat_airlink_cmd_t *cmd, void *userdata) {
     if (cmd->len < 10) {
@@ -56,32 +60,172 @@ int luat_airlink_cmd_exec_bt_resp_cb(luat_airlink_cmd_t *cmd, void *userdata) {
     if (g_drv_ble_cb == NULL) {
         return -101;
     }
+    uint32_t tmp = 0;
+    size_t offset = 4 + sizeof(luat_ble_param_t);
+    size_t tmplen = 0;
+    luat_ble_gatt_service_t* gatt;
+    luat_ble_gatt_chara_t* gatt_chara;
+    luat_ble_gatt_descriptor_t* gatt_chara_desc;
     luat_drv_ble_msg_t* msg = (luat_drv_ble_msg_t *)(cmd->data);
     if (msg->cmd_id == LUAT_DRV_BT_CMD_BLE_EVENT_CB) {
-        uint32_t tmp = 0;
         memcpy(&tmp, msg->data, 4);
         luat_ble_event_t event = (luat_ble_event_t)tmp;
-        luat_ble_param_t param = {0};
-        memcpy(&param, msg->data + 4, sizeof(luat_ble_param_t));
-        // LLOGD("收到bt event %d %d", event, cmd->len - sizeof(luat_drv_ble_msg_t));
+        luat_ble_param_t* param = (luat_ble_param_t*)(msg->data + 4);
+        // memcpy(param, msg->data + 4, sizeof(luat_ble_param_t));
+        LLOGD("收到bt event %d %d", event, cmd->len - sizeof(luat_drv_ble_msg_t));
+        // param->write_req.value = NULL;
+        // param->adv_req.data = NULL;
+        // param->read_req.value = NULL;
 
         // 把能处理的先尝试处理一下
-        if (event == LUAT_BLE_EVENT_WRITE && param.write_req.value_len) {
-            param.write_req.value = luat_heap_malloc(param.write_req.value_len);
-            memcpy(param.write_req.value, msg->data + 4 + sizeof(luat_ble_param_t), param.write_req.value_len);
+        if (event == LUAT_BLE_EVENT_WRITE && param->write_req.value_len) {
+            param->write_req.value = (uint8_t*)(msg->data + offset);
         }
         // else if (event == LUAT_BLE_EVENT_READ && param.read_req.len) {
         //     param.read_req.value = luat_heap_malloc(param.read_req.len);
-        //     memcpy(param.read_req.value, msg->data + 4 + sizeof(luat_ble_param_t), param.read_req.len);
+        //     memcpy(param.read_req.value, msg->data + offset, param.read_req.len);
         // }
-        else if (event == LUAT_BLE_EVENT_SCAN_REPORT && param.adv_req.data_len) {
-            param.adv_req.data = luat_heap_malloc(param.adv_req.data_len);
-            memcpy(param.adv_req.data, msg->data + 4 + sizeof(luat_ble_param_t), param.adv_req.data_len);
+        else if (event == LUAT_BLE_EVENT_SCAN_REPORT && param->adv_req.data_len) {
+            param->adv_req.data = (uint8_t*)(msg->data + offset);
+        }
+        else if (event == LUAT_BLE_EVENT_READ_VALUE && param->read_req.value_len) {
+            param->read_req.value = (uint8_t*)(msg->data + offset);
         }
-        g_drv_ble_cb(NULL, event, &param);
+        else if (event == LUAT_BLE_EVENT_GATT_DONE) {
+            LLOGI("gatt done, gatt len %d, unpack now", param->gatt_done_ind.gatt_service_num);
+            
+            param->gatt_done_ind.gatt_service = s_gatts;
+            for (size_t i = 0; i < param->gatt_done_ind.gatt_service_num; i++)
+            {
+                if (param->gatt_done_ind.gatt_service[i] == NULL) {
+                    param->gatt_done_ind.gatt_service[i] = luat_heap_malloc(sizeof(luat_ble_gatt_service_t));
+                }
+                else  {
+                    gatt = param->gatt_done_ind.gatt_service[i];
+                    for (size_t ch_i = 0; ch_i < gatt->characteristics_num; ch_i++)
+                    {
+                        if (gatt->characteristics[ch_i].descriptor != NULL) {
+                            luat_heap_free(gatt->characteristics[ch_i].descriptor);
+                            gatt->characteristics[ch_i].descriptor = NULL;
+                        } 
+                    }
+                    luat_heap_free(gatt->characteristics);
+                    gatt->characteristics = NULL;
+                    gatt->characteristics_num = 0;
+                }
+                // TODO 这个函数还是会有内存泄露的情况
+                luat_ble_gatt_unpack(param->gatt_done_ind.gatt_service[i], msg->data + offset, &tmplen);
+                // LLOGI("gatt service %d unpacked, len %d", i, tmplen);
+                if (tmplen == 0) {
+                    break;
+                }
+                offset += tmplen;
+            }
+            // 暂时还是不返回解码完成再说
+            // return 0;
+        }
+        g_drv_ble_cb(NULL, event, param);
         return 0;
     }
     return 0;
 }
 
 #endif
+
+#if defined(LUAT_USE_AIRLINK_EXEC_BLUETOOTH) || defined(LUAT_USE_AIRLINK_EXEC_BLUETOOTH_RESP)
+// 辅助函数
+
+
+int luat_ble_gatt_pack(luat_ble_gatt_service_t* gatt, uint8_t* ptr, size_t* _len) {
+    uint16_t descriptor_totalNum = 0;
+    for (size_t i = 0; i < gatt->characteristics_num; i++) {
+        descriptor_totalNum += gatt->characteristics[i].descriptors_num;
+        // LLOGD("统计GATT描述符数量 %d/%d", gatt->characteristics[i].descriptors_num, descriptor_totalNum);
+    }
+    uint16_t tmp = 0;
+    uint16_t offset = 0;
+    uint8_t descriptor_num;
+
+    tmp = sizeof(luat_ble_gatt_service_t);
+    memcpy(ptr, &tmp, 2);
+    // 然后是luat_ble_gatt_chara_t的大小
+    tmp = sizeof(luat_ble_gatt_chara_t);
+    memcpy(ptr + 2, &tmp, 2);
+    // 然后是服务id的数量
+    tmp = gatt->characteristics_num;
+    memcpy(ptr + 2 + 2, &tmp, 2);
+    // 然后是luat_ble_gatt_descriptor_t的大小
+    tmp = sizeof(luat_ble_gatt_descriptor_t);
+    memcpy(ptr + 2 + 2 + 2, &tmp, 2);
+
+    // 头部拷贝完成, 拷贝数据
+    memcpy(ptr + 8, gatt, sizeof(luat_ble_gatt_service_t));
+    // 然后是服务id
+    memcpy(ptr + 8 + sizeof(luat_ble_gatt_service_t), 
+        gatt->characteristics, gatt->characteristics_num * sizeof(luat_ble_gatt_chara_t));
+    
+    offset = 8 + sizeof(luat_ble_gatt_service_t) + gatt->characteristics_num * sizeof(luat_ble_gatt_chara_t);
+    for (size_t i = 0; i < gatt->characteristics_num; i++)
+    {
+        descriptor_num = gatt->characteristics[i].descriptors_num;
+        if (descriptor_num == 0) {
+            continue;
+        }
+        // 然后是描述符id
+        memcpy(ptr + offset, gatt->characteristics[i].descriptor, descriptor_num * sizeof(luat_ble_gatt_descriptor_t));
+        offset += descriptor_num * sizeof(luat_ble_gatt_descriptor_t);
+    }
+    if (_len) {
+        // 如果有传入len, 则返回实际长度
+        *_len = 8
+                + sizeof(luat_ble_gatt_service_t) 
+                + gatt->characteristics_num * sizeof(luat_ble_gatt_chara_t)
+                + descriptor_totalNum * sizeof(luat_ble_gatt_descriptor_t);
+    }
+    return 0;
+}
+
+int luat_ble_gatt_unpack(luat_ble_gatt_service_t* gatt, uint8_t* data, size_t* len) {
+    size_t offset = 0;
+    uint16_t sizeof_gatt = 0;
+    uint16_t sizeof_gatt_chara = 0;
+    uint16_t num_of_gatt_srv = 0;
+    uint16_t sizeof_gatt_desc = 0;
+    memcpy(&sizeof_gatt, data, 2);
+    memcpy(&sizeof_gatt_chara, data + 2, 2);
+    memcpy(&num_of_gatt_srv, data + 4, 2);
+    memcpy(&sizeof_gatt_desc, data + 6, 2);
+    // LLOGD("sizeof(luat_ble_gatt_service_t) = %d act %d", sizeof(luat_ble_gatt_service_t), sizeof_gatt);
+    // LLOGD("sizeof(luat_ble_gatt_chara_t) = %d act %d", sizeof(luat_ble_gatt_chara_t), sizeof_gatt_chara);
+    offset = 8;
+    
+    memcpy(gatt, data + offset, sizeof(luat_ble_gatt_service_t));
+    offset += sizeof(luat_ble_gatt_service_t);
+    // LLOGD("Gatt uuid_type %d uuid %02X%02X", gatt->uuid_type, gatt->uuid[0], gatt->uuid[1]);
+
+    // LLOGD("Gatt characteristics_num %d", gatt->characteristics_num);
+    gatt->characteristics = luat_heap_malloc(sizeof(luat_ble_gatt_chara_t) * gatt->characteristics_num);
+    for (size_t i = 0; i < gatt->characteristics_num; i++)
+    {
+        memcpy(&gatt->characteristics[i], data + 8 + sizeof(luat_ble_gatt_service_t) + sizeof_gatt_chara * i, sizeof(luat_ble_gatt_chara_t));
+        if (gatt->characteristics[i].descriptors_num) {
+            // LLOGD("gatt->characteristics[%d].descriptors_num %d", i, gatt->characteristics[i].descriptors_num);
+            gatt->characteristics[i].descriptor = luat_heap_malloc(gatt->characteristics[i].descriptors_num * sizeof(luat_ble_gatt_descriptor_t));
+        }
+        offset += sizeof(luat_ble_gatt_chara_t);
+    }
+    for (size_t i = 0; i < gatt->characteristics_num; i++)
+    {
+        if (gatt->characteristics[i].descriptors_num) {
+            memcpy(gatt->characteristics[i].descriptor, data + offset, gatt->characteristics[i].descriptors_num * sizeof(luat_ble_gatt_descriptor_t));
+        }
+        offset += gatt->characteristics[i].descriptors_num * sizeof(luat_ble_gatt_descriptor_t);
+    }
+    if (len) {
+        *len = offset;
+    }
+    return 0;
+}
+
+#endif
+

+ 0 - 0
module/Air8000/demo/ble/scan → components/bluetooth/drv/luat_drv_ble_gatt.c


+ 129 - 18
components/bluetooth/drv/luat_drv_ble_port.c

@@ -11,10 +11,10 @@ uint32_t reserved; // 保留字段, 目前都是0
 // 然后是命令自身的数据
 */
 #include "luat_base.h"
-#include "luat_drv_ble.h"
 #include "luat_airlink.h"
 #include "luat_bluetooth.h"
 #include "luat_ble.h"
+#include "luat_drv_ble.h"
 
 #define LUAT_LOG_TAG "drv.ble"
 #include "luat_log.h"
@@ -24,6 +24,16 @@ luat_ble_cb_t g_drv_ble_cb;
 #undef LLOGD
 #define LLOGD(...)
 
+// 读取wifi固件版本, 控制API适配状态
+extern luat_airlink_dev_info_t g_airlink_ext_dev_info;
+static uint32_t get_ble_version(void) {
+    uint32_t version = 0;
+    if (g_airlink_ext_dev_info.tp == 1) {
+        memcpy(&version, g_airlink_ext_dev_info.wifi.version, 4);
+    }
+    return version;
+}
+
 int luat_ble_init(void* args, luat_ble_cb_t luat_ble_cb) {
     LLOGD("执行luat_ble_init %p", luat_ble_cb);
     g_drv_ble_cb = luat_ble_cb;
@@ -228,13 +238,16 @@ int luat_ble_delete_advertising(void* args) {
 
 // gatt
 int luat_ble_create_gatt(void* args, luat_ble_gatt_service_t* gatt) {
-    LLOGD("执行luat_ble_create_gatt");
+    LLOGD("执行luat_ble_create_gatt %d", gatt->characteristics_num);
     uint16_t tmp = 0;
     uint64_t seq = luat_airlink_get_next_cmd_id();
     int ret = 0;
 
     uint16_t descriptor_totalNum = 0;
-    for (size_t i = 0; i < gatt->characteristics_num; i++) { descriptor_totalNum += gatt->characteristics[i].descriptors_num; }
+    for (size_t i = 0; i < gatt->characteristics_num; i++) {
+        descriptor_totalNum += gatt->characteristics[i].descriptors_num;
+        // LLOGD("统计GATT描述符数量 %d/%d", gatt->characteristics[i].descriptors_num, descriptor_totalNum);
+    }
 
     airlink_queue_item_t item = {
         .len = sizeof(luat_airlink_cmd_t) 
@@ -249,37 +262,48 @@ int luat_ble_create_gatt(void* args, luat_ble_gatt_service_t* gatt) {
         goto cleanup;
     }
     
+    // 数据部分
+    uint8_t ptr[1024] = {0};
+
     luat_drv_ble_msg_t msg = { .id = seq};
     msg.cmd_id = LUAT_DRV_BT_CMD_BLE_GATT_CREATE;
-    memcpy(cmd->data, &msg, sizeof(luat_drv_ble_msg_t));
+    memcpy(ptr, &msg, sizeof(luat_drv_ble_msg_t));
     
-    // 数据部分
-    // 首先是luat_ble_gatt_service_t结构的大小
+    // LLOGD("ptr %p cmd %p cmd->data %p", ptr, cmd + 1, cmd->data);
+    #if 1
+    luat_ble_gatt_pack(gatt, ptr + sizeof(luat_drv_ble_msg_t), NULL);
+    // luat_airlink_hexdump("GATT_A1", cmd->data, cmd->len);
+    #else
     tmp = sizeof(luat_ble_gatt_service_t);
-    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t), &tmp, 2);
+    memcpy(ptr + sizeof(luat_drv_ble_msg_t), &tmp, 2);
     // 然后是luat_ble_gatt_chara_t的大小
     tmp = sizeof(luat_ble_gatt_chara_t);
-    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 2, &tmp, 2);
+    memcpy(ptr + sizeof(luat_drv_ble_msg_t) + 2, &tmp, 2);
     // 然后是服务id的数量
     tmp = gatt->characteristics_num;
-    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 2 + 2, &tmp, 2);
+    memcpy(ptr + sizeof(luat_drv_ble_msg_t) + 2 + 2, &tmp, 2);
     // 然后是luat_ble_gatt_descriptor_t的大小
     tmp = sizeof(luat_ble_gatt_descriptor_t);
-    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 2 + 2 + 2, &tmp, 2);
+    memcpy(ptr + sizeof(luat_drv_ble_msg_t) + 2 + 2 + 2, &tmp, 2);
 
     // 头部拷贝完成, 拷贝数据
-    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 8, gatt, sizeof(luat_ble_gatt_service_t));
+    memcpy(ptr + sizeof(luat_drv_ble_msg_t) + 8, gatt, sizeof(luat_ble_gatt_service_t));
     // 然后是服务id
-    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 8 + sizeof(luat_ble_gatt_service_t), 
+    memcpy(ptr + sizeof(luat_drv_ble_msg_t) + 8 + sizeof(luat_ble_gatt_service_t), 
         gatt->characteristics, gatt->characteristics_num * sizeof(luat_ble_gatt_chara_t));
     
     for (size_t i = 0; i < gatt->characteristics_num; i++)
     {
         uint8_t descriptor_num = gatt->characteristics[i].descriptors_num;
         // 然后是描述符id
-        memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 8 + sizeof(luat_ble_gatt_service_t) + gatt->characteristics_num * sizeof(luat_ble_gatt_chara_t) + i * sizeof(luat_ble_gatt_descriptor_t), 
+        memcpy(ptr + sizeof(luat_drv_ble_msg_t) + 8 + sizeof(luat_ble_gatt_service_t) + gatt->characteristics_num * sizeof(luat_ble_gatt_chara_t) + i * sizeof(luat_ble_gatt_descriptor_t), 
         gatt->characteristics[i].descriptor, descriptor_num * sizeof(luat_ble_gatt_descriptor_t));
     }
+    #endif
+    memcpy(cmd->data, ptr, cmd->len);
+    // LLOGD("----> %s %s", __DATE__, __TIME__);
+
+    // luat_airlink_hexdump("GATT_A2", cmd->data, cmd->len);
 
     item.cmd = cmd;
 
@@ -508,18 +532,105 @@ int luat_ble_delete_scanning(void* args) {
 }
 
 
-int luat_ble_connect(void* args, uint8_t* adv_addr,uint8_t adv_addr_type) {
-    LLOGE("not support yet");
+int luat_ble_connect(void* args, luat_ble_connect_req_t *conn) {
+    LLOGD("执行luat_ble_connect");
+    if (get_ble_version() < 11) {
+        LLOGE("luat_ble_connect not support, ble version is %d", get_ble_version());
+        return -1;
+    }
+    uint64_t seq = luat_airlink_get_next_cmd_id();
+    airlink_queue_item_t item = {
+        .len = 8 + sizeof(luat_airlink_cmd_t) + 8 + sizeof(luat_ble_connect_req_t) + 2
+    };
+    luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x500, item.len - sizeof(luat_airlink_cmd_t));
+    if (cmd == NULL) {
+        return -101;
+    }
+    luat_drv_ble_msg_t msg = { .id = seq};
+    uint16_t tmp = sizeof(luat_ble_connect_req_t);
+    msg.cmd_id = LUAT_DRV_BT_CMD_BLE_CONNECT;
+    memcpy(cmd->data, &msg, sizeof(luat_drv_ble_msg_t));
+    // 然后是结构体大小
+    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t), &tmp, 2);
+    // 然后是连接请求数据
+    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t) + 2, conn, sizeof(luat_ble_connect_req_t));
+
+    item.cmd = cmd;
+    luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
     return -1;
 }
 
 int luat_ble_disconnect(void* args) {
-    LLOGE("not support yet");
-    return -1;
+    LLOGD("执行luat_ble_disconnect");
+    if (get_ble_version() < 11) {
+        LLOGE("luat_ble_connect not support, ble version is %d", get_ble_version());
+        return -1;
+    }
+    uint64_t seq = luat_airlink_get_next_cmd_id();
+    airlink_queue_item_t item = {
+        .len = 8 + sizeof(luat_airlink_cmd_t) + sizeof(luat_drv_ble_msg_t)
+    };
+    luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x500, item.len - sizeof(luat_airlink_cmd_t));
+    if (cmd == NULL) {
+        return -101;
+    }
+    luat_drv_ble_msg_t msg = { .id = seq};
+    msg.cmd_id = LUAT_DRV_BT_CMD_BLE_DISCONNECT;
+    memcpy(cmd->data, &msg, sizeof(luat_drv_ble_msg_t));
+
+    item.cmd = cmd;
+    luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
+    return 0;
 }
 
 int luat_ble_read_value(luat_ble_uuid_t* uuid_service, luat_ble_uuid_t* uuid_characteristic, luat_ble_uuid_t* uuid_descriptor, uint8_t **data, uint16_t* len) {
-    LLOGE("not support yet -> luat_ble_read_value");
+    LLOGD("执行luat_ble_read_value");
+    uint16_t tmp = 0;
+    uint64_t seq = luat_airlink_get_next_cmd_id();
+    airlink_queue_item_t item = {
+        .len = sizeof(luat_airlink_cmd_t) 
+               + sizeof(luat_drv_ble_msg_t) + sizeof(luat_ble_rw_req_t) 
+               + sizeof(uint16_t)
+               + 16
+    };
+    // 暂时全是0
+    *data = NULL;
+    *len = 0;
+
+    luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x500, item.len - sizeof(luat_airlink_cmd_t));
+    if (cmd == NULL) {
+        return -101;
+    }
+    
+    luat_drv_ble_msg_t msg = { .id = seq};
+    msg.cmd_id = LUAT_DRV_BT_CMD_BLE_READ_VALUE;
+    memcpy(cmd->data, &msg, sizeof(luat_drv_ble_msg_t));
+    luat_ble_rw_req_t req = {
+        .len = len
+    };
+    if (uuid_service) {
+        memcpy(&req.service, uuid_service, sizeof(luat_ble_uuid_t));
+    }
+    if (uuid_characteristic) {
+        memcpy(&req.characteristic, uuid_characteristic, sizeof(luat_ble_uuid_t));
+    }
+    if (uuid_descriptor) {
+        memcpy(&req.descriptor, uuid_descriptor, sizeof(luat_ble_uuid_t));
+    }
+    tmp = sizeof(luat_ble_rw_req_t);
+    memcpy(cmd->data + sizeof(luat_drv_ble_msg_t), &tmp, 2);
+    memcpy(cmd->data + 2 + sizeof(luat_drv_ble_msg_t), &req, sizeof(luat_ble_rw_req_t));
+    // memcpy(cmd->data + 2 + sizeof(luat_drv_ble_msg_t) + sizeof(luat_ble_rw_req_t), data, len);
+
+    item.cmd = cmd;
+    luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
+    LLOGI("luat_ble_read_value 执行完成");
+    return 0;
+}
+
+int luat_ble_notify_enable(luat_ble_uuid_t* uuid_service, luat_ble_uuid_t* uuid_characteristic, uint8_t enable) {
+    LLOGE("not support yet -> luat_ble_notify_enable");
     return -1;
 }
 
+

+ 1 - 0
components/bluetooth/drv/luat_drv_bt_port.c

@@ -5,6 +5,7 @@
 2. 一律打包luat_drv_ble_msg_t
 */
 #include "luat_base.h"
+#include "luat_ble.h"
 #include "luat_drv_ble.h"
 #include "luat_airlink.h"
 

+ 24 - 11
components/bluetooth/include/luat_ble.h

@@ -122,9 +122,10 @@ typedef enum{
     LUAT_BLE_EVENT_DISCONN,     // BLE断开连接
     LUAT_BLE_EVENT_GATT_DONE,     // BLE GATT
 
-    // WRITE
-    LUAT_BLE_EVENT_WRITE,       // BLE写数据
-    LUAT_BLE_EVENT_READ,        // BLE读数据
+    // WRITE/READ
+    LUAT_BLE_EVENT_WRITE,         // BLE从模式下,主设备写操作事件
+    LUAT_BLE_EVENT_READ,         // BLE从模式下,主设备读操作事件
+    LUAT_BLE_EVENT_READ_VALUE,      // BLE读value数据回调
 
     LUAT_BLE_EVENT_MAX,
 
@@ -179,8 +180,8 @@ typedef struct {
 typedef enum{
     LUAT_BLE_ADDR_MODE_PUBLIC,   // 控制器的公共地址
     LUAT_BLE_ADDR_MODE_RANDOM,   // 生成的静态地址
-    LUAT_BLE_ADDR_MODE_RPA,      // 可解析的私有地址
-    LUAT_BLE_ADDR_MODE_NRPA,     // 不可解析的私有地址
+    LUAT_BLE_ADDR_MODE_RPA,    // 可解析的私有地址
+    LUAT_BLE_ADDR_MODE_NRPA,    // 不可解析的私有地址
 }luat_ble_addr_mode_t;
 
 typedef enum{
@@ -233,8 +234,11 @@ typedef struct{
 
 typedef struct{
     uint16_t handle;       /**< The index of the attribute */
+    luat_ble_uuid_t uuid_service;
+    luat_ble_uuid_t uuid_characteristic;
+    luat_ble_uuid_t uuid_descriptor;
     uint8_t *value;         /**< The attribute value */
-    uint16_t len;           /**< The data length read */
+    uint16_t value_len;           /**< The data length read */
     uint16_t size;          /**< The size of attribute value to read */
 } luat_ble_read_req_t;
 
@@ -278,7 +282,7 @@ typedef struct{
     union {
         luat_ble_device_info_t luat_ble_device_info;
         luat_ble_write_req_t write_req;
-        // luat_ble_read_req_t read_req;
+        luat_ble_read_req_t read_req;
         luat_ble_adv_req_t adv_req;
         luat_ble_conn_ind_t conn_ind;
         luat_ble_disconn_ind_t disconn_ind;
@@ -341,7 +345,7 @@ int luat_ble_read_value(luat_ble_uuid_t* uuid_service, luat_ble_uuid_t* uuid_cha
 
 
 // master
-int luat_ble_notify_enable(luat_ble_uuid_t* uuid_service, luat_ble_uuid_t* uuid_characteristic, luat_ble_uuid_t* uuid_descriptor, uint8_t enable);
+int luat_ble_notify_enable(luat_ble_uuid_t* uuid_service, luat_ble_uuid_t* uuid_characteristic, uint8_t enable);
 
 // scanning
 int luat_ble_create_scanning(void* args, luat_ble_scan_cfg_t* scan_cfg);
@@ -352,9 +356,6 @@ int luat_ble_stop_scanning(void* args);
 
 int luat_ble_delete_scanning(void* args);
 
-int luat_ble_connect(void* args, uint8_t* adv_addr,uint8_t adv_addr_type);
-
-int luat_ble_disconnect(void* args);
 
 typedef struct luat_ble_rw_req{
     uint32_t len;
@@ -364,4 +365,16 @@ typedef struct luat_ble_rw_req{
     uint8_t data[0];
 }luat_ble_rw_req_t;
 
+typedef struct luat_ble_conn_req {
+    uint8_t adv_addr_type;  /**< Advertising address type: public/random */
+    uint8_t adv_addr[6];    /**< Advertising address value */
+    uint16_t conn_interval;  /**< Connection interval */
+    uint16_t conn_latency;   /**< Connection latency */
+    uint16_t sup_to;         /**< Link supervision timeout */
+}luat_ble_connect_req_t;
+
+int luat_ble_connect(void* args, luat_ble_connect_req_t *conn);
+
+int luat_ble_disconnect(void* args);
+
 #endif

+ 9 - 1
components/bluetooth/include/luat_drv_ble.h

@@ -34,7 +34,10 @@ enum {
     LUAT_DRV_BT_CMD_BLE_WRITE_INDICATION, // 写入,带订阅的
     LUAT_DRV_BT_CMD_BLE_SEND_READ_RESP, // 发送读响应
     LUAT_DRV_BT_CMD_BLE_WRITE_VALUE, // 写入值
-
+    LUAT_DRV_BT_CMD_BLE_READ_REQ, // 请求读取值
+    LUAT_DRV_BT_CMD_BLE_CONNECT, // 读取值
+    LUAT_DRV_BT_CMD_BLE_DISCONNECT, // 断开连接
+    LUAT_DRV_BT_CMD_BLE_READ_VALUE, // 读取值, 异步的
     LUAT_DRV_BT_CMD_BLE_EVENT_CB = 128, // 事件回调
 
     LUAT_DRV_BT_CMD_MAX
@@ -43,4 +46,9 @@ enum {
 int luat_drv_bt_task_start(void);
 int luat_drv_bt_msg_send(luat_drv_ble_msg_t *msg);
 
+
+int luat_ble_gatt_pack(luat_ble_gatt_service_t *gatt, uint8_t *data, size_t *len);
+
+int luat_ble_gatt_unpack(luat_ble_gatt_service_t *gatt, uint8_t *data, size_t *len);
+
 #endif

+ 258 - 91
components/bluetooth/src/luat_lib_ble.c

@@ -99,46 +99,35 @@ int l_ble_callback(lua_State *L, void *ptr)
         lua_pushlstring(L, (const char *)write_req->value, write_req->value_len);
         lua_settable(L, -3);
         lua_call(L, 3, 0);
-        if (write_req->value){
-            luat_heap_free(write_req->value);
-            write_req->value = NULL;
-        }
         break;
     }
     case LUAT_BLE_EVENT_READ:
-    {
-        // luat_ble_read_req_t *read_req = &(param->read_req);
-        // lua_createtable(L, 0, 5);
-        // lua_pushliteral(L, "handle");
-        // lua_pushinteger(L, read_req->handle);
-        // lua_settable(L, -3);
-
-        // luat_ble_uuid_t uuid_service = {0};
-        // luat_ble_uuid_t uuid_characteristic = {0};
-        // luat_ble_uuid_t uuid_descriptor = {0};
-        // luat_ble_handle2uuid(read_req->handle, &uuid_service, &uuid_characteristic, &uuid_descriptor);
-        // // LLOGD("service:0x%02X %d characteristic:0x%02X %d descriptor:0x%02X %d",
-        // //     uuid_service.uuid[0]<<8|uuid_service.uuid[1],uuid_service.uuid_type,
-        // //     uuid_characteristic.uuid[0]<<8|uuid_characteristic.uuid[1],uuid_characteristic.uuid_type,
-        // //     uuid_descriptor.uuid[0]<<8|uuid_descriptor.uuid[1],uuid_descriptor.uuid_type);
-        // lua_pushliteral(L, "uuid_service");
-        // lua_pushlstring(L, (const char *)uuid_service.uuid, uuid_service.uuid_type);
-        // lua_settable(L, -3);
-        // lua_pushliteral(L, "uuid_characteristic");
-        // lua_pushlstring(L, (const char *)uuid_characteristic.uuid, uuid_characteristic.uuid_type);
-        // lua_settable(L, -3);
-        // if (uuid_descriptor.uuid[0] != 0 || uuid_descriptor.uuid[1] != 0){
-        //     lua_pushliteral(L, "uuid_descriptor");
-        //     lua_pushlstring(L, (const char *)uuid_descriptor.uuid, uuid_descriptor.uuid_type);
-        //     lua_settable(L, -3);
-        // }
-
-        // lua_call(L, 3, 0);
+    case LUAT_BLE_EVENT_READ_VALUE:{
+        luat_ble_read_req_t *read_req = &(param->read_req);
+        lua_createtable(L, 0, 5);
+        lua_pushliteral(L, "handle");
+        lua_pushinteger(L, read_req->handle);
+        lua_settable(L, -3);
 
+        lua_pushliteral(L, "uuid_service");
+        lua_pushlstring(L, (const char *)read_req->uuid_service.uuid, read_req->uuid_service.uuid_type);
+        lua_settable(L, -3);
+        lua_pushliteral(L, "uuid_characteristic");
+        lua_pushlstring(L, (const char *)read_req->uuid_characteristic.uuid, read_req->uuid_characteristic.uuid_type);
+        lua_settable(L, -3);
+        if (read_req->uuid_descriptor.uuid[0] != 0 || read_req->uuid_descriptor.uuid[1] != 0){
+            lua_pushliteral(L, "uuid_descriptor");
+            lua_pushlstring(L, (const char *)read_req->uuid_descriptor.uuid, read_req->uuid_descriptor.uuid_type);
+            lua_settable(L, -3);
+        }
+        if (evt == LUAT_BLE_EVENT_READ_VALUE){
+            lua_pushliteral(L, "data");
+            lua_pushlstring(L, (const char *)read_req->value, read_req->value_len);
+            lua_settable(L, -3);
+        }
+        lua_call(L, 3, 0);
         break;
-    }
-    case LUAT_BLE_EVENT_SCAN_REPORT:
-    {
+    }case LUAT_BLE_EVENT_SCAN_REPORT:{
         luat_ble_adv_req_t *adv_req = &(param->adv_req);
         lua_createtable(L, 0, 4);
 
@@ -158,13 +147,8 @@ int l_ble_callback(lua_State *L, void *ptr)
         // uint8_t evt_type;     /**< Event type (see enum \ref adv_report_info and see enum \ref adv_report_type)*/
 
         lua_call(L, 3, 0);
-        if (adv_req->data){
-            luat_heap_free(adv_req->data);
-            adv_req->data = NULL;
-        }
         break;
-    }
-    case LUAT_BLE_EVENT_GATT_DONE:{
+    }case LUAT_BLE_EVENT_GATT_DONE:{
         luat_ble_gatt_service_t **gatt_services = param->gatt_done_ind.gatt_service;
         uint8_t gatt_service_num = param->gatt_done_ind.gatt_service_num;
         lua_createtable(L, gatt_service_num, 0);
@@ -179,7 +163,7 @@ int l_ble_callback(lua_State *L, void *ptr)
             for (size_t m = 0; m < characteristics_num; m++){
                 luat_ble_gatt_chara_t *gatt_chara = &gatt_service->characteristics[m];
                 lua_newtable(L);
-                lua_pushlstring(L, (const char *)gatt_chara->uuid, gatt_service->uuid_type);
+                lua_pushlstring(L, (const char *)gatt_chara->uuid, gatt_chara->uuid_type);
                 lua_seti(L, -2, 1);
                 // Properties
                 lua_pushnumber(L, gatt_chara->perm);
@@ -191,9 +175,7 @@ int l_ble_callback(lua_State *L, void *ptr)
         }
         lua_call(L, 3, 0);
         break;
-    }
-    case LUAT_BLE_EVENT_CONN:
-    {
+    }case LUAT_BLE_EVENT_CONN:{
         luat_ble_conn_ind_t *conn = &(param->conn_ind);
         lua_newtable(L);
         memcpy(tmpbuff, conn->peer_addr, 6);
@@ -204,9 +186,7 @@ int l_ble_callback(lua_State *L, void *ptr)
         lua_setfield(L, -2, "addr_type");
         lua_call(L, 3, 0);
         break;
-    }
-    case LUAT_BLE_EVENT_DISCONN:
-    {
+    }case LUAT_BLE_EVENT_DISCONN:{
         luat_ble_disconn_ind_t *disconn = &(param->disconn_ind);
         lua_newtable(L);
         lua_pushinteger(L, disconn->reason);
@@ -221,32 +201,43 @@ int l_ble_callback(lua_State *L, void *ptr)
 exit:
     if (param)
     {
+        if (LUAT_BLE_EVENT_WRITE == evt && param->write_req.value)
+        {
+            // LLOGD("free write_req.value %p", param->write_req.value);
+            luat_heap_free(param->write_req.value);
+            param->write_req.value = NULL;
+        }
+        else if (LUAT_BLE_EVENT_SCAN_REPORT == evt && param->adv_req.data)
+        {
+            // LLOGD("free adv_req.data %p", param->adv_req.data);
+            luat_heap_free(param->adv_req.data);
+            param->adv_req.data = NULL;
+        }
+        else if (LUAT_BLE_EVENT_READ_VALUE == evt && param->read_req.value){
+            // LLOGD("free read_req.value %p", param->read_req.value);
+            luat_heap_free(param->read_req.value);
+            param->read_req.value = NULL;
+        }
         luat_heap_free(param);
         param = NULL;
     }
     return 0;
 }
 
-void luat_ble_cb(luat_ble_t *args, luat_ble_event_t ble_event, luat_ble_param_t *ble_param)
-{
+void luat_ble_cb(luat_ble_t *args, luat_ble_event_t ble_event, luat_ble_param_t *ble_param){
     // LLOGD("ble event: %d param: %p", ble_event, ble_param);
     luat_ble_param_t *luat_ble_param = NULL;
-    if (ble_param)
-    {
+    if (ble_param){
         // LLOGD("ble param: %p", ble_param);
         luat_ble_param = luat_heap_malloc(sizeof(luat_ble_param_t));
         memcpy(luat_ble_param, ble_param, sizeof(luat_ble_param_t));
-        if (ble_event == LUAT_BLE_EVENT_WRITE && ble_param->write_req.value_len)
-        {
+        if (ble_event == LUAT_BLE_EVENT_WRITE && ble_param->write_req.value_len){
             luat_ble_param->write_req.value = luat_heap_malloc(ble_param->write_req.value_len);
             memcpy(luat_ble_param->write_req.value, ble_param->write_req.value, ble_param->write_req.value_len);
-        }
-        // else if (ble_event == LUAT_BLE_EVENT_READ && ble_param->read_req.value_len)
-        // {
-        //     LLOGD("ble read read_req value: %p", ble_param->read_req.value);
-        // }
-        else if (ble_event == LUAT_BLE_EVENT_SCAN_REPORT && ble_param->adv_req.data_len)
-        {
+        }else if (ble_event == LUAT_BLE_EVENT_READ_VALUE && ble_param->read_req.value_len){
+            luat_ble_param->read_req.value = luat_heap_malloc(ble_param->read_req.value_len);
+            memcpy(luat_ble_param->read_req.value, ble_param->read_req.value, ble_param->read_req.value_len);
+        }else if (ble_event == LUAT_BLE_EVENT_SCAN_REPORT && ble_param->adv_req.data_len){
             luat_ble_param->adv_req.data = luat_heap_malloc(ble_param->adv_req.data_len);
             memcpy(luat_ble_param->adv_req.data, ble_param->adv_req.data, ble_param->adv_req.data_len);
         }
@@ -272,11 +263,8 @@ local att_db = { -- Service
     -- Characteristic
     { -- Characteristic 1
         string.fromHex("EA01"), -- Characteristic UUID Value, 特征的UUID值, 可以是16位、32位或128位
-        ble.NOTIFY | ble.READ | ble.WRITE -- Properties, 对应蓝牙特征的属性, 可以是以下值的组合:
-        -- ble.READ: 可读
-        -- ble.WRITE: 可写
-        -- ble.NOTIFY: 可通知
-        -- ble.INDICATE: 可指示
+        ble.NOTIFY | ble.READ | ble.WRITE -- Properties, 对应蓝牙特征的属性, 参考权限常量
+        string.fromHex("1234"), -- 默认value
     }
 }
 ble_device:gatt_create(att_db)
@@ -445,7 +433,7 @@ ble_device:adv_create({
     channel_map = ble.CHNLS_ALL, -- 广播的通道, 可选值: ble.CHNLS_37, ble.CHNLS_38, ble.CHNLS_39, ble.CHNLS_ALL
     intv_min = 120, -- 广播间隔最小值, 单位为0.625ms, 最小值为20, 最大值为10240
     intv_max = 120, -- 广播间隔最大值, 单位为0.625ms, 最小值为20, 最大值为10240
-    adv_data = {
+    adv_data = { -- 支持表格形式, 也支持字符串形式(255字节以内)
         {ble.FLAGS, string.char(0x06)},
         {ble.COMPLETE_LOCAL_NAME, "LuatOS123"}, -- 广播的设备名
         {ble.SERVICE_DATA, string.fromHex("FE01")}, -- 广播的服务数据
@@ -538,6 +526,20 @@ static int l_ble_advertising_create(lua_State *L){
             lua_pop(L, 1);
         }
     }
+    else if (lua_isstring(L, -1)){
+        // 字符串形式
+        const char *data = luaL_checklstring(L, -1, &len);
+        if (len > 255) {
+            LLOGE("adv_data too long, max length is 255");
+            goto end;
+        }
+        memcpy(adv_data, data, len);
+        adv_index = len;
+    }
+    else {
+        LLOGE("error adv_data type");
+        goto end;
+    }
     lua_pop(L, 1);
 
     if (!local_name_set_flag){
@@ -602,7 +604,6 @@ static int l_ble_advertising_stop(lua_State *L){
 ble_device:write_notify({
     uuid_service = "FA00", -- 服务的UUID, 可以是16位、32位或128位
     uuid_characteristic = "EA01", -- 特征的UUID值, 可以是16位、32位或128位
-    uuid_descriptor = "2902" -- 可选, 描述符的UUID值, 可以是16位、32位或128位
 }, "Hello BLE") -- 要写入的值
 */
 static int l_ble_write_notify(lua_State *L){
@@ -623,7 +624,7 @@ static int l_ble_write_notify(lua_State *L){
             service_uuid = luaL_checklstring(L, -1, &tmp);
             service.uuid_type = tmp;
             memcpy(service.uuid, service_uuid, service.uuid_type);
-            LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
+            // LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_service 参数");
@@ -636,7 +637,7 @@ static int l_ble_write_notify(lua_State *L){
             characteristic_uuid = luaL_checklstring(L, -1, &tmp);
             characteristic.uuid_type = tmp;
             memcpy(characteristic.uuid, characteristic_uuid, characteristic.uuid_type);
-            LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
+            // LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_characteristic 参数");
@@ -649,7 +650,7 @@ static int l_ble_write_notify(lua_State *L){
             descriptor_uuid = luaL_checklstring(L, -1, &tmp);
             descriptor.uuid_type = tmp;
             memcpy(descriptor.uuid, descriptor_uuid, descriptor.uuid_type);
-            LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
+            // LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
             ret = luat_ble_write_notify_value(&service, &characteristic, &descriptor, (uint8_t *)value, len);
         }else{
             ret = luat_ble_write_notify_value(&service, &characteristic, NULL, (uint8_t *)value, len);
@@ -676,7 +677,6 @@ end_error:
 ble_device:write_indicate({
     uuid_service = "FA00", -- 服务的UUID, 可以是16位、32位或128位
     uuid_characteristic = "EA01", -- 特征的UUID值, 可以是16位、32位或128位
-    uuid_descriptor = "2902" -- 可选, 描述符的UUID值, 可以是16位、32位或128位
 }, "Hello BLE") -- 要写入的值
 */
 static int l_ble_write_indicate(lua_State *L){
@@ -697,7 +697,7 @@ static int l_ble_write_indicate(lua_State *L){
             service_uuid = luaL_checklstring(L, -1, &tmp);
             service.uuid_type = tmp;
             memcpy(service.uuid, service_uuid, service.uuid_type);
-            LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
+            // LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_service 参数");
@@ -710,7 +710,7 @@ static int l_ble_write_indicate(lua_State *L){
             characteristic_uuid = luaL_checklstring(L, -1, &tmp);
             characteristic.uuid_type = tmp;
             memcpy(characteristic.uuid, characteristic_uuid, characteristic.uuid_type);
-            LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
+            // LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_characteristic 参数");
@@ -723,7 +723,7 @@ static int l_ble_write_indicate(lua_State *L){
             descriptor_uuid = luaL_checklstring(L, -1, &tmp);
             descriptor.uuid_type = tmp;
             memcpy(descriptor.uuid, descriptor_uuid, descriptor.uuid_type);
-            LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
+            // LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
             ret = luat_ble_write_indicate_value(&service, &characteristic, &descriptor, (uint8_t *)value, len);
         }else{
             ret = luat_ble_write_indicate_value(&service, &characteristic, NULL, (uint8_t *)value, len);
@@ -750,7 +750,6 @@ end_error:
 ble_device:write_value({
     uuid_service = "FA00", -- 服务的UUID, 可以是16位、32位或128位
     uuid_characteristic = "EA01", -- 特征的UUID值, 可以是16位、32位或128位
-    uuid_descriptor = "2902" -- 可选, 描述符的UUID值, 可以是16位、32位或128位
 }, "Hello BLE") -- 要写入的值
 */
 static int l_ble_write_value(lua_State *L){
@@ -771,7 +770,7 @@ static int l_ble_write_value(lua_State *L){
             service_uuid = luaL_checklstring(L, -1, &tmp);
             service.uuid_type = tmp;
             memcpy(service.uuid, service_uuid, service.uuid_type);
-            LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
+            // LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_service 参数");
@@ -784,7 +783,7 @@ static int l_ble_write_value(lua_State *L){
             characteristic_uuid = luaL_checklstring(L, -1, &tmp);
             characteristic.uuid_type = tmp;
             memcpy(characteristic.uuid, characteristic_uuid, characteristic.uuid_type);
-            LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
+            // LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_characteristic 参数");
@@ -797,7 +796,7 @@ static int l_ble_write_value(lua_State *L){
             descriptor_uuid = luaL_checklstring(L, -1, &tmp);
             descriptor.uuid_type = tmp;
             memcpy(descriptor.uuid, descriptor_uuid, descriptor.uuid_type);
-            LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
+            // LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
             ret = luat_ble_write_value(&service, &characteristic, &descriptor, (uint8_t *)value, len);
 
         }else{
@@ -814,6 +813,18 @@ end_error:
     return 0;
 }
 
+/*
+读取特征值
+@api ble.read_value(opts)
+@table 特征值的描述信息
+@return boolean 是否成功
+@usage
+-- 读取特征值,通过回调中的 EVENT_READ_VALUE 事件返回读取的value值
+ble_device:read_value({
+    uuid_service = "FA00", -- 服务的UUID, 可以是16位、32位或128位
+    uuid_characteristic = "EA01", -- 特征的UUID值, 可以是16位、32位或128位
+})
+*/
 static int l_ble_read_value(lua_State *L){
     uint16_t ret = 0;
     const char *service_uuid = NULL;
@@ -832,7 +843,7 @@ static int l_ble_read_value(lua_State *L){
             service_uuid = luaL_checklstring(L, -1, &tmp);
             service.uuid_type = tmp;
             memcpy(service.uuid, service_uuid, service.uuid_type);
-            LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
+            // LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_service 参数");
@@ -845,7 +856,7 @@ static int l_ble_read_value(lua_State *L){
             characteristic_uuid = luaL_checklstring(L, -1, &tmp);
             characteristic.uuid_type = tmp;
             memcpy(characteristic.uuid, characteristic_uuid, characteristic.uuid_type);
-            LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
+            // LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
         }
         else{
             LLOGW("缺失 uuid_characteristic 参数");
@@ -858,7 +869,7 @@ static int l_ble_read_value(lua_State *L){
             descriptor_uuid = luaL_checklstring(L, -1, &tmp);
             descriptor.uuid_type = tmp;
             memcpy(descriptor.uuid, descriptor_uuid, descriptor.uuid_type);
-            LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
+            // LLOGD("uuid_descriptor: %02X %02X", descriptor.uuid[0], descriptor.uuid[1]);
             ret = luat_ble_read_value(&service, &characteristic, &descriptor, &value, &len);
 
         }else{
@@ -878,7 +889,71 @@ end_error:
     return 0;
 }
 
-/*创建一个BLE扫描
+/*
+开关监听
+@api ble.notify_enable(opts, value)
+@table 特征值的描述信息
+@boolean enable 开/关 可选,默认开
+@return boolean 是否成功
+@usage
+-- 写入特征值,填充预设值,被动读取
+ble_device:notify_enable({
+    uuid_service = "FA00", -- 服务的UUID, 可以是16位、32位或128位
+    uuid_characteristic = "EA01", -- 特征的UUID值, 可以是16位、32位或128位
+}, true) -- 开/关
+*/
+static int l_ble_notify_enable(lua_State *L){
+    uint16_t ret = 0;
+    const char *service_uuid = NULL;
+    const char *characteristic_uuid = NULL;
+    luat_ble_uuid_t service = {0};
+    luat_ble_uuid_t characteristic = {0};
+    size_t tmp = 0;
+    if (1){
+        uint8_t enable = 1;
+        if (lua_isboolean(L, 3)) {
+            enable = lua_toboolean(L, 3);
+        }
+
+        lua_pushstring(L, "uuid_service");
+        if (LUA_TSTRING == lua_gettable(L, 2)){
+            service_uuid = luaL_checklstring(L, -1, &tmp);
+            service.uuid_type = tmp;
+            memcpy(service.uuid, service_uuid, service.uuid_type);
+            // LLOGD("uuid_service: %02X %02X", service.uuid[0], service.uuid[1]);
+        }
+        else{
+            LLOGW("缺失 uuid_service 参数");
+            goto end_error;
+        }
+        lua_pop(L, 1);
+
+        lua_pushstring(L, "uuid_characteristic");
+        if (LUA_TSTRING == lua_gettable(L, 2)){
+            characteristic_uuid = luaL_checklstring(L, -1, &tmp);
+            characteristic.uuid_type = tmp;
+            memcpy(characteristic.uuid, characteristic_uuid, characteristic.uuid_type);
+            // LLOGD("uuid_characteristic: %02X %02X", characteristic.uuid[0], characteristic.uuid[1]);
+        }
+        else{
+            LLOGW("缺失 uuid_characteristic 参数");
+            goto end_error;
+        }
+        lua_pop(L, 1);
+
+        ret = luat_ble_notify_enable(&service, &characteristic, enable);
+        
+        // LLOGD("luat_ble_write_value ret %d", ret);
+        lua_pushboolean(L, ret == 0 ? 1 : 0);
+        return 1;
+    }
+end_error:
+    LLOGE("error param");
+    return 0;
+}
+
+/*
+创建一个BLE扫描
 @api ble.scan_create(addr_mode, scan_interval, scan_window)
 @number addr_mode 广播地址模式, 可选值: ble.PUBLIC, ble.RANDOM, ble.RPA, ble.NRPA
 @number scan_interval 扫描间隔, 单位为0.625ms, 最小值为20, 最大值为10240
@@ -945,22 +1020,97 @@ static int l_ble_scanning_stop(lua_State *L){
     return 1;
 }
 
+/*
+BLE连接
+@api ble.connect()
+@string mac 地址
+@int 地址类型 ble.PUBLIC ble.RANDOM
+@return boolean 是否成功
+@usage
+-- BLE连接
+ble_device:connect(string.fromHex("C8478C4E027D"),0)
+*/
 static int l_ble_connect(lua_State *L){
-    size_t len;
+    size_t len = 0;
+    luat_ble_connect_req_t conn = {0};
     uint8_t *adv_addr = luaL_checklstring(L, 2, &len);
     uint8_t adv_addr_type = luaL_checknumber(L, 3);
-    LLOGD(" adv_addr_type:%d, adv_addr:%02x:%02x:%02x:%02x:%02x:%02x",
-          adv_addr_type, adv_addr[0], adv_addr[1], adv_addr[2],
-          adv_addr[3], adv_addr[4], adv_addr[5]);
-    lua_pushboolean(L, luat_ble_connect(NULL, adv_addr, adv_addr_type) ? 0 : 1);
+    if (len != 6){
+        LLOGE("error adv_addr len %d", len);
+        return 0;
+    }
+    memcpy(conn.adv_addr, adv_addr, len);
+    conn.adv_addr_type = adv_addr_type;
+    // LLOGD(" adv_addr_type:%d, adv_addr:%02x:%02x:%02x:%02x:%02x:%02x",
+    //       adv_addr_type, adv_addr[0], adv_addr[1], adv_addr[2],
+    //       adv_addr[3], adv_addr[4], adv_addr[5]);
+    lua_pushboolean(L, luat_ble_connect(NULL, &conn) ? 0 : 1);
     return 1;
 }
 
+/*
+BLE断开连接
+@api ble.disconnect()
+@return boolean 是否成功
+@usage
+-- BLE断开连接
+ble_device:disconnect()
+*/
 static int l_ble_disconnect(lua_State *L){
     lua_pushboolean(L, luat_ble_disconnect(NULL) ? 0 : 1);
     return 1;
 }
 
+/*
+解码广播数据
+@api ble.adv_decode(data)
+@string data 广播数据
+@return table 广播数据的解码结果
+@usage
+-- 解码广播数据
+local data = string.fromHex("1EFF060001092002BE0F0AAD8A6D2E251ED6DFBB3D15249929E10BE138DF7B")
+-- 解析广播数据
+local adv_data = ble_device:adv_decode(data)
+if adv_data then
+    for k, v in pairs(adv_data) do
+        log.info("ble", "adv data", v.len, v.tp, v.data:toHex())
+    end
+end
+*/
+static int l_ble_adv_decode(lua_State *L) {
+    size_t len = 0;
+    const char *data = luaL_checklstring(L, 2, &len);
+    if (len == 0) {
+        lua_pushnil(L);
+        return 1;
+    }
+    lua_newtable(L);
+    uint8_t *p = (uint8_t *)data;
+    size_t offset = 0;
+    int index = 1;
+    while (offset < len) {
+        uint8_t length = p[offset++];
+        if (length == 0 || offset + length > len) {
+            LLOGE("Invalid BLE advertisement data");
+            lua_pushnil(L);
+            return 1;
+        }
+        uint8_t type = p[offset++];
+        lua_newtable(L);
+        lua_pushinteger(L, length - 1); // Length does not include the length byte itself
+        lua_setfield(L, -2, "len");
+        lua_pushinteger(L, type);
+        lua_setfield(L, -2, "tp");
+        lua_pushlstring(L, (const char *)&p[offset], length - 1); // Data does not include the length byte
+        lua_setfield(L, -2, "data");
+
+        lua_seti(L, -2, index);
+        offset += length - 1;
+        index ++;
+    }
+    return 1;
+}
+
 static int _ble_struct_newindex(lua_State *L);
 
 void luat_ble_struct_init(lua_State *L){
@@ -976,6 +1126,7 @@ static const rotable_Reg_t reg_ble[] = {
     {"adv_create", ROREG_FUNC(l_ble_advertising_create)},
     {"adv_start", ROREG_FUNC(l_ble_advertising_start)},
     {"adv_stop", ROREG_FUNC(l_ble_advertising_stop)},
+    {"adv_decode", ROREG_FUNC(l_ble_adv_decode)},
 
     // gatt
     // slaver
@@ -984,7 +1135,8 @@ static const rotable_Reg_t reg_ble[] = {
     {"write_indicate", ROREG_FUNC(l_ble_write_indicate)},
     {"write_value", ROREG_FUNC(l_ble_write_value)},
     {"read_value", ROREG_FUNC(l_ble_read_value)},
-    
+
+    {"notify_enable", ROREG_FUNC(l_ble_notify_enable)},
     // scanning
     {"scan_create", ROREG_FUNC(l_ble_scanning_create)},
     {"scan_start", ROREG_FUNC(l_ble_scanning_start)},
@@ -993,6 +1145,7 @@ static const rotable_Reg_t reg_ble[] = {
     {"connect", ROREG_FUNC(l_ble_connect)},
     {"disconnect", ROREG_FUNC(l_ble_disconnect)},
 
+
     // BLE_EVENT
     {"EVENT_NONE", ROREG_INT(LUAT_BLE_EVENT_NONE)},
     {"EVENT_INIT", ROREG_INT(LUAT_BLE_EVENT_INIT)},
@@ -1009,26 +1162,40 @@ static const rotable_Reg_t reg_ble[] = {
     {"EVENT_CONN", ROREG_INT(LUAT_BLE_EVENT_CONN)},
     {"EVENT_DISCONN", ROREG_INT(LUAT_BLE_EVENT_DISCONN)},
     {"EVENT_WRITE", ROREG_INT(LUAT_BLE_EVENT_WRITE)},
-    {"EVENT_WRITE_REQ", ROREG_INT(LUAT_BLE_EVENT_WRITE)},
     {"EVENT_READ", ROREG_INT(LUAT_BLE_EVENT_READ)},
-    {"EVENT_READ_REQ", ROREG_INT(LUAT_BLE_EVENT_READ)},
+    {"EVENT_READ_VALUE", ROREG_INT(LUAT_BLE_EVENT_READ_VALUE)},
+    {"EVENT_GATT_DONE", ROREG_INT(LUAT_BLE_EVENT_GATT_DONE)},
 
     // ADV_ADDR_MODE
+    //@const PUBLIC 控制器的公共地址
     {"PUBLIC", ROREG_INT(LUAT_BLE_ADDR_MODE_PUBLIC)},
+    //@const RANDOM 生成的静态地址
     {"RANDOM", ROREG_INT(LUAT_BLE_ADDR_MODE_RANDOM)},
     {"RPA", ROREG_INT(LUAT_BLE_ADDR_MODE_RPA)},
     {"NRPA", ROREG_INT(LUAT_BLE_ADDR_MODE_NRPA)},
+
     // ADV_CHNL
+    //@const CHNL_37 37通道
     {"CHNL_37", ROREG_INT(LUAT_BLE_ADV_CHNL_37)},
+    //@const CHNL_38 38通道
     {"CHNL_38", ROREG_INT(LUAT_BLE_ADV_CHNL_38)},
+    //@const CHNL_39 39通道
     {"CHNL_39", ROREG_INT(LUAT_BLE_ADV_CHNL_39)},
+    //@const CHNLS_ALL 所有通道(37 38 39)
     {"CHNLS_ALL", ROREG_INT(LUAT_BLE_ADV_CHNLS_ALL)},
+
     // Permission
+    //@const READ 读权限
     {"READ", ROREG_INT(LUAT_BLE_GATT_PERM_READ)},
+    //@const WRITE 写权限
     {"WRITE", ROREG_INT(LUAT_BLE_GATT_PERM_WRITE)},
+    //@const IND 指示权限
     {"IND", ROREG_INT(LUAT_BLE_GATT_PERM_IND)},
+    //@const NOTIFY 通知权限
     {"NOTIFY", ROREG_INT(LUAT_BLE_GATT_PERM_NOTIFY)},
+    //@const WRITE_CMD 写权限(无需确认)
     {"WRITE_CMD", ROREG_INT(LUAT_BLE_GATT_PERM_WRITE_CMD)},
+
     // FLAGS
     {"FLAGS", ROREG_INT(LUAT_ADV_TYPE_FLAGS)},
     {"COMPLETE_LOCAL_NAME", ROREG_INT(LUAT_ADV_TYPE_COMPLETE_LOCAL_NAME)},

+ 2 - 2
components/bluetooth/src/luat_lib_bluetooth.c

@@ -99,7 +99,7 @@ function ble_callback(dev, evt, param)
     -- ble.EVENT_DISCONN: 断开连接
         -- param 是事件参数, 包含以下字段:
         -- param.reason: 断开连接的原因
-    -- ble.EVENT_WRITE_REQ: 收到写请求
+    -- ble.EVENT_WRITE: 收到写请求
         -- param 是事件参数, 包含以下字段:
         -- param.uuid_service: 服务的UUID
         -- param.uuid_characteristic: 特征的UUID
@@ -121,7 +121,7 @@ function ble_callback(dev, evt, param)
         sys.timerStart(function() dev:adv_start() end, 1000)
     elseif ble_event == ble.EVENT_SCAN_REPORT then
         log.info("ble", "scan report", param.rssi, param.adv_addr:toHex(), param.data:toHex())
-    elseif evt == ble.EVENT_WRITE_REQ then
+    elseif evt == ble.EVENT_WRITE then
         -- 收到写请求
         log.info("ble", "接收到写请求", param.uuid_service:toHex(), param.uuid_characteristic:toHex(), param.data:toHex())
     end

+ 5 - 0
components/ethernet/common/dhcp_client.c

@@ -223,6 +223,11 @@ __CHECK:
 			if (DHCP_ACK == ack)
 			{
 				dhcp->lease_time = BytesGetBe32(&in->Data[in->Pos + 2]);
+				if (dhcp->lease_time < 60)
+				{
+					LLOGW("lease time too short %d, set to 60", dhcp->lease_time);
+					dhcp->lease_time = 60; // 最小租约时间为60秒
+				}
 				lease_time = dhcp->lease_time;
 				lease_time *= 1000;
 				dhcp->lease_end_time = luat_mcu_tick64_ms() + lease_time;

+ 4 - 0
components/network/libhttp/luat_http_client.c

@@ -703,6 +703,10 @@ int32_t luat_lib_http_callback(void *data, void *param){
 			if (http_ctrl->resp_headers_done) {
 				size_t nParseBytes = http_parser_execute(&http_ctrl->parser, &parser_settings, http_ctrl->resp_buff, http_ctrl->resp_buff_offset);
 				LLOGD("nParseBytes %d resp_buff_offset %d", nParseBytes, http_ctrl->resp_buff_offset);
+				if(http_ctrl->parser.http_errno) {
+					LLOGW("http exit reason by errno: %d != HPE_OK!!!", http_ctrl->parser.http_errno);
+					return 0;
+				}
 				if (http_ctrl->close_state) {
 					http_ctrl->resp_buff_offset = 0;
 					on_complete(&http_ctrl->parser, http_ctrl);

+ 4 - 0
components/wlan/luat_lib_wlan.c

@@ -147,6 +147,10 @@ static int l_wlan_connect(lua_State* L){
     if (len == 6) {
         memcpy(info.bssid, bssid, 6);
     }
+    else if (ssid == NULL || strlen(ssid) == 0) {
+        LLOGE("ssid is emtry!!!");
+        return 0;
+    }
     #ifdef LUAT_USE_DRV_WLAN
     int ret = luat_drv_wlan_connect(&info);
     #else

+ 79 - 0
luat/demo/airlink/air8000_ble/master/main.lua

@@ -0,0 +1,79 @@
+
+-- LuaTools需要PROJECT和VERSION这两个信息
+PROJECT = "ble"
+VERSION = "1.0.0"
+
+log.info("main", "project name is ", PROJECT, "version is ", VERSION)
+
+-- 通过boot按键方便刷Air8000S
+function PWR8000S(val) gpio.set(23, val) end
+
+gpio.debounce(0, 1000)
+gpio.setup(0, function()
+    sys.taskInit(function()
+        log.info("复位Air8000S")
+        PWR8000S(0)
+        sys.wait(20)
+        PWR8000S(1)
+    end)
+end, gpio.PULLDOWN)
+
+local scan_count = 0
+
+local function ble_callback(ble_device, ble_event, ble_param)
+    if ble_event == ble.EVENT_CONN then
+        log.info("ble", "connect 成功")
+    elseif ble_event == ble.EVENT_DISCONN then
+        log.info("ble", "disconnect", ble_param.reason)
+        sys.timerStart(function() ble_device:scan_start() end, 1000)
+    elseif ble_event == ble.EVENT_WRITE then
+        log.info("ble", "write", ble_param.handle,ble_param.uuid_service:toHex(),ble_param.uuid_characteristic:toHex())
+        log.info("ble", "data", ble_param.data:toHex())
+    elseif ble_event == ble.EVENT_READ_VALUE then
+        log.info("ble", "read", ble_param.handle,ble_param.uuid_service:toHex(),ble_param.uuid_characteristic:toHex(),ble_param.data:toHex())
+    elseif ble_event == ble.EVENT_SCAN_REPORT then
+        print("ble scan report",ble_param.addr_type,ble_param.rssi,ble_param.adv_addr:toHex(),ble_param.data:toHex())
+        scan_count = scan_count + 1
+        if scan_count > 100 then
+            log.info("ble", "扫描次数超过100次, 停止扫描, 15秒后重新开始")
+            scan_count = 0
+            ble_device:scan_stop()
+            sys.timerStart(function() ble_device:scan_start() end, 15000)
+        end
+        -- 注意, 这里是连接到另外一个设备, 设备名称带LuatOS字样
+        if ble_param.addr_type == 0 and ble_param.data:find("LuatOS") then
+            log.info("ble", "停止扫描, 连接设备", ble_param.adv_addr:toHex(), ble_param.addr_type)
+            ble_device:scan_stop()
+            ble_device:connect(ble_param.adv_addr,ble_param.addr_type)
+        end
+    elseif ble_event == ble.EVENT_GATT_DONE then
+        -- 读取GATT完成, 打印出来
+        for k, v in pairs(ble_param) do
+            log.info("ble", "gatt", k, v[1]:toHex())
+        end
+        local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA02")}
+        ble_device:write_value(wt,string.fromHex("1234"))
+
+        local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA03")}
+        ble_device:read_value(wt)
+    end
+end
+
+
+sys.taskInit(function()
+    log.info("开始初始化蓝牙核心")
+    bluetooth_device = bluetooth.init()
+    log.info("初始化BLE功能")
+    ble_device = bluetooth_device:ble(ble_callback)
+
+    -- master
+    ble_device:scan_create({})
+    ble_device:scan_start()
+    -- ble_device:scan_stop()
+end)
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 1 - 1
luat/demo/airlink/air8000_ble/peripheral/main.lua

@@ -46,7 +46,7 @@ local function ble_callback(dev, evt, param)
         ble_stat = false
         -- 1秒后重新开始广播
         sys.timerStart(function() dev:adv_start() end, 1000)
-    elseif evt == ble.EVENT_WRITE_REQ then
+    elseif evt == ble.EVENT_WRITE then
         -- 收到写请求
         log.info("ble", "接收到写请求", param.uuid_service:toHex(), param.uuid_characteristic:toHex(), param.data:toHex())
     end

+ 7 - 0
luat/demo/airlink/air8000_ble/scan/main.lua

@@ -28,6 +28,13 @@ local function ble_callback(ble_device, ble_event, ble_param)
         log.info("ble", "scan init")
     elseif ble_event == ble.EVENT_SCAN_REPORT then
         log.info("ble", "scan report", ble_param.rssi, ble_param.adv_addr:toHex(), ble_param.data:toHex())
+        -- 解析广播数据, 日志很多, 按需使用
+        -- local adv_data = ble_device:adv_decode(ble_param.data)
+        -- if adv_data then
+        --     for k, v in pairs(adv_data) do
+        --         log.info("ble", "adv data", v.len, v.tp, v.data:toHex())
+        --     end
+        -- end
     elseif ble_event == ble.EVENT_SCAN_STOP then
         log.info("ble", "scan stop")
     end

+ 40 - 16
luat/demo/bluetooth/ble/main.lua

@@ -8,30 +8,32 @@ sys = require("sys")
 
 log.info("main", "project name is ", PROJECT, "version is ", VERSION)
 
--- characteristic handle
-local characteristic1,characteristic2,characteristic3,characteristic4
-
 local att_db = {--Service
         string.fromHex("FA00"),             --Service UUID
         -- Characteristic
         { -- Characteristic 1
-            string.fromHex("EA01"),         -- Characteristic UUID Value
+            0xEA01,                         -- Characteristic UUID Value
             ble.NOTIFY|ble.READ|ble.WRITE,  -- Properties
+            string.fromHex("1234")          -- Value
+
         },
         { -- Characteristic 2
-            string.fromHex("EA02"),
+            0xEA02,
             ble.WRITE,
         },
         { -- Characteristic 3
-            string.fromHex("EA03"),
+            0xEA03,
             ble.READ,
+            string.fromHex("5678")
         },
         { -- Characteristic 4
-            string.fromHex("EA04"),
-            ble.READ|ble.WRITE,
+            0xEA04,
+            ble.NOTIFY|ble.READ|ble.WRITE,
         },
     }
 
+local scan_count = 0
+
 local function ble_callback(ble_device, ble_event, ble_param)
     if ble_event == ble.EVENT_CONN then
         log.info("ble", "connect 成功")
@@ -40,10 +42,28 @@ local function ble_callback(ble_device, ble_event, ble_param)
         -- 1秒后重新开始广播
         sys.timerStart(function() ble_device:adv_start() end, 1000)
     elseif ble_event == ble.EVENT_WRITE then
-        log.info("ble", "write", ble_param.conn_idx,ble_param.service_id,ble_param.handle,ble_param.data:toHex())
-    elseif ble_event == ble.EVENT_READ then
-        log.info("ble", "read", ble_param.conn_idx,ble_param.service_id,ble_param.handle)
-        ble_device:read_response(ble_param,string.fromHex("1234"))
+        log.info("ble", "write", ble_param.handle,ble_param.uuid_service:toHex(),ble_param.uuid_characteristic:toHex())
+        log.info("ble", "data", ble_param.data:toHex())
+        -- ble_device:write_notify(ble_param,string.fromHex("123456"))
+    elseif ble_event == ble.EVENT_READ_VALUE then
+        log.info("ble", "read", ble_param.handle,ble_param.uuid_service:toHex(),ble_param.uuid_characteristic:toHex(),ble_param.data:toHex(),ble_param.data)
+    elseif ble_event == ble.EVENT_SCAN_REPORT then
+        print("ble scan report",ble_param.addr_type,ble_param.rssi,ble_param.adv_addr:toHex(),ble_param.data:toHex(),ble_param.data)
+        scan_count = scan_count + 1
+        if scan_count > 20 then
+            ble_device:scan_stop()
+        end
+        if ble_param.addr_type == 0 and ble_param.data:find("LuatOS") then
+            ble_device:scan_stop()
+            ble_device:connect(ble_param.adv_addr,ble_param.addr_type)
+        end
+    elseif ble_event == 14 then
+
+        local characteristic = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA02")}
+        ble_device:write_value(characteristic,string.fromHex("1234"))
+
+        local characteristic = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA03")}
+        ble_device:read_value(characteristic)
     end
 end
 
@@ -54,9 +74,9 @@ sys.taskInit(function()
     log.info("初始化BLE功能")
     ble_device = bluetooth_device:ble(ble_callback)
 
+    -- slaver
     log.info('开始创建GATT')
-    characteristic1,characteristic2,characteristic3,characteristic4 = ble_device:gatt_create(att_db)
-    log.info("创建的GATT为",characteristic1,characteristic2,characteristic3,characteristic4)
+    ble_device:gatt_create(att_db)
 
     log.info("开始设置广播内容")
     ble_device:adv_create({
@@ -69,13 +89,17 @@ sys.taskInit(function()
             {ble.COMPLETE_LOCAL_NAME, "LuatOS"},
             {ble.SERVICE_DATA, string.fromHex("FE01")},
             {ble.MANUFACTURER_SPECIFIC_DATA, string.fromHex("05F0")},
-        }
+        },
     })
-
     log.info("开始广播")
     ble_device:adv_start()
     -- ble_device:adv_stop()
 
+    -- master
+    -- ble_device:scan_create({})
+    -- ble_device:scan_start()
+    -- -- ble_device:scan_stop()
+
     while 1 do
         sys.wait(1000)
     end

+ 3 - 3
luat/include/luat_debug.h

@@ -51,7 +51,7 @@ void luat_debug_print(const char *fmt, ...);
  * @param fmt 格式
  * @param ... 后续变量
  */
-#define LUAT_DEBUG_PRINT(fmt, argv...) luat_debug_print("%s %d:"fmt, __FUNCTION__,__LINE__, ##argv)
+#define LUAT_DEBUG_PRINT(fmt, ...) luat_debug_print("%s %d:"fmt, __FUNCTION__,__LINE__, ##__VA_ARGS__)
 
 /**
  * @brief 断言处理,并格式打印输出到LOG口
@@ -64,11 +64,11 @@ void luat_debug_print(const char *fmt, ...);
 void luat_debug_assert(const char *fun_name, unsigned int line_no, const char *fmt, ...);
 
 
-#define LUAT_DEBUG_ASSERT(condition, fmt, argv...)  do {  \
+#define LUAT_DEBUG_ASSERT(condition, fmt, ...)  do {  \
 														{ \
 															if((condition) == 0) \
 															{ \
-																luat_debug_assert(__FUNCTION__, __LINE__, fmt, ##argv); \
+																luat_debug_assert(__FUNCTION__, __LINE__, fmt, ##__VA_ARGS__); \
 															}\
 														} \
 													} while(0) ///< luat_debug_assert宏定义为LUAT_DEBUG_ASSERT

+ 0 - 2
module/Air780EGH/demo/u8g2/main.lua

@@ -25,8 +25,6 @@ sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
 -- gpio.setup(14, nil) -- 关闭GPIO14,防止camera复用关系出问题
 -- gpio.setup(15, nil) -- 关闭GPIO15,防止camera复用关系出问题
 
--- mcu.altfun(mcu.I2C, 4, 67, 3, nil)
--- mcu.altfun(mcu.I2C, 4, 66, 3, nil)
 
 local rtos_bsp = rtos.bsp()
 

BIN
module/Air780EHM/core/LuatOS-SoC_V2007_Air780EHM.soc


+ 1 - 2
module/Air780EHM/demo/camera/spi_cam/main.lua

@@ -10,8 +10,7 @@ log.style(1)
 
 pm.ioVol(pm.IOVOL_ALL_GPIO, 3000)
 
---  mcu.altfun(mcu.I2C, 0, 66, 2, nil)
---  mcu.altfun(mcu.I2C, 0, 67, 2, nil)
+
 
 gpio.setup(2,1)--GPIO2打开给camera_3.3V供电
 

+ 40 - 0
module/Air780EHM/demo/fastlz/readme.md

@@ -0,0 +1,40 @@
+
+## 演示功能概述
+
+将使用Air780EHM核心板,演示FastLZ的压缩与解压缩的使用方法,实现读取文件系统中的文件,并演示压缩与解压缩的代码实现。
+
+## 演示硬件环境
+
+1、Air780EHM核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air780EHM核心板和数据线的硬件接线方式为
+
+- Air780EHM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2007版本固件](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHM/core)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V2007固件对比验证)
+
+## 演示核心步骤
+
+1、核心板通过usb数据线连接到电脑上
+
+2、通过Luatools将demo与固件烧录到核心板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印:
+
+```lua
+[2025-06-26 15:02:23.677][000000001.239] I/user.原始数据长度	3456
+[2025-06-26 15:02:23.728][000000001.241] I/user.压缩等级1:压缩后的数据长度	2170
+[2025-06-26 15:02:23.772][000000001.242] I/user.压缩等级1:解压后的的数据长度	3456
+[2025-06-26 15:02:23.811][000000001.242] I/user.压缩等级1:解压后的数据与原始数据相同
+[2025-06-26 15:02:23.847][000000002.244] I/user.压缩等级2:压缩后的数据长度	2170
+[2025-06-26 15:02:23.890][000000002.245] I/user.压缩等级2:解压后的数据长度	3456
+[2025-06-26 15:02:23.931][000000002.245] I/user.压缩等级2:解压后的数据与原始数据相同
+
+```

+ 0 - 4
module/Air780EHM/demo/onewire/onewire_single_18b20/main.lua

@@ -15,10 +15,6 @@ log.style(1)
 2. ONEWIRE功能支持在4个引脚使用, 但硬件通道只有一个, 默认是GPIO2
 3. 如需切换到其他脚, 参考如下切换逻辑, 选其中一种
 
-mcu.altfun(mcu.ONEWIRE, 0, 17, 4, 0) -- GPIO2, 也就是默认值
-mcu.altfun(mcu.ONEWIRE, 0, 18, 4, 0) -- GPIO3
-mcu.altfun(mcu.ONEWIRE, 0, 22, 4, 0) -- GPIO7
-mcu.altfun(mcu.ONEWIRE, 0, 53, 4, 0) -- GPIO28
 ]]
 
 local function read_ds18b20(id)

+ 0 - 2
module/Air780EHM/demo/u8g2/main.lua

@@ -25,8 +25,6 @@ sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
 -- gpio.setup(14, nil) -- 关闭GPIO14,防止camera复用关系出问题
 -- gpio.setup(15, nil) -- 关闭GPIO15,防止camera复用关系出问题
 
--- mcu.altfun(mcu.I2C, 4, 67, 3, nil)
--- mcu.altfun(mcu.I2C, 4, 66, 3, nil)
 
 local rtos_bsp = rtos.bsp()
 

+ 72 - 0
module/Air780EHM/demo/ymodem/main.lua

@@ -0,0 +1,72 @@
+-- LuaTools需要PROJECT和VERSION这两个信息
+PROJECT = "uart_ymodem"
+VERSION = "1.0.0"
+log.style(1)
+log.info("main", PROJECT, VERSION)
+
+-- 引入必要的库文件(lua编写), 内部库不需要require
+sys = require("sys")
+
+if wdt then
+    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+end
+
+local uartid = 1 -- 根据实际设备选取不同的uartid
+
+--初始化
+local result = uart.setup(
+    uartid,--串口id
+    115200,--波特率
+    8,--数据位
+    1--停止位
+)
+local ymodem_running = false --  定义一个局部变量,用于表示Ymodem协议是否正在运行
+
+local rxbuff = zbuff.create(1024 + 32) --  创建一个缓冲区,大小为1024 + 32
+local ymodem_handler = ymodem.create("/","save.bin") --  创建一个ymodem处理程序,保存路径为"/",文件名为"save.bin"
+local function ymodem_to() --  定义一个ymodem_to函数,用于发送C字符,并重置ymodem处理程序
+    if not ymodem_running then --  如果ymodem协议没有在运行,则发送请求
+        uart.write(uartid, "C")
+        ymodem.reset(ymodem_handler) --  重置ymodem处理程序
+    end
+end
+
+
+sys.timerLoopStart(ymodem_to,500) --  每隔500ms调用ymodem_to函数
+
+local function ymodem_rx(id,len) --  定义一个ymodem_rx函数,用于接收数据
+    uart.rx(id,rxbuff) --  从uart接收数据到缓冲区
+    log.info(rxbuff:used()) --  打印缓冲区已使用的大小
+    local result,ack,flag,file_done,all_done = ymodem.receive(ymodem_handler,rxbuff) --  调用ymodem.receive函数,接收数据
+    ymodem_running = result
+    log.info(ymodem_running,ack,flag,file_done,all_done)
+    rxbuff:del()
+    if result then
+        rxbuff:copy(0, ack,flag)
+        uart.tx(id, rxbuff)
+    end
+    if all_done then --  所有数据都接收完毕
+        local exists=io.exists("/save.bin") -- 判断/save.bin文件是否存在
+        if exists then
+            log.info("io", "save.bin file exists:", exists) --  打印日志,判断/save.bin文件是否存在
+            log.info("io", "save.bin file size:", io.fileSize("/save.bin")) --  打印日志,显示/save.bin文件大小
+        else
+            log.info("io", "save.bin file not exists") --  打印日志,/save.bin文件不存在
+        end
+            
+        ymodem_running = false  --再次开始接收
+    end
+    rxbuff:del()
+end
+uart.on(uartid, "receive", ymodem_rx) --  监听串口接收事件
+
+uart.on(uartid, "sent", function(id) --  监听串口发送事件
+    log.info("uart", "sent", id) --  打印发送事件
+end)
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 0 - 2
module/Air780EHV/DEMO/u8g2/main.lua

@@ -25,8 +25,6 @@ sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
 -- gpio.setup(14, nil) -- 关闭GPIO14,防止camera复用关系出问题
 -- gpio.setup(15, nil) -- 关闭GPIO15,防止camera复用关系出问题
 
--- mcu.altfun(mcu.I2C, 4, 67, 3, nil)
--- mcu.altfun(mcu.I2C, 4, 66, 3, nil)
 
 local rtos_bsp = rtos.bsp()
 

BIN
module/Air780EPM/core/LuatOS-SoC_V2007_Air780EPM.soc


+ 0 - 2
module/Air780EPM/demo/camera/spi_cam/main.lua

@@ -10,8 +10,6 @@ log.style(1)
 
 pm.ioVol(pm.IOVOL_ALL_GPIO, 3000)
 
---  mcu.altfun(mcu.I2C, 0, 66, 2, nil)
---  mcu.altfun(mcu.I2C, 0, 67, 2, nil)
 
 gpio.setup(2,1)--GPIO2打开给camera_3.3V供电
 

+ 1 - 6
module/Air780EPM/demo/onewire/onewire_single_18b20/main.lua

@@ -13,12 +13,7 @@ log.style(1)
 注意:
 1. 3.3v在老版本的开发板上没有引脚, 所以需要外接, 一定要确保共地
 2. ONEWIRE功能支持在4个引脚使用, 但硬件通道只有一个, 默认是GPIO2
-3. 如需切换到其他脚, 参考如下切换逻辑, 选其中一种
-
-mcu.altfun(mcu.ONEWIRE, 0, 17, 4, 0) -- GPIO2, 也就是默认值
-mcu.altfun(mcu.ONEWIRE, 0, 18, 4, 0) -- GPIO3
-mcu.altfun(mcu.ONEWIRE, 0, 22, 4, 0) -- GPIO7
-mcu.altfun(mcu.ONEWIRE, 0, 53, 4, 0) -- GPIO28
+3. 如需切换到其他脚, 参考上一级目录下的onewire_multi_18b20_swich_read
 ]]
 
 local function read_ds18b20(id)

+ 0 - 2
module/Air780EPM/demo/u8g2/main.lua

@@ -25,8 +25,6 @@ sys.timerLoopStart(wdt.feed, 3000) -- 3s喂一次狗
 -- gpio.setup(14, nil) -- 关闭GPIO14,防止camera复用关系出问题
 -- gpio.setup(15, nil) -- 关闭GPIO15,防止camera复用关系出问题
 
--- mcu.altfun(mcu.I2C, 4, 67, 3, nil)
--- mcu.altfun(mcu.I2C, 4, 66, 3, nil)
 
 local rtos_bsp = rtos.bsp()
 

+ 1 - 2
module/Air8000/core/固件功能列表.md

@@ -39,7 +39,7 @@
 | 34   | [json](json.md)                 | json生成和解析库                    | 基础软件 | √       | √          | √         |
 | 35   | [lcd](lcd.md)                   | lcd驱动模块                         | 外设驱动 | √       | √          | √         |
 | 36   | [libgnss](libgnss.md)           | NMEA数据处理                        |          | √       | √          | √         |
-| 37   | [little_flash](little_flash.md) | NAMD flash操作                      | 协议组件 | ×       | √          | √         |
+| 37   | [little_flash](little_flash.md) | NAND flash操作                      | 协议组件 | ×       | √          | √         |
 | 38   | [log](log.md)                   | 日志库                              | 基础软件 | √       | √          | √         |
 | 39   | [lora2](lora2.md)               | lora2驱动模块(支持多挂)             | 外设驱动 | ×       | √          | √         |
 | 40   | [lvgl](lvgl.md)                 | LVGL图像库                          | 基础软件 | ×       | ×          | √         |
@@ -87,4 +87,3 @@
 | Air8000_VOLTE |   8MB   |   8MB   | 4MB  | 512KB  | 64KB  |  512KB |
 | Air8000_LVGL  |   8MB   |   8MB   | 4MB |  512KB |  64KB | 512KB  |
 
-

+ 32 - 7
module/Air8000/demo/ble/peripheral/main.lua

@@ -1,9 +1,34 @@
 -- LuaTools需要PROJECT和VERSION这两个信息
 PROJECT = "ble"
 VERSION = "1.0.0"
-
--- 引入必要的库文件(lua编写), 内部库不需要require
-sys = require("sys")
+--[[
+Air8000的BLE支持4种模式,分别是主机模式(central),从机模式(peripheral),广播者模式(ibeacon),以及观察者模式(scan)。
+1.主机模式(central):
+主机模式是能够搜索别人并主动建立连接的一方,从扫描状态转化而来的。其可以和一个或多个从设备进行连接通信,它会定期的扫描周围的广播状态设备发送的广播信息,可以对周围设备进行搜索并选择所需要连接的从设备进行配对连接,建立通信链路成功后,主从双方就可以发送接收数据。
+2.从机模式(peripheral):
+从机模式是从广播者模式转化而来的,未被连接的从机首先进入广播状态,等待被主机搜索,当主机扫描到从设备建立连接后,就可以和主机设备进行数据的收发,其不能主动的建立连接,只能等别人来连接自己。和广播模式有区别的地方在于,从机模式的设备是可以被连接的,定期的和主机进行连接和数据传输,在数据传输过程中作从机。
+3.广播者模式(ibeacon)
+处于广播模式的设备,会周期性的广播beacon信息, 但不会被扫描到, 也不会连接其他设备。
+4.观察者模式(scan)
+观察者模式,该模式下模块为非连接,相对广播者模式的一对多发送广播,观察者可以一对多接收数据。在该模式中,设备可以仅监听和读取空中的广播数据。和主机唯一的区别是不能发起连接,只能持续扫描从机。
+蓝牙中的重要概念
+1. GATT(通用属性配置文件)
+  - 定义 BLE 设备如何组织和传输数据,以 “服务(Service)” 和 “特征(Characteristic)” 为单位。
+  - 示例:心率监测设备的 GATT 服务包含 “心率特征”,手机通过读取该特征获取心率数据。
+2. 服务和特征
+- 服务是特征的容器,通过逻辑分组简化复杂功能的管理;
+- 特征是数据交互的最小单元,通过属性定义实现灵活的读写与推送机制;
+- 两者结合构成 GATT 协议的核心框架,支撑蓝牙设备间的标准化数据交互(如智能穿戴、医疗设备、物联网传感器)。
+3. 特征的关键属性(Properties)
+特征通过 “属性” 定义数据的操作方式,常见属性包括:
+  1. 可读(Read):允许客户端读取特征值(如读取电池电量)。
+  2. 可写(Write):允许客户端写入特征值(如设置设备参数)。
+  3. 通知(Notification):服务端主动发送特征值更新(如心率变化时推送给手机)。
+  4. 指示(Indication):比通知更可靠的推送(需客户端确认接收)。
+4. UUID
+  UUID 是蓝牙 GATT 协议的 “数字身份证”,通过标准化的唯一标识机制,实现了跨厂商设备的功能互认(标准 UUID)与厂商个性化功能的扩展(自定义 UUID)
+  Air8000 的所有操作,都通过UUID来索引和管理
+]]
 
 log.info("main", "project name is ", PROJECT, "version is ", VERSION)
 
@@ -21,11 +46,11 @@ gpio.setup(0, function()
 end, gpio.PULLDOWN)
 
 local att_db =   { -- Service
-    string.fromHex("FA00"), -- Service UUID
+    string.fromHex("FA00"), -- 服务 UUID
     -- Characteristic
     { -- Characteristic 1
-        string.fromHex("EA01"), -- Characteristic UUID Value
-        ble.NOTIFY | ble.READ | ble.WRITE -- Properties
+        string.fromHex("EA01"), -- 特征 UUID Value
+        ble.NOTIFY | ble.READ | ble.WRITE -- 属性
     }, { -- Characteristic 2
         string.fromHex("EA02"), ble.WRITE
     }, { -- Characteristic 3
@@ -46,7 +71,7 @@ local function ble_callback(dev, evt, param)
         ble_stat = false
         -- 1秒后重新开始广播
         sys.timerStart(function() dev:adv_start() end, 1000)
-    elseif evt == ble.EVENT_WRITE_REQ then
+    elseif evt == ble.EVENT_WRITE then
         -- 收到写请求
         log.info("ble", "接收到写请求", param.uuid_service:toHex(), param.data:toHex())
     end

+ 36 - 0
module/Air8000/demo/ble/peripheral/readme.md

@@ -0,0 +1,36 @@
+
+## 演示功能概述
+
+将使用Air8000核心板,演示Air8000蓝牙从机模式下发送通知到主机,以及如何通过手机向Air8000进行读写操作。
+
+## 演示硬件环境
+
+1、Air8000核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air8000核心板和数据线的硬件接线方式为
+
+- Air8000核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到 "充电" 一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+[如何使用 LuaTools 烧录软件 - luatos@air8000 - 合宙模组资料中心](https://docs.openluat.com/air8000/luatos/common/download/)
+
+2、[Air8000 固件](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/core)
+
+3、[Air8000 BLE从机代码](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/ble/peripheral)
+
+## 演示核心步骤
+
+1、核心板通过usb数据线连接到电脑上
+
+2、通过Luatools将demo与固件烧录到核心板中
+
+3、烧录成功后,自动开机运行
+
+4、接下来通过蓝牙APP 连接作为蓝牙从机设备的Air8000进行操作,详见文档:https://docs.openluat.com/air8000/luatos/app/BLE/peripheral/

+ 94 - 0
module/Air8000/demo/ble/scan/main.lua

@@ -0,0 +1,94 @@
+
+-- LuaTools需要PROJECT和VERSION这两个信息
+PROJECT = "ble_scan"
+VERSION = "1.0.0"
+
+--[[
+Air8000的BLE支持4种模式,分别是主机模式(central),从机模式(peripheral),广播者模式(ibeacon),以及观察者模式(scan)。
+1.主机模式(central):
+主机模式是能够搜索别人并主动建立连接的一方,从扫描状态转化而来的。其可以和一个或多个从设备进行连接通信,它会定期的扫描周围的广播状态设备发送的广播信息,可以对周围设备进行搜索并选择所需要连接的从设备进行配对连接,建立通信链路成功后,主从双方就可以发送接收数据。
+2.从机模式(peripheral):
+从机模式是从广播者模式转化而来的,未被连接的从机首先进入广播状态,等待被主机搜索,当主机扫描到从设备建立连接后,就可以和主机设备进行数据的收发,其不能主动的建立连接,只能等别人来连接自己。和广播模式有区别的地方在于,从机模式的设备是可以被连接的,定期的和主机进行连接和数据传输,在数据传输过程中作从机。
+3.广播者模式(ibeacon)
+处于广播模式的设备,会周期性的广播beacon信息, 但不会被扫描到, 也不会连接其他设备。
+4.观察者模式(scan)
+观察者模式,该模式下模块为非连接,相对广播者模式的一对多发送广播,观察者可以一对多接收数据。在该模式中,设备可以仅监听和读取空中的广播数据。和主机唯一的区别是不能发起连接,只能持续扫描从机。
+蓝牙中的重要概念
+1. GATT(通用属性配置文件)
+  - 定义 BLE 设备如何组织和传输数据,以 “服务(Service)” 和 “特征(Characteristic)” 为单位。
+  - 示例:心率监测设备的 GATT 服务包含 “心率特征”,手机通过读取该特征获取心率数据。
+2. 服务和特征
+- 服务是特征的容器,通过逻辑分组简化复杂功能的管理;
+- 特征是数据交互的最小单元,通过属性定义实现灵活的读写与推送机制;
+- 两者结合构成 GATT 协议的核心框架,支撑蓝牙设备间的标准化数据交互(如智能穿戴、医疗设备、物联网传感器)。
+3. 特征的关键属性(Properties)
+特征通过 “属性” 定义数据的操作方式,常见属性包括:
+  1. 可读(Read):允许客户端读取特征值(如读取电池电量)。
+  2. 可写(Write):允许客户端写入特征值(如设置设备参数)。
+  3. 通知(Notification):服务端主动发送特征值更新(如心率变化时推送给手机)。
+  4. 指示(Indication):比通知更可靠的推送(需客户端确认接收)。
+4. UUID
+  UUID 是蓝牙 GATT 协议的 “数字身份证”,通过标准化的唯一标识机制,实现了跨厂商设备的功能互认(标准 UUID)与厂商个性化功能的扩展(自定义 UUID)
+  Air8000 的所有操作,都通过UUID来索引和管理
+]]
+
+log.info("main", "project name is ", PROJECT, "version is ", VERSION)
+
+-- 通过boot按键方便刷Air8000S
+function PWR8000S(val)
+    gpio.set(23, val)
+end
+
+gpio.debounce(0, 1000)
+gpio.setup(0, function()
+    sys.taskInit(function()
+        log.info("复位Air8000S")
+        PWR8000S(0)
+        sys.wait(20)
+        PWR8000S(1)
+    end)
+end, gpio.PULLDOWN)
+
+local function ble_callback(ble_device, ble_event, ble_param)
+    if ble_event == ble.EVENT_SCAN_INIT then
+        log.info("ble", "scan init")
+    elseif ble_event == ble.EVENT_SCAN_REPORT then
+        log.info("ble", "scan report", ble_param.rssi, ble_param.adv_addr:toHex(), ble_param.data:toHex())
+    elseif ble_event == ble.EVENT_SCAN_STOP then
+        log.info("ble", "scan stop")
+    end
+end
+
+local bt_scan = false   -- 是否扫描蓝牙
+
+sys.taskInit(function()
+    sys.wait(500)
+    log.info("开始初始化蓝牙核心")
+    bluetooth_device = bluetooth.init()
+    sys.wait(100)
+    log.info("初始化BLE功能")
+    ble_device = bluetooth_device:ble(ble_callback)
+    if ble_device == nil then
+        log.error("当前固件不支持完整的BLE")
+        return
+    end
+    sys.wait(100)
+    -- 扫描模式
+    sys.wait(1000)
+    ble_device:scan_create() -- 使用默认参数, addr_mode=0, scan_interval=100, scan_window=100
+    -- ble_device:scan_create(0, 10, 10) -- 使用自定义参数
+    sys.wait(100)
+    log.info("开始扫描")
+    ble_device:scan_start()
+
+    sys.wait(15000)
+    log.info("停止扫描")
+    ble_device:scan_stop()
+
+end)
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 36 - 0
module/Air8000/demo/ble/scan/readme.md

@@ -0,0 +1,36 @@
+
+## 演示功能概述
+
+将使用Air8000核心板,演示Air8000蓝牙在观察者模式下扫描蓝牙设备的操作。
+
+## 演示硬件环境
+
+1、Air8000核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air8000核心板和数据线的硬件接线方式为
+
+- Air8000核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到 "充电" 一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+[如何使用 LuaTools 烧录软件 - luatos@air8000 - 合宙模组资料中心](https://docs.openluat.com/air8000/luatos/common/download/)
+
+2、[Air8000 固件](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/core)
+
+3、[Air8000 BLE扫描代码](https://gitee.com/openLuat/LuatOS/blob/master/module/Air8000/demo/ble/scan)
+
+## 演示核心步骤
+
+1、核心板通过usb数据线连接到电脑上
+
+2、通过Luatools将demo与固件烧录到核心板中
+
+3、烧录成功后,自动开机运行
+
+4、通过luatools日志查看扫描到的设备信息

+ 1 - 6
module/Air8000/demo/onewire/main.lua

@@ -20,12 +20,7 @@ log.style(1)
 
 注意:
 1. ONEWIRE功能支持在4个引脚使用, 但硬件通道只有一个, 默认是GPIO2
-2. 如需切换到其他脚, 参考如下切换逻辑, 选其中一种
-
-mcu.altfun(mcu.ONEWIRE, 0, 17, 4, 0) -- GPIO2, 也就是默认值
-mcu.altfun(mcu.ONEWIRE, 0, 18, 4, 0) -- GPIO3
-mcu.altfun(mcu.ONEWIRE, 0, 22, 4, 0) -- GPIO7
-mcu.altfun(mcu.ONEWIRE, 0, 53, 4, 0) -- GPIO28
+2. 如需切换到其他脚, 参考Air780EPM目录下的onewire_multi_18b20_swich_read
 ]]
 
 local function read_ds18b20(id)

+ 1 - 4
module/Air8000/project/School_To_Home/code/test.lua

@@ -70,10 +70,7 @@ audio.on(0, function(id, event, buff)
 end)
 
 sys.taskInit(function()
-    mcu.altfun(mcu.I2C, Gsensori2cId, 23, 2, 0)
-    mcu.altfun(mcu.I2C, Gsensori2cId, 24, 2, 0)
-    mcu.altfun(mcu.I2C, es8311i2cId, 13, 2, 0)
-    mcu.altfun(mcu.I2C, es8311i2cId, 14, 2, 0)
+
     local codecIsInit = false
     while true do
         local result, param1, param2 = sys.waitUntil("CONTROL")

+ 77 - 33
module/Air8000/project/整机开发板出厂工程/user/airble.lua

@@ -5,23 +5,24 @@ dhcpsrv =  require("dhcpsrv")
 httpplus = require("httpplus")
 local run_state = false  -- 判断本UI DEMO 是否运行
 local ble_state = "未初始"
+local ble_flag = "false"
 
 local Characteristic1 = "EA01"
-local Characteristic1_read = nil
-local Characteristic1_write = nil
+local Characteristic1_read = ""
+local Characteristic1_write = ""
 local Characteristic2 = "EA02"
-local Characteristic2_write = nil
+local Characteristic2_write = ""
 local Characteristic3 = "EA03"
-local Characteristic3_read = nil
+local Characteristic3_read = ""
 local Characteristic4 = "EA04"
-local Characteristic4_read = nil
-local Characteristic4ind = nil
+local Characteristic4_read = ""
+local Characteristic4ind = ""
 
 
 local att_db = nil
 
 
-ble_stat = false
+
 
 local function set_att_db()
     att_db = { -- Service
@@ -42,47 +43,75 @@ end
 
 local function ble_callback(dev, evt, param)
     if evt == ble.EVENT_CONN then
-        log.info("ble", "connect 成功", param, param and param.addr and param.addr:toHex() or "unknow")
-        ble_stat = true
+        ble_state = "蓝牙链接成功"
+        log.info("ble", ble_state, param, param and param.addr and param.addr:toHex() or "unknow")
+        ble_flag = true
     elseif evt == ble.EVENT_DISCONN then
-        log.info("ble", "disconnect")
-        ble_stat = false
+        ble_state = "蓝牙已断开"
+        log.info("ble", ble_state)
+        ble_flag = false
         -- 1秒后重新开始广播
         sys.timerStart(function() dev:adv_start() end, 1000)
-    elseif evt == ble.EVENT_WRITE_REQ then
+    elseif evt == ble.EVENT_WRITE then
         -- 收到写请求
-        log.info("ble", "接收到写请求", param.uuid_service:toHex(), param.data:toHex())
-        if param.uuid_service == Characteristic1 then
+        ble_state = "接收到写请求"
+        log.info("ble", "接收到写请求", param.uuid_service:toHex(), param.data:toHex(),param.uuid_characteristic:toHex())
+        if param.uuid_characteristic:toHex() == Characteristic1 then
             Characteristic1_write = param.data:toHex()
-        elseif param.uuid_service == Characteristic2 then
+        elseif param.uuid_characteristic:toHex() == Characteristic2 then
             Characteristic2_write = param.data:toHex()
         end
 
     end
 end
 
+local function write_read()
+    local wr = {
+        uuid_service = string.fromHex("FA00"),
+        uuid_characteristic = string.fromHex("EA01"), 
+    }
+    ble_device:write_value(wr, "FA00EA01 HELLO" .. os.date())
+
+    wr = {
+        uuid_service = string.fromHex("FA00"),
+        uuid_characteristic = string.fromHex("EA03"), 
+    }   
+    ble_device:write_value(wr, "FA00EA03 HELLO" .. os.date())
+    
+    wr = {
+        uuid_service = string.fromHex("FA00"),
+        uuid_characteristic = string.fromHex("EA04"), 
+    }
+    ble_device:write_value(wr, "FA00EA04 HELLO" .. os.date())
+    
+end
 
 local function ble_peripheral_setup()
     local ret = 0
     set_att_db()
     sys.wait(500)
-    log.info("开始初始化蓝牙核心")
+    
+    ble_state = "开始初始化蓝牙核心"
+    log.info(ble_state)
     bluetooth_device = bluetooth.init()
     sys.wait(100)
-    log.info("初始化BLE功能")
+    ble_state = "初始化BLE功能"
+    log.info(ble_state)
     ble_device = bluetooth_device:ble(ble_callback)
     if ble_device == nil then
-        log.error("当前固件不支持完整的BLE")
+        ble_state = "当前固件不支持完整的BLE"
+        log.error(ble_state)
         return
     end
     sys.wait(100)
-
-    log.info('开始创建GATT')
+    ble_state = '开始创建GATT'
+    log.info(ble_state)
     ret = ble_device:gatt_create(att_db)
     log.info("创建的GATT", ret)
 
     sys.wait(100)
-    log.info("开始设置广播内容")
+    ble_state = "开始设置广播内容"
+    log.info(ble_state)
     ble_device:adv_create({
         addr_mode = ble.PUBLIC,
         channel_map = ble.CHNLS_ALL,
@@ -90,24 +119,38 @@ local function ble_peripheral_setup()
         intv_max = 120,
         adv_data = {
             {ble.FLAGS, string.char(0x06)},
-            {ble.COMPLETE_LOCAL_NAME, "LuatOS123"},
+            {ble.COMPLETE_LOCAL_NAME, "LuatOS_Air8000"},
             {ble.SERVICE_DATA, string.fromHex("FE01")},
             {ble.MANUFACTURER_SPECIFIC_DATA, string.fromHex("05F0")}
         }
     })
-
     sys.wait(100)
-    log.info("开始广播")
+    ble_state = "开始广播"
+    log.info(ble_state)
     ble_device:adv_start()
-
+    write_read()
 end
 
-local function start_notify()
+local function start_notify_and_ind()
+    if ble_flag then
+        local wt = {
+            uuid_service = string.fromHex("FA00"),
+            uuid_characteristic = string.fromHex("EA01"), 
+        }
+        local result = ble_device:write_notify(wt, "123456" .. os.date())
+        log.info("ble", "发送通知数据", result)
 
+        local wi = {
+            uuid_service = string.fromHex("FA00"),
+            uuid_characteristic = string.fromHex("EA04"), 
+        }
+        local result = ble_device:write_indicate(wi, "please read" .. os.date())
+        log.info("ble", "发送指示数据", result)
+        ble_state = "通知指示数据已经发送完毕"
+    end
 end
 
 
-
 function airble.run()       
     log.info("airble.run")
     lcd.setFont(lcd.font_opposansm12_chinese) -- 设置中文字体
@@ -117,10 +160,10 @@ function airble.run()
         sys.wait(10)
         lcd.clear(_G.bkcolor) 
         lcd.drawStr(0,80,"当前蓝牙状态:" .. ble_state )
-        lcd.drawStr(0,100,"特征:" .. Characteristic1  .. ",可读数据为:" ..  Characteristic1_read.. "被写入数据为:" .. Characteristic1_write)
-        lcd.drawStr(0,120,"特征:" .. Characteristic2   .. "被写入数据为:" .. Characteristic2_write)
-        lcd.drawStr(0,140,"特征:" .. Characteristic3   .. ",可读数据为:" .. Characteristic3_read)
-        lcd.drawStr(0,160,"特征:" .. Characteristic4   .. "可读数据为:" .. Characteristic4_read)
+        lcd.drawStr(0,100,"服务:FA00,特征:" .. Characteristic1  .. ",可读数据为:" ..  Characteristic1_read.. "被写入数据为:" .. Characteristic1_write)
+        lcd.drawStr(0,120,"服务:FA00,特征:" .. Characteristic2   .. "被写入数据为:" .. Characteristic2_write)
+        lcd.drawStr(0,140,"服务:FA00,特征:" .. Characteristic3   .. ",可读数据为:" .. Characteristic3_read)
+        lcd.drawStr(0,160,"服务:FA00,特征:" .. Characteristic4   .. "可读数据为:" .. Characteristic4_read)
 
 
 
@@ -136,10 +179,11 @@ end
 
 
 function airble.tp_handal(x,y,event)       
+    log.info("airble.tp_handal",x,y)
     if x > 20 and  x < 100 and y > 360  and  y < 440 then
         run_state = false
-    elseif x > 130 and  x < 230 and y > 397  and  y < 444 then
-        sysplus.taskInitEx(start_notify, "start_notify")
+    elseif x > 130 and  x < 239 and y > 350  and  y < 393 then
+        sysplus.taskInitEx(start_notify_and_ind, "start_notify_and_ind")
     end
 end
 

+ 1 - 1
module/Air8000/project/整机开发板出厂工程/user/main.lua

@@ -484,7 +484,7 @@ local function draw()
     draw_power()
   elseif cur_fun == "multi_network" then
     draw_multi_network()    
-  elseif cur_fun == "multi_network" then
+  elseif cur_fun == "airble" then
     draw_airble()
   end
   

+ 213 - 0
script/libs/fota_wifi.lua

@@ -0,0 +1,213 @@
+--[[
+-- @module fota_wifi
+-- @summary 用于Air8000/8000A/8000W型号模组自动升级WIFI
+-- @version 1.0.1
+-- @date    2025.6.26
+-- @author  tuoyiheng
+-- @usage
+--注:使用时在创建的一个task处理函数中直接调用fota_wifi.request()即可开始执行WiFi升级任务
+--用法实例
+local fota_wifi = require("fota_wifi")
+
+local function wifi_fota_task_func()
+    -- ...此处省略很多代码
+
+    local result = fota_wifi.request()
+    if result then
+        log.info("fota_wifi", "升级任务执行成功")
+    else
+        log.info("fota_wifi", "升级任务执行失败")
+    end
+
+    -- ...此处省略很多代码
+end
+
+-- 两种调用方式均可,任选其一
+-- sys.taskInit(wifi_fota_task_func)
+sysplus.taskInitEx(wifi_fota_task_func, "wifi_fota_task")
+]]
+local fota_wifi = {}
+local is_request = false -- 标记是否正在执行request任务
+local fota_result = false -- 记录fota任务的执行结果
+
+-- 判断是否为空
+local function is_nil(s)
+    return s == nil or s == ""
+end
+
+-- 判断json是否合法
+local function is_json(str)
+    local success, result = pcall(json.decode, str)
+    return success and type(result) == "table"
+end
+
+-- 解析服务器响应的json数据
+local function parse_response(body)
+    if not body or body == "" then
+        log.error("fota_wifi", "返回的body为空")
+        return nil
+    end
+
+    local success, json_body = pcall(json.decode, body)
+    if success and type(json_body) == "table" then
+        log.info("fota_wifi", "解析服务器响应成功")
+        return json_body
+    else
+        log.error("fota_wifi", "解析服务器响应失败,body内容:", body)
+        return nil
+    end
+end
+
+-- 判断是否需要升级,返回true或false
+local function need_fota(version, server_version)
+    local version_num = tonumber(version)
+    local server_version_num = tonumber(server_version)
+    if version_num < server_version_num then
+        return true
+    end
+    return false
+end
+
+-- 下载升级文件,支持断点续传
+local function download_file(url)
+    local file_path = "/ram/fotawifi.bin"
+    local downloaded_size = 0
+
+    -- 检查文件是否存在,获取已下载的大小
+    if io.exists(file_path) then
+        downloaded_size = io.fileSize(file_path)
+        log.info("fota_wifi", "检测到未完成的下载,已下载大小:", downloaded_size)
+    end
+
+    -- 设置请求头,支持断点续传
+    local headers = {}
+    if downloaded_size > 0 then
+        headers["Range"] = "bytes=" .. downloaded_size .. "-"
+    end
+
+    local code, headers, body = http.request("GET", url, headers, nil, nil).wait()
+    if code == 200 or code == 206 then
+        -- 开始写入文件
+        local file_mode = downloaded_size > 0 and "a+" or "w+"
+        local file = io.open(file_path, file_mode)
+        if file then
+            file:seek("end", downloaded_size)
+            file:write(body)
+            file:close()
+
+            -- 判断文件是否下载完整
+            local file_size = io.fileSize(file_path)
+            local content_length = tonumber(headers["content-length"] or headers["Content-Length"])
+            if file_size >= (content_length or file_size) then
+                log.info("fota_wifi", "下载升级文件成功,文件路径:", file_path)
+                return file_path
+            else
+                log.info("fota_wifi", "下载中...当前大小:", file_size, "目标大小:", content_length)
+            end
+        else
+            log.error("fota_wifi", "无法创建文件")
+            -- 删除不完整的文件
+            os.remove(file_path)
+        end
+    else
+        log.error("fota_wifi", "下载失败,状态码:", code)
+        -- 删除不完整的文件
+        if io.exists(file_path) then
+            os.remove(file_path)
+        end
+
+    end
+    return nil
+end
+
+-- 执行升级操作
+local function fota_start(file_path)
+    -- 执行airlink.sfota操作
+    local result = airlink.sfota(file_path)
+    if result then
+        log.info("fota_wifi", "升级成功")
+        -- 释放文件占用的空间
+        -- 因为sfota是异步执行的,所以这里不能用os.remove()删除文件
+        file_path = nil
+        return true
+    else
+        log.error("fota_wifi", "升级失败")
+        os.remove(file_path)
+        return false
+    end
+end
+
+--[[
+Air8000系列模组自动升级wifi
+@api fota_wifi.request()
+@number 挂载ina226的i2c总线id
+@return bool 成功返回true
+@usage
+local result = fota_wifi.request()
+if result then
+    log.info("fota_wifi", "升级任务执行成功")
+else
+    log.info("fota_wifi", "升级任务执行失败")
+end
+]]
+function fota_wifi.request()
+    if is_request then
+        log.warn("fota_wifi", "升级任务正在执行中,请勿重复调用")
+        return false
+    end
+
+    is_request = true
+    fota_result = false
+
+    -- 构建请求URL
+    local url = "http://wififota.openluat.com/air8000/update.json"
+    local imei = is_nil(mobile.imei()) and "未知imei" or mobile.imei()
+    local version = is_nil(airlink.sver()) and "未知版本" or airlink.sver()
+    local muid = is_nil(mobile.muid()) and "未知muid" or mobile.muid()
+    local hw = is_nil(hmeta.hwver()) and "未知硬件版本" or hmeta.hwver()
+    local request_url = string.format("%s?imei=%s&version=%s&muid=%s&hw=%s", url, imei, version, muid, hw)
+
+    log.info("fota_wifi", "正在请求升级信息, URL:", request_url)
+
+    -- 发送HTTP请求获取服务器响应
+    local code, headers, body = http.request("GET", request_url, {}, nil, nil).wait()
+    if code == 200 then
+        log.info("fota_wifi", "获取服务器响应成功")
+        -- 打印返回的body内容
+        -- log.info("fota_wifi", "body:", body)
+        -- 解析服务器响应的json数据
+        local response = parse_response(body)
+        if response then
+            -- 获取服务器返回的版本号和下载链接
+            local server_version = response.version
+            local download_url = response.url
+
+            -- 获取本地版本号
+            local local_version = airlink.sver()
+
+            -- 判断是否需要升级
+            if need_fota(local_version, server_version) then
+                log.info("fota_wifi", "需要升级, 本地版本:", local_version, "服务器版本:", server_version)
+                -- 下载升级文件
+                local file_path = download_file(download_url)
+                if file_path then
+                    -- 开始升级
+                    fota_result = fota_start(file_path)
+                end
+            else
+                log.info("fota_wifi", "当前已是最新WIFI固件")
+                fota_result = true
+            end
+        else
+            log.error("fota_wifi", "解析服务器响应失败")
+        end
+    else
+        log.error("fota_wifi", "获取服务器响应失败,状态码:", code)
+    end
+
+    -- 释放请求标记
+    is_request = false
+    return fota_result
+end
+
+return fota_wifi