├── .gitignore ├── .idea ├── vcs.xml ├── .gitignore └── misc.xml ├── .vscode └── extensions.json ├── keywords.txt ├── platformio.ini ├── library.properties ├── test └── README ├── .github └── workflows │ ├── release.yml │ ├── arduino-ci.yml │ └── platformio-ci.yml ├── LICENSE ├── examples ├── SerialToSerialBLE │ └── SerialToSerialBLE.ino ├── SerialToSerialBLE_TransparentUART-NimBLE │ └── SerialToSerialBLE_TransparentUART-NimBLE.ino ├── SerialToSerialBLE_TransparentUART │ └── SerialToSerialBLE_TransparentUART.ino └── SerialToSerialBLE_Secure │ └── SerialToSerialBLE_Secure.ino ├── library.json ├── README.md └── src └── BLESerial.h /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Classes, datatypes (KEYWORD1) 3 | ####################################### 4 | 5 | BLESerial KEYWORD1 6 | 7 | ####################################### 8 | # Methods and Functions (KEYWORD2) 9 | ####################################### 10 | 11 | begin KEYWORD2 12 | available KEYWORD2 13 | peek KEYWORD2 14 | read KEYWORD2 15 | write KEYWORD2 16 | end KEYWORD2 -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Serial_BLE 2 | version=1.2.2 3 | author=Leonid Meleshin 4 | maintainer=Leonid Meleshin 5 | sentence=Customizable BLE Serial (UART) library. 6 | paragraph=Customizable Arduino and ESP32 BLE Serial library, compliant with Nordic UART Service and others. Supports both NimBLE and esp-idf BLE stacks. 7 | category=Communication 8 | url=https://github.com/senseshift/arduino-ble-serial 9 | architectures=esp32 10 | includes=BLESerial.h 11 | repository=https://github.com/senseshift/arduino-ble-serial.git 12 | license=MIT -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: 7 | - created 8 | pull_request: 9 | paths: 10 | - '.github/workflows/release.yml' 11 | 12 | jobs: 13 | platformio: 14 | name: PlatformIO 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Set up Python 3.x 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: "3.x" 21 | 22 | - name: Install PlatformIO 23 | run: pip install platformio 24 | 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | - name: Publish 29 | if: startsWith(github.ref, 'refs/tags/') && github.repository == 'senseshift/arduino-ble-serial' 30 | run: pio pkg publish --no-interactive 31 | env: 32 | PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SenseShift 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/SerialToSerialBLE/SerialToSerialBLE.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // FOR ETL: Uncomment the following lines 4 | // #include 5 | // #include 6 | // #include 7 | 8 | String device_name = "ESP32-BLE-Slave"; 9 | 10 | BLESerial SerialBLE; 11 | // If you are using an older version of Arduino IDE or C++ compiler, you may need to use 12 | // an empty template argument (<>), as Class Template Argument Deduction (CTAD) is not 13 | // supported in C++ versions older than C++17. For more details, see: 14 | // https://www.cppreference.com/w/cpp/language/ctad.html 15 | // 16 | // Uncomment the line below if you are using an older version of Arduino IDE/C++ compiler 17 | // BLESerial<> SerialBLE; 18 | 19 | // FOR ETL: Uncomment one of the following lines 20 | // BLESerial> SerialBLE; 21 | // OR 22 | // BLESerial> SerialBLE; 23 | 24 | void setup() { 25 | Serial.begin(9600); 26 | SerialBLE.begin(device_name); 27 | } 28 | 29 | void loop() { 30 | if (Serial.available()) { 31 | SerialBLE.write(Serial.read()); 32 | SerialBLE.flush(); 33 | } 34 | if (SerialBLE.available()) { 35 | Serial.write(SerialBLE.read()); 36 | } 37 | } -------------------------------------------------------------------------------- /.github/workflows/arduino-ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths-ignore: 4 | - "**/*.md" 5 | push: 6 | branches: 7 | - master 8 | - main 9 | - develop 10 | - support/* 11 | paths-ignore: 12 | - "**/*.md" 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: arduino/arduino-lint-action@v1 22 | with: 23 | library-manager: update 24 | 25 | build-examples: 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | matrix: 30 | fqbn: 31 | - "esp32:esp32:esp32" 32 | - "esp32:esp32:esp32s3" 33 | - "esp32:esp32:esp32c3" 34 | nimble: 35 | - false 36 | - '1.4.0' 37 | - '2.3.2' 38 | fail-fast: false 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - uses: arduino/compile-sketches@v1 44 | with: 45 | fqbn: ${{ matrix.fqbn }} 46 | sketch-paths: | 47 | - examples/SerialToSerialBLE 48 | libraries: | 49 | - source-path: ./ 50 | ${{ matrix.nimble != false && format('- name: NimBLE-Arduino@{0}', matrix.nimble) || '' }} 51 | cli-compile-flags: | 52 | - --build-property 53 | - build.extra_flags="-DBLESERIAL_USE_NIMBLE=${{ matrix.nimble != false }}" -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json", 3 | "name": "Serial_BLE", 4 | "version": "1.2.2", 5 | "description": "Customizable Arduino and ESP32 BLE Serial library, compliant with Nordic UART Service and others.", 6 | "keywords": [ 7 | "serial", 8 | "ble", 9 | "uart", 10 | "nus", 11 | "nimble" 12 | ], 13 | "homepage": "https://github.com/senseshift/arduino-ble-serial", 14 | "repository": { 15 | "url": "git@github.com:senseshift/arduino-ble-serial.git", 16 | "type": "git" 17 | }, 18 | "authors": [{ 19 | "name": "Leonid Meleshin", 20 | "email": "hello@leon0399.ru", 21 | "url": "https://leon0399.ru/", 22 | "maintainer": true 23 | }], 24 | "license": "MIT", 25 | "frameworks": "arduino", 26 | "platforms": "espressif32", 27 | "headers": ["BLESerial.h"], 28 | "export": { 29 | "include": [ 30 | "src", 31 | "examples", 32 | "README.md", 33 | "LICENSE", 34 | "library.properties", 35 | "library.json" 36 | ], 37 | "exclude": [ 38 | ".git", 39 | ".github", 40 | ".gitignore", 41 | ".idea", 42 | ".vscode" 43 | ] 44 | }, 45 | "build": { 46 | "srcDir": "src" 47 | }, 48 | "dependencies": { 49 | "BLE": "^2.0.0" 50 | } 51 | } -------------------------------------------------------------------------------- /examples/SerialToSerialBLE_TransparentUART-NimBLE/SerialToSerialBLE_TransparentUART-NimBLE.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // FOR ETL: Uncomment the following lines 4 | // #include 5 | // #include 6 | // #include 7 | 8 | BLESerial SerialBLE; 9 | // If you are using an older version of Arduino IDE or C++ compiler, you may need to use 10 | // an empty template argument (<>), as Class Template Argument Deduction (CTAD) is not 11 | // supported in C++ versions older than C++17. For more details, see: 12 | // https://www.cppreference.com/w/cpp/language/ctad.html 13 | // 14 | // Uncomment the line below if you are using an older version of Arduino IDE/C++ compiler 15 | // BLESerial<> SerialBLE; 16 | 17 | // FOR ETL: Uncomment one of the following lines 18 | // BLESerial> SerialBLE; 19 | // OR 20 | // BLESerial> SerialBLE; 21 | 22 | void setup() { 23 | BLEDevice::init("ESP32-BLE-Slave"); 24 | 25 | BLEServer* pServer = BLEDevice::createServer(); 26 | 27 | // Transparent UART Service 28 | // https://developerhelp.microchip.com/xwiki/bin/view/applications/ble/android-development-for-bm70rn4870/transparent-uart-service-for-bm70rn4870/ 29 | auto pService = pServer->createService("49535343-FE7D-4AE5-8FA9-9FAFD205E455"); 30 | 31 | auto pRxCharacteristic = pService->createCharacteristic("49535343-1E4D-4BD9-BA61-23C647249616", NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::NOTIFY); 32 | auto pTxCharacteristic = pService->createCharacteristic("49535343-8841-43F4-A8D4-ECBE34729BB3", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); 33 | 34 | SerialBLE.begin(pRxCharacteristic, pTxCharacteristic); 35 | 36 | BLEAdvertising* pAdvertising = pServer->getAdvertising(); 37 | pAdvertising->start(); 38 | } 39 | 40 | void loop() { 41 | if (Serial.available()) { 42 | SerialBLE.write(Serial.read()); 43 | SerialBLE.flush(); 44 | } 45 | if (SerialBLE.available()) { 46 | Serial.write(SerialBLE.read()); 47 | } 48 | } -------------------------------------------------------------------------------- /examples/SerialToSerialBLE_TransparentUART/SerialToSerialBLE_TransparentUART.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // FOR ETL: Uncomment the following lines 4 | // #include 5 | // #include 6 | // #include 7 | 8 | BLESerial SerialBLE; 9 | // If you are using an older version of Arduino IDE or C++ compiler, you may need to use 10 | // an empty template argument (<>), as Class Template Argument Deduction (CTAD) is not 11 | // supported in C++ versions older than C++17. For more details, see: 12 | // https://www.cppreference.com/w/cpp/language/ctad.html 13 | // 14 | // Uncomment the line below if you are using an older version of Arduino IDE/C++ compiler 15 | // BLESerial<> SerialBLE; 16 | 17 | // FOR ETL: Uncomment one of the following lines 18 | // BLESerial> SerialBLE; 19 | // OR 20 | // BLESerial> SerialBLE; 21 | 22 | void setup() { 23 | BLEDevice::init("ESP32-BLE-Slave"); 24 | 25 | BLEServer* pServer = BLEDevice::createServer(); 26 | 27 | // Transparent UART Service 28 | // https://developerhelp.microchip.com/xwiki/bin/view/applications/ble/android-development-for-bm70rn4870/transparent-uart-service-for-bm70rn4870/ 29 | auto pService = pServer->createService("49535343-FE7D-4AE5-8FA9-9FAFD205E455"); 30 | 31 | auto pRxCharacteristic = pService->createCharacteristic("49535343-1E4D-4BD9-BA61-23C647249616", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_NOTIFY); 32 | auto pTxCharacteristic = pService->createCharacteristic("49535343-8841-43F4-A8D4-ECBE34729BB3", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); 33 | 34 | SerialBLE.begin(pRxCharacteristic, pTxCharacteristic); 35 | 36 | BLEAdvertising* pAdvertising = pServer->getAdvertising(); 37 | pAdvertising->start(); 38 | } 39 | 40 | void loop() { 41 | if (Serial.available()) { 42 | SerialBLE.write(Serial.read()); 43 | SerialBLE.flush(); 44 | } 45 | if (SerialBLE.available()) { 46 | Serial.write(SerialBLE.read()); 47 | } 48 | } -------------------------------------------------------------------------------- /examples/SerialToSerialBLE_Secure/SerialToSerialBLE_Secure.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // FOR ETL: Uncomment the following lines 4 | // #include 5 | // #include 6 | // #include 7 | 8 | BLESerial SerialBLE; 9 | // If you are using an older version of Arduino IDE or C++ compiler, you may need to use 10 | // an empty template argument (<>), as Class Template Argument Deduction (CTAD) is not 11 | // supported in C++ versions older than C++17. For more details, see: 12 | // https://www.cppreference.com/w/cpp/language/ctad.html 13 | // 14 | // Uncomment the line below if you are using an older version of Arduino IDE/C++ compiler 15 | // BLESerial<> SerialBLE; 16 | 17 | // FOR ETL: Uncomment one of the following lines 18 | // BLESerial> SerialBLE; 19 | // OR 20 | // BLESerial> SerialBLE; 21 | 22 | class AppSecurityCallbacks : public BLESecurityCallbacks { 23 | public: 24 | AppSecurityCallbacks() { 25 | this->passKey = random(111111, 999999); 26 | } 27 | 28 | uint32_t onPassKeyRequest(){ 29 | ESP_LOGI(LOG_TAG, "PassKeyRequest"); 30 | 31 | // Generate a random passkey 32 | this->passKey = random(111111, 999999); 33 | 34 | return this->passKey; 35 | } 36 | 37 | void onPassKeyNotify(uint32_t pass_key){ 38 | ESP_LOGI(LOG_TAG, "The passkey Notify number: %d", pass_key); 39 | } 40 | 41 | bool onConfirmPIN(uint32_t pass_key){ 42 | ESP_LOGI(LOG_TAG, "The passkey YES/NO number: %d", pass_key); 43 | vTaskDelay(5000); 44 | return true; 45 | } 46 | 47 | bool onSecurityRequest(){ 48 | ESP_LOGI(LOG_TAG, "SecurityRequest"); 49 | return true; 50 | } 51 | 52 | void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ 53 | ESP_LOGI(LOG_TAG, "Starting BLE work!"); 54 | } 55 | 56 | private: 57 | uint32_t passKey; 58 | }; 59 | 60 | void setup() { 61 | BLEDevice::init("ESP32-BLE-Slave"); 62 | BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); 63 | BLEDevice::setSecurityCallbacks(new AppSecurityCallbacks()); 64 | 65 | BLEServer* pServer = BLEDevice::createServer(); 66 | 67 | BLESecurity *pSecurity = new BLESecurity(); 68 | pSecurity->setKeySize(); 69 | pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); 70 | pSecurity->setCapability(ESP_IO_CAP_IO); 71 | pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); 72 | 73 | Serial.begin(9600); 74 | SerialBLE.begin(pServer); 75 | } 76 | 77 | void loop() { 78 | if (Serial.available()) { 79 | SerialBLE.write(Serial.read()); 80 | SerialBLE.flush(); 81 | } 82 | 83 | if (SerialBLE.available()) { 84 | Serial.write(SerialBLE.read()); 85 | } 86 | } -------------------------------------------------------------------------------- /.github/workflows/platformio-ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths-ignore: 4 | - "**/*.md" 5 | push: 6 | branches: 7 | - master 8 | - main 9 | - develop 10 | - support/* 11 | paths-ignore: 12 | - "**/*.md" 13 | 14 | jobs: 15 | platformio: 16 | runs-on: ${{ matrix.os }} 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ ubuntu-latest ] 22 | example: 23 | - "SerialToSerialBLE" 24 | # - "SerialToSerialBLE_TransparentUART" 25 | - "SerialToSerialBLE_TransparentUART-NimBLE" 26 | # - "SerialToSerialBLE_Secure" 27 | boards: 28 | - [ esp32dev, esp32-s3-devkitc-1, esp32-c3-devkitm-1 ] 29 | nimble: 30 | - false 31 | - '^1.4.0' 32 | - '^2.0.0' 33 | 34 | exclude: 35 | - example: "SerialToSerialBLE_TransparentUART-NimBLE" 36 | nimble: false 37 | # - example: "SerialToSerialBLE_TransparentUART" 38 | # nimble: '^1.4.0' 39 | # - example: "SerialToSerialBLE_TransparentUART" 40 | # nimble: '^2.0.0' 41 | include: 42 | - example: "SerialToSerialBLE_TransparentUART-NimBLE" 43 | nimble: '^2.0.0' 44 | os: ubuntu-latest 45 | boards: [esp32dev, esp32-s3-devkitc-1, esp32-c3-devkitm-1] 46 | - example: "SerialToSerialBLE_TransparentUART-NimBLE" 47 | nimble: '^1.4.0' 48 | os: ubuntu-latest 49 | boards: [esp32dev, esp32-s3-devkitc-1, esp32-c3-devkitm-1] 50 | 51 | - example: "SerialToSerialBLE_Secure" 52 | nimble: false 53 | os: ubuntu-latest 54 | boards: [esp32dev, esp32-s3-devkitc-1, esp32-c3-devkitm-1] 55 | 56 | steps: 57 | - uses: actions/checkout@v4 58 | 59 | - name: Cache pip 60 | uses: actions/cache@v4 61 | with: 62 | path: ~/.cache/pip 63 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 64 | restore-keys: | 65 | ${{ runner.os }}-pip- 66 | 67 | - name: Cache PlatformIO 68 | uses: actions/cache@v4 69 | with: 70 | path: | 71 | ~/.platformio/.cache 72 | ./.pio 73 | key: ${{ runner.os }}-pio-${{ hashFiles('**/*.ini') }} 74 | restore-keys: | 75 | ${{ runner.os }}-pio- 76 | 77 | - name: Set up Python 78 | uses: actions/setup-python@v5 79 | with: 80 | python-version: "3.9" 81 | 82 | - name: Install PlatformIO 83 | run: | 84 | python -m pip install --upgrade pip 85 | pip install --upgrade platformio 86 | pio upgrade --dev 87 | pio pkg update --global 88 | 89 | - name: Build example 90 | run: | 91 | pio ci --lib="." --board=${{ join(matrix.boards, ' --board=') }} ${{ matrix.nimble && format('--project-option="lib_deps=h2zero/NimBLE-Arduino@{0}"', matrix.nimble) || '' }} 92 | env: 93 | PLATFORMIO_CI_SRC: "./examples/${{ matrix.example }}/*.ino" 94 | PLATFORMIO_BUILD_UNFLAGS: | 95 | -std=gnu++11 96 | PLATFORMIO_BUILD_FLAGS: | 97 | -std=gnu++17 98 | -D BLESERIAL_USE_NIMBLE=${{ matrix.nimble != false }} 99 | -Wall 100 | -Wextra 101 | -Wpedantic 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino Serial BLE 2 | 3 | This library allows using Nordic UART Service (NUS) with ESP32 Arduino. 4 | 5 | Get involved: 💬 [Discord](https://discord.gg/YUtRKAqty2) • 🌐 [Website](https://senseshift.io) • 🐛 [Issues](https://github.com/senseshift/arduino-ble-serial/issues) • 📢 [Twitter](https://twitter.com/senseshiftio) • 💎 [Patreon](https://www.patreon.com/senseshift) 6 | 7 | [![Discord Widget](https://discord.com/api/guilds/966090258104062023/widget.png?style=banner2)](https://discord.gg/YUtRKAqty2) 8 | 9 | [![PlatformIO Registry](https://badges.registry.platformio.org/packages/senseshift/library/Serial_BLE.svg)](https://registry.platformio.org/libraries/senseshift/Serial_BLE) 10 | [![Arduino Library](https://www.ardu-badge.com/badge/Serial_BLE.svg?)](https://www.ardu-badge.com/Serial_BLE) 11 | [![GitHub release](https://img.shields.io/github/v/release/senseshift/arduino-ble-serial)](https://github.com/senseshift/arduino-ble-serial/releases/latest) 12 | 13 | [![PlatformIO CI](https://github.com/senseshift/arduino-ble-serial/actions/workflows/platformio-ci.yml/badge.svg)](https://github.com/senseshift/arduino-ble-serial/actions/workflows/platformio-ci.yml) 14 | [![Arduino CI](https://github.com/senseshift/arduino-ble-serial/actions/workflows/arduino-ci.yml/badge.svg)](https://github.com/senseshift/arduino-ble-serial/actions/workflows/arduino-ci.yml) 15 | 16 | [![MIT](https://img.shields.io/github/license/senseshift/arduino-ble-serial)](/LICENSE) 17 | [![GitHub contributors](https://img.shields.io/github/contributors/senseshift/arduino-ble-serial)](https://github.com/senseshift/arduino-ble-serial/graphs/contributors) 18 | [![GitHub](https://img.shields.io/github/stars/senseshift/arduino-ble-serial.svg)](https://github.com/senseshift/arduino-ble-serial) 19 | 20 | ## Features 21 | 22 | - [x] [`HardwareSerial`](https://www.arduino.cc/reference/en/language/functions/communication/serial/)-compatible API 23 | - [x] [ETL (Embedded Template Library)](https://github.com/ETLCPP/etl) support 24 | - [x] NimBLE support through [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino) library. 25 | - [x] Custom Server and Characteristics configuration 26 | 27 | ## Installation 28 | 29 | ### PlatformIO 30 | 31 | ```diff 32 | lib_deps = 33 | + senseshift/Serial_BLE 34 | ``` 35 | 36 | ## Client-side usage 37 | 38 | - Android: 39 | - [nRF connect for mobile](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp) 40 | - [Serial Bluetooth Terminal](https://play.google.com/store/apps/details?id=de.kai_morich.serial_bluetooth_terminal) 41 | - iOS: 42 | - [nRF connect for mobile](https://apps.apple.com/es/app/nrf-connect-for-mobile/id1054362403) 43 | 44 | ## Usage 45 | 46 | For all examples, take a look at the [`examples`](./examples) folder. 47 | 48 | ### Basic Example 49 | 50 | ```ino 51 | #include 52 | 53 | BLESerial<> SerialBLE; 54 | 55 | void setup() { 56 | Serial.begin(9600); 57 | SerialBLE.begin("ESP32-BLE-Slave"); 58 | } 59 | 60 | void loop() { 61 | if (Serial.available()) { 62 | SerialBLE.write(Serial.read()); 63 | SerialBLE.flush(); 64 | } 65 | if (SerialBLE.available()) { 66 | Serial.write(SerialBLE.read()); 67 | } 68 | } 69 | ``` 70 | 71 | ### Custom UART characteristics 72 | 73 | Using custom UUIDs for [Microchip BM70/RN4870 Transparent UART](https://developerhelp.microchip.com/xwiki/bin/view/applications/ble/android-development-for-bm70rn4870/transparent-uart-service-for-bm70rn4870/) 74 | 75 | ```ino 76 | // ... 77 | 78 | void setup() { 79 | // ... 80 | 81 | SerialBLE.begin( 82 | "ESP32-BLE-Slave", 83 | "49535343-FE7D-4AE5-8FA9-9FAFD205E455", 84 | "49535343-1E4D-4BD9-BA61-23C647249616", 85 | "49535343-8841-43F4-A8D4-ECBE34729BB3" 86 | ); 87 | 88 | // ... 89 | } 90 | 91 | // ... 92 | ``` 93 | 94 | #### Alternative 95 | 96 | 97 | ```ino 98 | // ... 99 | 100 | void setup() { 101 | // ... 102 | 103 | BLEDevice::init("ESP32-BLE-Slave"); 104 | BLEServer* pServer = BLEDevice::createServer(); 105 | 106 | auto pService = pServer->createService("49535343-FE7D-4AE5-8FA9-9FAFD205E455"); 107 | 108 | auto pRxCharacteristic = pService->createCharacteristic("49535343-1E4D-4BD9-BA61-23C647249616", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_NOTIFY); 109 | auto pTxCharacteristic = pService->createCharacteristic("49535343-8841-43F4-A8D4-ECBE34729BB3", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); 110 | 111 | SerialBLE.begin(pRxCharacteristic, pTxCharacteristic); 112 | 113 | // ... 114 | } 115 | 116 | // ... 117 | ``` 118 | 119 | ### Custom Read Buffer 120 | 121 | #### ETL (Embedded Template Library) 122 | 123 | Using [ETL](https://github.com/ETLCPP/etl) provides a way to use this library without dynamic memory allocation. 124 | 125 | ```ino 126 | #include 127 | #include 128 | #include 129 | 130 | BLESerial> SerialBLE; 131 | BLESerial> SerialBLE; 132 | ``` 133 | 134 | ## NimBLE 135 | 136 | Using the NimBLE library saves a significant amount of RAM and Flash memory. 137 | 138 | 139 | 140 | 143 | 147 | 148 | 149 | 152 | 156 | 157 |
141 | ESP32 BLE 142 | 144 |
RAM:   [=         ]  11.9% (used 39124 bytes from 327680 bytes)
145 | Flash: [========= ]  85.9% (used 1125553 bytes from 1310720 bytes)
146 |
150 | NimBLE-Arduino 151 | 153 |
RAM:   [=         ]   9.3% (used 30548 bytes from 327680 bytes)
154 | Flash: [====      ]  44.2% (used 579158 bytes from 1310720 bytes)
155 |
158 | 159 | ### Arduino IDE 160 | 161 | 1. Make sure to install `NimBLE-Arduino` library in Arduino IDE. 162 | 2. Update `BLESERIAL_USE_NIMBLE` setting **Before** including library header: 163 | ```diff 164 | + #define BLESERIAL_USE_NIMBLE true 165 | #include 166 | ``` 167 | 168 | Alternatively, сhange the following line in `BLESerial.h`: 169 | 170 | ```diff 171 | - # define BLESERIAL_USE_NIMBLE false 172 | + # define BLESERIAL_USE_NIMBLE true 173 | ``` 174 | 175 | ### PlatformIO 176 | 177 | Change your `platformio.ini` file to the following settings: 178 | 179 | ```diff 180 | lib_deps = 181 | + h2zero/NimBLE-Arduino@^1.4.0 182 | 183 | build_flags = 184 | + -D BLESERIAL_USE_NIMBLE=true 185 | ``` 186 | -------------------------------------------------------------------------------- /src/BLESerial.h: -------------------------------------------------------------------------------- 1 | #ifndef BLESERIAL_H 2 | #define BLESERIAL_H 3 | 4 | #include 5 | 6 | #ifndef BLESERIAL_USE_NIMBLE 7 | // Global flag to use NimBLE in all SenseShift libraries 8 | # ifdef SS_USE_NIMBLE 9 | # define BLESERIAL_USE_NIMBLE SS_USE_NIMBLE 10 | # else // SS_USE_NIMBLE 11 | # define BLESERIAL_USE_NIMBLE false 12 | # endif // SS_USE_NIMBLE 13 | #endif // BLESERIAL_USE_NIMBLE 14 | 15 | #if defined(BLESERIAL_USE_NIMBLE) && BLESERIAL_USE_NIMBLE 16 | # include 17 | #else // BLESERIAL_USE_NIMBLE 18 | # include 19 | # include 20 | #endif // BLESERIAL_USE_NIMBLE 21 | 22 | #if !defined(BLESERIAL_NIMBLE_VERSION_MAJOR) && defined(BLESERIAL_USE_NIMBLE) && BLESERIAL_USE_NIMBLE 23 | # if __has_include() 24 | # define BLESERIAL_NIMBLE_VERSION_MAJOR 2 25 | // # warning "Using NimBLE version 2" 26 | # else // __has_include() 27 | # define BLESERIAL_NIMBLE_VERSION_MAJOR 1 28 | // # warning "Using NimBLE version 1" 29 | # endif // __has_include() 30 | #endif // BLESERIAL_USE_NIMBLE 31 | 32 | #ifndef BLESERIAL_USE_STL 33 | # define BLESERIAL_USE_STL true 34 | #endif // BLESERIAL_USE_STL 35 | 36 | #if defined(BLESERIAL_USE_STL) && BLESERIAL_USE_STL 37 | # include 38 | #endif // BLESERIAL_USE_STL 39 | 40 | template 41 | class BLESerialCharacteristicCallbacks; 42 | 43 | template 44 | class BLESerialServerCallbacks; 45 | 46 | /** 47 | * @tparam T Type of the receive buffer 48 | */ 49 | #if defined(BLESERIAL_USE_STL) && BLESERIAL_USE_STL 50 | template> 51 | #else // BLESERIAL_USE_STL 52 | template 53 | #endif // BLESERIAL_USE_STL 54 | class BLESerial : public Stream { 55 | friend class BLESerialCharacteristicCallbacks; 56 | friend class BLESerialServerCallbacks; 57 | 58 | private: 59 | T m_receiveBuffer; 60 | 61 | /** 62 | * BLE server instance 63 | * @note This is only used if the BLESerial instance is managing the BLE server 64 | */ 65 | BLEServer* m_pServer = nullptr; 66 | 67 | /** 68 | * BLE service instance 69 | * @note This is only used if the BLESerial instance is managing the BLE service 70 | */ 71 | BLEService* m_pService = nullptr; 72 | 73 | /** 74 | * BLE characteristic instance for receiving data 75 | */ 76 | BLECharacteristic* m_pRxCharacteristic = nullptr; 77 | 78 | /** 79 | * BLE characteristic instance for transmitting data 80 | */ 81 | BLECharacteristic* m_pTxCharacteristic = nullptr; 82 | 83 | public: 84 | static const char* SERVICE_UUID; 85 | static const char* RX_UUID; 86 | static const char* TX_UUID; 87 | 88 | BLESerial() : m_receiveBuffer() {} 89 | 90 | BLESerial(BLESerial const& other) = delete; // disable copy constructor 91 | void operator=(BLESerial const& other) = delete; // disable assign constructor 92 | 93 | inline int available() override { return m_receiveBuffer.size(); } 94 | 95 | inline int peek() override { return m_receiveBuffer.front(); } 96 | 97 | int read() override 98 | { 99 | auto front = m_receiveBuffer.front(); 100 | m_receiveBuffer.pop(); 101 | return front; 102 | } 103 | 104 | size_t write(const uint8_t* buffer, size_t bufferSize) override 105 | { 106 | if (this->m_pTxCharacteristic == nullptr || !this->connected()) { 107 | return 0; 108 | } 109 | 110 | this->m_pTxCharacteristic->setValue(const_cast(buffer), bufferSize); 111 | // this->flush(); 112 | 113 | return bufferSize; 114 | } 115 | 116 | size_t write(uint8_t byte) override 117 | { 118 | if (this->m_pTxCharacteristic == nullptr || !this->connected()) { 119 | return 0; 120 | } 121 | 122 | this->m_pTxCharacteristic->setValue(&byte, 1); 123 | // this->flush(); 124 | 125 | return 1; 126 | } 127 | 128 | void flush(void) override { this->m_pTxCharacteristic->notify(true); } 129 | 130 | void begin( 131 | const String& deviceName = String(), 132 | const char* serviceUuid = SERVICE_UUID, 133 | const char* rxUuid = RX_UUID, 134 | const char* txUuid = TX_UUID 135 | ) 136 | { 137 | this->begin(deviceName.c_str(), serviceUuid, rxUuid, txUuid); 138 | } 139 | 140 | /** 141 | * Begin BLE serial. This will create and start BLE server, service and characteristics. 142 | * 143 | * @note This will manage the BLE server, service and characteristics. If you want to manage them yourself, use the 144 | * other begin(). 145 | * 146 | * @param deviceName Name of the BLE device 147 | * @param serviceUuid UUID of the BLE service 148 | * @param rxUuid UUID of the BLE characteristic for receiving data 149 | * @param txUuid UUID of the BLE characteristic for transmitting data 150 | */ 151 | void begin( 152 | const char* deviceName, 153 | const char* serviceUuid = SERVICE_UUID, 154 | const char* rxUuid = RX_UUID, 155 | const char* txUuid = TX_UUID 156 | ); 157 | 158 | /** 159 | * Begin BLE serial. This will create and start BLE service and characteristics. 160 | * 161 | * @note This will manage the BLE service and characteristics. If you want to manage them yourself, use the other 162 | * begin(). 163 | * 164 | * @param pServer BLE server instance 165 | * @param serviceUuid UUID of the BLE service 166 | * @param rxUuid UUID of the BLE characteristic for receiving data 167 | * @param txUuid UUID of the BLE characteristic for transmitting data 168 | */ 169 | void begin( 170 | BLEServer* pServer, 171 | const char* serviceUuid = SERVICE_UUID, 172 | const char* rxUuid = RX_UUID, 173 | const char* txUuid = TX_UUID 174 | ) 175 | { 176 | BLEService* pService = pServer->getServiceByUUID(serviceUuid); 177 | if (pService == nullptr) { 178 | log_d("Creating BLE service with UUID '%s'", serviceUuid); 179 | pService = pServer->createService(serviceUuid); 180 | } else { 181 | log_w("BLE service with UUID '%s' already exists", serviceUuid); 182 | } 183 | 184 | // Store the service, so we know if we're managing it 185 | this->m_pService = pService; 186 | 187 | this->begin(pService, rxUuid, txUuid); 188 | 189 | pService->start(); 190 | log_d("Started BLE service"); 191 | } 192 | 193 | /** 194 | * Begin BLE serial. This will create and start BLE characteristics. 195 | * 196 | * @note If you want to create characteristics yourself, use the other begin(). 197 | * 198 | * @param pService BLE service instance 199 | * @param rxUuid UUID of the BLE characteristic for receiving data 200 | * @param txUuid UUID of the BLE characteristic for transmitting data 201 | */ 202 | void begin(BLEService* pService, const char* rxUuid = RX_UUID, const char* txUuid = TX_UUID) 203 | { 204 | auto* pRxCharacteristic = pService->getCharacteristic(rxUuid); 205 | if (pRxCharacteristic == nullptr) { 206 | log_d("Creating BLE characteristic with UUIDs '%s' (RX)", rxUuid); 207 | #if defined(BLESERIAL_USE_NIMBLE) && BLESERIAL_USE_NIMBLE 208 | pRxCharacteristic = 209 | pService->createCharacteristic(rxUuid, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); 210 | #else // BLESERIAL_USE_NIMBLE 211 | pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE_NR); 212 | #endif // BLESERIAL_USE_NIMBLE 213 | } else { 214 | log_w("BLE characteristic with UUID '%s' (RX) already exists", rxUuid); 215 | } 216 | 217 | auto* pTxCharacteristic = pService->getCharacteristic(txUuid); 218 | if (pTxCharacteristic == nullptr) { 219 | log_d("Creating BLE characteristic with UUIDs '%s' (TX)", txUuid); 220 | #if defined(BLESERIAL_USE_NIMBLE) && BLESERIAL_USE_NIMBLE 221 | pTxCharacteristic = pService->createCharacteristic(txUuid, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); 222 | #else // BLESERIAL_USE_NIMBLE 223 | pTxCharacteristic = pService->createCharacteristic( 224 | txUuid, 225 | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY 226 | ); 227 | #endif // BLESERIAL_USE_NIMBLE 228 | } else { 229 | log_w("BLE characteristic with UUID '%s' (TX) already exists", txUuid); 230 | } 231 | 232 | this->begin(pRxCharacteristic, pTxCharacteristic); 233 | } 234 | 235 | /** 236 | * Begin BLE serial. This will setup the BLE characteristics. 237 | * 238 | * @param pServer BLE server instance 239 | * @param pRxCharacteristic BLE characteristic instance for receiving data 240 | * @param pTxCharacteristic BLE characteristic instance for transmitting data 241 | */ 242 | void begin(BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic); 243 | 244 | #if !defined(BLESERIAL_USE_NIMBLE) && !BLESERIAL_USE_NIMBLE 245 | void end() 246 | { 247 | if (this->m_pService != nullptr) { 248 | this->m_pService->stop(); 249 | } 250 | 251 | if (this->m_pServer != nullptr) { 252 | this->m_pServer->getAdvertising()->stop(); 253 | } 254 | 255 | this->m_pServer = nullptr; 256 | } 257 | #endif // BLESERIAL_USE_NIMBLE 258 | 259 | auto connected() -> bool { return m_pServer != nullptr && m_pServer->getConnectedCount() > 0; } 260 | 261 | auto getRxCharacteristic() -> BLECharacteristic* { return m_pRxCharacteristic; } 262 | 263 | auto getTxCharacteristic() -> BLECharacteristic* { return m_pTxCharacteristic; } 264 | }; 265 | 266 | template 267 | class BLESerialServerCallbacks : public BLEServerCallbacks { 268 | public: 269 | explicit BLESerialServerCallbacks(BLESerial* bleSerial) : bleSerial(bleSerial) {} 270 | 271 | #if defined(BLESERIAL_USE_NIMBLE) && BLESERIAL_USE_NIMBLE 272 | # if defined(BLESERIAL_NIMBLE_VERSION_MAJOR) && BLESERIAL_NIMBLE_VERSION_MAJOR >= 2 273 | // # warning "Using NimBLE version 2 for BLESerialServerCallbacks" 274 | void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override 275 | # else // BLESERIAL_NIMBLE_VERSION_MAJOR >= 2 276 | // # warning "Using NimBLE version 1 for BLESerialServerCallbacks" 277 | void onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override 278 | # endif // BLESERIAL_NIMBLE_VERSION_MAJOR >= 2 279 | #else // BLESERIAL_USE_NIMBLE 280 | void onDisconnect(BLEServer* pServer) override 281 | #endif // BLESERIAL_USE_NIMBLE 282 | { 283 | auto* pAdvertising = pServer->getAdvertising(); 284 | if (pAdvertising == nullptr) { 285 | return; 286 | } 287 | pAdvertising->start(); 288 | } 289 | 290 | private: 291 | BLESerial* bleSerial; 292 | }; 293 | 294 | template 295 | class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks { 296 | public: 297 | explicit BLESerialCharacteristicCallbacks(BLESerial* bleSerial) : bleSerial(bleSerial) {} 298 | 299 | #if defined(BLESERIAL_USE_NIMBLE) && BLESERIAL_USE_NIMBLE 300 | # if defined(BLESERIAL_NIMBLE_VERSION_MAJOR) && BLESERIAL_NIMBLE_VERSION_MAJOR >= 2 301 | // # warning "Using NimBLE version 2 for BLESerialCharacteristicCallbacks" 302 | void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override 303 | # else // BLESERIAL_NIMBLE_VERSION_MAJOR >= 2 304 | // # warning "Using NimBLE version 1 for BLESerialCharacteristicCallbacks" 305 | void onWrite(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) override 306 | # endif // BLESERIAL_NIMBLE_VERSION_MAJOR >= 2 307 | #else // BLESERIAL_USE_NIMBLE 308 | void onWrite(BLECharacteristic* pCharacteristic) override 309 | #endif // BLESERIAL_USE_NIMBLE 310 | { 311 | if (pCharacteristic != bleSerial->m_pRxCharacteristic) { 312 | return; 313 | } 314 | 315 | auto rxValue = pCharacteristic->getValue(); 316 | for (int i = 0; i < rxValue.length(); i++) { 317 | bleSerial->m_receiveBuffer.push(rxValue[i]); 318 | } 319 | } 320 | 321 | private: 322 | BLESerial* bleSerial; 323 | }; 324 | 325 | template 326 | const char* BLESerial::SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; 327 | 328 | template 329 | const char* BLESerial::RX_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; 330 | 331 | template 332 | const char* BLESerial::TX_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; 333 | 334 | template 335 | void BLESerial::begin(const char* deviceName, const char* serviceUuid, const char* rxUuid, const char* txUuid) 336 | { 337 | // Create the BLE Device 338 | log_d("Initializing BLE device with name '%s'", deviceName); 339 | BLEDevice::init(deviceName); 340 | 341 | log_d("Creating BLE server"); 342 | BLEServer* pServer = BLEDevice::createServer(); 343 | pServer->setCallbacks(new BLESerialServerCallbacks(this)); 344 | 345 | // Store the server, so we know if we're managing it 346 | this->m_pServer = pServer; 347 | 348 | this->begin(pServer, serviceUuid, rxUuid, txUuid); 349 | 350 | BLEAdvertising* pAdvertising = pServer->getAdvertising(); 351 | pAdvertising->start(); 352 | log_d("Started BLE advertising"); 353 | } 354 | 355 | template 356 | void BLESerial::begin(BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic) 357 | { 358 | // Store the characteristics, so we know if we're managing them 359 | this->m_pRxCharacteristic = pRxCharacteristic; 360 | this->m_pTxCharacteristic = pTxCharacteristic; 361 | 362 | this->m_pRxCharacteristic->setCallbacks(new BLESerialCharacteristicCallbacks(this)); 363 | 364 | #if !defined(BLESERIAL_USE_NIMBLE) || !BLESERIAL_USE_NIMBLE 365 | // this->m_pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENCRYPTED); 366 | this->m_pRxCharacteristic->addDescriptor(new BLE2902()); 367 | this->m_pRxCharacteristic->setWriteProperty(true); 368 | #endif 369 | 370 | #if !defined(BLESERIAL_USE_NIMBLE) || !BLESERIAL_USE_NIMBLE 371 | // this->m_pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED); 372 | this->m_pTxCharacteristic->addDescriptor(new BLE2902()); 373 | this->m_pTxCharacteristic->setReadProperty(true); 374 | this->m_pTxCharacteristic->setNotifyProperty(true); 375 | #endif 376 | } 377 | 378 | #endif // BLESERIAL_H --------------------------------------------------------------------------------