19 Commits

Author SHA1 Message Date
h2zero
9c1cf6ed2e WIP 2022-05-11 07:06:44 -06:00
h2zero
1dedff342b Use macros for adding models. 2022-04-27 21:16:18 -06:00
h2zero
d47281f40f Use NimBLEAttValue for mesh values. 2022-04-27 20:50:19 -06:00
h2zero
123825d483 Fix health model fault reporting. 2022-04-13 20:42:34 -06:00
h2zero
5b206b97a3 Update include directives. 2022-04-13 20:42:28 -06:00
h2zero
08a3e676f8 Add files to cmake. 2022-04-13 20:41:42 -06:00
h2zero
356459d352 Merge branch 'master' into mesh 2022-04-13 20:40:18 -06:00
h2zero
06e2d188b1 Update to work with NimBLE core changes. 2021-09-06 09:15:44 -06:00
h2zero
afb76b8cb6 Implement health server callbacks. 2021-09-06 09:14:46 -06:00
h2zero
56c68d7eea Add support for app created health server models. 2021-09-06 09:14:46 -06:00
h2zero
bceda5a8cf Update example 2021-09-06 09:14:46 -06:00
h2zero
799a7a84db Use std::vector instead of std::string for values. 2021-09-06 09:14:45 -06:00
h2zero
6de87945e7 Implement set/get values and add templates. 2021-09-06 09:14:45 -06:00
h2zero
dc7d9d5b73 Use std::string for internal values 2021-09-06 09:14:45 -06:00
h2zero
06037f8bf6 Return NimBLEMeshModel pointer from NimBLEMeshElement::createModel 2021-09-06 09:14:45 -06:00
h2zero
4d78c3013b Add publish methods to server models. 2021-09-06 09:14:45 -06:00
h2zero
bc2e11637e Add transition and delay timers to models.
* Remove level server setDelta() callback as it is unnecessary.

* Add target value and remaining time to status messages.
2021-09-06 09:14:45 -06:00
h2zero
8efa7d2acc Add example. 2021-09-06 09:14:45 -06:00
h2zero
bc2cecd2db Basic mesh implementation with on/off and level models. 2021-09-06 09:14:45 -06:00
22 changed files with 2259 additions and 10 deletions

View File

@@ -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"
@@ -49,7 +45,25 @@ 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/mesh_config_store/config/config_store.c"
"src/NimBLEMeshCreateModel.c"
"src/NimBLEMeshElement.cpp"
"src/NimBLEMeshModel.cpp"
"src/NimBLEMeshNode.cpp")
endif()
idf_component_register(
REQUIRED_IDF_TARGETS
"esp32"
"esp32s3"
"esp32c3"
INCLUDE_DIRS
"src"
SRCS "${srcs}"
REQUIRES
bt
nvs_flash
@@ -57,3 +71,7 @@ idf_component_register(
${ESP_NIMBLE_PRIV_REQUIRES}
)
if(CONFIG_BT_NIMBLE_MESH)
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()

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(SUPPORTED_TARGETS esp32)
project(basic_on_off_level_server)

View File

@@ -0,0 +1,3 @@
PROJECT_NAME := basic_on_off_level_server
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,64 @@
#include "NimBLEDevice.h"
#include "driver/gpio.h"
extern "C" {void app_main(void);}
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
// LED pins
#define LEDR GPIO_NUM_16
#define LEDG GPIO_NUM_17
#define LEDB GPIO_NUM_18
#define OUTPUT_PIN (1ULL<<LEDG)
static uint8_t onOffVal = 0;
static int16_t levelVal = 0;
class onOffSrvModelCallbacks : public NimBLEMeshModelCallbacks {
void setOnOff(NimBLEMeshModel *pModel, uint8_t val) {
printf("on/off set val %d, transition time: %dms\n", val, pModel->getTransTime());
onOffVal = val;
gpio_set_level(LEDG, !onOffVal);
}
uint8_t getOnOff(NimBLEMeshModel *pModel) {
printf("on/off get val %d\n", onOffVal);
return onOffVal;
}
};
class levelSrvModelCallbacks : public NimBLEMeshModelCallbacks {
void setLevel(NimBLEMeshModel *pModel, int16_t val) {
printf("Level set val %d, transition time: %dms\n", val, pModel->getTransTime());
levelVal = val;
}
int16_t getLevel(NimBLEMeshModel *pModel) {
printf("Level get val %d\n", levelVal);
return levelVal;
}
};
void app_main(void) {
gpio_config_t io_conf;
io_conf.intr_type = (gpio_int_type_t)GPIO_PIN_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = OUTPUT_PIN;
io_conf.pull_down_en = (gpio_pulldown_t)0;
io_conf.pull_up_en = (gpio_pullup_t)0;
gpio_config(&io_conf);
gpio_set_level(LEDG, 1);
NimBLEDevice::init("");
NimBLEMeshNode *pMesh = NimBLEDevice::createMeshNode(NimBLEUUID(SERVICE_UUID),0);
NimBLEMeshElement* pElem = pMesh->getElement();
NimBLEMeshModel* pModel = pElem->createModel(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, new onOffSrvModelCallbacks());
pModel->setValue(onOffVal);
//pElem = pMesh->createElement();
pModel = pElem->createModel(BT_MESH_MODEL_ID_GEN_LEVEL_SRV, new levelSrvModelCallbacks());
pModel->setValue(levelVal);
pMesh->start();
printf("Mesh Started");
}

View File

@@ -76,6 +76,8 @@ NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr;
# endif
#endif
NimBLEMeshNode* NimBLEDevice::m_pMeshNode = nullptr;
gap_event_handler NimBLEDevice::m_customGapHandler = nullptr;
ble_gap_event_listener NimBLEDevice::m_listener;
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
@@ -90,6 +92,30 @@ uint16_t NimBLEDevice::m_scanDuplicateSize = CONFIG_BTDM_SCAN
uint8_t NimBLEDevice::m_scanFilterMode = CONFIG_BTDM_SCAN_DUPL_TYPE;
#endif
/**
* @brief Create a new mesh node.
* @param [in] uuid The uuid to advertise before being provisioned.
* @param [in] type A bitmask of the node type to create.
* @return A point to new instance of the mesh node.
*/
NimBLEMeshNode* NimBLEDevice::createMeshNode(NimBLEUUID uuid, uint8_t type) {
if(m_pMeshNode == nullptr) {
m_pMeshNode = new NimBLEMeshNode(uuid, type);
}
return m_pMeshNode;
}
/**
* @brief Get the mesh node instance.
* @return a pointer to the mesh node instance or nullptr if no node exists.
*/
NimBLEMeshNode* NimBLEDevice::getMeshNode() {
return m_pMeshNode;
}
/**
* @brief Create a new instance of a server.
* @return A new instance of the server.

View File

@@ -38,6 +38,8 @@
#include "NimBLEServer.h"
#endif
#include "NimBLEMeshNode.h"
#include "NimBLEUtils.h"
#include "NimBLESecurity.h"
#include "NimBLEAddress.h"
@@ -115,6 +117,9 @@ public:
static NimBLEServer* getServer();
#endif
static NimBLEMeshNode* createMeshNode(NimBLEUUID uuid, uint8_t type);
static NimBLEMeshNode* getMeshNode();
#ifdef ESP_PLATFORM
static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT);
static int getPower(esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT);
@@ -232,6 +237,7 @@ private:
static uint8_t m_scanFilterMode;
#endif
static std::vector<NimBLEAddress> m_whiteList;
static NimBLEMeshNode* m_pMeshNode;
};

View File

@@ -0,0 +1,30 @@
/*
* NimBLEMeshCreateModel.cpp
*
* Created: on April 27 2022
* Author H2zero
*
*/
#include "NimBLEMeshCreateModel.h"
static struct bt_mesh_model_cb mod_cb = {
//.init = modelInitCallback
};
struct bt_mesh_model createConfigSrvModel(struct bt_mesh_cfg_srv* cfg) {
struct bt_mesh_model cmod = BT_MESH_MODEL_CFG_SRV(cfg);
return cmod;
}
struct bt_mesh_model createHealthModel(struct bt_mesh_health_srv* hsrv,
struct bt_mesh_model_pub* hpub) {
struct bt_mesh_model hmod = BT_MESH_MODEL_HEALTH_SRV(hsrv, hpub);
return hmod;
}
struct bt_mesh_model createGenModel(int16_t _id, struct bt_mesh_model_op* op,
struct bt_mesh_model_pub* pub, void* udata) {
struct bt_mesh_model mod = BT_MESH_MODEL_CB(_id, op, pub, udata, &mod_cb);
return mod;
}

View File

@@ -0,0 +1,35 @@
/*
* NimBLEMeshCreateModel.h
*
* Created: on April 27 2022
* Author H2zero
*
*/
#ifndef __NIMBLE_MESH_CREATE_MODEL_H
#define __NIMBLE_MESH_CREATE_MODEL_H
#include "nimconfig.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "mesh/mesh.h"
# include "mesh/cfg_srv.h"
#else
# include "nimble/nimble/host/mesh/include/mesh/mesh.h"
# include "nimble/nimble/host/mesh/include/mesh/cfg_srv.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
//int modelInitCallback(struct bt_mesh_model *model);
struct bt_mesh_model createConfigSrvModel(struct bt_mesh_cfg_srv* cfg);
struct bt_mesh_model createHealthModel(struct bt_mesh_health_srv* hsrv,
struct bt_mesh_model_pub* hpub);
struct bt_mesh_model createGenModel(int16_t _id, struct bt_mesh_model_op* op,
struct bt_mesh_model_pub* pub, void* udata);
#ifdef __cplusplus
}
#endif
#endif

142
src/NimBLEMeshElement.cpp Normal file
View File

@@ -0,0 +1,142 @@
/*
* NimBLEMeshElement.cpp
*
* Created: on Aug 23 2020
* Author H2zero
*
*/
#include "NimBLEMeshElement.h"
#include "NimBLELog.h"
#include "NimBLEMeshCreateModel.h"
static const char* LOG_TAG = "NimBLEMeshElement";
NimBLEMeshElement::NimBLEMeshElement() {
m_pElem = nullptr;
m_pHealthModel = nullptr;
}
NimBLEMeshElement::~NimBLEMeshElement() {
if(m_pElem != nullptr) {
delete m_pElem;
}
if(m_pHealthModel != nullptr) {
delete m_pHealthModel;
}
for(auto &it : m_modelsVec) {
if(it.id != BT_MESH_MODEL_ID_HEALTH_SRV) {
delete (NimBLEMeshModel*)it.user_data;
}
}
m_modelsVec.clear();
}
/**
* @brief Creates a model and adds it the the elements model vector.
* @param [in] type The type of model to create.
* @param [in] pCallbacks a pointer to a callback instance for this model.
*/
NimBLEMeshModel* NimBLEMeshElement::createModel(uint16_t type, NimBLEMeshModelCallbacks *pCallbacks) {
if(getModel(type) != nullptr) {
NIMBLE_LOGE(LOG_TAG, "Error: element already has a type %04x model", type);
return nullptr;
}
NIMBLE_LOGD(LOG_TAG, "Creating model type: %04x", type);
NimBLEMeshModel* pModel = nullptr;
switch(type)
{
case BT_MESH_MODEL_ID_GEN_ONOFF_SRV:
pModel = new NimBLEGenOnOffSrvModel(pCallbacks);
break;
case BT_MESH_MODEL_ID_GEN_LEVEL_SRV:
pModel = new NimBLEGenLevelSrvModel(pCallbacks);
break;
case BT_MESH_MODEL_ID_HEALTH_SRV:
m_pHealthModel = new NimBLEHealthSrvModel(pCallbacks);
pModel = m_pHealthModel;
m_modelsVec.push_back(createHealthModel(&m_pHealthModel->m_healthSrv, &pModel->m_opPub));
return pModel;
default:
NIMBLE_LOGE(LOG_TAG, "Error: model type %04x not supported", type);
return nullptr;
}
m_modelsVec.push_back(createGenModel(type, pModel->m_opList, &pModel->m_opPub, pModel));
return pModel;
}
/**
* @brief Adds a model created outside of element context to the elements model vector.
* @param [in] model A pointer to the model instance to add.
*/
void NimBLEMeshElement::addModel(const bt_mesh_model & model) {
m_modelsVec.push_back(model);
}
/**
* @brief Get a pointer to the model in the element with the type specified.
* @param [in] The model type requested.
* @returns A pointer to the model or nullptr if not found.
*/
NimBLEMeshModel* NimBLEMeshElement::getModel(uint16_t type) {
if(type == BT_MESH_MODEL_ID_HEALTH_SRV) {
return m_pHealthModel;
}
for(auto &it : m_modelsVec) {
if(it.id == type) {
return (NimBLEMeshModel*)it.user_data;
}
}
return nullptr;
}
/**
* @brief Get a pointer to a model with matching type and ID.
* @param [in] eidx The element ID to compare.
* @param [in] midx The model ID to compare.
* @param [in] The model type requested.
* @returns A pointer to the model or nullptr if not found.
*/
NimBLEMeshModel* NimBLEMeshElement::getModelByIdx(uint8_t eidx, uint8_t midx, uint16_t type) {
for(auto &it : m_modelsVec) {
if(it.elem_idx == eidx && it.mod_idx == midx) {
if(type == BT_MESH_MODEL_ID_HEALTH_SRV) {
return m_pHealthModel;
} else {
return (NimBLEMeshModel*)it.user_data;
}
}
}
return nullptr;
}
/**
* @brief Creates a bt_mesh_elem for registering with the nimble stack.
* @returns A pointer to the bt_mesh_elem created.
* @details Must not be called until all models have been added.
*/
bt_mesh_elem* NimBLEMeshElement::start() {
m_pElem = new bt_mesh_elem{0, 0, uint8_t(m_modelsVec.size()), 0, &m_modelsVec[0], NULL};
return m_pElem;
}

46
src/NimBLEMeshElement.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* NimBLEMeshElement.h
*
* Created: on Aug 23 2020
* Author H2zero
*
*/
#ifndef MAIN_NIMBLE_MESH_ELEMENT_H_
#define MAIN_NIMBLE_MESH_ELEMENT_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimconfig.h"
#include "NimBLEMeshNode.h"
#include "NimBLEMeshModel.h"
#include <vector>
class NimBLEMeshModelCallbacks;
class NimBLEMeshModel;
class NimBLEHealthSrvModel;
class NimBLEMeshElement {
public:
NimBLEMeshModel* createModel(uint16_t type, NimBLEMeshModelCallbacks* pCallbacks=nullptr);
NimBLEMeshModel* getModel(uint16_t type);
NimBLEMeshModel* getModelByIdx(uint8_t eidx, uint8_t midx, uint16_t type);
private:
friend class NimBLEMeshNode;
NimBLEMeshElement();
~NimBLEMeshElement();
void addModel(const bt_mesh_model & model);
bt_mesh_elem* start();
bt_mesh_elem *m_pElem;
NimBLEHealthSrvModel* m_pHealthModel;
std::vector<bt_mesh_model> m_modelsVec;
};
#endif // CONFIG_BT_ENABLED
#endif // MAIN_NIMBLE_MESH_ELEMENT_H_

693
src/NimBLEMeshModel.cpp Normal file
View File

@@ -0,0 +1,693 @@
/*
* NimBLEMeshModel.cpp
*
* Created: on Aug 25 2020
* Author H2zero
*
*/
#include "NimBLEMeshModel.h"
#include "NimBLEUtils.h"
#include "NimBLELog.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_port.h"
#else
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
#endif
#include "NimBLEDevice.h"
#define CID_VENDOR 0x05C3
#define STANDARD_TEST_ID 0x00
static const char* LOG_TAG = "NimBLEMeshModel";
static NimBLEMeshModelCallbacks defaultCallbacks;
static const struct bt_mesh_health_srv_cb health_srv_cb = {
NimBLEHealthSrvCallbacks::faultGetCurrent,
NimBLEHealthSrvCallbacks::faultGetRegistered,
NimBLEHealthSrvCallbacks::faultClear,
NimBLEHealthSrvCallbacks::faultTest,
NimBLEHealthSrvCallbacks::attentionOn,
NimBLEHealthSrvCallbacks::attentionOff
};
/**
* @brief base model constructor
* @param [in] pCallbacks, a pointer to a callback instance for model operations
*/
NimBLEMeshModel::NimBLEMeshModel(NimBLEMeshModelCallbacks *pCallbacks,
uint16_t initDataSize, uint16_t maxDataSize)
: m_value(initDataSize, maxDataSize),
m_targetValue(initDataSize, maxDataSize)
{
if(pCallbacks == nullptr) {
m_callbacks = &defaultCallbacks;
} else {
m_callbacks = pCallbacks;
}
m_opList = nullptr;
m_lastTid = 0;
m_lastSrcAddr = 0;
m_lastDstAddr = 0;
m_lastMsgTime = 0;
m_transTime = 0;
m_delayTime = 0;
m_transStep = 0;
memset(&m_opPub, 0, sizeof(m_opPub));
}
/**
* @brief destructor
*/
NimBLEMeshModel::~NimBLEMeshModel() {
if(m_opList != nullptr) {
delete[] m_opList;
}
}
int NimBLEMeshModel::extractTransTimeDelay(os_mbuf *buf)
{
switch(buf->om_len) {
case 0x00:
m_transTime = 0;
m_delayTime = 0;
return 0;
case 0x02:
m_transTime = buf->om_data[0];
if((m_transTime & 0x3F) == 0x3F) {
// unknown transition time
m_transTime = 0;
m_delayTime = 0;
return BLE_HS_EINVAL;
}
m_delayTime = buf->om_data[1];
return 0;
default:
return BLE_HS_EMSGSIZE;
}
}
bool NimBLEMeshModel::checkRetransmit(uint8_t tid, bt_mesh_msg_ctx *ctx) {
time_t now = time(nullptr);
if(m_lastTid == tid &&
m_lastSrcAddr == ctx->addr &&
m_lastDstAddr == ctx->recv_dst &&
(now - m_lastMsgTime <= 6)) {
NIMBLE_LOGD(LOG_TAG, "Ignoring retransmit");
return true;
}
m_lastTid = tid;
m_lastSrcAddr = ctx->addr;
m_lastDstAddr = ctx->recv_dst;
m_lastMsgTime = now;
return false;
}
void NimBLEMeshModel::sendMessage(bt_mesh_model *model, bt_mesh_msg_ctx *ctx, os_mbuf *msg) {
if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
NIMBLE_LOGE(LOG_TAG, "Send status failed");
}
os_mbuf_free_chain(msg);
}
void NimBLEMeshModel::startTdTimer(ble_npl_time_t timerMs) {
ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks(timerMs, &ticks);
ble_npl_callout_reset(&m_tdTimer, ticks);
}
void NimBLEMeshModel::publish() {
ble_npl_callout_reset(&m_pubTimer, 1);
}
uint32_t NimBLEMeshModel::getTransTime() {
return (m_transTime & 0x3F) * NimBLEUtils::meshTransTimeMs(m_transTime);
}
uint16_t NimBLEMeshModel::getDelayTime() {
return m_delayTime * 5;
}
/**
* @brief Generic on/off server model constructor
* @param [in] pCallbacks, a pointer to a callback instance for model operations
*/
NimBLEGenOnOffSrvModel::NimBLEGenOnOffSrvModel(NimBLEMeshModelCallbacks *pCallbacks)
:NimBLEMeshModel(pCallbacks, 1, 1)
{
// Register the opcodes for this model with the required callbacks
m_opList = new bt_mesh_model_op[4]{
{ BT_MESH_MODEL_OP_2(0x82, 0x01), 0, NimBLEGenOnOffSrvModel::getOnOff },
{ BT_MESH_MODEL_OP_2(0x82, 0x02), 2, NimBLEGenOnOffSrvModel::setOnOff },
{ BT_MESH_MODEL_OP_2(0x82, 0x03), 2, NimBLEGenOnOffSrvModel::setOnOffUnack },
BT_MESH_MODEL_OP_END};
ble_npl_callout_init(&m_tdTimer, nimble_port_get_dflt_eventq(),
NimBLEGenOnOffSrvModel::tdTimerCb, this);
ble_npl_callout_init(&m_pubTimer, nimble_port_get_dflt_eventq(),
NimBLEGenOnOffSrvModel::pubTimerCb, this);
m_opPub.msg = NET_BUF_SIMPLE(2 + 3);
}
/**
* @brief Called by the NimBLE stack to get the on/off status of the model
*/
void NimBLEGenOnOffSrvModel::getOnOff(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
if(pModel->m_callbacks != &defaultCallbacks) {
pModel->setValue(pModel->m_callbacks->getOnOff(pModel));
}
pModel->setPubMsg();
if (bt_mesh_model_send(model, ctx, pModel->m_opPub.msg, NULL, NULL)) {
NIMBLE_LOGE(LOG_TAG, "Send status failed");
}
}
/**
* @brief Called by the NimBLE stack to set the status of the model with acknowledgement.
*/
void NimBLEGenOnOffSrvModel::setOnOff(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
// Rather than duplicate code just call the unack function then send the status
NimBLEGenOnOffSrvModel::setOnOffUnack(model,ctx,buf);
NimBLEGenOnOffSrvModel::getOnOff(model,ctx,buf);
}
/**
* @brief Called by the NimBLE stack to set the status of the model without acknowledgement.
*/
void NimBLEGenOnOffSrvModel::setOnOffUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
uint8_t newval = net_buf_simple_pull_u8(buf);
uint8_t tid = net_buf_simple_pull_u8(buf);
if(pModel->checkRetransmit(tid, ctx)) {
return;
}
if(pModel->extractTransTimeDelay(buf) != 0) {
NIMBLE_LOGI(LOG_TAG, "Transition time / delay data error");
return;
}
// stop the transition timer to handle the new input
ble_npl_callout_stop(&pModel->m_tdTimer);
// Mesh spec says transition to "ON state" happens immediately
// after delay, so ignore the transition time.
if(newval == 1) {
pModel->m_transTime = 0;
}
ble_npl_time_t timerMs = 0;
if(newval != pModel->m_value[0]) {
pModel->m_targetValue.setValue(&newval, sizeof(newval));
if(pModel->m_delayTime > 0) {
timerMs = 5 * pModel->m_delayTime;
} else if(pModel->m_transTime & 0x3F) {
timerMs = NimBLEUtils::meshTransTimeMs(pModel->m_transTime);
pModel->m_transTime -= 1;
}
}
if(timerMs > 0) {
pModel->startTdTimer(timerMs);
} else {
pModel->m_value = pModel->m_targetValue;
pModel->m_callbacks->setOnOff(pModel, pModel->m_value[0]);
}
}
void NimBLEGenOnOffSrvModel::tdTimerCb(ble_npl_event *event) {
NimBLEMeshModel *pModel = (NimBLEMeshModel*)event->arg;
if(pModel->m_delayTime > 0) {
pModel->m_delayTime = 0;
}
if((pModel->m_transTime & 0x3F) && pModel->m_targetValue[0] == 0) {
pModel->startTdTimer(NimBLEUtils::meshTransTimeMs(pModel->m_transTime));
pModel->m_transTime -= 1;
pModel->publish();
return;
}
pModel->m_transTime = 0;
pModel->m_value = pModel->m_targetValue;
pModel->m_callbacks->setOnOff(pModel, pModel->m_value[0]);
}
void NimBLEGenOnOffSrvModel::pubTimerCb(ble_npl_event *event) {
NimBLEMeshModel *pModel = (NimBLEMeshModel*)event->arg;
pModel->setPubMsg();
int err = bt_mesh_model_publish(pModel->m_opPub.mod);
if(err != 0) {
NIMBLE_LOGD(LOG_TAG, "Publish rc: %d",err);
}
}
void NimBLEGenOnOffSrvModel::setPubMsg() {
bt_mesh_model_msg_init(m_opPub.msg, BT_MESH_MODEL_OP_2(0x82, 0x04));
net_buf_simple_add_u8(m_opPub.msg, m_value[0]);
if(m_transTime > 0) {
net_buf_simple_add_u8(m_opPub.msg, m_targetValue[0]);
// If we started the transition timer in setOnOff we need to correct the reported remaining time.
net_buf_simple_add_u8(m_opPub.msg, (m_delayTime > 0) ?
m_transTime : m_transTime + 1);
}
}
void NimBLEGenOnOffSrvModel::setValue(uint8_t *val, size_t len) {
if(len != sizeof(uint8_t)) {
NIMBLE_LOGE(LOG_TAG, "NimBLEGenOnOffSrvModel: Incorrect value length");
return;
}
m_value.setValue(val, len);
}
void NimBLEGenOnOffSrvModel::setTargetValue(uint8_t *val, size_t len) {
if(len != sizeof(uint8_t)) {
NIMBLE_LOGE(LOG_TAG, "NimBLEGenOnOffSrvModel: Incorrect target value length");
return;
}
m_targetValue.setValue(val, len);
}
/**
* @brief Generic level server model constructor
* @param [in] pCallbacks, a pointer to a callback instance for model operations
*/
NimBLEGenLevelSrvModel::NimBLEGenLevelSrvModel(NimBLEMeshModelCallbacks *pCallbacks)
:NimBLEMeshModel(pCallbacks, 2, 2)
{
// Register the opcodes for this model with the required callbacks
m_opList = new bt_mesh_model_op[8]{
{ BT_MESH_MODEL_OP_2(0x82, 0x05), 0, NimBLEGenLevelSrvModel::getLevel },
{ BT_MESH_MODEL_OP_2(0x82, 0x06), 3, NimBLEGenLevelSrvModel::setLevel },
{ BT_MESH_MODEL_OP_2(0x82, 0x07), 3, NimBLEGenLevelSrvModel::setLevelUnack },
{ BT_MESH_MODEL_OP_2(0x82, 0x09), 5, NimBLEGenLevelSrvModel::setDelta },
{ BT_MESH_MODEL_OP_2(0x82, 0x0a), 5, NimBLEGenLevelSrvModel::setDeltaUnack },
{ BT_MESH_MODEL_OP_2(0x82, 0x0b), 3, NimBLEGenLevelSrvModel::setMove },
{ BT_MESH_MODEL_OP_2(0x82, 0x0c), 3, NimBLEGenLevelSrvModel::setMoveUnack },
BT_MESH_MODEL_OP_END};
ble_npl_callout_init(&m_tdTimer, nimble_port_get_dflt_eventq(),
NimBLEGenLevelSrvModel::tdTimerCb, this);
ble_npl_callout_init(&m_pubTimer, nimble_port_get_dflt_eventq(),
NimBLEGenLevelSrvModel::pubTimerCb, this);
m_opPub.msg = NET_BUF_SIMPLE(2 + 5);
}
/**
* @brief Called by the NimBLE stack to get the level value of the model.
*/
void NimBLEGenLevelSrvModel::getLevel(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
if(pModel->m_callbacks != &defaultCallbacks) {
pModel->setValue(pModel->m_callbacks->getLevel(pModel));
}
pModel->setPubMsg();
if (bt_mesh_model_send(model, ctx, pModel->m_opPub.msg, NULL, NULL)) {
NIMBLE_LOGE(LOG_TAG, "Send status failed");
}
}
/**
* @brief Called by the NimBLE stack to set the level value of the model.
*/
void NimBLEGenLevelSrvModel::setLevel(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEGenLevelSrvModel::setLevelUnack(model, ctx, buf);
NimBLEGenLevelSrvModel::getLevel(model, ctx, buf);
}
/**
* @brief Called by the NimBLE stack to set the level value of the model without acknowledgement.
*/
void NimBLEGenLevelSrvModel::setLevelUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
int16_t newval = (int16_t) net_buf_simple_pull_le16(buf);
uint8_t tid = net_buf_simple_pull_u8(buf);
if(pModel->checkRetransmit(tid, ctx)) {
return;
}
if(pModel->extractTransTimeDelay(buf) != 0) {
NIMBLE_LOGI(LOG_TAG, "Transition time / delay data error");
return;
}
// stop the transition timer to handle the new input
ble_npl_callout_stop(&pModel->m_tdTimer);
ble_npl_time_t timerMs = 0;
int16_t curval = pModel->m_value.getValue<int16_t>(nullptr, true);
if(newval != curval) {
pModel->m_targetValue.setValue(newval);
if(pModel->m_delayTime > 0) {
timerMs = 5 * pModel->m_delayTime;
}
if(pModel->m_transTime & 0x3F) {
pModel->m_transStep = -1 * ((curval - newval) / (pModel->m_transTime & 0x3F));
if(timerMs == 0) {
timerMs = NimBLEUtils::meshTransTimeMs(pModel->m_transTime);
pModel->m_transTime -= 1;
}
}
}
if(timerMs > 0) {
pModel->startTdTimer(timerMs);
} else {
pModel->m_value = pModel->m_targetValue;
pModel->m_callbacks->setLevel(pModel, newval);
}
}
/**
* @brief Called by the NimBLE stack to set the level value by delta of the model.
*/
void NimBLEGenLevelSrvModel::setDelta(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEGenLevelSrvModel::setDeltaUnack(model, ctx, buf);
NimBLEGenLevelSrvModel::getLevel(model, ctx, buf);
}
/**
* @brief Called by the NimBLE stack to set the level value by delta without acknowledgement.
*/
void NimBLEGenLevelSrvModel::setDeltaUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
int32_t delta = (int32_t) net_buf_simple_pull_le32(buf);
int32_t temp32 = pModel->m_value.getValue<int16_t>(nullptr, true) + delta;
if (temp32 < INT16_MIN) {
temp32 = INT16_MIN;
} else if (temp32 > INT16_MAX) {
temp32 = INT16_MAX;
}
net_buf_simple_push_le16(buf, (uint16_t)temp32);
NimBLEGenLevelSrvModel::setLevelUnack(model, ctx, buf);
}
void NimBLEGenLevelSrvModel::setMove(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
NimBLEGenLevelSrvModel::setMoveUnack(model, ctx, buf);
NimBLEGenLevelSrvModel::getLevel(model, ctx, buf);
}
void NimBLEGenLevelSrvModel::setMoveUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf)
{
int16_t delta = (int16_t) net_buf_simple_pull_le16(buf);
// Check if a transition time is present, if not then ignore this message.
// See: bluetooth mesh specifcation
if(buf->om_len < 3) {
return;
}
put_le32(net_buf_simple_push(buf, 4), (int32_t)delta);
NimBLEGenLevelSrvModel::setDeltaUnack(model, ctx, buf);
}
void NimBLEGenLevelSrvModel::tdTimerCb(ble_npl_event *event) {
NimBLEMeshModel *pModel = (NimBLEMeshModel*)event->arg;
if(pModel->m_delayTime > 0) {
pModel->m_delayTime = 0;
}
if(pModel->m_transTime & 0x3F) {
int16_t newval = pModel->m_value.getValue<int16_t>(nullptr, true) + pModel->m_transStep;
pModel->m_value.setValue(newval);
pModel->m_callbacks->setLevel(pModel, newval);
pModel->startTdTimer(NimBLEUtils::meshTransTimeMs(pModel->m_transTime));
pModel->m_transTime -= 1;
return;
}
pModel->m_transTime = 0;
pModel->m_value = pModel->m_targetValue;
pModel->m_callbacks->setLevel(pModel, pModel->m_value.getValue<int16_t>(nullptr, true));
}
void NimBLEGenLevelSrvModel::pubTimerCb(ble_npl_event *event) {
NimBLEMeshModel *pModel = (NimBLEMeshModel*)event->arg;
pModel->setPubMsg();
int err = bt_mesh_model_publish(pModel->m_opPub.mod);
if(err != 0) {
NIMBLE_LOGD(LOG_TAG, "Publish rc: %d",err);
}
}
void NimBLEGenLevelSrvModel::setPubMsg() {
bt_mesh_model_msg_init(m_opPub.msg, BT_MESH_MODEL_OP_2(0x82, 0x08));
net_buf_simple_add_le16(m_opPub.msg, m_value.getValue<int16_t>(nullptr, true));
if(m_transTime > 0) {
net_buf_simple_add_le16(m_opPub.msg, m_targetValue.getValue<int16_t>(nullptr, true));
// If we started the transition timer in setOnOff we need to correct the reported remaining time.
net_buf_simple_add_u8(m_opPub.msg, (m_delayTime > 0) ?
m_transTime : m_transTime + 1);
}
}
void NimBLEGenLevelSrvModel::setValue(uint8_t *val, size_t len) {
if(len != sizeof(int16_t)) {
NIMBLE_LOGE(LOG_TAG, "NimBLEGenLevelSrvModel: Incorrect value length");
return;
}
m_value.setValue(val, len);
}
void NimBLEGenLevelSrvModel::setTargetValue(uint8_t *val, size_t len) {
if(len != sizeof(int16_t)) {
NIMBLE_LOGE(LOG_TAG, "NimBLEGenLevelSrvModel: Incorrect target value length");
return;
}
m_targetValue.setValue(val, len);
}
/**
* @brief Health server model constructor
* @param [in] pCallbacks, a pointer to a callback instance for model operations
*/
NimBLEHealthSrvModel::NimBLEHealthSrvModel(NimBLEMeshModelCallbacks *pCallbacks)
:NimBLEMeshModel(pCallbacks, 1, 1)
{
memset(&m_healthSrv, 0, sizeof(m_healthSrv));
m_healthSrv.cb = &health_srv_cb;
m_opPub.msg = NET_BUF_SIMPLE(1 + 3);
m_hasFault = false;
m_testId = 0;
}
void NimBLEHealthSrvModel::setFault(uint8_t fault) {
m_faults.push_back(fault);
m_hasFault = true;
}
void NimBLEHealthSrvModel::clearFaults() {
m_faults.clear();
m_hasFault = false;
}
/**
* Default model callbacks
*/
NimBLEMeshModelCallbacks::~NimBLEMeshModelCallbacks() {}
void NimBLEMeshModelCallbacks::setOnOff(NimBLEMeshModel *pModel, uint8_t val) {
NIMBLE_LOGD(LOG_TAG, "Gen On/Off set val: %d", val);
}
uint8_t NimBLEMeshModelCallbacks::getOnOff(NimBLEMeshModel *pModel) {
NIMBLE_LOGD(LOG_TAG, "Gen On/Off get");
return 0;
}
void NimBLEMeshModelCallbacks::setLevel(NimBLEMeshModel *pModel, int16_t val) {
NIMBLE_LOGD(LOG_TAG, "Gen Level set val: %d", val);
}
int16_t NimBLEMeshModelCallbacks::getLevel(NimBLEMeshModel *pModel) {
NIMBLE_LOGD(LOG_TAG, "Gen Level get");
return 0;
}
void NimBLEMeshModelCallbacks::attentionOn(NimBLEMeshModel *pModel) {
NIMBLE_LOGD(LOG_TAG, "Attention On Default");
}
void NimBLEMeshModelCallbacks::attentionOff(NimBLEMeshModel *pModel) {
NIMBLE_LOGD(LOG_TAG, "Attention Off Default");
}
void NimBLEMeshModelCallbacks::faultTest(NimBLEMeshModel *pModel) {
NIMBLE_LOGD(LOG_TAG, "Fault Test");
}
void NimBLEMeshModelCallbacks::faultClear(NimBLEMeshModel *pModel) {
NIMBLE_LOGD(LOG_TAG, "Fault Clear");
}
/**
* @brief Health server callbacks
*/
int NimBLEHealthSrvCallbacks::faultGetCurrent(bt_mesh_model *model, uint8_t *test_id,
uint16_t *company_id, uint8_t *faults,
uint8_t *fault_count)
{
NIMBLE_LOGD(LOG_TAG, "faultGetCurrent");
NimBLEHealthSrvModel* pModel = (NimBLEHealthSrvModel*)NimBLEDevice::getMeshNode()->getHealthModel(model);
*test_id = pModel->m_testId;
*company_id = CID_VENDOR;
*fault_count = std::min(*(size_t*)fault_count, pModel->m_faults.size());
memcpy(faults, &pModel->m_faults[0], *fault_count);
return 0;
}
int NimBLEHealthSrvCallbacks::faultGetRegistered(bt_mesh_model *model, uint16_t company_id,
uint8_t *test_id, uint8_t *faults,
uint8_t *fault_count)
{
NIMBLE_LOGD(LOG_TAG, "faultGetRegistered");
if (company_id != CID_VENDOR) {
return -BLE_HS_EINVAL;
}
NimBLEHealthSrvModel* pModel = (NimBLEHealthSrvModel*)NimBLEDevice::getMeshNode()->getHealthModel(model);
*test_id = pModel->m_testId;
*fault_count = std::min(*(size_t*)fault_count, pModel->m_faults.size());
memcpy(faults, &pModel->m_faults[0], *fault_count);
return 0;
}
int NimBLEHealthSrvCallbacks::faultClear(bt_mesh_model *model, uint16_t company_id)
{
NIMBLE_LOGD(LOG_TAG, "faultClear - default");
if (company_id != CID_VENDOR) {
return -BLE_HS_EINVAL;
}
NimBLEHealthSrvModel* pModel = (NimBLEHealthSrvModel*)NimBLEDevice::getMeshNode()->getHealthModel(model);
pModel->m_callbacks->faultClear(pModel);
pModel->clearFaults();
return 0;
}
int NimBLEHealthSrvCallbacks::faultTest(bt_mesh_model *model, uint8_t test_id, uint16_t company_id)
{
NIMBLE_LOGD(LOG_TAG, "faultTest - default");
if (company_id != CID_VENDOR) {
return -BLE_HS_EINVAL;
}
if (test_id != STANDARD_TEST_ID) {
return -BLE_HS_EINVAL;
}
NimBLEHealthSrvModel* pModel = (NimBLEHealthSrvModel*)NimBLEDevice::getMeshNode()->getHealthModel(model);
pModel->setFault(0);
pModel->m_testId = test_id;
pModel->m_callbacks->faultTest(pModel);
return 0;
}
void NimBLEHealthSrvCallbacks::attentionOn(bt_mesh_model *model)
{
NIMBLE_LOGD(LOG_TAG, "attentionOn - default");
NimBLEMeshModel* pModel = NimBLEDevice::getMeshNode()->getHealthModel(model);
pModel->m_callbacks->attentionOn(pModel);
}
void NimBLEHealthSrvCallbacks::attentionOff(bt_mesh_model *model)
{
NIMBLE_LOGD(LOG_TAG, "attentionOff - default");
NimBLEMeshModel* pModel = NimBLEDevice::getMeshNode()->getHealthModel(model);
pModel->m_callbacks->attentionOff(pModel);
}

197
src/NimBLEMeshModel.h Normal file
View File

@@ -0,0 +1,197 @@
/*
* NimBLEMeshModel.h
*
* Created: on Aug 25 2020
* Author H2zero
*
*/
#ifndef MAIN_NIMBLE_MESH_MODEL_H_
#define MAIN_NIMBLE_MESH_MODEL_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimconfig.h"
#include "NimBLEMeshElement.h"
#include "NimBLEAttValue.h"
#include <vector>
class NimBLEMeshModelCallbacks;
class NimBLEMeshModel {
public:
NimBLEMeshModel(NimBLEMeshModelCallbacks* pCallbacks,
uint16_t initDataSize = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH,
uint16_t maxDataSize = BLE_ATT_ATTR_MAX_LEN);
virtual ~NimBLEMeshModel();
int extractTransTimeDelay(os_mbuf *buf);
bool checkRetransmit(uint8_t tid, bt_mesh_msg_ctx *ctx);
void sendMessage(bt_mesh_model *model, bt_mesh_msg_ctx *ctx, os_mbuf *msg);
void startTdTimer(ble_npl_time_t timerMs);
void publish();
uint32_t getTransTime();
uint16_t getDelayTime();
virtual void setPubMsg(){};
virtual void setValue(uint8_t *val, size_t len){};
virtual void setTargetValue(uint8_t *val, size_t len){};
virtual void setFault(uint8_t){};
virtual void clearFaults(){};
template<typename T>
void setValue(const T &s) {
setValue((uint8_t*)&s, sizeof(T));
}
template<typename T>
void setTargetValue(const T &s) {
setTargetValue((uint8_t*)&s, sizeof(T));
}
template<typename T>
void getValue(T &s) {
s = (T)m_value[0];
}
template<typename T>
void getTargetValue(T &s) {
s = (T)m_targetValue[0];
}
bt_mesh_model_op* m_opList;
bt_mesh_model_pub m_opPub;
NimBLEMeshModelCallbacks* m_callbacks;
uint8_t m_lastTid;
uint16_t m_lastSrcAddr;
uint16_t m_lastDstAddr;
time_t m_lastMsgTime;
uint8_t m_transTime;
uint8_t m_delayTime;
NimBLEAttValue m_value;
NimBLEAttValue m_targetValue;
int16_t m_transStep;
ble_npl_callout m_tdTimer;
ble_npl_callout m_pubTimer;
};
class NimBLEGenOnOffSrvModel : NimBLEMeshModel {
friend class NimBLEMeshElement;
friend class NimBLEMeshNode;
NimBLEGenOnOffSrvModel(NimBLEMeshModelCallbacks *pCallbacks);
~NimBLEGenOnOffSrvModel(){};
static void getOnOff(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setOnOff(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setOnOffUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void tdTimerCb(ble_npl_event *event);
static void pubTimerCb(ble_npl_event *event);
void setPubMsg() override;
void setValue(uint8_t *val, size_t len) override;
void setTargetValue(uint8_t *val, size_t len) override;
};
class NimBLEGenLevelSrvModel : NimBLEMeshModel {
friend class NimBLEMeshElement;
friend class NimBLEMeshNode;
NimBLEGenLevelSrvModel(NimBLEMeshModelCallbacks *pCallbacks);
~NimBLEGenLevelSrvModel(){};
static void getLevel(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setLevel(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setLevelUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setDelta(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setDeltaUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setMove(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void setMoveUnack(bt_mesh_model *model,
bt_mesh_msg_ctx *ctx,
os_mbuf *buf);
static void tdTimerCb(ble_npl_event *event);
static void pubTimerCb(ble_npl_event *event);
void setPubMsg() override;
void setValue(uint8_t *val, size_t len) override;
void setTargetValue(uint8_t *val, size_t len) override;
};
class NimBLEHealthSrvModel : NimBLEMeshModel {
friend class NimBLEMeshElement;
friend class NimBLEMeshNode;
friend class NimBLEHealthSrvCallbacks;
NimBLEHealthSrvModel(NimBLEMeshModelCallbacks *pCallbacks);
~NimBLEHealthSrvModel(){};
public:
void setFault(uint8_t) override;
void clearFaults() override;
private:
bt_mesh_health_srv m_healthSrv;
bool m_hasFault;
uint8_t m_testId;
std::vector<uint8_t> m_faults;
};
class NimBLEMeshModelCallbacks {
public:
virtual ~NimBLEMeshModelCallbacks();
virtual void setOnOff(NimBLEMeshModel *pModel, uint8_t val);
virtual uint8_t getOnOff(NimBLEMeshModel *pModel);
virtual void setLevel(NimBLEMeshModel *pModel, int16_t val);
virtual int16_t getLevel(NimBLEMeshModel *pModel);
virtual void attentionOn(NimBLEMeshModel *pModel);
virtual void attentionOff(NimBLEMeshModel *pModel);
virtual void faultTest(NimBLEMeshModel *pModel);
virtual void faultClear(NimBLEMeshModel *pModel);
};
class NimBLEHealthSrvCallbacks {
public:
static int faultGetCurrent(bt_mesh_model *model, uint8_t *test_id,
uint16_t *company_id, uint8_t *faults,
uint8_t *fault_count);
static int faultGetRegistered(bt_mesh_model *model, uint16_t company_id,
uint8_t *test_id, uint8_t *faults,
uint8_t *fault_count);
static int faultClear(bt_mesh_model *model, uint16_t company_id);
static int faultTest(bt_mesh_model *model, uint8_t test_id, uint16_t company_id);
static void attentionOn(bt_mesh_model *model);
static void attentionOff(bt_mesh_model *model);
};
#endif // CONFIG_BT_ENABLED
#endif // MAIN_NIMBLE_MESH_MODEL_H_

210
src/NimBLEMeshNode.cpp Normal file
View File

@@ -0,0 +1,210 @@
/*
* NimBLEMeshNode.cpp
*
* Created: on July 22 2020
* Author H2zero
*
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "NimBLEMeshNode.h"
#include "NimBLELog.h"
#include "NimBLEDevice.h"
#include "NimBLEMeshCreateModel.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "services/gap/ble_svc_gap.h"
# include "services/gatt/ble_svc_gatt.h"
#else
# include "nimble/nimble/host/services/gap/include/services/gap/ble_svc_gap.h"
# include "nimble/nimble/host/services/gatt/include/services/gatt/ble_svc_gatt.h"
#endif
#define CID_VENDOR 0x05C3
static const char* LOG_TAG = "NimBLEMeshNode";
/**
* @brief Construct a mesh node.
* @param [in] uuid The uuid used to advertise for provisioning.
* @param [in] type Bitmask of the node features supported.
*/
NimBLEMeshNode::NimBLEMeshNode(const NimBLEUUID &uuid, uint8_t type) {
assert(uuid.bitSize() == 128);
memset(&m_serverConfig, 0, sizeof(m_serverConfig));
memset(&m_prov, 0, sizeof(m_prov));
memset(&m_comp, 0, sizeof(m_comp));
// Default server config
m_serverConfig.relay = BT_MESH_RELAY_DISABLED;/*(type & NIMBLE_MESH::RELAY) ?
BT_MESH_RELAY_ENABLED :
BT_MESH_RELAY_DISABLED;*/
m_serverConfig.beacon = BT_MESH_BEACON_ENABLED;
m_serverConfig.frnd = BT_MESH_FRIEND_DISABLED;/*(type & NIMBLE_MESH::FRIEND) ?
BT_MESH_FRIEND_ENABLED :
BT_MESH_FRIEND_DISABLED;*/
m_serverConfig.gatt_proxy = BT_MESH_GATT_PROXY_ENABLED; /*(type & NIMBLE_MESH::RELAY) ?
BT_MESH_GATT_PROXY_ENABLED :
BT_MESH_GATT_PROXY_DISABLED;*/
m_serverConfig.default_ttl = 7;
// 3 transmissions with 20ms interval
m_serverConfig.net_transmit = BT_MESH_TRANSMIT(2, 20);
m_serverConfig.relay_retransmit = BT_MESH_TRANSMIT(2, 20);
// Provisioning config
m_uuid = uuid;
m_prov.uuid = m_uuid.getNative()->u128.value;
m_prov.complete = NimBLEMeshNode::provComplete;
m_prov.reset = NimBLEMeshNode::provReset;
// Create the primary element
m_elemVec.push_back(new NimBLEMeshElement());
}
/**
* @brief Destructor, cleanup any resources created.
*/
NimBLEMeshNode::~NimBLEMeshNode() {
if(m_comp.elem != nullptr) {
free (m_comp.elem);
}
}
/**
* @brief Called from the callbacks when provisioning changes.
*/
void NimBLEMeshNode::setProvData(uint16_t netIdx, uint16_t addr) {
m_primAddr = addr;
m_primNetIdx = netIdx;
}
/**
* @brief callback, Called by NimBLE stack when provisioning is complete.
*/
void NimBLEMeshNode::provComplete(uint16_t netIdx, uint16_t addr) {
NIMBLE_LOGI(LOG_TAG,
"provisioning complete for netIdx 0x%04x addr 0x%04x",
netIdx, addr);
NimBLEDevice::getMeshNode()->setProvData(netIdx, addr);
}
/**
* @brief callback, Called by NimBLE stack when provisioning is reset.
*/
void NimBLEMeshNode::provReset() {
NIMBLE_LOGI(LOG_TAG, "provisioning reset");
NimBLEDevice::getMeshNode()->setProvData(0, 0);
}
/**
* @brief get a pointer an element.
* @param [in] index The element vector index of the element.
* @returns a pointer to the element requested.
*/
NimBLEMeshElement* NimBLEMeshNode::getElement(uint8_t index) {
return m_elemVec[index];
}
/**
* @brief Create a new mesh element.
* @returns a pointer to the newly created element.
*/
NimBLEMeshElement* NimBLEMeshNode::createElement() {
m_elemVec.push_back(new NimBLEMeshElement());
return m_elemVec.back();
}
/**
* @brief Get a pointer to the health model instance that matches the ID's of the input model.
* @param [in] model A pointer to the NimBLE internal model instance.
* @returns A pointer to the model.
*/
NimBLEMeshModel* NimBLEMeshNode::getHealthModel(bt_mesh_model *model) {
NimBLEMeshModel* pModel;
for(auto &it : m_elemVec) {
pModel = it->getModelByIdx(model->elem_idx, model->mod_idx, BT_MESH_MODEL_ID_HEALTH_SRV);
if(pModel != nullptr) {
return pModel;
}
}
return nullptr;
}
/**
* @brief Start the Mesh mode.
* @returns true on success.
*/
bool NimBLEMeshNode::start() {
// Reset and restart gatts so we can register mesh gatt
ble_gatts_reset();
ble_svc_gap_init();
ble_svc_gatt_init();
bt_mesh_register_gatt();
ble_gatts_start();
// Config server and primary health models are required in the primary element
// create them here and add them as the first models.
m_elemVec[0]->addModel(createConfigSrvModel(&m_serverConfig));
if(m_elemVec[0]->getModel(BT_MESH_MODEL_ID_HEALTH_SRV) == nullptr) {
m_elemVec[0]->createModel(BT_MESH_MODEL_ID_HEALTH_SRV);
}
// setup node composition
m_comp.cid = CID_VENDOR;
m_comp.elem = (bt_mesh_elem*)calloc(m_elemVec.size(), sizeof(bt_mesh_elem));
if(m_comp.elem == nullptr) {
NIMBLE_LOGE(LOG_TAG, "Error: No Mem");
return false;
}
for(size_t i = 0; i < m_elemVec.size(); i++) {
memcpy((void*)&m_comp.elem[i], (void*)m_elemVec[i]->start(), sizeof(bt_mesh_elem));
}
m_comp.elem_count = (uint8_t)m_elemVec.size();
// Use random address
ble_addr_t addr;
int err = ble_hs_id_gen_rnd(1, &addr);
assert(err == 0);
err = ble_hs_id_set_rnd(addr.val);
assert(err == 0);
err = bt_mesh_init(addr.type, &m_prov, &m_comp);
if (err) {
NIMBLE_LOGE(LOG_TAG, "Initializing mesh failed (err %d)", err);
return false;
}
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
if (bt_mesh_is_provisioned()) {
NIMBLE_LOGI(LOG_TAG, "Mesh network restored from flash");
}
return true;
}
#endif // CONFIG_BT_ENABLED

76
src/NimBLEMeshNode.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* NimBLEMeshNode.h
*
* Created: on July 22 2020
* Author H2zero
*
*/
#ifndef MAIN_NIMBLE_MESH_NODE_H_
#define MAIN_NIMBLE_MESH_NODE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpointer-arith"
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "mesh/glue.h"
# include "mesh/mesh.h"
#else
# include "nimble/nimble/host/mesh/include/mesh/glue.h"
# include "nimble/nimble/host/mesh/include/mesh/mesh.h"
#endif
#pragma GCC diagnostic pop
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
#include "NimBLEUUID.h"
#include "NimBLEMeshElement.h"
#include <vector>
class NimBLEMeshModel;
typedef enum {
RELAY = 0x01 << 0,
BEACON = 0x01 << 1,
FRIEND = 0x01 << 2,
PROXY = 0x01 << 3,
} NIMBLE_MESH;
class NimBLEMeshElement;
class NimBLEMeshNode {
public:
bool start();
NimBLEMeshElement* createElement();
NimBLEMeshElement* getElement(uint8_t index = 0);
NimBLEMeshModel* getHealthModel(bt_mesh_model *model);
private:
friend class NimBLEDevice;
friend class NimBLEMeshElement;
NimBLEMeshNode(const NimBLEUUID &uuid, uint8_t type);
~NimBLEMeshNode();
static void provComplete(uint16_t netIdx, uint16_t addr);
static void provReset();
void setProvData(uint16_t netIdx, uint16_t addr);
bt_mesh_cfg_srv m_serverConfig;
bt_mesh_prov m_prov;
bt_mesh_comp m_comp;
uint16_t m_primAddr;
uint16_t m_primNetIdx;
NimBLEUUID m_uuid;
std::vector<NimBLEMeshElement*> m_elemVec;
};
#endif // CONFIG_BT_ENABLED
#endif // MAIN_NIMBLE_MESH_NODE_H_

View File

@@ -54,6 +54,22 @@ int NimBLEUtils::checkConnParams(ble_gap_conn_params* params) {
return 0;
}
ble_npl_time_t NimBLEUtils::meshTransTimeMs(uint8_t tt) {
switch(tt >> 6) {
case 0:
return 100;
case 1:
return 1000;
case 2:
return 10000;
case 3:
return 600000;
default:
return 0;
}
}
/**
* @brief Converts a return code from the NimBLE stack to a text string.

View File

@@ -44,6 +44,7 @@ public:
static const char* advTypeToString(uint8_t advType);
static const char* returnCodeToString(int rc);
static int checkConnParams(ble_gap_conn_params* params);
static ble_npl_time_t meshTransTimeMs(uint8_t tt);
};

View File

@@ -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 <stdint.h>
#include <string.h>
#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__ */

View File

@@ -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 <os/queue.h>
#else
# include "nimble/porting/nimble/include/os/queue.h"
#endif
#include <stdint.h>
#include <stdbool.h>
#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)
#endif /* __SYS_CONFIG_H_ */

View File

@@ -0,0 +1,137 @@
/*
* 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
#include "config.h"
#include "nvs.h"
#include <string.h>
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_t 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;
}
printf("key '%s', type '%d' value %s \n", info.key, info.type, val);
}
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
#endif // ESP_PLATFORM

View File

@@ -131,6 +131,7 @@
/** @brief Un-comment to use external PSRAM for the NimBLE host */
#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1
/** @brief Un-comment to change the core NimBLE host runs on */
#define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0