Merge pull request #10600 from btsimonh/BLE_ESP32_FINAL

Ble esp32 final
This commit is contained in:
Theo Arends 2021-01-18 11:18:27 +01:00 committed by GitHub
commit ff6c328ba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 8913 additions and 407 deletions

View File

@ -2,6 +2,75 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
- `NimBLEDevice::setOwnAddrType` added to enable the use of random and random-resolvable addresses, by asukiaaa
- New examples for securing and authenticating client/server connections, by mblasee.
- `NimBLEAdvertiseing::SetMinPreferred` and `NimBLEAdvertiseing::SetMinPreferred` re-added.
- Conditional checks added for command line config options in `nimconfig.h` to support custom configuration in platformio.
- `NimBLEClient::setValue` Now takes an extra bool parameter `response` to enable the use of write with response (default = false).
- `NimBLEClient::getCharacteristic(uint16_t handle)` Enabling the use of the characteristic handle to be used to find
the NimBLERemoteCharacteristic object.
- `NimBLEHIDDevice` class added by wakwak-koba.
- `NimBLEServerCallbacks::onDisconnect` overloaded callback added to provide a ble_gap_conn_desc parameter for the application
to obtain information about the disconnected client.
- Conditional checks in `nimconfig.h` for command line defined macros to support platformio config settings.
### Changed
- `NimBLEAdvertising::start` now returns a bool value to indicate success/failure.
- Some asserts were removed in `NimBLEAdvertising::start` and replaced with better return code handling and logging.
- If a host reset event occurs, scanning and advertising will now only be restarted if their previous duration was indefinite.
- `NimBLERemoteCharacteristic::subscribe` and `NimBLERemoteCharacteristic::registerForNotify` will now set the callback
regardless of the existance of the CCCD and return true unless the descriptor write operation failed.
- Advertising tx power level is now sent in the advertisement packet instead of scan response.
- `NimBLEScan` When the scan ends the scan stopped flag is now set before calling the scan complete callback (if used)
this allows the starting of a new scan from the callback function.
### Fixed
- Sometimes `NimBLEClient::connect` would hang on the task block if no event arrived to unblock.
A time limit has been added to timeout appropriately.
- When getting descriptors for a characterisic the end handle of the service was used as a proxy for the characteristic end
handle. This would be rejected by some devices and has been changed to use the next characteristic handle as the end when possible.
- An exception could occur when deleting a client instance if a notification arrived while the attribute vectors were being
deleted. A flag has been added to prevent this.
- An exception could occur after a host reset event when the host re-synced if the tasks that were stopped during the event did
not finish processing. A yield has been added after re-syncing to allow tasks to finish before proceeding.
- Occasionally the controller would fail to send a disconnected event causing the client to indicate it is connected
and would be unable to reconnect. A timer has been added to reset the host/controller if it expires.
- Occasionally the call to start scanning would get stuck in a loop on BLE_HS_EBUSY, this loop has been removed.
- 16bit and 32bit UUID's in some cases were not discovered or compared correctly if the device
advertised them as 16/32bit but resolved them to 128bits. Both are now checked.
- `FreeRTOS` compile errors resolved in latest Ardruino core and IDF v3.3.
- Multiple instances of `time()` called inside critical sections caused sporadic crashes, these have been moved out of critical regions.
- Advertisement type now correctly set when using non-connectable (advertiser only) mode.
- Advertising payload length correction, now accounts for appearance.
- (Arduino) Ensure controller mode is set to BLE Only.
## [1.0.2] - 2020-09-13 ## [1.0.2] - 2020-09-13
### Changed ### Changed

View File

@ -68,9 +68,9 @@ such as increasing max connections, default is 3, absolute maximum connections i
<br/> <br/>
# Development Status # Development Status
This Library is tracking the esp-nimble repo, nimble-1.2.0-idf master branch, currently [@95bd864.](https://github.com/espressif/esp-nimble) This Library is tracking the esp-nimble repo, nimble-1.2.0-idf master branch, currently [@f4ae049.](https://github.com/espressif/esp-nimble)
Also tracking the NimBLE related changes in ESP-IDF, master branch, currently [@2ef4890.](https://github.com/espressif/esp-idf/tree/master/components/bt/host/nimble) Also tracking the NimBLE related changes in ESP-IDF, master branch, currently [@3caa969.](https://github.com/espressif/esp-idf/tree/master/components/bt/host/nimble)
<br/> <br/>
# Acknowledgments # Acknowledgments

View File

@ -0,0 +1,93 @@
# Arduino command line and platformio config options
`CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED`
If defined, NimBLE Client functions will not be included.
- Reduces flash size by approx. 7kB.
<br>
`CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED`
If defined, NimBLE Scan functions will not be included.
- Reduces flash size by approx. 26kB.
<br>
`CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED`
If defined NimBLE Server functions will not be included.
- Reduces flash size by approx. 16kB.
<br>
`CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED`
If defined, NimBLE Advertising functions will not be included.
- Reduces flash size by approx. 5kB.
<br>
`CONFIG_BT_NIMBLE_DEBUG`
If defined, enables debug log messages from the NimBLE host
- Uses approx. 32kB of flash memory.
<br>
`CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT`
If defined, NimBLE host return codes will be printed as text in debug log messages.
- Uses approx. 7kB of flash memory.
<br>
`CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT`
If defined, GAP event codes will be printed as text in debug log messages.
- Uses approx. 1kB of flash memory.
<br>
`CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT`
If defined, advertisment types will be printed as text while scanning in debug log messages.
- Uses approx. 250 bytes of flash memory.
<br>
`CONFIG_BT_NIMBLE_PINNED_TO_CORE`
Sets the core the NimBLE host stack will run on
- Options: 0 or 1
<br>
`CONFIG_BT_NIMBLE_TASK_STACK_SIZE`
Set the task stack size for the NimBLE core.
- Default is 4096
<br>
`CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL`
Sets the NimBLE stack to use external PSRAM will be loaded
- Must be defined with a value of 1; Default is CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1
<br>
`CONFIG_BT_NIMBLE_MAX_CONNECTIONS`
Sets the number of simultaneous connections (esp controller max is 9)
- Default value is 3
<br>
`CONFIG_BT_NIMBLE_MAX_BONDS`
Sets the number of devices allowed to store/bond with
- Default value is 3
<br>
`CONFIG_BT_NIMBLE_MAX_CCCDS`
Sets the maximum number of CCCD subscriptions to store
- Default value is 8
<br>
`CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME`
Set the default device name
- Default value is "nimble"
<br>

View File

@ -0,0 +1,91 @@
/** NimBLE_Secure_Client Demo:
*
* This example demonstrates the secure passkey protected conenction and communication between an esp32 server and an esp32 client.
* Please note that esp32 stores auth info in nvs memory. After a successful connection it is possible that a passkey change will be ineffective.
* To avoid this clear the memory of the esp32's between security testings. esptool.py is capable of this, example: esptool.py --port /dev/ttyUSB0 erase_flash.
*
* Created: on Jan 08 2021
* Author: mblasee
*/
#include <NimBLEDevice.h>
class ClientCallbacks : public NimBLEClientCallbacks
{
uint32_t onPassKeyRequest()
{
Serial.println("Client Passkey Request");
/** return the passkey to send to the server */
/** Change this to be different from NimBLE_Secure_Server if you want to test what happens on key mismatch */
return 123456;
};
};
static ClientCallbacks clientCB;
void setup()
{
Serial.begin(115200);
Serial.println("Starting NimBLE Client");
NimBLEDevice::init("");
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY);
NimBLEScan *pScan = NimBLEDevice::getScan();
NimBLEScanResults results = pScan->start(5);
NimBLEUUID serviceUuid("ABCD");
for (int i = 0; i < results.getCount(); i++)
{
NimBLEAdvertisedDevice device = results.getDevice(i);
Serial.println(device.getName().c_str());
if (device.isAdvertisingService(serviceUuid))
{
NimBLEClient *pClient = NimBLEDevice::createClient();
pClient->setClientCallbacks(&clientCB, false);
if (pClient->connect(&device))
{
pClient->secureConnection();
NimBLERemoteService *pService = pClient->getService(serviceUuid);
if (pService != nullptr)
{
NimBLERemoteCharacteristic *pNonSecureCharacteristic = pService->getCharacteristic("1234");
if (pNonSecureCharacteristic != nullptr)
{
// Testing to read a non secured characteristic, you should be able to read this even if you have mismatching passkeys.
std::string value = pNonSecureCharacteristic->readValue();
// print or do whatever you need with the value
Serial.println(value.c_str());
}
NimBLERemoteCharacteristic *pSecureCharacteristic = pService->getCharacteristic("1235");
if (pSecureCharacteristic != nullptr)
{
// Testing to read a secured characteristic, you should be able to read this only if you have matching passkeys, otherwise you should
// get an error like this. E NimBLERemoteCharacteristic: "<< readValue rc=261"
// This means you are trying to do something without the proper permissions.
std::string value = pSecureCharacteristic->readValue();
// print or do whatever you need with the value
Serial.println(value.c_str());
}
}
}
else
{
// failed to connect
Serial.println("failed to connect");
}
NimBLEDevice::deleteClient(pClient);
}
}
}
void loop()
{
}

View File

@ -0,0 +1,37 @@
/** NimBLE_Secure_Server Demo:
*
* This example demonstrates the secure passkey protected conenction and communication between an esp32 server and an esp32 client.
* Please note that esp32 stores auth info in nvs memory. After a successful connection it is possible that a passkey change will be ineffective.
* To avoid this clear the memory of the esp32's between security testings. esptool.py is capable of this, example: esptool.py --port /dev/ttyUSB0 erase_flash.
*
* Created: on Jan 08 2021
* Author: mblasee
*/
#include <NimBLEDevice.h>
void setup() {
Serial.begin(115200);
Serial.println("Starting NimBLE Server");
NimBLEDevice::init("NimBLE");
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityPasskey(123456);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
NimBLEServer *pServer = NimBLEDevice::createServer();
NimBLEService *pService = pServer->createService("ABCD");
NimBLECharacteristic *pNonSecureCharacteristic = pService->createCharacteristic("1234", NIMBLE_PROPERTY::READ );
NimBLECharacteristic *pSecureCharacteristic = pService->createCharacteristic("1235", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN);
pService->start();
pNonSecureCharacteristic->setValue("Hello Non Secure BLE");
pSecureCharacteristic->setValue("Hello Secure BLE");
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID("ABCD");
pAdvertising->start();
}
void loop() {
}

View File

@ -116,9 +116,9 @@ void setup() {
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false); pAdvertising->setScanResponse(false);
/**This method is removed as it was no longer useful and consumed advertising space /** Note, this could be left out as that is the default value */
* pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
*/
BLEDevice::startAdvertising(); BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify..."); Serial.println("Waiting a client connection to notify...");
} }

View File

@ -44,10 +44,9 @@ void setup() {
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true); pAdvertising->setScanResponse(true);
/**These methods are removed as they are no longer useful and consumed advertising space pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
* pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue pAdvertising->setMaxPreferred(0x12);
* pAdvertising->setMinPreferred(0x12);
*/
BLEDevice::startAdvertising(); BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!"); Serial.println("Characteristic defined! Now you can read it in your phone!");
} }

View File

@ -120,9 +120,9 @@ void setup() {
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false); pAdvertising->setScanResponse(false);
/**This method is removed it was no longer useful and consumed advertising space /** Note, this could be left out as that is the default value */
* pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
*/
BLEDevice::startAdvertising(); BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify..."); Serial.println("Waiting a client connection to notify...");
} }

View File

@ -264,10 +264,14 @@ void FreeRTOS::Semaphore::setName(std::string name) {
* @param [in] length The amount of storage to allocate for the ring buffer. * @param [in] length The amount of storage to allocate for the ring buffer.
* @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF. * @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF.
*/ */
#if defined(ESP_IDF_VERSION) && !defined(ESP_IDF_VERSION_VAL) //Quick hack to detect if using IDF version that replaced ringbuf_type_t, ESP_IDF_VERSION_VAL is for IDF>4.0.0 #ifdef ESP_IDF_VERSION //Quick hack to detect if using IDF version that replaced ringbuf_type_t
Ringbuffer::Ringbuffer(size_t length, RingbufferType_t type) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
Ringbuffer::Ringbuffer(size_t length, RingbufferType_t type) {
#else #else
Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) { Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
#endif
#else
Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
#endif #endif
m_handle = ::xRingbufferCreate(length, type); m_handle = ::xRingbufferCreate(length, type);
} // Ringbuffer } // Ringbuffer

View File

@ -68,8 +68,12 @@ public:
*/ */
class Ringbuffer { class Ringbuffer {
public: public:
#if defined(ESP_IDF_VERSION) && !defined(ESP_IDF_VERSION_VAL) //Quick hack to detect if using IDF version that replaced ringbuf_type_t, ESP_IDF_VERSION_VAL is for IDF>4.0.0 #ifdef ESP_IDF_VERSION //Quick hack to detect if using IDF version that replaced ringbuf_type_t
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
Ringbuffer(size_t length, RingbufferType_t type = RINGBUF_TYPE_NOSPLIT); Ringbuffer(size_t length, RingbufferType_t type = RINGBUF_TYPE_NOSPLIT);
#else
Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT);
#endif
#else #else
Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT); Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT);
#endif #endif

View File

@ -37,7 +37,7 @@ NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacterisitic)
m_data.m_unit = 0; m_data.m_unit = 0;
m_data.m_description = 0; m_data.m_description = 0;
setValue((uint8_t*) &m_data, sizeof(m_data)); setValue((uint8_t*) &m_data, sizeof(m_data));
} // BLE2902 } // BLE2904
/** /**

View File

@ -32,7 +32,7 @@ static const char* LOG_TAG = "NimBLEAdvertising";
/** /**
* @brief Construct a default advertising object. * @brief Construct a default advertising object.
*/ */
NimBLEAdvertising::NimBLEAdvertising() { NimBLEAdvertising::NimBLEAdvertising() : m_slaveItvl() {
memset(&m_advData, 0, sizeof m_advData); memset(&m_advData, 0, sizeof m_advData);
memset(&m_scanData, 0, sizeof m_scanData); memset(&m_scanData, 0, sizeof m_scanData);
memset(&m_advParams, 0, sizeof m_advParams); memset(&m_advParams, 0, sizeof m_advParams);
@ -41,15 +41,20 @@ NimBLEAdvertising::NimBLEAdvertising() {
m_advData.name = (uint8_t *)name; m_advData.name = (uint8_t *)name;
m_advData.name_len = strlen(name); m_advData.name_len = strlen(name);
m_advData.name_is_complete = 1; m_advData.name_is_complete = 1;
m_scanData.tx_pwr_lvl_is_present = 1; m_advData.tx_pwr_lvl_is_present = 1;
m_scanData.tx_pwr_lvl = NimBLEDevice::getPower(); m_advData.tx_pwr_lvl = NimBLEDevice::getPower();
m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
m_advData.appearance = 0; m_advData.appearance = 0;
m_advData.appearance_is_present = 0; m_advData.appearance_is_present = 0;
m_advData.mfg_data_len = 0; m_advData.mfg_data_len = 0;
m_advData.mfg_data = nullptr; m_advData.mfg_data = nullptr;
m_advData.slave_itvl_range = nullptr;
#if !defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
m_advParams.conn_mode = BLE_GAP_CONN_MODE_NON;
#else
m_advParams.conn_mode = BLE_GAP_CONN_MODE_UND; m_advParams.conn_mode = BLE_GAP_CONN_MODE_UND;
#endif
m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN; m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN;
m_advParams.itvl_min = 0; m_advParams.itvl_min = 0;
m_advParams.itvl_max = 0; m_advParams.itvl_max = 0;
@ -58,6 +63,8 @@ NimBLEAdvertising::NimBLEAdvertising() {
m_customScanResponseData = false; m_customScanResponseData = false;
m_scanResp = true; m_scanResp = true;
m_advDataSet = false; m_advDataSet = false;
// Set this to non-zero to prevent auto start if host reset before started by app.
m_duration = BLE_HS_FOREVER;
} // NimBLEAdvertising } // NimBLEAdvertising
@ -86,7 +93,6 @@ void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) {
* @param [in] serviceUUID The UUID of the service to expose. * @param [in] serviceUUID The UUID of the service to expose.
*/ */
void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) { void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) {
//m_serviceUUIDs.erase(std::remove_if(m_serviceUUIDs.begin(), m_serviceUUIDs.end(),[serviceUUID](const NimBLEUUID &s) {return serviceUUID == s;}), m_serviceUUIDs.end());
for(auto it = m_serviceUUIDs.begin(); it != m_serviceUUIDs.end(); ++it) { for(auto it = m_serviceUUIDs.begin(); it != m_serviceUUIDs.end(); ++it) {
if((*it) == serviceUUID) { if((*it) == serviceUUID) {
m_serviceUUIDs.erase(it); m_serviceUUIDs.erase(it);
@ -112,11 +118,9 @@ void NimBLEAdvertising::setAppearance(uint16_t appearance) {
/** /**
* @brief Set the type of advertisment to use. * @brief Set the type of advertisment to use.
* @param [in] adv_type: * @param [in] adv_type:
* * BLE_HCI_ADV_TYPE_ADV_IND (0) - indirect advertising * * BLE_GAP_CONN_MODE_NON (0) - not connectable advertising
* * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD (1) - direct advertisng - high duty cycle * * BLE_GAP_CONN_MODE_DIR (1) - directed connectable advertising
* * BLE_HCI_ADV_TYPE_ADV_SCAN_IND (2) - indirect scan response * * BLE_GAP_CONN_MODE_UND (2) - undirected connectable advertising
* * BLE_HCI_ADV_TYPE_ADV_NONCONN_IND (3) - indirect advertisng - not connectable
* * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD (4) - direct advertising - low duty cycle
*/ */
void NimBLEAdvertising::setAdvertisementType(uint8_t adv_type){ void NimBLEAdvertising::setAdvertisementType(uint8_t adv_type){
m_advParams.conn_mode = adv_type; m_advParams.conn_mode = adv_type;
@ -141,6 +145,64 @@ void NimBLEAdvertising::setMaxInterval(uint16_t maxinterval) {
} // setMaxInterval } // setMaxInterval
/**
* @brief Set the advertised min connection interval preferred by this device.
* @param [in] mininterval the max interval value. Range = 0x0006 to 0x0C80.
* @details Values not within the range will cancel advertising of this data.\n
* Consumes 6 bytes of advertising space (combined with max interval).
*/
void NimBLEAdvertising::setMinPreferred(uint16_t mininterval) {
// invalid paramters, set the slave interval to null
if(mininterval < 0x0006 || mininterval > 0x0C80) {
m_advData.slave_itvl_range = nullptr;
return;
}
if(m_advData.slave_itvl_range == nullptr) {
m_advData.slave_itvl_range = m_slaveItvl;
}
m_slaveItvl[0] = mininterval;
m_slaveItvl[1] = mininterval >> 8;
uint16_t maxinterval = *(uint16_t*)(m_advData.slave_itvl_range+2);
// If mininterval is higher than the maxinterval make them the same
if(mininterval > maxinterval) {
m_slaveItvl[2] = m_slaveItvl[0];
m_slaveItvl[3] = m_slaveItvl[1];
}
} // setMinPreferred
/**
* @brief Set the advertised max connection interval preferred by this device.
* @param [in] maxinterval the max interval value. Range = 0x0006 to 0x0C80.
* @details Values not within the range will cancel advertising of this data.\n
* Consumes 6 bytes of advertising space (combined with min interval).
*/
void NimBLEAdvertising::setMaxPreferred(uint16_t maxinterval) {
// invalid paramters, set the slave interval to null
if(maxinterval < 0x0006 || maxinterval > 0x0C80) {
m_advData.slave_itvl_range = nullptr;
return;
}
if(m_advData.slave_itvl_range == nullptr) {
m_advData.slave_itvl_range = m_slaveItvl;
}
m_slaveItvl[2] = maxinterval;
m_slaveItvl[3] = maxinterval >> 8;
uint16_t mininterval = *(uint16_t*)(m_advData.slave_itvl_range);
// If mininterval is higher than the maxinterval make them the same
if(mininterval > maxinterval) {
m_slaveItvl[0] = m_slaveItvl[2];
m_slaveItvl[1] = m_slaveItvl[3];
}
} // setMaxPreferred
/** /**
* @brief Set if scan response is available. * @brief Set if scan response is available.
* @param [in] set true = scan response available. * @param [in] set true = scan response available.
@ -156,7 +218,8 @@ void NimBLEAdvertising::setScanResponse(bool set) {
* @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list.
*/ */
void NimBLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { void NimBLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) {
NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d",
scanRequestWhitelistOnly, connectWhitelistOnly);
if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { if (!scanRequestWhitelistOnly && !connectWhitelistOnly) {
m_advParams.filter_policy = BLE_HCI_ADV_FILT_NONE; m_advParams.filter_policy = BLE_HCI_ADV_FILT_NONE;
NIMBLE_LOGD(LOG_TAG, "<< setScanFilter"); NIMBLE_LOGD(LOG_TAG, "<< setScanFilter");
@ -194,7 +257,8 @@ void NimBLEAdvertising::setAdvertisementData(NimBLEAdvertisementData& advertisem
(uint8_t*)advertisementData.getPayload().data(), (uint8_t*)advertisementData.getPayload().data(),
advertisementData.getPayload().length()); advertisementData.getPayload().length());
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s",
rc, NimBLEUtils::returnCodeToString(rc));
} }
m_customAdvData = true; // Set the flag that indicates we are using custom advertising data. m_customAdvData = true; // Set the flag that indicates we are using custom advertising data.
NIMBLE_LOGD(LOG_TAG, "<< setAdvertisementData"); NIMBLE_LOGD(LOG_TAG, "<< setAdvertisementData");
@ -213,7 +277,8 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme
(uint8_t*)advertisementData.getPayload().data(), (uint8_t*)advertisementData.getPayload().data(),
advertisementData.getPayload().length()); advertisementData.getPayload().length());
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s",
rc, NimBLEUtils::returnCodeToString(rc));
} }
m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data.
NIMBLE_LOGD(LOG_TAG, "<< setScanResponseData"); NIMBLE_LOGD(LOG_TAG, "<< setScanResponseData");
@ -225,13 +290,14 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme
* @param [in] duration The duration, in seconds, to advertise, 0 == advertise forever. * @param [in] duration The duration, in seconds, to advertise, 0 == advertise forever.
* @param [in] advCompleteCB A pointer to a callback to be invoked when advertising ends. * @param [in] advCompleteCB A pointer to a callback to be invoked when advertising ends.
*/ */
void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) { bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) {
NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d",
m_customAdvData, m_customScanResponseData);
// If Host is not synced we cannot start advertising. // If Host is not synced we cannot start advertising.
if(!NimBLEDevice::m_synced) { if(!NimBLEDevice::m_synced) {
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync."); NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
return; return false;
} }
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
@ -240,17 +306,21 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(!pServer->m_gattsStarted){ if(!pServer->m_gattsStarted){
pServer->start(); pServer->start();
} else if(pServer->getConnectedCount() >= NIMBLE_MAX_CONNECTIONS) { } else if(pServer->getConnectedCount() >= NIMBLE_MAX_CONNECTIONS) {
NIMBLE_LOGW(LOG_TAG, "Max connections reached - not advertising"); NIMBLE_LOGE(LOG_TAG, "Max connections reached - not advertising");
return; return false;
} }
} }
#endif #endif
// If already advertising just return // If already advertising just return
if(ble_gap_adv_active()) { if(ble_gap_adv_active()) {
return; NIMBLE_LOGW(LOG_TAG, "Advertising already active");
return false;
} }
// Save the duration incase of host reset so we can restart with the same params
m_duration = duration;
if(duration == 0){ if(duration == 0){
duration = BLE_HS_FOREVER; duration = BLE_HS_FOREVER;
} }
@ -260,16 +330,31 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
m_advCompCB = advCompleteCB; m_advCompCB = advCompleteCB;
m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN;
m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
if(m_advParams.conn_mode == BLE_GAP_CONN_MODE_NON) {
if(!m_scanResp) {
m_advParams.disc_mode = BLE_GAP_DISC_MODE_NON;
m_advData.flags = BLE_HS_ADV_F_BREDR_UNSUP;
}
}
int rc = 0; int rc = 0;
if (!m_customAdvData && !m_advDataSet) { if (!m_customAdvData && !m_advDataSet) {
//start with 3 bytes for the flags data //start with 3 bytes for the flags data
uint8_t payloadLen = 3; uint8_t payloadLen = (2 + 1);
if(m_advData.appearance_is_present)
payloadLen += (2 + BLE_HS_ADV_APPEARANCE_LEN);
if(m_advData.tx_pwr_lvl_is_present)
payloadLen += (2 + 1);
if(m_advData.slave_itvl_range != nullptr)
payloadLen += (2 + 4);
for(auto &it : m_serviceUUIDs) { for(auto &it : m_serviceUUIDs) {
if(it.getNative()->u.type == BLE_UUID_TYPE_16) { if(it.getNative()->u.type == BLE_UUID_TYPE_16) {
int add = (m_advData.num_uuids16 > 0) ? 2 : 4; int add = (m_advData.num_uuids16 > 0) ? 2 : 4;
if((payloadLen + add) > 31){ if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids16_is_complete = 0; m_advData.uuids16_is_complete = 0;
continue; continue;
} }
@ -278,7 +363,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(nullptr == (m_advData.uuids16 = (ble_uuid16_t*)realloc(m_advData.uuids16, if(nullptr == (m_advData.uuids16 = (ble_uuid16_t*)realloc(m_advData.uuids16,
(m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t)))) (m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t))))
{ {
NIMBLE_LOGE(LOG_TAG, "Error, no mem"); NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort(); abort();
} }
memcpy(&m_advData.uuids16[m_advData.num_uuids16].value, memcpy(&m_advData.uuids16[m_advData.num_uuids16].value,
@ -290,7 +375,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
} }
if(it.getNative()->u.type == BLE_UUID_TYPE_32) { if(it.getNative()->u.type == BLE_UUID_TYPE_32) {
int add = (m_advData.num_uuids32 > 0) ? 4 : 6; int add = (m_advData.num_uuids32 > 0) ? 4 : 6;
if((payloadLen + add) > 31){ if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids32_is_complete = 0; m_advData.uuids32_is_complete = 0;
continue; continue;
} }
@ -299,7 +384,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(nullptr == (m_advData.uuids32 = (ble_uuid32_t*)realloc(m_advData.uuids32, if(nullptr == (m_advData.uuids32 = (ble_uuid32_t*)realloc(m_advData.uuids32,
(m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t)))) (m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t))))
{ {
NIMBLE_LOGE(LOG_TAG, "Error, no mem"); NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort(); abort();
} }
memcpy(&m_advData.uuids32[m_advData.num_uuids32].value, memcpy(&m_advData.uuids32[m_advData.num_uuids32].value,
@ -311,7 +396,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
} }
if(it.getNative()->u.type == BLE_UUID_TYPE_128){ if(it.getNative()->u.type == BLE_UUID_TYPE_128){
int add = (m_advData.num_uuids128 > 0) ? 16 : 18; int add = (m_advData.num_uuids128 > 0) ? 16 : 18;
if((payloadLen + add) > 31){ if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids128_is_complete = 0; m_advData.uuids128_is_complete = 0;
continue; continue;
} }
@ -320,7 +405,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(nullptr == (m_advData.uuids128 = (ble_uuid128_t*)realloc(m_advData.uuids128, if(nullptr == (m_advData.uuids128 = (ble_uuid128_t*)realloc(m_advData.uuids128,
(m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t)))) (m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t))))
{ {
NIMBLE_LOGE(LOG_TAG, "Error, no mem"); NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort(); abort();
} }
memcpy(&m_advData.uuids128[m_advData.num_uuids128].value, memcpy(&m_advData.uuids128[m_advData.num_uuids128].value,
@ -333,54 +418,74 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
} }
// check if there is room for the name, if not put it in scan data // check if there is room for the name, if not put it in scan data
if((payloadLen + m_advData.name_len) > 29) { if((payloadLen + (2 + m_advData.name_len)) > BLE_HS_ADV_MAX_SZ) {
if(m_scanResp){ if(m_scanResp){
m_scanData.name = m_advData.name; m_scanData.name = m_advData.name;
m_scanData.name_len = m_advData.name_len; m_scanData.name_len = m_advData.name_len;
m_scanData.name_is_complete = m_advData.name_is_complete; if(m_scanData.name_len > BLE_HS_ADV_MAX_SZ - 2) {
m_scanData.name_len = BLE_HS_ADV_MAX_SZ - 2;
m_scanData.name_is_complete = 0;
} else {
m_scanData.name_is_complete = 1;
}
m_advData.name = nullptr; m_advData.name = nullptr;
m_advData.name_len = 0; m_advData.name_len = 0;
m_advData.name_is_complete = 0;
} else { } else {
if(m_advData.tx_pwr_lvl_is_present) {
m_advData.tx_pwr_lvl = 0;
m_advData.tx_pwr_lvl_is_present = 0;
payloadLen -= (2 + 1);
}
// if not using scan response just cut the name down // if not using scan response just cut the name down
// leaving 2 bytes for the data specifier. // leaving 2 bytes for the data specifier.
m_advData.name_len = (29 - payloadLen); if(m_advData.name_len > (BLE_HS_ADV_MAX_SZ - payloadLen - 2)) {
m_advData.name_len = (BLE_HS_ADV_MAX_SZ - payloadLen - 2);
m_advData.name_is_complete = 0;
}
} }
m_advData.name_is_complete = 0;
}
if(m_advData.name_len > 0) {
payloadLen += (m_advData.name_len + 2);
} }
if(m_scanResp) { if(m_scanResp) {
// name length + type byte + length byte + tx power type + length + data
if((m_scanData.name_len + 5) > 31) {
// prioritize name data over tx power
m_scanData.tx_pwr_lvl_is_present = 0;
m_scanData.tx_pwr_lvl = 0;
// limit name to 29 to leave room for the data specifiers
if(m_scanData.name_len > 29) {
m_scanData.name_len = 29;
m_scanData.name_is_complete = false;
}
}
rc = ble_gap_adv_rsp_set_fields(&m_scanData); rc = ble_gap_adv_rsp_set_fields(&m_scanData);
if (rc != 0) { switch(rc) {
NIMBLE_LOGC(LOG_TAG, "error setting scan response data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); case 0:
abort(); break;
case BLE_HS_EBUSY:
NIMBLE_LOGE(LOG_TAG, "Already advertising");
break;
case BLE_HS_EMSGSIZE:
NIMBLE_LOGE(LOG_TAG, "Scan data too long");
break;
default:
NIMBLE_LOGE(LOG_TAG, "Error setting scan response data; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
break;
} }
// if not using scan response and there is room,
// put the tx power data into the advertisment
} else if (payloadLen < 29) {
m_advData.tx_pwr_lvl_is_present = 1;
m_advData.tx_pwr_lvl = NimBLEDevice::getPower();
} }
rc = ble_gap_adv_set_fields(&m_advData); if(rc == 0) {
if (rc != 0) { rc = ble_gap_adv_set_fields(&m_advData);
NIMBLE_LOGC(LOG_TAG, "error setting advertisement data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); switch(rc) {
abort(); case 0:
break;
case BLE_HS_EBUSY:
NIMBLE_LOGE(LOG_TAG, "Already advertising");
break;
case BLE_HS_EMSGSIZE:
NIMBLE_LOGE(LOG_TAG, "Advertisement data too long");
break;
default:
NIMBLE_LOGE(LOG_TAG, "Error setting advertisement data; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
break;
}
} }
if(m_advData.num_uuids128 > 0) { if(m_advData.num_uuids128 > 0) {
@ -401,24 +506,54 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
m_advData.num_uuids16 = 0; m_advData.num_uuids16 = 0;
} }
if(rc !=0) {
return false;
}
m_advDataSet = true; m_advDataSet = true;
} }
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
rc = ble_gap_adv_start(0, NULL, duration, rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration,
&m_advParams, &m_advParams,
(pServer != nullptr) ? NimBLEServer::handleGapEvent : NimBLEAdvertising::handleGapEvent, (pServer != nullptr) ? NimBLEServer::handleGapEvent :
NimBLEAdvertising::handleGapEvent,
(pServer != nullptr) ? (void*)pServer : (void*)this); (pServer != nullptr) ? (void*)pServer : (void*)this);
#else #else
rc = ble_gap_adv_start(0, NULL, duration, rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration,
&m_advParams, NimBLEAdvertising::handleGapEvent, this); &m_advParams, NimBLEAdvertising::handleGapEvent, this);
#endif #endif
if (rc != 0) { switch(rc) {
NIMBLE_LOGC(LOG_TAG, "Error enabling advertising; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc)); case 0:
abort(); break;
case BLE_HS_EINVAL:
NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Duration too long");
break;
case BLE_HS_EPREEMPTED:
NIMBLE_LOGE(LOG_TAG, "Unable to advertise - busy");
break;
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Host Reset");
break;
default:
NIMBLE_LOGE(LOG_TAG, "Error enabling advertising; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
break;
}
if(rc != 0) {
return false;
} }
NIMBLE_LOGD(LOG_TAG, "<< Advertising start"); NIMBLE_LOGD(LOG_TAG, "<< Advertising start");
return true;
} // start } // start
@ -427,9 +562,11 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
*/ */
void NimBLEAdvertising::stop() { void NimBLEAdvertising::stop() {
NIMBLE_LOGD(LOG_TAG, ">> stop"); NIMBLE_LOGD(LOG_TAG, ">> stop");
int rc = ble_gap_adv_stop(); int rc = ble_gap_adv_stop();
if (rc != 0 && rc != BLE_HS_EALREADY) { if (rc != 0 && rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s",
rc, NimBLEUtils::returnCodeToString(rc));
return; return;
} }
@ -460,8 +597,17 @@ bool NimBLEAdvertising::isAdvertising() {
* Host reset seems to clear advertising data, * Host reset seems to clear advertising data,
* we need clear the flag so it reloads it. * we need clear the flag so it reloads it.
*/ */
void NimBLEAdvertising::onHostReset() { void NimBLEAdvertising::onHostSync() {
NIMBLE_LOGD(LOG_TAG, "Host re-synced");
m_advDataSet = false; m_advDataSet = false;
// If we were advertising forever, restart it now
if(m_duration == 0) {
start(m_duration, m_advCompCB);
} else {
// Otherwise we should tell the app that advertising stopped.
advCompleteCB();
}
} }
@ -475,6 +621,19 @@ int NimBLEAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEAdvertising *pAdv = (NimBLEAdvertising*)arg; NimBLEAdvertising *pAdv = (NimBLEAdvertising*)arg;
if(event->type == BLE_GAP_EVENT_ADV_COMPLETE) { if(event->type == BLE_GAP_EVENT_ADV_COMPLETE) {
switch(event->adv_complete.reason) {
// Don't call the callback if host reset, we want to
// preserve the active flag until re-sync to restart advertising.
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "host reset, rc=%d", event->adv_complete.reason);
NimBLEDevice::onReset(event->adv_complete.reason);
return 0;
default:
break;
}
pAdv->advCompleteCB(); pAdv->advCompleteCB();
} }
return 0; return 0;

View File

@ -77,7 +77,7 @@ public:
void addServiceUUID(const NimBLEUUID &serviceUUID); void addServiceUUID(const NimBLEUUID &serviceUUID);
void addServiceUUID(const char* serviceUUID); void addServiceUUID(const char* serviceUUID);
void removeServiceUUID(const NimBLEUUID &serviceUUID); void removeServiceUUID(const NimBLEUUID &serviceUUID);
void start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr); bool start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr);
void stop(); void stop();
void setAppearance(uint16_t appearance); void setAppearance(uint16_t appearance);
void setAdvertisementType(uint8_t adv_type); void setAdvertisementType(uint8_t adv_type);
@ -87,13 +87,15 @@ public:
void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly); void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly);
void setScanResponseData(NimBLEAdvertisementData& advertisementData); void setScanResponseData(NimBLEAdvertisementData& advertisementData);
void setScanResponse(bool); void setScanResponse(bool);
void setMinPreferred(uint16_t);
void setMaxPreferred(uint16_t);
void advCompleteCB(); void advCompleteCB();
bool isAdvertising(); bool isAdvertising();
private: private:
friend class NimBLEDevice; friend class NimBLEDevice;
void onHostReset(); void onHostSync();
static int handleGapEvent(struct ble_gap_event *event, void *arg); static int handleGapEvent(struct ble_gap_event *event, void *arg);
ble_hs_adv_fields m_advData; ble_hs_adv_fields m_advData;
@ -104,8 +106,9 @@ private:
bool m_customScanResponseData; bool m_customScanResponseData;
bool m_scanResp; bool m_scanResp;
bool m_advDataSet; bool m_advDataSet;
void (*m_advCompCB)(NimBLEAdvertising *pAdv); void (*m_advCompCB)(NimBLEAdvertising *pAdv);
uint8_t m_slaveItvl[4];
uint32_t m_duration;
}; };
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)

View File

@ -473,9 +473,10 @@ void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) {
return; return;
} }
time_t t = time(nullptr);
portENTER_CRITICAL(&m_valMux); portENTER_CRITICAL(&m_valMux);
m_value = std::string((char*)data, length); m_value = std::string((char*)data, length);
m_timestamp = time(nullptr); m_timestamp = t;
portEXIT_CRITICAL(&m_valMux); portEXIT_CRITICAL(&m_valMux);
NIMBLE_LOGD(LOG_TAG, "<< setValue"); NIMBLE_LOGD(LOG_TAG, "<< setValue");

View File

@ -24,6 +24,9 @@
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include "nimble/nimble_port.h"
static const char* LOG_TAG = "NimBLEClient"; static const char* LOG_TAG = "NimBLEClient";
static NimBLEClientCallbacks defaultCallbacks; static NimBLEClientCallbacks defaultCallbacks;
@ -56,11 +59,10 @@ static NimBLEClientCallbacks defaultCallbacks;
NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(peerAddress) { NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(peerAddress) {
m_pClientCallbacks = &defaultCallbacks; m_pClientCallbacks = &defaultCallbacks;
m_conn_id = BLE_HS_CONN_HANDLE_NONE; m_conn_id = BLE_HS_CONN_HANDLE_NONE;
m_isConnected = false;
m_waitingToConnect = false;
m_connectTimeout = 30000; m_connectTimeout = 30000;
m_deleteCallbacks = false; m_deleteCallbacks = false;
m_pTaskData = nullptr; m_pTaskData = nullptr;
m_connEstablished = false;
m_pConnParams.scan_itvl = 16; // Scan interval in 0.625ms units (NimBLE Default) m_pConnParams.scan_itvl = 16; // Scan interval in 0.625ms units (NimBLE Default)
m_pConnParams.scan_window = 16; // Scan window in 0.625ms units (NimBLE Default) m_pConnParams.scan_window = 16; // Scan window in 0.625ms units (NimBLE Default)
@ -70,6 +72,9 @@ NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(pee
m_pConnParams.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT; // timeout = 400*10ms = 4000ms m_pConnParams.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT; // timeout = 400*10ms = 4000ms
m_pConnParams.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units m_pConnParams.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units
m_pConnParams.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units m_pConnParams.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units
ble_npl_callout_init(&m_dcTimer, nimble_port_get_dflt_eventq(),
NimBLEClient::dcTimerCb, this);
} // NimBLEClient } // NimBLEClient
@ -89,6 +94,19 @@ NimBLEClient::~NimBLEClient() {
} // ~NimBLEClient } // ~NimBLEClient
/**
* @brief If we have asked to disconnect and the event does not
* occur within the supervision timeout + added delay, this will
* be called to reset the host in the case of a stalled controller.
*/
void NimBLEClient::dcTimerCb(ble_npl_event *event) {
NimBLEClient *pClient = (NimBLEClient*)event->arg;
NIMBLE_LOGC(LOG_TAG, "Timed out disconnecting from %s - resetting host",
std::string(pClient->getPeerAddress()).c_str());
ble_hs_sched_reset(BLE_HS_ECONTROLLER);
}
/** /**
* @brief Delete all service objects created by this client and clear the vector. * @brief Delete all service objects created by this client and clear the vector.
*/ */
@ -164,12 +182,9 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
return false; return false;
} }
if(ble_gap_conn_active()) { if(isConnected() || m_pTaskData != nullptr) {
NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait."); NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, id=%d",
return false; std::string(m_peerAddress).c_str(), getConnId());
}
if(!NimBLEDevice::getScan()->stop()) {
return false; return false;
} }
@ -180,54 +195,97 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
m_peerAddress = address; m_peerAddress = address;
} }
ble_addr_t peerAddrt; ble_addr_t peerAddr_t;
memcpy(&peerAddrt.val, m_peerAddress.getNative(),6); memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6);
peerAddrt.type = m_peerAddress.getType(); peerAddr_t.type = m_peerAddress.getType();
ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr};
m_pTaskData = &taskData;
int rc = 0; int rc = 0;
/* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for
* timeout (default value of m_connectTimeout). * timeout (default value of m_connectTimeout).
* Loop on BLE_HS_EBUSY if the scan hasn't stopped yet. * Loop on BLE_HS_EBUSY if the scan hasn't stopped yet.
*/ */
do{ do {
rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peerAddrt, m_connectTimeout, &m_pConnParams, rc = ble_gap_connect(NimBLEDevice::m_own_addr_type, &peerAddr_t,
NimBLEClient::handleGapEvent, this); m_connectTimeout, &m_pConnParams,
if(rc == BLE_HS_EBUSY) { NimBLEClient::handleGapEvent, this);
vTaskDelay(1 / portTICK_PERIOD_MS); switch (rc) {
case 0:
m_pTaskData = &taskData;
break;
case BLE_HS_EBUSY:
// Scan was still running, stop it and try again
if (!NimBLEDevice::getScan()->stop()) {
return false;
}
break;
case BLE_HS_EDONE:
// A connection to this device already exists, do not connect twice.
NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s",
std::string(m_peerAddress).c_str());
return false;
case BLE_HS_EALREADY:
// Already attemting to connect to this device, cancel the previous
// attempt and report failure here so we don't get 2 connections.
NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling",
std::string(m_peerAddress).c_str());
ble_gap_conn_cancel();
return false;
default:
NIMBLE_LOGE(LOG_TAG, "Failed to connect to %s, rc=%d; %s",
std::string(m_peerAddress).c_str(),
rc, NimBLEUtils::returnCodeToString(rc));
return false;
} }
}while(rc == BLE_HS_EBUSY);
if (rc != 0 && rc != BLE_HS_EDONE) { } while (rc == BLE_HS_EBUSY);
NIMBLE_LOGE(LOG_TAG, "Error: Failed to connect to device; "
"addr=%s, rc=%d; %s", // Wait for the connect timeout time +1 second for the connection to complete
std::string(m_peerAddress).c_str(), if(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) {
rc, NimBLEUtils::returnCodeToString(rc));
m_pTaskData = nullptr; m_pTaskData = nullptr;
m_waitingToConnect = false; // If a connection was made but no response from MTU exchange; disconnect
if(isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Connect timeout - no response");
disconnect();
} else {
// workaround; if the controller doesn't cancel the connection
// at the timeout cancel it here.
NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling");
ble_gap_conn_cancel();
}
return false; return false;
}
m_waitingToConnect = true; } else if(taskData.rc != 0){
NIMBLE_LOGE(LOG_TAG, "Connection failed; status=%d %s",
// Wait for the connection to complete. taskData.rc,
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); NimBLEUtils::returnCodeToString(taskData.rc));
// If the failure was not a result of a disconnection
if(taskData.rc != 0){ // make sure we disconnect now to avoid dangling connections
if(isConnected()) {
ble_gap_terminate(m_conn_id, BLE_ERR_REM_USER_CONN_TERM);
}
return false; return false;
} else {
NIMBLE_LOGI(LOG_TAG, "Connection established");
} }
if(deleteAttibutes) { if(deleteAttibutes) {
deleteServices(); deleteServices();
} }
m_connEstablished = true;
m_pClientCallbacks->onConnect(this); m_pClientCallbacks->onConnect(this);
NIMBLE_LOGD(LOG_TAG, "<< connect()"); NIMBLE_LOGD(LOG_TAG, "<< connect()");
return true; // Check if still connected before returning
return isConnected();
} // connect } // connect
@ -267,13 +325,31 @@ bool NimBLEClient::secureConnection() {
*/ */
int NimBLEClient::disconnect(uint8_t reason) { int NimBLEClient::disconnect(uint8_t reason) {
NIMBLE_LOGD(LOG_TAG, ">> disconnect()"); NIMBLE_LOGD(LOG_TAG, ">> disconnect()");
int rc = 0; int rc = 0;
if(m_isConnected){ if(isConnected()){
rc = ble_gap_terminate(m_conn_id, reason); rc = ble_gap_terminate(m_conn_id, reason);
if(rc != 0){ if (rc == 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc, ble_addr_t peerAddr_t;
NimBLEUtils::returnCodeToString(rc)); memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6);
peerAddr_t.type = m_peerAddress.getType();
// Set the disconnect timeout to the supervison timeout time + 1 second
// In case the event triggers shortly after the supervision timeout.
// We don't want to prematurely reset the host.
ble_gap_conn_desc desc;
if(ble_gap_conn_find_by_addr(&peerAddr_t, &desc) == 0){
ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks);
ble_npl_callout_reset(&m_dcTimer, ticks);
NIMBLE_LOGD(LOG_TAG, "DC TIMEOUT = %dms", (desc.supervision_timeout + 100) * 10);
}
} else if (rc != BLE_HS_EALREADY) {
NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s",
rc, NimBLEUtils::returnCodeToString(rc));
} }
} else {
NIMBLE_LOGD(LOG_TAG, "Not connected to any peers");
} }
NIMBLE_LOGD(LOG_TAG, "<< disconnect()"); NIMBLE_LOGD(LOG_TAG, "<< disconnect()");
@ -454,6 +530,16 @@ NimBLERemoteService* NimBLEClient::getService(const NimBLEUUID &uuid) {
if(m_servicesVector.size() > prev_size) { if(m_servicesVector.size() > prev_size) {
return m_servicesVector.back(); return m_servicesVector.back();
} }
// If the request was successful but 16/32 bit service not found
// try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
uuid.bitSize() == BLE_UUID_TYPE_32)
{
NimBLEUUID uuid128(uuid);
uuid128.to128();
return getService(uuid128);
}
} }
NIMBLE_LOGD(LOG_TAG, "<< getService: not found"); NIMBLE_LOGD(LOG_TAG, "<< getService: not found");
@ -510,7 +596,7 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveServices"); NIMBLE_LOGD(LOG_TAG, ">> retrieveServices");
if(!m_isConnected){ if(!isConnected()){
NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve services -aborting"); NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve services -aborting");
return false; return false;
} }
@ -618,10 +704,11 @@ std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUU
* @param [in] serviceUUID The service that owns the characteristic. * @param [in] serviceUUID The service that owns the characteristic.
* @param [in] characteristicUUID The characteristic whose value we wish to write. * @param [in] characteristicUUID The characteristic whose value we wish to write.
* @param [in] value The value to write to the characteristic. * @param [in] value The value to write to the characteristic.
* @param [in] response If true, uses write with response operation.
* @returns true if successful otherwise false * @returns true if successful otherwise false
*/ */
bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID,
const std::string &value) const std::string &value, bool response)
{ {
NIMBLE_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s", NIMBLE_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s",
serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); serviceUUID.toString().c_str(), characteristicUUID.toString().c_str());
@ -632,7 +719,7 @@ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &cha
if(pService != nullptr) { if(pService != nullptr) {
NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID); NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID);
if(pChar != nullptr) { if(pChar != nullptr) {
ret = pChar->writeValue(value); ret = pChar->writeValue(value, response);
} }
} }
@ -641,6 +728,31 @@ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &cha
} // setValue } // setValue
/**
* @brief Get the remote characteristic with the specified handle.
* @param [in] handle The handle of the desired characteristic.
* @returns The matching remote characteristic, nullptr otherwise.
*/
NimBLERemoteCharacteristic* NimBLEClient::getCharacteristic(const uint16_t handle)
{
NimBLERemoteService *pService = nullptr;
for(auto it = m_servicesVector.begin(); it != m_servicesVector.end(); ++it) {
if ((*it)->getStartHandle() <= handle && handle <= (*it)->getEndHandle()) {
pService = *it;
break;
}
}
if (pService != nullptr) {
for (auto it = pService->begin(); it != pService->end(); ++it) {
if ((*it)->getHandle() == handle) {
return *it;
}
}
}
return nullptr;
}
/** /**
* @brief Get the current mtu of this connection. * @brief Get the current mtu of this connection.
@ -656,7 +768,8 @@ uint16_t NimBLEClient::getMTU() {
* @param [in] event The event structure sent by the NimBLE stack. * @param [in] event The event structure sent by the NimBLE stack.
* @param [in] arg A pointer to the client instance that registered for this callback. * @param [in] arg A pointer to the client instance that registered for this callback.
*/ */
/*STATIC*/ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { /*STATIC*/
int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEClient* client = (NimBLEClient*)arg; NimBLEClient* client = (NimBLEClient*)arg;
int rc; int rc;
@ -665,61 +778,66 @@ uint16_t NimBLEClient::getMTU() {
switch(event->type) { switch(event->type) {
case BLE_GAP_EVENT_DISCONNECT: { case BLE_GAP_EVENT_DISCONNECT: {
if(!client->m_isConnected) rc = event->disconnect.reason;
return 0;
if(client->m_conn_id != event->disconnect.conn.conn_handle)
return 0;
client->m_isConnected = false;
client->m_waitingToConnect=false;
// Remove the device from ignore list so we will scan it again
NimBLEDevice::removeIgnored(client->m_peerAddress);
NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s", event->disconnect.reason,
NimBLEUtils::returnCodeToString(event->disconnect.reason));
// If Host reset tell the device now before returning to prevent // If Host reset tell the device now before returning to prevent
// any errors caused by calling host functions before resyncing. // any errors caused by calling host functions before resyncing.
switch(event->disconnect.reason) { switch(rc) {
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER: case BLE_HS_ECONTROLLER:
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_ENOTSYNCED: case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason); case BLE_HS_EOS:
NimBLEDevice::onReset(event->disconnect.reason); NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", rc);
NimBLEDevice::onReset(rc);
break; break;
default: default:
// Check that the event is for this client.
if(client->m_conn_id != event->disconnect.conn.conn_handle) {
return 0;
}
break; break;
} }
//client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
// Stop the disconnect timer since we are now disconnected.
ble_npl_callout_stop(&client->m_dcTimer);
// Remove the device from ignore list so we will scan it again
NimBLEDevice::removeIgnored(client->m_peerAddress);
// If we received a connected event but did not get established (no PDU)
// then a disconnect event will be sent but we should not send it to the
// app for processing. Instead we will ensure the task is released
// and report the error.
if(!client->m_connEstablished)
break;
NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
client->m_connEstablished = false;
client->m_pClientCallbacks->onDisconnect(client); client->m_pClientCallbacks->onDisconnect(client);
rc = event->disconnect.reason;
break; break;
} // BLE_GAP_EVENT_DISCONNECT } // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_CONNECT: { case BLE_GAP_EVENT_CONNECT: {
// If we aren't waiting for this connection response
if(!client->m_waitingToConnect) // we should drop the connection immediately.
if(client->isConnected() || client->m_pTaskData == nullptr) {
ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return 0; return 0;
}
//if(client->m_conn_id != BLE_HS_CONN_HANDLE_NONE) rc = event->connect.status;
// return 0; if (rc == 0) {
NIMBLE_LOGI(LOG_TAG, "Connected event");
client->m_waitingToConnect=false;
if (event->connect.status == 0) {
client->m_isConnected = true;
NIMBLE_LOGD(LOG_TAG, "Connection established");
client->m_conn_id = event->connect.conn_handle; client->m_conn_id = event->connect.conn_handle;
rc = ble_gattc_exchange_mtu(client->m_conn_id, NULL,NULL); rc = ble_gattc_exchange_mtu(client->m_conn_id, NULL,NULL);
if(rc != 0) { if(rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_exchange_mtu: rc=%d %s",rc, NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s",
NimBLEUtils::returnCodeToString(rc)); rc, NimBLEUtils::returnCodeToString(rc));
break; break;
} }
@ -727,14 +845,10 @@ uint16_t NimBLEClient::getMTU() {
// scanning since we are already connected to it // scanning since we are already connected to it
NimBLEDevice::addIgnored(client->m_peerAddress); NimBLEDevice::addIgnored(client->m_peerAddress);
} else { } else {
NIMBLE_LOGE(LOG_TAG, "Error: Connection failed; status=%d %s", client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
event->connect.status,
NimBLEUtils::returnCodeToString(event->connect.status));
client->m_isConnected = false;
rc = event->connect.status;
break; break;
} }
return 0; return 0;
} // BLE_GAP_EVENT_CONNECT } // BLE_GAP_EVENT_CONNECT
@ -742,7 +856,14 @@ uint16_t NimBLEClient::getMTU() {
if(client->m_conn_id != event->notify_rx.conn_handle) if(client->m_conn_id != event->notify_rx.conn_handle)
return 0; return 0;
NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d",event->notify_rx.attr_handle); // If a notification comes before this flag is set we might
// access a vector while it is being cleared in connect()
if(!client->m_connEstablished) {
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d",
event->notify_rx.attr_handle);
for(auto &it: client->m_servicesVector) { for(auto &it: client->m_servicesVector) {
// Dont waste cycles searching services without this handle in its range // Dont waste cycles searching services without this handle in its range
@ -752,8 +873,8 @@ uint16_t NimBLEClient::getMTU() {
auto cVector = &it->m_characteristicVector; auto cVector = &it->m_characteristicVector;
NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d", NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d",
it->getUUID().toString().c_str(), it->getUUID().toString().c_str(),
event->notify_rx.attr_handle); event->notify_rx.attr_handle);
auto characteristic = cVector->cbegin(); auto characteristic = cVector->cbegin();
for(; characteristic != cVector->cend(); ++characteristic) { for(; characteristic != cVector->cend(); ++characteristic) {
@ -762,16 +883,19 @@ uint16_t NimBLEClient::getMTU() {
} }
if(characteristic != cVector->cend()) { if(characteristic != cVector->cend()) {
NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", (*characteristic)->toString().c_str()); NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s",
(*characteristic)->toString().c_str());
time_t t = time(nullptr);
portENTER_CRITICAL(&(*characteristic)->m_valMux); portENTER_CRITICAL(&(*characteristic)->m_valMux);
(*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data, event->notify_rx.om->om_len); (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data,
(*characteristic)->m_timestamp = time(nullptr); event->notify_rx.om->om_len);
(*characteristic)->m_timestamp = t;
portEXIT_CRITICAL(&(*characteristic)->m_valMux); portEXIT_CRITICAL(&(*characteristic)->m_valMux);
if ((*characteristic)->m_notifyCallback != nullptr) { if ((*characteristic)->m_notifyCallback != nullptr) {
NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s",
(*characteristic)->toString().c_str()); (*characteristic)->toString().c_str());
(*characteristic)->m_notifyCallback(*characteristic, event->notify_rx.om->om_data, (*characteristic)->m_notifyCallback(*characteristic, event->notify_rx.om->om_data,
event->notify_rx.om->om_len, event->notify_rx.om->om_len,
!event->notify_rx.indication); !event->notify_rx.indication);
@ -790,10 +914,10 @@ uint16_t NimBLEClient::getMTU() {
} }
NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters"); NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters");
NIMBLE_LOGD(LOG_TAG, "MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d", NIMBLE_LOGD(LOG_TAG, "MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d",
event->conn_update_req.peer_params->itvl_min, event->conn_update_req.peer_params->itvl_min,
event->conn_update_req.peer_params->itvl_max, event->conn_update_req.peer_params->itvl_max,
event->conn_update_req.peer_params->latency, event->conn_update_req.peer_params->latency,
event->conn_update_req.peer_params->supervision_timeout); event->conn_update_req.peer_params->supervision_timeout);
rc = client->m_pClientCallbacks->onConnParamsUpdateRequest(client, rc = client->m_pClientCallbacks->onConnParamsUpdateRequest(client,
event->conn_update_req.peer_params) ? 0 : BLE_ERR_CONN_PARMS; event->conn_update_req.peer_params) ? 0 : BLE_ERR_CONN_PARMS;
@ -827,7 +951,9 @@ uint16_t NimBLEClient::getMTU() {
return 0; return 0;
} }
if(event->enc_change.status == 0 || event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) { if(event->enc_change.status == 0 ||
event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING))
{
struct ble_gap_conn_desc desc; struct ble_gap_conn_desc desc;
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0); assert(rc == 0);
@ -922,7 +1048,9 @@ uint16_t NimBLEClient::getMTU() {
if(client->m_pTaskData != nullptr) { if(client->m_pTaskData != nullptr) {
client->m_pTaskData->rc = rc; client->m_pTaskData->rc = rc;
xTaskNotifyGive(client->m_pTaskData->task); if(client->m_pTaskData->task) {
xTaskNotifyGive(client->m_pTaskData->task);
}
client->m_pTaskData = nullptr; client->m_pTaskData = nullptr;
} }
@ -935,7 +1063,7 @@ uint16_t NimBLEClient::getMTU() {
* @return True if we are connected and false if we are not connected. * @return True if we are connected and false if we are not connected.
*/ */
bool NimBLEClient::isConnected() { bool NimBLEClient::isConnected() {
return m_isConnected; return m_conn_id != BLE_HS_CONN_HANDLE_NONE;
} // isConnected } // isConnected

View File

@ -30,6 +30,7 @@
#include <string> #include <string>
class NimBLERemoteService; class NimBLERemoteService;
class NimBLERemoteCharacteristic;
class NimBLEClientCallbacks; class NimBLEClientCallbacks;
class NimBLEAdvertisedDevice; class NimBLEAdvertisedDevice;
@ -54,7 +55,8 @@ public:
size_t deleteService(const NimBLEUUID &uuid); size_t deleteService(const NimBLEUUID &uuid);
std::string getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID); std::string getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID);
bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID,
const std::string &value); const std::string &value, bool response = false);
NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle);
bool isConnected(); bool isConnected();
void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks,
bool deleteCallbacks = true); bool deleteCallbacks = true);
@ -82,16 +84,17 @@ private:
const struct ble_gatt_error *error, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, const struct ble_gatt_svc *service,
void *arg); void *arg);
static void dcTimerCb(ble_npl_event *event);
bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr); bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr);
NimBLEAddress m_peerAddress; NimBLEAddress m_peerAddress;
uint16_t m_conn_id; uint16_t m_conn_id;
bool m_isConnected; bool m_connEstablished;
bool m_waitingToConnect;
bool m_deleteCallbacks; bool m_deleteCallbacks;
int32_t m_connectTimeout; int32_t m_connectTimeout;
NimBLEClientCallbacks* m_pClientCallbacks; NimBLEClientCallbacks* m_pClientCallbacks;
ble_task_data_t *m_pTaskData; ble_task_data_t* m_pTaskData;
ble_npl_callout m_dcTimer;
std::vector<NimBLERemoteService*> m_servicesVector; std::vector<NimBLERemoteService*> m_servicesVector;

View File

@ -25,6 +25,7 @@
#include "nimble/nimble_port.h" #include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h" #include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h" #include "host/ble_hs.h"
#include "host/ble_hs_pvcy.h"
#include "host/util/util.h" #include "host/util/util.h"
#include "services/gap/ble_svc_gap.h" #include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h" #include "services/gatt/ble_svc_gatt.h"
@ -60,6 +61,7 @@ std::list <NimBLEClient*> NimBLEDevice::m_cList;
#endif #endif
std::list <NimBLEAddress> NimBLEDevice::m_ignoreList; std::list <NimBLEAddress> NimBLEDevice::m_ignoreList;
NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr; NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr;
uint8_t NimBLEDevice::m_own_addr_type = BLE_OWN_ADDR_PUBLIC;
/** /**
@ -144,8 +146,8 @@ void NimBLEDevice::stopAdvertising() {
#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
/* STATIC */ NimBLEClient* NimBLEDevice::createClient(NimBLEAddress peerAddress) { /* STATIC */ NimBLEClient* NimBLEDevice::createClient(NimBLEAddress peerAddress) {
if(m_cList.size() >= NIMBLE_MAX_CONNECTIONS) { if(m_cList.size() >= NIMBLE_MAX_CONNECTIONS) {
NIMBLE_LOGW("Number of clients exceeds Max connections. Max=(%d)", NIMBLE_LOGW(LOG_TAG,"Number of clients exceeds Max connections. Cur=%d Max=%d",
NIMBLE_MAX_CONNECTIONS); m_cList.size(), NIMBLE_MAX_CONNECTIONS);
} }
NimBLEClient* pClient = new NimBLEClient(peerAddress); NimBLEClient* pClient = new NimBLEClient(peerAddress);
@ -165,26 +167,31 @@ void NimBLEDevice::stopAdvertising() {
return false; return false;
} }
// Set the connection established flag to false to stop notifications
// from accessing the attribute vectors while they are being deleted.
pClient->m_connEstablished = false;
int rc =0; int rc =0;
if(pClient->m_isConnected) { if(pClient->isConnected()) {
rc = pClient->disconnect(); rc = pClient->disconnect();
if (rc != 0 && rc != BLE_HS_EALREADY && rc != BLE_HS_ENOTCONN) { if (rc != 0 && rc != BLE_HS_EALREADY && rc != BLE_HS_ENOTCONN) {
return false; return false;
} }
while(pClient->m_isConnected) { while(pClient->isConnected()) {
vTaskDelay(10); taskYIELD();
} }
} // Since we set the flag to false the app will not get a callback
// in the disconnect event so we call it here for good measure.
pClient->m_pClientCallbacks->onDisconnect(pClient);
if(pClient->m_waitingToConnect) { } else if(pClient->m_pTaskData != nullptr) {
rc = ble_gap_conn_cancel(); rc = ble_gap_conn_cancel();
if (rc != 0 && rc != BLE_HS_EALREADY) { if (rc != 0 && rc != BLE_HS_EALREADY) {
return false; return false;
} }
while(pClient->m_waitingToConnect) { while(pClient->m_pTaskData != nullptr) {
vTaskDelay(10); taskYIELD();
} }
} }
@ -405,30 +412,16 @@ void NimBLEDevice::stopAdvertising() {
m_synced = false; m_synced = false;
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
if(m_pScan != nullptr) {
m_pScan->onHostReset();
}
#endif
/* Not needed
if(m_pServer != nullptr) {
m_pServer->onHostReset();
}
for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) {
(*it)->onHostReset();
}
*/
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
if(m_bleAdvertising != nullptr) {
m_bleAdvertising->onHostReset();
}
#endif
NIMBLE_LOGC(LOG_TAG, "Resetting state; reason=%d, %s", reason, NIMBLE_LOGC(LOG_TAG, "Resetting state; reason=%d, %s", reason,
NimBLEUtils::returnCodeToString(reason)); NimBLEUtils::returnCodeToString(reason));
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
if(initialized) {
if(m_pScan != nullptr) {
m_pScan->onHostReset();
}
}
#endif
} // onReset } // onReset
@ -448,20 +441,22 @@ void NimBLEDevice::stopAdvertising() {
int rc = ble_hs_util_ensure_addr(0); int rc = ble_hs_util_ensure_addr(0);
assert(rc == 0); assert(rc == 0);
// Yield for houskeeping before returning to operations.
// Occasionally triggers exception without.
taskYIELD();
m_synced = true; m_synced = true;
if(initialized) { if(initialized) {
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
if(m_pScan != nullptr) { if(m_pScan != nullptr) {
// Restart scanning with the last values sent, allow to clear results. m_pScan->onHostSync();
m_pScan->start(m_pScan->m_duration, m_pScan->m_scanCompleteCB);
} }
#endif #endif
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER) #if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
if(m_bleAdvertising != nullptr) { if(m_bleAdvertising != nullptr) {
// Restart advertisng, parameters should already be set. m_bleAdvertising->onHostSync();
m_bleAdvertising->start();
} }
#endif #endif
} }
@ -705,6 +700,35 @@ void NimBLEDevice::setSecurityCallbacks(NimBLESecurityCallbacks* callbacks) {
} // setSecurityCallbacks } // setSecurityCallbacks
/**
* @brief Set the own address type.
* @param [in] own_addr_type Own Bluetooth Device address type.\n
* The available bits are defined as:
* * 0x00: BLE_OWN_ADDR_PUBLIC
* * 0x01: BLE_OWN_ADDR_RANDOM
* * 0x02: BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT
* * 0x03: BLE_OWN_ADDR_RPA_RANDOM_DEFAULT
* @param [in] useNRPA If true, and address type is random, uses a non-resolvable random address.
*/
void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) {
m_own_addr_type = own_addr_type;
switch (own_addr_type) {
case BLE_OWN_ADDR_PUBLIC:
ble_hs_pvcy_rpa_config(NIMBLE_HOST_DISABLE_PRIVACY);
break;
case BLE_OWN_ADDR_RANDOM:
setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
ble_hs_pvcy_rpa_config(useNRPA ? NIMBLE_HOST_ENABLE_NRPA : NIMBLE_HOST_ENABLE_RPA);
break;
case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT:
case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA);
break;
}
} // setOwnAddrType
/** /**
* @brief Start the connection securing and authorization for this connection. * @brief Start the connection securing and authorization for this connection.
* @param conn_id The connection id of the peer device. * @param conn_id The connection id of the peer device.

View File

@ -116,6 +116,7 @@ public:
static void setSecurityPasskey(uint32_t pin); static void setSecurityPasskey(uint32_t pin);
static uint32_t getSecurityPasskey(); static uint32_t getSecurityPasskey();
static void setSecurityCallbacks(NimBLESecurityCallbacks* pCallbacks); static void setSecurityCallbacks(NimBLESecurityCallbacks* pCallbacks);
static void setOwnAddrType(uint8_t own_addr_type, bool useNRPA=false);
static int startSecurity(uint16_t conn_id); static int startSecurity(uint16_t conn_id);
static int setMTU(uint16_t mtu); static int setMTU(uint16_t mtu);
static uint16_t getMTU(); static uint16_t getMTU();
@ -182,6 +183,7 @@ private:
static uint32_t m_passkey; static uint32_t m_passkey;
static ble_gap_event_listener m_listener; static ble_gap_event_listener m_listener;
static gap_event_handler m_customGapHandler; static gap_event_handler m_customGapHandler;
static uint8_t m_own_addr_type;
}; };

View File

@ -0,0 +1,233 @@
/*
* NimBLEHIDDevice.cpp
*
* Created: on Oct 06 2020
* Author wakwak-koba
*
* Originally:
*
* BLEHIDDevice.cpp
*
* Created on: Jan 03, 2018
* Author: chegewara
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimconfig.h"
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEHIDDevice.h"
#include "NimBLE2904.h"
NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) {
/*
* Here we create mandatory services described in bluetooth specification
*/
m_deviceInfoService = server->createService(NimBLEUUID((uint16_t) 0x180a));
m_hidService = server->createService(NimBLEUUID((uint16_t) 0x1812), 40);
m_batteryService = server->createService(NimBLEUUID((uint16_t) 0x180f));
/*
* Mandatory characteristic for device info service
*/
m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a50, NIMBLE_PROPERTY::READ);
/*
* Mandatory characteristics for HID service
*/
m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4a, NIMBLE_PROPERTY::READ);
m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4b, NIMBLE_PROPERTY::READ);
m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4c, NIMBLE_PROPERTY::WRITE_NR);
m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4e, NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ);
/*
* Mandatory battery level characteristic with notification and presence descriptor
*/
m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t) 0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
NimBLE2904* batteryLevelDescriptor = (NimBLE2904*)m_batteryLevelCharacteristic->createDescriptor((uint16_t) 0x2904);
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
/*
* This value is setup here because its default value in most usage cases, its very rare to use boot mode
* and we want to simplify library using as much as possible
*/
const uint8_t pMode[] = { 0x01 };
protocolMode()->setValue((uint8_t*) pMode, 1);
}
NimBLEHIDDevice::~NimBLEHIDDevice() {
}
/*
* @brief
*/
void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) {
m_reportMapCharacteristic->setValue(map, size);
}
/*
* @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service
*/
void NimBLEHIDDevice::startServices() {
m_deviceInfoService->start();
m_hidService->start();
m_batteryService->start();
}
/*
* @brief Create manufacturer characteristic (this characteristic is optional)
*/
NimBLECharacteristic* NimBLEHIDDevice::manufacturer() {
m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ);
return m_manufacturerCharacteristic;
}
/*
* @brief Set manufacturer name
* @param [in] name manufacturer name
*/
void NimBLEHIDDevice::manufacturer(std::string name) {
m_manufacturerCharacteristic->setValue(name);
}
/*
* @brief
*/
void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) {
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
}
/*
* @brief
*/
void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) {
uint8_t info[] = { 0x11, 0x1, country, flags };
m_hidInfoCharacteristic->setValue(info, sizeof(info));
}
/*
* @brief Create input report characteristic that need to be saved as new characteristic object so can be further used
* @param [in] reportID input report ID, the same as in report map for input object related to created characteristic
* @return pointer to new input report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) {
NimBLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
NimBLEDescriptor* inputReportDescriptor = inputReportCharacteristic->createDescriptor((uint16_t) 0x2908);
uint8_t desc1_val[] = { reportID, 0x01 };
inputReportDescriptor->setValue((uint8_t*) desc1_val, 2);
return inputReportCharacteristic;
}
/*
* @brief Create output report characteristic that need to be saved as new characteristic object so can be further used
* @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic
* @return Pointer to new output report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) {
NimBLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
NimBLEDescriptor* outputReportDescriptor = outputReportCharacteristic->createDescriptor((uint16_t) 0x2908);
uint8_t desc1_val[] = { reportID, 0x02 };
outputReportDescriptor->setValue((uint8_t*) desc1_val, 2);
return outputReportCharacteristic;
}
/*
* @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used
* @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic
* @return Pointer to new feature report characteristic
*/
NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) {
NimBLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
NimBLEDescriptor* featureReportDescriptor = featureReportCharacteristic->createDescriptor((uint16_t) 0x2908);
uint8_t desc1_val[] = { reportID, 0x03 };
featureReportDescriptor->setValue((uint8_t*) desc1_val, 2);
return featureReportCharacteristic;
}
/*
* @brief
*/
NimBLECharacteristic* NimBLEHIDDevice::bootInput() {
return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY);
}
/*
* @brief
*/
NimBLECharacteristic* NimBLEHIDDevice::bootOutput() {
return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
}
/*
* @brief
*/
NimBLECharacteristic* NimBLEHIDDevice::hidControl() {
return m_hidControlCharacteristic;
}
/*
* @brief
*/
NimBLECharacteristic* NimBLEHIDDevice::protocolMode() {
return m_protocolModeCharacteristic;
}
void NimBLEHIDDevice::setBatteryLevel(uint8_t level) {
m_batteryLevelCharacteristic->setValue(&level, 1);
}
/*
* @brief Returns battery level characteristic
* @ return battery level characteristic
*//*
BLECharacteristic* BLEHIDDevice::batteryLevel() {
return m_batteryLevelCharacteristic;
}
BLECharacteristic* BLEHIDDevice::reportMap() {
return m_reportMapCharacteristic;
}
BLECharacteristic* BLEHIDDevice::pnp() {
return m_pnpCharacteristic;
}
BLECharacteristic* BLEHIDDevice::hidInfo() {
return m_hidInfoCharacteristic;
}
*/
/*
* @brief
*/
NimBLEService* NimBLEHIDDevice::deviceInfo() {
return m_deviceInfoService;
}
/*
* @brief
*/
NimBLEService* NimBLEHIDDevice::hidService() {
return m_hidService;
}
/*
* @brief
*/
NimBLEService* NimBLEHIDDevice::batteryService() {
return m_batteryService;
}
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#endif // #if defined(CONFIG_BT_ENABLED)

View File

@ -0,0 +1,85 @@
/*
* NimBLEHIDDevice.h
*
* Created: on Oct 06 2020
* Author wakwak-koba
*
* Originally:
*
* BLEHIDDevice.h
*
* Created on: Jan 03, 2018
* Author: chegewara
*/
#ifndef _BLEHIDDEVICE_H_
#define _BLEHIDDEVICE_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimconfig.h"
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
#include "NimBLECharacteristic.h"
#include "NimBLEService.h"
#include "NimBLEDescriptor.h"
#include "HIDTypes.h"
#define GENERIC_HID 0x03C0
#define HID_KEYBOARD 0x03C1
#define HID_MOUSE 0x03C2
#define HID_JOYSTICK 0x03C3
#define HID_GAMEPAD 0x03C4
#define HID_TABLET 0x03C5
#define HID_CARD_READER 0x03C6
#define HID_DIGITAL_PEN 0x03C7
#define HID_BARCODE 0x03C8
class NimBLEHIDDevice {
public:
NimBLEHIDDevice(NimBLEServer*);
virtual ~NimBLEHIDDevice();
void reportMap(uint8_t* map, uint16_t);
void startServices();
NimBLEService* deviceInfo();
NimBLEService* hidService();
NimBLEService* batteryService();
NimBLECharacteristic* manufacturer();
void manufacturer(std::string name);
//NimBLECharacteristic* pnp();
void pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version);
//NimBLECharacteristic* hidInfo();
void hidInfo(uint8_t country, uint8_t flags);
//NimBLECharacteristic* batteryLevel();
void setBatteryLevel(uint8_t level);
//NimBLECharacteristic* reportMap();
NimBLECharacteristic* hidControl();
NimBLECharacteristic* inputReport(uint8_t reportID);
NimBLECharacteristic* outputReport(uint8_t reportID);
NimBLECharacteristic* featureReport(uint8_t reportID);
NimBLECharacteristic* protocolMode();
NimBLECharacteristic* bootInput();
NimBLECharacteristic* bootOutput();
private:
NimBLEService* m_deviceInfoService; //0x180a
NimBLEService* m_hidService; //0x1812
NimBLEService* m_batteryService = 0; //0x180f
NimBLECharacteristic* m_manufacturerCharacteristic; //0x2a29
NimBLECharacteristic* m_pnpCharacteristic; //0x2a50
NimBLECharacteristic* m_hidInfoCharacteristic; //0x2a4a
NimBLECharacteristic* m_reportMapCharacteristic; //0x2a4b
NimBLECharacteristic* m_hidControlCharacteristic; //0x2a4c
NimBLECharacteristic* m_protocolModeCharacteristic; //0x2a4e
NimBLECharacteristic* m_batteryLevelCharacteristic; //0x2a19
};
#endif // CONFIG_BT_NIMBLE_ROLE_BROADCASTER
#endif // CONFIG_BT_ENABLED
#endif /* _BLEHIDDEVICE_H_ */

View File

@ -38,7 +38,7 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService, NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService,
const struct ble_gatt_chr *chr) const struct ble_gatt_chr *chr)
{ {
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()");
switch (chr->uuid.u.type) { switch (chr->uuid.u.type) {
case BLE_UUID_TYPE_16: case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(chr->uuid.u16.value); m_uuid = NimBLEUUID(chr->uuid.u16.value);
@ -50,7 +50,6 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&chr->uuid.u128)); m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&chr->uuid.u128));
break; break;
default: default:
m_uuid = nullptr;
break; break;
} }
@ -61,6 +60,8 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
m_notifyCallback = nullptr; m_notifyCallback = nullptr;
m_timestamp = 0; m_timestamp = 0;
m_valMux = portMUX_INITIALIZER_UNLOCKED; m_valMux = portMUX_INITIALIZER_UNLOCKED;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str());
} // NimBLERemoteCharacteristic } // NimBLERemoteCharacteristic
@ -208,15 +209,21 @@ int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle,
bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filter) { bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filter) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
uint16_t endHandle = getRemoteService()->getEndHandle(this);
if(m_handle >= endHandle) {
return false;
}
int rc = 0; int rc = 0;
ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr};
desc_filter_t filter = {uuid_filter, &taskData}; desc_filter_t filter = {uuid_filter, &taskData};
rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(),
m_handle, m_handle,
getRemoteService()->getEndHandle(), endHandle,
NimBLERemoteCharacteristic::descriptorDiscCB, NimBLERemoteCharacteristic::descriptorDiscCB,
&filter); &filter);
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false; return false;
@ -225,12 +232,13 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(taskData.rc != 0) { if(taskData.rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: startHandle:%d endHandle:%d taskData.rc=%d %s", m_handle, endHandle, taskData.rc, NimBLEUtils::returnCodeToString(0x0100+taskData.rc));
return false; return false;
} }
return true; return true;
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size()); NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size());
} // getDescriptors } // retrieveDescriptors
/** /**
@ -243,7 +251,7 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
for(auto &it: m_descriptorVector) { for(auto &it: m_descriptorVector) {
if(it->getUUID() == uuid) { if(it->getUUID() == uuid) {
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found"); NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str());
return it; return it;
} }
} }
@ -253,7 +261,18 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
if(m_descriptorVector.size() > prev_size) { if(m_descriptorVector.size() > prev_size) {
return m_descriptorVector.back(); return m_descriptorVector.back();
} }
// If the request was successful but 16/32 bit descriptor not found
// try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
uuid.bitSize() == BLE_UUID_TYPE_32)
{
NimBLEUUID uuid128(uuid);
uuid128.to128();
return getDescriptor(uuid128);
}
} }
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found"); NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found");
return nullptr; return nullptr;
} // getDescriptor } // getDescriptor
@ -447,9 +466,10 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) {
} }
} while(rc != 0 && retryCount--); } while(rc != 0 && retryCount--);
time_t t = time(nullptr);
portENTER_CRITICAL(&m_valMux); portENTER_CRITICAL(&m_valMux);
m_value = value; m_value = value;
m_timestamp = time(nullptr); m_timestamp = t;
if(timestamp != nullptr) { if(timestamp != nullptr) {
*timestamp = m_timestamp; *timestamp = m_timestamp;
} }
@ -506,19 +526,19 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
* @param [in] notifyCallback A callback to be invoked for a notification. * @param [in] notifyCallback A callback to be invoked for a notification.
* @param [in] response If write response required set this to true. * @param [in] response If write response required set this to true.
* If NULL is provided then no callback is performed. * If NULL is provided then no callback is performed.
* @return true if successful. * @return false if writing to the descriptor failed.
*/ */
bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) { bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val); NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val);
m_notifyCallback = notifyCallback;
NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902)); NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902));
if(desc == nullptr) { if(desc == nullptr) {
NIMBLE_LOGE(LOG_TAG, "<< setNotify(): Could not get descriptor"); NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found");
return false; return true;
} }
m_notifyCallback = notifyCallback;
NIMBLE_LOGD(LOG_TAG, "<< setNotify()"); NIMBLE_LOGD(LOG_TAG, "<< setNotify()");
return desc->writeValue((uint8_t *)&val, 2, response); return desc->writeValue((uint8_t *)&val, 2, response);
@ -531,7 +551,7 @@ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyC
* @param [in] notifyCallback A callback to be invoked for a notification. * @param [in] notifyCallback A callback to be invoked for a notification.
* @param [in] response If true, require a write response from the descriptor write operation. * @param [in] response If true, require a write response from the descriptor write operation.
* If NULL is provided then no callback is performed. * If NULL is provided then no callback is performed.
* @return true if successful. * @return false if writing to the descriptor failed.
*/ */
bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) { bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) {
if(notifications) { if(notifications) {
@ -545,7 +565,7 @@ bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback n
/** /**
* @brief Unsubscribe for notifications or indications. * @brief Unsubscribe for notifications or indications.
* @param [in] response bool if true, require a write response from the descriptor write operation. * @param [in] response bool if true, require a write response from the descriptor write operation.
* @return true if successful. * @return false if writing to the descriptor failed.
*/ */
bool NimBLERemoteCharacteristic::unsubscribe(bool response) { bool NimBLERemoteCharacteristic::unsubscribe(bool response) {
return setNotify(0x00, nullptr, response); return setNotify(0x00, nullptr, response);

View File

@ -31,6 +31,7 @@ static const char* LOG_TAG = "NimBLERemoteDescriptor";
NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic, NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic,
const struct ble_gatt_dsc *dsc) const struct ble_gatt_dsc *dsc)
{ {
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteDescriptor()");
switch (dsc->uuid.u.type) { switch (dsc->uuid.u.type) {
case BLE_UUID_TYPE_16: case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(dsc->uuid.u16.value); m_uuid = NimBLEUUID(dsc->uuid.u16.value);
@ -42,12 +43,13 @@ NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemo
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&dsc->uuid.u128)); m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&dsc->uuid.u128));
break; break;
default: default:
m_uuid = nullptr;
break; break;
} }
m_handle = dsc->handle; m_handle = dsc->handle;
m_pRemoteCharacteristic = pRemoteCharacteristic; m_pRemoteCharacteristic = pRemoteCharacteristic;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteDescriptor(): %s", m_uuid.toString().c_str());
} }

View File

@ -44,12 +44,11 @@ NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&service->uuid.u128)); m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&service->uuid.u128));
break; break;
default: default:
m_uuid = nullptr;
break; break;
} }
m_startHandle = service->start_handle; m_startHandle = service->start_handle;
m_endHandle = service->end_handle; m_endHandle = service->end_handle;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService()"); NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService(): %s", m_uuid.toString().c_str());
} }
@ -95,8 +94,11 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* u
* @return A pointer to the characteristic object, or nullptr if not found. * @return A pointer to the characteristic object, or nullptr if not found.
*/ */
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID &uuid) { NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID &uuid) {
NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str());
for(auto &it: m_characteristicVector) { for(auto &it: m_characteristicVector) {
if(it->getUUID() == uuid) { if(it->getUUID() == uuid) {
NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: found the characteristic with uuid: %s", uuid.toString().c_str());
return it; return it;
} }
} }
@ -106,8 +108,19 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU
if(m_characteristicVector.size() > prev_size) { if(m_characteristicVector.size() > prev_size) {
return m_characteristicVector.back(); return m_characteristicVector.back();
} }
// If the request was successful but 16/32 bit characteristic not found
// try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
uuid.bitSize() == BLE_UUID_TYPE_32)
{
NimBLEUUID uuid128(uuid);
uuid128.to128();
return getCharacteristic(uuid128);
}
} }
NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: not found");
return nullptr; return nullptr;
} // getCharacteristic } // getCharacteristic
@ -236,6 +249,23 @@ uint16_t NimBLERemoteService::getEndHandle() {
return m_endHandle; return m_endHandle;
} // getEndHandle } // getEndHandle
/**
* @brief Get the end handle of specified NimBLERemoteCharacteristic.
*/
uint16_t NimBLERemoteService::getEndHandle(NimBLERemoteCharacteristic *pCharacteristic) {
uint16_t endHandle = m_endHandle;
for(auto &it: m_characteristicVector) {
uint16_t defHandle = it->getDefHandle() - 1;
if(defHandle > pCharacteristic->getDefHandle() && endHandle > defHandle) {
endHandle = defHandle;
}
}
return endHandle;
} // getEndHandle
/** /**
* @brief Get the service start handle. * @brief Get the service start handle.

View File

@ -70,6 +70,7 @@ private:
uint16_t getStartHandle(); uint16_t getStartHandle();
uint16_t getEndHandle(); uint16_t getEndHandle();
uint16_t getEndHandle(NimBLERemoteCharacteristic *pCharacteristic);
void releaseSemaphores(); void releaseSemaphores();
// Properties // Properties

View File

@ -30,7 +30,6 @@ static const char* LOG_TAG = "NimBLEScan";
* @brief Scan constuctor. * @brief Scan constuctor.
*/ */
NimBLEScan::NimBLEScan() { NimBLEScan::NimBLEScan() {
m_own_addr_type = 0;
m_scan_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL; m_scan_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL;
m_scan_params.passive = 1; // If set, dont send scan requests to advertisers (i.e., dont request additional advertising data). m_scan_params.passive = 1; // If set, dont send scan requests to advertisers (i.e., dont request additional advertising data).
m_scan_params.itvl = 0; // This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan. (units=0.625 msec) m_scan_params.itvl = 0; // This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan. (units=0.625 msec)
@ -38,9 +37,10 @@ NimBLEScan::NimBLEScan() {
m_scan_params.limited = 0; // If set, only discover devices in limited discoverable mode. m_scan_params.limited = 0; // If set, only discover devices in limited discoverable mode.
m_scan_params.filter_duplicates = 0; // If set, the controller ignores all but the first advertisement from each device. m_scan_params.filter_duplicates = 0; // If set, the controller ignores all but the first advertisement from each device.
m_pAdvertisedDeviceCallbacks = nullptr; m_pAdvertisedDeviceCallbacks = nullptr;
m_stopped = true; m_ignoreResults = false;
m_wantDuplicates = false; m_wantDuplicates = false;
m_pTaskData = nullptr; m_pTaskData = nullptr;
m_duration = BLE_HS_FOREVER; // make sure this is non-zero in the event of a host reset
} }
@ -63,8 +63,8 @@ NimBLEScan::~NimBLEScan() {
switch(event->type) { switch(event->type) {
case BLE_GAP_EVENT_DISC: { case BLE_GAP_EVENT_DISC: {
if(pScan->m_stopped) { if(pScan->m_ignoreResults) {
NIMBLE_LOGE(LOG_TAG, "Scan stop called, ignoring results."); NIMBLE_LOGE(LOG_TAG, "Scan op in progress - ignoring results");
return 0; return 0;
} }
@ -129,7 +129,6 @@ NimBLEScan::~NimBLEScan() {
pScan->m_scanCompleteCB(pScan->m_scanResults); pScan->m_scanCompleteCB(pScan->m_scanResults);
} }
pScan->m_stopped = true;
if(pScan->m_pTaskData != nullptr) { if(pScan->m_pTaskData != nullptr) {
pScan->m_pTaskData->rc = event->disc_complete.reason; pScan->m_pTaskData->rc = event->disc_complete.reason;
xTaskNotifyGive(pScan->m_pTaskData->task); xTaskNotifyGive(pScan->m_pTaskData->task);
@ -238,7 +237,7 @@ void NimBLEScan::setWindow(uint16_t windowMSecs) {
* @return true if scanning or scan starting. * @return true if scanning or scan starting.
*/ */
bool NimBLEScan::isScanning() { bool NimBLEScan::isScanning() {
return !m_stopped; return ble_gap_disc_active();
} }
@ -252,25 +251,6 @@ bool NimBLEScan::isScanning() {
bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) { bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) {
NIMBLE_LOGD(LOG_TAG, ">> start(duration=%d)", duration); NIMBLE_LOGD(LOG_TAG, ">> start(duration=%d)", duration);
// If Host is not synced we cannot start scanning.
if(!NimBLEDevice::m_synced) {
NIMBLE_LOGC(LOG_TAG, "Host reset, wait for sync.");
return false;
}
if(ble_gap_conn_active()) {
NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait.");
return false;
}
// If we are already scanning don't start again or we will get stuck on the semaphore.
if(!m_stopped || ble_gap_disc_active()) { // double check - can cause host reset.
NIMBLE_LOGE(LOG_TAG, "Scan already in progress");
return false;
}
m_stopped = false;
// Save the callback to be invoked when the scan completes. // Save the callback to be invoked when the scan completes.
m_scanCompleteCB = scanCompleteCB; m_scanCompleteCB = scanCompleteCB;
// Save the duration in the case that the host is reset so we can reuse it. // Save the duration in the case that the host is reset so we can reuse it.
@ -281,32 +261,51 @@ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResul
duration = BLE_HS_FOREVER; duration = BLE_HS_FOREVER;
} }
else{ else{
duration = duration*1000; // convert duration to milliseconds // convert duration to milliseconds
duration = duration * 1000;
} }
// if we are connecting to devices that are advertising even after being connected, multiconnecting peripherals // Set the flag to ignore the results while we are deleting the vector
// then we should not clear vector or we will connect the same device few times
if(!is_continue) { if(!is_continue) {
clearResults(); m_ignoreResults = true;
} }
int rc = 0; int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, duration, &m_scan_params,
do{ NimBLEScan::handleGapEvent, this);
rc = ble_gap_disc(m_own_addr_type, duration, &m_scan_params,
NimBLEScan::handleGapEvent, this);
if(rc == BLE_HS_EBUSY) {
vTaskDelay(1 / portTICK_PERIOD_MS);
}
} while(rc == BLE_HS_EBUSY);
if (rc != 0 && rc != BLE_HS_EDONE) { switch(rc) {
NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s", case 0:
rc, NimBLEUtils::returnCodeToString(rc)); if(!is_continue) {
m_stopped = true; clearResults();
}
break;
case BLE_HS_EALREADY:
break;
case BLE_HS_EBUSY:
NIMBLE_LOGE(LOG_TAG, "Unable to scan - connection in progress.");
break;
case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_EOS:
case BLE_HS_ECONTROLLER:
case BLE_HS_ENOTSYNCED:
NIMBLE_LOGC(LOG_TAG, "Unable to scan - Host Reset");
break;
default:
NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
break;
}
m_ignoreResults = false;
NIMBLE_LOGD(LOG_TAG, "<< start()");
if(rc != 0 && rc != BLE_HS_EALREADY) {
return false; return false;
} }
NIMBLE_LOGD(LOG_TAG, "<< start()");
return true; return true;
} // start } // start
@ -347,8 +346,6 @@ bool NimBLEScan::stop() {
return false; return false;
} }
m_stopped = true;
if (rc != BLE_HS_EALREADY && m_scanCompleteCB != nullptr) { if (rc != BLE_HS_EALREADY && m_scanCompleteCB != nullptr) {
m_scanCompleteCB(m_scanResults); m_scanCompleteCB(m_scanResults);
} }
@ -381,13 +378,25 @@ void NimBLEScan::erase(const NimBLEAddress &address) {
/** /**
* @brief If the host reset the scan will have stopped so we should set the flag as stopped. * @brief Called when host reset, we set a flag to stop scanning until synced.
*/ */
void NimBLEScan::onHostReset() { void NimBLEScan::onHostReset() {
m_stopped = true; m_ignoreResults = true;
} }
/**
* @brief If the host reset and re-synced this is called.
* If the application was scanning indefinitely with a callback, restart it.
*/
void NimBLEScan::onHostSync() {
m_ignoreResults = false;
if(m_duration == 0 && m_pAdvertisedDeviceCallbacks != nullptr) {
start(m_duration, m_scanCompleteCB);
}
}
/** /**
* @brief Get the results of the scan. * @brief Get the results of the scan.
* @return NimBLEScanResults object. * @return NimBLEScanResults object.

View File

@ -83,12 +83,12 @@ private:
~NimBLEScan(); ~NimBLEScan();
static int handleGapEvent(ble_gap_event* event, void* arg); static int handleGapEvent(ble_gap_event* event, void* arg);
void onHostReset(); void onHostReset();
void onHostSync();
NimBLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr; NimBLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr;
void (*m_scanCompleteCB)(NimBLEScanResults scanResults); void (*m_scanCompleteCB)(NimBLEScanResults scanResults);
ble_gap_disc_params m_scan_params; ble_gap_disc_params m_scan_params;
uint8_t m_own_addr_type; bool m_ignoreResults;
bool m_stopped;
bool m_wantDuplicates; bool m_wantDuplicates;
NimBLEScanResults m_scanResults; NimBLEScanResults m_scanResults;
uint32_t m_duration; uint32_t m_duration;

View File

@ -296,6 +296,7 @@ size_t NimBLEServer::getConnectedCount() {
} }
server->m_pServerCallbacks->onDisconnect(server); server->m_pServerCallbacks->onDisconnect(server);
server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn);
if(server->m_advertiseOnDisconnect) { if(server->m_advertiseOnDisconnect) {
server->startAdvertising(); server->startAdvertising();
@ -658,6 +659,10 @@ void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default"); NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect } // onDisconnect
void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect
uint32_t NimBLEServerCallbacks::onPassKeyRequest(){ uint32_t NimBLEServerCallbacks::onPassKeyRequest(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyRequest: default: 123456"); NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyRequest: default: 123456");
return 123456; return 123456;

View File

@ -114,6 +114,15 @@ public:
*/ */
virtual void onDisconnect(NimBLEServer* pServer); virtual void onDisconnect(NimBLEServer* pServer);
/**
* @brief Handle a client disconnection.
* This is called when a client discconnects.
* @param [in] pServer A pointer to the %BLE server that received the client disconnection.
* @param [in] desc A pointer to the connection description structure containig information
* about the connection.
*/
virtual void onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc);
/** /**
* @brief Called when a client requests a passkey for pairing. * @brief Called when a client requests a passkey for pairing.
* @return The passkey to be sent to the client. * @return The passkey to be sent to the client.

View File

@ -264,6 +264,37 @@ std::string NimBLEUUID::toString() const {
*/ */
bool NimBLEUUID::operator ==(const NimBLEUUID & rhs) const { bool NimBLEUUID::operator ==(const NimBLEUUID & rhs) const {
if(m_valueSet && rhs.m_valueSet) { if(m_valueSet && rhs.m_valueSet) {
NIMBLE_LOGD(LOG_TAG,"Comparing UUIDs; type %u to %u; UUID %s to %s",
m_uuid.u.type, rhs.m_uuid.u.type,
this->toString().c_str(), rhs.toString().c_str());
if(m_uuid.u.type != rhs.m_uuid.u.type) {
uint8_t uuidBase[16] = {
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
if(m_uuid.u.type == BLE_UUID_TYPE_128){
if(rhs.m_uuid.u.type == BLE_UUID_TYPE_16){
memcpy(uuidBase+12, &rhs.m_uuid.u16.value, 2);
} else if (rhs.m_uuid.u.type == BLE_UUID_TYPE_32){
memcpy(uuidBase+12, &rhs.m_uuid.u32.value, 4);
}
return memcmp(m_uuid.u128.value,uuidBase,16) == 0;
} else if(rhs.m_uuid.u.type == BLE_UUID_TYPE_128) {
if(m_uuid.u.type == BLE_UUID_TYPE_16){
memcpy(uuidBase+12, &m_uuid.u16.value, 2);
} else if (m_uuid.u.type == BLE_UUID_TYPE_32){
memcpy(uuidBase+12, &m_uuid.u32.value, 4);
}
return memcmp(rhs.m_uuid.u128.value,uuidBase,16) == 0;
} else {
return false;
}
}
return ble_uuid_cmp(&m_uuid.u, &rhs.m_uuid.u) == 0; return ble_uuid_cmp(&m_uuid.u, &rhs.m_uuid.u) == 0;
} }

View File

@ -19,6 +19,13 @@
* under the License. * under the License.
*/ */
/*
* This file has been modified by Ryan Powell, aka h2zero.
* The modifications are for the purpose of improving performance and support
* for Esprssif versions used by the ardruino-esp32 core that are less current
* than the esp-idf releases.
*/
#include <assert.h> #include <assert.h>
#include "sysinit/sysinit.h" #include "sysinit/sysinit.h"
#include "nimble/hci_common.h" #include "nimble/hci_common.h"
@ -30,8 +37,10 @@
#include "esp_bt.h" #include "esp_bt.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "esp_compiler.h" #include "esp_compiler.h"
/* IPC is used to improve performance when calls come from a processor not running the NimBLE stack */
/* but does not exist for solo */
#ifndef CONFIG_FREERTOS_UNICORE #ifndef CONFIG_FREERTOS_UNICORE
#include "esp_ipc.h" #include "esp_ipc.h"
#endif #endif
#define NIMBLE_VHCI_TIMEOUT_MS 2000 #define NIMBLE_VHCI_TIMEOUT_MS 2000
@ -81,31 +90,40 @@ void ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb,
ble_hci_rx_acl_hs_arg = acl_arg; ble_hci_rx_acl_hs_arg = acl_arg;
} }
void ble_hci_trans_hs_cmd_tx_on_core_0(void *arg) /* Added; Called from the core NimBLE is running on, not used for unicore */
#ifndef CONFIG_FREERTOS_UNICORE
void ble_hci_trans_hs_cmd_tx_on_core(void *arg)
{ {
uint8_t *cmd = arg; // Ugly but necessary as the arduino core does not provide enough IPC stack for variables.
uint16_t len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1; esp_vhci_host_send_packet((uint8_t*)arg, *((uint8_t*)arg + 3) + 1 + BLE_HCI_CMD_HDR_LEN);
esp_vhci_host_send_packet(cmd, len);
} }
#endif
/* Modified to use ipc calls in arduino to correct performance issues */
int ble_hci_trans_hs_cmd_tx(uint8_t *cmd) int ble_hci_trans_hs_cmd_tx(uint8_t *cmd)
{ {
uint16_t len;
uint8_t rc = 0; uint8_t rc = 0;
assert(cmd != NULL); assert(cmd != NULL);
*cmd = BLE_HCI_UART_H4_CMD; *cmd = BLE_HCI_UART_H4_CMD;
len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1;
if (!esp_vhci_host_check_send_available()) { if (!esp_vhci_host_check_send_available()) {
ESP_LOGD(TAG, "Controller not ready to receive packets"); ESP_LOGD(TAG, "Controller not ready to receive packets");
} }
if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) { if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
if (xPortGetCoreID() != 0) { /* esp_ipc_call_blocking does not exist for solo */
#ifndef CONFIG_FREERTOS_UNICORE #ifndef CONFIG_FREERTOS_UNICORE
esp_ipc_call_blocking(0, ble_hci_trans_hs_cmd_tx_on_core_0, cmd); if (xPortGetCoreID() != CONFIG_BT_NIMBLE_PINNED_TO_CORE) {
#endif esp_ipc_call_blocking(CONFIG_BT_NIMBLE_PINNED_TO_CORE,
ble_hci_trans_hs_cmd_tx_on_core, cmd);
} else { } else {
ble_hci_trans_hs_cmd_tx_on_core_0(cmd); esp_vhci_host_send_packet(cmd, len);
} }
#else /* Unicore */
esp_vhci_host_send_packet(cmd, len);
#endif
} else { } else {
rc = BLE_HS_ETIMEOUT_HCI; rc = BLE_HS_ETIMEOUT_HCI;
} }
@ -124,21 +142,21 @@ int ble_hci_trans_ll_evt_tx(uint8_t *hci_ev)
return rc; return rc;
} }
void ble_hci_trans_hs_acl_tx_on_core_0(void *arg) /* Added; Called from the core NimBLE is running on, not used for unicore */
#ifndef CONFIG_FREERTOS_UNICORE
void ble_hci_trans_hs_acl_tx_on_core(void *arg)
{ {
uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 1]; // Ugly but necessary as the arduino core does not provide enough IPC stack for variables.
struct os_mbuf *om = arg; esp_vhci_host_send_packet((uint8_t*)arg + 2, *(uint16_t*)arg);
uint16_t len = 1 + OS_MBUF_PKTLEN(om);
data[0] = BLE_HCI_UART_H4_ACL;
os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
esp_vhci_host_send_packet(data, len);
} }
#endif
/* Modified to use ipc calls in arduino to correct performance issues */
int ble_hci_trans_hs_acl_tx(struct os_mbuf *om) int ble_hci_trans_hs_acl_tx(struct os_mbuf *om)
{ {
uint8_t rc = 0; uint16_t len = 0;
uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 3], rc = 0;
bool tx_using_nimble_core = 0;
/* If this packet is zero length, just free it */ /* If this packet is zero length, just free it */
if (OS_MBUF_PKTLEN(om) == 0) { if (OS_MBUF_PKTLEN(om) == 0) {
os_mbuf_free_chain(om); os_mbuf_free_chain(om);
@ -149,14 +167,36 @@ int ble_hci_trans_hs_acl_tx(struct os_mbuf *om)
ESP_LOGD(TAG, "Controller not ready to receive packets"); ESP_LOGD(TAG, "Controller not ready to receive packets");
} }
if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) { len = 1 + OS_MBUF_PKTLEN(om);
if (xPortGetCoreID() != 0) { /* Don't check core ID if unicore */
#ifndef CONFIG_FREERTOS_UNICORE #ifndef CONFIG_FREERTOS_UNICORE
esp_ipc_call_blocking(0, ble_hci_trans_hs_acl_tx_on_core_0, om); tx_using_nimble_core = xPortGetCoreID() != CONFIG_BT_NIMBLE_PINNED_TO_CORE;
if (tx_using_nimble_core) {
data[0] = len;
data[1] = (len >> 8);
data[2] = BLE_HCI_UART_H4_ACL;
os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[3]);
} else {
data[0] = BLE_HCI_UART_H4_ACL;
os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
}
#else /* Unicore */
data[0] = BLE_HCI_UART_H4_ACL;
os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
#endif #endif
if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
/* esp_ipc_call_blocking does not exist for solo */
#ifndef CONFIG_FREERTOS_UNICORE
if (tx_using_nimble_core) {
esp_ipc_call_blocking(CONFIG_BT_NIMBLE_PINNED_TO_CORE,
ble_hci_trans_hs_acl_tx_on_core, data);
} else { } else {
ble_hci_trans_hs_acl_tx_on_core_0(om); esp_vhci_host_send_packet(data, len);
} }
#else /* Unicore */
esp_vhci_host_send_packet(data, len);
#endif
} else { } else {
rc = BLE_HS_ETIMEOUT_HCI; rc = BLE_HS_ETIMEOUT_HCI;
} }
@ -367,6 +407,13 @@ static int host_rcv_pkt(uint8_t *data, uint16_t len)
totlen = BLE_HCI_EVENT_HDR_LEN + data[2]; totlen = BLE_HCI_EVENT_HDR_LEN + data[2];
assert(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN); assert(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN);
if (totlen > MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE)) {
ESP_LOGE(TAG, "Received HCI data length at host (%d) exceeds maximum configured HCI event buffer size (%d).",
totlen, MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE));
ble_hs_sched_reset(BLE_HS_ECONTROLLER);
return 0;
}
if (data[1] == BLE_HCI_EVCODE_HW_ERROR) { if (data[1] == BLE_HCI_EVCODE_HW_ERROR) {
assert(0); assert(0);
} }
@ -476,6 +523,9 @@ esp_err_t esp_nimble_hci_and_controller_init(void)
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
/* Added to ensure BLE only mode */
bt_cfg.mode = ESP_BT_MODE_BLE;
/* Added to set max connections from nimconfig */
bt_cfg.ble_max_conn = CONFIG_BT_NIMBLE_MAX_CONNECTIONS; bt_cfg.ble_max_conn = CONFIG_BT_NIMBLE_MAX_CONNECTIONS;
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
@ -485,6 +535,7 @@ esp_err_t esp_nimble_hci_and_controller_init(void)
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) { if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {
return ret; return ret;
} }
return esp_nimble_hci_init(); return esp_nimble_hci_init();
} }

View File

@ -483,6 +483,10 @@
#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM
#endif #endif
#ifndef MYNEWT_VAL_BLE_L2CAP_COC_MPS
#define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE - 8)
#endif
#ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS
#define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
#endif #endif

View File

@ -76,7 +76,7 @@ ble_eddystone_set_adv_data_gen(struct ble_hs_adv_fields *adv_fields,
if (adv_fields->num_uuids16 > BLE_EDDYSTONE_MAX_UUIDS16) { if (adv_fields->num_uuids16 > BLE_EDDYSTONE_MAX_UUIDS16) {
return BLE_HS_EINVAL; return BLE_HS_EINVAL;
} }
if (svc_data_len > BLE_EDDYSTONE_MAX_SVC_DATA_LEN) { if (svc_data_len > (BLE_EDDYSTONE_MAX_SVC_DATA_LEN - BLE_EDDYSTONE_SVC_DATA_BASE_SZ)) {
return BLE_HS_EINVAL; return BLE_HS_EINVAL;
} }
if (adv_fields->num_uuids16 > 0 && !adv_fields->uuids16_is_complete) { if (adv_fields->num_uuids16 > 0 && !adv_fields->uuids16_is_complete) {

View File

@ -1017,7 +1017,7 @@ ble_gap_master_failed(int status)
#endif #endif
default: default:
BLE_HS_DBG_ASSERT(0); //BLE_HS_DBG_ASSERT(0);
break; break;
} }
} }
@ -1458,8 +1458,8 @@ ble_gap_rx_periodic_adv_rpt(struct hci_le_subev_periodic_adv_rpt *evt)
{ {
struct ble_hs_periodic_sync *psync; struct ble_hs_periodic_sync *psync;
struct ble_gap_event event; struct ble_gap_event event;
ble_gap_event_fn *cb; ble_gap_event_fn *cb = NULL;
void *cb_arg; void *cb_arg = NULL;
ble_hs_lock(); ble_hs_lock();
psync = ble_hs_periodic_sync_find_by_handle(evt->sync_handle); psync = ble_hs_periodic_sync_find_by_handle(evt->sync_handle);

View File

@ -470,29 +470,52 @@ ble_hs_conn_timer(void)
int32_t time_diff; int32_t time_diff;
uint16_t conn_handle; uint16_t conn_handle;
conn_handle = BLE_HS_CONN_HANDLE_NONE; for (;;) {
next_exp_in = BLE_HS_FOREVER; conn_handle = BLE_HS_CONN_HANDLE_NONE;
now = ble_npl_time_get(); next_exp_in = BLE_HS_FOREVER;
now = ble_npl_time_get();
ble_hs_lock(); ble_hs_lock();
/* This loop performs one of two tasks: /* This loop performs one of two tasks:
* 1. Determine if any connections need to be terminated due to timeout. * 1. Determine if any connections need to be terminated due to timeout.
* If so, break out of the loop and terminate the connection. This * If so, break out of the loop and terminate the connection. This
* function will need to be executed again. * function will need to be executed again.
* 2. Otherwise, determine when the next timeout will occur. * 2. Otherwise, determine when the next timeout will occur.
*/ */
SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) { SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) { if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) {
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0 #if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
/* Check each connection's rx fragment timer. If too much time /* Check each connection's rx fragment timer. If too much time
* passes after a partial packet is received, the connection is * passes after a partial packet is received, the connection is
* terminated. * terminated.
*/ */
if (conn->bhc_rx_chan != NULL) { if (conn->bhc_rx_chan != NULL) {
time_diff = conn->bhc_rx_timeout - now; time_diff = conn->bhc_rx_timeout - now;
if (time_diff <= 0) {
/* ACL reassembly has timed out. Remember the connection
* handle so it can be terminated after the mutex is
* unlocked.
*/
conn_handle = conn->bhc_handle;
break;
}
/* Determine if this connection is the soonest to time out. */
if (time_diff < next_exp_in) {
next_exp_in = time_diff;
}
}
#endif
#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO
/* Check each connection's rx queued write timer. If too much
* time passes after a prep write is received, the queue is
* cleared.
*/
time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now);
if (time_diff <= 0) { if (time_diff <= 0) {
/* ACL reassembly has timed out. Remember the connection /* ACL reassembly has timed out. Remember the connection
* handle so it can be terminated after the mutex is * handle so it can be terminated after the mutex is
@ -506,45 +529,22 @@ ble_hs_conn_timer(void)
if (time_diff < next_exp_in) { if (time_diff < next_exp_in) {
next_exp_in = time_diff; next_exp_in = time_diff;
} }
}
#endif #endif
#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO
/* Check each connection's rx queued write timer. If too much
* time passes after a prep write is received, the queue is
* cleared.
*/
time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now);
if (time_diff <= 0) {
/* ACL reassembly has timed out. Remember the connection
* handle so it can be terminated after the mutex is
* unlocked.
*/
conn_handle = conn->bhc_handle;
break;
} }
/* Determine if this connection is the soonest to time out. */
if (time_diff < next_exp_in) {
next_exp_in = time_diff;
}
#endif
} }
ble_hs_unlock();
/* If a connection has timed out, terminate it. We need to repeatedly
* call this function again to determine when the next timeout is.
*/
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
continue;
}
return next_exp_in;
} }
ble_hs_unlock();
/* If a connection has timed out, terminate it. We need to recursively
* call this function again to determine when the next timeout is. This
* is a tail-recursive call, so it should be optimized to execute in the
* same stack frame.
*/
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return ble_hs_conn_timer();
}
return next_exp_in;
} }
int int

View File

@ -180,7 +180,9 @@ static int
get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value) get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value)
{ {
union ble_store_value cur = {0}; union ble_store_value cur = {0};
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
struct ble_hs_dev_records p_dev_rec = {0}; struct ble_hs_dev_records p_dev_rec = {0};
#endif
esp_err_t err; esp_err_t err;
int i, count = 0, max_limit = 0; int i, count = 0, max_limit = 0;
char key_string[NIMBLE_NVS_STR_NAME_MAX_LEN]; char key_string[NIMBLE_NVS_STR_NAME_MAX_LEN];
@ -190,11 +192,15 @@ get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value)
for (i = 1; i <= max_limit; i++) { for (i = 1; i <= max_limit; i++) {
get_nvs_key_string(obj_type, i, key_string); get_nvs_key_string(obj_type, i, key_string);
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) { if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) {
#endif
err = get_nvs_db_value(obj_type, key_string, &cur); err = get_nvs_db_value(obj_type, key_string, &cur);
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
} else { } else {
err = get_nvs_peer_record(key_string, &p_dev_rec); err = get_nvs_peer_record(key_string, &p_dev_rec);
} }
#endif
/* Check if the user is searching for empty index to write to */ /* Check if the user is searching for empty index to write to */
if (err == ESP_ERR_NVS_NOT_FOUND) { if (err == ESP_ERR_NVS_NOT_FOUND) {
if (empty) { if (empty) {
@ -206,10 +212,13 @@ get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value)
/* If user has provided value, then the purpose is to find /* If user has provided value, then the purpose is to find
* non-matching entry from NVS */ * non-matching entry from NVS */
if (value) { if (value) {
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
if (obj_type == BLE_STORE_OBJ_TYPE_PEER_DEV_REC) { if (obj_type == BLE_STORE_OBJ_TYPE_PEER_DEV_REC) {
err = get_nvs_matching_index(&p_dev_rec, value, num_value, err = get_nvs_matching_index(&p_dev_rec, value, num_value,
sizeof(struct ble_hs_dev_records)); sizeof(struct ble_hs_dev_records));
} else { } else
#endif
{
if (obj_type != BLE_STORE_OBJ_TYPE_CCCD) { if (obj_type != BLE_STORE_OBJ_TYPE_CCCD) {
err = get_nvs_matching_index(&cur.sec, value, num_value, err = get_nvs_matching_index(&cur.sec, value, num_value,
sizeof(struct ble_store_value_sec)); sizeof(struct ble_store_value_sec));
@ -376,7 +385,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
{ {
uint8_t *db_item = (uint8_t *)dst; uint8_t *db_item = (uint8_t *)dst;
union ble_store_value cur = {0}; union ble_store_value cur = {0};
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
struct ble_hs_dev_records p_dev_rec = {0}; struct ble_hs_dev_records p_dev_rec = {0};
#endif
esp_err_t err; esp_err_t err;
int i; int i;
@ -385,8 +396,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
for (i = 1; i <= get_nvs_max_obj_value(obj_type); i++) { for (i = 1; i <= get_nvs_max_obj_value(obj_type); i++) {
get_nvs_key_string(obj_type, i, key_string); get_nvs_key_string(obj_type, i, key_string);
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) { if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) {
#endif
err = get_nvs_db_value(obj_type, key_string, &cur); err = get_nvs_db_value(obj_type, key_string, &cur);
if (err == ESP_ERR_NVS_NOT_FOUND) { if (err == ESP_ERR_NVS_NOT_FOUND) {
continue; continue;
@ -394,6 +406,7 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
ESP_LOGE(TAG, "NVS read operation failed !!"); ESP_LOGE(TAG, "NVS read operation failed !!");
return -1; return -1;
} }
#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
} else { } else {
err = get_nvs_peer_record(key_string, &p_dev_rec); err = get_nvs_peer_record(key_string, &p_dev_rec);
if (err == ESP_ERR_NVS_NOT_FOUND) { if (err == ESP_ERR_NVS_NOT_FOUND) {
@ -410,7 +423,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
memcpy(db_item, &p_dev_rec, sizeof(struct ble_hs_dev_records)); memcpy(db_item, &p_dev_rec, sizeof(struct ble_hs_dev_records));
db_item += sizeof(struct ble_hs_dev_records); db_item += sizeof(struct ble_hs_dev_records);
(*db_num)++; (*db_num)++;
} else { } else
#endif
{
if (obj_type == BLE_STORE_OBJ_TYPE_CCCD) { if (obj_type == BLE_STORE_OBJ_TYPE_CCCD) {
ESP_LOGD(TAG, "CCCD in RAM is filled up from NVS index = %d", i); ESP_LOGD(TAG, "CCCD in RAM is filled up from NVS index = %d", i);
memcpy(db_item, &cur.cccd, sizeof(struct ble_store_value_cccd)); memcpy(db_item, &cur.cccd, sizeof(struct ble_store_value_cccd));
@ -492,6 +507,11 @@ int ble_store_config_persist_cccds(void)
union ble_store_value val; union ble_store_value val;
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_CCCD, 0, NULL, 0); nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_CCCD, 0, NULL, 0);
if (nvs_count == -1) {
ESP_LOGE(TAG, "NVS operation failed while persisting CCCD");
return BLE_HS_ESTORE_FAIL;
}
if (nvs_count < ble_store_config_num_cccds) { if (nvs_count < ble_store_config_num_cccds) {
/* NVS db count less than RAM count, write operation */ /* NVS db count less than RAM count, write operation */
@ -518,6 +538,11 @@ int ble_store_config_persist_peer_secs(void)
union ble_store_value val; union ble_store_value val;
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_SEC, 0, NULL, 0); nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_SEC, 0, NULL, 0);
if (nvs_count == -1) {
ESP_LOGE(TAG, "NVS operation failed while persisting peer sec");
return BLE_HS_ESTORE_FAIL;
}
if (nvs_count < ble_store_config_num_peer_secs) { if (nvs_count < ble_store_config_num_peer_secs) {
/* NVS db count less than RAM count, write operation */ /* NVS db count less than RAM count, write operation */
@ -544,6 +569,11 @@ int ble_store_config_persist_our_secs(void)
union ble_store_value val; union ble_store_value val;
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_OUR_SEC, 0, NULL, 0); nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_OUR_SEC, 0, NULL, 0);
if (nvs_count == -1) {
ESP_LOGE(TAG, "NVS operation failed while persisting our sec");
return BLE_HS_ESTORE_FAIL;
}
if (nvs_count < ble_store_config_num_our_secs) { if (nvs_count < ble_store_config_num_our_secs) {
/* NVS db count less than RAM count, write operation */ /* NVS db count less than RAM count, write operation */
@ -573,7 +603,13 @@ int ble_store_persist_peer_records(void)
struct ble_hs_dev_records *peer_dev_rec = ble_rpa_get_peer_dev_records(); struct ble_hs_dev_records *peer_dev_rec = ble_rpa_get_peer_dev_records();
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_DEV_REC, 0, NULL, 0); nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_DEV_REC, 0, NULL, 0);
if (nvs_count == -1) {
ESP_LOGE(TAG, "NVS operation failed while persisting peer_dev_rec");
return BLE_HS_ESTORE_FAIL;
}
if (nvs_count < ble_store_num_peer_dev_rec) { if (nvs_count < ble_store_num_peer_dev_rec) {
/* NVS db count less than RAM count, write operation */ /* NVS db count less than RAM count, write operation */
ESP_LOGD(TAG, "Persisting peer dev record to NVS..."); ESP_LOGD(TAG, "Persisting peer dev record to NVS...");
peer_rec = peer_dev_rec[ble_store_num_peer_dev_rec - 1]; peer_rec = peer_dev_rec[ble_store_num_peer_dev_rec - 1];

View File

@ -15,8 +15,13 @@
* This converts them to "CONFIG_BT_NIMBLE_" format used in the latest IDF. * This converts them to "CONFIG_BT_NIMBLE_" format used in the latest IDF.
*/ */
/* Detect if using ESP-IDF or Arduino (Arduino won't have these defines in sdkconfig) */ /* Detect if using ESP-IDF or Arduino (Arduino won't have these defines in sdkconfig)
#if defined(CONFIG_BT_NIMBLE_TASK_STACK_SIZE) || defined(CONFIG_NIMBLE_TASK_STACK_SIZE) *
* Note: We do not use #ifdef CONFIG_BT_NIMBLE_ENABLED since we cannot enable NimBLE when using
* Arduino as a component and the esp-nimble-compnent, so we check if other config options are defined.
* We also need to use a config parameter that must be present and not likely defined in the command line.
*/
#if defined(CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN) || defined(CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN)
#if defined(CONFIG_NIMBLE_ENABLED) && !defined(CONFIG_BT_NIMBLE_ENABLED) #if defined(CONFIG_NIMBLE_ENABLED) && !defined(CONFIG_BT_NIMBLE_ENABLED)
#define CONFIG_BT_NIMBLE_ENABLED #define CONFIG_BT_NIMBLE_ENABLED
@ -51,22 +56,30 @@
/** @brief Comment out if not using NimBLE Client functions \n /** @brief Comment out if not using NimBLE Client functions \n
* Reduces flash size by approx. 7kB. * Reduces flash size by approx. 7kB.
*/ */
#ifndef CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED
#define CONFIG_BT_NIMBLE_ROLE_CENTRAL #define CONFIG_BT_NIMBLE_ROLE_CENTRAL
#endif
/** @brief Comment out if not using NimBLE Scan functions \n /** @brief Comment out if not using NimBLE Scan functions \n
* Reduces flash size by approx. 26kB. * Reduces flash size by approx. 26kB.
*/ */
#ifndef CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED
#define CONFIG_BT_NIMBLE_ROLE_OBSERVER #define CONFIG_BT_NIMBLE_ROLE_OBSERVER
#endif
/** @brief Comment out if not using NimBLE Server functions \n /** @brief Comment out if not using NimBLE Server functions \n
* Reduces flash size by approx. 16kB. * Reduces flash size by approx. 16kB.
*/ */
// #define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL #ifndef CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED
#define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
#endif
/** @brief Comment out if not using NimBLE Advertising functions \n /** @brief Comment out if not using NimBLE Advertising functions \n
* Reduces flash size by approx. 5kB. * Reduces flash size by approx. 5kB.
*/ */
// #define CONFIG_BT_NIMBLE_ROLE_BROADCASTER #ifndef CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED
#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER
#endif
/* Uncomment to see debug log messages from the NimBLE host /* Uncomment to see debug log messages from the NimBLE host
* Uses approx. 32kB of flash memory. * Uses approx. 32kB of flash memory.
@ -89,29 +102,46 @@
// #define CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT // #define CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT
/** @brief Sets the core NimBLE host runs on */ /** @brief Sets the core NimBLE host runs on */
#ifndef CONFIG_BT_NIMBLE_PINNED_TO_CORE
#define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0 #define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0
#endif
/** @brief Sets the stack size for the NimBLE host task */ /** @brief Sets the stack size for the NimBLE host task */
#ifndef CONFIG_BT_NIMBLE_TASK_STACK_SIZE
#define CONFIG_BT_NIMBLE_TASK_STACK_SIZE 4096 #define CONFIG_BT_NIMBLE_TASK_STACK_SIZE 4096
#endif
/** /**
* @brief Sets the memory pool where NimBLE will be loaded * @brief Sets the memory pool where NimBLE will be loaded
* @details By default NimBLE is loaded in internal ram.\n * @details By default NimBLE is loaded in internal ram.\n
* To use external PSRAM you must change this to `#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1` * To use external PSRAM you must change this to `#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1`
*/ */
#ifndef CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL
#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1 #define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1
#endif
/** @brief Sets the number of simultaneous connections (esp controller max is 9) */ /** @brief Sets the number of simultaneous connections (esp controller max is 9) */
#ifndef CONFIG_BT_NIMBLE_MAX_CONNECTIONS
#define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3 #define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3
#endif
/** @brief Sets the number of devices allowed to store/bond with */ /** @brief Sets the number of devices allowed to store/bond with */
#ifndef CONFIG_BT_NIMBLE_MAX_BONDS
#define CONFIG_BT_NIMBLE_MAX_BONDS 3 #define CONFIG_BT_NIMBLE_MAX_BONDS 3
#endif
/** @brief Sets the maximum number of CCCD subscriptions to store */ /** @brief Sets the maximum number of CCCD subscriptions to store */
#ifndef CONFIG_BT_NIMBLE_MAX_CCCDS
#define CONFIG_BT_NIMBLE_MAX_CCCDS 8 #define CONFIG_BT_NIMBLE_MAX_CCCDS 8
#endif
/** @brief Default device name */
#ifndef CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME
#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble"
#endif
/** @brief Set if CCCD's and bond data should be stored in NVS */ /** @brief Set if CCCD's and bond data should be stored in NVS */
#define CONFIG_BT_NIMBLE_NVS_PERSIST 0 #define CONFIG_BT_NIMBLE_NVS_PERSIST 1
/** @brief Allow legacy paring */ /** @brief Allow legacy paring */
#define CONFIG_BT_NIMBLE_SM_LEGACY 1 #define CONFIG_BT_NIMBLE_SM_LEGACY 1
@ -119,9 +149,6 @@
/** @brief Allow BLE secure connections */ /** @brief Allow BLE secure connections */
#define CONFIG_BT_NIMBLE_SM_SC 1 #define CONFIG_BT_NIMBLE_SM_SC 1
/** @brief Default device name */
#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble"
/** @brief Max device name length (bytes) */ /** @brief Max device name length (bytes) */
#define CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN 31 #define CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN 31
@ -154,7 +181,6 @@
*/ */
#define CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT 12 #define CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT 12
/** @brief Random address refresh time in seconds */ /** @brief Random address refresh time in seconds */
#define CONFIG_BT_NIMBLE_RPA_TIMEOUT 900 #define CONFIG_BT_NIMBLE_RPA_TIMEOUT 900

View File

@ -861,6 +861,9 @@
//#define USE_SPI // Add support for hardware SPI //#define USE_SPI // Add support for hardware SPI
#define USE_MI_ESP32 // Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #define USE_MI_ESP32 // Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
//#define USE_BLE_ESP32 // Add support for ESP32 as a BLE-bridge (+9k2? mem, +292k? flash)
//#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
//#define USE_IBEACON_ESP32
//#define USE_WEBCAM // Add support for webcam //#define USE_WEBCAM // Add support for webcam
#endif // ESP32 #endif // ESP32

View File

@ -690,6 +690,12 @@ void CmndSleep(void)
} }
#ifdef USE_BLE_ESP32
// declare the fn
int ExtStopBLE();
#endif
void CmndUpgrade(void) void CmndUpgrade(void)
{ {
// Check if the payload is numerically 1, and had no trailing chars. // Check if the payload is numerically 1, and had no trailing chars.
@ -700,6 +706,11 @@ void CmndUpgrade(void)
TasmotaGlobal.ota_state_flag = 3; TasmotaGlobal.ota_state_flag = 3;
char stemp1[TOPSZ]; char stemp1[TOPSZ];
Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, TasmotaGlobal.version, GetOtaUrl(stemp1, sizeof(stemp1))); Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, TasmotaGlobal.version, GetOtaUrl(stemp1, sizeof(stemp1)));
#ifdef USE_BLE_ESP32
ExtStopBLE();
#endif
} else { } else {
Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, TasmotaGlobal.version); Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, TasmotaGlobal.version);
} }

View File

@ -2374,6 +2374,11 @@ void UploadServices(uint32_t start_service) {
} }
} }
#ifdef USE_BLE_ESP32
// declare the fn
int ExtStopBLE();
#endif
void HandleUploadLoop(void) { void HandleUploadLoop(void) {
// Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update) // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update)
static uint32_t upload_size; static uint32_t upload_size;
@ -2409,6 +2414,11 @@ void HandleUploadLoop(void) {
} }
SettingsSave(1); // Free flash for upload SettingsSave(1); // Free flash for upload
#ifdef USE_BLE_ESP32
ExtStopBLE();
#endif
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_FILE " %s"), upload.filename.c_str()); AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_FILE " %s"), upload.filename.c_str());
if (UPL_SETTINGS == Web.upload_file_type) { if (UPL_SETTINGS == Web.upload_file_type) {
@ -2632,7 +2642,9 @@ void HandleUploadLoop(void) {
Web.upload_error = 7; // Upload aborted Web.upload_error = 7; // Upload aborted
if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); } if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); }
} }
delay(0); // do actually wait a little to allow ESP32 tasks to tick
// fixes task timeout in ESP32Solo1 style unicore code.
delay(10);
OsWatchLoop(); OsWatchLoop();
// Scheduler(); // Feed OsWatch timer to prevent restart on long uploads // Scheduler(); // Feed OsWatch timer to prevent restart on long uploads
} }

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// for testing of BLE_ESP32, we remove this completely, and instead add the modified xsns_52_ibeacon_BLE_ESP32.ino
// in the future this may be more fine-grained, e.g. to allow hm17 for this, and BLE-ESP32 for other
#ifndef USE_BLE_ESP32
#ifdef USE_IBEACON #ifdef USE_IBEACON
#define XSNS_52 52 #define XSNS_52 52
@ -279,13 +283,16 @@ void ESP32Init() {
if (TasmotaGlobal.global_state.wifi_down) { return; } if (TasmotaGlobal.global_state.wifi_down) { return; }
TasmotaGlobal.wifi_stay_asleep = true;
if (WiFi.getSleep() == false) { if (WiFi.getSleep() == false) {
AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Put WiFi modem in sleep mode"),"BLE"); if (0 == Settings.flag3.sleep_normal) {
WiFi.setSleep(true); // Sleep AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: About to restart to put WiFi modem in sleep mode"),"BLE");
Settings.flag3.sleep_normal = 1; // SetOption60 - Enable normal sleep instead of dynamic sleep
TasmotaGlobal.restart_flag = 2;
}
return;
} }
AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Initializing Bluetooth..."),"BLE"); AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Initializing Blueetooth..."),"BLE");
if (!ESP32BLE.mode.init) { if (!ESP32BLE.mode.init) {
NimBLEDevice::init(""); NimBLEDevice::init("");
@ -1012,3 +1019,5 @@ bool Xsns52(byte function)
} }
#endif // USE_IBEACON #endif // USE_IBEACON
#endif

View File

@ -0,0 +1,952 @@
/*
xsns_52_ibeacon.ino - Support for HM17 BLE Module + ibeacon reader on Tasmota
Copyright (C) 2020 Gerhard Mutz and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// for testing of BLE_ESP32, we remove xsns_52_ibeacon.ino completely, and instead add this modified xsns_52_ibeacon_BLE_ESP32.ino
// in the future this may be more fine-grained, e.g. to allow hm17 for this, and BLE-ESP32 for other
#ifdef USE_BLE_ESP32
#ifdef USE_IBEACON_ESP32
#ifdef USE_IBEACON
#define XSNS_52 52
// keyfob expires after N seconds
#define IB_TIMEOUT_INTERVAL 30
// does a passive scan every N seconds
#define IB_UPDATE_TIME_INTERVAL 10
// should be in Settings
#if 1
uint8_t ib_upd_interval,ib_tout_interval;
#define IB_UPDATE_TIME ib_upd_interval
#define IB_TIMEOUT_TIME ib_tout_interval
#else
#undef IB_UPDATE_TIME
#undef IB_TIMEOUT_TIME
#define IB_UPDATE_TIME Settings.ib_upd_interval
#define IB_TIMEOUT_TIME Settings.ib_tout_interval
#endif
char ib_mac[14];
struct {
union {
struct {
uint32_t init:1;
};
uint32_t all = 0;
} mode;
} ESP32BLE;
void *beaconmutex = nullptr;
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
#else
#include <TasmotaSerial.h>
#define TMSBSIZ52 512
#define HM17_BAUDRATE 9600
#define IBEACON_DEBUG
// use this for Version 110
#define HM17_V110
TasmotaSerial *IBEACON_Serial = nullptr;
uint8_t hm17_found,hm17_cmd,hm17_flag;
#ifdef IBEACON_DEBUG
uint8_t hm17_debug=0;
#endif
// 78 is max serial response
#define HM17_BSIZ 128
char hm17_sbuffer[HM17_BSIZ];
uint8_t hm17_sindex,hm17_result,hm17_scanning,hm17_connecting;
uint32_t hm17_lastms;
enum {HM17_TEST,HM17_ROLE,HM17_IMME,HM17_DISI,HM17_IBEA,HM17_SCAN,HM17_DISC,HM17_RESET,HM17_RENEW,HM17_CON};
#define HM17_SUCESS 99
#endif
struct IBEACON {
char FACID[8];
char UID[32];
char MAJOR[4];
char MINOR[4];
char PWR[2];
char MAC[12];
char RSSI[4];
#ifdef USE_IBEACON_ESP32
char NAME[16];
#endif
};
#ifdef USE_IBEACON_ESP32
#define MAX_IBEACONS 32
#else
#define MAX_IBEACONS 16
#endif
struct IBEACON_UID {
char MAC[12];
char RSSI[4];
char UID[32];
char MAJOR[4];
char MINOR[4];
uint8_t FLAGS;
uint8_t TIME;
#ifdef USE_IBEACON_ESP32
uint8_t REPORTED;
uint8_t REPTIME;
char NAME[16];
#endif
} ibeacons[MAX_IBEACONS];
#ifdef USE_IBEACON_ESP32
uint32_t ibeacon_add(struct IBEACON *ib);
void ESP32BLE_ReverseStr(uint8_t _mac[], uint8_t len=6){
uint8_t _reversedMAC[len];
for (uint8_t i=0; i<len; i++){
_reversedMAC[len-1-i] = _mac[i];
}
memcpy(_mac,_reversedMAC, sizeof(_reversedMAC));
}
void DumpHex(const unsigned char * in, size_t insz, char * out)
{
static const char * hex = "0123456789ABCDEF";
const unsigned char * pin = in;
char * pout = out;
for (; pin < in+insz; pout+=2, pin++) {
pout[0] = hex[(pgm_read_byte(pin)>>4) & 0xF];
pout[1] = hex[ pgm_read_byte(pin) & 0xF];
}
}
int advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct)
{
struct IBEACON ib;
BLEAdvertisedDevice *advertisedDevice = pStruct->advertisedDevice;
char sRSSI[6];
itoa(pStruct->RSSI,sRSSI,10);
const uint8_t *MAC = pStruct->addr;
int manufacturerDataLen = 0;
std::string data;
if (advertisedDevice->haveManufacturerData()){
data = advertisedDevice->getManufacturerData();
manufacturerDataLen = data.length();
}
if (manufacturerDataLen){
const uint8_t *manufacturerData = (const uint8_t *)data.data();
DumpHex(manufacturerData, 2, ib.FACID);
if (manufacturerDataLen == 25 &&
manufacturerData[0] == 0x4C &&
manufacturerData[1] == 0x00)
{
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setData(std::string((char *)manufacturerData, manufacturerDataLen));
uint8_t UUID[16];
memcpy(UUID,oBeacon.getProximityUUID().getNative()->u128.value,16);
ESP32BLE_ReverseStr(UUID,16);
uint16_t Major = ENDIAN_CHANGE_U16(oBeacon.getMajor());
uint16_t Minor = ENDIAN_CHANGE_U16(oBeacon.getMinor());
uint8_t PWR = oBeacon.getSignalPower();
DumpHex((const unsigned char*)&UUID,16,ib.UID);
DumpHex((const unsigned char*)&Major,2,ib.MAJOR);
DumpHex((const unsigned char*)&Minor,2,ib.MINOR);
DumpHex((const unsigned char*)&PWR,1,ib.PWR);
DumpHex((const unsigned char*)MAC,6,ib.MAC);
memcpy(ib.RSSI,sRSSI,4);
memset(ib.NAME,0x0,16);
// if we added it
if (ibeacon_add(&ib) == 1){
AddLog_P(LOG_LEVEL_DEBUG, PSTR("%s: MAC: %s Major: %d Minor: %d UUID: %s Power: %d RSSI: %d"),
"iBeacon",
advertisedDevice->getAddress().toString().c_str(),
Major, Minor,
oBeacon.getProximityUUID().toString().c_str(),
PWR, pStruct->RSSI);
}
return 0;
}
}
// no manufacturer data, or not recognised.
// still have an RSSi....
memset(ib.UID,'0',32);
memset(ib.MAJOR,'0',4);
memset(ib.MINOR,'0',4);
memset(ib.PWR,'0',2);
DumpHex((const unsigned char*)MAC,6,ib.MAC);
memcpy(ib.RSSI,sRSSI,4);
if (advertisedDevice->haveName()) {
strncpy(ib.NAME,advertisedDevice->getName().c_str(),16);
} else {
memset(ib.NAME,0x0,16);
}
ibeacon_add(&ib);
return 0;
}
void ESP32Init() {
if (!ESP32BLE.mode.init) {
ESP32BLE.mode.init = 1;
IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL;
IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL;
}
}
#endif
void IBEACON_Init() {
#ifdef USE_IBEACON_ESP32
BLE_ESP32::registerForAdvertismentCallbacks((const char *)"iBeacon", advertismentCallback);
#else
hm17_found=0;
// actually doesnt work reliably with software serial
if (PinUsed(GPIO_IBEACON_RX) && PinUsed(GPIO_IBEACON_TX)) {
IBEACON_Serial = new TasmotaSerial(Pin(GPIO_IBEACON_RX), Pin(GPIO_IBEACON_TX),1,0,TMSBSIZ52);
if (IBEACON_Serial->begin(HM17_BAUDRATE)) {
if (IBEACON_Serial->hardwareSerial()) {
ClaimSerial();
}
hm17_sendcmd(HM17_TEST);
hm17_lastms=millis();
// in case of using Settings this has to be moved
IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL;
IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL;
}
}
#endif
}
#ifdef USE_IBEACON_ESP32
void esp32_every_second(void) {
for (uint32_t cnt=0; cnt < MAX_IBEACONS; cnt++) {
if (ibeacons[cnt].FLAGS) {
uint8_t mac[6];
char tmp[13];
memcpy(tmp, ibeacons[cnt].MAC, 12);
tmp[12] = 0;
BLE_ESP32::fromHex(mac, tmp, 6);
// use global device timeouts from BLE_ESP32.
uint32_t ageS = BLE_ESP32::devicePresent(mac);
// if device not present at all.
if (!ageS){
//AddLog_P(LOG_LEVEL_INFO, PSTR("iBeacon no device %s %02x%02x%02x%02x%02x%02x"),tmp, mac[0],mac[1], mac[2],mac[3], mac[4],mac[5]);
ibeacons[cnt].FLAGS=0;
ibeacon_mqtt(ibeacons[cnt].MAC,"0000",ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR,ibeacons[cnt].NAME);
} else {
//AddLog_P(LOG_LEVEL_INFO, PSTR("iBeacon device %s %02x%02x%02x%02x%02x%02x"),tmp, mac[0],mac[1], mac[2],mac[3], mac[4],mac[5]);
}
//ibeacons[cnt].TIME++;
ibeacons[cnt].REPTIME++; // counter used to send mqtt for a dev regularly
}
}
}
#else
void hm17_every_second(void) {
if (!IBEACON_Serial) return;
if (hm17_found) {
if (IB_UPDATE_TIME && (TasmotaGlobal.uptime%IB_UPDATE_TIME==0)) {
if (hm17_cmd!=99) {
if (hm17_flag&2) {
ib_sendbeep();
} else {
if (!hm17_connecting) {
hm17_sendcmd(HM17_DISI);
}
}
}
}
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS) {
ibeacons[cnt].TIME++;
if (ibeacons[cnt].TIME>IB_TIMEOUT_TIME) {
ibeacons[cnt].FLAGS=0;
ibeacon_mqtt(ibeacons[cnt].MAC,"0000",ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR);
}
}
}
} else {
if (TasmotaGlobal.uptime%20==0) {
hm17_sendcmd(HM17_TEST);
}
}
}
void hm17_sbclr(void) {
memset(hm17_sbuffer,0,HM17_BSIZ);
hm17_sindex=0;
//IBEACON_Serial->flush();
}
void hm17_sendcmd(uint8_t cmd) {
hm17_sbclr();
hm17_cmd=cmd;
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("hm17cmd %d"),cmd);
#endif
switch (cmd) {
case HM17_TEST:
IBEACON_Serial->write("AT");
break;
case HM17_ROLE:
IBEACON_Serial->write("AT+ROLE1");
break;
case HM17_IMME:
IBEACON_Serial->write("AT+IMME1");
break;
case HM17_DISI:
IBEACON_Serial->write("AT+DISI?");
hm17_scanning=1;
break;
case HM17_IBEA:
IBEACON_Serial->write("AT+IBEA1");
break;
case HM17_RESET:
IBEACON_Serial->write("AT+RESET");
break;
case HM17_RENEW:
IBEACON_Serial->write("AT+RENEW");
break;
case HM17_SCAN:
IBEACON_Serial->write("AT+SCAN5");
break;
case HM17_DISC:
IBEACON_Serial->write("AT+DISC?");
hm17_scanning=1;
break;
case HM17_CON:
IBEACON_Serial->write((const uint8_t*)"AT+CON",6);
IBEACON_Serial->write((const uint8_t*)ib_mac,12);
hm17_connecting=1;
break;
}
}
#endif
uint32_t ibeacon_add(struct IBEACON *ib) {
/* if (!strncmp(ib->MAJOR,"4B1C",4)) {
return 0;
}
*/
if (!strncmp(ib->RSSI,"0",1)) {
return 0;
}
// don't bother protecting this.
//TasAutoMutex localmutex(&beaconmutex, "iBeacAdd");
// keyfob starts with ffff, ibeacon has valid facid
if (!strncmp(ib->MAC,"FFFF",4) || strncmp(ib->FACID,"00000000",8)) {
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS) {
if (!strncmp_P(ib->UID,PSTR("00000000000000000000000000000000"),32)) {
if (!strncmp(ibeacons[cnt].MAC,ib->MAC,12)) {
// exists
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
ibeacons[cnt].TIME=0;
#ifdef USE_IBEACON_ESP32
if (ibeacons[cnt].REPTIME >= IB_UPDATE_TIME) {
ibeacons[cnt].REPTIME = 0;
ibeacons[cnt].REPORTED = 0;
}
#endif
return 2;
}
} else {
if (!strncmp(ibeacons[cnt].UID,ib->UID,32)) {
// exists
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
ibeacons[cnt].TIME=0;
#ifdef USE_IBEACON_ESP32
if (ibeacons[cnt].REPTIME >= IB_UPDATE_TIME) {
ibeacons[cnt].REPTIME = 0;
ibeacons[cnt].REPORTED = 0;
}
#endif
return 2;
}
}
}
}
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
if (!ibeacons[cnt].FLAGS) {
memcpy(ibeacons[cnt].MAC,ib->MAC,12);
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
memcpy(ibeacons[cnt].UID,ib->UID,32);
memcpy(ibeacons[cnt].MAJOR,ib->MAJOR,4);
memcpy(ibeacons[cnt].MINOR,ib->MINOR,4);
ibeacons[cnt].FLAGS=1;
ibeacons[cnt].TIME=0;
#ifdef USE_IBEACON_ESP32
memcpy(ibeacons[cnt].NAME,ib->NAME,16);
ibeacons[cnt].REPTIME = 0;
ibeacons[cnt].REPORTED = 0;
#endif
return 1;
}
}
}
return 0;
}
#ifndef USE_IBEACON_ESP32
void hm17_decode(void) {
struct IBEACON ib;
switch (hm17_cmd) {
case HM17_TEST:
if (!strncmp(hm17_sbuffer,"OK",2)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("AT OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
hm17_found=1;
}
break;
case HM17_ROLE:
if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("ROLE OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
}
break;
case HM17_IMME:
if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("IMME OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
}
break;
case HM17_IBEA:
if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("IBEA OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
}
break;
case HM17_SCAN:
if (!strncmp(hm17_sbuffer,"OK+Set:5",8)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("SCAN OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
}
break;
case HM17_RESET:
if (!strncmp(hm17_sbuffer,"OK+RESET",8)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("RESET OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
}
break;
case HM17_RENEW:
if (!strncmp(hm17_sbuffer,"OK+RENEW",8)) {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("RENEW OK"));
#endif
hm17_sbclr();
hm17_result=HM17_SUCESS;
}
break;
case HM17_CON:
if (!strncmp(hm17_sbuffer,"OK+CONNA",8)) {
hm17_sbclr();
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONNA OK"));
#endif
hm17_connecting=2;
break;
}
if (!strncmp(hm17_sbuffer,"OK+CONNE",8)) {
hm17_sbclr();
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONNE ERROR"));
#endif
break;
}
if (!strncmp(hm17_sbuffer,"OK+CONNF",8)) {
hm17_sbclr();
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONNF ERROR"));
#endif
break;
}
if (hm17_connecting==2 && !strncmp(hm17_sbuffer,"OK+CONN",7)) {
hm17_sbclr();
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONN OK"));
#endif
hm17_connecting=3;
hm17_sendcmd(HM17_TEST);
hm17_connecting=0;
break;
}
break;
case HM17_DISI:
case HM17_DISC:
if (!strncmp(hm17_sbuffer,"OK+DISCS",8)) {
hm17_sbclr();
hm17_result=1;
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("DISCS OK"));
#endif
break;
}
if (!strncmp(hm17_sbuffer,"OK+DISIS",8)) {
hm17_sbclr();
hm17_result=1;
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("DISIS OK"));
#endif
break;
}
if (!strncmp(hm17_sbuffer,"OK+DISCE",8)) {
hm17_sbclr();
hm17_result=HM17_SUCESS;
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("DISCE OK"));
#endif
hm17_scanning=0;
break;
}
if (!strncmp(hm17_sbuffer,"OK+NAME:",8)) {
if (hm17_sbuffer[hm17_sindex-1]=='\n') {
hm17_result=HM17_SUCESS;
#ifdef IBEACON_DEBUG
if (hm17_debug) {
AddLog_P(LOG_LEVEL_INFO, PSTR("NAME OK"));
AddLog_P(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
}
#endif
hm17_sbclr();
}
break;
}
if (!strncmp(hm17_sbuffer,"OK+DIS0:",8)) {
if (hm17_cmd==HM17_DISI) {
#ifdef HM17_V110
goto hm17_v110;
#endif
} else {
if (hm17_sindex==20) {
hm17_result=HM17_SUCESS;
#ifdef IBEACON_DEBUG
if (hm17_debug) {
AddLog_P(LOG_LEVEL_INFO, PSTR("DIS0 OK"));
AddLog_P(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
}
#endif
hm17_sbclr();
}
}
break;
}
if (!strncmp(hm17_sbuffer,"OK+DISC:",8)) {
hm17_v110:
if (hm17_cmd==HM17_DISI) {
if (hm17_sindex==78) {
#ifdef IBEACON_DEBUG
if (hm17_debug) {
AddLog_P(LOG_LEVEL_INFO, PSTR("DISC: OK"));
//OK+DISC:4C 000C0E:003 A9144081A8 3B16849611 862EC1005: 0B1CE7485D :4DB4E940F C0E:-078
AddLog_P(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
}
#endif
memcpy(ib.FACID,&hm17_sbuffer[8],8);
memcpy(ib.UID,&hm17_sbuffer[8+8+1],32);
memcpy(ib.MAJOR,&hm17_sbuffer[8+8+1+32+1],4);
memcpy(ib.MINOR,&hm17_sbuffer[8+8+1+32+1+4],4);
memcpy(ib.PWR,&hm17_sbuffer[8+8+1+32+1+4+4],2);
memcpy(ib.MAC,&hm17_sbuffer[8+8+1+32+1+4+4+2+1],12);
memcpy(ib.RSSI,&hm17_sbuffer[8+8+1+32+1+4+4+2+1+12+1],4);
if (ibeacon_add(&ib)) {
ibeacon_mqtt(ib.MAC,ib.RSSI,ib.UID,ib.MAJOR,ib.MINOR);
}
hm17_sbclr();
hm17_result=1;
}
} else {
#ifdef IBEACON_DEBUG
if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR(">->%s"),&hm17_sbuffer[8]);
#endif
}
break;
}
}
}
#endif
void IBEACON_loop() {
#ifdef USE_IBEACON_ESP32
//TasAutoMutex localmutex(&beaconmutex, "iBeacLoop");
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS && ! ibeacons[cnt].REPORTED) {
ibeacon_mqtt(ibeacons[cnt].MAC,ibeacons[cnt].RSSI,ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR,ibeacons[cnt].NAME);
ibeacons[cnt].REPORTED=1;
}
}
#else
if (!IBEACON_Serial) return;
uint32_t difftime=millis()-hm17_lastms;
while (IBEACON_Serial->available()) {
hm17_lastms=millis();
// shift in
if (hm17_sindex<HM17_BSIZ) {
hm17_sbuffer[hm17_sindex]=IBEACON_Serial->read();
hm17_sindex++;
hm17_decode();
} else {
hm17_sindex=0;
break;
}
}
if (hm17_cmd==99) {
if (hm17_sindex>=HM17_BSIZ-2 || (hm17_sindex && (difftime>100))) {
AddLog_P(LOG_LEVEL_INFO, PSTR("%s"),hm17_sbuffer);
hm17_sbclr();
}
}
#endif
}
#ifdef USE_WEBSERVER
const char HTTP_IBEACON_HL[] PROGMEM = "{s}<hr>{m}<hr>{e}";
const char HTTP_IBEACON_mac[] PROGMEM =
"{s}IBEACON-MAC : %s" " {m} RSSI : %s" "{e}";
const char HTTP_IBEACON_uid[] PROGMEM =
"{s}IBEACON-UID : %s" " {m} RSSI : %s" "{e}";
#ifdef USE_IBEACON_ESP32
const char HTTP_IBEACON_name[] PROGMEM =
"{s}IBEACON-NAME : %s (%s)" " {m} RSSI : %s" "{e}";
#endif
void IBEACON_Show(void) {
char mac[14];
char rssi[6];
char uid[34];
#ifdef USE_IBEACON_ESP32
char name[18];
//TasAutoMutex localmutex(&beaconmutex, "iBeacShow");
#endif
int total = 0;
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
if (ibeacons[cnt].FLAGS) {
total++;
memcpy(mac,ibeacons[cnt].MAC,12);
mac[12]=0;
memcpy(rssi,ibeacons[cnt].RSSI,4);
rssi[4]=0;
memcpy(uid,ibeacons[cnt].UID,32);
uid[32]=0;
#ifdef USE_IBEACON_ESP32
memcpy(name,ibeacons[cnt].NAME,16);
name[16]=0;
#endif
if (!strncmp_P(uid,PSTR("00000000000000000000000000000000"),32)) {
#ifdef USE_IBEACON_ESP32
if (name[0]) {
WSContentSend_PD(HTTP_IBEACON_name,name,mac,rssi);
} else {
WSContentSend_PD(HTTP_IBEACON_mac,mac,rssi);
}
#else
WSContentSend_PD(HTTP_IBEACON_mac,mac,rssi);
#endif
} else {
WSContentSend_PD(HTTP_IBEACON_uid,uid,rssi);
}
}
}
// add a break line after us, if we showed anything.
if (total) WSContentSend_PD(HTTP_IBEACON_HL);
}
#endif // USE_WEBSERVER
/*
commands sensor52
to initialyze hm17 the cmds
sensor52 1
sensor52 2
must be given only once (stored in module)
uT = sets update interval in seconds (scan tags every T seonds) default=10
tT = sets timeout interval in seconds (after T seconds if tag is not detected send rssi=0) default=30
sending IBEACON_FFFF3D1B1E9D_RSSI with data 99 causes tag to beep (ID to be replaced with actual ID)
*** debugging
dx = sets debug mode to 0,1 (shows hm17 cmds + reactions in console)
c = clears sensor list
s AT+xxx = send native cmds to module
e.g. s AT+CONFFFF3D1B1E9D connects to module with ID, then send s AT to disconnect which activates the beeper in the TAG
*/
bool xsns52_cmd(void) {
bool serviced = true;
const char S_JSON_IBEACON[] = "{\"" D_CMND_SENSOR "%d\":%s:%d}";
const char S_JSON_IBEACON1[] = "{\"" D_CMND_SENSOR "%d\":%s:%s}";
uint16_t len=XdrvMailbox.data_len;
if (len > 0) {
char *cp=XdrvMailbox.data;
if (*cp=='u') {
cp++;
if (*cp) IB_UPDATE_TIME=atoi(cp);
Response_P(S_JSON_IBEACON, XSNS_52,"uintv",IB_UPDATE_TIME);
} else if (*cp=='t') {
cp++;
if (*cp) IB_TIMEOUT_TIME=atoi(cp);
Response_P(S_JSON_IBEACON, XSNS_52,"lintv",IB_TIMEOUT_TIME);
} else if (*cp=='c') {
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) ibeacons[cnt].FLAGS=0;
Response_P(S_JSON_IBEACON1, XSNS_52,"clr list","");
}
#ifndef USE_IBEACON_ESP32
else if (*cp>='0' && *cp<='8') {
hm17_sendcmd(*cp&7);
Response_P(S_JSON_IBEACON, XSNS_52,"hm17cmd",*cp&7);
} else if (*cp=='s') {
cp++;
len--;
while (*cp==' ') {
len--;
cp++;
}
IBEACON_Serial->write((uint8_t*)cp,len);
hm17_cmd=99;
Response_P(S_JSON_IBEACON1, XSNS_52,"hm17cmd",cp);
}
#endif
#ifdef IBEACON_DEBUG
else if (*cp=='d') {
cp++;
hm17_debug=atoi(cp);
Response_P(S_JSON_IBEACON, XSNS_52,"debug",hm17_debug);
}
#endif
} else {
serviced=false;
}
return serviced;
}
#define D_CMND_IBEACON "IBEACON"
#ifndef USE_IBEACON_ESP32
//"IBEACON_FFFF3D1B1E9D_RSSI", Data "99" causes TAG to beep
bool ibeacon_cmd(void) {
ib_mac[0]=0;
int16_t rssi=0;
const char S_JSON_IBEACON[] = "{\"" D_CMND_IBEACON "_%s_RSSI\":%d}";
uint8_t cmd_len = strlen(D_CMND_IBEACON);
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_IBEACON), cmd_len)) {
// IBEACON prefix
rssi = XdrvMailbox.payload;
if (rssi==99) {
memcpy(ib_mac,XdrvMailbox.topic+cmd_len+1,12);
ib_mac[12]=0;
if (hm17_scanning) {
// postpone sendbeep
hm17_flag|=2;
} else {
ib_sendbeep();
}
}
Response_P(S_JSON_IBEACON,ib_mac,rssi);
return true;
}
return false;
}
void ib_sendbeep(void) {
hm17_flag=0;
hm17_sendcmd(HM17_CON);
}
#endif
#ifdef USE_IBEACON_ESP32
void ibeacon_mqtt(const char *mac,const char *rssi,const char *uid,const char *major,const char *minor, const char *name) {
#else
void ibeacon_mqtt(const char *mac,const char *rssi,const char *uid,const char *major,const char *minor) {
#endif
char s_mac[14];
char s_uid[34];
char s_major[6];
char s_minor[6];
char s_rssi[6];
#ifdef USE_IBEACON_ESP32
char *s_state;
#endif
char s_name[18];
memcpy(s_mac,mac,12);
s_mac[12]=0;
memcpy(s_uid,uid,32);
s_uid[32]=0;
memcpy(s_major,major,4);
s_major[4]=0;
memcpy(s_minor,minor,4);
s_minor[4]=0;
memcpy(s_rssi,rssi,4);
s_rssi[4]=0;
int16_t n_rssi=atoi(s_rssi);
#ifdef USE_IBEACON_ESP32
if (n_rssi) {
s_state=(char *)"ON";
} else {
s_state=(char *)"OFF";
}
#endif
// if uid == all zeros, take mac
if (!strncmp_P(s_uid,PSTR("00000000000000000000000000000000"),32)) {
#ifdef USE_IBEACON_ESP32
if (name[0]) {
memcpy(s_name,name,16);
s_name[16]=0;
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"MAC\":\"%s\",\"NAME\":\"%s\",\"RSSI\":%d,\"STATE\":\"%s\"}}"),s_mac,s_name,n_rssi,s_state);
} else {
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"MAC\":\"%s\",\"RSSI\":%d,\"STATE\":\"%s\"}}"),s_mac,n_rssi,s_state);
}
#else
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"MAC\":\"%s\",\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"RSSI\":%d}}"),s_mac,s_uid,s_major,s_minor,n_rssi);
#endif
} else {
#ifdef USE_IBEACON_ESP32
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"MAC\":\"%s\",\"RSSI\":%d,\"STATE\":\"%s\"}}"),s_uid,s_major,s_minor,s_mac,n_rssi,s_state);
#else
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"MAC\":\"%s\",\"RSSI\":%d}}"),s_uid,s_major,s_minor,s_mac,n_rssi);
#endif
}
MqttPublishTeleSensor();
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns52(byte function)
{
bool result = false;
switch (function) {
case FUNC_INIT:
IBEACON_Init();
break;
#ifdef USE_IBEACON_ESP32
case FUNC_EVERY_250_MSECOND:
if (!ESP32BLE.mode.init) {
ESP32Init();
}
break;
#endif
case FUNC_LOOP:
IBEACON_loop();
break;
case FUNC_EVERY_SECOND:
#ifdef USE_IBEACON_ESP32
esp32_every_second();
#else
hm17_every_second();
#endif
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_52 == XdrvMailbox.index) {
result = xsns52_cmd();
}
break;
#ifndef USE_IBEACON_ESP32
case FUNC_COMMAND:
result=ibeacon_cmd();
break;
#endif
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
#ifndef USE_IBEACON_ESP32
if (hm17_found) IBEACON_Show();
#else
IBEACON_Show();
#endif
break;
#endif // USE_WEBSERVER
}
return result;
}
#endif // USE_IBEACON
#endif

View File

@ -45,6 +45,7 @@
forked - from arendst/tasmota - https://github.com/arendst/Tasmota forked - from arendst/tasmota - https://github.com/arendst/Tasmota
*/ */
#ifndef USE_BLE_ESP32
#ifdef ESP32 // ESP32 only. Use define USE_HM10 for ESP8266 support #ifdef ESP32 // ESP32 only. Use define USE_HM10 for ESP8266 support
#ifdef USE_MI_ESP32 #ifdef USE_MI_ESP32
@ -589,17 +590,17 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){
MI32_ReverseMAC(packet->MAC); MI32_ReverseMAC(packet->MAC);
uint8_t _bindkey[16] = {0x0}; uint8_t _bindkey[16] = {0x0};
bool foundNoKey = true; bool foundNoKey = true;
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]);
for(uint32_t i=0; i<MIBLEbindKeys.size(); i++){ for(uint32_t i=0; i<MIBLEbindKeys.size(); i++){
if(memcmp(packet->MAC,MIBLEbindKeys[i].MAC,sizeof(packet->MAC))==0){ if(memcmp(packet->MAC,MIBLEbindKeys[i].MAC,sizeof(packet->MAC))==0){
memcpy(_bindkey,MIBLEbindKeys[i].key,sizeof(_bindkey)); memcpy(_bindkey,MIBLEbindKeys[i].key,sizeof(_bindkey));
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Decryption Key found")); AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: decryption Key found"));
foundNoKey = false; foundNoKey = false;
break; break;
} }
} }
if(foundNoKey){ if(foundNoKey){
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: No Key found !!")); AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: no Key found !!"));
return -2; return -2;
} }
@ -619,7 +620,7 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){
ret = br_ccm_check_tag(&ctx, &tag); ret = br_ccm_check_tag(&ctx, &tag);
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]); AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]);
return ret-1; return ret-1;
} }
#endif // USE_MI_DECRYPTION #endif // USE_MI_DECRYPTION
@ -660,7 +661,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter)
bool _success = false; bool _success = false;
for (uint32_t i=0;i<MI32_TYPES;i++){ // i < sizeof(kMI32DeviceID) gives compiler warning for (uint32_t i=0;i<MI32_TYPES;i++){ // i < sizeof(kMI32DeviceID) gives compiler warning
if(_type == kMI32DeviceID[i]){ if(_type == kMI32DeviceID[i]){
DEBUG_SENSOR_LOG(PSTR("M32: ID is type %u"), i); DEBUG_SENSOR_LOG(PSTR("MI32: ID is type %u"), i);
_type = i+1; _type = i+1;
_success = true; _success = true;
} }
@ -779,23 +780,26 @@ void MI32PreInit(void) {
MI32.option.showRSSI = 1; MI32.option.showRSSI = 1;
MI32.option.ignoreBogusBattery = 1; // from advertisements MI32.option.ignoreBogusBattery = 1; // from advertisements
MI32.option.holdBackFirstAutodiscovery = 1; MI32.option.holdBackFirstAutodiscovery = 1;
AddLog_P(LOG_LEVEL_INFO,PSTR("M32: pre-init")); AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: pre-init"));
} }
void MI32Init(void) { void MI32Init(void) {
if (MI32.mode.init) { return; } if (MI32.mode.init) { return; }
if (TasmotaGlobal.global_state.wifi_down) { return; } if (TasmotaGlobal.global_state.wifi_down) { return; }
TasmotaGlobal.wifi_stay_asleep = true;
if (WiFi.getSleep() == false) { if (WiFi.getSleep() == false) {
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Put WiFi modem in sleep mode")); // AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: WiFi modem not in sleep mode, BLE cannot start yet"));
WiFi.setSleep(true); // Sleep if (0 == Settings.flag3.sleep_normal) {
AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: About to restart to put WiFi modem in sleep mode"));
Settings.flag3.sleep_normal = 1; // SetOption60 - Enable normal sleep instead of dynamic sleep
TasmotaGlobal.restart_flag = 2;
}
return;
} }
if (!MI32.mode.init) { if (!MI32.mode.init) {
NimBLEDevice::init(""); NimBLEDevice::init("");
AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Init BLE device")); AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: init BLE device"));
MI32.mode.canScan = 1; MI32.mode.canScan = 1;
MI32.mode.init = 1; MI32.mode.init = 1;
MI32.period = Settings.tele_period; MI32.period = Settings.tele_period;
@ -1283,7 +1287,7 @@ void MI32parseMiBeacon(char * _buf, uint32_t _slot, uint16_t _bufSize){
break; break;
} }
if(decryptRet!=0){ if(decryptRet!=0){
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Decryption failed with error: %d"),decryptRet); AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: decryption failed with error: %d"),decryptRet);
return; return;
} }
#endif //USE_MI_DECRYPTION #endif //USE_MI_DECRYPTION
@ -1456,7 +1460,7 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI
} }
break; break;
default: default:
DEBUG_SENSOR_LOG(PSTR("M32: Unexpected CGD1-packet")); DEBUG_SENSOR_LOG(PSTR("MI32: unexpected CGD1-packet"));
} }
if(MIBLEsensors[_slot].eventType.raw == 0) return; if(MIBLEsensors[_slot].eventType.raw == 0) return;
MIBLEsensors[_slot].shallSendMQTT = 1; MIBLEsensors[_slot].shallSendMQTT = 1;
@ -1488,7 +1492,7 @@ void MI32ParseResponse(char *buf, uint16_t bufsize, uint8_t addr[6], int RSSI) {
* @param UUID * @param UUID
*/ */
void MI32ParseGenericBeacon(uint8_t* payload, size_t payloadLength, uint16_t* CID, uint16_t*SVC, uint16_t* UUID){ void MI32ParseGenericBeacon(uint8_t* payload, size_t payloadLength, uint16_t* CID, uint16_t*SVC, uint16_t* UUID){
AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Beacon:____________")); AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("MI32: Beacon:____________"));
for (uint32_t i = 0; i<payloadLength;){ for (uint32_t i = 0; i<payloadLength;){
uint32_t ADtype = payload[i+1]; uint32_t ADtype = payload[i+1];
uint32_t offset = payload[i]; uint32_t offset = payload[i];
@ -1546,13 +1550,13 @@ void MI32HandleGenericBeacon(uint8_t* payload, size_t payloadLength, int RSSI, u
} }
// else handle scan // else handle scan
if(MIBLEscanResult.size()>19) { if(MIBLEscanResult.size()>19) {
AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Scan buffer full")); AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: Scan buffer full"));
MI32.state.beaconScanCounter = 1; MI32.state.beaconScanCounter = 1;
return; return;
} }
for(auto _scanResult : MIBLEscanResult){ for(auto _scanResult : MIBLEscanResult){
if(memcmp(addr,_scanResult.MAC,6)==0){ if(memcmp(addr,_scanResult.MAC,6)==0){
// AddLog_P(LOG_LEVEL_INFO,PSTR("M32: known device")); // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: known device"));
return; return;
} }
} }
@ -1582,12 +1586,12 @@ void MI32addBeacon(uint8_t index, char* data){
_new.time = 0; _new.time = 0;
if(memcmp(_empty,_new.MAC,6) == 0){ if(memcmp(_empty,_new.MAC,6) == 0){
_new.active = false; _new.active = false;
AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Beacon%u deactivated"), index); AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: beacon%u deactivated"), index);
} }
else{ else{
_new.active = true; _new.active = true;
MI32.mode.activeBeacon = 1; MI32.mode.activeBeacon = 1;
AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Beacon added with MAC: %s"), _MAC); AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: beacon added with MAC: %s"), _MAC);
} }
} }
@ -1842,7 +1846,7 @@ void CmndMi32Time(void) {
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
if (MIBLEsensors.size() > XdrvMailbox.payload) { if (MIBLEsensors.size() > XdrvMailbox.payload) {
if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR("M32: Will set Time")); AddLog_P(LOG_LEVEL_DEBUG, PSTR("MI32: will set Time"));
MI32.state.sensor = XdrvMailbox.payload; MI32.state.sensor = XdrvMailbox.payload;
MI32.mode.canScan = 0; MI32.mode.canScan = 0;
MI32.mode.canConnect = 0; MI32.mode.canConnect = 0;
@ -1872,7 +1876,7 @@ void CmndMi32Unit(void) {
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
if (MIBLEsensors.size() > XdrvMailbox.payload) { if (MIBLEsensors.size() > XdrvMailbox.payload) {
if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) {
AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Will set Unit")); AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: will set Unit"));
MI32.state.sensor = XdrvMailbox.payload; MI32.state.sensor = XdrvMailbox.payload;
MI32.mode.canScan = 0; MI32.mode.canScan = 0;
MI32.mode.canConnect = 0; MI32.mode.canConnect = 0;
@ -1922,11 +1926,11 @@ void CmndMi32Block(void){
switch (XdrvMailbox.index) { switch (XdrvMailbox.index) {
case 0: case 0:
MIBLEBlockList.clear(); MIBLEBlockList.clear();
// AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size());
ResponseCmndIdxChar(PSTR("Block list cleared")); ResponseCmndIdxChar(PSTR("block list cleared"));
break; break;
case 1: case 1:
ResponseCmndIdxChar(PSTR("Show block list")); ResponseCmndIdxChar(PSTR("show block list"));
break; break;
} }
} }
@ -1952,7 +1956,7 @@ void CmndMi32Block(void){
ResponseCmndIdxChar(XdrvMailbox.data); ResponseCmndIdxChar(XdrvMailbox.data);
MI32removeMIBLEsensor(_MACasBytes.buf); MI32removeMIBLEsensor(_MACasBytes.buf);
} }
// AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size());
break; break;
} }
} }
@ -2319,3 +2323,4 @@ bool Xsns62(uint8_t function)
} }
#endif // USE_MI_ESP32 #endif // USE_MI_ESP32
#endif // ESP32 #endif // ESP32
#endif

File diff suppressed because it is too large Load Diff