NVS is used to store PHY calibration data, WiFi configuration, and BT configuration. Previously BT examples did not call nvs_flash_init, relying on the fact that it is called during PHY init. However PHY init did not handle possible NVS initialization errors. This change moves PHY init procedure into the application, and adds diagnostic messages to BT config management routines if NVS is not initialized.
578 lines
15 KiB
C
578 lines
15 KiB
C
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#define LOG_TAG "bt_osi_config"
|
|
#include "esp_system.h"
|
|
#include "nvs_flash.h"
|
|
#include "nvs.h"
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "allocator.h"
|
|
#include "config.h"
|
|
#include "list.h"
|
|
#include "bt_trace.h"
|
|
|
|
#define CONFIG_FILE_MAX_SIZE (2048)
|
|
#define CONFIG_KEY "bt_cfg_key"
|
|
typedef struct {
|
|
char *key;
|
|
char *value;
|
|
} entry_t;
|
|
|
|
typedef struct {
|
|
char *name;
|
|
list_t *entries;
|
|
} section_t;
|
|
|
|
struct config_t {
|
|
list_t *sections;
|
|
};
|
|
|
|
// Empty definition; this type is aliased to list_node_t.
|
|
struct config_section_iter_t {};
|
|
|
|
static void config_parse(nvs_handle fp, config_t *config);
|
|
|
|
static section_t *section_new(const char *name);
|
|
static void section_free(void *ptr);
|
|
static section_t *section_find(const config_t *config, const char *section);
|
|
|
|
static entry_t *entry_new(const char *key, const char *value);
|
|
static void entry_free(void *ptr);
|
|
static entry_t *entry_find(const config_t *config, const char *section, const char *key);
|
|
|
|
config_t *config_new_empty(void)
|
|
{
|
|
config_t *config = osi_calloc(sizeof(config_t));
|
|
if (!config) {
|
|
LOG_ERROR("%s unable to allocate memory for config_t.\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
config->sections = list_new(section_free);
|
|
if (!config->sections) {
|
|
LOG_ERROR("%s unable to allocate list for sections.\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
return config;
|
|
|
|
error:;
|
|
config_free(config);
|
|
return NULL;
|
|
}
|
|
|
|
config_t *config_new(const char *filename)
|
|
{
|
|
assert(filename != NULL);
|
|
|
|
config_t *config = config_new_empty();
|
|
if (!config) {
|
|
return NULL;
|
|
}
|
|
|
|
esp_err_t err;
|
|
nvs_handle fp;
|
|
err = nvs_open(filename, NVS_READWRITE, &fp);
|
|
if (err != ESP_OK) {
|
|
if (err == ESP_ERR_NVS_NOT_INITIALIZED) {
|
|
LOG_ERROR("%s: NVS not initialized. "
|
|
"Call nvs_flash_init before initializing bluetooth.", __func__);
|
|
} else {
|
|
LOG_ERROR("%s unable to open NVS namespace '%s'\n", __func__, filename);
|
|
}
|
|
config_free(config);
|
|
return NULL;
|
|
}
|
|
|
|
config_parse(fp, config);
|
|
nvs_close(fp);
|
|
return config;
|
|
}
|
|
|
|
void config_free(config_t *config)
|
|
{
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
list_free(config->sections);
|
|
osi_free(config);
|
|
}
|
|
|
|
bool config_has_section(const config_t *config, const char *section)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
|
|
return (section_find(config, section) != NULL);
|
|
}
|
|
|
|
bool config_has_key(const config_t *config, const char *section, const char *key)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
return (entry_find(config, section, key) != NULL);
|
|
}
|
|
|
|
bool config_has_key_in_section(config_t *config, char *key, char *key_value)
|
|
{
|
|
LOG_DEBUG("key = %s, value = %s", key, key_value);
|
|
for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
|
|
const section_t *section = (const section_t *)list_node(node);
|
|
|
|
for (const list_node_t *node = list_begin(section->entries); node != list_end(section->entries); node = list_next(node)) {
|
|
entry_t *entry = list_node(node);
|
|
LOG_DEBUG("entry->key = %s, entry->value = %s", entry->key, entry->value);
|
|
if (!strcmp(entry->key, key) && !strcmp(entry->value, key_value)) {
|
|
LOG_DEBUG("%s, the irk aready in the flash.", __func__);
|
|
section_free((void *)section);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int config_get_int(const config_t *config, const char *section, const char *key, int def_value)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
entry_t *entry = entry_find(config, section, key);
|
|
if (!entry) {
|
|
return def_value;
|
|
}
|
|
|
|
char *endptr;
|
|
int ret = strtol(entry->value, &endptr, 0);
|
|
return (*endptr == '\0') ? ret : def_value;
|
|
}
|
|
|
|
bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
entry_t *entry = entry_find(config, section, key);
|
|
if (!entry) {
|
|
return def_value;
|
|
}
|
|
|
|
if (!strcmp(entry->value, "true")) {
|
|
return true;
|
|
}
|
|
if (!strcmp(entry->value, "false")) {
|
|
return false;
|
|
}
|
|
|
|
return def_value;
|
|
}
|
|
|
|
const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
entry_t *entry = entry_find(config, section, key);
|
|
if (!entry) {
|
|
return def_value;
|
|
}
|
|
|
|
return entry->value;
|
|
}
|
|
|
|
void config_set_int(config_t *config, const char *section, const char *key, int value)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
char value_str[32] = { 0 };
|
|
sprintf(value_str, "%d", value);
|
|
config_set_string(config, section, key, value_str, false);
|
|
}
|
|
|
|
void config_set_bool(config_t *config, const char *section, const char *key, bool value)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
config_set_string(config, section, key, value ? "true" : "false", false);
|
|
}
|
|
|
|
void config_set_string(config_t *config, const char *section, const char *key, const char *value, bool insert_back)
|
|
{
|
|
section_t *sec = section_find(config, section);
|
|
if (!sec) {
|
|
sec = section_new(section);
|
|
if (insert_back) {
|
|
list_append(config->sections, sec);
|
|
} else {
|
|
list_prepend(config->sections, sec);
|
|
}
|
|
}
|
|
|
|
for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
|
|
entry_t *entry = list_node(node);
|
|
if (!strcmp(entry->key, key)) {
|
|
osi_free(entry->value);
|
|
entry->value = osi_strdup(value);
|
|
return;
|
|
}
|
|
}
|
|
|
|
entry_t *entry = entry_new(key, value);
|
|
list_append(sec->entries, entry);
|
|
}
|
|
|
|
bool config_remove_section(config_t *config, const char *section)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
|
|
section_t *sec = section_find(config, section);
|
|
if (!sec) {
|
|
return false;
|
|
}
|
|
|
|
return list_remove(config->sections, sec);
|
|
}
|
|
|
|
bool config_remove_key(config_t *config, const char *section, const char *key)
|
|
{
|
|
assert(config != NULL);
|
|
assert(section != NULL);
|
|
assert(key != NULL);
|
|
|
|
section_t *sec = section_find(config, section);
|
|
entry_t *entry = entry_find(config, section, key);
|
|
if (!sec || !entry) {
|
|
return false;
|
|
}
|
|
|
|
return list_remove(sec->entries, entry);
|
|
}
|
|
|
|
const config_section_node_t *config_section_begin(const config_t *config)
|
|
{
|
|
assert(config != NULL);
|
|
return (const config_section_node_t *)list_begin(config->sections);
|
|
}
|
|
|
|
const config_section_node_t *config_section_end(const config_t *config)
|
|
{
|
|
assert(config != NULL);
|
|
return (const config_section_node_t *)list_end(config->sections);
|
|
}
|
|
|
|
const config_section_node_t *config_section_next(const config_section_node_t *node)
|
|
{
|
|
assert(node != NULL);
|
|
return (const config_section_node_t *)list_next((const list_node_t *)node);
|
|
}
|
|
|
|
const char *config_section_name(const config_section_node_t *node)
|
|
{
|
|
assert(node != NULL);
|
|
const list_node_t *lnode = (const list_node_t *)node;
|
|
const section_t *section = (const section_t *)list_node(lnode);
|
|
return section->name;
|
|
}
|
|
|
|
bool config_save(const config_t *config, const char *filename)
|
|
{
|
|
assert(config != NULL);
|
|
assert(filename != NULL);
|
|
assert(*filename != '\0');
|
|
|
|
esp_err_t err;
|
|
int err_code = 0;
|
|
nvs_handle fp;
|
|
char *line = osi_calloc(1024);
|
|
char *buf = osi_calloc(CONFIG_FILE_MAX_SIZE);
|
|
if (!line || !buf) {
|
|
err_code |= 0x01;
|
|
goto error;
|
|
}
|
|
|
|
err = nvs_open(filename, NVS_READWRITE, &fp);
|
|
if (err != ESP_OK) {
|
|
if (err == ESP_ERR_NVS_NOT_INITIALIZED) {
|
|
LOG_ERROR("%s: NVS not initialized. "
|
|
"Call nvs_flash_init before initializing bluetooth.", __func__);
|
|
}
|
|
err_code |= 0x02;
|
|
goto error;
|
|
}
|
|
|
|
int w_cnt, w_cnt_total = 0;
|
|
for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
|
|
const section_t *section = (const section_t *)list_node(node);
|
|
w_cnt = snprintf(line, 1024, "[%s]\n", section->name);
|
|
LOG_DEBUG("section name: %s, w_cnt + w_cnt_total = %d\n", section->name, w_cnt + w_cnt_total);
|
|
if (w_cnt + w_cnt_total < CONFIG_FILE_MAX_SIZE) {
|
|
memcpy(buf + w_cnt_total, line, w_cnt);
|
|
w_cnt_total += w_cnt;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
|
|
const entry_t *entry = (const entry_t *)list_node(enode);
|
|
LOG_DEBUG("(key, val): (%s, %s)\n", entry->key, entry->value);
|
|
w_cnt = snprintf(line, 1024, "%s = %s\n", entry->key, entry->value);
|
|
LOG_DEBUG("%s, w_cnt + w_cnt_total = %d", __func__, w_cnt + w_cnt_total);
|
|
if (w_cnt + w_cnt_total < CONFIG_FILE_MAX_SIZE) {
|
|
memcpy(buf + w_cnt_total, line, w_cnt);
|
|
w_cnt_total += w_cnt;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only add a separating newline if there are more sections.
|
|
if (list_next(node) != list_end(config->sections)) {
|
|
if (1 + w_cnt_total < CONFIG_FILE_MAX_SIZE) {
|
|
buf[w_cnt_total] = '\n';
|
|
w_cnt_total += 1;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
buf[w_cnt_total] = '\0';
|
|
|
|
err = nvs_set_blob(fp, CONFIG_KEY, buf, w_cnt_total);
|
|
|
|
if (err != ESP_OK) {
|
|
nvs_close(fp);
|
|
err_code |= 0x04;
|
|
goto error;
|
|
}
|
|
|
|
err = nvs_commit(fp);
|
|
if (err != ESP_OK) {
|
|
nvs_close(fp);
|
|
err_code |= 0x08;
|
|
goto error;
|
|
}
|
|
|
|
nvs_close(fp);
|
|
osi_free(line);
|
|
osi_free(buf);
|
|
return true;
|
|
|
|
error:
|
|
if (buf) {
|
|
osi_free(buf);
|
|
}
|
|
if (line) {
|
|
osi_free(line);
|
|
}
|
|
if (err_code) {
|
|
LOG_ERROR("%s, err_code: 0x%x\n", __func__, err_code);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static char *trim(char *str)
|
|
{
|
|
while (isspace((unsigned char)(*str))) {
|
|
++str;
|
|
}
|
|
|
|
if (!*str) {
|
|
return str;
|
|
}
|
|
|
|
char *end_str = str + strlen(str) - 1;
|
|
while (end_str > str && isspace((unsigned char)(*end_str))) {
|
|
--end_str;
|
|
}
|
|
|
|
end_str[1] = '\0';
|
|
return str;
|
|
}
|
|
|
|
static void config_parse(nvs_handle fp, config_t *config)
|
|
{
|
|
assert(fp != 0);
|
|
assert(config != NULL);
|
|
|
|
int line_num = 0;
|
|
int err_code = 0;
|
|
char *line = osi_calloc(1024);
|
|
char *section = osi_calloc(1024);
|
|
char *buf = osi_calloc(CONFIG_FILE_MAX_SIZE);
|
|
if (!line || !section || !buf) {
|
|
err_code |= 0x01;
|
|
goto error;
|
|
}
|
|
|
|
esp_err_t err;
|
|
size_t length = CONFIG_FILE_MAX_SIZE;
|
|
err = nvs_get_blob(fp, CONFIG_KEY, buf, &length);
|
|
if (err != ESP_OK) {
|
|
err_code |= 0x02;
|
|
goto error;
|
|
}
|
|
|
|
char *p_line_end;
|
|
char *p_line_bgn = buf;
|
|
strcpy(section, CONFIG_DEFAULT_SECTION);
|
|
|
|
while ( (p_line_bgn < buf + length - 1) && (p_line_end = strchr(p_line_bgn, '\n'))) {
|
|
|
|
// get one line
|
|
int line_len = p_line_end - p_line_bgn;
|
|
if (line_len > 1023) {
|
|
LOG_WARN("%s exceed max line length on line %d.\n", __func__, line_num);
|
|
break;
|
|
}
|
|
memcpy(line, p_line_bgn, line_len);
|
|
line[line_len] = '\0';
|
|
p_line_bgn = p_line_end + 1;
|
|
char *line_ptr = trim(line);
|
|
++line_num;
|
|
|
|
// Skip blank and comment lines.
|
|
if (*line_ptr == '\0' || *line_ptr == '#') {
|
|
continue;
|
|
}
|
|
|
|
if (*line_ptr == '[') {
|
|
size_t len = strlen(line_ptr);
|
|
if (line_ptr[len - 1] != ']') {
|
|
LOG_WARN("%s unterminated section name on line %d.\n", __func__, line_num);
|
|
continue;
|
|
}
|
|
strncpy(section, line_ptr + 1, len - 2);
|
|
section[len - 2] = '\0';
|
|
} else {
|
|
char *split = strchr(line_ptr, '=');
|
|
if (!split) {
|
|
LOG_DEBUG("%s no key/value separator found on line %d.\n", __func__, line_num);
|
|
continue;
|
|
}
|
|
*split = '\0';
|
|
config_set_string(config, section, trim(line_ptr), trim(split + 1), true);
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (buf) {
|
|
osi_free(buf);
|
|
}
|
|
if (line) {
|
|
osi_free(line);
|
|
}
|
|
if (section) {
|
|
osi_free(section);
|
|
}
|
|
if (err_code) {
|
|
LOG_ERROR("%s returned with err code: %d\n", __func__, err_code);
|
|
}
|
|
}
|
|
|
|
static section_t *section_new(const char *name)
|
|
{
|
|
section_t *section = osi_calloc(sizeof(section_t));
|
|
if (!section) {
|
|
return NULL;
|
|
}
|
|
|
|
section->name = osi_strdup(name);
|
|
section->entries = list_new(entry_free);
|
|
return section;
|
|
}
|
|
|
|
static void section_free(void *ptr)
|
|
{
|
|
if (!ptr) {
|
|
return;
|
|
}
|
|
|
|
section_t *section = ptr;
|
|
osi_free(section->name);
|
|
list_free(section->entries);
|
|
osi_free(section);
|
|
}
|
|
|
|
static section_t *section_find(const config_t *config, const char *section)
|
|
{
|
|
for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
|
|
section_t *sec = list_node(node);
|
|
if (!strcmp(sec->name, section)) {
|
|
return sec;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static entry_t *entry_new(const char *key, const char *value)
|
|
{
|
|
entry_t *entry = osi_calloc(sizeof(entry_t));
|
|
if (!entry) {
|
|
return NULL;
|
|
}
|
|
|
|
entry->key = osi_strdup(key);
|
|
entry->value = osi_strdup(value);
|
|
return entry;
|
|
}
|
|
|
|
static void entry_free(void *ptr)
|
|
{
|
|
if (!ptr) {
|
|
return;
|
|
}
|
|
|
|
entry_t *entry = ptr;
|
|
osi_free(entry->key);
|
|
osi_free(entry->value);
|
|
osi_free(entry);
|
|
}
|
|
|
|
static entry_t *entry_find(const config_t *config, const char *section, const char *key)
|
|
{
|
|
section_t *sec = section_find(config, section);
|
|
if (!sec) {
|
|
return NULL;
|
|
}
|
|
|
|
for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
|
|
entry_t *entry = list_node(node);
|
|
if (!strcmp(entry->key, key)) {
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|