This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
@@ -0,0 +1,94 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <WiFi.h>
#include <ESPmDNS.h>
#include <NetworkUdp.h>
#include <ArduinoOTA.h>
const char *ssid = "..........";
const char *password = "..........";
uint32_t last_ota_time = 0;
void setup() {
Serial.begin(115200);
Serial.println("Booting");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
// ArduinoOTA.setHostname("myesp32");
// Password can be set with plain text (will be hashed internally)
// The authentication uses PBKDF2-HMAC-SHA256 with 10,000 iterations
// ArduinoOTA.setPassword("admin");
// Or set password with pre-hashed value (SHA256 hash of "admin")
// SHA256(admin) = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
// ArduinoOTA.setPasswordHash("8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918");
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_SPIFFS
type = "filesystem";
}
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
if (millis() - last_ota_time > 500) {
Serial.printf("Progress: %u%%\n", (progress / (total / 100)));
last_ota_time = millis();
}
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
ArduinoOTA.handle();
}
@@ -0,0 +1,3 @@
requires_any:
- CONFIG_SOC_WIFI_SUPPORTED=y
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
@@ -0,0 +1,323 @@
# SignedOTA - Secure OTA Updates with Signature Verification
This example demonstrates how to perform secure OTA updates with cryptographic signature verification using the ArduinoOTA library.
## Overview
**SignedOTA** adds an extra layer of security to Arduino OTA updates by requiring all firmware to be cryptographically signed with your private key. This protects against:
- ✅ Unauthorized firmware updates
- ✅ Man-in-the-middle attacks
- ✅ Compromised networks
- ✅ Firmware tampering
- ✅ Supply chain attacks
Even if an attacker gains access to your network, they **cannot** install unsigned firmware on your devices.
## Features
- **RSA & ECDSA Support**: RSA-2048/3072/4096 and ECDSA-P256/P384
- **Multiple Hash Algorithms**: SHA-256, SHA-384, SHA-512
- **Arduino IDE Compatible**: Works with standard Arduino OTA workflow
- **Optional Password Protection**: Add password authentication in addition to signature verification
- **Easy Integration**: Just a few lines of code
## Requirements
- **ESP32 Arduino Core 3.3.0+**
- **Python 3.6+** with `cryptography` library
<!-- vale Espressif-latest.Units = NO -->
- **OTA-capable partition scheme** (e.g., "Minimal SPIFFS (1.9MB APP with OTA)")
<!-- vale Espressif-latest.Units = YES -->
## Quick Start Guide
### 1. Generate Cryptographic Keys
```bash
# Navigate to Arduino ESP32 tools directory
cd <ARDUINO_ROOT>/tools
# Install Python dependencies
pip install cryptography
# Generate RSA-2048 key pair (recommended)
python bin_signing.py --generate-key rsa-2048 --out private_key.pem
# Extract public key
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
```
**⚠️ IMPORTANT: Keep `private_key.pem` secure! Anyone with this key can sign firmware for your devices.**
### 2. Setup the Example
1. Copy `public_key.h` (generated in step 1) to this sketch directory
2. Open `SignedOTA.ino` in Arduino IDE
<!-- vale Espressif-latest.TermsSingleCorrectSpelling = NO -->
3. Configure WiFi credentials:
```cpp
const char *ssid = "YourWiFiSSID";
const char *password = "YourWiFiPassword";
```
<!-- vale Espressif-latest.TermsSingleCorrectSpelling = YES -->
<!-- vale Espressif-latest.Units = NO -->
4. Select appropriate partition scheme:
- **Tools → Partition Scheme → "Minimal SPIFFS (1.9MB APP with OTA)"**
<!-- vale Espressif-latest.Units = YES -->
### 3. Upload Initial Firmware
1. Connect your ESP32 via USB
2. Upload the sketch normally
3. Open Serial Monitor (115200 baud)
4. Note the device IP address
### 4. Build & Sign Firmware for OTA Update Example
**Option A: Using Arduino IDE**
```bash
# Export compiled binary
# In Arduino IDE: Sketch → Export Compiled Binary
# Sign the firmware
cd <ARDUINO_ROOT>/tools
python bin_signing.py \
--bin /path/to/SignedOTA.ino.bin \
--key private_key.pem \
--out firmware_signed.bin
```
**Option B: Using arduino-cli**
```bash
# Compile and export
arduino-cli compile --fqbn esp32:esp32:esp32 --export-binaries SignedOTA
# Sign the firmware
cd <ARDUINO_ROOT>/tools
python bin_signing.py \
--bin build/esp32.esp32.esp32/SignedOTA.ino.bin \
--key private_key.pem \
--out firmware_signed.bin
```
### 5. Upload Signed Firmware via OTA
Upload the signed firmware using `espota.py`:
```bash
python <ARDUINO_ROOT>/tools/espota.py -i <device-ip> -f firmware_signed.bin
```
The device will automatically:
1. Receive the signed firmware (firmware + signature)
2. Hash only the firmware portion
3. Verify the signature
4. Install if valid, reject if invalid
**Note**: You can also use the Update library's `Signed_OTA_Update` example for HTTP-based OTA updates.
## Configuration Options
### Hash Algorithms
Choose one in `SignedOTA.ino`:
```cpp
#define USE_SHA256 // Default, fastest
// #define USE_SHA384
// #define USE_SHA512
```
**Must match** the `--hash` parameter when signing:
```bash
python bin_signing.py --bin firmware.bin --key private.pem --out signed.bin --hash sha256
```
### Signature Algorithms
Choose one in `SignedOTA.ino`:
```cpp
#define USE_RSA // For RSA keys
// #define USE_ECDSA // For ECDSA keys
```
### Optional Password Protection
Add password authentication **in addition to** signature verification:
```cpp
const char *ota_password = "yourpassword"; // Set password
// const char *ota_password = nullptr; // Disable password
```
## How It Works
```
┌─────────────────┐
│ Build Firmware │
└────────┬────────┘
┌─────────────────┐
│ Sign Firmware │ ← Uses your private key
│ (bin_signing) │
└────────┬────────┘
┌─────────────────────────┐
│ firmware_signed.bin │
│ [firmware][signature] │
└────────┬────────────────┘
▼ OTA Upload
┌─────────────────────────┐
│ ESP32 Device │
│ ┌──────────────────┐ │
│ │ Verify Signature │ │ ← Uses your public key
│ │ ✓ or ✗ │ │
│ └──────────────────┘ │
│ │ │
│ ✓ Valid? │
│ ├─ Yes: Install │
│ └─ No: Reject │
└─────────────────────────┘
```
## Troubleshooting
### "Begin Failed" Error
**Cause**: Signature verification setup failed, or partition scheme issue
**Solutions**:
<!-- vale Espressif-latest.Units = NO -->
1. Check partition scheme (use "Minimal SPIFFS (1.9MB APP with OTA)")
2. Verify `public_key.h` is in the sketch directory
3. Check hash and signature algorithm match your key type
<!-- vale Espressif-latest.Units = YES -->
### "End Failed" Error
**Cause**: Signature verification failed
**Solutions**:
1. Ensure firmware was signed with the **correct private key**
2. Verify hash algorithm matches (SHA-256, SHA-384, SHA-512)
3. Check firmware wasn't corrupted during signing/transfer
4. Confirm you signed the **correct** `.bin` file
### "Receive Failed" Error
**Cause**: Network timeout or connection issue
**Solutions**:
1. Check Wi-Fi signal strength
2. Ensure device is reachable on the network
3. Try increasing timeout: `ArduinoOTA.setTimeout(5000)`
### Upload Fails
**Issue**: OTA upload fails or times out
**Solutions**:
1. Verify device is on the same network
2. Check firewall settings aren't blocking port 3232
3. Ensure Wi-Fi signal strength is adequate
4. If using password protection, ensure the password is correct
5. Try: `python <ARDUINO_ROOT>/tools/espota.py -i <device-ip> -f firmware_signed.bin -d`
## Security Considerations
### Best Practices
✅ **Keep private key secure**: Never commit to git, store encrypted
✅ **Use strong keys**: RSA-2048+ or ECDSA-P256+
✅ **Use HTTPS when possible**: For additional transport security
✅ **Add password authentication**: Extra layer of protection
✅ **Rotate keys periodically**: Generate new keys every 1-2 years
### What This Protects Against
- ✅ Unsigned firmware installation
- ✅ Firmware signed with wrong key
- ✅ Tampered/corrupted firmware
- ✅ Network-based attacks (when combined with password)
### What This Does NOT Protect Against
<!-- vale Espressif-latest.Cursing = NO -->
- ❌ Physical access (USB flashing still works)
- ❌ Downgrade attacks (no version checking by default)
- ❌ Replay attacks (no timestamp/nonce by default)
- ❌ Key compromise (if private key is stolen)
<!-- vale Espressif-latest.Cursing = YES -->
### Additional Security
For production deployments, consider:
1. **Add version checking** to prevent downgrades
2. **Add timestamp validation** to prevent replay attacks
3. **Use secure boot** for additional protection
4. **Store keys in HSM** or secure key management system
5. **Implement key rotation** mechanism
## Advanced Usage
### Using ECDSA Instead of RSA
ECDSA keys are smaller and faster:
```bash
# Generate ECDSA-P256 key
python bin_signing.py --generate-key ecdsa-p256 --out private_key.pem
python bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
```
In `SignedOTA.ino`:
```cpp
#define USE_SHA256
#define USE_ECDSA // Instead of USE_RSA
```
### Using SHA-384 or SHA-512
For higher security:
```bash
# Sign with SHA-384
python bin_signing.py --bin firmware.bin --key private.pem --out signed.bin --hash sha384
```
In `SignedOTA.ino`:
```cpp
#define USE_SHA384 // Instead of USE_SHA256
#define USE_RSA
```
### Custom Partition Label
To update a specific partition:
```cpp
ArduinoOTA.setPartitionLabel("my_partition");
```
## Support
For issues and questions:
- Update Library README: `libraries/Update/README.md`
- ESP32 Arduino Core: https://github.com/espressif/arduino-esp32
- Forum: https://github.com/espressif/arduino-esp32/discussions
## License
This library is part of the Arduino-ESP32 project and is licensed under the Apache License 2.0.
@@ -0,0 +1,204 @@
/*
* SignedOTA Example - Secure OTA Updates with Signature Verification
*
* This example demonstrates how to perform OTA updates with cryptographic
* signature verification using ArduinoOTA library.
*
* IMPORTANT: This example requires firmware to be signed with bin_signing.py
*
* NOTE: Signature verification support is enabled via the build_opt.h file
* in this directory.
*
* Setup:
* 1. Generate keys:
* python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key rsa-2048 --out private_key.pem
* python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
*
* 2. Copy public_key.h to this sketch directory
*
* 3. Configure WiFi credentials below
*
* 4. Upload this sketch to your device
*
* 5. Build your firmware and sign it:
* arduino-cli compile --fqbn esp32:esp32:esp32 --export-binaries SignedOTA
* python <ARDUINO_ROOT>/tools/bin_signing.py --bin build/<file>.bin --key private_key.pem --out firmware_signed.bin
*
* 6. Upload signed firmware using espota.py or Arduino IDE (after modifying espota.py to handle signed binaries)
* python <ARDUINO_ROOT>/tools/espota.py -i <device-ip> -f firmware_signed.bin
*
* For more information, see the Update library's Signed_OTA_Update example
* and README.md in the Update library folder.
*
* Created by lucasssvaz
*/
#include <WiFi.h>
#include <ESPmDNS.h>
#include <NetworkUdp.h>
#include <ArduinoOTA.h>
// Include your public key (generated with bin_signing.py)
#include "public_key.h"
// ==================== CONFIGURATION ====================
// WiFi credentials
const char *ssid = "..........";
const char *password = "..........";
// Optional: Set a password for OTA authentication
// This is in ADDITION to signature verification
// ArduinoOTA password protects the OTA connection
// Signature verification ensures firmware authenticity
const char *ota_password = nullptr; // Set to nullptr to disable, or "yourpassword" to enable
// Choose hash algorithm (must match what you use with bin_signing.py --hash)
// Uncomment ONE of these:
#define USE_SHA256 // Default, recommended
// #define USE_SHA384
// #define USE_SHA512
// Choose signature algorithm (must match your key type)
// Uncomment ONE of these:
#define USE_RSA // Recommended (works with rsa-2048, rsa-3072, rsa-4096)
// #define USE_ECDSA // Works with ecdsa-p256, ecdsa-p384
// =======================================================
uint32_t last_ota_time = 0;
void setup() {
Serial.begin(115200);
Serial.println("\n\n=================================");
Serial.println("SignedOTA - Secure OTA Updates");
Serial.println("=================================\n");
Serial.println("Booting...");
// Connect to WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
Serial.println("WiFi Connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// ==================== SIGNATURE VERIFICATION SETUP ====================
// Select hash algorithm
#ifdef USE_SHA256
int hashType = HASH_SHA256;
Serial.println("Using SHA-256 hash");
#elif defined(USE_SHA384)
int hashType = HASH_SHA384;
Serial.println("Using SHA-384 hash");
#elif defined(USE_SHA512)
int hashType = HASH_SHA512;
Serial.println("Using SHA-512 hash");
#else
#error "Please define a hash algorithm (USE_SHA256, USE_SHA384, or USE_SHA512)"
#endif
// Create verifier object
#ifdef USE_RSA
static UpdaterRSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, hashType);
Serial.println("Using RSA signature verification");
#elif defined(USE_ECDSA)
static UpdaterECDSAVerifier sign(PUBLIC_KEY, PUBLIC_KEY_LEN, hashType);
Serial.println("Using ECDSA signature verification");
#else
#error "Please define a signature type (USE_RSA or USE_ECDSA)"
#endif
// Install signature verification BEFORE ArduinoOTA.begin()
ArduinoOTA.setSignature(&sign);
Serial.println("✓ Signature verification enabled");
// =======================================================================
// Optional: Set hostname
// ArduinoOTA.setHostname("myesp32");
// Optional: Set OTA password (in addition to signature verification)
if (ota_password != nullptr) {
ArduinoOTA.setPassword(ota_password);
Serial.println("✓ OTA password protection enabled");
}
// Configure OTA callbacks
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_SPIFFS
type = "filesystem";
}
Serial.println("\n=================================");
Serial.println("OTA Update Starting: " + type);
Serial.println("=================================");
Serial.println("⚠️ Signature will be verified!");
})
.onEnd([]() {
Serial.println("\n=================================");
Serial.println("✅ OTA Update Complete!");
Serial.println("✅ Signature Verified!");
Serial.println("=================================");
Serial.println("Rebooting...");
})
.onProgress([](unsigned int progress, unsigned int total) {
if (millis() - last_ota_time > 500) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
last_ota_time = millis();
}
})
.onError([](ota_error_t error) {
Serial.println("\n=================================");
Serial.println("❌ OTA Update Failed!");
Serial.println("=================================");
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Authentication Failed");
Serial.println("Check your OTA password");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
Serial.println("This could be:");
Serial.println("- Signature verification setup failed");
Serial.println("- Not enough space for update");
Serial.println("- Invalid partition");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
Serial.println("This could be:");
Serial.println("- ❌ SIGNATURE VERIFICATION FAILED!");
Serial.println("- Firmware not signed with correct key");
Serial.println("- Firmware corrupted during transfer");
Serial.println("- MD5 checksum mismatch");
}
Serial.println("=================================");
});
// Start ArduinoOTA service
ArduinoOTA.begin();
Serial.println("\n=================================");
Serial.println("✓ OTA Server Ready");
Serial.println("=================================");
Serial.printf("Hostname: %s.local\n", ArduinoOTA.getHostname().c_str());
Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
Serial.println("Port: 3232");
Serial.println("\n⚠️ Only signed firmware will be accepted!");
Serial.println("=================================\n");
}
void loop() {
ArduinoOTA.handle();
}
@@ -0,0 +1 @@
-DUPDATE_SIGN
@@ -0,0 +1,3 @@
requires_any:
- CONFIG_SOC_WIFI_SUPPORTED=y
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
@@ -0,0 +1,30 @@
// Public key for OTA signature verification
// Include this in your Arduino sketch
// ⚠️ THIS IS A TEST KEY - DO NOT USE IN PRODUCTION!
// Generate your own keys using:
// python <ARDUINO_ROOT>/tools/bin_signing.py --generate-key rsa-2048 --out private_key.pem
// python <ARDUINO_ROOT>/tools/bin_signing.py --extract-pubkey private_key.pem --out public_key.pem
//
// Then replace this file with the generated public_key.h
// Test RSA-2048 Public Key (PEM format)
const uint8_t PUBLIC_KEY[] PROGMEM = {
0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
0x0a, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41,
0x41, 0x4f, 0x43, 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, 0x73, 0x35, 0x35, 0x66, 0x4f, 0x74, 0x51,
0x64, 0x69, 0x70, 0x39, 0x58, 0x6f, 0x49, 0x61, 0x6c, 0x52, 0x5a, 0x4b, 0x6c, 0x4a, 0x0a, 0x52, 0x62, 0x55, 0x73, 0x49, 0x30, 0x4c, 0x48, 0x5a, 0x74, 0x2b,
0x50, 0x58, 0x35, 0x4b, 0x58, 0x43, 0x79, 0x54, 0x64, 0x63, 0x78, 0x71, 0x6c, 0x6f, 0x44, 0x45, 0x2b, 0x63, 0x38, 0x43, 0x6f, 0x32, 0x50, 0x77, 0x37, 0x6f,
0x66, 0x63, 0x66, 0x30, 0x47, 0x41, 0x38, 0x4a, 0x55, 0x65, 0x6e, 0x6d, 0x45, 0x46, 0x6b, 0x57, 0x6a, 0x50, 0x53, 0x48, 0x4c, 0x55, 0x55, 0x79, 0x44, 0x0a,
0x63, 0x65, 0x4b, 0x63, 0x2b, 0x71, 0x45, 0x47, 0x54, 0x58, 0x72, 0x59, 0x39, 0x56, 0x6f, 0x4d, 0x38, 0x6f, 0x42, 0x58, 0x39, 0x67, 0x48, 0x41, 0x64, 0x4b,
0x4f, 0x51, 0x48, 0x33, 0x50, 0x4d, 0x70, 0x4a, 0x69, 0x56, 0x51, 0x71, 0x4e, 0x43, 0x36, 0x37, 0x31, 0x44, 0x37, 0x54, 0x45, 0x76, 0x4e, 0x52, 0x43, 0x67,
0x6e, 0x4f, 0x41, 0x37, 0x77, 0x62, 0x77, 0x6f, 0x78, 0x4e, 0x0a, 0x63, 0x75, 0x59, 0x30, 0x49, 0x6e, 0x51, 0x4e, 0x30, 0x64, 0x6b, 0x42, 0x43, 0x4f, 0x63,
0x34, 0x4e, 0x66, 0x31, 0x56, 0x42, 0x76, 0x35, 0x64, 0x71, 0x55, 0x57, 0x41, 0x62, 0x66, 0x43, 0x57, 0x68, 0x5a, 0x37, 0x31, 0x72, 0x4a, 0x56, 0x32, 0x53,
0x68, 0x79, 0x35, 0x48, 0x42, 0x48, 0x48, 0x52, 0x4e, 0x43, 0x78, 0x4f, 0x67, 0x58, 0x68, 0x4f, 0x6c, 0x66, 0x6c, 0x66, 0x0a, 0x72, 0x49, 0x57, 0x56, 0x71,
0x66, 0x51, 0x4b, 0x2b, 0x75, 0x54, 0x4d, 0x62, 0x39, 0x4a, 0x4c, 0x51, 0x67, 0x76, 0x4a, 0x66, 0x70, 0x4c, 0x61, 0x65, 0x35, 0x35, 0x61, 0x61, 0x4e, 0x77,
0x63, 0x72, 0x62, 0x59, 0x38, 0x58, 0x67, 0x53, 0x79, 0x31, 0x64, 0x6c, 0x58, 0x76, 0x4e, 0x37, 0x4d, 0x33, 0x75, 0x4c, 0x52, 0x72, 0x4b, 0x79, 0x61, 0x75,
0x34, 0x59, 0x0a, 0x39, 0x51, 0x53, 0x71, 0x76, 0x4a, 0x71, 0x67, 0x52, 0x61, 0x36, 0x66, 0x47, 0x51, 0x2f, 0x4d, 0x41, 0x63, 0x6c, 0x48, 0x59, 0x33, 0x6d,
0x4b, 0x64, 0x6e, 0x64, 0x68, 0x51, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c,
0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x00,
};
const size_t PUBLIC_KEY_LEN = 451;
+47
View File
@@ -0,0 +1,47 @@
#######################################
# Syntax Coloring Map For Ultrasound
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
ArduinoOTA KEYWORD1
ArduinoOTAClass KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
handle KEYWORD2
onStart KEYWORD2
onEnd KEYWORD2
onError KEYWORD2
onProgress KEYWORD2
setPort KEYWORD2
setHostname KEYWORD2
getHostname KEYWORD2
setPassword KEYWORD2
setPasswordHash KEYWORD2
setPartitionLabel KEYWORD2
getPartitionLabel KEYWORD2
setRebootOnSuccess KEYWORD2
setMdnsEnabled KEYWORD2
setSignature KEYWORD2
getCommand KEYWORD2
setTimeout KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
OTA_IDLE LITERAL1
OTA_WAITAUTH LITERAL1
OTA_RUNUPDATE LITERAL1
OTA_AUTH_ERROR LITERAL1
OTA_BEGIN_ERROR LITERAL1
OTA_CONNECT_ERROR LITERAL1
OTA_RECEIVE_ERROR LITERAL1
OTA_END_ERROR LITERAL1
+9
View File
@@ -0,0 +1,9 @@
name=ArduinoOTA
version=3.3.7
author=Ivan Grokhotkov and Hristo Gochkov
maintainer=Hristo Gochkov <hristo@espressif.com>
sentence=Enables Over The Air upgrades, via wifi and espota.py UDP request/TCP download.
paragraph=With this library you can enable your sketch to be upgraded over network. Includes mdns announces to get discovered by the arduino IDE.
category=Communication
url=
architectures=esp32
+499
View File
@@ -0,0 +1,499 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LWIP_OPEN_SRC
#define LWIP_OPEN_SRC
#endif
#include <functional>
#include "ArduinoOTA.h"
#include "NetworkClient.h"
#include "ESPmDNS.h"
#include "HEXBuilder.h"
#include "SHA2Builder.h"
#include "PBKDF2_HMACBuilder.h"
#include "Update.h"
// #define OTA_DEBUG Serial
ArduinoOTAClass::ArduinoOTAClass(UpdateClass *updater)
: _updater(updater), _port(0), _initialized(false), _rebootOnSuccess(true), _mdnsEnabled(true), _state(OTA_IDLE), _size(0), _cmd(0), _ota_port(0),
_ota_timeout(1000), _start_callback(NULL), _end_callback(NULL), _error_callback(NULL), _progress_callback(NULL)
#ifdef UPDATE_SIGN
,
_sign(NULL)
#endif /* UPDATE_SIGN */
{
}
ArduinoOTAClass::~ArduinoOTAClass() {
end();
}
ArduinoOTAClass &ArduinoOTAClass::onStart(THandlerFunction fn) {
_start_callback = fn;
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::onEnd(THandlerFunction fn) {
_end_callback = fn;
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::onProgress(THandlerFunction_Progress fn) {
_progress_callback = fn;
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::onError(THandlerFunction_Error fn) {
_error_callback = fn;
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::setPort(uint16_t port) {
if (!_initialized && !_port && port) {
_port = port;
}
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::setHostname(const char *hostname) {
if (!_initialized && !_hostname.length() && hostname) {
_hostname = hostname;
}
return *this;
}
String ArduinoOTAClass::getHostname() {
return _hostname;
}
ArduinoOTAClass &ArduinoOTAClass::setPassword(const char *password) {
if (_state == OTA_IDLE && password) {
// Hash the password with SHA256 for storage (not plain text)
SHA256Builder pass_hash;
pass_hash.begin();
pass_hash.add(password);
pass_hash.calculate();
_password.clear();
_password = pass_hash.toString();
}
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::setPasswordHash(const char *password) {
if (_state == OTA_IDLE && password) {
size_t len = strlen(password);
bool is_hex = HEXBuilder::isHexString(password, len);
if (!is_hex) {
log_e("Invalid password hash. Expected hex string (0-9, a-f, A-F).");
return *this;
}
if (len == 32) {
// Warn if MD5 hash is detected (32 hex characters)
log_w("MD5 password hash detected. MD5 is deprecated and insecure.");
log_w("Please use setPassword() with plain text or setPasswordHash() with SHA256 hash (64 chars).");
log_w("To generate SHA256: echo -n 'yourpassword' | sha256sum");
} else if (len == 64) {
log_i("Using SHA256 password hash.");
} else {
log_e("Invalid password hash length. Expected 32 (deprecated MD5) or 64 (SHA256) characters.");
return *this;
}
// Store the pre-hashed password directly
_password.clear();
_password = password;
}
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::setPartitionLabel(const char *partition_label) {
if (_state == OTA_IDLE && partition_label) {
_partition_label.clear();
_partition_label = partition_label;
}
return *this;
}
String ArduinoOTAClass::getPartitionLabel() {
return _partition_label;
}
ArduinoOTAClass &ArduinoOTAClass::setRebootOnSuccess(bool reboot) {
_rebootOnSuccess = reboot;
return *this;
}
ArduinoOTAClass &ArduinoOTAClass::setMdnsEnabled(bool enabled) {
_mdnsEnabled = enabled;
return *this;
}
#ifdef UPDATE_SIGN
ArduinoOTAClass &ArduinoOTAClass::setSignature(UpdaterVerifyClass *sign) {
if (_state == OTA_IDLE && sign) {
_sign = sign;
int hashType = sign->getHashType();
[[maybe_unused]]
const char *hashName = (hashType == HASH_SHA256) ? "SHA-256"
: (hashType == HASH_SHA384) ? "SHA-384"
: "SHA-512";
log_i("Signature verification enabled for ArduinoOTA (hash: %s)", hashName);
}
return *this;
}
#endif /* UPDATE_SIGN */
void ArduinoOTAClass::begin() {
if (_initialized) {
log_w("already initialized");
return;
}
if (!_port) {
_port = 3232;
}
if (!_udp_ota.begin(_port)) {
log_e("udp bind failed");
return;
}
if (!_hostname.length()) {
char tmp[20];
uint8_t mac[6];
Network.macAddress(mac);
sprintf(tmp, "esp32-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
_hostname = tmp;
}
#ifdef CONFIG_MDNS_MAX_INTERFACES
if (_mdnsEnabled) {
MDNS.begin(_hostname.c_str());
MDNS.enableArduino(_port, (_password.length() > 0));
}
#endif
_initialized = true;
_state = OTA_IDLE;
log_i("OTA server at: %s.local:%u", _hostname.c_str(), _port);
}
int ArduinoOTAClass::parseInt() {
char data[INT_BUFFER_SIZE];
uint8_t index = 0;
char value;
while (_udp_ota.peek() == ' ') {
_udp_ota.read();
}
while (index < INT_BUFFER_SIZE - 1) {
value = _udp_ota.peek();
if (value < '0' || value > '9') {
data[index++] = '\0';
return atoi(data);
}
data[index++] = _udp_ota.read();
}
return 0;
}
String ArduinoOTAClass::readStringUntil(char end) {
String res = "";
int value;
while (true) {
value = _udp_ota.read();
if (value <= 0 || value == end) {
return res;
}
res += (char)value;
}
return res;
}
void ArduinoOTAClass::_onRx() {
if (_state == OTA_IDLE) {
int cmd = parseInt();
if (cmd != U_FLASH && cmd != U_FLASHFS) {
return;
}
_cmd = cmd;
_ota_port = parseInt();
_size = parseInt();
_udp_ota.read();
_md5 = readStringUntil('\n');
_md5.trim();
if (_md5.length() != 32) { // MD5 produces 32 character hex string for firmware integrity
log_e("bad md5 length");
return;
}
if (_password.length()) {
// Generate a random challenge (nonce)
SHA256Builder nonce_sha256;
nonce_sha256.begin();
nonce_sha256.add(String(micros()) + String(random(1000000)));
nonce_sha256.calculate();
_nonce = nonce_sha256.toString();
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
_udp_ota.printf("AUTH %s", _nonce.c_str());
_udp_ota.endPacket();
_state = OTA_WAITAUTH;
return;
} else {
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
_udp_ota.print("OK");
_udp_ota.endPacket();
_ota_ip = _udp_ota.remoteIP();
_state = OTA_RUNUPDATE;
}
} else if (_state == OTA_WAITAUTH) {
int cmd = parseInt();
if (cmd != U_AUTH) {
log_e("%d was expected. got %d instead", U_AUTH, cmd);
_state = OTA_IDLE;
return;
}
_udp_ota.read();
String cnonce = readStringUntil(' ');
String response = readStringUntil('\n');
if (cnonce.length() != 64 || response.length() != 64) { // SHA256 produces 64 character hex string
log_e("auth param fail");
_state = OTA_IDLE;
return;
}
// Verify the challenge/response using PBKDF2-HMAC-SHA256
// The client should derive a key using PBKDF2-HMAC-SHA256 with:
// - password: the OTA password (or its hash if using setPasswordHash)
// - salt: nonce + cnonce
// - iterations: 10000 (or configurable)
// Then hash the challenge with the derived key
String salt = _nonce + ":" + cnonce;
SHA256Builder sha256;
// Use the stored password hash for PBKDF2 derivation
PBKDF2_HMACBuilder pbkdf2(&sha256, _password, salt, 10000);
pbkdf2.begin();
pbkdf2.calculate();
String derived_key = pbkdf2.toString();
// Create challenge: derived_key + nonce + cnonce
String challenge = derived_key + ":" + _nonce + ":" + cnonce;
SHA256Builder challenge_sha256;
challenge_sha256.begin();
challenge_sha256.add(challenge);
challenge_sha256.calculate();
String expected_response = challenge_sha256.toString();
if (expected_response.equals(response)) {
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
_udp_ota.print("OK");
_udp_ota.endPacket();
_ota_ip = _udp_ota.remoteIP();
_state = OTA_RUNUPDATE;
} else {
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
_udp_ota.print("Authentication Failed");
log_w("Authentication Failed");
_udp_ota.endPacket();
if (_error_callback) {
_error_callback(OTA_AUTH_ERROR);
}
_state = OTA_IDLE;
}
}
}
void ArduinoOTAClass::_runUpdate() {
if (!_updater) {
log_e("UpdateClass is NULL!");
return;
}
#ifdef UPDATE_SIGN
// Install signature verification if enabled
if (_sign) {
if (!_updater->installSignature(_sign)) {
log_e("Failed to install signature verification");
if (_error_callback) {
_error_callback(OTA_BEGIN_ERROR);
}
_state = OTA_IDLE;
return;
}
log_i("Signature verification installed for OTA update");
}
#endif /* UPDATE_SIGN */
const char *partition_label = _partition_label.length() ? _partition_label.c_str() : NULL;
if (!_updater->begin(_size, _cmd, -1, LOW, partition_label)) {
log_e("Begin ERROR: %s", _updater->errorString());
if (_error_callback) {
_error_callback(OTA_BEGIN_ERROR);
}
_state = OTA_IDLE;
return;
}
_updater->setMD5(_md5.c_str()); // Note: Update library still uses MD5 for firmware integrity, this is separate from authentication
if (_start_callback) {
_start_callback();
}
if (_progress_callback) {
_progress_callback(0, _size);
}
NetworkClient client;
if (!client.connect(_ota_ip, _ota_port)) {
if (_error_callback) {
_error_callback(OTA_CONNECT_ERROR);
}
_state = OTA_IDLE;
}
uint32_t written = 0, total = 0, tried = 0;
while (!_updater->isFinished() && client.connected()) {
size_t waited = _ota_timeout;
size_t available = client.available();
while (!available && waited) {
delay(1);
waited -= 1;
available = client.available();
}
if (!waited) {
if (written && tried++ < 3) {
log_i("Try[%u]: %u", tried, written);
if (!client.printf("%lu", written)) {
log_e("failed to respond");
_state = OTA_IDLE;
break;
}
continue;
}
log_e("Receive Failed");
if (_error_callback) {
_error_callback(OTA_RECEIVE_ERROR);
}
_state = OTA_IDLE;
_updater->abort();
return;
}
if (!available) {
log_e("No Data: %u", waited);
_state = OTA_IDLE;
break;
}
tried = 0;
static uint8_t buf[1460];
if (available > 1460) {
available = 1460;
}
size_t r = client.read(buf, available);
if (r != available) {
log_w("didn't read enough! %u != %u", r, available);
if ((int32_t)r < 0) {
delay(1);
continue; //let's not try to write 4 gigabytes when client.read returns -1
}
}
written = _updater->write(buf, r);
if (written > 0) {
if (written != r) {
log_w("didn't write enough! %u != %u", written, r);
}
if (!client.printf("%lu", written)) {
log_w("failed to respond");
}
total += written;
if (_progress_callback) {
_progress_callback(total, _size);
}
} else {
log_e("Write ERROR: %s", _updater->errorString());
}
}
if (_updater->end()) {
client.print("OK");
client.stop();
delay(10);
if (_end_callback) {
_end_callback();
}
if (_rebootOnSuccess) {
//let serial/network finish tasks that might be given in _end_callback
delay(100);
ESP.restart();
}
} else {
if (_error_callback) {
_error_callback(OTA_END_ERROR);
}
_updater->printError(client);
client.stop();
delay(10);
log_e("Update ERROR: %s", _updater->errorString());
_state = OTA_IDLE;
}
}
void ArduinoOTAClass::end() {
_initialized = false;
_udp_ota.stop();
#ifdef CONFIG_MDNS_MAX_INTERFACES
if (_mdnsEnabled) {
MDNS.end();
}
#endif
_state = OTA_IDLE;
log_i("OTA server stopped.");
}
void ArduinoOTAClass::handle() {
if (!_initialized) {
return;
}
if (_state == OTA_RUNUPDATE) {
_runUpdate();
_state = OTA_IDLE;
}
if (_udp_ota.parsePacket()) {
_onRx();
}
_udp_ota.clear(); // always clear, even zero length packets must be cleared.
}
int ArduinoOTAClass::getCommand() {
return _cmd;
}
void ArduinoOTAClass::setTimeout(int timeoutInMillis) {
_ota_timeout = timeoutInMillis;
}
ArduinoOTAClass &ArduinoOTAClass::setUpdaterInstance(UpdateClass *updater) {
_updater = updater;
return *this;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA)
ArduinoOTAClass ArduinoOTA;
#endif
+149
View File
@@ -0,0 +1,149 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __ARDUINO_OTA_H
#define __ARDUINO_OTA_H
#include "Network.h"
#include "Update.h"
#include <functional>
#define INT_BUFFER_SIZE 16
typedef enum {
OTA_IDLE,
OTA_WAITAUTH,
OTA_RUNUPDATE
} ota_state_t;
typedef enum {
OTA_AUTH_ERROR,
OTA_BEGIN_ERROR,
OTA_CONNECT_ERROR,
OTA_RECEIVE_ERROR,
OTA_END_ERROR
} ota_error_t;
class ArduinoOTAClass {
public:
typedef std::function<void(void)> THandlerFunction;
typedef std::function<void(ota_error_t)> THandlerFunction_Error;
typedef std::function<void(unsigned int, unsigned int)> THandlerFunction_Progress;
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE)
ArduinoOTAClass(UpdateClass *updater = &Update);
#else
ArduinoOTAClass(UpdateClass *updater = nullptr);
#endif
~ArduinoOTAClass();
//Sets the service port. Default 3232
ArduinoOTAClass &setPort(uint16_t port);
//Sets the device hostname. Default esp32-xxxxxx
ArduinoOTAClass &setHostname(const char *hostname);
String getHostname();
//Sets the password that will be required for OTA. Default NULL
ArduinoOTAClass &setPassword(const char *password);
//Sets the password as above but in the form SHA256(password). Default NULL
ArduinoOTAClass &setPasswordHash(const char *password);
//Sets the partition label to write to when updating SPIFFS. Default NULL
ArduinoOTAClass &setPartitionLabel(const char *partition_label);
String getPartitionLabel();
//Sets instance of UpdateClass to perform updating operations
ArduinoOTAClass &setUpdaterInstance(UpdateClass *updater);
//Sets if the device should be rebooted after successful update. Default true
ArduinoOTAClass &setRebootOnSuccess(bool reboot);
//Sets if the device should advertise itself to Arduino IDE. Default true
ArduinoOTAClass &setMdnsEnabled(bool enabled);
#ifdef UPDATE_SIGN
//Install signature verification for OTA updates
//Must be called before begin()
//sign: Signature verifier to use (e.g., UpdaterRSAVerifier or UpdaterECDSAVerifier)
// The hash type is determined from the verifier's configuration
ArduinoOTAClass &setSignature(UpdaterVerifyClass *sign);
#endif /* UPDATE_SIGN */
//This callback will be called when OTA connection has begun
ArduinoOTAClass &onStart(THandlerFunction fn);
//This callback will be called when OTA has finished
ArduinoOTAClass &onEnd(THandlerFunction fn);
//This callback will be called when OTA encountered Error
ArduinoOTAClass &onError(THandlerFunction_Error fn);
//This callback will be called when OTA is receiving data
ArduinoOTAClass &onProgress(THandlerFunction_Progress fn);
//Starts the ArduinoOTA service
void begin();
//Ends the ArduinoOTA service
void end();
//Call this in loop() to run the service
void handle();
//Gets update command type after OTA has started. Either U_FLASH or U_SPIFFS
int getCommand();
void setTimeout(int timeoutInMillis);
private:
UpdateClass *_updater;
int _port;
String _password;
String _hostname;
String _partition_label;
String _nonce;
NetworkUDP _udp_ota;
bool _initialized;
bool _rebootOnSuccess;
bool _mdnsEnabled;
ota_state_t _state;
int _size;
int _cmd;
int _ota_port;
int _ota_timeout;
IPAddress _ota_ip;
String _md5;
THandlerFunction _start_callback;
THandlerFunction _end_callback;
THandlerFunction_Error _error_callback;
THandlerFunction_Progress _progress_callback;
#ifdef UPDATE_SIGN
UpdaterVerifyClass *_sign;
#endif /* UPDATE_SIGN */
void _runUpdate(void);
void _onRx(void);
int parseInt(void);
String readStringUntil(char end);
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA)
extern ArduinoOTAClass ArduinoOTA;
#endif
#endif /* __ARDUINO_OTA_H */
@@ -0,0 +1,49 @@
#include "WiFi.h"
#include "AsyncUDP.h"
const char *ssid = "***********";
const char *password = "***********";
AsyncUDP udp;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Failed");
while (1) {
delay(1000);
}
}
if (udp.connect(IPAddress(192, 168, 1, 100), 1234)) {
Serial.println("UDP connected");
udp.onPacket([](AsyncUDPPacket packet) {
Serial.print("UDP Packet Type: ");
Serial.print(packet.isBroadcast() ? "Broadcast" : packet.isMulticast() ? "Multicast" : "Unicast");
Serial.print(", From: ");
Serial.print(packet.remoteIP());
Serial.print(":");
Serial.print(packet.remotePort());
Serial.print(", To: ");
Serial.print(packet.localIP());
Serial.print(":");
Serial.print(packet.localPort());
Serial.print(", Length: ");
Serial.print(packet.length());
Serial.print(", Data: ");
Serial.write(packet.data(), packet.length());
Serial.println();
//reply to the client
packet.printf("Got %zu bytes of data", packet.length());
});
//Send unicast
udp.print("Hello Server!");
}
}
void loop() {
delay(1000);
//Send broadcast on port 1234
udp.broadcastTo("Anyone here?", 1234);
}
@@ -0,0 +1,3 @@
requires_any:
- CONFIG_SOC_WIFI_SUPPORTED=y
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
@@ -0,0 +1,50 @@
#include "WiFi.h"
#include "AsyncUDP.h"
const char *ssid = "***********";
const char *password = "***********";
AsyncUDP udp;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Failed");
while (1) {
delay(1000);
}
}
if (udp.listenMulticast(IPAddress(239, 1, 2, 3), 1234)) {
Serial.print("UDP Listening on IP: ");
Serial.println(WiFi.localIP());
udp.onPacket([](AsyncUDPPacket packet) {
Serial.print("UDP Packet Type: ");
Serial.print(packet.isBroadcast() ? "Broadcast" : packet.isMulticast() ? "Multicast" : "Unicast");
Serial.print(", From: ");
Serial.print(packet.remoteIP());
Serial.print(":");
Serial.print(packet.remotePort());
Serial.print(", To: ");
Serial.print(packet.localIP());
Serial.print(":");
Serial.print(packet.localPort());
Serial.print(", Length: ");
Serial.print(packet.length());
Serial.print(", Data: ");
Serial.write(packet.data(), packet.length());
Serial.println();
//reply to the client
packet.printf("Got %zu bytes of data", packet.length());
});
//Send multicast
udp.print("Hello!");
}
}
void loop() {
delay(1000);
//Send multicast
udp.print("Anyone here?");
}
@@ -0,0 +1,3 @@
requires_any:
- CONFIG_SOC_WIFI_SUPPORTED=y
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
@@ -0,0 +1,48 @@
#include "WiFi.h"
#include "AsyncUDP.h"
const char *ssid = "***********";
const char *password = "***********";
AsyncUDP udp;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Failed");
while (1) {
delay(1000);
}
}
if (udp.listen(1234)) {
Serial.print("UDP Listening on IP: ");
Serial.println(WiFi.localIP());
udp.onPacket([](AsyncUDPPacket packet) {
Serial.print("UDP Packet Type: ");
Serial.print(packet.isBroadcast() ? "Broadcast" : packet.isMulticast() ? "Multicast" : "Unicast");
Serial.print(", From: ");
Serial.print(packet.remoteIP());
Serial.print(":");
Serial.print(packet.remotePort());
Serial.print(", To: ");
Serial.print(packet.localIP());
Serial.print(":");
Serial.print(packet.localPort());
Serial.print(", Length: ");
Serial.print(packet.length());
Serial.print(", Data: ");
Serial.write(packet.data(), packet.length());
Serial.println();
//reply to the client
packet.printf("Got %zu bytes of data", packet.length());
});
}
}
void loop() {
delay(1000);
//Send broadcast
udp.broadcast("Anyone here?");
}
@@ -0,0 +1,3 @@
requires_any:
- CONFIG_SOC_WIFI_SUPPORTED=y
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
+59
View File
@@ -0,0 +1,59 @@
#######################################
# Syntax Coloring Map For Ultrasound
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
AsyncUDP KEYWORD1
AsyncUDPPacket KEYWORD1
AsyncUDPMessage KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
connect KEYWORD2
connected KEYWORD2
listen KEYWORD2
listenMulticast KEYWORD2
close KEYWORD2
write KEYWORD2
space KEYWORD2
flush KEYWORD2
isBroadcast KEYWORD2
isMulticast KEYWORD2
isIPv6 KEYWORD2
interface KEYWORD2
localIPv6 KEYWORD2
remoteIPv6 KEYWORD2
remoteMac KEYWORD2
send KEYWORD2
peek KEYWORD2
available KEYWORD2
writeTo KEYWORD2
broadcastTo KEYWORD2
sendTo KEYWORD2
broadcast KEYWORD2
onPacket KEYWORD2
data KEYWORD2
length KEYWORD2
localIP KEYWORD2
localPort KEYWORD2
remoteIP KEYWORD2
remotePort KEYWORD2
listenIP KEYWORD2
listenIPv6 KEYWORD2
lastErr KEYWORD2
_s_recv KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
TCPIP_ADAPTER_IF_STA LITERAL1
TCPIP_ADAPTER_IF_STA LITERAL1
TCPIP_ADAPTER_IF_AP LITERAL1
TCPIP_ADAPTER_IF_ETH LITERAL1
TCPIP_ADAPTER_IF_PPP LITERAL1
+9
View File
@@ -0,0 +1,9 @@
name=ESP32 Async UDP
version=3.3.7
author=Me-No-Dev
maintainer=Me-No-Dev
sentence=Async UDP Library for ESP32
paragraph=Async UDP Library for ESP32
category=Other
url=https://github.com/me-no-dev/ESPAsyncUDP
architectures=*
+942
View File
@@ -0,0 +1,942 @@
#include "Arduino.h"
#include "AsyncUDP.h"
extern "C" {
#include "lwip/opt.h"
#include "lwip/inet.h"
#include "lwip/udp.h"
#include "lwip/igmp.h"
#include "lwip/ip_addr.h"
#include "lwip/mld6.h"
#include "lwip/prot/ethernet.h"
#include <esp_err.h>
#include <esp_wifi.h>
}
#include "lwip/priv/tcpip_priv.h"
#define CONFIG_UDP_MSS 1460
#ifndef CONFIG_ARDUINO_UDP_TASK_STACK_SIZE
#define CONFIG_ARDUINO_UDP_TASK_STACK_SIZE 4096
#endif
#ifndef ARDUINO_UDP_TASK_STACK_SIZE
#define ARDUINO_UDP_TASK_STACK_SIZE CONFIG_ARDUINO_UDP_TASK_STACK_SIZE
#endif
#ifndef CONFIG_ARDUINO_UDP_TASK_PRIORITY
#define CONFIG_ARDUINO_UDP_TASK_PRIORITY 3
#endif
#ifndef ARDUINO_UDP_TASK_PRIORITY
#define ARDUINO_UDP_TASK_PRIORITY CONFIG_ARDUINO_UDP_TASK_PRIORITY
#endif
#ifndef CONFIG_ARDUINO_UDP_RUNNING_CORE
#define CONFIG_ARDUINO_UDP_RUNNING_CORE -1
#endif
#ifndef ARDUINO_UDP_RUNNING_CORE
#define ARDUINO_UDP_RUNNING_CORE CONFIG_ARDUINO_UDP_RUNNING_CORE
#endif
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
#define UDP_MUTEX_LOCK() \
if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \
LOCK_TCPIP_CORE(); \
}
#define UDP_MUTEX_UNLOCK() \
if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \
UNLOCK_TCPIP_CORE(); \
}
#else // CONFIG_LWIP_TCPIP_CORE_LOCKING
#define UDP_MUTEX_LOCK()
#define UDP_MUTEX_UNLOCK()
#endif // CONFIG_LWIP_TCPIP_CORE_LOCKING
static const char *netif_ifkeys[TCPIP_ADAPTER_IF_MAX] = {"WIFI_STA_DEF", "WIFI_AP_DEF", "ETH_DEF", "PPP_DEF"};
static esp_err_t tcpip_adapter_get_netif(tcpip_adapter_if_t tcpip_if, void **netif) {
*netif = NULL;
if (tcpip_if < TCPIP_ADAPTER_IF_MAX) {
esp_netif_t *esp_netif = esp_netif_get_handle_from_ifkey(netif_ifkeys[tcpip_if]);
if (esp_netif == NULL) {
return ESP_FAIL;
}
int netif_index = esp_netif_get_netif_impl_index(esp_netif);
if (netif_index < 0) {
return ESP_FAIL;
}
UDP_MUTEX_LOCK();
*netif = (void *)netif_get_by_index(netif_index);
UDP_MUTEX_UNLOCK();
} else {
*netif = netif_default;
}
return (*netif != NULL) ? ESP_OK : ESP_FAIL;
}
typedef struct {
struct tcpip_api_call_data call;
udp_pcb *pcb;
const ip_addr_t *addr;
uint16_t port;
struct pbuf *pb;
struct netif *netif;
err_t err;
} udp_api_call_t;
static err_t _udp_connect_api(struct tcpip_api_call_data *api_call_msg) {
udp_api_call_t *msg = (udp_api_call_t *)api_call_msg;
msg->err = udp_connect(msg->pcb, msg->addr, msg->port);
return msg->err;
}
static err_t _udp_connect(struct udp_pcb *pcb, const ip_addr_t *addr, u16_t port) {
udp_api_call_t msg;
msg.pcb = pcb;
msg.addr = addr;
msg.port = port;
tcpip_api_call(_udp_connect_api, (struct tcpip_api_call_data *)&msg);
return msg.err;
}
static err_t _udp_disconnect_api(struct tcpip_api_call_data *api_call_msg) {
udp_api_call_t *msg = (udp_api_call_t *)api_call_msg;
msg->err = 0;
udp_disconnect(msg->pcb);
return msg->err;
}
static void _udp_disconnect(struct udp_pcb *pcb) {
udp_api_call_t msg;
msg.pcb = pcb;
tcpip_api_call(_udp_disconnect_api, (struct tcpip_api_call_data *)&msg);
}
static err_t _udp_remove_api(struct tcpip_api_call_data *api_call_msg) {
udp_api_call_t *msg = (udp_api_call_t *)api_call_msg;
msg->err = 0;
udp_remove(msg->pcb);
return msg->err;
}
static void _udp_remove(struct udp_pcb *pcb) {
udp_api_call_t msg;
msg.pcb = pcb;
tcpip_api_call(_udp_remove_api, (struct tcpip_api_call_data *)&msg);
}
static err_t _udp_bind_api(struct tcpip_api_call_data *api_call_msg) {
udp_api_call_t *msg = (udp_api_call_t *)api_call_msg;
msg->err = udp_bind(msg->pcb, msg->addr, msg->port);
return msg->err;
}
static err_t _udp_bind(struct udp_pcb *pcb, const ip_addr_t *addr, u16_t port) {
udp_api_call_t msg;
msg.pcb = pcb;
msg.addr = addr;
msg.port = port;
tcpip_api_call(_udp_bind_api, (struct tcpip_api_call_data *)&msg);
return msg.err;
}
static err_t _udp_sendto_api(struct tcpip_api_call_data *api_call_msg) {
udp_api_call_t *msg = (udp_api_call_t *)api_call_msg;
msg->err = udp_sendto(msg->pcb, msg->pb, msg->addr, msg->port);
return msg->err;
}
static err_t _udp_sendto(struct udp_pcb *pcb, struct pbuf *pb, const ip_addr_t *addr, u16_t port) {
udp_api_call_t msg;
msg.pcb = pcb;
msg.addr = addr;
msg.port = port;
msg.pb = pb;
tcpip_api_call(_udp_sendto_api, (struct tcpip_api_call_data *)&msg);
return msg.err;
}
static err_t _udp_sendto_if_api(struct tcpip_api_call_data *api_call_msg) {
udp_api_call_t *msg = (udp_api_call_t *)api_call_msg;
msg->err = udp_sendto_if(msg->pcb, msg->pb, msg->addr, msg->port, msg->netif);
return msg->err;
}
static err_t _udp_sendto_if(struct udp_pcb *pcb, struct pbuf *pb, const ip_addr_t *addr, u16_t port, struct netif *netif) {
udp_api_call_t msg;
msg.pcb = pcb;
msg.addr = addr;
msg.port = port;
msg.pb = pb;
msg.netif = netif;
tcpip_api_call(_udp_sendto_if_api, (struct tcpip_api_call_data *)&msg);
return msg.err;
}
typedef struct {
void *arg;
udp_pcb *pcb;
pbuf *pb;
const ip_addr_t *addr;
uint16_t port;
struct netif *netif;
} lwip_event_packet_t;
static QueueHandle_t _udp_queue;
static volatile TaskHandle_t _udp_task_handle = NULL;
static void _udp_task(void *pvParameters) {
(void)pvParameters;
lwip_event_packet_t *e = NULL;
for (;;) {
if (xQueueReceive(_udp_queue, &e, portMAX_DELAY) == pdTRUE) {
if (!e->pb) {
free((void *)(e));
continue;
}
AsyncUDP::_s_recv(e->arg, e->pcb, e->pb, e->addr, e->port, e->netif);
free((void *)(e));
}
}
_udp_task_handle = NULL;
vTaskDelete(NULL);
}
static bool _udp_task_start() {
if (!_udp_queue) {
_udp_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *));
if (!_udp_queue) {
return false;
}
}
if (!_udp_task_handle) {
xTaskCreateUniversal(
_udp_task, "async_udp", ARDUINO_UDP_TASK_STACK_SIZE, NULL, ARDUINO_UDP_TASK_PRIORITY, (TaskHandle_t *)&_udp_task_handle, ARDUINO_UDP_RUNNING_CORE
);
if (!_udp_task_handle) {
return false;
}
}
return true;
}
static bool _udp_task_post(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif *netif) {
if (!_udp_task_handle || !_udp_queue) {
return false;
}
lwip_event_packet_t *e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t));
if (!e) {
return false;
}
e->arg = arg;
e->pcb = pcb;
e->pb = pb;
e->addr = addr;
e->port = port;
e->netif = netif;
if (xQueueSend(_udp_queue, &e, portMAX_DELAY) != pdPASS) {
free((void *)(e));
return false;
}
return true;
}
static void _udp_recv(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port) {
while (pb != NULL) {
pbuf *this_pb = pb;
pb = pb->next;
this_pb->next = NULL;
if (!_udp_task_post(arg, pcb, this_pb, addr, port, ip_current_input_netif())) {
pbuf_free(this_pb);
}
}
}
/*
static bool _udp_task_stop(){
if(!_udp_task_post(NULL, NULL, NULL, NULL, 0, NULL)){
return false;
}
while(_udp_task_handle){
vTaskDelay(10);
}
lwip_event_packet_t * e;
while (xQueueReceive(_udp_queue, &e, 0) == pdTRUE) {
if(e->pb){
pbuf_free(e->pb);
}
free((void*)(e));
}
vQueueDelete(_udp_queue);
_udp_queue = NULL;
}
*/
AsyncUDPMessage::AsyncUDPMessage(size_t size) {
_index = 0;
if (size > CONFIG_UDP_MSS) {
size = CONFIG_UDP_MSS;
}
_size = size;
_buffer = (uint8_t *)malloc(size);
}
AsyncUDPMessage::~AsyncUDPMessage() {
if (_buffer) {
free(_buffer);
}
}
size_t AsyncUDPMessage::write(const uint8_t *data, size_t len) {
if (_buffer == NULL) {
return 0;
}
size_t s = space();
if (len > s) {
len = s;
}
memcpy(_buffer + _index, data, len);
_index += len;
return len;
}
size_t AsyncUDPMessage::write(uint8_t data) {
return write(&data, 1);
}
size_t AsyncUDPMessage::space() {
if (_buffer == NULL) {
return 0;
}
return _size - _index;
}
uint8_t *AsyncUDPMessage::data() {
return _buffer;
}
size_t AsyncUDPMessage::length() {
return _index;
}
void AsyncUDPMessage::flush() {
_index = 0;
}
AsyncUDPPacket::AsyncUDPPacket(AsyncUDPPacket &packet) {
_udp = packet._udp;
_pb = packet._pb;
_if = packet._if;
_data = packet._data;
_len = packet._len;
_index = 0;
memcpy(&_remoteIp, &packet._remoteIp, sizeof(ip_addr_t));
memcpy(&_localIp, &packet._localIp, sizeof(ip_addr_t));
_localPort = packet._localPort;
_remotePort = packet._remotePort;
memcpy(_remoteMac, packet._remoteMac, 6);
pbuf_ref(_pb);
}
AsyncUDPPacket &AsyncUDPPacket::operator=(const AsyncUDPPacket &packet) {
if (this != &packet) {
if (_pb) {
// Free existing pbuf reference
pbuf_free(_pb);
}
// Copy all members
_udp = packet._udp;
_pb = packet._pb;
_if = packet._if;
_data = packet._data;
_len = packet._len;
_index = 0;
memcpy(&_remoteIp, &packet._remoteIp, sizeof(ip_addr_t));
memcpy(&_localIp, &packet._localIp, sizeof(ip_addr_t));
_localPort = packet._localPort;
_remotePort = packet._remotePort;
memcpy(_remoteMac, packet._remoteMac, 6);
// Increment reference count for the new pbuf
pbuf_ref(_pb);
}
return *this;
}
AsyncUDPPacket::AsyncUDPPacket(AsyncUDP *udp, pbuf *pb, const ip_addr_t *raddr, uint16_t rport, struct netif *ntif) {
_udp = udp;
_pb = pb;
_if = TCPIP_ADAPTER_IF_MAX;
_data = (uint8_t *)(pb->payload);
_len = pb->len;
_index = 0;
pbuf_ref(_pb);
//memcpy(&_remoteIp, raddr, sizeof(ip_addr_t));
#if CONFIG_LWIP_IPV6
_remoteIp.type = raddr->type;
_localIp.type = _remoteIp.type;
#endif
eth_hdr *eth = NULL;
udp_hdr *udphdr = (udp_hdr *)(_data - UDP_HLEN);
_localPort = ntohs(udphdr->dest);
_remotePort = ntohs(udphdr->src);
#if CONFIG_LWIP_IPV6
if (_remoteIp.type == IPADDR_TYPE_V4) {
#endif
eth = (eth_hdr *)(_data - UDP_HLEN - IP_HLEN - SIZEOF_ETH_HDR);
struct ip_hdr *iphdr = (struct ip_hdr *)(_data - UDP_HLEN - IP_HLEN);
#if CONFIG_LWIP_IPV6
_localIp.u_addr.ip4.addr = iphdr->dest.addr;
_remoteIp.u_addr.ip4.addr = iphdr->src.addr;
#else
_localIp.addr = iphdr->dest.addr;
_remoteIp.addr = iphdr->src.addr;
#endif
#if CONFIG_LWIP_IPV6
} else {
eth = (eth_hdr *)(_data - UDP_HLEN - IP6_HLEN - SIZEOF_ETH_HDR);
struct ip6_hdr *ip6hdr = (struct ip6_hdr *)(_data - UDP_HLEN - IP6_HLEN);
memcpy(&_localIp.u_addr.ip6.addr, (uint8_t *)ip6hdr->dest.addr, 16);
memcpy(&_remoteIp.u_addr.ip6.addr, (uint8_t *)ip6hdr->src.addr, 16);
}
#endif
memcpy(_remoteMac, eth->src.addr, 6);
struct netif *netif = NULL;
void *nif = NULL;
int i;
for (i = 0; i < TCPIP_ADAPTER_IF_MAX; i++) {
tcpip_adapter_get_netif((tcpip_adapter_if_t)i, &nif);
netif = (struct netif *)nif;
if (netif && netif == ntif) {
_if = (tcpip_adapter_if_t)i;
break;
}
}
}
AsyncUDPPacket::~AsyncUDPPacket() {
pbuf_free(_pb);
}
uint8_t *AsyncUDPPacket::data() {
return _data;
}
size_t AsyncUDPPacket::length() {
return _len;
}
int AsyncUDPPacket::available() {
return _len - _index;
}
size_t AsyncUDPPacket::read(uint8_t *data, size_t len) {
size_t i;
size_t a = _len - _index;
if (len > a) {
len = a;
}
for (i = 0; i < len; i++) {
data[i] = read();
}
return len;
}
int AsyncUDPPacket::read() {
if (_index < _len) {
return _data[_index++];
}
return -1;
}
int AsyncUDPPacket::peek() {
if (_index < _len) {
return _data[_index];
}
return -1;
}
void AsyncUDPPacket::flush() {
_index = _len;
}
tcpip_adapter_if_t AsyncUDPPacket::interface() {
return _if;
}
IPAddress AsyncUDPPacket::localIP() {
#if CONFIG_LWIP_IPV6
if (_localIp.type != IPADDR_TYPE_V4) {
return IPAddress();
}
return IPAddress(_localIp.u_addr.ip4.addr);
#else
return IPAddress(_localIp.addr);
#endif
}
#if CONFIG_LWIP_IPV6
IPAddress AsyncUDPPacket::localIPv6() {
if (_localIp.type != IPADDR_TYPE_V6) {
return IPAddress(IPv6);
}
return IPAddress(IPv6, (const uint8_t *)_localIp.u_addr.ip6.addr, _localIp.u_addr.ip6.zone);
}
#endif
uint16_t AsyncUDPPacket::localPort() {
return _localPort;
}
IPAddress AsyncUDPPacket::remoteIP() {
#if CONFIG_LWIP_IPV6
if (_remoteIp.type != IPADDR_TYPE_V4) {
return IPAddress();
}
return IPAddress(_remoteIp.u_addr.ip4.addr);
#else
return IPAddress(_remoteIp.addr);
#endif
}
#if CONFIG_LWIP_IPV6
IPAddress AsyncUDPPacket::remoteIPv6() {
if (_remoteIp.type != IPADDR_TYPE_V6) {
return IPAddress(IPv6);
}
return IPAddress(IPv6, (const uint8_t *)_remoteIp.u_addr.ip6.addr, _remoteIp.u_addr.ip6.zone);
}
#endif
uint16_t AsyncUDPPacket::remotePort() {
return _remotePort;
}
void AsyncUDPPacket::remoteMac(uint8_t *mac) {
memcpy(mac, _remoteMac, 6);
}
bool AsyncUDPPacket::isIPv6() {
#if CONFIG_LWIP_IPV6
return _localIp.type == IPADDR_TYPE_V6;
#else
return false;
#endif
}
bool AsyncUDPPacket::isBroadcast() {
#if CONFIG_LWIP_IPV6
if (_localIp.type == IPADDR_TYPE_V6) {
return false;
}
uint32_t ip = _localIp.u_addr.ip4.addr;
#else
uint32_t ip = _localIp.addr;
#endif
return ip == 0xFFFFFFFF || ip == 0 || (ip & 0xFF000000) == 0xFF000000;
}
bool AsyncUDPPacket::isMulticast() {
return ip_addr_ismulticast(&(_localIp));
}
size_t AsyncUDPPacket::write(const uint8_t *data, size_t len) {
if (!data) {
return 0;
}
return _udp->writeTo(data, len, &_remoteIp, _remotePort, _if);
}
size_t AsyncUDPPacket::write(uint8_t data) {
return write(&data, 1);
}
size_t AsyncUDPPacket::send(AsyncUDPMessage &message) {
return write(message.data(), message.length());
}
bool AsyncUDP::_init() {
if (_pcb) {
return true;
}
UDP_MUTEX_LOCK();
_pcb = udp_new();
if (!_pcb) {
UDP_MUTEX_UNLOCK();
return false;
}
udp_recv(_pcb, &_udp_recv, (void *)this);
UDP_MUTEX_UNLOCK();
return true;
}
AsyncUDP::AsyncUDP() {
_pcb = NULL;
_connected = false;
_lastErr = ERR_OK;
_handler = NULL;
}
AsyncUDP::~AsyncUDP() {
close();
UDP_MUTEX_LOCK();
udp_recv(_pcb, NULL, NULL);
UDP_MUTEX_UNLOCK();
_udp_remove(_pcb);
_pcb = NULL;
}
void AsyncUDP::close() {
if (_pcb != NULL) {
if (_connected) {
_udp_disconnect(_pcb);
}
_connected = false;
//todo: unjoin multicast group
}
}
bool AsyncUDP::connect(const ip_addr_t *addr, uint16_t port) {
if (!_udp_task_start()) {
log_e("failed to start task");
return false;
}
if (!_init()) {
return false;
}
close();
_lastErr = _udp_connect(_pcb, addr, port);
if (_lastErr != ERR_OK) {
return false;
}
_connected = true;
return true;
}
bool AsyncUDP::listen(const ip_addr_t *addr, uint16_t port) {
if (!_udp_task_start()) {
log_e("failed to start task");
return false;
}
if (!_init()) {
return false;
}
close();
if (addr) {
IP_SET_TYPE_VAL(_pcb->local_ip, IP_GET_TYPE(addr));
IP_SET_TYPE_VAL(_pcb->remote_ip, IP_GET_TYPE(addr));
}
if (_udp_bind(_pcb, addr, port) != ERR_OK) {
return false;
}
_connected = true;
return true;
}
static esp_err_t joinMulticastGroup(const ip_addr_t *addr, bool join, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX) {
struct netif *netif = NULL;
if (tcpip_if < TCPIP_ADAPTER_IF_MAX) {
void *nif = NULL;
esp_err_t err = tcpip_adapter_get_netif(tcpip_if, &nif);
if (err) {
return ESP_ERR_INVALID_ARG;
}
netif = (struct netif *)nif;
UDP_MUTEX_LOCK();
#if CONFIG_LWIP_IPV6
if (addr->type == IPADDR_TYPE_V4) {
if (join) {
if (igmp_joingroup_netif(netif, (const ip4_addr *)&(addr->u_addr.ip4))) {
goto igmp_fail;
}
} else {
if (igmp_leavegroup_netif(netif, (const ip4_addr *)&(addr->u_addr.ip4))) {
goto igmp_fail;
}
}
} else {
if (join) {
if (mld6_joingroup_netif(netif, &(addr->u_addr.ip6))) {
goto igmp_fail;
}
} else {
if (mld6_leavegroup_netif(netif, &(addr->u_addr.ip6))) {
goto igmp_fail;
}
}
}
#else
if (join) {
if (igmp_joingroup_netif(netif, (const ip4_addr *)(addr))) {
goto igmp_fail;
}
} else {
if (igmp_leavegroup_netif(netif, (const ip4_addr *)(addr))) {
goto igmp_fail;
}
}
#endif
UDP_MUTEX_UNLOCK();
} else {
UDP_MUTEX_LOCK();
#if CONFIG_LWIP_IPV6
if (addr->type == IPADDR_TYPE_V4) {
if (join) {
if (igmp_joingroup((const ip4_addr *)IP4_ADDR_ANY, (const ip4_addr *)&(addr->u_addr.ip4))) {
goto igmp_fail;
}
} else {
if (igmp_leavegroup((const ip4_addr *)IP4_ADDR_ANY, (const ip4_addr *)&(addr->u_addr.ip4))) {
goto igmp_fail;
}
}
} else {
if (join) {
if (mld6_joingroup((const ip6_addr *)IP6_ADDR_ANY, &(addr->u_addr.ip6))) {
goto igmp_fail;
}
} else {
if (mld6_leavegroup((const ip6_addr *)IP6_ADDR_ANY, &(addr->u_addr.ip6))) {
goto igmp_fail;
}
}
}
#else
if (join) {
if (igmp_joingroup((const ip4_addr *)IP4_ADDR_ANY, (const ip4_addr *)(addr))) {
goto igmp_fail;
}
} else {
if (igmp_leavegroup((const ip4_addr *)IP4_ADDR_ANY, (const ip4_addr *)(addr))) {
goto igmp_fail;
}
}
#endif
UDP_MUTEX_UNLOCK();
}
return ESP_OK;
igmp_fail:
UDP_MUTEX_UNLOCK();
return ESP_ERR_INVALID_STATE;
}
bool AsyncUDP::listenMulticast(const ip_addr_t *addr, uint16_t port, uint8_t ttl, tcpip_adapter_if_t tcpip_if) {
ip_addr_t bind_addr;
if (!ip_addr_ismulticast(addr)) {
return false;
}
if (joinMulticastGroup(addr, true, tcpip_if) != ERR_OK) {
return false;
}
IP_SET_TYPE(&bind_addr, IP_GET_TYPE(addr));
ip_addr_set_any(IP_IS_V6(addr), &bind_addr);
if (!listen(&bind_addr, port)) {
return false;
}
_pcb->mcast_ttl = ttl;
_pcb->remote_port = port;
ip_addr_copy(_pcb->remote_ip, *addr);
//ip_addr_copy(_pcb->remote_ip, ip_addr_any_type);
return true;
}
size_t AsyncUDP::writeTo(const uint8_t *data, size_t len, const ip_addr_t *addr, uint16_t port, tcpip_adapter_if_t tcpip_if) {
if (!_pcb) {
UDP_MUTEX_LOCK();
_pcb = udp_new();
UDP_MUTEX_UNLOCK();
if (_pcb == NULL) {
return 0;
}
}
if (len > CONFIG_UDP_MSS) {
len = CONFIG_UDP_MSS;
}
_lastErr = ERR_OK;
pbuf *pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
if (pbt != NULL) {
uint8_t *dst = reinterpret_cast<uint8_t *>(pbt->payload);
memcpy(dst, data, len);
if (tcpip_if < TCPIP_ADAPTER_IF_MAX) {
void *nif = NULL;
tcpip_adapter_get_netif((tcpip_adapter_if_t)tcpip_if, &nif);
if (!nif) {
_lastErr = _udp_sendto(_pcb, pbt, addr, port);
} else {
_lastErr = _udp_sendto_if(_pcb, pbt, addr, port, (struct netif *)nif);
}
} else {
_lastErr = _udp_sendto(_pcb, pbt, addr, port);
}
pbuf_free(pbt);
if (_lastErr < ERR_OK) {
return 0;
}
return len;
}
return 0;
}
void AsyncUDP::_recv(udp_pcb *upcb, pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif *netif) {
while (pb != NULL) {
pbuf *this_pb = pb;
pb = pb->next;
this_pb->next = NULL;
if (_handler) {
AsyncUDPPacket packet(this, this_pb, addr, port, netif);
_handler(packet);
}
pbuf_free(this_pb);
}
}
void AsyncUDP::_s_recv(void *arg, udp_pcb *upcb, pbuf *p, const ip_addr_t *addr, uint16_t port, struct netif *netif) {
reinterpret_cast<AsyncUDP *>(arg)->_recv(upcb, p, addr, port, netif);
}
bool AsyncUDP::listen(uint16_t port) {
return listen(IP_ANY_TYPE, port);
}
bool AsyncUDP::listen(const IPAddress addr, uint16_t port) {
ip_addr_t laddr;
addr.to_ip_addr_t(&laddr);
return listen(&laddr, port);
}
bool AsyncUDP::listenMulticast(const IPAddress addr, uint16_t port, uint8_t ttl, tcpip_adapter_if_t tcpip_if) {
ip_addr_t laddr;
addr.to_ip_addr_t(&laddr);
return listenMulticast(&laddr, port, ttl, tcpip_if);
}
bool AsyncUDP::connect(const IPAddress addr, uint16_t port) {
ip_addr_t daddr;
addr.to_ip_addr_t(&daddr);
return connect(&daddr, port);
}
size_t AsyncUDP::writeTo(const uint8_t *data, size_t len, const IPAddress addr, uint16_t port, tcpip_adapter_if_t tcpip_if) {
ip_addr_t daddr;
addr.to_ip_addr_t(&daddr);
return writeTo(data, len, &daddr, port, tcpip_if);
}
IPAddress AsyncUDP::listenIP() {
#if CONFIG_LWIP_IPV6
if (!_pcb || _pcb->remote_ip.type != IPADDR_TYPE_V4) {
return IPAddress();
}
return IPAddress(_pcb->remote_ip.u_addr.ip4.addr);
#else
return IPAddress(_pcb->remote_ip.addr);
#endif
}
#if CONFIG_LWIP_IPV6
IPAddress AsyncUDP::listenIPv6() {
if (!_pcb || _pcb->remote_ip.type != IPADDR_TYPE_V6) {
return IPAddress(IPv6);
}
return IPAddress(IPv6, (const uint8_t *)_pcb->remote_ip.u_addr.ip6.addr, _pcb->remote_ip.u_addr.ip6.zone);
}
#endif
size_t AsyncUDP::write(const uint8_t *data, size_t len) {
return writeTo(data, len, &(_pcb->remote_ip), _pcb->remote_port);
}
size_t AsyncUDP::write(uint8_t data) {
return write(&data, 1);
}
size_t AsyncUDP::broadcastTo(uint8_t *data, size_t len, uint16_t port, tcpip_adapter_if_t tcpip_if) {
return writeTo(data, len, IP_ADDR_BROADCAST, port, tcpip_if);
}
size_t AsyncUDP::broadcastTo(const char *data, uint16_t port, tcpip_adapter_if_t tcpip_if) {
return broadcastTo((uint8_t *)data, strlen(data), port, tcpip_if);
}
size_t AsyncUDP::broadcast(uint8_t *data, size_t len) {
if (_pcb->local_port != 0) {
return broadcastTo(data, len, _pcb->local_port);
}
return 0;
}
size_t AsyncUDP::broadcast(const char *data) {
return broadcast((uint8_t *)data, strlen(data));
}
size_t AsyncUDP::sendTo(AsyncUDPMessage &message, const ip_addr_t *addr, uint16_t port, tcpip_adapter_if_t tcpip_if) {
if (!message) {
return 0;
}
return writeTo(message.data(), message.length(), addr, port, tcpip_if);
}
size_t AsyncUDP::sendTo(AsyncUDPMessage &message, const IPAddress addr, uint16_t port, tcpip_adapter_if_t tcpip_if) {
if (!message) {
return 0;
}
return writeTo(message.data(), message.length(), addr, port, tcpip_if);
}
size_t AsyncUDP::send(AsyncUDPMessage &message) {
if (!message) {
return 0;
}
return writeTo(message.data(), message.length(), &(_pcb->remote_ip), _pcb->remote_port);
}
size_t AsyncUDP::broadcastTo(AsyncUDPMessage &message, uint16_t port, tcpip_adapter_if_t tcpip_if) {
if (!message) {
return 0;
}
return broadcastTo(message.data(), message.length(), port, tcpip_if);
}
size_t AsyncUDP::broadcast(AsyncUDPMessage &message) {
if (!message) {
return 0;
}
return broadcast(message.data(), message.length());
}
AsyncUDP::operator bool() {
return _connected;
}
bool AsyncUDP::connected() {
return _connected;
}
esp_err_t AsyncUDP::lastErr() {
return _lastErr;
}
void AsyncUDP::onPacket(AuPacketHandlerFunctionWithArg cb, void *arg) {
onPacket(std::bind(cb, arg, std::placeholders::_1));
}
void AsyncUDP::onPacket(AuPacketHandlerFunction cb) {
_handler = cb;
}
+166
View File
@@ -0,0 +1,166 @@
#ifndef ESPASYNCUDP_H
#define ESPASYNCUDP_H
#include "IPAddress.h"
#include "Print.h"
#include "Stream.h"
#include <functional>
extern "C" {
#include "esp_netif.h"
#include "lwip/ip_addr.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
}
// This enum and it's uses are copied and adapted for compatibility from ESP-IDF 4-
typedef enum {
TCPIP_ADAPTER_IF_STA = 0, /**< Wi-Fi STA (station) interface */
TCPIP_ADAPTER_IF_AP, /**< Wi-Fi soft-AP interface */
TCPIP_ADAPTER_IF_ETH, /**< Ethernet interface */
TCPIP_ADAPTER_IF_PPP, /**< PPP interface */
TCPIP_ADAPTER_IF_MAX
} tcpip_adapter_if_t;
class AsyncUDP;
class AsyncUDPPacket;
class AsyncUDPMessage;
struct udp_pcb;
struct pbuf;
struct netif;
typedef std::function<void(AsyncUDPPacket &packet)> AuPacketHandlerFunction;
typedef std::function<void(void *arg, AsyncUDPPacket &packet)> AuPacketHandlerFunctionWithArg;
class AsyncUDPMessage : public Print {
protected:
uint8_t *_buffer;
size_t _index;
size_t _size;
public:
AsyncUDPMessage(size_t size = CONFIG_TCP_MSS);
virtual ~AsyncUDPMessage();
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
size_t space();
uint8_t *data();
size_t length();
void flush();
operator bool() {
return _buffer != NULL;
}
};
class AsyncUDPPacket : public Stream {
protected:
AsyncUDP *_udp;
pbuf *_pb;
tcpip_adapter_if_t _if;
ip_addr_t _localIp;
uint16_t _localPort;
ip_addr_t _remoteIp;
uint16_t _remotePort;
uint8_t _remoteMac[6];
uint8_t *_data;
size_t _len;
size_t _index;
public:
AsyncUDPPacket(AsyncUDPPacket &packet);
AsyncUDPPacket(AsyncUDP *udp, pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif *netif);
virtual ~AsyncUDPPacket();
uint8_t *data();
size_t length();
bool isBroadcast();
bool isMulticast();
bool isIPv6();
tcpip_adapter_if_t interface();
IPAddress localIP();
#if CONFIG_LWIP_IPV6
IPAddress localIPv6();
#endif
uint16_t localPort();
IPAddress remoteIP();
#if CONFIG_LWIP_IPV6
IPAddress remoteIPv6();
#endif
uint16_t remotePort();
void remoteMac(uint8_t *mac);
size_t send(AsyncUDPMessage &message);
int available();
size_t read(uint8_t *data, size_t len);
int read();
int peek();
void flush();
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
// Copy assignment operator
AsyncUDPPacket &operator=(const AsyncUDPPacket &packet);
};
class AsyncUDP : public Print {
protected:
udp_pcb *_pcb;
//SemaphoreHandle_t _lock;
bool _connected;
esp_err_t _lastErr;
AuPacketHandlerFunction _handler;
bool _init();
void _recv(udp_pcb *upcb, pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif *netif);
public:
AsyncUDP();
virtual ~AsyncUDP();
void onPacket(AuPacketHandlerFunctionWithArg cb, void *arg = NULL);
void onPacket(AuPacketHandlerFunction cb);
bool listen(const ip_addr_t *addr, uint16_t port);
bool listen(const IPAddress addr, uint16_t port);
bool listen(uint16_t port);
bool listenMulticast(const ip_addr_t *addr, uint16_t port, uint8_t ttl = 1, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
bool listenMulticast(const IPAddress addr, uint16_t port, uint8_t ttl = 1, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
bool connect(const ip_addr_t *addr, uint16_t port);
bool connect(const IPAddress addr, uint16_t port);
void close();
size_t writeTo(const uint8_t *data, size_t len, const ip_addr_t *addr, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t writeTo(const uint8_t *data, size_t len, const IPAddress addr, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
size_t broadcastTo(uint8_t *data, size_t len, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t broadcastTo(const char *data, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t broadcast(uint8_t *data, size_t len);
size_t broadcast(const char *data);
size_t sendTo(AsyncUDPMessage &message, const ip_addr_t *addr, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t sendTo(AsyncUDPMessage &message, const IPAddress addr, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t send(AsyncUDPMessage &message);
size_t broadcastTo(AsyncUDPMessage &message, uint16_t port, tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_MAX);
size_t broadcast(AsyncUDPMessage &message);
IPAddress listenIP();
#if CONFIG_LWIP_IPV6
IPAddress listenIPv6();
#endif
bool connected();
esp_err_t lastErr();
operator bool();
static void _s_recv(void *arg, udp_pcb *upcb, pbuf *p, const ip_addr_t *addr, uint16_t port, struct netif *netif);
};
#endif
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Neil Kolban
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+467
View File
@@ -0,0 +1,467 @@
# BLE for ESP32 Arduino Core
A comprehensive reference for ESP32 Bluetooth Low Energy (BLE) pairing and security implementation using the ESP32 Arduino BLE library.
## Overview
This guide provides ESP32 developers with comprehensive information about BLE security implementation using the ESP32 Arduino BLE library. It covers both Bluedroid (ESP32) and NimBLE (other SoCs) implementations with realistic scenarios and troubleshooting guidance.
Issues and questions should be raised here: https://github.com/espressif/arduino-esp32/issues <br> (please don't use https://github.com/nkolban/esp32-snippets/issues or https://github.com/h2zero/NimBLE-Arduino/issues)
## Security
### Quick Start
1. **Choose your ESP32's IO capabilities** using `ESP_IO_CAP_*` constants
2. **Configure authentication requirements** with properties or permissions
3. **Set up security** using `BLESecurity` class methods
4. **Handle stack differences** between Bluedroid (ESP32) and NimBLE (other SoCs)
5. **Test with NVS clearing** during development
### Understanding BLE Pairing
#### Pairing vs Bonding
- **Pairing**: The process of establishing encryption keys between devices
- **Bonding**: Storing those keys for future reconnections (persistent pairing)
#### Pairing Types
- **Legacy Pairing**: Original BLE pairing (Bluetooth 4.0/4.1)
- **Secure Connections**: Enhanced security (Bluetooth 4.2+) using FIPS-approved algorithms
#### Security Levels
- **Just Works**: Encryption without user verification (vulnerable to passive eavesdropping)
- **MITM Protected**: User verification prevents man-in-the-middle attacks
### IO Capabilities Explained
The ESP32 BLE library defines the following IO capabilities:
| Capability | Library Constant | Can Display | Can Input | Can Confirm | Example Devices |
|------------|-----------------|-------------|-----------|-------------|-----------------|
| **No Input No Output** | `ESP_IO_CAP_NONE` | ❌ | ❌ | ❌ | Sensor nodes, beacons, simple actuators |
| **Display Only** | `ESP_IO_CAP_OUT` | ✅ | ❌ | ❌ | E-ink displays, LED matrix displays |
| **Keyboard Only** | `ESP_IO_CAP_IN` | ❌ | ✅ | ❌ | Button-only devices, rotary encoders |
| **Display Yes/No** | `ESP_IO_CAP_IO` | ✅ | ❌ | ✅ | Devices with display + confirmation button |
| **Keyboard Display** | `ESP_IO_CAP_KBDISP` | ✅ | ✅ | ✅ | Full-featured ESP32 devices with UI |
### Pairing Methods Explained
#### 🔓 Just Works
- **Security**: Encryption only (no MITM protection)
- **User Experience**: Automatic, no user interaction
- **Use Case**: Convenience over security (fitness trackers, mice)
- **Vulnerability**: Susceptible to passive eavesdropping during pairing
#### 🔐 Passkey Entry
- **Security**: Full MITM protection
- **User Experience**: One device shows 6-digit code, other inputs it
- **Use Case**: Keyboards pairing to computers
- **Process**:
1. Display device shows random 6-digit number (000000-999999)
2. Input device user types the number
3. Pairing succeeds if numbers match
#### 🔐 Numeric Comparison (Secure Connections Only)
- **Security**: Full MITM protection
- **User Experience**: Both devices show same number, user confirms match
- **Use Case**: Two smartphones/tablets pairing
- **Process**:
1. Both devices display identical 6-digit number
2. User verifies numbers match on both screens
3. User confirms "Yes" on both devices
#### 🔐 Out-of-Band (OOB) (Not supported by this library)
- **Security**: Highest security level
- **User Experience**: Uses external channel (NFC, QR code)
- **Use Case**: High-security applications
- **Priority**: Always used when OOB data is available
### Pairing Methods Compatibility Matrix
Here is the compatibility matrix for the pairing methods depending on the IO capabilities of the devices.
Note that the initiator is the device that starts the pairing process (usually the client) and the responder is
the device that accepts the pairing request (usually the server).
![Pairing Methods Compatibility Matrix](https://www.bluetooth.com/wp-content/uploads/2016/06/screen-shot-06-08-16-at-0124-pm.png)
### Bluedroid vs NimBLE
Bluedroid and NimBLE are two different Bluetooth stack implementations.
#### Bluedroid
Bluedroid is the default Bluetooth stack in ESP-IDF. It supports both Bluetooth Classic and Bluetooth LE. It is used by the ESP32 in the Arduino Core.
Bluedroid requires more flash and RAM than NimBLE and access permissions for characteristics and descriptors are set using a specific API through the `setAccessPermissions()` function.
The original source of the Bluedroid project, **which is not maintained anymore**, can be found here: https://github.com/nkolban/esp32-snippets
**Bluedroid will be replaced by NimBLE in version 4.0.0 of the Arduino Core. Bluetooth Classic and Bluedroid will no longer be supported but can be used by using Arduino as an ESP-IDF component.**
#### NimBLE
NimBLE is a lightweight Bluetooth stack for Bluetooth LE only. It is used by all SoCs that are not the ESP32.
NimBLE requires less flash and RAM than Bluedroid. Access permissions for characteristics are set using exclusive properties in the characteristic creation. Access permissions for descriptors are set using the `setAccessPermissions()` function just like in Bluedroid.
Some parts of the NimBLE implementation are based on the work of h2zero, which can be found here: https://github.com/h2zero/NimBLE-Arduino. For a more customizable and feature-rich implementation of the NimBLE stack, you can use the [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino) library.
### Common Scenarios
Here are some common scenarios for the pairing methods depending on the IO capabilities of the devices. Check also the secure BLE examples in the ESP32 Arduino Core for more detailed usage examples.
#### Scenario 1: Mobile App ↔ ESP32 Sensor Node
- **Devices**: Mobile (`ESP_IO_CAP_IO`) ↔ ESP32 Sensor (`ESP_IO_CAP_NONE`)
- **MITM**: Not achievable with this IO combination (falls back to Just Works)
- **Characteristic Authentication**: Bonding only
- **Result**: Just Works with bonding for reconnection
- **Use Case**: Weather stations, environmental monitors
#### Scenario 2: ESP32 Smart Lock ↔ Mobile App
- **Devices**: ESP32 Lock (`ESP_IO_CAP_OUT`) ↔ Mobile (`ESP_IO_CAP_KBDISP`)
- **MITM**: Required for security
- **Characteristic Authentication**: Bonding + Secure Connection + MITM
- **Result**: Passkey Entry (ESP32 displays, mobile enters)
- **Implementation**: Static passkey or dynamic display
#### Scenario 3: ESP32 Configuration Device ↔ Admin Tool
- **Devices**: ESP32 (`ESP_IO_CAP_KBDISP`) ↔ Admin Tool (`ESP_IO_CAP_KBDISP`)
- **MITM**: Required for configuration security
- **Characteristic Authentication**: Bonding + Secure Connection + MITM
- **Result**:
- Legacy: Passkey Entry
- Secure Connections: Numeric Comparison
- **Use Case**: Industrial IoT configuration, network setup
#### Scenario 4: ESP32 Beacon ↔ Scanner App
- **Devices**: ESP32 Beacon (`ESP_IO_CAP_NONE`) ↔ Scanner (`ESP_IO_CAP_IO`)
- **MITM**: Not required (broadcast only)
- **Characteristic Authentication**: None
- **Result**: No pairing required
- **Use Case**: Asset tracking, proximity detection
#### Scenario 5: ESP32 Smart Home Hub ↔ Multiple Devices
- **Devices**: ESP32 Hub (`ESP_IO_CAP_IO`) ↔ Various sensors (`ESP_IO_CAP_NONE`)
- **MITM**: Not possible when any of the peers are `ESP_IO_CAP_NONE`
- **Characteristic Authentication**: Bonding only (no MITM possible with `ESP_IO_CAP_NONE`)
- **Result**: Just Works only
- **Use Case**: Centralized home automation controller
### Implementation Guidelines
#### For ESP32 Device Developers
##### Choosing IO Capabilities
```cpp
#include <BLESecurity.h>
// Conservative approach - limits pairing methods but ensures compatibility
pSecurity->setCapability(ESP_IO_CAP_NONE); // Just Works only
// Balanced approach - good UX with optional security
pSecurity->setCapability(ESP_IO_CAP_IO); // Just Works or Numeric Comparison
// Maximum security - supports all methods
pSecurity->setCapability(ESP_IO_CAP_KBDISP); // All pairing methods available
```
##### Authentication Configuration
```cpp
BLESecurity *pSecurity = new BLESecurity();
// Low security applications (sensors, environmental monitoring)
pSecurity->setAuthenticationMode(ESP_LE_AUTH_NO_BOND);
// Standard security with bonding (smart home devices)
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
// MITM protection required (access control, payments)
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_MITM | ESP_LE_AUTH_BOND);
// Maximum security with Secure Connections (critical systems)
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
// Alternative syntax (more readable for complex requirements)
pSecurity->setAuthenticationMode(true, true, true); // Bonding, MITM, Secure Connections
```
##### Static Passkey Example (from secure examples)
```cpp
// Set a static passkey for consistent pairing experience
#define DEVICE_PASSKEY 123456
pSecurity->setPassKey(true, DEVICE_PASSKEY); // static=true, passkey=123456
pSecurity->setCapability(ESP_IO_CAP_KBDISP); // Required for MITM even with static passkey
```
### Security Considerations
#### When to Require MITM
- **Always**: Payment systems, medical devices, access control
- **Usually**: File transfers, personal data sync, keyboards
- **Optional**: Fitness trackers, environmental sensors, mice
- **Never**: Beacons, broadcast-only devices
#### Legacy vs Secure Connections
- **Legacy**: Compatible with all BLE devices (2010+)
- **Secure Connections**: Better security but requires Bluetooth 4.2+ (2014+)
- **Recommendation**: Support both, prefer Secure Connections when available
#### Implementation Differences
```cpp
// Basic characteristic properties (both stacks)
uint32_t properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE;
// NimBLE: Add authentication properties (ignored by Bluedroid)
properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_WRITE_AUTHEN;
BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHAR_UUID, properties);
// Bluedroid: Set access permissions (ignored by NimBLE)
pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
// Check which stack is running
String stackType = BLEDevice::getBLEStackString();
Serial.println("Using BLE stack: " + stackType);
```
#### Known Vulnerabilities
1. **Just Works**: Vulnerable to passive eavesdropping during initial pairing
2. **Legacy Pairing**: Uses weaker cryptographic algorithms
3. **Passkey Brute Force**: 6-digit passkeys have only 1M combinations
4. **Physical Security**: Displayed passkeys can be shoulder-surfed
### Troubleshooting
#### Common Issues
##### Pairing Always Uses Just Works
```cpp
// ❌ Problem: Missing MITM flag
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
// ✅ Solution: Add MITM protection
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
```
##### Static passkey not being requested / Nothing happens when trying to read secure characteristic
```cpp
// ❌ Problem: Wrong IO capability for MITM
pSecurity->setCapability(ESP_IO_CAP_NONE); // Can't support MITM
// ✅ Solution: Set proper capability even for static passkey
pSecurity->setCapability(ESP_IO_CAP_KBDISP); // Required for MITM
pSecurity->setPassKey(true, 123456);
```
##### Secure Characteristic Access Fails
```cpp
// ❌ Problem: Wrong security method for stack
// Bluedroid approach (won't work on NimBLE)
uint32_t properties = BLECharacteristic::PROPERTY_READ;
pCharacteristic = pService->createCharacteristic(uuid, properties);
pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM); // Ignored by NimBLE!
// ✅ Solution: Use both methods for cross-compatibility
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_READ_AUTHEN; // For NimBLE
pCharacteristic = pService->createCharacteristic(uuid, properties);
pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM); // For Bluedroid
```
##### Pairing Works Once, Then Fails (NVS Cache Issue)
```cpp
// ✅ Solution: Clear NVS for testing/development
Serial.println("Clearing NVS pairing data for testing...");
nvs_flash_erase();
nvs_flash_init();
```
##### Default Passkey Warning
```
*WARNING* Using default passkey: 123456
*WARNING* Please use a random passkey or set a different static passkey
```
```cpp
// ✅ Solution: Change from default
#define CUSTOM_PASSKEY 567890 // Your unique passkey
pSecurity->setPassKey(true, CUSTOM_PASSKEY);
```
##### Connection drops during pairing
```cpp
// ✅ Solution: Implement security callbacks for better error handling
class MySecurityCallbacks : public BLESecurityCallbacks {
void onAuthenticationComplete(esp_ble_auth_cmpl_t param) override {
if (param.success) {
Serial.println("Pairing successful!");
} else {
Serial.printf("Pairing failed, reason: %d\n", param.fail_reason);
}
}
};
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
```
#### Cross-Platform Best Practice
```cpp
// Always use both methods for maximum compatibility
uint32_t secure_properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_READ_AUTHEN | // NimBLE
BLECharacteristic::PROPERTY_WRITE_AUTHEN; // NimBLE
BLECharacteristic *pChar = pService->createCharacteristic(uuid, secure_properties);
// Bluedroid permissions (ignored by NimBLE, but doesn't hurt)
pChar->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
```
### Complete Properties and Permissions Reference
#### Bluedroid
Bluedroid uses properties to define the capabilities of a characteristic and permissions to define the access permissions. NimBLE will ignore the access permissions.
##### Supported Properties
```cpp
BLECharacteristic::PROPERTY_READ // Read operation
BLECharacteristic::PROPERTY_WRITE // Write operation
BLECharacteristic::PROPERTY_WRITE_NR // Write without response
BLECharacteristic::PROPERTY_NOTIFY // Notifications
BLECharacteristic::PROPERTY_INDICATE // Indications
BLECharacteristic::PROPERTY_BROADCAST // Broadcast
```
##### Characteristic and Descriptor Access Permissions
```cpp
// Basic permissions
ESP_GATT_PERM_READ // Read allowed
ESP_GATT_PERM_WRITE // Write allowed
// Encryption required
ESP_GATT_PERM_READ_ENCRYPTED // Read requires encryption
ESP_GATT_PERM_WRITE_ENCRYPTED // Write requires encryption
// Authentication required (MITM protection)
ESP_GATT_PERM_READ_ENC_MITM // Read requires encryption + MITM
ESP_GATT_PERM_WRITE_ENC_MITM // Write requires encryption + MITM
// Authorization required
ESP_GATT_PERM_READ_AUTHORIZATION // Read requires authorization callback
ESP_GATT_PERM_WRITE_AUTHORIZATION // Write requires authorization callback
```
#### NimBLE
NimBLE uses properties to define both the capabilities of a characteristic and the access permissions. Bluedroid will ignore the NimBLE exclusive properties.
##### Supported Properties
```cpp
// Basic properties
BLECharacteristic::PROPERTY_READ // Read operation
BLECharacteristic::PROPERTY_WRITE // Write operation
BLECharacteristic::PROPERTY_WRITE_NR // Write without response
BLECharacteristic::PROPERTY_NOTIFY // Notifications
BLECharacteristic::PROPERTY_INDICATE // Indications
BLECharacteristic::PROPERTY_BROADCAST // Broadcast
// NimBLE specific properties
// Encryption required
BLECharacteristic::PROPERTY_READ_ENC // Read requires encryption
BLECharacteristic::PROPERTY_WRITE_ENC // Write requires encryption
// Authentication required (MITM protection)
BLECharacteristic::PROPERTY_READ_AUTHEN // Read requires encryption + MITM protection
BLECharacteristic::PROPERTY_WRITE_AUTHEN // Write requires encryption + MITM protection
// Authorization required
BLECharacteristic::PROPERTY_READ_AUTHOR // Read requires authorization callback
BLECharacteristic::PROPERTY_WRITE_AUTHOR // Write requires authorization callback
```
##### Descriptor Access Permissions
```cpp
// Basic permissions
ESP_GATT_PERM_READ // Read allowed
ESP_GATT_PERM_WRITE // Write allowed
// Encryption required
ESP_GATT_PERM_READ_ENCRYPTED // Read requires encryption
ESP_GATT_PERM_WRITE_ENCRYPTED // Write requires encryption
// Authentication required (MITM protection)
ESP_GATT_PERM_READ_ENC_MITM // Read requires encryption + MITM
ESP_GATT_PERM_WRITE_ENC_MITM // Write requires encryption + MITM
// Authorization required
ESP_GATT_PERM_READ_AUTHORIZATION // Read requires authorization callback
ESP_GATT_PERM_WRITE_AUTHORIZATION // Write requires authorization callback
```
#### Usage Examples by Security Level
##### No Security (Both Stacks)
```cpp
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE;
```
##### Encryption Only
```cpp
// NimBLE
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_READ_ENC |
BLECharacteristic::PROPERTY_WRITE_ENC;
// Bluedroid
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE;
pChar->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
```
##### MITM Protection (Authentication)
```cpp
// NimBLE
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_READ_AUTHEN |
BLECharacteristic::PROPERTY_WRITE_AUTHEN;
// Bluedroid
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE;
pChar->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
```
##### Authorization Required
```cpp
// NimBLE
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_READ_AUTHOR |
BLECharacteristic::PROPERTY_WRITE_AUTHOR;
// Bluedroid
uint32_t properties = BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE;
pChar->setAccessPermissions(ESP_GATT_PERM_READ_AUTHORIZATION | ESP_GATT_PERM_WRITE_AUTHORIZATION);
```
#### Debug Tips
1. **Log pairing features** exchanged between devices
2. **Monitor pairing method** selected by the stack
3. **Check timeout values** for user input methods
4. **Verify key distribution** flags match on both sides
### Standards References
- **Bluetooth Core Specification v5.4**: Volume 3, Part H (Security Manager)
- **Bluetooth Assigned Numbers**: IO Capability values
- **FIPS-140-2**: Cryptographic standards for Secure Connections
---
*This guide is based on the ESP32 Arduino BLE library implementation and the official Bluetooth Core Specification. For the latest API documentation, refer to the ESP32 Arduino BLE library source code and examples.*
@@ -0,0 +1,52 @@
/*
BLE5 extended scan example for esp32 C3 and S3
with this code it is simple to scan legacy (BLE4) compatible advertising,
and BLE5 extended advertising. New coded added in BLEScan is not changing old behavior,
which can be used with old esp32, but is adding functionality to use on C3/S3.
With this new API advertised device wont be stored in API, it is now user responsibility
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support extended scan yet. Try using Bluedroid."
#elif !defined(SOC_BLE_50_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
uint32_t scanTime = 100; //In 10ms (1000ms)
BLEScan *pBLEScan;
class MyBLEExtAdvertisingCallbacks : public BLEExtAdvertisingCallbacks {
void onResult(esp_ble_gap_ext_adv_report_t report) {
if (report.event_type & ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY) {
// here we can receive regular advertising data from BLE4.x devices
Serial.println("BLE4.2");
} else {
// here we will get extended advertising data that are advertised over data channel by BLE5 devices
Serial.printf("Ext advertise: data_le: %d, data_status: %d \n", report.adv_data_len, report.data_status);
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setExtendedScanCallback(new MyBLEExtAdvertisingCallbacks());
pBLEScan->setExtScanParams(); // use with pre-defined/default values, overloaded function allows to pass parameters
delay(1000); // it is just for simplicity this example, to let ble stack to set extended scan params
pBLEScan->startExtScan(scanTime, 3); // scan duration in n * 10ms, period - repeat after n seconds (period >= duration)
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
#endif // SOC_BLE_50_SUPPORTED
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,143 @@
/*
Simple BLE5 multi advertising example on esp32 C3/S3
only ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND is backward compatible
and can be scanned with BLE4.2 devices
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support multi advertising yet. Try using Bluedroid."
#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEAdvertising.h>
esp_ble_gap_ext_adv_params_t ext_adv_params_1M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
.interval_min = 0x30,
.interval_max = 0x30,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_CODED,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 0,
.scan_req_notif = false,
};
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE,
.interval_min = 0x40,
.interval_max = 0x40,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 1,
.scan_req_notif = false,
};
esp_ble_gap_ext_adv_params_t legacy_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND,
.interval_min = 0x45,
.interval_max = 0x45,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 2,
.scan_req_notif = false,
};
esp_ble_gap_ext_adv_params_t ext_adv_params_coded = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE,
.interval_min = 0x50,
.interval_max = 0x50,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_CODED,
.sid = 3,
.scan_req_notif = false,
};
static uint8_t raw_adv_data_1m[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x12, 0x09, 'E', 'S', 'P', '_', 'M',
'U', 'L', 'T', 'I', '_', 'A', 'D', 'V', '_', '1', 'M', 0X0};
static uint8_t raw_scan_rsp_data_2m[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x12, 0x09, 'E', 'S', 'P', '_', 'M',
'U', 'L', 'T', 'I', '_', 'A', 'D', 'V', '_', '2', 'M', 0X0};
static uint8_t legacy_adv_data[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x15, 0x09, 'E', 'S', 'P', '_', 'M', 'U',
'L', 'T', 'I', '_', 'A', 'D', 'V', '_', 'C', 'O', 'D', 'E', 'D', 0X0};
static uint8_t legacy_scan_rsp_data[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x16, 0x09, 'E', 'S', 'P', '_', 'M', 'U', 'L',
'T', 'I', '_', 'A', 'D', 'V', '_', 'L', 'E', 'G', 'A', 'C', 'Y', 0X0};
static uint8_t raw_scan_rsp_data_coded[] = {0x37, 0x09, 'V', 'E', 'R', 'Y', '_', 'L', 'O', 'N', 'G', '_', 'D', 'E', 'V', 'I', 'C', 'E', '_',
'N', 'A', 'M', 'E', '_', 'S', 'E', 'N', 'T', '_', 'U', 'S', 'I', 'N', 'G', '_', 'E', 'X', 'T',
'E', 'N', 'D', 'E', 'D', '_', 'A', 'D', 'V', 'E', 'R', 'T', 'I', 'S', 'I', 'N', 'G', 0X0};
uint8_t addr_1m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x01};
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
uint8_t addr_legacy[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x03};
uint8_t addr_coded[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x04};
BLEMultiAdvertising advert(4); // max number of advertisement data
void setup() {
Serial.begin(115200);
Serial.println("Multi-Advertising...");
BLEDevice::init("");
advert.setAdvertisingParams(0, &ext_adv_params_1M);
advert.setAdvertisingData(0, sizeof(raw_adv_data_1m), &raw_adv_data_1m[0]);
advert.setInstanceAddress(0, addr_1m);
advert.setDuration(0);
advert.setAdvertisingParams(1, &ext_adv_params_2M);
advert.setScanRspData(1, sizeof(raw_scan_rsp_data_2m), &raw_scan_rsp_data_2m[0]);
advert.setInstanceAddress(1, addr_2m);
advert.setDuration(1);
advert.setAdvertisingParams(2, &legacy_adv_params);
advert.setAdvertisingData(2, sizeof(legacy_adv_data), &legacy_adv_data[0]);
advert.setScanRspData(2, sizeof(legacy_scan_rsp_data), &legacy_scan_rsp_data[0]);
advert.setInstanceAddress(2, addr_legacy);
advert.setDuration(2);
advert.setAdvertisingParams(3, &ext_adv_params_coded);
advert.setDuration(3);
advert.setScanRspData(3, sizeof(raw_scan_rsp_data_coded), &raw_scan_rsp_data_coded[0]);
advert.setInstanceAddress(3, addr_coded);
delay(1000);
advert.start(4, 0);
}
void loop() {
delay(2000);
}
#endif
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,70 @@
/*
Simple BLE5 periodic advertising example on esp32 C3/S3
only ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED can be used for periodic advertising
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support periodic advertising yet. Try using Bluedroid."
#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEAdvertising.h>
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
.interval_min = 0x40,
.interval_max = 0x40,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr_type = BLE_ADDR_TYPE_RANDOM,
.peer_addr = {0, 0, 0, 0, 0, 0},
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 1,
.scan_req_notif = false,
};
static uint8_t raw_scan_rsp_data_2m[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x12, 0x09, 'E', 'S', 'P', '_', 'M',
'U', 'L', 'T', 'I', '_', 'A', 'D', 'V', '_', '2', 'M', 0X0};
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
.interval_min = 0x320, // 1000 ms interval
.interval_max = 0x640,
.properties = 0, // Do not include TX power
};
static uint8_t periodic_adv_raw_data[] = {0x02, 0x01, 0x06, 0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd, 0x11, 0x09, 'E', 'S',
'P', '_', 'P', 'E', 'R', 'I', 'O', 'D', 'I', 'C', '_', 'A', 'D', 'V'};
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
BLEMultiAdvertising advert(1); // max number of advertisement data
void setup() {
Serial.begin(115200);
Serial.println("Multi-Advertising...");
BLEDevice::init("");
advert.setAdvertisingParams(0, &ext_adv_params_2M);
advert.setAdvertisingData(0, sizeof(raw_scan_rsp_data_2m), &raw_scan_rsp_data_2m[0]);
advert.setInstanceAddress(0, addr_2m);
advert.setDuration(0, 0, 0);
delay(100);
advert.start();
advert.setPeriodicAdvertisingParams(0, &periodic_adv_params);
advert.setPeriodicAdvertisingData(0, sizeof(periodic_adv_raw_data), &periodic_adv_raw_data[0]);
advert.startPeriodicAdvertising(0);
}
void loop() {
delay(2000);
}
#endif
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,93 @@
/*
BLE5 extended scan example for esp32 C3 and S3
with this code it is simple to scan legacy (BLE4) compatible advertising,
and BLE5 extended advertising. New coded added in BLEScan is not changing old behavior,
which can be used with old esp32, but is adding functionality to use on C3/S3.
With this new API advertised device wont be stored in API, it is now user responsibility
author: chegewara
*/
#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support periodic sync yet. Try using Bluedroid."
#elif !defined(SOC_BLE_50_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
BLEScan *pBLEScan;
static bool periodic_sync = false;
static esp_ble_gap_periodic_adv_sync_params_t periodic_adv_sync_params = {
.filter_policy = 0,
.sid = 0,
.addr_type = BLE_ADDR_TYPE_RANDOM,
.addr = {0, 0, 0, 0, 0, 0},
.skip = 10,
.sync_timeout = 1000, // timeout: 1000 * 10ms
};
class MyBLEExtAdvertisingCallbacks : public BLEExtAdvertisingCallbacks {
void onResult(esp_ble_gap_ext_adv_report_t params) {
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
adv_name = esp_ble_resolve_adv_data(params.adv_data, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, "ESP_MULTI_ADV_2M", adv_name_len) == 0) && !periodic_sync) {
periodic_sync = true;
char adv_temp_name[60] = {'0'};
memcpy(adv_temp_name, adv_name, adv_name_len);
log_i("Start create sync with the peer device %s", adv_temp_name);
periodic_adv_sync_params.sid = params.sid;
// periodic_adv_sync_params.addr_type = params.addr_type;
memcpy(periodic_adv_sync_params.addr, params.addr, sizeof(esp_bd_addr_t));
esp_ble_gap_periodic_adv_create_sync(&periodic_adv_sync_params);
}
}
};
class MyPeriodicScan : public BLEPeriodicScanCallbacks {
// void onCreateSync(esp_bt_status_t status){}
// void onCancelSync(esp_bt_status_t status){}
// void onTerminateSync(esp_bt_status_t status){}
void onStop(esp_bt_status_t status) {
log_i("ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT");
periodic_sync = false;
pBLEScan->startExtScan(0, 0); // scan duration in n * 10ms, period - repeat after n seconds (period >= duration)
}
void onLostSync(uint16_t sync_handle) {
log_i("ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT");
esp_ble_gap_stop_ext_scan();
}
void onSync(esp_ble_periodic_adv_sync_estab_param_t params) {
log_i("ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT, status %d", params.status);
// esp_log_buffer_hex("sync addr", param->periodic_adv_sync_estab.adv_addr, 6);
log_i("sync handle %d sid %d perioic adv interval %d adv phy %d", params.sync_handle, params.sid, params.period_adv_interval, params.adv_phy);
}
void onReport(esp_ble_gap_periodic_adv_report_t params) {
log_i("periodic adv report, sync handle %d data status %d data len %d rssi %d", params.sync_handle, params.data_status, params.data_length, params.rssi);
}
};
void setup() {
Serial.begin(115200);
Serial.println("Periodic scan...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setExtendedScanCallback(new MyBLEExtAdvertisingCallbacks());
pBLEScan->setExtScanParams(); // use with pre-defined/default values, overloaded function allows to pass parameters
pBLEScan->setPeriodicScanCallback(new MyPeriodicScan());
delay(100); // it is just for simplicity this example, to let ble stack to set extended scan params
pBLEScan->startExtScan(0, 0);
}
void loop() {
delay(2000);
}
#endif // SOC_BLE_50_SUPPORTED
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_50_SUPPORTED=y
- CONFIG_BLUEDROID_ENABLED=y
@@ -0,0 +1,118 @@
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
Ported to Arduino ESP32 by Evandro Copercini
Changed to a beacon scanner to report iBeacon, EddystoneURL and EddystoneTLM beacons by beegee-tokyo
Upgraded Eddystone part by Tomas Pilny on Feb 20, 2023
*/
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <BLEEddystoneURL.h>
#include <BLEEddystoneTLM.h>
#include <BLEBeacon.h>
int scanTime = 5; //In seconds
BLEScan *pBLEScan;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
if (advertisedDevice.haveName()) {
Serial.print("Device name: ");
Serial.println(advertisedDevice.getName().c_str());
Serial.println("");
}
if (advertisedDevice.haveServiceUUID()) {
BLEUUID devUUID = advertisedDevice.getServiceUUID();
Serial.print("Found ServiceUUID: ");
Serial.println(devUUID.toString().c_str());
Serial.println("");
}
if (advertisedDevice.haveManufacturerData() == true) {
String strManufacturerData = advertisedDevice.getManufacturerData();
// Buffer to store manufacturer data (BLE max is 255 bytes)
uint8_t cManufacturerData[255];
size_t dataLength = strManufacturerData.length();
// Bounds checking to prevent buffer overflow
if (dataLength <= sizeof(cManufacturerData)) {
memcpy(cManufacturerData, strManufacturerData.c_str(), dataLength);
if (dataLength == 25 && cManufacturerData[0] == 0x4C && cManufacturerData[1] == 0x00) {
Serial.println("Found an iBeacon!");
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setData(strManufacturerData);
Serial.printf("iBeacon Frame\n");
Serial.printf(
"ID: %04X Major: %d Minor: %d UUID: %s Power: %d\n", oBeacon.getManufacturerId(), ENDIAN_CHANGE_U16(oBeacon.getMajor()),
ENDIAN_CHANGE_U16(oBeacon.getMinor()), oBeacon.getProximityUUID().toString().c_str(), oBeacon.getSignalPower()
);
} else {
Serial.println("Found another manufacturers beacon!");
Serial.printf("strManufacturerData: %zu ", dataLength);
for (int i = 0; i < dataLength; i++) {
Serial.printf("[%X]", cManufacturerData[i]);
}
Serial.printf("\n");
}
} else {
Serial.printf("Manufacturer data too large (%zu bytes), skipping\n", dataLength);
}
}
if (advertisedDevice.getFrameType() == BLE_EDDYSTONE_URL_FRAME) {
Serial.println("Found an EddystoneURL beacon!");
BLEEddystoneURL EddystoneURL = BLEEddystoneURL(&advertisedDevice);
Serial.printf("URL bytes: 0x");
String url = EddystoneURL.getURL();
for (auto byte : url) {
Serial.printf("%02X", byte);
}
Serial.printf("\n");
Serial.printf("Decoded URL: %s\n", EddystoneURL.getDecodedURL().c_str());
Serial.printf("EddystoneURL.getDecodedURL(): %s\n", EddystoneURL.getDecodedURL().c_str());
Serial.printf("TX power %d (Raw 0x%02X)\n", EddystoneURL.getPower(), EddystoneURL.getPower());
Serial.println("\n");
}
if (advertisedDevice.getFrameType() == BLE_EDDYSTONE_TLM_FRAME) {
Serial.println("Found an EddystoneTLM beacon!");
BLEEddystoneTLM EddystoneTLM(&advertisedDevice);
Serial.printf("Reported battery voltage: %dmV\n", EddystoneTLM.getVolt());
Serial.printf("Reported temperature: %.2f°C (raw data=0x%04X)\n", EddystoneTLM.getTemp(), EddystoneTLM.getRawTemp());
Serial.printf("Reported advertise count: %lu\n", EddystoneTLM.getCount());
Serial.printf("Reported time since last reboot: %lus\n", EddystoneTLM.getTime());
Serial.println("\n");
Serial.print(EddystoneTLM.toString().c_str());
Serial.println("\n");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices->getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
@@ -0,0 +1,9 @@
## BLE Beacon Scanner
Initiates a BLE device scan.
Checks if the discovered devices are
- an iBeacon
- an Eddystone TLM beacon
- an Eddystone URL beacon
and sends the decoded beacon information over Serial log
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+158
View File
@@ -0,0 +1,158 @@
/**
* A BLE client example that is rich in capabilities.
* There is a lot new capabilities implemented.
* author unknown
* updated by chegewara
*/
#include "BLEDevice.h"
//#include "BLEScan.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *myDevice;
// Callback function to handle notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.write(pData, length);
Serial.println();
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer() {
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
BLEClient *pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if (pRemoteCharacteristic->canRead()) {
String value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if (pRemoteCharacteristic->canNotify()) {
// Register/Subscribe for notifications
pRemoteCharacteristic->registerForNotify(notifyCallback);
}
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
void setup() {
Serial.begin(115200);
Serial.println("Starting Arduino BLE Client application...");
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
} // End of setup.
// This is the Arduino main loop function.
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
// with the current time since boot.
if (connected) {
String newValue = "Time since boot: " + String(millis() / 1000);
Serial.println("Setting new characteristic value to \"" + newValue + "\"");
// Set the characteristic's value to be the array of bytes that is actually a string.
pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
} else if (doScan) {
BLEDevice::getScan()->start(0); // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
}
delay(1000); // Delay a second between loops.
} // End of loop
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,253 @@
/*
* BLE HID Gamepad Client Example
*
* This example demonstrates how to connect to a BLE HID Gamepad and read its input.
* The ESP32 acts as a BLE Central (client) that connects to a BLE gamepad peripheral.
*
* Features:
* - Scans for BLE HID gamepad devices
* - Connects to the first gamepad found
* - Secure pairing with bonding
* - Subscribes to input report notifications
* - Parses and displays gamepad input (buttons and axes)
* - Automatic reconnection on disconnect
*
* Usage:
* 1. Upload this sketch to your ESP32
* 2. Turn on your BLE gamepad (or run the Server_Gamepad example on another ESP32)
* 3. The ESP32 will scan, connect, and display gamepad input in the serial monitor
*
* Note: This example uses "Just Works" pairing for automatic connection without
* PIN entry or confirmation. The bond is saved for future connections.
*
* Compatible with gamepads using the standard HID Report Descriptor format
*
* Created by lucasssvaz
*/
#include <BLEDevice.h>
#include <BLESecurity.h>
// HID Service UUID (standard UUID for HID over GATT)
static BLEUUID hidServiceUUID((uint16_t)0x1812);
// HID Report characteristic UUID (used for input/output reports)
static BLEUUID reportCharUUID((uint16_t)0x2A4D);
// HID Report Map characteristic UUID
static BLEUUID reportMapUUID((uint16_t)0x2A4B);
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pInputReportCharacteristic = nullptr;
static BLEAdvertisedDevice *myDevice = nullptr;
static BLEClient *pClient = nullptr;
// Gamepad report structure (adjust based on your gamepad's report descriptor)
// This matches the Server_Gamepad example format
struct GamepadReport {
uint8_t reportId; // Report ID
int8_t x; // X axis (-127 to 127)
int8_t y; // Y axis (-127 to 127)
uint8_t buttons; // 8 buttons (bit 0-7)
} __attribute__((packed));
// Callback function to handle gamepad input notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.printf("Received %d bytes: ", length);
// Check if data length matches our expected gamepad report
if (length == sizeof(GamepadReport)) {
GamepadReport *report = (GamepadReport *)pData;
Serial.printf("ID=%d, X=%4d, Y=%4d, Buttons=0x%02X [", report->reportId, report->x, report->y, report->buttons);
// Display which buttons are pressed
for (int i = 0; i < 8; i++) {
if (report->buttons & (1 << i)) {
Serial.printf("%d ", i + 1);
}
}
Serial.println("]");
} else {
// Unknown format, just display hex dump
Serial.print("Raw data: ");
for (size_t i = 0; i < length; i++) {
Serial.printf("%02X ", pData[i]);
}
Serial.println();
}
}
// Client callbacks to handle connection events
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {
Serial.println("Connected to gamepad");
}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("Disconnected from gamepad");
}
};
// Function to connect to the gamepad
bool connectToServer() {
Serial.print("Connecting to gamepad at ");
Serial.println(myDevice->getAddress().toString().c_str());
pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the gamepad
pClient->connect(myDevice);
Serial.println(" - Connected to server");
pClient->setMTU(185); // Set MTU for larger data transfers
// Obtain a reference to the HID service
BLERemoteService *pRemoteService = pClient->getService(hidServiceUUID);
if (pRemoteService == nullptr) {
Serial.println("Failed to find HID service");
pClient->disconnect();
return false;
}
Serial.println(" - Found HID service");
// Get all characteristics to find input reports
std::map<std::string, BLERemoteCharacteristic *> *pCharMap = pRemoteService->getCharacteristics();
// Look for input report characteristics (UUID 0x2A4D)
for (auto const &entry : *pCharMap) {
BLERemoteCharacteristic *pChar = entry.second;
if (pChar->getUUID().equals(reportCharUUID)) {
// Check if this characteristic has notify property (input report)
if (pChar->canNotify()) {
Serial.printf(" - Found input report characteristic (handle: 0x%04X)\n", pChar->getHandle());
// Try to read Report Reference Descriptor to identify report type and ID
BLERemoteDescriptor *pReportRefDesc = pChar->getDescriptor(BLEUUID((uint16_t)0x2908));
if (pReportRefDesc != nullptr) {
String refValue = pReportRefDesc->readValue();
if (refValue.length() >= 2) {
uint8_t reportId = refValue[0];
uint8_t reportType = refValue[1];
Serial.printf(" Report ID: %d, Type: %d (1=Input, 2=Output, 3=Feature)\n", reportId, reportType);
// We want input reports (type = 1)
if (reportType == 1) {
pInputReportCharacteristic = pChar;
}
}
} else {
// No report reference descriptor, assume it's an input report
pInputReportCharacteristic = pChar;
}
}
}
}
if (pInputReportCharacteristic == nullptr) {
Serial.println("Failed to find input report characteristic");
pClient->disconnect();
return false;
}
// Subscribe to input report notifications
Serial.println(" - Subscribing to input report notifications");
pInputReportCharacteristic->registerForNotify(notifyCallback);
connected = true;
Serial.println("Successfully connected and subscribed to gamepad!");
// Note: Security/encryption will be automatically handled by the BLE stack
// when the HID device requires it (using "Just Works" pairing).
return true;
}
// Scan callback to detect gamepad devices
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Device found: ");
Serial.print(advertisedDevice.toString().c_str());
// Check if device advertises HID service
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(hidServiceUUID)) {
Serial.print(" - HID Device!");
// Check if it's a gamepad by appearance (0x03C4 = HID Gamepad)
if (advertisedDevice.haveAppearance()) {
uint16_t appearance = advertisedDevice.getAppearance();
Serial.printf(" (Appearance: 0x%04X)", appearance);
if (appearance == 0x03C4) {
Serial.print(" - GAMEPAD!");
}
}
Serial.println();
// Stop scanning and connect
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
} else {
Serial.println();
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("\n=== BLE HID Gamepad Client ===");
Serial.println("Scanning for BLE HID gamepads...\n");
BLEDevice::init("ESP32-Gamepad-Client");
// Configure BLE Security for pairing with HID devices
BLESecurity *pSecurity = new BLESecurity();
// Set security capabilities and authentication mode
// HID devices typically use "Just Works" pairing (no MITM) with bonding
// Set IO capability to NONE for "Just Works" pairing
pSecurity->setCapability(ESP_IO_CAP_NONE);
// Bonding, no MITM, secure connections (for "Just Works" pairing)
pSecurity->setAuthenticationMode(true, false, true);
// Set security callbacks (using default implementation)
BLEDevice::setSecurityCallbacks(new BLESecurityCallbacks());
Serial.println("Security configured: Bonding + Secure Connections\n");
// Create scanner and set callbacks
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
}
void loop() {
// Connect to gamepad if found
if (doConnect == true) {
if (connectToServer()) {
Serial.println("\n*** Ready to receive gamepad input ***\n");
} else {
Serial.println("Failed to connect to gamepad");
}
doConnect = false;
}
// Restart scanning if disconnected
if (!connected && doScan) {
Serial.println("\nScanning for gamepads...");
BLEDevice::getScan()->start(5, false);
delay(1000);
}
delay(100);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,280 @@
/*
* BLE Client and Server Coexistence Example
*
* This example demonstrates how to run both BLE client and server
* functionality on the same ESP32 device simultaneously.
*
* The device will:
* - Act as a BLE server, advertising a service with a characteristic
* - Act as a BLE client, scanning for other BLE servers
* - Connect to found servers and interact with their services
* - Handle both incoming and outgoing connections
*
* You can test this example by uploading it to two ESP32 boards.
*
* Author: lucasssvaz
* Based on Arduino BLE examples
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEClient.h>
#include <BLEScan.h>
// Server-side definitions
#define SERVER_SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define SERVER_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Client-side definitions (looking for the same service)
static BLEUUID clientServiceUUID(SERVER_SERVICE_UUID);
static BLEUUID clientCharUUID(SERVER_CHARACTERISTIC_UUID);
// Server objects
BLEServer *pServer = nullptr;
BLECharacteristic *pServerCharacteristic = nullptr;
// Client objects
static boolean doConnect = false;
static boolean clientConnected = false;
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *targetDevice;
static BLEClient *pClient = nullptr;
BLEScan *pBLEScan = nullptr;
// Server callbacks
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
Serial.println("Server: Client connected");
}
void onDisconnect(BLEServer *pServer) {
Serial.println("Server: Client disconnected");
// Restart advertising
BLEDevice::startAdvertising();
}
};
// Characteristic callbacks for server
class CharacteristicCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String value = pCharacteristic->getValue();
Serial.print("Server: Characteristic written, value: ");
Serial.println(value.c_str());
}
void onRead(BLECharacteristic *pCharacteristic) {
Serial.println("Server: Characteristic read");
}
};
// Client callbacks
class ClientCallbacks : public BLEClientCallbacks {
void onConnect(BLEClient *pClient) {
Serial.println("Client: Connected to server");
clientConnected = true;
}
void onDisconnect(BLEClient *pClient) {
Serial.println("Client: Disconnected from server");
clientConnected = false;
}
};
// Client notification callback
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Client: Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("Client: Data: ");
Serial.write(pData, length);
Serial.println();
}
// Scan callbacks
class AdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("Client: Found device: ");
Serial.println(advertisedDevice.toString().c_str());
// Check if this device has our target service
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(clientServiceUUID) && !clientConnected) {
Serial.println("Client: Found target service, attempting connection...");
BLEDevice::getScan()->stop();
targetDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
}
}
};
bool connectToServer() {
Serial.print("Client: Forming connection to ");
Serial.println(targetDevice->getAddress().toString().c_str());
// Create client if it doesn't exist, otherwise reuse existing one
if (pClient == nullptr) {
pClient = BLEDevice::createClient();
pClient->setClientCallbacks(new ClientCallbacks());
Serial.println("Client: Created new client");
} else {
Serial.println("Client: Reusing existing client");
}
if (!pClient->connect(targetDevice)) {
Serial.println("Client: Failed to connect");
return false;
}
Serial.println("Client: Connected to server");
pClient->setMTU(517); // Request maximum MTU
// Get the service
BLERemoteService *pRemoteService = pClient->getService(clientServiceUUID);
if (pRemoteService == nullptr) {
Serial.print("Client: Failed to find service UUID: ");
Serial.println(clientServiceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println("Client: Found service");
// Get the characteristic
pRemoteCharacteristic = pRemoteService->getCharacteristic(clientCharUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Client: Failed to find characteristic UUID: ");
Serial.println(clientCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println("Client: Found characteristic");
// Read the initial value
if (pRemoteCharacteristic->canRead()) {
String value = pRemoteCharacteristic->readValue();
Serial.print("Client: Initial characteristic value: ");
Serial.println(value.c_str());
}
// Register for notifications if available
if (pRemoteCharacteristic->canNotify()) {
pRemoteCharacteristic->registerForNotify(notifyCallback);
Serial.println("Client: Registered for notifications");
}
return true;
}
void setupServer() {
Serial.println("Setting up BLE Server...");
// Create server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// Create service
BLEService *pService = pServer->createService(SERVER_SERVICE_UUID);
// Create characteristic
pServerCharacteristic = pService->createCharacteristic(
SERVER_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
);
pServerCharacteristic->setCallbacks(new CharacteristicCallbacks());
pServerCharacteristic->setValue("Hello from Coexistence Server");
// Start service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVER_SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Server: Advertising started");
}
void setupClient() {
Serial.println("Setting up BLE Client...");
// Create scanner
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->setInterval(100);
pBLEScan->setWindow(99);
Serial.println("Client: Scanner configured");
}
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Client-Server Coexistence Example...");
// Initialize BLE device with a name
BLEDevice::init("ESP32-Coexistence");
// Setup both server and client
setupServer();
setupClient();
// Start initial scan
pBLEScan->start(10, false); // Scan for 10 seconds, don't repeat
Serial.println("Setup complete. Device is advertising as server and scanning as client.");
}
void loop() {
static unsigned long lastServerUpdate = 0;
static unsigned long lastClientWrite = 0;
static unsigned long lastScanStart = 0;
unsigned long currentTime = millis();
// Handle client connection attempts
if (doConnect && !clientConnected) {
if (connectToServer()) {
Serial.println("Client: Successfully connected to remote server");
} else {
Serial.println("Client: Failed to connect, will retry scanning");
// Restart scanning after failed connection
pBLEScan->start(10, false);
}
doConnect = false;
}
// Update server characteristic periodically
if (currentTime - lastServerUpdate > 5000) { // Every 5 seconds
String value = "Server time: " + String(millis() / 1000);
pServerCharacteristic->setValue(value.c_str());
pServerCharacteristic->notify(); // Notify connected clients
Serial.print("Server: Updated characteristic to: ");
Serial.println(value);
lastServerUpdate = currentTime;
}
// Write to remote characteristic if connected as client
if (clientConnected && pRemoteCharacteristic && currentTime - lastClientWrite > 3000) {
if (pRemoteCharacteristic->canWrite()) {
String clientValue = "Client msg: " + String(millis() / 1000);
pRemoteCharacteristic->writeValue(clientValue.c_str(), clientValue.length());
Serial.print("Client: Wrote to remote characteristic: ");
Serial.println(clientValue);
lastClientWrite = currentTime;
}
}
// Restart scanning periodically if not connected
if (!clientConnected && currentTime - lastScanStart > 15000) { // Every 15 seconds
Serial.println("Client: Restarting scan...");
pBLEScan->start(10, false);
lastScanStart = currentTime;
}
delay(100);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,276 @@
/**
* A BLE client example that connects to multiple BLE servers simultaneously.
*
* This example demonstrates how to:
* - Scan for multiple BLE servers
* - Connect to multiple servers at the same time
* - Interact with characteristics on different servers
* - Handle disconnections and reconnections
*
* The example looks for servers advertising the service UUID: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
* and connects to up to MAX_SERVERS servers.
*
* Created by lucasssvaz
* Based on the original Client example by Neil Kolban and chegewara
*/
#include "BLEDevice.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
// Maximum number of servers to connect to
#define MAX_SERVERS 3
// Structure to hold information about each connected server
struct ServerConnection {
BLEClient *pClient;
BLEAdvertisedDevice *pDevice;
BLERemoteCharacteristic *pRemoteCharacteristic;
bool connected;
bool doConnect;
String name;
};
// Array to manage multiple server connections
ServerConnection servers[MAX_SERVERS];
int connectedServers = 0;
static bool doScan = true;
// Callback function to handle notifications from any server
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
// Find which server this notification came from
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].connected && servers[i].pRemoteCharacteristic == pBLERemoteCharacteristic) {
Serial.print("Notify from server ");
Serial.print(servers[i].name);
Serial.print(" - Characteristic: ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" | Length: ");
Serial.print(length);
Serial.print(" | Data: ");
Serial.write(pData, length);
Serial.println();
break;
}
}
}
// Client callback class to handle connect/disconnect events
class MyClientCallback : public BLEClientCallbacks {
int serverIndex;
public:
MyClientCallback(int index) : serverIndex(index) {}
void onConnect(BLEClient *pclient) {
Serial.print("Connected to server ");
Serial.println(servers[serverIndex].name);
}
void onDisconnect(BLEClient *pclient) {
servers[serverIndex].connected = false;
connectedServers--;
Serial.print("Disconnected from server ");
Serial.print(servers[serverIndex].name);
Serial.print(" | Total connected: ");
Serial.println(connectedServers);
doScan = true; // Resume scanning to find replacement servers
}
};
// Function to connect to a specific server
bool connectToServer(int serverIndex) {
Serial.print("Connecting to server ");
Serial.print(serverIndex);
Serial.print(" at address: ");
Serial.println(servers[serverIndex].pDevice->getAddress().toString().c_str());
servers[serverIndex].pClient = BLEDevice::createClient();
Serial.println(" - Created client");
// Set the callback for this specific server connection
servers[serverIndex].pClient->setClientCallbacks(new MyClientCallback(serverIndex));
// Connect to the remote BLE Server
servers[serverIndex].pClient->connect(servers[serverIndex].pDevice);
Serial.println(" - Connected to server");
servers[serverIndex].pClient->setMTU(517); // Request maximum MTU from server
// Obtain a reference to the service we are after in the remote BLE server
BLERemoteService *pRemoteService = servers[serverIndex].pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find service UUID: ");
Serial.println(serviceUUID.toString().c_str());
servers[serverIndex].pClient->disconnect();
return false;
}
Serial.println(" - Found service");
// Obtain a reference to the characteristic in the service
servers[serverIndex].pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (servers[serverIndex].pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
servers[serverIndex].pClient->disconnect();
return false;
}
Serial.println(" - Found characteristic");
// Read the value of the characteristic
if (servers[serverIndex].pRemoteCharacteristic->canRead()) {
String value = servers[serverIndex].pRemoteCharacteristic->readValue();
Serial.print("Initial characteristic value: ");
Serial.println(value.c_str());
}
// Register for notifications if available
if (servers[serverIndex].pRemoteCharacteristic->canNotify()) {
servers[serverIndex].pRemoteCharacteristic->registerForNotify(notifyCallback);
Serial.println(" - Registered for notifications");
}
servers[serverIndex].connected = true;
connectedServers++;
Serial.print("Successfully connected! Total servers connected: ");
Serial.println(connectedServers);
return true;
}
// Scan callback class to find BLE servers
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// Check if this device has the service we're looking for
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
Serial.println(" -> This device has our service!");
// Check if we already know about this device
String deviceAddress = advertisedDevice.getAddress().toString().c_str();
bool alreadyKnown = false;
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].pDevice != nullptr) {
if (servers[i].pDevice->getAddress().toString() == deviceAddress) {
alreadyKnown = true;
break;
}
}
}
if (alreadyKnown) {
Serial.println(" -> Already connected or connecting to this device");
return;
}
// Find an empty slot for this server
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].pDevice == nullptr || (!servers[i].connected && !servers[i].doConnect)) {
servers[i].pDevice = new BLEAdvertisedDevice(advertisedDevice);
servers[i].doConnect = true;
servers[i].name = "Server_" + String(i);
Serial.print(" -> Assigned to slot ");
Serial.println(i);
// If we've found enough servers, stop scanning
int pendingConnections = 0;
for (int j = 0; j < MAX_SERVERS; j++) {
if (servers[j].connected || servers[j].doConnect) {
pendingConnections++;
}
}
if (pendingConnections >= MAX_SERVERS) {
Serial.println("Found enough servers, stopping scan");
BLEDevice::getScan()->stop();
doScan = false;
}
break;
}
}
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("=================================");
Serial.println("BLE Multi-Client Example");
Serial.println("=================================");
Serial.print("Max servers to connect: ");
Serial.println(MAX_SERVERS);
Serial.println();
// Initialize all server connections
for (int i = 0; i < MAX_SERVERS; i++) {
servers[i].pClient = nullptr;
servers[i].pDevice = nullptr;
servers[i].pRemoteCharacteristic = nullptr;
servers[i].connected = false;
servers[i].doConnect = false;
servers[i].name = "";
}
// Initialize BLE
BLEDevice::init("ESP32_MultiClient");
// Set up BLE scanner
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
Serial.println("Scanning for BLE servers...");
}
void loop() {
// Process any pending connections
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].doConnect) {
if (connectToServer(i)) {
Serial.println("Connection successful");
} else {
Serial.println("Connection failed");
// Clear this slot so we can try another server
delete servers[i].pDevice;
servers[i].pDevice = nullptr;
}
servers[i].doConnect = false;
}
}
// If we're connected to servers, send data to each one
if (connectedServers > 0) {
for (int i = 0; i < MAX_SERVERS; i++) {
if (servers[i].connected && servers[i].pRemoteCharacteristic != nullptr) {
// Create a unique message for each server
String newValue = servers[i].name + " | Time: " + String(millis() / 1000);
Serial.print("Sending to ");
Serial.print(servers[i].name);
Serial.print(": ");
Serial.println(newValue);
// Write the value to the characteristic
servers[i].pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
}
}
} else {
Serial.println("No servers connected");
}
// Resume scanning if we have room for more connections
if (doScan && connectedServers < MAX_SERVERS) {
Serial.println("Resuming scan for more servers...");
BLEDevice::getScan()->start(5, false);
doScan = false;
delay(5000); // Wait for scan to complete
}
delay(2000); // Delay between loop iterations
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,327 @@
/*
Secure client with static passkey and IRK retrieval
This example demonstrates how to create a secure BLE client that connects to
a secure BLE server using a static passkey without prompting the user.
The client will automatically use the same passkey (123456) as the server.
After successful bonding, the example demonstrates how to retrieve the
server's Identity Resolving Key (IRK) in multiple formats:
- Comma-separated hex format: 0x1A,0x1B,0x1C,...
- Base64 encoded (for Home Assistant Private BLE Device service)
- Reverse hex order (for Home Assistant ESPresense)
This client is designed to work with the Server_secure_static_passkey example.
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
This means that in NimBLE you can read the insecure characteristic without entering
the passkey. This is not possible in Bluedroid.
IMPORTANT:
- MITM (Man-In-The-Middle protection) must be enabled for password prompts to work.
- Bonding must be enabled to store and retrieve the IRK.
- The server must distribute its Identity Key during pairing.
Based on examples from Neil Kolban and h2zero.
Created by lucasssvaz.
*/
#include "BLEDevice.h"
#include "BLESecurity.h"
#include "nvs_flash.h"
// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristics of the remote service we are interested in.
static BLEUUID insecureCharUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8");
// This must match the server's passkey
#define CLIENT_PIN 123456
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLEClient *pClient = nullptr;
static BLERemoteCharacteristic *pRemoteInsecureCharacteristic;
static BLERemoteCharacteristic *pRemoteSecureCharacteristic;
static BLEAdvertisedDevice *myDevice;
// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
for (int i = 0; i < 16; i++) {
if (irk[i] < 0x10) {
Serial.print("0");
}
Serial.print(irk[i], HEX);
if (i < 15) {
Serial.print(":");
}
}
}
static void get_peer_irk(BLEAddress peerAddr) {
Serial.println("\n=== Retrieving peer IRK (Server) ===\n");
uint8_t irk[16];
// Get IRK in binary format
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
Serial.println("Successfully retrieved peer IRK in binary format:");
printIrkBinary(irk);
Serial.println("\n");
}
// Get IRK in different string formats
String irkString = BLEDevice::getPeerIRKString(peerAddr);
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
if (irkString.length() > 0) {
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
Serial.print("IRK (comma-separated hex): ");
Serial.println(irkString);
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
Serial.println(irkBase64);
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
Serial.println(irkReverse);
Serial.println();
} else {
Serial.println("!!! Failed to retrieve peer IRK !!!");
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
}
Serial.println("=======================================\n");
}
// Callback function to handle notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.write(pData, length);
Serial.println();
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient *pclient) {
Serial.println("Connected to secure server");
}
void onDisconnect(BLEClient *pclient) {
connected = false;
Serial.println("Disconnected from server");
}
};
// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc.bd_addr);
get_peer_irk(peerAddr);
}
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
get_peer_irk(peerAddr);
}
#endif
};
bool connectToServer() {
Serial.print("Forming a secure connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remote BLE Server.
pClient->connect(myDevice);
Serial.println(" - Connected to server");
// Set MTU to maximum for better performance
pClient->setMTU(517);
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the insecure characteristic
pRemoteInsecureCharacteristic = pRemoteService->getCharacteristic(insecureCharUUID);
if (pRemoteInsecureCharacteristic == nullptr) {
Serial.print("Failed to find insecure characteristic UUID: ");
Serial.println(insecureCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found insecure characteristic");
// Obtain a reference to the secure characteristic
pRemoteSecureCharacteristic = pRemoteService->getCharacteristic(secureCharUUID);
if (pRemoteSecureCharacteristic == nullptr) {
Serial.print("Failed to find secure characteristic UUID: ");
Serial.println(secureCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found secure characteristic");
// Read the value of the insecure characteristic (should work without authentication)
if (pRemoteInsecureCharacteristic->canRead()) {
String value = pRemoteInsecureCharacteristic->readValue();
Serial.print("Insecure characteristic value: ");
Serial.println(value.c_str());
}
// For Bluedroid, we need to set the authentication request type for the secure characteristic
// This is not needed for NimBLE and will be ignored.
pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_MITM);
// Try to read the secure characteristic (this will trigger security negotiation in NimBLE)
if (pRemoteSecureCharacteristic->canRead()) {
Serial.println("Attempting to read secure characteristic...");
String value = pRemoteSecureCharacteristic->readValue();
Serial.print("Secure characteristic value: ");
Serial.println(value.c_str());
}
// Register for notifications on both characteristics if they support it
if (pRemoteInsecureCharacteristic->canNotify()) {
pRemoteInsecureCharacteristic->registerForNotify(notifyCallback);
Serial.println(" - Registered for insecure characteristic notifications");
}
if (pRemoteSecureCharacteristic->canNotify()) {
pRemoteSecureCharacteristic->registerForNotify(notifyCallback);
Serial.println(" - Registered for secure characteristic notifications");
}
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
Serial.println("Found our secure server!");
BLEDevice::getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
doScan = true;
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting Secure BLE Client application...");
// Clear NVS to remove any cached pairing information
// This ensures fresh authentication for testing
Serial.println("Clearing NVS pairing data...");
nvs_flash_erase();
nvs_flash_init();
BLEDevice::init("Secure BLE Client");
// Set up security with the same passkey as the server
BLESecurity *pSecurity = new BLESecurity();
// Set security parameters
// Default parameters:
// - IO capability is set to NONE
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
// - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
// - Key size is set to 16 bytes
// Set the same static passkey as the server
// The first argument defines if the passkey is static or random.
// The second argument is the passkey (ignored when using a random passkey).
pSecurity->setPassKey(true, CLIENT_PIN);
// Set authentication mode to match server requirements
// Enable bonding, MITM (for password prompts), and secure connection for this example
// Bonding is required to store and retrieve the IRK
pSecurity->setAuthenticationMode(true, true, true);
// Set IO capability to KeyboardOnly
// We need the proper IO capability for MITM authentication even
// if the passkey is static and won't be entered by the user
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
pSecurity->setCapability(ESP_IO_CAP_IN);
// Set callbacks to handle authentication completion and print IRKs
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
}
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it.
if (doConnect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the secure BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, demonstrate secure communication
if (connected) {
// Write to the insecure characteristic
String insecureValue = "Client time: " + String(millis() / 1000);
if (pRemoteInsecureCharacteristic->canWrite()) {
pRemoteInsecureCharacteristic->writeValue(insecureValue.c_str(), insecureValue.length());
Serial.println("Wrote to insecure characteristic: " + insecureValue);
}
// Write to the secure characteristic
String secureValue = "Secure client time: " + String(millis() / 1000);
if (pRemoteSecureCharacteristic->canWrite()) {
pRemoteSecureCharacteristic->writeValue(secureValue.c_str(), secureValue.length());
Serial.println("Wrote to secure characteristic: " + secureValue);
}
} else if (doScan) {
// Restart scanning if we're disconnected
BLEDevice::getScan()->start(0);
}
delay(2000); // Delay 2 seconds between loops
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,92 @@
/*
EddystoneTLM beacon by BeeGee based on https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino
EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
*/
/*
Create a BLE server that will send periodic Eddystone URL frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
To read data advertised by this beacon use second ESP with example sketch BLE_Beacon_Scanner
*/
#include "sys/time.h"
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include "BLEAdvertising.h"
#include "BLEEddystoneTLM.h"
#include "esp_sleep.h"
#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up
#define BEACON_POWER ESP_PWR_LVL_N12
RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory
RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
BLEAdvertising *pAdvertising;
struct timeval nowTimeStruct;
time_t lastTenth;
#define BEACON_UUID "8ec76ea3-6668-48da-9866-75be8bc86f4d" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/)
// Check
// https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
// and http://www.hugi.scene.org/online/coding/hugi%2015%20-%20cmtadfix.htm
// for the temperature value. It is a 8.8 fixed-point notation
void setBeacon() {
BLEEddystoneTLM EddystoneTLM;
EddystoneTLM.setVolt((uint16_t)random(2800, 3700)); // 3300mV = 3.3V
EddystoneTLM.setTemp(random(-3000, 3000) / 100.0f); // 3000 = 30.00 ˚C
Serial.printf("Random Battery voltage is %d mV = 0x%04X\n", EddystoneTLM.getVolt(), EddystoneTLM.getVolt());
Serial.printf("Random temperature is %.2f°C\n", EddystoneTLM.getTemp());
Serial.printf("Converted to 8.8 format: 0x%04X\n", EddystoneTLM.getRawTemp());
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
oScanResponseData.setServiceData(BLEUUID((uint16_t)0xFEAA), String(EddystoneTLM.getData().c_str(), EddystoneTLM.getData().length()));
oAdvertisementData.setName("ESP32 TLM Beacon");
pAdvertising->setAdvertisementData(oAdvertisementData);
pAdvertising->setScanResponseData(oScanResponseData);
}
void setup() {
Serial.begin(115200);
gettimeofday(&nowTimeStruct, NULL);
Serial.printf("Starting ESP32. Bootcount = %lu\n", bootcount++);
Serial.printf("Deep sleep (%llds since last reset, %llds since last boot)\n", nowTimeStruct.tv_sec, nowTimeStruct.tv_sec - last);
last = nowTimeStruct.tv_sec;
lastTenth = nowTimeStruct.tv_sec * 10; // Time since last reset as 0.1 second resolution counter
// Create the BLE Device
BLEDevice::init("TLMBeacon");
BLEDevice::setPower(BEACON_POWER);
pAdvertising = BLEDevice::getAdvertising();
setBeacon();
// Start advertising
pAdvertising->start();
Serial.println("Advertising started for 10s ...");
delay(10000);
pAdvertising->stop();
Serial.printf("Enter deep sleep for 10s\n");
esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
}
void loop() {}
@@ -0,0 +1,14 @@
## Eddystone TLM beacon
EddystoneTLM beacon by BeeGee based on
[pcbreflux ESP32 Eddystone TLM deepsleep](https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino)
[EddystoneTLM frame specification](https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md)
Create a BLE server that will send periodic Eddystone TLM frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,105 @@
/*
EddystoneURL beacon by BeeGee
EddystoneURL frame specification https://github.com/google/eddystone/blob/master/eddystone-url/README.md
Upgraded on: Feb 20, 2023
By: Tomas Pilny
*/
/*
Create a BLE server that will send periodic Eddystone URL frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
*/
#include "sys/time.h"
#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include "BLEAdvertising.h"
#include "BLEEddystoneURL.h"
#include "esp_sleep.h"
char unprintable[] = {0x01, 0xFF, 0xDE, 0xAD};
String URL[] = {
"http://www.espressif.com/", // prefix 0x00, suffix 0x00
"https://www.texas.gov", // prefix 0x01, suffix 0x0D
"http://en.mapy.cz", // prefix 0x02, no valid suffix
"https://arduino.cc", // prefix 0x03, no valid suffix
"google.com", // URL without specified prefix - the function will assume default prefix "http://www." = 0x00
"diginfo.tv", // URL without specified prefix - the function will assume default prefix "http://www." = 0x00
// "http://www.URLsAbove17BytesAreNotAllowed.com", // Too long URL - setSmartURL() will return 0 = ERR
// "", // Empty string - setSmartURL() will return 0 = ERR
// String(unprintable), // Unprintable characters / corrupted String - setSmartURL() will return 0 = ERR
};
#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up
#define BEACON_POWER ESP_PWR_LVL_N12
RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory
RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
BLEAdvertising *pAdvertising;
struct timeval now;
int setBeacon() {
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
BLEEddystoneURL EddystoneURL;
EddystoneURL.setPower(BEACON_POWER); // This is only information about the power. The actual power is set by `BLEDevice::setPower(BEACON_POWER)`
if (EddystoneURL.setSmartURL(URL[bootcount % (sizeof(URL) / sizeof(URL[0]))])) {
String frame = EddystoneURL.getFrame();
String data(EddystoneURL.getFrame().c_str(), frame.length());
oAdvertisementData.addData(data);
oScanResponseData.setName("ESP32 URLBeacon");
pAdvertising->setAdvertisementData(oAdvertisementData);
pAdvertising->setScanResponseData(oScanResponseData);
Serial.printf("Advertise URL \"%s\"\n", URL[bootcount % (sizeof(URL) / sizeof(URL[0]))].c_str());
return 1; // OK
} else {
Serial.println("Smart URL set ERR");
return 0; // ERR
}
}
void setup() {
Serial.begin(115200);
gettimeofday(&now, NULL);
Serial.printf("Start ESP32 %lu\n", bootcount++);
Serial.printf("Deep sleep (%llds since last reset, %llds since last boot)\n", now.tv_sec, now.tv_sec - last);
last = now.tv_sec;
// Create the BLE Device
BLEDevice::init("URLBeacon");
BLEDevice::setPower(BEACON_POWER);
// Create the BLE Server
// BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage
pAdvertising = BLEDevice::getAdvertising();
if (setBeacon()) {
// Start advertising
pAdvertising->start();
Serial.println("Advertising started...");
delay(10000);
pAdvertising->stop();
}
Serial.println("Enter deep sleep");
bootcount++;
esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
}
void loop() {}
@@ -0,0 +1,14 @@
## Eddystone URL beacon
EddystoneURL beacon by BeeGee based on
[pcbreflux ESP32 Eddystone URL deepsleep](https://github.com/pcbreflux/espressif/tree/master/esp32/arduino/sketchbook/ESP32_Eddystone_URL_deepsleep)
[EddystoneURL frame specification](https://github.com/google/eddystone/blob/master/eddystone-url/README.md)
Create a BLE server that will send periodic Eddystone URL frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
6. deep sleep
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+112
View File
@@ -0,0 +1,112 @@
/*
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
Ported to Arduino ESP32 by Evandro Copercini
updated by chegewara
Create a BLE server that, once we receive a connection, will send periodic notifications.
The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8
The design of creating the BLE server is:
1. Create a BLE Server
2. Create a BLE Service
3. Create a BLE Characteristic on the Service
4. Create a BLE Descriptor on the characteristic
5. Start the service.
6. Start advertising.
A connect handler associated with the server starts a background task that performs notification
every couple of seconds.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLE2901.h>
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
BLE2901 *descriptor_2901 = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE
);
// Creates BLE Descriptor 0x2902: Client Characteristic Configuration Descriptor (CCCD)
// Descriptor 2902 is not required when using NimBLE as it is automatically added based on the characteristic properties
pCharacteristic->addDescriptor(new BLE2902());
// Adds also the Characteristic User Description - 0x2901 descriptor
descriptor_2901 = new BLE2901();
descriptor_2901->setDescription("My own description for this characteristic.");
descriptor_2901->setAccessPermissions(ESP_GATT_PERM_READ); // enforce read only - default is Read|Write
pCharacteristic->addDescriptor(descriptor_2901);
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
// notify changed value
if (deviceConnected) {
pCharacteristic->setValue((uint8_t *)&value, 4);
pCharacteristic->notify();
value++;
delay(500);
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+40
View File
@@ -0,0 +1,40 @@
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
int scanTime = 5; //In seconds
BLEScan *pBLEScan;
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
}
};
void setup() {
Serial.begin(115200);
Serial.println("Scanning...");
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
}
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults *foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices->getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+46
View File
@@ -0,0 +1,46 @@
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
Ported to Arduino ESP32 by Evandro Copercini
updates by chegewara
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
if (!BLEDevice::init("BLE Server Example")) {
Serial.println("BLE initialization failed!");
return;
}
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic =
pService->createCharacteristic(CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setValue("Hello World says Neil");
pService->start();
// BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,214 @@
/*
* BLE HID Gamepad Example
*
* This example demonstrates how to create a BLE HID Gamepad device using ESP32.
* The gamepad will appear as a standard HID game controller on Windows, macOS, Linux, Android, and iOS.
*
* Features:
* - 8 buttons (mapped to buttons 1-8)
* - 2 axes (X, Y for joystick movement)
* - Secure pairing with bonding
* - Battery level reporting
* - Automatic reconnection after power cycle
*
* Usage:
* 1. Upload this sketch to your ESP32
* 2. Pair with your device (Windows: Settings > Bluetooth & devices)
* 3. The gamepad will send test input (circular motion and button toggle)
* 4. Test in any game or with gamepad testing tools like https://hardwaretester.com/gamepad
*
* Note: This example uses "Just Works" pairing for automatic connection without
* PIN entry or confirmation, just like real gamepads. The bond is saved for future connections.
*
* Created by lucasssvaz
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEHIDDevice.h>
#include <BLESecurity.h>
// HID Report Descriptor for a gamepad with 8 buttons and 2 axes (X, Y)
// This descriptor defines the gamepad as having:
// - 8 buttons (usage buttons 1-8)
// - 2 8-bit axes (X, Y) with range -127 to 127
const uint8_t hidReportDescriptor[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x05, // Usage (Gamepad)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0, // End Collection
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button 1)
0x29, 0x08, // Usage Maximum (Button 8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0xC0 // End Collection
};
// Gamepad report structure (matches the HID descriptor)
struct GamepadReport {
uint8_t reportId; // Report ID (must be 1)
int8_t x; // X axis (-127 to 127)
int8_t y; // Y axis (-127 to 127)
uint8_t buttons; // 8 buttons (bit 0-7)
} __attribute__((packed));
BLEHIDDevice *hid;
BLECharacteristic *inputGamepad;
BLEServer *server;
bool deviceConnected = false;
// Server callbacks to track connection status
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("Client connected");
}
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("Client disconnected");
// Restart advertising so we can reconnect
BLEDevice::startAdvertising();
Serial.println("Advertising restarted");
}
};
// Security callbacks for "Just Works" pairing
class SecurityCallbacks : public BLESecurityCallbacks {
bool onSecurityRequest() {
return true; // Accept all pairing requests
}
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl) {
if (auth_cmpl.success) {
Serial.println("Pairing successful!");
} else {
Serial.printf("Pairing failed, status: %d\n", auth_cmpl.fail_reason);
}
}
#elif defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) {
if (desc->sec_state.encrypted) {
Serial.println("Pairing successful!");
} else {
Serial.println("Pairing failed");
}
}
#endif
// These are not used with "Just Works" pairing (ESP_IO_CAP_NONE)
uint32_t onPassKeyRequest() {
return 0;
}
void onPassKeyNotify(uint32_t pass_key) {}
bool onConfirmPIN(uint32_t pass_key) {
return true;
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE HID Gamepad");
// Initialize BLE
BLEDevice::init("ESP32-Gamepad");
// Configure BLE Security for pairing and bonding
// Use "Just Works" pairing - no user interaction required (like a real gamepad)
BLESecurity *pSecurity = new BLESecurity();
// Set IO capability to NONE (no display, no keyboard - like a real gamepad)
pSecurity->setCapability(ESP_IO_CAP_NONE);
// Set authentication mode: bonding=true, MITM=false, secure connection=true
// This enables "Just Works" pairing with bonding
pSecurity->setAuthenticationMode(true, false, true);
// Set security callbacks
BLEDevice::setSecurityCallbacks(new SecurityCallbacks());
// Create BLE Server
server = BLEDevice::createServer();
server->setCallbacks(new ServerCallbacks());
// Create HID Device
hid = new BLEHIDDevice(server);
// Set HID device information
hid->manufacturer()->setValue("Espressif");
hid->pnp(0x02, 0x05ac, 0x820a, 0x0110); // Vendor ID, Product ID, Product Version
hid->hidInfo(0x00, 0x01); // HID version, country code
// Set Report Map (HID descriptor)
hid->reportMap((uint8_t *)hidReportDescriptor, sizeof(hidReportDescriptor));
// Create input report characteristic for gamepad
inputGamepad = hid->inputReport(1); // Report ID 1
// Set battery level to 100%
hid->setBatteryLevel(100);
// Start HID services
hid->startServices();
// Setup advertising
BLEAdvertising *advertising = BLEDevice::getAdvertising();
advertising->setAppearance(0x03C4); // HID Gamepad appearance
advertising->addServiceUUID(hid->hidService()->getUUID());
advertising->setScanResponse(true);
advertising->setMinPreferred(0x06); // Help with connection issues
advertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE HID Gamepad ready!");
Serial.println("Waiting for connection...");
}
void loop() {
if (deviceConnected) {
static uint32_t lastReportTime = 0;
static uint32_t counter = 0;
// Send a report every 50ms (20Hz)
if (millis() - lastReportTime >= 50) {
lastReportTime = millis();
counter++;
GamepadReport report;
report.reportId = 1;
// Simulate some movement (sine wave pattern)
report.x = (int8_t)(127 * sin(counter * 0.1)); // X axis: -127 to 127
report.y = (int8_t)(127 * cos(counter * 0.1)); // Y axis: -127 to 127
report.buttons = (counter % 40 < 20) ? 0x01 : 0x00; // Toggle first button every second
// Send the report
inputGamepad->setValue((uint8_t *)&report, sizeof(report));
inputGamepad->notify();
// Print status every 2 seconds
if (counter % 40 == 0) {
Serial.printf("Report #%lu: X=%d, Y=%d, Buttons=0x%02X\n", counter, report.x, report.y, report.buttons);
}
}
}
delay(10);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,123 @@
/*
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
Ported to Arduino ESP32 by Evandro Copercini
updated by chegewara
Create a BLE server that, once we receive a connection, will send periodic notifications.
The server will continue advertising for more connections after the first one and will notify
the value of a counter to all connected clients.
The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8
The design of creating the BLE server is:
1. Create a BLE Server
2. Create a BLE Service
3. Create a BLE Characteristic on the Service
4. Create a BLE Descriptor on the characteristic
5. Start the service.
6. Start advertising.
A connect handler associated with the server starts a background task that performs notification
every couple of seconds.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
int connectedClients = 0;
bool deviceConnected = false;
uint32_t value = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
connectedClients++;
Serial.print("Client connected. Total clients: ");
Serial.println(connectedClients);
// Continue advertising for more connections
BLEDevice::startAdvertising();
};
void onDisconnect(BLEServer *pServer) {
connectedClients--;
Serial.print("Client disconnected. Total clients: ");
Serial.println(connectedClients);
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE
);
// Descriptor 2902 is not required when using NimBLE as it is automatically added based on the characteristic properties
pCharacteristic->addDescriptor(new BLE2902());
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting for client connections to notify...");
}
void loop() {
// Notify changed value to all connected clients
if (connectedClients > 0) {
Serial.print("Notifying value: ");
Serial.print(value);
Serial.print(" to ");
Serial.print(connectedClients);
Serial.println(" client(s)");
pCharacteristic->setValue((uint8_t *)&value, 4);
pCharacteristic->notify();
value++;
// Bluetooth stack will go into congestion, if too many packets are sent.
// In 6 hours of testing, I was able to go as low as 3ms.
// When using core debug level "debug" or "verbose", the delay can be increased in
// order to reduce the number of debug messages in the serial monitor.
delay(100);
}
// Disconnecting - restart advertising when no clients are connected
if (connectedClients == 0 && deviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("No clients connected, restarting advertising");
deviceConnected = false;
}
// Connecting - update state when first client connects
if (connectedClients > 0 && !deviceConnected) {
// do stuff here on first connecting
deviceConnected = true;
}
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
@@ -0,0 +1,160 @@
/*
Simple BLE Server Authorization Example
This example demonstrates how to create a BLE server with authorization
requirements. It shows the essential setup for:
- Authorization with static passkey
- Secure connection
- MITM (Man-In-The-Middle) protection
The server creates a single characteristic that requires authorization
to access. Clients must provide the correct passkey (123456) to read
or write to the characteristic.
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
Due to a bug in ESP-IDF's Bluedroid, this example will currently not work with ESP32.
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
"Just Works" pairing method (with encryption if secure connection is enabled).
Created by lucasssvaz.
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLESecurity.h>
#include <nvs_flash.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Example passkey - change this for production use
#define AUTH_PASSKEY 123456
static int s_readCount = 0;
static BLECharacteristic *s_pCharacteristic;
class MySecurityCallbacks : public BLESecurityCallbacks {
bool onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) {
Serial.println("Authorization request received");
if (isRead) {
s_readCount++;
// Keep value length <= (MTU - 1) to avoid a follow-up read request
uint16_t maxLen = BLEDevice::getServer()->getPeerMTU(connHandle) - 1;
String msg = "Authorized #" + String(s_readCount);
if (msg.length() > maxLen) {
msg = msg.substring(0, maxLen);
}
s_pCharacteristic->setValue(msg);
// Grant authorization to the first 3 reads
if (s_readCount <= 3) {
Serial.println("Authorization granted");
return true;
} else {
Serial.println("Authorization denied, read count exceeded");
Serial.println("Please reset the read counter to continue");
return false;
}
}
// Fallback to deny
Serial.println("Authorization denied");
return false;
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Authorization Example!");
// Initialize the BOOT pin for resetting the read count
pinMode(BOOT_PIN, INPUT_PULLUP);
// Clear NVS to remove any cached pairing information
// This ensures fresh authentication for testing
Serial.println("Clearing NVS pairing data...");
nvs_flash_erase();
nvs_flash_init();
Serial.print("Using BLE stack: ");
Serial.println(BLEDevice::getBLEStackString());
BLEDevice::init("BLE Auth Server");
// Set MTU to 517 to avoid a follow-up read request
BLEDevice::setMTU(517);
// Configure BLE Security
BLESecurity *pSecurity = new BLESecurity();
// Set static passkey for authentication
pSecurity->setPassKey(true, AUTH_PASSKEY);
// Set IO capability to DisplayOnly for MITM authentication
pSecurity->setCapability(ESP_IO_CAP_OUT);
// Enable authorization requirements:
// - bonding: true (for persistent storage of the keys)
// - MITM: true (enables Man-In-The-Middle protection for password prompts)
// - secure connection: true (enables secure connection for encryption)
pSecurity->setAuthenticationMode(true, true, true);
// Set the security callbacks
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
// Create BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->advertiseOnDisconnect(true);
// Create BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create characteristic with read and write properties
uint32_t properties = BLECharacteristic::PROPERTY_READ;
// For NimBLE: Add authentication properties
// These properties ensure the characteristic requires authorization
// (ignored by Bluedroid but harmless)
properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_AUTHOR;
s_pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, properties);
// For Bluedroid: Set access permissions that require encryption and MITM
// This ensures authorization is required (ignored by NimBLE)
s_pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_READ_AUTHORIZATION);
// Set initial value
s_pCharacteristic->setValue("Hello! You needed authorization to read this!");
// Start the service
pService->start();
// Configure and start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // helps with iPhone connections
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE Server is running!");
Serial.println("Authorization is required to access the characteristic.");
Serial.printf("Use passkey: %d when prompted\n", AUTH_PASSKEY);
}
void loop() {
// Reset the read count if the BOOT pin is pressed
if (digitalRead(BOOT_PIN) == LOW) {
s_readCount = 0;
Serial.println("Read count reset");
}
delay(100);
}
@@ -0,0 +1,7 @@
targets:
esp32: false
fqbn_append: PartitionScheme=huge_app
requires:
- CONFIG_SOC_BLE_SUPPORTED=y
@@ -0,0 +1,195 @@
/*
Secure server with static passkey
This example demonstrates how to create a secure BLE server with no
IO capability using a static passkey.
The server will accept connections from devices that have the same passkey set.
The example passkey is set to 123456.
The server will create a service and a secure and an insecure characteristic
to be used as example.
This server is designed to be used with the Client_secure_static_passkey example.
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
This means that in NimBLE you can read the insecure characteristic without entering
the passkey. This is not possible in Bluedroid.
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
"Just Works" pairing method (with encryption if secure connection is enabled).
Based on examples from Neil Kolban and h2zero.
Created by lucasssvaz.
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLESecurity.h>
#include <nvs_flash.h>
#include <string>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define INSECURE_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define SECURE_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8"
// This is an example passkey. You should use a different or random passkey.
#define SERVER_PIN 123456
// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
for (int i = 0; i < 16; i++) {
if (irk[i] < 0x10) {
Serial.print("0");
}
Serial.print(irk[i], HEX);
if (i < 15) {
Serial.print(":");
}
}
}
static void get_peer_irk(BLEAddress peerAddr) {
Serial.println("\n=== Retrieving peer IRK (Client) ===\n");
uint8_t irk[16];
// Get IRK in binary format
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
Serial.println("Successfully retrieved peer IRK in binary format:");
printIrkBinary(irk);
Serial.println("\n");
}
// Get IRK in different string formats
String irkString = BLEDevice::getPeerIRKString(peerAddr);
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);
if (irkString.length() > 0) {
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
Serial.print("IRK (comma-separated hex): ");
Serial.println(irkString);
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
Serial.println(irkBase64);
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
Serial.println(irkReverse);
Serial.println();
} else {
Serial.println("!!! Failed to retrieve peer IRK !!!");
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
}
Serial.println("=======================================\n");
}
// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc.bd_addr);
get_peer_irk(peerAddr);
}
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
get_peer_irk(peerAddr);
}
#endif
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
// Clear NVS to remove any cached pairing information
// This ensures fresh authentication for testing
Serial.println("Clearing NVS pairing data...");
nvs_flash_erase();
nvs_flash_init();
Serial.print("Using BLE stack: ");
Serial.println(BLEDevice::getBLEStackString());
BLEDevice::init("Secure BLE Server");
BLESecurity *pSecurity = new BLESecurity();
// Set security parameters
// Default parameters:
// - IO capability is set to NONE
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
// - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
// - Key size is set to 16 bytes
// Set static passkey
// The first argument defines if the passkey is static or random.
// The second argument is the passkey (ignored when using a random passkey).
pSecurity->setPassKey(true, SERVER_PIN);
// Set IO capability to DisplayOnly
// We need the proper IO capability for MITM authentication even
// if the passkey is static and won't be shown to the user
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
pSecurity->setCapability(ESP_IO_CAP_OUT);
// Set authentication mode
// Enable bonding, MITM (for password prompts), and secure connection for this example
pSecurity->setAuthenticationMode(true, true, true);
// Set callbacks to handle authentication completion and print IRKs
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
BLEServer *pServer = BLEDevice::createServer();
pServer->advertiseOnDisconnect(true);
BLEService *pService = pServer->createService(SERVICE_UUID);
uint32_t insecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE;
uint32_t secure_properties = insecure_properties;
// NimBLE uses properties to secure characteristics.
// These special permission properties are not supported by Bluedroid and will be ignored.
// This can be removed if only using Bluedroid (ESP32).
// Check the BLECharacteristic.h file for more information.
secure_properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_WRITE_AUTHEN;
BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties);
BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(INSECURE_CHARACTERISTIC_UUID, insecure_properties);
// Bluedroid uses permissions to secure characteristics.
// This is the same as using the properties above.
// NimBLE does not use permissions and will ignore these calls.
// This can be removed if only using NimBLE (any SoC except ESP32).
pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
pInsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
// Set value for secure characteristic
pSecureCharacteristic->setValue("Secure Hello World!");
// Set value for insecure characteristic
// When using NimBLE you will be able to read this characteristic without entering the passkey.
pInsecureCharacteristic->setValue("Insecure Hello World!");
pService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
delay(2000);
}
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+123
View File
@@ -0,0 +1,123 @@
/*
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
Ported to Arduino ESP32 by Evandro Copercini
Create a BLE server that, once we receive a connection, will send periodic notifications.
The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY"
The design of creating the BLE server is:
1. Create a BLE Server
2. Create a BLE Service
3. Create a BLE Characteristic on the Service
4. Create a BLE Descriptor on the characteristic
5. Start the service.
6. Start advertising.
In this example rxValue is the data received (only accessible inside that function).
And txValue is the data to be sent, in this example just a byte incremented every second.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("Device connected");
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("Device disconnected");
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("UART Service");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
// Descriptor 2902 is not required when using NimBLE as it is automatically added based on the characteristic properties
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
Serial.print("Notifying Value: ");
Serial.println(txValue);
pTxCharacteristic->setValue(&txValue, 1);
pTxCharacteristic->notify();
txValue++;
delay(1000); // Notifying every 1 second
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("Started advertising again...");
oldDeviceConnected = false;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = true;
}
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+62
View File
@@ -0,0 +1,62 @@
/*
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
Ported to Arduino ESP32 by Evandro Copercini
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++) {
Serial.print(value[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
Serial.println("1- Download and install an BLE scanner app in your phone");
Serial.println("2- Scan for BLE devices in the app");
Serial.println("3- Connect to MyESP32");
Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");
Serial.println("5- See the magic =)");
BLEDevice::init("MyESP32");
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic =
pService->createCharacteristic(CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+5
View File
@@ -0,0 +1,5 @@
fqbn_append: PartitionScheme=huge_app
requires_any:
- CONFIG_SOC_BLE_SUPPORTED=y
- CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
+133
View File
@@ -0,0 +1,133 @@
/*
Based on 31337Ghost's reference code from https://github.com/nkolban/esp32-snippets/issues/385#issuecomment-362535434
which is based on pcbreflux's Arduino ESP32 port of Neil Kolban's example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
*/
/*
Create a BLE server that will send periodic iBeacon frames.
The design of creating the BLE server is:
1. Create a BLE Server
2. Create advertising data
3. Start advertising.
4. wait
5. Stop advertising.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLEBeacon.h>
#define DEVICE_NAME "ESP32"
#define SERVICE_UUID "7A0247E7-8E88-409B-A959-AB5092DDB03E"
#define BEACON_UUID "2D7A9F0C-E0E8-4CC9-A71B-A21DB2D034A1"
#define BEACON_UUID_REV "A134D0B2-1DA2-1BA7-C94C-E8E00C9F7A2D"
#define CHARACTERISTIC_UUID "82258BAA-DF72-47E8-99BC-B73D7ECD08A5"
BLEServer *pServer;
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
uint8_t value = 0;
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("deviceConnected = true");
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("deviceConnected = false");
// Restart advertising to be visible and connectable again
BLEAdvertising *pAdvertising;
pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Serial.println("iBeacon advertising restarted");
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
Serial.println("*********");
}
}
};
void init_service() {
BLEAdvertising *pAdvertising;
pAdvertising = pServer->getAdvertising();
pAdvertising->stop();
// Create the BLE Service
BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID));
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->addDescriptor(new BLE2902());
pAdvertising->addServiceUUID(BLEUUID(SERVICE_UUID));
// Start the service
pService->start();
pAdvertising->start();
}
void init_beacon() {
BLEAdvertising *pAdvertising;
pAdvertising = pServer->getAdvertising();
pAdvertising->stop();
// iBeacon
BLEBeacon myBeacon;
myBeacon.setManufacturerId(0x4c00);
myBeacon.setMajor(5);
myBeacon.setMinor(88);
myBeacon.setSignalPower(0xc5);
myBeacon.setProximityUUID(BLEUUID(BEACON_UUID_REV));
BLEAdvertisementData advertisementData;
advertisementData.setFlags(0x1A);
advertisementData.setManufacturerData(myBeacon.getData());
pAdvertising->setAdvertisementData(advertisementData);
pAdvertising->start();
}
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Initializing...");
Serial.flush();
BLEDevice::init(DEVICE_NAME);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
init_service();
init_beacon();
Serial.println("iBeacon + service defined and advertising!");
}
void loop() {
if (deviceConnected) {
Serial.printf("*** NOTIFY: %d ***\n", value);
pCharacteristic->setValue(&value, 1);
pCharacteristic->notify();
value++;
}
delay(2000);
}
+10
View File
@@ -0,0 +1,10 @@
name=BLE
version=3.3.7
author=Neil Kolban <kolban1@kolban.com>
maintainer=lucasssvaz
sentence=BLE functions for ESP32
paragraph=This library provides an implementation Bluetooth Low Energy support for the ESP32 using the Arduino platform.
category=Communication
url=https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE
architectures=esp32
includes=BLEDevice.h, BLEUtils.h, BLEScan.h, BLEAdvertisedDevice.h
+58
View File
@@ -0,0 +1,58 @@
/*
BLE2901.h
GATT Descriptor 0x2901 Characteristic User Description
The value of this description is a user-readable string
describing the characteristic.
The Characteristic User Description descriptor
provides a textual user description for a characteristic
value.
If the Writable Auxiliary bit of the Characteristics
Properties is set then this descriptor is written. Only one
User Description descriptor exists in a characteristic
definition.
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes and definitions *
***************************************************************************/
#include "BLE2901.h"
#define BLE2901_UUID 0x2901
/***************************************************************************
* NimBLE includes and definitions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <host/ble_att.h>
#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN
#endif
/***************************************************************************
* Common functions *
***************************************************************************/
BLE2901::BLE2901() : BLEDescriptor(BLEUUID((uint16_t)BLE2901_UUID)) {}
/**
* @brief Set the Characteristic User Description
*/
void BLE2901::setDescription(const String &userDesc) {
if (userDesc.length() > ESP_GATT_MAX_ATTR_LEN) {
log_e("Size %d too large, must be no bigger than %d", userDesc.length(), ESP_GATT_MAX_ATTR_LEN);
return;
}
setValue(userDesc);
}
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+48
View File
@@ -0,0 +1,48 @@
/*
BLE2901.h
GATT Descriptor 0x2901 Characteristic User Description
The value of this description is a user-readable string
describing the characteristic.
The Characteristic User Description descriptor
provides a textual user description for a characteristic
value.
If the Writable Auxiliary bit of the Characteristics
Properties is set then this descriptor is written. Only one
User Description descriptor exists in a characteristic
definition.
*/
#ifndef COMPONENTS_CPP_UTILS_BLE2901_H_
#define COMPONENTS_CPP_UTILS_BLE2901_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "BLEDescriptor.h"
/**
* @brief GATT Descriptor 0x2901 Characteristic User Description
*/
class BLE2901 : public BLEDescriptor {
public:
/***************************************************************************
* Common public functions *
***************************************************************************/
BLE2901();
void setDescription(const String &desc);
}; // BLE2901
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLE2901_H_ */
+252
View File
@@ -0,0 +1,252 @@
/*
* BLE2902.cpp
*
* Created on: Jun 25, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes and definitions *
***************************************************************************/
#include "BLE2902.h"
#include "esp32-hal-log.h"
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <Preferences.h>
#endif
#define BLE2902_UUID 0x2902
#define BLE_CCCD_NVS_NS "ble_cccd"
#define BLE_CCCD_KEY_PREFIX "c"
/***************************************************************************
* Common functions *
***************************************************************************/
BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t)BLE2902_UUID)) {
#if defined(CONFIG_BLUEDROID_ENABLED)
uint8_t data[2] = {0, 0};
setValue(data, 2);
#endif
}
/**
* @brief Get the notifications value.
* @return The notifications value. True if notifications are enabled and false if not.
*/
bool BLE2902::getNotifications() {
#if defined(CONFIG_BLUEDROID_ENABLED)
return (getValue()[0] & (1 << 0)) != 0;
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
if (m_pCharacteristic != nullptr) {
return (m_pCharacteristic->getProperties() & BLECharacteristic::PROPERTY_NOTIFY) != 0;
} else {
log_w("BLE2902::getNotifications() called on an uninitialized descriptor");
return false;
}
#endif
}
/**
* @brief Get the indications value.
* @return The indications value. True if indications are enabled and false if not.
*/
bool BLE2902::getIndications() {
#if defined(CONFIG_BLUEDROID_ENABLED)
return (getValue()[0] & (1 << 1)) != 0;
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
if (m_pCharacteristic != nullptr) {
return (m_pCharacteristic->getProperties() & BLECharacteristic::PROPERTY_INDICATE) != 0;
} else {
log_w("BLE2902::getIndications() called on an uninitialized descriptor");
return false;
}
#endif
}
/**
* @brief Set the indications flag.
* @param [in] flag The indications flag.
*/
void BLE2902::setIndications(bool flag) {
#if defined(CONFIG_BLUEDROID_ENABLED)
uint8_t *pValue = getValue();
if (flag) {
pValue[0] |= 1 << 1;
} else {
pValue[0] &= ~(1 << 1);
}
setValue(pValue, 2);
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
if (m_pCharacteristic != nullptr) {
m_pCharacteristic->setIndicateProperty(flag);
} else {
log_w("BLE2902::setIndications() called on an uninitialized descriptor");
}
#endif
}
/**
* @brief Set the notifications flag.
* @param [in] flag The notifications flag.
*/
void BLE2902::setNotifications(bool flag) {
#if defined(CONFIG_BLUEDROID_ENABLED)
uint8_t *pValue = getValue();
if (flag) {
pValue[0] |= 1 << 0;
} else {
pValue[0] &= ~(1 << 0);
}
setValue(pValue, 2);
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
if (m_pCharacteristic != nullptr) {
m_pCharacteristic->setNotifyProperty(flag);
} else {
log_w("BLE2902::setNotifications() called on an uninitialized descriptor");
}
#endif
}
/***************************************************************************
* Bluedroid CCCD persistence *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
/**
* @brief Generate NVS key for CCCD persistence.
* Format: "c" + last 4 chars of MAC (no colons) + "_" + handle in hex
* Example: "c5AC2_002a" for address ending in :5A:C2 and handle 0x002a
* Max length: 1 (prefix) + 4 (addr) + 1 (_) + 4 (handle) + 1 (null) = 11 chars
*/
String BLE2902::getNvsKey(const BLEAddress &peerAddress, uint16_t charHandle) {
String addrStr = peerAddress.toString();
// Get last 4 characters of address (without colons) - e.g., "5AC2" from "XX:XX:XX:XX:5A:C2"
String shortAddr = addrStr.substring(addrStr.length() - 5);
shortAddr.replace(":", "");
char key[24]; // Sized generously to avoid truncation warnings
snprintf(key, sizeof(key), "%s%s_%04x", BLE_CCCD_KEY_PREFIX, shortAddr.c_str(), charHandle);
return String(key);
}
/**
* @brief Persist CCCD value to NVS for a bonded device.
*/
bool BLE2902::persistValue(const BLEAddress &peerAddress, uint16_t charHandle) {
Preferences prefs;
if (!prefs.begin(BLE_CCCD_NVS_NS, false)) {
log_e("Failed to open NVS namespace for CCCD persistence");
return false;
}
String key = getNvsKey(peerAddress, charHandle);
uint8_t *pValue = getValue();
uint16_t cccdValue = pValue[0] | (pValue[1] << 8);
size_t written = prefs.putUShort(key.c_str(), cccdValue);
prefs.end();
if (written == 0) {
log_e("Failed to persist CCCD value to NVS");
return false;
}
log_i("Persisted CCCD value 0x%04x for peer %s, handle 0x%04x (key: %s)", cccdValue, peerAddress.toString().c_str(), charHandle, key.c_str());
return true;
}
/**
* @brief Restore CCCD value from NVS for a bonded device.
*/
bool BLE2902::restoreValue(const BLEAddress &peerAddress, uint16_t charHandle) {
Preferences prefs;
if (!prefs.begin(BLE_CCCD_NVS_NS, true)) { // Read-only
log_d("No CCCD persistence namespace found (first boot?)");
return false;
}
String key = getNvsKey(peerAddress, charHandle);
if (!prefs.isKey(key.c_str())) {
prefs.end();
log_d("No persisted CCCD value for peer %s, handle 0x%04x", peerAddress.toString().c_str(), charHandle);
return false;
}
uint16_t cccdValue = prefs.getUShort(key.c_str(), 0);
prefs.end();
// Set the value in the descriptor
uint8_t data[2] = {(uint8_t)(cccdValue & 0xFF), (uint8_t)((cccdValue >> 8) & 0xFF)};
setValue(data, 2);
log_i("Restored CCCD value 0x%04x for peer %s, handle 0x%04x (key: %s)", cccdValue, peerAddress.toString().c_str(), charHandle, key.c_str());
return true;
}
/**
* @brief Delete persisted CCCD value from NVS for a device.
*/
bool BLE2902::deletePersistedValue(const BLEAddress &peerAddress, uint16_t charHandle) {
Preferences prefs;
if (!prefs.begin(BLE_CCCD_NVS_NS, false)) {
return false;
}
String key = getNvsKey(peerAddress, charHandle);
bool result = prefs.remove(key.c_str());
prefs.end();
if (result) {
log_i("Deleted persisted CCCD value for peer %s, handle 0x%04x", peerAddress.toString().c_str(), charHandle);
}
return result;
}
/**
* @brief Delete all persisted CCCD values from NVS.
*/
bool BLE2902::deleteAllPersistedValues() {
Preferences prefs;
if (!prefs.begin(BLE_CCCD_NVS_NS, false)) {
return false;
}
bool result = prefs.clear();
prefs.end();
if (result) {
log_i("Deleted all persisted CCCD values");
}
return result;
}
#endif /* CONFIG_BLUEDROID_ENABLED */
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+106
View File
@@ -0,0 +1,106 @@
/*
* BLE2902.h
*
* Created on: Jun 25, 2017
* Author: kolban
*
* Modified on: Feb 28, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLE2902_H_
#define COMPONENTS_CPP_UTILS_BLE2902_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "BLEDescriptor.h"
#if defined(CONFIG_BLUEDROID_ENABLED)
#include "BLEAddress.h"
#endif
/**
* @brief Descriptor for Client Characteristic Configuration.
*
* This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
// Class declaration for Bluedroid
#if defined(CONFIG_BLUEDROID_ENABLED)
class BLE2902 : public BLEDescriptor {
#endif
// Class declaration for NimBLE (deprecated)
#if defined(CONFIG_NIMBLE_ENABLED)
class [[deprecated("NimBLE does not support manually adding 2902 descriptors as they \
are automatically added when the characteristic has notifications or indications enabled. \
Get/Set the notifications/indications properties of the characteristic instead. \
This class will be removed in a future version.")]] BLE2902 : public BLEDescriptor {
#endif
public:
/***************************************************************************
* Common public functions *
***************************************************************************/
BLE2902();
bool getNotifications();
bool getIndications();
void setNotifications(bool flag);
void setIndications(bool flag);
#if defined(CONFIG_BLUEDROID_ENABLED)
/***************************************************************************
* Bluedroid CCCD persistence *
***************************************************************************/
/**
* @brief Persist CCCD value to NVS for a bonded device.
* @param [in] peerAddress The address of the bonded peer device.
* @param [in] charHandle The handle of the characteristic this CCCD belongs to.
* @return true if the value was successfully persisted, false otherwise.
*/
bool persistValue(const BLEAddress &peerAddress, uint16_t charHandle);
/**
* @brief Restore CCCD value from NVS for a bonded device.
* @param [in] peerAddress The address of the bonded peer device.
* @param [in] charHandle The handle of the characteristic this CCCD belongs to.
* @return true if the value was successfully restored, false otherwise.
*/
bool restoreValue(const BLEAddress &peerAddress, uint16_t charHandle);
/**
* @brief Delete persisted CCCD value from NVS for a device.
* @param [in] peerAddress The address of the peer device.
* @param [in] charHandle The handle of the characteristic this CCCD belongs to.
* @return true if the value was successfully deleted, false otherwise.
*/
static bool deletePersistedValue(const BLEAddress &peerAddress, uint16_t charHandle);
/**
* @brief Delete all persisted CCCD values from NVS.
* @return true if all values were successfully deleted, false otherwise.
*/
static bool deleteAllPersistedValues();
private:
static String getNvsKey(const BLEAddress &peerAddress, uint16_t charHandle);
#endif
}; // BLE2902
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLE2902_H_ */
+88
View File
@@ -0,0 +1,88 @@
/*
* BLE2904.cpp
*
* Created on: Dec 23, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes and definitions *
***************************************************************************/
#include "BLE2904.h"
#define BLE2904_UUID 0x2904
#define BLE2904_DEFAULT_NAMESPACE 1 // 1 = Bluetooth SIG Assigned Numbers
#define BLE2904_DEFAULT_UNIT 0x2700 // 0x2700 = Unitless
/***************************************************************************
* Common functions *
***************************************************************************/
BLE2904::BLE2904() : BLEDescriptor(BLEUUID((uint16_t)BLE2904_UUID)) {
m_data.m_format = 0;
m_data.m_exponent = 0;
m_data.m_namespace = BLE2904_DEFAULT_NAMESPACE;
m_data.m_unit = BLE2904_DEFAULT_UNIT;
m_data.m_description = 0;
setValue((uint8_t *)&m_data, sizeof(m_data));
}
/**
* @brief Set the description.
*/
void BLE2904::setDescription(uint16_t description) {
m_data.m_description = description;
setValue((uint8_t *)&m_data, sizeof(m_data));
}
/**
* @brief Set the exponent.
*/
void BLE2904::setExponent(int8_t exponent) {
m_data.m_exponent = exponent;
setValue((uint8_t *)&m_data, sizeof(m_data));
}
/**
* @brief Set the format.
*/
void BLE2904::setFormat(uint8_t format) {
m_data.m_format = format;
setValue((uint8_t *)&m_data, sizeof(m_data));
}
/**
* @brief Set the namespace.
*/
void BLE2904::setNamespace(uint8_t namespace_value) {
m_data.m_namespace = namespace_value;
setValue((uint8_t *)&m_data, sizeof(m_data));
}
/**
* @brief Set the units for this value. It should be one of the encoded values defined here:
* https://www.bluetooth.com/specifications/assigned-numbers/units
* @param [in] unit The type of units of this characteristic as defined by assigned numbers.
*/
void BLE2904::setUnit(uint16_t unit) {
m_data.m_unit = unit;
setValue((uint8_t *)&m_data, sizeof(m_data));
}
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+107
View File
@@ -0,0 +1,107 @@
/*
* BLE2904.h
*
* Created on: Dec 23, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLE2904_H_
#define COMPONENTS_CPP_UTILS_BLE2904_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "BLEDescriptor.h"
/***************************************************************************
* Common types *
***************************************************************************/
struct BLE2904_Data {
uint8_t m_format;
int8_t m_exponent;
uint16_t m_unit; // See https://www.bluetooth.com/specifications/assigned-numbers/units
uint8_t m_namespace;
uint16_t m_description;
} __attribute__((packed));
/***************************************************************************
* Common functions *
***************************************************************************/
/**
* @brief Descriptor for Characteristic Presentation Format.
*
* This is a convenience descriptor for the Characteristic Presentation Format which has a UUID of 0x2904.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
*/
class BLE2904 : public BLEDescriptor {
public:
/***************************************************************************
* Common public constants *
***************************************************************************/
static const uint8_t FORMAT_BOOLEAN = 1;
static const uint8_t FORMAT_UINT2 = 2;
static const uint8_t FORMAT_UINT4 = 3;
static const uint8_t FORMAT_UINT8 = 4;
static const uint8_t FORMAT_UINT12 = 5;
static const uint8_t FORMAT_UINT16 = 6;
static const uint8_t FORMAT_UINT24 = 7;
static const uint8_t FORMAT_UINT32 = 8;
static const uint8_t FORMAT_UINT48 = 9;
static const uint8_t FORMAT_UINT64 = 10;
static const uint8_t FORMAT_UINT128 = 11;
static const uint8_t FORMAT_SINT8 = 12;
static const uint8_t FORMAT_SINT12 = 13;
static const uint8_t FORMAT_SINT16 = 14;
static const uint8_t FORMAT_SINT24 = 15;
static const uint8_t FORMAT_SINT32 = 16;
static const uint8_t FORMAT_SINT48 = 17;
static const uint8_t FORMAT_SINT64 = 18;
static const uint8_t FORMAT_SINT128 = 19;
static const uint8_t FORMAT_FLOAT32 = 20;
static const uint8_t FORMAT_FLOAT64 = 21;
static const uint8_t FORMAT_SFLOAT16 = 22;
static const uint8_t FORMAT_SFLOAT32 = 23;
static const uint8_t FORMAT_IEEE20601 = 24;
static const uint8_t FORMAT_UTF8 = 25;
static const uint8_t FORMAT_UTF16 = 26;
static const uint8_t FORMAT_OPAQUE = 27;
static const uint8_t FORMAT_MEDASN1 = 28;
/***************************************************************************
* Common public functions *
***************************************************************************/
BLE2904();
void setDescription(uint16_t);
void setExponent(int8_t exponent);
void setFormat(uint8_t format);
void setNamespace(uint8_t namespace_value);
void setUnit(uint16_t unit);
private:
/***************************************************************************
* Common private properties *
***************************************************************************/
BLE2904_Data m_data;
}; // BLE2904
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLE2904_H_ */
+235
View File
@@ -0,0 +1,235 @@
/*
* BLEAddress.cpp
*
* Created on: Jul 2, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "BLEAddress.h"
#include <string>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/***************************************************************************
* NimBLE includes *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
/*************************************************
* NOTE: NimBLE address bytes are in INVERSE ORDER!
* We will accommodate that fact in these methods.
*************************************************/
#include <algorithm>
#endif
/***************************************************************************
* Common functions *
***************************************************************************/
BLEAddress::BLEAddress() {
memset(m_address, 0, ESP_BD_ADDR_LEN);
m_addrType = 0;
}
/**
* @brief Determine if this address equals another.
* @param [in] otherAddress The other address to compare against.
* @return True if the addresses are equal.
*/
bool BLEAddress::equals(const BLEAddress &otherAddress) const {
return *this == otherAddress;
}
bool BLEAddress::operator==(const BLEAddress &otherAddress) const {
if (m_addrType != otherAddress.m_addrType) {
return false;
}
return memcmp(otherAddress.m_address, m_address, ESP_BD_ADDR_LEN) == 0;
}
bool BLEAddress::operator!=(const BLEAddress &otherAddress) const {
return !(*this == otherAddress);
}
bool BLEAddress::operator<(const BLEAddress &otherAddress) const {
return memcmp(m_address, otherAddress.m_address, ESP_BD_ADDR_LEN) < 0;
}
bool BLEAddress::operator<=(const BLEAddress &otherAddress) const {
return !(*this > otherAddress);
}
bool BLEAddress::operator>=(const BLEAddress &otherAddress) const {
return !(*this < otherAddress);
}
bool BLEAddress::operator>(const BLEAddress &otherAddress) const {
return memcmp(m_address, otherAddress.m_address, ESP_BD_ADDR_LEN) > 0;
}
/**
* @brief Return the native representation of the address.
* @return The native representation of the address.
*/
uint8_t *BLEAddress::getNative() {
return m_address;
}
/**
* @brief Return the address type.
* @return The address type.
*/
uint8_t BLEAddress::getType() const {
return m_addrType;
}
/**
* @brief Set the address type.
* @param [in] type The address type.
*/
void BLEAddress::setType(uint8_t type) {
m_addrType = type;
}
/**
* @brief Convert a BLE address to a string.
*
* A string representation of an address is in the format:
*
* ```
* xx:xx:xx:xx:xx:xx
* ```
*
* @return The string representation of the address.
*/
String BLEAddress::toString() const {
constexpr size_t size = 18;
char res[size];
#if defined(CONFIG_BLUEDROID_ENABLED)
snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[0], m_address[1], m_address[2], m_address[3], m_address[4], m_address[5]);
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[5], m_address[4], m_address[3], m_address[2], m_address[1], m_address[0]);
#endif
String ret(res);
return ret;
}
/***************************************************************************
* Bluedroid functions *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
/**
* @brief Create an address from the native ESP32 representation.
* @param [in] address The native representation.
* @param [in] type The address type.
*/
BLEAddress::BLEAddress(esp_bd_addr_t address, uint8_t type) {
memcpy(m_address, address, ESP_BD_ADDR_LEN);
m_addrType = type;
}
/**
* @brief Create an address from a hex string
*
* A hex string is of the format:
* ```
* 00:00:00:00:00:00
* ```
* which is 17 characters in length.
*
* @param [in] stringAddress The hex representation of the address.
* @param [in] type The address type.
*/
BLEAddress::BLEAddress(const String &stringAddress, uint8_t type) {
if (stringAddress.length() != 17) {
return;
}
int data[6];
m_addrType = type;
sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]);
for (size_t index = 0; index < sizeof(m_address); index++) {
m_address[index] = (uint8_t)data[index];
}
}
#endif
/***************************************************************************
* NimBLE functions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
/*************************************************
* NOTE: NimBLE address bytes are in INVERSE ORDER!
* We will accommodate that fact in these methods.
*************************************************/
BLEAddress::BLEAddress(uint8_t address[ESP_BD_ADDR_LEN], uint8_t type) {
std::reverse_copy(address, address + sizeof(m_address), m_address);
m_addrType = type;
}
BLEAddress::BLEAddress(ble_addr_t address) {
memcpy(m_address, address.val, ESP_BD_ADDR_LEN);
m_addrType = address.type;
}
/**
* @brief Create an address from a hex string
*
* A hex string is of the format:
* ```
* 00:00:00:00:00:00
* ```
* which is 17 characters in length.
*
* @param [in] stringAddress The hex representation of the address.
* @param [in] type The address type.
*/
BLEAddress::BLEAddress(const String &stringAddress, uint8_t type) {
if (stringAddress.length() != 17) {
return;
}
int data[6];
m_addrType = type;
// NimBLE addresses are in INVERSE ORDER!
sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[5], &data[4], &data[3], &data[2], &data[1], &data[0]);
for (size_t index = 0; index < sizeof(m_address); index++) {
m_address[index] = (uint8_t)data[index];
}
}
#endif
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+110
View File
@@ -0,0 +1,110 @@
/*
* BLEAddress.h
*
* Created on: Jul 2, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLEADDRESS_H_
#define COMPONENTS_CPP_UTILS_BLEADDRESS_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "WString.h"
#if SOC_BLE_SUPPORTED
#include <esp_bt.h>
#endif
#include <string>
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gap_ble_api.h>
#endif
/***************************************************************************
* NimBLE includes and definitions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <nimble/ble.h>
#define ESP_BD_ADDR_LEN BLE_DEV_ADDR_LEN
#endif
/***************************************************************************
* NimBLE types *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
typedef uint8_t esp_bd_addr_t[ESP_BD_ADDR_LEN];
#endif
/**
* @brief A %BLE device address.
*
* Every %BLE device has a unique address which can be used to identify it and form connections.
*/
class BLEAddress {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLEAddress();
bool equals(const BLEAddress &otherAddress) const;
bool operator==(const BLEAddress &otherAddress) const;
bool operator!=(const BLEAddress &otherAddress) const;
bool operator<(const BLEAddress &otherAddress) const;
bool operator<=(const BLEAddress &otherAddress) const;
bool operator>(const BLEAddress &otherAddress) const;
bool operator>=(const BLEAddress &otherAddress) const;
uint8_t *getNative();
uint8_t getType() const;
void setType(uint8_t type);
String toString() const;
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
BLEAddress(esp_bd_addr_t address, uint8_t type = 0);
BLEAddress(const String &stringAddress, uint8_t type = 0);
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
BLEAddress(ble_addr_t address);
BLEAddress(const String &stringAddress, uint8_t type = BLE_ADDR_PUBLIC);
BLEAddress(uint8_t address[ESP_BD_ADDR_LEN], uint8_t type = BLE_ADDR_PUBLIC);
#endif
private:
/***************************************************************************
* Common private properties *
***************************************************************************/
uint8_t m_address[ESP_BD_ADDR_LEN];
uint8_t m_addrType;
};
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLEADDRESS_H_ */
+805
View File
@@ -0,0 +1,805 @@
/*
* BLEAdvertisedDevice.cpp
*
* During the scanning procedure, we will be finding advertised BLE devices. This class
* models a found device.
*
* See also:
* https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
*
* Created on: Jul 3, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <sstream>
#include "BLEAdvertisedDevice.h"
#include "BLEUtils.h"
#include "esp32-hal-log.h"
/***************************************************************************
* Common functions *
***************************************************************************/
BLEAdvertisedDevice::BLEAdvertisedDevice() {
m_adFlag = 0;
m_appearance = 0;
m_deviceType = 0;
m_manufacturerData = "";
m_name = "";
m_rssi = -9999;
m_serviceUUIDs = {};
m_serviceData = {};
m_serviceDataUUIDs = {};
m_txPower = 0;
m_pScan = nullptr;
m_advType = 0;
m_payload = nullptr;
m_payloadLength = 0;
#if defined(CONFIG_NIMBLE_ENABLED)
m_callbackSent = false;
#endif
m_haveAppearance = false;
m_haveManufacturerData = false;
m_haveName = false;
m_haveRSSI = false;
m_haveTXPower = false;
m_isLegacyAdv = true;
} // BLEAdvertisedDevice
BLEAdvertisedDevice::~BLEAdvertisedDevice() {
if (m_payload != nullptr) {
free(m_payload);
m_payload = nullptr;
m_payloadLength = 0;
}
} // ~BLEAdvertisedDevice
BLEAdvertisedDevice::BLEAdvertisedDevice(const BLEAdvertisedDevice &other) {
m_adFlag = other.m_adFlag;
m_appearance = other.m_appearance;
m_deviceType = other.m_deviceType;
m_manufacturerData = other.m_manufacturerData;
m_name = other.m_name;
m_rssi = other.m_rssi;
m_serviceUUIDs = other.m_serviceUUIDs;
m_serviceData = other.m_serviceData;
m_serviceDataUUIDs = other.m_serviceDataUUIDs;
m_txPower = other.m_txPower;
m_pScan = other.m_pScan;
m_advType = other.m_advType;
m_address = other.m_address;
#if defined(CONFIG_NIMBLE_ENABLED)
m_callbackSent = other.m_callbackSent;
#endif
m_haveAppearance = other.m_haveAppearance;
m_haveManufacturerData = other.m_haveManufacturerData;
m_haveName = other.m_haveName;
m_haveRSSI = other.m_haveRSSI;
m_haveTXPower = other.m_haveTXPower;
m_isLegacyAdv = other.m_isLegacyAdv;
// Deep copy the payload
m_payloadLength = other.m_payloadLength;
if (other.m_payload != nullptr && other.m_payloadLength > 0) {
m_payload = (uint8_t *)malloc(m_payloadLength);
if (m_payload != nullptr) {
memcpy(m_payload, other.m_payload, m_payloadLength);
} else {
log_e("Failed to allocate %zu bytes for payload in copy constructor", m_payloadLength);
m_payloadLength = 0;
}
} else {
m_payload = nullptr;
m_payloadLength = 0;
}
} // BLEAdvertisedDevice copy constructor
BLEAdvertisedDevice &BLEAdvertisedDevice::operator=(const BLEAdvertisedDevice &other) {
if (this == &other) {
return *this;
}
m_adFlag = other.m_adFlag;
m_appearance = other.m_appearance;
m_deviceType = other.m_deviceType;
m_manufacturerData = other.m_manufacturerData;
m_name = other.m_name;
m_rssi = other.m_rssi;
m_serviceUUIDs = other.m_serviceUUIDs;
m_serviceData = other.m_serviceData;
m_serviceDataUUIDs = other.m_serviceDataUUIDs;
m_txPower = other.m_txPower;
m_pScan = other.m_pScan;
m_advType = other.m_advType;
m_address = other.m_address;
#if defined(CONFIG_NIMBLE_ENABLED)
m_callbackSent = other.m_callbackSent;
#endif
m_haveAppearance = other.m_haveAppearance;
m_haveManufacturerData = other.m_haveManufacturerData;
m_haveName = other.m_haveName;
m_haveRSSI = other.m_haveRSSI;
m_haveTXPower = other.m_haveTXPower;
m_isLegacyAdv = other.m_isLegacyAdv;
// Free existing payload and deep copy the new one
if (m_payload != nullptr) {
free(m_payload);
}
m_payloadLength = other.m_payloadLength;
if (other.m_payload != nullptr && other.m_payloadLength > 0) {
m_payload = (uint8_t *)malloc(m_payloadLength);
if (m_payload != nullptr) {
memcpy(m_payload, other.m_payload, m_payloadLength);
} else {
log_e("Failed to allocate %zu bytes for payload in assignment operator", m_payloadLength);
m_payloadLength = 0;
}
} else {
m_payload = nullptr;
m_payloadLength = 0;
}
return *this;
} // BLEAdvertisedDevice assignment operator
bool BLEAdvertisedDevice::isLegacyAdvertisement() {
return m_isLegacyAdv;
}
bool BLEAdvertisedDevice::isScannable() {
#if defined(CONFIG_BLUEDROID_ENABLED)
return isLegacyAdvertisement() && (m_advType == ESP_BLE_EVT_CONN_ADV || m_advType == ESP_BLE_EVT_DISC_ADV);
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
return isLegacyAdvertisement() && (m_advType == BLE_HCI_ADV_TYPE_ADV_IND || m_advType == BLE_HCI_ADV_TYPE_ADV_SCAN_IND);
#endif
}
bool BLEAdvertisedDevice::isConnectable() {
#if defined(CONFIG_BLUEDROID_ENABLED)
return m_advType == ESP_BLE_EVT_CONN_ADV || m_advType == ESP_BLE_EVT_CONN_DIR_ADV;
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
if (m_isLegacyAdv) {
return m_advType == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND || m_advType == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
}
return (m_advType & BLE_HCI_ADV_CONN_MASK) || (m_advType & BLE_HCI_ADV_DIRECT_MASK);
#endif
}
/**
* @brief Get the address.
*
* Every %BLE device exposes an address that is used to identify it and subsequently connect to it.
* Call this function to obtain the address of the advertised device.
*
* @return The address of the advertised device.
*/
BLEAddress BLEAdvertisedDevice::getAddress() {
return m_address;
} // getAddress
/**
* @brief Get the appearance.
*
* A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user
* typically in the form of an icon.
*
* @return The appearance of the advertised device.
*/
uint16_t BLEAdvertisedDevice::getAppearance() {
return m_appearance;
} // getAppearance
/**
* @brief Get the manufacturer data.
* @return The manufacturer data of the advertised device.
*/
String BLEAdvertisedDevice::getManufacturerData() {
return m_manufacturerData;
} // getManufacturerData
/**
* @brief Get the name.
* @return The name of the advertised device.
*/
String BLEAdvertisedDevice::getName() {
return m_name;
} // getName
/**
* @brief Get the RSSI.
* @return The RSSI of the advertised device.
*/
int BLEAdvertisedDevice::getRSSI() {
return m_rssi;
} // getRSSI
/**
* @brief Get the scan object that created this advertisement.
* @return The scan object.
*/
BLEScan *BLEAdvertisedDevice::getScan() {
return m_pScan;
} // getScan
/**
* @brief Get the number of service data.
* @return Number of service data discovered.
*/
int BLEAdvertisedDevice::getServiceDataCount() {
return m_serviceData.size();
} //getServiceDataCount
/**
* @brief Get the service data.
* @return The ServiceData of the advertised device.
*/
String BLEAdvertisedDevice::getServiceData() {
return m_serviceData.empty() ? String() : m_serviceData.front();
} //getServiceData
/**
* @brief Get the service data.
* @return The ServiceData of the advertised device.
*/
String BLEAdvertisedDevice::getServiceData(int i) {
return m_serviceData[i];
} //getServiceData
/**
* @brief Get the number of service data UUIDs.
* @return Number of service data UUIDs discovered.
*/
int BLEAdvertisedDevice::getServiceDataUUIDCount() {
return m_serviceDataUUIDs.size();
} //getServiceDataUUIDCount
/**
* @brief Get the service data UUID.
* @return The service data UUID.
*/
BLEUUID BLEAdvertisedDevice::getServiceDataUUID() {
return m_serviceDataUUIDs.empty() ? BLEUUID() : m_serviceDataUUIDs.front();
} // getServiceDataUUID
/**
* @brief Get the service data UUID.
* @return The service data UUID.
*/
BLEUUID BLEAdvertisedDevice::getServiceDataUUID(int i) {
return m_serviceDataUUIDs[i];
} // getServiceDataUUID
/**
* @brief Get the number of service UUIDs.
* @return Number of service UUIDs discovered.
*/
int BLEAdvertisedDevice::getServiceUUIDCount() {
return m_serviceUUIDs.size();
} //getServiceUUIDCount
/**
* @brief Get the Service UUID.
* @return The Service UUID of the advertised device.
*/
BLEUUID BLEAdvertisedDevice::getServiceUUID() {
return m_serviceUUIDs.empty() ? BLEUUID() : m_serviceUUIDs.front();
} // getServiceUUID
/**
* @brief Get the Service UUID.
* @return The Service UUID of the advertised device.
*/
BLEUUID BLEAdvertisedDevice::getServiceUUID(int i) {
return m_serviceUUIDs[i];
} // getServiceUUID
/**
* @brief Check advertised serviced for existence required UUID
* @return Return true if service is advertised
*/
bool BLEAdvertisedDevice::isAdvertisingService(BLEUUID uuid) {
for (int i = 0; i < getServiceUUIDCount(); i++) {
if (m_serviceUUIDs[i].equals(uuid)) {
return true;
}
}
return false;
}
/**
* @brief Get the TX Power.
* @return The TX Power of the advertised device.
*/
int8_t BLEAdvertisedDevice::getTXPower() {
return m_txPower;
} // getTXPower
/**
* @brief Does this advertisement have an appearance value?
* @return True if there is an appearance value present.
*/
bool BLEAdvertisedDevice::haveAppearance() {
return m_haveAppearance;
} // haveAppearance
/**
* @brief Does this advertisement have manufacturer data?
* @return True if there is manufacturer data present.
*/
bool BLEAdvertisedDevice::haveManufacturerData() {
return m_haveManufacturerData;
} // haveManufacturerData
/**
* @brief Does this advertisement have a name value?
* @return True if there is a name value present.
*/
bool BLEAdvertisedDevice::haveName() {
return m_haveName;
} // haveName
/**
* @brief Does this advertisement have a signal strength value?
* @return True if there is a signal strength value present.
*/
bool BLEAdvertisedDevice::haveRSSI() {
return m_haveRSSI;
} // haveRSSI
/**
* @brief Does this advertisement have a service data value?
* @return True if there is a service data value present.
*/
bool BLEAdvertisedDevice::haveServiceData() {
return !m_serviceData.empty();
} // haveServiceData
/**
* @brief Does this advertisement have a service UUID value?
* @return True if there is a service UUID value present.
*/
bool BLEAdvertisedDevice::haveServiceUUID() {
return !m_serviceUUIDs.empty();
} // haveServiceUUID
/**
* @brief Does this advertisement have a transmission power value?
* @return True if there is a transmission power value present.
*/
bool BLEAdvertisedDevice::haveTXPower() {
return m_haveTXPower;
} // haveTXPower
/**
* @brief Parse the advertising pay load.
*
* The pay load is a buffer of bytes that is either 31 bytes long or terminated by
* a 0 length value. Each entry in the buffer has the format:
* [length][type][data...]
*
* The length does not include itself but does include everything after it until the next record. A record
* with a length value of 0 indicates a terminator.
*
* https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
*/
void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) {
uint8_t length;
uint8_t ad_type;
uint8_t sizeConsumed = 0;
bool finished = false;
// Store/append raw payload data for later retrieval
// This handles both ADV and Scan Response packets by merging them
if (m_payload != nullptr && m_payloadLength > 0) {
// Append new payload data (scan response) to existing (advertisement)
uint8_t *new_payload = (uint8_t *)realloc(m_payload, m_payloadLength + total_len);
if (new_payload != nullptr) {
memcpy(new_payload + m_payloadLength, payload, total_len);
m_payload = new_payload;
m_payloadLength += total_len;
} else {
log_e("Failed to reallocate %zu bytes for payload (append)", m_payloadLength + total_len);
}
} else {
// First payload - make a copy since the original buffer may be reused
m_payload = (uint8_t *)malloc(total_len);
if (m_payload != nullptr) {
memcpy(m_payload, payload, total_len);
m_payloadLength = total_len;
} else {
log_e("Failed to allocate %zu bytes for payload", total_len);
m_payloadLength = 0;
}
}
while (!finished) {
length = *payload; // Retrieve the length of the record.
payload++; // Skip to type
sizeConsumed += 1 + length; // increase the size consumed.
if (length != 0) { // A length of 0 indicates that we have reached the end.
ad_type = *payload;
payload++;
length--;
char *pHex = BLEUtils::buildHexData(nullptr, payload, length);
log_d("Type: 0x%.2x (%s), length: %d, data: %s", ad_type, BLEUtils::advDataTypeToString(ad_type), length, pHex);
free(pHex);
switch (ad_type) {
case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09
{ // Adv Data Type: ESP_BLE_AD_TYPE_NAME_CMPL
setName(String(reinterpret_cast<char *>(payload), length));
break;
} // 0x09
case ESP_BLE_AD_TYPE_TX_PWR: // 0x0A
{ // Adv Data Type: ESP_BLE_AD_TYPE_TX_PWR
setTXPower(*payload);
break;
} // 0x0A
case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19
{ // Adv Data Type: ESP_BLE_AD_TYPE_APPEARANCE
setAppearance(*reinterpret_cast<uint16_t *>(payload));
break;
} // 0x19
case ESP_BLE_AD_TYPE_FLAG: // 0x01
{ // Adv Data Type: ESP_BLE_AD_TYPE_FLAG
setAdFlag(*payload);
break;
} // 0x01
case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02
case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03
{ // Adv Data Type: ESP_BLE_AD_TYPE_16SRV_PART/CMPL
for (int var = 0; var < length / 2; ++var) {
setServiceUUID(BLEUUID(*reinterpret_cast<uint16_t *>(payload + var * 2)));
}
break;
} // 0x02, 0x03
case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04
case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05
{ // Adv Data Type: ESP_BLE_AD_TYPE_32SRV_PART/CMPL
for (int var = 0; var < length / 4; ++var) {
setServiceUUID(BLEUUID(*reinterpret_cast<uint32_t *>(payload + var * 4)));
}
break;
} // 0x04, 0x05
case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07
{ // Adv Data Type: ESP_BLE_AD_TYPE_128SRV_CMPL
setServiceUUID(BLEUUID(payload, 16, false));
break;
} // 0x07
case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06
{ // Adv Data Type: ESP_BLE_AD_TYPE_128SRV_PART
setServiceUUID(BLEUUID(payload, 16, false));
break;
} // 0x06
// See CSS Part A 1.4 Manufacturer Specific Data
case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xFF
{
setManufacturerData(String(reinterpret_cast<char *>(payload), length));
break;
} // 0xFF
case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16
{ // Adv Data Type: ESP_BLE_AD_TYPE_SERVICE_DATA - 2 byte UUID
if (length < 2) {
log_e("Length too small for SERVICE_DATA");
break;
}
uint16_t uuid = *(uint16_t *)payload;
setServiceDataUUID(BLEUUID(uuid));
if (length > 2) {
setServiceData(String(reinterpret_cast<char *>(payload + 2), length - 2));
}
break;
} // 0x16
case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20
{ // Adv Data Type: ESP_BLE_AD_TYPE_32SERVICE_DATA - 4 byte UUID
if (length < 4) {
log_e("Length too small for 32SERVICE_DATA");
break;
}
uint32_t uuid = *(uint32_t *)payload;
setServiceDataUUID(BLEUUID(uuid));
if (length > 4) {
setServiceData(String(reinterpret_cast<char *>(payload + 4), length - 4));
}
break;
} // 0x20
case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21
{ // Adv Data Type: ESP_BLE_AD_TYPE_128SERVICE_DATA - 16 byte UUID
if (length < 16) {
log_e("Length too small for 128SERVICE_DATA");
break;
}
setServiceDataUUID(BLEUUID(payload, (size_t)16, false));
if (length > 16) {
setServiceData(String(reinterpret_cast<char *>(payload + 16), length - 16));
}
break;
} // 0x21
default:
{
log_d("Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type);
break;
} // default
} // switch
payload += length;
} // Length <> 0
if (sizeConsumed >= total_len) {
finished = true;
}
} // !finished
} // parseAdvertisement
/**
* @brief Set the advertising payload.
* @param [in] payload The payload of the advertised device.
* @param [in] total_len The length of payload
* @param [in] append If true, append to existing payload (for scan response merging)
*/
void BLEAdvertisedDevice::setPayload(uint8_t *payload, size_t total_len, bool append) {
if (total_len == 0 || payload == nullptr) {
return;
}
if (append && m_payload != nullptr && m_payloadLength > 0) {
// Append scan response data to existing advertisement data
uint8_t *new_payload = (uint8_t *)realloc(m_payload, m_payloadLength + total_len);
if (new_payload == nullptr) {
log_e("Failed to reallocate %zu bytes for payload buffer", m_payloadLength + total_len);
return;
}
memcpy(new_payload + m_payloadLength, payload, total_len);
m_payload = new_payload;
m_payloadLength += total_len;
} else {
// First payload or replacing existing - make a copy
if (m_payload != nullptr && m_payloadLength > 0) {
free(m_payload);
}
m_payload = (uint8_t *)malloc(total_len);
if (m_payload == nullptr) {
log_e("Failed to allocate %zu bytes for payload buffer", total_len);
m_payloadLength = 0;
return;
}
memcpy(m_payload, payload, total_len);
m_payloadLength = total_len;
}
} // setPayload
/**
* @brief Set the address of the advertised device.
* @param [in] address The address of the advertised device.
*/
void BLEAdvertisedDevice::setAddress(BLEAddress address) {
m_address = address;
} // setAddress
/**
* @brief Set the adFlag for this device.
* @param [in] The discovered adFlag.
*/
void BLEAdvertisedDevice::setAdFlag(uint8_t adFlag) {
m_adFlag = adFlag;
} // setAdFlag
/**
* @brief Set the appearance for this device.
* @param [in] The discovered appearance.
*/
void BLEAdvertisedDevice::setAppearance(uint16_t appearance) {
m_appearance = appearance;
m_haveAppearance = true;
log_d("- appearance: %d", m_appearance);
} // setAppearance
/**
* @brief Set the manufacturer data for this device.
* @param [in] The discovered manufacturer data.
*/
void BLEAdvertisedDevice::setManufacturerData(String manufacturerData) {
m_manufacturerData = manufacturerData;
m_haveManufacturerData = true;
char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t *)m_manufacturerData.c_str(), (uint8_t)m_manufacturerData.length());
log_d("- manufacturer data: %s", pHex);
free(pHex);
} // setManufacturerData
/**
* @brief Set the name for this device.
* @param [in] name The discovered name.
*/
void BLEAdvertisedDevice::setName(String name) {
m_name = name;
m_haveName = true;
log_d("- setName(): name: %s", m_name.c_str());
} // setName
/**
* @brief Set the RSSI for this device.
* @param [in] rssi The discovered RSSI.
*/
void BLEAdvertisedDevice::setRSSI(int rssi) {
m_rssi = rssi;
m_haveRSSI = true;
log_d("- setRSSI(): rssi: %d", m_rssi);
} // setRSSI
/**
* @brief Set the Scan that created this advertised device.
* @param pScan The Scan that created this advertised device.
*/
void BLEAdvertisedDevice::setScan(BLEScan *pScan) {
m_pScan = pScan;
} // setScan
/**
* @brief Set the Service UUID for this device.
* @param [in] serviceUUID The discovered serviceUUID
*/
void BLEAdvertisedDevice::setServiceUUID(const char *serviceUUID) {
return setServiceUUID(BLEUUID(serviceUUID));
} // setServiceUUID
/**
* @brief Set the Service UUID for this device.
* @param [in] serviceUUID The discovered serviceUUID
*/
void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) {
m_serviceUUIDs.push_back(serviceUUID);
log_d("- addServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str());
} // setServiceUUID
/**
* @brief Set the ServiceData value.
* @param [in] data ServiceData value.
*/
void BLEAdvertisedDevice::setServiceData(String serviceData) {
m_serviceData.push_back(serviceData); // Save the service data that we received.
} //setServiceData
/**
* @brief Set the ServiceDataUUID value.
* @param [in] data ServiceDataUUID value.
*/
void BLEAdvertisedDevice::setServiceDataUUID(BLEUUID uuid) {
m_serviceDataUUIDs.push_back(uuid);
log_d("- addServiceDataUUID(): serviceDataUUID: %s", uuid.toString().c_str());
} // setServiceDataUUID
/**
* @brief Set the power level for this device.
* @param [in] txPower The discovered power level.
*/
void BLEAdvertisedDevice::setTXPower(int8_t txPower) {
m_txPower = txPower;
m_haveTXPower = true;
log_d("- txPower: %d", m_txPower);
} // setTXPower
/**
* @brief Create a string representation of this device.
* @return A string representation of this device.
*/
String BLEAdvertisedDevice::toString() {
String res = "Name: " + getName() + ", Address: " + getAddress().toString();
if (haveAppearance()) {
char val[6];
snprintf(val, sizeof(val), "%d", getAppearance());
res += ", appearance: ";
res += val;
}
if (haveManufacturerData()) {
char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t *)getManufacturerData().c_str(), getManufacturerData().length());
res += ", manufacturer data: ";
res += pHex;
free(pHex);
}
if (haveServiceUUID()) {
for (int i = 0; i < getServiceUUIDCount(); i++) {
res += ", serviceUUID: " + getServiceUUID(i).toString();
}
}
if (haveTXPower()) {
char val[6];
snprintf(val, sizeof(val), "%d", getTXPower());
res += ", txPower: ";
res += val;
}
if (haveRSSI()) {
char val[5];
snprintf(val, sizeof(val), "%i", getRSSI());
res += ", rssi: ";
res += val;
}
if (haveServiceData()) {
for (int i = 0; i < getServiceDataCount(); i++) {
res += ", serviceData: " + getServiceData(i);
}
}
return res;
} // toString
uint8_t *BLEAdvertisedDevice::getPayload() {
return m_payload;
}
uint8_t BLEAdvertisedDevice::getAddressType() {
return m_address.getType();
}
ble_frame_type_t BLEAdvertisedDevice::getFrameType() {
for (int i = 0; i < m_payloadLength; ++i) {
log_d("check [%d]=0x%02X", i, m_payload[i]);
if (m_payload[i] == 0x16 && m_payloadLength >= i + 3 && m_payload[i + 1] == 0xAA && m_payload[i + 2] == 0xFE && m_payload[i + 3] == 0x00) {
return BLE_EDDYSTONE_UUID_FRAME;
}
if (m_payload[i] == 0x16 && m_payloadLength >= i + 3 && m_payload[i + 1] == 0xAA && m_payload[i + 2] == 0xFE && m_payload[i + 3] == 0x10) {
return BLE_EDDYSTONE_URL_FRAME;
}
if (m_payload[i] == 0x16 && m_payloadLength >= i + 3 && m_payload[i + 1] == 0xAA && m_payload[i + 2] == 0xFE && m_payload[i + 3] == 0x20) {
return BLE_EDDYSTONE_TLM_FRAME;
}
}
return BLE_UNKNOWN_FRAME;
}
void BLEAdvertisedDevice::setAddressType(uint8_t type) {
m_address.setType(type);
}
size_t BLEAdvertisedDevice::getPayloadLength() {
return m_payloadLength;
}
void BLEAdvertisedDevice::setAdvType(uint8_t type) {
m_advType = type;
}
uint8_t BLEAdvertisedDevice::getAdvType() {
return m_advType;
}
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+237
View File
@@ -0,0 +1,237 @@
/*
* BLEAdvertisedDevice.h
*
* Created on: Jul 3, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_
#define COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <map>
#include "BLEAddress.h"
#include "BLEScan.h"
#include "BLEUUID.h"
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gattc_api.h>
#endif
/***************************************************************************
* NimBLE includes *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <nimble/ble.h>
#include <host/ble_gap.h>
#endif
/***************************************************************************
* Common types *
***************************************************************************/
typedef enum {
BLE_UNKNOWN_FRAME,
BLE_EDDYSTONE_UUID_FRAME,
BLE_EDDYSTONE_URL_FRAME,
BLE_EDDYSTONE_TLM_FRAME,
BLE_FRAME_MAX
} ble_frame_type_t;
/***************************************************************************
* Forward declarations *
***************************************************************************/
class BLEScan;
/**
* @brief A representation of a %BLE advertised device found by a scan.
*
* When we perform a %BLE scan, the result will be a set of devices that are advertising. This
* class provides a model of a detected device.
*/
class BLEAdvertisedDevice {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLEAdvertisedDevice();
~BLEAdvertisedDevice();
BLEAdvertisedDevice(const BLEAdvertisedDevice &other);
BLEAdvertisedDevice &operator=(const BLEAdvertisedDevice &other);
BLEAddress getAddress();
uint16_t getAppearance();
String getManufacturerData();
String getName();
int getRSSI();
BLEScan *getScan();
String getServiceData();
String getServiceData(int i);
BLEUUID getServiceDataUUID();
BLEUUID getServiceDataUUID(int i);
BLEUUID getServiceUUID();
BLEUUID getServiceUUID(int i);
int getServiceDataCount();
int getServiceDataUUIDCount();
int getServiceUUIDCount();
int8_t getTXPower();
uint8_t *getPayload();
size_t getPayloadLength();
uint8_t getAddressType();
ble_frame_type_t getFrameType();
void setAddressType(uint8_t type);
void setAdvType(uint8_t type);
uint8_t getAdvType();
bool isLegacyAdvertisement();
bool isScannable();
bool isConnectable();
bool isAdvertisingService(BLEUUID uuid);
bool haveAppearance();
bool haveManufacturerData();
bool haveName();
bool haveRSSI();
bool haveServiceData();
bool haveServiceUUID();
bool haveTXPower();
String toString();
private:
friend class BLEScan;
/***************************************************************************
* Common private properties *
***************************************************************************/
bool m_haveAppearance;
bool m_haveManufacturerData;
bool m_haveName;
bool m_haveRSSI;
bool m_haveTXPower;
BLEAddress m_address = BLEAddress((uint8_t *)"\0\0\0\0\0\0");
uint8_t m_adFlag;
uint16_t m_appearance;
int m_deviceType;
String m_manufacturerData;
String m_name;
BLEScan *m_pScan;
int m_rssi;
std::vector<BLEUUID> m_serviceUUIDs;
int8_t m_txPower;
std::vector<String> m_serviceData;
std::vector<BLEUUID> m_serviceDataUUIDs;
uint8_t *m_payload;
size_t m_payloadLength = 0;
uint8_t m_advType;
bool m_isLegacyAdv;
/***************************************************************************
* NimBLE private properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
bool m_callbackSent;
#endif
/***************************************************************************
* Common private declarations *
***************************************************************************/
void parseAdvertisement(uint8_t *payload, size_t total_len = 62);
void setPayload(uint8_t *payload, size_t total_len = 62, bool append = false);
void setAddress(BLEAddress address);
void setAdFlag(uint8_t adFlag);
void setAdvertizementResult(uint8_t *payload);
void setAppearance(uint16_t appearance);
void setManufacturerData(String manufacturerData);
void setName(String name);
void setRSSI(int rssi);
void setScan(BLEScan *pScan);
void setServiceData(String data);
void setServiceDataUUID(BLEUUID uuid);
void setServiceUUID(const char *serviceUUID);
void setServiceUUID(BLEUUID serviceUUID);
void setTXPower(int8_t txPower);
};
/**
* @brief A callback handler for callbacks associated device scanning.
*
* When we are performing a scan as a %BLE client, we may wish to know when a new device that is advertising
* has been found. This class can be sub-classed and registered such that when a scan is performed and
* a new advertised device has been found, we will be called back to be notified.
*/
class BLEAdvertisedDeviceCallbacks {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
virtual ~BLEAdvertisedDeviceCallbacks() {}
/**
* @brief Called when a new scan result is detected.
*
* As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the
* device that was found. During any individual scan, a device will only be detected one time.
*/
virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0;
};
#if defined(SOC_BLE_50_SUPPORTED) && defined(CONFIG_BLUEDROID_ENABLED)
class BLEExtAdvertisingCallbacks {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
virtual ~BLEExtAdvertisingCallbacks() {}
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
/**
* @brief Called when a new scan result is detected.
*
* As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the
* device that was found. During any individual scan, a device will only be detected one time.
*/
#if defined(CONFIG_BLUEDROID_ENABLED)
virtual void onResult(esp_ble_gap_ext_adv_report_t report) = 0;
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
// Extended advertising for NimBLE is not supported yet.
#if defined(CONFIG_NIMBLE_ENABLED)
virtual void onResult(struct ble_gap_ext_disc_desc report) = 0;
#endif
};
#endif // SOC_BLE_50_SUPPORTED && CONFIG_BLUEDROID_ENABLED
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ */
File diff suppressed because it is too large Load Diff
+258
View File
@@ -0,0 +1,258 @@
/*
* BLEAdvertising.h
*
* Created on: Jun 21, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISING_H_
#define COMPONENTS_CPP_UTILS_BLEADVERTISING_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "BLEUUID.h"
#include <vector>
#include "RTOS.h"
#include "BLEUtils.h"
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gap_ble_api.h>
#endif
/***************************************************************************
* NimBLE includes and definitions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <host/ble_gap.h>
#include <services/gap/ble_svc_gap.h>
#include <host/ble_hs_adv.h>
#define ESP_BLE_ADV_DATA_LEN_MAX BLE_HS_ADV_MAX_SZ
#define ESP_BLE_ADV_FLAG_LIMIT_DISC (0x01 << 0)
#define ESP_BLE_ADV_FLAG_GEN_DISC (0x01 << 1)
#define ESP_BLE_ADV_FLAG_BREDR_NOT_SPT (0x01 << 2)
#define ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT (0x01 << 3)
#define ESP_BLE_ADV_FLAG_DMT_HOST_SPT (0x01 << 4)
#define ESP_BLE_ADV_FLAG_NON_LIMIT_DISC (0x00)
#endif /* CONFIG_NIMBLE_ENABLED */
/***************************************************************************
* NimBLE types *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
typedef enum {
ESP_BLE_AD_TYPE_FLAG = 0x01,
ESP_BLE_AD_TYPE_16SRV_PART = 0x02,
ESP_BLE_AD_TYPE_16SRV_CMPL = 0x03,
ESP_BLE_AD_TYPE_32SRV_PART = 0x04,
ESP_BLE_AD_TYPE_32SRV_CMPL = 0x05,
ESP_BLE_AD_TYPE_128SRV_PART = 0x06,
ESP_BLE_AD_TYPE_128SRV_CMPL = 0x07,
ESP_BLE_AD_TYPE_NAME_SHORT = 0x08,
ESP_BLE_AD_TYPE_NAME_CMPL = 0x09,
ESP_BLE_AD_TYPE_TX_PWR = 0x0A,
ESP_BLE_AD_TYPE_DEV_CLASS = 0x0D,
ESP_BLE_AD_TYPE_SM_TK = 0x10,
ESP_BLE_AD_TYPE_SM_OOB_FLAG = 0x11,
ESP_BLE_AD_TYPE_INT_RANGE = 0x12,
ESP_BLE_AD_TYPE_SOL_SRV_UUID = 0x14,
ESP_BLE_AD_TYPE_128SOL_SRV_UUID = 0x15,
ESP_BLE_AD_TYPE_SERVICE_DATA = 0x16,
ESP_BLE_AD_TYPE_PUBLIC_TARGET = 0x17,
ESP_BLE_AD_TYPE_RANDOM_TARGET = 0x18,
ESP_BLE_AD_TYPE_APPEARANCE = 0x19,
ESP_BLE_AD_TYPE_ADV_INT = 0x1A,
ESP_BLE_AD_TYPE_LE_DEV_ADDR = 0x1b,
ESP_BLE_AD_TYPE_LE_ROLE = 0x1c,
ESP_BLE_AD_TYPE_SPAIR_C256 = 0x1d,
ESP_BLE_AD_TYPE_SPAIR_R256 = 0x1e,
ESP_BLE_AD_TYPE_32SOL_SRV_UUID = 0x1f,
ESP_BLE_AD_TYPE_32SERVICE_DATA = 0x20,
ESP_BLE_AD_TYPE_128SERVICE_DATA = 0x21,
ESP_BLE_AD_TYPE_LE_SECURE_CONFIRM = 0x22,
ESP_BLE_AD_TYPE_LE_SECURE_RANDOM = 0x23,
ESP_BLE_AD_TYPE_URI = 0x24,
ESP_BLE_AD_TYPE_INDOOR_POSITION = 0x25,
ESP_BLE_AD_TYPE_TRANS_DISC_DATA = 0x26,
ESP_BLE_AD_TYPE_LE_SUPPORT_FEATURE = 0x27,
ESP_BLE_AD_TYPE_CHAN_MAP_UPDATE = 0x28,
ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE = 0xFF,
} esp_ble_adv_data_type;
#endif
/**
* @brief Advertisement data set by the programmer to be published by the %BLE server.
*/
class BLEAdvertisementData {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
void setAppearance(uint16_t appearance);
void setCompleteServices(BLEUUID uuid);
void setFlags(uint8_t);
void setManufacturerData(String data);
void setName(String name);
void setPartialServices(BLEUUID uuid);
void setServiceData(BLEUUID uuid, String data);
void setShortName(String name);
void setPreferredParams(uint16_t min, uint16_t max);
void addTxPower();
void addData(String data);
void addData(char *data, size_t length);
String getPayload();
private:
friend class BLEAdvertising;
/***************************************************************************
* Common private declarations *
***************************************************************************/
String m_payload;
};
/**
* @brief Perform and manage %BLE advertising.
*/
class BLEAdvertising {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLEAdvertising();
void addServiceUUID(BLEUUID serviceUUID);
void addServiceUUID(const char *serviceUUID);
bool removeServiceUUID(int index);
bool removeServiceUUID(BLEUUID serviceUUID);
bool removeServiceUUID(const char *serviceUUID);
bool stop();
void reset();
void setAppearance(uint16_t appearance);
void setAdvertisementType(uint8_t adv_type);
void setMaxInterval(uint16_t maxinterval);
void setMinInterval(uint16_t mininterval);
bool setAdvertisementData(BLEAdvertisementData &advertisementData);
void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly);
bool setScanResponseData(BLEAdvertisementData &advertisementData);
void setMinPreferred(uint16_t);
void setMaxPreferred(uint16_t);
void setScanResponse(bool);
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
void setPrivateAddress(esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM);
bool setDeviceAddress(esp_bd_addr_t addr, esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM);
void setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map);
void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
bool start();
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
void setName(String name);
void addTxPower();
void advCompleteCB();
bool isAdvertising();
void onHostSync();
bool start(uint32_t duration = 0, void (*advCompleteCB)(BLEAdvertising *pAdv) = nullptr);
static int handleGAPEvent(ble_gap_event *event, void *arg);
#endif
private:
/***************************************************************************
* Common private properties *
***************************************************************************/
std::vector<BLEUUID> m_serviceUUIDs;
bool m_customAdvData = false;
bool m_customScanResponseData = false;
FreeRTOS::Semaphore m_semaphoreSetAdv = FreeRTOS::Semaphore("startAdvert");
bool m_scanResp = true;
/***************************************************************************
* Bluedroid private properties *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
esp_ble_adv_data_t m_advData;
esp_ble_adv_data_t m_scanRespData;
esp_ble_adv_params_t m_advParams;
#endif
/***************************************************************************
* NimBLE private properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
ble_hs_adv_fields m_advData;
ble_hs_adv_fields m_scanData;
ble_gap_adv_params m_advParams;
bool m_advDataSet;
void (*m_advCompCB)(BLEAdvertising *pAdv);
uint8_t m_slaveItvl[4];
uint32_t m_duration;
String m_name;
#endif
};
/***************************************************************************
* Bluedroid 5.0 specific classes *
***************************************************************************/
#if defined(SOC_BLE_50_SUPPORTED) && defined(CONFIG_BLUEDROID_ENABLED)
class BLEMultiAdvertising {
public:
BLEMultiAdvertising(uint8_t num = 1);
~BLEMultiAdvertising() {}
bool setAdvertisingData(uint8_t instance, uint16_t length, const uint8_t *data);
bool setScanRspData(uint8_t instance, uint16_t length, const uint8_t *data);
bool start();
bool start(uint8_t num, uint8_t from);
void setDuration(uint8_t instance, int duration = 0, int max_events = 0);
bool setInstanceAddress(uint8_t instance, uint8_t *rand_addr);
bool stop(uint8_t num_adv, const uint8_t *ext_adv_inst);
bool remove(uint8_t instance);
bool clear();
bool setPeriodicAdvertisingData(uint8_t instance, uint16_t length, const uint8_t *data);
bool startPeriodicAdvertising(uint8_t instance);
bool setAdvertisingParams(uint8_t instance, const esp_ble_gap_ext_adv_params_t *params);
bool setPeriodicAdvertisingParams(uint8_t instance, const esp_ble_gap_periodic_adv_params_t *params);
private:
esp_ble_gap_ext_adv_params_t *params_arrays;
esp_ble_gap_ext_adv_t *ext_adv;
uint8_t count;
};
#endif
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */
+98
View File
@@ -0,0 +1,98 @@
/*
* BLEBeacon.cpp
*
* Created on: Jan 4, 2018
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes and definitions *
***************************************************************************/
#include "BLEBeacon.h"
#include "esp32-hal-log.h"
#define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8))
/***************************************************************************
* Common functions *
***************************************************************************/
BLEBeacon::BLEBeacon() {
m_beaconData.manufacturerId = 0x4c00;
m_beaconData.subType = 0x02;
m_beaconData.subTypeLength = 0x15;
m_beaconData.major = 0;
m_beaconData.minor = 0;
m_beaconData.signalPower = 0;
memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID));
}
String BLEBeacon::getData() {
return String((char *)&m_beaconData, sizeof(m_beaconData));
}
uint16_t BLEBeacon::getMajor() {
return m_beaconData.major;
}
uint16_t BLEBeacon::getManufacturerId() {
return m_beaconData.manufacturerId;
}
uint16_t BLEBeacon::getMinor() {
return m_beaconData.minor;
}
BLEUUID BLEBeacon::getProximityUUID() {
return BLEUUID(m_beaconData.proximityUUID, 16, true);
}
int8_t BLEBeacon::getSignalPower() {
return m_beaconData.signalPower;
}
void BLEBeacon::setData(const String &data) {
if (data.length() != sizeof(m_beaconData)) {
log_e("Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_beaconData));
return;
}
memcpy(&m_beaconData, data.c_str(), sizeof(m_beaconData));
}
void BLEBeacon::setMajor(uint16_t major) {
m_beaconData.major = ENDIAN_CHANGE_U16(major);
}
void BLEBeacon::setManufacturerId(uint16_t manufacturerId) {
m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId);
}
void BLEBeacon::setMinor(uint16_t minor) {
m_beaconData.minor = ENDIAN_CHANGE_U16(minor);
}
void BLEBeacon::setSignalPower(int8_t signalPower) {
m_beaconData.signalPower = signalPower;
}
void BLEBeacon::setProximityUUID(BLEUUID uuid) {
uuid = uuid.to128();
#if defined(CONFIG_BLUEDROID_ENABLED)
memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16);
#elif defined(CONFIG_NIMBLE_ENABLED)
memcpy(m_beaconData.proximityUUID, uuid.getNative()->u128.value, 16);
#endif
}
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+69
View File
@@ -0,0 +1,69 @@
/*
* BLEBeacon2.h
*
* Created on: Jan 4, 2018
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLEBEACON_H_
#define COMPONENTS_CPP_UTILS_BLEBEACON_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include "BLEUUID.h"
/**
* @brief Representation of a beacon.
* See:
* * https://en.wikipedia.org/wiki/IBeacon
*/
class BLEBeacon {
private:
/***************************************************************************
* Common types *
***************************************************************************/
struct {
uint16_t manufacturerId;
uint8_t subType;
uint8_t subTypeLength;
uint8_t proximityUUID[16];
uint16_t major;
uint16_t minor;
int8_t signalPower;
} __attribute__((packed)) m_beaconData;
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLEBeacon();
String getData();
uint16_t getMajor();
uint16_t getMinor();
uint16_t getManufacturerId();
BLEUUID getProximityUUID();
int8_t getSignalPower();
void setData(const String &data);
void setMajor(uint16_t major);
void setMinor(uint16_t minor);
void setManufacturerId(uint16_t manufacturerId);
void setProximityUUID(BLEUUID uuid);
void setSignalPower(int8_t signalPower);
}; // BLEBeacon
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLEBEACON_H_ */
File diff suppressed because it is too large Load Diff
+337
View File
@@ -0,0 +1,337 @@
/*
* BLECharacteristic.h
*
* Created on: Jun 22, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_
#define COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <string>
#include <map>
#include "BLEUUID.h"
#include "BLEDescriptor.h"
#include "BLEValue.h"
#include "RTOS.h"
#include "BLEUtils.h"
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gatts_api.h>
#include <esp_gap_ble_api.h>
#endif
/***************************************************************************
* NimBLE includes and definitions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <vector>
#include <host/ble_gatt.h>
#include <host/ble_att.h>
#include "BLEConnInfo.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN
#define ESP_GATT_CHAR_PROP_BIT_READ BLE_GATT_CHR_PROP_READ
#define ESP_GATT_CHAR_PROP_BIT_WRITE BLE_GATT_CHR_PROP_WRITE
#define ESP_GATT_CHAR_PROP_BIT_WRITE_NR BLE_GATT_CHR_PROP_WRITE_NO_RSP
#define ESP_GATT_CHAR_PROP_BIT_BROADCAST BLE_GATT_CHR_PROP_BROADCAST
#define ESP_GATT_CHAR_PROP_BIT_NOTIFY BLE_GATT_CHR_PROP_NOTIFY
#define ESP_GATT_CHAR_PROP_BIT_INDICATE BLE_GATT_CHR_PROP_INDICATE
#endif
/***************************************************************************
* NimBLE types *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
typedef uint16_t esp_gatt_char_prop_t;
typedef uint16_t esp_gatt_perm_t;
#endif
/***************************************************************************
* Forward declarations *
***************************************************************************/
class BLEService;
class BLEDescriptor;
class BLECharacteristicCallbacks;
/**
* @brief A management structure for %BLE descriptors.
*/
class BLEDescriptorMap {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
void setByUUID(const char *uuid, BLEDescriptor *pDescriptor);
void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor);
void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor);
BLEDescriptor *getByUUID(const char *uuid) const;
BLEDescriptor *getByUUID(BLEUUID uuid) const;
BLEDescriptor *getByHandle(uint16_t handle) const;
String toString() const;
BLEDescriptor *getFirst();
BLEDescriptor *getNext();
int getRegisteredDescriptorCount() const;
void removeDescriptor(BLEDescriptor *pDescriptor);
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
void handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg);
#endif
private:
/***************************************************************************
* Common private properties *
***************************************************************************/
std::map<BLEDescriptor *, String> m_uuidMap;
std::map<uint16_t, BLEDescriptor *> m_handleMap;
std::map<BLEDescriptor *, String>::iterator m_iterator;
};
/**
* @brief The model of a %BLE Characteristic.
*
* A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE server and
* can be read and written to by a %BLE client.
*/
class BLECharacteristic {
public:
/***************************************************************************
* Common properties *
***************************************************************************/
static const uint32_t indicationTimeout = 1000;
/***************************************************************************
* Bluedroid public properties *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
static const uint32_t PROPERTY_READ = 1 << 0;
static const uint32_t PROPERTY_READ_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead.
static const uint32_t PROPERTY_READ_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead.
static const uint32_t PROPERTY_READ_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead.
static const uint32_t PROPERTY_WRITE = 1 << 1;
static const uint32_t PROPERTY_WRITE_NR = 1 << 5;
static const uint32_t PROPERTY_WRITE_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead.
static const uint32_t PROPERTY_WRITE_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead.
static const uint32_t PROPERTY_WRITE_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead.
static const uint32_t PROPERTY_NOTIFY = 1 << 2;
static const uint32_t PROPERTY_BROADCAST = 1 << 3;
static const uint32_t PROPERTY_INDICATE = 1 << 4;
#endif
/***************************************************************************
* NimBLE public properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
static const uint32_t PROPERTY_READ = BLE_GATT_CHR_F_READ;
static const uint32_t PROPERTY_READ_ENC = BLE_GATT_CHR_F_READ_ENC;
static const uint32_t PROPERTY_READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN;
static const uint32_t PROPERTY_READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR;
static const uint32_t PROPERTY_WRITE = BLE_GATT_CHR_F_WRITE;
static const uint32_t PROPERTY_WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP;
static const uint32_t PROPERTY_WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC;
static const uint32_t PROPERTY_WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN;
static const uint32_t PROPERTY_WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR;
static const uint32_t PROPERTY_NOTIFY = BLE_GATT_CHR_F_NOTIFY;
static const uint32_t PROPERTY_BROADCAST = BLE_GATT_CHR_F_BROADCAST;
static const uint32_t PROPERTY_INDICATE = BLE_GATT_CHR_F_INDICATE;
#endif
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLECharacteristic(const char *uuid, uint32_t properties = 0);
BLECharacteristic(BLEUUID uuid, uint32_t properties = 0);
virtual ~BLECharacteristic();
void addDescriptor(BLEDescriptor *pDescriptor);
BLEDescriptor *getDescriptorByUUID(const char *descriptorUUID) const;
BLEDescriptor *getDescriptorByUUID(BLEUUID descriptorUUID) const;
BLEUUID getUUID() const;
String getValue() const;
uint8_t *getData();
size_t getLength() const;
void indicate();
void notify(bool is_notification = true);
void setCallbacks(BLECharacteristicCallbacks *pCallbacks);
void setValue(const uint8_t *data, size_t size);
void setValue(const String &value);
void setValue(uint16_t data16);
void setValue(uint32_t data32);
void setValue(int data32);
void setValue(float data32);
void setValue(double data64);
String toString() const;
uint16_t getHandle() const;
void setAccessPermissions(uint16_t perm);
esp_gatt_char_prop_t getProperties() const;
void setReadProperty(bool value);
void setWriteProperty(bool value);
void setNotifyProperty(bool value);
void setBroadcastProperty(bool value);
void setIndicateProperty(bool value);
void setWriteNoResponseProperty(bool value);
private:
friend class BLEServer;
friend class BLEService;
friend class BLEDescriptor;
friend class BLECharacteristicMap;
/***************************************************************************
* Common private properties *
***************************************************************************/
BLEUUID m_bleUUID;
BLEDescriptorMap m_descriptorMap;
uint16_t m_handle;
esp_gatt_char_prop_t m_properties;
BLECharacteristicCallbacks *m_pCallbacks;
BLEService *m_pService;
BLEValue m_value;
bool m_writeEvt = false;
FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt");
FreeRTOS::Semaphore m_semaphoreSetValue = FreeRTOS::Semaphore("SetValue");
/***************************************************************************
* Bluedroid private properties *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
#endif
/***************************************************************************
* NimBLE private properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
portMUX_TYPE m_readMux;
uint8_t m_removed;
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
#endif
/***************************************************************************
* Common private declarations *
***************************************************************************/
void executeCreate(BLEService *pService);
BLEService *getService() const;
void setHandle(uint16_t handle);
/***************************************************************************
* Bluedroid private declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE private declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
void setSubscribe(struct ble_gap_event *event);
static int handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
#endif
}; // BLECharacteristic
/**
* @brief Callbacks that can be associated with a %BLE characteristic to inform of events.
*
* When a server application creates a %BLE characteristic, we may wish to be informed when there is either
* a read or write request to the characteristic's value. An application can register a
* sub-classed instance of this class and will be notified when such an event happens.
*/
class BLECharacteristicCallbacks {
public:
/***************************************************************************
* Common public types *
***************************************************************************/
typedef enum {
SUCCESS_INDICATE,
SUCCESS_NOTIFY,
ERROR_INDICATE_DISABLED,
ERROR_NOTIFY_DISABLED,
ERROR_GATT,
ERROR_NO_CLIENT,
ERROR_NO_SUBSCRIBER,
ERROR_INDICATE_TIMEOUT,
ERROR_INDICATE_FAILURE
} Status;
/***************************************************************************
* Common public declarations *
***************************************************************************/
virtual ~BLECharacteristicCallbacks();
virtual void onRead(BLECharacteristic *pCharacteristic);
virtual void onWrite(BLECharacteristic *pCharacteristic);
virtual void onNotify(BLECharacteristic *pCharacteristic);
virtual void onStatus(BLECharacteristic *pCharacteristic, Status s, uint32_t code);
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
virtual void onRead(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param);
virtual void onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
virtual void onRead(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc);
virtual void onWrite(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc);
virtual void onSubscribe(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue);
#endif
};
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */
+185
View File
@@ -0,0 +1,185 @@
/*
* BLECharacteristicMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <sstream>
#include <iomanip>
#include "BLEService.h"
#include "BLEUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/***************************************************************************
* Common functions *
***************************************************************************/
/**
* @brief Return the characteristic by handle.
* @param [in] handle The handle to look up the characteristic.
* @return The characteristic.
*/
BLECharacteristic *BLECharacteristicMap::getByHandle(uint16_t handle) const {
return m_handleMap.at(handle);
} // getByHandle
/**
* @brief Return the characteristic by UUID.
* @param [in] UUID The UUID to look up the characteristic.
* @return The characteristic.
*/
BLECharacteristic *BLECharacteristicMap::getByUUID(const char *uuid) const {
return getByUUID(BLEUUID(uuid));
}
/**
* @brief Return the characteristic by UUID.
* @param [in] UUID The UUID to look up the characteristic.
* @return The characteristic.
*/
BLECharacteristic *BLECharacteristicMap::getByUUID(BLEUUID uuid) const {
for (auto &myPair : m_uuidMap) {
if (myPair.first->getUUID().equals(uuid)) {
return myPair.first;
}
}
//return m_uuidMap.at(uuid.toString());
return nullptr;
} // getByUUID
/**
* @brief Get the first characteristic in the map.
* @return The first characteristic in the map.
*/
BLECharacteristic *BLECharacteristicMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLECharacteristic *pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next characteristic in the map.
* @return The next characteristic in the map.
*/
BLECharacteristic *BLECharacteristicMap::getNext() {
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLECharacteristic *pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getNext
/**
* @brief Get the number of registered characteristics.
* @return The number of registered characteristics.
*/
int BLECharacteristicMap::getRegisteredCharacteristicCount() const {
return m_uuidMap.size();
} // getRegisteredCharacteristicCount
/**
* @brief Removes characteristic from maps.
* @param [in] characteristic The characteristic to remove.
* @return N/A.
*/
void BLECharacteristicMap::removeCharacteristic(BLECharacteristic *characteristic) {
m_handleMap.erase(characteristic->getHandle());
m_uuidMap.erase(characteristic);
} // removeCharacteristic
/**
* @brief Set the characteristic by handle.
* @param [in] handle The handle of the characteristic.
* @param [in] characteristic The characteristic to cache.
* @return N/A.
*/
void BLECharacteristicMap::setByHandle(uint16_t handle, BLECharacteristic *characteristic) {
m_handleMap.insert(std::pair<uint16_t, BLECharacteristic *>(handle, characteristic));
} // setByHandle
/**
* @brief Set the characteristic by UUID.
* @param [in] uuid The uuid of the characteristic.
* @param [in] characteristic The characteristic to cache.
* @return N/A.
*/
void BLECharacteristicMap::setByUUID(BLECharacteristic *pCharacteristic, BLEUUID uuid) {
m_uuidMap.insert(std::pair<BLECharacteristic *, String>(pCharacteristic, uuid.toString()));
} // setByUUID
/**
* @brief Return a string representation of the characteristic map.
* @return A string representation of the characteristic map.
*/
String BLECharacteristicMap::toString() const {
String res;
int count = 0;
char hex[5];
for (auto &myPair : m_uuidMap) {
if (count > 0) {
res += "\n";
}
snprintf(hex, sizeof(hex), "%04x", myPair.first->getHandle());
count++;
res += "handle: 0x";
res += hex;
res += ", uuid: " + myPair.first->getUUID().toString();
}
return res;
} // toString
/***************************************************************************
* Bluedroid functions *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
/**
* @brief Pass the GATT server event onwards to each of the characteristics found in the mapping
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLECharacteristicMap::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
// Invoke the handler for every Service we have.
for (auto &myPair : m_uuidMap) {
myPair.first->handleGATTServerEvent(event, gatts_if, param);
}
} // handleGATTServerEvent
#endif // CONFIG_BLUEDROID_ENABLED
/***************************************************************************
* NimBLE functions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
void BLECharacteristicMap::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg) {
// Invoke the handler for every Service we have.
for (auto &myPair : m_uuidMap) {
myPair.first->handleGATTServerEvent(conn_handle, attr_handle, ctxt, arg);
}
}
#endif // CONFIG_NIMBLE_ENABLED
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
File diff suppressed because it is too large Load Diff
+232
View File
@@ -0,0 +1,232 @@
/*
* BLEClient.h
*
* Created on: Mar 22, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef MAIN_BLECLIENT_H_
#define MAIN_BLECLIENT_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <string.h>
#include <map>
#include <string>
#include "BLERemoteService.h"
#include "BLEService.h"
#include "BLEAddress.h"
#include "BLEAdvertisedDevice.h"
#include "BLEUtils.h"
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gattc_api.h>
#ifndef BLE_ERR_REM_USER_CONN_TERM
#define BLE_ERR_REM_USER_CONN_TERM 0x13
#endif
#endif
/***************************************************************************
* NimBLE includes and definitions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <nimble/ble.h>
#include <host/ble_hs.h>
#include <nimble/nimble_port.h>
#define ESP_GATT_IF_NONE BLE_HS_CONN_HANDLE_NONE
#endif
/***************************************************************************
* NimBLE types *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
typedef uint16_t esp_gatt_if_t;
#endif
/***************************************************************************
* Forward declarations *
***************************************************************************/
class BLERemoteService;
class BLEClientCallbacks;
class BLEAdvertisedDevice;
struct BLETaskData;
/**
* @brief A model of a %BLE client.
*/
class BLEClient {
public:
/***************************************************************************
* Common public properties *
***************************************************************************/
uint16_t m_appId;
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLEClient();
~BLEClient();
bool connect(BLEAdvertisedDevice *device);
bool connectTimeout(BLEAdvertisedDevice *device, uint32_t timeoutMS = portMAX_DELAY);
bool connect(BLEAddress address, uint8_t type = 0xFF, uint32_t timeoutMS = portMAX_DELAY);
bool secureConnection();
int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
BLEAddress getPeerAddress();
int getRssi();
std::map<std::string, BLERemoteService *> *getServices();
BLERemoteService *getService(const char *uuid);
BLERemoteService *getService(BLEUUID uuid);
String getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID);
bool isConnected();
void setClientCallbacks(BLEClientCallbacks *pClientCallbacks);
void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, String value);
String toString();
uint16_t getConnId();
esp_gatt_if_t getGattcIf();
uint16_t getMTU();
bool setMTU(uint16_t mtu);
bool updateConnParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout);
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
static int handleGAPEvent(struct ble_gap_event *event, void *arg);
static int serviceDiscoveredCB(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg);
#endif
private:
friend class BLEDevice;
friend class BLERemoteService;
friend class BLERemoteCharacteristic;
friend class BLERemoteDescriptor;
/***************************************************************************
* Common private properties *
***************************************************************************/
BLEAddress m_peerAddress = BLEAddress((uint8_t *)"\0\0\0\0\0\0");
uint16_t m_conn_id;
bool m_haveServices = false;
bool m_isConnected = false;
BLEClientCallbacks *m_pClientCallbacks;
FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt");
FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt");
FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt");
FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt");
std::map<std::string, BLERemoteService *> m_servicesMap;
std::map<BLERemoteService *, uint16_t> m_servicesMapByInstID;
uint16_t m_mtu = 23;
/***************************************************************************
* Bluedroid private properties *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
esp_gatt_if_t m_gattc_if;
#endif
/***************************************************************************
* NimBLE private properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
int m_lastErr;
int32_t m_connectTimeout;
uint8_t m_terminateFailCount;
ble_gap_conn_params m_pConnParams;
mutable BLETaskData *m_pTaskData;
#endif
/***************************************************************************
* Common private declarations *
***************************************************************************/
void clearServices();
/***************************************************************************
* Bluedroid private declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE private declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
static void dcTimerCb(ble_npl_event *event);
#endif
}; // class BLEClient
/**
* @brief Callbacks associated with a %BLE client.
*/
class BLEClientCallbacks {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
virtual ~BLEClientCallbacks(){};
virtual void onConnect(BLEClient *pClient);
virtual void onDisconnect(BLEClient *pClient);
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
/**
* @brief Callback when the server requests connection parameter updates.
*
* This callback is invoked when the peripheral (server) requests to update
* connection parameters. The central (client) can accept or reject the request.
*
* NOTE: This callback is NimBLE-only. Bluedroid handles parameter update
* requests automatically within the Bluetooth stack without application-level
* intervention.
*
* @param [in] pClient Pointer to the BLEClient instance.
* @param [in] params Pointer to the requested connection parameters.
* @return true to accept the request, false to reject it.
*/
virtual bool onConnParamsUpdateRequest(BLEClient *pClient, const ble_gap_upd_params *params);
#endif
};
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* MAIN_BLECLIENT_H_ */
+110
View File
@@ -0,0 +1,110 @@
/*
* Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
* esp-nimble-cpp, NimBLE-Arduino contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef BLECONNINFO_H_
#define BLECONNINFO_H_
#if defined(CONFIG_NIMBLE_ENABLED)
#include <host/ble_gap.h>
#include "BLEAddress.h"
/**
* @brief Connection information.
*/
class BLEConnInfo {
public:
/** @brief Gets the over-the-air address of the connected peer */
BLEAddress getAddress() const {
return BLEAddress(m_desc.peer_ota_addr);
}
/** @brief Gets the ID address of the connected peer */
BLEAddress getIdAddress() const {
return BLEAddress(m_desc.peer_id_addr);
}
/** @brief Gets the connection handle (also known as the connection id) of the connected peer */
uint16_t getConnHandle() const {
return m_desc.conn_handle;
}
/** @brief Gets the connection interval for this connection (in 1.25ms units) */
uint16_t getConnInterval() const {
return m_desc.conn_itvl;
}
/** @brief Gets the supervision timeout for this connection (in 10ms units) */
uint16_t getConnTimeout() const {
return m_desc.supervision_timeout;
}
/** @brief Gets the allowable latency for this connection (unit = number of intervals) */
uint16_t getConnLatency() const {
return m_desc.conn_latency;
}
/** @brief Gets the maximum transmission unit size for this connection (in bytes) */
uint16_t getMTU() const {
return ble_att_mtu(m_desc.conn_handle);
}
/** @brief Check if we are in the master role in this connection */
bool isMaster() const {
return (m_desc.role == BLE_GAP_ROLE_MASTER);
}
/** @brief Check if we are in the slave role in this connection */
bool isSlave() const {
return (m_desc.role == BLE_GAP_ROLE_SLAVE);
}
/** @brief Check if we are connected to a bonded peer */
bool isBonded() const {
return (m_desc.sec_state.bonded == 1);
}
/** @brief Check if the connection in encrypted */
bool isEncrypted() const {
return (m_desc.sec_state.encrypted == 1);
}
/** @brief Check if the the connection has been authenticated */
bool isAuthenticated() const {
return (m_desc.sec_state.authenticated == 1);
}
/** @brief Gets the key size used to encrypt the connection */
uint8_t getSecKeySize() const {
return m_desc.sec_state.key_size;
}
private:
friend class BLEServer;
friend class BLEClient;
friend class BLECharacteristic;
friend class BLEDescriptor;
ble_gap_conn_desc m_desc{};
BLEConnInfo(){};
BLEConnInfo(ble_gap_conn_desc desc) {
m_desc = desc;
}
};
#endif
#endif // BLECONNINFO_H_
+423
View File
@@ -0,0 +1,423 @@
/*
* BLEDescriptor.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*
* Modified on: Apr 3, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <sstream>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include "sdkconfig.h"
#include <esp_err.h>
#include "BLE2904.h"
#include "BLEService.h"
#include "BLEDescriptor.h"
#include "GeneralUtils.h"
#include "esp32-hal-log.h"
#if defined(CONFIG_BLUEDROID_ENABLED)
#include "BLE2902.h"
#include "BLEDevice.h"
#endif
/***************************************************************************
* Common definitions *
***************************************************************************/
#define NULL_HANDLE (0xffff)
/***************************************************************************
* Common global variables *
***************************************************************************/
static BLEDescriptorCallbacks defaultCallbacks;
/***************************************************************************
* Common functions *
***************************************************************************/
/**
* @brief BLEDescriptor constructor.
*/
BLEDescriptor::BLEDescriptor(const char *uuid, uint16_t len) : BLEDescriptor(BLEUUID(uuid), len) {}
/**
* @brief BLEDescriptor constructor.
*/
BLEDescriptor::BLEDescriptor(BLEUUID uuid, uint16_t max_len) {
m_bleUUID = uuid;
m_handle = NULL_HANDLE; // Handle is initially unknown.
m_pCharacteristic = nullptr; // No initial characteristic.
m_pCallback = nullptr; // No initial callback.
m_value.attr_len = 0; // Initial length is 0.
m_value.attr_max_len = max_len; // Maximum length of the data.
m_value.attr_value = (uint8_t *)malloc(max_len); // Allocate storage for the value.
#if CONFIG_NIMBLE_ENABLED
m_removed = 0;
#endif
} // BLEDescriptor
/**
* @brief BLEDescriptor destructor.
*/
BLEDescriptor::~BLEDescriptor() {
free(m_value.attr_value); // Release the storage we created in the constructor.
} // ~BLEDescriptor
/**
* @brief Execute the creation of the descriptor with the BLE runtime in ESP.
* @param [in] pCharacteristic The characteristic to which to register this descriptor.
*/
void BLEDescriptor::executeCreate(BLECharacteristic *pCharacteristic) {
log_v(">> executeCreate(): %s", toString().c_str());
if (m_handle != NULL_HANDLE) {
log_e("Descriptor already has a handle.");
return;
}
m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service.
#if CONFIG_BLUEDROID_ENABLED
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_AUTO_RSP;
m_semaphoreCreateEvt.take("executeCreate");
esp_err_t errRc =
::esp_ble_gatts_add_char_descr(pCharacteristic->getService()->getHandle(), getUUID().getNative(), (esp_gatt_perm_t)m_permissions, &m_value, &control);
if (errRc != ESP_OK) {
log_e("<< esp_ble_gatts_add_char_descr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreCreateEvt.wait("executeCreate");
#endif
log_v("<< executeCreate");
} // executeCreate
/**
* @brief Get the BLE handle for this descriptor.
* @return The handle for this descriptor.
*/
uint16_t BLEDescriptor::getHandle() const {
return m_handle;
} // getHandle
/**
* @brief Get the length of the value of this descriptor.
* @return The length (in bytes) of the value of this descriptor.
*/
size_t BLEDescriptor::getLength() const {
return m_value.attr_len;
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
BLEUUID BLEDescriptor::getUUID() const {
return m_bleUUID;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return A pointer to the value of this descriptor.
*/
uint8_t *BLEDescriptor::getValue() const {
return m_value.attr_value;
} // getValue
/**
* @brief Get the characteristic this descriptor belongs to.
* @return A pointer to the characteristic this descriptor belongs to.
*/
BLECharacteristic *BLEDescriptor::getCharacteristic() const {
return m_pCharacteristic;
} // getCharacteristic
/**
* @brief Set the callback handlers for this descriptor.
* @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor.
*/
void BLEDescriptor::setCallbacks(BLEDescriptorCallbacks *pCallback) {
log_v(">> setCallbacks: 0x%x", (uint32_t)pCallback);
if (pCallback != nullptr) {
m_pCallback = pCallback;
} else {
m_pCallback = &defaultCallbacks;
}
log_v("<< setCallbacks");
} // setCallbacks
/**
* @brief Set the handle of this descriptor.
* Set the handle of this descriptor to be the supplied value.
* @param [in] handle The handle to be associated with this descriptor.
* @return N/A.
*/
void BLEDescriptor::setHandle(uint16_t handle) {
#if defined(CONFIG_BLUEDROID_ENABLED)
log_v(">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle);
m_handle = handle;
log_v("<< setHandle()");
#endif
#if defined(CONFIG_NIMBLE_ENABLED)
log_w("NimBLE does not support manually setting the handle of a descriptor. Ignoring request.");
#endif
} // setHandle
/**
* @brief Set the value of the descriptor.
* @param [in] data The data to set for the descriptor.
* @param [in] length The length of the data in bytes.
*/
void BLEDescriptor::setValue(const uint8_t *data, size_t length) {
if (length > m_value.attr_max_len) {
log_e("Size %d too large, must be no bigger than %d", length, m_value.attr_max_len);
return;
}
m_semaphoreSetValue.take();
m_value.attr_len = length;
memcpy(m_value.attr_value, data, length);
#if CONFIG_BLUEDROID_ENABLED
if (m_handle != NULL_HANDLE) {
esp_ble_gatts_set_attr_value(m_handle, length, (const uint8_t *)data);
log_d("Set the value in the GATTS database using handle 0x%x", m_handle);
}
#endif
m_semaphoreSetValue.give();
} // setValue
/**
* @brief Set the value of the descriptor.
* @param [in] value The value of the descriptor in string form.
*/
void BLEDescriptor::setValue(const String &value) {
setValue(reinterpret_cast<const uint8_t *>(value.c_str()), value.length());
} // setValue
void BLEDescriptor::setAccessPermissions(uint16_t perm) {
m_permissions = perm;
}
/**
* @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor.
*/
String BLEDescriptor::toString() const {
char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle);
String res = "UUID: " + m_bleUUID.toString() + ", handle: 0x" + hex;
return res;
} // toString
BLEDescriptorCallbacks::~BLEDescriptorCallbacks() = default;
/**
* @brief Callback function to support a read request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void BLEDescriptorCallbacks::onRead(BLEDescriptor *pDescriptor) {
log_d("BLEDescriptorCallbacks", ">> onRead: default");
log_d("BLEDescriptorCallbacks", "<< onRead");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void BLEDescriptorCallbacks::onWrite(BLEDescriptor *pDescriptor) {
log_d("BLEDescriptorCallbacks", ">> onWrite: default");
log_d("BLEDescriptorCallbacks", "<< onWrite");
} // onWrite
/***************************************************************************
* Bluedroid functions *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
/**
* @brief Handle GATT server events for the descripttor.
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLEDescriptor::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
switch (event) {
// ESP_GATTS_ADD_CHAR_DESCR_EVT
//
// add_char_descr:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
{
if (m_pCharacteristic != nullptr && m_bleUUID.equals(BLEUUID(param->add_char_descr.descr_uuid))
&& m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle
&& m_pCharacteristic == m_pCharacteristic->getService()->getLastCreatedCharacteristic()) {
setHandle(param->add_char_descr.attr_handle);
m_semaphoreCreateEvt.give();
}
break;
} // ESP_GATTS_ADD_CHAR_DESCR_EVT
// ESP_GATTS_WRITE_EVT - A request to write the value of a descriptor has arrived.
//
// write:
// - uint16_t conn_id
// - uint16_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool need_rsp
// - bool is_prep
// - uint16_t len
// - uint8_t *value
case ESP_GATTS_WRITE_EVT:
{
if (param->write.handle == m_handle) {
setValue(param->write.value, param->write.len); // Set the value of the descriptor.
// If this is a CCCD (0x2902), persist the value for bonded device reconnection
if (m_bleUUID.equals(BLEUUID((uint16_t)0x2902)) && m_pCharacteristic != nullptr) {
BLE2902 *pCCCD = (BLE2902 *)this;
BLEAddress peerAddr(param->write.bda);
uint16_t charHandle = m_pCharacteristic->getHandle();
pCCCD->persistValue(peerAddr, charHandle);
log_d(
"CCCD write from %s: notifications=%s, indications=%s", peerAddr.toString().c_str(), pCCCD->getNotifications() ? "enabled" : "disabled",
pCCCD->getIndications() ? "enabled" : "disabled"
);
}
if (m_pCallback != nullptr) { // We have completed the write, if there is a user supplied callback handler, invoke it now.
m_pCallback->onWrite(this); // Invoke the onWrite callback handler.
}
} // End of ... this is our handle.
break;
} // ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT - A request to read the value of a descriptor has arrived.
//
// read:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool is_long
// - bool need_rsp
//
case ESP_GATTS_READ_EVT:
{
if (param->read.handle == m_handle) { // If this event is for this descriptor ... process it
if (m_pCallback != nullptr) { // If we have a user supplied callback, invoke it now.
m_pCallback->onRead(this); // Invoke the onRead callback method in the callback handler.
}
} // End of this is our handle
break;
} // ESP_GATTS_READ_EVT
default: break;
} // switch event
} // handleGATTServerEvent
#endif
/***************************************************************************
* NimBLE functions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
/**
* @brief Handle GATT server events for the descriptor.
* @param [in] conn_handle The connection handle.
* @param [in] attr_handle The attribute handle.
* @param [in] ctxt The GATT access context.
* @param [in] arg The argument.
*/
int BLEDescriptor::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
const ble_uuid_t *uuid;
int rc;
BLEDescriptor *pDescriptor = (BLEDescriptor *)arg;
log_d("Descriptor %s %s event", pDescriptor->getUUID().toString().c_str(), ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC ? "Read" : "Write");
uuid = ctxt->chr->uuid;
if (ble_uuid_cmp(uuid, &pDescriptor->getUUID().getNative()->u) == 0) {
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_DSC:
{
// Only call the onRead() callback if the buffer length is greater than 0 and conn_handle is not NONE
// For long reads, follow-up requests will have om_len == 0
if (ctxt->om->om_len > 0 && conn_handle != BLE_HS_CONN_HANDLE_NONE && pDescriptor->m_pCallback != nullptr) {
pDescriptor->m_pCallback->onRead(pDescriptor);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pDescriptor->m_value.attr_value, pDescriptor->m_value.attr_len);
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC:
{
uint16_t att_max_len = pDescriptor->m_value.attr_max_len;
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data, len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while (next != NULL) {
if ((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
pDescriptor->setValue(buf, len);
if (pDescriptor->m_pCallback != nullptr) {
pDescriptor->m_pCallback->onWrite(pDescriptor);
}
return 0;
}
default: break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
#endif
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+187
View File
@@ -0,0 +1,187 @@
/*
* BLEDescriptor.h
*
* Created on: Jun 22, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_
#define COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <string>
#include "BLEUUID.h"
#include "BLECharacteristic.h"
#include "RTOS.h"
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gatts_api.h>
#endif
/***************************************************************************
* NimBLE includes *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <host/ble_att.h>
#include "BLEConnInfo.h"
// Bluedroid compatibility
// NimBLE does not support signed reads and writes
#define ESP_GATT_PERM_READ BLE_ATT_F_READ
#define ESP_GATT_PERM_WRITE BLE_ATT_F_WRITE
#define ESP_GATT_PERM_READ_ENCRYPTED BLE_ATT_F_READ_ENC
#define ESP_GATT_PERM_WRITE_ENCRYPTED BLE_ATT_F_WRITE_ENC
#define ESP_GATT_PERM_READ_AUTHORIZATION BLE_ATT_F_READ_AUTHOR
#define ESP_GATT_PERM_WRITE_AUTHORIZATION BLE_ATT_F_WRITE_AUTHOR
#define ESP_GATT_PERM_READ_ENC_MITM BLE_ATT_F_READ_AUTHEN
#define ESP_GATT_PERM_WRITE_ENC_MITM BLE_ATT_F_WRITE_AUTHEN
#endif
/***************************************************************************
* NimBLE types *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
typedef struct {
uint16_t attr_max_len; /*!< attribute max value length */
uint16_t attr_len; /*!< attribute current value length */
uint8_t *attr_value; /*!< the pointer to attribute value */
} esp_attr_value_t;
#endif
/***************************************************************************
* Forward declarations *
***************************************************************************/
class BLEService;
class BLECharacteristic;
class BLEDescriptorCallbacks;
/**
* @brief A model of a %BLE descriptor.
*/
class BLEDescriptor {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
BLEDescriptor(const char *uuid, uint16_t max_len = 100);
BLEDescriptor(BLEUUID uuid, uint16_t max_len = 100);
virtual ~BLEDescriptor();
uint16_t getHandle() const; // Get the handle of the descriptor.
size_t getLength() const; // Get the length of the value of the descriptor.
BLEUUID getUUID() const; // Get the UUID of the descriptor.
uint8_t *getValue() const; // Get a pointer to the value of the descriptor.
BLECharacteristic *getCharacteristic() const; // Get the characteristic that this descriptor belongs to.
void setAccessPermissions(uint16_t perm); // Set the permissions of the descriptor.
void setCallbacks(BLEDescriptorCallbacks *pCallbacks); // Set callbacks to be invoked for the descriptor.
void setValue(const uint8_t *data, size_t size); // Set the value of the descriptor as a pointer to data.
void setValue(const String &value); // Set the value of the descriptor as a data buffer.
String toString() const; // Convert the descriptor to a string representation.
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
static int handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
#endif
private:
friend class BLEDescriptorMap;
friend class BLECharacteristic;
friend class BLEService;
friend class BLE2901;
friend class BLE2902;
friend class BLE2904;
/***************************************************************************
* Common private properties *
***************************************************************************/
BLEUUID m_bleUUID;
uint16_t m_handle;
esp_attr_value_t m_value;
BLEDescriptorCallbacks *m_pCallback;
BLECharacteristic *m_pCharacteristic;
FreeRTOS::Semaphore m_semaphoreSetValue = FreeRTOS::Semaphore("SetValue");
/***************************************************************************
* Bluedroid private properties *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
uint16_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
#endif
/***************************************************************************
* NimBLE private properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
uint8_t m_permissions = HA_FLAG_PERM_RW;
uint8_t m_removed = 0;
#endif
/***************************************************************************
* Common private declarations *
***************************************************************************/
void executeCreate(BLECharacteristic *pCharacteristic);
void setHandle(uint16_t handle);
}; // BLEDescriptor
/**
* @brief Callbacks that can be associated with a %BLE descriptors to inform of events.
*
* When a server application creates a %BLE descriptor, we may wish to be informed when there is either
* a read or write request to the descriptors value. An application can register a
* sub-classed instance of this class and will be notified when such an event happens.
*/
class BLEDescriptorCallbacks {
public:
/***************************************************************************
* Common public declarations *
***************************************************************************/
virtual ~BLEDescriptorCallbacks();
virtual void onRead(BLEDescriptor *pDescriptor);
virtual void onWrite(BLEDescriptor *pDescriptor);
};
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */
+215
View File
@@ -0,0 +1,215 @@
/*
* BLEDescriptorMap.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <sstream>
#include <iomanip>
#include "BLECharacteristic.h"
#include "BLEDescriptor.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gatts_api.h> // ESP32 BLE
#endif
/***************************************************************************
* NimBLE includes *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include "host/ble_gatt.h"
#endif
/***************************************************************************
* Common functions *
***************************************************************************/
/**
* @brief Return the descriptor by UUID.
* @param [in] UUID The UUID to look up the descriptor.
* @return The descriptor. If not present, then nullptr is returned.
*/
BLEDescriptor *BLEDescriptorMap::getByUUID(const char *uuid) const {
return getByUUID(BLEUUID(uuid));
}
/**
* @brief Return the descriptor by UUID.
* @param [in] UUID The UUID to look up the descriptor.
* @return The descriptor. If not present, then nullptr is returned.
*/
BLEDescriptor *BLEDescriptorMap::getByUUID(BLEUUID uuid) const {
for (auto &myPair : m_uuidMap) {
if (myPair.first->getUUID().equals(uuid)) {
return myPair.first;
}
}
//return m_uuidMap.at(uuid.toString());
return nullptr;
} // getByUUID
/**
* @brief Return the descriptor by handle.
* @param [in] handle The handle to look up the descriptor.
* @return The descriptor.
*/
BLEDescriptor *BLEDescriptorMap::getByHandle(uint16_t handle) const {
return m_handleMap.at(handle);
} // getByHandle
/**
* @brief Set the descriptor by UUID.
* @param [in] uuid The uuid of the descriptor.
* @param [in] characteristic The descriptor to cache.
* @return N/A.
*/
void BLEDescriptorMap::setByUUID(const char *uuid, BLEDescriptor *pDescriptor) {
m_uuidMap.insert(std::pair<BLEDescriptor *, String>(pDescriptor, uuid));
} // setByUUID
/**
* @brief Set the descriptor by UUID.
* @param [in] uuid The uuid of the descriptor.
* @param [in] characteristic The descriptor to cache.
* @return N/A.
*/
void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) {
m_uuidMap.insert(std::pair<BLEDescriptor *, String>(pDescriptor, uuid.toString()));
} // setByUUID
/**
* @brief Set the descriptor by handle.
* @param [in] handle The handle of the descriptor.
* @param [in] descriptor The descriptor to cache.
* @return N/A.
*/
void BLEDescriptorMap::setByHandle(uint16_t handle, BLEDescriptor *pDescriptor) {
m_handleMap.insert(std::pair<uint16_t, BLEDescriptor *>(handle, pDescriptor));
} // setByHandle
/**
* @brief Get the number of registered descriptors.
* @return The number of registered descriptors.
*/
int BLEDescriptorMap::getRegisteredDescriptorCount() const {
return m_uuidMap.size();
}
/**
* @brief Remove a descriptor from the map.
* @param [in] pDescriptor The descriptor to remove.
* @return N/A.
*/
void BLEDescriptorMap::removeDescriptor(BLEDescriptor *pDescriptor) {
m_uuidMap.erase(pDescriptor);
m_handleMap.erase(pDescriptor->getHandle());
}
/**
* @brief Return a string representation of the descriptor map.
* @return A string representation of the descriptor map.
*/
String BLEDescriptorMap::toString() const {
String res;
char hex[5];
int count = 0;
for (auto &myPair : m_uuidMap) {
if (count > 0) {
res += "\n";
}
snprintf(hex, sizeof(hex), "%04x", myPair.first->getHandle());
count++;
res += "handle: 0x";
res += hex;
res += ", uuid: " + myPair.first->getUUID().toString();
}
return res;
} // toString
/**
* @brief Get the first descriptor in the map.
* @return The first descriptor in the map.
*/
BLEDescriptor *BLEDescriptorMap::getFirst() {
m_iterator = m_uuidMap.begin();
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLEDescriptor *pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getFirst
/**
* @brief Get the next descriptor in the map.
* @return The next descriptor in the map.
*/
BLEDescriptor *BLEDescriptorMap::getNext() {
if (m_iterator == m_uuidMap.end()) {
return nullptr;
}
BLEDescriptor *pRet = m_iterator->first;
m_iterator++;
return pRet;
} // getNext
/***************************************************************************
* Bluedroid functions *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
/**
* @brief Pass the GATT server event onwards to each of the descriptors found in the mapping
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLEDescriptorMap::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
// Invoke the handler for every descriptor we have.
for (auto &myPair : m_uuidMap) {
myPair.first->handleGATTServerEvent(event, gatts_if, param);
}
} // handleGATTServerEvent
#endif
/***************************************************************************
* NimBLE functions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
void BLEDescriptorMap::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg) {
// Invoke the handler for every descriptor we have.
for (auto &myPair : m_uuidMap) {
myPair.first->handleGATTServerEvent(conn_handle, attr_handle, ctxt, arg);
}
}
#endif
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
File diff suppressed because it is too large Load Diff
+304
View File
@@ -0,0 +1,304 @@
/*
* BLEDevice.h
*
* Created on: Mar 16, 2017
* Author: kolban
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on kolban's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef MAIN_BLEDevice_H_
#define MAIN_BLEDevice_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
/***************************************************************************
* Common includes *
***************************************************************************/
#include <map>
#if defined(SOC_BLE_SUPPORTED)
#include <esp_bt.h>
#else
// For ESP32-P4 and other chips without native BLE support
// Define minimal types needed for interface compatibility
typedef int esp_power_level_t;
typedef int esp_ble_power_type_t;
#define ESP_BLE_PWR_TYPE_DEFAULT 0
#define ESP_PWR_LVL_N12 0
#endif
#include "WString.h"
#include "BLEServer.h"
#include "BLEClient.h"
#include "BLEUtils.h"
#include "BLEScan.h"
#include "BLEAdvertising.h"
#include "BLESecurity.h"
#include "BLEAddress.h"
#include "BLEUtils.h"
#include "BLEUUID.h"
#include "BLEAdvertisedDevice.h"
/***************************************************************************
* Common definitions *
***************************************************************************/
enum class BLEStack {
BLUEDROID,
NIMBLE,
UNKNOWN
};
/***************************************************************************
* Bluedroid includes *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_gatts_api.h>
#endif
/***************************************************************************
* NimBLE includes and definitions *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
#include <host/ble_gap.h>
#define ESP_GATT_IF_NONE BLE_HS_CONN_HANDLE_NONE
// Hosted HCI transport implementation is provided in BLEHostedHCI.cpp
// and is automatically linked when building for ESP32-P4
// NimBLE configuration compatibility macros
#if defined(CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR
#endif
#if defined(CONFIG_SCAN_DUPLICATE_BY_ADV_DATA) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA)
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA CONFIG_SCAN_DUPLICATE_BY_ADV_DATA
#endif
#if defined(CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR
#endif
#if defined(CONFIG_SCAN_DUPLICATE_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_SCAN_DUPLICATE_TYPE
#endif
#if defined(CONFIG_BT_CTRL_SCAN_DUPL_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_BT_CTRL_SCAN_DUPL_TYPE
#endif
#if defined(CONFIG_BT_LE_SCAN_DUPL_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE)
#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_BT_LE_SCAN_DUPL_TYPE
#endif
#if defined(CONFIG_DUPLICATE_SCAN_CACHE_SIZE) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE)
#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_DUPLICATE_SCAN_CACHE_SIZE
#endif
#if defined(CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE)
#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE
#endif
#if defined(CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE)
#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT
#endif
#if defined(CONFIG_NIMBLE_MAX_CONNECTIONS) && !defined(CONFIG_BT_NIMBLE_MAX_CONNECTIONS)
#define CONFIG_BT_NIMBLE_MAX_CONNECTIONS CONFIG_NIMBLE_MAX_CONNECTIONS
#endif
#endif
/***************************************************************************
* Forward declarations *
***************************************************************************/
class BLEAddress;
class BLEDeviceCallbacks;
class BLESecurityCallbacks;
class BLEServer;
class BLEScan;
class BLEAdvertising;
class BLEClient;
class BLESecurity;
/***************************************************************************
* Bluedroid type definitions *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
typedef void (*gap_event_handler)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
typedef void (*gattc_event_handler)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
typedef void (*gatts_event_handler)(esp_gatts_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gatts_cb_param_t *param);
#endif
/***************************************************************************
* NimBLE type definitions and externals *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
extern "C" void ble_store_config_init(void);
typedef int (*gap_event_handler)(struct ble_gap_event *event, void *param);
#endif
class BLEDevice {
public:
/***************************************************************************
* Common public properties *
***************************************************************************/
static uint16_t m_appId;
static uint16_t m_localMTU;
static gap_event_handler m_customGapHandler;
/***************************************************************************
* Bluedroid public properties *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
static gattc_event_handler m_customGattcHandler;
static gatts_event_handler m_customGattsHandler;
#endif
/***************************************************************************
* Common public declarations *
***************************************************************************/
static BLEClient *createClient();
static BLEServer *createServer();
static BLEAddress getAddress();
static BLEServer *getServer();
static BLEScan *getScan();
static String getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID);
static bool init(String deviceName = "");
static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType = ESP_BLE_PWR_TYPE_DEFAULT);
static int getPower(esp_ble_power_type_t powerType = ESP_BLE_PWR_TYPE_DEFAULT);
static void setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, String value);
static String toString();
static void whiteListAdd(BLEAddress address);
static void whiteListRemove(BLEAddress address);
static void setSecurityCallbacks(BLESecurityCallbacks *pCallbacks);
static esp_err_t setMTU(uint16_t mtu);
static uint16_t getMTU();
static bool getInitialized();
static bool getPeerIRK(BLEAddress peerAddress, uint8_t *irk);
static String getPeerIRKString(BLEAddress peerAddress);
static String getPeerIRKBase64(BLEAddress peerAddress);
static String getPeerIRKReverse(BLEAddress peerAddress);
static BLEAdvertising *getAdvertising();
static void startAdvertising();
static void stopAdvertising();
static std::map<uint16_t, conn_status_t> getPeerDevices(bool client);
static void addPeerDevice(void *peer, bool is_client, uint16_t conn_id);
static void updatePeerDevice(void *peer, bool _client, uint16_t conn_id);
static void removePeerDevice(uint16_t conn_id, bool client);
static BLEClient *getClientByID(uint16_t conn_id);
static BLEClient *getClientByAddress(BLEAddress address);
static BLEClient *getClientByGattIf(uint16_t conn_id);
static void setCustomGapHandler(gap_event_handler handler);
static void deinit(bool release_memory = false);
static BLEStack getBLEStack();
static String getBLEStackString();
static bool isHostedBLE();
/***************************************************************************
* Bluedroid public declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
static void setCustomGattcHandler(gattc_event_handler handler);
static void setCustomGattsHandler(gatts_event_handler handler);
#endif
/***************************************************************************
* NimBLE public declarations *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
static void onReset(int reason);
static void onSync(void);
static void host_task(void *param);
static String getDeviceName();
static bool setOwnAddrType(uint8_t type);
static bool setOwnAddr(BLEAddress &addr);
static bool setOwnAddr(uint8_t *addr);
static void setDeviceCallbacks(BLEDeviceCallbacks *cb);
static bool onWhiteList(BLEAddress &address);
#if CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE
// Set SDIO pins for connection to external ESP MCU
static bool setPins(int8_t clk, int8_t cmd, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst);
#endif
#endif
private:
friend class BLEClient;
friend class BLEScan;
friend class BLEServer;
friend class BLECharacteristic;
friend class BLEAdvertising;
/***************************************************************************
* Common private properties *
***************************************************************************/
static BLEServer *m_pServer;
static BLEScan *m_pScan;
static BLEClient *m_pClient;
static BLESecurityCallbacks *m_securityCallbacks;
static BLEAdvertising *m_bleAdvertising;
static std::map<uint16_t, conn_status_t> m_connectedClientsMap;
static portMUX_TYPE mux;
/***************************************************************************
* NimBLE private properties *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
static uint8_t m_ownAddrType;
static bool m_synced;
static std::vector<BLEAddress> m_whiteList;
static BLEDeviceCallbacks defaultDeviceCallbacks;
static BLEDeviceCallbacks *m_pDeviceCallbacks;
static ble_gap_event_listener m_listener;
static String m_deviceName;
#endif
/***************************************************************************
* Bluedroid private declarations *
***************************************************************************/
#if defined(CONFIG_BLUEDROID_ENABLED)
static esp_gatt_if_t getGattcIF();
static void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static void gattServerEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
#endif
}; // class BLE
/***************************************************************************
* NimBLE specific classes *
***************************************************************************/
#if defined(CONFIG_NIMBLE_ENABLED)
class BLEDeviceCallbacks {
public:
virtual ~BLEDeviceCallbacks(){};
virtual int onStoreStatus(struct ble_store_status_event *event, void *arg);
};
#endif
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* MAIN_BLEDevice_H_ */
+184
View File
@@ -0,0 +1,184 @@
/*
* BLEEddystoneTLM.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
* Edited on: Mar 20, 2020 by beegee-tokyo
* Fix temperature value (8.8 fixed format)
* Fix time stamp (0.1 second resolution)
* Fixes based on EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on pcbreflux's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
#include <string.h>
#include <stdio.h>
#include "esp32-hal-log.h"
#include "BLEEddystoneTLM.h"
static const char LOG_TAG[] = "BLEEddystoneTLM";
BLEEddystoneTLM::BLEEddystoneTLM() {
m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE;
m_eddystoneData.version = 0;
m_eddystoneData.volt = 3300; // 3300mV = 3.3V
m_eddystoneData.temp = (uint16_t)((float)23.00) / 256;
m_eddystoneData.advCount = 0;
m_eddystoneData.tmil = 0;
} // BLEEddystoneTLM
BLEEddystoneTLM::BLEEddystoneTLM(BLEAdvertisedDevice *advertisedDevice) {
char *payload = (char *)advertisedDevice->getPayload();
for (int i = 0; i < advertisedDevice->getPayloadLength(); ++i) {
if (payload[i] == 0x16 && advertisedDevice->getPayloadLength() >= i + 2 + sizeof(m_eddystoneData) && payload[i + 1] == 0xAA && payload[i + 2] == 0xFE
&& payload[i + 3] == 0x20) {
log_d("Eddystone TLM data frame starting at byte [%d]", i + 3);
setData(String(payload + i + 3, sizeof(m_eddystoneData)));
break;
}
}
}
String BLEEddystoneTLM::getData() {
return String((char *)&m_eddystoneData, sizeof(m_eddystoneData));
} // getData
BLEUUID BLEEddystoneTLM::getUUID() {
return beaconUUID;
} // getUUID
uint8_t BLEEddystoneTLM::getVersion() {
return m_eddystoneData.version;
} // getVersion
uint16_t BLEEddystoneTLM::getVolt() {
return ENDIAN_CHANGE_U16(m_eddystoneData.volt);
} // getVolt
float BLEEddystoneTLM::getTemp() {
return EDDYSTONE_TEMP_U16_TO_FLOAT(m_eddystoneData.temp);
} // getTemp
uint16_t BLEEddystoneTLM::getRawTemp() {
return ENDIAN_CHANGE_U16(m_eddystoneData.temp);
} // getRawTemp
uint32_t BLEEddystoneTLM::getCount() {
return ENDIAN_CHANGE_U32(m_eddystoneData.advCount);
} // getCount
uint32_t BLEEddystoneTLM::getTime() {
return (ENDIAN_CHANGE_U32(m_eddystoneData.tmil)) / 10;
} // getTime
String BLEEddystoneTLM::toString() {
String out = "";
uint32_t rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil);
char val[12];
out += "Version " + String(m_eddystoneData.version);
//snprintf(val, sizeof(val), "%d", m_eddystoneData.version);
//out += val;
out += "\n";
out += "Battery Voltage "; // + ENDIAN_CHANGE_U16(m_eddystoneData.volt);
snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U16(m_eddystoneData.volt));
out += val;
out += " mV\n";
out += "Temperature ";
snprintf(val, sizeof(val), "%.2f", ((int16_t)ENDIAN_CHANGE_U16(m_eddystoneData.temp)) / 256.0f);
out += val;
out += " C\n";
out += "Adv. Count ";
snprintf(val, sizeof(val), "%ld", ENDIAN_CHANGE_U32(m_eddystoneData.advCount));
out += val;
out += "\n";
out += "Time in seconds ";
snprintf(val, sizeof(val), "%ld", rawsec / 10);
out += val;
out += "\n";
out += "Time ";
snprintf(val, sizeof(val), "%04ld", rawsec / 864000);
out += val;
out += ".";
snprintf(val, sizeof(val), "%02ld", (rawsec / 36000) % 24);
out += val;
out += ":";
snprintf(val, sizeof(val), "%02ld", (rawsec / 600) % 60);
out += val;
out += ":";
snprintf(val, sizeof(val), "%02ld", (rawsec / 10) % 60);
out += val;
out += "\n";
return out;
} // toString
/**
* Set the raw data for the beacon record.
* Example:
* uint8_t *payload = advertisedDevice.getPayload();
* eddystoneTLM.setData(String((char*)payload+22, advertisedDevice.getPayloadLength() - 22));
* Note: the offset 22 works for current implementation of example BLE_EddystoneTLM Beacon.ino, however
* the position is not static and it is programmers responsibility to align the data.
* Data frame:
* | Field || Len | Type | UUID | EddyStone TLM |
* | Offset || 0 | 1 | 2 | 4 |
* | Len || 1 B | 1 B | 2 B | 14 B |
* | Data || ?? | ?? | 0xAA | 0xFE | ??? |
*
* EddyStone TLM frame:
* | Field || Type | Version | Batt mV | Beacon temp | Cnt since boot | Time since boot |
* | Offset || 0 | 1 | 2 | 4 | 6 | 10 |
* | Len || 1 B | 1 B | 2 B | 2 B | 4 B | 4 B |
* | Data || 0x20 | ?? | ?? | ?? | ?? | ?? | | | | | | | | |
*/
void BLEEddystoneTLM::setData(String data) {
if (data.length() != sizeof(m_eddystoneData)) {
log_e("Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_eddystoneData));
return;
}
memcpy(&m_eddystoneData, data.c_str(), data.length());
} // setData
void BLEEddystoneTLM::setUUID(BLEUUID l_uuid) {
beaconUUID = l_uuid;
} // setUUID
void BLEEddystoneTLM::setVersion(uint8_t version) {
m_eddystoneData.version = version;
} // setVersion
// Set voltage in ESP32 native Big endian and convert it to little endian used for BLE Frame
void BLEEddystoneTLM::setVolt(uint16_t volt) {
m_eddystoneData.volt = ENDIAN_CHANGE_U16(volt);
} // setVolt
void BLEEddystoneTLM::setTemp(float temp) {
m_eddystoneData.temp = EDDYSTONE_TEMP_FLOAT_TO_U16(temp);
} // setTemp
void BLEEddystoneTLM::setCount(uint32_t advCount) {
m_eddystoneData.advCount = advCount;
} // setCount
void BLEEddystoneTLM::setTime(uint32_t tmil) {
m_eddystoneData.tmil = tmil;
} // setTime
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+70
View File
@@ -0,0 +1,70 @@
/*
* BLEEddystoneTLM.h
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on pcbreflux's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef _BLEEddystoneTLM_H_
#define _BLEEddystoneTLM_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
#include "BLEUUID.h"
#include <BLEAdvertisedDevice.h>
#define EDDYSTONE_TLM_FRAME_TYPE 0x20
#define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8))
#define ENDIAN_CHANGE_U32(x) ((((x) & 0xFF000000) >> 24) + (((x) & 0x00FF0000) >> 8)) + ((((x) & 0xFF00) << 8) + (((x) & 0xFF) << 24))
#define EDDYSTONE_TEMP_U16_TO_FLOAT(tempU16) (((int16_t)ENDIAN_CHANGE_U16(tempU16)) / 256.0f)
#define EDDYSTONE_TEMP_FLOAT_TO_U16(tempFloat) (ENDIAN_CHANGE_U16(((int)((tempFloat) * 256))))
/**
* @brief Representation of a beacon.
* See:
* * https://github.com/google/eddystone
*/
class BLEEddystoneTLM {
public:
BLEEddystoneTLM();
BLEEddystoneTLM(BLEAdvertisedDevice *advertisedDevice);
String getData();
BLEUUID getUUID();
uint8_t getVersion();
uint16_t getVolt();
float getTemp();
uint16_t getRawTemp();
uint32_t getCount();
uint32_t getTime();
String toString();
void setData(String data);
void setUUID(BLEUUID l_uuid);
void setVersion(uint8_t version);
void setVolt(uint16_t volt);
void setTemp(float temp);
void setCount(uint32_t advCount);
void setTime(uint32_t tmil);
private:
BLEUUID beaconUUID;
struct {
uint8_t frameType;
uint8_t version;
uint16_t volt;
uint16_t temp;
uint32_t advCount;
uint32_t tmil;
} __attribute__((packed)) m_eddystoneData;
}; // BLEEddystoneTLM
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* _BLEEddystoneTLM_H_ */
+291
View File
@@ -0,0 +1,291 @@
/*
* BLEEddystoneURL.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*
* Upgraded on: Feb 20, 2023
* By: Tomas Pilny
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on pcbreflux's and h2zero's work)
* Description: Added support for NimBLE
*/
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
#include <string.h>
#include "esp32-hal-log.h"
#include "BLEEddystoneURL.h"
String EDDYSTONE_URL_PREFIX[] = {
"http://www.", // 0x00
"https://www.", // 0x01
"http://", // 0x02
"https://", // 0x03
"" // Any other code number results in empty string
};
String EDDYSTONE_URL_SUFFIX[] = {
".com/", // 0x00
".org/", // 0x01
".edu/", // 0x02
".net/", // 0x03
".info/", // 0x04
".biz/", // 0x05
".gov/", // 0x06
".com", // 0x07
".org", // 0x08
".edu", // 0x09
".net", // 0x0A
".info", // 0x0B
".biz", // 0x0C
".gov", // 0x0D
"" // Any other code number results in empty string
};
BLEEddystoneURL::BLEEddystoneURL() {
lengthURL = 0;
m_eddystoneData.advertisedTxPower = 0;
memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url));
_initHeadder();
} // BLEEddystoneURL
BLEEddystoneURL::BLEEddystoneURL(BLEAdvertisedDevice *advertisedDevice) {
const char *payload = (char *)advertisedDevice->getPayload();
memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url));
lengthURL = 0;
m_eddystoneData.advertisedTxPower = 0;
for (int i = 0; i < advertisedDevice->getPayloadLength(); ++i) {
if (payload[i] == 0x16 && advertisedDevice->getPayloadLength() >= i + 2 + sizeof(m_eddystoneData) && payload[i + 1] == 0xAA && payload[i + 2] == 0xFE
&& payload[i + 3] == 0x10) {
lengthURL = payload[i - 1] - 5; // Subtracting 5 Bytes containing header and other data which are not actual URL data
m_eddystoneData.advertisedTxPower = payload[i + 1];
if (lengthURL <= 18) {
setData(String(payload + i + 4, lengthURL + 1));
} else {
log_e("Too long URL %d", lengthURL);
}
}
}
_initHeadder();
}
String BLEEddystoneURL::getData() {
return String((char *)&m_eddystoneData, sizeof(m_eddystoneData));
} // getData
String BLEEddystoneURL::getFrame() {
BLEHeadder[7] = lengthURL + 5; // Fill in real: Type + 2B UUID + Frame Type + Tx power + URL (note: the Byte holding the length does not count itself)
String frame(BLEHeadder, sizeof(BLEHeadder));
frame += String((char *)&m_eddystoneData, lengthURL + 1); // + 1 for TX power
return frame;
} // getFrame
BLEUUID BLEEddystoneURL::getUUID() {
uint16_t uuid = (((uint16_t)BLEHeadder[10]) << 8) | BLEHeadder[9];
return BLEUUID(uuid);
} // getUUID
int8_t BLEEddystoneURL::getPower() {
return m_eddystoneData.advertisedTxPower;
} // getPower
String BLEEddystoneURL::getURL() {
return String((char *)&m_eddystoneData.url, lengthURL);
} // getURL
String BLEEddystoneURL::getPrefix() {
if (m_eddystoneData.url[0] <= 0x03) {
return EDDYSTONE_URL_PREFIX[m_eddystoneData.url[0]];
} else {
return "";
}
}
String BLEEddystoneURL::getSuffix() {
if (m_eddystoneData.url[lengthURL - 1] <= 0x0D) {
return EDDYSTONE_URL_SUFFIX[m_eddystoneData.url[lengthURL - 1]];
} else {
return "";
}
}
String BLEEddystoneURL::getDecodedURL() {
std::string decodedURL = "";
decodedURL += getPrefix().c_str();
if (decodedURL.length() == 0) { // No prefix extracted - interpret byte [0] as character
decodedURL += (char)m_eddystoneData.url[0];
}
for (int i = 1; i < lengthURL; i++) {
if (m_eddystoneData.url[i] >= 33 && m_eddystoneData.url[i] < 127) {
decodedURL += (char)m_eddystoneData.url[i];
} else {
if (i != lengthURL - 1 || m_eddystoneData.url[i] > 0x0D) { // Ignore last Byte and values used for suffix
log_e("Unexpected unprintable char in URL 0x%02X: m_eddystoneData.url[%d]", m_eddystoneData.url[i], i);
}
}
}
decodedURL += getSuffix().c_str();
return String(decodedURL.c_str());
} // getDecodedURL
/**
* Set the raw data for the beacon record.
* Example:
* uint8_t *payload = advertisedDevice.getPayload();
* eddystoneTLM.setData(String((char*)payload+11, advertisedDevice.getPayloadLength() - 11));
* Note: the offset 11 works for current implementation of example BLE_EddystoneTLM Beacon.ino, however
* the position is not static and it is programmers responsibility to align the data.
* Data frame:
* | Field || Len | Type | UUID | EddyStone URL |
* | Offset || 0 | 1 | 2 | 4 |
* | Len || 1 B | 1 B | 2 B | up to 20 B |
* | Data || ?? | ?? | 0xAA | 0xFE | ??? |
*
* EddyStone TLM frame:
* | Field || Type | TX Power | URL prefix | URL |
* | Offset || 0 | 1 | 2 | 3 |
* | Len || 1 B | 1 B | 1 B | 0-17 B |
* | Data || 0x10 | ?? | ?? | ?? |
*/
void BLEEddystoneURL::setData(String data) {
if (data.length() > sizeof(m_eddystoneData)) {
log_e("Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData));
return;
}
memset(&m_eddystoneData, 0, sizeof(m_eddystoneData));
memcpy(&m_eddystoneData, data.c_str(), data.length());
lengthURL = data.length() - (sizeof(m_eddystoneData) - sizeof(m_eddystoneData.url));
} // setData
void BLEEddystoneURL::setUUID(BLEUUID l_uuid) {
#if defined(CONFIG_BLUEDROID_ENABLED)
uint16_t beaconUUID = l_uuid.getNative()->uuid.uuid16;
#elif defined(CONFIG_NIMBLE_ENABLED)
uint16_t beaconUUID = l_uuid.getNative()->u16.value;
#endif
BLEHeadder[10] = beaconUUID >> 8;
BLEHeadder[9] = beaconUUID & 0x00FF;
} // setUUID
void BLEEddystoneURL::setPower(esp_power_level_t advertisedTxPower) {
int tx_power = 0;
#if SOC_BLE_SUPPORTED
switch (advertisedTxPower) {
case ESP_PWR_LVL_N12: tx_power = -12; break;
case ESP_PWR_LVL_N9: tx_power = -9; break;
case ESP_PWR_LVL_N6: tx_power = -6; break;
case ESP_PWR_LVL_N3: tx_power = -3; break;
case ESP_PWR_LVL_N0: tx_power = 0; break;
case ESP_PWR_LVL_P3: tx_power = +3; break;
case ESP_PWR_LVL_P6: tx_power = +6; break;
case ESP_PWR_LVL_P9: tx_power = +9; break;
default: tx_power = 0; break;
}
#else
log_w("setPower not supported with hosted HCI - power controlled by co-processor");
#endif
m_eddystoneData.advertisedTxPower = int8_t((tx_power - -100) / 2);
} // setPower
void BLEEddystoneURL::setPower(int8_t advertisedTxPower) {
m_eddystoneData.advertisedTxPower = advertisedTxPower;
} // setPower
// Set URL bytes including prefix and optional suffix
// | Field | Prefix | URL + optional Suffix |
// | Offset | 0 | 1 |
// | Length | 1 B | 0 - 17 B |
// | Example | 0x02 | 0x676F6F676C65 0x07 |
// | Decoded | http:// | g o o g l e .com |
void BLEEddystoneURL::setURL(String url) {
if (url.length() > sizeof(m_eddystoneData.url)) {
log_e("Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url));
return;
}
memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url));
memcpy(m_eddystoneData.url, url.c_str(), url.length());
lengthURL = url.length();
} // setURL
int BLEEddystoneURL::setSmartURL(String url) {
if (url.length() == 0) {
log_e("URL String has 0 length");
return 0; // ERROR
}
for (auto character : url) {
if (!isPrintable(character)) {
log_e("URL contains unprintable character(s)");
return 0; // ERROR
}
}
bool hasPrefix = false;
bool hasSuffix = false;
m_eddystoneData.url[0] = 0x00; // Init with default prefix "http://www."
uint8_t suffix = 0x0E; // Init with empty string
log_d("Encode url \"%s\" with length %d", url.c_str(), url.length());
for (uint8_t i = 0; i < 4; ++i) {
if (url.substring(0, EDDYSTONE_URL_PREFIX[i].length()) == EDDYSTONE_URL_PREFIX[i]) {
m_eddystoneData.url[0] = i;
hasPrefix = true;
break;
}
}
if (hasPrefix == false) {
log_w("Prefix not found - using default prefix \"http://www.\" = 0x00\n\tNote: URL must contain one of the prefixes: \"http://www.\", \"https://www.\", "
"\"http://\", \"https://\"");
}
for (uint8_t i = 0; i < 0x0E; ++i) {
std::string std_url(url.c_str());
std::string std_suffix(EDDYSTONE_URL_SUFFIX[i].c_str());
size_t found_pos = std_url.find(std_suffix);
if (found_pos != std::string::npos) {
hasSuffix = true;
suffix = i;
break;
}
}
size_t baseUrlLen = url.length() - (hasPrefix ? EDDYSTONE_URL_PREFIX[m_eddystoneData.url[0]].length() : 0) - EDDYSTONE_URL_SUFFIX[suffix].length();
lengthURL = baseUrlLen + 1 + (hasSuffix ? 1 : 0);
if (lengthURL > 18) {
log_e("Encoded URL is too long %d B - max 18 B", lengthURL);
return 0; // ERROR
}
String baseUrl = url.substring(
(hasPrefix ? EDDYSTONE_URL_PREFIX[m_eddystoneData.url[0]].length() : 0),
baseUrlLen + (hasPrefix ? EDDYSTONE_URL_PREFIX[m_eddystoneData.url[0]].length() : 0)
);
memcpy((void *)(m_eddystoneData.url + 1), (void *)baseUrl.c_str(), baseUrl.length()); // substr for Arduino String
if (hasSuffix) {
m_eddystoneData.url[1 + baseUrlLen] = suffix;
}
return 1; // OK
} // setSmartURL
void BLEEddystoneURL::_initHeadder() {
BLEHeadder[0] = 0x02; // Len
BLEHeadder[1] = 0x01; // Type Flags
BLEHeadder[2] = 0x06; // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04
BLEHeadder[3] = 0x03; // Len
BLEHeadder[4] = 0x03; // Type 16-Bit UUID
BLEHeadder[5] = 0xAA; // Eddystone UUID 2 -> 0xFEAA LSB
BLEHeadder[6] = 0xFE; // Eddystone UUID 1 MSB
BLEHeadder[7] = 0x00; // Length of Beacon Data shall be calculated later
BLEHeadder[8] = 0x16; // Type Service Data
BLEHeadder[9] = 0xAA; // Eddystone UUID 2 -> 0xFEAA LSB
BLEHeadder[10] = 0xFE; // Eddystone UUID 1 MSB
BLEHeadder[11] = 0x10; // Eddystone Frame Type - URL
}
#endif
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
+72
View File
@@ -0,0 +1,72 @@
/*
* BLEEddystoneURL.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*
* Upgraded on: Feb 20, 2023
* By: Tomas Pilny
*
* Modified on: Feb 18, 2025
* Author: lucasssvaz (based on pcbreflux's and h2zero's work)
* Description: Added support for NimBLE
*/
#ifndef _BLEEddystoneURL_H_
#define _BLEEddystoneURL_H_
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if defined(SOC_BLE_SUPPORTED) || defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE)
#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)
#include "BLEUUID.h"
#include <BLEAdvertisedDevice.h>
#if SOC_BLE_SUPPORTED
#include <esp_bt.h>
#endif
#define EDDYSTONE_URL_FRAME_TYPE 0x10
extern String EDDYSTONE_URL_PREFIX[];
extern String EDDYSTONE_URL_SUFFIX[];
/**
* @brief Representation of a beacon.
* See:
* * https://github.com/google/eddystone
*/
class BLEEddystoneURL {
public:
BLEEddystoneURL();
BLEEddystoneURL(BLEAdvertisedDevice *advertisedDevice);
String getData();
String getFrame();
BLEUUID getUUID();
int8_t getPower();
String getURL();
String getPrefix();
String getSuffix();
String getDecodedURL();
void setData(String data);
void setUUID(BLEUUID l_uuid);
void setPower(int8_t advertisedTxPower);
void setPower(esp_power_level_t advertisedTxPower);
void setURL(String url);
int setSmartURL(String url);
private:
uint8_t
lengthURL; // Describes the length of the URL part including prefix and optional suffix - max 18 B (excluding TX power, frame type and preceding header)
struct {
int8_t advertisedTxPower;
uint8_t url[18]; // Byte [0] is for prefix. Last valid byte **can** contain suffix - i.e. the next byte after the URL
} __attribute__((packed)) m_eddystoneData;
void _initHeadder();
char BLEHeadder[12];
}; // BLEEddystoneURL
#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */
#endif /* SOC_BLE_SUPPORTED || CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE */
#endif /* _BLEEddystoneURL_H_ */

Some files were not shown because too many files have changed in this diff Show More