From bc2e11637eb68419a5311657b64ffbac7a2544d2 Mon Sep 17 00:00:00 2001 From: h2zero Date: Sun, 30 Aug 2020 07:48:24 -0600 Subject: [PATCH] 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. --- .../basic_on_off_level_server/main/main.cpp | 7 +- src/NimBLEMeshModel.cpp | 277 ++++++++++++++++-- src/NimBLEMeshModel.h | 21 +- src/NimBLEMeshNode.cpp | 10 +- src/NimBLEMeshNode.h | 11 +- src/NimBLEUtils.cpp | 16 + src/NimBLEUtils.h | 1 + 7 files changed, 295 insertions(+), 48 deletions(-) diff --git a/examples/mesh/basic_on_off_level_server/main/main.cpp b/examples/mesh/basic_on_off_level_server/main/main.cpp index def8e2f..de346a9 100644 --- a/examples/mesh/basic_on_off_level_server/main/main.cpp +++ b/examples/mesh/basic_on_off_level_server/main/main.cpp @@ -22,7 +22,7 @@ class onOffSrvModelCallbacks : public NimBLEMeshModelCallbacks { } uint8_t getOnOff() { - printf("on/off get val %d", onOffVal); + printf("on/off get val %d\n", onOffVal); return onOffVal; } }; @@ -33,11 +33,6 @@ class levelSrvModelCallbacks : public NimBLEMeshModelCallbacks { levelVal = val; } - void setDelta(int16_t val) { - printf("Level set delta %d\n", val); - levelVal += val; - } - int16_t getLevel() { printf("Level get val %d\n", levelVal); return levelVal; diff --git a/src/NimBLEMeshModel.cpp b/src/NimBLEMeshModel.cpp index cc2e1b7..5c53ab4 100644 --- a/src/NimBLEMeshModel.cpp +++ b/src/NimBLEMeshModel.cpp @@ -7,8 +7,11 @@ */ #include "NimBLEMeshModel.h" +#include "NimBLEUtils.h" #include "NimBLELog.h" +#include "nimble/nimble_port.h" + static const char* LOG_TAG = "NimBLEMeshModel"; static NimBLEMeshModelCallbacks defaultCallbacks; @@ -26,13 +29,24 @@ NimBLEMeshModel::NimBLEMeshModel(NimBLEMeshModelCallbacks* pCallbacks) { opList = nullptr; opPub = nullptr; + m_lastTid = 0; + m_lastSrcAddr = 0; + m_lastDstAddr = 0; + m_lastMsgTime = 0; + m_transTime = 0; + m_delayTime = 0; + m_onOffValue = 0; + m_onOffTarget = 0; + m_levelValue = 0; + m_levelTarget = 0; + m_transStep = 0; } /** * @brief destructor */ -NimBLEMeshModel::~NimBLEMeshModel(){ +NimBLEMeshModel::~NimBLEMeshModel() { if(opList != nullptr) { delete[] opList; } @@ -43,6 +57,65 @@ NimBLEMeshModel::~NimBLEMeshModel(){ } +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); +} + + /** * @brief Generic on/off server model constructor * @param [in] pCallbacks, a pointer to a callback instance for model operations @@ -56,6 +129,9 @@ NimBLEGenOnOffSrvModel::NimBLEGenOnOffSrvModel(NimBLEMeshModelCallbacks* pCallba { 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); } @@ -66,19 +142,24 @@ 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; + NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data; + struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 3); 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"); + if(pModel->m_callbacks != &defaultCallbacks) { + pModel->m_onOffValue = pModel->m_callbacks->getOnOff(); + } + net_buf_simple_add_u8(msg, pModel->m_onOffValue); + + if(pModel->m_transTime > 0) { + net_buf_simple_add_u8(msg, pModel->m_onOffTarget); + // If we started the transition timer in setOnOff we need to correct the reported remaining time. + net_buf_simple_add_u8(msg, (pModel->m_delayTime > 0) ? + pModel->m_transTime : pModel->m_transTime + 1); } - os_mbuf_free_chain(msg); + pModel->sendMessage(model, ctx, msg); } @@ -89,10 +170,8 @@ 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 + // Rather than duplicate code just call the unack function then send the status + NimBLEGenOnOffSrvModel::setOnOffUnack(model,ctx,buf); NimBLEGenOnOffSrvModel::getOnOff(model,ctx,buf); } @@ -104,10 +183,67 @@ 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]); + NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data; + int16_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_onOffValue) { + pModel->m_onOffTarget = 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_onOffValue = pModel->m_onOffTarget; + pModel->m_callbacks->setOnOff(pModel->m_onOffValue); + } } +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_onOffTarget == 0) { + ble_npl_time_t ticks = 0; + ble_npl_time_ms_to_ticks(NimBLEUtils::meshTransTimeMs(pModel->m_transTime), &ticks); + ble_npl_callout_reset(&pModel->m_tdTimer, ticks); + pModel->m_transTime -= 1; + return; + } + + pModel->m_transTime = 0; + pModel->m_onOffValue = pModel->m_onOffTarget; + pModel->m_callbacks->setOnOff(pModel->m_onOffValue); +} /** * @brief Generic level server model constructor @@ -126,6 +262,9 @@ NimBLEGenLevelSrvModel::NimBLEGenLevelSrvModel(NimBLEMeshModelCallbacks* pCallba { 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); } @@ -138,16 +277,19 @@ void NimBLEGenLevelSrvModel::getLevel(bt_mesh_model *model, { NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data; - struct os_mbuf *msg = NET_BUF_SIMPLE(4); + struct os_mbuf *msg = NET_BUF_SIMPLE(4 + 3); 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"); + if(pModel->m_transTime > 0) { + net_buf_simple_add_le16(msg, pModel->m_levelTarget); + // If we started the transition timer in setLevel we need to correct the reported remaining time. + net_buf_simple_add_u8(msg, (pModel->m_delayTime > 0) ? + pModel->m_transTime : pModel->m_transTime + 1); } - os_mbuf_free_chain(msg); + pModel->sendMessage(model, ctx, msg); } @@ -158,9 +300,7 @@ 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::setLevelUnack(model, ctx, buf); NimBLEGenLevelSrvModel::getLevel(model, ctx, buf); } @@ -173,7 +313,46 @@ void NimBLEGenLevelSrvModel::setLevelUnack(bt_mesh_model *model, os_mbuf *buf) { NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data; - pModel->m_callbacks->setLevel((int16_t) net_buf_simple_pull_le16(buf)); + 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; + + if(newval != pModel->m_levelValue) { + pModel->m_levelTarget = newval; + + if(pModel->m_delayTime > 0) { + timerMs = 5 * pModel->m_delayTime; + } + + if(pModel->m_transTime & 0x3F) { + pModel->m_transStep = -1 *((pModel->m_levelValue - pModel->m_levelTarget) / + (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_levelValue = pModel->m_levelTarget; + pModel->m_callbacks->setLevel(pModel->m_levelValue); + } } @@ -184,9 +363,7 @@ 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::setDeltaUnack(model, ctx, buf); NimBLEGenLevelSrvModel::getLevel(model, ctx, buf); } @@ -199,7 +376,17 @@ void NimBLEGenLevelSrvModel::setDeltaUnack(bt_mesh_model *model, os_mbuf *buf) { NimBLEMeshModel *pModel = (NimBLEMeshModel*)model->user_data; - pModel->m_callbacks->setDelta((int16_t) net_buf_simple_pull_le16(buf)); + int32_t delta = (int32_t) net_buf_simple_pull_le32(buf); + + int32_t temp32 = pModel->m_levelValue + 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); } @@ -207,15 +394,47 @@ 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) && pModel->m_onOffTarget == 0) { + pModel->m_levelValue += pModel->m_transStep; + pModel->m_callbacks->setLevel(pModel->m_levelValue); + ble_npl_time_t ticks = 0; + ble_npl_time_ms_to_ticks(NimBLEUtils::meshTransTimeMs(pModel->m_transTime), &ticks); + ble_npl_callout_reset(&pModel->m_tdTimer, ticks); + pModel->m_transTime -= 1; + return; + } + + pModel->m_transTime = 0; + pModel->m_levelValue = pModel->m_levelTarget; + pModel->m_callbacks->setLevel(pModel->m_levelValue); +} + /** * Default model callbacks */ @@ -238,7 +457,3 @@ 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); -} \ No newline at end of file diff --git a/src/NimBLEMeshModel.h b/src/NimBLEMeshModel.h index a802171..464208b 100644 --- a/src/NimBLEMeshModel.h +++ b/src/NimBLEMeshModel.h @@ -26,6 +26,23 @@ public: bt_mesh_model_pub* opPub; NimBLEMeshModelCallbacks* m_callbacks; + 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); + + uint8_t m_lastTid; + uint16_t m_lastSrcAddr; + uint16_t m_lastDstAddr; + time_t m_lastMsgTime; + uint8_t m_transTime; + uint8_t m_delayTime; + uint8_t m_onOffValue; + uint8_t m_onOffTarget; + int16_t m_levelValue; + int16_t m_levelTarget; + int16_t m_transStep; + ble_npl_callout m_tdTimer; }; class NimBLEGenOnOffSrvModel : NimBLEMeshModel { @@ -44,6 +61,7 @@ class NimBLEGenOnOffSrvModel : NimBLEMeshModel { static void setOnOffUnack(bt_mesh_model *model, bt_mesh_msg_ctx *ctx, os_mbuf *buf); + static void tdTimerCb(ble_npl_event *event); }; class NimBLEGenLevelSrvModel : NimBLEMeshModel { @@ -74,6 +92,7 @@ class NimBLEGenLevelSrvModel : NimBLEMeshModel { static void setMoveUnack(bt_mesh_model *model, bt_mesh_msg_ctx *ctx, os_mbuf *buf); + static void tdTimerCb(ble_npl_event *event); }; class NimBLEMeshModelCallbacks { @@ -83,8 +102,6 @@ public: virtual uint8_t getOnOff(); virtual void setLevel(int16_t); virtual int16_t getLevel(); - virtual void setDelta(int16_t); - }; #endif // CONFIG_BT_ENABLED diff --git a/src/NimBLEMeshNode.cpp b/src/NimBLEMeshNode.cpp index 7c6164f..2dc64a0 100644 --- a/src/NimBLEMeshNode.cpp +++ b/src/NimBLEMeshNode.cpp @@ -44,6 +44,7 @@ NimBLEMeshNode::NimBLEMeshNode(const NimBLEUUID &uuid, uint8_t type) { memset(&m_prov, 0, sizeof(m_prov)); memset(&m_comp, 0, sizeof(m_comp)); memset(&m_healthPub, 0, sizeof(m_healthPub)); + memset(&m_healthSrv, 0, sizeof(m_healthSrv)); // Default server config m_serverConfig.relay = BT_MESH_RELAY_DISABLED;/*(type & NIMBLE_MESH::RELAY) ? @@ -66,7 +67,6 @@ NimBLEMeshNode::NimBLEMeshNode(const NimBLEUUID &uuid, uint8_t type) { 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 @@ -222,16 +222,16 @@ bool NimBLEMeshNode::start() { * @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) + 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) + uint8_t *test_id, uint8_t *faults, + uint8_t *fault_count) { NIMBLE_LOGD(LOG_TAG, "faultGetRegistered - default"); return 0; diff --git a/src/NimBLEMeshNode.h b/src/NimBLEMeshNode.h index 867d7e9..6862ca5 100644 --- a/src/NimBLEMeshNode.h +++ b/src/NimBLEMeshNode.h @@ -13,8 +13,11 @@ #if defined(CONFIG_BT_ENABLED) #include "nimconfig.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-arith" #include "mesh/glue.h" #include "mesh/mesh.h" +#pragma GCC diagnostic pop /**** FIX COMPILATION ****/ #undef min @@ -69,12 +72,12 @@ private: 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); + 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); + uint8_t *test_id, uint8_t *faults, + uint8_t *fault_count); static int faultClear(bt_mesh_model *model, uint16_t company_id); diff --git a/src/NimBLEUtils.cpp b/src/NimBLEUtils.cpp index 7a1f55b..e528914 100644 --- a/src/NimBLEUtils.cpp +++ b/src/NimBLEUtils.cpp @@ -53,6 +53,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. diff --git a/src/NimBLEUtils.h b/src/NimBLEUtils.h index acbc93e..ac90bc7 100644 --- a/src/NimBLEUtils.h +++ b/src/NimBLEUtils.h @@ -39,6 +39,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); };