Sfoglia il codice sorgente

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

alienwalker 5 mesi fa
parent
commit
8cd2927c2f
100 ha cambiato i file con 4095 aggiunte e 608 eliminazioni
  1. 11 1
      components/airlink/src/task/luat_airlink_spi_master_task.c
  2. 7 7
      components/airtalk/binding/luat_lib_airtalk.c
  3. 0 2
      components/coremark/luat_lib_coremark.c
  4. 19 0
      components/crypto/luat_crc.c
  5. 16 0
      components/crypto/luat_crypto_mbedtls.c
  6. 4 1
      components/eink/luat_lib_eink.c
  7. 13 13
      components/io_queue/luat_lib_io_queue.c
  8. 24 16
      components/lcd/luat_lib_lcd.c
  9. 1 1
      components/little_flash/luat_lib_little_flash.c
  10. 12 10
      components/mbedtls/library/ssl_ciphersuites.c
  11. 1 1
      components/mlx90640-library/luat_lib_mlx90640.c
  12. 1 1
      components/network/adapter/luat_lib_socket.c
  13. 12 4
      components/network/httpsrv/src/luat_httpsrv_lwip.c
  14. 10 16
      components/network/libftp/luat_ftp_client.c
  15. 1 1
      components/network/libftp/luat_lib_ftp.c
  16. 21 1
      components/network/netdrv/binding/luat_lib_netdrv.c
  17. 4 2
      components/network/netdrv/src/luat_netdrv.c
  18. 1 1
      components/network/ulwip/binding/luat_lib_napt.c
  19. 3 3
      components/network/ulwip/binding/luat_lib_ulwip.c
  20. 1 1
      components/network/ulwip/include/luat_ulwip.h
  21. 27 23
      components/network/ulwip/src/ulwip_dhcp_client.c
  22. 4 4
      components/onewire/binding/luat_lib_onewire.c
  23. 4 0
      components/u8g2/luat_lib_u8g2.c
  24. 2 2
      components/u8g2/u8g2_font.c
  25. 1 1
      lua/src/lauxlib.c
  26. 3 3
      lua/src/loslib.c
  27. 17 0
      luat/demo/crypto/main.lua
  28. 5 0
      luat/include/luat_crypto.h
  29. 1 1
      luat/modules/luat_lib_adc.c
  30. 1 1
      luat/modules/luat_lib_can.c
  31. 30 58
      luat/modules/luat_lib_crypto.c
  32. 4 3
      luat/modules/luat_lib_rtc.c
  33. 0 1
      module/Air780E/demo/README.md
  34. 1 1
      module/Air780E/demo/protobuf/main.lua
  35. 1 1
      module/Air780E/demo/u8g2/main.lua
  36. 1 1
      module/Air780E/demo/xxtea/main.lua
  37. 141 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/http_app.lua
  38. 80 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/main.lua
  39. 90 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_eth_spi.lua
  40. 79 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_multiple.lua
  41. 25 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/netdrv_device.lua
  42. 75 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/readme.md
  43. 14 12
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/main.lua
  44. 48 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/netif_app.lua
  45. 60 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/readme.md
  46. 11 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/readme.md
  47. 459 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/AirGPIO_1000.lua
  48. 96 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/gpio_app.lua
  49. 6 8
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/main.lua
  50. 120 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/readme.md
  51. 119 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua
  52. 5 7
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/main.lua
  53. 57 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/readme.md
  54. 34 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/sht30_app.lua
  55. 63 36
      module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/extalk.lua
  56. 46 201
      module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/main.lua
  57. 74 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/readme.md
  58. 205 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/talk.lua
  59. 71 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in/main.lua
  60. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in/netif_app.lua
  61. 3 7
      module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in/readme.md
  62. 0 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/protobuf/main.lua
  63. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/u8g2/main.lua
  64. 141 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/http/http_app.lua
  65. 80 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/http/main.lua
  66. 90 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_eth_spi.lua
  67. 79 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_multiple.lua
  68. 27 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/http/netdrv_device.lua
  69. 75 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/http/readme.md
  70. 71 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/main.lua
  71. 48 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/netif_app.lua
  72. 60 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/readme.md
  73. 11 0
      module/Air780EPM/demo/accessory_board/AirETH_1000/readme.md
  74. 1 1
      module/Air780EPM/demo/accessory_board/AirSHT30_1000/main.lua
  75. 0 1
      module/Air780EPM/demo/protobuf/main.lua
  76. 1 1
      module/Air780EPM/demo/u8g2/main.lua
  77. 154 0
      module/Air8000/demo/Aircloud/excloud_test.lua
  78. 65 0
      module/Air8000/demo/Aircloud/main.lua
  79. 33 0
      module/Air8000/demo/Aircloud/netdrv/netdrv_4g.lua
  80. 85 0
      module/Air8000/demo/Aircloud/netdrv/netdrv_eth_spi.lua
  81. 95 0
      module/Air8000/demo/Aircloud/netdrv/netdrv_multiple.lua
  82. 50 0
      module/Air8000/demo/Aircloud/netdrv/netdrv_wifi.lua
  83. 33 0
      module/Air8000/demo/Aircloud/netdrv_device.lua
  84. 78 0
      module/Air8000/demo/accessory_board/AirETH_1000/http/main.lua
  85. 24 20
      module/Air8000/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_eth_spi.lua
  86. 2 2
      module/Air8000/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_multiple.lua
  87. 4 6
      module/Air8000/demo/accessory_board/AirETH_1000/http/netdrv_device.lua
  88. 81 0
      module/Air8000/demo/accessory_board/AirETH_1000/http/readme.md
  89. 0 0
      module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/main.lua
  90. 13 28
      module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/netif_app.lua
  91. 64 0
      module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/readme.md
  92. 69 0
      module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/main.lua
  93. 14 11
      module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/netif_app.lua
  94. 64 0
      module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/readme.md
  95. 8 72
      module/Air8000/demo/accessory_board/AirETH_1000/readme.md
  96. 114 0
      module/Air8000/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua
  97. 19 9
      module/Air8000/demo/accessory_board/AirSHT30_1000/main.lua
  98. 57 0
      module/Air8000/demo/accessory_board/AirSHT30_1000/readme.md
  99. 35 0
      module/Air8000/demo/accessory_board/AirSHT30_1000/sht30_app.lua
  100. 4 2
      module/Air8000/demo/accessory_board/AirSPINAND_1000/AirSPINAND_1000.lua

+ 11 - 1
components/airlink/src/task/luat_airlink_spi_master_task.c

@@ -217,7 +217,12 @@ __AIRLINK_CODE_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t
     // 拉低片选, 准备发送数据
     luat_gpio_set(AIRLINK_SPI_CS_PIN, 0);
     // 发送数据
+    #ifdef TYPE_EC718M
+    extern void SPI_FastTransfer(uint8_t SpiID, const uint8_t *TxData, uint8_t *RxData, uint32_t Len);
+    SPI_FastTransfer(MASTER_SPI_ID, txbuff, rxbuff, TEST_BUFF_SIZE);
+    #else
     luat_spi_transfer(MASTER_SPI_ID, (const char *)txbuff, TEST_BUFF_SIZE, (char *)rxbuff, TEST_BUFF_SIZE);
+    #endif
     // 拉高片选, 结束发送
     luat_gpio_set(AIRLINK_SPI_CS_PIN, 1);
     //luat_spi_unlock(MASTER_SPI_ID);
@@ -463,7 +468,12 @@ void luat_airlink_start_master(void)
     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);
+    #ifdef TYPE_EC718M
+    #define AIRLINK_TASK_PRIORITY 150
+    #else
+    #define AIRLINK_TASK_PRIORITY 80
+    #endif
+    luat_rtos_task_create(&spi_task_handle, 8 * 1024, AIRLINK_TASK_PRIORITY, "spi", spi_master_task, NULL, 0);
 }
 
 int luat_airlink_irqmode(luat_airlink_irq_ctx_t *ctx) {

+ 7 - 7
components/airtalk/binding/luat_lib_airtalk.c

@@ -58,7 +58,7 @@ static int l_airtalk_handler(lua_State *L, void* ptr) {
 @int 单次解码帧数,如果缓冲没有足够的帧数,自动补0,默认值5,不能低于2,不能高于10,不能低于encode_cnt, decode_cnt * 4 必须是 encode_cnt的整数倍
 @int 对讲停止后,audio的pm状态,默认是audio.SHUTDOWN
 @int 多长时间判定对端长时间无数据发送,超过这个时间会上报event_error,用户决定接下来的操作。默认5000ms,单位ms
-@return nil
+@return nil 无返回值
 @usage
 mqttc = mqtt.create(nil,"120.55.137.106", 1884)
 airtalk.config(airtalk.PROTOCOL_MQTT, mqttc)
@@ -131,7 +131,7 @@ static int l_airtalk_on(lua_State *L) {
 /*
 airtalk启动
 @api airtalk.start()
-@return nil
+@return nil 无返回值
 @usage
 mqttc = mqtt.create(nil,"120.55.137.106", 1884)
 airtalk.config(airtalk.PROTOCOL_MQTT, mqttc)
@@ -150,7 +150,7 @@ static int l_airtalk_start(lua_State *L)
 配置airtalk RTP协议中的SSRC
 @api airtalk.set_ssrc(ssrc)
 @int/string ssrc,可以是int也是可以8字节string
-@return nil
+@return nil 无返回值
 @usage
 
 */
@@ -175,7 +175,7 @@ static int l_airtalk_set_ssrc(lua_State *L)
 配置airtalk mqtt类型语音数据的专用topic
 @api airtalk.set_topic(topic)
 @string topic
-@return nil
+@return nil 无返回值
 @usage
 airtalk.set_topic("xxxxxxxxxx")
 */
@@ -193,7 +193,7 @@ airtalk对讲工作启动/停止
 @boolean 启停控制,true开始,false停止
 @int 工作模式,见airtalk.MODE_XXX
 @int 音频采样率,目前只有8000和16000,默认16000
-@return nil
+@return nil 无返回值
 @usage
 --1对1对讲开始
 airtalk.speech(true,airtalk.MODE_PERSON,16000)
@@ -245,7 +245,7 @@ static int l_airtalk_speech(lua_State *L)
 //airtalk上行控制
 //@api airtalk.uplink(on_off)
 //@boolean  录音上行控制,true开始,false停止
-//@return nil
+//@return nil 无返回值
 //@usage
 //--开始录音
 //airtalk.uplink(true)
@@ -262,7 +262,7 @@ static int l_airtalk_speech(lua_State *L)
 airtalk的详细调试信息开关
 @api airtalk.debug(on_off)
 @boolean 调试信息开关,true打开,false关闭
-@return nil
+@return nil 无返回值
 */
 static int l_airtalk_debug(lua_State *L)
 {

+ 0 - 2
components/coremark/luat_lib_coremark.c

@@ -30,8 +30,6 @@ int ee_printf(const char *fmt, ...) {
 @return nil 无返回值,结果直接打印在日志中
 @usage
 -- 大部分情况下, 这个库都不会包含在正式版固件里
--- 若需使用,可以参考wiki文档自行编译或使用云编译
--- https://wiki.luatos.com/develop/compile.html
 
 -- 跑分的main.lua 应移除硬狗代码, 防止重启
 -- 若设备支持自动休眠, 应关闭休眠功能

+ 19 - 0
components/crypto/luat_crc.c

@@ -279,3 +279,22 @@ __USER_FUNC_IN_RAM__ uint16_t luat_crc16_modbus( const uint8_t *buf, uint32_t le
 
 	return crc;
 }
+
+// CRC7算法
+uint8_t luat_crc7(const uint8_t* message, int length, uint8_t CRCPoly, uint8_t CRC)
+{
+    // unsigned char CRCPoly = 0xe5;
+    unsigned char CRCTable[256];
+    // unsigned char CRC = 0x00;
+    for (int i = 0; i < 256; i++){
+        CRCTable[i] = (i & 0x80) ? i ^ CRCPoly : i;
+        for (int j = 1; j < 8; j++){
+            CRCTable[i] <<= 1;
+            if (CRCTable[i] & 0x80)
+                CRCTable[i] ^= CRCPoly;
+        }
+    }
+    for (int i = 0; i < length; i++)
+        CRC = CRCTable[(CRC << 1) ^ message[i]];
+    return CRC<< 1;
+}

+ 16 - 0
components/crypto/luat_crypto_mbedtls.c

@@ -199,6 +199,7 @@ _error_exit:
 int luat_crypto_md(const char* md, const char* str, size_t str_size, void* out_ptr, const char* key, size_t key_len) {
     const mbedtls_md_info_t * info = mbedtls_md_info_from_string(md);
     if (info == NULL) {
+        LLOGW("no such message digest %s", md);
         return -1;
     }
     if (key_len < 1) {
@@ -210,6 +211,21 @@ int luat_crypto_md(const char* md, const char* str, size_t str_size, void* out_p
     return 0;
 }
 
+int luat_crypto_md_v2(const char* md, const char* str, size_t str_size, void* out_ptr, const char* key, size_t key_len) {
+    const mbedtls_md_info_t * info = mbedtls_md_info_from_string(md);
+    if (info == NULL) {
+        LLOGW("no such message digest %s", md);
+        return -1;
+    }
+    if (key_len < 1) {
+        mbedtls_md(info, (const unsigned char*)str, str_size, (unsigned char*)out_ptr);
+    }
+    else {
+        mbedtls_md_hmac(info, (const unsigned char*)key, key_len, (const unsigned char*)str, str_size, (unsigned char*)out_ptr);
+    }
+    return mbedtls_md_get_size(info);
+}
+
 int luat_crypto_md_file(const char* md, void* out_ptr, const char* key, size_t key_len, const char* path) {
     const mbedtls_md_info_t * info = mbedtls_md_info_from_string(md);
     if (info == NULL) {

+ 4 - 1
components/eink/luat_lib_eink.c

@@ -581,8 +581,11 @@ static int l_eink_set_font(lua_State *L) {
 @int 颜色, 可以是0或者1, 默认是0
 @return nil 无返回值
 @usage
+-- 注意, 本库只支持普通SPI, 不支持LCD专用SPI
+-- 请确保已经正确连接了屏幕, 连接方式请参考demo
+
 -- 先设置字体, 然后写字
--- 可用字体取决于具体的固件, 如果没有你想要的大小,可以云编译一份自定义固件
+-- 可用字体取决于具体的固件, 如果没有你想要的大小, 请联系销售人员定制
 -- font_opposansm8_chinese
 -- font_opposansm10_chinese
 -- font_opposansm12_chinese

+ 13 - 13
components/io_queue/luat_lib_io_queue.c

@@ -48,7 +48,7 @@ int l_io_queue_capture_handler(lua_State *L, void* ptr)
 @int  硬件定时器id,默认用0,根据实际MCU确定,air105为0~5,与pwm共用,同一个通道号不能同时为pwm和ioqueue
 @int  一个完整周期需要的命令,可以比实际的多
 @int  重复次数,默认是1,如果写0则表示无限次数循环
-@return 无
+@return nil 返回值
 @usage
 ioqueue.init(0,10,5) --以timer0为时钟源初始化一个io操作队列,有10个有效命令,循环5次
 */
@@ -68,7 +68,7 @@ static int l_io_queue_init(lua_State *L) {
 @int  延时微调时间,0~255tick,总的延时时间是time_us * 1us_tick + time_tick
 @boolean 是否连续是连续延时,默认否,如果是,定时器在时间到后不会停止而是重新计时,
 从而实现在下一个setdelay命令前,每次调用delay都会重复相同时间延时,提高连续定时的精度
-@return 无
+@return nil 返回值
 @usage
 ioqueue.setdelay(0,10,0) --延时10us+0个tick
 ioqueue.setdelay(0,9,15,true) --延时9us+15个tick,在之后遇到delay命令时,会延时9us+15个tick
@@ -90,7 +90,7 @@ static int l_io_queue_set_delay(lua_State *L) {
 对io操作队列增加一次重复延时,在前面必须有setdelay且是连续延时
 @api  ioqueue.delay(hwtimer_id)
 @int  硬件定时器id
-@return 无
+@return nil 返回值
 @usage
 ioqueue.setdelay(0,9,15,true) --延时9us+15个tick,在之后遇到delay命令时,会延时9us+15个tick
 ioqueue.delay(0)
@@ -111,7 +111,7 @@ static int l_io_queue_delay(lua_State *L) {
 @boolean  是否是输入
 @int 上下拉模式,只能是0,gpio.PULLUP,gpio.PULLDOWN
 @int 初始输出电平
-@return 无
+@return nil 返回值
 @usage
 ioqueue.setgpio(0,17,true,gpio.PULLUP,0) --GPIO17设置成上拉输入
 ioqueue.setgpio(0,17,false,0,1)--GPIO17设置成默认上下拉输出高电平
@@ -136,7 +136,7 @@ static int l_io_queue_set_gpio(lua_State *L) {
 @api  ioqueue.input(hwtimer_id,pin)
 @int  硬件定时器id
 @int pin
-@return 无
+@return nil 返回值
 @usage
 --- 对GPIO17进行输入读取
 ioqueue.input(0, 17)
@@ -155,7 +155,7 @@ static int l_io_queue_gpio_input(lua_State *L) {
 @int  硬件定时器id
 @int pin
 @int 输出电平
-@return 无
+@return nil 返回值
 @usage
 -- 对GPIO17输出低电平
 ioqueue.output(0, 17, 0)
@@ -176,7 +176,7 @@ static int l_io_queue_gpio_output(lua_State *L) {
 @int 上下拉模式,只能是0,gpio.PULLUP,gpio.PULLDOWN
 @int 中断模式,只能是gpio.BOTH,gpio.RISING,gpio.FALLING
 @int 定时器最大计时时间 考虑到lua是int类型,最小0x10000, 最大值为0x7fffffff,默认为最大值
-@return 无
+@return nil 返回值
 @usage
 -- 捕获指令
 ioqueue.setcap(0, 17, gpio.PULLUP, gpio.FALLING, 48000000)
@@ -199,7 +199,7 @@ static int l_io_queue_set_capture(lua_State *L) {
 对io操作队列增加捕获一次IO状态命令
 @api  ioqueue.capture(hwtimer_id)
 @int  硬件定时器id
-@return 无
+@return nil 返回值
 @usage
 ioqueue.capture(0)
 */
@@ -214,7 +214,7 @@ static int l_io_queue_capture_pin(lua_State *L) {
 @api  ioqueue.capend(hwtimer_id,pin)
 @int  硬件定时器id
 @int  pin
-@return 无
+@return nil 返回值
 @usage
 -- 结束捕获
 ioqueue.capend(0, 17)
@@ -257,7 +257,7 @@ static int l_io_queue_get(lua_State *L) {
 启动io操作队列
 @api  ioqueue.start(hwtimer_id)
 @int  硬件定时器id
-@return 无
+@return nil 返回值
 @usage
 ioqueue.start(0)
 */
@@ -291,7 +291,7 @@ static int l_io_queue_stop(lua_State *L) {
 释放io操作队列的资源,下次使用必须重新init
 @api  ioqueue.release(hwtimer_id)
 @int  硬件定时器id
-@return 无
+@return nil 返回值
 @usage
 ioqueue.clear(0)
 */
@@ -305,7 +305,7 @@ static int l_io_queue_release(lua_State *L) {
 清空io操作队列
 @api  ioqueue.clear(hwtimer_id)
 @int  硬件定时器id
-@return 无
+@return nil 返回值
 @usage
 ioqueue.clear(0)
 */
@@ -336,7 +336,7 @@ static int l_io_queue_is_done(lua_State *L) {
 @int 上下拉模式,只能是0,gpio.PULLUP,gpio.PULLDOWN
 @int 中断模式,只能是gpio.BOTH,gpio.RISING,gpio.FALLING
 @boolean  开关,默认是false关
-@return 无
+@return nil 返回值
 @usage
 -- 对GPIO17进行外部中断捕获
 ioqueue.exti(17, gpio.PULLUP, gpio.BOTH, true)

+ 24 - 16
components/lcd/luat_lib_lcd.c

@@ -552,7 +552,7 @@ lcd命令
 @int lcd命令模式下的命令值
 @int/zbuff lcd命令模式下的参数值,如果只有1个参数,可以用int,如果有多个,使用zbuff传入
 @int 参数长度,如果上一个参数是int,则忽略长度
-@return boolean
+@return boolean 成功与否
 @usage
 -- lcd命令
 lcd.cmd(0x21)
@@ -1079,8 +1079,7 @@ extern void luat_u8g2_set_ascii_indentation(uint8_t value);
 @usage
 -- 设置为字体,对之后的drawStr有效,调用lcd.drawStr前一定要先设置
 
--- 若提示 "only font pointer is allow" , 则代表当前固件不含对应字体, 可使用云编译服务免费定制
--- 云编译文档: https://wiki.luatos.com/develop/compile/Cloud_compilation.html
+-- 若提示 "only font pointer is allow" , 则代表当前固件不含对应字体
 
 -- lcd库的默认字体均以 lcd.font_ 开头
 lcd.setFont(lcd.font_opposansm12)
@@ -1116,28 +1115,37 @@ static int l_lcd_set_font(lua_State *L) {
 }
 
 /*
-设置使用文件系统中的字体文件
-@api lcd.setFontfile(font, indentation)
+设置使用文件系统中的字体文件 
+@api lcd.setFontFile(font, indentation)
 @string filename 字体文件
 @int indentation, 等宽字体ascii右侧缩进0~127个pixel,等宽字体的ascii字符可能在右侧有大片空白,用户可以选择删除部分。留空或者超过127则直接删除右半边, 非等宽字体无效
+@return boolean 成功返回true, 失败返回nil
 @usage
+-- 字体文件制作工具: https://gitee.com/Dozingfiretruck/u8g2_font_tool
 -- 设置为字体,对之后的drawStr有效,调用lcd.drawStr前一定要先设置
 
--- 若提示 "only font pointer is allow" , 则代表当前固件不含对应字体, 可使用云编译服务免费定制
--- 云编译文档: https://wiki.luatos.com/develop/compile/Cloud_compilation.html
-
-lcd.setFontfile("/sd/u8g2_font_opposansm12.bin")
+lcd.setFontFile("/sd/u8g2_font_opposansm12.bin")
 lcd.drawStr(40,10,"drawStr")
-sys.wait(2000)
 */
 static int l_lcd_set_fontfile(lua_State *L) {
     if (lcd_dft_conf == NULL) {
         LLOGE("lcd not init");
         return 0;
     }
-    size_t sz;
+    if (lcd_dft_conf->luat_lcd_u8g2.font_file) {
+        luat_fs_fclose(lcd_dft_conf->luat_lcd_u8g2.font_file);
+        lcd_dft_conf->luat_lcd_u8g2.font_file = NULL;
+    }
+    size_t sz = 0;
+    if (lua_isnil(L, 1)) {
+        return 0;
+    }
     const uint8_t* font_filename = (const uint8_t*)luaL_checklstring(L, 1, &sz);
-    lcd_dft_conf->luat_lcd_u8g2.font_file = luat_fs_fopen(font_filename, "rb");
+    lcd_dft_conf->luat_lcd_u8g2.font_file = luat_fs_fopen((const char*)font_filename, "rb");
+    if (lcd_dft_conf->luat_lcd_u8g2.font_file == NULL) {
+        LLOGE("open font file fail %s", font_filename);
+        return 0;
+    }
     luat_u8g2_set_ascii_indentation(0xff);
     u8g2_SetFont(&(lcd_dft_conf->luat_lcd_u8g2), NULL);
     if (lua_isinteger(L, 2)) {
@@ -1913,7 +1921,7 @@ static const int l_lcd_draw_utf8(lua_State *L) {
 @int 帧同步时的地址值,只有无ram的屏幕需要,如果能用0x2c发送数据则不需要这个参数
 @int 行同步时的指令,一般情况和命令模式下的指令一致,只有无ram的屏幕需要,如果能用0x2c发送数据则不需要这个参数
 @int 行同步时的地址值,只有无ram的屏幕需要,如果能用0x2c发送数据则不需要这个参数
-@return nil
+@return nil 无返回值
 @usage
 -- sh8601z驱动ic所需的qspi配置
 lcd.qspi(0x02, 0x32, 0x12)
@@ -1937,7 +1945,7 @@ static int l_lcd_qspi_config(lua_State* L){
 /*
 用户使用脚本初始化LCD完成后,必须调用本API
 @api lcd.user_done()
-@return nil
+@return nil 无返回值
 */
 static int l_lcd_user_ctrl_done(lua_State* L){
 	lcd_dft_conf->is_init_done = 1;
@@ -1954,7 +1962,7 @@ static int l_lcd_user_ctrl_done(lua_State* L){
 @api lcd.setAcchw(type,enable)
 @number type 支持的类型, 可选,默认全部类型 目前支持 lcd.ACC_HW_JPEG lcd.ACC_HW_ALL
 @bool enable 开关, 可选 默认关闭 ture开启 false关闭
-@return nil
+@return nil 无返回值
 @usage
     lcd.setAcchw(lcd.ACC_HW_JPEG,false) -- 关闭硬件加速的jpeg解码功能
     lcd.setAcchw(lcd.ACC_HW_ALL,false) -- 关闭所有硬件加速
@@ -1999,7 +2007,7 @@ static const rotable_Reg_t reg_lcd[] =
     { "setupBuff",  ROREG_FUNC(l_lcd_setup_buff)},
     { "autoFlush",  ROREG_FUNC(l_lcd_auto_flush)},
     { "setFont",    ROREG_FUNC(l_lcd_set_font)},
-    { "setFontfile",    ROREG_FUNC(l_lcd_set_fontfile)},
+    { "setFontFile",ROREG_FUNC(l_lcd_set_fontfile)},
     { "setDefault", ROREG_FUNC(l_lcd_set_default)},
     { "getDefault", ROREG_FUNC(l_lcd_get_default)},
     { "getSize",    ROREG_FUNC(l_lcd_get_size)},

+ 1 - 1
components/little_flash/luat_lib_little_flash.c

@@ -37,7 +37,7 @@ static int luat_little_flash_init(lua_State *L){
         LLOGW("little_flash init spi_device is nil");
         return 0;
     }
-    little_flash_init();
+    // little_flash_init();
     lf_err_t re = little_flash_device_init(lf_flash);
     if (re == LF_ERR_OK){
         lua_pushlightuserdata(L, lf_flash);

+ 12 - 10
components/mbedtls/library/ssl_ciphersuites.c

@@ -52,6 +52,18 @@ static const int ciphersuite_preference[] =
 #if defined(MBEDTLS_SSL_CIPHERSUITES)
     MBEDTLS_SSL_CIPHERSUITES,
 #else
+    // 把RSA的套件放前面, 优先选择RSA  
+    MBEDTLS_TLS_RSA_WITH_AES_128_CCM,
+    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA,
+    MBEDTLS_TLS_RSA_WITH_AES_128_CCM_8,
+    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,
+    MBEDTLS_TLS_RSA_WITH_AES_256_CCM,
+    MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA256,
+    MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA,
+    MBEDTLS_TLS_RSA_WITH_AES_256_CCM_8,
+    MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,
+    MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384,
+  
     /* Chacha-Poly ephemeral suites */
     MBEDTLS_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
     MBEDTLS_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
@@ -156,17 +168,12 @@ static const int ciphersuite_preference[] =
     MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8,
 
     /* All AES-256 suites */
-    MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384,
-    MBEDTLS_TLS_RSA_WITH_AES_256_CCM,
-    MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA256,
-    MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA,
     MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
     MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
     MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
     MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
     MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
     MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
-    MBEDTLS_TLS_RSA_WITH_AES_256_CCM_8,
 
     /* All CAMELLIA-256 suites */
     MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384,
@@ -186,17 +193,12 @@ static const int ciphersuite_preference[] =
     MBEDTLS_TLS_RSA_WITH_ARIA_256_CBC_SHA384,
 
     /* All AES-128 suites */
-    MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,
-    MBEDTLS_TLS_RSA_WITH_AES_128_CCM,
-    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,
-    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA,
     MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
     MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
     MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
     MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
     MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
     MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
-    MBEDTLS_TLS_RSA_WITH_AES_128_CCM_8,
 
     /* All CAMELLIA-128 suites */
     MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256,

+ 1 - 1
components/mlx90640-library/luat_lib_mlx90640.c

@@ -331,7 +331,7 @@ static int l_mlx90640_average_temp(lua_State *L) {
 /*
 获取vdd
 @api mlx90640.get_vdd()
-@return number vdd
+@return number 当前vdd
 */
 static int l_mlx90640_get_vdd(lua_State *L) {
     lua_pushnumber(L, vdd);

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

@@ -801,7 +801,7 @@ static int l_socket_rx(lua_State *L)
 }
 
 /*
-读取数据(非zbuff版本)
+读取数据(非zbuff版本,已废弃)
 @api socket.read(netc, len)
 @userdata socket.create得到的ctrl
 @int        限制读取数据长度,可选,不传就是读出全部

+ 12 - 4
components/network/httpsrv/src/luat_httpsrv_lwip.c

@@ -444,8 +444,12 @@ static void srv_stop_cb(void* arg) {
         return;
     }
     if (ctx->pcb) {
-        tcp_abort(ctx->pcb);
-        tcp_close(ctx->pcb);
+        tcp_recv(ctx->pcb, NULL);
+        tcp_sent(ctx->pcb, NULL);
+        if(tcp_close(ctx->pcb))
+        {
+            tcp_abort(ctx->pcb);
+        }
         ctx->pcb = NULL;
     }
     luat_httpsrv_free(ctx);
@@ -566,8 +570,12 @@ static int client_send_static_file(client_socket_ctx_t *client, char* path, size
     // 发送body
     FILE*  fd = luat_fs_fopen(path, "rb");
     if (fd == NULL) {
-        tcp_abort(client->pcb);
-        tcp_close(client->pcb);
+        tcp_recv(client->pcb, NULL);
+        tcp_sent(client->pcb, NULL);
+        if(tcp_close(client->pcb))
+        {
+            tcp_abort(client->pcb);
+        }
         LLOGE("open %s FAIL!!", path);
         return 1;
     }

+ 10 - 16
components/network/libftp/luat_ftp_client.c

@@ -398,7 +398,7 @@ static int pasv_recv(void)
 		LLOGE("pasv_recv %s error:%d", g_s_ftp.network->cmd_recv_data, ret);
 		return -1;
 	}
-	LLOGD("%.*s", g_s_ftp.network->cmd_recv_len, g_s_ftp.network->cmd_recv_data);
+	LLOGD("%s %d %.*s",__FUNCTION__ ,__LINE__ ,g_s_ftp.network->cmd_recv_len, g_s_ftp.network->cmd_recv_data);
 	g_s_ftp.network->cmd_recv_data[g_s_ftp.network->cmd_recv_len] = 0;
 	if (memcmp(g_s_ftp.network->cmd_recv_data, FTP_FILE_STATUS_OK, 3) && memcmp(g_s_ftp.network->cmd_recv_data, FTP_DATA_CON_OPEN, 3)){
 		return -1;
@@ -414,15 +414,15 @@ static int pasv_recv(void)
 			rx_finish = 1;
 		}
 	}
-	// LLOGD("rx_finish:%d data_netc_online:%d Pos:%d ", rx_finish, g_s_ftp.network->data_netc_online, g_s_ftp.result_buffer.Pos);
-	while(!rx_finish)	//data通道未断开或者已经接收到数据了
+	LLOGD("%s %d rx_finish:%d data_netc_online:%d Pos:%d ",__FUNCTION__ ,__LINE__ ,rx_finish, g_s_ftp.network->data_netc_online, g_s_ftp.result_buffer.Pos);
+    while(!rx_finish)	//data通道未断开或者已经接收到数据了
 	{
 		ret = luat_ftp_cmd_recv(&g_s_ftp,g_s_ftp.network->cmd_recv_data,&g_s_ftp.network->cmd_recv_len,FTP_SOCKET_TIMEOUT);
-		if (ret){
-			LLOGD("rx error!%d", ret);
+		if (ret<0){
+			LLOGD("%s %d rx error!%d",__FUNCTION__ ,__LINE__ , ret);
 			return -1;
 		} else if (!ret) {
-			LLOGD("%.*s", g_s_ftp.network->cmd_recv_len, g_s_ftp.network->cmd_recv_data);
+			LLOGD("%s %d %.*s",__FUNCTION__ ,__LINE__ , g_s_ftp.network->cmd_recv_len, g_s_ftp.network->cmd_recv_data);
 			if (memcmp(g_s_ftp.network->cmd_recv_data, FTP_CLOSE_CONNECT, 3)){
 				return -1;
 			}
@@ -435,17 +435,11 @@ static int pasv_recv(void)
 		LLOGD("???");
 		return -1;
 	}
-	//等服务器关闭接收通道
-	if (g_s_ftp.network->data_netc_online) {
-		ret = luat_ftp_cmd_recv(&g_s_ftp,g_s_ftp.network->cmd_recv_data,&g_s_ftp.network->cmd_recv_len,1000);
-		if (ret) {
-			LLOGE("pasv_recv %s error:%d", g_s_ftp.network->cmd_recv_data, ret);
-			return -1;
-		}
-	}
 	//主动关闭掉接收
-	if (g_s_ftp.network->data_netc_online && g_s_ftp.network->data_netc) {
-		network_close(g_s_ftp.network->data_netc, 0);
+	if (g_s_ftp.network->data_netc_online && g_s_ftp.network->data_netc){
+		network_force_close_socket(g_s_ftp.network->data_netc);
+		network_release_ctrl(g_s_ftp.network->data_netc);
+		g_s_ftp.network->data_netc = NULL;
 	}
 	return 0;
 }

+ 1 - 1
components/network/libftp/luat_lib_ftp.c

@@ -1,6 +1,6 @@
 /*
 @module  ftp
-@summary ftp 客户端
+@summary ftp 客户端 (服务器推荐使用vsftpd,其他暂不做支持)
 @version 1.0
 @date    2022.09.05
 @demo    ftp

+ 21 - 1
components/network/netdrv/binding/luat_lib_netdrv.c

@@ -122,17 +122,37 @@ static int l_netdrv_setup(lua_State *L) {
 
 /*
 开启或关闭DHCP
-@api netdrv.dhcp(id, enable)
+@api netdrv.dhcp(id, enable, name)
 @int 网络适配器编号, 例如 socket.LWIP_ETH
 @boolean 开启或者关闭
+@string dhcp主机名称, 可选, 最长31字节,填""清除
 @return boolean 成功与否
 @usgae
 -- 注意, 并非所有网络设备都支持关闭DHCP, 例如4G Cat.1
+-- name参数于2025.9.23添加
 netdrv.dhcp(socket.LWIP_ETH, true)
+netdrv.dhcp(socket.LWIP_ETH, true, "LuatOS")
 */
 static int l_netdrv_dhcp(lua_State *L) {
     int id = luaL_checkinteger(L, 1);
     int enable = lua_toboolean(L, 2);
+    if (lua_isstring(L, 3)) {
+        size_t len = 0;
+        const char* data = NULL;
+        luat_netdrv_t *drv = NULL;
+        data = luaL_checklstring(L, 3, &len);
+        drv = luat_netdrv_get(id);
+        if(((len + 1) > 32) || (drv == NULL) || (drv->ulwip == NULL)) {
+            LLOGD("dhcp name set fail");
+            lua_pushboolean(L, 0);
+            return -1;
+        }
+        if(0 == len){
+            memset(drv->ulwip->dhcp_client.name, 0x00, 32);
+        } else {
+            memcpy(drv->ulwip->dhcp_client.name, data, len + 1);
+        }
+    }
     int ret = luat_netdrv_dhcp(id, enable);
     lua_pushboolean(L, ret == 0);
     return 1;

+ 4 - 2
components/network/netdrv/src/luat_netdrv.c

@@ -3,6 +3,8 @@
 #include "luat_network_adapter.h"
 #include "luat_mem.h"
 #include "luat_mcu.h"
+#include "lwip/ip.h"
+#include "lwip/tcpip.h"
 
 #ifdef LUAT_USE_AIRLINK
 #include "luat_airlink.h"
@@ -342,10 +344,10 @@ int luat_netdrv_dhcp_opt(luat_netdrv_t* drv, void* userdata, int enable) {
         return 0;
     }
     if (enable) {
-        ulwip_dhcp_client_start(drv->ulwip);
+        tcpip_callback_with_block(ulwip_dhcp_client_start, drv->ulwip, 1);
     }
     else {
-        ulwip_dhcp_client_stop(drv->ulwip);
+        tcpip_callback_with_block(ulwip_dhcp_client_stop, drv->ulwip, 1);
     }
     return 0;
 }

+ 1 - 1
components/network/ulwip/binding/luat_lib_napt.c

@@ -149,7 +149,7 @@ static int l_napt_rebuild(lua_State *L) {
 /*
 检查和清理NAT表
 @api napt.check()
-@return nil
+@return nil 无返回值
 @usage
 -- 两次check之间没有数据包的映射记录,会被清理
 */

+ 3 - 3
components/network/ulwip/binding/luat_lib_ulwip.c

@@ -93,9 +93,9 @@ int ulwip_netif_ip_event(ulwip_ctx_t* ctx) {
     ready_now &= netif_is_up(netif);
     // luat_ip_addr_t ip = {0};
 
-    if (ctx->dhcp_client) {
-        net_lwip2_set_dhcp_client(ctx->adapter_index, ctx->dhcp_client);
-    }
+    // if (ctx->dhcp_client) {
+    net_lwip2_set_dhcp_client(ctx->adapter_index, &ctx->dhcp_client);
+    // }
 
     net_lwip2_set_link_state(ctx->adapter_index, ready_now);
     if (ctx->ip_ready == ready_now) {

+ 1 - 1
components/network/ulwip/include/luat_ulwip.h

@@ -47,7 +47,7 @@ typedef struct ulwip_ctx
     uint16_t use_zbuff_out;
     uint16_t mtu;
     uint8_t hwaddr[ETH_HWADDR_LEN];
-    dhcp_client_info_t *dhcp_client;
+    dhcp_client_info_t dhcp_client;
     luat_rtos_timer_t dhcp_timer;
     ulwip_event_cb event_cb;
 }ulwip_ctx_t;

+ 27 - 23
components/network/ulwip/src/ulwip_dhcp_client.c

@@ -27,7 +27,7 @@ static ulwip_ctx_t* s_ctxs[NW_ADAPTER_INDEX_LWIP_NETIF_QTY];
 static int ulwip_dhcp_client_run(ulwip_ctx_t* ctx, char* rxbuff, size_t len) {
     PV_Union uIP;
     // 检查dhcp的状态
-    dhcp_client_info_t* dhcp = ctx->dhcp_client;
+    dhcp_client_info_t* dhcp = (&ctx->dhcp_client);
     u8_t adapter_index = ctx->adapter_index;
     struct netif* netif = ctx->netif;
 
@@ -174,7 +174,7 @@ static int ulwip_dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const
     for (size_t i = 0; i < NW_ADAPTER_INDEX_LWIP_NETIF_QTY; i++)
     {
         ctx = s_ctxs[i];
-        if (ctx == NULL || ctx->dhcp_client == NULL || ctx->netif == NULL) {
+        if (ctx == NULL || ctx->netif == NULL) {
             continue;
         }
 
@@ -217,7 +217,7 @@ static void ulwip_dhcp_client_run_proxy(void* ctx) {
 static void dhcp_client_timer_cb(void *arg) {
     ulwip_ctx_t *ctx = (ulwip_ctx_t *)arg;
     // 简单防御一下
-    if (ctx->dhcp_client == NULL || ctx->dhcp_enable == 0) {
+    if (ctx->dhcp_enable == 0) {
         return;
     }
     #if NO_SYS
@@ -228,19 +228,23 @@ static void dhcp_client_timer_cb(void *arg) {
 }
 
 static void reset_dhcp_client(ulwip_ctx_t *ctx) {
-    memset(ctx->dhcp_client, 0, sizeof(dhcp_client_info_t));
-    memcpy(ctx->dhcp_client->mac, ctx->netif->hwaddr, 6);
+    char tmp[32] = {0};
+    memcpy(tmp, ctx->dhcp_client.name, 32);
+    memset(&ctx->dhcp_client, 0, sizeof(dhcp_client_info_t));
+    memcpy(&ctx->dhcp_client.mac, ctx->netif->hwaddr, 6);
     ctx->ip_ready = 0;
-    luat_crypto_trng((char*)&ctx->dhcp_client->xid, sizeof(ctx->dhcp_client->xid));
+    luat_crypto_trng((char*)&ctx->dhcp_client.xid, sizeof(ctx->dhcp_client.xid));
     #if LWIP_NETIF_HOSTNAME
     if (ctx->netif && ctx->netif->hostname) {
-        strncpy(ctx->dhcp_client->name, ctx->netif->hostname, strlen(ctx->dhcp_client->name) + 1);
+        strncpy(ctx->dhcp_client.name, ctx->netif->hostname, strlen(ctx->dhcp_client.name) + 1);
     }
     #endif
-    if (ctx->dhcp_client->name[0] == 0) {
-        sprintf_(ctx->dhcp_client->name, "LuatOS_%02X%02X%02X%02X%02X%02X",
-                ctx->dhcp_client->mac[0],ctx->dhcp_client->mac[1], ctx->dhcp_client->mac[2],
-                ctx->dhcp_client->mac[3],ctx->dhcp_client->mac[4], ctx->dhcp_client->mac[5]);
+    if (tmp[0] == 0) {
+        sprintf_(ctx->dhcp_client.name, "LuatOS_%02X%02X%02X%02X%02X%02X",
+                ctx->dhcp_client.mac[0],ctx->dhcp_client.mac[1], ctx->dhcp_client.mac[2],
+                ctx->dhcp_client.mac[3],ctx->dhcp_client.mac[4], ctx->dhcp_client.mac[5]);
+    } else {
+        memcpy(ctx->dhcp_client.name, tmp, 32);
     }
 }
 
@@ -258,21 +262,21 @@ void ulwip_dhcp_client_start(ulwip_ctx_t *ctx) {
         udp_connect(s_ulwip_dhcp, IP4_ADDR_ANY, 67);
         udp_recv(s_ulwip_dhcp, ulwip_dhcp_recv, ctx);
     }
-    if (!ctx->dhcp_client) {
-        ctx->dhcp_client = luat_heap_malloc(sizeof(dhcp_client_info_t));
-        reset_dhcp_client(ctx);
-        net_lwip2_set_dhcp_client(ctx->adapter_index, ctx->dhcp_client);
-        luat_rtos_timer_create(&ctx->dhcp_timer);
-        s_ctxs[ctx->adapter_index] = ctx; // 保存到全局数组中
-    }
+    // if (!ctx->dhcp_client) {
+        // ctx->dhcp_client = luat_heap_malloc(sizeof(dhcp_client_info_t));
+    reset_dhcp_client(ctx);
+    net_lwip2_set_dhcp_client(ctx->adapter_index, &ctx->dhcp_client);
+    luat_rtos_timer_create(&ctx->dhcp_timer);
+    s_ctxs[ctx->adapter_index] = ctx; // 保存到全局数组中
+    // }
     ip4_addr_t ipaddr = {0};
     ip4_addr_t netmask = {0};
     ip4_addr_t gw = {0};
     if (ctx->netif) {
         netif_set_addr(ctx->netif, &ipaddr, &netmask, &gw);
     }
-    ctx->dhcp_client->state = DHCP_STATE_DISCOVER;
-    ctx->dhcp_client->discover_cnt = 0;
+    ctx->dhcp_client.state = DHCP_STATE_DISCOVER;
+    ctx->dhcp_client.discover_cnt = 0;
     if (!luat_rtos_timer_is_active(ctx->dhcp_timer))
     {
         luat_rtos_timer_start(ctx->dhcp_timer, 1000, 1, dhcp_client_timer_cb, ctx);
@@ -287,10 +291,10 @@ void ulwip_dhcp_client_stop(ulwip_ctx_t *ctx) {
         
     }
     if (ctx->dhcp_enable) {
-        if (ctx->dhcp_client) {
+        // if (ctx->dhcp_client) {
             // 重置dhcp客户端
-            reset_dhcp_client(ctx);
-        }
+        reset_dhcp_client(ctx);
+        // }
         if (ctx->netif) {
             ip4_addr_t ipaddr = {0};
             ip4_addr_t netmask = {0};

+ 4 - 4
components/onewire/binding/luat_lib_onewire.c

@@ -21,7 +21,7 @@
 初始化单总线
 @api onewire.init(id)
 @int id, 硬件单总线编号,如果只有一条则随意填写
-@return nil
+@return nil 无返回值
 @usage
 onewire.init(0) --初始化硬件单总线
 */
@@ -46,7 +46,7 @@ static int l_onewire_init(lua_State *L)
 @int tLOW1, start信号到允许写的时间
 @int tRDV, start信号到允许读的时间
 @int tREC, 通信结束前恢复时间
-@return nil
+@return nil 无返回值
 @usage
 onewire.timing(0, false, 0, 500, 500, 15, 240, 65, 1, 15, 15, 2) --配置单总线时序匹配DS18B20,保留了点余量
 */
@@ -262,7 +262,7 @@ static int l_onewire_rx(lua_State *L)
 @api onewire.debug(id, onoff)
 @int id, GPIO模式对应GPIO编号,HW模式是硬件单总线编号,如果只有一条则随意填写
 @boolean onoff, true打开,false关闭
-@return nil
+@return nil 无返回值
 @usage
 onewire.debug(0, true)
 */
@@ -276,7 +276,7 @@ static int l_onewire_debug(lua_State *L)
 关闭单总线
 @api onewire.deinit(id)
 @int id, 硬件单总线编号,如果只有一条则随意填写
-@return nil
+@return nil 无返回值
 @usage
 onewire.deinit(0) --初始化硬件单总线
 */

+ 4 - 0
components/u8g2/luat_lib_u8g2.c

@@ -6,6 +6,10 @@
 @date    2021.01.25
 @demo u8g2
 @tag LUAT_USE_U8G2
+@usage
+-- 注意, 本库只支持普通SPI, 不支持LCD专用SPI
+-- 请确保已经正确连接了屏幕, 连接方式请参考demo
+-- OLED屏幕, 正确驱动才会显示内容, 不然就是黑屏的
 */
 #include "luat_base.h"
 #include "luat_mem.h"

+ 2 - 2
components/u8g2/u8g2_font.c

@@ -830,10 +830,10 @@ const uint8_t *u8g2_font_get_glyph_data(u8g2_t *u8g2, uint16_t encoding)
         #ifdef U8G2_WITH_UNICODE
             else{
                 uint16_t e;
-                const uint8_t *unicode_lookup_table;
+                // const uint8_t *unicode_lookup_table;
                 // font += u8g2->font_info.start_pos_unicode;
                 luat_fs_fseek(u8g2->font_file, u8g2->font_info.start_pos_unicode, SEEK_CUR);
-                unicode_lookup_table = font; 
+                // unicode_lookup_table = font; 
                 /* issue 596: search for the glyph start in the unicode lookup table */
                 // do{
                 //     font += u8g2_font_get_word(unicode_lookup_table, 0);

+ 1 - 1
lua/src/lauxlib.c

@@ -851,7 +851,7 @@ LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) {
         }
         else {
           n = (LUAI_UACNUMBER)lua_tonumber(L, idx);
-          sprintf_(buff, "%9g", n);
+          sprintf_(buff, LUA_NUMBER_FMT, n);
           lua_pushstring(L, buff);
         }
         break;

+ 3 - 3
lua/src/loslib.c

@@ -219,14 +219,14 @@ static int os_getenv (lua_State *L) {
 /*
 返回程序使用的按秒计 CPU 时间的近似值
 @api os.clock()
-@return 时间戳
+@return int 时间戳
 @usage
 -- 不推荐使用本API
 -- 如需要获取 时间戳, 请使用 os.time()
 -- 如需获取系统运行时长, 请使用 mcu.ticks()
 */
 static int os_clock (lua_State *L) {
-  lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC);
+  lua_pushinteger(L, ((lua_Integer)clock())/(lua_Integer)CLOCKS_PER_SEC);
   return 1;
 }
 
@@ -407,7 +407,7 @@ static int os_date (lua_State *L) {
 时间戳函数
 @api os.time(mytime)
 @table 日期时间的table
-@return 时间戳
+@return int 时间戳
 @usage
 -- 注意注意, 这个函数返回的是UTC时间戳
 -- 时间戳, 但lua下的精度只能到秒

+ 17 - 0
luat/demo/crypto/main.lua

@@ -232,6 +232,23 @@ sys.taskInit(function()
         log.info("crypto", "当前固件不支持crypto.crc7")
     end
 
+    
+    -- crypto.md测试, 要测试输出长度
+    log.info("crypto.md测试")
+    log.info("md5", crypto.md("MD5", "1234567890"))
+    log.info("sha1", crypto.md("SHA1", "1234567890"))
+    log.info("sha224", crypto.md("SHA224", "1234567890"))
+    log.info("sha256", crypto.md("SHA256", "1234567890"))
+    log.info("sha384", crypto.md("SHA384", "1234567890"))
+    log.info("sha512", crypto.md("SHA512", "1234567890"))
+    -- 还有hmac形式
+    log.info("hmac_md5", crypto.md("MD5", "1234567890", "1234567890"))
+    log.info("hmac_sha1", crypto.md("SHA1", "1234567890", "1234567890"))
+    log.info("hmac_sha224", crypto.md("SHA224", "1234567890", "1234567890"))
+    log.info("hmac_sha256", crypto.md("SHA256", "1234567890", "1234567890"))
+    log.info("hmac_sha384", crypto.md("SHA384", "1234567890", "1234567890"))
+    log.info("hmac_sha512", crypto.md("SHA512", "1234567890", "1234567890"))
+
     log.info("crypto", "ALL Done")
     sys.wait(100000)
 end)

+ 5 - 0
luat/include/luat_crypto.h

@@ -111,7 +111,10 @@ int luat_crypto_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, co
 int luat_crypto_cipher_list(const char** list, size_t* len);
 int luat_crypto_cipher_suites(const char** list, size_t* len);
 
+// 返回是 0 或者 -1
 int luat_crypto_md(const char* md, const char* str, size_t str_size, void* out_ptr, const char* key, size_t key_len);
+// 返回值是 -1 或者 hash长度
+int luat_crypto_md_v2(const char* md, const char* str, size_t str_size, void* out_ptr, const char* key, size_t key_len);
 int luat_crypto_md_file(const char* md, void* out_ptr, const char* key, size_t key_len, const char* path);
 
 int luat_crypto_md_init(const char* md, const char* key, luat_crypt_stream_t *stream);
@@ -172,4 +175,6 @@ uint32_t luat_crc32(const void *data, uint32_t len, uint32_t start, uint32_t pol
 
 // 快速modbus crc16算法
 uint16_t luat_crc16_modbus( const uint8_t *buf, uint32_t len);
+
+uint8_t luat_crc7(const uint8_t* message, int length, uint8_t CRCPoly, uint8_t CRC);
 #endif

+ 1 - 1
luat/modules/luat_lib_adc.c

@@ -52,7 +52,7 @@ static int l_adc_open(lua_State *L) {
 设置ADC的测量范围,注意这个和具体芯片有关,目前只支持air105/Air780EXXX系列
 @api adc.setRange(range)
 @int range参数,与具体设备有关,比如air105填adc.ADC_RANGE_1_8和adc.ADC_RANGE_3_6
-@return nil
+@return nil 无返回值
 @usage
 -- 本函数要在调用adc.open之前就调用, 之后调用无效!!!
 

+ 1 - 1
luat/modules/luat_lib_can.c

@@ -458,7 +458,7 @@ static int l_can_deinit(lua_State *L)
 CAN debug开关,打开后有更详细的打印
 @api can.debug(on_off)
 @boolean true打开,false关闭
-@return nil
+@return nil 无返回值
 @usage
 can.debug(true)
 */

+ 30 - 58
luat/modules/luat_lib_crypto.c

@@ -13,22 +13,12 @@
 #include "luat_str.h"
 #include <time.h>
 #include "luat_zbuff.h"
-// #include "mbedtls/md.h"
+#include "luat_str.h"
 
 #define LUAT_LOG_TAG "crypto"
 #define LUAT_CRYPTO_TYPE "crypto"
 #include "luat_log.h"
 
-static const unsigned char hexchars[] = "0123456789ABCDEF";
-static void fixhex(const char* source, char* dst, size_t len) {
-    for (size_t i = 0; i < len; i++)
-    {
-        char ch = *(source+i);
-        dst[i*2] = hexchars[(unsigned char)ch >> 4];
-        dst[i*2+1] = hexchars[(unsigned char)ch & 0xF];
-    }
-}
-
 /**
 计算md5值
 @api crypto.md5(str)
@@ -44,7 +34,7 @@ static int l_crypto_md5(lua_State *L) {
     char tmp[32] = {0};
     char dst[32] = {0};
     if (luat_crypto_md5_simple(str, size, tmp) == 0) {
-        fixhex(tmp, dst, 16);
+        luat_str_tohex(tmp, 16, dst);
         lua_pushlstring(L, dst, 32);
         return 1;
     }
@@ -69,7 +59,7 @@ static int l_crypto_hmac_md5(lua_State *L) {
     char tmp[32] = {0};
     char dst[32] = {0};
     if (luat_crypto_hmac_md5_simple(str, str_size, key, key_size, tmp) == 0) {
-        fixhex(tmp, dst, 16);
+        luat_str_tohex(tmp, 16, dst);
         lua_pushlstring(L, dst, 32);
         return 1;
     }
@@ -91,7 +81,7 @@ static int l_crypto_sha1(lua_State *L) {
     char tmp[40] = {0};
     char dst[40] = {0};
     if (luat_crypto_sha1_simple(str, size, tmp) == 0) {
-        fixhex(tmp, dst, 20);
+        luat_str_tohex(tmp, 20, dst);
         lua_pushlstring(L, dst, 40);
         return 1;
     }
@@ -116,7 +106,7 @@ static int l_crypto_hmac_sha1(lua_State *L) {
     char tmp[40] = {0};
     char dst[40] = {0};
     if (luat_crypto_hmac_sha1_simple(str, str_size, key, key_size, tmp) == 0) {
-        fixhex(tmp, dst, 20);
+        luat_str_tohex(tmp, 20, dst);
         lua_pushlstring(L, dst, 40);
         return 1;
     }
@@ -139,7 +129,7 @@ static int l_crypto_sha256(lua_State *L) {
     char tmp[64] = {0};
     char dst[64] = {0};
     if (luat_crypto_sha256_simple(str, size, tmp) == 0) {
-        fixhex(tmp, dst, 32);
+        luat_str_tohex(tmp, 32, dst);
         lua_pushlstring(L, dst, 64);
         return 1;
     }
@@ -171,7 +161,7 @@ static int l_crypto_hmac_sha256(lua_State *L) {
     }
 
     if (luat_crypto_hmac_sha256_simple(str, str_size, key, key_size, tmp) == 0) {
-        fixhex(tmp, dst, 32);
+        luat_str_tohex(tmp, 32, dst);
         lua_pushlstring(L, dst, 64);
         return 1;
     }
@@ -195,7 +185,7 @@ static int l_crypto_sha512(lua_State *L) {
     char tmp[128] = {0};
     char dst[128] = {0};
     if (luat_crypto_sha512_simple(str, size, tmp) == 0) {
-        fixhex(tmp, dst, 64);
+        luat_str_tohex(tmp, 64, dst);
         lua_pushlstring(L, dst, 128);
         return 1;
     }
@@ -227,7 +217,7 @@ static int l_crypto_hmac_sha512(lua_State *L) {
     }
 
     if (luat_crypto_hmac_sha512_simple(str, str_size, key, key_size, tmp) == 0) {
-        fixhex(tmp, dst, 64);
+        luat_str_tohex(tmp, 64, dst);
         lua_pushlstring(L, dst, 128);
         return 1;
     }
@@ -322,11 +312,11 @@ static int l_crypto_crc16(lua_State *L)
     }else{
         inputData = (const unsigned char*)lua_tolstring(L,2,&inputlen);
     }
-    uint16_t poly = luaL_optnumber(L,3,0x0000);
-    uint16_t initial = luaL_optnumber(L,4,0x0000);
-    uint16_t finally = luaL_optnumber(L,5,0x0000);
-    uint8_t inReverse = luaL_optnumber(L,6,0);
-    uint8_t outReverse = luaL_optnumber(L,7,0);
+    uint16_t poly = (uint16_t)luaL_optnumber(L,3,0x0000);
+    uint16_t initial = (uint16_t)luaL_optnumber(L,4,0x0000);
+    uint16_t finally = (uint16_t)luaL_optnumber(L,5,0x0000);
+    uint8_t inReverse = (uint8_t)luaL_optnumber(L,6,0);
+    uint8_t outReverse = (uint8_t)luaL_optnumber(L,7,0);
     lua_pushinteger(L, calcCRC16(inputData, inputmethod,inputlen,poly,initial,finally,inReverse,outReverse));
     return 1;
 }
@@ -347,7 +337,7 @@ static int l_crypto_crc16_modbus(lua_State *L)
 {
     size_t len = 0;
     const unsigned char *inputData = (const unsigned char*)luaL_checklstring(L, 1, &len);
-    uint16_t crc_init = luaL_optinteger(L, 2, 0xFFFF);
+    uint16_t crc_init = (uint16_t)luaL_optinteger(L, 2, 0xFFFF);
 
     lua_pushinteger(L, calcCRC16_modbus(inputData, len, crc_init));
     return 1;
@@ -371,9 +361,9 @@ static int l_crypto_crc32(lua_State *L)
 {
     size_t len = 0;
     const unsigned char *inputData = (const unsigned char*)luaL_checklstring(L, 1, &len);
-	uint32_t start = luaL_optinteger(L, 2, 0xffffffff);
-	uint32_t poly = luaL_optinteger(L, 3, 0x04C11DB7);
-	uint32_t end = luaL_optinteger(L, 4, 0xffffffff);
+	uint32_t start = (uint32_t)luaL_optinteger(L, 2, 0xffffffff);
+	uint32_t poly = (uint32_t)luaL_optinteger(L, 3, 0x04C11DB7);
+	uint32_t end = (uint32_t)luaL_optinteger(L, 4, 0xffffffff);
     lua_pushinteger(L, luat_crc32(inputData, len, start, poly) ^ end);
     return 1;
 }
@@ -398,11 +388,11 @@ static int l_crypto_crc8(lua_State *L)
     if (!lua_isinteger(L, 2)) {
         lua_pushinteger(L, calcCRC8(inputData, len));
     } else {
-    	uint8_t poly = lua_tointeger(L, 2);
-    	uint8_t start = luaL_optinteger(L, 3, 0);
+    	uint8_t poly = (uint8_t)lua_tointeger(L, 2);
+    	uint8_t start = (uint8_t)luaL_optinteger(L, 3, 0);
     	uint8_t is_rev = 0;
     	if (lua_isboolean(L, 4)) {
-    		is_rev = lua_toboolean(L, 4);
+    		is_rev = (uint8_t)lua_toboolean(L, 4);
     	}
 		lua_pushinteger(L, luat_crc8(inputData, len, start, poly, is_rev));
     }
@@ -410,25 +400,6 @@ static int l_crypto_crc8(lua_State *L)
 }
 
 
-
-static inline unsigned char crc7(const unsigned char* message, int length, unsigned char CRCPoly, unsigned char CRC)
-{
-    // unsigned char CRCPoly = 0xe5;
-    unsigned char CRCTable[256];
-    // unsigned char CRC = 0x00;
-    for (int i = 0; i < 256; i++){
-        CRCTable[i] = (i & 0x80) ? i ^ CRCPoly : i;
-        for (int j = 1; j < 8; j++){
-            CRCTable[i] <<= 1;
-            if (CRCTable[i] & 0x80)
-                CRCTable[i] ^= CRCPoly;
-        }
-    }
-    for (int i = 0; i < length; i++)
-        CRC = CRCTable[(CRC << 1) ^ message[i]];
-    return CRC<< 1;
-}
-
 /**
 计算crc7值
 @api crypto.crc7(data, poly, start)
@@ -444,9 +415,9 @@ local crc = crypto.crc7(data, 0x31, 0xff)
 static int l_crypto_crc7(lua_State* L) {
     size_t len = 0;
     const unsigned char *inputData = (const unsigned char*)luaL_checklstring(L, 1, &len);
-    unsigned char poly = luaL_optinteger(L, 2, 0xe5);
-    unsigned char start = luaL_optinteger(L, 3, 0);
-    unsigned char result = crc7(inputData, len, poly, start);
+    unsigned char poly = (unsigned char)luaL_optinteger(L, 2, 0xe5);
+    unsigned char start = (unsigned char)luaL_optinteger(L, 3, 0);
+    unsigned char result = luat_crc7(inputData, len, poly, start);
     lua_pushinteger(L, result);
     return 1;
 }
@@ -647,7 +618,7 @@ static int l_crypto_md_file(lua_State *L) {
         return 0;
     }
 
-    fixhex(output, buff, ret);
+    luat_str_tohex(output, ret, buff);
     lua_pushlstring(L, buff, ret *2);
     return 1;
 }
@@ -683,12 +654,13 @@ static int l_crypto_md(lua_State *L) {
     char buff[128] = {0};
     char output[64];
 
-    int ret = luat_crypto_md(md, data, data_size, output, key, key_len);
+    int ret = luat_crypto_md_v2(md, data, data_size, output, key, key_len);
     if (ret < 1) {
+        LLOGE("luat_crypto_md return %d", ret);
         return 0;
     }
 
-    fixhex(output, buff, ret);
+    luat_str_tohex(output, ret, buff);
     lua_pushlstring(L, buff, ret *2);
     return 1;
 }
@@ -736,7 +708,7 @@ static int l_crypt_hash_init(lua_State *L) {
 @api crypto.hash_update(stream, data)
 @userdata crypto.hash_init()创建的stream, 必选
 @string 待计算的数据,必选
-@return 无
+@return nil 返回值
 @usage
 crypto.hash_update(stream, "OK")
 */
@@ -765,7 +737,7 @@ static int l_crypt_hash_finish(lua_State *L) {
     if (ret < 1) {
         return 0;
     }
-    fixhex(output, buff, ret);
+    luat_str_tohex(output, ret, buff);
     lua_pushlstring(L, buff, ret * 2);
     return 1;
 }

+ 4 - 3
luat/modules/luat_lib_rtc.c

@@ -22,8 +22,8 @@ void LUAT_WEAK luat_rtc_set_tamp32(uint32_t tamp) {
 
 /*
 设置时钟
-@api rtc.set(tab)
-@table or int 时钟参数,见示例
+@api rtc.set(val)
+@table/int 时钟参数,见示例
 @return bool 成功返回true,否则返回nil或false
 @usage
 rtc.set({year=2021,mon=8,day=31,hour=17,min=8,sec=43})
@@ -277,6 +277,7 @@ static int l_rtc_timer_stop(lua_State *L){
 @api rtc.setBaseYear(Base_year)
 @int 基准年Base_year,通常1900
 @usage
+-- 本函数已经废弃, 不要使用
 rtc.setBaseYear(1900)
 */
 static int l_rtc_set_base_year(lua_State *L){
@@ -288,7 +289,7 @@ static int l_rtc_set_base_year(lua_State *L){
 读取或设置时区
 @api rtc.timezone(tz)
 @int 时区值,注意单位是1/4时区.例如东八区是 32,而非8. 可以不传
-@return 当前/设置后的时区值
+@return int 当前/设置后的时区值
 @usage
 -- 设置为东8区
 rtc.timezone(32)

+ 0 - 1
module/Air780E/demo/README.md

@@ -19,7 +19,6 @@
 |------|----|-------|-----------|----|
 |[adc](https://gitee.com/openLuat/LuatOS/tree/master/demo/adc/)|模数转换|adc|所有||
 |[camera](https://gitee.com/openLuat/LuatOS/tree/master/demo/camera/)|摄像头|camera|air105||
-|[coremark](https://gitee.com/openLuat/LuatOS/tree/master/demo/coremark/)|跑分|coremark|所有|生产固件均不带该库,可自行编译或云编译|
 |[crypto](https://gitee.com/openLuat/LuatOS/tree/master/demo/crypto/)|加解密|crypto|所有||
 |[dht12](https://gitee.com/openLuat/LuatOS/tree/master/demo/dht12/)|温湿度传感器|i2c|所有||
 |[eink](https://gitee.com/openLuat/LuatOS/tree/master/demo/eink/)|电子墨水屏|eink|所有||

+ 1 - 1
module/Air780E/demo/protobuf/main.lua

@@ -20,7 +20,7 @@ end
 
 sys.taskInit(function()
     sys.wait(500)
-    -- 如果没有这个库, 就云编译一份吧: https://wiki.luatos.com/develop/compile/Cloud_compilation.html
+    -- 如果没有这个库, 证明你选的的固件不支持
     if not protobuf then
         log.info("protobuf", "this demo need protobuf lib")
         return

+ 1 - 1
module/Air780E/demo/u8g2/main.lua

@@ -104,7 +104,7 @@ else
     print("no chinese font")
 end
 
-u8g2.DrawUTF8("中文测试", 40, 38) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据, 可自行云编译一份. wiki.luatos.com 有文档.
+u8g2.DrawUTF8("中文测试", 40, 38) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据
 u8g2.SendBuffer()
 
 --主流程

+ 1 - 1
module/Air780E/demo/xxtea/main.lua

@@ -12,7 +12,7 @@ sys.taskInit(function()
         while true do
             sys.wait(1000)
             -- 每隔1秒打印一条信息
-            log.info("testCrypto.xxteaTest","xxtea库不存在,请云编译一份最新版固件,并选上xxtea库")
+            log.info("testCrypto.xxteaTest","xxtea库不存在")
         end
     end
     while true do

+ 141 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/http_app.lua

@@ -0,0 +1,141 @@
+--[[
+@module  http_app
+@summary http应用功能模块
+@version 1.0
+@date    2025.09.17
+@author  王城钧
+@usage
+本文件为http应用功能模块,核心业务逻辑为:基于不同的应用场景,演示http核心库的使用方式;
+本文件没有对外接口,直接在main.lua中require "http_app"就可以加载运行;
+]]
+
+--[[
+此处先详细解释下http.request接口的使用方法
+
+接口定义:
+    http.request(method, url, headers, body, opts, server_ca_cert, client_cert, client_key, client_password)
+
+使用方法:
+    local code, headers, body = http.request(method, url, headers, body, opts, server_ca_cert, client_cert, client_key, client_password).wait()
+    只能在task中使用
+    发送http请求到服务器,等待服务器的http应答,此处会阻塞当前task,等待整个过程成功结束或者出现错误异常结束或者超时结束
+
+参数定义:
+    method,stirng类型,必须包含此参数,表示HTTP请求方法,支持"GET"、"POST"、"HEAD"等所有HTTP请求方法
+    url,string类型,必须包含此参数,表示HTTP请求URL地址,支持HTTP、HTTPS,支持域名、IP地址,支持自定义端口,标准的HTTP URL格式都支持
+    headers,table或者nil类型,可选包含此参数,表示HTTP请求头,例如 {["Content-Type"] = "application/x-www-form-urlencoded", ["self_defined_key"] = "self_defined_value"}
+    body,string或者zbuff或者nil类型,可选包含此参数,表示HTTP请求体,如果请求体是一个文件中的内容,要把文件内容读出来,赋值给body使用
+    opts,table或者nil类型,可选包含此参数,表示HTTP请求的一些额外配置,包含以下内容
+    {
+        timeout    -- -- number或者nil类型,单位毫秒,可选包含此参数,表示从发送请求到读取到服务器响应整个过程的超时时间,如果传入0,表示永久等待;如果没有传入此参数或者传入nil,则使用默认值10分钟
+        dst        -- 下载路径,string类型,当HTTP请求的数据需要保存到文件中时,此处填写完整的文件路径
+        adapter    -- 使用的网卡ID,number类型,例如4G网卡,SPI外挂以太网卡,WIFI网卡等;如果没有传入此参数,内核固件会自动选择当前时间点其他功能模块设置的默认网卡
+                    -- 除非你HTTP请求时,一定要使用某一种网卡,才设置此参数;如果没什么特别要求,不要使用此参数,使用系统中设置的默认网卡即可
+                    -- 这个参数和本demo中的netdrv_device.lua关系比较大,netdrv_device会设置默认网卡,此处http不要设置adapter参数,直接使用netdrv_device设置的默认网卡就行
+        debug      -- 调试开关,bool类型,true表示打开debug调试信息日志,false表示关闭debug调试信息日志,默认为关闭状态
+        ipv6       -- 是否为ipv6,bool类型,true表示使用ipv6,false表示不使用ipv6,默认为false
+        userdata   -- 下载回调函数使用的用户自定义回调参数,做为callback回调函数的第三个参数使用
+        callback   -- 下载回调函数,function类型,当下载数据时,无论是保存到内存中,还是保存到文件系统中,如果设置了callback,内核固件中每收到一包body数据,都会自动执行一次callback回调函数
+                    -- 回调函数的调用形式为callback(content_len, body_len, userdata)
+                    --     content_len:number类型,数据总长度
+                    --     body_len:number类型,已经下载的数据长度
+                    --     userdata:下载回调函数使用的用户自定义回调参数
+    }
+    server_ca_cert,string类型,服务器ca证书数据,可选包含此参数,当客户端需要验证服务器证书时,需要此参数,如果证书数据在一个文件中,要把文件内容读出来,赋值给server_ca_cert
+    client_cert,string类型,客户端证书数据,可选包含此参数,当服务器需要验证客户端证书时,需要此参数,如果证书数据在一个文件中,要把文件内容读出来,赋值给client_cert
+    client_key, string类型,客户端加密后的私钥数据,可选包含此参数,当服务器需要验证客户端证书时,需要此参数,如果加密后的私钥数据在一个文件中,要把文件内容读出来,赋值给client_key
+    client_password,string类型,客户端私钥口令数据,可选包含此参数,当服务器需要验证客户端证书时,需要此参数,如果私钥口令数据在一个文件中,要把文件内容读出来,赋值给client_password
+
+返回值定义:
+
+    http.request().wait()有三个返回值code,headers,body
+    code表示执行结果,number类型,有以下两种含义:
+        1、code大于等于100时,表示服务器返回的HTTP状态码,例如200表示成功,详细说明可以通过搜索引擎搜索“HTTP状态码”自行了解
+        2、code小于0时,表示内核固件中检测到通信异常,有如下几种:
+            -1 HTTP_ERROR_STATE 错误的状态, 一般是底层异常,请报issue
+            -2 HTTP_ERROR_HEADER 错误的响应头部, 通常是服务器问题
+            -3 HTTP_ERROR_BODY 错误的响应体,通常是服务器问题
+            -4 HTTP_ERROR_CONNECT 连接服务器失败, 未联网,地址错误,域名错误
+            -5 HTTP_ERROR_CLOSE 提前断开了连接, 网络或服务器问题
+            -6 HTTP_ERROR_RX 接收数据报错, 网络问题
+            -7 HTTP_ERROR_DOWNLOAD 下载文件过程报错, 网络问题或下载路径问题
+            -8 HTTP_ERROR_TIMEOUT 超时, 包括连接超时,读取数据超时
+            -9 HTTP_ERROR_FOTA fota功能报错,通常是更新包不合法
+    headers有以下两种含义:
+        1、当code的返回值大于等于100时,headers表示服务器返回的应答头,table类型
+        2、当code的返回值小于0时,headers为nil
+    body有以下三种含义
+        1、当code的返回值大于等于100时,如果请求的body数据不需要保存到文件中,而是直接保存到内存中,则body表示请求到的数据内容,string类型
+        2、当code的返回值大于等于100时,如果请求的body数据需要保存到文件中,则body表示保存请求数据后的文件的大小,number类型
+        3、当code的返回值小于0时,body为nil
+]]
+
+
+-- 普通的http get请求功能演示
+-- 请求的body数据保存到内存变量中,在内存够用的情况下,最大支持32KB的数据存储到内存中
+-- timeout可以设置超时时间
+-- callback可以设置回调函数,可用于实时检测body数据的下载进度
+local function http_app_get()
+    -- https get请求https://www.air32.cn/网页内容
+    -- 如果请求成功,请求的数据保存到body中
+    local code, headers, body = http.request("GET", "https://www.air32.cn/").wait()
+    log.info("http_app_get1",
+        code == 200 and "success" or "error",
+        code,
+        json.encode(headers or {}),
+        body and (body:len() > 512 and body:len() or body) or "nil")
+
+    -- https get请求https://www.luatos.com/网页内容,超时时间为10秒
+    -- 请求超时时间为10秒,用户自己写代码时,不要照抄10秒,根据自己业务逻辑的需要设置合适的超时时间
+    -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get2"
+    -- 如果请求成功,请求的数据保存到body中
+    code, headers, body = http.request("GET", "https://www.luatos.com/", nil, nil,
+        { timeout = 10000, userdata = "http_app_get2", callback = http_cbfunc }).wait()
+    log.info("http_app_get2",
+        code == 200 and "success" or "error",
+        code,
+        json.encode(headers or {}),
+        body and (body:len() > 512 and body:len() or body) or "nil")
+
+    -- http get请求http://httpbin.air32.cn/get网页内容,超时时间为3秒
+    -- 请求超时时间为3秒,用户自己写代码时,不要照抄3秒,根据自己业务逻辑的需要设置合适的超时时间
+    -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get3"
+    -- 如果请求成功,请求的数据保存到body中
+    code, headers, body = http.request("GET", "http://httpbin.air32.cn/get", nil, nil,
+        { timeout = 3000, userdata = "http_app_get3", callback = http_cbfunc }).wait()
+    log.info("http_app_get3",
+        code == 200 and "success" or "error",
+        code,
+        json.encode(headers or {}),
+        body and (body:len() > 512 and body:len() or body) or "nil")
+end
+
+-- http app task 的任务处理函数
+local function http_app_task_func()
+    while true do
+        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+        while not socket.adapter(socket.dft()) do
+            log.warn("http_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
+
+        -- 检测到了IP_READY消息
+        log.info("http_app_task_func", "recv IP_READY", socket.dft())
+
+        -- 普通的http get请求功能演示
+        http_app_get()
+
+        -- 50秒之后,循环测试
+        sys.wait(50000)
+    end
+end
+
+--创建并且启动一个task
+--运行这个task的处理函数http_app_task_func
+sys.taskInit(http_app_task_func)

+ 80 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/main.lua

@@ -0,0 +1,80 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.09.17
+@author  王城钧
+@usage
+本demo演示的核心功能为:
+1、分别使用http核心库和httpplus扩展库,演示以下这种应用场景的使用方式
+   (1) 普通的http get请求功能演示;
+2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(二选一)
+   (1) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+   (2) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+
+更多说明参考本目录下的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 = "HTTP"
+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 "netdrv_device"
+
+-- 加载http应用功能模块
+require "http_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 90 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,90 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi0,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air780EXX 核心板外挂AirETH_1000硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_ETH then
+        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- GPIO20为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(20, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    local result = exnetif.set_priority_order({
+    {
+        ETHUSER1 = {
+                    -- 供电使能GPIO
+                    pwrpin = 20,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=0, cs=8}
+        }
+    }
+})
+    --初始化以太网卡
+
+    --以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    --各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 0, 片选 GPIO8
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=0, cs=8})
+
+    -- 在以太网上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 79 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_multiple.lua

@@ -0,0 +1,79 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为多网卡驱动模块,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air780EXX. 核心板外挂AirETH_1000硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air780EXX核心板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO
+                    pwrpin = 20,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=0, cs=8}
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

+ 25 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/netdrv_device.lua

@@ -0,0 +1,25 @@
+--[[
+@module  netdrv_device
+@summary 网络驱动设备功能模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
+1、netdrv_ethernet_spi:socket.LWIP_ETH,通过SPI外挂CH390H芯片的以太网卡;
+2、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
+
+根据自己的项目需求,只需要require以上其中的一种即可;
+
+
+本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
+]]
+
+
+-- 根据自己的项目需求,只需要require以下其中的一种即可;
+
+-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
+require "netdrv_eth_spi"
+
+-- 加载“可以配置优先级的多种网卡”驱动模块
+-- require "netdrv_multiple"

+ 75 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/http/readme.md

@@ -0,0 +1,75 @@
+## 功能模块介绍:
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:加载网络驱动设备功能模块;
+
+3、http_lua:加载http应用模块;
+
+## 演示功能概述
+
+1、以太网给模组供网,通过连接http测试连通。
+
+## 演示硬件环境
+
+1、Air780EXX核心板一块+可上网的sim卡一张+网线一根+AirETH_1000板子一个;
+
+[](https://docs.openLuat.com/cdn/image/780EGH_AirETH1000.jpg)
+
+<img title="" src="https://docs.openLuat.com/cdn/image/780EGH_AirETH1000.jpg" alt="lan" style="zoom:33%;">
+
+2、TYPE-C USB数据线一根 + 杜邦线若干;
+
+* Air780EXX核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+* AirETH_1000板子网口与路由器网口通过网线连接;
+
+3、Air780EXX核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air780EXX核心板 | AirETH_1000配件板 |
+| ------------ | -------------- |
+| 3.3V         | 3.3v           |
+| gnd          | gnd            |
+| spi0_sclk    | SCK            |
+| spi0_cs      | CSS            |
+| spi0_miso    | SDO            |
+| spi0_mosi    | SDI            |
+| gpio21       | INT            |
+
+演示软件环境
+------
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2012版本固件]([固件版本 - luatos@air780epm - 合宙模组资料中心](https://docs.openluat.com/air780epm/luatos/firmware/version/))、[Air780EHV V2012版本固件]([固件版本 - luatos@air780ehv - 合宙模组资料中心](https://docs.openluat.com/air780ehv/luatos/firmware/version/))、[Air780EGH V2012版本固件]([固件版本 - luatos@air780egh - 合宙模组资料中心](https://docs.openluat.com/air780egh/luatos/firmware/version/)(理论上,2025年7月26日之后发布的固件都可以)
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件。
+
+2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口(需要在main.lua文件中打开require"netdrv_device"和require"http_app")
+
+3、启动设备,观察日志输出:
+
+出现类似如下打印,就表示成功。
+
+```
+[2025-09-18 11:31:29.931][000000003.367] I/user.netdrv_eth_spi.ip_ready_func IP_READY 192.168.0.52 255.255.255.0 192.168.0.1 nil
+
+[2025-09-18 11:31:29.934][000000003.368] I/user.http_app_task_func recv IP_READY 4 
+
+[2025-09-18 11:31:29.938][000000003.372] dns_run 676:www.air32.cn state 0 id 1 ipv6 0 use dns server0, try
+
+[2025-09-18 11:31:29.942][000000003.372] D/net adatper 4 dns server 192.168.0.1
+
+[2025-09-18 11:31:29.944][000000003.372] D/net dns udp sendto 192.168.0.1:53 from 192.168.0.52
+
+[2025-09-18 11:31:29.948][000000003.384] dns_run 693:dns all done ,now stop
+
+[2025-09-18 11:31:29.950][000000003.385] D/net connect 49.232.89.122:443 TCP
+
+[2025-09-18 11:31:30.641][000000004.123] I/user.http_app_get1 success 200 {"Transfer-Encoding":"chunked","Date":"Thu, 18 Sep 2025 03:31:33 GMT","Connection":"keep-alive","Server":"openresty\/1.27.1.2","Content-Type":"text\/html"} 2416
+
+```

+ 14 - 12
module/Air8000/demo/accessory_board/AirETH_1000/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/main.lua

@@ -2,11 +2,11 @@
 @module  main
 @summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
 @version 1.0
-@date    2025.09.17
+@date    2025.09.22
 @author  王城钧
 @usage
 本demo演示的核心功能为:
-1.设置三种以太网小板工作模式,一种为以太网提供网络供模组上网,一种为设置模组连接4G网络通过以太网口传输给其他设备,一种为以太网提供网络供wifi和以太网设备上网
+1.设置多网融合功能,4G提供网络供以太网设备上网
 更多说明参考本目录下的readme.md文件
 ]]
 --[[
@@ -19,7 +19,7 @@ VERSION:项目版本号,ascii string类型
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
 ]]
-PROJECT = "Air_ETH1000"
+PROJECT = "4g_out_ethernet_in"
 VERSION = "001.000.000"
 
 
@@ -51,19 +51,21 @@ end
 -- 也可以使用客户自己搭建的平台进行远程升级
 -- 远程升级的详细用法,可以参考fota的demo进行使用
 
--- 加载网络驱动设备功能模块
--- require "netdrv_device"
 
--- 开启多网融合功能,4G提供网络供以太网和wifi设备上网
-require "4g-eth-wifi"
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
 
--- 开启多网融合功能,WIFI提供网络供以太网和wifi设备上网 
--- require "wifi-eth-wifi"
 
--- 加载http应用功能模块
--- require "http_app"
 
--- 用户代码已结束
+-- 开启多网融合功能
+require "netif_app"
+
+-- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句
 sys.run()
 -- sys.run()之后后面不要加任何语句!!!!!

+ 48 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/netif_app.lua

@@ -0,0 +1,48 @@
+--[[
+@module  netif_app
+@summary netif_app 网络管理模块,开启多网融合功能,4G提供网络供以太网设备上网
+@version 1.0
+@date    2025.09.22
+@author  王城钧
+@usage
+本文件为网络管理模块,核心业务逻辑为:
+1、设置多网融合功能,4G提供网络供以太网设备上网
+2、http测试4G网络
+本文件没有对外接口,直接在main.lua中require "netif_app"就可以加载运行;
+]] 
+exnetif = require "exnetif"
+
+function netif_app_task_func()
+    local res
+    -- 等待4G网络连接成功
+    while not socket.adapter() do
+        -- 在此处阻塞等待4G网卡连接成功的消息"IP_READY"
+        -- 或者等待1秒超时退出阻塞等待状态;
+        -- 注意:此处的1000毫秒超时不要修改的更长;
+        sys.waitUntil("IP_READY", 1000)
+    end
+    -- 设置多网融合功能,4G提供网络供以太网设备上网
+    res = exnetif.setproxy(socket.LWIP_ETH, socket.LWIP_GP, {
+        ethpower_en = 20,                   -- 以太网模块的pwrpin引脚(gpio编号)
+        tp = netdrv.CH390,                  -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+        opts = {spi = 0, cs = 8},           -- 外挂方式,需要额外的参数(选填参数),仅spi方式外挂以太网时需要填写。
+        adapter_addr = "192.168.2.1",       -- 自定义LWIP_ETH网卡的ip地址(选填),需要自定义ip和网关ip时填写
+        adapter_gw = {192, 168, 2, 1}       -- 自定义LWIP_ETH网卡的网关地址(选填),需要自定义ip和网关ip时填写
+    })
+
+    if res then
+        log.info("exnetif", "setproxy success")
+    else
+        log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
+    end
+
+    -- 每5秒进行HTTPS连接测试,实时监测4G网络连接状态, 仅供测试需要,量产不需要,用来判断当前网络是否可用,需要的话可以打开注释
+    -- while 1 do
+    --     local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil,
+    --         { adapter = socket.LWIP_GP, timeout = 5000, debug = false }).wait()
+    --     log.info("http执行结果", code, headers, body and #body)
+    --     sys.wait(10000)
+    -- end
+end
+
+sys.taskInit(netif_app_task_func)

+ 60 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/readme.md

@@ -0,0 +1,60 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netif_app: 网络管理模块,开启多网融合功能,4G提供网络供以太网设备上网;
+
+## 演示功能概述
+
+1、开启多网融合模式,4G连接外部网络,以太网lan模式为其他以太网设备提供接入
+
+2、​网络监控​,每5秒进行HTTPS连接测试,实时监测4G网络的连接状态
+
+## 演示硬件环境
+
+<img src="https://docs.openLuat.com/cdn/image/780EGH_AirETH1000.jpg" title="" alt="" style="zoom:33%;">
+
+1、Air780EXX 核心板一块+可上网的sim卡一张+网线一根:
+
+- sim卡插入核心板的sim卡槽
+
+- 网线一端插入核心板外接的AirETH_1000小板上,另外一端连接需接入以太网的设备
+
+2、TYPE-C USB数据线一根 + 网线一根,Air780EXX核心板和数据线的硬件接线方式为:
+
+- Air780EXX 本核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+- 核心板与AirETH_1000的接线方式如下:
+  
+  | Air780EXX核心板 | AirETH_1000配件板 |
+  | ------------ | -------------- |
+  | vdd          | 3.3v           |
+  | gnd          | gnd            |
+  | spi0_sclk    | SCK            |
+  | spi0_cs      | CSS            |
+  | spi0_miso    | SDO            |
+  | spi0_mosi    | SDI            |
+  | gpio21       | INT            |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2012版本固件]([固件版本 - luatos@air780epm - 合宙模组资料中心](https://docs.openluat.com/air780epm/luatos/firmware/version/))、[Air780EHV V2012版本固件]([固件版本 - luatos@air780ehv - 合宙模组资料中心](https://docs.openluat.com/air780ehv/luatos/firmware/version/))、[Air780EGH V2012版本固件]([固件版本 - luatos@air780egh - 合宙模组资料中心](https://docs.openluat.com/air780egh/luatos/firmware/version/)(理论上,2025年7月26日之后发布的固件都可以)
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件,
+
+2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口,netif_app.lua:网络管理模块
+
+3、启动设备,观察日志输出:
+
+```lua
+[INFO] exnetif setproxy success
+[INFO] http执行结果 200 ... 
+```
+
+4、其他设备通过以太网接入780EXX,其他设备都能正常上网,则表示验证成功。

+ 11 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirETH_1000/readme.md

@@ -0,0 +1,11 @@
+本文件下共有三个示例demo
+
+1、http:通过外挂AirETH_1000小板使用WAN功能或者多网切换模式连接http测试网络连通性。
+
+2、network_routing:
+
+(1)4g_out_ethernet_in
+
+使用网络路由功能,4G提供网络供以太网设备上网
+
+

+ 459 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/AirGPIO_1000.lua

@@ -0,0 +1,459 @@
+--本文件中的主机是指I2C主机,具体指Air780EHV
+--本文件中的从机是指I2C从机,具体指AirGPIO_1000配件板上的IO扩展芯片
+
+local AirGPIO_1000 = 
+{
+    -- i2c_id:主机的i2c id;
+    -- gpio_int_id:主机的GPIO中断引脚id;
+    -- slave_address:从机地址;
+    -- ints =    --从机各个扩展IO配置为中断时的处理函数以及上一次的输入电平
+    -- {
+    --     [0x00] = {cb_func=, old_level=},
+    --     [0x01] = {cb_func=, old_level=},
+    --     [0x02] = {cb_func=, old_level=},
+    --     [0x03] = {cb_func=, old_level=},
+    --     [0x04] = {cb_func=, old_level=},
+    --     [0x05] = {cb_func=, old_level=},
+    --     [0x06] = {cb_func=, old_level=},
+    --     [0x07] = {cb_func=, old_level=},
+
+    --     [0x10] = {cb_func=, old_level=},
+    --     [0x11] = {cb_func=, old_level=},
+    --     [0x12] = {cb_func=, old_level=},
+    --     [0x13] = {cb_func=, old_level=},
+    --     [0x14] = {cb_func=, old_level=},
+    --     [0x15] = {cb_func=, old_level=},
+    --     [0x16] = {cb_func=, old_level=},
+    --     [0x17] = {cb_func=, old_level=},
+    -- }
+}
+
+-- 从机硬件上有三个引脚A0 A1 A2可以配置I2C从设备的地址
+-- 当A0 A1 A2都接地时的从设备基地址为(0x40 >> 1)
+-- A0 A1 A2有0到7一共八种排序组合,基地址+0/1/2/3/4/5/6/7即为八种从设备的地址
+-- AirGPIO_1000默认A0 A1 A2都接地,所以AirGPIO_1000的从设备地址默认也为(0x40 >> 1)
+local SALVE_ADDRESS_HIGH_4BIT = (0x40 >> 1)
+
+-- 寄存器地址
+local REG_INPUT_PORT_0 = 0x00    -- 输入端口0
+local REG_INPUT_PORT_1 = 0x01    -- 输入端口1
+local REG_OUTPUT_PORT_0 = 0x02   -- 输出端口0
+local REG_OUTPUT_PORT_1 = 0x03   -- 输出端口1
+local REG_POL_INV_0 = 0x04       -- 极性反转端口0
+local REG_POL_INV_1 = 0x05       -- 极性反转端口1
+local REG_CONFIG_0 = 0x06        -- 配置端口0
+local REG_CONFIG_1 = 0x07        -- 配置端口1
+
+
+-- 写入AirGPIO_1000的寄存器
+
+--reg:number类型;
+--         表示AirGPIO_1000上的寄存器地址;
+--         取值范围:0x00到0x07,参考本文件上方的寄存器地址列表;
+--         必须传入,不允许为空;
+
+--value:number类型;
+--         表示要写入到AirGPIO_1000寄存器中的数据;
+--         取值范围:0x00到0xFF,1个字节的长度;
+--         必须传入,不允许为空;
+
+--返回值:成功返回true,失败返回false
+local function write_register(reg, value)
+    local data = {reg, value}
+    local result = i2c.send(AirGPIO_1000.i2c_id, AirGPIO_1000.slave_address, data)
+    return result
+end
+
+-- 读取AirGPIO_1000的寄存器
+
+--reg:number类型;
+--         表示AirGPIO_1000上的寄存器地址;
+--         取值范围:0x00到0x07,参考本文件上方的寄存器地址列表;
+--         必须传入,不允许为空;
+
+--返回值:成功返回1个字节的number类型,失败返回nil
+local function read_register(reg)
+    i2c.send(AirGPIO_1000.i2c_id, AirGPIO_1000.slave_address, reg)
+    local data = i2c.recv(AirGPIO_1000.i2c_id, AirGPIO_1000.slave_address, 1)
+    if data and #data == 1 then
+        return string.byte(data, 1)
+    end
+    return nil
+end
+
+
+--主机上的中断引脚处理函数
+local function gpio_int_callback()
+    log.info("gpio_int_callback")
+    --在中断处理函数中不能直接执行耗时较长的动作
+    --所以在此处publish一个"AirGPIO_1000_INT"消息
+    --在其他位置订阅这个消息,进行异步处理
+    --异步处理这个消息的函数可以直接执行耗时较长的动作
+    sys.publish("AirGPIO_1000_INT")
+end
+
+--遍历用户扩展GPIO中断函数表,进行处理
+local function user_gpio_int_callback()
+    if AirGPIO_1000.ints then
+        --遍历用户扩展GPIO中断函数表
+        for k,v in pairs(AirGPIO_1000.ints) do
+            if v then
+                --读取扩展GPIO的输入电平
+                local cur_level = AirGPIO_1000.get(k)
+                --如果输入电平和上一次输入电平不一致
+                --则执行用户扩展GPIO中断函数
+                if v.old_level~=cur_level then
+                    v.old_level = cur_level
+                    if v.cb_func then v.cb_func(k, cur_level) end
+                end
+            end
+        end
+    end
+end
+
+--订阅"AirGPIO_1000_INT"消息的处理函数user_gpio_int_callback
+--当其他位置publish "AirGPIO_1000_INT"消息时,会执行user_gpio_int_callback
+sys.subscribe("AirGPIO_1000_INT", user_gpio_int_callback)
+
+--检查AirGPIO_1000上的扩展GPIO ID是否有效
+
+--gpio_id:number类型;
+--         表示AirGPIO_1000上的扩展GPIO ID;
+--         取值范围:0x00到0x07,0x10到0x17,一共16种,分别对应16个扩展GPIO引脚;
+--         必须传入,不允许为空;
+
+--返回值:有效返回true,无效返回false
+local function check_gpio_id_valid(gpio_id)
+    return (gpio_id>=0x00 and gpio_id<=0x07 or gpio_id>=0x10 and gpio_id<=0x17)
+end
+
+--配置主机和AirGPIO_1000之间的通信参数;
+
+--i2c_id:number类型;
+--        主机使用的I2C ID,用来控制AirGPIO_1000;
+--        取值范围:仅支持0和1;
+--        如果没有传入此参数,则默认为0;
+--int_id:number类型;
+--        主机使用的中断引脚GPIO ID,和AirGPIO_1000上的INT引脚相连;
+--        AirGPIO_1000可以扩展出来16个GPIO,这些GPIO支持配置为输入;
+--        AirGPIO_1000上的任意一个输入GPIO的状态发生上升沿或者下降沿变化时,会通过INT引脚通知到主机的int_id中断引脚;
+--        此时主机可以通过I2C接口立即读取AirGPIO_1000上配置为输入模式的扩展GPIO的电平状态,从而判断是哪些扩展GPIO的输入电平发生了变化;
+--        取值范围:nil或者空,或者0到9,或者12到55,注意不要使用已经复用为其他功能的引脚;nil或者空时,表示不使用中断通知功能;
+--        如果没有传入此参数,则默认为空,表示不使用中断通知功能;
+
+--返回值:成功返回true,失败返回false
+function AirGPIO_1000.init(i2c_id, gpio_int_id)
+    --检查参数的合法性
+    if not (i2c_id == 0 or i2c_id == 1) then
+        log.error("AirGPIO_1000.init", "invalid i2c_id", i2c_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
+
+    --初始化I2C
+    if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
+        log.error("AirGPIO_1000.init", "i2c.setup error", i2c_id)
+        return false
+    end
+
+    --自动识别从设备地址
+    --AirGPIO_1000上使用的TCA9555芯片有三个引脚,A2 A1 A0,可以配置三个bit的I2C从设备地址
+    --从 0 0 0 到 1 1 1,也就是十进制的0到7,一共可以配置8种;
+    --依次读取这8个从设备地址上的一个寄存器地址数据
+    --如果返回应答数据,则从设备地址自动识别成功
+    for i=0,7 do
+        i2c.send(i2c_id, SALVE_ADDRESS_HIGH_4BIT+i, REG_INPUT_PORT_0)
+        local data = i2c.recv(i2c_id, SALVE_ADDRESS_HIGH_4BIT+i, 1)
+        if data~=nil then
+            AirGPIO_1000.slave_address = SALVE_ADDRESS_HIGH_4BIT+i
+            log.error("AirGPIO_1000.init", "slave_address", SALVE_ADDRESS_HIGH_4BIT+i, data:byte())
+            break
+        end
+    end
+
+    --自动识别从设备地址失败
+    if not AirGPIO_1000.slave_address then
+        log.error("AirGPIO_1000.init", "slave_address unknown")
+        i2c.close(i2c_id)
+        return false
+    end
+
+
+    --配置主机上的中断GPIO,用来实时检测从机上扩展GPIO的输入电平变化
+    if gpio_int_id then
+        gpio.setup(gpio_int_id, gpio_int_callback, gpio.PULLUP, gpio.FALLING)
+    end
+
+    return true
+end
+
+--关闭主机和AirGPIO_1000之间的通信;
+
+--返回值:成功返回true,失败返回false
+function AirGPIO_1000.deinit()
+    --关闭主机I2C
+    if AirGPIO_1000.i2c_id then
+        i2c.close(AirGPIO_1000.i2c_id)
+        AirGPIO_1000.i2c_id = nil
+        AirGPIO_1000.slave_address = nil
+    end
+
+    --关闭主机中断GPIO
+    if AirGPIO_1000.gpio_int_id then
+        gpio.close(AirGPIO_1000.gpio_int_id)
+        AirGPIO_1000.gpio_int_id = nil
+    end
+
+    --清空用户注册的扩展GPIO中断处理表
+    if type(AirGPIO_1000.ints)=="table" then
+        for k,v in pairs(AirGPIO_1000.ints) do
+            AirGPIO_1000.ints[k] = nil
+        end        
+        AirGPIO_1000.ints = nil
+    end
+end
+
+--[[
+配置AirGPIO_1000上的扩展GPIO管脚功能;
+支持配置为输出,输入和中断三种模式;
+
+@api AirGPIO_1000.setup(gpio_id, gpio_mode)
+
+@number
+gpio_id
+表示AirGPIO_1000上的扩展GPIO ID;
+取值范围:0x00到0x07,0x10到0x17,一共16种,分别对应16个扩展GPIO引脚;
+必须传入,不允许为空或者nil;
+
+@number or function or nil or 空
+gpio_mode
+number类型时,表示输出模式,取值范围为0和1,0表示默认输出低电平,1表示默认输出高电平;
+nil或者空类型时,表示输入模式;
+function类型时,表示中断模式,此function为中断回调函数,函数的定义格式如下:
+function cb_func(id, level)
+    --id:表示触发中断的AirGPIO_1000上的扩展GPIO ID,取值范围为0x00到0x07,0x10到0x17,一共16种,分别对应16个扩展GPIO引脚;
+    --level:触发中断后,某一时刻,扩展GPIO输入的电平状态,高电平为1, 低电平为0;并不是指触发中断的电平状态;
+end
+
+@return bool
+成功返回true,失败返回false
+
+@usage
+-- GPIO ID 0x00配置为输出模式,默认输出低电平
+AirGPIO_1000.setup(0x00, 0)
+
+-- GPIO ID 0x11配置为输入模式
+AirGPIO_1000.setup(0x11)
+
+
+--P04引脚中断处理函数
+--id:0x04
+--level:触发中断后,某一时刻,扩展GPIO输入的电平状态,高电平为1, 低电平为0
+local function P04_int_cbfunc(id, level)
+    log.info("P04_int_cbfunc", id, level)
+end
+
+-- GPIO ID 0x04配置为中断模式,中断处理函数为P04_int_cbfunc
+AirGPIO_1000.setup(0x04, P04_int_cbfunc)
+]]
+function AirGPIO_1000.setup(gpio_id, gpio_mode)
+    --检查参数的合法性
+    if not check_gpio_id_valid(gpio_id) then
+        log.error("AirGPIO_1000.setup", "invalid gpio_id", gpio_id)
+        return false
+    end
+
+    if not (gpio_mode==0 or gpio_mode==1 or gpio_mode==nil or type(gpio_mode)=="function") then
+        log.error("AirGPIO_1000.setup", "invalid gpio_mode", type(gpio_mode), gpio_mode)
+        return false
+    end    
+
+    log.info("AirGPIO_1000.setup", "enter", gpio_id, type(gpio_mode), gpio_mode)
+
+
+    --根据扩展GPIO ID识别当前扩展GPIO使用的配置寄存器地址
+    --0x0x开头的ID为REG_CONFIG_0,0x01开头的ID为REG_CONFIG_1
+    local reg_addr = ((gpio_id>>4) == 0) and REG_CONFIG_0 or REG_CONFIG_1
+    --读取从机中输出寄存器当前的值
+    local reg_data = read_register(reg_addr)
+
+    if reg_data==nil then
+        log.error("AirGPIO_1000.setup", "read config register error", reg_addr)
+        return false
+    end    
+
+    local mask = 1<<(gpio_id&0x0F)
+    local value
+    --GPIO配置为输出模式
+    if gpio_mode==0 or gpio_mode==1 then
+        value = reg_data & (~mask)
+    --GPIO配置为输入模式
+    elseif gpio_mode==nil or type(gpio_mode)=="function" then
+        value = reg_data | mask
+    end
+
+    --如果寄存器新值和旧值相比,发生变化
+    --写新值到从机的配置寄存器中
+    if reg_data~=value then
+        if not write_register(reg_addr, value) then
+            log.error("AirGPIO_1000.setup", "config write error", reg_addr, value)
+            return false
+        end
+    end
+
+    log.info("AirGPIO_1000.setup", "config", reg_addr, reg_data, value)
+
+    --如果是中断模式,并且用户注册了中断处理函数
+    if type(gpio_mode)=="function" then
+        if AirGPIO_1000.ints==nil then
+            AirGPIO_1000.ints = {}
+        end
+        if AirGPIO_1000.ints[gpio_id]==nil then
+            AirGPIO_1000.ints[gpio_id] = {}
+        end
+        --存储中断处理函数
+        AirGPIO_1000.ints[gpio_id].cb_func = gpio_mode
+        --读取当前时刻GPIO的输入电平状态
+        AirGPIO_1000.ints[gpio_id].old_level = AirGPIO_1000.get(gpio_id)
+    end
+
+    --如果配置的是输入模式或者中断模式,可以直接返回了
+    if gpio_mode~=0 and gpio_mode~=1 then return true end
+
+
+    --如果配置的输出模式,初始化输出的电平为gpio_mode
+    if not AirGPIO_1000.set(gpio_id, gpio_mode) then
+        log.error("AirGPIO_1000.setup", "output set error")
+        return false
+    end
+
+    log.info("AirGPIO_1000.setup", "output", reg_addr, reg_data, value)
+
+    return true    
+end
+
+
+
+--设置AirGPIO_1000上配置为输出模式的扩展GPIO的输出电平
+
+--gpio_id:number类型;
+--         表示AirGPIO_1000上的扩展GPIO ID;
+--         取值范围:0x00到0x07,0x10到0x17,一共16种,分别对应16个扩展GPIO引脚;
+--         必须传入,不允许为空;
+--output_level:number类型;
+--              表示配置为输出模式的扩展GPIO对外输出的电平;
+--              取值范围:0和1,0表示输出低电平,1表示输出高电平;
+--              必须传入,不允许为空;
+
+--返回值:成功返回true,失败返回false
+function AirGPIO_1000.set(gpio_id, output_level)
+    --检查参数的合法性
+    if not check_gpio_id_valid(gpio_id) then
+        log.error("AirGPIO_1000.set", "invalid gpio_id", gpio_id)
+        return false
+    end
+
+    if not (output_level==0 or output_level==1) then
+        log.error("AirGPIO_1000.set", "invalid output_level", type(output_level), output_level)
+        return false
+    end    
+
+    log.info("AirGPIO_1000.set", "enter", gpio_id, output_level)
+
+    --根据扩展GPIO ID识别当前扩展GPIO使用的输出寄存器地址
+    --0x0x开头的ID为REG_OUTPUT_PORT_0,0x01开头的ID为REG_OUTPUT_PORT_1
+    local reg_addr = ((gpio_id>>4) == 0) and REG_OUTPUT_PORT_0 or REG_OUTPUT_PORT_1
+    --读取从机中输出寄存器当前的值
+    local reg_data = read_register(reg_addr)
+
+    if reg_data==nil then
+        log.error("AirGPIO_1000.set", "read output register error", reg_addr)
+        return false
+    end    
+
+    local mask = 1<<(gpio_id&0x0F)
+    local value
+
+    --输出低电平
+    if output_level==0 then
+        value = reg_data & (~mask)
+    --输出高电平
+    elseif output_level==1 then
+        value = reg_data | mask
+    end
+
+    --如果寄存器新值和旧值相比,发生变化
+    --写新值到从机的输出寄存器中
+    if reg_data~=value then
+        if not write_register(reg_addr, value) then
+            log.error("AirGPIO_1000.set", "output write error", reg_addr, value)
+            return false
+        end
+    end
+
+    log.info("AirGPIO_1000.set", "output", reg_addr, reg_data, value)
+
+    return true
+end
+
+
+--读取AirGPIO_1000上配置为输入或者中断模式的扩展GPIO的输入电平
+
+--gpio_id:number类型;
+--         表示AirGPIO_1000上的扩展GPIO ID;
+--         取值范围:0x00到0x07,0x10到0x17,一共16种,分别对应16个扩展GPIO引脚;
+--         必须传入,不允许为空;
+
+--返回值:number类型,表示输入的电平,0表示低电平,1表示高电平;如果读取失败,返回false
+function AirGPIO_1000.get(gpio_id)
+    --检查参数的合法性
+    if not check_gpio_id_valid(gpio_id) then
+        log.error("AirGPIO_1000.get", "invalid gpio_id", gpio_id)
+        return false
+    end
+
+    --根据扩展GPIO ID识别当前扩展GPIO使用的输入寄存器地址
+    --0x0x开头的ID为REG_INPUT_PORT_0,0x01开头的ID为REG_INPUT_PORT_1
+    local reg_addr = ((gpio_id>>4) == 0) and REG_INPUT_PORT_0 or REG_INPUT_PORT_1
+    --读取从机中输入寄存器当前的值
+    local value = read_register(reg_addr)
+
+    if not value then
+        log.error("AirGPIO_1000.get", "read_register error", reg_addr)
+        return false
+    end
+
+    --返回输入寄存器的值和GPIO对应的bit位的值
+    return ((value>>(gpio_id&0x0F)) & 0x01)
+end
+
+
+
+--关闭AirGPIO_1000上的扩展GPIO功能
+--实际上是恢复为默认状态(配置为输入)
+
+--gpio_id:number类型;
+--         表示AirGPIO_1000上的扩展GPIO ID;
+--         取值范围:0x00到0x07,0x10到0x17,一共16种,分别对应16个扩展GPIO引脚;
+--         必须传入,不允许为空;
+
+--返回值:成功返回true,失败返回false
+function AirGPIO_1000.close(gpio_id)
+    local result = AirGPIO_1000.setup(gpio_id)
+
+    if not result then
+        log.error("AirGPIO_1000.close", "error", gpio_id)
+    end
+
+    return result
+end
+
+
+return AirGPIO_1000

+ 96 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/gpio_app.lua

@@ -0,0 +1,96 @@
+--加载AirGPIO_1000驱动文件
+local air_gpio = require "AirGPIO_1000"
+
+
+--AirGPIO_1000扩展GPIO输出测试
+--P00每隔一秒切换输出一次高低电平,可以通过示波器或者万用表测量AirGPIO_1000上P00引脚电平
+local function gpio_output_task_func()
+    air_gpio.setup(0x00, 0)
+
+    while true do
+        air_gpio.set(0x00, 0)
+        sys.wait(1000)
+        air_gpio.set(0x00, 1)
+        sys.wait(1000)
+    end
+end
+
+
+--AirGPIO_1000扩展GPIO输入测试
+--P10配置为输出模式,每隔一秒切换输出一次高低电平
+--P11配置为输入模式,每隔一秒调用get接口读取一次输入的电平
+--将P10和P11两个引脚短接
+local function gpio_input_task_func()
+    air_gpio.setup(0x10, 0)
+    air_gpio.setup(0x11)
+
+    while true do
+        air_gpio.set(0x10, 0)
+        sys.wait(1000)
+        log.info("air_gpio.get(0x11)", air_gpio.get(0x11))
+        air_gpio.set(0x10, 1)
+        sys.wait(1000)
+        log.info("air_gpio.get(0x11)", air_gpio.get(0x11))
+    end
+end
+
+--P04引脚中断处理函数
+--id:0x04
+--level:触发中断后,某一时刻,扩展GPIO输入的电平状态,高电平为1, 低电平为0
+local function P04_int_cbfunc(id, level)
+    log.info("P04_int_cbfunc", id, level)
+end
+
+--P14引脚中断处理函数
+--id:0x14
+--level:触发中断后,某一时刻,扩展GPIO输入的电平状态,高电平为1, 低电平为0
+local function P14_int_cbfunc(id, level)
+    log.info("P14_int_cbfunc", id, level)
+end
+
+--AirGPIO_1000扩展GPIO中断测试
+--P03配置为输出模式,每隔一秒切换输出一次高低电平
+--P04配置为中断模式,并且配置中断处理函数P04_int_cbfunc
+--将P03和P04两个引脚短接
+--P13配置为输出模式,每隔一秒切换输出一次高低电平
+--P14配置为中断模式,并且配置中断处理函数P14_int_cbfunc
+--将P13和P14两个引脚短接
+local function gpio_int_task_func()
+    air_gpio.setup(0x03, 0)
+    air_gpio.setup(0x04, P04_int_cbfunc)
+
+    air_gpio.setup(0x13, 0)
+    air_gpio.setup(0x14, P14_int_cbfunc)
+
+    while true do
+        air_gpio.set(0x03, 0)
+        air_gpio.set(0x13, 0)
+        sys.wait(1000)
+        air_gpio.set(0x03, 1)
+        air_gpio.set(0x13, 1)
+        sys.wait(1000)
+    end
+end
+
+
+--初始化Air780EHV和AirGPIO_1000之间的通信参数
+--使用Air780EHV的I2C1
+--使用Air780EHV的GPIO2做为中断引脚
+--Air780EHV核心板和AirGPIO_1000配件板的接线方式如下
+--Air780EHV核心板             AirGPIO_1000配件板
+--      3V3-----------------3V3
+--       GND-----------------GND
+--     66/I2C1SDA-----------------SDA
+--     67/I2C1SCL-----------------SCL
+--  23/GPIO2-----------------INT
+air_gpio.init(1, 2)
+
+--AirGPIO_1000的GPIO输出测试
+sys.taskInit(gpio_output_task_func)
+
+--AirGPIO_1000的GPIO输入测试
+sys.taskInit(gpio_input_task_func)
+
+--AirGPIO_1000的GPIO中断测试
+sys.taskInit(gpio_int_task_func)
+

+ 6 - 8
module/Air8101/demo/accessory_board/AirETH_1000/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/main.lua

@@ -8,12 +8,13 @@ VERSION:项目版本号,ascii string类型
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
 
-AirETH_1000是合宙设计生产的一款搭载CH390H芯片的以太网配件板;
+AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
 本demo演示的核心功能为:
-Air8101核心板+AirETH_1000配件板,使用配件板上的以太网口通过网线连接路由器,演示以太网数传功能;
+Air8101核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
+分输出、输入和中断三种应用场景来演示;
 更多说明参考本目录下的readme.md文件
 ]]
-PROJECT = "AirETH_1000"
+PROJECT = "AirGPIO_1000"
 VERSION = "001.000.000"
 
 
@@ -53,11 +54,8 @@ end
 --     log.info("mem.sys", rtos.meminfo("sys"))
 --  end, 3000)
 
-
--- 加载以太网连接管理功能模块
-require "net_app"
--- 加载http get应用的功能模块
-require "http_app"
+ -- 加载gpio应用模块
+ require "gpio_app"
 
 
 -- 用户代码已结束---------------------------------------------

+ 120 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/readme.md

@@ -0,0 +1,120 @@
+
+## 演示功能概述
+
+AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
+
+本demo演示的核心功能为:
+
+Air780EHV核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
+
+分输出、输入和中断三种应用场景来演示;
+
+
+## 核心板+配件板资料
+
+[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/accessory/AirGPIO_1000/image/connect_Air780EHV.jpg)
+
+1、Air780EHV核心板
+
+2、AirGPIO_1000配件板
+
+3、母对母的杜邦线8根
+
+4、Air780EHV核心板和AirGPIO_1000配件板的硬件接线方式为
+
+| Air780EHV核心板 | AirGPIO_1000配件板 |
+| ------------ | ------------------ |
+|     3V3     |         3V3        |
+|     GND   |         GND        |
+| 66/I2C1SDA |         SDA        |
+| 67/I2C1SCL |         SCL        |
+|   23/GPIO2   |         INT        |
+
+- 扩展GPIO输出演示时,无需接线;通过万用表或者示波器检测AirGPIO_1000配件板上的P00电平即可
+
+- 扩展GPIO输入演示时,将AirGPIO_1000配件板上的P10和P11两个引脚通过杜邦线短接;软件上会将P10配置为输出(第一秒输出低电平,第二秒输出高电平,如此循环输出),将P11配置为输入,通过检测P11引脚输入电平的状态来演示
+
+- 扩展GPIO中断演示时,将AirGPIO_1000配件板上的P03和P04两个引脚通过杜邦线短接,将AirGPIO_1000配件板上的P13和P14两个引脚通过杜邦线短接;软件上会将P03和P13配置为输出(第一秒输出低电平,第二秒输出高电平,如此循环输出),将P04和P14配置为中断,通过检测中断函数的触发状态来演示
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+   (1) 通过万用表或者示波器检测AirGPIO_1000配件板上的P00电平,持续1秒输出0V的低电平,持续1秒输出3.3V的高电平,循环输出,表示GPIO输出测试正常;
+
+   (2) 通过观察Luatools的运行日志,首先打印 air_gpio.get(0x11) 0, 再隔一秒打印 air_gpio.get(0x11) 1,再隔一秒打印 air_gpio.get(0x11) 0,如此循环输出,表示GPIO输入测试正常;
+
+   (3) 通过观察Luatools的运行日志,首先打印 P04_int_cbfunc 4 0      P14_int_cbfunc 20 0, 再隔一秒打印  P04_int_cbfunc 4 1      P14_int_cbfunc 20 1,再隔一秒打印 P04_int_cbfunc 4 0      P14_int_cbfunc 20 0,如此循环输出,表示GPIO中断测试正常;
+
+```
+[2025-09-22 16:38:56.466][000000004.279] I/user.air_gpio.get(0x11) 1
+[2025-09-22 16:38:56.472][000000004.279] I/user.AirGPIO_1000.set enter 16 0
+[2025-09-22 16:38:56.477][000000004.281] I/user.AirGPIO_1000.set output 3 255 254
+[2025-09-22 16:38:56.482][000000004.281] I/user.gpio_int_callback
+[2025-09-22 16:38:56.488][000000004.294] I/user.AirGPIO_1000.set enter 3 0
+[2025-09-22 16:38:56.493][000000004.295] I/user.AirGPIO_1000.set output 2 254 246
+[2025-09-22 16:38:56.498][000000004.295] I/user.AirGPIO_1000.set enter 19 0
+[2025-09-22 16:38:56.504][000000004.296] I/user.AirGPIO_1000.set output 3 254 246
+[2025-09-22 16:38:56.510][000000004.297] I/user.gpio_int_callback
+[2025-09-22 16:38:56.515][000000004.298] I/user.P04_int_cbfunc 4 0
+[2025-09-22 16:38:56.519][000000004.299] I/user.P14_int_cbfunc 20 0
+[2025-09-22 16:38:57.453][000000005.273] I/user.AirGPIO_1000.set enter 0 1
+[2025-09-22 16:38:57.468][000000005.274] I/user.AirGPIO_1000.set output 2 246 247
+[2025-09-22 16:38:57.482][000000005.281] I/user.air_gpio.get(0x11) 0
+[2025-09-22 16:38:57.490][000000005.281] I/user.AirGPIO_1000.set enter 16 1
+[2025-09-22 16:38:57.498][000000005.283] I/user.AirGPIO_1000.set output 3 246 247
+[2025-09-22 16:38:57.503][000000005.283] I/user.gpio_int_callback
+[2025-09-22 16:38:57.509][000000005.297] I/user.AirGPIO_1000.set enter 3 1
+[2025-09-22 16:38:57.514][000000005.298] I/user.AirGPIO_1000.set output 2 247 255
+[2025-09-22 16:38:57.519][000000005.298] I/user.AirGPIO_1000.set enter 19 1
+[2025-09-22 16:38:57.522][000000005.299] I/user.AirGPIO_1000.set output 3 247 255
+[2025-09-22 16:38:57.527][000000005.300] I/user.gpio_int_callback
+[2025-09-22 16:38:57.531][000000005.301] I/user.P04_int_cbfunc 4 1
+[2025-09-22 16:38:57.534][000000005.302] I/user.P14_int_cbfunc 20 1
+[2025-09-22 16:38:58.462][000000006.274] I/user.AirGPIO_1000.set enter 0 0
+[2025-09-22 16:38:58.475][000000006.275] I/user.AirGPIO_1000.set output 2 255 254
+[2025-09-22 16:38:58.489][000000006.283] I/user.air_gpio.get(0x11) 1
+[2025-09-22 16:38:58.499][000000006.283] I/user.AirGPIO_1000.set enter 16 0
+[2025-09-22 16:38:58.507][000000006.285] I/user.AirGPIO_1000.set output 3 255 254
+[2025-09-22 16:38:58.515][000000006.285] I/user.gpio_int_callback
+[2025-09-22 16:38:58.526][000000006.300] I/user.AirGPIO_1000.set enter 3 0
+[2025-09-22 16:38:58.530][000000006.301] I/user.AirGPIO_1000.set output 2 254 246
+[2025-09-22 16:38:58.535][000000006.301] I/user.AirGPIO_1000.set enter 19 0
+[2025-09-22 16:38:58.540][000000006.302] I/user.AirGPIO_1000.set output 3 254 246
+[2025-09-22 16:38:58.544][000000006.303] I/user.gpio_int_callback
+[2025-09-22 16:38:58.550][000000006.304] I/user.P04_int_cbfunc 4 0
+[2025-09-22 16:38:58.555][000000006.305] I/user.P14_int_cbfunc 20 0
+[2025-09-22 16:38:59.459][000000007.275] I/user.AirGPIO_1000.set enter 0 1
+[2025-09-22 16:38:59.471][000000007.276] I/user.AirGPIO_1000.set output 2 246 247
+[2025-09-22 16:38:59.486][000000007.285] I/user.air_gpio.get(0x11) 0
+[2025-09-22 16:38:59.497][000000007.285] I/user.AirGPIO_1000.set enter 16 1
+[2025-09-22 16:38:59.512][000000007.287] I/user.AirGPIO_1000.set output 3 246 247
+[2025-09-22 16:38:59.520][000000007.287] I/user.gpio_int_callback
+[2025-09-22 16:38:59.525][000000007.303] I/user.AirGPIO_1000.set enter 3 1
+[2025-09-22 16:38:59.531][000000007.304] I/user.AirGPIO_1000.set output 2 247 255
+[2025-09-22 16:38:59.537][000000007.304] I/user.AirGPIO_1000.set enter 19 1
+[2025-09-22 16:38:59.544][000000007.305] I/user.AirGPIO_1000.set output 3 247 255
+[2025-09-22 16:38:59.548][000000007.306] I/user.gpio_int_callback
+[2025-09-22 16:38:59.552][000000007.307] I/user.P04_int_cbfunc 4 1
+[2025-09-22 16:38:59.562][000000007.308] I/user.P14_int_cbfunc 20 1
+
+```
+

+ 119 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua

@@ -0,0 +1,119 @@
+--本文件中的主机是指I2C主机,具体指Air780EHV
+--本文件中的从机是指I2C从机,具体指AirSHT30_1000配件板上的sht30温湿度传感器芯片
+
+local AirSHT30_1000 = 
+{
+    -- i2c_id:主机的i2c id;
+}   
+
+-- 从机地址为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
+    for i = 1, #data do
+        crc = bit.bxor(crc, data[i])
+        for j = 1, 8 do
+            crc = crc * 2
+            if crc >= 0x100 then
+                crc = bit.band(bit.bxor(crc, 0x31), 0xff)
+            end
+        end
+    end
+    return crc
+end
+
+
+--打开AirSHT30_1000;
+
+--i2c_id:number类型;
+--        主机使用的I2C ID,用来控制AirSHT30_1000;
+--        取值范围:仅支持0和1;
+--        如果没有传入此参数,则默认为0;
+
+--返回值:成功返回true,失败返回false
+function AirSHT30_1000.open(i2c_id)
+    --如果i2c_id为nil,则赋值为默认值0
+    if i2c_id==nil then i2c_id=0 end
+
+    --检查参数的合法性
+    if not (i2c_id == 0 or i2c_id == 1) then
+        log.error("AirSHT30_1000.open", "invalid i2c_id", i2c_id)
+        return false
+    end
+
+    AirSHT30_1000.i2c_id = i2c_id
+    
+    --初始化I2C
+    if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
+        log.error("AirSHT30_1000.open", "i2c.setup error", i2c_id)
+        return false
+    end
+
+    return true
+end
+
+--读取温湿度数据;
+
+--返回值:失败返回false;
+--       成功返回两个值,第一个为摄氏温度值(number类型,例如23.6表示23.6摄氏度),第二个为百分比湿度值(number类型,例如67表示67%的湿度)
+function AirSHT30_1000.read()
+
+    -- 发送启动测量命令(高精度)
+    i2c.send(AirSHT30_1000.i2c_id, slave_addr, {0x24, 0x00})
+        
+    -- 等待测量完成(SHT30高精度测量需~15ms)
+    sys.wait(20)
+    
+    -- 读取6字节数据(温度高/低 + CRC,湿度高/低 + CRC)
+    local data = i2c.recv(AirSHT30_1000.i2c_id, slave_addr, 6)
+
+    -- 如果没有读取到6字节数据
+    if type(data)~="string" or data:len()~=6 then
+        log.error("AirSHT30_1000.read", "i2c.recv error")
+        return false
+    end
+
+    -- log.info("AirSHT30_1000.read", data:toHex())
+
+    --如果校验值正确
+    if crc8({data:byte(1), data:byte(2)}) == data:byte(3) and crc8({data:byte(4), data:byte(5)}) == data:byte(6) then 
+        -- 提取原始温度值
+        local temp_raw = (data:byte(1) << 8) | data:byte(2)
+        -- 提取原始湿度值
+        local hum_raw = (data:byte(4) << 8) | data:byte(5)
+        
+        -- 转换为实际值(根据SHT30数据手册公式)
+        local temprature = (-45 + 175 * temp_raw / 65535.0)
+        local humidity = (100 * hum_raw / 65535.0)
+        
+        -- 打印输出结果(保留2位小数)
+        -- log.info("AirSHT30_1000.read", "temprature", string.format("%.2f ℃", temprature))
+        -- log.info("AirSHT30_1000.read", "temprature", string.format("%.2f %%RH", humidity))
+
+        return temprature, humidity
+    else
+        log.error("AirSHT30_1000.read", "crc error", i2c_id)
+        return false
+    end
+end
+
+
+--关闭AirSHT30_1000
+
+--返回值:成功返回true,失败返回false
+function AirSHT30_1000.close()
+    --close接口没有返回值,理论上不会关闭失败
+    i2c.close(AirSHT30_1000.i2c_id)
+
+    return true
+end
+
+
+return AirSHT30_1000

+ 5 - 7
module/Air8101/demo/accessory_board/AirPHY_1000/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/main.lua

@@ -8,12 +8,12 @@ VERSION:项目版本号,ascii string类型
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
 
-AirPHY_1000是合宙设计生产的一款搭载LAN8720Ai芯片的以太网配件板;
+AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
 本demo演示的核心功能为:
-Air8101核心板+AirPHY_1000配件板,使用配件板上的以太网口通过网线连接路由器,演示以太网数传功能
+Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据
 更多说明参考本目录下的readme.md文件
 ]]
-PROJECT = "AirPHY_1000"
+PROJECT = "AirSHT30_1000"
 VERSION = "001.000.000"
 
 
@@ -53,10 +53,8 @@ end
 --     log.info("mem.sys", rtos.meminfo("sys"))
 --  end, 3000)
 
--- 加载以太网连接管理功能模块
-require "phy_app"
--- 加载http get应用的功能模块
-require "http_app"
+ -- 加载sht30应用模块
+ require "sht30_app"
 
 
 -- 用户代码已结束---------------------------------------------

+ 57 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/readme.md

@@ -0,0 +1,57 @@
+
+## 演示功能概述
+
+AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
+
+本demo演示的核心功能为:
+
+Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+
+
+## 核心板+配件板资料
+
+[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/accessory/AirSHT30_1000/image/connect_780ehv.png)
+
+1、Air780EHV核心板
+
+2、AirSHT30_1000配件板
+
+3、母对母的杜邦线4根
+
+| Air780EHV核心板 | AirSHT30_1000配件板|
+| ------------ | ------------------ |
+|     3V3     |         3V3        |
+|     GND   |         GND        |
+|  66/I2C1SDA  |         SDA        |
+| 67/I2C1SCL |         SCL        |
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、通过观察Luatools的运行日志,每隔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
+
+

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

@@ -0,0 +1,34 @@
+--加载AirSHT30_1000驱动文件
+local air_sht30 = require "AirSHT30_1000"
+
+
+--每隔1秒读取一次温湿度数据
+local function read_sht30_task_func()
+    --打开sht30硬件
+    air_sht30.open(1)
+
+    while true do
+        --读取温湿度数据
+        local temprature, humidity = air_sht30.read()
+
+        --读取成功
+        if temprature then
+            -- 打印输出结果(保留2位小数)
+            log.info("read_sht30_task_func", "temprature", string.format("%.2f ℃", temprature))
+            log.info("read_sht30_task_func", "humidity", string.format("%.2f %%RH", humidity))
+        --读取失败
+        else
+            log.error("read_sht30_task_func", "read error")
+        end
+
+        --等待1秒
+        sys.wait(1000)
+    end
+
+    --关闭sht30硬件
+    air_sht30.close()
+end
+
+--创建一个task,并且运行task的主函数read_sht30_task_func
+sys.taskInit(read_sht30_task_func)
+

+ 63 - 36
module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/extalk.lua

@@ -51,6 +51,7 @@ local SUCC = "success"
 local g_state = SP_T_NO_READY   -- 设备状态
 local g_mqttc = nil             -- mqtt客户端
 local g_local_id                -- 本机ID
+local g_stask_start = false                -- 本机ID
 local g_remote_id               -- 对端ID
 local g_s_type                  -- 对讲的模式,字符串形式
 local g_s_topic                 -- 对讲用的topic
@@ -76,6 +77,24 @@ local function check_param(param, expected_type, name)
     return true
 end
 
+-- MQTT消息发布函数,集中处理所有发布操作并打印日志
+local function publish_message(topic, payload)
+    if g_mqttc then
+        log.info("MQTT发布 - 主题:", topic, "内容:", payload)
+        g_mqttc:publish(topic, payload)
+    else
+        log.error("MQTT客户端未初始化,无法发布消息")
+    end
+end
+
+
+-- 对讲超时处理
+function extalk.wait_speech_to()
+    log.info("主动请求对讲超时无应答")
+    extalk.speech_off(true, false)
+end
+
+
 -- 发送鉴权消息
 local function auth()
     if g_state == SP_T_NO_READY and g_mqttc then
@@ -84,19 +103,22 @@ local function auth()
             ["key"] = extalk_configs_local.key, 
             ["device_type"] = 1
         })
-        g_mqttc:publish(topic, payload)
+        publish_message(topic, payload)
     end
 end
 
 -- 发送心跳消息
 local function heart()
-    if g_state == SP_T_CONNECTED and g_mqttc then
+    if  g_mqttc then
+        adc.open(adc.CH_VBAT)
+        local vbat = adc.get(adc.CH_VBAT)
+        adc.close(adc.CH_VBAT)
         local topic = string.format("ctrl/uplink/%s/0005", g_local_id)
         local payload = json.encode({
-            ["from"] = g_local_id, 
-            ["to"] = g_remote_id
+            ["csq"] = mobile.csq(), 
+            ["battery"] = vbat
         })
-        g_mqttc:publish(topic, payload)
+        publish_message(topic, payload)
     end
 end
 
@@ -109,12 +131,12 @@ local function speech_on(ssrc, sample)
     log.info("对讲模式", g_s_mode)
     airtalk.speech(true, g_s_mode, sample)
     sys.sendMsg(AIRTALK_TASK_NAME, MSG_SPEECH_ON_IND, true) 
-    sys.timerLoopStart(heart, extalk_configs_local.heart_break_time * 1000)
-    sys.timerStopAll(wait_speech_to)
+    -- sys.timerLoopStart(heart, extalk_configs_local.heart_break_time * 1000)
+    sys.timerStopAll(extalk.wait_speech_to)
 end
 
 -- 结束对讲
-local function speech_off(need_upload, need_ind)
+function extalk.speech_off(need_upload, need_ind)
     if g_state == SP_T_CONNECTED then
         g_mqttc:unsubscribe(g_s_topic)
         airtalk.speech(false)
@@ -123,12 +145,12 @@ local function speech_off(need_upload, need_ind)
     
     g_state = SP_T_IDLE
     sys.timerStopAll(auth)
-    sys.timerStopAll(heart)
-    sys.timerStopAll(wait_speech_to)
+
+    sys.timerStopAll(extalk.wait_speech_to)
     
     if need_upload and g_mqttc then
         local topic = string.format("ctrl/uplink/%s/0004", g_local_id)
-        g_mqttc:publish(topic, json.encode({["to"] = g_remote_id}))
+        publish_message(topic, json.encode({["to"] = g_remote_id}))
     end
 
     if need_ind then
@@ -136,11 +158,6 @@ local function speech_off(need_upload, need_ind)
     end
 end
 
--- 对讲超时处理
-local function wait_speech_to()
-    log.info("主动请求对讲超时无应答")
-    speech_off(true, false)
-end
 
 -- 命令处理:请求对讲应答
 local function handle_speech_response(obj)
@@ -171,7 +188,7 @@ local function handle_incoming_call(obj)
             ["topic"] = obj and obj["topic"] or "", 
             ["info"] = "无效的请求参数"
         }
-        g_mqttc:publish(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
+        publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
         return
     end
 
@@ -183,7 +200,7 @@ local function handle_incoming_call(obj)
             ["topic"] = obj["topic"], 
             ["info"] = "device is busy"
         }
-        g_mqttc:publish(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
+        publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
         return
     end
 
@@ -197,7 +214,7 @@ local function handle_incoming_call(obj)
             ["topic"] = obj["topic"], 
             ["info"] = "topic error"
         }
-        g_mqttc:publish(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
+        publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
         return
     end
 
@@ -242,7 +259,7 @@ local function handle_incoming_call(obj)
     end
 
     -- 发送响应
-    g_mqttc:publish(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
+    publish_message(string.format("ctrl/uplink/%s/8102", g_local_id), json.encode(response))
 end
 
 -- 命令处理:对端挂断
@@ -255,13 +272,13 @@ local function handle_remote_hangup(obj)
         log.info("0103", obj, obj["type"], g_s_type)
         if obj and obj["type"] == g_s_type then
             response = {["result"] = SUCC, ["info"] = ""}
-            speech_off(false, true)
+            extalk.speech_off(false, true)
         else
             response = {["result"] = "failed", ["info"] = "type mismatch"}
         end
     end
     
-    g_mqttc:publish(string.format("ctrl/uplink/%s/8103", g_local_id), json.encode(response))
+    publish_message(string.format("ctrl/uplink/%s/8103", g_local_id), json.encode(response))
 end
 
 -- 命令处理:更新设备列表
@@ -274,16 +291,18 @@ local function handle_device_list_update(obj)
         response = {["result"] = "failed", ["info"] = "json info error"}
     end
     
-    g_mqttc:publish(string.format("ctrl/uplink/%s/8101", g_local_id), json.encode(response))
+    publish_message(string.format("ctrl/uplink/%s/8101", g_local_id), json.encode(response))
 end
 
 -- 命令处理:鉴权结果
 local function handle_auth_result(obj)
     if obj and obj["result"] == SUCC then
-        g_mqttc:publish(string.format("ctrl/uplink/%s/0002", g_local_id), "")  -- 更新列表
+        publish_message(string.format("ctrl/uplink/%s/0002", g_local_id), "")  -- 更新列表
+        sys.timerLoopStart(heart, extalk_configs_local.heart_break_time * 1000)   --  发起心跳
     else
         sys.sendMsg(AIRTALK_TASK_NAME, MSG_AUTH_IND, false, 
             "鉴权失败" .. (obj and obj["info"] or "")) 
+        log.error("鉴权失败,可能是没有修改PRODUCT_KEY")
     end
 end
 
@@ -344,7 +363,7 @@ local function mqtt_cb(mqttc, event, topic, payload)
                     "订阅失败" .. "ctrl/downlink/" .. g_local_id .. "/#") 
             end
         elseif g_state == SP_T_CONNECTED and not topic then
-            speech_off(false, true)
+            extalk.speech_off(false, true)
         end
     elseif event == "recv" then
         local result = string.match(topic, g_dl_topic)
@@ -353,17 +372,17 @@ local function mqtt_cb(mqttc, event, topic, payload)
             analyze_v1(result, topic, obj)
         end
     elseif event == "disconnect" then
-        speech_off(false, true)
+        extalk.speech_off(false, true)
         g_state = SP_T_NO_READY
     elseif event == "error" then
-        log.error("MQTT错误发生")
+        log.error("MQTT错误发生",topic,payload)
     end
 end
 
 -- 任务消息处理
 local function task_cb(msg)
     if msg[1] == MSG_SPEECH_CONNECT_TO then
-        speech_off(true, false)
+        extalk.speech_off(true, false)
     else
         log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
     end
@@ -373,15 +392,21 @@ end
 local function airtalk_event_cb(event, param)
     log.info("airtalk event", event, param)
     if event == airtalk.EVENT_ERROR then
-        if param == airtalk.ERROR_NO_DATA then
+        if param == airtalk.ERROR_NO_DATA  and g_s_mode == airtalk.MODE_PERSON then
             log.error("长时间没有收到音频数据")
-            speech_off(true, true)
+            extalk.speech_off(true, true)
         end
     end
 end
 
 -- MQTT任务主循环
 local function airtalk_mqtt_task()
+    if g_stask_start  then
+        log.info("airtalk task 已经初始化了")
+        return true
+    end
+    
+    g_stask_start = true
     local msg, online = nil, false
     
     -- 初始化本地ID
@@ -445,7 +470,7 @@ local function airtalk_mqtt_task()
                     if g_state ~= SP_T_CONNECTING and g_state ~= SP_T_CONNECTED then
                         log.info("没有对讲", g_state)
                     else
-                        speech_off(true, false)
+                        extalk.speech_off(true, false)
                     end
                 elseif msg[1] == MSG_SPEECH_ON_IND then
                     if extalk_configs_local.state_cbfnc then
@@ -473,6 +498,7 @@ end
 
 -- 模块初始化
 function extalk.setup(extalk_configs)
+
     if not extalk_configs or type(extalk_configs) ~= "table" then
         log.error("AirTalk配置必须为table类型")
         return false
@@ -506,6 +532,7 @@ end
 
 -- 开始对讲
 function extalk.start(id)
+
     if g_state ~= SP_T_IDLE then
         log.warn("正在对讲无法开始,当前状态:", g_state)
         return false
@@ -520,9 +547,9 @@ function extalk.start(id)
         g_s_topic = string.format("audio/%s/all/%s", 
             g_local_id, string.sub(tostring(mcu.ticks()), -4, -1))
         
-        g_mqttc:publish(string.format("ctrl/uplink/%s/0003", g_local_id), 
+        publish_message(string.format("ctrl/uplink/%s/0003", g_local_id), 
             json.encode({["topic"] = g_s_topic, ["type"] = g_s_type}))
-        sys.timerStart(wait_speech_to, 15000)
+        sys.timerStart(extalk.wait_speech_to, 15000)
     else
         -- 一对一模式
         log.info("向", id, "主动发起对讲")
@@ -538,9 +565,9 @@ function extalk.start(id)
         g_s_topic = string.format("audio/%s/%s/%s", 
             g_local_id, id, string.sub(tostring(mcu.ticks()), -4, -1))
         
-        g_mqttc:publish(string.format("ctrl/uplink/%s/0003", g_local_id), 
+        publish_message(string.format("ctrl/uplink/%s/0003", g_local_id), 
             json.encode({["topic"] = g_s_topic, ["type"] = g_s_type}))
-        sys.timerStart(wait_speech_to, 15000)
+        sys.timerStart(extalk.wait_speech_to, 15000)
     end
     
     return true
@@ -554,7 +581,7 @@ function extalk.stop()
     end
 
     log.info("主动断开对讲")
-    speech_off(true, false)
+    extalk.speech_off(true, false)
     return true
 end
 

+ 46 - 201
module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/main.lua

@@ -1,213 +1,58 @@
 --[[
-    演示airtalk基本功能
-    按一次boot,开始1对1对讲,再按一次boot,结束对讲
-    按一次powerkey,开始1对多对讲,再按一次powerkey或者boot,结束对讲
-]]
-
-PROJECT = "airtalk_demo"
-VERSION = "1.0.2"
-PRODUCT_KEY = "29uptfBkJMwFC7x7QeW10UPO3LecPYFu" -- 到 iot.openluat.com 创建项目,获取正确的项目id
-
--- 引入必要模块
-
-local extalk = require "extalk"
-local exaudio = require "exaudio"
-
--- 配置日志格式
-log.style(1)
-
--- 常量定义
-local USER_TASK_NAME = "user_task"
-local MSG_KEY_PRESS = 12  -- 按键消息
-
--- 全局状态变量
-local g_dev_list = nil    -- 设备列表
-local g_speech_active = false  -- 对讲状态标记
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.09.19
+@author  梁健
+@usage
+本demo演示的核心功能为:
 
--- 音频初始化参数
-local audio_setup_param = {
-    model = "es8311",       -- 音频编解码类型,可填入"es8311","es8211"
-    i2c_id = 0,             -- i2c_id,可填入0,1 并使用pins工具配置对应的管脚
-    pa_ctrl = gpio.AUDIOPA_EN,          -- 音频放大器电源控制管脚
-    dac_ctrl = 20,         -- 音频编解码芯片电源控制管脚    
-}
-
--- 联系人列表回调函数
-local function contact_list_callback(dev_list)
-    g_dev_list = dev_list
-    if dev_list and #dev_list > 0 then
-        log.info("联系人列表更新:")
-        for i = 1, #dev_list do
-            log.info(string.format("  %d. ID: %s, 名称: %s", 
-                i, dev_list[i]["id"], dev_list[i]["name"] or "未知"))
-        end
-    else
-        log.info("联系人列表为空")
-    end
-end
-
--- 对讲状态回调函数
-local function speech_state_callback(event_table)
-    if not event_table then return end
-    
-    if event_table.state == extalk.START then
-        log.info("对讲开始,可以说话了")
-        g_speech_active = true
-    elseif event_table.state == extalk.STOP then
-        log.info("对讲结束")
-        g_speech_active = false
-    elseif event_table.state == extalk.UNRESPONSIVE then
-        log.info("对端未响应")
-        g_speech_active = false
-    elseif event_table.state == extalk.ONE_ON_ONE then
-        g_speech_active = true
-        
-        local dev_name = "未知设备"
-        if g_dev_list then
-            for i = 1, #g_dev_list do
-                if g_dev_list[i]["id"] == event_table.id then
-                    dev_name = g_dev_list[i]["name"] or "未知设备"
-                    break
-                end
-            end
-        end
-        log.info(string.format("%s 来电", dev_name))
-    elseif event_table.state == extalk.BROADCAST then
-        g_speech_active = true
-        
-        local dev_name = "未知设备"
-        if g_dev_list then
-            for i = 1, #g_dev_list do
-                if g_dev_list[i]["id"] == event_table.id then
-                    dev_name = g_dev_list[i]["name"] or "未知设备"
-                    break
-                end
-            end
-        end
-        log.info(string.format("%s 开始广播", dev_name))
-    end
-end
-
--- extalk配置参数
-local extalk_configs = {
-    key = PRODUCT_KEY,
-    heart_break_time = 120,  -- 心跳间隔(单位秒)
-    contact_list_cbfnc = contact_list_callback,
-    state_cbfnc = speech_state_callback,
-}
-
--- 按键回调函数 - Boot键
-local function boot_key_callback()
-    sys.sendMsg(USER_TASK_NAME, MSG_KEY_PRESS, false)  -- false表示Boot键
-end
+airtalk.lua: 进行airtalk 对讲业务,相关使用说明,请见https://docs.openluat.com/value/airtalk/
 
--- 按键回调函数 - Power键
-local function power_key_callback()
-    sys.sendMsg(USER_TASK_NAME, MSG_KEY_PRESS, true)   -- true表示Power键
-end
-
--- 初始化按键
-local function init_buttons()
-    -- 配置Boot键 (GPIO0)
-    gpio.setup(0, boot_key_callback, gpio.PULLDOWN, gpio.RISING)
-    gpio.debounce(0, 200, 1)  -- 200ms去抖
-    
-    -- 配置Power键
-    gpio.setup(gpio.PWR_KEY, power_key_callback, gpio.PULLUP, gpio.FALLING)
-    gpio.debounce(gpio.PWR_KEY, 200, 1)  -- 200ms去抖
-end
-
--- 查找第一个可用的对端设备ID
-local function find_first_remote_device()
-    if not g_dev_list or #g_dev_list == 0 then
-        log.warn("没有找到可用的设备")
-        return nil
-    end
-    
-    local local_id = mobile.imei()
-    for i = 1, #g_dev_list do
-        local dev_id = g_dev_list[i]["id"]
-        if dev_id and dev_id ~= local_id then
-            return dev_id
-        end
-    end
-    
-    log.warn("没有找到其他可用设备")
-    return nil
-end
+更多说明参考本目录下的readme.md文件
+]]
 
--- 处理按键消息
-local function handle_key_press(is_power_key)
-    if g_speech_active then
-        -- 当前正在对讲,按任何键都结束对讲
-        log.info("结束当前对讲")
-        extalk.stop()
-        g_speech_active = false
-    else
-        -- 当前未在对讲,根据按键类型开始不同对讲
-        if is_power_key then
-            -- Power键:开始一对多广播
-            log.info("开始一对多广播")
-            extalk.start()  -- 不带参数表示广播
-        else
-            -- Boot键:开始一对一对讲
-            log.info("开始一对一对讲")
-            local remote_id = find_first_remote_device()
-            if remote_id then
-                extalk.start(remote_id)
-            else
-                log.error("无法开始一对一对讲,没有找到可用设备")
-            end
-        end
-    end
-end
+--[[
+必须定义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可直接在Air8000整机开发板上运行
+]]
 
+PROJECT = "audio"
+VERSION = "1.0.0"
+PRODUCT_KEY =  "NrkXcjWwjcc5EFdCrrYnvypBCyJlEaIO"
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+exnetif = require("exnetif")
 
--- 用户主任务
-local function user_main_task()
-    -- 初始化音频
-    local audio_init_ok = exaudio.setup(audio_setup_param)
-    if not audio_init_ok then
-        log.error("音频初始化失败")
-        return
-    end
-    log.info("音频初始化成功")
-    
-    -- 初始化extalk
-    local extalk_init_ok = extalk.setup(extalk_configs)
-    if not extalk_init_ok then
-        log.error("extalk初始化失败")
-        return
-    end
-    log.info("extalk初始化成功")
-    
-    -- 等待按键消息并处理
-    while true do
-        local msg = sys.waitMsg(USER_TASK_NAME, MSG_KEY_PRESS)
-        if msg and msg[1] == MSG_KEY_PRESS then
-            handle_key_press(msg[2])  -- msg[2]区分是Power键(true)还是Boot键(false)
-        end
-    end
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
 
--- 初始化按键
-init_buttons()
-
--- 启动用户任务
-sys.taskInitEx(user_main_task, USER_TASK_NAME)
+require "talk"            --  启动airtalk
 
--- 内存监控任务
-sys.taskInit(function()
-    while true do
-        sys.wait(60000)  -- 每分钟检查一次
-        log.info("系统状态监控:")
-        log.info("  时间:", os.time())
-        log.info("  Lua内存:", rtos.meminfo("lua"))
-        log.info("  系统内存:", rtos.meminfo("sys"))
-        log.info("  PSRAM内存:", rtos.meminfo("psram"))
-    end
-end)
+-- 音频对内存影响较大,不断的打印内存,用于判断是否异常
+sys.timerLoopStart(function()
+    log.info("mem.lua", rtos.meminfo())
+    log.info("mem.sys", rtos.meminfo("sys"))
+ end, 3000)
 
--- 启动系统
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
 sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!
+

+ 74 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/readme.md

@@ -0,0 +1,74 @@
+## 总体设计框图
+
+ 
+
+
+
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、talk.lua:airtalk 运行主程序
+
+## 常量的介绍
+
+1. extalk.START = 1     -- 通话开始
+2. extalk.STOP = 2      -- 通话结束
+3. extalk.UNRESPONSIVE = 3  -- 对端未响应
+4. extalk.ONE_ON_ONE = 5  -- 一对一来电
+5. extalk.BROADCAST = 6 -- 广播
+
+
+## 演示功能概述
+
+1.    按一次boot,选择群组内第一个联系人,开始1对1对讲,再按一次boot,结束对讲
+2.    按一次powerkey,开始1对多广播,再按一次powerkey或者boot,结束对讲
+
+
+## 演示硬件环境
+
+![](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 的全部文件(可不包含readme)
+
+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),不断输出录音的数据地址和录音长度,供给应用层调用
+
+
+

+ 205 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/airtalk/talk.lua

@@ -0,0 +1,205 @@
+--[[
+    演示airtalk基本功能
+    按一次boot,开始1对1对讲,再按一次boot,结束对讲
+    按一次powerkey,开始1对多对讲,再按一次powerkey或者boot,结束对讲
+]]
+
+-- 引入必要模块
+
+local extalk = require "extalk"
+local exaudio = require "exaudio"
+
+-- 配置日志格式
+log.style(1)
+
+-- 常量定义
+local USER_TASK_NAME = "user_task"
+local MSG_KEY_PRESS = 12  -- 按键消息
+
+-- 全局状态变量
+local g_dev_list = nil    -- 设备列表
+local g_speech_active = false  -- 对讲状态标记
+
+-- 音频初始化参数
+local audio_setup_param = {
+    model = "es8311",       -- 音频编解码类型,可填入"es8311","es8211"
+    i2c_id = 0,             -- i2c_id,可填入0,1 并使用pins工具配置对应的管脚
+    pa_ctrl = 162,          -- 音频放大器电源控制管脚
+    dac_ctrl = 164,         -- 音频编解码芯片电源控制管脚    
+}
+
+-- 联系人列表回调函数
+local function contact_list_callback(dev_list)
+    g_dev_list = dev_list
+    if dev_list and #dev_list > 0 then
+        log.info("联系人列表更新:")
+        for i = 1, #dev_list do
+            log.info(string.format("  %d. ID: %s, 名称: %s", 
+                i, dev_list[i]["id"], dev_list[i]["name"] or "未知"))
+        end
+    else
+        log.info("联系人列表为空")
+    end
+end
+
+local gpio_number = 20 -- air8000 核心板上的23 管脚
+
+LED = gpio.setup(gpio_number, 1) -- 设置GPIO20为输出模式
+
+
+-- 对讲状态回调函数
+local function speech_state_callback(event_table)
+    if not event_table then return end
+    
+    if event_table.state == extalk.START then
+        log.info("对讲开始,可以说话了")
+        LED(1)
+        g_speech_active = true
+    elseif event_table.state == extalk.STOP then
+        LED(0)
+        log.info("对讲结束")
+        g_speech_active = false
+    elseif event_table.state == extalk.UNRESPONSIVE then
+        log.info("对端未响应")
+        g_speech_active = false
+    elseif event_table.state == extalk.ONE_ON_ONE then
+        g_speech_active = true
+        LED(1)
+        local dev_name = "未知设备"
+        if g_dev_list then
+            for i = 1, #g_dev_list do
+                if g_dev_list[i]["id"] == event_table.id then
+                    dev_name = g_dev_list[i]["name"] or "未知设备"
+                    break
+                end
+            end
+        end
+        log.info(string.format("%s 来电", dev_name))
+    elseif event_table.state == extalk.BROADCAST then
+        g_speech_active = true
+        LED(1)
+        local dev_name = "未知设备"
+        if g_dev_list then
+            for i = 1, #g_dev_list do
+                if g_dev_list[i]["id"] == event_table.id then
+                    dev_name = g_dev_list[i]["name"] or "未知设备"
+                    break
+                end
+            end
+        end
+        log.info(string.format("%s 开始广播", dev_name))
+    end
+end
+
+-- extalk配置参数
+local extalk_configs = {
+    key = PRODUCT_KEY,
+    heart_break_time = 120,  -- 心跳间隔(单位秒)
+    contact_list_cbfnc = contact_list_callback,
+    state_cbfnc = speech_state_callback,
+}
+
+-- 按键回调函数 - Boot键
+local function boot_key_callback()
+    log.info("boot_key_callback++++++")
+    sys.sendMsg(USER_TASK_NAME, MSG_KEY_PRESS, false)  -- false表示Boot键
+end
+
+-- 按键回调函数 - Power键
+local function power_key_callback()
+    log.info("power_key_callback++++++")
+    sys.sendMsg(USER_TASK_NAME, MSG_KEY_PRESS, true)   -- true表示Power键
+end
+
+-- 初始化按键
+local function init_buttons()
+    -- 配置Boot键 (GPIO0)
+    gpio.setup(0, boot_key_callback, gpio.PULLDOWN, gpio.RISING)
+    gpio.debounce(0, 200, 1)  -- 200ms去抖
+    
+    -- 配置Power键
+    gpio.setup(gpio.PWR_KEY, power_key_callback, gpio.PULLUP, gpio.FALLING)
+    gpio.debounce(gpio.PWR_KEY, 200, 1)  -- 200ms去抖
+end
+
+-- 查找第一个可用的对端设备ID
+local function find_first_remote_device()
+    if not g_dev_list or #g_dev_list == 0 then
+        log.warn("没有找到可用的设备")
+        return nil
+    end
+    
+    local local_id = mobile.imei()
+    for i = 1, #g_dev_list do
+        local dev_id = g_dev_list[i]["id"]
+        if dev_id and dev_id ~= local_id then
+            return dev_id
+        end
+    end
+    
+    log.warn("没有找到其他可用设备")
+    return nil
+end
+
+-- 处理按键消息
+local function handle_key_press(is_power_key)
+    if g_speech_active then
+        -- 当前正在对讲,按任何键都结束对讲
+        log.info("结束当前对讲")
+        extalk.stop()
+        g_speech_active = false
+    else
+        -- 当前未在对讲,根据按键类型开始不同对讲
+        if is_power_key then
+            -- Power键:开始一对多广播
+            log.info("开始一对多广播")
+            extalk.start()  -- 不带参数表示广播
+        else
+            -- Boot键:开始一对一对讲
+            log.info("开始一对一对讲")
+            local remote_id = find_first_remote_device()
+            if remote_id then
+                extalk.start(remote_id)
+            else
+                log.error("无法开始一对一对讲,没有找到可用设备")
+            end
+        end
+    end
+end
+
+
+
+-- 用户主任务
+local function user_main_task()
+    -- 初始化音频
+    local audio_init_ok = exaudio.setup(audio_setup_param)
+    if not audio_init_ok then
+        log.error("音频初始化失败")
+        return
+    end
+    log.info("音频初始化成功")
+    
+    -- 初始化extalk
+    local extalk_init_ok = extalk.setup(extalk_configs)
+    if not extalk_init_ok then
+        log.error("extalk初始化失败")
+        return
+    end
+    log.info("extalk初始化成功")
+    LED(0)
+    -- 等待按键消息并处理
+    while true do
+        local msg = sys.waitMsg(USER_TASK_NAME, MSG_KEY_PRESS)
+        if msg and msg[1] == MSG_KEY_PRESS then
+            handle_key_press(msg[2])  -- msg[2]区分是Power键(true)还是Boot键(false)
+        end
+    end
+end
+
+-- 初始化按键
+init_buttons()
+
+-- 启动用户任务
+sys.taskInitEx(user_main_task, USER_TASK_NAME)
+
+

+ 71 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in/main.lua

@@ -0,0 +1,71 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.08.05
+@author  魏健强
+@usage
+本demo演示的核心功能为:
+1.设置多网融合功能,4G提供网络供以太网设备上网
+更多说明参考本目录下的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 = "4g_out_ethernet_in"
+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 "netif_app"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in_wifi_in/netif_app.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in/netif_app.lua


+ 3 - 7
module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in_wifi_in/readme.md → module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in/readme.md

@@ -55,17 +55,13 @@
 
 1、搭建好硬件环境,按接线图连接硬件,
 
-2、按需修改WiFi热点配置(在netif_app.lua中):
-ssid = "AP热点名称"
-password = "AP热点密码"
+2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口,netif_app.lua:网络管理模块
 
-4、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口,netif_app.lua:网络管理模块
-
-5、启动设备,观察日志输出:
+3、启动设备,观察日志输出:
 
 ``` lua
 [INFO] exnetif setproxy success
 [INFO] http执行结果 200 ... 
 ```
 
-6、其他设备通过以太网接入780EXX,其他设备都能正常上网,则表示验证成功。
+4、其他设备通过以太网接入780EXX,其他设备都能正常上网,则表示验证成功。

+ 0 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/protobuf/main.lua

@@ -20,7 +20,6 @@ end
 
 sys.taskInit(function()
     sys.wait(500)
-    -- 如果没有这个库, 就云编译一份吧: https://wiki.luatos.com/develop/compile/Cloud_compilation.html
     if not protobuf then
         log.info("protobuf", "this demo need protobuf lib")
         return

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/u8g2/main.lua

@@ -81,7 +81,7 @@ sys.taskInit(function()
     end
 
     if chinese then
-    log.info("在显示屏显示中文", u8g2.DrawUTF8("中文测试", 40, 38)) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据, 可自行云编译一份. wiki.luatos.com 有文档.
+    log.info("在显示屏显示中文", u8g2.DrawUTF8("中文测试", 40, 38)) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据
         
     end
     log.info("将存储器帧缓冲区的内容发送到显示器", u8g2.SendBuffer())

+ 141 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/http/http_app.lua

@@ -0,0 +1,141 @@
+--[[
+@module  http_app
+@summary http应用功能模块
+@version 1.0
+@date    2025.09.17
+@author  王城钧
+@usage
+本文件为http应用功能模块,核心业务逻辑为:基于不同的应用场景,演示http核心库的使用方式;
+本文件没有对外接口,直接在main.lua中require "http_app"就可以加载运行;
+]]
+
+--[[
+此处先详细解释下http.request接口的使用方法
+
+接口定义:
+    http.request(method, url, headers, body, opts, server_ca_cert, client_cert, client_key, client_password)
+
+使用方法:
+    local code, headers, body = http.request(method, url, headers, body, opts, server_ca_cert, client_cert, client_key, client_password).wait()
+    只能在task中使用
+    发送http请求到服务器,等待服务器的http应答,此处会阻塞当前task,等待整个过程成功结束或者出现错误异常结束或者超时结束
+
+参数定义:
+    method,stirng类型,必须包含此参数,表示HTTP请求方法,支持"GET"、"POST"、"HEAD"等所有HTTP请求方法
+    url,string类型,必须包含此参数,表示HTTP请求URL地址,支持HTTP、HTTPS,支持域名、IP地址,支持自定义端口,标准的HTTP URL格式都支持
+    headers,table或者nil类型,可选包含此参数,表示HTTP请求头,例如 {["Content-Type"] = "application/x-www-form-urlencoded", ["self_defined_key"] = "self_defined_value"}
+    body,string或者zbuff或者nil类型,可选包含此参数,表示HTTP请求体,如果请求体是一个文件中的内容,要把文件内容读出来,赋值给body使用
+    opts,table或者nil类型,可选包含此参数,表示HTTP请求的一些额外配置,包含以下内容
+    {
+        timeout    -- -- number或者nil类型,单位毫秒,可选包含此参数,表示从发送请求到读取到服务器响应整个过程的超时时间,如果传入0,表示永久等待;如果没有传入此参数或者传入nil,则使用默认值10分钟
+        dst        -- 下载路径,string类型,当HTTP请求的数据需要保存到文件中时,此处填写完整的文件路径
+        adapter    -- 使用的网卡ID,number类型,例如4G网卡,SPI外挂以太网卡,WIFI网卡等;如果没有传入此参数,内核固件会自动选择当前时间点其他功能模块设置的默认网卡
+                    -- 除非你HTTP请求时,一定要使用某一种网卡,才设置此参数;如果没什么特别要求,不要使用此参数,使用系统中设置的默认网卡即可
+                    -- 这个参数和本demo中的netdrv_device.lua关系比较大,netdrv_device会设置默认网卡,此处http不要设置adapter参数,直接使用netdrv_device设置的默认网卡就行
+        debug      -- 调试开关,bool类型,true表示打开debug调试信息日志,false表示关闭debug调试信息日志,默认为关闭状态
+        ipv6       -- 是否为ipv6,bool类型,true表示使用ipv6,false表示不使用ipv6,默认为false
+        userdata   -- 下载回调函数使用的用户自定义回调参数,做为callback回调函数的第三个参数使用
+        callback   -- 下载回调函数,function类型,当下载数据时,无论是保存到内存中,还是保存到文件系统中,如果设置了callback,内核固件中每收到一包body数据,都会自动执行一次callback回调函数
+                    -- 回调函数的调用形式为callback(content_len, body_len, userdata)
+                    --     content_len:number类型,数据总长度
+                    --     body_len:number类型,已经下载的数据长度
+                    --     userdata:下载回调函数使用的用户自定义回调参数
+    }
+    server_ca_cert,string类型,服务器ca证书数据,可选包含此参数,当客户端需要验证服务器证书时,需要此参数,如果证书数据在一个文件中,要把文件内容读出来,赋值给server_ca_cert
+    client_cert,string类型,客户端证书数据,可选包含此参数,当服务器需要验证客户端证书时,需要此参数,如果证书数据在一个文件中,要把文件内容读出来,赋值给client_cert
+    client_key, string类型,客户端加密后的私钥数据,可选包含此参数,当服务器需要验证客户端证书时,需要此参数,如果加密后的私钥数据在一个文件中,要把文件内容读出来,赋值给client_key
+    client_password,string类型,客户端私钥口令数据,可选包含此参数,当服务器需要验证客户端证书时,需要此参数,如果私钥口令数据在一个文件中,要把文件内容读出来,赋值给client_password
+
+返回值定义:
+
+    http.request().wait()有三个返回值code,headers,body
+    code表示执行结果,number类型,有以下两种含义:
+        1、code大于等于100时,表示服务器返回的HTTP状态码,例如200表示成功,详细说明可以通过搜索引擎搜索“HTTP状态码”自行了解
+        2、code小于0时,表示内核固件中检测到通信异常,有如下几种:
+            -1 HTTP_ERROR_STATE 错误的状态, 一般是底层异常,请报issue
+            -2 HTTP_ERROR_HEADER 错误的响应头部, 通常是服务器问题
+            -3 HTTP_ERROR_BODY 错误的响应体,通常是服务器问题
+            -4 HTTP_ERROR_CONNECT 连接服务器失败, 未联网,地址错误,域名错误
+            -5 HTTP_ERROR_CLOSE 提前断开了连接, 网络或服务器问题
+            -6 HTTP_ERROR_RX 接收数据报错, 网络问题
+            -7 HTTP_ERROR_DOWNLOAD 下载文件过程报错, 网络问题或下载路径问题
+            -8 HTTP_ERROR_TIMEOUT 超时, 包括连接超时,读取数据超时
+            -9 HTTP_ERROR_FOTA fota功能报错,通常是更新包不合法
+    headers有以下两种含义:
+        1、当code的返回值大于等于100时,headers表示服务器返回的应答头,table类型
+        2、当code的返回值小于0时,headers为nil
+    body有以下三种含义
+        1、当code的返回值大于等于100时,如果请求的body数据不需要保存到文件中,而是直接保存到内存中,则body表示请求到的数据内容,string类型
+        2、当code的返回值大于等于100时,如果请求的body数据需要保存到文件中,则body表示保存请求数据后的文件的大小,number类型
+        3、当code的返回值小于0时,body为nil
+]]
+
+
+-- 普通的http get请求功能演示
+-- 请求的body数据保存到内存变量中,在内存够用的情况下,最大支持32KB的数据存储到内存中
+-- timeout可以设置超时时间
+-- callback可以设置回调函数,可用于实时检测body数据的下载进度
+local function http_app_get()
+    -- https get请求https://www.air32.cn/网页内容
+    -- 如果请求成功,请求的数据保存到body中
+    local code, headers, body = http.request("GET", "https://www.air32.cn/").wait()
+    log.info("http_app_get1",
+        code == 200 and "success" or "error",
+        code,
+        json.encode(headers or {}),
+        body and (body:len() > 512 and body:len() or body) or "nil")
+
+    -- https get请求https://www.luatos.com/网页内容,超时时间为10秒
+    -- 请求超时时间为10秒,用户自己写代码时,不要照抄10秒,根据自己业务逻辑的需要设置合适的超时时间
+    -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get2"
+    -- 如果请求成功,请求的数据保存到body中
+    code, headers, body = http.request("GET", "https://www.luatos.com/", nil, nil,
+        { timeout = 10000, userdata = "http_app_get2", callback = http_cbfunc }).wait()
+    log.info("http_app_get2",
+        code == 200 and "success" or "error",
+        code,
+        json.encode(headers or {}),
+        body and (body:len() > 512 and body:len() or body) or "nil")
+
+    -- http get请求http://httpbin.air32.cn/get网页内容,超时时间为3秒
+    -- 请求超时时间为3秒,用户自己写代码时,不要照抄3秒,根据自己业务逻辑的需要设置合适的超时时间
+    -- 回调函数为http_cbfunc,回调函数使用的第三个回调参数为"http_app_get3"
+    -- 如果请求成功,请求的数据保存到body中
+    code, headers, body = http.request("GET", "http://httpbin.air32.cn/get", nil, nil,
+        { timeout = 3000, userdata = "http_app_get3", callback = http_cbfunc }).wait()
+    log.info("http_app_get3",
+        code == 200 and "success" or "error",
+        code,
+        json.encode(headers or {}),
+        body and (body:len() > 512 and body:len() or body) or "nil")
+end
+
+-- http app task 的任务处理函数
+local function http_app_task_func()
+    while true do
+        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+        while not socket.adapter(socket.dft()) do
+            log.warn("http_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
+
+        -- 检测到了IP_READY消息
+        log.info("http_app_task_func", "recv IP_READY", socket.dft())
+
+        -- 普通的http get请求功能演示
+        http_app_get()
+
+        -- 50秒之后,循环测试
+        sys.wait(50000)
+    end
+end
+
+--创建并且启动一个task
+--运行这个task的处理函数http_app_task_func
+sys.taskInit(http_app_task_func)

+ 80 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/http/main.lua

@@ -0,0 +1,80 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.09.17
+@author  王城钧
+@usage
+本demo演示的核心功能为:
+1、分别使用http核心库和httpplus扩展库,演示以下这种应用场景的使用方式
+   (1) 普通的http get请求功能演示;
+2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(二选一)
+   (1) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+   (2) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+
+更多说明参考本目录下的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 = "HTTP"
+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 "netdrv_device"
+
+-- 加载http应用功能模块
+require "http_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 90 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,90 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi0,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air780EPM 核心板外挂AirETH_1000硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_ETH then
+        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- GPIO20为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(20, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    local result = exnetif.set_priority_order({
+    {
+        ETHUSER1 = {
+                    -- 供电使能GPIO
+                    pwrpin = 20,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=0, cs=8}
+        }
+    }
+})
+    --初始化以太网卡
+
+    --以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    --各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 0, 片选 GPIO8
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=0, cs=8})
+
+    -- 在以太网上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 79 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_multiple.lua

@@ -0,0 +1,79 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为多网卡驱动模块,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air780EPM 核心板外挂AirETH_1000硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air780EPM 核心板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO
+                    pwrpin = 20,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=0, cs=8}
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

+ 27 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/http/netdrv_device.lua

@@ -0,0 +1,27 @@
+--[[
+@module  netdrv_device
+@summary 网络驱动设备功能模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
+1、netdrv_4g:socket.LWIP_GP,4G网卡;
+2、netdrv_wifi:socket.LWIP_STA,WIFI STA网卡;
+3、netdrv_ethernet_spi:socket.LWIP_ETH,通过SPI外挂CH390H芯片的以太网卡;
+4、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
+
+根据自己的项目需求,只需要require以上其中的一种即可;
+
+
+本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
+]]
+
+
+-- 根据自己的项目需求,只需要require以下其中的一种即可;
+
+-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
+require "netdrv_eth_spi"
+
+-- 加载“可以配置优先级的多种网卡”驱动模块
+-- require "netdrv_multiple"

+ 75 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/http/readme.md

@@ -0,0 +1,75 @@
+## 功能模块介绍:
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:加载网络驱动设备功能模块;
+
+3、http_lua:加载http应用模块;
+
+## 演示功能概述
+
+1、以太网给模组供网,通过连接http测试连通。
+
+## 演示硬件环境
+
+1、Air780EPM核心板一块+可上网的sim卡一张+网线一根+AirETH_1000板子一个;
+
+[](https://docs.openLuat.com/cdn/image/780EPM_AirETH1000.jpg)
+
+<img title="" src="https://docs.openLuat.com/cdn/image/780EPM_AirETH1000.jpg" alt="lan" style="zoom:25%;">
+
+2、TYPE-C USB数据线一根 + 杜邦线若干;
+
+* Air780EPM核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+* AirETH_1000板子网口与路由器网口通过网线连接;
+
+3、Air780EPM核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air780EPM核心板 | AirETH_1000配件板 |
+| ------------ | -------------- |
+| 3.3V         | 3.3v           |
+| gnd          | gnd            |
+| spi0_sclk    | SCK            |
+| spi0_cs      | CSS            |
+| spi0_miso    | SDO            |
+| spi0_mosi    | SDI            |
+| gpio21       | INT            |
+
+演示软件环境
+------
+
+1、Luatools下载调试工具
+
+2、[Air780EPM V2014版本固件](https://docs.openluat.com/air780EPM/luatos/firmware/)(理论上,2025年9月12日之后发布的固件都可以) 
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件。
+
+2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口(需要在main.lua文件中打开require"netdrv_device"和require"http_app")
+
+3、启动设备,观察日志输出:
+
+出现类似如下打印,就表示成功。
+
+```
+[2025-09-18 11:31:29.931][000000003.367] I/user.netdrv_eth_spi.ip_ready_func IP_READY 192.168.0.52 255.255.255.0 192.168.0.1 nil
+
+[2025-09-18 11:31:29.934][000000003.368] I/user.http_app_task_func recv IP_READY 4 
+
+[2025-09-18 11:31:29.938][000000003.372] dns_run 676:www.air32.cn state 0 id 1 ipv6 0 use dns server0, try
+
+[2025-09-18 11:31:29.942][000000003.372] D/net adatper 4 dns server 192.168.0.1
+
+[2025-09-18 11:31:29.944][000000003.372] D/net dns udp sendto 192.168.0.1:53 from 192.168.0.52
+
+[2025-09-18 11:31:29.948][000000003.384] dns_run 693:dns all done ,now stop
+
+[2025-09-18 11:31:29.950][000000003.385] D/net connect 49.232.89.122:443 TCP
+
+[2025-09-18 11:31:30.641][000000004.123] I/user.http_app_get1 success 200 {"Transfer-Encoding":"chunked","Date":"Thu, 18 Sep 2025 03:31:33 GMT","Connection":"keep-alive","Server":"openresty\/1.27.1.2","Content-Type":"text\/html"} 2416
+
+```

+ 71 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/main.lua

@@ -0,0 +1,71 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.09.22
+@author  王城钧
+@usage
+本demo演示的核心功能为:
+1.设置多网融合功能,4G提供网络供以太网设备上网
+更多说明参考本目录下的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 = "4g_out_ethernet_in"
+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 "netif_app"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 48 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/netif_app.lua

@@ -0,0 +1,48 @@
+--[[
+@module  netif_app
+@summary netif_app 网络管理模块,开启多网融合功能,4G提供网络供以太网设备上网
+@version 1.0
+@date    2025.09.22
+@author  王城钧
+@usage
+本文件为网络管理模块,核心业务逻辑为:
+1、设置多网融合功能,4G提供网络供以太网设备上网
+2、http测试4G网络
+本文件没有对外接口,直接在main.lua中require "netif_app"就可以加载运行;
+]] 
+exnetif = require "exnetif"
+
+function netif_app_task_func()
+    local res
+    -- 等待4G网络连接成功
+    while not socket.adapter() do
+        -- 在此处阻塞等待4G网卡连接成功的消息"IP_READY"
+        -- 或者等待1秒超时退出阻塞等待状态;
+        -- 注意:此处的1000毫秒超时不要修改的更长;
+        sys.waitUntil("IP_READY", 1000)
+    end
+    -- 设置多网融合功能,4G提供网络供以太网设备上网
+    res = exnetif.setproxy(socket.LWIP_ETH, socket.LWIP_GP, {
+        ethpower_en = 20,                   -- 以太网模块的pwrpin引脚(gpio编号)
+        tp = netdrv.CH390,                  -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+        opts = {spi = 0, cs = 8},           -- 外挂方式,需要额外的参数(选填参数),仅spi方式外挂以太网时需要填写。
+        adapter_addr = "192.168.2.1",       -- 自定义LWIP_ETH网卡的ip地址(选填),需要自定义ip和网关ip时填写
+        adapter_gw = {192, 168, 2, 1}       -- 自定义LWIP_ETH网卡的网关地址(选填),需要自定义ip和网关ip时填写
+    })
+
+    if res then
+        log.info("exnetif", "setproxy success")
+    else
+        log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
+    end
+
+    -- 每5秒进行HTTPS连接测试,实时监测4G网络连接状态, 仅供测试需要,量产不需要,用来判断当前网络是否可用,需要的话可以打开注释
+    -- while 1 do
+    --     local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil,
+    --         { adapter = socket.LWIP_GP, timeout = 5000, debug = false }).wait()
+    --     log.info("http执行结果", code, headers, body and #body)
+    --     sys.wait(10000)
+    -- end
+end
+
+sys.taskInit(netif_app_task_func)

+ 60 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in/readme.md

@@ -0,0 +1,60 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netif_app: 网络管理模块,开启多网融合功能,4G提供网络供以太网设备上网;
+
+## 演示功能概述
+
+1、开启多网融合模式,4G连接外部网络,以太网lan模式为其他以太网设备提供接入
+
+2、​网络监控​,每5秒进行HTTPS连接测试,实时监测4G网络的连接状态
+
+## 演示硬件环境
+
+![](%20https://docs.openLuat.com/cdn/image/780EPM_adc.jpg)
+
+1、Air780EPM 核心板一块+可上网的sim卡一张+网线一根:
+
+- sim卡插入核心板的sim卡槽
+
+- 网线一端插入核心板外接的AirETH_1000小板上,另外一端连接需接入以太网的设备
+
+2、TYPE-C USB数据线一根 + 网线一根,Air780EPM核心板和数据线的硬件接线方式为:
+
+- Air780EPM 本核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+- 核心板与AirETH_1000的接线方式如下:
+  
+  | Air780EPM核心板 | AirETH_1000配件板 |
+  | ------------ | -------------- |
+  | vdd          | 3.3v           |
+  | gnd          | gnd            |
+  | spi0_sclk    | SCK            |
+  | spi0_cs      | CSS            |
+  | spi0_miso    | SDO            |
+  | spi0_mosi    | SDI            |
+  | gpio21       | INT            |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EPM V2014版本固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)(理论上,2025年8月10日之后发布的固件都可以)
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件,
+
+2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口,netif_app.lua:网络管理模块
+
+3、启动设备,观察日志输出:
+
+```lua
+[INFO] exnetif setproxy success
+[INFO] http执行结果 200 ... 
+```
+
+4、其他设备通过以太网接入780EPM,其他设备都能正常上网,则表示验证成功。

+ 11 - 0
module/Air780EPM/demo/accessory_board/AirETH_1000/readme.md

@@ -0,0 +1,11 @@
+本文件下共有三个示例demo
+
+1、http:通过外挂AirETH_1000小板使用WAN功能或者多网切换模式连接http测试网络连通性。
+
+2、network_routing:
+
+(1)4g_out_ethernet_in
+
+使用网络路由功能,4G提供网络供以太网设备上网
+
+

+ 1 - 1
module/Air780EPM/demo/accessory_board/AirSHT30_1000/main.lua

@@ -10,7 +10,7 @@ VERSION:项目版本号,ascii string类型
 
 AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
 本demo演示的核心功能为:
-Air8101核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+Air780EPM开发板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirSHT30_1000"

+ 0 - 1
module/Air780EPM/demo/protobuf/main.lua

@@ -20,7 +20,6 @@ end
 
 sys.taskInit(function()
     sys.wait(500)
-    -- 如果没有这个库, 就云编译一份吧: https://wiki.luatos.com/develop/compile/Cloud_compilation.html
     if not protobuf then
         log.info("protobuf", "this demo need protobuf lib")
         return

+ 1 - 1
module/Air780EPM/demo/u8g2/main.lua

@@ -81,7 +81,7 @@ sys.taskInit(function()
     end
 
     if chinese then
-    log.info("在显示屏显示中文", u8g2.DrawUTF8("中文测试", 40, 38)) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据, 可自行云编译一份. wiki.luatos.com 有文档.
+    log.info("在显示屏显示中文", u8g2.DrawUTF8("中文测试", 40, 38)) -- 若中文不显示或乱码,代表所刷固件不带这个字号的字体数据
         
     end
     log.info("将存储器帧缓冲区的内容发送到显示器", u8g2.SendBuffer())

+ 154 - 0
module/Air8000/demo/Aircloud/excloud_test.lua

@@ -0,0 +1,154 @@
+--[[
+@module  excloud_test
+@summary excloud测试文件
+@version 1.0
+@date    2025.09.22
+@author  孟伟
+@usage
+本demo演示的功能为:
+演示excloud扩展库的使用。
+]]
+-- 导入excloud库
+local excloud = require("excloud")
+
+-- 注册回调函数
+excloud.on(function(event, data)
+    log.info("用户回调函数", event, json.encode(data))
+
+    if event == "connect_result" then
+        if data.success then
+            log.info("连接成功")
+        else
+            log.info("连接失败: " .. (data.error or "未知错误"))
+        end
+    elseif event == "auth_result" then
+        if data.success then
+            log.info("认证成功")
+        else
+            log.info("认证失败: " .. data.message)
+        end
+    elseif event == "message" then
+        log.info("收到消息, 流水号: " .. data.header.sequence_num)
+
+        -- 处理服务器下发的消息
+        for _, tlv in ipairs(data.tlvs) do
+            if tlv.field == excloud.FIELD_MEANINGS.CONTROL_COMMAND then
+                log.info("收到控制命令: " .. tostring(tlv.value))
+
+                -- 处理控制命令并发送响应
+                local response_ok, err_msg = excloud.send({
+                    {
+                        field_meaning = excloud.FIELD_MEANINGS.CONTROL_RESPONSE,
+                        data_type = excloud.DATA_TYPES.ASCII,
+                        value = "命令执行成功"
+                    }
+                }, false)
+
+                if not response_ok then
+                    log.info("发送控制响应失败: " .. err_msg)
+                end
+            end
+        end
+    elseif event == "disconnect" then
+        log.warn("与服务器断开连接")
+    elseif event == "reconnect_failed" then
+        log.info("重连失败,已尝试 " .. data.count .. " 次")
+    elseif event == "send_result" then
+        if data.success then
+            log.info("发送成功,流水号: " .. data.sequence_num)
+        else
+            log.info("发送失败: " .. data.error_msg)
+        end
+    end
+end)
+
+
+sys.taskInit(function()
+    -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
+    while not socket.adapter(socket.dft()) do
+        log.warn("tcp_client_main_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
+    sys.wait(1000)
+    -- 配置excloud参数
+    -- local ok, err_msg = excloud.setup({
+    --     -- device_id = "862419074073247",   -- 设备ID (IMEI前14位)
+    --     device_type = 1,                 -- 设备类型: 4G
+    --     host = "112.125.89.8",         -- 服务器地址
+    --     port = 33316,                     -- 服务器端口
+    --     auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi", -- 鉴权密钥
+    --     transport = "tcp",               -- 使用TCP传输
+    --     auto_reconnect = true,           -- 自动重连
+    --     reconnect_interval = 10,         -- 重连间隔(秒)
+    --     max_reconnect = 5,               -- 最大重连次数
+    --     timeout = 30,                    -- 超时时间(秒)
+    -- })
+    -- 配置MQTT连接参数
+    local ok, err_msg = excloud.setup({
+        device_type = 1,             -- 设备类型: 4G设备
+        transport = "mqtt",          -- 使用MQTT传输协议
+        host = "airtest.openluat.com",   -- MQTT服务器地址
+        port = 1883,                 -- MQTT服务器端口
+        auth_key = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi", -- 鉴权密钥(请替换为实际密钥)
+        username = "root",    -- MQTT用户名(可选)
+        password = "Luat123456", -- MQTT密码(可选)
+        keepalive = 300,             -- 心跳间隔(秒)
+        -- auto_reconnect = true,       -- 自动重连
+        reconnect_interval = 10,     -- 重连间隔(秒)
+        max_reconnect = 5,           -- 最大重连次数
+        timeout = 30,                -- 超时时间(秒)
+        qos = 1,                     -- MQTT QoS等级(0/1/2)
+        -- retain = false,              -- MQTT retain标志
+        -- clean_session = true,        -- MQTT clean session
+        ssl = false                  -- 不使用SSL
+    })
+
+    if not ok then
+        log.info("初始化失败: " .. err_msg)
+        return
+    end
+    log.info("excloud初始化成功")
+    -- 开启excloud服务
+    local ok, err_msg = excloud.open()
+    if not ok then
+        log.info("开启excloud服务失败: " .. err_msg)
+        return
+    end
+
+    log.info("excloud服务已开启")
+    -- 在主循环中定期上报数据
+    while true do
+        -- 每30秒上报一次数据
+        sys.wait(30000)
+        -- 检查连接状态
+        local status = excloud.status()
+        -- if not status.is_connected or not status.is_authenticated then
+        --     log.info("设备未连接或未认证,跳过数据上报")
+        -- else
+        local ok, err_msg = excloud.send({
+            {
+                field_meaning = excloud.FIELD_MEANINGS.LOCATION_METHOD,
+                data_type = excloud.DATA_TYPES.INTEGER,
+                value = 22     -- 随机温度值
+            },
+            {
+                field_meaning = excloud.FIELD_MEANINGS.HUMIDITY,
+                data_type = excloud.DATA_TYPES.FLOAT,
+                value = 33.2543     -- 随机湿度值
+            }
+        }, false)                   -- 不需要服务器回复
+
+        if not ok then
+            log.info("发送数据失败: " .. err_msg)
+        else
+            log.info("数据发送成功")
+        end
+        -- end
+    end
+end)

+ 65 - 0
module/Air8000/demo/Aircloud/main.lua

@@ -0,0 +1,65 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.09.22
+@author  孟伟
+@usage
+本demo演示的功能为:
+演示excloud扩展库的使用。
+]]
+
+--[[
+必须定义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 = "excloud_test"
+VERSION = "001.000.000"
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+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 "netdrv_device"
+
+-- 加载excloud测试模块
+require"excloud_test"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 33 - 0
module/Air8000/demo/Aircloud/netdrv/netdrv_4g.lua

@@ -0,0 +1,33 @@
+--[[
+@module  netdrv_4g
+@summary “4G网卡”驱动模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为4G网卡驱动模块,核心业务逻辑为:
+1、监听"IP_READY"和"IP_LOSE",在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_4g"就可以加载运行;
+]]
+
+local function ip_ready_func()
+    log.info("netdrv_4g.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_GP))
+end
+
+local function ip_lose_func()
+    log.warn("netdrv_4g.ip_lose_func", "IP_LOSE")
+end
+
+
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G网络的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+-- 设置默认网卡为socket.LWIP_GP
+-- 在Air8000上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
+-- 在单4G网卡使用场景下,下面这一行代码加不加都没有影响,为了和其他网卡驱动模块的代码风格保持一致,所以加上了
+socket.dft(socket.LWIP_GP)

+ 85 - 0
module/Air8000/demo/Aircloud/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,85 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块 
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块 ,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func()
+    log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+end
+
+local function ip_lose_func()
+    log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+end
+
+
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+--本demo测试使用的是Air8000开发板
+--GPIO140为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(140, 1, gpio.PULLUP)
+
+--这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI1
+    local result = spi.setup(
+        1,--spi_id
+        nil,
+        0,--CPHA
+        0,--CPOL
+        8,--数据宽度
+        25600000--,--频率
+        -- spi.MSB,--高低位顺序    可选,默认高位在前
+        -- spi.master,--主模式     可选,默认主
+        -- spi.full--全双工       可选,默认全双工
+    )
+    log.info("netdrv_eth_spi", "spi open result", result)
+    --返回值为0,表示打开成功
+    if result ~= 0 then
+        log.error("netdrv_eth_spi", "spi open error",result)
+        return
+    end
+
+    --初始化以太网卡
+
+    --以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+    --各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    --以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    --各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    --也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+    -- socket.LWIP_ETH 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 1, 片选 GPIO12
+    netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spi=1, cs=12})
+
+    --在以太上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_ETH, true)
+end
+
+--创建并且启动一个task
+--task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 95 - 0
module/Air8000/demo/Aircloud/netdrv/netdrv_multiple.lua

@@ -0,0 +1,95 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、WIFI STA网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块 
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为多网卡驱动模块 ,核心业务逻辑为:
+1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当exnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     net_type:string类型,表示当前使用的网卡字符串
+--     adapter:number类型,表示当前使用的网卡id
+-- 当所有网卡断网时:
+--     net_type:为nil
+--     adapter:number类型,为-1
+local function netdrv_multiple_notify_cbfunc(net_type,adapter)
+    if type(net_type)=="string" then
+        log.info("netdrv_multiple_notify_cbfunc", "use new adapter", net_type, adapter)
+    elseif type(net_type)=="nil" then
+        log.warn("netdrv_multiple_notify_cbfunc", "no available adapter", net_type, adapter)
+    else
+        log.warn("netdrv_multiple_notify_cbfunc", "unknown status", net_type, adapter)
+    end
+end
+
+local function netdrv_multiple_task_func()
+    --设置网卡优先级
+    exnetif.set_priority_order(
+        {
+            -- “通过SPI外挂CH390H芯片”的以太网卡,使用Air8000开发板验证
+            {
+                ETHERNET = {
+                    -- 供电使能GPIO
+                    pwrpin = 140,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",     
+                    
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390, 
+                    opts = {spi=1, cs=12}
+                }
+            },
+
+            -- WIFI STA网卡
+            {
+                WIFI = {
+                    -- 要连接的WIFI路由器名称
+                    ssid = "茶室-降功耗,找合宙!",
+                    -- 要连接的WIFI路由器密码
+                    password = "Air123456", 
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+                }
+            },
+
+            -- 4G网卡
+            {
+                LWIP_GP = true
+            }
+        }
+    )    
+end
+
+-- 设置网卡状态变化通知回调函数netdrv_multiple_notify_cbfunc
+exnetif.notify_status(netdrv_multiple_notify_cbfunc)
+
+-- 如果存在udp网络应用,并且udp网络应用中,根据应用层的心跳能够判断出来udp数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次exnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用exnetif.check_network_status()接口去控制,exnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用exnetif.set_priority_order设置网卡优先级
+-- 因为exnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

+ 50 - 0
module/Air8000/demo/Aircloud/netdrv/netdrv_wifi.lua

@@ -0,0 +1,50 @@
+--[[
+@module  netdrv_wifi
+@summary “WIFI STA网卡”驱动模块 
+@version 1.0
+@date    2025.07.01
+@author  朱天华
+@usage
+本文件为WIFI STA网卡驱动模块,核心业务逻辑为:
+1、初始化WIFI网络;
+2、连接WIFI路由器;
+3、和WIFI路由器之间的连接状态发生变化时,在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_wifi"就可以加载运行;
+]]
+
+local function ip_ready_func()
+    log.info("netdrv_wifi.ip_ready_func", "IP_READY", json.encode(wlan.getInfo()))
+end
+
+local function ip_lose_func()
+    log.warn("netdrv_wifi.ip_lose_func", "IP_LOSE")
+end
+
+
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察WIFI的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_STA
+socket.dft(socket.LWIP_STA)
+
+
+wlan.init()
+--连接WIFI热点,连接结果会通过"IP_READY"或者"IP_LOSE"消息通知
+--Air8000仅支持2.4G的WIFI,不支持5G的WIFI
+--此处前两个参数表示WIFI热点名称以及密码,更换为自己测试时的真实参数即可
+--第三个参数1表示WIFI连接异常时,内核固件会自动重连
+wlan.connect("iPhone", "xiaoshuai", 1)
+
+--WIFI联网成功(做为STATION成功连接AP,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+--各个功能模块可以订阅"IP_READY"消息实时处理WIFI联网成功的事件
+--也可以在任何时刻调用socket.adapter(socket.LWIP_STA)来获取WIFI网络是否连接成功
+
+--WIFI断网后,内核固件会产生一个"IP_LOSE"消息
+--各个功能模块可以订阅"IP_LOSE"消息实时处理WIFI断网的事件
+--也可以在任何时刻调用socket.adapter(socket.LWIP_STA)来获取WIFI网络是否连接成功

+ 33 - 0
module/Air8000/demo/Aircloud/netdrv_device.lua

@@ -0,0 +1,33 @@
+--[[
+@module  netdrv_device
+@summary 网络驱动设备功能模块
+@version 1.0
+@date    2025.07.24
+@author  朱天华
+@usage
+本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
+1、netdrv_4g:socket.LWIP_GP,4G网卡;
+2、netdrv_wifi:socket.LWIP_STA,WIFI STA网卡;
+3、netdrv_ethernet_spi:socket.LWIP_USER1,通过SPI外挂CH390H芯片的以太网卡;
+4、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
+
+根据自己的项目需求,只需要require以上四种中的一种即可;
+
+
+本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
+]]
+
+
+-- 根据自己的项目需求,只需要require以下四种中的一种即可;
+
+-- 加载“4G网卡”驱动模块
+require "netdrv_4g"
+
+-- 加载“WIFI STA网卡”驱动模块
+-- require "netdrv_wifi"
+
+-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
+-- require "netdrv_eth_spi"
+
+-- 加载“可以配置优先级的多种网卡”驱动模块
+-- require "netdrv_multiple"

+ 78 - 0
module/Air8000/demo/accessory_board/AirETH_1000/http/main.lua

@@ -0,0 +1,78 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.09.17
+@author  王城钧
+@usage
+本demo演示的核心功能为:
+1、分别使用http核心库和httpplus扩展库,演示以下一种应用场景的使用方式
+   (1) 普通的http get请求功能演示;
+2、netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(二选一)
+   (1) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+   (2) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+更多说明参考本目录下的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 = "HTTP"
+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 "netdrv_device"
+
+-- 加载http应用功能模块
+require "http_app"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 24 - 20
module/Air8000/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_eth_spi.lua

@@ -2,8 +2,8 @@
 @module  netdrv_eth_spi
 @summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
 @version 1.0
-@date    2025.07.24
-@author  马梦阳
+@date    2025.09.19
+@author  王城钧
 @usage
 本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
 1、打开CH390H芯片供电开关;
@@ -15,6 +15,8 @@
 本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
 ]]
 
+exnetif = require "exnetif"
+
 local function ip_ready_func(ip, adapter)
     if adapter == socket.LWIP_ETH then
         log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
@@ -45,24 +47,26 @@ gpio.setup(140, 1, gpio.PULLUP)
 
 -- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
 local function netdrv_eth_spi_task_func()
-    -- 初始化SPI1
-    local result = spi.setup(
-        1,--spi_id
-        nil,
-        0,--CPHA
-        0,--CPOL
-        8,--数据宽度
-        25600000--,--频率
-        -- spi.MSB,--高低位顺序    可选,默认高位在前
-        -- spi.master,--主模式     可选,默认主
-        -- spi.full--全双工       可选,默认全双工
-    )
-    log.info("netdrv_eth_spi", "spi open result", result)
-    --返回值为0,表示打开成功
-    if result ~= 0 then
-        log.error("netdrv_eth_spi", "spi open error",result)
-        return
-    end
+    local result = exnetif.set_priority_order({
+    {
+        ETHUSER1 = {
+                    -- 供电使能GPIO
+                    pwrpin = 140,
+                    -- 设置的多个“已经IP READY,但是还没有ping通”网卡,循环执行ping动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,exnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,exnetif中会默认使用httpdns能否成功获取baidu.com的ip作为是否连通的判断条件;
+                    -- 如果传入,一定要传入可靠的并且可以ping通的ip地址;
+                    -- ping_ip = "填入可靠的并且可以ping通的ip地址",
+
+                    -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+                    tp = netdrv.CH390,
+                    opts = {spi=1, cs=12}
+        }
+    }
+})
 
     -- 初始化以太网卡
 

+ 2 - 2
module/Air8000/demo/accessory_board/AirETH_1000/http/netdrv/netdrv_multiple.lua

@@ -2,8 +2,8 @@
 @module  netdrv_multiple
 @summary 多网卡(4G网卡、WIFI STA网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
 @version 1.0
-@date    2025.07.24
-@author  马梦阳
+@date    2025.09.19
+@author  王城钧
 @usage
 本文件为多网卡驱动模块,核心业务逻辑为:
 1、调用exnetif.set_priority_order配置多网卡的控制参数以及优先级;

+ 4 - 6
module/Air8000/demo/accessory_board/AirETH_1000/http/netdrv_device.lua

@@ -2,14 +2,12 @@
 @module  netdrv_device
 @summary 网络驱动设备功能模块
 @version 1.0
-@date    2025.07.24
-@author  马梦阳
+@date    2025.09.17
+@author  王城钧
 @usage
 本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
-1、netdrv_4g:socket.LWIP_GP,4G网卡;
-2、netdrv_wifi:socket.LWIP_STA,WIFI STA网卡;
-3、netdrv_ethernet_spi:socket.LWIP_ETH,通过SPI外挂CH390H芯片的以太网卡;
-4、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
+1、netdrv_ethernet_spi:socket.LWIP_ETH,通过SPI外挂CH390H芯片的以太网卡;
+2、netdrv_multiple:可以配置多种网卡的优先级,按照优先级配置,使用其中一种网卡连接外网;
 
 根据自己的项目需求,只需要require以上其中的一种即可;
 

+ 81 - 0
module/Air8000/demo/accessory_board/AirETH_1000/http/readme.md

@@ -0,0 +1,81 @@
+## 功能模块介绍:
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:加载网络驱动设备功能模块;
+
+3、http_app.lua:加载http应用功能模块;
+
+## 演示功能概述
+
+1、模组连接4G网络通过以太网口传输给其他设备供网 
+
+## 演示硬件环境
+
+1、Air8000核心板一块+可上网的sim卡一张+网线一根+AirETH_1000板子一个;
+
+[](https://docs.openLuat.com/cdn/image/AirETH_1000.jpg)
+
+![lan](E:\文档池\新建文件夹\luatos-doc-pool\docs\root\docs\air8000\luatos\app\image\lan.jpg)
+
+2、TYPE-C USB数据线一根 + 杜邦线若干;
+
+* Air8000核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+* AirETH_1000板子网口与路由器网口通过网线连接;
+
+3、Air8000核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air8000核心板 | AirETH_1000配件板 |
+| ---------- | -------------- |
+| vdd        | 3.3v           |
+| gnd        | gnd            |
+| spi1_sclk  | SCK            |
+| spi1_cs    | CSS            |
+| spi1_miso  | SDO            |
+| spi1_mosi  | SDI            |
+| gpio21     | INT            |
+
+演示软件环境
+------
+
+1、Luatools下载调试工具
+
+2、[Air8000 V2014版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上,2025年9月12日之后发布的固件都可以) 
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件。
+
+2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口(需要在main.lua文件中打开require"netdrv_device"和require"http_app")
+
+3、启动设备,观察日志输出:
+
+出现类似如下打印,就表示成功。
+
+```
+[2025-09-17 14:35:59.774][000000005.877] D/ulwip IP_READY 4 192.168.3.99
+
+[2025-09-17 14:35:59.777][000000005.878] I/user.netdrv_eth_spi.ip_ready_func IP_READY 192.168.3.99 255.255.255.0 
+192.168.3.1 nil
+
+[2025-09-17 14:35:59.783][000000005.879] I/user.http_app_task_func recv IP_READY 4 4
+
+[2025-09-17 14:35:59.786][000000005.883] dns_run 676:www.air32.cn state 0 id 1 ipv6 0 use dns server0, try 0
+
+[2025-09-17 14:35:59.789][000000005.883] D/net adatper 4 dns server 192.168.3.1
+
+[2025-09-17 14:35:59.793][000000005.883] D/net dns udp sendto 192.168.3.1:53 from 192.168.3.99
+
+[2025-09-17 14:35:59.799][000000005.891] dns_run 693:dns all done ,now stop
+
+[2025-09-17 14:35:59.802][000000005.891] D/net connect 49.232.89.122:443 TCP
+
+[2025-09-17 14:36:00.215][000000006.395] I/user.http_app_get1 success 200 {"Transfer-Encoding":"chunked","Date":"Wed, 17 Sep 2025 06:36:02 GMT","Connection":"keep-alive","Server":"openresty\/1.27.1.2","Content-Type":"text\/html"} 2416
+
+[2025-09-17 14:36:00.226][000000006.396] dns_run 676:www.luatos.com state 0 id 2 ipv6 0 use dns server0, try 0
+
+
+```

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/network_routing/4g_out_ethernet_in_wifi_in/main.lua → module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/main.lua


+ 13 - 28
module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/4g-eth-wifi.lua → module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/netif_app.lua

@@ -2,7 +2,7 @@
 @module  netif_app
 @summary netif_app 网络管理模块,开启多网融合功能,4G提供网络供wifi和以太网设备上网
 @version 1.0
-@date    2025.08.05
+@date    2025.09.17
 @author  魏健强
 @usage
 本文件为网络管理模块,核心业务逻辑为:
@@ -22,21 +22,6 @@ function netif_app_task_func()
         -- 注意:此处的1000毫秒超时不要修改的更长;
         sys.waitUntil("IP_READY", 1000)
     end
-    log.info("test")
-    sys.wait(10000)
-        log.info("test2")
-      res = exnetif.setproxy(socket.LWIP_ETH, socket.LWIP_GP, {
-            ethpower_en = 140,              -- 以太网模块的pwrpin引脚(gpio编号)
-            tp = netdrv.CH390,              -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
-            opts = { spi = 1, cs = 12 },    -- 外挂方式,需要额外的参数(选填参数),仅spi方式外挂以太网时需要填写。
-            -- adapter_addr = "192.168.2.1",   -- 自定义LWIP_ETH网卡的ip地址(选填),需要自定义ip和网关ip时填写
-            -- adapter_gw = { 192, 168, 2, 1 } -- 自定义LWIP_ETH网卡的网关地址(选填),需要自定义ip和网关ip时填写
-        })
-    if res then
-        log.info("exnetif", "setproxy success")
-    else
-        log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
-    end    
     --设置多网融合功能,4G提供网络供wifi设备上网
     res = exnetif.setproxy(socket.LWIP_AP, socket.LWIP_GP, {
             ssid = "test2",                  -- AP热点名称(string),网卡包含wifi时填写
@@ -54,18 +39,18 @@ function netif_app_task_func()
         log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
     end
     --设置多网融合功能,4G提供网络供以太网设备上网
-    -- res = exnetif.setproxy(socket.LWIP_ETH, socket.LWIP_GP, {
-    --         ethpower_en = 140,              -- 以太网模块的pwrpin引脚(gpio编号)
-    --         tp = netdrv.CH390,              -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
-    --         opts = { spi = 1, cs = 12 },    -- 外挂方式,需要额外的参数(选填参数),仅spi方式外挂以太网时需要填写。
-    --         -- adapter_addr = "192.168.2.1",   -- 自定义LWIP_ETH网卡的ip地址(选填),需要自定义ip和网关ip时填写
-    --         -- adapter_gw = { 192, 168, 2, 1 } -- 自定义LWIP_ETH网卡的网关地址(选填),需要自定义ip和网关ip时填写
-    --     })
-    -- if res then
-    --     log.info("exnetif", "setproxy success")
-    -- else
-    --     log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
-    -- end    
+    res = exnetif.setproxy(socket.LWIP_ETH, socket.LWIP_GP, {
+            ethpower_en = 140,              -- 以太网模块的pwrpin引脚(gpio编号)
+            tp = netdrv.CH390,              -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
+            opts = { spi = 1, cs = 12 },    -- 外挂方式,需要额外的参数(选填参数),仅spi方式外挂以太网时需要填写。
+            -- adapter_addr = "192.168.2.1",   -- 自定义LWIP_ETH网卡的ip地址(选填),需要自定义ip和网关ip时填写
+            -- adapter_gw = { 192, 168, 2, 1 } -- 自定义LWIP_ETH网卡的网关地址(选填),需要自定义ip和网关ip时填写
+        })
+    if res then
+        log.info("exnetif", "setproxy success")
+    else
+        log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
+    end    
     -- 每5秒进行HTTPS连接测试,实时监测4G网络连接状态, 仅供测试需要,量产不需要,用来判断当前网络是否可用,需要的话可以打开注释
     -- while 1 do
     --     local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil,

+ 64 - 0
module/Air8000/demo/accessory_board/AirETH_1000/network_routing/4g_out_ethernet_in_wifi_in/readme.md

@@ -0,0 +1,64 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netif_app: 网络管理模块,开启多网融合功能,4G提供网络供以太网和wifi设备上网;
+
+## 演示功能概述
+
+1、开启多网融合模式,4G连接外部网络,生成WiFi热点为WiFi终端设备提供接入,支持以太网lan模式为其他以太网设备提供接入
+
+2、​网络监控​,每5秒进行HTTPS连接测试,实时监测4G网络的连接状态
+
+## 演示硬件环境
+
+1、Air8000核心板一块+可上网的sim卡一张+网线一根+AirETH_1000板子一个;
+
+[](https://docs.openLuat.com/cdn/image/AirETH_1000.jpg)
+
+![lan](E:\文档池\新建文件夹\luatos-doc-pool\docs\root\docs\air8000\luatos\app\image\lan.jpg)
+
+2、TYPE-C USB数据线一根 + 杜邦线若干;
+
+* Air8000核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+* AirETH_1000板子网口与电脑网口通过网线连接;
+
+3、Air8000核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air8000核心板 | AirETH_1000配件板 |
+| ---------- | -------------- |
+| vdd        | 3.3v           |
+| gnd        | gnd            |
+| spi1_sclk  | SCK            |
+| spi1_cs    | CSS            |
+| spi1_miso  | SDO            |
+| spi1_mosi  | SDI            |
+| gpio21     | INT            |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8000 V2011版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上,2025年7月26日之后发布的固件都可以)
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件,
+
+2、按需修改WiFi热点配置(在netif_app.lua中):
+ssid = "AP热点名称"
+password = "AP热点密码"
+
+4、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口,netif_app.lua:网络管理模块
+
+5、启动设备,观察日志输出:
+
+```lua
+[INFO] exnetif setproxy success
+[INFO] http执行结果 200 ... 
+```
+
+6、其他设备通过wifi或以太网接入Air8000,其他设备都能正常上网,则表示验证成功。

+ 69 - 0
module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/main.lua

@@ -0,0 +1,69 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.08.05
+@author  魏健强
+@usage
+本demo演示的核心功能为:
+1.设置多网融合功能,wifi提供网络供wifi和以太网设备上网
+更多说明参考本目录下的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 = "wifi_out_ethernet_in_wifi_in"
+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 "netif_app"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 14 - 11
module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/wifi-eth-wifi.lua → module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/netif_app.lua

@@ -1,15 +1,15 @@
 --[[
-@module  wifi-eth-wifi
-@summary wifi-eth-wifi wifi提供网络供wifi和以太网设备上网
+@module  netif_app
+@summary netif_app 网络管理模块,开启多网融合功能,wifi提供网络供wifi和以太网设备上网
 @version 1.0
 @date    2025.09.17
-@author  王城钧
+@author  魏健强
 @usage
 本文件为网络管理模块,核心业务逻辑为:
 1.设置多网融合功能,wifi提供网络供wifi和以太网设备上网
-本文件没有对外接口,直接在main.lua中require "wifi-eth-wifi"就可以加载运行;
-]]
-
+2、http测试wifi网络
+本文件没有对外接口,直接在main.lua中require "netif_app"就可以加载运行;
+]] 
 exnetif = require "exnetif"
 
 function netif_app_task_func()
@@ -20,7 +20,7 @@ function netif_app_task_func()
         tp = netdrv.CH390,               -- 网卡芯片型号(选填参数),仅spi方式外挂以太网时需要填写。
         opts = { spi = 1, cs = 12 },     -- 外挂方式,需要额外的参数(选填参数),仅spi方式外挂以太网时需要填写。
         main_adapter = {                 -- 提供网络的网卡开启参数
-            ssid = "iPhone",               
+            ssid = "test",               
             password = "HZ88888888"
         }
     })
@@ -33,7 +33,7 @@ function netif_app_task_func()
         --     max_conn = 4 },              -- 最大客户端数量, 默认4
         -- channel = 6,                     -- AP建立的通道, 默认6
         main_adapter = {                    -- 提供网络的网卡开启参数
-            ssid = "iPhone",               
+            ssid = "test",
             password = "HZ88888888"
         }
     })
@@ -43,10 +43,13 @@ function netif_app_task_func()
     else
         log.info("开启失败,请检查配置项是否正确,日志中是否打印了错误信息")
     end
-
-    -- 每5秒进行HTTPS连接测试,实时监测以太网网络连接状态, 仅供测试需要,量产不需要,用来判断当前网络是否可用,需要的话可以打开注释
+    -- 每5秒进行HTTPS连接测试,实时监测wifi网络连接状态, 仅供测试需要,量产不需要,用来判断当前网络是否可用,需要的话可以打开注释
     -- while 1 do
-    --     local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil, {adapter=socket.LWIP_ETH,timeout=5000,debug=false}).wait()
+    --     local code, headers, body = http.request("GET", "https://httpbin.air32.cn/bytes/2048", nil, nil, {
+    --         adapter = socket.LWIP_STA,
+    --         timeout = 5000,
+    --         debug = false
+    --     }).wait()
     --     log.info("http执行结果", code, headers, body and #body)
     --     sys.wait(5000)
     -- end

+ 64 - 0
module/Air8000/demo/accessory_board/AirETH_1000/network_routing/wifi_out_ethernet_in_wifi_in/readme.md

@@ -0,0 +1,64 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、netif_app: 网络管理模块,开启多网融合功能,WIFI提供网络供以太网和wifi设备上网;
+
+## 演示功能概述
+
+1、开启多网融合模式,WIFI连接外部网络,支持以太网lan模式为其他以太网设备提供接入,支持生成WiFi热点为WiFi终端设备提供接入
+
+2、​网络监控​,每5秒进行HTTPS连接测试,实时监测WIFI网络的连接状态
+
+## 演示硬件环境
+
+1、Air8000核心板一块+可上网的sim卡一张+网线一根+AirETH_1000板子一个;
+
+[](https://docs.openLuat.com/cdn/image/AirETH_1000.jpg)
+
+![lan](E:\文档池\新建文件夹\luatos-doc-pool\docs\root\docs\air8000\luatos\app\image\lan.jpg)
+
+2、TYPE-C USB数据线一根 + 杜邦线若干;
+
+* Air8000核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+* AirETH_1000板子网口与电脑网口通过网线连接;
+
+3、Air8000核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air8000核心板 | AirETH_1000配件板 |
+| ---------- | -------------- |
+| vdd        | 3.3v           |
+| gnd        | gnd            |
+| spi1_sclk  | SCK            |
+| spi1_cs    | CSS            |
+| spi1_miso  | SDO            |
+| spi1_mosi  | SDI            |
+| gpio21     | INT            |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8000 V2011版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上,2025年7月26日之后发布的固件都可以)
+
+## 演示核心步骤
+
+1、搭建好硬件环境,按接线图连接硬件,
+
+2、按需修改WiFi配置(在netif_app.lua中):
+ssid = "WIFI名称"
+password = "WIFI密码"
+
+4、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口,netif_app.lua:网络管理模块
+
+5、启动设备,观察日志输出:
+
+```lua
+[INFO] exnetif setproxy success
+[INFO] http执行结果 200 ... 
+```
+
+6、其他设备通过wifi或以太网接入Air8000,其他设备都能正常上网,则表示验证成功。

+ 8 - 72
module/Air8000/demo/accessory_board/AirETH_1000/readme.md

@@ -1,79 +1,15 @@
-## 功能模块介绍
+## 演示功能概述
 
-1、main.lua:主程序入口;
+本文件下共有三个示例demo
 
-2、netdrv_device.lua:加载网络驱动设备功能模块;
+1、http:通过外挂AirETH_1000小板使用WAN功能或者多网切换模式连接http测试网络连通性。
 
-3、4g-eth-wifi.lua:模组通过4G提供网络供以太网和wifi设备上网;
+2、network_routing:
 
-4、wifi-eth-wifi: 模组通过WIFI提供网络供以太网和wifi设备上网;
+(1)4g_out_ethernet_in_wifi
 
-## 演示功能概述
+使用网络路由功能,4G提供网络供以太网和wifi设备上网
 
-1、以太网给模组供网,通过连接http测试连通。 
+(2)wifi_out_ethernet_in_wifi_in
 
-## 演示硬件环境
-
-1、Air8000核心板一块+可上网的sim卡一张+网线一根+AirETH_1000板子一个;
-
-[](https://docs.openLuat.com/cdn/image/AirETH_1000.jpg)
-
-![lan](E:\文档池\新建文件夹\luatos-doc-pool\docs\root\docs\air8000\luatos\app\image\lan.jpg)
-
-2、TYPE-C USB数据线一根 + 杜邦线若干;
-
-* Air8000核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
-
-* TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
-
-* AirETH_1000板子网口与电脑网口通过网线连接;
-
-3、Air8000核心板和AirETH_1000配件板的硬件接线方式为:
-
-| Air8000核心板 | AirETH_1000配件板 |
-| ---------- | -------------- |
-| vdd        | 3.3v           |
-| gnd        | gnd            |
-| spi1_sclk  | SCK            |
-| spi1_cs    | CSS            |
-| spi1_miso  | SDO            |
-| spi1_mosi  | SDI            |
-| gpio21     | INT            |
-
-演示软件环境
-------
-
-1、Luatools下载调试工具
-
-2、[Air8000 V2014版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上,2025年9月12日之后发布的固件都可以) 
-
-## 演示核心步骤
-
-1、搭建好硬件环境,按接线图连接硬件。
-
-2、烧录内核固件和本项目的Lua脚本:main.lua:主程序入口(需要在main.lua文件中打开require"netdrv_device"和require"http_app")
-
-3、启动设备,观察日志输出:
-
-出现类似如下打印,就表示成功。
-
-```
-[2025-09-17 14:35:59.777][000000005.878] I/user.netdrv_eth_spi.ip_ready_func IP_READY 192.168.3.99 255.255.255.0 
-192.168.3.1 nil
-
-[2025-09-17 14:35:59.783][000000005.879] I/user.http_app_task_func recv IP_READY 4 4
-
-[2025-09-17 14:35:59.786][000000005.883] dns_run 676:www.air32.cn state 0 id 1 ipv6 0 use dns server0, try 0
-
-[2025-09-17 14:35:59.789][000000005.883] D/net adatper 4 dns server 192.168.3.1
-
-[2025-09-17 14:35:59.793][000000005.883] D/net dns udp sendto 192.168.3.1:53 from 192.168.3.99
-
-[2025-09-17 14:35:59.799][000000005.891] dns_run 693:dns all done ,now stop
-
-[2025-09-17 14:35:59.802][000000005.891] D/net connect 49.232.89.122:443 TCP
-
-[2025-09-17 14:36:00.215][000000006.395] I/user.http_app_get1 success 200 {"Transfer-Encoding":"chunked","Date":"Wed, 17 Sep 2025 06:36:02 GMT","Connection":"keep-alive","Server":"openresty\/1.27.1.2","Content-Type":"text\/html"} 2416
-
-
-```
+使用网络路由功能,以太网提供网络供以太网和wifi设备上网

+ 114 - 0
module/Air8000/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua

@@ -0,0 +1,114 @@
+--本文件中的主机是指I2C主机,具体指Air8000
+--本文件中的从机是指I2C从机,具体指AirSHT30_1000配件板上的sht30温湿度传感器芯片
+
+local AirSHT30_1000 = 
+{
+    -- i2c_id:主机的i2c id;
+}   
+
+-- 从机地址为0x44
+local slave_addr = 0x44
+
+-- 计算数据表data中所有数据元素的crc8校验值
+local function crc8(data)
+    local crc = 0xFF
+    for i = 1, #data do
+        crc = bit.bxor(crc, data[i])
+        for j = 1, 8 do
+            crc = crc * 2
+            if crc >= 0x100 then
+                crc = bit.band(bit.bxor(crc, 0x31), 0xff)
+            end
+        end
+    end
+    return crc
+end
+
+
+--打开AirSHT30_1000;
+
+--i2c_id:number类型;
+--        主机使用的I2C ID,用来控制AirSHT30_1000;
+--        取值范围:仅支持0和1;
+--        如果没有传入此参数,则默认为0;
+
+--返回值:成功返回true,失败返回false
+function AirSHT30_1000.open(i2c_id)
+    --如果i2c_id为nil,则赋值为默认值0
+    if i2c_id==nil then i2c_id=0 end
+
+    --检查参数的合法性
+    if not (i2c_id == 0 or i2c_id == 1) then
+        log.error("AirSHT30_1000.open", "invalid i2c_id", i2c_id)
+        return false
+    end
+
+    AirSHT30_1000.i2c_id = i2c_id
+    
+    --初始化I2C
+    if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
+        log.error("AirSHT30_1000.open", "i2c.setup error", i2c_id)
+        return false
+    end
+
+    return true
+end
+
+--读取温湿度数据;
+
+--返回值:失败返回false;
+--       成功返回两个值,第一个为摄氏温度值(number类型,例如23.6表示23.6摄氏度),第二个为百分比湿度值(number类型,例如67表示67%的湿度)
+function AirSHT30_1000.read()
+
+    -- 发送启动测量命令(高精度)
+    i2c.send(AirSHT30_1000.i2c_id, slave_addr, {0x24, 0x00})
+        
+    -- 等待测量完成(SHT30高精度测量需~15ms)
+    sys.wait(20)
+    
+    -- 读取6字节数据(温度高/低 + CRC,湿度高/低 + CRC)
+    local data = i2c.recv(AirSHT30_1000.i2c_id, slave_addr, 6)
+
+    -- 如果没有读取到6字节数据
+    if type(data)~="string" or data:len()~=6 then
+        log.error("AirSHT30_1000.read", "i2c.recv error")
+        return false
+    end
+
+    -- log.info("AirSHT30_1000.read", data:toHex())
+
+    --如果校验值正确
+    if crc8({data:byte(1), data:byte(2)}) == data:byte(3) and crc8({data:byte(4), data:byte(5)}) == data:byte(6) then 
+        -- 提取原始温度值
+        local temp_raw = (data:byte(1) << 8) | data:byte(2)
+        -- 提取原始湿度值
+        local hum_raw = (data:byte(4) << 8) | data:byte(5)
+        
+        -- 转换为实际值(根据SHT30数据手册公式)
+        local temprature = (-45 + 175 * temp_raw / 65535.0)
+        local humidity = (100 * hum_raw / 65535.0)
+        
+        -- 打印输出结果(保留2位小数)
+        -- log.info("AirSHT30_1000.read", "temprature", string.format("%.2f ℃", temprature))
+        -- log.info("AirSHT30_1000.read", "temprature", string.format("%.2f %%RH", humidity))
+
+        return temprature, humidity
+    else
+        log.error("AirSHT30_1000.read", "crc error", i2c_id)
+        return false
+    end
+end
+
+
+--关闭AirSHT30_1000
+
+--返回值:成功返回true,失败返回false
+function AirSHT30_1000.close()
+    --close接口没有返回值,理论上不会关闭失败
+    i2c.close(AirSHT30_1000.i2c_id)
+
+    return true
+end
+
+
+return AirSHT30_1000

+ 19 - 9
module/Air8000/demo/accessory_board/AIRSPINAND_1000/main.lua → module/Air8000/demo/accessory_board/AirSHT30_1000/main.lua

@@ -1,11 +1,25 @@
-PROJECT = "Air8000_SPI_lf_NAND"
+--[[
+必须定义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进行远程升级,根据自己项目的需求,自定义格式即可
+
+AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
+本demo演示的核心功能为:
+Air8000核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+更多说明参考本目录下的readme.md文件
+]]
+PROJECT = "AirSHT30_1000"
 VERSION = "001.000.000"
 
 
 -- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
-
 -- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
 -- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
@@ -37,14 +51,10 @@ end
 -- sys.timerLoopStart(function()
 --     log.info("mem.lua", rtos.meminfo())
 --     log.info("mem.sys", rtos.meminfo("sys"))
--- end, 3000)
-
-
-
--- 加载AIRSPINAND_1000功能模块
-require "AIRSPINAND_1000"
-
+--  end, 3000)
 
+ -- 加载sht30应用模块
+ require "sht30_app"
 
 
 -- 用户代码已结束---------------------------------------------

+ 57 - 0
module/Air8000/demo/accessory_board/AirSHT30_1000/readme.md

@@ -0,0 +1,57 @@
+
+## 演示功能概述
+
+AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
+
+本demo演示的核心功能为:
+
+Air8000开发板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+
+
+## 核心板+配件板资料
+
+[Air8000开发板+配件板相关资料](https://docs.openluat.com/air8000/product/shouce/#air8000_1)
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/accessory/AirSHT30_1000/image/connect_8000.jpg)
+
+![](https://docs.openluat.com/accessory/AirSHT30_1000/image/8000.png)
+
+1、Air8000开发板
+
+2、AirSHT30_1000配件板
+
+3、母对母的杜邦线4根
+
+| Air8000开发板 | AirSHT30_1000配件板|
+| ------------ | ------------------ |
+|     VDD_EXT     |         3V3        |
+|     GND   |         GND        |
+| I2C1_SDA  |         SDA        |
+| I2C1_SCL |         SCL        |
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8000最新版本的内核固件](https://docs.openluat.com/air8000/luatos/firmware/)
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、通过观察Luatools的运行日志,每隔1秒出现一次类似于下面的打印,就表示测试正常
+
+``` lua
+[2025-09-23 14:56:38.486][000000007.559] I/user.read_sht30_task_func temprature 27.13 ℃
+[2025-09-23 14:56:38.486][000000007.559] I/user.read_sht30_task_func humidity 70.86 %RH

+ 35 - 0
module/Air8000/demo/accessory_board/AirSHT30_1000/sht30_app.lua

@@ -0,0 +1,35 @@
+--加载AirSHT30_1000驱动文件
+local air_sht30 = require "AirSHT30_1000"
+
+
+--每隔1秒读取一次温湿度数据
+local function read_sht30_task_func()
+    
+    --打开sht30硬件
+    air_sht30.open(1)
+
+    while true do
+        --读取温湿度数据
+        local temprature, humidity = air_sht30.read()
+
+        --读取成功
+        if temprature then
+            -- 打印输出结果(保留2位小数)
+            log.info("read_sht30_task_func", "temprature", string.format("%.2f ℃", temprature))
+            log.info("read_sht30_task_func", "humidity", string.format("%.2f %%RH", humidity))
+        --读取失败
+        else
+            log.error("read_sht30_task_func", "read error")
+        end
+
+        --等待1秒
+        sys.wait(1000)
+    end
+
+    --关闭sht30硬件
+    air_sht30.close()
+end
+
+--创建一个task,并且运行task的主函数read_sht30_task_func
+sys.taskInit(read_sht30_task_func)
+

+ 4 - 2
module/Air8000/demo/accessory_board/AIRSPINAND_1000/AIRSPINAND_1000.lua → module/Air8000/demo/accessory_board/AirSPINAND_1000/AirSPINAND_1000.lua

@@ -20,7 +20,7 @@ SPI1_MISO          DO,主机输入,从机输出
 1.以对象的方式配置参数,初始化启用SPI,返回SPI对象
 2.用SPI对象初始化flash设备,返回flash设备对象
 3.用lf库挂载flash设备对象为文件系统
-4.读取文件系统的信息,以确认内存足够用于文件操作
+4.读取文件系统的信息,以确认内存情况
 5.操作文件读写,并验证写入一致性,追加文件等。
 
 ]]
@@ -173,7 +173,8 @@ local function spinand_test_func()
     -- 流程2:初始化Flash设备
     local flash_device = init_flash_device(spi_device)
     if not flash_device then
-        log.error("主流程", "Flash初始化失败,终止")
+        log.error("主流程", "Flash初始化失败,终止")        
+        spi_close_func()
         return
     end
 
@@ -181,6 +182,7 @@ local function spinand_test_func()
     local mount_point = "/little_flash"
     if not mount_filesystem(flash_device, mount_point) then
         log.error("主流程", "文件系统挂载失败,终止")
+        spi_close_func()
         return
     end
 

Some files were not shown because too many files changed in this diff