Skip to content

fdb — 数据存储 ​

模块名:fdb

基于 FlashDB 的掉电不丢失存储,提供两套独立能力:

  • KVDB(键值库):按键名存取配置、校准参数、累计量。
  • TSDB(时序库):按时间戳追加采样数据,按时间范围回查。

全部是库级函数,没有对象。数据库的初始化在固件侧完成,Lua 里不需要也没有 kvdb_init 之类的函数。

KVDB 与 TSDB 分别存什么,以及时序库的环形覆盖与固件兼容性

函数总览 ​

键值库(KVDB) ​

函数作用
fdb.kv_set(string, integer)写入键值,支持数字/字符串/布尔/table
fdb.kv_get(string)按键读值,可取 table 里的某个字段
fdb.kv_del(string)按键删除
fdb.kv_iter创建遍历迭代器
fdb.kv_next取迭代器的下一个键名

时序库(TSDB) ​

函数作用
fdb.ts_add(integer)追加一条时序数据
fdb.ts_select(integer, integer)按时间范围查询,逐条回调

本模块不注册常量。


fdb.kv_set ​

lua
fdb.kv_set(key, value) --> boolean

写入一个键值对。值的类型会被记录,kv_get 时按原类型返回。

参数

#参数类型必填默认值说明
1keystring是—键名
2valuenumber | string | boolean | table是—值。table 会先用 json.encode 序列化后存储

返回值

类型说明
boolean是否写入成功

示例

lua
fdb.kv_set("site", "workshop-A")
fdb.kv_set("calib", 1.0234)
fdb.kv_set("enabled", true)
fdb.kv_set("pid", { p = 2.0, i = 0.3, d = 0.1 })   -- table 走 JSON

说明

  • Flash 有擦写寿命。不要在高频定时器里反复写同一个键,只在值真正变化时写。

fdb.kv_get ​

lua
fdb.kv_get(key, [field]) --> value | nil

按键名读值。

参数

#参数类型必填默认值说明
1keystring是—键名
2fieldstring否""当值是 table(JSON)时,只取这个字段

返回值

类型说明
number / string / boolean / table按写入时的类型还原
nil键不存在或读取失败

示例

lua
local site = fdb.kv_get("site")
local pid  = fdb.kv_get("pid")            -- 整个 table
local kp   = fdb.kv_get("pid", "p")       -- 只取 p 字段

if kp then ctrl = pid.creat(kp, ...) end

说明

  • 读不到时返回 nil,使用前需判空。

fdb.kv_del ​

lua
fdb.kv_del(key) --> boolean

删除一个键。

参数

#参数类型必填默认值说明
1keystring是—键名

返回值

类型说明
boolean是否删除成功

fdb.kv_iter ​

lua
fdb.kv_iter() --> iter | nil

创建一个 KVDB 迭代器,用于遍历所有键。

参数

无参数。

返回值

类型说明
userdata迭代器
nil创建失败

fdb.kv_next ​

lua
fdb.kv_next(iter) --> string | nil

取迭代器的下一个键名。

参数

#参数类型必填默认值说明
1iteruserdata是—fdb.kv_iter() 返回的迭代器

返回值

类型说明
string下一个键名
nil已遍历完

示例

lua
local iter = fdb.kv_iter()
if iter then
    while true do
        local k = fdb.kv_next(iter)
        if not k then break end
        print(k, fdb.kv_get(k))
    end
end

fdb.ts_add ​

lua
fdb.ts_add(key, value) --> boolean

向时序库追加一条数据。时间戳由固件自动打上。

参数

#参数类型必填默认值说明
1keystring是—点位名,会随值一起存入记录,供 ts_select 按点位过滤(最长 255 字节)
2valuenumber | string | boolean | table是—值,类型规则同 kv_set

返回值

类型说明
boolean是否写入成功

示例

lua
timer.start(60000, function()             -- 每分钟各存一条,用 key 区分点位
    fdb.ts_add("temp", plc.df(100))
    fdb.ts_add("press", plc.df(102))
end)

说明

  • key 会与值一起写进每条记录,ts_select 可据此只取某个点位(见下)。
  • 新版固件能自动读出旧版固件写的老数据,老数据没有 key,在 ts_select 里 key 回调参数为 nil。
  • 每条记录会多占 2 + key 长度 个字节。key 应尽量简短(如 "t1" 而不是 "workshop1_temperature"),否则会明显增加存储占用。
  • 反过来不成立:新版固件写的数据,旧版固件读不出来,值会返回 nil。降级固件前请注意。
  • 时序库是环形存储,写满后覆盖最旧的数据。采样周期需与存储容量匹配,容量不足以保存长周期的秒级数据。

fdb.ts_select ​

lua
fdb.ts_select(start_time, end_time, callback)         --> boolean   -- 不过滤,返回全部点位
fdb.ts_select(start_time, end_time, key, callback)    --> boolean   -- 只返回匹配 key 的记录

按时间范围查询时序库,每查到一条就调一次回调。可选传入 key 只查某个点位。

参数

#参数类型必填默认值说明
1start_timeinteger是—起始 Unix 时间戳(秒)
2end_timeinteger是—结束 Unix 时间戳(秒)
3keystring否—传入则只回调 key 匹配的记录;省略则返回全部。老格式(无 key)记录在指定 key 时一律被跳过
—callbackfunction是—逐条回调 (timestamp, value, key)。第 3 个参数是记录里的点位名,老数据为 nil

返回值

类型说明
boolean是否查到了数据(读取长度 > 0)

示例

lua
local now = os.time()

-- 查全部点位
fdb.ts_select(now - 3600, now, function(ts, value, key)
    print(os.date("%H:%M:%S", ts), key, value)
end)

-- 只查温度这一个点位
fdb.ts_select(now - 3600, now, "temp", function(ts, value)
    print(os.date("%H:%M:%S", ts), value)
end)

说明

  • 回调是同步调用的,ts_select 返回时所有回调都已执行完。范围内数据较多时会占用较长时间,不要在高频定时器里查询大范围。
  • 过滤是读取时按记录里的 key 逐条比对,不是索引查找。范围内记录较多时仍会遍历全部记录,只是不对不匹配的记录触发回调。
  • 返回值只表示"这段时间内读到过数据",不表示过滤后有命中。判断是否命中需要在回调里自行计数。

常见用法 ​

开机加载参数,参数变化时才保存

lua
-- 开机取回上次保存的调节参数,没存过就用默认值
local kp = fdb.kv_get("gain", "p") or 2.0
local ki = fdb.kv_get("gain", "i") or 0.3
local kd = fdb.kv_get("gain", "d") or 0.1

-- 交给梯形图使用:放大 100 倍存进 D 区
plc.d(500, math.floor(kp * 100))
plc.d(501, math.floor(ki * 100))
plc.d(502, math.floor(kd * 100))

-- 上位机改完参数后调一次,只在变化时写 Flash
function save_gain(p, i, d)
    fdb.kv_set("gain", { p = p, i = i, d = d })
end

本地缓存历史曲线,供上位机回查

lua
timer.start(60000, function()
    fdb.ts_add("temp", plc.df(100))
    fdb.ts_add("press", plc.df(102))
end)

-- 上位机通过 MQTT 请求近一小时的温度曲线
mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
    if topic:match("history$") then
        local now, rows = os.time(), {}
        fdb.ts_select(now - 3600, now, "temp", function(ts, v)   -- 只取 temp 这一路
            rows[#rows + 1] = { t = ts, v = v }
        end)
        mqtt.publish(mqttc, "data/history", json.encode(rows), 0, 0)
    end
end)

注意事项 ​

  • Flash 擦写次数有限,不要在秒级定时器里写 KV。配置类数据只在变化时写。
  • kv_get 读不到返回 nil,应配合 or 默认值 或显式判空。
  • 时序库容量有限、写满覆盖最旧数据,长期历史请上传到服务器保存。
  • ts_select 是同步遍历,大范围查询会阻塞主循环。
  • 个别裁剪过的固件可能未启用本模块,此时脚本里访问 fdb 会得到 nil 并报 "attempt to index a nil value"。

例程 ​

统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。

例程 1:开机加载参数,变化时保存 ​

适用:LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / 软PLC模拟器 / 上位机 | 依赖模块:fdb | 难度:★☆☆

场景:PID 参数掉电不丢,开机取回,没存过用默认值。

完整代码(可直接下载运行):

lua
local kp = fdb.kv_get("gain", "p") or 2.0
local ki = fdb.kv_get("gain", "i") or 0.3
local kd = fdb.kv_get("gain", "d") or 0.1
print("kp=", kp, "ki=", ki, "kd=", kd)

-- 参数变化时才写 Flash
function save_gain(p, i, d)
    fdb.kv_set("gain", { p = p, i = i, d = d })
end

要点:kv_get 读不到返回 nil,用 or 默认值 兜底;table 走 JSON 存取。

验证方法:存一次后重启,参数还在。

注意事项:Flash 有擦写寿命,别在秒级定时器反复写同一键;只读变化写。


例程 2:时序库存曲线 + 回查 ​

适用:同上 | 依赖模块:fdb json mqtt plc | 难度:★★☆

场景:每分钟存温度/压力,上位机要近一小时曲线时回查。

完整代码(可直接下载运行):

lua
timer.start(60000, function()
    fdb.ts_add("temp", plc.df(100))
    fdb.ts_add("press", plc.df(102))
end)

mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
    if topic:match("history$") then
        local now, rows = os.time(), {}
        fdb.ts_select(now - 3600, now, "temp", function(ts, v)
            rows[#rows + 1] = { t = ts, v = v }
        end)
        mqtt.publish(mqttc, "data/history", json.encode(rows), 0, 0)
    end
end)

要点:ts_add 自动打时间戳,key 短小省空间;ts_select 同步遍历回调。

验证方法:请求 history,收到一小时温度点数组。

注意事项:ts_select 同步阻塞主循环,别查太大范围;时序库环形覆盖,长历史上传服务器。


例程 3:注意事项——kv_get 返回 nil 判空 ​

适用:同上 | 依赖模块:fdb | 难度:★☆☆

完整代码(可直接下载运行):

lua
local v = fdb.kv_get("site")
print(v:upper())            -- ❌ v 为 nil 时报错
local v2 = fdb.kv_get("site") or "default"
print(v2:upper())           -- ✅ 先兜底

要点:kv_get 读不到返回 nil,使用前必须判空或 or。

验证方法:键不存在时第一段报错,第二段正常。

注意事项:个别裁剪固件未启用本模块,访问 fdb 为 nil 会报错。

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研