VisualHMI - 简易数据库 (定制)
1.概述
本系统实现了一种轻量级、无文件系统的嵌入式简易数据库,专为资源受限的 HMI 或工业控制器设计。其核心机制是在 Flash 存储器中划定一块连续物理区域,以固定长度记录的方式实现结构化数据的持久化存储与访问,并支持运行时按需初始化。
⚠️ 简易数据库 Flash 地址规划注意事项
本简易数据库直接操作 Flash 存储区域,不具备文件系统隔离机制,因此其 flashaddr 起始地址必须严格避开系统其他关键功能所占用的存储区块,避免数据覆盖、程序异常或设备变砖。特别需注意以下四类高风险区域:
工程资源存储区
资料采样块存储区
操作记录块存储区
OTA 升级备份区域
适用范围:VisualHMI - HMI&M系列&Dx系列( 定制固件)
2. API说明
2.1.easydb_open(flashaddr,createIfNotExist,dataRowSize,maxCount)
数据库打开函数,easydb_open() 是 HMI 系统提供的嵌入式轻量级数据库接口,用于在内部 Flash 存储器中打开或创建一个结构化数据表。该数据库以固定长度记录方式存储,适用于参数配置、事件日志、运行记录等掉电存数据读写场景。
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
flashaddr |
number | Flash 起始地址(字节对齐) |
createIfNotExist |
number | 创建策略标志 • 0:仅打开已有数据库,若不存在则失败• 1:若数据库不存在,则按后续参数创建新库 |
dataRowSize |
number | 单条记录大小(字节) • 必须 ≥ 1 |
maxCount |
number | 最大记录数量 • 决定数据库总占用空间 = dataRowSize × maxCount |
2.2.easydb_close(db)
数据库关闭函数,easydb_close(db) 是 HMI 系统提供的嵌入式数据库资源释放接口,用于关闭已打开的 Flash 数据库,释放其占用的内存上下文或驱动句柄
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 数据库句柄 • 由 easydb_open() 成功调用时返回数据 |
2.3.easydb_get_count(db)
获取数据库当前有效记录数,easydb_get_count(db) 是 HMI 系统提供的轻量级 Flash 数据库查询接口,用于获取指定数据库中当前已写入的有效记录数量。
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 有效的数据库句柄 • 必须是由 easydb_open() 成功返回的句柄 |
2.4.easydb_get(db,idx)
读取数据库指定记录,easydb_get(db, idx) 是 HMI 系统提供的轻量级 Flash 数据库读取接口,用于从已打开的数据库中读取指定索引位置的完整数据记录。
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 有效的数据库句柄 • 由 easydb_open() 成功返回的句柄 |
idx |
number | 记录索引(行号) • 从 0 开始计数 • 有效范围:0 ≤ idx < 当前记录总数 |
📊 返回值说明
| 项目 | 说明 |
|---|---|
| 成功返回 | 字符串 |
| 失败返回 | nil |
2.5.easydb_add(db,dataset)
向数据库追加新记录,easydb_add(db, dataset) 是 HMI 系统提供的轻量级 Flash 数据库写入接口,用于在指定数据库末尾追加一条新记录。
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 有效的数据库句柄 • 由 easydb_open() 成功返回的句柄 |
dataset |
string | 要写入的数据内容 |
2.6.easydb_update(db,idx,dataset)
更新数据库指定记录,easydb_update(db, idx, dataset) 是 HMI 系统提供的轻量级 Flash 数据库更新接口,用于修改已存在记录的指定索引位置的数据内容。
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 有效的数据库句柄 • 由 easydb_open() 成功返回的句柄 |
idx |
number | 要更新的记录索引(行号) • 从 0 开始计数 • 有效范围:0 ≤ idx < easydb_get_count(db) |
dataset |
string | 新的数据内容 • 长度必须严格等于创建时指定的 dataRowSize |
2.7.easydb_del(db,idx)
删除数据库指定记录,easydb_del(db, idx) 是 HMI 系统提供的轻量级 Flash 数据库删除接口,用于删除指定索引位置的数据记录
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 有效的数据库句柄 • 由 easydb_open() 成功返回的句柄 |
idx |
number | 要删除的记录索引(行号) • 从 0 开始计数 • 有效范围:0 ≤ idx < easydb_get_count(db) |
2.8.easydb_clear(db)
清空数据库所有记录,easydb_clear(db) 是 HMI 系统提供的轻量级 Flash 数据库清空接口,用于一次性删除数据库中的所有有效记录,将数据库恢复至初始空状态。
📊 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
db |
number | 有效的数据库句柄 • 由 easydb_open() 成功返回的句 |
3. 应用
本示例采用嵌入式简易数据库(由 db.lua 封装),以 “姓名;职业;年龄” 作为单条记录的存储格式(例如:"张三;工程师;32")。所有字段之间使用英文分号 ; 作为分隔符,确保与 HMI 表格控件的数据解析规则一致,便于直接映射至多列表格进行可视化展示。,完整演示数据库的读取、新增、修改、删除等核心操作流程。
3.1. 添加数据记录控件
- 数据来源:自定义
- 字符编码:UTF8
- 翻页控制:✔
- 控制地址:LW4000
- 选中行通知:✔(LW4004,UINT32),目的选中对应数据,修改删除简易数据库

3.2. 编辑Lua
本DEMO,设计的数据库操作段均采用句柄驱动(Handle-Based)的设计范式,并封装在db.lua中。调用 db.open 成功后,将返回一个非零整数作为数据库操作的唯一有效句柄。该句柄应被声明为全局变量(如 g_db_handle),并在整个应用生命周期内由主逻辑脚本统一持有。
所有对简易数据库的读写操作(包括添加、查询、修改、删除、清空等)必须显式地将该句柄作为首个关键形参传入,模块内部不维护任何全局状态或隐式上下文。此设计确保了接口的无状态性、线程安全性及多实例兼容性。
3.2.1. 数据记录控件设置
在数据记录控件的数据来源设置为“自定义”的模式下,需要脚本处数量获取和内容填充两个阶段。
1. 记录数量回调:on_get_record_count
此函数作为数据记录控件的行数提供者。当控件的数据来源设置为“自定义”时,系统无法自动获知数据量,必须通过该回调函数来确定表格的逻辑尺寸(即总行数)。
- 动态同步:函数内部调用
easydb_get_count(g_db_handle),向底层简易数据库驱动查询当前的有效记录总数。 - 作用:实时返回数据库的物理记录数,驱动表格控件创建相应数量的行模板,确保 UI 展示范围与数据库数据量严格一致。
function on_get_record_count(screen_id,control_id)
if screen_id == 0 and control_id == 1
then
return db.getCnt(g_db_handle)
end
end
2. 记录内容回调:on_get_record_row
此函数作为数据记录控件的内容提供者。在表格确定行数后,每当某一行需要显示或刷新时,系统会调用此函数来获取该行的具体数据内容。
- 参数映射:接收系统传入的
row参数(代表当前请求的行索引)。 - 数据抓取:根据传入的行索引
row,调用封装好的db.get接口,从全局数据库句柄g_db_handle中提取对应索引的记录数据。 - 作用:将数据库中第
row条记录的内容实时映射并返回给控件,完成表格单元格的动态数据填充。
function on_get_record_row(screen_id,control_id,row)
local ret, msg = db.get(g_db_handle, row)
print('on_get_record_row('..screen_id..','..control_id..','..row..') : '..((ret == 0) and msg or 'is nil'))
if ret == 0 then return msg end
end
3.2.2. 初始化数据库
在 main.lua的 on_init() 回调中完成数据库初始化:
- 调用
db.open(flashaddr, createIfNotExist, dataRowSize, maxCount)打开数据库; - 返回值为非零整数句柄,表示数据库操作上下文;返回
0表示打开失败。
--db.lua
--- 打开数据库,返回操作句柄。
-- @param flashaddr (number) Flash 存储起始地址(单位:字节),例如 10*1024*1024
-- @param createIfNotExist (number) 是否在数据库不存在时自动创建(0: 否, 1: 是)
-- @param dataRowSize (number) 单条记录最大字节数(建议 ≤ 1024)
-- @param maxCount (number) 数据库最多可存储的记录条数(建议 ≤ 10000)
-- @return handle (number) 数据库句柄;非0表示成功,0表示失败
function db.open(flashaddr, createIfNotExist, dataRowSize, maxCount)
local handle = easydb_open(flashaddr, createIfNotExist, dataRowSize, maxCount)
return handle
end
--main.lua
function on_init()
dofile('db.lua')
_EN_ON_UPDATA_API_ = 0
g_db_handle = db.open(10*1024*1024, 1, 1000, 1000 )
if g_db_handle == 0 then
print("ERROR: 无法打开数据库!")
return
end
-- 清空旧数据(可选)
db.clear(g_db_handle)
-- 添加示例数据
db.add(g_db_handle, "Alice;Engineer;30")
db.add(g_db_handle, "Bob;Technician;25")
db.add(g_db_handle, "Charlie;Manager;40")
db.add(g_db_handle, "Harden;Basketball players;36")
_EN_ON_UPDATA_API_ = 1
end
3.2.3. 获取数据库记录条数
🛡️ 系统支持通过数据库句柄实时查询当前的有效记录总数。为了确保调用数据操作的安全性,执行具体的增、删、改、查逻辑之前,增加一个边界检查的前置步骤。这个步骤用于验证用户选中的索引(idx)是否在数据库当前的有效范围内。
--- 获取当前数据库中的记录总数。
-- @param handle (number) 数据库句柄
-- @return count (number) 记录数量;若句柄无效,返回 -1
function db.getCnt(handle)
if not handle or handle == 0 then return -1 end
return easydb_get_count(handle)
end
3.2.4. 数据库添加一条记录
在 HMI 主画面中,用户通过三个独立输入控件分别填写 姓名、职业 和 年龄,系统将这些字段绑定至指定的内部寄存器地址。当用户点击“确认”按钮时,触发写入逻辑,将结构化数据以分号分隔格式追加至简易数据库。
| 字段 | 寄存器地址(LW) | 说明 |
|---|---|---|
| 姓名 | LW5010 |
文本输入框,存储用户姓名 |
| 职业 | LW5020 |
文本输入框,存储用户职业 |
| 年龄 | LW5030 |
数值输入框或文本框,存储年龄(整数) |
| 确认信号 | LW5000 |
按钮触发标志:写入后置 0,或由按钮事件直接调用 |
--- 向数据库末尾追加一条新记录。
-- @param handle (number) 数据库句柄
-- @param msg (string) 要写入的字符串数据(长度 ≤ dataRowSize)
-- @return ret (number) 0 表示成功,-1 表示失败(如句柄无效或空间不足)
function db.add(handle, msg)
if not handle or handle == 0 then return -1 end
return easydb_add(handle, msg)
end
function on_update(slave,vtype,addr)
......
elseif addr == 0x5000
then
local key = get_uint16(VT_LW, addr)
if key == 0 --添加
then
local name = get_string(VT_LW, 0x5010)
local position = get_string(VT_LW, 0x5020)
local age = get_string(VT_LW, 0x5030)
db.add(g_db_handle, name..';'..position..';'..age..';') --添加记录
......
end
end
end
3.2.5. 数据库读取一条记录
在 HMI 主画面的表格中,当用户选中某一条记录时,系统将触发数据记录选中通知,并将选中行的索引写入寄存器 LW4004。随后,系统自动读取该索引对应的数据库记录,并按照分号 ; 作为分隔符对记录内容进行解析。解析后的字段依次回填至指定寄存器:姓名(LW4500) 、职业(LW4510)、年龄 (LW4520),并在界面上弹出对话框,实时显示所选记录的详细信息。
--db.lua
--- 根据索引读取一条记录(索引从 0 开始)。
-- @param handle (number) 数据库句柄
-- @param idx (number) 记录索引(0 ~ getCnt()-1)
-- @return ret (number) 0 表示成功,-1 表示句柄无效,-2 表示索引越界
function db.get(handle, idx)
if not handle or handle == 0 then return nil end
local count = easydb_get_count(handle)
if idx < 0 or idx >= count then return -2 end -- 索引越界
return 0, easydb_get(handle, idx)
end
--main.lua
function on_update(slave,vtype,addr)
if addr == 0x4000+4
then
local idx = get_uint32(VT_LW, addr)
local ret, msg = db.get(g_db_handle, idx)
if ret == 0
then
local parts = my_split(msg, ';')
if parts ~= nil
then
set_uint16(VT_LW, 0x4530, idx)
set_string(VT_LW, 0x4500, ((parts[1] == nil) and '--' or parts[1]))
set_string(VT_LW, 0x4510, ((parts[2] == nil) and '--' or parts[2]))
set_string(VT_LW, 0x4520, ((parts[3] == nil) and '--' or parts[3]))
end
show_dialog(1, 232, 33, 50)
end
......
end
end
3.2.6. 数据库修改一条记录
用户在 HMI 界面选中表格中的某一行记录时,记录对应的易数据库(EasyDB)索引 idx 。当用户点击 修改按钮(LW5000 Set 1) 时,系统将 姓名(LW4500)、职业(LW4510)、年龄(LW4520) 输入框中的最新内容,拼接为一条完整的记录字符串,完成对易数据库中第idx记录的修改操作。
--db.lua
--- 修改指定索引的记录内容(索引从 0 开始)。
-- @param handle (number) 数据库句柄
-- @param idx (number) 要修改的记录索引(0 ~ getCnt()-1)
-- @param msg (string) 新的数据内容
-- @return ret (number) 0 表示成功,-1 表示句柄无效,-2 表示索引越界
function db.modify(handle, idx, msg)
if not handle or handle == 0 then return -1 end
local count = easydb_get_count(handle)
if idx < 0 or idx >= count then return -2 end -- 索引越界
print(string.format("db.modify(%d, '%s')......", idx, msg))
return 0, easydb_update(handle, idx, msg)
end
--main.lua
function on_update(slave,vtype,addr)
......
elseif addr == 0x5000
then
local key = get_uint16(VT_LW, addr)
......
elseif key == 1--修改
then
local name = get_string(VT_LW, 0x4500)
local position = get_string(VT_LW, 0x4510)
local age = get_string(VT_LW, 0x4520)
local idx = get_uint16(VT_LW, 0x4530)
db.modify(g_db_handle, idx, name..';'..position..';'..age..';')
......
end
end
end
3.2.7. 数据库删除一条记录
当用户在 HMI 界面选中表格中的某一行记录时,记录对应的易数据库(EasyDB)索引 idx ,当用户点击 删除按钮(LW5000 Set 2) 时,执行删除操作对于数据库的第idx条记录,从而实现数据的移除。
--db.lua
--- 删除指定索引的记录(索引从 0 开始)。
-- @param handle (number) 数据库句柄
-- @param idx (number) 要删除的记录索引(0 ~ getCnt()-1)
-- @return ret (number) 0 表示成功,-1 表示句柄无效,-2 表示索引越界
function db.del(handle, idx)
if not handle or handle == 0 then return -1 end
local count = easydb_get_count(handle)
if idx < 0 or idx >= count then return -2 end -- 索引越界
return 0, easydb_delete(handle, idx)
end
--main.lua
function on_update(slave,vtype,addr)
......
elseif addr == 0x5000
then
......
elseif key == 2--删除
then
local idx = get_uint16(VT_LW, 0x4530)
db.del(g_db_handle, idx)
end
end
end
3.2.8. 关闭数据库
当用户完成数据操作(添加、删除、修改)后,可以关闭数据库,将释放易数据库(EasyDB)占用的文件句柄与内存资源。
--- 关闭数据库,释放资源。
-- @param handle (number) 由 db.open() 返回的数据库句柄
function db.close(handle)
if handle and handle ~= 0 then
easydb_close(handle)
end
end
3.2.9. 清除数据库
在获取数据库连接句柄(Handle)后,通过该句柄执行数据库清除操作。
--- 清空数据库中所有记录。
-- @param handle (number) 数据库句柄
-- @return ret (number) 0 表示成功,-1 表示失败
function db.clear(handle)
if not handle or handle == 0 then return -1 end
return easydb_clear(handle)
end
4. 运行预览
系统启动时,执行数据库(Easy DB)初始化指令。通过 db.open 接口建立连接并获取句柄 g_db_handle。随后,向数据库追加四条预设记录("Alice;Engineer;30"、"Bob;Technician;25"、"Charlie;Manager;40"、"Harden;Basketball players;36"),确保数据记录控件在启动后可实时呈现该初始数据集。
添加操作:系统通过
db.add指令向数据库末尾追加新记录 "张三;FAE;22"。数据记录控件通过on_get_record_count回调感知数据量变化,自动刷新显示,将新记录纳入可见列表。读取操作:用户选中 "张三"(内部索引 4)、"Harden"(内部索引 3)、"Charlie"(内部索引 2)时,系统调用
db.get(g_db_handle, idx)接口读取对应记录,并通过弹窗界面展示详情。- 修改操作:在 "Harden" 的编辑对话框中,用户将年龄字段更新为 "35"。调用
db.modify(g_db_handle, 3, "Harden;Basketball players;35"),并更新保存到数据库。 - 删除操作:用户确认删除 "Charlie"(内部索引 2)。系统执行
db.del(g_db_handle, 2)指令,移除该记录。数据记录控件随之动态调整行数与内容。
