mirror of
https://github.com/h2zero/esp-nimble-cpp.git
synced 2024-11-23 21:50:55 +01:00
Basic mesh implementation with on/off and level models.
This commit is contained in:
parent
e238a18a80
commit
bc2cecd2db
10 changed files with 841 additions and 1 deletions
|
@ -55,4 +55,3 @@ idf_component_register(
|
||||||
PRIV_REQUIRES
|
PRIV_REQUIRES
|
||||||
${ESP_NIMBLE_PRIV_REQUIRES}
|
${ESP_NIMBLE_PRIV_REQUIRES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ bool NimBLEDevice::m_synced = false;
|
||||||
NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr;
|
NimBLEAdvertising* NimBLEDevice::m_bleAdvertising = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
NimBLEMeshNode* NimBLEDevice::m_pMeshNode = nullptr;
|
||||||
|
|
||||||
gap_event_handler NimBLEDevice::m_customGapHandler = nullptr;
|
gap_event_handler NimBLEDevice::m_customGapHandler = nullptr;
|
||||||
ble_gap_event_listener NimBLEDevice::m_listener;
|
ble_gap_event_listener NimBLEDevice::m_listener;
|
||||||
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
||||||
|
@ -67,6 +69,30 @@ uint16_t NimBLEDevice::m_scanDuplicateSize = CONFIG_BTDM_SCAN
|
||||||
uint8_t NimBLEDevice::m_scanFilterMode = CONFIG_BTDM_SCAN_DUPL_TYPE;
|
uint8_t NimBLEDevice::m_scanFilterMode = CONFIG_BTDM_SCAN_DUPL_TYPE;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
* @brief Create a new instance of a server.
|
||||||
* @return A new instance of the server.
|
* @return A new instance of the server.
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
#include "NimBLEServer.h"
|
#include "NimBLEServer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "NimBLEMeshNode.h"
|
||||||
|
|
||||||
#include "NimBLEUtils.h"
|
#include "NimBLEUtils.h"
|
||||||
#include "NimBLESecurity.h"
|
#include "NimBLESecurity.h"
|
||||||
#include "NimBLEAddress.h"
|
#include "NimBLEAddress.h"
|
||||||
|
@ -110,6 +112,9 @@ public:
|
||||||
static NimBLEServer* getServer();
|
static NimBLEServer* getServer();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static NimBLEMeshNode* createMeshNode(NimBLEUUID uuid, uint8_t type);
|
||||||
|
static NimBLEMeshNode* getMeshNode();
|
||||||
|
|
||||||
static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT);
|
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);
|
static int getPower(esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT);
|
||||||
static void setCustomGapHandler(gap_event_handler handler);
|
static void setCustomGapHandler(gap_event_handler handler);
|
||||||
|
@ -202,6 +207,7 @@ private:
|
||||||
static uint16_t m_scanDuplicateSize;
|
static uint16_t m_scanDuplicateSize;
|
||||||
static uint8_t m_scanFilterMode;
|
static uint8_t m_scanFilterMode;
|
||||||
static std::vector<NimBLEAddress> m_whiteList;
|
static std::vector<NimBLEAddress> m_whiteList;
|
||||||
|
static NimBLEMeshNode* m_pMeshNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
80
src/NimBLEMeshElement.cpp
Normal file
80
src/NimBLEMeshElement.cpp
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* NimBLEMeshElement.cpp
|
||||||
|
*
|
||||||
|
* Created: on Aug 23 2020
|
||||||
|
* Author H2zero
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "NimBLEMeshElement.h"
|
||||||
|
#include "NimBLELog.h"
|
||||||
|
|
||||||
|
static const char* LOG_TAG = "NimBLEMeshElement";
|
||||||
|
|
||||||
|
NimBLEMeshElement::NimBLEMeshElement() {
|
||||||
|
m_pElem = nullptr;
|
||||||
|
}
|
||||||
|
NimBLEMeshElement::~NimBLEMeshElement() {
|
||||||
|
if(m_pElem != nullptr) {
|
||||||
|
delete m_pElem;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto &it : m_modelsVec) {
|
||||||
|
delete (NimBLEMeshModel*)it.user_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
void NimBLEMeshElement::createModel(uint16_t type, NimBLEMeshModelCallbacks *pCallbacks) {
|
||||||
|
for(auto &it : m_modelsVec) {
|
||||||
|
if(it.id == type) {
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Error: element already has a type %04x model", type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Error: model type %04x not supported", type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_modelsVec.push_back(bt_mesh_model{{type},0,0,0, pModel->opPub,{0},{0},pModel->opList, 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(bt_mesh_model* model) {
|
||||||
|
m_modelsVec.push_back(*model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
41
src/NimBLEMeshElement.h
Normal file
41
src/NimBLEMeshElement.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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 NimBLEMeshElement {
|
||||||
|
public:
|
||||||
|
void createModel(uint16_t type, NimBLEMeshModelCallbacks* pCallbacks=nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class NimBLEMeshNode;
|
||||||
|
|
||||||
|
NimBLEMeshElement();
|
||||||
|
~NimBLEMeshElement();
|
||||||
|
void addModel(bt_mesh_model* model);
|
||||||
|
bt_mesh_elem* start();
|
||||||
|
|
||||||
|
bt_mesh_elem *m_pElem;
|
||||||
|
std::vector<bt_mesh_model> m_modelsVec;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CONFIG_BT_ENABLED
|
||||||
|
#endif // MAIN_NIMBLE_MESH_ELEMENT_H_
|
244
src/NimBLEMeshModel.cpp
Normal file
244
src/NimBLEMeshModel.cpp
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
/*
|
||||||
|
* NimBLEMeshModel.cpp
|
||||||
|
*
|
||||||
|
* Created: on Aug 25 2020
|
||||||
|
* Author H2zero
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "NimBLEMeshModel.h"
|
||||||
|
#include "NimBLELog.h"
|
||||||
|
|
||||||
|
static const char* LOG_TAG = "NimBLEMeshModel";
|
||||||
|
|
||||||
|
static NimBLEMeshModelCallbacks defaultCallbacks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief base model constructor
|
||||||
|
* @param [in] pCallbacks, a pointer to a callback instance for model operations
|
||||||
|
*/
|
||||||
|
NimBLEMeshModel::NimBLEMeshModel(NimBLEMeshModelCallbacks* pCallbacks) {
|
||||||
|
if(pCallbacks == nullptr) {
|
||||||
|
m_callbacks = &defaultCallbacks;
|
||||||
|
} else {
|
||||||
|
m_callbacks = pCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
opList = nullptr;
|
||||||
|
opPub = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief destructor
|
||||||
|
*/
|
||||||
|
NimBLEMeshModel::~NimBLEMeshModel(){
|
||||||
|
if(opList != nullptr) {
|
||||||
|
delete[] opList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(opPub != nullptr) {
|
||||||
|
delete[] opPub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
// Register the opcodes for this model with the required callbacks
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
NimBLEGenOnOffSrvModel *pModel = (NimBLEGenOnOffSrvModel*)model->user_data;
|
||||||
|
struct os_mbuf *msg = NET_BUF_SIMPLE(3);
|
||||||
|
uint8_t *status;
|
||||||
|
|
||||||
|
bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_2(0x82, 0x04));
|
||||||
|
status = (uint8_t*)net_buf_simple_add(msg, 1);
|
||||||
|
*status = pModel->m_callbacks->getOnOff();
|
||||||
|
|
||||||
|
if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Send status failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
os_mbuf_free_chain(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
NimBLEGenOnOffSrvModel *pModel = (NimBLEGenOnOffSrvModel*)model->user_data;
|
||||||
|
pModel->m_callbacks->setOnOff(buf->om_data[0]);
|
||||||
|
|
||||||
|
// send the status update
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
NimBLEGenOnOffSrvModel *pModel = (NimBLEGenOnOffSrvModel*)model->user_data;
|
||||||
|
pModel->m_callbacks->setOnOff(buf->om_data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic level server model constructor
|
||||||
|
* @param [in] pCallbacks, a pointer to a callback instance for model operations
|
||||||
|
*/
|
||||||
|
NimBLEGenLevelSrvModel::NimBLEGenLevelSrvModel(NimBLEMeshModelCallbacks* pCallbacks)
|
||||||
|
:NimBLEMeshModel(pCallbacks)
|
||||||
|
{
|
||||||
|
// Register the opcodes for this model with the required callbacks
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
struct os_mbuf *msg = NET_BUF_SIMPLE(4);
|
||||||
|
|
||||||
|
bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_2(0x82, 0x08));
|
||||||
|
net_buf_simple_add_le16(msg, pModel->m_callbacks->getLevel());
|
||||||
|
|
||||||
|
if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Send status failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
os_mbuf_free_chain(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
|
||||||
|
pModel->m_callbacks->setLevel((int16_t) net_buf_simple_pull_le16(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;
|
||||||
|
pModel->m_callbacks->setLevel((int16_t) net_buf_simple_pull_le16(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
{
|
||||||
|
NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data;
|
||||||
|
pModel->m_callbacks->setDelta((int16_t) net_buf_simple_pull_le16(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;
|
||||||
|
pModel->m_callbacks->setDelta((int16_t) net_buf_simple_pull_le16(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NimBLEGenLevelSrvModel::setMove(bt_mesh_model *model,
|
||||||
|
bt_mesh_msg_ctx *ctx,
|
||||||
|
os_mbuf *buf)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEGenLevelSrvModel::setMoveUnack(bt_mesh_model *model,
|
||||||
|
bt_mesh_msg_ctx *ctx,
|
||||||
|
os_mbuf *buf)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default model callbacks
|
||||||
|
*/
|
||||||
|
NimBLEMeshModelCallbacks::~NimBLEMeshModelCallbacks() {}
|
||||||
|
|
||||||
|
void NimBLEMeshModelCallbacks::setOnOff(uint8_t val) {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Gen On/Off set val: %d", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t NimBLEMeshModelCallbacks::getOnOff() {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Gen On/Off get");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEMeshModelCallbacks::setLevel(int16_t val) {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Gen Level set val: %d", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t NimBLEMeshModelCallbacks::getLevel() {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Gen Level get");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEMeshModelCallbacks::setDelta(int16_t val) {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Gen Delta set val: %d", val);
|
||||||
|
}
|
91
src/NimBLEMeshModel.h
Normal file
91
src/NimBLEMeshModel.h
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
class NimBLEMeshModelCallbacks;
|
||||||
|
|
||||||
|
class NimBLEMeshModel {
|
||||||
|
public:
|
||||||
|
NimBLEMeshModel(NimBLEMeshModelCallbacks* pCallbacks);
|
||||||
|
~NimBLEMeshModel();
|
||||||
|
|
||||||
|
bt_mesh_model_op* opList;
|
||||||
|
|
||||||
|
bt_mesh_model_pub* opPub;
|
||||||
|
NimBLEMeshModelCallbacks* m_callbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
class NimBLEMeshModelCallbacks {
|
||||||
|
public:
|
||||||
|
virtual ~NimBLEMeshModelCallbacks();
|
||||||
|
virtual void setOnOff(uint8_t);
|
||||||
|
virtual uint8_t getOnOff();
|
||||||
|
virtual void setLevel(int16_t);
|
||||||
|
virtual int16_t getLevel();
|
||||||
|
virtual void setDelta(int16_t);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONFIG_BT_ENABLED
|
||||||
|
#endif // MAIN_NIMBLE_MESH_MODEL_H_
|
263
src/NimBLEMeshNode.cpp
Normal file
263
src/NimBLEMeshNode.cpp
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
/*
|
||||||
|
* NimBLEMeshNoce.cpp
|
||||||
|
*
|
||||||
|
* Created: on July 22 2020
|
||||||
|
* Author H2zero
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#if defined(CONFIG_BT_ENABLED)
|
||||||
|
|
||||||
|
#include "NimBLEMeshNode.h"
|
||||||
|
#include "NimBLELog.h"
|
||||||
|
#include "NimBLEDevice.h"
|
||||||
|
|
||||||
|
#include "services/gap/ble_svc_gap.h"
|
||||||
|
#include "services/gatt/ble_svc_gatt.h"
|
||||||
|
|
||||||
|
#define CID_VENDOR 0x05C3
|
||||||
|
|
||||||
|
static const char* LOG_TAG = "NimBLEMeshNode";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Health server callback struct
|
||||||
|
*/
|
||||||
|
static const struct bt_mesh_health_srv_cb health_srv_cb = {
|
||||||
|
NimBLEHealthSrvCallbacks::faultGetCurrent,
|
||||||
|
NimBLEHealthSrvCallbacks::faultGetRegistered,
|
||||||
|
NimBLEHealthSrvCallbacks::faultClear,
|
||||||
|
NimBLEHealthSrvCallbacks::faultTest,
|
||||||
|
NimBLEHealthSrvCallbacks::attentionOn,
|
||||||
|
NimBLEHealthSrvCallbacks::attentionOff
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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));
|
||||||
|
memset(&m_healthPub, 0, sizeof(m_healthPub));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Default health server config
|
||||||
|
m_healthSrv = {0};
|
||||||
|
m_healthSrv.cb = &health_srv_cb;
|
||||||
|
|
||||||
|
// Default health pub config
|
||||||
|
m_healthPub.msg = BT_MESH_HEALTH_FAULT_MSG(0);
|
||||||
|
|
||||||
|
// Provisioning config
|
||||||
|
m_uuid = uuid;
|
||||||
|
m_prov.uuid = m_uuid.getNative()->u128.value;
|
||||||
|
m_prov.complete = NimBLEMeshNode::provComplete;
|
||||||
|
m_prov.reset = NimBLEMeshNode::provReset;
|
||||||
|
|
||||||
|
m_configSrvModel = nullptr;
|
||||||
|
m_configHthModel = nullptr;
|
||||||
|
|
||||||
|
// Create the primary element
|
||||||
|
m_elemVec.push_back(new NimBLEMeshElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor, cleanup any resources created.
|
||||||
|
*/
|
||||||
|
NimBLEMeshNode::~NimBLEMeshNode() {
|
||||||
|
if(m_configSrvModel != nullptr) {
|
||||||
|
delete m_configSrvModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_configHthModel != nullptr) {
|
||||||
|
delete m_configHthModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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_configSrvModel = new bt_mesh_model{{BT_MESH_MODEL_ID_CFG_SRV},0,0,0,nullptr,{0},{0},bt_mesh_cfg_srv_op,&m_serverConfig};
|
||||||
|
for(int i = 0; i < CONFIG_BT_MESH_MODEL_KEY_COUNT; i++) {
|
||||||
|
m_configSrvModel->keys[i] = BT_MESH_KEY_UNUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < CONFIG_BT_MESH_MODEL_GROUP_COUNT; i++) {
|
||||||
|
m_configSrvModel->groups[i] = BT_MESH_ADDR_UNASSIGNED;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_configHthModel = new bt_mesh_model{{BT_MESH_MODEL_ID_HEALTH_SRV},0,0,0,&m_healthPub,{0},{0},bt_mesh_health_srv_op,&m_healthSrv};
|
||||||
|
|
||||||
|
m_elemVec[0]->addModel(m_configSrvModel);
|
||||||
|
m_elemVec[0]->addModel(m_configHthModel);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 - default");
|
||||||
|
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 - default");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NimBLEHealthSrvCallbacks::faultClear(bt_mesh_model *model, uint16_t company_id)
|
||||||
|
{
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "faultClear - default");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NimBLEHealthSrvCallbacks::faultTest(bt_mesh_model *model, uint8_t test_id, uint16_t company_id)
|
||||||
|
{
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "faultTest - default");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEHealthSrvCallbacks::attentionOn(bt_mesh_model *model)
|
||||||
|
{
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "attentionOn - default");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEHealthSrvCallbacks::attentionOff(bt_mesh_model *model)
|
||||||
|
{
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "attentionOff - default");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CONFIG_BT_ENABLED
|
89
src/NimBLEMeshNode.h
Normal file
89
src/NimBLEMeshNode.h
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* NimBLEMeshNode.h
|
||||||
|
*
|
||||||
|
* Created: on July 22 2020
|
||||||
|
* Author H2zero
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_NIMBLE_MESH_NODE_H_
|
||||||
|
#define MAIN_NIMBLE_MESH_NODE_H_
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#if defined(CONFIG_BT_ENABLED)
|
||||||
|
#include "nimconfig.h"
|
||||||
|
|
||||||
|
#include "mesh/glue.h"
|
||||||
|
#include "mesh/mesh.h"
|
||||||
|
|
||||||
|
/**** FIX COMPILATION ****/
|
||||||
|
#undef min
|
||||||
|
#undef max
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
#include "NimBLEUUID.h"
|
||||||
|
#include "NimBLEMeshElement.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
bt_mesh_model* m_configSrvModel;
|
||||||
|
bt_mesh_model* m_configHthModel;
|
||||||
|
bt_mesh_health_srv m_healthSrv;
|
||||||
|
bt_mesh_model_pub m_healthPub;
|
||||||
|
NimBLEUUID m_uuid;
|
||||||
|
|
||||||
|
std::vector<NimBLEMeshElement*> m_elemVec;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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_NODE_H_
|
|
@ -84,6 +84,7 @@
|
||||||
/** @brief Un-comment to use external PSRAM for the NimBLE host */
|
/** @brief Un-comment to use external PSRAM for the NimBLE host */
|
||||||
#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1
|
#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1
|
||||||
|
|
||||||
|
|
||||||
/** @brief Un-comment to change the core NimBLE host runs on */
|
/** @brief Un-comment to change the core NimBLE host runs on */
|
||||||
#define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0
|
#define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue