Browse Source

add: 添加各型号的httpsrv demo

tuoyiheng 2 months ago
parent
commit
faf556d545

+ 77 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/httpsrv/httpsrv_start.lua

@@ -0,0 +1,77 @@
+--[[
+@module  httpsrv_start
+@summary HTTP服务器应用模块
+@version 1.0
+@date    2025.10.24
+@author  拓毅恒
+@usage
+本文件为Air780EHM/Air780EHV/Air780EGH核心板+AirETH_1000配件板演示 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 start_http_server()
+    -- 等待网络就绪事件
+    sys.waitUntil("CREATE_OK")
+    
+    local local_ip = socket.localIP(socket.LWIP_ETH)
+    
+    -- 启动HTTP服务器
+    httpsrv.start(80, handle_http_request, socket.LWIP_ETH)
+    log.info("HTTP", "文件服务器已启动,使用AirETH_1000-以太网模式")
+    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/Air780EHM_Air780EHV_Air780EGH/demo/httpsrv/index.html

@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html lang="zh">
+<header>
+    <meta charset="utf-8" />
+    <title>Air780Exx 控制中心</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>Air780Exx 控制中心</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>

+ 69 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/httpsrv/main.lua

@@ -0,0 +1,69 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.24
+@author  拓毅恒
+@usage
+本demo演示的核心功能为:
+HTTP服务器应用功能,通过加载httpsrv_start模块来启动和配置HTTP服务器,处理HTTP请求和响应。
+
+netdrv_eth_spi:配置连接外网使用的网卡,通过SPI外挂CH390H芯片的以太网卡。
+更多说明参考本目录下的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_eth_spi"
+
+-- 加载 httpsrv_start 功能模块
+require "httpsrv_start"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 89 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/httpsrv/netdrv_eth_spi.lua

@@ -0,0 +1,89 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air780EHM/Air780EHV/Air780EGH核心板+AirETH_1000配件板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+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))
+        sys.publish("CREATE_OK")
+    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)
+
+
+-- 本demo测试使用的是Air780EHM/Air780EHV/Air780EGH核心板+AirETH_1000配件板
+-- GPIO20为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(20, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI1
+    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_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)

+ 114 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/httpsrv/readme.md

@@ -0,0 +1,114 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、httpsrv_start.lua:HTTP服务器实现模块,包含服务器初始化、路由处理、文本发送和WiFi扫描功能;
+
+3、index.html:Web控制界面,提供LED控制按钮、文本发送输入框和WiFi扫描功能;
+
+4、netdrv_eth_spi.lua:网络驱动设备功能模块,通过以太网SPI网卡驱动,SPI接口连接CH390H芯片实现有线网络连接;
+
+## 演示功能概述
+
+1、HTTP服务器:创建Web服务器,提供Web控制界面
+
+- 支持以太网SPI模式
+- HTTP服务器监听80端口,通过网线连接网络,IP地址由路由器DHCP分配
+- 支持访问Web控制界面
+
+2、文本发送功能:通过Web界面发送文本数据
+
+- 提供文本发送(/send/text)接口
+- 支持在Web界面的输入框中输入文本并发送
+- 发送的文本会在设备日志中显示
+
+3、WiFi扫描功能:搜索周围可用的WiFi热点
+
+- 提供开始扫描(/scan/go)接口
+- 提供获取扫描结果(/scan/list)接口
+- Web界面上有扫描按钮,点击后显示周围WiFi热点列表
+- 显示WiFi的SSID和信号强度信息
+
+## 演示硬件环境
+
+1、Air780EHM/Air780EHV/Air780EGH核心板一块+AirETH_1000配件板+可上网的sim卡一张+wifi天线一根:
+
+2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为:
+
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+  
+3、AirETH_1000配件板一块,Air780EHM/Air780EHV/Air780EGH核心板和AirETH_1000配件板的硬件接线方式为:
+
+| Air780EHM/Air780EHV/Air780EGH核心板  |  AirETH_1000配件板 |
+| --------------- | ----------------- |
+| 3V3             | 3.3v              |
+| gnd             | gnd               |
+| 86/SPI0CLK      | SCK               |
+| 83/SPI0CS       | CSS               |
+| 84/SPI0MISO     | SDO               |
+| 85/SPI0MOSI     | SDI               |
+| 107/GPIO21      | INT               |
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、固件获取地址:
+
+[Air780EHM 固件](https://docs.openluat.com/air780epm/luatos/firmware/version/#air780ehmluatos)
+
+[Air780EHV 固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+[Air780EGH 固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+## 演示核心步骤
+
+### 以太网SPI模式
+
+1、按照选择网卡模式的说明,配置为以太网SPI模式
+
+2、确保AirETH_1000配件板正确连接到Air780EHM/Air780EHV/Air780EGH核心板
+
+3、使用网线将AirETH_1000配件板连接到路由器或网络交换机
+
+4、使用Luatools烧录内核固件和demo脚本代码
+
+5、烧录成功后,设备自动开机运行并尝试通过AirETH_1000配件板连接到网络
+
+6、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.101)
+
+7、确保你的电脑连接到同一路由器或网络
+
+8、在浏览器中输入设备的IP地址(如http://192.168.1.101),访问Web控制界面
+
+## Web控制界面功能
+
+在浏览器访问Web控制界面后,你可以使用以下功能:
+
+- 发送文本消息(会显示在设备日志中)
+- 点击WiFi扫描按钮,查看周围可用的WiFi热点列表
+
+```lua
+[2025-11-28 13:31:17.997][000000000.201] I/user.main project name is  httpsrv_testdemo version is  001.000.000
+[2025-11-28 13:31:17.999][000000000.207] change from 1 to 4
+[2025-11-28 13:31:18.000][000000000.207] SPI_HWInit 552:spi0 speed 25600000,25600000,12
+[2025-11-28 13:31:18.002][000000000.208] I/user.netdrv_eth_spi spi open result 0
+[2025-11-28 13:31:18.003][000000000.208] D/ch390h 注册CH390H设备(4) SPI id 0 cs 8 irq 255
+[2025-11-28 13:31:18.006][000000000.209] D/ch390h adapter 4 netif init ok
+[2025-11-28 13:31:25.657][000000008.223] D/DHCP DHCP get ip ready
+[2025-11-28 13:31:25.659][000000008.223] D/ulwip adapter 4 ip 192.168.1.2
+[2025-11-28 13:31:25.661][000000008.223] D/ulwip adapter 4 mask 255.255.255.0
+[2025-11-28 13:31:25.662][000000008.223] D/ulwip adapter 4 gateway 192.168.1.1
+[2025-11-28 13:31:25.663][000000008.223] D/ulwip adapter 4 lease_time 86400s
+[2025-11-28 13:31:25.665][000000008.224] D/ulwip adapter 4 DNS1:192.168.1.1
+[2025-11-28 13:31:25.667][000000008.224] D/net network ready 4, setup dns server
+[2025-11-28 13:31:25.668][000000008.225] D/netdrv IP_READY 4 192.168.1.2
+[2025-11-28 13:31:25.670][000000008.226] I/user.netdrv_eth_spi.ip_ready_func IP_READY 192.168.1.2 255.255.255.0 192.168.1.1 nil
+[2025-11-28 13:31:25.671][000000008.227] I/httpsrv http listen at 192.168.1.2:80
+[2025-11-28 13:31:25.674][000000008.227] I/user.HTTP 文件服务器已启动,使用AirETH_1000-以太网模式
+[2025-11-28 13:31:25.676][000000008.228] I/user.HTTP 访问地址: http://192.168.1.2
+
+```
+
+通过Luatools工具,可以查看设备的运行日志,包括收到的文本消息和WiFi扫描结果

+ 77 - 0
module/Air780EPM/demo/httpsrv/httpsrv_start.lua

@@ -0,0 +1,77 @@
+--[[
+@module  httpsrv_start
+@summary Air780EPM HTTP服务器应用模块
+@version 1.0
+@date    2025.10.24
+@author  拓毅恒
+@usage
+本文件为Air780EPM开发板演示 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 start_http_server()
+    -- 等待网络就绪事件
+    sys.waitUntil("CREATE_OK")
+    
+    local local_ip = socket.localIP(socket.LWIP_ETH)
+    
+    -- 启动HTTP服务器
+    httpsrv.start(80, handle_http_request, socket.LWIP_ETH)
+    log.info("HTTP", "文件服务器已启动,使用AirETH_1000-以太网模式")
+    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/Air780EPM/demo/httpsrv/index.html

@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html lang="zh">
+<header>
+    <meta charset="utf-8" />
+    <title>Air780EPM 控制中心</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>Air780EPM 控制中心</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>

+ 69 - 0
module/Air780EPM/demo/httpsrv/main.lua

@@ -0,0 +1,69 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.10.24
+@author  拓毅恒
+@usage
+本demo演示的核心功能为:
+HTTP服务器应用功能,通过加载httpsrv_start模块来启动和配置HTTP服务器,处理HTTP请求和响应。
+
+netdrv_eth_spi:配置连接外网使用的网卡,通过SPI外挂CH390H芯片的以太网卡。
+更多说明参考本目录下的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_eth_spi"
+
+-- 加载 httpsrv_start 功能模块
+require "httpsrv_start"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 89 - 0
module/Air780EPM/demo/httpsrv/netdrv_eth_spi.lua

@@ -0,0 +1,89 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.07.24
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air780EPM V1.3版本开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+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))
+        sys.publish("CREATE_OK")
+    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)
+
+
+-- 本demo测试使用的是Air780EPM V1.3版本开发板
+-- GPIO20为CH390H以太网芯片的供电使能控制引脚
+gpio.setup(20, 1, gpio.PULLUP)
+
+-- 这个task的核心业务逻辑是:初始化SPI,初始化以太网卡,并在以太网卡上开启动态主机配置协议
+local function netdrv_eth_spi_task_func()
+    -- 初始化SPI1
+    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_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)

+ 98 - 0
module/Air780EPM/demo/httpsrv/readme.md

@@ -0,0 +1,98 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、httpsrv_start.lua:HTTP服务器实现模块,包含服务器初始化、路由处理、文本发送和WiFi扫描功能;
+
+3、index.html:Web控制界面,提供LED控制按钮、文本发送输入框和WiFi扫描功能;
+
+4、netdrv_eth_spi.lua:网络驱动设备功能模块,通过以太网SPI网卡驱动,SPI接口连接CH390H芯片实现有线网络连接;
+
+## 演示功能概述
+
+1、HTTP服务器:创建Web服务器,提供Web控制界面
+
+- 支持以太网SPI模式
+- HTTP服务器监听80端口,通过网线连接网络,IP地址由路由器DHCP分配
+- 支持访问Web控制界面
+
+2、文本发送功能:通过Web界面发送文本数据
+
+- 提供文本发送(/send/text)接口
+- 支持在Web界面的输入框中输入文本并发送
+- 发送的文本会在设备日志中显示
+
+3、WiFi扫描功能:搜索周围可用的WiFi热点
+
+- 提供开始扫描(/scan/go)接口
+- 提供获取扫描结果(/scan/list)接口
+- Web界面上有扫描按钮,点击后显示周围WiFi热点列表
+- 显示WiFi的SSID和信号强度信息
+
+## 演示硬件环境
+
+1、Air780EPM开发板一块+可上网的sim卡一张+wifi天线一根:
+
+- 天线装到开发板上
+
+2、TYPE-C USB数据线一根 + USB转串口数据线一根,Air780EPM开发板和数据线的硬件接线方式为:
+
+- Air780EPM开发板通过TYPE-C USB口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+- TYPE-C USB数据线直接插到开发板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、Air780EPM固件[Air780EPM 版本固件](https://docs.openluat.com/air780epm/luatos/firmware/version/#air780epmluatos)
+
+## 演示核心步骤
+
+### 以太网SPI模式
+
+1、按照选择网卡模式的说明,配置为以太网SPI模式
+
+2、确保CH390H以太网模块正确连接到Air780EPM开发板
+
+3、使用网线将以太网模块连接到路由器或网络交换机
+
+4、使用Luatools烧录内核固件和demo脚本代码
+
+5、烧录成功后,设备自动开机运行并尝试通过以太网连接到网络
+
+6、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.101)
+
+7、确保你的电脑连接到同一路由器或网络
+
+8、在浏览器中输入设备的IP地址(如http://192.168.1.101),访问Web控制界面
+
+## Web控制界面功能
+
+在浏览器访问Web控制界面后,你可以使用以下功能:
+
+- 发送文本消息(会显示在设备日志中)
+- 点击WiFi扫描按钮,查看周围可用的WiFi热点列表
+
+```lua
+[2025-11-28 13:31:17.997][000000000.201] I/user.main project name is  httpsrv_testdemo version is  001.000.000
+[2025-11-28 13:31:17.999][000000000.207] change from 1 to 4
+[2025-11-28 13:31:18.000][000000000.207] SPI_HWInit 552:spi0 speed 25600000,25600000,12
+[2025-11-28 13:31:18.002][000000000.208] I/user.netdrv_eth_spi spi open result 0
+[2025-11-28 13:31:18.003][000000000.208] D/ch390h 注册CH390H设备(4) SPI id 0 cs 8 irq 255
+[2025-11-28 13:31:18.006][000000000.209] D/ch390h adapter 4 netif init ok
+[2025-11-28 13:31:25.657][000000008.223] D/DHCP DHCP get ip ready
+[2025-11-28 13:31:25.659][000000008.223] D/ulwip adapter 4 ip 192.168.1.2
+[2025-11-28 13:31:25.661][000000008.223] D/ulwip adapter 4 mask 255.255.255.0
+[2025-11-28 13:31:25.662][000000008.223] D/ulwip adapter 4 gateway 192.168.1.1
+[2025-11-28 13:31:25.663][000000008.223] D/ulwip adapter 4 lease_time 86400s
+[2025-11-28 13:31:25.665][000000008.224] D/ulwip adapter 4 DNS1:192.168.1.1
+[2025-11-28 13:31:25.667][000000008.224] D/net network ready 4, setup dns server
+[2025-11-28 13:31:25.668][000000008.225] D/netdrv IP_READY 4 192.168.1.2
+[2025-11-28 13:31:25.670][000000008.226] I/user.netdrv_eth_spi.ip_ready_func IP_READY 192.168.1.2 255.255.255.0 192.168.1.1 nil
+[2025-11-28 13:31:25.671][000000008.227] I/httpsrv http listen at 192.168.1.2:80
+[2025-11-28 13:31:25.674][000000008.227] I/user.HTTP 文件服务器已启动,使用AirETH_1000-以太网模式
+[2025-11-28 13:31:25.676][000000008.228] I/user.HTTP 访问地址: http://192.168.1.2
+
+```
+
+通过Luatools工具,可以查看设备的运行日志,包括收到的文本消息和WiFi扫描结果

+ 22 - 8
module/Air8101/demo/httpsrv/readme.md

@@ -163,19 +163,33 @@ wlan.connect("你的WiFi名称", "你的WiFi密码", 1)
 
 1、按照选择网卡模式的说明,配置为以太网SPI模式
 
-2、确保CH390H以太网模块正确连接到Air8000开发板
+2、确保AirETH_1000配件板正确连接到Air8101核心板,接线方式如下:
+
+| Air8101核心板 | AirETH_1000配件板 |
+| ------------- | ----------------- |
+| 59/3V3        | 3.3v              |
+| gnd           | gnd               |
+| 28/DCLK       | SCK               |
+| 54/DISP       | CSS               |
+| 55/HSYN       | SDO               |
+| 57/DE         | SDI               |
+| 14/GPIO8      | INT               |
 
-3、使用网线将以太网模块连接到路由器或网络交换机
+3、硬件连接注意事项:
+- Air8101核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端)
+- 如果测试发现软件重启,并且日志中出现 "poweron reason 0",表示供电不足,此时需要通过直流稳压电源对核心板的VIN管脚进行5V供电
 
-4、使用Luatools烧录内核固件和demo脚本代码
+4、使用网线将AirETH_1000配件板连接到路由器或网络交换机
 
-5、烧录成功后,设备自动开机运行并尝试通过以太网连接到网络
+5、使用Luatools烧录内核固件和demo脚本代码
+
+6、烧录成功后,设备自动开机运行,开启ETH芯片供电并初始化以太网卡
 
-6、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.101)
+7、通过Luatools日志查看设备获取的IP地址(例如:192.168.1.101)
 
-7、确保你的电脑连接到同一路由器或网络
+8、确保你的电脑连接到同一路由器或网络
 
-8、在浏览器中输入设备的IP地址(如http://192.168.1.101),访问Web控制界面
+9、在浏览器中输入设备的IP地址(如http://192.168.1.101),访问Web控制界面
 
 ### 以太网RMII模式
 
@@ -240,4 +254,4 @@ wlan.connect("你的WiFi名称", "你的WiFi密码", 1)
 [2025-10-23 23:48:33.725] I/user.HTTP	然后访问: http://192.168.4.1/
 ```
 
-7、通过Luatools工具,可以查看设备的运行日志,包括收到的文本消息和WiFi扫描结果
+通过Luatools工具,可以查看设备的运行日志,包括收到的文本消息和WiFi扫描结果