esp-nimble-cpp/src/NimBLEService.cpp
2024-09-30 16:08:51 -06:00

372 lines
12 KiB
C++

/*
* NimBLEService.cpp
*
* Created: on March 2, 2020
* Author H2zero
*
* Originally:
*
* BLEService.cpp
*
* Created on: Mar 25, 2017
* Author: kolban
*/
// A service is identified by a UUID. A service is also the container for one or more characteristics.
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# include "NimBLEDevice.h"
# include "NimBLEService.h"
# include "NimBLEUtils.h"
# include "NimBLELog.h"
# include <string>
static const char* LOG_TAG = "NimBLEService";
/**
* @brief Construct an instance of the NimBLEService
* @param [in] uuid The UUID of the service.
*/
NimBLEService::NimBLEService(const char* uuid) : NimBLEService(NimBLEUUID(uuid)) {}
/**
* @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service.
*/
NimBLEService::NimBLEService(const NimBLEUUID& uuid)
: NimBLELocalAttribute{uuid, 0}, m_pSvcDef{{0, getUUID().getBase(), nullptr, nullptr},{}} {}
/**
* @brief Destructor, make sure we release the resources allocated for the service.
*/
NimBLEService::~NimBLEService() {
if (m_pSvcDef->characteristics) {
if (m_pSvcDef->characteristics->descriptors) {
delete[] m_pSvcDef->characteristics->descriptors;
}
delete[] m_pSvcDef->characteristics;
}
for (const auto& it : m_vChars) {
delete it;
}
} // ~NimBLEService
/**
* @brief Dump details of this BLE GATT service.
*/
void NimBLEService::dump() const {
NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x", getUUID().toString().c_str(), getHandle());
std::string res;
int count = 0;
char hex[5];
for (const auto& it : m_vChars) {
if (count > 0) {
res += "\n";
}
snprintf(hex, sizeof(hex), "%04x", it->getHandle());
count++;
res += "handle: 0x";
res += hex;
res += ", uuid: " + std::string(it->getUUID());
}
NIMBLE_LOGD(LOG_TAG, "Characteristics:\n%s", res.c_str());
} // dump
/**
* @brief Builds the database of characteristics/descriptors for the service
* and registers it with the NimBLE stack.
* @return bool success/failure .
*/
bool NimBLEService::start() {
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
// Rebuild the service definition if the server attributes have changed.
if (getServer()->m_svcChanged) {
if (m_pSvcDef->characteristics) {
if (m_pSvcDef->characteristics->descriptors) {
delete[] m_pSvcDef->characteristics->descriptors;
}
delete[] m_pSvcDef->characteristics;
}
m_pSvcDef->type = 0;
}
if (!m_pSvcDef->type) {
m_pSvcDef->type = BLE_GATT_SVC_TYPE_PRIMARY;
size_t numChrs = 0;
for (const auto& chr : m_vChars) {
if (chr->getRemoved()) {
continue;
}
++numChrs;
}
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str());
if (numChrs) {
int i = 0;
// Nimble requires the last characteristic to have it's uuid = 0 to indicate the end
// of the characteristics for the service. We create 1 extra and set it to null
// for this purpose.
ble_gatt_chr_def* pChrs = new ble_gatt_chr_def[numChrs + 1]{};
for (const auto& chr : m_vChars) {
if (chr->getRemoved()) {
continue;
}
size_t numDscs = 0;
for (const auto& dsc : chr->m_vDescriptors) {
if (dsc->getRemoved()) {
continue;
}
++numDscs;
}
if (numDscs) {
int j = 0;
// Must have last descriptor uuid = 0 so we have to create 1 extra
ble_gatt_dsc_def* pDscs = new ble_gatt_dsc_def[numDscs + 1]{};
for (const auto& dsc : chr->m_vDescriptors) {
if (dsc->getRemoved()) {
continue;
}
pDscs[j].uuid = dsc->getUUID().getBase();
pDscs[j].att_flags = dsc->getProperties();
pDscs[j].min_key_size = 0;
pDscs[j].access_cb = NimBLEServer::handleGattEvent;
pDscs[j].arg = dsc;
++j;
}
pChrs[i].descriptors = pDscs;
}
pChrs[i].uuid = chr->getUUID().getBase();
pChrs[i].access_cb = NimBLEServer::handleGattEvent;
pChrs[i].arg = chr;
pChrs[i].flags = chr->getProperties();
pChrs[i].min_key_size = 0;
pChrs[i].val_handle = &chr->m_handle;
++i;
}
m_pSvcDef->characteristics = pChrs;
}
}
int rc = ble_gatts_count_cfg(m_pSvcDef);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
rc = ble_gatts_add_svcs(m_pSvcDef);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
NIMBLE_LOGD(LOG_TAG, "<< start()");
return true;
} // start
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
* @param [in] properties - The properties of the characteristic.
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold.
* @return The new BLE characteristic.
*/
NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties, uint16_t max_len) {
return createCharacteristic(NimBLEUUID(uuid), properties, max_len);
} // createCharacteristic
/**
* @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic.
* @param [in] properties - The properties of the characteristic.
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold.
* @return The new BLE characteristic.
*/
NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) {
NimBLECharacteristic* pChar = new NimBLECharacteristic(uuid, properties, max_len, this);
if (getCharacteristic(uuid) != nullptr) {
NIMBLE_LOGD(LOG_TAG, "Adding a duplicate characteristic with UUID: %s", std::string(uuid).c_str());
}
addCharacteristic(pChar);
return pChar;
} // createCharacteristic
/**
* @brief Add a characteristic to the service.
* @param[in] pChar A pointer to the characteristic instance to add to the service.
*/
void NimBLEService::addCharacteristic(NimBLECharacteristic* pChar) {
bool foundRemoved = false;
if (pChar->getRemoved() > 0) {
for (const auto& chr : m_vChars) {
if (chr == pChar) {
foundRemoved = true;
pChar->setRemoved(0);
}
}
}
// Check if the characteristic is already in the service and if so, return.
for (const auto& chr : m_vChars) {
if (chr == pChar) {
pChar->setService(this); // Update the service pointer in the characteristic.
return;
}
}
if (!foundRemoved) {
m_vChars.push_back(pChar);
}
pChar->setService(this);
getServer()->serviceChanged();
} // addCharacteristic
/**
* @brief Remove a characteristic from the service.
* @param[in] pChar A pointer to the characteristic instance to remove from the service.
* @param[in] deleteChr If true it will delete the characteristic instance and free it's resources.
*/
void NimBLEService::removeCharacteristic(NimBLECharacteristic* pChar, bool deleteChr) {
// Check if the characteristic was already removed and if so, check if this
// is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return.
if (pChar->getRemoved() > 0) {
if (deleteChr) {
for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) {
if ((*it) == pChar) {
delete (*it);
m_vChars.erase(it);
break;
}
}
}
return;
}
pChar->setRemoved(deleteChr ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
getServer()->serviceChanged();
} // removeCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param idx The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid, uint16_t idx) const {
return getCharacteristic(NimBLEUUID(uuid), idx);
} // getCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param idx The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const NimBLEUUID& uuid, uint16_t idx) const {
uint16_t position = 0;
for (const auto& chr : m_vChars) {
if (chr->getUUID() == uuid) {
if (position == idx) {
return chr;
}
position++;
}
}
return nullptr;
} // getCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified handle.
* @param handle The handle of the characteristic.
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristicByHandle(uint16_t handle) const {
for (const auto& chr : m_vChars) {
if (chr->getHandle() == handle) {
return chr;
}
}
return nullptr;
} // getCharacteristicByHandle
/**
* @return A vector containing pointers to each characteristic associated with this service.
*/
const std::vector<NimBLECharacteristic*>& NimBLEService::getCharacteristics() const {
return m_vChars;
} // getCharacteristics
/**
* @return A vector containing pointers to each characteristic with the provided UUID associated with this service.
*/
std::vector<NimBLECharacteristic*> NimBLEService::getCharacteristics(const char* uuid) const {
return getCharacteristics(NimBLEUUID(uuid));
} // getCharacteristics
/**
* @return A vector containing pointers to each characteristic with the provided UUID associated with this service.
*/
std::vector<NimBLECharacteristic*> NimBLEService::getCharacteristics(const NimBLEUUID& uuid) const {
std::vector<NimBLECharacteristic*> result;
for (const auto& chr : m_vChars) {
if (chr->getUUID() == uuid) {
result.push_back(chr);
}
}
return result;
} // getCharacteristics
/**
* @brief Return a string representation of this service.
* A service is defined by:
* * Its UUID
* * Its handle
* @return A string representation of this service.
*/
std::string NimBLEService::toString() const {
std::string res = "UUID: " + getUUID().toString();
char hex[5];
snprintf(hex, sizeof(hex), "%04x", getHandle());
res += ", handle: 0x";
res += hex;
return res;
} // toString
/**
* @brief Get the BLE server associated with this service.
* @return The BLEServer associated with this service.
*/
NimBLEServer* NimBLEService::getServer() const {
return NimBLEDevice::getServer();
} // getServer
/**
* @brief Checks if the service has been started.
* @return True if the service has been started.
*/
bool NimBLEService::isStarted() const {
return m_pSvcDef->type > 0;
} // isStarted
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */