Quellcode durchsuchen

add: Air8101添加httpsrv demo

王文中 vor 4 Monaten
Ursprung
Commit
c97e97ff90

+ 96 - 0
module/Air8101/demo/httpsrv/httpsrv_start.lua

@@ -0,0 +1,96 @@
+--[[
+@module  httpsrv_start
+@summary Air8101 HTTP服务器应用模块
+@version 1.0
+@date    2025.10.24
+@author  拓毅恒
+@usage
+本文件为Air8101核心板演示 HTTP 服务器功能的应用模块,核心业务逻辑为:
+1. 初始化HTTP服务器
+2. 配置HTTP服务器参数
+3. 启动HTTP服务器服务
+4. 处理HTTP请求和响应
+]]
+
+local function handle_http_request(fd, method, uri, headers, body)
+    log.info("httpsrv", method, uri, body or "")
+    
+    if uri == "/send/text" then
+        log.info("Text Request", "Method:", method, "Body Length:", body and #body or 0)
+        if method == "POST" and body and #body > 0 then
+            -- 直接打印接收到的文本内容
+            log.info("Received Text:", body)
+            return 200, {}, "ok"
+        end
+        return 400, {}, "Invalid request"
+    elseif uri == "/scan/go" then
+        wlan.scan()
+        return 200, {}, "ok"
+    elseif uri == "/scan/list" then
+        return 200, {["Content-Type"]="application/json"}, (json.encode({data=scan_result, ok=true}))
+    elseif uri == "/connect" then
+        if method == "POST" and body and #body > 2 then
+            local jdata = json.decode(body)
+            if jdata and jdata.ssid then
+                sys.timerStart(wlan.connect, 500, jdata.ssid, jdata.passwd)
+                return 200, {}, "ok"
+            end
+        end
+        return 400, {}, "ok"
+    elseif uri == "/connok" then
+        log.info("connok", json.encode({ip=socket.localIP(2)}))
+        return 200, {["Content-Type"]="application/json"}, json.encode({ip=socket.localIP(2)})
+    end
+    return 404, {}, "Not Found" .. uri
+end
+
+-- 检测当前使用的网卡适配器
+local function get_current_adapter()
+    -- 检查各个网卡是否就绪
+    if netdrv and netdrv.ready(socket.LWIP_AP) then
+        return socket.LWIP_AP, "AP"
+    elseif netdrv and netdrv.ready(socket.LWIP_STA) then
+        return socket.LWIP_STA, "STA"
+    elseif netdrv and netdrv.ready(socket.LWIP_ETH) then
+        return socket.LWIP_ETH, "ETH"
+    end
+end
+
+local function start_http_server()
+    -- 等待网络就绪事件
+    sys.waitUntil("CREATE_OK")
+    
+    -- 获取当前使用的网卡适配器
+    local adapter, adapter_name = get_current_adapter()
+    local local_ip = socket.localIP(adapter)
+    
+    -- 启动HTTP服务器
+    httpsrv.start(80, handle_http_request, adapter)
+    log.info("HTTP", "文件服务器已启动,使用" .. adapter_name .. "模式")
+    
+    if adapter == socket.LWIP_AP then
+        log.info("HTTP", "请连接WiFi: luatos8888 密码: 12345678")
+    end
+    
+    log.info("HTTP", "访问地址: http://" .. (local_ip or "192.168.4.1"))
+end
+
+local function scan_done_handle()
+    local result = wlan.scanResult()
+    scan_result = {}
+    for k, v in pairs(result) do
+        log.info("scan", (v["ssid"] and #v["ssid"] > 0) and v["ssid"] or "[隐藏SSID]", v["rssi"], (v["bssid"]:toHex()))
+        if v["ssid"] and #v["ssid"] > 0 then
+            table.insert(scan_result, {
+                ssid = v["ssid"],
+                rssi = v["rssi"]
+            })
+        end
+    end
+    log.info("scan", "aplist count:", #scan_result)
+end
+
+-- 订阅WiFi扫描完成事件
+sys.subscribe("WLAN_SCAN_DONE", scan_done_handle)
+
+sys.taskInit(start_http_server)

+ 228 - 0
module/Air8101/demo/httpsrv/index.html

@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html lang="zh">
+<header>
+    <meta charset="utf-8" />
+    <title>Air8101 控制中心</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            background-color: #f5f5f5;
+            margin: 0;
+            padding: 20px;
+            color: #333;
+        }
+        h2, h4 {
+            color: #2c3e50;
+        }
+        .container {
+            max-width: 600px;
+            margin: auto;
+            background: white;
+            padding: 20px;
+            border-radius: 8px;
+            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+        }
+        button {
+            background-color: #3498db;
+            color: white;
+            border: none;
+            padding: 10px 15px;
+            border-radius: 5px;
+            cursor: pointer;
+            font-size: 16px;
+            margin: 5px 0;
+            transition: background-color 0.3s;
+        }
+        button:hover {
+            background-color: #2980b9;
+        }
+        input[type="text"] {
+            width: 100%;
+            padding: 10px;
+            margin: 5px 0 15px;
+            border: 1px solid #ccc;
+            border-radius: 5px;
+            box-sizing: border-box;
+        }
+        .control-panel {
+            margin-bottom: 20px;
+            padding: 15px;
+            border: 1px solid #eee;
+            border-radius: 8px;
+            background-color: #fafafa;
+        }
+        .status {
+            font-weight: bold;
+            margin-top: 10px;
+            color: #27ae60;
+        }
+
+        #sendBtn {
+            background-color: #27ae60;
+        }
+        #sendBtn:hover {
+            background-color: #229954;
+        }
+        #scanBtn {
+            background-color: #9b59b6;
+        }
+        #scanBtn:hover {
+            background-color: #8e44ad;
+        }
+        #wifiResults {
+            margin-top: 15px;
+            max-height: 300px;
+            overflow-y: auto;
+            border: 1px solid #ddd;
+            border-radius: 5px;
+            padding: 10px;
+            background-color: #f9f9f9;
+        }
+        #wifiResults ul {
+            list-style: none;
+            padding: 0;
+            margin: 0;
+        }
+        #wifiResults li {
+            padding: 8px;
+            margin: 2px 0;
+            background-color: white;
+            border-radius: 4px;
+            border: 1px solid #eee;
+        }
+        .ssid {
+            font-weight: bold;
+            margin-right: 10px;
+        }
+        .rssi-strong {
+            color: #27ae60;
+        }
+        .rssi-medium {
+            color: #f39c12;
+        }
+        .rssi-weak {
+            color: #e74c3c;
+        }
+    </style>
+    <script type="text/javascript">
+
+        function sendText() {
+            var text = document.getElementById("inputText").value;
+            if (text.trim() === "") {
+                alert("请输入文本内容");
+                return;
+            }
+            
+            // 添加调试日志
+            console.log("发送文本:", text);
+            
+            fetch("/send/text", {
+                method: "POST",
+                headers: {
+                    "Content-Type": "text/plain"
+                },
+                body: text
+            }).then(function(resp) {
+                console.log("响应状态:", resp.status);
+                if (resp.status == 200) {
+                    document.getElementById("status").textContent = "文本已发送至设备日志";
+                    document.getElementById("inputText").value = "";
+                } else {
+                    document.getElementById("status").textContent = "发送失败: " + resp.status;
+                }
+            }).catch(function(error) {
+                console.error("发送错误:", error);
+                document.getElementById("status").textContent = "发送出错: " + error.message;
+            });
+        }
+        
+        // 按下回车键时发送文本
+        function handleKeyPress(event) {
+            if (event.key === "Enter") {
+                sendText();
+            }
+        }
+        
+        // WiFi扫描功能
+        function scanWifi() {
+            document.getElementById("status").textContent = "正在扫描WiFi...";
+            document.getElementById("wifiResults").innerHTML = "";
+            
+            fetch("/scan/go")
+                .then(function(resp) {
+                    if (resp.status == 200) {
+                        document.getElementById("status").textContent = "扫描已开始,正在获取结果...";
+                        // 等待1秒后获取扫描结果
+                        setTimeout(getWifiResults, 1000);
+                    } else {
+                        document.getElementById("status").textContent = "扫描失败: " + resp.status;
+                    }
+                })
+                .catch(function(error) {
+                    console.error("扫描错误:", error);
+                    document.getElementById("status").textContent = "扫描出错: " + error.message;
+                });
+        }
+        
+        // 获取WiFi扫描结果
+        function getWifiResults() {
+            fetch("/scan/list")
+                .then(function(resp) {
+                    if (resp.status == 200) {
+                        return resp.json();
+                    } else {
+                        throw new Error("获取结果失败: " + resp.status);
+                    }
+                })
+                .then(function(data) {
+                    const resultsDiv = document.getElementById("wifiResults");
+                    if (data && data.data && data.data.length > 0) {
+                        let html = "<ul>";
+                        data.data.forEach(function(ap) {
+                            let rssiClass = "rssi-weak";
+                            if (ap.rssi > -70) rssiClass = "rssi-strong";
+                            else if (ap.rssi > -85) rssiClass = "rssi-medium";
+                            
+                            html += `<li><span class="ssid">${ap.ssid}</span> <span class="${rssiClass}">信号: ${ap.rssi} dBm</span></li>`;
+                        });
+                        html += "</ul>";
+                        resultsDiv.innerHTML = html;
+                        document.getElementById("status").textContent = "扫描完成,共发现 " + data.data.length + " 个WiFi网络";
+                    } else {
+                        resultsDiv.innerHTML = "未发现WiFi网络";
+                        document.getElementById("status").textContent = "扫描完成,未发现WiFi网络";
+                    }
+                })
+                .catch(function(error) {
+                    console.error("获取结果错误:", error);
+                    document.getElementById("status").textContent = "获取结果出错: " + error.message;
+                });
+        }
+    </script>
+</header>
+<body>
+    <div class="container">
+        <h2>Air8101 控制中心</h2>
+        
+        <!-- 文本发送功能 -->
+        <div class="control-panel">
+            <h4>文本发送</h4>
+            <label for="inputText">输入文本:</label>
+            <input type="text" id="inputText" placeholder="请输入要发送到设备日志的文本" onkeypress="handleKeyPress(event)">
+            <button id="sendBtn" onclick="sendText()">发送文本</button>
+        </div>
+        
+
+        
+        <!-- WiFi扫描功能 -->
+        <div class="control-panel">
+            <h4>WiFi扫描</h4>
+            <button id="scanBtn" onclick="scanWifi()">扫描WiFi网络</button>
+            <div id="wifiResults">请点击上方按钮开始扫描</div>
+        </div>
+        
+        <!-- 状态显示 -->
+        <div class="status" id="status">就绪</div>
+    </div>
+</body>
+</html>

+ 74 - 0
module/Air8101/demo/httpsrv/main.lua

@@ -0,0 +1,74 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.24
+@author  拓毅恒
+@usage
+本demo演示的核心功能为:
+HTTP服务器应用功能,通过加载httpsrv_start模块来启动和配置HTTP服务器,处理HTTP请求和响应。
+
+netdrv_device:配置连接外网使用的网卡,目前支持以下四种选择(四选一)
+   (1) netdrv_4g:4G网卡
+   (2) netdrv_wifi:WIFI STA网卡
+   (3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
+   (4) netdrv_eth_rmii:通过MAC层的RMII接口外挂PHY芯片(LAN8720Ai)的以太网卡驱动,支持AirPHY_1000配件板,实现稳定的有线网络连接;
+
+更多说明参考本目录下的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 = "httpsrv_testdemo"
+VERSION = "001.000.000"
+
+log.info("main", "project name is ", PROJECT, "version is ", 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"
+
+-- 加载 httpsrv_start 功能模块
+require "httpsrv_start"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 50 - 0
module/Air8101/demo/httpsrv/netdrv/netdrv_ap.lua

@@ -0,0 +1,50 @@
+--[[
+@module  netdrv_ap
+@summary "WIFI AP网卡"驱动模块
+@version 1.0
+@date    2025.11.4
+@author  拓毅恒
+@usage
+本文件为WIFI AP网卡驱动模块,核心业务逻辑为:
+1、初始化网络;
+2、创建WIFI AP热点;
+3、配置IP地址和DHCP服务器;
+4、发布AP创建完成事件;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_ap"就可以加载运行;
+]]
+
+dnsproxy = require("dnsproxy")
+dhcpsrv = require("dhcpsrv")
+
+-- AP热点创建完成回调函数
+local function ap_ready_func()
+    log.info("netdrv_ap", "AP热点创建成功,IP地址为: 192.168.4.1")
+    -- 发布AP创建完成事件
+    sys.publish("CREATE_OK")
+end
+
+-- 创建并启动AP热点初始化任务
+local function netdrv_ap_init_task()
+    -- 初始化WIFI
+    wlan.init()
+    log.info("netdrv_ap", "执行AP创建操作", "luatos8888")
+    sys.wait(100)
+    -- 创建AP热点,名称为luatos8888,密码为12345678
+    wlan.createAP("luatos8888", "12345678")
+    -- AP启动成功后,设置IP地址和DHCP服务器
+    netdrv.ipv4(socket.LWIP_AP, "192.168.4.1", "255.255.255.0", "0.0.0.0")
+    -- 等待网络准备就绪
+    while netdrv.ready(socket.LWIP_AP) ~= true do
+        sys.wait(100)
+    end
+    -- 设置DNS代理
+    dnsproxy.setup(socket.LWIP_AP, socket.LWIP_GP)
+    -- 创建DHCP服务器
+    dhcpsrv.create({adapter=socket.LWIP_AP})
+    -- 调用AP就绪回调
+    ap_ready_func()
+end
+
+-- 启动AP初始化任务
+sys.taskInit(netdrv_ap_init_task)

+ 78 - 0
module/Air8101/demo/httpsrv/netdrv/netdrv_eth_rmii.lua

@@ -0,0 +1,78 @@
+--[[
+@module  netdrv_eth_rmii
+@summary “通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”驱动模块 
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”驱动模块 ,核心业务逻辑为:
+1、打开PHY芯片供电开关;
+2、初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+Air8101核心板和AirPHY_1000配件板的硬件接线方式为:
+Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+| Air8101核心板 |  AirPHY_1000配件板  |
+| ------------ | ------------------ |
+|    59/3V3    |         3.3v       |
+|     gnd      |         gnd        |
+|     5/D2     |         RX1        |
+|    72/D1     |         RX0        |
+|    71/D3     |         CRS        |
+|     4/D0     |         MDIO       |
+|     6/D4     |         TX0        |
+|    74/PCK    |         MDC        |
+|    70/D5     |         TX1        |
+|     7/D6     |         TXEN       |
+|     不接     |          NC        |
+|    69/D7     |         CLK        |
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_rmii"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_ETH then
+        log.info("netdrv_eth_rmii.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+        sys.publish("CREATE_OK")
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_rmii.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
+-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”的连接状态
+-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 设置默认网卡为socket.LWIP_ETH
+socket.dft(socket.LWIP_ETH)
+
+
+-- 本demo测试使用的是核心板的VDD 3V3引脚对AirPHY_1000配件板进行供电
+-- VDD 3V3引脚是Air8101内部的LDO输出引脚,最大输出电流300mA
+-- GPIO13在Air8101内部使能控制这个LDO的输出
+-- 所以在此处GPIO13输出高电平打开这个LDO
+gpio.setup(13, 1, gpio.PULLUP)
+
+
+--初始化以太网卡
+
+--以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+--各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+--也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+--以太网断网后,内核固件会产生一个"IP_LOSE"消息
+--各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+--也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+netdrv.setup(socket.LWIP_ETH)
+
+--在以太网卡上开启动态主机配置协议
+netdrv.dhcp(socket.LWIP_ETH, true)

+ 103 - 0
module/Air8101/demo/httpsrv/netdrv/netdrv_eth_spi.lua

@@ -0,0 +1,103 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块 ,核心业务逻辑为:
+1、打开AirETH_1000配件板供电开关;
+2、初始化spi0,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+Air8101核心板和AirETH_1000配件板的硬件接线方式为:
+Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端);
+如果测试发现软件重启,并且日志中出现  poweron reason 0,表示供电不足,此时再通过直流稳压电源对核心板的VIN管脚进行5V供电;
+| Air8101核心板    | AirETH_1000配件板  |
+| --------------- | ----------------- |
+| 59/3V3          | 3.3v              |
+| gnd             | gnd               |
+| 28/DCLK         | SCK               |
+| 54/DISP         | CSS               |
+| 55/HSYN         | SDO               |
+| 57/DE           | SDI               |
+| 14/GPIO8        | INT               |
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_USER1 then
+        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_USER1))
+        sys.publish("CREATE_OK")
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_USER1 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_USER1
+socket.dft(socket.LWIP_USER1)
+
+
+-- 本demo测试使用的是核心板的VDD 3V3引脚对AirETH_1000配件板进行供电
+-- VDD 3V3引脚是Air8101内部的LDO输出引脚,最大输出电流300mA
+-- GPIO13在Air8101内部使能控制这个LDO的输出
+-- 所以在此处GPIO13输出高电平打开这个LDO
+gpio.setup(13, 1, gpio.PULLUP)
+
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI0
+    local result = spi.setup(
+        0,--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_USER1)来获取以太网是否连接成功
+
+    -- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
+    -- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+    -- 也可以在任何时刻调用socket.adapter(socket.LWIP_USER1)来获取以太网是否连接成功
+
+    -- socket.LWIP_USER1 指定网络适配器编号
+    -- netdrv.CH390外挂CH390
+    -- SPI ID 0, 片选 GPIO15
+    netdrv.setup(socket.LWIP_USER1, netdrv.CH390, {spi=0, cs=15})
+
+    -- 在以太上开启动态主机配置协议
+    netdrv.dhcp(socket.LWIP_USER1, true)
+end
+
+-- 创建并且启动一个task
+-- task的处理函数为netdrv_eth_spi_task_func
+sys.taskInit(netdrv_eth_spi_task_func)

+ 55 - 0
module/Air8101/demo/httpsrv/netdrv/netdrv_wifi.lua

@@ -0,0 +1,55 @@
+--[[
+@module  netdrv_wifi
+@summary “WIFI STA网卡”驱动模块
+@version 1.0
+@date    2025.11.4
+@author  拓毅恒
+@usage
+本文件为WIFI STA网卡驱动模块,核心业务逻辑为:
+1、初始化WIFI网络;
+2、连接WIFI路由器;
+3、和WIFI路由器之间的连接状态发生变化时,在日志中进行打印;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_wifi"就可以加载运行;
+]]
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_STA then
+        log.info("netdrv_wifi.ip_ready_func", "IP_READY", json.encode(wlan.getInfo()))
+        -- STA模式联网成功后发布CREATE_OK事件,通知HTTP服务器启动
+        sys.publish("CREATE_OK")
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_STA then
+        log.warn("netdrv_wifi.ip_lose_func", "IP_LOSE")
+    end
+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("茶室-降功耗,找合宙!", "Air123456", 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网络是否连接成功

+ 31 - 0
module/Air8101/demo/httpsrv/netdrv_device.lua

@@ -0,0 +1,31 @@
+--[[
+@module  netdrv_device
+@summary 网络驱动设备功能模块
+@version 1.0
+@date    2025.11.4
+@author  拓毅恒
+@usage
+本文件为网络驱动设备功能模块,核心业务逻辑为:根据项目需求,选择并且配置合适的网卡(网络适配器)
+1、netdrv_ap:socket.LWIP_AP,WIFI AP网卡;
+2、netdrv_wifi:socket.LWIP_STA,WIFI STA网卡;
+3、netdrv_eth_spi:socket.LWIP_ETH,通过SPI外挂CH390H芯片的以太网卡;
+
+无论选择哪种网卡模式,在成功联网后都会发布"CREATE_OK"事件,用于通知httpsrv_start.lua启动HTTP服务器。
+
+使用说明:取消注释下面对应网卡的require语句即可使用该网卡模式。
+本文件没有对外接口,直接在main.lua中require "netdrv_device"就可以加载运行;
+]]
+
+-- 配置选择的网卡模式,取消注释对应行以启用
+
+-- 加载"WIFI AP网卡"驱动模块(默认启用)
+require "netdrv_ap"
+
+-- 加载"WIFI STA网卡"驱动模块
+-- require "netdrv_wifi"
+
+-- 加载“通过MAC层的rmii接口外挂PHY芯片(LAN8720Ai)的以太网卡”驱动模块
+-- require "netdrv_eth_rmii"
+
+-- 加载“通过SPI外挂CH390H芯片的以太网卡”驱动模块
+-- require "netdrv_eth_spi"

+ 250 - 0
module/Air8101/demo/httpsrv/readme.md

@@ -0,0 +1,250 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、httpsrv_start.lua:HTTP服务器实现模块,包含服务器初始化、路由处理、文本发送和WiFi扫描功能;
+
+3、index.html:Web控制界面,提供LED控制按钮、文本发送输入框和WiFi扫描功能;
+
+4、netdrv_device.lua:网络驱动设备功能模块,用于选择和配置合适的网卡模式;
+
+5、netdrv/netdrv_ap.lua:WiFi AP模式网卡驱动,创建WiFi热点;
+
+6、netdrv/netdrv_wifi.lua:WiFi STA模式网卡驱动,连接外部WiFi路由器;
+
+7、netdrv/netdrv_eth_spi.lua:以太网SPI网卡驱动,通过SPI接口连接CH390H芯片实现有线网络连接;
+
+8、netdrv/netdrv_eth_rmii.lua:通过MAC层的RMII接口外挂PHY芯片(LAN8720Ai)的以太网卡驱动,支持AirPHY_1000配件板,实现稳定的有线网络连接;
+
+## 演示功能概述
+
+1、HTTP服务器:创建Web服务器,提供Web控制界面
+
+- 支持四种网卡模式:WiFi AP模式、WiFi STA模式、以太网SPI模式和以太网RMII模式
+- HTTP服务器监听80端口,具体IP地址取决于使用的网卡模式:
+  - WiFi AP模式:自动创建名为"luatos8888"的WiFi热点,密码为"12345678",IP地址为192.168.4.1
+  - WiFi STA模式:连接外部WiFi路由器,IP地址由路由器DHCP分配
+  - 以太网SPI模式:通过网线连接网络,IP地址由路由器DHCP分配
+  - 以太网RMII模式:通过网线连接网络,IP地址由路由器DHCP分配
+- 支持访问Web控制界面
+
+2、LED控制功能:通过Web界面控制设备上的LED灯
+
+- 提供点亮LED(/led/1)接口
+- 提供熄灭LED(/led/0)接口
+- Web界面上有对应的控制按钮
+
+3、文本发送功能:通过Web界面发送文本数据
+
+- 提供文本发送(/send/text)接口
+- 支持在Web界面的输入框中输入文本并发送
+- 发送的文本会在设备日志中显示
+
+4、WiFi扫描功能:搜索周围可用的WiFi热点
+
+- 提供开始扫描(/scan/go)接口
+- 提供获取扫描结果(/scan/list)接口
+- Web界面上有扫描按钮,点击后显示周围WiFi热点列表
+- 显示WiFi的SSID和信号强度信息
+
+## 演示硬件环境
+
+1、Air8101核心板一块+可上网的sim卡一张+wifi天线一根:
+
+- 天线装到开发板上
+
+2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air8101核心板和数据线的硬件接线方式为:
+
+- Air8101核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 选择网卡模式
+
+本项目支持三种网卡模式,默认使用WiFi AP模式。要切换网卡模式,请按照以下步骤操作:
+
+1、打开 `netdrv_device.lua` 文件
+
+2、取消注释你想要使用的网卡模式对应的require语句,并确保其他模式的require语句保持注释状态
+
+示例:
+
+- 使用WiFi AP模式(默认):
+
+```lua
+-- 加载"WIFI AP网卡"驱动模块(默认启用)
+require "netdrv_ap"
+
+-- 加载"WIFI STA网卡"驱动模块
+-- require "netdrv_wifi"
+
+-- 加载"通过SPI外挂CH390H芯片的以太网卡"驱动模块
+-- require "netdrv_eth_spi"
+```
+
+- 使用WiFi STA模式:
+
+```lua
+-- 加载"WIFI AP网卡"驱动模块(默认启用)
+-- require "netdrv_ap"
+
+-- 加载"WIFI STA网卡"驱动模块
+require "netdrv_wifi"
+
+-- 加载"通过SPI外挂CH390H芯片的以太网卡"驱动模块
+-- require "netdrv_eth_spi"
+```
+
+- 使用以太网SPI模式:
+
+```lua
+-- 加载"WIFI AP网卡"驱动模块(默认启用)
+-- require "netdrv_ap"
+
+-- 加载"WIFI STA网卡"驱动模块
+-- require "netdrv_wifi"
+
+-- 加载"通过SPI外挂CH390H芯片的以太网卡"驱动模块
+require "netdrv_eth_spi"
+```
+
+- 使用以太网RMII模式:
+
+```lua
+-- 加载"WIFI AP网卡"驱动模块(默认启用)
+-- require "netdrv_ap"
+
+-- 加载"WIFI STA网卡"驱动模块
+-- require "netdrv_wifi"
+
+-- 加载"通过SPI外挂CH390H芯片的以太网卡"驱动模块
+-- require "netdrv_eth_spi"
+
+-- 加载"通过MAC层的rmii接口外挂PHY芯片的以太网卡"驱动模块
+require "netdrv_eth_rmii"
+```
+
+3、如果使用WiFi STA模式,请修改 `netdrv/netdrv_wifi.lua` 文件中的WiFi配置:
+
+```lua
+-- 连接WIFI热点,连接结果会通过"IP_READY"或者"IP_LOSE"消息通知
+-- 此处前两个参数表示WIFI热点名称以及密码,更换为自己测试时的真实参数
+wlan.connect("你的WiFi名称", "你的WiFi密码", 1)
+```
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、Air8101固件[Air8101 版本固件](https://docs.openluat.com/air8101/luatos/firmware/)
+
+## 演示核心步骤
+
+### WiFi AP模式(默认)
+
+1、搭建好硬件环境
+
+2、使用Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,设备自动开机运行,创建名为"luatos8888"的WiFi热点(密码:12345678)
+
+4、使用电脑或手机连接到"luatos8888" WiFi热点
+
+5、在浏览器中输入地址:http://192.168.4.1,访问Web控制界面
+
+### WiFi STA模式
+
+1、按照选择网卡模式的说明,配置为WiFi STA模式并设置正确的WiFi名称和密码
+
+2、使用Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,设备自动开机运行并尝试连接配置的WiFi路由器
+
+4、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.100)
+
+5、确保你的电脑或手机连接到同一WiFi网络
+
+6、在浏览器中输入设备的IP地址(如http://192.168.1.100),访问Web控制界面
+
+### 以太网SPI模式
+
+1、按照选择网卡模式的说明,配置为以太网SPI模式
+
+2、确保CH390H以太网模块正确连接到Air8000开发板
+
+3、使用网线将以太网模块连接到路由器或网络交换机
+
+4、使用Luatools烧录内核固件和demo脚本代码
+
+5、烧录成功后,设备自动开机运行并尝试通过以太网连接到网络
+
+6、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.101)
+
+7、确保你的电脑连接到同一路由器或网络
+
+8、在浏览器中输入设备的IP地址(如http://192.168.1.101),访问Web控制界面
+
+### 以太网RMII模式
+
+1、按照选择网卡模式的说明,配置为以太网RMII模式
+
+2、确保AirPHY_1000配件板正确连接到Air8101核心板,接线方式如下:
+
+| Air8101核心板 |  AirPHY_1000配件板  |
+| ------------ | ------------------ |
+|    59/3V3    |         3.3v       |
+|     gnd      |         gnd        |
+|     5/D2     |         RX1        |
+|    72/D1     |         RX0        |
+|    71/D3     |         CRS        |
+|     4/D0     |         MDIO       |
+|     6/D4     |         TX0        |
+|    74/PCK    |         MDC        |
+|    70/D5     |         TX1        |
+|     7/D6     |         TXEN       |
+|     不接     |          NC        |
+|    69/D7     |         CLK        |
+
+3、硬件连接注意事项:
+- Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端)
+- 如果测试发现软件重启,并且日志中出现 "poweron reason 0",表示供电不足,此时需要通过直流稳压电源对核心板的VIN管脚进行5V供电
+
+4、使用网线将AirPHY_1000配件板连接到路由器或网络交换机
+
+5、使用Luatools烧录内核固件和demo脚本代码
+
+6、烧录成功后,设备自动开机运行,开启PHY芯片供电并初始化以太网卡
+
+7、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.102)
+
+8、确保你的电脑连接到同一路由器或网络
+
+9、在浏览器中输入设备的IP地址(如http://192.168.1.102),访问Web控制界面
+
+## Web控制界面功能
+
+在浏览器访问Web控制界面后,你可以使用以下功能:
+
+- 控制LED灯的开关
+- 发送文本消息(会显示在设备日志中)
+- 点击WiFi扫描按钮,查看周围可用的WiFi热点列表
+
+```lua
+[2025-10-23 23:48:33.725] I/main LuatOS@Air8101 base 25.03 bsp V1004 32bit
+[2025-10-23 23:48:33.725] I/main ROM Build: Jun 15 2025 09:58:28
+[2025-10-23 23:48:33.725] W/pins /luadb/pins_Air8101.json not exist!!
+[2025-10-23 23:48:33.725] D/main loadlibs luavm 1572856 16696 16696
+[2025-10-23 23:48:33.725] D/main loadlibs sys   255160 112584 123552
+[2025-10-23 23:48:33.725] D/main loadlibs psram 3145728 1840912 2102424
+[2025-10-23 23:48:33.725] I/user.main	project name is 	httpsrv_testdemo	version is 	001.000.000
+[2025-10-23 23:48:33.725] I/user.执行AP创建操作	luatos8888
+[2025-10-23 23:48:33.725] W/netdrv 该netdrv不支持设置dhcp开关
+[2025-10-23 23:48:33.725] D/net network ready 3
+[2025-10-23 23:48:33.725] D/net 使用223.5.5.5作为默认DNS服务器
+[2025-10-23 23:48:33.725] I/user.WIFI	AP热点创建成功
+[2025-10-23 23:48:33.725] I/httpsrv http listen at 192.168.4.1:80
+[2025-10-23 23:48:33.725] I/user.HTTP	文件服务器已启动
+[2025-10-23 23:48:33.725] I/user.HTTP	请连接WiFi: luatos8888 密码: 12345678
+[2025-10-23 23:48:33.725] I/user.HTTP	然后访问: http://192.168.4.1/
+```
+
+7、通过Luatools工具,可以查看设备的运行日志,包括收到的文本消息和WiFi扫描结果