Skip to content

Instantly share code, notes, and snippets.

@elcritch
Created June 15, 2022 07:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elcritch/83a43a16094bc3b2d1000bfc6da5f896 to your computer and use it in GitHub Desktop.
Save elcritch/83a43a16094bc3b2d1000bfc6da5f896 to your computer and use it in GitHub Desktop.
Example NVS Setting Object (ESP-IDF)
## Example usage of NVS
## license: Apache-2.0
import macros, strutils, md5, options
import json
import nesper
import nesper/net_utils
import nesper/consts, nesper/general
import nesper/nvs_utils
import nesper/servers/rpc/router
import nesper/esp/nvs
import nesper/timers
discard """
Module for getting and setting global constants that need to be
written or read from flash memory. This uses the "NVS" flash library
from the esp-idf.
"""
type
SerialNumber* = object
board_major*: int
board_minor*: int
number*: int
proc `$`*(serial: SerialNumber): string =
let
bmajor = serial.board_major.int.intToStr(3)
bminor = serial.board_minor.int.intToStr(1)
number = serial.number.int.intToStr(4)
"" & bmajor & bminor & number
proc parseSerialNumber*(raw_serno: string): SerialNumber =
let serno = raw_serno.replace("0x", "")
assert serno.len() == 8
result.board_major = parseInt(serno[0..2])
result.board_minor = parseInt(serno[3..3])
result.number = parseInt(serno[4..7])
proc parseSerialNumber*(serno: int): SerialNumber =
serno.toHex(8).parseSerialNumber()
proc toInt*(serial: SerialNumber): int32 =
let serstr: string = $serial
serstr.parseHexInt().int32
type
ConfigSettings* = object
# Object with all possible configuration settings
# Note only values that are changed from the defaults
# are written the NVS flash.
serial_number*: int32
reading_time*: int32
dac_calib_zero_cha*: int32
dac_calib_gain_cha*: int32
adc_ch1_calib_gain*: float32
adc_ch1_calib_zero*: int32
## The code below is a bit ugly, it's to handle putting in different
## types like ints, floats, strings, etc
##
## Don't read too closely, it may produce a headache. Read
## the "public api" after this section ;)
template setField[T: int](val: var T, input: int32) =
val = input
template setField[T: float](val: var T, input: int32) =
val = cast[float32](input)
proc mangleFieldName*(name: string): string =
let nh: string = $toMD5(name)
return $nh[0..7]
template implSetObjectField(obj: object, field: string, val: int32): untyped =
block fieldFound:
for objField, objVal in fieldPairs(obj):
if objField == field:
setField(objVal, val)
# objVal = val
break fieldFound
raise newException(ValueError, "unexpected field: " & field)
proc setObjectField*[T: object](obj: var T, field: string, val: int32) =
# inside a generic proc to avoid side-effects and reduce code size.
expandMacros: # to see what it generates
implSetObjectField(obj, field, val)
template implGetObjectField(obj: object, field: string): untyped =
block fieldFound:
for objField, objVal in fieldPairs(obj):
if objField == field:
# return objVal
return cast[int32](objVal)
raise newException(ValueError, "unexpected field: " & field)
proc getObjectField*[T: object](obj: var T, field: string): int32 =
implGetObjectField(obj, field)
## Primary "SETTINGS" API
##
var
SETTINGS*: ConfigSettings
store: NvsObject
proc load_field*(settings: var ConfigSettings, name: string): int32 =
var mname = mangleFieldName(name)
var resp = store.getInt(mname)
if resp.isSome():
var rval = resp.get()
logi("CFG", "name: %s => %s", name, $rval)
setObjectField(settings, name, rval)
else:
logi("CFG", "skipping name: %s", $name)
proc save_field*(settings: var ConfigSettings, name: string, val: int32) =
logi("CFG", "saving settings ")
var mName = mangleFieldName(name)
var oldVal = getObjectField(settings, name)
var currVal = val
if currVal != oldVal:
logi("CFG", "save setting field: %s(%s) => %d => %d", name.cstring, mName.cstring, oldVal, currVal)
store.setInt(mName, val)
else:
logi("CFG", "skip setting field: %s(%s) => %d => %d", name.cstring, mName.cstring, oldVal, currVal)
proc config_settings_default*(): ConfigSettings =
# set up the default values for the ConfigSettings type
result = ConfigSettings(
dac_calib_zero_cha: -100,
dac_calib_gain_cha: 194,
adc_uA_calib_gain: 3.452e-3,
adc_uA_calib_zero: 0,
)
proc load_settings*(settings: var ConfigSettings) =
for name, val in settings.fieldPairs:
discard settings.load_field(name)
proc save_settings*(ns: var ConfigSettings) =
logi("CFG", "saving settings ")
for name, val in ns.fieldPairs:
ns.save_field(name, cast[int32](val))
proc init_settings() =
store = newNvs("fields", NVS_READWRITE, part_name="storage")
logi("CFG", "init fields: %s", repr(store))
SETTINGS.load_settings()
proc cfg_settings*() =
logi("CFG", "cfg settings")
SETTINGS = config_settings_default()
init_settings()
proc addHalSettings*(rt: var RpcRouter) =
rpc(rt, "erase-fields") do(part_name: string) -> int:
assert part_name in ["nvs", "storage"]
let ret = eraseNvs(part_name)
return ret.int
rpc(rt, "find-fields") do(part: string, name: string) -> int:
# nvs_entry_find(part_name: cstring; namespace_name: cstring; `type`: nvs_type_t): nvs_iterator_t {.
var iter: nvs_iterator_t
iter = nvs_entry_find(part, name, NVS_TYPE_ANY)
while iter != nil:
var info: nvs_entry_info_t
nvs_entry_info(iter, addr info)
logi("CFG", "entry: name = (%s), key: %s, type: %d", info.namespace_name, $info.key, info.type)
iter = nvs_entry_next(iter)
rpc(rt, "stats-fields") do() -> int:
var
nvs_stats: nvs_stats_t
let res = nvs_get_stats("nvs", addr nvs_stats);
logi("CFG", "nvs: stats res: %s", $res)
logi("CFG", "nvs: Count: UsedEntries = (%d), FreeEntries = (%d), AllEntries = (%d)",
nvs_stats.used_entries, nvs_stats.free_entries, nvs_stats.total_entries)
let res1 = nvs_get_stats("storage", addr nvs_stats);
logi("CFG", "storage: stats res: %s", $res1)
logi("CFG", "storage: Count: UsedEntries = (%d), FreeEntries = (%d), AllEntries = (%d)",
nvs_stats.used_entries, nvs_stats.free_entries, nvs_stats.total_entries)
rpc(rt, "init-fields") do() -> int:
init_settings()
return ESP_OK
rpc(rt, "load-all-fields") do() -> JsonNode:
return %* SETTINGS
rpc(rt, "save-field") do(name: string, value: int) -> int:
SETTINGS.save_field(name, value.int32)
SETTINGS.load_settings()
return ESP_OK
rpc(rt, "save-float-field") do(name: string, value: float) -> int:
let val32 = value.float32()
SETTINGS.save_field(name, cast[int32](val32))
SETTINGS.load_settings()
return ESP_OK
rpc(rt, "serial-number") do() -> string:
let val: int32 = SETTINGS.serial_number
return "0x" & val.int.toHex()
rpc(rt, "serial-info") do() -> JsonNode:
let val: int32 = SETTINGS.serial_number
return % val.parseSerialNumber()
rpc(rt, "sensor-id") do() -> string:
return generate_sensor_id()
rpc(rt, "uptime") do() -> int:
return millis().int
rpc(rt, "set-serial-number") do(serial: SerialNumber) -> JsonNode:
SETTINGS.save_field("serial_number", serial.toInt())
SETTINGS.load_settings()
let sout: SerialNumber = parseSerialNumber(SETTINGS.serial_number)
return %sout
rpc(rt, "set-dac-gain") do(val: int) -> int:
SETTINGS.save_field("dac_calib_gain_cha", val.int32)
SETTINGS.load_settings()
return SETTINGS.dac_calib_gain_cha
rpc(rt, "set-dac-offset") do(val: int) -> int:
SETTINGS.save_field("dac_calib_zero_cha", val.int32)
SETTINGS.load_settings()
return SETTINGS.dac_calib_zero_cha
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment