init: Air780EPM 官方 LuatOS 项目代码基线

- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
wangfq
2026-08-31 08:53:49 +08:00
commit 173dd6874f
813 changed files with 125484 additions and 0 deletions
@@ -0,0 +1,282 @@
--[[
@module flash_fs_io
@summary 内置Flash文件系统操作测试模块
@version 1.0.0
@date 2025.09.23
@author 王棚嶙
@usage
本文件为内置Flash文件系统的操作测试流程:
1. 获取文件系统信息( io.fsstat)
2. 创建目录
3. 创建并写入文件
4. 检查文件是否存在
5. 获取文件大小(io.fileSize)
6. 读取文件内容
7. 启动计数文件操作
8. 文件追加测试
9. 按行读取测试
10. 文件重命名
11. 列举目录内容
12. 删除文件
13. 删除目录
本文件没有对外接口,直接在main.lua中require "flash_fs_io"就可以加载运行
]]
function flash_fs_io_task()
-- 使用内置Flash文件系统,根目录为"/",
local base_path = "/"
-- 创建一个目录
local demo_dir = "flash_demo"
-- 文件名
local dir_path = base_path .. demo_dir
-- ########## 开始进行内置Flash文件系统操作 ##########
log.info("文件系统操作", "===== 开始文件系统操作 =====")
-- 1. 获取文件系统信息 (使用 io.fsstat接口)
local success, total_blocks, used_blocks, block_size, fs_type = io.fsstat(base_path)
if success then
log.info(" io.fsstat成功:",
"总空间=" .. total_blocks .. "",
"已用=" .. used_blocks .. "",
"块大小=" .. block_size.."字节",
"类型=" .. fs_type)
else
log.error(" io.fsstat", "获取文件系统信息失败")
return
end
-- 2. 创建目录
-- 如果目录不存在,则创建目录
if not io.dexist(dir_path) then
-- 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
else
log.error("io.mkdir", "目录创建失败", "路径:" .. dir_path)
return
end
else
log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
end
-- 3. 创建并写入文件
local file_path = dir_path .. "/boottime"
local file = io.open(file_path, "wb")
if file then
file:write("这是内置Flash文件系统API文档示例的测试内容")
file:close()
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
log.error("文件创建", "文件创建失败", "路径:" .. file_path)
return
end
-- 4. 检查文件是否存在
if io.exists(file_path) then
log.info("io.exists", "文件存在", "路径:" .. file_path)
else
log.error("io.exists", "文件不存在", "路径:" .. file_path)
return
end
-- 5. 获取文件大小 (使用io.fileSize接口)
local file_size = io.fileSize(file_path)
if file_size then
log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. file_path)
else
log.error("io.fileSize", "获取文件大小失败", "路径:" .. file_path)
return
end
-- 6. 读取文件内容
file = io.open(file_path, "rb")
if file then
local content = file:read("*a")
log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
file:close()
else
log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
return
end
-- 7. 启动计数文件操作
local count = 0
file = io.open(file_path, "rb")
if file then
local data = file:read("*a")
log.info("启动计数", "文件内容:", data, "十六进制:", data:toHex())
count = tonumber(data) or 0
file:close()
else
log.warn("启动计数", "文件不存在或无法打开")
end
log.info("启动计数", "当前值:", count)
count = count + 1
log.info("启动计数", "更新值:", count)
file = io.open(file_path, "wb")
if file then
file:write(tostring(count))
file:close()
log.info("文件写入", "路径:" .. file_path, "内容:", count)
else
log.error("文件写入", "无法打开文件", "路径:" .. file_path)
return
end
-- 8. 文件追加测试
local append_file = dir_path .. "/test_a"
os.remove(append_file) -- 清理旧文件
file = io.open(append_file, "wb")
if file then
file:write("ABC")
file:close()
log.info("文件创建", "路径:" .. append_file, "初始内容:ABC")
else
log.error("文件创建", "无法创建文件", "路径:" .. append_file)
return
end
file = io.open(append_file, "a+")
if file then
file:write("def")
file:close()
log.info("文件追加", "路径:" .. append_file, "追加内容:def")
else
log.error("文件追加", "无法打开文件进行追加", "路径:" .. append_file)
return
end
-- 验证追加结果
file = io.open(append_file, "r")
if file then
local data = file:read("*a")
log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
data == "ABCdef" and "成功" or "失败")
file:close()
else
log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
return
end
-- 9. 按行读取测试
local line_file = dir_path .. "/testline"
file = io.open(line_file, "w")
if file then
file:write("abc\n")
file:write("123\n")
file:write("wendal\n")
file:close()
log.info("文件创建", "路径:" .. line_file, "写入3行文本")
else
log.error("文件创建", "无法创建文件", "路径:" .. line_file)
return
end
file = io.open(line_file, "r")
if file then
log.info("按行读取", "路径:" .. line_file, "第1行:", file:read("*l"))
log.info("按行读取", "路径:" .. line_file, "第2行:", file:read("*l"))
log.info("按行读取", "路径:" .. line_file, "第3行:", file:read("*l"))
file:close()
else
log.error("按行读取", "无法打开文件", "路径:" .. line_file)
return
end
-- 10. 文件重命名
local old_path = append_file
local new_path = dir_path .. "/renamed_file.txt"
local success, err = os.rename(old_path, new_path)
if success then
log.info("os.rename", "文件重命名成功", "原路径:" .. old_path, "新路径:" .. new_path)
-- 验证重命名结果
if io.exists(new_path) and not io.exists(old_path) then
log.info("验证结果", "重命名验证成功", "新文件存在", "原文件不存在")
else
log.error("验证结果", "重命名验证失败")
end
else
log.error("os.rename", "重命名失败", "错误:" .. tostring(err), "原路径:" .. old_path)
return
end
-- 11. 列举目录内容
log.info("目录操作", "===== 开始目录列举 =====")
local ret, data = io.lsdir(dir_path, 50, 0)
if ret then
log.info("fs", "lsdir", json.encode(data))
else
log.info("fs", "lsdir", "fail", ret, data)
return
end
-- 12. 删除文件测试
if os.remove(new_path) then
log.info("os.remove", "文件删除成功", "路径:" .. new_path)
if not io.exists(new_path) then
log.info("验证结果", "renamed_file.txt文件删除验证成功")
else
log.error("验证结果", "renamed_file.txt文件删除验证失败")
end
else
log.error("os.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
return
end
if os.remove(line_file) then
log.info("os.remove", "testline文件删除成功", "路径:" .. line_file)
if not io.exists(line_file) then
log.info("验证结果", "testline文件删除验证成功")
else
log.error("验证结果", "testline文件删除验证失败")
end
else
log.error("os.remove", "testline文件删除失败", "路径:" .. line_file)
return
end
if os.remove(file_path) then
log.info("os.remove", "文件删除成功", "路径:" .. file_path)
if not io.exists(file_path) then
log.info("验证结果", "boottime文件删除验证成功")
else
log.error("验证结果", "boottime文件删除验证失败")
end
else
log.error("os.remove", "boottime文件删除失败", "路径:" .. file_path)
return
end
-- 13. 删除目录
if io.rmdir(dir_path) then
log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
if not io.dexist(dir_path) then
log.info("验证结果", "目录删除验证成功")
else
log.error("验证结果", "目录删除验证失败")
end
else
log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
return
end
-- 再次获取文件系统信息,查看空间变化
local final_success, final_total_blocks, final_used_blocks, final_block_size, final_fs_type = io.fsstat(base_path)
if final_success then
log.info(" io.fsstat", "操作后文件系统信息:",
"总空间=" .. final_total_blocks .. "",
"已用=" .. final_used_blocks .. "",
"块大小=" .. final_block_size.."字节",
"类型=" .. final_fs_type)
end
log.info("文件系统操作", "===== 文件系统操作完成 =====")
end
sys.taskInit(flash_fs_io_task)
@@ -0,0 +1,74 @@
--[[
@module http_download_flash
@summary HTTP下载文件到内置Flash模块
@version 1.0.0
@date 2025.09.23
@author 王棚嶙
@usage
本文件演示的功能为通过HTTP下载文件到内置Flash中:
1. 网络就绪检测
2. 创建HTTP下载任务并等待完成
3. 记录下载结果
4. 获取并记录文件大小(使用io.fileSize)
本文件没有对外接口,直接在main.lua中require "http_download_flash"即可
]]
local function http_download_flash_task()
-- 阶段1: 网络就绪检测
while not socket.adapter(socket.dft()) do
log.warn("HTTP下载", "等待网络连接", socket.dft())
sys.waitUntil("IP_READY", 1000)
end
log.info("HTTP下载", "网络已就绪", socket.dft())
-- 阶段2: 执行下载任务
log.info("HTTP下载", "开始下载任务")
-- 创建下载目录
local download_dir = "/downloads"
if not io.dexist(download_dir) then
io.mkdir(download_dir)
end
-- 核心下载操作开始
local code, headers, body_size = http.request("GET",
"https://www.air32.cn/demo/sample-6s.mp3",
nil, nil, {dst = download_dir .. "/sample-6s.mp3"}).wait()
-- 阶段3: 记录下载结果
log.info("HTTP下载", "下载完成",
code == 200 and "success" or "error",
code,
json.encode(headers or {}),
body_size)
if code == 200 then
-- 获取实际文件大小 (使用io.fileSize接口)
local actual_size = io.fileSize(download_dir .. "/sample-6s.mp3")
if not actual_size then
-- 备用方案
actual_size = io.fileSize(download_dir .. "/sample-6s.mp3")
end
log.info("HTTP下载", "文件大小验证", "预期:", body_size, "实际:", actual_size)
if actual_size ~= body_size then
log.error("HTTP下载", "文件大小不一致", "预期:", body_size, "实际:", actual_size)
end
-- 展示下载后的文件系统状态
local success, total_blocks, used_blocks, block_size, fs_type = io.fsstat("/")
if success then
log.info("HTTP下载", "下载后文件系统信息:",
"总空间=" .. total_blocks .. "",
"已用=" .. used_blocks .. "",
"块大小=" .. block_size.."字节",
"类型=" .. fs_type)
end
end
end
-- 创建下载任务
sys.taskInit(http_download_flash_task)
@@ -0,0 +1,72 @@
--[[
@module main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 001.999.000
@date 2025.09.23
@author 王棚嶙
@usage
本 Demo 演示了在Air780EPM内置Flash文件系统中的完整操作流程:
1. 基础操作:看门狗守护机制
2. 文件系统操作:
- 文件系统信息查询( io.fsstat)
- 文件大小获取(io.fileSize)
- 文件创建/读写/追加
- 目录创建/删除
- 文件重命名/删除
- 文件存在性检查
3. 下载功能:
- 网络检测与HTTP文件下载到内置Flash
]]
--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为999
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "flash_fs_io_demo"
VERSION = "001.999.000"
-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)
-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
-- errDump.config(true, 600)
-- end
-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用
-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
-- log.info("mem.lua", rtos.meminfo())
-- log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)
--[[在加载以下两个功能时,建议分别打开进行测试,因为文件操作和http下载功能是异步操作。放到一个项目中,如果加载的时间点是随机的,就会出现哪个任务先抢到CPU时间片,哪个就先执行,不符合正常的业务逻辑,用户在参考编程的时候也要注意。]]
-- 加载内置Flash文件系统操作演示模块
require "flash_fs_io"
-- 加载HTTP下载存入内置Flash功能演示模块
-- require "http_download_flash"
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
-- sys.run()之后后面不要加任何语句!!!!!
sys.run()
@@ -0,0 +1,141 @@
## **功能模块介绍**
本 Demo 演示了在Air780EPM内置Flash文件系统中的完整操作流程,覆盖了从文件系统读写到高级文件操作的完整功能链。项目分为两个核心模块:
1、main.lua:主程序入口 <br>
2、flash_fs_io.lua:内置Flash文件系统的操作测试流程模块,实现文件系统管理、文件操作和目录管理功能。<br>
3、http_download_flash.luaHTTP下载模块,演示HTTP下载文件到内置Flash中的功能
## **演示功能概述**
### 1、主程序入口模块(main.lua
- 初始化项目信息和版本号
- 初始化看门狗,并定时喂狗
- 启动一个循环定时器,每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
- 加载flash_fs_io模块(通过require "flash_fs_io"
- 加载http_download_flash模块(通过require "http_download_flash"
- 最后运行sys.run()。
### 2、内置Flash文件系统演示模块(flash_fs_io.lua
#### 文件操作
- 获取文件系统信息( io.fsstat)
- 创建目录:io.mkdir("/flash_demo")
- 创建/写入文件: io.open("/flash_demo/boottime", "wb")
- 检查文件存在: io.exists(file_path)
- 获取文件大小:io.fileSize(file_path)
- 读取文件内容: io.open(file_path, "rb"):read("*a")
- 启动计数文件: 记录设备启动次数
- 文件追加: io.open(append_file, "a+")
- 按行读取: file:read("*l")
- 文件关闭: file:close()
- 文件重命名: os.rename(old_path, new_path)
- 列举目录: io.lsdir(dir_path)
- 删除文件: os.remove(file_path)
- 删除目录: io.rmdir(dir_path)
### 3、HTTP下载功能 (http_download_flash.lua)
#### 网络就绪检测
- 1秒循环等待IP就绪
- 网络故障处理机制
#### 安全下载
- HTTP下载
#### 结果处理
- 下载状态码解析
- 自动文件大小验证
- 获取文件系统信息( io.fsstat)
## **演示硬件环境**
1、Air780EPM核心板一块
2、TYPE-C USB数据线一根
3、SIM卡一张
4、Air780EPM核心板和数据线的硬件接线方式为
- Air780EPM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
## **演示软件环境**
1、Luatools下载调试工具:https://docs.openluat.com/air780epm/common/Luatools/
2、内核固件版本:https://docs.openluat.com/air780epm/luatos/firmware/version/
## **演示核心步骤**
1、搭建好硬件环境
2、通过Luatools将demo与固件烧录到开发板中
3、烧录好后,板子开机将会在Luatools上看到如下打印
```lua
1
[2025-10-22 16:04:28.032][000000000.214] I/user. ===== =====
[2025-10-22 16:04:28.064][000000000.218] I/user. io.fsstat成功: =42 =4 =4096 =lfs
[2025-10-22 16:04:28.085][000000000.319] I/user.io.mkdir :/flash_demo
[2025-10-22 16:04:28.105][000000000.328] I/user. :/flash_demo/boottime
[2025-10-22 16:04:28.114][000000000.331] I/user.io.exists :/flash_demo/boottime
[2025-10-22 16:04:28.130][000000000.334] I/user.io.fileSize :59 :/flash_demo/boottime
[2025-10-22 16:04:28.142][000000000.337] I/user. :/flash_demo/boottime :Flash文件系统API文档示例的测试内容
[2025-10-22 16:04:28.167][000000000.340] I/user. : Flash文件系统API文档示例的测试内容 : E8BF99E698AFE58685E7BDAE466C617368E69687E4BBB6E7B3BBE7BB9F415049E69687E6A1A3E7A4BAE4BE8BE79A84E6B58BE8AF95E58685E5AEB9 118
[2025-10-22 16:04:28.192][000000000.340] I/user. : 0
[2025-10-22 16:04:28.217][000000000.340] I/user. : 1
[2025-10-22 16:04:28.221][000000000.345] I/user. :/flash_demo/boottime : 1
[2025-10-22 16:04:28.234][000000000.357] I/user. :/flash_demo/test_a :ABC
[2025-10-22 16:04:28.242][000000000.360] I/user. :/flash_demo/test_a :def
[2025-10-22 16:04:28.248][000000000.363] I/user. :/flash_demo/test_a :ABCdef :
[2025-10-22 16:04:28.253][000000000.367] I/user. :/flash_demo/testline 3
[2025-10-22 16:04:28.271][000000000.370] I/user. :/flash_demo/testline 1: abc
[2025-10-22 16:04:28.279][000000000.370] I/user. :/flash_demo/testline 2: 123
[2025-10-22 16:04:28.302][000000000.371] I/user. :/flash_demo/testline 3: wendal
[2025-10-22 16:04:28.331][000000000.377] I/user.os.rename :/flash_demo/test_a :/flash_demo/renamed_file.txt
[2025-10-22 16:04:28.347][000000000.383] D/vfs fopen /flash_demo/test_a r not found
[2025-10-22 16:04:28.361][000000000.383] I/user.
[2025-10-22 16:04:28.382][000000000.383] I/user. ===== =====
[2025-10-22 16:04:28.393][000000000.396] I/user.fs lsdir [{"name":"boottime","size":1,"type":0},{"name":"renamed_file.txt","size":6,"type":0},{"name":"testline","size":15,"type":0}]
[2025-10-22 16:04:28.417][000000000.400] I/user.os.remove :/flash_demo/renamed_file.txt
[2025-10-22 16:04:28.435][000000000.403] D/vfs fopen /flash_demo/renamed_file.txt r not found
[2025-10-22 16:04:28.449][000000000.403] I/user. renamed_file.txt文件删除验证成功
[2025-10-22 16:04:28.469][000000000.407] I/user.os.remove testline文件删除成功 :/flash_demo/testline
[2025-10-22 16:04:28.479][000000000.410] D/vfs fopen /flash_demo/testline r not found
[2025-10-22 16:04:28.489][000000000.411] I/user. testline文件删除验证成功
[2025-10-22 16:04:28.507][000000000.417] I/user.os.remove :/flash_demo/boottime
[2025-10-22 16:04:28.514][000000000.420] D/vfs fopen /flash_demo/boottime r not found
[2025-10-22 16:04:28.519][000000000.421] I/user. boottime文件删除验证成功
[2025-10-22 16:04:28.532][000000000.427] I/user.io.rmdir :/flash_demo
[2025-10-22 16:04:28.538][000000000.430] I/user.
[2025-10-22 16:04:28.547][000000000.435] I/user. io.fsstat : =42 =4 =4096 =lfs
[2025-10-22 16:04:28.554][000000000.435] I/user. ===== =====
2HTTP下载
[2025-10-22 15:34:04.507][000000007.471] I/user.HTTP下载 1 3
[2025-10-22 15:34:04.550][000000007.471] I/user.HTTP下载
[2025-10-22 15:34:04.579][000000007.478] dns_run 676:gitee.com state 0 id 1 ipv6 0 use dns server2, try 0
[2025-10-22 15:34:04.604][000000007.508] D/mobile TIME_SYNC 0
[2025-10-22 15:34:04.734][000000007.517] dns_run 693:dns all done ,now stop
[2025-10-22 15:34:06.390][000000009.741] I/user.HTTP下载 success 200
[2025-10-22 15:34:06.422][000000009.741] {"Age":"0","Cache-Control":"public, max-age=60","Via":"1.1 varnish","Transfer-Encoding":"chunked","Date":"Wed, 22 Oct 2025 07:34:04 GMT","Access-Control-Allow-Credentials":"true","Vary":"Accept-Encoding","X-Served-By":"cache-ffe9","X-Gitee-Server":"http-pilot 1.9.21","Connection":"keep-alive","Server":"ADAS\/1.0.214","Access-Control-Allow-Headers":"Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-CustomHeader,Content-Range,Range,Set-Language","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; sandbox","X-Request-Id":"fa536af1-51bd-400f-8d4b-7322355a9db2","Accept-Ranges":"bytes","Etag":"W\/\"2aaa2788d394a924e258d6f26ad78b8c948950f5\"","Content-Type":"text\/plain; charset=utf-8","Access-Control-Allow-Methods":"GET, POST, PUT, PATCH, DELETE, OPTIONS","X-Frame-Options":"DENY","X-Cache":"MISS","Set-Cookie":"BEC=1f1759df3ccd099821dcf0da6feb0357;Path=\/;Max-Age=126000"}
[2025-10-22 15:34:06.477][000000009.742] 103070
[2025-10-22 15:34:06.492][000000009.745] I/user.HTTP下载 : 103070 : 103070
[2025-10-22 15:34:06.525][000000009.751] I/user.HTTP下载 : =42 =32 =4096 =lfs
```