NVM(参数存储)
# 简介
nvm:非易失性存储器(英语:non-volatile memory,缩写为NVM)是指当电流关掉后,所存储的数据不会消失的电脑存储器,利用文件系统实现的一种非易失性参数存储管理模块。
典型的应用场景为:小数据量的简单键值对参数。不适合大容量数据的存储管理,如果数据量超过10K,建议直接使用io接口操作文件来管理
## API说明
> 详细的API介绍见[nvm API章节](https://doc.openluat.com/wiki/21?wiki_page_id=2284)
# 实现流程
两个文件一个表

所以,我们只需要操作第一个默认参数文件。
1.初始化
* 初始化是使用nvm必须先执行的动作,只有先执行初始化才能使用nvm。nvm初始化函数也特别简单nvm.init("config.lua"):config.lua是创建用来储存默认参数的文件。
2.写操作
* 初始化后,我们就可以进行参数的储存了。nvm.set("date",4)。这个函数代表我们讲一个默认参数“date”赋值为4。写操作首先修改tPara中的值,然后将tPara中的所有数据全量覆盖写到para.lua文件中,会执行写文件的动作,存在一定耗时;如果频繁的写,会导致运行效率低下。还有nvm.sett(),设置某个table类型参数的某一个索引的值。
3.读操作
* 读操作,就是读默认参数文件里的参数数据。nvm.get("date")
"date"是刚才我们写操作写入的数值,现在我们读出来,返回值就是4。读操作直接读取的是tPara中的值,直接读取内存,比读取实时参数文件效率高。还有nvm.gett(),读取某个table类型参数的某一个索引的值。
4.恢复出厂设置
* nvm.restore(),是恢复出厂设置功能,会清除所存储的默认参数数据。恢复出厂设置之后,建议软件重启;如果不想软重启,需要调用nvm.init接口重新初始化。
-----
# table类型参数的写入和读取
* nvm.set()和nvm.get()是对某个参数进行读写,如果想对table类型参数进行读写操作也可以。nvm.sett()和nvm.gett()两个函数可以进行对table类型参数的操作。
# 示例
1.初始化
* 初始化代码如下,你还需要创建一个config.lua文件,不然nvm功能初始化失败。
```lua
require"config"
require"nvm"
nvm.init("config.lua")
```
2.写操作
* 写操作代码如下,初始化成功后就可以进行nvm的读写操作了。写操作首先修改tPara中的值,然后将tPara中的所有数据全量覆盖写到para.lua文件中,会执行写文件的动作,存在一定耗时;如果频繁的写,会导致运行效率低下。当连续写入参数较多时,就可参考第二种写入方法,最后一次再写入。
```lua
require"config"
require"nvm"
nvm.set("date",1)
--多参数连续录入时
nvm.set("date1",1,nil,false)
nvm.set("date2",2,nil,false)
nvm.set("date3",3,nil,false)
nvm.set("date4",4,)
```
3.读操作
* 写完后,要进行读操作,代码如下。读操作直接读取的是tPara中的值,直接读取内存,比读取实时参数文件效率高
```lua
require"config"
require"nvm"
local date =nvm.get("date")
print(date)
```
# 常见问题
* 1.nvm适用于什么场景?
nvm每次更新参数,保存到参数文件中时,会把所有参数拼接在一起,然后执行一次全量写文件的动作;此拼接操作消耗内存较多,如果数据量很大,需要的内存就很大,在系统可用内存紧张的情况下,很容易出现内存不足,从而导致参数更新失败,严重情况下还会造成系统重启
因此,nvm仅适用于小数据量的简单键值对参数;不适用于大容量数据的存储管理,如果数据量超过10KB【仅仅为经验值,实际能够存储的数据量和系统运行过程中的动态可用内存有关:可用内存越小,允许存储的数据量就越少;可用内存越大,允许存储的数据量就越多,但是也不建议超过10KB】,建议参考fs的demo直接使用io接口操作文件来管理
* 2.本地烧录时会清除原有的para.lua吗?
原有的para.lua是在nvm.init(“config.lua”,true)时创建的,则不会清除
原有的para.lua是在nvm.init(“config.lua”)时创建的,则会清除
* 3.fota远程升级时,会清除原有的para.lua吗?
默认不会
如果需要清除,根据产品的业务逻辑,在合适的时间点,调用nvm.restore()恢复出厂设置即可
* 4.软件版本升级时,可以在config.lua中增加新的参数吗?
可以,参考初始化流程图
需要执行一次初始化动作才会生效,一般是直接重启执行初始化
* 5.软件版本升级时,可以在config.lua中删除旧的参数吗?
可以,但是操作起来比较复杂,不建议这样做
如果旧的参数没用了,可以在config.lua中一直保留;脚本代码不要再读写这些参数就行了
* 6.程序运行过程中,可以使用nvm.set接口增加新的参数吗?
可以,参考写操作流程图
新增的参数,在恢复出厂设置后会被清除,因为config.lua中没有此参数
* 7.程序运行过程中,可以使用nvm.set接口删除已有的参数吗?
可以,参考写操作流程图,将某个参数设置为nil就相当于删除
删除的参数,在恢复出厂设置后会还原为config.lua中的默认值,因为config.lua中存在此参数
* 8.读写操作过程中,掉电会丢失数据吗?
nvm有备份机制,如果掉电时正在写参数,可能会造成正在写入的参数数据不生效,下次开机会恢复为上次正常写入的数据,不会导致数据内容出错
如果掉电时正在读参数,不会对参数数据造成任何影响;因为读取的是内存中的数据
* 9.nvm.set接口可以写多少次?
nvm通过文件接口写flash,具有擦写均衡机制
flash可以完全擦写至少10万次
假设通过rtos.get_fs_free_size()获取到的文件系统剩余空间为512K字节,nvm参数文件总大小为1K字节,因为nvm参数文件还存在一个备份文件,所以写256次可以完全擦写一次文件系统剩余空间。允许擦写的总次数理论值就是256*10万次
* 10.config文件不加换行符为什么会报错?
因为每个lua文件每个文件末尾必须加上换行符,不然程序程序会报错。不加换行符报错如下:

11. /#lua_user# 目录为什么重刷固件还存在,它是特殊目录吗?
* #lua_user#目录就是一个普通目录,在/目录下,与其他目录共用一个空间,操作方式与其他目录一致,唯一的区别是这个目录模块会保护它,当重新烧录固件时它还存在。
操作方法:
首先现需要创建这个目录,然后进行io文件文件操作
测试demo:[testfs.zip](https://cdn.openluat-luatcommunity.openluat.com/attachment/20210708154524982_testfs.zip)
运行后注释掉创建文件部分再次烧录固件和脚本,还是能够读取到创建文件

12. 使用nvm.set()接口参数设置正确为什么会报错?
如图所示:

以demo/nvm这个demo为例,这个日志不是报错,当写入相同参数会有这个提示,写入不同参数就会提示写入正确

在写入数据前可以先加个判断,判断想写入数据与目前存储的数据是否一致,不一致再写入到模块
13. 出现lualibc_fopen fail -4200062,0,/nvm_para.lua是什么问题?

这个不是问题,烧录后,第一次开机,没有文件正常