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

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

alienwalker 3 месяцев назад
Родитель
Сommit
1af112aec5
100 измененных файлов с 8998 добавлено и 8374 удалено
  1. 1 1
      components/airlink/include/luat_airlink.h
  2. 7 0
      components/airlink/include/luat_airlink_fota.h
  3. 41 15
      components/airlink/src/driver/luat_airlink_drv_uart.c
  4. 56 1
      components/airlink/src/luat_airlink_fota.c
  5. 84 30
      components/airlink/src/task/luat_airlink_spi_master_task.c
  6. 3 3
      components/fatfs/diskio.h
  7. 241 183
      components/fatfs/ff.c
  8. 91 73
      components/fatfs/ff.h
  9. 27 10
      components/fatfs/ffconf.h
  10. 3600 6381
      components/fatfs/ffunicode.c
  11. 12 0
      components/lcd/luat_lcd.c
  12. 1 1
      components/network/adapter/luat_network_adapter.c
  13. 13 4
      components/network/errdump/luat_lib_errdump.c
  14. 39 18
      components/network/httpsrv/src/luat_httpsrv_lwip.c
  15. 0 327
      components/network/posix/luat_network_posix.c
  16. 0 20
      components/network/posix/luat_network_posix.h
  17. 6 0
      components/pins/src/luat_pins.c
  18. 48 56
      components/sms/binding/luat_lib_sms.c
  19. 10 1
      components/wlan/luat_lib_wlan.c
  20. 6 1
      lua/src/loslib.c
  21. 9 0
      luat/modules/luat_lib_rtos.c
  22. 2 2
      module/Air780E/demo/fota2/main.lua
  23. 58 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/audio_drv.lua
  24. 246 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/cc_app.lua
  25. 78 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/main.lua
  26. 240 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/pins_Air780EHM.json
  27. 210 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/readme.md
  28. 58 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/audio_drv.lua
  29. 246 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/cc_app.lua
  30. 79 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/main.lua
  31. 198 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/readme.md
  32. 0 126
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/main.lua
  33. 16 7
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/AirGPIO_1000.lua
  34. 20 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/gpio_app.lua
  35. 14 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/main.lua
  36. 13 8
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/readme.md
  37. 15 11
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/AirKEY_1000.lua
  38. 21 13
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/key_app.lua
  39. 14 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/main.lua
  40. 10 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/readme.md
  41. 16 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua
  42. 14 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/main.lua
  43. 19 8
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/readme.md
  44. 14 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/sht30_app.lua
  45. 16 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua
  46. 15 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/main.lua
  47. 20 7
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/readme.md
  48. 14 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/voc_app.lua
  49. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/10.amr
  50. 92 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/main.lua
  51. 240 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/pins_air780ehm.json
  52. 101 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_file.lua
  53. 108 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_stream.lua
  54. 104 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_tts.lua
  55. 212 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/readme.md
  56. 84 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_file.lua
  57. 55 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_stream.lua
  58. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/sample-6s.mp3
  59. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/test.pcm
  60. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/10.amr
  61. 20 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/main.lua
  62. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_file.lua
  63. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_stream.lua
  64. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_tts.lua
  65. 204 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/readme.md
  66. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_file.lua
  67. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_stream.lua
  68. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/sample-6s.mp3
  69. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/test.pcm
  70. 0 541
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/exaudio.lua
  71. 0 106
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/readme.md
  72. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/音频硬件框架.png
  73. 124 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/bit64_app.lua
  74. 55 98
      module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/main.lua
  75. 85 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/readme.md
  76. 282 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/crypto_app.lua
  77. 55 226
      module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/main.lua
  78. 152 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/readme.md
  79. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/air_srv_fota.lua
  80. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/psm_power_fota.lua
  81. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/update.lua
  82. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/self_server/customer_srv_fota.lua
  83. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/self_server/psm_power_fota.lua
  84. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/self_server/update.lua
  85. 282 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/flash_fs_io.lua
  86. 74 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/http_download_flash.lua
  87. 78 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/main.lua
  88. 143 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/readme.md
  89. 8 10
      module/Air780EHM_Air780EHV_Air780EGH/demo/http/http_app.lua
  90. 1 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/lbsLoc2/lbsloc2_app.lua
  91. 4 4
      module/Air780EHM_Air780EHV_Air780EGH/demo/luatos_framework/hello_luatos/readme.md
  92. 4 4
      module/Air780EHM_Air780EHV_Air780EGH/demo/luatos_framework/luatos_task/readme.md
  93. 74 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/mcu/main.lua
  94. 64 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/mcu/mcu_test.lua
  95. 53 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/mcu/readme.md
  96. 75 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/main.lua
  97. 82 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/readme.md
  98. 1 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/send.bin
  99. 93 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/xmodem_demo.lua
  100. 13 21
      module/Air780EHM_Air780EHV_Air780EGH/demo/xxtea/main.lua

+ 1 - 1
components/airlink/include/luat_airlink.h

@@ -79,7 +79,7 @@ void luat_airlink_task_start(void);
 void luat_airlink_print_buff(const char* tag, uint8_t* buff, size_t len);
 void luat_airlink_on_data_recv(uint8_t *data, size_t len);
 
-void airlink_wait_for_slave_ready(size_t timeout_ms);
+void airlink_wait_for_slave_reply(size_t timeout_ms);
 void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rxbuff);
 void airlink_wait_and_prepare_data(uint8_t *txbuff);
 

+ 7 - 0
components/airlink/include/luat_airlink_fota.h

@@ -3,6 +3,13 @@
 #include "luat_base.h"
 #include "luat_fs.h"
 
+#define AIRLINK_FOTA_SUCCESS            (0)
+#define AIRLINK_FOTA_NO_MEM             (1)
+#define AIRLINK_FOTA_OPEN_FILE_FAIL     (2)
+
+
+
+
 typedef struct luat_airlink_fota {
     uint32_t state;
     size_t total_size;

+ 41 - 15
components/airlink/src/driver/luat_airlink_drv_uart.c

@@ -37,22 +37,48 @@ int luat_airlink_drv_uart_setup(luat_uart_t* conf) {
 }
 
 int luat_airlink_drv_uart_write(int uart_id, void* data, size_t length) {
-    // LLOGD("执行uart write %d %p %d", uart_id, data, length);
-    uint64_t luat_airlink_next_cmd_id = luat_airlink_get_next_cmd_id();
-    size_t total_len = length + sizeof(luat_airlink_cmd_t) + 8 + 1;
-    airlink_queue_item_t item = {.len = total_len};
-    luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x401, length + 1 + 8);
-    if (cmd == NULL) {
-        LLOGE("内存分配失败!!");
-        return 0;
+    if(length <= 1536) {
+        // LLOGD("执行uart write %d %p %d", uart_id, data, length);
+        uint64_t luat_airlink_next_cmd_id = luat_airlink_get_next_cmd_id();
+        size_t total_len = length + sizeof(luat_airlink_cmd_t) + 8 + 1;
+        airlink_queue_item_t item = {.len = total_len};
+        luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x401, length + 1 + 8);
+        if (cmd == NULL) {
+            LLOGE("内存分配失败!!");
+            return 0;
+        }
+        memcpy(cmd->data, &luat_airlink_next_cmd_id, 8);
+        uint8_t tmp = (uint8_t)uart_id;
+        memcpy(cmd->data + 8, &tmp, 1);
+        memcpy(cmd->data + 9, data, length);
+        item.cmd = cmd;
+        int ret = luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
+        return ret == 0 ? length : 0;
+    } else {
+        // 如果数据长度大于1536,分段发送
+        size_t segment_size = 1535;  // 使用1535避免无限递归
+        const char* segment_data = (const char*)data;
+        size_t remaining = length;
+        size_t sent_total = 0;
+
+        while (remaining > 0) {
+            size_t current_segment = (remaining > segment_size) ? segment_size : remaining;
+
+            int ret = luat_airlink_drv_uart_write(uart_id, (void*)segment_data, current_segment);
+            if (ret <= 0) {
+                return sent_total;
+            }
+            sent_total += ret;
+            segment_data += ret;
+            remaining -= ret;
+
+            if (ret < current_segment) {
+                break;
+            }
+        }
+
+        return sent_total;
     }
-    memcpy(cmd->data, &luat_airlink_next_cmd_id, 8);
-    uint8_t tmp = (uint8_t)uart_id;
-    memcpy(cmd->data + 8, &tmp, 1);
-    memcpy(cmd->data + 9, data, length);
-    item.cmd = cmd;
-    int ret = luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
-    return ret == 0 ? length : 0;
 }
 
 int luat_airlink_drv_uart_close(int uart_id) {

+ 56 - 1
components/airlink/src/luat_airlink_fota.c

@@ -8,6 +8,46 @@
 #define LUAT_LOG_TAG "airlink.fota"
 #include "luat_log.h"
 
+#ifdef __LUATOS__
+#include "luat_msgbus.h"
+/*
+@sys_pub airlink
+AIRLINK升级结束消息 2025/10/24启用
+AIRLINK_SFOTA_DONE
+@bool result, 升级成功为true,否则为false
+@string reason, 失败原因,当前取值有"no_memory" 内存不足, "file_error" 文件打开异常
+@usage
+-- 订阅式
+sys.subscribe("AIRLINK_SFOTA_DONE", function(result, reason)
+    log.info("airlink fota", result, reason)
+end)
+*/
+static int airlink_sfota_lua_cb(lua_State *L, void *ptr)
+{
+    rtos_msg_t *msg = (rtos_msg_t *)lua_topointer(L, -1);
+    lua_getglobal(L, "sys_pub");
+    lua_pushstring(L, "AIRLINK_SFOTA_DONE");
+    lua_pushboolean(L, msg->arg1 == AIRLINK_FOTA_SUCCESS ? 1 : 0);
+    switch (msg->arg1)
+    {
+    case AIRLINK_FOTA_SUCCESS:
+        lua_call(L, 2, 0);
+        break;
+    case AIRLINK_FOTA_NO_MEM:
+        lua_pushstring(L, "no_memory");
+        lua_call(L, 3, 0);
+        break;
+    case AIRLINK_FOTA_OPEN_FILE_FAIL:
+        lua_pushstring(L, "file_error");
+        lua_call(L, 3, 0);
+        break;
+    default:
+        break;
+    }
+    return 0;
+}
+#endif
+
 luat_airlink_fota_t *g_airlink_fota;
 
 int luat_airlink_fota_init(luat_airlink_fota_t *ctx)
@@ -60,7 +100,7 @@ static void pack_and_send(uint16_t cmd_id, uint8_t *data, size_t len)
     luat_airlink_data_pack(cmd, sizeof(luat_airlink_cmd_t) + len, s_airlink_fota_txbuff);
     // LLOGD("发送sFOTA %d %d", cmd->cmd, cmd->len);
     airlink_transfer_and_exec(s_airlink_fota_txbuff, s_airlink_fota_rxbuff);
-    airlink_wait_for_slave_ready(5000); // 等待从机回复完成接收
+    airlink_wait_for_slave_reply(5000); // 等待从机回复完成接收
 
     memset(s_airlink_fota_txbuff, 0, AIRLINK_SFOTA_BUFF_SIZE);
 }
@@ -69,6 +109,7 @@ void airlink_sfota_exec(void)
 {
     size_t wait_timeout = 0;
     int ret = 0;
+    uint8_t fota_ret = AIRLINK_FOTA_SUCCESS;
     g_airlink_fota->total_size = luat_fs_fsize(g_airlink_fota->path);
     LLOGI("开始执行sFOTA file size %ld", g_airlink_fota->total_size);
     uint32_t tmpv = 0;
@@ -77,6 +118,7 @@ void airlink_sfota_exec(void)
     if (fd == NULL)
     {
         LLOGE("打开sFOTA文件失败 %s", g_airlink_fota->path);
+        fota_ret = AIRLINK_FOTA_OPEN_FILE_FAIL;
         goto clean;
     }
     if (s_airlink_fota_txbuff == NULL)
@@ -87,6 +129,7 @@ void airlink_sfota_exec(void)
         if (s_airlink_fota_txbuff == NULL || s_airlink_fota_rxbuff == NULL || s_airlink_fota_cmdbuff == NULL)
         {
             LLOGE("申请sFOTA内存失败");
+            fota_ret = AIRLINK_FOTA_NO_MEM;
             goto clean;
         }
         memset(s_airlink_fota_txbuff, 0, AIRLINK_SFOTA_BUFF_SIZE);
@@ -187,6 +230,8 @@ void airlink_sfota_exec(void)
     luat_rtos_task_sleep(wait_timeout);
     LLOGI("FOTA执行完毕");
 
+
+
 clean:
     g_airlink_fota->state = 0;
     if (fd)
@@ -208,5 +253,15 @@ clean:
         luat_heap_opt_free(AIRLINK_MEM_TYPE, s_airlink_fota_cmdbuff);
         s_airlink_fota_cmdbuff = NULL;
     }
+
+#ifdef __LUATOS__
+    rtos_msg_t msg = {
+        .handler = airlink_sfota_lua_cb,
+        .arg1 = fota_ret,
+        .arg2 = 0,
+        .ptr = NULL
+    };
+    luat_msgbus_put(&msg, 0);
+#endif
     return;
 }

+ 84 - 30
components/airlink/src/task/luat_airlink_spi_master_task.c

@@ -41,11 +41,15 @@ static uint8_t thread_rdy;
 static uint8_t spi_rdy;
 static luat_rtos_task_handle spi_task_handle;
 
+#if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+extern luat_airlink_dev_info_t g_airlink_self_dev_info;
+#endif
 static uint8_t basic_info[256];
 
 // static uint32_t is_waiting_queue = 0;
 
 static luat_rtos_queue_t evt_queue;
+static luat_rtos_queue_t rdy_evt_queue;  // 专门用于RDY事件(id=6)的队列
 
 extern luat_airlink_irq_ctx_t g_airlink_irq_ctx;
 extern luat_airlink_irq_ctx_t g_airlink_wakeup_irq_ctx;
@@ -53,6 +57,25 @@ extern luat_airlink_irq_ctx_t g_airlink_wakeup_irq_ctx;
 luat_airlink_irq_ctx_t g_airlink_irq_ctx;
 luat_airlink_irq_ctx_t g_airlink_wakeup_irq_ctx;
 
+// RDY引脚中断等待相关变量
+static volatile uint8_t rdy_ready_flag = 0;           // RDY就绪标志
+
+// RDY引脚下降沿中断处理函数,设置就绪标志并发送事件
+__USER_FUNC_IN_RAM__ static int rdy_pin_irq_handler(void* param)
+{
+    // 设置RDY就绪标志
+    if (rdy_evt_queue == NULL) {
+        return 0;
+    }
+    rdy_ready_flag = 1;
+    // 发送通知事件,告知任务RDY已就绪
+    luat_event_t evt = {.id = 6};
+    luat_rtos_queue_send(rdy_evt_queue, &evt, sizeof(evt), 0);
+    
+    // LLOGD("RDY中断触发,设置就绪标志");
+    return 0;
+}
+
 __USER_FUNC_IN_RAM__ static int slave_irq_cb(void *data, void *args)
 {
     uint32_t len = 0;
@@ -116,22 +139,31 @@ void luat_airlink_spi_master_pin_setup(void)
         .CPHA = 1,
         .CPOL = 1,
         .dataw = 8,
-        .bit_dict = 0,
+        .bit_dict = 1, // MSB, 大部分平台也只支持MSB
         .master = 1,
         .mode = 1, // mode设置为1,全双工
         .bandrate = g_airlink_spi_conf.speed > 0 ? g_airlink_spi_conf.speed : 31000000,
         .cs = 255};
-    luat_pm_iovolt_ctrl(0, 3300);
+    // luat_pm_iovolt_ctrl(0, 3300);
 
     luat_spi_setup(&spi_conf);
     luat_gpio_cfg_t gpio_cfg = {0};
 
     // 从机准备好脚
+    // luat_gpio_set_default_cfg(&gpio_cfg);
+    // gpio_cfg.pin = AIRLINK_SPI_RDY_PIN;
+    // gpio_cfg.mode = LUAT_GPIO_INPUT;
+    // gpio_cfg.irq_type = LUAT_GPIO_FALLING_IRQ;
+    // gpio_cfg.pull = 0;
+    // luat_gpio_open(&gpio_cfg);
+
+    // 设置RDY引脚中断,
     luat_gpio_set_default_cfg(&gpio_cfg);
     gpio_cfg.pin = AIRLINK_SPI_RDY_PIN;
-    gpio_cfg.mode = LUAT_GPIO_INPUT;
-    gpio_cfg.irq_type = LUAT_GPIO_FALLING_IRQ;
-    gpio_cfg.pull = 0;
+    gpio_cfg.mode = LUAT_GPIO_IRQ;
+    gpio_cfg.irq_type = LUAT_GPIO_FALLING_IRQ;  // 下降沿中断
+    gpio_cfg.pull = LUAT_GPIO_PULLUP;
+    gpio_cfg.irq_cb = rdy_pin_irq_handler;
     luat_gpio_open(&gpio_cfg);
 
     // CS片选脚
@@ -187,7 +219,21 @@ __USER_FUNC_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rx
     airlink_link_data_t *link = NULL;
 
     g_airlink_statistic.tx_pkg.total++;
+    // 拉低片选, 准备发送数据
+    luat_gpio_set(AIRLINK_SPI_CS_PIN, 0);
+    // 发送数据
     luat_spi_transfer(MASTER_SPI_ID, (const char *)txbuff, TEST_BUFF_SIZE, (char *)rxbuff, TEST_BUFF_SIZE);
+    // 拉高片选之前,先检查一下是否有RDY事件未处理,如果有,则全部清除
+    size_t qlen = 0;
+    luat_rtos_queue_get_cnt(rdy_evt_queue, &qlen);
+    if (qlen > 0) {
+        // LLOGW("发送数据后发现有%d个RDY事件未处理", (int)qlen);
+        // 清除掉这些冗余的RDY事件
+        luat_event_t evt = {0};
+        luat_rtos_queue_recv(rdy_evt_queue, &evt, sizeof(evt), 0);
+    }
+    rdy_ready_flag = 0;
+    // 拉高片选, 数据发送完毕
     luat_gpio_set(AIRLINK_SPI_CS_PIN, 1);
     // luat_airlink_print_buff("RX", rxbuff, 32);
     // 对接收到的数据进行解析
@@ -209,30 +255,16 @@ __USER_FUNC_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rx
     }
 }
 
-__USER_FUNC_IN_RAM__ void airlink_wait_for_slave_ready(size_t timeout_ms)
+__USER_FUNC_IN_RAM__ void airlink_wait_for_slave_reply(size_t timeout_ms)
 {
-    luat_gpio_set(AIRLINK_SPI_CS_PIN, 0); // 拉低片选, 等待从机就绪
-    int tmpval = 0;
-    for (size_t i = 0; i < timeout_ms; i++)
+    luat_event_t event = {0};
+    while (1)
     {
-        tmpval = luat_gpio_get(AIRLINK_SPI_RDY_PIN);
-        if (tmpval == 1)
-        {
-            g_airlink_statistic.wait_rdy.total++;
-            if (g_airlink_debug)
-            {
-                tnow = luat_mcu_tick64_ms();
-                if (tnow - warn_slave_no_ready > 1000)
-                {
-                    warn_slave_no_ready = tnow;
-                    LLOGD("从机未就绪,等1ms");
-                }
-            }
-            luat_rtos_task_sleep(1);
-            continue;
+        event.id = 0;
+        luat_rtos_queue_recv(rdy_evt_queue, &event, sizeof(luat_event_t), 10);
+        if (event.id != 0) {
+            break;
         }
-        // LLOGD("从机已就绪!! %s %s", __DATE__, __TIME__);
-        break;
     }
 }
 
@@ -321,6 +353,9 @@ __USER_FUNC_IN_RAM__ void airlink_wait_and_prepare_data(uint8_t *txbuff)
     else
     {
         // LLOGD("填充PING数据");
+        #if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+        memcpy(basic_info + sizeof(luat_airlink_cmd_t), &g_airlink_self_dev_info, sizeof(g_airlink_self_dev_info));
+        #endif
         luat_airlink_data_pack(basic_info, sizeof(basic_info), txbuff);
         queue_emtry_counter ++;
     }
@@ -334,11 +369,27 @@ __USER_FUNC_IN_RAM__ static void on_link_data_notify(airlink_link_data_t* link)
     }
 }
 
+#if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+static void send_devinfo_update_evt(void) {
+    airlink_queue_item_t item = {0};
+    // 发送空消息, 会自动转为devinfo消息
+    luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item); 
+}
+#endif
 
 __USER_FUNC_IN_RAM__ static void spi_master_task(void *param)
 {
     // int i;
     // luat_event_t event = {0};
+    #if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+    luat_airlink_cmd_t *cmd = (luat_airlink_cmd_t *)basic_info;
+    cmd->cmd = 0x10;
+    cmd->len = 128;
+
+    extern void luat_airlink_devinfo_init();
+    luat_airlink_devinfo_init(send_devinfo_update_evt);
+    #endif
+
     luat_rtos_task_sleep(5); // 等5ms
     luat_airlink_spi_master_pin_setup();
     g_airlink_newdata_notify_cb = on_newdata_notify;
@@ -355,13 +406,13 @@ __USER_FUNC_IN_RAM__ static void spi_master_task(void *param)
 
         memset(s_txbuff, 0, TEST_BUFF_SIZE);
         airlink_wait_and_prepare_data(s_txbuff);
-        // slave_rdy = 0;
-        airlink_wait_for_slave_ready(1000); // 最多等1秒
 
+        // 立即发送数据给从机
         airlink_transfer_and_exec(s_txbuff, s_rxbuff);
-
         memset(s_rxbuff, 0, TEST_BUFF_SIZE);
-        // start = 0;
+
+        // 发送完成后,等待从机的响应/确认
+        airlink_wait_for_slave_reply(5000);
     }
 }
 
@@ -376,7 +427,10 @@ void luat_airlink_start_master(void)
     s_txbuff = luat_heap_opt_malloc(AIRLINK_MEM_TYPE, TEST_BUFF_SIZE);
     s_rxbuff = luat_heap_opt_malloc(AIRLINK_MEM_TYPE, TEST_BUFF_SIZE);
 
+    // 创建通用事件队列 (id=2,3等)
     luat_rtos_queue_create(&evt_queue, 4 * 1024, sizeof(luat_event_t));
+    // 创建专门的RDY事件队列 (id=6)
+    luat_rtos_queue_create(&rdy_evt_queue, 1, sizeof(luat_event_t));
     luat_rtos_task_create(&spi_task_handle, 8 * 1024, 50, "spi", spi_master_task, NULL, 0);
 }
 

+ 3 - 3
components/fatfs/diskio.h

@@ -1,5 +1,5 @@
 /*-----------------------------------------------------------------------/
-/  Low level disk interface modlue include file   (C)ChaN, 2019          /
+/  Low level disk interface modlue include file   (C)ChaN, 2025          /
 /-----------------------------------------------------------------------*/
 
 #ifndef _DISKIO_DEFINED
@@ -77,7 +77,7 @@ DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
 #define CTRL_EJECT			7	/* Eject media */
 #define CTRL_FORMAT			8	/* Create physical format on the media */
 
-/* MMC/SDC specific ioctl command */
+/* MMC/SDC specific ioctl command (Not used by FatFs) */
 #define MMC_GET_TYPE		10	/* Get card type */
 #define MMC_GET_CSD			11	/* Get CSD */
 #define MMC_GET_CID			12	/* Get CID */
@@ -87,7 +87,7 @@ DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
 #define ISDIO_WRITE			56	/* Write data to SD iSDIO register */
 #define ISDIO_MRITE			57	/* Masked write data to SD iSDIO register */
 
-/* ATA/CF specific ioctl command */
+/* ATA/CF specific ioctl command (Not used by FatFs) */
 #define ATA_GET_REV			20	/* Get F/W revision */
 #define ATA_GET_MODEL		21	/* Get model name */
 #define ATA_GET_SN			22	/* Get serial number */

Разница между файлами не показана из-за своего большого размера
+ 241 - 183
components/fatfs/ff.c


+ 91 - 73
components/fatfs/ff.h

@@ -1,8 +1,8 @@
 /*----------------------------------------------------------------------------/
-/  FatFs - Generic FAT Filesystem module  R0.15a                              /
+/  FatFs - Generic FAT Filesystem module  R0.16                               /
 /-----------------------------------------------------------------------------/
 /
-/ Copyright (C) 2024, ChaN, all right reserved.
+/ Copyright (C) 2025, ChaN, all right reserved.
 /
 / FatFs module is an open source software. Redistribution and use of FatFs in
 / source and binary forms, with or without modification, are permitted provided
@@ -20,7 +20,7 @@
 
 
 #ifndef FF_DEFINED
-#define FF_DEFINED	5380	/* Revision ID */
+#define FF_DEFINED	80386	/* Revision ID */
 
 #ifdef __cplusplus
 extern "C" {
@@ -127,51 +127,65 @@ extern const char* VolumeStr[FF_VOLUMES];	/* User defined volume ID table */
 #endif
 
 
+/* Current working directory structure (FFXCWDS) */
+
+#if FF_FS_EXFAT && FF_FS_RPATH
+#if FF_PATH_DEPTH < 1
+#error FF_PATH_DEPTH must not be zero
+#endif
+typedef struct {
+	DWORD	d_scl;		/* Directory start cluster (0:root dir) */
+	DWORD	d_size;		/* Size of directory (b7-b0: cluster chain status) (invalid if d_scl == 0) */
+	DWORD	nxt_ofs;	/* Offset of entry of next dir in this directory (invalid if last link) */
+} FFXCWDL;
+typedef struct {
+	UINT	depth;		/* Current directory depth (0:root dir) */
+	FFXCWDL	tbl[FF_PATH_DEPTH + 1];	/* Directory chain of current working directory path */
+} FFXCWDS;
+#endif
+
 
 /* Filesystem object structure (FATFS) */
 
 typedef struct {
-	BYTE	fs_type;		/* Filesystem type (0:blank filesystem object) */
-	BYTE	pdrv;			/* Volume hosting physical drive */
-	BYTE	ldrv;			/* Logical drive number (used only when FF_FS_REENTRANT) */
-	BYTE	n_fats;			/* Number of FATs (1 or 2) */
-	BYTE	wflag;			/* win[] status (1:dirty) */
-	BYTE	fsi_flag;		/* Allocation information control (b7:disabled, b0:dirty) */
-	WORD	id;				/* Volume mount ID */
-	WORD	n_rootdir;		/* Number of root directory entries (FAT12/16) */
-	WORD	csize;			/* Cluster size [sectors] */
+	BYTE	fs_type;	/* Filesystem type (0:not mounted) */
+	BYTE	pdrv;		/* Physical drive that holds this volume */
+	BYTE	ldrv;		/* Logical drive number (used only when FF_FS_REENTRANT) */
+	BYTE	n_fats;		/* Number of FATs (1 or 2) */
+	BYTE	wflag;		/* win[] status (b0:dirty) */
+	BYTE	fsi_flag;	/* Allocation information control (b7:disabled, b0:dirty) */
+	WORD	id;			/* Volume mount ID */
+	WORD	n_rootdir;	/* Number of root directory entries (FAT12/16) */
+	WORD	csize;		/* Cluster size [sectors] */
 #if FF_MAX_SS != FF_MIN_SS
-	WORD	ssize;			/* Sector size (512, 1024, 2048 or 4096) */
+	WORD	ssize;		/* Sector size (512, 1024, 2048 or 4096) */
 #endif
 #if FF_USE_LFN
-	WCHAR*	lfnbuf;			/* LFN working buffer */
-#endif
-#if FF_FS_EXFAT
-	BYTE*	dirbuf;			/* Directory entry block scratch pad buffer for exFAT */
+	WCHAR*	lfnbuf;		/* Pointer to LFN working buffer */
 #endif
 #if !FF_FS_READONLY
-	DWORD	last_clst;		/* Last allocated cluster (Unknown if >= n_fatent) */
-	DWORD	free_clst;		/* Number of free clusters (Unknown if >= n_fatent-2) */
+	DWORD	last_clst;	/* Last allocated cluster (invalid if >=n_fatent) */
+	DWORD	free_clst;	/* Number of free clusters (invalid if >=fs->n_fatent-2) */
 #endif
 #if FF_FS_RPATH
-	DWORD	cdir;			/* Current directory start cluster (0:root) */
+	DWORD	cdir;		/* Current directory start cluster (0:root) */
+#endif
+	DWORD	n_fatent;	/* Number of FAT entries (number of clusters + 2) */
+	DWORD	fsize;		/* Number of sectors per FAT */
+	LBA_t	winsect;	/* Current sector appearing in the win[] */
+	LBA_t	volbase;	/* Volume base sector */
+	LBA_t	fatbase;	/* FAT base sector */
+	LBA_t	dirbase;	/* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
+	LBA_t	database;	/* Data base sector */
 #if FF_FS_EXFAT
-	DWORD	cdc_scl;		/* Containing directory start cluster (invalid when cdir is 0) */
-	DWORD	cdc_size;		/* b31-b8:Size of containing directory, b7-b0: Chain status */
-	DWORD	cdc_ofs;		/* Offset in the containing directory (invalid when cdir is 0) */
-#endif
+	LBA_t	bitbase;	/* Allocation bitmap base sector */
+	BYTE*	dirbuf;		/* Pointer to directory entry block buffer */
+#if FF_FS_RPATH
+	FFXCWDS	xcwds;		/* Crrent working directory structure */
+	FFXCWDS	xcwds2;		/* Working buffer to follow the path */
 #endif
-	DWORD	n_fatent;		/* Number of FAT entries (number of clusters + 2) */
-	DWORD	fsize;			/* Number of sectors per FAT */
-	LBA_t	volbase;		/* Volume base sector */
-	LBA_t	fatbase;		/* FAT base sector */
-	LBA_t	dirbase;		/* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
-	LBA_t	database;		/* Data base sector */
-#if FF_FS_EXFAT
-	LBA_t	bitbase;		/* Allocation bitmap base sector */
 #endif
-	LBA_t	winsect;		/* Current sector appearing in the win[] */
-	BYTE	win[FF_MAX_SS];	/* Disk access window for Directory, FAT (and file data at tiny cfg) */
+	BYTE	win[FF_MAX_SS];	/* Disk access window for directory, FAT (and file data in tiny cfg) */
 } FATFS;
 
 
@@ -179,21 +193,21 @@ typedef struct {
 /* Object ID and allocation information (FFOBJID) */
 
 typedef struct {
-	FATFS*	fs;				/* Pointer to the hosting volume of this object */
-	WORD	id;				/* Hosting volume's mount ID */
-	BYTE	attr;			/* Object attribute */
-	BYTE	stat;			/* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
-	DWORD	sclust;			/* Object data start cluster (0:no cluster or root directory) */
-	FSIZE_t	objsize;		/* Object size (valid when sclust != 0) */
+	FATFS*	fs;			/* Pointer to the volume holding this object */
+	WORD	id;			/* Volume mount ID when this object was opened */
+	BYTE	attr;		/* Object attribute */
+	BYTE	stat;		/* Object chain status (exFAT: b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
+	DWORD	sclust;		/* Object data cluster (0:no data or root directory) */
+	FSIZE_t	objsize;	/* Object size (valid when sclust != 0) */
 #if FF_FS_EXFAT
-	DWORD	n_cont;			/* Size of first fragment - 1 (valid when stat == 3) */
-	DWORD	n_frag;			/* Size of last fragment needs to be written to FAT (valid when not zero) */
-	DWORD	c_scl;			/* Containing directory start cluster (valid when sclust != 0) */
-	DWORD	c_size;			/* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
-	DWORD	c_ofs;			/* Offset in the containing directory (valid when file object and sclust != 0) */
+	DWORD	n_cont;		/* Size of first fragment - 1 (valid when stat == 3) */
+	DWORD	n_frag;		/* Size of last fragment needs to be written to FAT (valid when not zero) */
+	DWORD	c_scl;		/* Cluster of directory holding this object (valid when sclust != 0) */
+	DWORD	c_size;		/* Size of directory holding this object (b7-b0: allocation status, valid when c_scl != 0) */
+	DWORD	c_ofs;		/* Offset of entry in the holding directory */
 #endif
 #if FF_FS_LOCK
-	UINT	lockid;			/* File lock ID origin from 1 (index of file semaphore table Files[]) */
+	UINT	lockid;		/* File lock ID origin from 1 (index of file semaphore table Files[]) */
 #endif
 } FFOBJID;
 
@@ -202,18 +216,18 @@ typedef struct {
 /* File object structure (FIL) */
 
 typedef struct {
-	FFOBJID	obj;			/* Object identifier (must be the 1st member to detect invalid object pointer) */
-	BYTE	flag;			/* File status flags */
-	BYTE	err;			/* Abort flag (error code) */
-	FSIZE_t	fptr;			/* File read/write pointer (Zeroed on file open) */
-	DWORD	clust;			/* Current cluster of fpter (invalid when fptr is 0) */
-	LBA_t	sect;			/* Sector number appearing in buf[] (0:invalid) */
+	FFOBJID	obj;		/* Object identifier (must be the 1st member to detect invalid object pointer) */
+	BYTE	flag;		/* File status flags */
+	BYTE	err;		/* Abort flag (error code) */
+	FSIZE_t	fptr;		/* File read/write pointer (0 on open) */
+	DWORD	clust;		/* Current cluster of fptr (invalid when fptr is 0) */
+	LBA_t	sect;		/* Sector number appearing in buf[] (0:invalid) */
 #if !FF_FS_READONLY
-	LBA_t	dir_sect;		/* Sector number containing the directory entry (not used at exFAT) */
-	BYTE*	dir_ptr;		/* Pointer to the directory entry in the win[] (not used at exFAT) */
+	LBA_t	dir_sect;	/* Sector number containing the directory entry (not used in exFAT) */
+	BYTE*	dir_ptr;	/* Pointer to the directory entry in the win[] (not used in exFAT) */
 #endif
 #if FF_USE_FASTSEEK
-	DWORD*	cltbl;			/* Pointer to the cluster link map table (nulled on open, set by application) */
+	DWORD*	cltbl;		/* Pointer to the cluster link map table (nulled on open; set by application) */
 #endif
 #if !FF_FS_TINY
 	BYTE	buf[FF_MAX_SS];	/* File private data read/write window */
@@ -225,40 +239,44 @@ typedef struct {
 /* Directory object structure (DIR) */
 
 typedef struct {
-	FFOBJID	obj;			/* Object identifier */
-	DWORD	dptr;			/* Current read/write offset */
-	DWORD	clust;			/* Current cluster */
-	LBA_t	sect;			/* Current sector (0:Read operation has terminated) */
-	BYTE*	dir;			/* Pointer to the directory item in the win[] */
-	BYTE	fn[12];			/* SFN (in/out) {body[8],ext[3],status[1]} */
+	FFOBJID	obj;		/* Object identifier (must be the 1st member to detect invalid object pointer) */
+	DWORD	dptr;		/* Current read/write offset */
+	DWORD	clust;		/* Current cluster */
+	LBA_t	sect;		/* Current sector (0:no more item to read) */
+	BYTE*	dir;		/* Pointer to the directory item in the win[] in filesystem object */
+	BYTE	fn[12];		/* SFN (in/out) {body[0-7],ext[8-10],status[11]} */
 #if FF_USE_LFN
-	DWORD	blk_ofs;		/* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
+	DWORD	blk_ofs;	/* Offset of current entry block being processed (0xFFFFFFFF:invalid) */
 #endif
 #if FF_USE_FIND
-	const TCHAR* pat;		/* Pointer to the name matching pattern */
+	const TCHAR *pat;	/* Pointer to the name matching pattern */
 #endif
 } DIR;
 
 
 
-/* File information structure (FILINFO) */
+/* File/directory information structure (FILINFO) */
 
 typedef struct {
-	FSIZE_t	fsize;			/* File size */
-	WORD	fdate;			/* Modified date */
-	WORD	ftime;			/* Modified time */
-	BYTE	fattrib;		/* File attribute */
+	FSIZE_t	fsize;			/* File size (invalid for directory) */
+	WORD	fdate;			/* Date of file modification or directory creation */
+	WORD	ftime;			/* Time of file modification or directory creation */
+#if FF_FS_CRTIME
+	WORD	crdate;			/* Date of object createion */
+	WORD	crtime;			/* Time of object createion */
+#endif
+	BYTE	fattrib;		/* Object attribute */
 #if FF_USE_LFN
-	TCHAR	altname[FF_SFN_BUF + 1];/* Alternative file name */
-	TCHAR	fname[FF_LFN_BUF + 1];	/* Primary file name */
+	TCHAR	altname[FF_SFN_BUF + 1];/* Alternative object name */
+	TCHAR	fname[FF_LFN_BUF + 1];	/* Primary object name */
 #else
-	TCHAR	fname[12 + 1];	/* File name */
+	TCHAR	fname[12 + 1];	/* Object name */
 #endif
 } FILINFO;
 
 
 
-/* Format parameter structure (MKFS_PARM) */
+/* Format parameter structure (MKFS_PARM) used for f_mkfs() */
 
 typedef struct {
 	BYTE fmt;			/* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
@@ -290,7 +308,7 @@ typedef enum {
 	FR_MKFS_ABORTED,		/* (14) The f_mkfs function aborted due to some problem */
 	FR_TIMEOUT,				/* (15) Could not take control of the volume within defined period */
 	FR_LOCKED,				/* (16) The operation is rejected according to the file sharing policy */
-	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated or given buffer is insufficient in size */
+	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated, given buffer size is insufficient or too deep path */
 	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > FF_FS_LOCK */
 	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid */
 } FRESULT;

+ 27 - 10
components/fatfs/ffconf.h

@@ -5,7 +5,7 @@
 /  Configurations of FatFs Module
 /---------------------------------------------------------------------------*/
 
-#define FFCONF_DEF	5380	/* Revision ID */
+#define FFCONF_DEF	80386	/* Revision ID */
 
 /*---------------------------------------------------------------------------/
 / Function Configurations
@@ -63,12 +63,12 @@
 #define FF_PRINT_LLI	0
 #define FF_PRINT_FLOAT	0
 #define FF_STRF_ENCODE	0
-/* FF_USE_STRFUNC switches the string API functions, f_gets(), f_putc(), f_puts()
-/  and f_printf().
+/* FF_USE_STRFUNC switches string API functions, f_gets(), f_putc(), f_puts() and
+/  f_printf().
 /
 /   0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
-/   1: Enable without LF - CRLF conversion.
-/   2: Enable with LF - CRLF conversion.
+/   1: Enable without LF-CRLF conversion.
+/   2: Enable with LF-CRLF conversion.
 /
 /  FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
 /  makes f_printf() support floating point argument. These features want C99 or later.
@@ -165,14 +165,26 @@
 
 
 #define FF_FS_RPATH		0
-/* This option configures support for relative path.
+/* This option configures support for relative path feature.
 /
 /   0: Disable relative path and remove related API functions.
-/   1: Enable relative path. f_chdir() and f_chdrive() are available.
+/   1: Enable relative path and dot names. f_chdir() and f_chdrive() are available.
 /   2: f_getcwd() is available in addition to 1.
 */
 
 
+#define FF_PATH_DEPTH	10
+/*  This option defines maximum depth of directory in the exFAT volume. It is NOT
+/   relevant to FAT/FAT32 volume.
+/   For example, FF_PATH_DEPTH = 3 will able to follow a path "/dir1/dir2/dir3/file"
+/   but a sub-directory in the dir3 will not able to be followed and set current
+/   directory.
+/   The size of filesystem object (FATFS) increases FF_PATH_DEPTH * 24 bytes.
+/   When FF_FS_EXFAT == 0 or FF_FS_RPATH == 0, this option has no effect.
+*/
+
+
+
 /*---------------------------------------------------------------------------/
 / Drive/Volume Configurations
 /---------------------------------------------------------------------------*/
@@ -237,7 +249,7 @@
 
 #define FF_FS_TINY		0
 /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
-/  At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
+/  At the tiny configuration, size of file object (FIL) is reduced FF_MAX_SS bytes.
 /  Instead of private sector buffer eliminated from the file object, common sector
 /  buffer in the filesystem object (FATFS) is used for the file data transfer. */
 
@@ -262,9 +274,14 @@
 /  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
 
 
+#define FF_FS_CRTIME	0
+/* This option enables(1)/disables(0) the timestamp of the file created. When
+/  set 1, the file created time is available in FILINFO structure. */
+
+
 #define FF_FS_NOFSINFO	0
-/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
-/  option, and f_getfree() at the first time after volume mount will force
+/* If you need to know the correct free space on the FAT32 volume, set bit 0 of
+/  this option, and f_getfree() on the first time after volume mount will force
 /  a full FAT scan. Bit 1 controls the use of last allocated cluster number.
 /
 /  bit0=0: Use free cluster count in the FSINFO if available.

Разница между файлами не показана из-за своего большого размера
+ 3600 - 6381
components/fatfs/ffunicode.c


+ 12 - 0
components/lcd/luat_lcd.c

@@ -566,6 +566,18 @@ int luat_lcd_draw_line(luat_lcd_conf_t* conf,int16_t x1, int16_t y1, int16_t x2,
     int incx, incy, row, col;
     if (x1 == x2 || y1 == y2) // 直线
     {
+        if (x1 > x2)
+        {
+            int16_t tmp = x1;
+            x1 = x2;
+            x2 = tmp;
+        }
+        if (y1 > y2)
+        {
+            int16_t tmp = y1;
+            y1 = y2;
+            y2 = tmp;
+        }
         size_t dots = (x2 - x1 + 1) * (y2 - y1 + 1);//点数量
         luat_color_t* line_buf = (luat_color_t*) luat_heap_malloc(dots * sizeof(luat_color_t));
         if (conf->endianness_swap)

+ 1 - 1
components/network/adapter/luat_network_adapter.c

@@ -26,8 +26,8 @@ typedef struct
 {
 #ifdef LUAT_USE_LWIP
 	network_ctrl_t lwip_ctrl_table[LWIP_NUM_SOCKETS];
-	HANDLE network_mutex;
 #endif
+	HANDLE network_mutex;
 	int last_adapter_index;
 	int default_adapter_index;
 	llist_head dns_cache_head;

+ 13 - 4
components/network/errdump/luat_lib_errdump.c

@@ -75,6 +75,7 @@ typedef struct luat_errdump_conf
 	char custom_id[49];
 	char custom_domain_host[49];
 	uint16_t custom_domain_port;
+	int adapter_id;
 }luat_errdump_conf_t;
 
 static luat_errdump_conf_t econf;
@@ -236,13 +237,17 @@ static int32_t l_errdump_callback(lua_State *L, void* ptr)
 	uint32_t dummy_len;
     rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
     const char *ok_result = "{\"r\": 1}";
+	int adapter_id = econf.adapter_id;
+	if (adapter_id <= 0) {
+		adapter_id = network_register_get_default();
+	}
     switch(msg->arg1)
     {
     case LUAT_ERRDUMP_CONNECT:
-    	econf.netc = network_alloc_ctrl(network_register_get_default());
+    	econf.netc = network_alloc_ctrl(adapter_id);
     	if (!econf.netc)
     	{
-    		LLOGE("no socket, errdump fail, after %d second retry", econf.upload_period);
+    		LLOGE("no socket, errdump fail, after %d second retry, adapter %d", econf.upload_period, adapter_id);
     		econf.is_uploading = 0;
     		luat_rtos_timer_start(econf.upload_timer, econf.upload_period * 1000, 0, luat_errdump_timer_callback, NULL);
     		OS_DeInitBuffer(&econf.tx_buf);
@@ -635,13 +640,14 @@ static int l_errdump_record(lua_State *L) {
 
 /*
 配置关键日志上传IOT平台,这里的日志包括引起luavm异常退出的日志和用户通过record写入的日志,类似于air的errDump
-@api    errDump.config(enable, period, user_flag, custom_id, host, port)
+@api    errDump.config(enable, period, user_flag, custom_id, host, port, adapter)
 @boolean  是否启用记录功能,false的话将不会记录任何日志
 @int     定时上传周期,单位秒,默认600秒,这个是自动上传时候后的重试时间时间,在开机后或者有record操作后会很快尝试上传到合宙IOT平台一次,如果为0,则不会上传,由用户dump后自己上传自己的平台
 @string 用户的特殊标识,可以为空
 @string 设备识别号, 4G设备默认是imei,wifi设备默认STA的MAC,其他设备默认是mcu.unique_id
 @string 服务器域名,默认dev_msg1.openluat.com
-@int    服务器端口,默认
+@int    服务器端口,默认12425
+@int    网络适配器,默认-1,一般不需要设置. 2025.10.22 新增
 @return nil 无返回值
 @usage
 errDump.config(true, 3600, "12345678")	--一个小时尝试上次一次,上传时会在imei后附加上12345678
@@ -691,6 +697,9 @@ static int l_errdump_upload_config(lua_State *L) {
 			}
 			LLOGD("自定义服务器 %s %d", econf.custom_domain_host, econf.custom_domain_port);
 		}
+		if (LUA_TNUMBER == lua_type(L, 7)) {
+			econf.adapter_id = lua_tointeger(L, 7);
+		}
 	}
 	return 0;
 }

+ 39 - 18
components/network/httpsrv/src/luat_httpsrv_lwip.c

@@ -32,6 +32,8 @@ typedef struct client_socket_ctx
     char *body;
 
     size_t body_size;
+    size_t expect_body_size;
+    int next_header_value_is_content_length;
     uint32_t recv_done;
     char *buff;
     size_t buff_offset;
@@ -58,8 +60,8 @@ static int my_on_message_complete(http_parser* parser);
 // static int my_on_chunk_complete(http_parser* parser);
 static int my_on_url(http_parser* parser, const char *at, size_t length);
 // static int my_on_status(http_parser* parser, const char *at, size_t length);
-// static int my_on_header_field(http_parser* parser, const char *at, size_t length);
-// static int my_on_header_value(http_parser* parser, const char *at, size_t length);
+static int my_on_header_field(http_parser* parser, const char *at, size_t length);
+static int my_on_header_value(http_parser* parser, const char *at, size_t length);
 static int my_on_body(http_parser* parser, const char *at, size_t length);
 
 //================================
@@ -69,8 +71,8 @@ static const struct http_parser_settings hp_settings = {
     // .on_message_begin = my_on_message_begin,
     .on_url = my_on_url,
     // .on_status = my_on_status,
-    // .on_header_field = my_on_header_field,
-    // .on_header_value = my_on_header_value,
+    .on_header_field = my_on_header_field,
+    .on_header_value = my_on_header_value,
     // .on_headers_complete = my_on_headers_complete,
     .on_body = my_on_body,
     .on_message_complete = my_on_message_complete,
@@ -94,7 +96,6 @@ static const ct_reg_t ct_regs[] = {
     {"css",     "text/css"},
     {"wav",     "audio/wave"},
     {"ogg",     "audio/ogg"},
-    {"wav",     "audio/wave"},
     {"webm",    "video/webm"},
     {"mp4",     "video/mpeg4"},
     {"bin",     "application/octet-stream"},
@@ -170,10 +171,9 @@ static void client_resp(void* arg) {
         LLOGD("send body %d", body_size);
         client_write(client, client->body, body_size);
     }
-    // client_cleanup(client);
-    // tcp_output(pcb);
-    // tcp_close(pcb);
     client->write_done = 1;
+    LLOGD("resp done, total send %d", client->send_size);
+    tcp_output(client->pcb);
 }
 
 //================================
@@ -405,11 +405,12 @@ static err_t client_sent_cb(void *arg, struct tcp_pcb *tpcb, u16_t len) {
     return ERR_OK;
 }
 static void client_err_cb(void *arg, err_t err) {
-    LLOGD("client cb %d", err);
+    LLOGD("client_err_cb %d", err);
     client_socket_ctx_t* client = (client_socket_ctx_t*)arg;
     if(ERR_RST == err)
     {
         tcp_err(client->pcb, NULL);
+        tcp_sent(client->pcb, NULL);
     }
     client_cleanup(client);
 }
@@ -632,6 +633,10 @@ static int handle_static_file(client_socket_ctx_t *client) {
 static int my_on_message_complete(http_parser* parser) {
     LLOGD("on_message_complete");
     client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+    if (client->expect_body_size > 0 && client->body_size < client->expect_body_size) {
+        LLOGD("body size not match expect %d/%d", client->body_size, client->expect_body_size);
+        return HPE_INVALID_CONTENT_LENGTH;
+    }
     client->recv_done = 1;
     return 0;
 }
@@ -671,23 +676,39 @@ static int my_on_url(http_parser* parser, const char *at, size_t length) {
 //     return 0;
 // }
 
-// static int my_on_header_field(http_parser* parser, const char *at, size_t length) {
-//     // LLOGD("on_header_field %p %d", at, length);
-//     return 0;
-// }
+static int my_on_header_field(http_parser* parser, const char *at, size_t length) {
+    // LLOGI("on_header_field %.*s", (int)length, at);
+    if (length == 14 && !memcmp(at, "Content-Length", 14)) {
+        client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+        client->next_header_value_is_content_length = 1;
+    }
+    else {
+        client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+        client->next_header_value_is_content_length = 0;
+    }
+    return 0;
+}
 
-// static int my_on_header_value(http_parser* parser, const char *at, size_t length) {
-//     // LLOGD("on_header_value %p %d", at, length);
-//     return 0;
-// }
+static int my_on_header_value(http_parser* parser, const char *at, size_t length) {
+    // LLOGI("on_header_value %.*s", (int)length, at);
+    client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+    if (client->next_header_value_is_content_length) {
+        client->expect_body_size = atoi(at);
+        client->next_header_value_is_content_length = 0;
+    }
+    return 0;
+}
 
 static int my_on_body(http_parser* parser, const char *at, size_t length) {
     LLOGD("on_body %p %d", at, length);
     if (length == 0)
         return 0;
     client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+    if (client->expect_body_size > 0 && length < client->expect_body_size) {
+        return 0; // 等数据
+    }
     if (client->body == NULL) {
-        client->body = luat_heap_malloc(length);
+        client->body = luat_heap_malloc(client->expect_body_size > 0 ? client->expect_body_size : length);
         //client->body_size = length;
         if (client->body == NULL) {
             LLOGE("malloc body FAIL!!!");

+ 0 - 327
components/network/posix/luat_network_posix.c

@@ -1,327 +0,0 @@
-#include "luat_base.h"
-#include "luat_network_adapter.h"
-#include "luat_mem.h"
-#include "luat_msgbus.h"
-#include "luat_crypto.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <netdb.h>
-#include <signal.h>
-#include <fcntl.h>
-#include <sys/select.h>
-
-#define LUAT_LOG_TAG "network"
-#include "luat_log.h"
-
-#include "luat_network_posix.h"
-
-CBFuncEx_t posix_network_cb;
-void * posix_network_param;
-uint8_t posix_network_ready;
-
-static luat_rtos_mutex_t* master_lock;
-
-static void posix_send_event(int id, int p1, int p2, int p3) {
-    luat_network_cb_param_t params = {0};
-    params.tag = 0;
-    params.param = posix_network_param;
-    // 触发一下回调
-    // if (ready) {
-    OS_EVENT event = {
-        .ID = id,
-        .Param1 = p1,
-        .Param2 = p2,
-        .Param3 = p3
-    };
-    LLOGD("posix event %d %d %d %d", id, p1, p2, p3);
-    posix_network_cb(&event, &params);
-}
-
-void posix_network_client_thread_entry(posix_socket_t *ps) {
-
-    luat_network_cb_param_t params = {0};
-    params.tag = 0;
-    params.param = posix_network_param;
-    // 触发一下回调
-    // if (ready) {
-    OS_EVENT event = {0};
-
-    struct sockaddr_in sockaddr = {0};
-    sockaddr.sin_family = AF_INET;
-    sockaddr.sin_port = htons(ps->remote_port);
-    sockaddr.sin_addr.s_addr = ps->remote_ip.ipv4;
-    luat_rtos_task_sleep(50);
-
-    LLOGD("ready to connect %d", ps->socket_id);
-    int ret = connect(ps->socket_id, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
-
-    LLOGD("connect ret %d", ret);
-    if (ret) {
-        // 失败了
-        LLOGD("connect FAIL ret %d", ret);
-        posix_send_event(EV_NW_SOCKET_ERROR, ps->socket_id, 0, 0);
-        luat_heap_free(ps);
-        return;
-    }
-    // 发送系统消息, 通知连接成功
-    posix_send_event(EV_NW_SOCKET_CONNECT_OK, ps->socket_id, 0, 0);
-    LLOGD("wait data now");
-
-    fd_set readfds;
-    fd_set writefds;
-    fd_set errorfds;
-    int maxsock;
-    struct timeval tv;
-    maxsock = ps->socket_id;
-    // timeout setting
-    tv.tv_sec = 0;
-    tv.tv_usec = 3000; //暂时3ms吧
-    while (1) {
-        // initialize file descriptor set
-        FD_ZERO(&readfds);
-        // FD_ZERO(&writefds);
-        FD_ZERO(&errorfds);
-        FD_SET(ps->socket_id, &readfds);
-        // FD_SET(ps->socket_id, &writefds);
-        FD_SET(ps->socket_id, &errorfds);
-
-        if (master_lock)
-            if (luat_rtos_mutex_lock(master_lock, 100))
-                continue;
-        ret = select(maxsock + 1, &readfds, NULL, &errorfds, &tv);
-        if (master_lock)
-            luat_rtos_mutex_unlock(master_lock);
-
-        if (ret < 0) {
-            LLOGE("select ret %d", ret);
-            break;
-        } else if (ret == 0) {
-            //printf("select timeout\n");
-            continue;
-        }
-
-        if (FD_ISSET(maxsock, &readfds)) {
-            // 发消息,可读了
-        }
-        // if (FD_ISSET(maxsock, &writefds)) {
-        //     // 发消息,发送完成了??
-        // }
-        if (FD_ISSET(maxsock, &errorfds)) {
-            // 发消息,出错了
-            break;
-        }
-    }
-
-    luat_heap_free(ps);
-    LLOGI("socket thread exit");
-}
-
-void posix_network_set_ready(uint8_t ready) {
-    LLOGD("CALL posix_network_set_ready");
-    posix_network_ready = ready;
-    luat_network_cb_param_t params = {0};
-    params.tag = 0;
-    params.param = posix_network_param;
-    // 触发一下回调
-    // if (ready) {
-        OS_EVENT event = {
-            .ID = EV_NW_STATE,
-            .Param1 = 0,
-            .Param2 = ready,
-            .Param3 = 0
-        };
-        posix_network_cb(&event, &params);
-    // }
-}
-
-//检查网络是否准备好,返回非0准备好,user_data是注册时的user_data,传入给底层api
-uint8_t (posix_check_ready)(void *user_data) {
-    LLOGD("CALL posix_check_ready %d", posix_network_ready);
-    return posix_network_ready;
-};
-
-//创建一个socket,并设置成非阻塞模式,user_data传入对应适配器, tag作为socket的合法依据,给check_socket_vaild比对用
-//成功返回socketid,失败 < 0
-int (posix_create_socket)(uint8_t is_tcp, uint64_t *tag, void *param, uint8_t is_ipv6, void *user_data) {
-    // TODO 支持IPV6
-    int s = socket(AF_INET, is_tcp ? SOCK_STREAM : SOCK_DGRAM, is_tcp ? IPPROTO_TCP : IPPROTO_UDP);
-    LLOGD("CALL posix_create_socket %d %d", s, is_tcp);
-    return s;
-}
-
-//作为client绑定一个port,并连接remote_ip和remote_port对应的server
-//成功返回0,失败 < 0
-int (posix_socket_connect)(int socket_id, uint64_t tag, uint16_t local_port, luat_ip_addr_t *remote_ip, uint16_t remote_port, void *user_data) {
-    LLOGD("CALL posix_socket_connect %d", socket_id);
-    posix_socket_t *ps = luat_heap_malloc(sizeof(posix_socket_t));
-    if (ps == NULL) {
-        LLOGE("out of memory when malloc posix_socket_t");
-        return -1;
-    }
-    ps->socket_id = socket_id;
-    ps->tag = tag;
-    ps->local_port = local_port;
-    memcpy(&ps->remote_ip, remote_ip, sizeof(luat_ip_addr_t));
-    ps->remote_port = remote_port;
-    ps->user_data = user_data;
-
-    int ret = network_posix_client_thread_start(ps);
-    LLOGD("socket thread start %d", ret);
-
-    if (ret) {
-        luat_heap_free(ps);
-    }
-    return ret;
-}
-//作为server绑定一个port,开始监听
-//成功返回0,失败 < 0
-int (posix_socket_listen)(int socket_id, uint64_t tag, uint16_t local_port, void *user_data) {
-    // 尚未支持
-    return -1;
-}
-//作为server接受一个client
-//成功返回0,失败 < 0
-int (posix_socket_accept)(int socket_id, uint64_t tag, luat_ip_addr_t *remote_ip, uint16_t *remote_port, void *user_data) {
-    // 尚未支持
-    return -1;
-}
-
-//主动断开一个tcp连接,需要走完整个tcp流程,用户需要接收到close ok回调才能确认彻底断开
-//成功返回0,失败 < 0
-int (posix_socket_disconnect)(int socket_id, uint64_t tag, void *user_data) {
-    return close(socket_id);
-}
-
-//释放掉socket的控制权,除了tag异常外,必须立刻生效
-//成功返回0,失败 < 0
-int (posix_socket_close)(int socket_id, uint64_t tag, void *user_data) {
-    return close(socket_id);
-}
-
-//强行释放掉socket的控制权,必须立刻生效
-//成功返回0,失败 < 0
-int (posix_socket_force_close)(int socket_id, void *user_data) {
-    return close(socket_id);
-}
-
-//tcp时,不需要remote_ip和remote_port,如果buf为NULL,则返回当前缓存区的数据量,当返回值小于len时说明已经读完了
-//udp时,只返回1个block数据,需要多次读直到没有数据为止
-//成功返回实际读取的值,失败 < 0
-int (posix_socket_receive)(int socket_id, uint64_t tag, uint8_t *buf, uint32_t len, int flags, luat_ip_addr_t *remote_ip, uint16_t *remote_port, void *user_data) {
-    
-    struct timeval tv;
-    tv.tv_sec = 0;
-    tv.tv_usec = 1000; //暂时1ms吧
-    setsockopt(socket_id, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
-
-    if (master_lock)
-        if (luat_rtos_mutex_lock(master_lock, 100))
-            return -1;
-    int ret = recv(socket_id, buf, len, flags);
-    if (master_lock)
-        luat_rtos_mutex_unlock(master_lock);
-    return ret;
-}
-
-//tcp时,不需要remote_ip和remote_port
-//成功返回>0的len,缓冲区满了=0,失败 < 0,如果发送了len=0的空包,也是返回0,注意判断
-int (posix_socket_send)(int socket_id, uint64_t tag, const uint8_t *buf, uint32_t len, int flags, luat_ip_addr_t *remote_ip, uint16_t remote_port, void *user_data) {
-    
-    struct timeval tv;
-    tv.tv_sec = 0;
-    tv.tv_usec = 1000; //暂时1ms吧
-    setsockopt(socket_id, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
-
-    if (master_lock)
-        if (luat_rtos_mutex_lock(master_lock, 100))
-            return -1;
-    int ret = send(socket_id, buf, len, flags);
-    if (master_lock)
-        luat_rtos_mutex_unlock(master_lock);
-    return ret;
-}
-
-//检查socket合法性,成功返回0,失败 < 0
-int (posix_socket_check)(int socket_id, uint64_t tag, void *user_data) {
-    // TODO 通过select errorfds?
-    LLOGD("CALL posix_socket_check %d %lld", socket_id, tag);
-    return 0;
-}
-
-//保留有效的socket,将无效的socket关闭
-void (posix_socket_clean)(int *vaild_socket_list, uint32_t num, void *user_data) {
-    
-}
-
-int (posix_getsockopt)(int socket_id, uint64_t tag, int level, int optname, void *optval, uint32_t *optlen, void *user_data) {
-    return getsockopt(socket_id, level, optname, optval, optlen);
-}
-
-int (posix_setsockopt)(int socket_id, uint64_t tag, int level, int optname, const void *optval, uint32_t optlen, void *user_data) {
-    return setsockopt(socket_id, level, optname, optval, optlen);
-}
-
-//非posix的socket,用这个根据实际硬件设置参数
-int (posix_user_cmd)(int socket_id, uint64_t tag, uint32_t cmd, uint32_t value, void *user_data) {
-    return 0; // 没有这些东西
-}
-
-
-int (posix_dns)(const char *domain_name, uint32_t len, void *param,  void *user_data) {
-    LLOGD("CALL posix_dns %.*s", len, domain_name);
-    return -1; // 暂不支持DNS
-}
-
-int (posix_set_dns_server)(uint8_t server_index, luat_ip_addr_t *ip, void *user_data) {
-    return 0; // 暂不支持设置DNS
-}
-
-#ifdef LUAT_USE_LWIP
-int (posix_set_mac)(uint8_t *mac, void *user_data);
-int (posix_set_static_ip)(luat_ip_addr_t *ip, luat_ip_addr_t *submask, luat_ip_addr_t *gateway, luat_ip_addr_t *ipv6, void *user_data);
-#endif
-int (posix_get_local_ip_info)(luat_ip_addr_t *ip, luat_ip_addr_t *submask, luat_ip_addr_t *gateway, void *user_data) {
-    ip->ipv4 = 0;
-    submask->ipv4 = 0;
-    gateway->ipv4 = 0;
-    return 0;
-}
-
-//所有网络消息都是通过cb_fun回调
-//cb_fun回调时第一个参数为OS_EVENT,包含了socket的必要信息,第二个是luat_network_cb_param_t,其中的param是这里传入的param(就是适配器序号)
-//OS_EVENT ID为EV_NW_XXX,param1是socket id param2是各自参数 param3是create_soceket传入的socket_param(就是network_ctrl *)
-//dns结果是特别的,ID为EV_NW_SOCKET_DNS_RESULT,param1是获取到的IP数据量,0就是失败了,param2是ip组,动态分配的, param3是dns传入的param(就是network_ctrl *)
-void (posix_socket_set_callback)(CBFuncEx_t cb_fun, void *param, void *user_data) {
-    LLOGD("call posix_socket_set_callback %p %p", cb_fun, param);
-    if (master_lock == NULL)
-        luat_rtos_mutex_create(master_lock);
-    posix_network_cb = cb_fun;
-    posix_network_param = param;
-}
-
-
-network_adapter_info network_posix = {
-    .check_ready = posix_check_ready,
-    .create_soceket = posix_create_socket,
-    .socket_connect  = posix_socket_connect,
-    .socket_accept = posix_socket_accept,
-    .socket_disconnect  = posix_socket_disconnect,
-    .socket_close = posix_socket_close,
-    .socket_force_close = posix_socket_force_close,
-    .socket_receive = posix_socket_receive,
-    .socket_send = posix_socket_send,
-    .socket_clean = posix_socket_clean,
-    .getsockopt = posix_getsockopt,
-    .setsockopt = posix_setsockopt,
-    .user_cmd  = posix_user_cmd,
-    .dns = posix_dns,
-    .set_dns_server = posix_set_dns_server,
-    .get_local_ip_info = posix_get_local_ip_info,
-    .socket_set_callback = posix_socket_set_callback,
-    .name = "posix",
-    .max_socket_num = 4,
-    .no_accept = 1, // 暂时不支持接收
-    .is_posix = 1,
-};

+ 0 - 20
components/network/posix/luat_network_posix.h

@@ -1,20 +0,0 @@
-#ifndef LUAT_NETWORK_POSIX_H
-#define LUAT_NETWORK_POSIX_H
-
-#include "luat_base.h"
-#include "luat_network_adapter.h"
-
-typedef struct posix_socket
-{
-    int socket_id;
-    uint64_t tag;
-    uint16_t local_port;
-    luat_ip_addr_t remote_ip;
-    uint16_t remote_port;
-    void *user_data;
-}posix_socket_t;
-
-int network_posix_client_thread_start(posix_socket_t* ps);
-void posix_network_client_thread_entry(posix_socket_t* args);
-
-#endif

+ 6 - 0
components/pins/src/luat_pins.c

@@ -322,6 +322,12 @@ int luat_pins_setup(uint16_t pin, const char* func_name, size_t name_len, int al
 		return 1;
 	}
 	#endif
+	#ifdef __BK72XX__
+	if (pin == 26 || pin == 27){	// air8101的26/27不支持配置,只能作为uart0调试串口
+		LLOGE("pin%d不支持修改", pin);
+		goto LUAT_PIN_SETUP_DONE;
+	}
+	#endif
 	if (func_name != NULL)
 	{
 		if (name_len < 2)

+ 48 - 56
components/sms/binding/luat_lib_sms.c

@@ -54,20 +54,31 @@ static uint8_t ref_idx = 254;
 static uint64_t long_sms_send_idp = 0;
 
 
-
 static int l_long_sms_send_callback(lua_State *L, void* ptr){
     rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
-    if (msg->arg1)
-    {
-        lua_pushboolean(L, 0);
-    }
-    else
+
+    if (long_sms_send_idp)
     {
-        lua_pushboolean(L, 1);
+        lua_pushboolean(L, msg->arg1 == 0 ? 0 : 1);
+        luat_cbcwait(L, long_sms_send_idp, 1);
+        long_sms_send_idp = 0;
     }
-    luat_cbcwait(L, long_sms_send_idp, 1);
-    long_sms_send_idp = 0;
-    g_s_sms_pdu_packet.maxNum = 0;             // 通过sms.sendLong发送的短信,需要在回调里确定发送结束
+
+/*
+@sys_pub sms
+短信发送结果
+SMS_SENT
+@result boolean 发送结果,成功为true, 失败为false
+@usage
+sys.subscribe("SMS_SENT", function(result)
+    log.info("sms send result", result)
+end)
+*/
+    lua_getglobal(L, "sys_pub");
+    lua_pushliteral(L, "SMS_SENT");
+    lua_pushboolean(L, msg->arg1 == 0 ? 0 : 1);
+    lua_call(L, 2, 0);
+    g_s_sms_pdu_packet.maxNum = 0;
     return 0;
 }
 
@@ -259,77 +270,59 @@ void luat_sms_recv_cb(uint32_t event, void *param)
     luat_msgbus_put(&msg, 0);
 }
 
+static void luat_sms_send_done(uint8_t is_success)
+{
+    rtos_msg_t msg = {
+        .handler = l_long_sms_send_callback,
+        .arg1 = is_success,
+        .arg2 = 0
+    };
+    luat_msgbus_put(&msg, 0);
+    if (g_s_sms_send.payload != NULL) {
+        luat_heap_free(g_s_sms_send.payload);
+        g_s_sms_send.payload = NULL;
+    }
+}
+
+
 void luat_sms_send_cb(int ret)
 {
     // 当前没有短信在发送,应该不会产生这个回调吧?
     if (!g_s_sms_pdu_packet.maxNum) {
         return;
     }
-    rtos_msg_t msg = {
-        .handler = l_long_sms_send_callback,
-        .arg1 = 0,
-        .arg2 = 0
-    };
+
     // 发送失败
     if (ret) {
-        // 长短信发送失败
-        if (long_sms_send_idp) {
-            msg.arg1 = 1;
-            luat_msgbus_put(&msg, 0);
-        } else {
-            // 通过sms.send发送的短信,这里可以直接判断发送结束
-            g_s_sms_pdu_packet.maxNum = 0;
-        }
-        if (g_s_sms_send.payload != NULL) {
-            luat_heap_free(g_s_sms_send.payload);
-            g_s_sms_send.payload = NULL;
-        }
+        luat_sms_send_done(0);
         return;
     }
-    LLOGI("long sms callback seqNum = %d", g_s_sms_pdu_packet.seqNum);
+    LLOGD("long sms callback seqNum = %d", g_s_sms_pdu_packet.seqNum);
     // 全部短信发送完成
     if (g_s_sms_pdu_packet.seqNum == g_s_sms_pdu_packet.maxNum) {
-        if (long_sms_send_idp) {
-            msg.arg1 = 0;
-            luat_msgbus_put(&msg, 0);
-        } else {
-            g_s_sms_pdu_packet.maxNum = 0;
-        }
-
-        if (g_s_sms_send.payload != NULL) {
-            luat_heap_free(g_s_sms_send.payload);
-            g_s_sms_send.payload = NULL;
-        }
+        luat_sms_send_done(1);
         return;
     }
 
     // 长短信继续发送
     g_s_sms_pdu_packet.seqNum++;
+    uint8_t packet_len = g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE;
+    uint32_t addr = g_s_sms_send.payload + (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE;
     // 最后一包
-    if (g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE <= LUAT_SMS_LONG_MSG_PDU_SIZE) {
-        memcpy(g_s_sms_pdu_packet.payload_buf, g_s_sms_send.payload + (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE, g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE);
-        g_s_sms_pdu_packet.payload_len = g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE ;
+    if (packet_len <= LUAT_SMS_LONG_MSG_PDU_SIZE) {
+        memcpy(g_s_sms_pdu_packet.payload_buf, (void *)addr, packet_len);
+        g_s_sms_pdu_packet.payload_len = packet_len;
     } else {
         // 继续发送
-        memcpy(g_s_sms_pdu_packet.payload_buf, g_s_sms_send.payload + (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE, LUAT_SMS_LONG_MSG_PDU_SIZE);
+        memcpy(g_s_sms_pdu_packet.payload_buf, (void *)addr, LUAT_SMS_LONG_MSG_PDU_SIZE);
         g_s_sms_pdu_packet.payload_len = LUAT_SMS_LONG_MSG_PDU_SIZE;
     }
     
     int len = luat_sms_pdu_packet(&g_s_sms_pdu_packet);
     ret = luat_sms_send_msg_v2(g_s_sms_pdu_packet.pdu_buf, len);
-    // 发送失败
+    // 发送失败
     if (ret) {
-        // 长短信接口
-        if(long_sms_send_idp) {
-            msg.arg1 = 0;
-            luat_msgbus_put(&msg, 0);
-        } else {
-            g_s_sms_pdu_packet.maxNum = 0;
-        }
-        if (g_s_sms_send.payload != NULL) {
-            luat_heap_free(g_s_sms_send.payload);
-            g_s_sms_send.payload = NULL;
-        }
+        luat_sms_send_done(0);
     }
     return;
 }
@@ -547,7 +540,6 @@ static int l_long_sms_send(lua_State *L) {
     if (!ret) {
         return 1;
     }
-    LLOGE("sms send task create failed");
 SMS_FAIL:
     long_sms_send_idp = 0;
     g_s_sms_pdu_packet.maxNum = 0;

+ 10 - 1
components/wlan/luat_lib_wlan.c

@@ -237,7 +237,16 @@ static int l_wlan_scan_result(lua_State* L) {
     }
     memset(results, 0, sizeof(luat_wlan_scan_result_t) * ap_limit);
     #ifdef LUAT_USE_DRV_WLAN
-    int len = luat_drv_wlan_scan_get_result(results, ap_limit);
+    int len = 0;
+    #ifdef LUAT_USE_AIRLINK
+    if (luat_airlink_has_wifi()) {
+        len = luat_drv_wlan_scan_get_result(results, ap_limit);
+    }
+    else
+    {
+        len = luat_wlan_scan_get_result(results, ap_limit);
+    }
+    #endif
     #else
     int len = luat_wlan_scan_get_result(results, ap_limit);
     #endif

+ 6 - 1
lua/src/loslib.c

@@ -215,7 +215,12 @@ static int os_getenv (lua_State *L) {
 #endif
 
 static int os_clock (lua_State *L) {
-  lua_pushinteger(L, ((lua_Integer)clock())/(lua_Integer)CLOCKS_PER_SEC);
+  #ifdef LUAT_USE_MCU
+  extern uint64_t luat_mcu_tick64(void);
+  lua_pushinteger(L, (lua_Integer)luat_mcu_tick64());
+  #else
+  lua_pushinteger(L, 0);
+  #endif
   return 1;
 }
 

+ 9 - 0
luat/modules/luat_lib_rtos.c

@@ -248,6 +248,15 @@ local luatos_version = rtos.version()
 */
 static int l_rtos_version(lua_State *L) {
     lua_pushstring(L, luat_version_str());
+    // 当多传入一个参数时,且值为true,则返回数字版本号
+    if (lua_isboolean(L, 1) && lua_toboolean(L, 1)) {
+        #ifdef LUAT_CONF_FIRMWARE_TYPE_NUM
+        lua_pushinteger(L, LUAT_CONF_FIRMWARE_TYPE_NUM);
+        #else
+        lua_pushinteger(L, 0);
+        #endif
+        return 2;
+    }
     return 1;
 }
 

+ 2 - 2
module/Air780E/demo/fota2/main.lua

@@ -37,7 +37,7 @@ end)
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     if ret == 0 then
@@ -52,7 +52,7 @@ local function fota_cb(ret)
     elseif ret == 4 then
         log.info("接收报文错误", "检查模块固件或升级包内文件是否正常")
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 58 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/audio_drv.lua

@@ -0,0 +1,58 @@
+--[[
+@module  audio_drv
+@summary 音频设备管理模块,负责音频设备的初始化和控制(仅使用exaudio扩展库)
+@version 2.0
+@date    2025.10.23
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1、定义所有硬件引脚常量
+2、使用exaudio扩展库初始化音频设备
+]]
+
+-- 引入exaudio库
+local exaudio = require("exaudio")
+
+-- exaudio配置参数
+local audio_configs = {
+    model = "es8311",         -- dac类型: "es8311"
+    i2c_id = 1,               -- i2c_id: 可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,            -- 音频放大器电源控制管脚
+    dac_ctrl = 2,           -- 音频编解码芯片电源控制管脚
+    dac_delay = 3,            -- DAC启动前冗余时间(单位100ms)
+    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(单位1ms)
+    dac_time_delay = 100,     -- 播放完毕后PA与DAC关闭间隔(单位1ms)
+    bits_per_sample = 16,     -- 采样位深
+    pa_on_level = 1           -- PA打开电平 1:高 0:低
+}
+
+
+exaudio.vol(70)            -- 喇叭音量
+exaudio.mic_vol(65)        -- 麦克风音量
+
+-- 初始化音频设备
+local function initAudioDevice()
+
+    -- 使用exaudio.setup统一配置音频设备
+    log.info("audio_drv", "使用exaudio.setup初始化音频设备")
+    if exaudio.setup(audio_configs) then
+        log.info("audio_drv", "exaudio.setup初始化成功")
+    else
+        log.error("audio_drv", "exaudio.setup初始化失败")
+        return false
+    end
+    
+    -- log.info("audio_drv", "Audio device initialized using exaudio only")
+    return true
+end
+
+-- 获取音频通道ID(保留用于兼容性)
+local function getMultimediaId()
+    return 0  -- 返回默认值0
+end
+
+-- 导出接口
+return {
+    initAudioDevice = initAudioDevice,
+    getMultimediaId = getMultimediaId
+}

+ 246 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/cc_app.lua

@@ -0,0 +1,246 @@
+--[[
+@module  cc_app
+@summary 通话业务逻辑模块,实现4种通话场景的处理和通话录音功能
+@version 1.0
+@date    2025.10.21
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1. 设置 ACTIVE_SCENARIO 变量选择需要的场景(1-4)
+2. 其他场景自动禁用,不会影响程序运行
+3. 每个场景都有独立的状态处理逻辑
+4. 实现通话录音功能
+
+支持的场景:
+[场景1] 呼入立即挂断(响铃3次后自动拒接)
+[场景2] 呼入自动接听(响2声)+10秒后主动挂断
+[场景3] 呼入自动接听(响2声)+等待对方挂断
+[场景4] 主动呼出电话+等待对方挂断
+
+
+注意事项:
+1. 设置ACTIVE_SCENARIO选择要启用的场景(1-4)
+2. 场景4会主动拨打电话(修改为自己测试时要拨打的电话号码)
+3. 所有通话都会被录音(需确保存储空间足够)
+]]
+
+-- 引入音频设备模块
+local audio_drv = require "audio_drv"
+
+-- ====================== 配置区域 ======================
+-- 设置当前激活的场景(1-4),注释掉不需要的场景
+-- local ACTIVE_SCENARIO = 1  -- 场景1:呼入立即挂断
+-- local ACTIVE_SCENARIO = 2  -- 场景2:呼入自动接听,10秒后主动挂断
+local ACTIVE_SCENARIO = 3  -- 场景3:呼入自动接听,等待对方挂断
+-- local ACTIVE_SCENARIO = 4  -- 场景4:主动呼出,等待对方挂断
+
+-- 全局状态变量
+local call_counter = 0                 -- 响铃计数器(用于场景1-3)
+local caller_number = ""               -- 来电号码(用于场景1-3)
+local is_connected = false             -- 通话连接状态标志(用于场景2)
+local outgoing_number = "10000"  -- 呼出号码(用于场景4),修改为自己测试时要拨打的电话号码
+
+-- ====================== 录音功能 ======================
+-- 创建音频数据缓冲区
+local up1 = zbuff.create(6400,0)      -- 上行数据保存区1
+local up2 = zbuff.create(6400,0)      -- 上行数据保存区2
+local down1 = zbuff.create(6400,0)    -- 下行数据保存区1
+local down2 = zbuff.create(6400,0)    -- 下行数据保存区2
+
+-- 音频数据回调函数
+local function recordCallback(is_dl, point)
+    if is_dl then
+        log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
+    else
+        log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
+    end
+    log.info("通话质量", cc.quality())
+    -- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
+end
+
+-- 启用通话录音
+local function enableRecording()
+    cc.record(true, up1, up2, down1, down2)
+    cc.on("record", recordCallback)
+    log.info("cc_app", "通话录音已启用")
+end
+
+-- 获取所有缓冲区
+local function getRecordingBuffers()
+    return {
+        up1 = up1,
+        up2 = up2,
+        down1 = down1,
+        down2 = down2
+    }
+end
+
+-- ====================== 场景处理函数 ======================
+
+-- 场景1:呼入立即挂断(响铃3次后)
+local function handle_scenario1(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景1", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃3声后拒接
+        if call_counter >= 3 then
+            log.info("场景1", "拒接来电")
+            cc.hangUp(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "HANGUP_CALL_DONE" then
+        log.info("场景1", "挂断完成")
+        call_counter = 0
+    end
+end
+
+-- 场景2挂断回调函数
+local function scenario2_hangup_callback()
+    log.info("场景2", "10秒通话结束,主动挂断")
+    cc.hangUp(0)
+    is_connected = false
+end
+
+-- 场景2:呼入自动接听,10秒后主动挂断
+local function handle_scenario2(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景2", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景2", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "ANSWER_CALL_DONE" then
+        log.info("场景2", "接听完成,等待通话建立")
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        if not is_connected then
+            log.info("场景2", "通话已建立,开始计时")
+            is_connected = true
+            
+            -- 创建10秒后挂断的定时器
+            sys.timerStart(scenario2_hangup_callback, 10000)  -- 10秒后执行挂断
+            log.info("场景2", "10秒挂断定时器创建成功")
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "DISCONNECTED" then
+        log.info("场景2", "通话结束")
+        is_connected = false
+        
+        -- 取消挂断定时器
+        sys.timerStop(scenario2_hangup_callback)
+        log.info("场景2", "已取消挂断定时器")
+        
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景3:呼入自动接听,等待对方挂断
+local function handle_scenario3(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景3", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景3", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        log.info("场景3", "电话已接通,电话号码:", caller_number)
+    elseif status == "DISCONNECTED" then
+        -- 对方挂断通话
+        log.info("场景3", "通话结束对方挂断")
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景4:主动呼出,等待对方挂断
+local function handle_scenario4(status)
+    if status == "CONNECTED" then
+        log.info("场景4", "呼叫接通")
+    elseif status == "DISCONNECTED" then
+        log.info("场景4", "通话结束(对方挂断)")
+    elseif status == "MAKE_CALL_FAILED" then
+        log.info("场景4", "呼叫失败")
+    end
+end
+
+-- 场景4拨号函数
+local function dial_for_scenario4()
+    log.info("场景4", "开始拨打", outgoing_number)
+    cc.dial(0, outgoing_number)
+end
+
+-- ====================== 主事件处理器 ======================
+sys.subscribe("CC_IND", function(status)
+    log.info("CC状态", status)
+    
+    -- 根据激活的场景调用对应的处理函数
+    if ACTIVE_SCENARIO == 1 then
+        handle_scenario1(status)
+    elseif ACTIVE_SCENARIO == 2 then
+        handle_scenario2(status)
+    elseif ACTIVE_SCENARIO == 3 then
+        handle_scenario3(status)
+    elseif ACTIVE_SCENARIO == 4 then
+        handle_scenario4(status)
+    end
+    
+    -- 所有场景都需要处理的通用状态
+    if status == "READY" then
+        sys.publish("CC_READY")  -- 发布系统就绪事件
+        
+        -- 场景4:电话系统就绪后自动拨号
+        if ACTIVE_SCENARIO == 4 then
+            sys.timerStart(dial_for_scenario4, 1000)  -- 延迟1秒拨号
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "MAKE_CALL_FAILED" or status == "DISCONNECTED" then
+        
+        audio.pm(0,audio.STANDBY)
+        -- audio.pm(0,audio.SHUTDOWN)   --低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
+    end
+end)
+
+-- ====================== 电话系统初始化 ======================
+local function init_cc()
+    -- 初始化音频设备(使用exaudio)
+    audio_drv.initAudioDevice()
+    
+    -- 等待电话系统就绪
+    sys.waitUntil("CC_READY")
+    
+    -- 初始化电话功能
+    cc.init(audio_drv.getMultimediaId())
+    
+    -- 启用通话录音(录音功能在cc_app中)
+    enableRecording()
+    
+    log.info("cc_app", "电话系统初始化完成")
+end
+
+-- 启动初始化任务
+sys.taskInit(init_cc)
+
+log.info("cc_app", "通话业务逻辑模块加载完成,当前场景:", ACTIVE_SCENARIO)
+
+-- 导出录音相关功能(如果需要被其他模块使用)
+-- return {
+--     enableRecording = enableRecording,
+--     getRecordingBuffers = getRecordingBuffers,
+--     recordCallback = recordCallback
+-- }

+ 78 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/main.lua

@@ -0,0 +1,78 @@
+--[[
+@module  main
+@summary LuatOS语音通话应用主入口,负责加载功能模块
+@version 1.0
+@date    2025.07.16
+@author  陈媛媛
+@usage
+本demo演示的核心功能为:
+1、音频设备初始化与控制
+2、完整通话业务逻辑处理(4种通话场景),详情如下:
+-呼入,挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,主动挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,对方挂断,挂断消息识别打印;
+-呼出,对方接通,接听消息识别打印,建立通话后一段时间,对方主动挂断,挂断消息识别打印;
+3、通话状态监控与日志记录
+4、支持Air8000系列和Air780EHV等硬件平台
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "VOICE_CALL_DEMO"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+-- 仅加载必要的功能模块
+require "audio_drv"  -- 音频设备管理模块
+require "cc_app"        -- 通话业务逻辑模块
+
+-- 用户代码已结束---------------------------------------------
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!

+ 240 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/pins_Air780EHM.json

@@ -0,0 +1,240 @@
+{
+  "model": "Air780EHM",
+  "pins": [
+    [
+      7,
+      "PWR_KEY",
+      ""
+    ],
+    [
+      16,
+      "GPIO27",
+      ""
+    ],
+    [
+      17,
+      "UART1_RXD",
+      ""
+    ],
+    [
+      18,
+      "UART1_TXD",
+      ""
+    ],
+    [
+      19,
+      "GPIO22",
+      ""
+    ],
+    [
+      20,
+      "PWM1",
+      ""
+    ],
+    [
+      22,
+      "PWM0",
+      ""
+    ],
+    [
+      23,
+      "ONEWIRE",
+      ""
+    ],
+    [
+      25,
+      "CAN_TXD",
+      ""
+    ],
+    [
+      26,
+      "PWM4",
+      ""
+    ],
+    [
+      28,
+      "UART2_RXD",
+      ""
+    ],
+    [
+      29,
+      "UART2_TXD",
+      ""
+    ],
+    [
+      30,
+      "GPIO29",
+      ""
+    ],
+    [
+      31,
+      "GPIO30",
+      ""
+    ],
+    [
+      32,
+      "GPIO31",
+      ""
+    ],
+    [
+      33,
+      "GPIO32",
+      ""
+    ],
+    [
+      38,
+      "DBG_RXD",
+      ""
+    ],
+    [
+      39,
+      "DBG_TXD",
+      ""
+    ],
+    [
+      49,
+      "LCD_RST",
+      ""
+    ],
+    [
+      50,
+      "LCD_SDA",
+      ""
+    ],
+    [
+      51,
+      "LCD_RS",
+      ""
+    ],
+    [
+      52,
+      "LCD_CS",
+      ""
+    ],
+    [
+      53,
+      "LCD_CLK",
+      ""
+    ],
+    [
+      54,
+      "CAM_MCLK",
+      ""
+    ],
+    [
+      55,
+      "CAM_RX0",
+      ""
+    ],
+    [
+      56,
+      "CAM_RX1",
+      ""
+    ],
+    [
+      57,
+      "UART3_TXD",
+      ""
+    ],
+    [
+      58,
+      "UART3_RXD",
+      ""
+    ],
+    [
+      61,
+      "VBUS",
+      ""
+    ],
+    [
+      66,
+      "I2C1_SDA",
+      ""
+    ],
+    [
+      67,
+      "I2C1_SCL",
+      ""
+    ],
+    [
+      78,
+      "GPIO28",
+      ""
+    ],
+    [
+      79,
+      "USIM_DET",
+      ""
+    ],
+    [
+      80,
+      "CAM_BCLK",
+      ""
+    ],
+    [
+      81,
+      "CAM_CS",
+      ""
+    ],
+    [
+      82,
+      "USB_BOOT",
+      ""
+    ],
+    [
+      83,
+      "SPI0_CS",
+      ""
+    ],
+    [
+      84,
+      "SPI0_MISO",
+      ""
+    ],
+    [
+      85,
+      "SPI0_MOSI",
+      ""
+    ],
+    [
+      86,
+      "SPI0_CLK",
+      ""
+    ],
+    [
+      97,
+      "GPIO16",
+      ""
+    ],
+    [
+      99,
+      "GPIO23",
+      ""
+    ],
+    [
+      100,
+      "GPIO17",
+      ""
+    ],
+    [
+      101,
+      "WAKEUP0",
+      ""
+    ],
+    [
+      102,
+      "GPIO20",
+      ""
+    ],
+    [
+      106,
+      "CAN_RXD",
+      ""
+    ],
+    [
+      107,
+      "GPIO21",
+      ""
+    ]
+  ]
+}

+ 210 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/readme.md

@@ -0,0 +1,210 @@
+# CC_DEMO 项目说明
+
+## 项目概述
+本项目是基于 Air780EHM/Air780EGH 的语音通信演示demo,实现了基本的语音通话功能,包括音频设备初始化、通话建立、通话管理等核心功能。
+
+## 文件结构
+- main.lua: 主程序入口,仅加载初始化模块
+- audio_device.lua: 管理音频设备初始化与控制
+- cc_app.lua: 实现完整通话业务逻辑
+- pins_Air780EHM.json/pins_Air780EGH.json: IO复用描述文件,由LuatIO工具自动生成
+
+## 功能说明
+1. **音频设备初始化与控制**:配置并管理ES8311音频编解码芯片和扬声器功放,包括I2C、I2S接口设置及音量控制。
+
+2. **完整通话业务逻辑处理**:实现4种通话场景,包括呼入和呼出的各种情况处理。
+
+按照自己的通话需求启用对应的Lua文件,其余注释掉;
+
+- (1)呼入,主动挂断(响铃3次后自动拒接);
+
+- (2)呼入,自动接听,接听消息识别打印,主动挂断,挂断消息识别打印;
+
+- (3)呼入,自动接听,接听消息识别打印,等待对方挂断,挂断消息识别打印;
+
+- (4)呼出,对方接通,接听消息识别打印,建立通话后一段时间,等待对方挂断,挂断消息识别打印;
+
+3. **通话状态监控与日志记录**:实时记录通话状态变化及相关信息。
+
+## 演示硬件环境
+ 
+ Air780EHM核心板/Air780EGH核心板+AirAUDIO_1010 音频扩展板+喇叭
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EHM+Airaudio1010.jpg)
+
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EGH-AIRAUDIO_1010.jpg)
+
+- 具备volte功能的电话卡插入开发板/核心板的sim卡槽
+
+2、TYPE-C USB数据线一根
+- Air780EHM核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+3、可Air780EHM核心板/Air780EGH核心板和AirAudio_1010 扩展板的硬件接线方式为:
+|  Air780EHM核心板/Air780EGH核心板 | AirAUDIO_1010扩展板 |
+| ---------------                 | -----------------   |
+| 33/I2S_MCLK                     | I2S_MCLK            |
+| 30/I2S_BCK                      | I2S_BCK             |
+| 31/I2S_LRCK                     | I2S_LRCK            |
+| 32/I2S_DIN                      | I2S_DIN             |
+| 33/I2S_DOUT                     | I2S_DOUT            |
+| 67/I2C1_SCL                     | I2C_SCL             |
+| 66/I2C1_SDA                     | I2C_SDA             |
+| 25/GPIO26                       | PA_EN               |
+| 23/GPIO2                        | 8311_EN             |
+| VDD_EXT                         | VCC                 |
+| GND                             | GND                 |
+
+## 演示软件环境
+1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/)
+
+2、[Air780EHM V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EHM/LuatOS-SoC_V2016_Air780EHM.zip),选择支持Volte功能的1、2、13或101、102、113号固件。不同版本区别请见[Air780EHM LuatOS固件版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+
+ [Air780EGH V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EGH/LuatOS-SoC_V2016_Air780EGH.zip)选择支持Volte功能的1、2、13或101、102、113号固件。不同版本区别参考[Air780EGH LuatOS固件版本](https://docs.openluat.com/air780egh/luatos/firmware/version/)。
+
+3、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明] (https://docs.openluat.com/air780epm/common/luatio/)
+
+4、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+## 相关软件资料
+1、cc库   https://docs.openluat.com/osapi/core/cc/
+
+2、exaudio - 音频扩展库  https://docs.openluat.com/osapi/ext/exaudio/
+
+3、CC_IND -- 通话状态变化
+
+  "READY":通话准备完成,可以拨打电话或者呼入电话了
+
+  "INCOMINGCALL":有电话呼入
+  
+  "CONNECTED":电话已经接通
+
+  "DISCONNECTED":电话被对方挂断
+
+  "SPEECH_START":通话开始
+
+  "MAKE_CALL_OK":拨打电话请求成功
+
+  "MAKE_CALL_FAILED":拨打电话请求失败
+
+  "ANSWER_CALL_DONE":接听电话请求完成
+
+  "HANGUP_CALL_DONE":挂断电话请求完成
+
+  "PLAY":开始有音频输出
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、demo脚本代码cc_app.lua中的local TEST_PHONE_NUMBER = "139xxxxxxxx"  -- 场景4拨打的测试号码,修改为自己测试时要拨打的电话号码
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、运行程序,观察日志输出了解通话状态
+
+- 场景1 呼入立即挂断
+
+    当设备启动并初始化完成后,打印READY和电话系统初始化完成。
+
+    当有来电时,会打印INCOMINGCALL,并开始计数响铃次数。
+
+    响铃3次后,自动拒接来电,打印拒接来电和挂断完成。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 1
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 1
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 2
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 3
+    I/user.场景1 拒接来电
+    I/user.CC状态 HANGUP_CALL_DONE
+    I/user.场景1 挂断完成
+    ``` 
+- 场景2 呼入自动接听+10秒后主动挂断
+
+    来电响铃2次后自动接听,通话建立10秒后设备会自动挂断。通话期间持续进行双向录音。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 2
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电   
+    I/user.场景2 接听完成,等待通话建立             
+    I/user.场景2 通话已建立,开始计时  
+    I/user.场景2 10秒挂断定时器创建成功,ID: 2097153
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景2 10秒通话结束,主动挂断  
+    I/user.场景2 已取消挂断定时器 
+    
+           
+- 场景3 呼入自动接听+等待对方挂断
+
+    呼入主动接听并等待对方主动挂断。通话期间持续进行双向录音。
+    
+    类似以下日志:
+
+  ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 3
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电  
+    I/user.场景3 电话已接通,电话号码: 139xxxxxxxx
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景3 通话结束对方挂断
+
+- 场景4 主动呼出预设号码并等待对方挂断。
+  
+  类似以下效果:
+
+  ``` lua
+   I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 4
+   I/user.exaudio_device 使用exaudio.setup初始化音频设备
+   I/user.exaudio_device exaudio.setup初始化成功
+   I/user.CC状态 READY
+   I/user.exaudio_device 通话录音已启用
+   I/user.cc_app 电话系统初始化完成
+   I/user.场景4 开始拨打 139xxxxxxxx
+   I/user.CC状态 MAKE_CALL_OK
+   I/user.CC状态 PLAY
+   I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+   I/user.通话质量 2
+   I/user.CC状态 DISCONNECTED
+   I/user.场景4 通话结束(对方挂断)
+
+
+
+
+    

+ 58 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/audio_drv.lua

@@ -0,0 +1,58 @@
+--[[
+@module  audio_drv
+@summary 音频设备管理模块,负责音频设备的初始化和控制(仅使用exaudio扩展库)
+@version 2.0
+@date    2025.10.23
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1、定义所有硬件引脚常量
+2、使用exaudio扩展库初始化音频设备
+]]
+
+-- 引入exaudio库
+local exaudio = require("exaudio")
+
+-- exaudio配置参数
+local audio_configs = {
+    model = "es8311",         -- dac类型: "es8311"
+    i2c_id = 0,               -- i2c_id: 可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = gpio.AUDIOPA_EN,            -- 音频放大器电源控制管脚
+    dac_ctrl = 20,           -- 音频编解码芯片电源控制管脚
+    dac_delay = 3,            -- DAC启动前冗余时间(单位100ms)
+    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(单位1ms)
+    dac_time_delay = 100,     -- 播放完毕后PA与DAC关闭间隔(单位1ms)
+    bits_per_sample = 16,     -- 采样位深
+    pa_on_level = 1           -- PA打开电平 1:高 0:低
+}
+
+
+exaudio.vol(70)            -- 喇叭音量
+exaudio.mic_vol(65)        -- 麦克风音量
+
+-- 初始化音频设备
+local function initAudioDevice()
+
+    -- 使用exaudio.setup统一配置音频设备
+    log.info("audio_drv", "使用exaudio.setup初始化音频设备")
+    if exaudio.setup(audio_configs) then
+        log.info("audio_drv", "exaudio.setup初始化成功")
+    else
+        log.error("audio_drv", "exaudio.setup初始化失败")
+        return false
+    end
+    
+    -- log.info("audio_drv", "Audio device initialized using exaudio only")
+    return true
+end
+
+-- 获取音频通道ID(保留用于兼容性)
+local function getMultimediaId()
+    return 0  -- 返回默认值0
+end
+
+-- 导出接口
+return {
+    initAudioDevice = initAudioDevice,
+    getMultimediaId = getMultimediaId
+}

+ 246 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/cc_app.lua

@@ -0,0 +1,246 @@
+--[[
+@module  cc_app
+@summary 通话业务逻辑模块,实现4种通话场景的处理和通话录音功能
+@version 1.0
+@date    2025.10.21
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1. 设置 ACTIVE_SCENARIO 变量选择需要的场景(1-4)
+2. 其他场景自动禁用,不会影响程序运行
+3. 每个场景都有独立的状态处理逻辑
+4. 实现通话录音功能
+
+支持的场景:
+[场景1] 呼入立即挂断(响铃3次后自动拒接)
+[场景2] 呼入自动接听(响2声)+10秒后主动挂断
+[场景3] 呼入自动接听(响2声)+等待对方挂断
+[场景4] 主动呼出电话+等待对方挂断
+
+
+注意事项:
+1. 设置ACTIVE_SCENARIO选择要启用的场景(1-4)
+2. 场景4会主动拨打电话(修改为自己测试时要拨打的电话号码)
+3. 所有通话都会被录音(需确保存储空间足够)
+]]
+
+-- 引入音频设备模块
+local audio_drv = require "audio_drv"
+
+-- ====================== 配置区域 ======================
+-- 设置当前激活的场景(1-4),注释掉不需要的场景
+-- local ACTIVE_SCENARIO = 1  -- 场景1:呼入立即挂断
+-- local ACTIVE_SCENARIO = 2  -- 场景2:呼入自动接听,10秒后主动挂断
+--local ACTIVE_SCENARIO = 3  -- 场景3:呼入自动接听,等待对方挂断
+-- local ACTIVE_SCENARIO = 4  -- 场景4:主动呼出,等待对方挂断
+
+-- 全局状态变量
+local call_counter = 0                 -- 响铃计数器(用于场景1-3)
+local caller_number = ""               -- 来电号码(用于场景1-3)
+local is_connected = false             -- 通话连接状态标志(用于场景2)
+local outgoing_number = "10000"  -- 呼出号码(用于场景4),修改为自己测试时要拨打的电话号码
+
+-- ====================== 录音功能 ======================
+-- 创建音频数据缓冲区
+local up1 = zbuff.create(6400,0)      -- 上行数据保存区1
+local up2 = zbuff.create(6400,0)      -- 上行数据保存区2
+local down1 = zbuff.create(6400,0)    -- 下行数据保存区1
+local down2 = zbuff.create(6400,0)    -- 下行数据保存区2
+
+-- 音频数据回调函数
+local function recordCallback(is_dl, point)
+    if is_dl then
+        log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
+    else
+        log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
+    end
+    log.info("通话质量", cc.quality())
+    -- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
+end
+
+-- 启用通话录音
+local function enableRecording()
+    cc.record(true, up1, up2, down1, down2)
+    cc.on("record", recordCallback)
+    log.info("cc_app", "通话录音已启用")
+end
+
+-- 获取所有缓冲区
+local function getRecordingBuffers()
+    return {
+        up1 = up1,
+        up2 = up2,
+        down1 = down1,
+        down2 = down2
+    }
+end
+
+-- ====================== 场景处理函数 ======================
+
+-- 场景1:呼入立即挂断(响铃3次后)
+local function handle_scenario1(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景1", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃3声后拒接
+        if call_counter >= 3 then
+            log.info("场景1", "拒接来电")
+            cc.hangUp(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "HANGUP_CALL_DONE" then
+        log.info("场景1", "挂断完成")
+        call_counter = 0
+    end
+end
+
+-- 场景2挂断回调函数
+local function scenario2_hangup_callback()
+    log.info("场景2", "10秒通话结束,主动挂断")
+    cc.hangUp(0)
+    is_connected = false
+end
+
+-- 场景2:呼入自动接听,10秒后主动挂断
+local function handle_scenario2(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景2", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景2", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "ANSWER_CALL_DONE" then
+        log.info("场景2", "接听完成,等待通话建立")
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        if not is_connected then
+            log.info("场景2", "通话已建立,开始计时")
+            is_connected = true
+            
+            -- 创建10秒后挂断的定时器
+            sys.timerStart(scenario2_hangup_callback, 10000)  -- 10秒后执行挂断
+            log.info("场景2", "10秒挂断定时器创建成功")
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "DISCONNECTED" then
+        log.info("场景2", "通话结束")
+        is_connected = false
+        
+        -- 取消挂断定时器
+        sys.timerStop(scenario2_hangup_callback)
+        log.info("场景2", "已取消挂断定时器")
+        
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景3:呼入自动接听,等待对方挂断
+local function handle_scenario3(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景3", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景3", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        log.info("场景3", "电话已接通,电话号码:", caller_number)
+    elseif status == "DISCONNECTED" then
+        -- 对方挂断通话
+        log.info("场景3", "通话结束对方挂断")
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景4:主动呼出,等待对方挂断
+local function handle_scenario4(status)
+    if status == "CONNECTED" then
+        log.info("场景4", "呼叫接通")
+    elseif status == "DISCONNECTED" then
+        log.info("场景4", "通话结束(对方挂断)")
+    elseif status == "MAKE_CALL_FAILED" then
+        log.info("场景4", "呼叫失败")
+    end
+end
+
+-- 场景4拨号函数
+local function dial_for_scenario4()
+    log.info("场景4", "开始拨打", outgoing_number)
+    cc.dial(0, outgoing_number)
+end
+
+-- ====================== 主事件处理器 ======================
+sys.subscribe("CC_IND", function(status)
+    log.info("CC状态", status)
+    
+    -- 根据激活的场景调用对应的处理函数
+    if ACTIVE_SCENARIO == 1 then
+        handle_scenario1(status)
+    elseif ACTIVE_SCENARIO == 2 then
+        handle_scenario2(status)
+    elseif ACTIVE_SCENARIO == 3 then
+        handle_scenario3(status)
+    elseif ACTIVE_SCENARIO == 4 then
+        handle_scenario4(status)
+    end
+    
+    -- 所有场景都需要处理的通用状态
+    if status == "READY" then
+        sys.publish("CC_READY")  -- 发布系统就绪事件
+        
+        -- 场景4:电话系统就绪后自动拨号
+        if ACTIVE_SCENARIO == 4 then
+            sys.timerStart(dial_for_scenario4, 1000)  -- 延迟1秒拨号
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "MAKE_CALL_FAILED" or status == "DISCONNECTED" then
+        
+        audio.pm(0,audio.STANDBY)
+        -- audio.pm(0,audio.SHUTDOWN)   --低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
+    end
+end)
+
+-- ====================== 电话系统初始化 ======================
+local function init_cc()
+    -- 初始化音频设备(使用exaudio)
+    audio_drv.initAudioDevice()
+    
+    -- 等待电话系统就绪
+    sys.waitUntil("CC_READY")
+    
+    -- 初始化电话功能
+    cc.init(audio_drv.getMultimediaId())
+    
+    -- 启用通话录音(录音功能在cc_app中)
+    enableRecording()
+    
+    log.info("cc_app", "电话系统初始化完成")
+end
+
+-- 启动初始化任务
+sys.taskInit(init_cc)
+
+log.info("cc_app", "通话业务逻辑模块加载完成,当前场景:", ACTIVE_SCENARIO)
+
+-- 导出录音相关功能(如果需要被其他模块使用)
+-- return {
+--     enableRecording = enableRecording,
+--     getRecordingBuffers = getRecordingBuffers,
+--     recordCallback = recordCallback
+-- }

+ 79 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/main.lua

@@ -0,0 +1,79 @@
+--[[
+@module  main
+@summary LuatOS语音通话应用主入口,负责加载功能模块
+@version 1.0
+@date    2025.07.16
+@author  陈媛媛
+@usage
+本demo演示的核心功能为:
+1、音频设备初始化与控制
+2、完整通话业务逻辑处理(5种通话场景),详情如下:
+-呼入,挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,主动挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,对方挂断,挂断消息识别打印;
+-呼出,对方接通,接听消息识别打印,建立通话后一段时间,对方主动挂断,挂断消息识别打印;
+-通话中给对方播放音频功能(暂不支持,待补充)
+3、通话状态监控与日志记录
+4、支持Air8000系列和Air780EHV等硬件平台
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "VOICE_CALL_DEMO"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+-- 仅加载必要的功能模块
+require "audio_drv"  -- 音频设备管理模块
+require "cc_app"        -- 通话业务逻辑模块
+
+-- 用户代码已结束---------------------------------------------
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!

+ 198 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/readme.md

@@ -0,0 +1,198 @@
+# CC_DEMO 项目说明
+
+## 项目概述
+本项目是基于 Air780EHV 的语音通信演示demo,实现了基本的语音通话功能,包括音频设备初始化、通话建立、通话管理等核心功能。
+
+## 文件结构
+- main.lua: 主程序入口,仅加载初始化模块
+- audio_device.lua: 管理音频设备初始化与控制
+- cc_app.lua: 实现完整通话业务逻辑
+
+## 功能说明
+1. **音频设备初始化与控制**:使用exaudio.setup统一配置ES8311音频编解码芯片和扬声器功放,包括I2C、I2S接口设置及音量控制。
+
+2. **完整通话业务逻辑处理**:实现4种通话场景,包括呼入和呼出的各种情况处理。
+
+按照自己的通话需求启用对应的Lua文件,其余注释掉;
+
+- (1)呼入,主动挂断(响铃3次后自动拒接);
+
+- (2)呼入,自动接听,接听消息识别打印,主动挂断,挂断消息识别打印;
+
+- (3)呼入,自动接听,接听消息识别打印,等待对方挂断,挂断消息识别打印;
+
+- (4)呼出,对方接通,接听消息识别打印,建立通话后一段时间,等待对方挂断,挂断消息识别打印;
+
+3. **通话状态监控与日志记录**:实时记录通话状态变化及相关信息,所有通话场景均支持双向录音(上行+下行),实时记录通话数据。
+
+## 演示硬件环境
+1、Air780EHV核心板+AirAUDIO_1000配件板+喇叭
+
+![alt text](https://docs.openLuat.com/cdn/image/Air780EHV+Airaudio1000.jpg)
+ 
+- 具备volte功能的电话卡插入开发板/核心板的sim卡槽
+
+2、TYPE-C USB数据线一根
+- Air780EHV核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+3、可选AirAudio_1000 配件板一块,Air780EHV核心板和AirAudio_1000 配件板的硬件接线方式为:
+| Air780EHV核心板 | AirAUDIO_1000配件板 |
+| ---------------| -----------------   |
+| 3/MIC+         |     MIC+            |
+| 4/MIC-         |     MIC-            |
+| 5/SPK+         |     SPK+            |
+| 6/SPK-         |     SPK-            |
+| 19/GPIO22      |     PA_EN           |
+| 3V3            |     VCC             |
+| GND            |     GND             |
+
+## 演示软件环境
+1、Luatools下载调试工具 [https://docs.openluat.com/air780epm/common/Luatools/]
+
+2、Air780EHV V2014版本固件(理论上,2025年7月26日之后发布的固件都可以),选择支持Volte功能的固件。不同版本区别请见https://docs.openluat.com/air780ehv/luatos/firmware/version/
+
+3、合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明  https://docs.openluat.com/air780epm/common/luatio/
+
+4、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+## 相关软件资料
+1、cc库   https://docs.openluat.com/osapi/core/cc/
+
+2、exaudio - 音频扩展库  https://docs.openluat.com/osapi/ext/exaudio/
+
+3、CC_IND -- 通话状态变化
+
+  "READY":通话准备完成,可以拨打电话或者呼入电话了
+
+  "INCOMINGCALL":有电话呼入
+
+  "CONNECTED":电话已经接通
+
+  "DISCONNECTED":电话被对方挂断
+
+  "SPEECH_START":通话开始
+
+  "MAKE_CALL_OK":拨打电话请求成功
+
+  "MAKE_CALL_FAILED":拨打电话请求失败
+
+  "ANSWER_CALL_DONE":接听电话请求完成
+
+  "HANGUP_CALL_DONE":挂断电话请求完成
+  
+  "PLAY":开始有音频输出
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、demo脚本代码cc_app.lua中的local TEST_PHONE_NUMBER = "139XXXXXXXX"  -- 场景4拨打的测试号码,修改为自己测试时要拨打的电话号码
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、运行程序,观察日志输出了解通话状态
+
+- 场景1 呼入立即挂断
+
+    当设备启动并初始化完成后,打印READY和电话系统初始化完成。
+
+    当有来电时,会打印INCOMINGCALL,并开始计数响铃次数。
+
+    响铃3次后,自动拒接来电,打印拒接来电和挂断完成。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 1
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 1
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 2
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 3
+    I/user.场景1 拒接来电
+    I/user.CC状态 HANGUP_CALL_DONE
+    I/user.场景1 挂断完成
+    ``` 
+- 场景2 呼入自动接听+10秒后主动挂断
+
+    来电响铃2次后自动接听,通话建立10秒后设备会自动挂断。通话期间持续进行双向录音。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 2
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电   
+    I/user.场景2 接听完成,等待通话建立             
+    I/user.场景2 通话已建立,开始计时  
+    I/user.场景2 10秒挂断定时器创建成功,ID: 2097153
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景2 10秒通话结束,主动挂断  
+    I/user.场景2 已取消挂断定时器 
+    
+           
+- 场景3 呼入自动接听+等待对方挂断
+
+    呼入主动接听并等待对方主动挂断。通话期间持续进行双向录音。
+    
+    类似以下日志:
+
+  ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 3
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电  
+    I/user.场景3 电话已接通,电话号码: 139xxxxxxxx
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景3 通话结束对方挂断
+
+- 场景4 主动呼出预设号码并等待对方挂断。
+  
+  类似以下效果:
+
+  ``` lua
+   I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 4
+   I/user.exaudio_device 使用exaudio.setup初始化音频设备
+   I/user.exaudio_device exaudio.setup初始化成功
+   I/user.CC状态 READY
+   I/user.exaudio_device 通话录音已启用
+   I/user.cc_app 电话系统初始化完成
+   I/user.场景4 开始拨打 139xxxxxxxx
+   I/user.CC状态 MAKE_CALL_OK
+   I/user.CC状态 PLAY
+   I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+   I/user.通话质量 2
+   I/user.CC状态 DISCONNECTED
+   I/user.场景4 通话结束(对方挂断)
+
+
+
+
+    

+ 0 - 126
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/main.lua

@@ -1,126 +0,0 @@
-
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "ccdemo"
-VERSION = "1.0.0"
-log.style(1)
---[[]
-运行环境:Air780EHV核心板+AirAUDIO_1000配件板
-最后修改时间:2025-6-17
-使用了如下IO口:
-[3, "MIC+", " PIN3脚, 用于麦克风正极"],
-[4, "MIC-", " PIN4脚, 用于麦克风负极"],
-[5, "spk+", " PIN5脚, 用于喇叭正极"],
-[6, "spk-", " PIN6脚, 用于喇叭负极"],
-[20, "AudioPA_EN", " PIN20脚, 用于PA使能脚"],
-3.3V
-GND
-执行逻辑为:
-设置i2s和音频参数,读取文件qianzw.txt里面的内容,然后播放出来
-]]
-
--- sys库是标配
-sys = require("sys")
-
-local up1 = zbuff.create(6400,0)
-local up2 = zbuff.create(6400,0)
-local down1 = zbuff.create(6400,0)
-local down2 = zbuff.create(6400,0)
-local cnt = 0
-
-local function record(is_dl, point)
-    if is_dl then
-        log.info("下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
-    else
-        log.info("上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
-    end
-	log.info("通话质量", cc.quality())
-    -- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
-end
-
-sys.subscribe("CC_IND", function(state)
-    log.info("cc状态", state)
-    if state == "READY" then
-        sys.publish("CC_READY")
-    elseif state == "INCOMINGCALL" then
-		cnt = cnt + 1
-		if cnt > 1 then
-			cc.accept(0)
-		end
-    elseif state == "HANGUP_CALL_DONE" or state == "MAKE_CALL_FAILED" or state == "DISCONNECTED" then
-		audio.pm(0,audio.STANDBY)
-		-- audio.pm(0,audio.SHUTDOWN)	--低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
-	end
-end)
-
-
-
-sys.taskInit(function()
-    cc.on("record", record)
-    cc.record(true, up1, up2, down1, down2)
-    local multimedia_id = 0
- local i2c_id = 0 -- i2c_id 0
-
-    local pa_pin = gpio.AUDIOPA_EN -- 喇叭pa功放脚
-    local power_pin = 20 -- es8311电源脚
-
-    local i2s_id = 0 -- i2s_id 0
-    local i2s_mode = 0 -- i2s模式 0 主机 1 从机
-    local i2s_sample_rate = 16000 -- 采样率
-    local i2s_bits_per_sample = 16 -- 数据位数
-    local i2s_channel_format = i2s.MONO_R -- 声道, 0 左声道, 1 右声道, 2 立体声
-    local i2s_communication_format = i2s.MODE_LSB -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
-    local i2s_channel_bits = 16 -- 声道的BCLK数量
-
-    local multimedia_id = 0 -- 音频通道 0
-    local pa_on_level = 1 -- PA打开电平 1 高电平 0 低电平
-    local power_delay = 3 -- 在DAC启动前插入的冗余时间,单位100ms
-    local pa_delay = 100 -- 在DAC启动后,延迟多长时间打开PA,单位1ms
-    local power_on_level = 1 -- 电源控制IO的电平,默认拉高
-    local power_time_delay = 100 -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms
-
-    local voice_vol = 70 -- 喇叭音量
-    local mic_vol = 80 -- 麦克风音量
-    gpio.setup(power_pin, 1, gpio.PULLUP)
-    gpio.setup(pa_pin, 1, gpio.PULLUP)
-
-    sys.wait(200)
-
-
-    i2c.setup(i2c_id, i2c.FAST) -- 设置i2c
-    i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,
-        i2s_channel_bits) -- 设置i2s
-
-    audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
-    audio.setBus(multimedia_id, audio.BUS_I2S, {
-        chip = "es8311",
-        i2cid = i2c_id,
-        i2sid = i2s_id,
-    }) -- 通道0的硬件输出通道设置为I2S
-
-    audio.vol(multimedia_id, voice_vol)
-    audio.micVol(multimedia_id, mic_vol)
-
-    cc.init(multimedia_id)
-	audio.pm(0,audio.STANDBY)
-    sys.waitUntil("CC_READY")
-    log.info("一切就绪,5S后准备打电话")
-    sys.wait(5000)   
-    -- cc.dial(0,"12345678910) --拨打电话
-
-
-
-end)
-
--- sys.taskInit(function()
---     while 1 do
---         -- 打印内存状态, 调试用
---         sys.wait(1000)
---         log.info("lua", rtos.meminfo())
---         log.info("sys", rtos.meminfo("sys"))
---     end
--- end)
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
-sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 16 - 7
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/AirGPIO_1000.lua

@@ -1,4 +1,19 @@
---本文件中的主机是指I2C主机,具体指Air780EHV
+--[[
+@module  AirGPIO_1000
+@summary AirGPIO_1000应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为AirGPIO_1000驱动配置文件,核心业务逻辑为:
+1、配置主机和AirGPIO_1000之间的通信参数;
+2、配置AirGPIO_1000上的扩展GPIO管脚功能;支持配置为输出,输入和中断三种模式;
+
+本文件没有对外接口,直接require "AirGPIO_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指I2C主机,具体指Air780EHM/Air780EHV/Air780EGH
 --本文件中的从机是指I2C从机,具体指AirGPIO_1000配件板上的IO扩展芯片
 
 local AirGPIO_1000 = 
@@ -138,7 +153,6 @@ end
 --        AirGPIO_1000可以扩展出来16个GPIO,这些GPIO支持配置为输入;
 --        AirGPIO_1000上的任意一个输入GPIO的状态发生上升沿或者下降沿变化时,会通过INT引脚通知到主机的int_id中断引脚;
 --        此时主机可以通过I2C接口立即读取AirGPIO_1000上配置为输入模式的扩展GPIO的电平状态,从而判断是哪些扩展GPIO的输入电平发生了变化;
---        取值范围:nil或者空,或者0到9,或者12到55,注意不要使用已经复用为其他功能的引脚;nil或者空时,表示不使用中断通知功能;
 --        如果没有传入此参数,则默认为空,表示不使用中断通知功能;
 
 --返回值:成功返回true,失败返回false
@@ -149,11 +163,6 @@ function AirGPIO_1000.init(i2c_id, gpio_int_id)
         return false
     end
 
-    if not (gpio_int_id==nil or gpio_int_id>=0 and gpio_int_id<=9 or gpio_int_id>=12 and gpio_int_id<=55) then
-        log.error("AirGPIO_1000.init", "invalid gpio_int_id", gpio_int_id)
-        return false
-    end
-
     AirGPIO_1000.i2c_id = i2c_id
     AirGPIO_1000.gpio_int_id = gpio_int_id
 

+ 20 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/gpio_app.lua

@@ -1,3 +1,18 @@
+--[[
+@module  gpio_app
+@summary gpio_app应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为gpio_app应用功能模块,核心业务逻辑为:
+1、初始化Air780EHM/Air780EHV/Air780EGH和AirGPIO_1000之间的通信参数
+2、GPIO输出测试,输入测试,GPIO中断测试
+
+本文件没有对外接口,直接在main.lua中require "gpio_app"就可以加载运行;
+]]
+
+
 --加载AirGPIO_1000驱动文件
 local air_gpio = require "AirGPIO_1000"
 
@@ -73,11 +88,11 @@ local function gpio_int_task_func()
 end
 
 
---初始化Air780EHV和AirGPIO_1000之间的通信参数
---使用Air780EHV的I2C1
---使用Air780EHV的GPIO2做为中断引脚
---Air780EHV核心板和AirGPIO_1000配件板的接线方式如下
---Air780EHV核心板             AirGPIO_1000配件板
+--初始化Air780EHM/Air780EHV/Air780EGH和AirGPIO_1000之间的通信参数
+--使用Air780EHM/Air780EHV/Air780EGH的I2C1
+--使用Air780EHM/Air780EHV/Air780EGH的GPIO2做为中断引脚
+--Air780EHM/Air780EHV/Air780EGH核心板和AirGPIO_1000配件板的接线方式如下
+--Air780EHM/Air780EHV/Air780EGH核心板             AirGPIO_1000配件板
 --      3V3-----------------3V3
 --       GND-----------------GND
 --     66/I2C1SDA-----------------SDA

+ 14 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/main.lua

@@ -1,3 +1,17 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
+分输出、输入和中断三种应用场景来演示;
+更多说明参考本目录下的readme.md文件
+]]
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,12 +21,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
-本demo演示的核心功能为:
-Air780EHV核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
-分输出、输入和中断三种应用场景来演示;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirGPIO_1000"
 VERSION = "001.000.000"

+ 13 - 8
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/readme.md

@@ -12,28 +12,29 @@ AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
+Air780EHM/Air780EHV/Air780EGH核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
 
 分输出、输入和中断三种应用场景来演示;
 
-
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirGPIO_1000配件板相关资料](https://docs.openluat.com/accessory/AirGPIO_1000/)
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirGPIO_1000/image/connect_Air780EHV.jpg)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirGPIO_1000配件板
 
 3、母对母的杜邦线8根
 
-4、Air780EHV核心板和AirGPIO_1000配件板的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和AirGPIO_1000配件板的硬件接线方式为
 
-| Air780EHV核心板 | AirGPIO_1000配件板 |
+| Air780EHM/Air780EHV/Air780EGH核心板 | AirGPIO_1000配件板 |
 | ------------ | ------------------ |
 |     3V3     |         3V3        |
 |     GND   |         GND        |
@@ -50,9 +51,13 @@ Air780EHV核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
 
 ## 演示软件环境
 
-1、Luatools下载调试工具
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
 
-2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
 
 
 ## 演示操作步骤

+ 15 - 11
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/AirKEY_1000.lua

@@ -1,4 +1,18 @@
---本文件中的主机是指Air780EHV核心板
+--[[
+@module  AirKEY_1000
+@summary AirKEY_1000应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为AirKEY_1000驱动配置文件,核心业务逻辑为:
+1、配置主机和AirKEY_1000之间的控制参数;
+
+本文件没有对外接口,直接require "AirKEY_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指Air780EHM/Air780EHV/Air780EGH核心板
 --AirKEY_1000是合宙设计生产的一款8路独立按键的配件板
 
 local AirKEY_1000 = {}
@@ -31,16 +45,6 @@ local AirKEY_1000 = {}
 
 --返回值:成功返回true,失败返回false
 function AirKEY_1000.setup(key_id, gpio_id, int_mode, int_cbfunc)
-    if not (key_id>=1 and key_id<=8) then
-        log.error("AirKEY_1000.setup error", "invalid key_id", key_id)
-        return false
-    end
-
-    if not (gpio_id>=0 and gpio_id<=9 or gpio_id>=12 and gpio_id<=55) then
-        log.error("AirKEY_1000.setup error", "invalid gpio_id", gpio_id)
-        return false
-    end
-
     if not (int_mode==gpio.RISING or int_mode==gpio.FALLING) then
         log.error("AirKEY_1000.setup error", "invalid int_mode", int_mode)
         return false

+ 21 - 13
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/key_app.lua

@@ -1,14 +1,22 @@
 --[[
-本功能模块演示的内容为:
-使用Air780EHV核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态
-AirKEY_1000是合宙设计生产的一款8路独立按键的配件板
+@module  key_app
+@summary key_app应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为key_app应用功能模块,核心业务逻辑为:
+1、使用Air780EHM/Air780EHV/Air780EGH核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态
+2、AirKEY_1000是合宙设计生产的一款8路独立按键的配件板
+
+本文件没有对外接口,直接在main.lua中require "key_app"就可以加载运行;
 ]]
 
 
 --加载AirKEY_1000驱动文件
 local air_key = require "AirKEY_1000"
 
---AirKEY_1000上8个按键对应的Air780EHV的GPIO ID
+--AirKEY_1000上8个按键对应的Air780EHM/Air780EHV/Air780EGH的GPIO ID
 local KEY1_GPIO_ID = 26
 local KEY2_GPIO_ID = 21
 local KEY3_GPIO_ID = 24
@@ -21,7 +29,7 @@ local KEY8_GPIO_ID = 27
 
 --按键1的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键1时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键1时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key1_int_cbfunc(int_level, gpio_id)
@@ -34,7 +42,7 @@ end
 
 --按键2的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键2时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键2时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key2_int_cbfunc(int_level, gpio_id)
@@ -47,7 +55,7 @@ end
 
 --按键3的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键3时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键3时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key3_int_cbfunc(int_level, gpio_id)
@@ -60,7 +68,7 @@ end
 
 --按键4的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键4时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键4时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key4_int_cbfunc(int_level, gpio_id)
@@ -74,7 +82,7 @@ end
 
 --按键5、6、7、8的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键5、6、7、8时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键5、6、7、8时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key5678_int_cbfunc(int_level, gpio_id)
@@ -99,8 +107,8 @@ end
 
 
 
---本demo中,Air780EHV核心板和AirKEY_1000配件板的接线方式如下
---Air780EHV核心板             AirKEY_1000配件板
+--本demo中,Air780EHM/Air780EHV/Air780EGH核心板和AirKEY_1000配件板的接线方式如下
+--Air780EHM/Air780EHV/Air780EGH核心板             AirKEY_1000配件板
 --     25/GPIO26-----------------K1
 --     107/GPIO21-----------------K2
 --     20/GPIO24-----------------K3
@@ -114,7 +122,7 @@ end
 
 
 --AirKEY_1000上的1、2、3、4,四个按键的引脚
---分别和Air780EHV的KEY1_GPIO_ID、KEY2_GPIO_ID、KEY3_GPIO_ID、KEY4_GPIO_ID四个引脚相连
+--分别和Air780EHM/Air780EHV/Air780EGH的KEY1_GPIO_ID、KEY2_GPIO_ID、KEY3_GPIO_ID、KEY4_GPIO_ID四个引脚相连
 --GPIO配置为上升沿触发中断,可以实时检测到按键弹起的动作
 --按键弹起时,会执行对应的中断处理函数key1_int_cbfunc、key2_int_cbfunc、key3_int_cbfunc、key4_int_cbfunc
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
@@ -125,7 +133,7 @@ air_key.setup(3, KEY3_GPIO_ID, gpio.RISING, key3_int_cbfunc)
 air_key.setup(4, KEY4_GPIO_ID, gpio.RISING, key4_int_cbfunc)
 
 --AirKEY_1000上的5、6、7、8,四个按键的引脚
---分别和Air780EHV的KEY5_GPIO_ID、KEY6_GPIO_ID、KEY7_GPIO_ID、KEY8_GPIO_ID四个引脚相连
+--分别和Air780EHM/Air780EHV/Air780EGH的KEY5_GPIO_ID、KEY6_GPIO_ID、KEY7_GPIO_ID、KEY8_GPIO_ID四个引脚相连
 --GPIO配置为下降沿触发中断,可以实时检测到按键按下的动作
 --按键按下时,会执行对应的中断处理函数key5678_int_cbfunc
 --这四个按键共用了同一个中断处理函数,可以通过函数传入的GPIO ID来区分是哪一个按键被按下

+ 14 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/main.lua

@@ -1,3 +1,17 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirKEY_1000是合宙设计生产的一款支持8个独立按键的配件板;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirKEY_1000配件板,使用Air780EHM/Air780EHV/Air780EGH核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
+更多说明参考本目录下的readme.md文件
+]]
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,11 +21,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirKEY_1000是合宙设计生产的一款支持8个独立按键的配件板;
-本demo演示的核心功能为:
-Air780EHV核心板+AirKEY_1000配件板,使用Air780EHV核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirKEY_1000"
 VERSION = "001.000.000"

+ 10 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/readme.md

@@ -8,6 +8,8 @@
 
 ## 用户消息介绍
 
+## 用户消息介绍
+
 1、"KEY1_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
 
 2、"KEY2_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
@@ -30,30 +32,32 @@ AirKEY_1000是合宙设计生产的一款支持8个独立按键的配件板;
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirKEY_1000配件板,使用Air780EHV核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
+Air780EHM/Air780EHV/Air780EGH核心板+AirKEY_1000配件板,使用Air780EHM/Air780EHV/Air780EGH核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
 
 
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirKEY_1000配件板相关资料](https://docs.openluat.com/accessory/AirKEY_1000/)
 
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirKEY_1000/image/Air780EHV_connection.jpg)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirKEY_1000配件板
 
 3、母对母的杜邦线9根
 
-4、Air780EHV核心板和AirKEY_1000配件板的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和AirKEY_1000配件板的硬件接线方式为
 
-- Air780EHV核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
 - 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
 
-| Air780EHV核心板 |  AirKEY_1000配件板 |
+| Air780EHM/Air780EHV/Air780EGH核心板 |  AirKEY_1000配件板 |
 | ------------ | ------------------ |
 |    25/GPIO26    |         K1         |
 |    107/GPIO21    |         K2         |

+ 16 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua

@@ -1,4 +1,19 @@
---本文件中的主机是指I2C主机,具体指Air780EHV
+--[[
+@module  AirSHT30_1000
+@summary AirSHT30_1000应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为AirSHT30_1000驱动配置文件,核心业务逻辑为:
+1、打开AirSHT30_1000;
+2、读取温湿度数据;
+
+本文件没有对外接口,直接require "AirSHT30_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指I2C主机,具体指Air780EHM/Air780EHV/Air780EGH
 --本文件中的从机是指I2C从机,具体指AirSHT30_1000配件板上的sht30温湿度传感器芯片
 
 local AirSHT30_1000 = 
@@ -9,11 +24,6 @@ local AirSHT30_1000 =
 -- 从机地址为0x44
 local slave_addr = 0x44
 
---电平设为3.3v
-pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
---设置gpio2输出,给camera_sda、camera_scl引脚提供上拉
-gpio.setup(2, 1)
-
 -- 计算数据表data中所有数据元素的crc8校验值
 local function crc8(data)
     local crc = 0xFF

+ 14 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/main.lua

@@ -1,3 +1,17 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+更多说明参考本目录下的readme.md文件
+]]
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,11 +21,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
-本demo演示的核心功能为:
-Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirSHT30_1000"
 VERSION = "001.000.000"

+ 19 - 8
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/readme.md

@@ -1,3 +1,10 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、sht30_app.lua:每隔1秒读取一次温湿度数据;
+
+3、AirSHT30_1000.lua:AirSHT30_1000驱动文件;
 
 ## 演示功能概述
 
@@ -5,25 +12,27 @@ AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+Air780EHM/Air780EHV/Air780EGH核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 
 
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirSHT30_1000配件板相关资料](https://docs.openluat.com/accessory/AirSHT30_1000/)
 
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirSHT30_1000/image/connect_780ehv.png)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirSHT30_1000配件板
 
 3、母对母的杜邦线4根
 
-| Air780EHV核心板 | AirSHT30_1000配件板|
+| Air780EHM/Air780EHV/Air780EGH核心板 | AirSHT30_1000配件板|
 | ------------ | ------------------ |
 |     3V3     |         3V3        |
 |     GND   |         GND        |
@@ -33,9 +42,13 @@ Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据
 
 ## 演示软件环境
 
-1、Luatools下载调试工具
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
 
-2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
 
 
 ## 演示操作步骤
@@ -53,5 +66,3 @@ Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据
 ``` lua
 [2025-09-22 11:32:35.955][000000203.154] I/user.read_sht30_task_func temprature 27.61 ℃
 [2025-09-22 11:32:35.957][000000203.154] I/user.read_sht30_task_func humidity 60.17 %RH
-
-

+ 14 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/sht30_app.lua

@@ -1,3 +1,17 @@
+--[[
+@module  sht30_app
+@summary sht30_app应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为sht30_app应用功能模块,核心业务逻辑为:
+1、每隔1秒读取一次温湿度数据;
+
+本文件没有对外接口,直接在main.lua中require "sht30_app"就可以加载运行;
+]]
+
+
 --加载AirSHT30_1000驱动文件
 local air_sht30 = require "AirSHT30_1000"
 

+ 16 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua

@@ -1,4 +1,19 @@
---本文件中的主机是指I2C主机,具体指Air780EHV
+--[[
+@module  AirVOC_1000
+@summary AirVOC_1000应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为AirVOC_1000驱动配置文件,核心业务逻辑为:
+1、打开AirVOC_1000;
+2、读取TVOC的空气质量等级;
+
+本文件没有对外接口,直接require "AirVOC_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指I2C主机,具体指Air780EHM/Air780EHV/Air780EGH
 --本文件中的从机是指I2C从机,具体指AirVOC_1000配件板上的ags02ma VOC(挥发性有机化合物)气体传感器芯片
 
 local AirVOC_1000 = 

+ 15 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/main.lua

@@ -1,3 +1,18 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+更多说明参考本目录下的readme.md文件
+]]
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,12 +22,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
-主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
-本demo演示的核心功能为:
-Air8101核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirVOC_1000"
 VERSION = "001.000.000"

+ 20 - 7
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/readme.md

@@ -1,3 +1,10 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、voc_app.lua:每隔1秒读取一次TVOC数据;
+
+3、AirVOC_1000.lua:AirVOC_1000驱动文件;
 
 ## 演示功能概述
 
@@ -7,27 +14,29 @@ AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+Air780EHM/Air780EHV/Air780EGH核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
 
 
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirVOC_1000配件板相关资料](https://docs.openluat.com/accessory/AirVOC_1000/)
 
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirVOC_1000/image/connect_Air780ehv.jpg)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirVOC_1000配件板
 
 3、母对母的杜邦线4根
 
-4、Air780EHV核心板和AirVOC_1000配件板的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和AirVOC_1000配件板的硬件接线方式为
 
-| Air780EHV核心板 | AirVOC_1000配件板  |
+| Air780EHM/Air780EHV/Air780EGH核心板 | AirVOC_1000配件板  |
 | ------------ | ------------------ |
 |     3V3     |         3V3        |
 |     GND   |         GND        |
@@ -37,9 +46,13 @@ Air780EHV核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量
 
 ## 演示软件环境
 
-1、Luatools下载调试工具
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
 
-2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
 
 
 ## 演示操作步骤

+ 14 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/voc_app.lua

@@ -1,3 +1,17 @@
+--[[
+@module  voc_app
+@summary voc_app应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为voc_app应用功能模块,核心业务逻辑为:
+1、每隔1秒读取一次TVOC数据空气质量数据;
+
+本文件没有对外接口,直接在main.lua中require "voc_app"就可以加载运行;
+]]
+
+
 --加载AirVOC_1000驱动文件
 local air_voc = require "AirVOC_1000"
 

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/10.amr → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/10.amr


+ 92 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/main.lua

@@ -0,0 +1,92 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+本demo演示的核心功能为:
+1、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
+
+2、play_tts: 支持文字转普通话输出需要固件支持
+
+3、play_stream: 流式播放音频,仅支持PCM 格式,可以将音频推流到云端,用来对接大模型或者流式录音的应用。
+
+4、record_file: 录音到文件,仅支持PCM 格式
+
+5、record_stream:  流式录音,仅支持PCM,可以将音频流不断的拉取,可用来对接大模型
+
+6、sample-6s: 用于测试本地mp3文件播放
+
+7、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
+
+
+更多说明参考本目录下的readme.md文件
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+--[[
+本demo可使用Air780EHM核心板/Air780EGH核心板+AirAUDIO_1010 音频扩展板+喇叭两种硬件环境演示
+]]
+
+PROJECT = "audio"
+VERSION = "1.0.0"
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+
+require "play_file"     --   播放音频文件,可支持wav,amr,mp3 格式音频
+-- require "play_tts"      -- 支持文字转普通话输出需要固件支持
+-- require "play_stream"        -- 流式播放音频,仅支持PCM 格式,可以将音频推流到云端,用来对接大模型或者流式录音的应用。
+-- require "record_file"        -- 录音到文件
+-- require "record_stream"        -- 流式录音   
+
+-- 音频对内存影响较大,不断的打印内存,用于判断是否异常
+sys.timerLoopStart(function()
+    log.info("mem.lua", rtos.meminfo())
+    log.info("mem.sys", rtos.meminfo("sys"))
+ end, 3000)
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 240 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/pins_air780ehm.json

@@ -0,0 +1,240 @@
+{
+  "model": "Air780EHM",
+  "pins": [
+    [
+      7,
+      "PWR_KEY",
+      ""
+    ],
+    [
+      16,
+      "GPIO27",
+      ""
+    ],
+    [
+      17,
+      "UART1_RXD",
+      ""
+    ],
+    [
+      18,
+      "UART1_TXD",
+      ""
+    ],
+    [
+      19,
+      "GPIO22",
+      ""
+    ],
+    [
+      20,
+      "PWM1",
+      ""
+    ],
+    [
+      22,
+      "PWM0",
+      ""
+    ],
+    [
+      23,
+      "ONEWIRE",
+      ""
+    ],
+    [
+      25,
+      "CAN_TXD",
+      ""
+    ],
+    [
+      26,
+      "PWM4",
+      ""
+    ],
+    [
+      28,
+      "UART2_RXD",
+      ""
+    ],
+    [
+      29,
+      "UART2_TXD",
+      ""
+    ],
+    [
+      30,
+      "GPIO29",
+      ""
+    ],
+    [
+      31,
+      "GPIO30",
+      ""
+    ],
+    [
+      32,
+      "GPIO31",
+      ""
+    ],
+    [
+      33,
+      "GPIO32",
+      ""
+    ],
+    [
+      38,
+      "DBG_RXD",
+      ""
+    ],
+    [
+      39,
+      "DBG_TXD",
+      ""
+    ],
+    [
+      49,
+      "LCD_RST",
+      ""
+    ],
+    [
+      50,
+      "LCD_SDA",
+      ""
+    ],
+    [
+      51,
+      "LCD_RS",
+      ""
+    ],
+    [
+      52,
+      "LCD_CS",
+      ""
+    ],
+    [
+      53,
+      "LCD_CLK",
+      ""
+    ],
+    [
+      54,
+      "CAM_MCLK",
+      ""
+    ],
+    [
+      55,
+      "CAM_RX0",
+      ""
+    ],
+    [
+      56,
+      "CAM_RX1",
+      ""
+    ],
+    [
+      57,
+      "UART3_TXD",
+      ""
+    ],
+    [
+      58,
+      "UART3_RXD",
+      ""
+    ],
+    [
+      61,
+      "VBUS",
+      ""
+    ],
+    [
+      66,
+      "I2C1_SDA",
+      ""
+    ],
+    [
+      67,
+      "I2C1_SCL",
+      ""
+    ],
+    [
+      78,
+      "GPIO28",
+      ""
+    ],
+    [
+      79,
+      "USIM_DET",
+      ""
+    ],
+    [
+      80,
+      "CAM_BCLK",
+      ""
+    ],
+    [
+      81,
+      "CAM_CS",
+      ""
+    ],
+    [
+      82,
+      "USB_BOOT",
+      ""
+    ],
+    [
+      83,
+      "SPI0_CS",
+      ""
+    ],
+    [
+      84,
+      "SPI0_MISO",
+      ""
+    ],
+    [
+      85,
+      "SPI0_MOSI",
+      ""
+    ],
+    [
+      86,
+      "SPI0_CLK",
+      ""
+    ],
+    [
+      97,
+      "GPIO16",
+      ""
+    ],
+    [
+      99,
+      "GPIO23",
+      ""
+    ],
+    [
+      100,
+      "GPIO17",
+      ""
+    ],
+    [
+      101,
+      "WAKEUP0",
+      ""
+    ],
+    [
+      102,
+      "GPIO20",
+      ""
+    ],
+    [
+      106,
+      "CAN_RXD",
+      ""
+    ],
+    [
+      107,
+      "GPIO21",
+      ""
+    ]
+  ]
+}

+ 101 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_file.lua

@@ -0,0 +1,101 @@
+--[[
+@module  play_file
+@summary 播放文件
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+本文件为播放文件的应用功能模块,核心业务逻辑为:
+1、自动播放一个1.mp3音乐,
+2、点powerkey 按键进行音频切换,点击boot 按键停止音频播放
+3、点击boot 按键停止音频播放
+本文件没有对外接口,直接在main.lua中require "play_file"就可以加载运行;
+]]
+
+exaudio = require("exaudio")
+local taskName = "task_audio"
+
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+}
+
+--  播放结束回调
+local function play_end(event)
+    if event == exaudio.PLAY_DONE then
+        log.info("播放完成",exaudio.is_end())
+    end
+end
+
+--  音频播放的配置
+local audio_play_param ={
+    type= 0,                -- 播放类型,有0,播放文件,1.播放tts 2. 流式播放
+                            -- 如果是播放文件,支持mp3,amr,wav格式
+                            -- 如果是tts,内容格式见:https://wiki.luatos.com/chips/air780e/tts.html?highlight=tts
+                            -- 流式播放,仅支持PCM 格式音频,如果是流式播放,则sampling_rate, sampling_depth,signed_or_unsigned 必填写
+    content = "/luadb/sample-6s.mp3",          -- 如果播放类型为0时,则填入string 是播放单个音频文件,如果是表则是播放多段音频文件。
+    cbfnc = play_end,            -- 播放完毕回调函数
+}
+
+
+---------------------------------
+---通过BOOT 按键进行播放停止操作---
+---------------------------------
+local function stop_audio()
+    log.info("停止播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "STOP_AUDIO")
+end
+--按下boot 停止播放
+gpio.setup(0, stop_audio, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1) -- 防抖,防止频繁触发
+
+---------------------------------
+---通过POWERKEY按键进行音频切换---
+---------------------------------
+
+local function next_audio()
+    log.info("切换播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "NEXT_AUDIO")
+end
+
+--按下powerkey 打断播放,播放优先级更高的音频
+gpio.setup(gpio.PWR_KEY, next_audio, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1) -- 防抖,防止频繁触发
+
+
+---------------------------------
+-----主task,处理播放音频---------
+---------------------------------
+
+
+local index_number = 1
+local audio_path = nil
+local function audio_task()
+    log.info("开始播放音频文件")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.play_start(audio_play_param) -- 仅仅支持task 中运行
+        while true do
+            local msg = sys.waitMsg(taskName, MSG_KEY_PRESS)   -- 等待按键触发
+            if msg[2] ==  "NEXT_AUDIO" then  
+                
+                if index_number %2 == 0 then     --  切换音频路径
+                    audio_path = "/luadb/sample-6s.mp3"
+                else
+                    audio_path = "/luadb/10.amr"
+                end
+
+                exaudio.play_start({type= 0, content = audio_path,cbfnc = play_end,priority = index_number})
+                index_number= index_number +1 
+            elseif msg[2] ==  "STOP_AUDIO" then
+                exaudio.play_stop()
+            end 
+        end
+    end
+    
+end
+sys.taskInitEx(audio_task, taskName)

+ 108 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_stream.lua

@@ -0,0 +1,108 @@
+--[[
+@module  play_stream
+@summary 流式播放
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+本文件为流式播放应用功能模块,核心业务逻辑为:
+1、创建一个播放流式音频task(task_audio)
+2、创建一个模拟获取流式音频的task(audio_get_data)
+3、此task通过流式传输不断向exaudio.play_stream_write填入播放的音频
+4、播放task 不断播放传入流式音频
+5、使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+本文件没有对外接口,直接在main.lua中require "play_stream"就可以加载运行;
+]]
+
+exaudio = require("exaudio")
+
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+}
+
+-- 播放完成回调
+local function play_end(event)
+    if event == exaudio.PLAY_DONE then
+        log.info("播放完成",exaudio.is_end())
+
+    end
+end 
+
+-- 流式播放音频播放的配置
+local audio_play_param ={
+    type= 2,                -- 播放类型,有0,播放文件,1.播放tts 2. 流式播放
+                            -- 如果是播放文件,支持mp3,amr,wav格式
+                            -- 如果是tts,内容格式见:https://wiki.luatos.com/chips/air780e/tts.html?highlight=tts
+                            -- 流式播放,仅支持PCM 格式音频,如果是流式播放,则sampling_rate, sampling_depth,signed_or_unsigned 必填写
+    cbfnc = play_end,            -- 播放完毕回调函数
+    sampling_rate = 16000,  -- 采样率,仅为流式播放起作用
+    sampling_depth =  16,   -- 采样位位深,仅流式播放的时候才有作用
+    signed_or_unsigned = true  -- PCM 的数据是否有符号,仅为流式播放起作用
+}
+
+---------------------------------
+---通过BOOT 按键增大音量---
+---------------------------------
+local volume_number = 50 
+local function add_volume()
+    volume_number = volume_number + 20
+    log.info("增大音量",volume_number)
+    exaudio.vol(volume_number)
+end
+
+gpio.setup(0, add_volume, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1)
+
+---------------------------------
+---通过POWERKEY按键减小音量-------
+---------------------------------
+
+local function down_volume()
+    volume_number = volume_number - 15
+    log.info("减小音量",volume_number)
+    exaudio.vol(volume_number)
+end
+
+gpio.setup(gpio.PWR_KEY, down_volume, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)   -- 防抖,防止频繁触发
+
+
+---------------------------------
+---------模拟获取音频task---------
+---------------------------------
+local function audio_get_data()
+    log.info("开始流式获取音频数据")
+    local file = io.open("/luadb/test.pcm", "rb")   -- 模拟流式播放音源,实际的音频数据来源也可以来自网络或者本地存储
+    while true do
+        local read_data = file:read(4096)  --  读取文件,模拟流式音频源,需要1024 的倍数
+        if read_data  == nil then
+            file:close()                -- 模拟音频获取完毕,关闭音频文件
+            break
+        end
+        exaudio.play_stream_write(read_data)  -- 流式写入音频数据
+        sys.wait(100)                   -- 写数据需要留出事件给其他task 运行代码
+    end
+end
+
+sys.taskInitEx(audio_get_data, "audio_get_data")
+
+
+---------------------------------
+------------通过主task------------
+---------------------------------
+local taskName = "task_audio"
+local function audio_task()
+    log.info("开始流式播报")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.play_start(audio_play_param)
+        log.info("播放状态",exaudio.is_end())
+    end
+end
+
+sys.taskInitEx(audio_task, taskName)

+ 104 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_tts.lua

@@ -0,0 +1,104 @@
+--[[
+@module  play_tts
+@summary 文字转语音
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+本文件为流式播放应用功能模块,核心业务逻辑为:
+1、播放一个TTS
+2、点powerkey 按键进行tts 的音色切换
+3、点击boot 按键停止音频播放
+本文件没有对外接口,直接在main.lua中require "play_tts"就可以加载运行;
+]]
+exaudio = require("exaudio")
+local taskName = "task_audio"
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+}
+
+local function play_end(event)
+    if event == exaudio.PLAY_DONE then
+        log.info("播放完成",exaudio.is_end())
+        exaudio.play_stop()
+    end
+end 
+
+local audio_play_param ={
+    type= 1,                -- 播放类型,有0,播放文件,1.播放tts 2. 流式播放
+                            -- 如果是播放文件,支持mp3,amr,wav格式
+                            -- 如果是tts,内容格式见:https://docs.openluat.com/air780epm/common/tts/
+                            -- 流式播放,仅支持PCM 格式音频,如果是流式播放,则sampling_rate, sampling_depth,signed_or_unsigned 必填写
+    content = "支付宝到账,1千万元",          -- 如果播放类型为0时,则填入string 是播放单个音频文件,如果是表则是播放多段音频文件。
+    cbfnc = play_end,            -- 播放完毕回调函数
+}
+
+
+---------------------------------
+---通过BOOT 按键进行播放停止操作---
+---------------------------------
+local function stop_audio()
+    log.info("停止播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "STOP_AUDIO")
+end
+--按下boot 停止播放
+gpio.setup(0, stop_audio, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1)
+
+---------------------------------
+---通过POWERKEY按键进行音频切换---
+---------------------------------
+
+local function next_audio()
+    log.info("切换播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "NEXT_AUDIO")
+end
+
+--按下powerkey 打断播放,播放优先级更高的音频
+gpio.setup(gpio.PWR_KEY, next_audio, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)  -- 防抖,防止频繁触发
+
+
+---------------------------------------------------------------------------------------------------
+---------------主task------------------------------------------------------------------------------
+--- 关于TTS 音色设置请见: https://docs.openluat.com/air780epm/common/tts/
+---------------------------------------------------------------------------------------------------
+
+
+local index_number = 1
+local audio_path = nil
+local function audio_task()
+    log.info("开始播放TTS")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.play_start(audio_play_param) -- 仅仅支持task 中运行
+        while true do
+            local msg = sys.waitMsg(taskName, MSG_KEY_PRESS)   -- 等待按键触发
+            if msg[2] ==  "NEXT_AUDIO" then      
+                if index_number %5 == 0 then     --  切换播报音色
+                    audio_path = "[m51]支付宝到账,1千万元"   -- 许久
+                elseif index_number %5 == 1 then
+                    audio_path = "[m52]支付宝到账,1千万元"   -- 许多
+                elseif index_number %5 == 2 then
+                    audio_path = "[m53]支付宝到账,1千万元"   -- 晓萍
+                elseif index_number %5 == 3 then                    
+                    audio_path = "[m54]支付宝到账,1千万元"   -- 唐老鸭
+                elseif index_number %5 == 4 then                    
+                    audio_path = "[m55]支付宝到账,1千万元"   -- 许宝宝 
+                end
+
+                exaudio.play_start({type= 1, content = audio_path,cbfnc = play_end,priority = index_number})
+                index_number= index_number +1 
+            elseif msg[2] ==  "STOP_AUDIO" then
+                exaudio.play_stop()
+            end 
+        end
+    end
+    
+end
+sys.taskInitEx(audio_task, taskName)

+ 212 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/readme.md

@@ -0,0 +1,212 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
+
+3、play_tts: 支持文字转普通话输出需要固件支持
+
+4、play_stream: 流式播放音频,仅支持PCM 格式
+
+5、record_file: 录音到文件,仅支持PCM 格式
+
+6、record_stream:  流式录音,仅支持PCM。
+
+7、sample-6s.mp3/10.amr: 用于测试本地mp3和amr文件播放
+
+8、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
+
+
+**注意:目前不支持录音和放音同时进行**
+
+
+## 常量的介绍
+
+1、exaudio.PLAY_DONE : 当播放音频结束时,会在回调函数返回播放完成的时间
+
+2、exaudio.RECORD_DONE : 当录音结束时,会在回调函数返回播放完成的时间
+
+3、exaudio.AMR_NB : 仅录音时有用,表示使用AMR_NB 方式录音
+
+4、exaudio.AMR_WB : 仅录音时有用,表示使用AMR_WB 方式录音
+
+5、exaudio.PCM_8000/exaudio.PCM_16000/exaudio.PCM_24000/exaudio.PCM_32000 :  仅录音时有用,表示使用8000/16000/24000/32000/秒 的速度对音频进行采样
+
+
+## 演示功能概述
+
+1、play_flie.lua 自动播放一个sample-6s.mp3音乐,点powerkey 按键进行音频切换,播放10.amr文件,点击boot 按键停止音频播放
+
+2、play_tts.lua 播放一个TTS,点powerkey 按键进行tts 的音色切换,点击boot 按键停止音频播放
+
+3、play_stream.lua 流式播放PCM,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+4、record_file.lua 录音到文件,演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+5、record_stream.lua 流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
+
+
+## 演示硬件环境
+1、Air780EHM核心板/Air780EGH核心板+AirAUDIO_1010 音频扩展板+喇叭
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EHM+Airaudio1010.jpg)
+
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EGH-AIRAUDIO_1010.jpg)
+
+Air780EHM核心板/Air780EGH核心板和AirAudio_1010 扩展板的硬件接线方式为:
+|  Air780EHM核心板/Air780EGH核心板 | AirAUDIO_1010扩展板 |
+| ---------------                 | -----------------   |
+| 33/I2S_MCLK                     | I2S_MCLK            |
+| 30/I2S_BCK                      | I2S_BCK             |
+| 31/I2S_LRCK                     | I2S_LRCK            |
+| 32/I2S_DIN                      | I2S_DIN             |
+| 33/I2S_DOUT                     | I2S_DOUT            |
+| 67/I2C1_SCL                     | I2C_SCL             |
+| 66/I2C1_SDA                     | I2C_SDA             |
+| 25/GPIO26                       | PA_EN               |
+| 23/GPIO2                        | 8311_EN             |
+| VDD_EXT                         | VCC                 |
+| GND                             | GND                 |
+
+2、TYPE-C USB数据线一根
+- Air780EHM核心板通过/Air780EGH核心板 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EHM/LuatOS-SoC_V2016_Air780EHM.zip),选择支持TTS功能的1、3、5、7、13或101、103、105、107、113号固件。不同版本区别参考[Air780EHM LuatOS固件版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)。
+
+ [Air780EGH V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EGH/LuatOS-SoC_V2016_Air780EGH.zip),选择支持TTS功能的1、3、5、7、13或101、103、105、107、113号固件。不同版本区别参考[Air780EGH LuatOS固件版本](https://docs.openluat.com/air780egh/luatos/firmware/version/)。
+
+3、 luatos需要的脚本和资源文件
+- 脚本和资源文件[点我浏览所有文件](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio)
+
+- 准备好软件环境之后,接下来查看[如何烧录项目文件到Air780EHM/Air780EGH核心板](https://docs.openluat.com/air8000/luatos/common/download/),将本篇文章中演示使用的项目文件烧录到Air780EHM/Air780EGH核心板中。
+
+4、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明](https://docs.openluat.com/air780epm/common/luatio/)
+
+5、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码main.lua中,按照自己的需求选择对应的功能
+
+- 如果需要测试播放音频文件,则选择play_file 文件
+
+- 如果需要测试播放tts,则选择play_tts 文件
+
+- 如果需要测试流式播放音频,则选择play_stream 文件
+
+- 如果需要测试录音频到文件,则选择record_file 文件
+
+- 如果需要测试流式录音,则选择record_stream 文件
+
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、 在测试播放音频文件的时候,点powerkey 按键进行音频切换,切换内容是MP3,AMR格式,切换是通过播放优先级进行区分的,注意音频格式仅仅支持:MP3,WAV,AMR,点击boot 按键停止音频播放
+
+5、 在测试播放TTS的时候,点powerkey 按键进行TTS 音色切换,点击boot 按键停止音频播放,注意:仅支持中文TTS。
+
+
+6、在进行流式播放测试的时候,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加,注意流式播放目前仅支持PCM 格式音频,可选择不同的采样率,以及位深
+
+7、在测试录音到文件(仅支持PCM),演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+8、在测试流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用。
+
+9、运行结果展示
+
+- 播放文件 (play_file.lua)
+
+自动播放一个sample-6s.mp3 音乐;
+
+点击powerkey 按键进行音频切换播放10.amr文件;
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放音频文件
+ I/user.播放完成 true
+
+ I/user.切换播放
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+ ```
+
+ - 文字转语音 (play_tts.lua)
+
+ 播放一个TTS;
+
+点击powerkey 按键进行tts 的音色切换
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放TTS
+ I/user.切换播放
+
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+
+```
+
+- 流式播放 (play_stream.lua)
+
+创建一个播放流式音频task(task_audio)和一个模拟获取流式音频的task(audio_get_data),此task通过流式传输不断向exaudio.play_stream_write填入播放的音频,播放task 不断播放传入流式音频。
+
+使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+``` lua
+ I/user.开始流式获取音频数据
+ I/user.开始流式播报
+
+ I/user.减小音量55
+ I/user.增大音量75
+
+ I/user.播放状态 true
+ I/user.播放完成 true
+
+```
+
+- 录音到文件  (record_file.lua)
+
+主程序录音到/record.amr 文件
+
+使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+``` lua
+ I/user.开始录制音频到文件
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后文件大小 320000
+```
+
+- 流式录音(record_stream.lua),仅支持PCM格式
+
+主程序录音进行流式录音
+
+录音过程中不断的进行recode_data_callback回调,回调内容为音频流的地址和长度。
+
+``` lua
+ I/user.开始流式录制音频到文件
+ I/user.收到音频流,地址为: ZBUFF*:0C7F70A8 有效数据长度为: 9600
+ I/user.录音完成
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后
+
+

+ 84 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_file.lua

@@ -0,0 +1,84 @@
+--[[
+@module  record_file
+@summary 录音到文件
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+录音到文件,核心业务逻辑为:
+1、主程序录音到/record.amr 文件
+2、使用powerkey 按键进行录音音量减小
+3、点击boot 按键进行录音音量增加
+本文件没有对外接口,直接在main.lua中require "record_file"就可以加载运行;
+]]
+exaudio = require("exaudio")
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl =26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+    bits_per_sample = 16  -- 录音的位深,可选8,16,24 位,但是当选择音频格式为AMR_NB,自动调节为8位,当音频格式为AMR_WB,自动调节为16位
+}
+local recordPath = "/record.amr"
+
+-- 录音完成回调
+local function record_end(event)
+    if event == exaudio.RECORD_DONE then
+        log.info("录音后文件大小",io.fileSize(recordPath))
+    end
+end 
+
+-- 录音配置参数,exaudio.record_start 的入参
+local audio_record_param ={
+    format= exaudio.PCM_32000,    -- 录制格式,有exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
+                                  -- 如果选择exaudio.AMR_WB,则需要固件支持volte 功能
+    time = 5,               -- 录制时间,单位(秒)
+    path = recordPath,             -- 如果填入的是字符串,则表示是文件路径,录音会传输到这个路径里
+                                   -- 如果填入的是函数,则表示是流式录音,录音的数据会传输到此函数内,返回的是zbuf地址,以及数据长度
+                                   -- 如果是流式录音,则仅支持PCM 格式 
+    cbfnc = record_end,            -- 录音完毕回调函数
+}
+
+
+---------------------------------
+---通过BOOT 按键增大录音---
+---------------------------------
+local volume_number = 50 
+local function add_volume()
+    volume_number = volume_number + 20
+    log.info("增大录音音量",volume_number)
+    exaudio.mic_vol(volume_number)
+end
+--按下boot 停止播放
+gpio.setup(0, add_volume, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1)
+
+---------------------------------
+---通过POWERKEY按键减小录音-------
+---------------------------------
+
+local function down_volume()
+    volume_number = volume_number - 15
+    log.info("减小录音音量",volume_number)
+    exaudio.mic_vol(volume_number)
+end
+
+gpio.setup(gpio.PWR_KEY, down_volume, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)   -- 防抖,防止频繁触发
+
+---------------------------------
+---音频 task 初始化函数-----------
+---------------------------------
+local taskName = "task_audio"
+local function audio_task()
+    sys.wait(100)
+    log.info("开始录制音频到文件")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.record_start(audio_record_param)
+    end
+end
+
+sys.taskInitEx(audio_task, taskName)

+ 55 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_stream.lua

@@ -0,0 +1,55 @@
+--[[
+@module  record_stream
+@summary 流式录音
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+流式录音(仅支持PCM),核心业务逻辑为:
+1、主程序录音进行流式录音
+2、录音过程中不断的进行recode_data_callback回调,回调内容为音频流的地址和长度
+本文件没有对外接口,直接在main.lua中require "record_stream"就可以加载运行;
+]]
+exaudio = require("exaudio")
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+    bits_per_sample = 16  -- 录音的位深,可选8,16,24 位,但是当选择音频格式为AMR_NB,自动调节为8位,当音频格式为AMR_WB,自动调节为16位
+}
+
+-- 录音的数据流回调函数,注意不可以在回调函数中加入耗时和延迟的操作
+local function  recode_data_callback(addr,data_len)
+    log.info("收到音频流,地址为:",addr,"有效数据长度为:",data_len)
+end
+local function record_end(event)
+    if event == exaudio.RECORD_DONE then
+        log.info("录音完成")
+    end
+end 
+
+-- 录音配置参数,exaudio.record_start 的入参
+local audio_record_param ={
+    format= exaudio.PCM_16000,    -- 录制格式,有exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
+    time = 5,               -- 录制时间,单位(秒)
+    path = recode_data_callback,             -- 如果填入的是字符串,则表示是文件路径,录音会传输到这个路径里
+                                        -- 如果填入的是函数,则表示是流式录音,录音的数据会传输到此函数内,返回的是zbuf地址,以及数据长度
+                                        -- 如果是流式录音,则仅支持PCM 格式 
+    cbfnc = record_end,            -- 录音完毕回调函数
+}
+
+
+
+local taskName = "task_audio"
+local function audio_task()
+    log.info("开始流式录制音频到文件")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.record_start(audio_record_param)
+    end
+end
+
+sys.taskInitEx(audio_task, taskName)

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/sample-6s.mp3 → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/sample-6s.mp3


+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/test.pcm → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/test.pcm


BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/10.amr


+ 20 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/main.lua

@@ -17,7 +17,7 @@
 
 5、record_stream:  流式录音,仅支持PCM,可以将音频流不断的拉取,可用来对接大模型
 
-6、1.mp3: 用于测试本地mp3文件播放
+6、sample-6s: 用于测试本地mp3文件播放
 
 7、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
 
@@ -37,7 +37,7 @@ VERSION:项目版本号,ascii string类型
 ]]
 
 --[[
-本demo可直接在Air8000整机开发板上运行
+本demo可使用Air780EHV核心板+AirAUDIO_1000 音频配件板+喇叭演示
 ]]
 
 PROJECT = "audio"
@@ -55,6 +55,24 @@ if wdt then
     sys.timerLoopStart(wdt.feed, 3000)
 end
 
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
 
 require "play_file"     --   播放音频文件,可支持wav,amr,mp3 格式音频
 -- require "play_tts"      -- 支持文字转普通话输出需要固件支持

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/play_file.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_file.lua


+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/play_stream.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_stream.lua

@@ -20,7 +20,7 @@ exaudio = require("exaudio")
 
 -- 音频初始化设置参数,exaudio.setup 传入参数
 local audio_setup_param ={
-    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
     i2c_id = 0,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
     pa_ctrl = gpio.AUDIOPA_EN,         -- 音频放大器电源控制管脚
     dac_ctrl = 20,        --  音频编解码芯片电源控制管脚,780ehv 默认使用20

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/play_tts.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_tts.lua

@@ -20,8 +20,8 @@ local audio_setup_param ={
     model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
     i2c_id = 0,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
     pa_ctrl = gpio.AUDIOPA_EN,         -- 音频放大器电源控制管脚
-    dac_ctrl = 20,        --  音频编解码芯片电源控制管脚,780ehv 默认使用20  
-}
+    dac_ctrl = 20,        --  音频编解码芯片电源控制管脚,780ehv 默认使用20
+}}
 
 local function play_end(event)
     if event == exaudio.PLAY_DONE then

+ 204 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/readme.md

@@ -0,0 +1,204 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
+
+3、play_tts: 支持文字转普通话输出需要固件支持
+
+4、play_stream: 流式播放音频,仅支持PCM 格式
+
+5、record_file: 录音到文件,仅支持PCM 格式
+
+6、record_stream:  流式录音,仅支持PCM。
+
+7、sample-6s.mp3/10.amr: 用于测试本地mp3和amr文件播放
+
+8、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
+
+
+**注意:目前不支持录音和放音同时进行**
+
+
+## 常量的介绍
+
+1、exaudio.PLAY_DONE : 当播放音频结束时,会在回调函数返回播放完成的时间
+
+2、exaudio.RECORD_DONE : 当录音结束时,会在回调函数返回播放完成的时间
+
+3、exaudio.AMR_NB : 仅录音时有用,表示使用AMR_NB 方式录音
+
+4、exaudio.AMR_WB : 仅录音时有用,表示使用AMR_WB 方式录音
+
+5、exaudio.PCM_8000/exaudio.PCM_16000/exaudio.PCM_24000/exaudio.PCM_32000 :  仅录音时有用,表示使用8000/16000/24000/32000/秒 的速度对音频进行采样
+
+
+## 演示功能概述
+
+1、play_flie.lua 自动播放一个sample-6s.mp3音乐,点powerkey 按键进行音频切换,播放10.amr文件,点击boot 按键停止音频播放
+
+2、play_tts.lua 播放一个TTS,点powerkey 按键进行tts 的音色切换,点击boot 按键停止音频播放
+
+3、play_stream.lua 流式播放PCM,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+4、record_file.lua 录音到文件,演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+5、record_stream.lua 流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
+
+
+## 演示硬件环境
+1、Air780EHV核心板+AirAUDIO_1000配件板+喇叭
+
+![alt text](https://docs.openLuat.com/cdn/image/Air780EHV+Airaudio1000.jpg)
+
+Air780EHV核心板和AirAudio_1000 配件板的硬件接线方式为:
+
+| Air780EHV核心板 | AirAUDIO_1000配件板 |
+| ---------------| -----------------   |
+| 3/MIC+         |     MIC+            |
+| 4/MIC-         |     MIC-            |
+| 5/SPK+         |     SPK+            |
+| 6/SPK-         |     SPK-            |
+| 19/GPIO22      |     PA_EN           |
+| 3V3            |     VCC             |
+| GND            |     GND             |
+
+2、TYPE-C USB数据线一根
+- Air780EHM核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/) 
+
+2、[Air780EHV V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EHV/LuatOS-SoC_V2016_Air780EHV.zip)(理论上,2025年7月26日之后发布的固件都可以)选择支持TTS功能的1、3、5、7、13或101、103、105、107、113号固件。[不同版本区别请见](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+3、 luatos需要的脚本和资源文件
+- 脚本和资源文件[点我浏览所有文件](https://gitee.com/openLuat/LuatOS_demo_v2_temp/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV_audio)
+
+- 准备好软件环境之后,接下来查看[如何烧录项目文件到Air780EHV核心板](https://docs.openluat.com/air8000/luatos/common/download/),将本篇文章中演示使用的项目文件烧录到Air780EHV核心板中。
+
+4、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明](https://docs.openluat.com/air780epm/common/luatio/)
+
+5、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码main.lua中,按照自己的需求选择对应的功能
+
+- 如果需要测试播放音频文件,则选择play_file 文件
+
+- 如果需要测试播放tts,则选择play_tts 文件
+
+- 如果需要测试流式播放音频,则选择play_stream 文件
+
+- 如果需要测试录音频到文件,则选择record_file 文件
+
+- 如果需要测试流式录音,则选择record_stream 文件
+
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、 在测试播放音频文件的时候,点powerkey 按键进行音频切换,切换内容是MP3,AMR格式,切换是通过播放优先级进行区分的,注意音频格式仅仅支持:MP3,WAV,AMR,点击boot 按键停止音频播放
+
+5、 在测试播放TTS的时候,点powerkey 按键进行TTS 音色切换,点击boot 按键停止音频播放,注意:仅支持中文TTS。
+
+
+6、在进行流式播放测试的时候,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加,注意流式播放目前仅支持PCM 格式音频,可选择不同的采样率,以及位深
+
+7、在测试录音到文件(仅支持PCM),演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+8、在测试流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用。
+
+9、运行结果展示
+
+- 播放文件 (play_file.lua)
+
+自动播放一个sample-6s.mp3 音乐;
+
+点击powerkey 按键进行音频切换播放10.amr文件;
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放音频文件
+ I/user.播放完成 true
+
+ I/user.切换播放
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+ ```
+
+ - 文字转语音 (play_tts.lua)
+
+ 播放一个TTS;
+
+点击powerkey 按键进行tts 的音色切换
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放TTS
+ I/user.切换播放
+
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+
+```
+
+- 流式播放 (play_stream.lua)
+
+创建一个播放流式音频task(task_audio)和一个模拟获取流式音频的task(audio_get_data),此task通过流式传输不断向exaudio.play_stream_write填入播放的音频,播放task 不断播放传入流式音频。
+
+使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+``` lua
+ I/user.开始流式获取音频数据
+ I/user.开始流式播报
+
+ I/user.减小音量55
+ I/user.增大音量75
+
+ I/user.播放状态 true
+ I/user.播放完成 true
+
+```
+
+- 录音到文件  (record_file.lua)
+
+主程序录音到/record.amr 文件
+
+使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+``` lua
+ I/user.开始录制音频到文件
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后文件大小 320000
+```
+
+- 流式录音(record_stream.lua),仅支持PCM格式
+
+主程序录音进行流式录音
+
+录音过程中不断的进行recode_data_callback回调,回调内容为音频流的地址和长度。
+
+``` lua
+ I/user.开始流式录制音频到文件
+ I/user.收到音频流,地址为: ZBUFF*:0C7F70A8 有效数据长度为: 9600
+ I/user.录音完成
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后
+

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/record_file.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_file.lua


+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/record_stream.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_stream.lua


BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/sample-6s.mp3


BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/test.pcm


+ 0 - 541
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/exaudio.lua

@@ -1,541 +0,0 @@
---[[
-@module exaudio
-@summary exaudio扩展库
-@version 1.1
-@date    2025.09.01
-@author  梁健
-@usage
-]]
-local exaudio = {}
-
--- 常量定义
-local I2S_ID = 0
-local I2S_MODE = 0          -- 0:主机 1:从机
-local I2S_SAMPLE_RATE = 16000
-local I2S_CHANNEL_FORMAT = i2s.MONO_R  
-local I2S_COMM_FORMAT = i2s.MODE_LSB   -- 可选MODE_I2S, MODE_LSB, MODE_MSB
-local I2S_CHANNEL_BITS = 16
-local MULTIMEDIA_ID = 0
-local EX_MSG_PLAY_DONE = "playDone"
-local ES8311_ADDR = 0x18    -- 7位地址
-local CHIP_ID_REG = 0x00    -- 芯片ID寄存器地址
-
--- 模块常量
-exaudio.PLAY_DONE = 1         --   音频播放完毕的事件之一
-exaudio.RECORD_DONE = 1       --   音频录音完毕的事件之一  
-exaudio.AMR_NB = 0
-exaudio.AMR_WB = 1
-exaudio.PCM_8000 = 2
-exaudio.PCM_16000 = 3 
-exaudio.PCM_24000 = 4
-exaudio.PCM_32000 = 5
-
-
--- 默认配置参数
-local audio_setup_param = {
-    model = "es8311",         -- dac类型: "es8311","es8211"
-    i2c_id = 0,               -- i2c_id: 0,1
-    pa_ctrl = 0,              -- 音频放大器电源控制管脚
-    dac_ctrl = 0,             -- 音频编解码芯片电源控制管脚
-    dac_delay = 3,            -- DAC启动前冗余时间(100ms)
-    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(ms)
-    dac_time_delay = 600,     -- 播放完毕后PA与DAC关闭间隔(ms)
-    bits_per_sample = 16,     -- 采样位数
-    pa_on_level = 1           -- PA打开电平 1:高 0:低        
-}
-
-local audio_play_param = {
-    type = 0,                 -- 0:文件 1:TTS 2:流式
-    content = nil,            -- 播放内容
-    cbfnc = nil,              -- 播放完毕回调
-    priority = 0,             -- 优先级(数值越大越高)
-    sampling_rate = 16000,    -- 采样率(仅流式)
-    sampling_depth = 16,      -- 采样位深(仅流式)
-    signed_or_unsigned = true -- PCM是否有符号(仅流式)
-}
-
-local audio_record_param = {
-    format = 0,               -- 录制格式,支持exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
-    time = 5,                 -- 录制时间(秒)
-    path = nil,               -- 文件路径或流式回调
-    cbfnc = nil               -- 录音完毕回调
-}
-
--- 内部变量
-local pcm_buff0 = nil
-local pcm_buff1 = nil
-local voice_vol = 55
-local mic_vol = 80
-
--- 定义全局队列表
-local audio_play_queue = {
-    data = {},       -- 存储字符串的数组
-    sequenceIndex = 1  -- 用于跟踪插入顺序的索引
-}
-
--- 向队列中添加字符串(按调用顺序插入)
-local function audio_play_queue_push(str)
-    if type(str) == "string" then
-        -- 存储格式: {index = 顺序索引, value = 字符串值}
-        table.insert(audio_play_queue.data, {
-            index = audio_play_queue.sequenceIndex,
-            value = str
-        })
-        audio_play_queue.sequenceIndex = audio_play_queue.sequenceIndex + 1
-        return true
-    end
-    return false
-end
-
--- 从队列中取出最早插入的字符串(按顺序取出)
-local function audio_play_queue_pop()
-    if #audio_play_queue.data > 0 then
-        -- 取出并移除第一个元素
-        local item = table.remove(audio_play_queue.data, 1)
-        return item.value  -- 返回值
-    end
-    return nil
-end
--- 清空队列中所有数据
-function audio_queue_clear()
-    -- 清空数组
-    audio_play_queue.data = {}
-    -- 重置顺序索引
-    audio_play_queue.sequenceIndex = 1
-    return true
-end
-
--- 工具函数:参数检查
-local function check_param(param, expected_type, name)
-    if type(param) ~= expected_type then
-        log.error(string.format("参数错误: %s 应为 %s 类型", name, expected_type))
-        return false
-    end
-    return true 
-end
-
--- 音频回调处理
-local function audio_callback(id, event, point)
-    -- log.info("audio_callback", "event:", event, 
-    --         "MORE_DATA:", audio.MORE_DATA, 
-    --         "DONE:", audio.DONE,
-    --         "RECORD_DATA:", audio.RECORD_DATA,
-    --         "RECORD_DONE:", audio.RECORD_DONE)
-
-    if event == audio.MORE_DATA then
-        audio.write(MULTIMEDIA_ID,audio_play_queue_pop())
-    elseif event == audio.DONE then
-        if type(audio_play_param.cbfnc) == "function" then
-            audio_play_param.cbfnc(exaudio.PLAY_DONE)
-        end
-        audio_queue_clear()  -- 清空流式播放数据队列
-        sys.publish(EX_MSG_PLAY_DONE)
-        
-    elseif event == audio.RECORD_DATA then
-        if type(audio_record_param.path) == "function" then
-            local buff, len = point == 0 and pcm_buff0 or pcm_buff1,
-                             point == 0 and pcm_buff0:used() or pcm_buff1:used()
-            audio_record_param.path(buff, len)
-        end
-        
-    elseif event == audio.RECORD_DONE then
-        if type(audio_record_param.cbfnc) == "function" then
-            audio_record_param.cbfnc(exaudio.RECORD_DONE)
-        end
-    end
-end
-
--- 读取ES8311芯片ID
-local function read_es8311_id()
-
-
-    -- 发送读取请求
-    local send_ok = i2c.send(audio_setup_param.i2c_id, ES8311_ADDR, CHIP_ID_REG)
-    if not send_ok then
-        log.error("发送芯片ID读取请求失败")
-        return false
-    end
-
-    -- 读取数据
-    local data = i2c.recv(audio_setup_param.i2c_id, ES8311_ADDR, 1)
-    if data and #data == 1 then
-        return true
-    end
-
-    log.error("读取ES8311芯片ID失败")
-    return false
-end
-
--- 音频硬件初始化
-local function audio_setup()
-    -- I2C配置
-    if not i2c.setup(audio_setup_param.i2c_id, i2c.FAST) then
-        log.error("I2C初始化失败")
-        return false
-    end
-    -- 初始化I2S
-    local result, data = i2s.setup(
-        I2S_ID, 
-        I2S_MODE, 
-        I2S_SAMPLE_RATE, 
-        audio_setup_param.bits_per_sample, 
-        I2S_CHANNEL_FORMAT, 
-        I2S_COMM_FORMAT,
-        I2S_CHANNEL_BITS
-    )
-
-    if not result then
-        log.error("I2S设置失败")
-        return false
-    end
-    -- 配置音频通道
-    audio.config(
-        MULTIMEDIA_ID, 
-        audio_setup_param.pa_ctrl, 
-        audio_setup_param.pa_on_level, 
-        audio_setup_param.dac_delay, 
-        audio_setup_param.pa_delay, 
-        audio_setup_param.dac_ctrl, 
-        1,  -- power_on_level
-        audio_setup_param.dac_time_delay
-    )
-    -- 设置总线
-    audio.setBus(
-        MULTIMEDIA_ID, 
-        audio.BUS_I2S,
-        {
-            chip = audio_setup_param.model,
-            i2cid = audio_setup_param.i2c_id,
-            i2sid = I2S_ID,
-            voltage = audio.VOLTAGE_1800
-        }
-    )
-
-  
-    -- 设置音量
-    audio.vol(MULTIMEDIA_ID, voice_vol)
-    audio.micVol(MULTIMEDIA_ID, mic_vol)
-    audio.pm(MULTIMEDIA_ID, audio.RESUME)
-    
-    -- 检查芯片连接
-    if audio_setup_param.model == "es8311" and not read_es8311_id() then
-        log.error("ES8311通讯失败,请检查硬件")
-        return false
-    end
-
-    -- 注册回调
-    audio.on(MULTIMEDIA_ID, audio_callback)
-    return true
-end
-
--- 模块接口:初始化
-function exaudio.setup(audioConfigs)
-    -- 检查必要参数
-    if not  audio  then
-        log.error("不支持audio 库,请选择支持audio 的core")
-        return false
-    end
-    if not audioConfigs or type(audioConfigs) ~= "table" then
-        log.error("配置参数必须为table类型")
-        return false
-    end
-    -- 检查codec型号
-    if not audioConfigs.model or 
-       (audioConfigs.model ~= "es8311" and audioConfigs.model ~= "es8211") then
-        log.error("请指定正确的codec型号(es8311或es8211)")
-        return false
-    end
-    audio_setup_param.model = audioConfigs.model
-    -- 针对ES8311的特殊检查
-    if audioConfigs.model == "es8311" then
-        if not check_param(audioConfigs.i2c_id, "number", "i2c_id") then
-            return false
-        end
-        audio_setup_param.i2c_id = audioConfigs.i2c_id
-    end
-
-    -- 检查功率放大器控制管脚
-    if audioConfigs.pa_ctrl == nil then
-        log.warn("pa_ctrl(功率放大器控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
-    end
-    audio_setup_param.pa_ctrl = audioConfigs.pa_ctrl
-
-    -- 检查功率放大器控制管脚
-    if audioConfigs.dac_ctrl == nil then
-        log.warn("dac_ctrl(音频编解码控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
-    end
-    audio_setup_param.dac_ctrl = audioConfigs.dac_ctrl
-
-
-    -- 处理可选参数
-    local optional_params = {
-        {name = "dac_delay", type = "number"},
-        {name = "pa_delay", type = "number"},
-        {name = "dac_time_delay", type = "number"},
-        {name = "bits_per_sample", type = "number"},
-        {name = "pa_on_level", type = "number"}
-    }
-
-    for _, param in ipairs(optional_params) do
-        if audioConfigs[param.name] ~= nil then
-            if check_param(audioConfigs[param.name], param.type, param.name) then
-                audio_setup_param[param.name] = audioConfigs[param.name]
-            else
-                return false
-            end
-        end
-    end
-
-    -- 确保采样位数有默认值
-    audio_setup_param.bits_per_sample = audio_setup_param.bits_per_sample or 16
-    return audio_setup()
-end
-
--- 模块接口:开始播放
-function exaudio.play_start(playConfigs)
-    if not playConfigs or type(playConfigs) ~= "table" then
-        log.error("播放配置必须为table类型")
-        return false
-    end
-
-    -- 检查播放类型
-    if not check_param(playConfigs.type, "number", "type") then
-        log.error("type必须为数值(0:文件,1:TTS,2:流式)")
-        return false
-    end
-    audio_play_param.type = playConfigs.type
-
-    -- 处理优先级
-    if playConfigs.priority ~= nil then
-        if check_param(playConfigs.priority, "number", "priority") then
-            if playConfigs.priority > audio_play_param.priority then
-                log.error("是否完成播放",audio.isEnd(MULTIMEDIA_ID))
-                if not audio.isEnd(MULTIMEDIA_ID) then
-                    if audio.play(MULTIMEDIA_ID) ~= true then
-                        return false
-                    end
-                    sys.waitUntil(EX_MSG_PLAY_DONE)
-                end
-                audio_play_param.priority = playConfigs.priority
-            end
-        else
-            return false
-        end
-    end
-
-    -- 处理不同播放类型
-    local play_type = audio_play_param.type
-    if play_type == 0 then  -- 文件播放
-        if not playConfigs.content then
-            log.error("文件播放需要指定content(文件路径或路径表)")
-            return false
-        end
-
-        local content_type = type(playConfigs.content)
-        if content_type == "table" then
-            for _, path in ipairs(playConfigs.content) do
-                if type(path) ~= "string" then
-                    log.error("播放列表元素必须为字符串路径")
-                    return false
-                end
-            end
-        elseif content_type ~= "string" then
-            log.error("文件播放content必须为字符串或路径表")
-            return false
-        end
-
-        audio_play_param.content = playConfigs.content
-        if audio.play(MULTIMEDIA_ID, audio_play_param.content) ~= true then
-            return false
-        end
-
-    elseif play_type == 1 then  -- TTS播放
-        if not audio.tts then
-            log.error("本固件不支持TTS,请更换支持TTS 的固件")
-            return false
-        end
-        if not check_param(playConfigs.content, "string", "content") then
-            log.error("TTS播放content必须为字符串")
-            return false
-        end
-        audio_play_param.content = playConfigs.content
-        if audio.tts(MULTIMEDIA_ID, audio_play_param.content)  ~= true  then
-            return false
-        end
-
-    elseif play_type == 2 then  -- 流式播放
-        if not check_param(playConfigs.sampling_rate, "number", "sampling_rate") then
-            return false
-        end
-        if not check_param(playConfigs.sampling_depth, "number", "sampling_depth") then
-            return false
-        end
-
-        audio_play_param.content = playConfigs.content
-        audio_play_param.sampling_rate = playConfigs.sampling_rate
-        audio_play_param.sampling_depth = playConfigs.sampling_depth
-        
-        if playConfigs.signed_or_unsigned ~= nil then
-            audio_play_param.signed_or_unsigned = playConfigs.signed_or_unsigned
-        end
-
-        audio.start(
-            MULTIMEDIA_ID, 
-            audio.PCM, 
-            1, 
-            playConfigs.sampling_rate, 
-            playConfigs.sampling_depth, 
-            audio_play_param.signed_or_unsigned
-        )
-        -- 发送初始数据
-        if audio.write(MULTIMEDIA_ID, string.rep("\0", 512)) ~= true then
-            return false
-        end
-    end
-
-    -- 处理回调函数
-    if playConfigs.cbfnc ~= nil then
-        if check_param(playConfigs.cbfnc, "function", "cbfnc") then
-            audio_play_param.cbfnc = playConfigs.cbfnc
-        else
-            return false
-        end
-    else
-        audio_play_param.cbfnc = nil
-    end
-    return true
-end
-
--- 模块接口:流式播放数据写入
-function exaudio.play_stream_write(data)
-    audio_play_queue_push(data)
-    return true
-end
-
--- 模块接口:停止播放
-function exaudio.play_stop()
-    return audio.play(MULTIMEDIA_ID)
-end
-
--- 模块接口:检查播放是否结束
-function exaudio.is_end()
-    return audio.isEnd(MULTIMEDIA_ID)
-end
-
--- 模块接口:获取错误信息
-function exaudio.get_error()
-    return audio.getError(MULTIMEDIA_ID)
-end
-
--- 模块接口:开始录音
-function exaudio.record_start(recodConfigs)
-    if not recodConfigs or type(recodConfigs) ~= "table" then
-        log.error("录音配置必须为table类型")
-        return false
-    end
-    -- 检查录音格式
-    if recodConfigs.format == nil or type(recodConfigs.format) ~= "number" or recodConfigs.format > 5 then
-        log.error("请指定正确的录音格式")
-        return false
-    end
-    audio_record_param.format = recodConfigs.format
-
-    -- 处理录音时间
-    if recodConfigs.time ~= nil then
-        if check_param(recodConfigs.time, "number", "time") then
-            audio_record_param.time = recodConfigs.time
-        else
-            return false
-        end
-    else
-        audio_record_param.time = 0
-    end
-
-    -- 处理存储路径/回调
-    if not recodConfigs.path then
-        log.error("必须指定录音路径或流式回调函数")
-        return false
-    end
-    audio_record_param.path = recodConfigs.path
-
-    -- 转换录音格式
-    local recod_format, amr_quailty
-    if audio_record_param.format == exaudio.AMR_NB then
-        recod_format = audio.AMR_NB
-        amr_quailty = 7
-    elseif audio_record_param.format == exaudio.AMR_WB then
-        recod_format = audio.AMR_WB
-        amr_quailty = 8
-    elseif audio_record_param.format == exaudio.PCM_8000 then
-        recod_format = 8000
-    elseif audio_record_param.format == exaudio.PCM_16000 then
-        recod_format = 16000
-    elseif audio_record_param.format == exaudio.PCM_24000 then
-        recod_format = 24000
-    elseif audio_record_param.format == exaudio.PCM_32000 then
-        recod_format = 32000
-    end
-
-    -- 处理回调函数
-    if recodConfigs.cbfnc ~= nil then
-        if check_param(recodConfigs.cbfnc, "function", "cbfnc") then
-            audio_record_param.cbfnc = recodConfigs.cbfnc
-        else
-            return false
-        end
-    else
-        audio_record_param.cbfnc = nil
-    end
-    -- 开始录音
-    local path_type = type(audio_record_param.path)
-    if path_type == "string" then
-        return audio.record(
-            MULTIMEDIA_ID, 
-            recod_format, 
-            audio_record_param.time, 
-            amr_quailty, 
-            audio_record_param.path
-        )
-    elseif path_type == "function" then
-        -- 初始化缓冲区
-        if not pcm_buff0 or not pcm_buff1 then
-            pcm_buff0 = zbuff.create(16000)
-            pcm_buff1 = zbuff.create(16000)
-        end
-        return audio.record(
-            MULTIMEDIA_ID, 
-            recod_format, 
-            audio_record_param.time, 
-            amr_quailty, 
-            nil, 
-            3,
-            pcm_buff0,
-            pcm_buff1
-        )
-    end
-    log.error("录音路径必须为字符串或函数")
-    return false
-end
-
--- 模块接口:停止录音
-function exaudio.record_stop()
-    return audio.recordStop(MULTIMEDIA_ID)
-end
-
--- 模块接口:设置音量
-function exaudio.vol(play_volume)
-    if check_param(play_volume, "number", "音量值") then
-        return audio.vol(MULTIMEDIA_ID, play_volume)
-    end
-    return false
-end
-
--- 模块接口:设置麦克风音量
-function exaudio.mic_vol(record_volume)
-    if check_param(record_volume, "number", "麦克风音量值") then
-        return audio.micVol(MULTIMEDIA_ID, record_volume)  
-    end
-    return false
-end
-
-return exaudio

+ 0 - 106
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/readme.md

@@ -1,106 +0,0 @@
-## 功能模块介绍
-
-1、main.lua:主程序入口;
-
-2、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
-
-3、play_tts: 支持文字转普通话输出需要固件支持
-
-4、play_stream: 流式播放音频,仅支持PCM 格式,可以将音频推流到云端,用来对接大模型或者流式录音的应用。
-
-5、record_file: 录音到文件,仅支持PCM 格式
-
-6、record_stream:  流式录音,仅支持PCM,可以将音频流不断的拉取,可用来对接大模型
-
-7、1.mp3: 用于测试本地mp3文件播放
-
-8、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
-
-
-
-
-
-## 常量的介绍
-
-1、exaudio.PLAY_DONE : 当播放音频结束时,会在回调函数返回播放完成的时间
-
-2、exaudio.RECORD_DONE : 当录音结束时,会在回调函数返回播放完成的时间
-
-3、exaudio.AMR_NB : 仅录音时有用,表示使用AMR_NB 方式录音
-
-4、exaudio.AMR_WB : 仅录音时有用,表示使用AMR_WB 方式录音
-
-5、exaudio.PCM_8000/exaudio.PCM_16000/exaudio.PCM_24000/exaudio.PCM_32000 :  仅录音时有用,表示使用8000/16000/24000/32000/秒 的速度对音频进行采样
-
-
-## 演示功能概述
-
-1、play_flie.lua 自动播放一个1.mp3音乐,点powerkey 按键进行音频切换,点击boot 按键停止音频播放
-
-2、play_tts.lua 播放一个TTS,点powerkey 按键进行tts 的音色切换,点击boot 按键停止音频播放
-
-3、play_stream.lua 流式播放PCM,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
-
-4、record_file.lua 录音到文件,演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
-
-5、record_stream.lua 流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
-
-
-## 演示硬件环境
-
-![](https://docs.openluat.com/air8000/luatos/app/image/netdrv_multi.jpg)
-
-1、Air8000开发板一块
-
-2、喇叭一个
-
-2、插入喇叭到开发板中
-
-
-## 演示软件环境
-
-1、Luatools下载调试工具
-
-2、[Air8000 V2014版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上,2025年7月26日之后发布的固件都可以)
-
-
-## 演示核心步骤
-
-1、搭建好硬件环境
-
-2、demo脚本代码main.lua中,按照自己的需求选择对应的功能
-
-- 如果需要测试播放音频文件,则选择play_file 文件
-
-- 如果需要测试播放tts,则选择play_tts 文件
-
-- 如果需要测试流式播放音频,则选择play_stream 文件
-
-- 如果需要测试录音频到文件,则选择record_file 文件
-
-- 如果需要测试流式录音,则选择record_stream 文件
-
-
-3、Luatools烧录内核固件和修改后的demo脚本代码
-
-4、烧录成功后,自动开机运行,如果出现以下日志,播放或者或者录音完成
-
-``` lua
-I/user.播放完成 true
-I/user.录音完成 
-I/user.录音后文件大小 
-```
-
-5、 在测试播放音频文件的时候,点powerkey 按键进行音频切换,切换内容是MP3,AMR格式,切换是通过播放优先级进行区分的,注意音频格式仅仅支持:MP3,WAV,AMR,点击boot 按键停止音频播放
-
-6、 在测试播放TTS的时候,点powerkey 按键进行TTS 音色切换,点击boot 按键停止音频播放,注意:仅支持中文TTS。
-
-
-7、在进行流式播放测试的时候,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加,注意流式播放目前仅支持PCM 格式音频,可选择不同的采样率,以及位深
-
-8、在测试录音到文件(仅支持PCM),演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
-
-9、在测试流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
-
-
-

BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/音频硬件框架.png


+ 124 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/bit64_app.lua

@@ -0,0 +1,124 @@
+--[[
+@module  bit64_app
+@summary bit64_app应用功能模块 
+@version 1.0
+@date    2025.10.14
+@author  沈园园
+@usage
+本文件为bit64应用功能模块,核心业务逻辑为:
+1、演示在 32 位系统上对 64 位数据的基本算术运算和逻辑运算;
+
+本文件没有对外接口,直接在main.lua中require "bit64_app"就可以加载运行;
+]]
+
+--定义几个变量
+local data,b64,b32,a,b
+
+--设置调试风格1: I/main.lua:12 ABC DEF 123(在开头添加文件位置信息)
+log.style(1)
+
+--本文的64bit数据为:小端格式的9字节数据的字符串,最后一个字节是类型(0是整数,1是浮点), 前面8个字节是数据。
+--十进制转123456对应64bit十六进制字符串"40E2010000000000"
+
+if bit64 then
+	log.info("bit64 演示")
+    
+    --将数据进行 32 位和 64 位互转
+    --123456对应64bit的9字节数据HEX字符
+    data = "40E201000000000000"
+    --需将HEX字符串转成Lua字符串填入 
+    b32 = bit64.to32(data:fromHex())
+    log.info("i32", b32, mcu.x32(b32))    
+    
+    data = 12345678
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("i32", b32, mcu.x32(b32))
+	data = -12345678
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("i32", b32, mcu.x32(b32))
+	data = 12.34234
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("f32", data, b32)
+	data = -12.34234
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("f32", data, b32)
+
+    --64 位数据之间进行运算
+    --64bit数据格式化打印成字符串,用于显示值
+    --64bit数据加,a+b,a和b中有一个为浮点,则按照浮点运算
+    --64bit数据减,a-b,a和b中有一个为浮点,则按照浮点运算
+    --64bit数据乘,a*b,a和b中有一个为浮点,则按照浮点运算
+    --64bit数据除,a/b,a和b中有一个为浮点,则按照浮点运算
+	a = bit64.to64(87654321)
+	b = bit64.to64(12345678)
+	log.info("87654321+12345678=", bit64.show(bit64.plus(a,b)))
+	log.info("87654321-12345678=", bit64.show(bit64.minus(a,b)))
+	log.info("87654321*12345678=", bit64.show(bit64.multi(a,b)))
+	log.info("87654321/12345678=", bit64.show(bit64.pide(a,b)))
+
+	--64 位与 32 位数据之间进行运算
+    a = bit64.to64(87654321)
+	b = 1234567
+	log.info("87654321+1234567=", bit64.show(bit64.plus(a,b)))
+	log.info("87654321-1234567=", bit64.show(bit64.minus(a,b)))
+	log.info("87654321*1234567=", bit64.show(bit64.multi(a,b)))
+	log.info("87654321/1234567=", bit64.show(bit64.pide(a,b)))
+
+
+	--64 位数据之间,一个数是浮点数进行运算
+    a = bit64.to64(87654.326)
+	b = bit64.to64(12345)
+	log.info("87654.326+12345=", 87654.326 + 12345)
+	log.info("87654.326+12345=", bit64.show(bit64.plus(a,b)))
+	log.info("87654.326-12345=", bit64.show(bit64.minus(a,b)))
+	log.info("87654.326*12345=", bit64.show(bit64.multi(a,b)))
+	log.info("87654.326/12345=", bit64.show(bit64.pide(a,b)))
+
+	--64 位浮点数计算
+    a = bit64.to64(87654.32)
+	b = bit64.to64(12345.67)
+	log.info("float", "87654.32+12345.67=", 87654.32 + 12345.67)
+	log.info("double","87654.32+12345.67=", bit64.show(bit64.plus(a,b)))
+	log.info("double to float","87654.32+12345.67=", bit64.to32(bit64.plus(a,b)))
+	log.info("87654.32-12345.67=", bit64.show(bit64.minus(a,b)))
+	log.info("87654.32*12345.67=", bit64.show(bit64.multi(a,b)))
+	log.info("87654.32/12345.67=", bit64.show(bit64.pide(a,b)))
+	log.info("double to int64", "87654.32/12345.67=", bit64.show(bit64.pide(a,b,nil,true)))
+
+	--64 位数据移位操作
+    a = bit64.to64(0xc0000000)
+	b = 2
+	a = bit64.shift(a,8,true)
+	log.info("0xc0000000 << 8 =", bit64.show(a, 16))
+	log.info("0xc000000000+2=", bit64.show(bit64.plus(a,b), 16))
+	log.info("0xc000000000-2=", bit64.show(bit64.minus(a,b), 16))
+	log.info("0xc000000000*2=", bit64.show(bit64.multi(a,b), 16))
+	log.info("0xc000000000/2=", bit64.show(bit64.pide(a,b), 16))
+	log.style(0)
+
+	--将字符串转为 LongLong 数据
+    if bit64.strtoll then
+		local data = bit64.strtoll("864040064024194", 10)
+		log.info("data", data:toHex())
+		log.info("data", bit64.show(data))
+	end
+end
+
+--获取高精度 tick,输出转换好的 64 位结构
+local function sys_run_time()
+	local tick64, per = mcu.tick64(true)
+	local per_cnt = per * 1000000
+	while true do
+		tick64, per = mcu.tick64(true)
+		log.info("work time","当前时间", bit64.to32(bit64.pide(tick64,per_cnt)))
+		sys.wait(1000)
+	end
+end
+
+if mcu.tick64 then
+	sys.taskInit(sys_run_time)
+end

+ 55 - 98
module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/main.lua

@@ -1,114 +1,71 @@
-
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "bit64_test"
-VERSION = "1.0.0"
-
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.14
+@author  沈园园
+@usage
+本demo演示的核心功能为:
+演示在 32 位系统上对 64 位数据的基本算术运算和逻辑运算
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "luatos_bit64_app"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- 引入必要的库文件(lua编写), 内部库不需要require
-sys = require("sys")
-
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
 
 
-local data,b64,b32,a,b
-
-if bit64 then
-	log.style(1)
-	log.info("bit64 演示")
-	data = 12345678
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("i32", b32, mcu.x32(b32))
-	data = -12345678
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("i32", b32, mcu.x32(b32))
-	data = 12.34234
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("f32", data, b32)
-	data = -12.34234
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("f32", data, b32)
-
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-	a = bit64.to64(87654321)
-	b = bit64.to64(12345678)
-	log.info("87654321+12345678=", bit64.show(bit64.plus(a,b)))
-	log.info("87654321-12345678=", bit64.show(bit64.minus(a,b)))
-	log.info("87654321*12345678=", bit64.show(bit64.multi(a,b)))
-	log.info("87654321/12345678=", bit64.show(bit64.pide(a,b)))
 
-	a = bit64.to64(87654321)
-	b = 1234567
-	log.info("87654321+1234567=", bit64.show(bit64.plus(a,b)))
-	log.info("87654321-1234567=", bit64.show(bit64.minus(a,b)))
-	log.info("87654321*1234567=", bit64.show(bit64.multi(a,b)))
-	log.info("87654321/1234567=", bit64.show(bit64.pide(a,b)))
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
 
 
-	a = bit64.to64(87654.326)
-	b = bit64.to64(12345)
-	log.info("87654.326+12345=", 87654.326 + 12345)
-	log.info("87654.326+12345=", bit64.show(bit64.plus(a,b)))
-	log.info("87654.326-12345=", bit64.show(bit64.minus(a,b)))
-	log.info("87654.326*12345=", bit64.show(bit64.multi(a,b)))
-	log.info("87654.326/12345=", bit64.show(bit64.pide(a,b)))
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
 
-	a = bit64.to64(87654.32)
-	b = bit64.to64(12345.67)
-	log.info("float", "87654.32+12345.67=", 87654.32 + 12345.67)
-	log.info("double","87654.32+12345.67=", bit64.show(bit64.plus(a,b)))
-	log.info("double to float","87654.32+12345.67=", bit64.to32(bit64.plus(a,b)))
-	log.info("87654.32-12345.67=", bit64.show(bit64.minus(a,b)))
-	log.info("87654.32*12345.67=", bit64.show(bit64.multi(a,b)))
-	log.info("87654.32/12345.67=", bit64.show(bit64.pide(a,b)))
-	log.info("double to int64", "87654.32/12345.67=", bit64.show(bit64.pide(a,b,nil,true)))
-
-	a = bit64.to64(0xc0000000)
-	b = 2
-	a = bit64.shift(a,8,true)
-	log.info("0xc0000000 << 8 =", bit64.show(a, 16))
-	log.info("0xc000000000+2=", bit64.show(bit64.plus(a,b), 16))
-	log.info("0xc000000000-2=", bit64.show(bit64.minus(a,b), 16))
-	log.info("0xc000000000*2=", bit64.show(bit64.multi(a,b), 16))
-	log.info("0xc000000000/2=", bit64.show(bit64.pide(a,b), 16))
-	log.style(0)
-
-	if bit64.strtoll then
-		local data = bit64.strtoll("864040064024194", 10)
-		log.info("data", data:toHex())
-		log.info("data", bit64.show(data))
-	end
-end
-
-local function sys_run_time()
-	local tick64, per = mcu.tick64(true)
-	local per_cnt = per * 1000000
-	while true do
-		tick64, per = mcu.tick64(true)
-		log.info("work time","当前时间", bit64.to32(bit64.pide(tick64,per_cnt)))
-		sys.wait(1000)
-	end
-end
-
-if mcu.tick64 then
-	sys.taskInit(sys_run_time)
-end
+-- 加载bit64_app应用功能模块
+require "bit64_app"
 
 -- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 85 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/readme.md

@@ -0,0 +1,85 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、bit64_app.lua:32 位系统上对 64 位数据的基本算术运算和逻辑运算;
+
+## 演示功能概述
+
+1、创建一个task;
+
+2、演示在 32 位系统上对 64 位数据的基本算术运算和逻辑运算;
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV2.png)
+
+1、Air780EHM/Air780EHV/Air780EGH核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为
+
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
+
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、出现类似于下面的日志,就表示运行成功:
+
+``` lua
+[2025-10-15 11:10:19.702][000000000.210] I/bit64_luatos.lua:24 bit64 演示
+[2025-10-15 11:10:19.706][000000000.211] I/bit64_luatos.lua:31 i32 123456 0x1e240
+[2025-10-15 11:10:19.713][000000000.211] I/bit64_luatos.lua:36 i32 12345678 0xbc614e
+[2025-10-15 11:10:19.721][000000000.211] I/bit64_luatos.lua:40 i32 -12345678 0xff439eb2
+[2025-10-15 11:10:19.727][000000000.212] I/bit64_luatos.lua:44 f32   12.3423   12.3423
+[2025-10-15 11:10:19.734][000000000.212] I/bit64_luatos.lua:48 f32  -12.3423  -12.3423
+[2025-10-15 11:10:19.742][000000000.212] I/bit64_luatos.lua:58 87654321+12345678= 99999999
+[2025-10-15 11:10:19.747][000000000.213] I/bit64_luatos.lua:59 87654321-12345678= 75308643
+[2025-10-15 11:10:19.752][000000000.213] I/bit64_luatos.lua:60 87654321*12345678= 1082152022374638
+[2025-10-15 11:10:19.757][000000000.213] I/bit64_luatos.lua:61 87654321/12345678= 7
+[2025-10-15 11:10:19.763][000000000.213] I/bit64_luatos.lua:66 87654321+1234567= 88888888
+[2025-10-15 11:10:19.768][000000000.214] I/bit64_luatos.lua:67 87654321-1234567= 86419754
+[2025-10-15 11:10:19.773][000000000.214] I/bit64_luatos.lua:68 87654321*1234567= 108215132114007
+[2025-10-15 11:10:19.777][000000000.214] I/bit64_luatos.lua:69 87654321/1234567= 71
+[2025-10-15 11:10:19.784][000000000.214] I/bit64_luatos.lua:75 87654.326+12345=   99999.3
+[2025-10-15 11:10:19.789][000000000.215] I/bit64_luatos.lua:76 87654.326+12345= 99999.328125
+[2025-10-15 11:10:19.794][000000000.215] I/bit64_luatos.lua:77 87654.326-12345= 75309.328125
+[2025-10-15 11:10:19.799][000000000.215] I/bit64_luatos.lua:78 87654.326*12345= 1.082093e+09
+[2025-10-15 11:10:19.804][000000000.216] I/bit64_luatos.lua:79 87654.326/12345= 7.100391
+[2025-10-15 11:10:19.811][000000000.216] I/bit64_luatos.lua:84 float 87654.32+12345.67=  100000.0
+[2025-10-15 11:10:19.816][000000000.216] I/bit64_luatos.lua:85 double 87654.32+12345.67= 99999.990234
+[2025-10-15 11:10:19.823][000000000.217] I/bit64_luatos.lua:86 double to float 87654.32+12345.67=  100000.0
+[2025-10-15 11:10:19.827][000000000.217] I/bit64_luatos.lua:87 87654.32-12345.67= 75308.650391
+[2025-10-15 11:10:19.832][000000000.217] I/bit64_luatos.lua:88 87654.32*12345.67= 1.082151e+09
+[2025-10-15 11:10:19.836][000000000.218] I/bit64_luatos.lua:89 87654.32/12345.67= 7.100005
+[2025-10-15 11:10:19.842][000000000.218] I/bit64_luatos.lua:90 double to int64 87654.32/12345.67= 7
+[2025-10-15 11:10:19.849][000000000.218] I/bit64_luatos.lua:96 0xc0000000 << 8 = 0xc000000000
+[2025-10-15 11:10:19.853][000000000.218] I/bit64_luatos.lua:97 0xc000000000+2= 0xc000000002
+[2025-10-15 11:10:19.860][000000000.219] I/bit64_luatos.lua:98 0xc000000000-2= 0xbffffffffe
+[2025-10-15 11:10:19.867][000000000.219] I/bit64_luatos.lua:99 0xc000000000*2= 0x18000000000
+[2025-10-15 11:10:19.871][000000000.219] I/bit64_luatos.lua:100 0xc000000000/2= 0x6000000000
+[2025-10-15 11:10:19.876][000000000.219] I/user.data 827E1601D711030000 18
+[2025-10-15 11:10:19.882][000000000.220] I/user.data 864040064024194
+[2025-10-15 11:10:19.887][000000000.220] I/user.work time 当前时间 0
+[2025-10-15 11:10:20.345][000000001.220] I/user.work time 当前时间 1
+```

+ 282 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/crypto_app.lua

@@ -0,0 +1,282 @@
+--[[
+@module  crypto_app
+@summary crypto_app应用功能模块 
+@version 1.0
+@date    2025.10.15
+@author  沈园园
+@usage
+本文件为crypto_app应用功能模块,核心业务逻辑为:
+1、演示有关加解密的各种 API 的功能包含MD5,SHA,哈希(MD5,SHA),AES/DEC/3DES,CRC,checksum校验和;
+
+本文件没有对外接口,直接在main.lua中require "crypto_app"就可以加载运行;
+]]
+
+--加密运算主函数
+local function crypto_task_func()
+
+    -- MD5/哈希MD5,输出结果已经hex编码
+    log.info("md5", crypto.md5("abc"))
+    log.info("hmac_md5", crypto.hmac_md5("abc", "1234567890"))
+
+    -- SHA1/哈希SHA1,输出结果已经hex编码
+    log.info("sha1", crypto.sha1("abc"))
+    log.info("hmac_sha1", crypto.hmac_sha1("abc", "1234567890"))
+
+    -- SHA256/哈希SHA256,输出结果已经hex编码
+    log.info("sha256", crypto.sha256("abc"))
+    log.info("hmac_sha256", crypto.hmac_sha256("abc", "1234567890"))
+
+    -- SHA512/哈希SHA512,输出结果已经hex编码
+    log.info("sha512", crypto.sha512("abc"))
+    log.info("hmac_sha512", crypto.hmac_sha512("abc", "1234567890"))
+
+	-- AES加密, 未经Hex编码. AES-128-ECB 算法 对称加密,对齐方式:"ZERO"
+    local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "ZERO", "023001", "HZBIT@WLW/YSBKEY")
+	log.info("AES", "aes-128-ecb", data_encrypt:toHex())
+	local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "ZERO", data_encrypt, "HZBIT@WLW/YSBKEY")
+	log.info("AES", "aes-128-ecb", data_decrypt)
+
+    -- AES加密, 未经Hex编码. AES-128-ECB/CBC 算法 对称加密,对齐方式:"PKCS7"
+    local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "PKCS7", "12345678901234 > 123456", "1234567890123456")
+    local data2_encrypt = crypto.cipher_encrypt("AES-128-CBC", "PKCS7", "12345678901234 > 123456", "1234567890123456", "1234567890666666")
+    log.info("AES", "aes-128-ecb", data_encrypt:toHex())
+    log.info("AES", "aes-128-cbc", data2_encrypt:toHex())
+
+    -- AES解密, 未经Hex编码, AES-128-ECB/CBC 算法 对称解密,,对齐方式:"PKCS7"
+    local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "PKCS7", data_encrypt, "1234567890123456")
+    local data2_decrypt = crypto.cipher_decrypt("AES-128-CBC", "PKCS7", data2_encrypt, "1234567890123456", "1234567890666666")
+    log.info("AES", "aes-128-ecb", data_decrypt)
+    log.info("AES", "aes-128-cbc", data2_decrypt)
+    log.info("mem", rtos.meminfo("sys"))
+
+    -- DES-ECB 加解密
+    local data1 = crypto.cipher_encrypt("DES-ECB", "PKCS7", "abcdefg", "12345678")
+    if data1 then 
+        log.info("des", data1:toHex())
+        local data2 = crypto.cipher_decrypt("DES-ECB", "PKCS7", data1, "12345678")
+        log.info("des", data2)
+    else
+        log.info("des", "当前固件不支持DES/3DES")
+    end
+
+    -- 3DES-ECB 加解密
+    local data1 = crypto.cipher_encrypt("DES-EDE3-ECB", "PKCS7", "abcdefg!!--ZZSS", "123456781234567812345678")
+    if data1 then 
+        log.info("3des", data1:toHex())
+        local data2 = crypto.cipher_decrypt("DES-EDE3-ECB", "PKCS7", data1, "123456781234567812345678")
+        log.info("3des", data2)
+    else
+        log.info("3des", "当前固件不支持DES/3DES")
+    end
+  
+    -- 计算CRC16
+    local originStr = "123456sdfdsfdsfdsffdsfdsfsdfs1234"
+    local crc16 = crypto.crc16("MODBUS",originStr)
+    log.info("crc16", crc16)
+    
+    -- 计算CRC16 modbus
+    local crc16 = crypto.crc16_modbus("123456sdfdsfdsfdsffdsfdsfsdfs1234")
+    log.info("crc16", crc16)
+    crc16 = crypto.crc16_modbus("123456sdfdsfdsfdsffdsfdsfsdfs1234", 0xFFFF)
+    log.info("crc16", crc16)
+    
+    -- 计算CRC32
+    local data = "123456sdfdsfdsfdsffdsfdsfsdfs1234" 
+    local crc32 = crypto.crc32(data)
+    log.info("crc32", crc32) --21438764
+    -- start和poly可选, 是 2025.4.14 新增的参数
+    local crc32 = crypto.crc32(data, 0xFFFFFFFF, 0x04C11DB7, 0xFFFFFFFF) --等同于crypto.crc32(data)
+    log.info("crc32", crc32)
+
+    -- 计算CRC8
+    local data= "sdfdsfdsfdsffdsfdsfsdfs1234"
+    local crc8 = crypto.crc8(data)
+    log.info("crc8", crc8)
+    local crc8 = crypto.crc8(data, 0x31, 0xff, false)
+    log.info("crc8", crc8)  
+        
+    -- 计算CRC7
+    if crypto.crc7 then
+        local result = crypto.crc7("abc", 0xE5, 0x00)
+        log.info("crc7", result, string.format("%02X", result))  --50 32
+    else
+        log.info("crypto", "当前固件不支持crypto.crc7")
+    end
+    
+    log.info("随机数测试")
+    for i=1, 10 do
+        sys.wait(100)
+        log.info("crypto", "真随机数",string.unpack("I",crypto.trng(4)))
+        -- log.info("crypto", "伪随机数",math.random()) -- 输出的是浮点数,不推荐
+        -- log.info("crypto", "伪随机数",math.random(1, 65525)) -- 不推荐
+    end
+
+    -- totp的密钥
+    log.info("totp的密钥")
+    local secret = "VK54ZXPO74ISEM2E"
+    --写死时间戳用来测试
+    local ts = 1646796576
+    --生成十分钟的动态码验证下
+    for i=1,600,30 do
+        local r = crypto.totp(secret,ts+i)
+        local time = os.date("*t",ts+i + 8*3600)--东八区
+        log.info("totp", string.format("%06d" ,r),time.hour,time.min,time.sec)
+    end
+    
+    --将数据进行base64编码
+    -- 本函数与 string.toBase64 是同一个
+    local bdata = crypto.base64_encode("123")
+    log.info("base64", "encode", bdata)
+    
+    --将数据进行base64解码
+    -- 本函数与 string.fromBase64 是同一个
+    local data = crypto.base64_decode("MTIz")
+    log.info("base64", "decode", bdata, data)--123    
+
+    -- 打印所有支持的cipher
+    if crypto.cipher_list then
+        log.info("cipher", "list", json.encode(crypto.cipher_list()))
+    else
+        log.info("cipher", "当前固件不支持crypto.cipher_list")
+    end
+    -- 打印所有支持的cipher suites
+    if crypto.cipher_suites then
+        log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
+    else
+        log.info("cipher", "当前固件不支持crypto.cipher_suites")
+    end
+    
+    -- 计算文件的hash值(md5/sha1/sha256及hmac形式)
+    log.info("文件hash值测试")
+    if crypto.md_file then
+        -- 无hmac的hash值
+        log.info("md5", crypto.md_file("MD5", "/luadb/logo.jpg"))
+        log.info("sha1", crypto.md_file("SHA1", "/luadb/logo.jpg"))
+        log.info("sha256", crypto.md_file("SHA256", "/luadb/logo.jpg"))
+        
+        -- 带hmac的hash值
+        log.info("hmac_md5", crypto.md_file("MD5", "/luadb/logo.jpg", "123456"))
+        log.info("hmac_sha1", crypto.md_file("SHA1", "/luadb/logo.jpg", "123456"))
+        log.info("hmac_sha256", crypto.md_file("SHA256", "/luadb/logo.jpg", "123456"))
+    else
+        log.info("文件hash值测试", "当前固件不支持crypto.md_file")
+    end
+    
+    --计算数据的hash值(md5/sha1/sha256及hmac形式)
+    if crypto.md then
+        -- 无hmac的hash值
+        log.info("md5", crypto.md("MD5", "1234567890"))
+        log.info("sha1", crypto.md("SHA1", "1234567890"))
+        log.info("sha256", crypto.md("SHA256", "1234567890"))
+        
+        -- 带hmac的hash值
+        log.info("hmac_md5", crypto.md("MD5", "1234567890", "123456"))
+        log.info("hmac_sha1", crypto.md("SHA1", "1234567890", "123456"))
+        log.info("hmac_sha256", crypto.md("SHA256", "1234567890", "123456"))
+    else
+        log.info("数据hash值测试", "当前固件不支持crypto.md")    
+    end
+    
+    -- 流式hash测试
+    log.info("流式hash测试")
+    if crypto.hash_init then
+        -- MD5
+        local md5_obj = crypto.hash_init("MD5")
+        crypto.hash_update(md5_obj, "1234567890")
+        crypto.hash_update(md5_obj, "1234567890")
+        crypto.hash_update(md5_obj, "1234567890")
+        crypto.hash_update(md5_obj, "1234567890")
+        local md5_result = crypto.hash_finish(md5_obj)
+        log.info("md5_stream", md5_result)
+        log.info("md5", crypto.md5("1234567890123456789012345678901234567890"))
+
+        -- HMAC_MD5
+        local hmac_md5_obj = crypto.hash_init("MD5", "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        local hmac_md5_result = crypto.hash_finish(hmac_md5_obj)
+        log.info("hmac_md5_stream", hmac_md5_result)
+        log.info("hmac_md5", crypto.hmac_md5("1234567890123456789012345678901234567890", "1234567890"))
+
+        -- SHA1
+        local sha1_obj = crypto.hash_init("SHA1")
+        crypto.hash_update(sha1_obj, "1234567890")
+        crypto.hash_update(sha1_obj, "1234567890")
+        crypto.hash_update(sha1_obj, "1234567890")
+        crypto.hash_update(sha1_obj, "1234567890")
+        local sha1_result = crypto.hash_finish(sha1_obj)
+        log.info("sha1_stream", sha1_result)
+        log.info("sha1", crypto.sha1("1234567890123456789012345678901234567890"))
+
+        -- HMAC_SHA1
+        local hmac_sha1_obj = crypto.hash_init("SHA1", "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        local hmac_sha1_result = crypto.hash_finish(hmac_sha1_obj)
+        log.info("hmac_sha1_stream", hmac_sha1_result)
+        log.info("hmac_sha1", crypto.hmac_sha1("1234567890123456789012345678901234567890", "1234567890"))
+
+        -- SHA256
+        local sha256_obj = crypto.hash_init("SHA256")
+        crypto.hash_update(sha256_obj, "1234567890")
+        crypto.hash_update(sha256_obj, "1234567890")
+        crypto.hash_update(sha256_obj, "1234567890")
+        crypto.hash_update(sha256_obj, "1234567890")
+        local sha256_result = crypto.hash_finish(sha256_obj)
+        log.info("sha256_stream", sha256_result)
+        log.info("sha256", crypto.sha256("1234567890123456789012345678901234567890"))
+
+        -- HMAC_SHA256
+        local hmac_sha256_obj = crypto.hash_init("SHA256", "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        local hmac_sha256_result = crypto.hash_finish(hmac_sha256_obj)
+        log.info("hmac_sha256_stream", hmac_sha256_result)
+        log.info("hmac_sha256", crypto.hmac_sha256("1234567890123456789012345678901234567890", "1234567890"))
+
+        -- SHA512
+        local sha512_obj = crypto.hash_init("SHA512")
+        if sha512_obj then
+            crypto.hash_update(sha512_obj, "1234567890")
+            crypto.hash_update(sha512_obj, "1234567890")
+            crypto.hash_update(sha512_obj, "1234567890")
+            crypto.hash_update(sha512_obj, "1234567890")
+            local sha512_result = crypto.hash_finish(sha512_obj)
+            log.info("sha512_stream", sha512_result)
+            log.info("sha512", crypto.sha512("1234567890123456789012345678901234567890"))
+        end
+
+        -- HMAC_SHA512
+        local hmac_sha512_obj = crypto.hash_init("SHA512", "1234567890")
+        if hmac_sha512_obj then
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            local hmac_sha512_result = crypto.hash_finish(hmac_sha512_obj)
+            log.info("hmac_sha512_stream", hmac_sha512_result)
+            log.info("hmac_sha512", crypto.hmac_sha512("1234567890123456789012345678901234567890", "1234567890"))
+        end
+    else
+        log.info("crypto", "当前固件不支持crypto.hash_init")
+    end
+   
+    if crypto.checksum then
+        log.info("checksum", "OK", string.char(crypto.checksum("OK")):toHex())
+        log.info("checksum", "357E", string.char(crypto.checksum("357E", 1)):toHex())
+    else
+        log.info("checksum", "当前固件不支持crypto.checksum")
+    end    
+
+    log.info("crypto", "ALL Done")
+    sys.wait(100000)
+end    
+
+--创建一个task,并且运行task的主函数crypto_task_func
+sys.taskInit(crypto_task_func)

+ 55 - 226
module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/main.lua

@@ -1,242 +1,71 @@
-
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "cryptodemo"
-VERSION = "1.0.0"
-
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.15
+@author  沈园园
+@usage
+本demo演示的核心功能为:
+演示有关加解密的各种 API 的功能包含MD5,SHA,哈希(MD5,SHA),AES/DEC/3DES,CRC,checksum校验和
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "luatos_crypto_app"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- sys库是标配
-_G.sys = require("sys")
-
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
 
-sys.taskInit(function()
-
-    sys.wait(1000)
-
-    -- MD5,输出结果已经hex编码
-    log.info("md5", crypto.md5("abc"))
-    log.info("hmac_md5", crypto.hmac_md5("abc", "1234567890"))
-
-    -- SHA1,输出结果已经hex编码
-    log.info("sha1", crypto.sha1("abc"))
-    log.info("hmac_sha1", crypto.hmac_sha1("abc", "1234567890"))
-
-    -- SHA256,输出结果已经hex编码
-    log.info("sha256", crypto.sha256("abc"))
-    log.info("hmac_sha256", crypto.hmac_sha256("abc", "1234567890"))
-
-    -- SHA512,输出结果已经hex编码
-    log.info("sha512", crypto.sha512("abc"))
-    log.info("hmac_sha512", crypto.hmac_sha512("abc", "1234567890"))
-
-	local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "ZERO", "023001", "HZBIT@WLW/YSBKEY")
-	log.info("AES", "aes-128-ecb", data_encrypt:toHex())
-	local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "ZERO", data_encrypt, "HZBIT@WLW/YSBKEY")
-	log.info("AES", "aes-128-ecb", data_decrypt)
-
-    -- AES加密, 未经Hex编码. AES-128-ECB 算法
-    local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "PKCS7", "12345678901234 > 123456", "1234567890123456")
-    local data2_encrypt = crypto.cipher_encrypt("AES-128-CBC", "PKCS7", "12345678901234 > 123456", "1234567890123456", "1234567890666666")
-    log.info("AES", "aes-128-ecb", data_encrypt:toHex())
-    log.info("AES", "aes-128-cbc", data2_encrypt:toHex())
-
-    -- AES解密, 未经Hex编码
-    local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "PKCS7", data_encrypt, "1234567890123456")
-    local data2_decrypt = crypto.cipher_decrypt("AES-128-CBC", "PKCS7", data2_encrypt, "1234567890123456", "1234567890666666")
-    log.info("AES", "aes-128-ecb", data_decrypt)
-    log.info("AES", "aes-128-cbc", data2_decrypt)
-    log.info("mem", rtos.meminfo("sys"))
-
-    -- DES-ECB 加解密
-    local data1 = crypto.cipher_encrypt("DES-ECB", "PKCS7", "abcdefg", "12345678")
-    if data1 then -- DES-ECB 在某些平台不支持的
-        log.info("des", data1:toHex())
-        local data2 = crypto.cipher_decrypt("DES-ECB", "PKCS7", data1, "12345678")
-        log.info("des", data2)
-    else
-        log.info("des", "当前固件不支持DES/3DES")
-    end
-
-    -- 3DES-ECB 加解密
-    local data1 = crypto.cipher_encrypt("DES-EDE3-ECB", "PKCS7", "abcdefg!!--ZZSS", "123456781234567812345678")
-    if data1 then -- DES-ECB 在某些平台不支持的
-        log.info("3des", data1:toHex())
-        local data2 = crypto.cipher_decrypt("DES-EDE3-ECB", "PKCS7", data1, "123456781234567812345678")
-        log.info("3des", data2)
-    else
-        log.info("3des", "当前固件不支持DES/3DES")
-    end
-
-
-    -- 打印所有支持的cipher
-    if crypto.cipher_list then
-        log.info("cipher", "list", json.encode(crypto.cipher_list()))
-    else
-        log.info("cipher", "当前固件不支持crypto.cipher_list")
-    end
-    -- 打印所有支持的cipher suites
-    if crypto.cipher_suites then
-        log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-    else
-        log.info("cipher", "当前固件不支持crypto.cipher_suites")
-    end
-
-    ---------------------------------------
-    log.info("随机数测试")
-    for i=1, 10 do
-        sys.wait(100)
-        log.info("crypto", "真随机数",string.unpack("I",crypto.trng(4)))
-        -- log.info("crypto", "伪随机数",math.random()) -- 输出的是浮点数,不推荐
-        -- log.info("crypto", "伪随机数",math.random(1, 65525)) -- 不推荐
-    end
-
-    -- totp的密钥
-    log.info("totp的密钥")
-    local secret = "VK54ZXPO74ISEM2E"
-    --写死时间戳用来测试
-    local ts = 1646796576
-    --生成十分钟的动态码验证下
-    for i=1,600,30 do
-        local r = crypto.totp(secret,ts+i)
-        local time = os.date("*t",ts+i + 8*3600)--东八区
-        log.info("totp", string.format("%06d" ,r),time.hour,time.min,time.sec)
-    end
-
-    -- 文件测试
-    log.info("文件hash值测试")
-    if crypto.md_file then
-        log.info("md5", crypto.md_file("MD5", "/luadb/logo.jpg"))
-        log.info("sha1", crypto.md_file("SHA1", "/luadb/logo.jpg"))
-        log.info("sha256", crypto.md_file("SHA256", "/luadb/logo.jpg"))
-        
-        log.info("hmac_md5", crypto.md_file("MD5", "/luadb/logo.jpg", "123456"))
-        log.info("hmac_sha1", crypto.md_file("SHA1", "/luadb/logo.jpg", "123456"))
-        log.info("hmac_sha256", crypto.md_file("SHA256", "/luadb/logo.jpg", "123456"))
-    else
-        log.info("文件hash值测试", "当前固件不支持crypto.md_file")
-    end
-
-    if crypto.checksum then
-        log.info("checksum", "OK", string.char(crypto.checksum("OK")):toHex())
-        log.info("checksum", "357E", string.char(crypto.checksum("357E", 1)):toHex())
-    else
-        log.info("checksum", "当前固件不支持crypto.checksum")
-    end
-
-    -- 流式hash测试
-    log.info("流式hash测试")
-    if crypto.hash_init then
-        -- MD5
-        local md5_obj = crypto.hash_init("MD5")
-        crypto.hash_update(md5_obj, "1234567890")
-        crypto.hash_update(md5_obj, "1234567890")
-        crypto.hash_update(md5_obj, "1234567890")
-        crypto.hash_update(md5_obj, "1234567890")
-        local md5_result = crypto.hash_finish(md5_obj)
-        log.info("md5_stream", md5_result)
-        log.info("md5", crypto.md5("1234567890123456789012345678901234567890"))
-
-        -- HMAC_MD5
-        local hmac_md5_obj = crypto.hash_init("MD5", "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        local hmac_md5_result = crypto.hash_finish(hmac_md5_obj)
-        log.info("hmac_md5_stream", hmac_md5_result)
-        log.info("hmac_md5", crypto.hmac_md5("1234567890123456789012345678901234567890", "1234567890"))
-
-        -- SHA1
-        local sha1_obj = crypto.hash_init("SHA1")
-        crypto.hash_update(sha1_obj, "1234567890")
-        crypto.hash_update(sha1_obj, "1234567890")
-        crypto.hash_update(sha1_obj, "1234567890")
-        crypto.hash_update(sha1_obj, "1234567890")
-        local sha1_result = crypto.hash_finish(sha1_obj)
-        log.info("sha1_stream", sha1_result)
-        log.info("sha1", crypto.sha1("1234567890123456789012345678901234567890"))
-
-        -- HMAC_SHA1
-        local hmac_sha1_obj = crypto.hash_init("SHA1", "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        local hmac_sha1_result = crypto.hash_finish(hmac_sha1_obj)
-        log.info("hmac_sha1_stream", hmac_sha1_result)
-        log.info("hmac_sha1", crypto.hmac_sha1("1234567890123456789012345678901234567890", "1234567890"))
 
-        -- SHA256
-        local sha256_obj = crypto.hash_init("SHA256")
-        crypto.hash_update(sha256_obj, "1234567890")
-        crypto.hash_update(sha256_obj, "1234567890")
-        crypto.hash_update(sha256_obj, "1234567890")
-        crypto.hash_update(sha256_obj, "1234567890")
-        local sha256_result = crypto.hash_finish(sha256_obj)
-        log.info("sha256_stream", sha256_result)
-        log.info("sha256", crypto.sha256("1234567890123456789012345678901234567890"))
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-        -- HMAC_SHA256
-        local hmac_sha256_obj = crypto.hash_init("SHA256", "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        local hmac_sha256_result = crypto.hash_finish(hmac_sha256_obj)
-        log.info("hmac_sha256_stream", hmac_sha256_result)
-        log.info("hmac_sha256", crypto.hmac_sha256("1234567890123456789012345678901234567890", "1234567890"))
 
-        -- SHA512
-        local sha512_obj = crypto.hash_init("SHA512")
-        if sha512_obj then
-            crypto.hash_update(sha512_obj, "1234567890")
-            crypto.hash_update(sha512_obj, "1234567890")
-            crypto.hash_update(sha512_obj, "1234567890")
-            crypto.hash_update(sha512_obj, "1234567890")
-            local sha512_result = crypto.hash_finish(sha512_obj)
-            log.info("sha512_stream", sha512_result)
-            log.info("sha512", crypto.sha512("1234567890123456789012345678901234567890"))
-        end
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
 
-        -- HMAC_SHA512
-        local hmac_sha512_obj = crypto.hash_init("SHA512", "1234567890")
-        if hmac_sha512_obj then
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            local hmac_sha512_result = crypto.hash_finish(hmac_sha512_obj)
-            log.info("hmac_sha512_stream", hmac_sha512_result)
-            log.info("hmac_sha512", crypto.hmac_sha512("1234567890123456789012345678901234567890", "1234567890"))
-        end
-    else
-        log.info("crypto", "当前固件不支持crypto.hash_init")
-    end
 
-    log.info("crc7测试")
-    if crypto.crc7 then
-        local result = crypto.crc7(string.char(0xAA), 0xE5, 0x00)
-        log.info("crc7测试", result, string.format("%02X", result))
-    else
-        log.info("crypto", "当前固件不支持crypto.crc7")
-    end
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
 
-    log.info("crypto", "ALL Done")
-    sys.wait(100000)
-end)
+-- 加载crypto_app应用功能模块
+require "crypto_app"
 
 -- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 152 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/readme.md

@@ -0,0 +1,152 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、crypto_app.lua:演示有关加解密的各种 API 的功能包含MD5,SHA,哈希(MD5,SHA),AES/DEC/3DES,CRC,checksum校验和;
+
+3、logo.jpg:待加密文件
+
+## 演示功能概述
+
+1、创建一个task;
+
+2、演示有关加解密的各种 API 的功能;
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV2.png)
+
+1、Air780EHM/Air780EHV/Air780EGH核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为
+
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
+
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、出现类似于下面的日志,就表示运行成功:
+
+``` lua
+[2025-10-16 15:00:32.204][000000000.203] I/user.main luatos_crypto_app 001.000.000
+[2025-10-16 15:00:32.794][000000001.212] I/user.md5 900150983CD24FB0D6963F7D28E17F72
+[2025-10-16 15:00:32.794][000000001.213] I/user.hmac_md5 416478FC0ACE1C4AB37F85F4F86A16B1
+[2025-10-16 15:00:32.794][000000001.213] I/user.sha1 A9993E364706816ABA3E25717850C26C9CD0D89D
+[2025-10-16 15:00:32.805][000000001.214] I/user.hmac_sha1 DAE54822C0DAF6C115C97B0AD62C7BCBE9D5E6FC
+[2025-10-16 15:00:32.810][000000001.214] I/user.sha256 BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD
+[2025-10-16 15:00:32.814][000000001.215] I/user.hmac_sha256 86055184805B4A466A7BE398FF4A7159F9055EA7EEF339FC94DCEC6F165898BA
+[2025-10-16 15:00:32.814][000000001.216] I/user.sha512 DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F
+[2025-10-16 15:00:32.826][000000001.217] I/user.hmac_sha512 0F92B9AC88949E0BF7C9F1E6F9901BAB8EDFDC9E561DFDE428BC4339961A0569AD01B44343AA56E439949655D15C4D28492D459E75015489920243F3C9986F2A
+[2025-10-16 15:00:32.834][000000001.217] D/crypto zero padding
+[2025-10-16 15:00:32.842][000000001.218] I/user.AES aes-128-ecb 013EEA6EBACCBD7AD990FAEF75FB99C4 32
+[2025-10-16 15:00:32.844][000000001.218] I/user.AES aes-128-ecb 023001
+[2025-10-16 15:00:32.844][000000001.219] I/user.AES aes-128-ecb A37DE67837A1A3006E47A7BC25AA0ECC030B4E058E1972FE5B257FD8C3436142 64
+[2025-10-16 15:00:32.858][000000001.219] I/user.AES aes-128-cbc 26D98EA512AE92BC487536B83F2BE99B467649A9700338F4B4FF75AA2654DD2C 64
+[2025-10-16 15:00:32.864][000000001.220] I/user.AES aes-128-ecb 12345678901234 > 123456
+[2025-10-16 15:00:32.864][000000001.220] I/user.AES aes-128-cbc 12345678901234 > 123456
+[2025-10-16 15:00:32.874][000000001.220] I/user.mem 2376584 49564 58880
+[2025-10-16 15:00:32.884][000000001.221] I/user.des 486CB8B81CACCDB7 16
+[2025-10-16 15:00:32.889][000000001.221] I/user.des abcdefg
+[2025-10-16 15:00:32.894][000000001.222] I/user.3des 14619F067B425995D0CD975B85491D98 32
+[2025-10-16 15:00:32.894][000000001.223] I/user.3des abcdefg!!--ZZSS
+[2025-10-16 15:00:32.894][000000001.223] I/user.crc16 54188
+[2025-10-16 15:00:32.905][000000001.224] I/user.crc16 54188
+[2025-10-16 15:00:32.905][000000001.224] I/user.crc16 54188
+[2025-10-16 15:00:32.914][000000001.224] I/user.crc32 21438764
+[2025-10-16 15:00:32.914][000000001.225] I/user.crc32 21438764
+[2025-10-16 15:00:32.924][000000001.225] I/user.crc8 197
+[2025-10-16 15:00:32.924][000000001.225] I/user.crc8 243
+[2025-10-16 15:00:32.937][000000001.226] I/user.crc7 50 32
+[2025-10-16 15:00:32.937][000000001.226] I/user.随机数测试
+[2025-10-16 15:00:32.944][000000001.334] I/user.crypto 真随机数 -939521674 5
+[2025-10-16 15:00:33.016][000000001.434] I/user.crypto 真随机数 -1872085296 5
+[2025-10-16 15:00:33.111][000000001.534] I/user.crypto 真随机数 517455164 5
+[2025-10-16 15:00:33.223][000000001.634] I/user.crypto 真随机数 657132096 5
+[2025-10-16 15:00:33.311][000000001.734] I/user.crypto 真随机数 -1080191914 5
+[2025-10-16 15:00:33.421][000000001.834] I/user.crypto 真随机数 -833578535 5
+[2025-10-16 15:00:33.532][000000001.942] I/user.crypto 真随机数 420499958 5
+[2025-10-16 15:00:33.626][000000002.042] I/user.crypto 真随机数 1233025030 5
+[2025-10-16 15:00:33.720][000000002.142] I/user.crypto 真随机数 2138275442 5
+[2025-10-16 15:00:33.830][000000002.242] I/user.crypto 真随机数 182791818 5
+[2025-10-16 15:00:33.830][000000002.242] I/user.totp的密钥
+[2025-10-16 15:00:33.830][000000002.243] I/user.totp 522113 19 29 37
+[2025-10-16 15:00:33.830][000000002.244] I/user.totp 964300 19 30 7
+[2025-10-16 15:00:33.830][000000002.245] I/user.totp 987714 19 30 37
+[2025-10-16 15:00:33.830][000000002.246] I/user.totp 037499 19 31 7
+[2025-10-16 15:00:33.830][000000002.246] I/user.totp 699697 19 31 37
+[2025-10-16 15:00:33.830][000000002.247] I/user.totp 548191 19 32 7
+[2025-10-16 15:00:33.845][000000002.248] I/user.totp 747517 19 32 37
+[2025-10-16 15:00:33.845][000000002.249] I/user.totp 243319 19 33 7
+[2025-10-16 15:00:33.861][000000002.249] I/user.totp 147474 19 33 37
+[2025-10-16 15:00:33.877][000000002.250] I/user.totp 039992 19 34 7
+[2025-10-16 15:00:33.877][000000002.251] I/user.totp 628512 19 34 37
+[2025-10-16 15:00:33.877][000000002.252] I/user.totp 529018 19 35 7
+[2025-10-16 15:00:33.892][000000002.253] I/user.totp 994006 19 35 37
+[2025-10-16 15:00:33.892][000000002.253] I/user.totp 851359 19 36 7
+[2025-10-16 15:00:33.907][000000002.254] I/user.totp 943237 19 36 37
+[2025-10-16 15:00:33.908][000000002.255] I/user.totp 410702 19 37 7
+[2025-10-16 15:00:33.908][000000002.256] I/user.totp 082993 19 37 37
+[2025-10-16 15:00:33.908][000000002.256] I/user.totp 193281 19 38 7
+[2025-10-16 15:00:33.908][000000002.257] I/user.totp 781573 19 38 37
+[2025-10-16 15:00:33.924][000000002.258] I/user.totp 243288 19 39 7
+[2025-10-16 15:00:33.924][000000002.258] I/user.base64 encode MTIz
+[2025-10-16 15:00:33.924][000000002.259] I/user.base64 decode MTIz 123
+[2025-10-16 15:00:33.924][000000002.260] I/user.cipher list ["AES-128-ECB","AES-192-ECB","AES-256-ECB","AES-128-CBC","AES-192-CBC","AES-256-CBC","AES-128-CTR","AES-192-CTR","AES-256-CTR","AES-128-GCM","AES-192-GCM","AES-256-GCM","AES-128-CCM","AES-192-CCM","AES-256-CCM","DES-ECB","DES-EDE-ECB","DES-EDE3-ECB","DES-CBC","DES-EDE-CBC","DES-EDE3-CBC"]
+[2025-10-16 15:00:33.939][000000002.266] I/user.cipher suites 
+[2025-10-16 15:00:33.939][000000002.266] ["TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384","TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384","TLS-ECDHE-ECDSA-WITH-AES-256-CCM","TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384","TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384","TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA","TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA","TLS-ECDHE-ECDSA-WITH-AES-256-CCM-8","TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256","TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256","TLS-ECDHE-ECDSA-WITH-AES-128-CCM","TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256","TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256","TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA","TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA","TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8","TLS-RSA-WITH-AES-256-GCM-SHA384","TLS-RSA-WITH-AES-256-CCM","TLS-RSA-WITH-AES-256-CBC-SHA256","TLS-RSA-WITH-AES-256-CBC-SHA","TLS-RSA-WITH-AES-256-CCM-8","TLS-RSA-WITH-AES-128-GCM-SHA256","TLS-RSA-WITH-AES-128-CCM","TLS-RSA-WITH-AES-128-CBC-SHA256","TLS-RSA-WITH-AES-128-CBC-SHA","TLS-RSA-WITH-AES-128-CCM-8","TLS-RSA-PSK-WITH-AES-256-GCM-SHA384","TLS-RSA-PSK-WITH-AES-256-CBC-SHA384","TLS-RSA-PSK-WITH-AES-256-CBC-SHA","TLS-RSA-PSK-WITH-AES-128-GCM-SHA256","TLS-RSA-PSK-WITH-AES-128-CBC-SHA256","TLS-RSA-PSK-WITH-AES-128-CBC-SHA","TLS-PSK-WITH-AES-256-GCM-SHA384","TLS-PSK-WITH-AES-256-CCM","TLS-PSK-WITH-AES-256-CBC-SHA384","TLS-PSK-WITH-AES-256-CBC-SHA","TLS-PSK-WITH-AES-256-CCM-8","TLS-PSK-WITH-AES-128-GCM-SHA256","TLS-PSK-WITH-AES-128-CCM","TLS-PSK-WITH-AES-128-CBC-SHA256","TLS-PSK-WITH-AES-128-CBC-SHA","TLS-PSK-WITH-AES-128-CCM-8"]
+[2025-10-16 15:00:33.955][000000002.266] I/user.文件hash值测试
+[2025-10-16 15:00:33.955][000000002.269] I/user.md5 D364D04CCD734D2757B25F3216CC431B
+[2025-10-16 15:00:33.955][000000002.273] I/user.sha1 04DA64874D415B1FB9CDD2E89927397D8F48C441
+[2025-10-16 15:00:33.955][000000002.284] I/user.sha256 0C601722B4BD2BC1A76BC3701F4EB646F5119C31702852978C326CD8D7C9212C
+[2025-10-16 15:00:33.955][000000002.287] I/user.hmac_md5 B5F5A687DB904DB67D3311899932DD61
+[2025-10-16 15:00:33.955][000000002.290] I/user.hmac_sha1 2A0B55C290D54ADF2F185EAE460254D267C7CE1F
+[2025-10-16 15:00:33.971][000000002.301] I/user.hmac_sha256 6E443C84D4D7A4721A9A195EB9038EDCFA8A8F60A3F1E8A0090B57B3BAB4942E
+[2025-10-16 15:00:33.971][000000002.301] I/user.md5
+[2025-10-16 15:00:33.971][000000002.302] I/user.sha1
+[2025-10-16 15:00:33.971][000000002.302] I/user.sha256
+[2025-10-16 15:00:33.971][000000002.303] I/user.hmac_md5
+[2025-10-16 15:00:33.971][000000002.303] I/user.hmac_sha1
+[2025-10-16 15:00:33.971][000000002.304] I/user.hmac_sha256
+[2025-10-16 15:00:33.971][000000002.304] I/user.流式hash测试
+[2025-10-16 15:00:33.986][000000002.304] I/user.md5_stream F5BF3E984432AE6F9F98840951E5CEF3
+[2025-10-16 15:00:33.986][000000002.305] I/user.md5 F5BF3E984432AE6F9F98840951E5CEF3
+[2025-10-16 15:00:33.986][000000002.306] I/user.hmac_md5_stream 45527D9407615C3A44F475BB7172752A
+[2025-10-16 15:00:33.986][000000002.306] I/user.hmac_md5 45527D9407615C3A44F475BB7172752A
+[2025-10-16 15:00:33.986][000000002.307] I/user.sha1_stream C61A2C245CB07A04482CE5B662AE67DBDBE010DB
+[2025-10-16 15:00:33.986][000000002.307] I/user.sha1 C61A2C245CB07A04482CE5B662AE67DBDBE010DB
+[2025-10-16 15:00:33.986][000000002.308] I/user.hmac_sha1_stream 88471065B8C5F64057418A0A58353A46E7841DE7
+[2025-10-16 15:00:33.986][000000002.308] I/user.hmac_sha1 88471065B8C5F64057418A0A58353A46E7841DE7
+[2025-10-16 15:00:34.002][000000002.309] I/user.sha256_stream A4EBDD541454B84CC670C9F1F5508BAF67FFD3FE59B883267808781F992A0B1D
+[2025-10-16 15:00:34.002][000000002.309] I/user.sha256 A4EBDD541454B84CC670C9F1F5508BAF67FFD3FE59B883267808781F992A0B1D
+[2025-10-16 15:00:34.002][000000002.310] I/user.hmac_sha256_stream EAF715932F064E462893B7FE04442E2C25ECF2F7C560820A648D0D94BEAEB581
+[2025-10-16 15:00:34.007][000000002.311] I/user.hmac_sha256 EAF715932F064E462893B7FE04442E2C25ECF2F7C560820A648D0D94BEAEB581
+[2025-10-16 15:00:34.007][000000002.311] I/user.sha512_stream 3A8529D8F0C7B1AD2FA54C944952829B718D5BEB4FF9BA8F4A849E02FE9A272DAF59AE3BD06DDE6F01DF863D87C8BA4AB016AC576B59A19078C26D8DBE63F79E
+[2025-10-16 15:00:34.007][000000002.312] I/user.sha512 3A8529D8F0C7B1AD2FA54C944952829B718D5BEB4FF9BA8F4A849E02FE9A272DAF59AE3BD06DDE6F01DF863D87C8BA4AB016AC576B59A19078C26D8DBE63F79E
+[2025-10-16 15:00:34.007][000000002.313] I/user.hmac_sha512_stream 18F74340A0048090521796F52A66A45B77AF5981512951B938BFCB026F2D6ED7945B0682731E4E7E3CA72021E5E8AB9810769E48C83F440DC73D6C942215E29E
+[2025-10-16 15:00:34.018][000000002.314] I/user.hmac_sha512 18F74340A0048090521796F52A66A45B77AF5981512951B938BFCB026F2D6ED7945B0682731E4E7E3CA72021E5E8AB9810769E48C83F440DC73D6C942215E29E
+[2025-10-16 15:00:34.018][000000002.315] I/user.checksum OK 04 2
+[2025-10-16 15:00:34.018][000000002.315] I/user.checksum 357E E4 2
+[2025-10-16 15:00:34.018][000000002.315] I/user.crypto ALL Done
+
+```

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/air_srv_fota.lua

@@ -43,7 +43,7 @@ local fota_running = false
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     -- fota结束,无论成功还是失败,都释放fota_running标志
@@ -65,7 +65,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/psm_power_fota.lua

@@ -33,7 +33,7 @@ libfota2 = require "libfota2"
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     --升级结束,触发升级回调,发布消息升级结束,可以进入休眠模式
@@ -54,7 +54,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/update.lua

@@ -36,7 +36,7 @@ sys.timerLoopStart(get_version, 3000)
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     if ret == 0 then
@@ -55,7 +55,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
             "3) 已经是最新版本,无需升级" )
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/self_server/customer_srv_fota.lua

@@ -37,7 +37,7 @@ local fota_running = false
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     -- fota结束,无论成功还是失败,都释放fota_running标志
@@ -59,7 +59,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/self_server/psm_power_fota.lua

@@ -29,7 +29,7 @@ libfota2 = require "libfota2"
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     --升级结束,触发升级回调,发布消息升级结束,可以进入休眠模式
@@ -50,7 +50,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
             "3) 已经是最新版本,无需升级" )
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/self_server/update.lua

@@ -33,7 +33,7 @@ sys.timerLoopStart(get_version, 3000)
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     if ret == 0 then
@@ -52,7 +52,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n"..
             "3) 已经是最新版本,无需升级" )
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 282 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/flash_fs_io.lua

@@ -0,0 +1,282 @@
+--[[
+@module  flash_fs_io
+@summary 内置Flash文件系统操作测试模块
+@version 1.0.0
+@date    2025.09.23
+@author  王棚嶙
+@usage
+本文件为内置Flash文件系统的操作测试流程:
+1. 获取文件系统信息( io.fsstat)
+2. 创建目录
+3. 创建并写入文件
+4. 检查文件是否存在
+5. 获取文件大小(io.fileSize)
+6. 读取文件内容
+7. 启动计数文件操作
+8. 文件追加测试
+9. 按行读取测试
+10. 文件重命名
+11. 列举目录内容
+12. 删除文件
+13. 删除目录
+本文件没有对外接口,直接在main.lua中require "flash_fs_io"就可以加载运行
+]]
+
+function flash_fs_io_task()
+    -- 使用内置Flash文件系统,根目录为"/",
+    local base_path = "/"
+    -- 创建一个目录
+    local demo_dir = "flash_demo"
+    -- 文件名
+    local dir_path = base_path .. demo_dir
+
+    -- ########## 开始进行内置Flash文件系统操作 ##########
+    log.info("文件系统操作", "===== 开始文件系统操作 =====")
+
+    -- 1. 获取文件系统信息 (使用 io.fsstat接口)
+    local success, total_blocks, used_blocks, block_size, fs_type  =  io.fsstat(base_path)
+    if success then
+        log.info(" io.fsstat成功:", 
+            "总空间=" .. total_blocks .. "块", 
+            "已用=" .. used_blocks .. "块", 
+            "块大小=" .. block_size.."字节",
+            "类型=" .. fs_type)
+    else
+        log.error(" io.fsstat", "获取文件系统信息失败")
+        return
+    end
+
+    -- 2. 创建目录
+    -- 如果目录不存在,则创建目录
+    if not io.dexist(dir_path) then
+        -- 创建目录
+        if io.mkdir(dir_path) then
+            log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
+        else
+            log.error("io.mkdir", "目录创建失败", "路径:" .. dir_path)
+            return
+        end
+    else
+        log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
+    end
+
+    -- 3. 创建并写入文件
+    local file_path = dir_path .. "/boottime"
+    local file = io.open(file_path, "wb")
+    if file then
+        file:write("这是内置Flash文件系统API文档示例的测试内容")
+        file:close()
+        log.info("文件创建", "文件写入成功", "路径:" .. file_path)
+    else
+        log.error("文件创建", "文件创建失败", "路径:" .. file_path)
+        return
+    end
+
+    -- 4. 检查文件是否存在
+    if io.exists(file_path) then
+        log.info("io.exists", "文件存在", "路径:" .. file_path)
+    else
+        log.error("io.exists", "文件不存在", "路径:" .. file_path)
+        return
+    end
+
+    -- 5. 获取文件大小 (使用io.fileSize接口)
+    local file_size = io.fileSize(file_path)
+    if file_size then
+        log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. file_path)
+    else
+        log.error("io.fileSize", "获取文件大小失败", "路径:" .. file_path)
+        return
+    end
+
+    -- 6. 读取文件内容
+    file = io.open(file_path, "rb")
+    if file then
+        local content = file:read("*a")
+        log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
+        file:close()
+    else
+        log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
+        return
+    end
+
+    -- 7. 启动计数文件操作
+    local count = 0
+    file = io.open(file_path, "rb")
+    if file then
+        local data = file:read("*a")
+        log.info("启动计数", "文件内容:", data, "十六进制:", data:toHex())
+        count = tonumber(data) or 0
+        file:close()
+    else
+        log.warn("启动计数", "文件不存在或无法打开")
+    end
+
+    log.info("启动计数", "当前值:", count)
+    count = count + 1
+    log.info("启动计数", "更新值:", count)
+
+    file = io.open(file_path, "wb")
+    if file then
+        file:write(tostring(count))
+        file:close()
+        log.info("文件写入", "路径:" .. file_path, "内容:", count)
+    else
+        log.error("文件写入", "无法打开文件", "路径:" .. file_path)
+        return
+    end
+
+    -- 8. 文件追加测试
+    local append_file = dir_path .. "/test_a"
+    os.remove(append_file) -- 清理旧文件
+
+    file = io.open(append_file, "wb")
+    if file then
+        file:write("ABC")
+        file:close()
+        log.info("文件创建", "路径:" .. append_file, "初始内容:ABC")
+    else
+        log.error("文件创建", "无法创建文件", "路径:" .. append_file)
+        return
+    end
+
+    file = io.open(append_file, "a+")
+    if file then
+        file:write("def")
+        file:close()
+        log.info("文件追加", "路径:" .. append_file, "追加内容:def")
+    else
+        log.error("文件追加", "无法打开文件进行追加", "路径:" .. append_file)
+        return
+    end
+
+    -- 验证追加结果
+    file = io.open(append_file, "r")
+    if file then
+        local data = file:read("*a")
+        log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
+            data == "ABCdef" and "成功" or "失败")
+        file:close()
+    else
+        log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
+        return
+    end
+
+    -- 9. 按行读取测试
+    local line_file = dir_path .. "/testline"
+    file = io.open(line_file, "w")
+    if file then
+        file:write("abc\n")
+        file:write("123\n")
+        file:write("wendal\n")
+        file:close()
+        log.info("文件创建", "路径:" .. line_file, "写入3行文本")
+    else
+        log.error("文件创建", "无法创建文件", "路径:" .. line_file)
+        return
+    end
+
+    file = io.open(line_file, "r")
+    if file then
+        log.info("按行读取", "路径:" .. line_file, "第1行:", file:read("*l"))
+        log.info("按行读取", "路径:" .. line_file, "第2行:", file:read("*l"))
+        log.info("按行读取", "路径:" .. line_file, "第3行:", file:read("*l"))
+        file:close()
+    else
+        log.error("按行读取", "无法打开文件", "路径:" .. line_file)
+        return
+    end
+
+    -- 10. 文件重命名
+    local old_path = append_file
+    local new_path = dir_path .. "/renamed_file.txt"
+    local success, err = os.rename(old_path, new_path)
+    if success then
+        log.info("os.rename", "文件重命名成功", "原路径:" .. old_path, "新路径:" .. new_path)
+
+        -- 验证重命名结果
+        if io.exists(new_path) and not io.exists(old_path) then
+            log.info("验证结果", "重命名验证成功", "新文件存在", "原文件不存在")
+        else
+            log.error("验证结果", "重命名验证失败")
+        end
+    else
+        log.error("os.rename", "重命名失败", "错误:" .. tostring(err), "原路径:" .. old_path)
+        return
+    end
+
+    -- 11. 列举目录内容
+    log.info("目录操作", "===== 开始目录列举 =====")
+
+    local ret, data = io.lsdir(dir_path, 50, 0)
+    if ret then
+        log.info("fs", "lsdir", json.encode(data))
+    else
+        log.info("fs", "lsdir", "fail", ret, data)
+        return
+    end
+
+    -- 12. 删除文件测试
+    if os.remove(new_path) then
+        log.info("os.remove", "文件删除成功", "路径:" .. new_path)
+        if not io.exists(new_path) then
+            log.info("验证结果", "renamed_file.txt文件删除验证成功")
+        else
+            log.error("验证结果", "renamed_file.txt文件删除验证失败")
+        end
+    else
+        log.error("os.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
+        return
+    end
+
+    if os.remove(line_file) then
+        log.info("os.remove", "testline文件删除成功", "路径:" .. line_file)
+        if not io.exists(line_file) then
+            log.info("验证结果", "testline文件删除验证成功")
+        else
+            log.error("验证结果", "testline文件删除验证失败")
+        end
+    else
+        log.error("os.remove", "testline文件删除失败", "路径:" .. line_file)
+        return
+    end
+
+    if os.remove(file_path) then
+        log.info("os.remove", "文件删除成功", "路径:" .. file_path)
+        if not io.exists(file_path) then
+            log.info("验证结果", "boottime文件删除验证成功")
+        else
+            log.error("验证结果", "boottime文件删除验证失败")
+        end
+    else
+        log.error("os.remove", "boottime文件删除失败", "路径:" .. file_path)
+        return
+    end
+
+    -- 13. 删除目录
+    if io.rmdir(dir_path) then
+        log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
+        if not io.dexist(dir_path) then
+            log.info("验证结果", "目录删除验证成功")
+        else
+            log.error("验证结果", "目录删除验证失败")
+        end
+    else
+        log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
+        return
+    end
+
+    -- 再次获取文件系统信息,查看空间变化
+    local final_success, final_total_blocks, final_used_blocks, final_block_size, final_fs_type =  io.fsstat(base_path)
+    if final_success then
+        log.info(" io.fsstat", "操作后文件系统信息:", 
+                 "总空间=" .. final_total_blocks .. "块", 
+                 "已用=" .. final_used_blocks .. "块", 
+                 "块大小=" .. final_block_size.."字节",
+                 "类型=" .. final_fs_type)
+    end
+
+    log.info("文件系统操作", "===== 文件系统操作完成 =====")
+end
+
+sys.taskInit(flash_fs_io_task)

+ 74 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/http_download_flash.lua

@@ -0,0 +1,74 @@
+--[[
+@module http_download_flash
+@summary HTTP下载文件到内置Flash模块
+@version 1.0.0
+@date    2025.09.23
+@author  王棚嶙
+@usage
+本文件演示的功能为通过HTTP下载文件到内置Flash中:
+1. 网络就绪检测
+2. 创建HTTP下载任务并等待完成
+3. 记录下载结果
+4. 获取并记录文件大小(使用io.fileSize)
+本文件没有对外接口,直接在main.lua中require "http_download_flash"即可
+]]
+
+local function http_download_flash_task()
+    -- 阶段1: 网络就绪检测
+    while not socket.adapter(socket.dft()) do
+        log.warn("HTTP下载", "等待网络连接", socket.dft())
+        sys.waitUntil("IP_READY", 1000)
+    end
+
+    log.info("HTTP下载", "网络已就绪", socket.dft())
+
+    -- 阶段2: 执行下载任务
+    log.info("HTTP下载", "开始下载任务")
+
+    -- 创建下载目录
+    local download_dir = "/downloads"
+    if not io.dexist(download_dir) then
+        io.mkdir(download_dir)
+    end
+
+    -- 核心下载操作开始
+    local code, headers, body_size = http.request("GET",
+                                    "https://gitee.com/openLuat/LuatOS/raw/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio/sample-6s.mp3",
+                                    nil, nil, {dst = download_dir .. "/sample-6s.mp3"}).wait()
+
+    -- 阶段3: 记录下载结果
+    log.info("HTTP下载", "下载完成", 
+        code == 200 and "success" or "error", 
+        code, 
+        json.encode(headers or {}), 
+        body_size) 
+        
+    if code == 200 then
+        -- 获取实际文件大小 (使用io.fileSize接口)
+        local actual_size = io.fileSize(download_dir .. "/sample-6s.mp3")
+        if not actual_size then
+            -- 备用方案
+            actual_size = io.fileSize(download_dir .. "/sample-6s.mp3")
+        end
+        
+        log.info("HTTP下载", "文件大小验证", "预期:", body_size, "实际:", actual_size)
+        
+        if actual_size ~= body_size then
+            log.error("HTTP下载", "文件大小不一致", "预期:", body_size, "实际:", actual_size)
+        end
+        
+        -- 展示下载后的文件系统状态
+        local success, total_blocks, used_blocks, block_size, fs_type =  io.fsstat("/")
+        if success then
+            log.info("HTTP下载", "下载后文件系统信息:", 
+                     "总空间=" .. total_blocks .. "块", 
+                     "已用=" .. used_blocks .. "块", 
+                     "块大小=" .. block_size.."字节",
+                     "类型=" .. fs_type)
+        end
+    end
+
+end
+
+-- 创建下载任务
+sys.taskInit(http_download_flash_task)

+ 78 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/main.lua

@@ -0,0 +1,78 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.09.23
+@author  王棚嶙
+@usage
+本 Demo 演示了在Air780EHM/780EGH/780EHV内置Flash文件系统中的完整操作流程:
+1. 基础操作:看门狗守护机制
+2. 文件系统操作:
+   - 文件系统信息查询( io.fsstat)
+   - 文件大小获取(io.fileSize)
+   - 文件创建/读写/追加
+   - 目录创建/删除
+   - 文件重命名/删除
+   - 文件存在性检查
+3. 下载功能:
+   - 网络检测与HTTP文件下载到内置Flash
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+PROJECT = "flash_fs_io_demo"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+-- 添加硬狗防止程序卡死
+if wdt then
+    -- 初始化watchdog设置为9s
+    wdt.init(9000)
+    -- 3s喂一次狗 
+    sys.timerLoopStart(wdt.feed, 3000) 
+end
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+--[[在加载以下两个功能时,建议分别打开进行测试,因为文件操作和http下载功能是异步操作。放到一个项目中,如果加载的时间点是随机的,就会出现哪个任务先抢到CPU时间片,哪个就先执行,不符合正常的业务逻辑,用户在参考编程的时候也要注意。]]
+
+-- 加载内置Flash文件系统操作演示模块
+require "flash_fs_io"
+-- 加载HTTP下载存入内置Flash功能演示模块
+-- require "http_download_flash"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+-- sys.run()之后后面不要加任何语句!!!!!
+sys.run()

+ 143 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/fs_io/readme.md

@@ -0,0 +1,143 @@
+## **功能模块介绍**
+
+本 Demo 演示了在Air780EHM/780EGH/780EHV内置Flash文件系统中的完整操作流程,覆盖了从文件系统读写到高级文件操作的完整功能链。项目分为两个核心模块:
+
+1、main.lua:主程序入口 <br> 
+2、flash_fs_io.lua:内置Flash文件系统的操作测试流程模块,实现文件系统管理、文件操作和目录管理功能。<br> 
+3、http_download_flash.lua:HTTP下载模块,演示HTTP下载文件到内置Flash中的功能
+
+## **演示功能概述**
+
+### 1、主程序入口模块(main.lua)
+
+- 初始化项目信息和版本号
+- 初始化看门狗,并定时喂狗
+- 启动一个循环定时器,每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
+- 加载flash_fs_io模块(通过require "flash_fs_io")
+- 加载http_download_flash模块(通过require "http_download_flash")
+- 最后运行sys.run()。
+
+### 2、内置Flash文件系统演示模块(flash_fs_io.lua)
+
+#### 文件操作
+- 获取文件系统信息( io.fsstat)
+- 创建目录:io.mkdir("/flash_demo")
+- 创建/写入文件: io.open("/flash_demo/boottime", "wb")
+- 检查文件存在: io.exists(file_path)
+- 获取文件大小:io.fileSize(file_path)
+- 读取文件内容: io.open(file_path, "rb"):read("*a")
+- 启动计数文件: 记录设备启动次数
+- 文件追加: io.open(append_file, "a+")
+- 按行读取: file:read("*l")
+- 文件关闭: file:close()
+- 文件重命名: os.rename(old_path, new_path)
+- 列举目录: io.lsdir(dir_path)
+- 删除文件: os.remove(file_path)
+- 删除目录: io.rmdir(dir_path)
+
+### 3、HTTP下载功能 (http_download_flash.lua)
+
+
+#### 网络就绪检测
+
+- 1秒循环等待IP就绪
+- 网络故障处理机制
+
+#### 安全下载
+
+- HTTP下载
+
+#### 结果处理
+
+- 下载状态码解析
+- 自动文件大小验证
+- 获取文件系统信息(fs.fsstat)
+
+## **演示硬件环境**
+
+1、Air780EHM核心板一块(Air780EHM/780EGH/780EHV三种模块的核心板接线方式相同,这里以Air780EHM为例)
+
+2、TYPE-C USB数据线一根
+
+3、SIM卡一张
+
+4、Air780EHM/780EGH/780EHV核心板和数据线的硬件接线方式为
+
+- Air780EHM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## **演示软件环境**
+
+1、Luatools下载调试工具: https://docs.openluat.com/air780epm/common/Luatools/
+
+2、内核固件版本:
+Air780EHM:https://docs.openluat.com/air780epm/luatos/firmware/version/
+Air780EGH:https://docs.openluat.com/air780egh/luatos/firmware/version/
+Air780EHV:https://docs.openluat.com/air780ehv/luatos/firmware/version/
+
+## **演示核心步骤**
+
+1、搭建好硬件环境
+
+2、通过Luatools将demo与固件烧录到开发板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印
+
+```lua
+
+(1)文件操作演示
+[2025-10-22 15:23:25.096][000000000.595] I/user.文件系统操作 ===== 开始文件系统操作 =====
+[2025-10-22 15:23:25.104][000000000.601] I/user. io.fsstat成功: 总空间=192块 已用=20块 块大小=4096字节 类型=lfs
+[2025-10-22 15:23:25.118][000000000.644] I/user.io.mkdir 目录创建成功 路径:/flash_demo
+[2025-10-22 15:23:25.127][000000000.648] I/user.文件创建 文件写入成功 路径:/flash_demo/boottime
+[2025-10-22 15:23:25.145][000000000.651] I/user.io.exists 文件存在 路径:/flash_demo/boottime
+[2025-10-22 15:23:25.157][000000000.654] I/user.io.fileSize 文件大小:59字节 路径:/flash_demo/boottime
+[2025-10-22 15:23:25.165][000000000.657] I/user.文件读取 路径:/flash_demo/boottime 内容:这是内置Flash文件系统API文档示例的测试内容
+[2025-10-22 15:23:25.181][000000000.660] I/user.启动计数 文件内容: 这是内置Flash文件系统API文档示例的测试内容 十六进制: E8BF99E698AFE58685E7BDAE466C617368E69687E4BBB6E7B3BBE7BB9F415049E69687E6A1A3E7A4BAE4BE8BE79A84E6B58BE8AF95E58685E5AEB9 118
+[2025-10-22 15:23:25.189][000000000.660] I/user.启动计数 当前值: 0
+[2025-10-22 15:23:25.211][000000000.660] I/user.启动计数 更新值: 1
+[2025-10-22 15:23:25.217][000000000.663] I/user.文件写入 路径:/flash_demo/boottime 内容: 1
+[2025-10-22 15:23:25.224][000000000.669] I/user.文件创建 路径:/flash_demo/test_a 初始内容:ABC
+[2025-10-22 15:23:25.245][000000000.672] I/user.文件追加 路径:/flash_demo/test_a 追加内容:def
+[2025-10-22 15:23:25.251][000000000.675] I/user.文件验证 路径:/flash_demo/test_a 内容:ABCdef 结果: 成功
+[2025-10-22 15:23:25.267][000000000.678] I/user.文件创建 路径:/flash_demo/testline 写入3行文本
+[2025-10-22 15:23:25.277][000000000.681] I/user.按行读取 路径:/flash_demo/testline 第1行: abc
+[2025-10-22 15:23:25.285][000000000.682] I/user.按行读取 路径:/flash_demo/testline 第2行: 123
+[2025-10-22 15:23:25.295][000000000.682] I/user.按行读取 路径:/flash_demo/testline 第3行: wendal
+[2025-10-22 15:23:25.301][000000000.689] I/user.os.rename 文件重命名成功 原路径:/flash_demo/test_a 新路径:/flash_demo/renamed_file.txt
+[2025-10-22 15:23:25.312][000000000.694] D/vfs fopen /flash_demo/test_a r not found
+[2025-10-22 15:23:25.321][000000000.694] I/user.验证结果 重命名验证成功 新文件存在 原文件不存在
+[2025-10-22 15:23:25.340][000000000.695] I/user.目录操作 ===== 开始目录列举 =====
+[2025-10-22 15:23:25.348][000000000.706] I/user.fs lsdir [{"name":"boottime","size":1,"type":0},{"name":"renamed_file.txt","size":6,"type":0},{"name":"testline","size":15,"type":0}]
+[2025-10-22 15:23:25.359][000000000.710] I/user.os.remove 文件删除成功 路径:/flash_demo/renamed_file.txt
+[2025-10-22 15:23:25.366][000000000.713] D/vfs fopen /flash_demo/renamed_file.txt r not found
+[2025-10-22 15:23:25.373][000000000.713] I/user.验证结果 renamed_file.txt文件删除验证成功
+[2025-10-22 15:23:25.378][000000000.716] I/user.os.remove testline文件删除成功 路径:/flash_demo/testline
+[2025-10-22 15:23:25.390][000000000.719] D/vfs fopen /flash_demo/testline r not found
+[2025-10-22 15:23:25.395][000000000.719] I/user.验证结果 testline文件删除验证成功
+[2025-10-22 15:23:25.415][000000000.723] I/user.os.remove 文件删除成功 路径:/flash_demo/boottime
+[2025-10-22 15:23:25.423][000000000.726] D/vfs fopen /flash_demo/boottime r not found
+[2025-10-22 15:23:25.444][000000000.726] I/user.验证结果 boottime文件删除验证成功
+[2025-10-22 15:23:25.453][000000000.732] I/user.io.rmdir 目录删除成功 路径:/flash_demo
+[2025-10-22 15:23:25.464][000000000.734] I/user.验证结果 目录删除验证成功
+[2025-10-22 15:23:25.477][000000000.740] I/user. io.fsstat 操作后文件系统信息: 总空间=192块 已用=20块 块大小=4096字节 类型=lfs
+[2025-10-22 15:23:25.484][000000000.740] I/user.文件系统操作 ===== 文件系统操作完成 =====
+
+
+
+
+(2)网络连接与HTTP下载
+[2025-10-22 15:34:04.507][000000007.471] I/user.HTTP下载 网络已就绪 1 3
+[2025-10-22 15:34:04.550][000000007.471] I/user.HTTP下载 开始下载任务
+[2025-10-22 15:34:04.579][000000007.478] dns_run 676:gitee.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-10-22 15:34:04.604][000000007.508] D/mobile TIME_SYNC 0
+[2025-10-22 15:34:04.734][000000007.517] dns_run 693:dns all done ,now stop
+[2025-10-22 15:34:06.390][000000009.741] I/user.HTTP下载 下载完成 success 200 
+[2025-10-22 15:34:06.422][000000009.741] {"Age":"0","Cache-Control":"public, max-age=60","Via":"1.1 varnish","Transfer-Encoding":"chunked","Date":"Wed, 22 Oct 2025 07:34:04 GMT","Access-Control-Allow-Credentials":"true","Vary":"Accept-Encoding","X-Served-By":"cache-ffe9","X-Gitee-Server":"http-pilot 1.9.21","Connection":"keep-alive","Server":"ADAS\/1.0.214","Access-Control-Allow-Headers":"Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-CustomHeader,Content-Range,Range,Set-Language","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; sandbox","X-Request-Id":"fa536af1-51bd-400f-8d4b-7322355a9db2","Accept-Ranges":"bytes","Etag":"W\/\"2aaa2788d394a924e258d6f26ad78b8c948950f5\"","Content-Type":"text\/plain; charset=utf-8","Access-Control-Allow-Methods":"GET, POST, PUT, PATCH, DELETE, OPTIONS","X-Frame-Options":"DENY","X-Cache":"MISS","Set-Cookie":"BEC=1f1759df3ccd099821dcf0da6feb0357;Path=\/;Max-Age=126000"}
+[2025-10-22 15:34:06.477][000000009.742]  103070
+[2025-10-22 15:34:06.492][000000009.745] I/user.HTTP下载 文件大小验证 预期: 103070 实际: 103070
+[2025-10-22 15:34:06.525][000000009.751] I/user.HTTP下载 下载后文件系统信息: 总空间=192块 已用=46块 块大小=4096字节 类型=lfs
+
+
+```

+ 8 - 10
module/Air780EHM_Air780EHV_Air780EGH/demo/http/http_app.lua

@@ -176,18 +176,16 @@ local function http_app_get_file()
 
     -- 创建/http_download目录,用来存放通过http下载的文件
     -- 重复创建目录会返回失败
-    -- 在创建目录之前可以使用api判断下目录是否存在
-    -- 不过只有最新版本的内核固件才支持判断目录是否存在的api
-    -- 在编写本demo时还没有这个api
-    -- 如果Luatools烧录软件时,没有勾选 清除FS分区,此处日志有可能输出error
-    -- 如果输出error,不用理会,不会影响后续逻辑的执行
-    -- 等后续的新版本内核固件支持 判断目录是否存在 的api之后,再加上api判断
+    -- 在创建目录之前可以使用io.dexist判断下目录是否存在
+    -- io.dexist接口仅新版本支持(Air780系列需要使用V2014及以上固件)
+    -- 若有报错提示,请检查是否是因为使用了旧版本的内核固件
     local download_dir = "/http_download/"
-    local result, reason = io.mkdir(download_dir)
-    if not result then
-        log.error("http_app_get_file io.mkdir error", reason)
+    if not io.dexist(download_dir) then
+        local result, reason = io.mkdir(download_dir)
+        if not result then
+            log.error("http_app_get_file io.mkdir error", reason)
+        end
     end
-
     
     local file_path = download_dir.."get_file1.html"
     -- https get请求https://www.air32.cn/网页内容

+ 1 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/lbsLoc2/lbsloc2_app.lua

@@ -35,6 +35,7 @@ local function lbsloc2_task_func()
     while true do 
         mobile.reqCellInfo(15)--进行基站扫描,超时时间为15s
         sys.waitUntil("CELL_INFO_UPDATE", 3000)--等到扫描成功,超时时间3S
+        log.info("扫描出的基站信息", json.encode(mobile.getCellInfo())) -- 打印基站信息
         local lat, lng, t = lbsLoc2.request(5000)--仅需要基站定位给出的经纬度
         --local lat, lng, t = lbsLoc2.request(5000,nil,nil,true)--需要经纬度和当前时间
         --(时间格式{"year":2024,"min":56,"month":11,"day":12,"sec":44,"hour":14})

+ 4 - 4
module/Air780EHM_Air780EHV_Air780EGH/demo/luatos_framework/hello_luatos/readme.md

@@ -8,19 +8,19 @@
 
 ## 演示硬件环境
 
-1、Air780EXX核心板一块
+1、Air780EHM/Air780EHV/Air780EGH核心板一块
 
 2、TYPE-C USB数据线一根
 
-3、Air780EXX核心板和数据线的硬件接线方式为
+3、Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为
 
-- Air780EXX核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
 - 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
 
 
 ## 演示软件环境
 
-1、[Luatools下载调试工具](https://docs.openluat.com/air8000/luatos/common/download/)
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
 
 2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
 

+ 4 - 4
module/Air780EHM_Air780EHV_Air780EGH/demo/luatos_framework/luatos_task/readme.md

@@ -16,19 +16,19 @@
 
 ## 演示硬件环境
 
-1、Air780EXX核心板一块
+1、Air780EHM/Air780EHV/Air780EGH核心板一块
 
 2、TYPE-C USB数据线一根
 
-4、Air780EXX核心板和数据线的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为
 
-- Air780EXX核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
 - 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
 
 
 ## 演示软件环境
 
-1、[Luatools下载调试工具](https://docs.openluat.com/air8000/luatos/common/download/)
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
 
 2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
 

+ 74 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/mcu/main.lua

@@ -0,0 +1,74 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.21
+@author  孟伟
+@usage
+本demo演示的功能为:
+    MCU死机时的处理模式设置
+    唯一ID获取与显示
+    系统tick计数功能测试
+    64位tick计数和差值计算
+    微秒、毫秒、秒级别的时间计数
+    16进制字符串转换输出
+]]
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+PROJECT = "mcu_demo"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+-- 加载mcu功能模块
+require "mcu_test"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 64 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/mcu/mcu_test.lua

@@ -0,0 +1,64 @@
+--[[
+@module  mcu_test
+@summary 测试mcu模块功能
+@version 1.0
+@date    2025.10.21
+@author  孟伟
+@usage
+本demo演示的功能为:
+    MCU死机时的处理模式设置
+    唯一ID获取与显示
+    系统tick计数功能测试
+    64位tick计数和差值计算
+    微秒、毫秒、秒级别的时间计数
+    16进制字符串转换输出
+
+    本文件没有对外接口,直接在main.lua中require "mcu_test"就可以加载运行;
+]]
+function mcu_test()
+    -- 测试MCU 死机时的处理模式
+    -- 死机后重启,一般用于正式产品_
+    mcu.hardfault(1)
+
+    -- 测试唯一ID
+    local unique_id = mcu.unique_id()
+    if #unique_id > 0 then
+        log.info("mcu", "Unique ID(hex):", unique_id:toHex())
+    else
+        log.warn("mcu", "Unique ID not supported")
+    end
+
+    -- 测试ticks相关函数
+    -- 获取启动后的 tick 数
+    log.info("mcu", "ticks:", mcu.ticks())
+    -- 获取每秒的 tick 数量
+    log.info("mcu", "获取每秒的tick数量:", mcu.hz())
+
+
+    -- 测试64位tick
+    local tick_str, tick_per = mcu.tick64()
+    log.info("mcu", "tick64:", tick_str:toHex(), "ticks per us:", tick_per)
+    -- 测试mcu.dtick64接口获取ticks差值计算
+    local tick1 = mcu.tick64()
+    sys.wait(100)
+    local tick2 = mcu.tick64()
+    local result, diff_tick = mcu.dtick64(tick1, tick2)
+    log.info("mcu", "dtick64 result:", result, "diff:", diff_tick)
+
+
+    -- 测试ticks2函数
+    local us_h, us_l = mcu.ticks2(0)
+    local ms_h, ms_l = mcu.ticks2(1)
+    local sec_h, sec_l = mcu.ticks2(2)
+    log.info("mcu", "us:", us_h, us_l)
+    log.info("mcu", "ms:", ms_h, ms_l)
+    log.info("mcu", "sec:", sec_h, sec_l)
+
+    -- 测试 转换 10 进制数为 16 进制字符串输出
+    local value = mcu.x32(0x2009FFFC) --输出"0x2009fffc"
+    log.info("mcu", "string", value)
+
+
+end
+
+sys.taskInit(mcu_test)

+ 53 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/mcu/readme.md

@@ -0,0 +1,53 @@
+## 演示模块概述
+
+1、main.lua:主程序入口;
+
+2、mcu_test.lua:MCU功能测试模块;
+
+## 演示功能概述
+
+使用Air780EHM核心板测试MCU相关功能,包括:
+
+- MCU死机时的处理模式设置
+- 唯一ID获取与显示
+- 系统tick计数功能测试
+- 64位tick计数和差值计算
+- 微秒、毫秒、秒级别的时间计数
+- 16进制字符串转换输出
+
+## 演示硬件环境
+
+1、Air780EHM核心板一块
+
+2、TYPE-C USB数据线一根
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2016版本固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、通过luatools工具查看下面日志:
+
+```lua
+[2025-10-21 17:37:39.953][000000000.242] I/user.mcu ticks: 165
+[2025-10-21 17:37:39.962][000000000.242] I/user.mcu 获取每秒的tick数量: 1000
+[2025-10-21 17:37:39.969][000000000.243] I/user.mcu tick64: 625B600000000000 ticks per us: 26
+[2025-10-21 17:37:39.982][000000000.343] I/user.mcu dtick64 result: false diff: -2610447
+[2025-10-21 17:37:40.015][000000000.344] I/user.mcu us: 0 344087
+[2025-10-21 17:37:40.030][000000000.344] I/user.mcu ms: 0 344
+[2025-10-21 17:37:40.037][000000000.344] I/user.mcu sec: 0 0
+[2025-10-21 17:37:40.043][000000000.345] I/user.mcu string 0x2009fffc
+[2025-10-21 17:37:40.048][000000000.345] I/user.us_h 0 us_l 345086
+[2025-10-21 17:37:40.064][000000000.345] I/user.ms_h 0 ms_l 345
+[2025-10-21 17:37:40.085][000000000.345] I/user.sec_h 0 sec_l 0
+
+```

+ 75 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/main.lua

@@ -0,0 +1,75 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.14
+@author  李源龙
+@usage
+本demo演示的核心功能为:
+用Air780EHM核心板利用xmodem协议,将模块内的文件从串口发送到对端
+主要提供了两种方式:
+1、文件存到脚本区里面,通过xmodem协议,把脚本区文件发给对端
+2、通过http下载文件到文件系统区,通过xmodem协议,把文件系统区文件发给对端
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "xmodem_demo"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+-- 加载xmodem_demo模块
+require "xmodem_demo"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 82 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/readme.md

@@ -0,0 +1,82 @@
+
+## 演示功能概述
+本demo演示的核心功能为:
+使用Air780EHM核心板的UART1连接PC端的串口工具,通过xmodem协议接收文件。
+
+## 演示硬件环境
+
+1、Air780EHM核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、USB转串口线数据线一根
+
+4、Air780EHM核心板和数据线的硬件接线方式为
+
+- Air780EHM核心板通过TYPE-C USB口供电;
+
+- 如果测试发现软件频繁重启,重启原因值为:poweron reason 0,可能是供电不足,此时再通过直流稳压电源对核心板的vbat管脚进行4V供电,或者VIN管脚进行5V供电;
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+- USB转串口数据线,一般来说,白线连接核心板的16/U1TX,绿线连接核心板的17/U1RX,黑线连接核心板的gnd,另外一端连接电脑USB口;
+
+| Air780EHM核心板 | USB转串口数据线 |
+| -------------- | -------------- |
+| UART1_TX       | UART_RX        |
+| UART1_RX       | UART_TX       |
+| GND            | GND            |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM最新版本固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、PC端的串口工具,例如SSCOM、LLCOM等都可以;
+
+
+## 演示核心步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、打开串口工具,连接上Air780EHM UATR1端口,Air780EHM等待接收到工具发送过来的字符'C',然后780EPM开始发送数据,工具端接收到数据返回0x06,0x06为xmodem协议的ack值表示正确接收,然后模块返回0x04,0x04为xmodem协议的​EOT​值,表示传输结束,然后对端发送0x06表示确认结束,Luatools的运行日志输出:
+
+发送脚本区的文件,日志内容如下:
+``` lua
+[2025-10-17 15:41:21.708][000000007.048] I/user.xmodem uart读取到数据: 430D0A 6
+[2025-10-17 15:41:21.754][000000007.050] I/user.xmodem 发送第 1 包
+[2025-10-17 15:41:21.762][000000007.107] D/mobile TIME_SYNC 0
+[2025-10-17 15:41:31.029][000000016.785] I/user.xmodem uart读取到数据: 06 2
+[2025-10-17 15:41:31.056][000000016.786] I/user.xmodem 文件到头了
+[2025-10-17 15:41:31.067][000000016.787] I/user.Xmodem start
+[2025-10-17 15:41:31.070][000000016.787] I/user.Xmodem send result true
+[2025-10-17 15:41:31.073][000000016.787] I/user.Xmodem send success
+
+```
+
+HTTP下载到文件系统区的,再通过xmodem协议发送日志如下:
+``` lua
+[2025-10-17 15:59:02.210][000000007.027] dns_run 676:airtest.openluat.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-10-17 15:59:02.216][000000007.050] D/mobile TIME_SYNC 0
+[2025-10-17 15:59:02.231][000000007.070] dns_run 693:dns all done ,now stop
+[2025-10-17 15:59:02.249][000000007.183] I/user.http success 200
+[2025-10-17 15:59:02.261][000000007.183] I/user.HTTP receive ok 29
+[2025-10-17 15:59:02.270][000000007.187] I/user.文件读取 路径:/send.bin 内容:AA BB CC DD 01 02 03 04 05 06
+[2025-10-17 15:59:02.277][000000007.190] Uart_ChangeBR 1338:uart1, 115200 115203 26000000 3611
+[2025-10-17 15:59:03.762][000000008.800] I/user.xmodem uart读取到数据: 430D0A 6
+[2025-10-17 15:59:03.796][000000008.802] I/user.xmodem 发送第 1 包
+[2025-10-17 15:59:09.955][000000014.991] I/user.xmodem uart读取到数据: 06 2
+[2025-10-17 15:59:09.980][000000014.992] I/user.xmodem 文件到头了
+[2025-10-17 15:59:09.990][000000014.993] I/user.Xmodem start
+[2025-10-17 15:59:09.995][000000014.993] I/user.Xmodem send result true
+[2025-10-17 15:59:10.006][000000014.993] I/user.Xmodem send success
+
+
+```

+ 1 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/send.bin

@@ -0,0 +1 @@
+AA BB CC DD 01 02 03 04 05 06

+ 93 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/xmodem/xmodem_demo.lua

@@ -0,0 +1,93 @@
+--[[
+@module  main
+@summary xmodem 发送文件应用功能模块 
+@version 1.0
+@date    2025.07.01
+@author  李源龙
+@usage
+本文件为Air780EPM整机开发板演示xmodem功能的代码示例,本文档主要提供了两种方案:
+1、把模块脚本区的文件利用xmodem协议通过uart发送到过去
+
+2、进行http下载文件,利用xmodem协议通过uart发送到过去
+]]
+
+--加载xmodem模块
+xmodem=require ("xmodem")
+
+--设置默认filepath为脚本区的send.bin文件
+local filepath="/luadb/send.bin"
+
+local taskName = "xmodem_run"
+
+
+local uart_id = 1    --串口号
+
+local baudrate = 115200  --波特率
+
+local file_path=filepath --文件路径
+
+local send_type=true  --true表示单次发送128字节,false表示单次发送1024字节
+
+local inform_data="wait C"    --发送前提示信息,告知对方要发送C字符来接收文件
+
+-- 处理未识别的消息
+local function xmodem_run_cb(msg)
+	log.info("xmodem_run_cb", msg[1], msg[2], msg[3], msg[4])
+end
+
+--http获取文件函数
+local  function http_recived_cb()
+    while not socket.adapter(socket.dft()) do
+        log.warn("httpplus_app_task_func", "wait IP_READY", socket.dft())
+        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
+        -- 或者等待1秒超时退出阻塞等待状态;
+        -- 注意:此处的1000毫秒超时不要修改的更长;
+        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
+        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
+        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
+        sys.waitUntil("IP_READY", 1000)
+    end
+    local path = "/send.bin"
+    -- 以下链接仅用于测试,禁止用于生产环境
+    local code, headers, body_size = http.request("GET", "http://airtest.openluat.com:2900/download/send.bin", nil, nil, {dst=path}).wait()
+    log.info("http", code==200 and "success" or "error", code)
+    if code==200 then
+       log.info("HTTP receive ok",body_size)
+       file = io.open(path, "rb")
+        if file then
+            content = file:read("*a")
+            log.info("文件读取", "路径:" .. path, "内容:" .. content)
+            file:close()
+        else
+            log.error("文件操作", "无法打开文件读取内容", "路径:" .. path)
+        end
+        file_path=path
+    end
+    
+end
+
+--  定义一个xmodem_run函数,用于用xmodem发送文件
+local function xmodem_run() 
+    --如果需要http下载文件,然后发送下载的文件,可以打开下面的http_recived_cb()函数
+    http_recived_cb()
+    
+    --启动xmodem发送
+    local result=xmodem.send(uart_id,baudrate,file_path,send_type)
+    --等待时间12秒,等待接收方发送C字符启动发送,发送结束后接收端发送ACK:0x06表示接收完成,文件全部传输完成之后模块发送EOT​:0x04表示传输结束,接收端返回0x06表示确认结束
+    log.info("Xmodem", "start")
+
+    log.info("Xmodem", "send result", result)
+    --判断是否传输成功,传输是否成功,都需要关闭xmodem
+    if result then
+        log.info("Xmodem", "send success")
+        xmodem.close(uart_id)
+    else
+        log.info("Xmodem", "send failed")
+        xmodem.close(uart_id)
+    end
+
+end
+
+--创建并且启动一个task
+--运行这个task的主函数xmodem_run
+sys.taskInit(xmodem_run, taskName,xmodem_run_cb)

+ 13 - 21
module/Air8000/project/sms_forward/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/xxtea/main.lua

@@ -1,13 +1,12 @@
 --[[
 @module  main
 @summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
-@version 001.000.000
-@date    2025.9.10
-@author  马亚丹
+@version 1.0
+@date    2025.09.25
+@author  李源龙
 @usage
-1. 详细逻辑请看cc_forward文件和sms_forward文件
-2. netdrv_multiple:设置WIFI STA网卡和4G网卡优先级
-
+本demo演示的核心功能为:
+使用xxtea加密算法,对数据进行加密和解密
 ]]
 
 
@@ -21,10 +20,10 @@ VERSION:项目版本号,ascii string类型
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
 ]]
-PROJECT = "cc_sms_forward"
+PROJECT = "xxtea"
 VERSION = "001.000.000"
 
-require "sys"
+
 -- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
@@ -38,6 +37,7 @@ if wdt then
     sys.timerLoopStart(wdt.feed, 3000)
 end
 
+
 -- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
 -- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
 -- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
@@ -61,18 +61,10 @@ end
 --     log.info("mem.sys", rtos.meminfo("sys"))
 -- end, 3000)
 
+--加载xxtea_demo模块
+require "xxtea_demo"
 
---加载cc_forward功能模块
-require "cc_forward"
-
---加载sms_forward功能模块
-require "sms_forward"
-
---加载网络驱动设备功能模块netdrv_multiple
-require "netdrv_multiple"
-
-
-
-
--- 启动系统调度(必须放在最后)
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
 sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

Некоторые файлы не были показаны из-за большого количества измененных файлов