Created
June 15, 2022 07:21
-
-
Save elcritch/83a43a16094bc3b2d1000bfc6da5f896 to your computer and use it in GitHub Desktop.
Example NVS Setting Object (ESP-IDF)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## 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