diff --git a/CMakeLists.txt b/CMakeLists.txt index ebcaf94..deabd33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,10 @@ elseif("nimble" IN_LIST BUILD_COMPONENTS OR "__nimble" IN_LIST __hack_component_ list(APPEND ESP_NIMBLE_PRIV_REQUIRES nimble ) +elseif("bt" IN_LIST BUILD_COMPONENTS OR "__bt" IN_LIST __hack_component_targets) + list(APPEND ESP_NIMBLE_PRIV_REQUIRES + bt + ) endif() if("arduino" IN_LIST BUILD_COMPONENTS OR __hack_component_targets MATCHES "__idf_arduino") @@ -20,15 +24,7 @@ if("arduino" IN_LIST BUILD_COMPONENTS OR __hack_component_targets MATCHES "__idf ) endif() -idf_component_register( - REQUIRED_IDF_TARGETS - "esp32" - "esp32s3" - "esp32c3" - INCLUDE_DIRS - "src" - SRCS - "src/NimBLE2904.cpp" +set(srcs "src/NimBLE2904.cpp" "src/NimBLEAddress.cpp" "src/NimBLEAdvertisedDevice.cpp" "src/NimBLEAdvertising.cpp" @@ -41,10 +37,6 @@ idf_component_register( "src/NimBLEEddystoneURL.cpp" "src/NimBLEExtAdvertising.cpp" "src/NimBLEHIDDevice.cpp" - "src/NimBLEMeshCreateModel.c" - "src/NimBLEMeshElement.cpp" - "src/NimBLEMeshModel.cpp" - "src/NimBLEMeshNode.cpp" "src/NimBLERemoteCharacteristic.cpp" "src/NimBLERemoteDescriptor.cpp" "src/NimBLERemoteService.cpp" @@ -53,10 +45,36 @@ idf_component_register( "src/NimBLEServer.cpp" "src/NimBLEService.cpp" "src/NimBLEUtils.cpp" - "src/NimBLEUUID.cpp" + "src/NimBLEUUID.cpp") + +if(CONFIG_BT_NIMBLE_MESH) + list(APPEND srcs "src/NimBLEMeshCreateModel.c" + "src/NimBLEMeshElement.cpp" + "src/NimBLEMeshModel.cpp" + "src/NimBLEMeshNode.cpp") +endif() + +if(CONFIG_NIMBLE_CPP_PERSIST_MESH_SETTINGS) + list(APPEND srcs "src/mesh_config_store/config/config_store.c") +endif() + +idf_component_register( + REQUIRED_IDF_TARGETS + "esp32" + "esp32s3" + "esp32c3" + INCLUDE_DIRS + "src" + SRCS "${srcs}" + REQUIRES bt nvs_flash PRIV_REQUIRES ${ESP_NIMBLE_PRIV_REQUIRES} ) + +if(CONFIG_BT_NIMBLE_MESH AND CONFIG_NIMBLE_CPP_PERSIST_MESH_SETTINGS) + idf_build_set_property(COMPILE_OPTIONS "-DMYNEWT_VAL_BLE_MESH_SETTINGS=1" APPEND) + idf_build_set_property(COMPILE_OPTIONS "-I${COMPONENT_DIR}/src/mesh_config_store" APPEND) +endif() diff --git a/Kconfig b/Kconfig index c13fde1..f30c801 100644 --- a/Kconfig +++ b/Kconfig @@ -33,7 +33,7 @@ config NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT Enabling this option will display return code values as text messages in the debug log. This will use approximately 8kB of flash memory. - + config NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT bool "Show NimBLE gap events as text in debug log." default "n" @@ -47,7 +47,7 @@ config NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT default "n" help Enabling this option will display advertisment types recieved - while scanning as text messages in the debug log. + while scanning as text messages in the debug log. This will use approximately 250 bytes of flash memory. config NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED @@ -68,5 +68,13 @@ config NIMBLE_CPP_ATT_VALUE_INIT_LENGTH when the constructor is called. This is also the size used when a remote characteristic or descriptor is constructed before a value is read/notifed. Increasing this will reduce reallocations but increase memory footprint. - + +config NIMBLE_CPP_PERSIST_MESH_SETTINGS + bool "Enable persistent storage of mesh config settings." + default "n" + depends on BT_NIMBLE_MESH + help + Enabling this option will store the provisioning and app key settings + in non-volatile storage when using NimBLE Mesh. + endmenu diff --git a/src/NimBLEMeshCreateModel.c b/src/NimBLEMeshCreateModel.c index ee8c2b3..b0b5176 100644 --- a/src/NimBLEMeshCreateModel.c +++ b/src/NimBLEMeshCreateModel.c @@ -6,6 +6,9 @@ * */ +#include "nimconfig.h" +#if CONFIG_BT_NIMBLE_MESH + #include "NimBLEMeshCreateModel.h" static struct bt_mesh_model_cb mod_cb = { @@ -28,3 +31,5 @@ struct bt_mesh_model createGenModel(int16_t _id, struct bt_mesh_model_op* op, struct bt_mesh_model mod = BT_MESH_MODEL_CB(_id, op, pub, udata, &mod_cb); return mod; } + +#endif // CONFIG_BT_NIMBLE_MESH diff --git a/src/NimBLEMeshElement.cpp b/src/NimBLEMeshElement.cpp index d96cc51..1f80277 100644 --- a/src/NimBLEMeshElement.cpp +++ b/src/NimBLEMeshElement.cpp @@ -6,6 +6,9 @@ * */ +#include "nimconfig.h" +#if CONFIG_BT_NIMBLE_MESH + #include "NimBLEMeshElement.h" #include "NimBLELog.h" #include "NimBLEMeshCreateModel.h" @@ -139,4 +142,4 @@ bt_mesh_elem* NimBLEMeshElement::start() { return m_pElem; } - +#endif // CONFIG_BT_NIMBLE_MESH diff --git a/src/NimBLEMeshModel.cpp b/src/NimBLEMeshModel.cpp index 6d7c390..3e3b033 100644 --- a/src/NimBLEMeshModel.cpp +++ b/src/NimBLEMeshModel.cpp @@ -6,6 +6,9 @@ * */ +#include "nimconfig.h" +#if CONFIG_BT_NIMBLE_MESH + #include "NimBLEMeshModel.h" #include "NimBLEUtils.h" #include "NimBLELog.h" @@ -693,3 +696,5 @@ void NimBLEHealthSrvCallbacks::attentionOff(bt_mesh_model *model) NimBLEMeshModel* pModel = NimBLEDevice::getMeshNode()->getHealthModel(model); pModel->m_callbacks->attentionOff(pModel); } + +#endif // CONFIG_BT_NIMBLE_MESH diff --git a/src/NimBLEMeshNode.cpp b/src/NimBLEMeshNode.cpp index 8313f25..0ca6d22 100644 --- a/src/NimBLEMeshNode.cpp +++ b/src/NimBLEMeshNode.cpp @@ -7,7 +7,7 @@ */ #include "nimconfig.h" -#if defined(CONFIG_BT_ENABLED) +#if CONFIG_BT_NIMBLE_MESH #include "NimBLEMeshNode.h" #include "NimBLELog.h" @@ -206,5 +206,4 @@ bool NimBLEMeshNode::start() { return true; } - -#endif // CONFIG_BT_ENABLED \ No newline at end of file +#endif // CONFIG_BT_NIMBLE_MESH diff --git a/src/mesh_config_store/base64/base64.h b/src/mesh_config_store/base64/base64.h new file mode 100644 index 0000000..466c188 --- /dev/null +++ b/src/mesh_config_store/base64/base64.h @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +#ifndef __UTIL_BASE64_H +#define __UTIL_BASE64_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct base64_decoder { + /*** public */ + const char *src; + void *dst; + int src_len; /* <=0 if src ends with '\0' */ + int dst_len; /* <=0 if dst unbounded */ + + /*** private */ + char buf[4]; + int buf_len; +}; + +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int +pos(char c) +{ + const char *p; + for (p = base64_chars; *p; p++) + if (*p == c) + return p - base64_chars; + return -1; +} + +int +base64_encode(const void *data, int size, char *s, uint8_t should_pad) +{ + char *p; + int i; + int c; + const unsigned char *q; + char *last; + int diff; + + p = s; + + q = (const unsigned char *) data; + last = NULL; + i = 0; + while (i < size) { + c = q[i++]; + c *= 256; + if (i < size) + c += q[i]; + i++; + c *= 256; + if (i < size) + c += q[i]; + i++; + p[0] = base64_chars[(c & 0x00fc0000) >> 18]; + p[1] = base64_chars[(c & 0x0003f000) >> 12]; + p[2] = base64_chars[(c & 0x00000fc0) >> 6]; + p[3] = base64_chars[(c & 0x0000003f) >> 0]; + last = p; + p += 4; + } + + if (last) { + diff = i - size; + if (diff > 0) { + if (should_pad) { + memset(last + (4 - diff), '=', diff); + } else { + p = last + (4 - diff); + } + } + } + + *p = 0; + + return (p - s); +} + +int +base64_pad(char *buf, int len) +{ + int remainder; + + remainder = len % 4; + if (remainder == 0) { + return (0); + } + + memset(buf, '=', 4 - remainder); + + return (4 - remainder); +} + +#define DECODE_ERROR -1 + +static unsigned int +token_decode(const char *token, int len) +{ + int i; + unsigned int val = 0; + int marker = 0; + + if (len < 4) { + return DECODE_ERROR; + } + + for (i = 0; i < 4; i++) { + val *= 64; + if (token[i] == '=') { + marker++; + } else if (marker > 0) { + return DECODE_ERROR; + } else { + val += pos(token[i]); + } + } + + if (marker > 2) { + return DECODE_ERROR; + } + + return (marker << 24) | val; +} + +int +base64_decoder_go(struct base64_decoder *dec) +{ + unsigned int marker; + unsigned int val; + uint8_t *dst; + char sval; + int read_len; + int src_len; + int src_rem; + int src_off; + int dst_len; + int dst_off; + int i; + + dst = dec->dst; + dst_off = 0; + src_off = 0; + + /* A length <= 0 means "unbounded". */ + if (dec->src_len <= 0) { + src_len = INT_MAX; + } else { + src_len = dec->src_len; + } + if (dec->dst_len <= 0) { + dst_len = INT_MAX; + } else { + dst_len = dec->dst_len; + } + + while (1) { + src_rem = src_len - src_off; + if (src_rem == 0) { + /* End of source input. */ + break; + } + + if (dec->src[src_off] == '\0') { + /* End of source string. */ + break; + } + + /* Account for possibility of partial token from previous call. */ + read_len = 4 - dec->buf_len; + + /* Detect invalid input. */ + for (i = 0; i < read_len; i++) { + sval = dec->src[src_off + i]; + if (sval == '\0') { + /* Incomplete input. */ + return -1; + } + if (sval != '=' && strchr(base64_chars, sval) == NULL) { + /* Invalid base64 character. */ + return -1; + } + } + + if (src_rem < read_len) { + /* Input contains a partial token. Stash it for use during the + * next call. + */ + memcpy(&dec->buf[dec->buf_len], &dec->src[src_off], src_rem); + dec->buf_len += src_rem; + break; + } + + /* Copy full token into buf and decode it. */ + memcpy(&dec->buf[dec->buf_len], &dec->src[src_off], read_len); + val = token_decode(dec->buf, read_len); + if (val == DECODE_ERROR) { + return -1; + } + src_off += read_len; + dec->buf_len = 0; + + marker = (val >> 24) & 0xff; + + if (dst_off >= dst_len) { + break; + } + dst[dst_off] = (val >> 16) & 0xff; + dst_off++; + + if (marker < 2) { + if (dst_off >= dst_len) { + break; + } + dst[dst_off] = (val >> 8) & 0xff; + dst_off++; + } + + if (marker < 1) { + if (dst_off >= dst_len) { + break; + } + dst[dst_off] = val & 0xff; + dst_off++; + } + } + + return dst_off; +} + +int +base64_decode(const char *str, void *data) +{ + struct base64_decoder dec = { + .src = str, + .dst = data, + }; + + return base64_decoder_go(&dec); +} + +int +base64_decode_maxlen(const char *str, void *data, int len) +{ + struct base64_decoder dec = { + .src = str, + .dst = data, + .dst_len = len, + }; + + return base64_decoder_go(&dec); +} + +int +base64_decode_len(const char *str) +{ + int len; + + len = strlen(str); + while (len && str[len - 1] == '=') { + len--; + } + return len * 3 / 4; +} + +#define BASE64_ENCODE_SIZE(__size) (((((__size) - 1) / 3) * 4) + 4) + +#ifdef __cplusplus +} +#endif + +#endif /* __UTIL_BASE64_H__ */ diff --git a/src/mesh_config_store/config/config.h b/src/mesh_config_store/config/config.h new file mode 100644 index 0000000..3002494 --- /dev/null +++ b/src/mesh_config_store/config/config.h @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +#ifndef __SYS_CONFIG_H_ +#define __SYS_CONFIG_H_ + +#include "../../nimconfig.h" +#if defined(CONFIG_NIMBLE_CPP_IDF) +# include +#else +# include "nimble/porting/nimble/include/os/queue.h" +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CONF_MAX_DIR_DEPTH 8 /* max depth of config tree */ +#define CONF_MAX_NAME_LEN (8 * CONF_MAX_DIR_DEPTH) + +/** + * Type of configuration value. + */ +typedef enum conf_type { + CONF_NONE = 0, + CONF_DIR, + /** 8-bit signed integer */ + CONF_INT8, + /** 16-bit signed integer */ + CONF_INT16, + /** 32-bit signed integer */ + CONF_INT32, + /** 64-bit signed integer */ + CONF_INT64, + /** String */ + CONF_STRING, + /** Bytes */ + CONF_BYTES, + /** Floating point */ + CONF_FLOAT, + /** Double precision */ + CONF_DOUBLE, + /** Boolean */ + CONF_BOOL, + /** 8-bit unsigned integer */ + CONF_UINT8, + /** 16-bit unsigned integer */ + CONF_UINT16, + /** 32-bit unsigned integer */ + CONF_UINT32, + /** 64-bit unsigned integer */ + CONF_UINT64, +} __attribute__((__packed__)) conf_type_t; + +/** + * Parameter to commit handler describing where data is going to. + */ +enum conf_export_tgt { + /** Value is to be persisted */ + CONF_EXPORT_PERSIST, + /** Value is to be display */ + CONF_EXPORT_SHOW +}; + +typedef enum conf_export_tgt conf_export_tgt_t; + +/** + * Handler for getting configuration items, this handler is called + * per-configuration section. Configuration sections are delimited + * by '/', for example: + * + * - section/name/value + * + * Would be passed as: + * + * - argc = 3 + * - argv[0] = section + * - argv[1] = name + * - argv[2] = value + * + * The handler returns the value into val, null terminated, up to + * val_len_max. + * + * @param argc The number of sections in the configuration variable + * @param argv The array of configuration sections + * @param val A pointer to the buffer to return the configuration + * value into. + * @param val_len_max The maximum length of the val buffer to copy into. + * + * @return A pointer to val or NULL if error. + */ +typedef char *(*conf_get_handler_t)(int argc, char **argv, char *val, int val_len_max); +typedef char *(*conf_get_handler_ext_t)(int argc, char **argv, char *val, int val_len_max, void *arg); + +/** + * Set the configuration variable pointed to by argc and argv. See + * description of ch_get_handler_t for format of these variables. This sets the + * configuration variable to the shadow value, but does not apply the configuration + * change. In order to apply the change, call the ch_commit() handler. + * + * @param argc The number of sections in the configuration variable. + * @param argv The array of configuration sections + * @param val The value to configure that variable to + * + * @return 0 on success, non-zero error code on failure. + */ +typedef int (*conf_set_handler_t)(int argc, char **argv, char *val); +typedef int (*conf_set_handler_ext_t)(int argc, char **argv, char *val, void *arg); + +/** + * Commit shadow configuration state to the active configuration. + * + * @return 0 on success, non-zero error code on failure. + */ +typedef int (*conf_commit_handler_t)(void); +typedef int (*conf_commit_handler_ext_t)(void *arg); + +/** + * Called per-configuration variable being exported. + * + * @param name The name of the variable to export + * @param val The value of the variable to export + */ +typedef void (*conf_export_func_t)(char *name, char *val); + +/** + * Export all of the configuration variables, calling the export_func + * per variable being exported. + * + * @param export_func The export function to call. + * @param tgt The target of the export, either for persistence or display. + * + * @return 0 on success, non-zero error code on failure. + */ +typedef int (*conf_export_handler_t)(conf_export_func_t export_func, + conf_export_tgt_t tgt); +typedef int (*conf_export_handler_ext_t)(conf_export_func_t export_func, + conf_export_tgt_t tgt, void *arg); + +/** + * Configuration handler, used to register a config item/subtree. + */ +struct conf_handler { + SLIST_ENTRY(conf_handler) ch_list; + /** + * The name of the conifguration item/subtree + */ + char *ch_name; + + /** + * Whether to use the extended callbacks. + * false: standard + * true: extended + */ + bool ch_ext; + + /** Get configuration value */ + union { + conf_get_handler_t ch_get; + conf_get_handler_ext_t ch_get_ext; + }; + + /** Set configuration value */ + union { + conf_set_handler_t ch_set; + conf_set_handler_ext_t ch_set_ext; + }; + + /** Commit configuration value */ + union { + conf_commit_handler_t ch_commit; + conf_commit_handler_ext_t ch_commit_ext; + }; + + /** Export configuration value */ + union { + conf_export_handler_t ch_export; + conf_export_handler_ext_t ch_export_ext; + }; + + /** Custom argument that gets passed to the extended callbacks */ + void *ch_arg; +}; + +/** + * Register a handler for configurations items. + * + * @param cf Structure containing registration info. + * + * @return 0 on success, non-zero on failure. + */ +int conf_register(struct conf_handler *cf); + +/** + * Load configuration from registered persistence sources. Handlers for + * configuration subtrees registered earlier will be called for encountered + * values. + * + * @return 0 on success, non-zero on failure. + */ +int conf_load(void); + +/** + * Write a single configuration value to persisted storage (if it has + * changed value). + * + * @param name Name/key of the configuration item. + * @param var Value of the configuration item. + * + * @return 0 on success, non-zero on failure. + */ +int conf_save_one(const char *name, char *var); + +#ifdef __cplusplus +} +#endif + +#define SYSINIT_PANIC_ASSERT_MSG(rc, msg) assert(rc) + +#endif /* __SYS_CONFIG_H_ */ \ No newline at end of file diff --git a/src/mesh_config_store/config/config_store.c b/src/mesh_config_store/config/config_store.c new file mode 100644 index 0000000..b78725e --- /dev/null +++ b/src/mesh_config_store/config/config_store.c @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#include "nimconfig.h" +#ifdef ESP_PLATFORM +#if CONFIG_BT_NIMBLE_MESH && CONFIG_NIMBLE_CPP_PERSIST_MESH_SETTINGS + +#include "config.h" +#include "nvs.h" + +#include + +static struct conf_handler* config_handler; + +int conf_parse_name(char *name, int *name_argc, char *name_argv[]) +{ + char *tok; + char *tok_ptr; + const char *sep = "/"; + int i; + + tok = strtok_r(name, sep, &tok_ptr); + + i = 0; + while (tok) { + name_argv[i++] = tok; + tok = strtok_r(NULL, sep, &tok_ptr); + } + *name_argc = i; + + return 0; +} + +int conf_load(void) +{ + esp_err_t err; + nvs_handle handle; + + err = nvs_open(config_handler->ch_name, NVS_READONLY, &handle); + if (err != ESP_OK) return err; + + nvs_iterator_t it = nvs_entry_find("nvs", config_handler->ch_name, NVS_TYPE_ANY); + + while (it != NULL) { + nvs_entry_info_t info; + nvs_entry_info(it, &info); + it = nvs_entry_next(it); + + size_t required_size = 0; + err = nvs_get_str(handle, info.key, NULL, &required_size); + if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err; + + char* val = malloc(required_size); + if (required_size > 0) { + err = nvs_get_str(handle, info.key, val, &required_size); + if (err != ESP_OK) { + free(val); + return err; + } + } + + int name_argc; + char *name_argv[8]; + conf_parse_name(info.key, &name_argc, name_argv); + + config_handler->ch_set(name_argc, &name_argv[0], val); + free(val); + } + + nvs_close(handle); + config_handler->ch_commit(); + return ESP_OK; +} + +int conf_save_one(const char *name, char *var) +{ + esp_err_t err; + nvs_handle_t handle; + int name_argc; + char *name_argv[CONF_MAX_DIR_DEPTH]; + char n[CONF_MAX_NAME_LEN]; + + strcpy(n, name); + conf_parse_name(n, &name_argc, name_argv); + + err = nvs_open(name_argv[0], NVS_READWRITE, &handle); + if (err != ESP_OK) return err; + + const char* key = name_argv[1]; + if (name_argc > 2) { + key = name; + while (*key != '/') { + key++; + } + key++; + } + + if (var) { + err = nvs_set_str(handle, key, var); + if (err != ESP_OK) return err; + } else { + err = nvs_erase_key(handle, key); + if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err; + } + + err = nvs_commit(handle); + if (err != ESP_OK) return err; + + nvs_close(handle); + return ESP_OK; +} + +int conf_register(struct conf_handler *cf) +{ + config_handler = cf; + return 0; +} + +#endif // CONFIG_BT_NIMBLE_MESH && MYNEWT_VAL_BLE_MESH_SETTINGS +#endif // ESP_PLATFORM