2024-07-26 14:47:36 -06:00
|
|
|
/*
|
2024-12-12 19:21:03 -07:00
|
|
|
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
|
|
|
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
2024-07-26 14:47:36 -06:00
|
|
|
*
|
2024-12-12 19:21:03 -07:00
|
|
|
* Licensed 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.
|
2024-07-26 14:47:36 -06:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "nimconfig.h"
|
|
|
|
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
|
|
|
|
|
|
|
|
# include "NimBLERemoteValueAttribute.h"
|
|
|
|
# include "NimBLEClient.h"
|
2024-11-02 19:00:07 -06:00
|
|
|
# include "NimBLEUtils.h"
|
2025-01-03 13:49:29 -05:00
|
|
|
# include "NimBLELog.h"
|
2024-07-26 14:47:36 -06:00
|
|
|
|
|
|
|
# include <climits>
|
|
|
|
|
2024-11-29 14:44:58 -07:00
|
|
|
static const char* LOG_TAG = "NimBLERemoteValueAttribute";
|
2024-07-26 14:47:36 -06:00
|
|
|
|
|
|
|
bool NimBLERemoteValueAttribute::writeValue(const uint8_t* data, size_t length, bool response) const {
|
|
|
|
NIMBLE_LOGD(LOG_TAG, ">> writeValue()");
|
|
|
|
|
|
|
|
const NimBLEClient* pClient = getClient();
|
|
|
|
int retryCount = 1;
|
|
|
|
int rc = 0;
|
|
|
|
uint16_t mtu = pClient->getMTU() - 3;
|
2024-11-10 13:31:37 -07:00
|
|
|
NimBLETaskData taskData(const_cast<NimBLERemoteValueAttribute*>(this));
|
2024-07-26 14:47:36 -06:00
|
|
|
|
|
|
|
// Check if the data length is longer than we can write in one connection event.
|
|
|
|
// If so we must do a long write which requires a response.
|
|
|
|
if (length <= mtu && !response) {
|
2024-11-02 19:00:07 -06:00
|
|
|
rc = ble_gattc_write_no_rsp_flat(pClient->getConnHandle(), getHandle(), data, length);
|
2024-07-26 14:47:36 -06:00
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (length > mtu) {
|
|
|
|
NIMBLE_LOGI(LOG_TAG, "writeValue: long write");
|
|
|
|
os_mbuf* om = ble_hs_mbuf_from_flat(data, length);
|
2024-11-02 19:00:07 -06:00
|
|
|
rc = ble_gattc_write_long(pClient->getConnHandle(), getHandle(), 0, om, NimBLERemoteValueAttribute::onWriteCB, &taskData);
|
2024-07-26 14:47:36 -06:00
|
|
|
} else {
|
2024-11-02 19:00:07 -06:00
|
|
|
rc = ble_gattc_write_flat(pClient->getConnHandle(),
|
2024-07-26 14:47:36 -06:00
|
|
|
getHandle(),
|
|
|
|
data,
|
|
|
|
length,
|
|
|
|
NimBLERemoteValueAttribute::onWriteCB,
|
|
|
|
&taskData);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
|
2024-11-10 13:31:37 -07:00
|
|
|
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
|
|
|
|
rc = taskData.m_flags;
|
2024-07-26 14:47:36 -06:00
|
|
|
switch (rc) {
|
|
|
|
case 0:
|
|
|
|
case BLE_HS_EDONE:
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
|
|
|
|
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
|
|
|
|
retryCount++;
|
|
|
|
length = mtu;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
|
|
|
|
if (retryCount && pClient->secureConnection()) break;
|
|
|
|
/* Else falls through. */
|
|
|
|
default:
|
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
} while (rc != 0 && retryCount--);
|
|
|
|
|
|
|
|
Done:
|
|
|
|
if (rc != 0) {
|
|
|
|
NIMBLE_LOGE(LOG_TAG, "<< writeValue failed, rc: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
|
|
|
|
} else {
|
2024-11-10 13:31:37 -07:00
|
|
|
NIMBLE_LOGD(LOG_TAG, "<< writeValue");
|
2024-07-26 14:47:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return (rc == 0);
|
|
|
|
} // writeValue
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Callback for characteristic write operation.
|
|
|
|
* @return success == 0 or error code.
|
|
|
|
*/
|
|
|
|
int NimBLERemoteValueAttribute::onWriteCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg) {
|
2024-11-10 13:31:37 -07:00
|
|
|
auto pTaskData = static_cast<NimBLETaskData*>(arg);
|
|
|
|
const auto pAtt = static_cast<NimBLERemoteValueAttribute*>(pTaskData->m_pInstance);
|
2024-07-26 14:47:36 -06:00
|
|
|
|
2024-11-14 15:55:09 -07:00
|
|
|
if (error->status == BLE_HS_ENOTCONN) {
|
|
|
|
NIMBLE_LOGE(LOG_TAG, "<< Write complete; Not connected");
|
|
|
|
NimBLEUtils::taskRelease(*pTaskData, error->status);
|
|
|
|
return error->status;
|
|
|
|
}
|
|
|
|
|
2024-11-02 19:00:07 -06:00
|
|
|
if (pAtt->getClient()->getConnHandle() != conn_handle) {
|
2024-07-26 14:47:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d", error->status);
|
2024-11-10 13:31:37 -07:00
|
|
|
NimBLEUtils::taskRelease(*pTaskData, error->status);
|
2024-07-26 14:47:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Read the value of the remote characteristic.
|
|
|
|
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
|
|
|
|
* @return The value of the remote characteristic.
|
|
|
|
*/
|
|
|
|
NimBLEAttValue NimBLERemoteValueAttribute::readValue(time_t* timestamp) const {
|
|
|
|
NIMBLE_LOGD(LOG_TAG, ">> readValue()");
|
|
|
|
|
|
|
|
NimBLEAttValue value{};
|
|
|
|
const NimBLEClient* pClient = getClient();
|
|
|
|
int rc = 0;
|
|
|
|
int retryCount = 1;
|
2024-11-10 13:31:37 -07:00
|
|
|
NimBLETaskData taskData(const_cast<NimBLERemoteValueAttribute*>(this), 0, &value);
|
2024-07-26 14:47:36 -06:00
|
|
|
|
|
|
|
do {
|
2024-11-02 19:00:07 -06:00
|
|
|
rc = ble_gattc_read_long(pClient->getConnHandle(), getHandle(), 0, NimBLERemoteValueAttribute::onReadCB, &taskData);
|
2024-07-26 14:47:36 -06:00
|
|
|
if (rc != 0) {
|
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
|
2024-11-10 13:31:37 -07:00
|
|
|
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
|
|
|
|
rc = taskData.m_flags;
|
2024-07-26 14:47:36 -06:00
|
|
|
switch (rc) {
|
|
|
|
case 0:
|
|
|
|
case BLE_HS_EDONE:
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
// Characteristic is not long-readable, return with what we have.
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
|
|
|
|
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
|
2024-11-02 19:00:07 -06:00
|
|
|
rc = ble_gattc_read(pClient->getConnHandle(), getHandle(), NimBLERemoteValueAttribute::onReadCB, &taskData);
|
2024-07-26 14:47:36 -06:00
|
|
|
if (rc != 0) {
|
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
retryCount++;
|
|
|
|
break;
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
|
|
|
|
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
|
|
|
|
if (retryCount && pClient->secureConnection()) break;
|
|
|
|
/* Else falls through. */
|
|
|
|
default:
|
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
} while (rc != 0 && retryCount--);
|
|
|
|
|
|
|
|
value.setTimeStamp();
|
|
|
|
m_value = value;
|
|
|
|
if (timestamp != nullptr) {
|
|
|
|
*timestamp = value.getTimeStamp();
|
|
|
|
}
|
|
|
|
|
|
|
|
Done:
|
|
|
|
if (rc != 0) {
|
|
|
|
NIMBLE_LOGE(LOG_TAG, "<< readValue failed rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
|
|
|
|
} else {
|
2024-11-10 13:31:37 -07:00
|
|
|
NIMBLE_LOGD(LOG_TAG, "<< readValue");
|
2024-07-26 14:47:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
} // readValue
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Callback for characteristic read operation.
|
|
|
|
* @return success == 0 or error code.
|
|
|
|
*/
|
|
|
|
int NimBLERemoteValueAttribute::onReadCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg) {
|
2024-11-10 13:31:37 -07:00
|
|
|
auto pTaskData = static_cast<NimBLETaskData*>(arg);
|
|
|
|
const auto pAtt = static_cast<NimBLERemoteValueAttribute*>(pTaskData->m_pInstance);
|
2024-07-26 14:47:36 -06:00
|
|
|
|
2024-11-14 15:55:09 -07:00
|
|
|
if (error->status == BLE_HS_ENOTCONN) {
|
|
|
|
NIMBLE_LOGE(LOG_TAG, "<< Read complete; Not connected");
|
|
|
|
NimBLEUtils::taskRelease(*pTaskData, error->status);
|
|
|
|
return error->status;
|
|
|
|
}
|
|
|
|
|
2024-11-02 19:00:07 -06:00
|
|
|
if (pAtt->getClient()->getConnHandle() != conn_handle) {
|
2024-07-26 14:47:36 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rc = error->status;
|
|
|
|
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d", rc);
|
|
|
|
|
|
|
|
if (rc == 0) {
|
|
|
|
if (attr) {
|
2024-11-10 13:31:37 -07:00
|
|
|
auto valBuf = static_cast<NimBLEAttValue*>(pTaskData->m_pBuf);
|
2024-07-26 14:47:36 -06:00
|
|
|
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
|
|
|
|
if ((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
|
|
|
|
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
|
|
|
} else {
|
|
|
|
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
|
|
|
|
valBuf->append(attr->om->om_data, data_len);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-10 13:31:37 -07:00
|
|
|
NimBLEUtils::taskRelease(*pTaskData, rc);
|
2024-07-26 14:47:36 -06:00
|
|
|
return rc;
|
|
|
|
} // onReadCB
|
|
|
|
|
|
|
|
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL
|