马亚丹 6 сар өмнө
parent
commit
7295bebe9f

+ 230 - 0
module/Air8000/demo/ftp/ftp_up_download.lua

@@ -0,0 +1,230 @@
+--[[
+@module  ftp_up_download
+@summary ftp服务器上传下载文件处理应用功能模块
+@version 001.000.000
+@date    2025.07.29
+@author  马亚丹
+@usage
+本文件为ftp服务器上传下载文件处理主应用功能模块,核心业务逻辑为:
+1. 配置FTP客户端登录服务器的参数和文件路径
+2. 封装一个重试机制,在登录失败、上传文件失败或者下载文件失败时尝试重新执行操作
+3. 登录FTP服务器,通过重试机制确保登录成功
+4. ftp.push上传本地文件到服务器,在本地新建文件并写入内容后上传到服务器指定路径,通过重试机制确保上传成功
+5. ftp.pull从服务器下载文件,保存在本地指定路径,并读取文件长度,当长度小于指定字节时,读取文件内容,通常是设定512字节,如果文件太大,会消耗ram,通过重试机制确保上传成功
+6. 主函数循环运行以下流程:  登录服务器、用 ftp.command 操作 ftp 服务器目录以及文件上传下载处理后关闭服务器。
+
+测试服务器为:
+非ssl加密:
+local server_ip = "121.43.224.154" -- 服务器IP
+local server_port = 21 -- 服务器端口号
+local server_username = "ftp_user" -- 服务器登陆用户名
+local server_password = "3QujbiMG" -- 服务器登陆密码
+
+本文件没有对外接口,直接在main.lua中require "ftp_up_download"就可以加载运行
+]]
+
+
+
+--1. 统一配置FTP参数和文件路径
+local config = {
+    server = {
+        ip = "121.43.224.154", -- FTP服务器IP
+        port = 21,             -- FTP端口
+        username = "ftp_user", -- 登陆用户名
+        password = "3QujbiMG", -- 登陆密码
+        is_ssl = false,        -- 若需SSL,补充证书
+    },
+    download = {
+        remote_file = "/12222.txt",      -- 服务器上要下载的文件
+        local_file = "/ftp_download.txt" -- 本地保存的文件名
+    },
+    upload = {
+        local_file = "/ftp_upload.txt",         -- 本地要上传的文件(会自动创建)
+        remote_file = "/uploaded_by_luatos.txt" -- 服务器保存的文件名
+    }
+}
+
+
+--2.定义功能函数: 重试机制封装,针对易失败操作
+local function retry_operation(operation, max_retries, interval)
+    local retries = 0
+    while retries < max_retries do
+        local result = operation()
+        if result then return true end
+        retries = retries + 1
+        log.warn("操作失败,重试第", retries, "次")
+        sys.wait(interval)
+    end
+    log.error("超过最大重试次数")
+    return false
+end
+
+--3.定义功能函数: 登录FTP服务器(带重试)
+local function ftp_login()
+    --查看网卡适配器的联网状态是否IP_READY,true表示已经准备好可以联网了,false暂时不可以联网
+    while not socket.adapter(socket.dft()) do
+        log.warn("ftp_login", "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("ftp_login", "recv IP_READY", socket.dft())
+
+    --登录FTP服务器核心函数
+    local function ftp_login_result()
+        local login_result = ftp.login(
+            nil,
+            config.server.ip,
+            config.server.port,
+            config.server.username,
+            config.server.password,
+            config.server.is_ssl
+        ).wait()
+        if login_result then
+            log.info("FTP登录成功")           
+            return true
+        end
+        log.error("FTP登录失败")
+        return false
+    end
+
+    -- 如果登录失败,最多重试登录3次,间隔3秒,可按需修改
+    return retry_operation(ftp_login_result, 3, 3000)
+end
+
+--4.定义功能函数: 上传文件到服务器(带重试)
+local function ftp_upload_file()
+    -- 确保本地文件创建成功
+    -- 要写入文件的内容,按需求修改
+    local upload_content = "Luatos FTP上传测试数据 "
+    local file, err = io.open(config.upload.local_file, "w")
+    if not file then
+        log.error("创建本地文件失败:", err)
+        return false
+    end
+    --写入内容到文件
+    file:write(upload_content)
+    file:close()
+
+    --打印创建文件的结果
+    log.info("本地文件" .. config.upload.local_file .. "创建成功," .. "并写入文件内容:", upload_content)
+
+    -- 上传文件核心函数
+    local function ftp_upload_result()
+        log.info("开始上传文件:" .. config.upload.local_file)
+        -- ftp.push参数:本地文件路径 → 服务器文件路径
+        local upload_ok = ftp.push(
+            config.upload.local_file,
+            config.upload.remote_file
+        ).wait()
+        if upload_ok then
+            log.info("本地文件上传成功,保存在服务器路径:", config.upload.remote_file)
+            return true
+        end
+        log.error("文件上传失败")
+        return false
+    end
+
+    -- 如果上传失败,最多重试上传3次,间隔2秒,可按需修改
+    return retry_operation(ftp_upload_result, 3, 2000)
+end
+
+--5.定义功能函数: 下载文件到本地(带重试)
+local function ftp_download_file()
+    local function ftp_download_result()
+        log.info(" 开始下载文件:" .. config.download.remote_file)
+        -- ftp.pull参数:  本地文件路径→服务器文件路径
+        local download_ok = ftp.pull(
+            config.download.local_file,
+            config.download.remote_file
+        ).wait()
+        if not download_ok then
+            log.error("文件下载失败")
+            return false
+        end
+        
+        --检查下载结果
+        local file = io.open(config.download.local_file, "r")
+        if not file then
+            log.error("下载文件本地打开失败!重新下载")
+            return false
+        end
+        
+        local fsize = io.fileSize(config.download.local_file)
+        if not fsize then
+            log.error("读取文件大小失败,重新下载")
+            return false
+        end
+        log.info("服务器上文件" .. config.download.remote_file .. "下载成功,保存在本地路径:", config.download.local_file, "大小:", fsize,
+            "字节")
+
+        if fsize and fsize <= 512 then
+            local content = file:read("*a")
+            file:close()
+            log.info("下载文件内容长度小于512字节,内容是:", content)
+            return true
+        end
+        -- 执行完操作后,一定要关掉文件
+        file:close()
+    end
+
+    -- 如果下载失败,最多重试10次,间隔2秒,可按需修改
+    return retry_operation(ftp_download_result, 10, 2000)
+end
+
+
+--6.定义功能函数: 功能测试主函数
+local function ftp_main_task()
+    while true do
+        -- 步骤1:登录FTP服务器
+        if not ftp_login() then
+            log.error("登录失败,退出流程")
+            --退出前关闭连接,释放资源
+            ftp.close().wait()
+            return
+        end
+
+        
+        -- 执行FTP命令并检查结果
+        log.info("空操作,防止连接断掉", ftp.command("NOOP").wait())
+        log.info("报告远程系统的操作系统类型", ftp.command("SYST").wait())
+        log.info("指定文件类型", ftp.command("TYPE I").wait())
+        log.info("显示当前工作目录名", ftp.command("PWD").wait())
+        log.info("创建一个目录 目录名为QWER", ftp.command("MKD QWER").wait())
+        log.info("改变当前工作目录为QWER", ftp.command("CWD /QWER").wait())
+        log.info("返回上一层目录", ftp.command("CDUP").wait())
+        log.info("获取当前工作目录下的文件名列表", ftp.command("LIST").wait())
+
+        -- 步骤2:上传文件
+        if not ftp_upload_file() then
+            log.error("上传失败,继续执行下载") -- 不中断流程
+        end
+       
+        -- 步骤3:下载文件
+        if not ftp_download_file() then
+            log.error("下载失败")
+        end
+
+        -- 步骤4:清理操作(可选),清理之前创建的目录
+        log.info("删除测试目录QWER", ftp.command("RMD QWER").wait())        
+
+        -- 步骤5:关闭连接
+        local close_result = ftp.close().wait()
+        log.info("FTP连接关闭结果:", close_result)
+
+        --步骤6:获取内存信息
+        local meminfo_result = rtos.meminfo("sys")
+        log.info("meminfo内存信息", rtos.meminfo("sys"))
+        --循环执行,每1分钟执行一次
+        sys.wait(1 * 60 * 1000)
+    end
+end
+
+-- 启动主任务
+sys.taskInit(ftp_main_task)

+ 75 - 86
module/Air8000/demo/ftp/main.lua

@@ -1,92 +1,81 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 001.000.000
+@date    2025.07.28
+@author  马亚丹
+@usage
+1. 详细逻辑请看ftp_up_download.lua文件
+2. netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+   (1) netdrv_4g:4G网卡
+   (2) netdrv_wifi:WIFI STA网卡
+   (3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+   (4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
+]]
 
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "ftpdemo"
-VERSION = "1.0.0"
 
 --[[
-本demo需要ftp库, 大部分能联网的设备都具有这个库
-ftp也是内置库, 无需require
+必须定义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 = "ftp_demo"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+
+-- 加载网络驱动设备功能模块,在该文件中修改自己使用的联网方式
+require"netdrv_device"
+
+
+--加载ftp_up_download功能模块
+require "ftp_up_download"
+
+
+
 
--- sys库是标配
-_G.sys = require("sys")
---[[特别注意, 使用ftp库需要下列语句]]
-_G.sysplus = require("sysplus")
-
-sys.taskInit(function()
-    -----------------------------
-    -- 统一联网函数, 可自行删减
-    ----------------------------
-    if wlan and wlan.connect then
-        -- wifi 联网, ESP32系列均支持
-        local ssid = "luatos1234"
-        local password = "12341234"
-        log.info("wifi", ssid, password)
-        -- TODO 改成esptouch配网
-        -- LED = gpio.setup(12, 0, gpio.PULLUP)
-        wlan.init()
-        wlan.setMode(wlan.STATION)
-        wlan.connect(ssid, password, 1)
-        local result, data = sys.waitUntil("IP_READY", 30000)
-        log.info("wlan", "IP_READY", result, data)
-        device_id = wlan.getMac()
-        -- TODO 获取mac地址作为device_id
-    elseif mobile then
-        -- Air8000/Air600E系列
-        --mobile.simid(2)
-        -- LED = gpio.setup(27, 0, gpio.PULLUP)
-        device_id = mobile.imei()
-        sys.waitUntil("IP_READY", 30000)
-    end
-
-    -- -- 打印一下支持的加密套件, 通常来说, 固件已包含常见的99%的加密套件
-    -- if crypto.cipher_suites then
-    --     log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-    -- end
-    while true do
-        sys.wait(1000)
-        log.info("ftp 启动")
-        print(ftp.login(nil,"121.43.224.154",21,"ftp_user","3QujbiMG").wait())
-    
-        print(ftp.command("NOOP").wait())
-        print(ftp.command("SYST").wait())
-
-        print(ftp.command("TYPE I").wait())
-        print(ftp.command("PWD").wait())
-        print(ftp.command("MKD QWER").wait())
-        print(ftp.command("CWD /QWER").wait())
-
-        print(ftp.command("CDUP").wait())
-        print(ftp.command("RMD QWER").wait())
-
-        print(ftp.command("LIST").wait())
-
-        -- io.writeFile("/1222.txt", "23noianfdiasfhnpqw39fhawe;fuibnnpw3fheaios;fna;osfhisao;fadsfl")
-        -- print(ftp.push("/1222.txt","/12222.txt").wait())
-        
-        print(ftp.pull("/122224.txt","/122224.txt").wait())
-
-        local f = io.open("/122224.txt", "r")
-        if f then
-            local data = f:read("*a")
-            f:close()
-            log.info("fs", "writed data", data)
-        else
-            log.info("fs", "open file for read failed")
-        end
-
-        print(ftp.command("DELE /12222.txt").wait())
-        print(ftp.push("/122224.txt","/12222.txt").wait())
-        print(ftp.close().wait())
-        log.info("meminfo", rtos.meminfo("sys"))
-        sys.wait(15000)
-    end
-
-
-end)
-
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
+-- 启动系统调度(必须放在最后)
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

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

@@ -0,0 +1,33 @@
+--[[
+@module  netdrv_4g
+@summary “4G网卡”驱动模块
+@version 1.0
+@date    2025.08.12
+@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/ftp/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,85 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.08.12
+@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/ftp/netdrv/netdrv_multiple.lua

@@ -0,0 +1,95 @@
+--[[
+@module  netdrv_multiple
+@summary 多网卡(4G网卡、WIFI STA网卡、通过SPI外挂CH390H芯片的以太网卡)驱动模块
+@version 1.0
+@date    2025.08.12
+@author  孟伟
+@usage
+本文件为多网卡驱动模块 ,核心业务逻辑为:
+1、调用libnetif.set_priority_order配置多网卡的控制参数以及优先级;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_multiple"就可以加载运行;
+]]
+
+
+local exnetif = require "exnetif"
+
+-- 网卡状态变化通知回调函数
+-- 当libnetif中检测到网卡切换或者所有网卡都断网时,会触发调用此回调函数
+-- 当网卡切换切换时:
+--     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动作的间隔(单位毫秒,可选)
+                    -- 如果没有传入此参数,libnetif会使用默认值10秒
+                    ping_time = 3000,
+
+                    -- 连通性检测ip(选填参数);
+                    -- 如果没有传入ip地址,libnetif中会默认使用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地址,libnetif中会默认使用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数据通信出现了异常;
+-- 可以在判断出现异常的位置,调用一次libnetif.check_network_status()接口,强制对当前正式使用的网卡进行一次连通性检测;
+-- 如果存在tcp网络应用,不需要用户调用libnetif.check_network_status()接口去控制,libnetif会在tcp网络应用通信异常时自动对当前使用的网卡进行连通性检测。
+
+
+-- 启动一个task,task的处理函数为netdrv_multiple_task_func
+-- 在处理函数中调用libnetif.set_priority_order设置网卡优先级
+-- 因为libnetif.set_priority_order要求必须在task中被调用,所以此处启动一个task
+sys.taskInit(netdrv_multiple_task_func)

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

@@ -0,0 +1,50 @@
+--[[
+@module  netdrv_wifi
+@summary “WIFI STA网卡”驱动模块
+@version 1.0
+@date    2025.08.12
+@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("kfyy123", "kfyy123456", 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/ftp/netdrv_device.lua

@@ -0,0 +1,33 @@
+--[[
+@module  netdrv_device
+@summary 网络驱动设备功能模块
+@version 1.0
+@date    2025.08.12
+@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"

+ 148 - 0
module/Air8000/demo/ftp/readme.md

@@ -0,0 +1,148 @@
+## 功能模块介绍:
+
+1、main.lua:主程序入口;
+
+2、netdrv_device.lua:网卡驱动设备,可以配置使用netdrv文件夹内的四种网卡(单4g网卡,单wifi网卡,单spi以太网卡,多网卡)中的任何一种网卡;
+
+3、netdrv文件夹:四种网卡,单4g网卡、单wifi网卡,、单spi以太网卡、多网卡,供netdrv_device.lua加载配置;
+
+4、ftp_up_download.lua:   功能演示核心脚本,操作ftp文件、上传和下载文件等,在main.lua中加载运行。
+
+## 演示功能概述:
+
+1、 配置FTP客户端登录服务器的参数和文件路径
+
+2、 封装一个重试机制,在登录失败、上传文件失败或者下载文件失败时尝试重新执行操作
+
+3、 登录FTP服务器,通过重试机制确保登录成功
+
+4、 ftp.push上传本地文件到服务器,在本地新建文件并写入内容后上传到服务器指定路径,通过重试机制确保上传成功
+
+5、 ftp.pull从服务器下载文件,保存在本地指定路径,并读取文件长度,当长度小于指定字节时,读取文件内容,通常是设定512字节,如果文件太大,会消耗ram。
+
+6、 主函数循环运行以下流程: 登录服务器、用 ftp.command 操作 ftp 服务器目录以及文件上传下载处理后关闭服务器。
+   
+   
+
+## 演示硬件环境
+
+![netdrv_multi](https://docs.openluat.com/air8000/luatos/app/image/netdrv_multi.jpg)
+
+
+
+1、Air8000开发板一块+可上网的sim卡一张+4g天线一根+wifi天线一根+网线一根:
+
+* sim卡插入开发板的sim卡槽
+
+* 天线装到开发板上
+
+* 网线一端插入开发板网口,另外一端连接可以上外网的路由器网口
+
+2、TYPE-C USB数据线一根 ,Air8000开发板和数据线的硬件接线方式为:
+
+* Air8000开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+
+* TYPE-C USB数据线直接插到开发板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、 Luatools下载调试工具
+
+2、 测试服务器,非 ssl 加密:
+   local server_ip = "121.43.224.154" -- 服务器 IP   
+   local server_port = 21 -- 服务器端口号   
+   local server_username = "ftp_user" -- 服务器登陆用户名   
+   local server_password = "3QujbiMG" -- 服务器登陆密码
+
+3、 固件版本:LuatOS-SoC_V2014_Air8000_1,固件地址,如有最新固件请用最新 [https://docs.openluat.com/air8000/luatos/firmware/](https://docs.openluat.com/air8000/luatos/firmware/)
+
+4、 脚本文件:
+   main.lua
+
+   ftp_up_download.lua
+
+   netdrv_device.lua
+
+   netdrv文件夹
+
+5、 pc 系统 win11(win10 及以上)
+   
+   
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码netdrv_device.lua中,按照自己的网卡需求启用对应的Lua文件
+
+* 如果需要单4G网卡,打开require "netdrv_4g",其余注释掉
+
+* 如果需要单WIFI STA网卡,打开require "netdrv_wifi",其余注释掉;同时netdrv_wifi.lua中的wlan.connect("茶室-降功耗,找合宙!", "Air123456", 1),前两个参数,修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+* 如果需要以太网卡,打开require "netdrv_eth_spi",其余注释掉
+
+* 如果需要多网卡,打开require "netdrv_multiple",其余注释掉;同时netdrv_multiple.lua中的ssid = "茶室-降功耗,找合宙!", password = "Air123456", 修改为自己测试时wifi热点的名称和密码;注意:仅支持2.4G的wifi,不支持5G的wifi
+
+3、如果是自己的ftp服务器,脚本代码ftp_up_download.lua中,在config表中按自己的服务器IP,端口号,用户名,密码修改参数。
+
+4、Luatools烧录内核固件和修改后的demo脚本代码
+
+5、烧录成功后,代码会自动运行,查看打印日志,如果正常运行,会打印ftp登录成功,空操作和文件目录打印,上传下载文件结果以及关闭FTP连接等信息,如下log显示:
+
+```
+[2025-08-22 16:34:47.428][000000001.784] I/user.ftp_login recv IP_READY 1 3
+[2025-08-22 16:34:47.434][000000001.830] D/mobile TIME_SYNC 0
+[2025-08-22 16:34:47.506][000000002.077] I/user.FTP登录成功
+[2025-08-22 16:34:47.552][000000002.128] I/user.空操作,防止连接断掉 200 NOOP ok.
+
+[2025-08-22 16:34:47.598][000000002.178] I/user.报告远程系统的操作系统类型 215 UNIX Type: L8
+
+[2025-08-22 16:34:47.644][000000002.229] I/user.指定文件类型 200 Switching to Binary mode.
+
+[2025-08-22 16:34:47.706][000000002.278] I/user.显示当前工作目录名 257 "/"
+
+[2025-08-22 16:34:47.751][000000002.329] I/user.创建一个目录 目录名为QWER 257 "/QWER" created
+
+[2025-08-22 16:34:47.797][000000002.379] I/user.改变当前工作目录为QWER 250 Directory successfully changed.
+
+[2025-08-22 16:34:47.843][000000002.427] I/user.返回上一层目录 250 Directory successfully changed.
+
+[2025-08-22 16:34:48.062][000000002.647] I/user.获取当前工作目录下的文件名列表 
+[2025-08-22 16:34:48.071][000000002.647] drwx------    2 ftp      ftp          4096 Apr 29 08:43 080307100000002
+drwx------    2 ftp      ftp          4096 Apr 11 01:22 080307100000003
+drwx------    2 ftp      ftp          4096 Apr 11 01:22 080307100000004
+drwx------    2 ftp      ftp          4096 Apr 11 01:22 080307100000005
+drwx------    2 ftp      ftp          4096 Apr 11 01:23 080307100000006
+drwx------    2 ftp      ftp          4096 Apr 30 06:16 080307100000022
+drwx------    2 ftp      ftp          4096 Apr 30 06:16 080307100000023
+drwx------    2 ftp      ftp          4096 Apr 30 06:16 080307100000024
+drwx------    2 ftp      ftp          4096 Apr 30 06:16 080307100000025
+drwx------    2 ftp      ftp          4096 Apr 30 06:16 080307100000026
+drwx------    2 ftp      ftp          4096 Jul 25 10:40 080307100000027
+drwx------    2 ftp      ftp          4096 Jul 25 10:04 080307100000028
+-rw-------    1 ftp      ftp         27588 Jun 03 07:04 12222.mp4
+-rw-------    1 ftp      ftp            62 Aug 22 08:32 12222.txt
+-rw-------    1 ftp      ftp        174016 Jun 02 15:43 122224.mp4
+-rw-------    1 ftp      ftp         27588 Jun 02 03:54 1__19700101080014.mp4
+drwx------    2 ftp      ftp          4096 Apr 19 04:49 23543121001
+drwx------    2 ftp      ftp          4096 Aug 04 11:46 MT_T
+drwx------    2 ftp      ftp          4096 Aug 22 08:34 QWER
+drwx------    3 ftp      ftp          4096 Jul 09 08:02 Test
+drwx------    2 ftp      ftp          4096 Apr 19 04:45 YKZ
+-rw-------    1 ftp      ftp       1186492 Apr 19 06:12 data.dat
+-rw-------    1 ftp      ftp        189943 Jun 27 07:04 ftp_test.mp4
+-rw-------    1 ftp      ftp             0 Jun 02 04:00 新建 文本文档.txt
+
+[2025-08-22 16:34:48.079][000000002.651] I/user.本地文件/ftp_upload.txt创建成功,并写入文件内容: Luatos FTP上传测试数据 
+[2025-08-22 16:34:48.086][000000002.652] I/user.开始上传文件:/ftp_upload.txt
+[2025-08-22 16:34:48.452][000000003.027] I/user.本地文件上传成功,保存在服务器路径: /uploaded_by_luatos.txt
+[2025-08-22 16:34:48.546][000000003.127] I/user. 开始下载文件:/12222.txt
+[2025-08-22 16:34:48.871][000000003.446] I/user.服务器上文件/12222.txt下载成功,保存在本地路径: /ftp_download.txt 大小: 62 字节
+[2025-08-22 16:34:48.880][000000003.447] I/user.下载文件内容长度小于512字节,内容是: 23noianfdiasfhnpqw39fhawe;fuibnnpw3fheaios;fna;osfhisao;fadsfl
+[2025-08-22 16:34:48.932][000000003.517] I/user.删除测试目录QWER 250 Remove directory operation successful.
+
+[2025-08-22 16:34:49.056][000000003.636] I/user.FTP连接关闭结果: 221 Goodbye.
+
+[2025-08-22 16:34:49.061][000000003.636] I/user.meminfo内存信息 3208112 279932 285960
+
+```