diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 00000000..cffc2173 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,9 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check/.codespellrc +# See: https://github.com/codespell-project/codespell#using-a-config-file +[codespell] +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = , +skip = ./.git,./.licenses,__pycache__,node_modules,./go.mod,./go.sum,./package-lock.json,./poetry.lock,./yarn.lock,./extras/test +builtin = clear,informal,en-GB_to_en-US +check-filenames = +check-hidden = diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f2bfa724 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# See: https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates#about-the-dependabotyml-file +version: 2 + +updates: + # Configure check for outdated GitHub Actions actions in workflows. + # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/dependabot/README.md + # See: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-actions-up-to-date-with-dependabot + - package-ecosystem: github-actions + directory: / # Check the repository's workflows under /.github/workflows/ + schedule: + interval: daily + labels: + - "topic: infrastructure" diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 884f11d6..d97458e6 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -1,22 +1,115 @@ name: Compile Examples -on: [push, pull_request] + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/compile-examples.yml" + - "examples/**" + - "src/**" + pull_request: + paths: + - ".github/workflows/compile-examples.yml" + - "examples/**" + - "src/**" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms). + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - fqbn: [ - "arduino:samd:mkrwifi1010", - "arduino:samd:nano_33_iot", - "arduino:megaavr:uno2018:mode=on", - "arduino:mbed:nano33ble" - ] - - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - uses: arduino/actions/libraries/compile-examples@master - with: - fqbn: ${{ matrix.fqbn }} + build: + name: ${{ matrix.board.fqbn }} + runs-on: ubuntu-latest + + env: + SKETCHES_REPORTS_PATH: sketches-reports + + strategy: + fail-fast: false + + matrix: + board: + - fqbn: arduino:samd:mkrwifi1010 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrwifi1010 + - fqbn: arduino:samd:nano_33_iot + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-nano_33_iot + - fqbn: arduino:megaavr:uno2018:mode=on + platforms: | + - name: arduino:megaavr + artifact-name-suffix: arduino-megaavr-uno2018 + - fqbn: arduino:mbed_nano:nano33ble + platforms: | + - name: arduino:mbed_nano + artifact-name-suffix: arduino-mbed_nano-nano33ble + - fqbn: arduino:mbed_nano:nanorp2040connect + platforms: | + - name: arduino:mbed_nano + artifact-name-suffix: arduino-mbed_nano-nanorp2040connect + - fqbn: arduino:renesas_uno:unor4wifi + platforms: | + - name: arduino:renesas_uno + artifact-name-suffix: arduino-renesas_uno-unor4wifi + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Compile examples + uses: arduino/compile-sketches@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fqbn: ${{ matrix.board.fqbn }} + platforms: ${{ matrix.board.platforms }} + libraries: | + # Install the library from the local path. + - source-path: ./ + sketch-paths: | + - examples + enable-deltas-report: true + sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} + + - name: Save sketches report as workflow artifact + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + path: ${{ env.SKETCHES_REPORTS_PATH }} + name: sketches-report-${{ matrix.board.artifact-name-suffix }} + + build-for-esp32: + runs-on: ubuntu-latest + + strategy: + matrix: + fqbn: + - esp32:esp32:esp32 + - esp32:esp32:esp32s3 + - esp32:esp32:esp32c3 + - esp32:esp32:esp32c6 + - esp32:esp32:esp32h2 + # Not supported out of the box by ESP32 Arduino core + #- esp32:esp32:esp32c2 + + steps: + - uses: actions/checkout@v4 + - uses: arduino/compile-sketches@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fqbn: ${{ matrix.fqbn }} + platforms: | + - name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + sketch-paths: | + - examples/Central/Scan + - examples/Central/PeripheralExplorer + - examples/Central/ScanCallback + - examples/Central/SensorTagButton + - examples/Peripheral/Advertising/EnhancedAdvertising + - examples/Peripheral/Advertising/RawDataAdvertising + cli-compile-flags: | + - --warnings="none" diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml new file mode 100644 index 00000000..39e2a0ad --- /dev/null +++ b/.github/workflows/report-size-deltas.yml @@ -0,0 +1,24 @@ +name: Report Size Deltas + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/report-size-deltas.yml" + schedule: + # Run at the minimum interval allowed by GitHub Actions. + # Note: GitHub Actions periodically has outages which result in workflow failures. + # In this event, the workflows will start passing again once the service recovers. + - cron: "*/5 * * * *" + workflow_dispatch: + repository_dispatch: + +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Comment size deltas reports to PRs + uses: arduino/report-size-deltas@v1 + with: + # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow + sketches-reports-source: ^sketches-report-.+ diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 7b45d77e..6faf09c8 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -1,11 +1,25 @@ +# Source: https://github.com/per1234/.github/blob/main/workflow-templates/spell-check.md name: Spell Check -on: [push, pull_request] + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + pull_request: + schedule: + # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - uses: arduino/actions/libraries/spell-check@master + spellcheck: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Spell check + uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 00000000..53a9f54f --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,138 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md +name: Sync Labels + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + pull_request: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + schedule: + # Run daily at 8 AM UTC to sync with changes to shared label configurations. + - cron: "0 8 * * *" + workflow_dispatch: + repository_dispatch: + +env: + CONFIGURATIONS_FOLDER: .github/label-configuration-files + CONFIGURATIONS_ARTIFACT: label-configuration-files + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download JSON schema for labels configuration file + id: download-schema + uses: carlosperate/download-file-action@v2 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json + location: ${{ runner.temp }}/label-configuration-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate local labels configuration + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" + + download: + needs: check + runs-on: ubuntu-latest + + strategy: + matrix: + filename: + # Filenames of the shared configurations to apply to the repository in addition to the local configuration. + # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels + - universal.yml + + steps: + - name: Download + uses: carlosperate/download-file-action@v2 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} + + - name: Pass configuration files to next job via workflow artifact + uses: actions/upload-artifact@v4 + with: + path: | + *.yaml + *.yml + if-no-files-found: error + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + sync: + needs: download + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" + + - name: Determine whether to dry run + id: dry-run + if: > + github.event_name == 'pull_request' || + ( + ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' + ) && + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + ) + run: | + # Use of this flag in the github-label-sync command will cause it to only check the validity of the + # configuration. + echo "::set-output name=flag::--dry-run" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download configuration files artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + path: ${{ env.CONFIGURATIONS_FOLDER }} + + - name: Remove unneeded artifact + uses: geekyeggo/delete-artifact@v5 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + - name: Merge label configuration files + run: | + # Merge all configuration files + shopt -s extglob + cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" + + - name: Install github-label-sync + run: sudo npm install --global github-label-sync + + - name: Sync labels + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # See: https://github.com/Financial-Times/github-label-sync + github-label-sync \ + --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ + ${{ steps.dry-run.outputs.flag }} \ + ${{ github.repository }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..2a96b8a5 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,58 @@ +name: Unit Tests + +on: + pull_request: + paths: + - ".github/workflows/unit-tests.yml" + - 'extras/test/**' + - 'src/**' + + push: + paths: + - ".github/workflows/unit-tests.yml" + - 'extras/test/**' + - 'src/**' + +jobs: + test: + name: Run unit tests + runs-on: ubuntu-latest + + env: + COVERAGE_DATA_PATH: extras/coverage-data/coverage.info + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: arduino/cpp-test-action@main + with: + runtime-paths: | + - extras/test/build/bin/TEST_TARGET_UUID + - extras/test/build/bin/TEST_TARGET_DISC_DEVICE + - extras/test/build/bin/TEST_TARGET_ADVERTISING_DATA + coverage-exclude-paths: | + - '*/extras/test/*' + - '/usr/*' + coverage-data-path: ${{ env.COVERAGE_DATA_PATH }} + + # A token is used to avoid intermittent spurious job failures caused by rate limiting. + - name: Set up Codecov upload token + run: | + if [[ "${{ github.repository }}" == "arduino-libraries/ArduinoBLE" ]]; then + # In order to avoid uploads of data from forks, only use the token for runs in the parent repo. + # Token is intentionally exposed. + # See: https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954 + CODECOV_TOKEN="8118de48-b2af-48b4-a66a-26026847bfc5" + else + # codecov/codecov-action does unauthenticated upload if empty string is passed via the `token` input. + CODECOV_TOKEN="" + fi + echo "CODECOV_TOKEN=$CODECOV_TOKEN" >> "$GITHUB_ENV" + + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v3 + with: + file: "${{ env.COVERAGE_DATA_PATH }}" + fail_ci_if_error: true + token: ${{ env.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9bea4330 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +.DS_Store diff --git a/CHANGELOG b/CHANGELOG index a29f8598..d8a84716 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ ArduinoBLE ?.?.? - ????.??.?? ArduinoBLE 1.1.2 - 2019.11.12 -* cordio: switch to lower power when polling with timeout +* cordio: switch to lower power when polling with timeout * Fixed issue with wrong types for disconnection event handling. Thanks @cparata ArduinoBLE 1.1.1 - 2019.09.05 diff --git a/README.md b/README.md index b2f649ca..b0027583 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ [![Compile Examples Status](https://github.com/arduino-libraries/ArduinoBLE/workflows/Compile%20Examples/badge.svg)](https://github.com/arduino-libraries/ArduinoBLE/actions?workflow=Compile+Examples) [![Spell Check Status](https://github.com/arduino-libraries/ArduinoBLE/workflows/Spell%20Check/badge.svg)](https://github.com/arduino-libraries/ArduinoBLE/actions?workflow=Spell+Check) -Enables BLE connectivity on the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev.2, Arduino Nano 33 IoT, and Arduino Nano 33 BLE. +Enables Bluetooth® Low Energy connectivity on the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev2, Arduino Nano 33 IoT, Arduino Nano 33 BLE, Arduino Portenta H7, Arduino Giga R1 and Arduino UNO R4 WiFi. -This library supports creating a BLE peripheral and BLE central mode. +This library supports creating a Bluetooth® Low Energy peripheral & central mode. -For the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev.2, and Arduino Nano 33 IoT boards, it requires the NINA module to be running [Arduino NINA-W102 firmware](https://github.com/arduino/nina-fw) v1.2.0 or later. +For the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev2, and Arduino Nano 33 IoT boards, it requires the NINA module to be running [Arduino NINA-W102 firmware](https://github.com/arduino/nina-fw) v1.2.0 or later. + +For the Arduino UNO R4 WiFi, it requires the ESP32-S3 module to be running [firmware](https://github.com/arduino/uno-r4-wifi-usb-bridge) v0.2.0 or later. For more information about this library please visit us at: diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..d80d0cdc --- /dev/null +++ b/docs/api.md @@ -0,0 +1,3820 @@ +# ArduinoBLE library + +## BLE class + +Used to enable the Bluetooth® Low Energy module. + +### `BLE.begin()` + +Initializes the Bluetooth® Low Energy device. + +#### Syntax + +``` +BLE.begin() + +``` + +#### Parameters + +None + +#### Returns +- 1 on success +- 0 on failure + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + +``` + +### `BLE.end()` + +Stops the Bluetooth® Low Energy device. + +#### Syntax + +``` +BLE.end() + +``` + +#### Parameters + +None + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // .... + + BLE.end(); + + +``` + +### `BLE.poll()` + +Poll for Bluetooth® Low Energy radio events and handle them. + +#### Syntax + +``` +BLE.poll() +BLE.poll(timeout) + +``` + +#### Parameters + +**timeout**: optional timeout in ms, to wait for event. If not specified defaults to 0 ms. + +#### Returns +Nothing + +#### Example + +```arduino + + // assign event handlers for connected, disconnected to peripheral + BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler); + BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler); + + + BLE.poll(); + + +``` + +### `BLE.setEventHandler()` + +Set the event handler (callback) function that will be called when the specified event occurs. + +#### Syntax + +``` +BLE.setEventHandler(eventType, callback) + +``` + +#### Parameters + +- **eventType**: event type (BLEConnected, BLEDisconnected) +- **callback**: function to call when event occurs +#### Returns +Nothing. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + // assign event handlers for connected, disconnected to peripheral + BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler); + BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler); + + + +void blePeripheralConnectHandler(BLEDevice central) { + // central connected event handler + Serial.print("Connected event, central: "); + Serial.println(central.address()); +} + +void blePeripheralDisconnectHandler(BLEDevice central) { + // central disconnected event handler + Serial.print("Disconnected event, central: "); + Serial.println(central.address()); +} + + +``` + +### `BLE.connected()` + +Query if another Bluetooth® Low Energy device is connected + +#### Syntax + +``` +BLE.connected() + +``` + +#### Parameters + +None + +#### Returns +- **true** if another Bluetooth® Low Energy device is connected, +- otherwise **false**. + +#### Example + +```arduino + + // while the central is still connected to peripheral: + while (BLE.connected()) { + + // ... + } + + +``` + +### `BLE.disconnect()` + +Disconnect any Bluetooth® Low Energy devices that are connected + +#### Syntax + +``` +BLE.disconnect() + +``` + +#### Parameters + +None + +#### Returns +- **true** if any Bluetooth® Low Energy device that was previously connected was disconnected, +- otherwise **false**. + +#### Example + +```arduino + + if (BLE.connected()) { + BLE.disconnect(); + } + + +``` + +### `BLE.address()` + +Query the Bluetooth® address of the Bluetooth® Low Energy device. + +#### Syntax + +``` +BLE.address() + +``` + +#### Parameters + +None + +#### Returns +- The **Bluetooth® address** of the Bluetooth® Low Energy device (as a String). + +#### Example + +```arduino + + String address = BLE.address(); + + Serial.print("Local address is: "); + Serial.println(address); + + +``` + +### `BLE.rssi()` + +Query the RSSI (Received signal strength indication) of the connected Bluetooth® Low Energy device. + +#### Syntax + +``` +BLE.rssi() + +``` + +#### Parameters + +None + +#### Returns +- The **RSSI** of the connected Bluetooth® Low Energy device, 127 if no Bluetooth® Low Energy device is connected. + +#### Example + +```arduino + + if (BLE.connected()) { + Serial.print("RSSI = "); + Serial.println(BLE.rssi()); + } + + +``` + +### `BLE.setAdvertisedServiceUuid()` + +Set the advertised service UUID used when advertising. + +#### Syntax + +``` +BLE.setAdvertisedServiceUuid(uuid) + +``` + +#### Parameters + +- **uuid:** 16-bit or 128-bit Bluetooth® Low Energy UUID in **String** format + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + BLE.setAdvertisedServiceUuid("19B10000-E8F2-537E-4F6C-D104768A1214"); + + // ... + + // start advertising + BLE.advertise(); + + +``` + +### `BLE.setAdvertisedService()` + +Set the advertised service UUID used when advertising to the value of the BLEService provided. + +#### Syntax + +``` +BLE.setAdvertisedService(bleService) + +``` + +#### Parameters + +- **bleService:** BLEService to use UUID from + +#### Returns +Nothing + +#### Example + +```arduino + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // Bluetooth® Low Energy LED Service + +// ... + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + BLE.setAdvertisedService(ledService); + + // ... + + // start advertising + BLE.advertise(); + + +``` + +### `BLE.setManufacturerData()` + +Set the manufacturer data value used when advertising. + +#### Syntax + +``` +BLE.setManufacturerData(data, length) + +``` + +#### Parameters + +- **data:** byte array containing manufacturer data +- **length:** length of manufacturer data array + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + byte data[5] = { 0x01, 0x02, 0x03, 0x04, 0x05}; + + BLE.setManufacturerData(data, 5); + + // ... + + // start advertising + BLE.advertise(); + + + +``` + +### `BLE.setLocalName()` + +Set the local value used when advertising. + +#### Syntax + +``` +BLE.setLocalName(name) + +``` + +#### Parameters + +- **name:** local name value to use when advertising + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + BLE.setLocalName("LED"); + + // ... + + // start advertising + BLE.advertise(); + + + +``` + +### `BLE.setDeviceName()` + +Set the device name in the built in device name characteristic. If not set, the value defaults to “Arduino”. + +#### Syntax + +``` +BLE.setDeviceName(name) + +``` + +#### Parameters + +- **name:** device name value + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + BLE.setDeviceName("LED"); + + // ... + + // start advertising + BLE.advertise(); + + + +``` + +### `BLE.setAppearance()` + +Set the appearance in the built in appearance characteristic. If not set, the value defaults to 0x0000. + +#### Syntax + +``` +BLE.setAppearance(appearance) + +``` + +#### Parameters + +- **appearance:** appearance value + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + BLE.setAppearance(0x8000); + + // ... + + // start advertising + BLE.advertise(); + + + +``` + +### `BLE.addService()` + +Add a BLEService to the set of services the Bluetooth® Low Energy device provides + +#### Syntax + +``` +BLE.addService(service) + +``` + +#### Parameters + +- **service:** BLEService to add + +#### Returns +Nothing + +#### Example + +```arduino + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // Bluetooth® Low Energy LED Service + + + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + BLE.addService(ledService); + + // ... + + +``` + +### `BLE.advertise()` + +Start advertising. + +#### Syntax + +``` +BLE.advertise() + +``` + +#### Parameters + +None + +#### Returns +- 1 on success, +- 0 on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + BLE.advertise(); + + // ... + + +``` + +### `BLE.stopAdvertise()` + +Stop advertising. + +#### Syntax + +``` +BLE.stopAdvertise() + +``` + +#### Parameters + +None + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + BLE.advertise(); + + // ... + + BLE.stopAdvertise(); + + +``` + +### `BLE.central()` + +Query the central Bluetooth® Low Energy device connected. + +#### Syntax + +``` +BLE.central() + +``` + +#### Parameters + +None + +#### Returns +- **BLEDevice** representing the central. + +#### Example + +```arduino + + // listen for Bluetooth® Low Energy peripherals to connect: + BLEDevice central = BLE.central(); + + // if a central is connected to peripheral: + if (central) { + Serial.print("Connected to central: "); + // print the central's MAC address: + Serial.println(central.address()); + } + + +``` + +### `BLE.setAdvertisingInterval()` + +Set the advertising interval in units of 0.625 ms. Defaults to 100ms (160 * 0.625 ms) if not provided. + +#### Syntax + +``` +BLE.setAdvertisingInterval(advertisingInterval) + +``` + +#### Parameters + +- **advertisingInterval:** advertising interval in units of 0.625 ms + +#### Returns +Nothing. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + BLE.setAdvertisingInterval(320); // 200 * 0.625 ms + + BLE.advertise(); + + +``` + +### `BLE.setConnectionInterval()` + +Set the minimum and maximum desired connection intervals in units of 1.25 ms. + +#### Syntax + +``` +BLE.setConnectionInterval(minimum, maximum) + +``` + +#### Parameters + +- **minimum:** minimum desired connection interval in units of 1.25 ms +- **maximum:** maximum desired connection interval in units of 1.25 ms + +#### Returns +Nothing. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + BLE.setConnectionInterval(0x0006, 0x0c80); // 7.5 ms minimum, 4 s maximum + + + +``` + +### `BLE.setConnectable()` + +Set if the device is connectable after advertising, defaults to **true**. + +#### Syntax + +``` +BLE.setConnectable(connectable) + +``` + +#### Parameters + +- **true**: the device will be connectable when advertising +- **false**: the device will NOT be connectable when advertising + +#### Returns +Nothing. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + // ... + + BLE.setConnectable(false); // make the device unconnectable when advertising + + + +``` + +### `BLE.scan()` + +Start scanning for Bluetooth® Low Energy devices that are advertising. + +#### Syntax + +``` +BLE.scan() +BLE.scan(withDuplicates) + +``` + +#### Parameters + +- **withDuplicates:** optional, defaults to **false**. If **true**, advertisements received more than once will not be filtered + +#### Returns +- 1 on success, +- 0 on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + } + + +``` + +### `BLE.scanForName()` + +Start scanning for Bluetooth® Low Energy devices that are advertising with a particular (local) name. + +#### Syntax + +``` +BLE.scanForName(name) +BLE.scanForName(name, withDuplicates) + +``` + +#### Parameters + +- **name:** (local) name of device (as a **String**) to filter for +- **withDuplicates:** optional, defaults to **false**. If **true**, advertisements received more than once will not be filtered. + +#### Returns +- 1 on success, +- 0 on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scanForName("LED"); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + } + + +``` + +### `BLE.scanForAddress()` + +Start scanning for Bluetooth® Low Energy devices that are advertising with a particular (Bluetooth®) address. + +#### Syntax + +``` +BLE.scanForAddress(address) +BLE.scanForAddress(address, withDuplicates) + +``` + +#### Parameters + +- **address:** (Bluetooth®) address (as a String) to filter for +- **withDuplicates:** optional, defaults to **false**. If **true**, advertisements received more than once will not be filtered + +#### Returns +- 1 on success, +- 0 on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scanForAddress("aa:bb:cc:ee:dd:ff"); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + } + + +``` + +### `BLE.scanForUuid()` + +Start scanning for Bluetooth® Low Energy devices that are advertising with a particular (service) UUID. + +#### Syntax + +``` +BLE.scanForUuid(uuid) +BLE.scanForUuid(uuid, withDuplicates) + +``` + +#### Parameters + +- **uuid:** (service) UUID (as a **String**) to filter for +- **withDuplicates:** optional, defaults to **false**. If **true**, advertisements received more than once will not be filtered. + +#### Returns +- 1 on success, +- 0 on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scanForUuid("aa10"); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + } + + +``` + +### `BLE.stopScan()` + +Stop scanning for Bluetooth® Low Energy devices that are advertising. + +#### Syntax + +``` +BLE.stopScan() + +``` + +#### Parameters + +None + +#### Returns +Nothing + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + BLE.stopScan(); + + +``` + +### `BLE.available()` + +Query for a discovered Bluetooth® Low Energy device that was found during scanning. + +#### Syntax + +``` +BLE.available() + +``` + +#### Parameters + +Nothing + +#### Returns +- **BLEDevice** representing the discovered device. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + } + + +``` + +## BLEDevice Class + +Used to get information about the devices connected or discovered while scanning + +### `bleDevice.poll()` + +Poll for Bluetooth® Low Energy radio events for the specified Bluetooth® Low Energy device and handle them. + +#### Syntax + +``` +bleDevice.poll() +bleDevice.poll(timeout) + +``` + +#### Parameters + +- **timeout**: optional timeout in ms, to wait for event. If not specified defaults to 0 ms. + +#### Returns +Nothing + +#### Example + +```arduino + + // listen for Bluetooth® Low Energy centrals to connect: + BLEDevice central = BLE.central(); + + // if a central is connected to peripheral: + if (central) { + central.poll(); + + // ... + } + + +``` + +### `bleDevice.connected()` + +Query if a Bluetooth® Low Energy device is connected + +#### Syntax + +``` +bleDevice.connected() + +``` + +#### Parameters + +None + +#### Returns +- **true** if the Bluetooth® Low Energy device is connected, +- otherwise **false**. + +#### Example + +```arduino + + // listen for Bluetooth® Low Energy centrals to connect: + BLEDevice central = BLE.central(); + + // while the central is still connected + while (central.connected()) { + + // ... + } + + +``` + +### `bleDevice.disconnect()` + +Disconnect the Bluetooth® Low Energy device, if connected + +#### Syntax + +``` +bleDevice.disconnect() + +``` + +#### Parameters + +None + +#### Returns +- **true** if the Bluetooth® Low Energy device was disconnected, +- otherwise **false**. + +#### Example + +```arduino + + // listen for Bluetooth® Low Energy centrals to connect: + BLEDevice central = BLE.central(); + + + central.disconnect(); + + +``` + +### `bleDevice.address()` + +Query the Bluetooth® address of the Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.address() + +``` + +#### Parameters + +None + +#### Returns +- **Bluetooth® address** of the Bluetooth® Low Energy device (as a String). + +#### Example + +```arduino + + // listen for Bluetooth® Low Energy peripherals to connect: + BLEDevice central = BLE.central(); + + // if a central is connected to peripheral: + if (central) { + Serial.print("Connected to central: "); + // print the central's MAC address: + Serial.println(central.address()); + } + + +``` + +### `bleDevice.rssi()` + +Query the RSSI (Received signal strength indication) of the Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.rssi() + +``` + +#### Parameters + +None + +#### Returns +- **RSSI** of the connected Bluetooth® Low Energy device, 127 if the Bluetooth® Low Energy device is not connected. + +#### Example + +```arduino + + if (bleDevice.connected()) { + Serial.print("RSSI = "); + Serial.println(bleDevice.rssi()); + } + + +``` + +### `bleDevice.characteristic()` + +Get a BLECharacteristic representing a Bluetooth® Low Energy characteristic the device provides. + +#### Syntax + +``` +bleDevice.characteristic(index) +bleDevice.characteristic(uuid) +bleDevice.characteristic(uuid, index) + +``` + +#### Parameters + +- **index**: index of characteristic +- **uuid**: uuid (as a **String**) + +#### Returns +- **BLECharacteristic** for provided parameters + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + BLECharacteristic batteryLevelCharacteristic = peripheral.characteristic("2a19"); + + if (batteryLevelCharacteristic) { + // use the characteristic + } else { + Serial.println("Peripheral does NOT have battery level characteristic"); + } + + // ... + } + + +``` + +### `bleDevice.discoverAttributes()` + +Discover all of the attributes of Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.discoverAttributes() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if successful, +- **false** on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // ... + } + + +``` + +### `bleDevice.discoverService()` + +Discover the attributes of a particular service on the Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.discoverService(serviceUuid) + +``` + +#### Parameters + +- **serviceUuid:** service UUID to discover (as a **String**) + +#### Returns +- **true**, if successful, +- **false** on failure. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover service attributes + Serial.println("Discovering service attributes ..."); + if (peripheral.serviceUuid("fffe")) { + Serial.println("Service attributes discovered"); + } else { + Serial.println("Service attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // ... + } + + +``` + +### `bleDevice.deviceName()` + +Query the device name (BLE characteristic UUID 0x2a00) of a Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.deviceName() + +``` + +#### Parameters + +None + +#### Returns +- **Device name** (as a String). + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // read and print device name of peripheral + Serial.println(); + Serial.print("Device name: "); + Serial.println(peripheral.deviceName()); + Serial.print("Appearance: 0x"); + Serial.println(peripheral.appearance(), HEX); + Serial.println(); + + // ... + } + + +``` + +### `bleDevice.appearance()` + +Query the appearance (BLE characteristic UUID 0x2a01) of a Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.appearance() + +``` + +#### Parameters + +None + +#### Returns +- **Appearance value** (as a number). + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + // read and print device name of peripheral + Serial.println(); + Serial.print("Device name: "); + Serial.println(peripheral.deviceName()); + Serial.print("Appearance: 0x"); + Serial.println(peripheral.appearance(), HEX); + Serial.println(); + + // ... + } + + +``` + +### `bleDevice.serviceCount()` + +Query the number of services discovered for the Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.serviceCount() + +``` + +#### Parameters + +None + +#### Returns +- The number of **services discovered** for the Bluetooth® Low Energy device. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + int serviceCount = peripheral.serviceCount(); + + Serial.print(serviceCount); + Serial.println(" services discovered"); + + // ... + } + + +``` + +### `bleDevice.hasService()` + +Query if the Bluetooth® Low Energy device has a particular service. + +#### Syntax + +``` +bleDevice.hasService(uuid) +bleDevice.hasService(uuid, index) + +``` + +#### Parameters + +- **uuid**: uuid to check (as a **String**) +- **index**: optional, index of service to check if the device provides more than on. Defaults to 0, if not provided. + +#### Returns +- **true**, if the device provides the service, +- **false** otherwise. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + if (peripheral.hasService("180f")) { + Serial.println("Peripheral has battery service"); + } + + // ... + } + + +``` + +### `bleDevice.service()` + +Get a BLEService representing a Bluetooth® Low Energy service the device provides. + +#### Syntax + +``` +bleDevice.service(index) +bleDevice.service(uuid) +bleDevice.service(uuid, index) + +``` + +#### Parameters + +- **index**: index of service +- **uuid**: uuid (as a **String**) + +#### Returns +- **BLEService** for provided parameters + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + BLEService batteryService = peripheral.service("180f"); + + if (batteryService) { + // use the service + } else { + Serial.println("Peripheral does NOT have battery service"); + } + + // ... + } + + +``` + +### `bleDevice.characteristicCount()` + +Query the number of characteristics discovered for the Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.characteristicCount() + +``` + +#### Parameters + +None + +#### Returns +- The **number of characteristics** discovered for the Bluetooth® Low Energy device. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + int characteristicCount = peripheral.characteristicCount(); + + Serial.print(characteristicCount); + Serial.println(" characteristics discovered"); + + // ... + } + + +``` + +### `bleDevice.hasCharacteristic()` + +Query if the Bluetooth® Low Energy device has a particular characteristic. + +#### Syntax + +``` +bleDevice.hasCharacteristic(uuid) +bleDevice.hasCharacteristic(uuid, index) + +``` + +#### Parameters + +- **uuid**: uuid to check (as a **String**) +- **index**: optional, index of characteristic to check if the device provides more than on. Defaults to 0, if not provided. + +#### Returns +- **true**, if the device provides the characteristic, +- **false** otherwise. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + if (peripheral.hasCharacteristic("2a19")) { + Serial.println("Peripheral has battery level characteristic"); + } + + // ... + } + + +``` + +### `bleDevice.hasLocalName()` + +Query if a discovered Bluetooth® Low Energy device is advertising a local name. + +#### Syntax + +``` +bleDevice.hasLocalName() + +``` + +#### Parameters + +Nothing + +#### Returns +- **true**, if the device is advertising a local name, +- **false** otherwise. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + // print the local name, if present + if (peripheral.hasLocalName()) { + Serial.print("Local Name: "); + Serial.println(peripheral.localName()); + } + + // ... + } + + +``` + +### `bleDevice.hasAdvertisedServiceUuid()` + +Query if a discovered Bluetooth® Low Energy device is advertising a service UUID. + +#### Syntax + +``` +bleDevice.hasAdvertisedServiceUuid() +bleDevice.hasAdvertisedServiceUuid(index) + +``` + +#### Parameters + +- **index**: optional, defaults to 0, the index of the service UUID, if the device is advertising more than one. + +#### Returns +- **true**, if the device is advertising a service UUID, +- **false** otherwise. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + // print the advertised service UUIDs, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUIDs: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // ... + } + + +``` + +### `bleDevice.advertisedServiceUuidCount()` + +Query the number of advertised services a discovered Bluetooth® Low Energy device is advertising. + +#### Syntax + +``` +bleDevice.advertisedServiceUuidCount() + +``` + +#### Parameters + +None + +#### Returns +- The **number of advertised services** a discovered Bluetooth® Low Energy device is advertising. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + // print the advertised service UUIDs, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUIDs: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // ... + } + + +``` + +### `bleDevice.localName()` + +Query the local name a discovered Bluetooth® Low Energy device is advertising with. + +#### Syntax + +``` +bleDevice.localName() + +``` + +#### Parameters + +Nothing + +#### Returns +- **Advertised local name** (as a String). + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + // print the local name, if present + if (peripheral.hasLocalName()) { + Serial.print("Local Name: "); + Serial.println(peripheral.localName()); + } + + // ... + } + + +``` + +### `bleDevice.advertisedServiceUuid()` + +Query an advertised service UUID discovered Bluetooth® Low Energy device is advertising. + +#### Syntax + +``` +bleDevice.advertisedServiceUuid() +bleDevice.advertisedServiceUuid(index) + +``` + +#### Parameters + +- **index**: optional, defaults to 0, the index of the **service UUID**, if the device is advertising more than one. + +#### Returns +- Advertised service **UUID** (as a String). + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + // print the advertised service UUIDs, if present + if (peripheral.hasAdvertisedServiceUuid()) { + Serial.print("Service UUIDs: "); + for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) { + Serial.print(peripheral.advertisedServiceUuid(i)); + Serial.print(" "); + } + Serial.println(); + } + + // ... + } + + +``` + +### `bleDevice.connect()` + +Connect to a Bluetooth® Low Energy device. + +#### Syntax + +``` +bleDevice.connect() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if the connection was successful, +- **false** otherwise. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // ... + } + + +``` + +## BLEService Class + +Used to enable the services board provides or interact with services a remote board provides. + +### `BLEService()` + +Create a new Bluetooth® Low Energy service. + +#### Syntax + +``` +BLEService(uuid) + +``` + +#### Parameters + +- **uuid**: 16-bit or 128-bit UUID in **String** format + +#### Returns +- New **BLEService** with the specified **UUID** + +#### Example + +```arduino + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // Bluetooth® Low Energy LED Service + + +``` + +### `bleService.uuid()` + +Query the UUID of the specified BLEService. + +#### Syntax + +``` +bleService.uuid() + +``` + +#### Parameters + +None + +#### Returns +- UUID of the Bluetooth® Low Energy service as a **String**. + +#### Example + +```arduino + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // Bluetooth® Low Energy LED Service + + +Serial.print("LED service UUID = "); +Serial.println(ledService.uuid()); + + + +``` + +### `bleService.addCharacteristic()` + +Add a BLECharacteristic to the Bluetooth® Low Energy service. + +#### Syntax + +``` +bleService.addCharacteristic(bleCharacteristic) + +``` + +#### Parameters + +None + +#### Returns +Nothing + +#### Example + +```arduino + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // Bluetooth® Low Energy LED Service + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, readable and writable by central +BLECharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite, 1); + + + + +// add the characteristic to the service +ledService.addCharacteristic(switchCharacteristic); + + + +``` + +### `bleService.characteristicCount()` + +Query the number of characteristics discovered for the Bluetooth® Low Energy service. + +#### Syntax + +``` +bleService.characteristicCount() + +``` + +#### Parameters + +None + +#### Returns +- The **number of characteristics** discovered for the Bluetooth® Low Energy service. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + BLEService batteryService = peripheral.service("180f"); + + if (batteryService) { + // use the service + int characteristicCount = batteryService.characteristicCount(); + + Serial.print(characteristicCount); + Serial.println(" characteristics discovered in battery service"); + } else { + Serial.println("Peripheral does NOT have battery service"); + } + + // ... + } + + +``` + +### `bleService.hasCharacteristic()` + +Query if the Bluetooth® Low Energy service has a particular characteristic. + +#### Syntax + +``` +bleService.hasCharacteristic(uuid) +bleService.hasCharacteristic(uuid, index) + +``` + +#### Parameters + +- **uuid**: uuid to check (as a **String**) +- **index**: optional, index of characteristic to check if the device provides more than on. Defaults to 0, if not provided. + +#### Returns +- **true**, if the service provides the characteristic, +- **false** otherwise. + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + BLEService batteryService = peripheral.service("180f"); + + if (batteryService) { + // use the service + if (batteryService.hasCharacteristic("2a19")) { + Serial.println("Battery service has battery level characteristic"); + } + } else { + Serial.println("Peripheral does NOT have battery service"); + } + + // ... + } + + +``` + +### `bleService.characteristic()` + +Get a BLECharacteristic representing a Bluetooth® Low Energy characteristic the service provides. + +#### Syntax + +``` +bleService.characteristic(index) +bleService.characteristic(uuid) +bleService.characteristic(uuid, index) + +``` + +#### Parameters + +- **index**: index of characteristic +- **uuid**: uuid (as a **String**) + +#### Returns +- **BLECharacteristic** for provided parameters + +#### Example + +```arduino + + // begin initialization + if (!BLE.begin()) { + Serial.println("starting Bluetooth® Low Energy module failed!"); + + while (1); + } + + Serial.println("BLE Central scan"); + + // start scanning for peripheral + BLE.scan(); + + + BLEDevice peripheral = BLE.available(); + + if (peripheral) { + // ... + + Serial.println("Connecting ..."); + + if (peripheral.connect()) { + Serial.println("Connected"); + } else { + Serial.println("Failed to connect!"); + return; + } + + // discover peripheral attributes + Serial.println("Discovering attributes ..."); + if (peripheral.discoverAttributes()) { + Serial.println("Attributes discovered"); + } else { + Serial.println("Attribute discovery failed!"); + peripheral.disconnect(); + return; + } + + BLEService batteryService = peripheral.service("180f"); + + if (batteryService) { + // use the service + BLECharacteristic batteryLevelCharacteristic = peripheral.characteristic("2a19"); + + if (batteryLevelCharacteristic) { + // use the characteristic + } else { + Serial.println("Peripheral does NOT have battery level characteristic"); + } + } else { + Serial.println("Peripheral does NOT have battery service"); + } + + // ... + } + + +``` + +## BLECharacteristic Class + +Used to enable the characteristics board offers in a service or interact with characteristics a remote board provides. + +### `BLECharacteristic()` + +Create a new Bluetooth® Low Energy characteristic. + +#### Syntax + +``` +BLECharacteristic(uuid, properties, valueSize) +BLECharacteristic(uuid, properties, valueSize, fixedLength) +BLECharacteristic(uuid, properties, stringValue) + +BLEBoolCharacteristic(uuid, properties) +BLEBooleanCharacteristic(uuid, properties) +BLECharCharacteristic(uuid, properties) +BLEUnsignedCharCharacteristic(uuid, properties) +BLEByteCharacteristic(uuid, properties) +BLEShortCharacteristic(uuid, properties) +BLEUnsignedShortCharacteristic(uuid, properties) +BLEWordCharacteristic(uuid, properties) +BLEIntCharacteristic(uuid, properties) +BLEUnsignedIntCharacteristic(uuid, properties) +BLELongCharacteristic(uuid, properties) +BLEUnsignedLongCharacteristic(uuid, properties) +BLEFloatCharacteristic(uuid, properties) +BLEDoubleCharacteristic(uuid, properties) +``` + +#### Parameters + +- **uuid**: 16-bit or 128-bit UUID in **String** format +- **properties**: mask of the properties (BLEBroadcast, BLERead, BLEWriteWithoutResponse, BLEWrite, BLENotify, BLEIndicate) +- **valueSize**: (maximum) size of characteristic value +- **fixedLength**: if true, size of characteristic value is fixed +- **stringValue**: value as a string + +#### Returns +- New **BLECharacteristic** with the specified **UUID** and value + +#### Example + +```arduino + +// Bluetooth® Low Energy Battery Level Characteristic +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID + BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes + + + +``` + +### `bleCharacteristic.uuid()` + +Query the UUID of the specified BLECharacteristic. + +#### Syntax + +``` +bleCharacteristic.uuid() + +``` + +#### Parameters + +None + +#### Returns +- **UUID** of the Bluetooth® Low Energy service as a **String**. + +#### Example + +```arduino + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + +Serial.print("Switch characteristic UUID = "); +Serial.println(switchCharacteristic.uuid()); + + + +``` + +### `bleCharacteristic.properties()` + +Query the property mask of the specified BLECharacteristic. + +#### Syntax + +``` +bleCharacteristic.properties() + +``` + +#### Parameters + +None + +#### Returns +- **Properties of the characteristic masked** (BLEBroadcast, BLERead, BLEWriteWithoutResponse, BLEWrite, BLENotify, BLEIndicate) + +#### Example + +```arduino + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + +byte properties = switchCharacteristic.properties(); + +if (properties & BLERead) { + // characteristic is readable ... +} + +if (properties & (BLEWrite | BLEWriteWithoutResponse)) { + // characteristic is writable ... +} + + +``` + +### `bleCharacteristic.valueSize()` + +Query the maximum value size of the specified BLECharacteristic. + +#### Syntax + +``` +bleCharacteristic.valueSize() + +``` + +#### Parameters + +None + +#### Returns +- The **maximum value** size of the characteristic (in bytes) + +#### Example + +```arduino + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + + +Serial.print("value size = "); +Serial.println(switchCharacteristic.valueSize()); + + +``` + +### `bleCharacteristic.value()` + +Query the current value of the specified BLECharacteristic. + +#### Syntax + +``` +bleCharacteristic.value() + +``` + +#### Parameters + +None + +#### Returns +- The **current value** of the characteristic, value type depends on the constructor used + +#### Example + +```arduino + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + + + if (switchCharacteristic.value()) { // any value other than 0 + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); // will turn the LED on + } else { // a 0 value + Serial.println(F("LED off")); + digitalWrite(ledPin, LOW); // will turn the LED off + } + + + +``` + +### `bleCharacteristic.valueLength()` + +Query the current value size of the specified BLECharacteristic. + +#### Syntax + +``` +bleCharacteristic.valueLength() + +``` + +#### Parameters + +None + +#### Returns +- The **current value** size of the characteristic (in bytes) + +#### Example + +```arduino + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + + +Serial.print("value length = "); +Serial.println(switchCharacteristic.valueLength()); + + +``` + +### `bleCharacteristic.readValue()` + +Read the current value of the characteristic. If the characteristic is on a remote device, a read request will be sent. + +#### Syntax + +``` +bleCharacteristic.readValue(buffer, length) +bleCharacteristic.readValue(value) + +``` + +#### Parameters + +- **buffer:** byte array to read value into length: size of buffer argument in bytes +- **value**: variable to read value into (by reference) + +#### Returns +- **Number of bytes** read + +#### Example + +```arduino + + while (peripheral.connected()) { + // while the peripheral is connected + + // check if the value of the simple key characteristic has been updated + if (simpleKeyCharacteristic.valueUpdated()) { + // yes, get the value, characteristic is 1 byte so use byte value + byte value = 0; + + simpleKeyCharacteristic.readValue(value); + + if (value & 0x01) { + // first bit corresponds to the right button + Serial.println("Right button pressed"); + } + + if (value & 0x02) { + // second bit corresponds to the left button + Serial.println("Left button pressed"); + } + } + } + + +``` + +### `bleCharacteristic.writeValue()` + +Write the value of the characteristic. If the characteristic is on a remote device, a write request or command will be sent. + +#### Syntax + +``` +bleCharacteristic.writeValue(buffer, length) +bleCharacteristic.writeValue(value) + +``` + +#### Parameters + +- **buffer**: byte array to write value with +- **length**: number of bytes of the buffer argument to write +- **value**: value to write + +#### Returns +- 1 on success, +- 0 on failure + +#### Example + +```arduino + + // read the button pin + int buttonState = digitalRead(buttonPin); + + if (oldButtonState != buttonState) { + // button changed + oldButtonState = buttonState; + + if (buttonState) { + Serial.println("button pressed"); + + // button is pressed, write 0x01 to turn the LED on + ledCharacteristic.writeValue((byte)0x01); + } else { + Serial.println("button released"); + + // button is released, write 0x00 to turn the LED off + ledCharacteristic.writeValue((byte)0x00); + } + } + + +``` + +### `bleCharacteristic.setEventHandler()` + +Set the event handler (callback) function that will be called when the specified event occurs. + +#### Syntax + +``` +bleCharacteristic.setEventHandler(eventType, callback) + +``` + +#### Parameters + +- **eventType**: event type (BLESubscribed, BLEUnsubscribed, BLERead, BLEWritten) +- **callback**: function to call when the event occurs + +#### Returns +Nothing + +#### Example + +```arduino + +// create switch characteristic and allow remote device to read and write +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + + + + // assign event handlers for characteristic + switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten); + + + +void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) { + // central wrote new value to characteristic, update LED + Serial.print("Characteristic event, written: "); + + if (switchCharacteristic.value()) { + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); + } else { + Serial.println("LED off"); + digitalWrite(ledPin, LOW); + } +} + + +``` + +### `bleCharacteristic.broadcast()` + +Broadcast the characteristics value as service data when advertising. + +#### Syntax + +``` +bleCharacteristic.broadcast() + +``` + +#### Parameters + +None + +#### Returns +- 1 on success, +- 0 on failure + +#### Example + +```arduino + +// create button characteristic and allow remote device to get notifications +BLEByteCharacteristic buttonCharacteristic("19B10012-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify | BLEBroadcast); + + + +buttonCharacteristic.broadcast(); + + + +``` + +### `bleCharacteristic.written()` + +Query if the characteristic value has been written by another Bluetooth® Low Energy device. + +#### Syntax + +``` +bleCharacteristic.written() + +``` + +#### Parameters + +None + +#### Returns +- **true** if the characteristic value has been written by another Bluetooth® Low Energy device, +- **false** otherwise + +#### Example + +```arduino + +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + + + + + // listen for Bluetooth® Low Energy peripherals to connect: + BLEDevice central = BLE.central(); + + // if a central is connected to peripheral: + if (central) { + Serial.print("Connected to central: "); + // print the central's MAC address: + Serial.println(central.address()); + + // while the central is still connected to peripheral: + while (central.connected()) { + // if the remote device wrote to the characteristic, + // use the value to control the LED: + if (switchCharacteristic.written()) { + if (switchCharacteristic.value()) { // any value other than 0 + Serial.println("LED on"); + digitalWrite(ledPin, HIGH); // will turn the LED on + } else { // a 0 value + Serial.println(F("LED off")); + digitalWrite(ledPin, LOW); // will turn the LED off + } + } + } + + // when the central disconnects, print it out: + Serial.print(F("Disconnected from central: ")); + Serial.println(central.address()); + } + + + + +``` + +### `bleCharacteristic.subscribed()` + +Query if the characteristic has been subscribed to by another Bluetooth® Low Energy device. + +#### Syntax + +``` +bleCharacteristic.subscribed() + +``` + +#### Parameters + +None + +#### Returns +- **true** if the characteristic value has been subscribed to by another Bluetooth® Low Energy device, +- **false** otherwise + +#### Example + +```arduino + +// Bluetooth® Low Energy Battery Level Characteristic +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID + BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes + + + + + + if (batteryLevelChar.subscribed()) { + // set a new value , that well be pushed to subscribed Bluetooth® Low Energy devices + batteryLevelChar.writeValue(0xab); + } + + +``` + +### `bleCharacteristic.addDescriptor()` + +Add a BLEDescriptor to the characteristic. + +#### Syntax + +``` +bleCharacteristic.addDescriptor(bleDescriptor) + +``` + +#### Parameters + +- **bleDescriptor**: descriptor to add to the characteristic + +#### Returns +Nothing + +#### Example + +```arduino + +// Bluetooth® Low Energy Battery Level Characteristic +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID + BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes + +BLEDescriptor batteryLevelDescriptor("2901", "millis"); + + + + + + batteryLevelChar.addDescriptor(batteryLevelDescriptor); + + +``` + +### `bleCharacteristic.descriptorCount()` + +Query the number of Bluetooth® Low Energy descriptors discovered for the characteristic. + +#### Syntax + +``` +bleCharacteristic.descriptorCount() + +``` + +#### Parameters + +None + +#### Returns +- The **number of Bluetooth® Low Energy descriptors** discovered for the characteristic + +#### Example + +```arduino + + // loop the descriptors of the characteristic and explore each + for (int i = 0; i < characteristic.descriptorCount(); i++) { + BLEDescriptor descriptor = characteristic.descriptor(i); + + // ... + } + + +``` + +### `bleCharacteristic.hasDescriptor()` + +Check if a characteristic has a particular descriptor. + +#### Syntax + +``` +bleCharacteristic.hasDescriptor(uuid) +bleCharacteristic.hasDescriptor(uuid, index) + +``` + +#### Parameters + +- **index**: index of descriptor +- **uuid**: uuid (as a **String**) + +#### Returns +- **true**, if the characteristic has a matching descriptor, +- otherwise **false**. + +#### Example + +```arduino + + if (characteristic.hasDescriptor("2901")) { + Serial.println("characteristic has description descriptor"); + } + + +``` + +### `bleCharacteristic.descriptor()` + +Get a BLEDescriptor that represents a characteristics Bluetooth® Low Energy descriptor. + +#### Syntax + +``` +bleCharacteristic.descriptor(index) +bleCharacteristic.descriptor(uuid) +bleCharacteristic.descriptor(uuid, index) + +``` + +#### Parameters + +- **index**: index of descriptor +- **uuid**: uuid (as a **String**) + +#### Returns +- BLEDescriptor that represents a characteristics Bluetooth® Low Energy descriptor + +#### Example + +```arduino + + if (characteristic.hasDescriptor("2901")) { + Serial.println("characteristic has description descriptor"); + } + + +``` + +### `bleCharacteristic.canRead()` + +Query if a Bluetooth® Low Energy characteristic is readable. + +#### Syntax + +``` +bleCharacteristic.canRead() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if characteristic is readable, +- **false** otherwise + +#### Example + +```arduino + + if (characteristic.canRead("2901")) { + Serial.println("characteristic is readable"); + } + + +``` + +read + +Perform a read request for the characteristic. + +#### Syntax + +``` +bleCharacteristic.read() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if successful, +- **false** on failure + +#### Example + +```arduino + + if (characteristic.read()) { + Serial.println("characteristic value read"); + + // ... + } else { + Serial.println("error reading characteristic value"); + } + + +``` + +### `bleCharacteristic.canWrite()` + +Query if a Bluetooth® Low Energy characteristic is writable. + +#### Syntax + +``` +bleCharacteristic.canWrite() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if characteristic is writable, +- **false** otherwise + +#### Example + +```arduino + + if (characteristic.canWrite()) { + Serial.println("characteristic is writable"); + } + + +``` + +### `bleCharacteristic.canSubscribe()` + +Query if a Bluetooth® Low Energy characteristic is subscribable. + +#### Syntax + +``` +bleCharacteristic.canSubscribe() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if characteristic is subscribable, +- **false** otherwise + +#### Example + +```arduino + + if (characteristic.canSubscribe()) { + Serial.println("characteristic is subscribable"); + } + + +``` + +### `bleCharacteristic.subscribe()` + +Subscribe to a Bluetooth® Low Energy characteristics notification or indications. + +#### Syntax + +``` +bleCharacteristic.subscribe() + +``` + +#### Parameters + +None + +#### Returns +- **true**, on success, +- **false** on failure + +#### Example + +```arduino + + // ... + + // retrieve the simple key characteristic + BLECharacteristic simpleKeyCharacteristic = peripheral.characteristic("ffe1"); + + // subscribe to the simple key characteristic + Serial.println("Subscribing to simple key characteristic ..."); + if (!simpleKeyCharacteristic) { + Serial.println("no simple key characteristic found!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.canSubscribe()) { + Serial.println("simple key characteristic is not subscribable!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.subscribe()) { + Serial.println("subscription failed!"); + peripheral.disconnect(); + return; + } + + // ... + + +``` + +### `bleCharacteristic.canUnsubscribe()` + +Query if a Bluetooth® Low Energy characteristic is unsubscribable. + +#### Syntax + +``` +bleCharacteristic.canUnsubscribe() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if characteristic is unsubscribable, +- **false** otherwise + +#### Example + +```arduino + + if (characteristic.canUnsubscribe()) { + Serial.println("characteristic is unsubscribable"); + } + + +``` + +### `bleCharacteristic.unsubscribe()` + +Unsubscribe to a Bluetooth® Low Energy characteristics notifications or indications. + +#### Syntax + +``` +bleCharacteristic.unsubscribe() + +``` + +#### Parameters + +None + +#### Returns +- **true**, on success, +- **false** on failure + +#### Example + +```arduino + + // ... + + // retrieve the simple key characteristic + BLECharacteristic simpleKeyCharacteristic = peripheral.characteristic("ffe1"); + + // subscribe to the simple key characteristic + Serial.println("Subscribing to simple key characteristic ..."); + if (!simpleKeyCharacteristic) { + Serial.println("no simple key characteristic found!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.canSubscribe()) { + Serial.println("simple key characteristic is not subscribable!"); + peripheral.disconnect(); + return; + } else if (!simpleKeyCharacteristic.subscribe()) { + Serial.println("subscription failed!"); + peripheral.disconnect(); + return; + } + + // ... + + simpleKeyCharacteristic.unsubscribe(); + + +``` + +### `bleCharacteristic.valueUpdated()` + +Has the characteristics value been updated via a notification or indication. + +#### Syntax + +``` +bleCharacteristic.valueUpdated() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if the characteristics value been updated via a notification or indication + +#### Example + +```arduino + + while (peripheral.connected()) { + // while the peripheral is connected + + // check if the value of the simple key characteristic has been updated + if (simpleKeyCharacteristic.valueUpdated()) { + // yes, get the value, characteristic is 1 byte so use byte value + byte value = 0; + + simpleKeyCharacteristic.readValue(value); + + if (value & 0x01) { + // first bit corresponds to the right button + Serial.println("Right button pressed"); + } + + if (value & 0x02) { + // second bit corresponds to the left button + Serial.println("Left button pressed"); + } + } + } + + +``` + +## BLEDescriptor Class + +Used to describe a characteristic the board offers + +### `BLEDescriptor()` + +Create a new Bluetooth® Low Energy descriptor. + +#### Syntax + +``` +BLEDescriptor(uuid, value, valueSize) +BLEDescriptor(uuid, stringValue) + +``` + +#### Parameters + +- **uuid**: 16-bit or 128-bit UUID in string format +- **value**: byte array value +- **valueSize**: size of byte array value +- **stringValue**: value as a string + +#### Returns +- New **BLEDescriptor** with the specified **UUID** and value + +#### Example + +```arduino + +BLEDescriptor millisLabelDescriptor("2901", "millis"); + + +``` + +### `bleDescriptor.uuid()` + +Query the UUID of the specified BLEDescriptor. + +#### Syntax + +``` +bleDescriptor.uuid() + +``` + +#### Parameters + +None + +#### Returns +- **UUID** of the Bluetooth® Low Energy descriptor (as a String). + +#### Example + +```arduino + +BLEDescriptor millisLabelDescriptor("2901", "millis"); + + +Serial.print("millis label descriptor UUID = "); +Serial.println(millisLabelDescriptor.uuid()); + + + +``` + +### `bleDescriptor.valueSize()` + +Query the value size of the specified BLEDescriptor. + +#### Syntax + +``` +bleDescriptor.valueSize() + +``` + +#### Parameters + +None + +#### Returns +- **Value size** (in bytes) of the Bluetooth® Low Energy descriptor. + +#### Example + +```arduino + +BLEDescriptor millisLabelDescriptor("2901", "millis"); + + +Serial.print("millis label descriptor value size = "); +Serial.println(millisLabelDescriptor.valueSize()); + + + +``` + +### `bleDescriptor.valueLength()` + +Query the length, in bytes, of the descriptor current value. + +#### Syntax + +``` +bleDescriptor.valueLength() + +``` + +#### Parameters + +None + +#### Returns +- **Length of descriptor** value in bytes. + +#### Example + +```arduino + + // read the descriptor value + descriptor.read(); + + // print out the value of the descriptor + Serial.print(", value 0x"); + printData(descriptor.value(), descriptor.valueLength()); + + // ... + + void printData(const unsigned char data[], int length) { + for (int i = 0; i < length; i++) { + unsigned char b = data[i]; + + if (b < 16) { + Serial.print("0"); + } + + Serial.print(b, HEX); + } + } + + +``` + +### `bleDescriptor.value()` + +Query the value of the specified BLEDescriptor. + +#### Syntax + +``` +bleDescriptor.value() + +``` + +#### Parameters + +None + +#### Returns +- Value byte array of the **BLE descriptor**. + +#### Example + +```arduino + +BLEDescriptor millisLabelDescriptor("2901", "millis"); + + + + int descriptorValueSize = millisLabelDescriptor.valueSize(); + byte descriptorValue[descriptorValueSize]; + + for (int i = 0; i < descriptorValueSize; i++) { + descriptorValue[i] = millisLabelDescriptor.value()[i]; + } + + + +``` + +### `bleDescriptor.readValue()` + +Read the current value of the descriptor. If the descriptor is on a remote device, a read request will be sent. + +#### Syntax + +``` +bleDescriptor.readValue(buffer, length) +bleDescriptor.readValue(value) + +``` + +#### Parameters + +- **buffer**: byte array to read value into +- **length**: size of buffer argument in bytes +- **value**: variable to read value into (by reference) + +#### Returns +- **Number of bytes** read + +#### Example + + +```arduino + + byte value = 0; + + // get the value, descriptor is 1 byte so use byte value + descriptor.readValue(value); + + +``` + +### `bleDescriptor.read()` + +Perform a read request for the descriptor. + +#### Syntax + +``` +bleDescriptor.read() + +``` + +#### Parameters + +None + +#### Returns +- **true**, if successful, +- **false** on failure + +#### Example + +```arduino + + if (descriptor.read()) { + Serial.println("descriptor value read"); + + // ... + } else { + Serial.println("error reading descriptor value"); + } + +``` diff --git a/docs/assets/ble-bulletin-board-model.png b/docs/assets/ble-bulletin-board-model.png new file mode 100644 index 00000000..409ae9e8 Binary files /dev/null and b/docs/assets/ble-bulletin-board-model.png differ diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 00000000..b584d71e --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,91 @@ +# ArduinoBLE library + +This library supports all the Arduino boards that have the hardware enabled for Bluetooth® Low Energy and Bluetooth® 4.0 and above; these include Nano 33 BLE, Arduino NANO 33 IoT, Uno WiFi Rev2, MKR WiFi 1010, Nicla Sense ME. + +To use this library +``#include `` + +## A quick introduction to BLE + +Bluetooth® 4.0 includes both traditional Bluetooth®, now labeled "Bluetooth® Classic", and the Bluetooth® Low Energy. Bluetooth® Low Energy is optimized for low power use at low data rates, and was designed to operate from simple lithium coin cell batteries. + +Unlike standard Bluetooth® communication basically based on an asynchronous serial connection (UART) a Bluetooth® LE radio acts like a community bulletin board. The computers that connect to it are like community members that read the bulletin board. Each radio acts as either the bulletin board or the reader. If your radio is a bulletin board (called a peripheral device in Bluetooth® LE parlance) it posts data for all radios in the community to read. If your radio is a reader (called a central device in Bluetooth LE terms) it reads from any of the bulletin boards (peripheral devices) that have information about which it cares. You can also think of peripheral devices as the servers in a client-server transaction, because they contain the information that reader radios ask for. Similarly, central devices are the clients of the Bluetooth® LE world because they read information available from the peripherals. + +![Communication between central and peripheral devices](https://raw.githubusercontent.com/arduino-libraries/ArduinoBLE/master/docs/assets/ble-bulletin-board-model.png) + +Think of a Bluetooth® LE peripheral device as a bulletin board and central devices as viewers of the board. Central devices view the services, get the data, then move on. Each transaction is quick (a few milliseconds), so multiple central devices can get data from one peripheral. + +The information presented by a peripheral is structured as **services**, each of which is subdivided into **characteristics**. You can think of services as the notices on a bulletin board, and characteristics as the individual paragraphs of those notices. If you're a peripheral device, you just update each service characteristic when it needs updating and don't worry about whether the central devices read them or not. If you're a central device, you connect to the peripheral then read the boxes you want. If a given characteristic is readable and writable, then the peripheral and central can both change it. + +## Notify + +The Bluetooth® LE specification includes a mechanism known as **notify** that lets you know when data's changed. When notify on a characteristic is enabled and the sender writes to it, the new value is automatically sent to the receiver, without the receiver explicitly issuing a read command. This is commonly used for streaming data such as accelerometer or other sensor readings. There's a variation on this specification called **indicate** which works similarly, but in the indicate specification, the reader sends an acknowledgment of the pushed data. + +The client-server structure of Bluetooth® LE, combined with the notify characteristic, is generally called a **publish-and-subscribe model**. + +## Update a characteristic + +Your peripheral should update characteristics when there's a significant change to them. For example, when a switch changes from off to on, update its characteristic. When an analog sensor changes by a significant amount, update its characteristic. + +Just as with writing to a characteristic, you could update your characteristics on a regular interval, but this wastes processing power and energy if the characteristic has not changed. + +## Central and Peripheral Devices + +**Central** devices are **clients**. They read and write data from peripheral devices. **Peripheral** devices are **servers**. They provide data from sensors as readable characteristics, and provide read/writable characteristics to control actuators like motors, lights, and so forth. + +## Services, characteristics, and UUIDs + +A Bluetooth® Low Energy peripheral will provide **services**, which in turn provide **characteristics**. You can define your own services, or use standard services (see section 3.4 in the [Assigned Numbers document](https://www.bluetooth.com/specifications/assigned-numbers/)). + +Services are identified by unique numbers known as UUIDs. You know about UUIDs from other contexts. Standard services have a 16-bit UUID and custom services have a 128-bit UUID. The ability to define services and characteristics depends on the radio you're using and its firmware. + +## Service design patterns + +A characteristic value can be up to 512 bytes long. This is a key constraint in designing services. Given this limit, you should consider how best to store data about your sensors and actuators most effectively for your application. The simplest design pattern is to store one sensor or actuator value per characteristic, in ASCII encoded values. + +|**Characteristic**|**Value**| +|------------------|---------| +|Accelerometer X|200| +|Accelerometer Y|134| +|Accelerometer Z|150| + +This is also the most expensive in memory terms, and would take the longest to read. But it's the simplest for development and debugging. + +You could also combine readings into a single characteristic, when a given sensor or actuator has multiple values associated with it. + +|**Characteristic**|**Value**| +|------------------|---------| +|Motor Speed, Direction|150,1| +|Accelerometer X, Y, Z|200,133,150| + +This is more efficient, but you need to be careful not to exceed the 512-byte limit. The accelerometer characteristic above, for example, takes 11 bytes as an ASCII-encoded string. + +## Read/write/notify/indicate + +There are 4 things a central device can do with a characteristic: + +- **Read:** ask the peripheral to send back the current value of the characteristic. Often used for characteristics that don't change very often, for example characteristics used for configuration, version numbers, etc. +- **Write:** modify the value of the characteristic. Often used for things that are like commands, for example telling the peripheral to turn a motor on or off. +- **Indicate** and **Notify:** ask the peripheral to continuously send updated values of the characteristic, without the central having to constantly ask for it. + +## Advertising and GAP + +BLE devices let other devices know that they exist by advertising using the **General Advertising Profile (GAP)**. Advertising packets can contain a device name, some other information, and also a list of the services it provides. + +Advertising packets have a limited size. You will only be able to fit a single 128-bit service UUID in the packet. Make sure the device name is not too long, or you won't even be able to fit that. + +You can provide additional services that are not advertised. Central devices will learn about these through the connection/bonding process. Non-advertised services cannot be used to discover devices, though. Sometimes this is not an issue. For example, you may have a custom peripheral device with a custom service, but in your central device app you may know that it also provides the Battery Service and other services. + +## GATT + +The Bluetooth LE protocol operates on multiple layers. **General Attribute Profile (GATT)** is the layer that defines services and characteristics and enables read/write/notify/indicate operations on them. When reading more about GATT, you may encounter GATT concepts of a "server" and "client". These don't always correspond to central and peripherals. In most cases, though, the peripheral is the GATT server (since it provides the services and characteristics), while the central is the GATT client. + +## Library structure + +As the library enables multiple types of functionality, there are a number of different classes. + +- `BLE` used to enable the Bluetooth® Low Energy module. +- `BLEDevice` used to get information about the devices connected or discovered while scanning. +- `BLEService` used to enable the services board provides or interact with services a remote board provides. +- `BLECharacteristic` used to enable the characteristics board offers in a service or interact with characteristics a remote board provides. +- `BLEDescriptor` used to describe a characteristic the board offers. diff --git a/examples/Central/LedControl/LedControl.ino b/examples/Central/LedControl/LedControl.ino index 8301a311..953de7d8 100644 --- a/examples/Central/LedControl/LedControl.ino +++ b/examples/Central/LedControl/LedControl.ino @@ -1,9 +1,9 @@ /* LED Control - This example scans for BLE peripherals until one with the advertised service + This example scans for Bluetooth® Low Energy peripherals until one with the advertised service "19b10000-e8f2-537e-4f6c-d104768a1214" UUID is found. Once discovered and connected, - it will remotely control the BLE Peripheral's LED, when the button is pressed or released. + it will remotely control the Bluetooth® Low Energy peripheral's LED, when the button is pressed or released. The circuit: - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, @@ -29,10 +29,10 @@ void setup() { // configure the button pin as input pinMode(buttonPin, INPUT); - // initialize the BLE hardware + // initialize the Bluetooth® Low Energy hardware BLE.begin(); - Serial.println("BLE Central - LED control"); + Serial.println("Bluetooth® Low Energy Central - LED control"); // start scanning for peripherals BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214"); diff --git a/examples/Central/PeripheralExplorer/PeripheralExplorer.ino b/examples/Central/PeripheralExplorer/PeripheralExplorer.ino index 100225d6..919cdde0 100644 --- a/examples/Central/PeripheralExplorer/PeripheralExplorer.ino +++ b/examples/Central/PeripheralExplorer/PeripheralExplorer.ino @@ -1,7 +1,7 @@ /* Peripheral Explorer - This example scans for BLE peripherals until one with a particular name ("LED") + This example scans for Bluetooth® Low Energy peripherals until one with a particular name ("LED") is found. Then connects, and discovers + prints all the peripheral's attributes. The circuit: @@ -22,12 +22,12 @@ void setup() { // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } - Serial.println("BLE Central - Peripheral Explorer"); + Serial.println("Bluetooth® Low Energy Central - Peripheral Explorer"); // start scanning for peripherals BLE.scan(); diff --git a/examples/Central/Scan/Scan.ino b/examples/Central/Scan/Scan.ino index 3126d1a6..162e3c07 100644 --- a/examples/Central/Scan/Scan.ino +++ b/examples/Central/Scan/Scan.ino @@ -1,7 +1,7 @@ /* Scan - This example scans for BLE peripherals and prints out their advertising details: + This example scans for Bluetooth® Low Energy peripherals and prints out their advertising details: address, local name, advertised service UUID's. The circuit: @@ -19,12 +19,12 @@ void setup() { // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } - Serial.println("BLE Central scan"); + Serial.println("Bluetooth® Low Energy Central scan"); // start scanning for peripheral BLE.scan(); diff --git a/examples/Central/ScanCallback/ScanCallback.ino b/examples/Central/ScanCallback/ScanCallback.ino index 28ab9e9e..2687a3b9 100644 --- a/examples/Central/ScanCallback/ScanCallback.ino +++ b/examples/Central/ScanCallback/ScanCallback.ino @@ -1,7 +1,7 @@ /* Scan Callback - This example scans for BLE peripherals and prints out their advertising details: + This example scans for Bluetooth® Low Energy peripherals and prints out their advertising details: address, local name, advertised service UUIDs. Unlike the Scan example, it uses the callback style APIs and disables filtering so the peripheral discovery is reported for every single advertisement it makes. @@ -21,12 +21,12 @@ void setup() { // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } - Serial.println("BLE Central scan callback"); + Serial.println("Bluetooth® Low Energy Central scan callback"); // set the discovered event handle BLE.setEventHandler(BLEDiscovered, bleCentralDiscoverHandler); diff --git a/examples/Central/SensorTagButton/SensorTagButton.ino b/examples/Central/SensorTagButton/SensorTagButton.ino index 72dad90b..27c421fe 100644 --- a/examples/Central/SensorTagButton/SensorTagButton.ino +++ b/examples/Central/SensorTagButton/SensorTagButton.ino @@ -1,7 +1,7 @@ /* SensorTag Button - This example scans for BLE peripherals until a TI SensorTag is discovered. + This example scans for Bluetooth® Low Energy peripherals until a TI SensorTag is discovered. It then connects to it, discovers the attributes of the 0xffe0 service, subscribes to the Simple Key Characteristic (UUID 0xffe1). When a button is pressed on the SensorTag a notification is received and the button state is @@ -23,12 +23,12 @@ void setup() { // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } - Serial.println("BLE Central - SensorTag button"); + Serial.println("Bluetooth® Low Energy Central - SensorTag button"); Serial.println("Make sure to turn on the device."); // start scanning for peripheral @@ -114,7 +114,7 @@ void monitorSensorTagButtons(BLEDevice peripheral) { if (simpleKeyCharacteristic.valueUpdated()) { // yes, get the value, characteristic is 1 byte so use byte value byte value = 0; - + simpleKeyCharacteristic.readValue(value); if (value & 0x01) { diff --git a/examples/Peripheral/Advertising/EnhancedAdvertising/EnhancedAdvertising.ino b/examples/Peripheral/Advertising/EnhancedAdvertising/EnhancedAdvertising.ino new file mode 100644 index 00000000..979b69a8 --- /dev/null +++ b/examples/Peripheral/Advertising/EnhancedAdvertising/EnhancedAdvertising.ino @@ -0,0 +1,44 @@ +#include + +BLEService myService("fff0"); +BLEIntCharacteristic myCharacteristic("fff1", BLERead | BLEBroadcast); + +// Advertising parameters should have a global scope. Do NOT define them in 'setup' or in 'loop' +const uint8_t manufactData[4] = {0x01, 0x02, 0x03, 0x04}; +const uint8_t serviceData[3] = {0x00, 0x01, 0x02}; + +void setup() { + Serial.begin(9600); + while (!Serial); + + if (!BLE.begin()) { + Serial.println("failed to initialize BLE!"); + while (1); + } + + myService.addCharacteristic(myCharacteristic); + BLE.addService(myService); + + // Build scan response data packet + BLEAdvertisingData scanData; + // Set parameters for scan response packet + scanData.setLocalName("Test enhanced advertising"); + // Copy set parameters in the actual scan response packet + BLE.setScanResponseData(scanData); + + // Build advertising data packet + BLEAdvertisingData advData; + // Set parameters for advertising packet + advData.setManufacturerData(0x004C, manufactData, sizeof(manufactData)); + advData.setAdvertisedService(myService); + advData.setAdvertisedServiceData(0xfff0, serviceData, sizeof(serviceData)); + // Copy set parameters in the actual advertising packet + BLE.setAdvertisingData(advData); + + BLE.advertise(); + Serial.println("advertising ..."); +} + +void loop() { + BLE.poll(); +} diff --git a/examples/Peripheral/Advertising/RawDataAdvertising/RawDataAdvertising.ino b/examples/Peripheral/Advertising/RawDataAdvertising/RawDataAdvertising.ino new file mode 100644 index 00000000..5e7ba7f3 --- /dev/null +++ b/examples/Peripheral/Advertising/RawDataAdvertising/RawDataAdvertising.ino @@ -0,0 +1,41 @@ +#include + +BLEService myService("fff0"); +BLEIntCharacteristic myCharacteristic("fff1", BLERead | BLEBroadcast); + +// Advertising parameters should have a global scope. Do NOT define them in 'setup' or in 'loop' +const uint8_t completeRawAdvertisingData[] = {0x02,0x01,0x06,0x09,0xff,0x01,0x01,0x00,0x01,0x02,0x03,0x04,0x05}; + +void setup() { + Serial.begin(9600); + while (!Serial); + + if (!BLE.begin()) { + Serial.println("failed to initialize BLE!"); + while (1); + } + + myService.addCharacteristic(myCharacteristic); + BLE.addService(myService); + + // Build advertising data packet + BLEAdvertisingData advData; + // If a packet has a raw data parameter, then all the other parameters of the packet will be ignored + advData.setRawData(completeRawAdvertisingData, sizeof(completeRawAdvertisingData)); + // Copy set parameters in the actual advertising packet + BLE.setAdvertisingData(advData); + + // Build scan response data packet + BLEAdvertisingData scanData; + scanData.setLocalName("Test advertising raw data"); + // Copy set parameters in the actual scan response packet + BLE.setScanResponseData(scanData); + + BLE.advertise(); + + Serial.println("advertising ..."); +} + +void loop() { + BLE.poll(); +} diff --git a/examples/Peripheral/BatteryMonitor/BatteryMonitor.ino b/examples/Peripheral/BatteryMonitor/BatteryMonitor.ino index 6c5d3d30..0d786627 100644 --- a/examples/Peripheral/BatteryMonitor/BatteryMonitor.ino +++ b/examples/Peripheral/BatteryMonitor/BatteryMonitor.ino @@ -1,14 +1,14 @@ /* Battery Monitor - This example creates a BLE peripheral with the standard battery service and + This example creates a Bluetooth® Low Energy peripheral with the standard battery service and level characteristic. The A0 pin is used to calculate the battery level. The circuit: - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. - You can use a generic BLE central app, like LightBlue (iOS and Android) or + You can use a generic Bluetooth® Low Energy central app, like LightBlue (iOS and Android) or nRF Connect (Android), to interact with the services and characteristics created in this sketch. @@ -17,10 +17,10 @@ #include - // BLE Battery Service + // Bluetooth® Low Energy Battery Service BLEService batteryService("180F"); -// BLE Battery Level Characteristic +// Bluetooth® Low Energy Battery Level Characteristic BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes @@ -40,9 +40,9 @@ void setup() { while (1); } - /* Set a local name for the BLE device + /* Set a local name for the Bluetooth® Low Energy device This name will appear in advertising packets - and can be used by remote devices to identify this BLE device + and can be used by remote devices to identify this Bluetooth® Low Energy device The name can be changed but maybe be truncated based on space left in advertisement packet */ BLE.setLocalName("BatteryMonitor"); @@ -51,18 +51,18 @@ void setup() { BLE.addService(batteryService); // Add the battery service batteryLevelChar.writeValue(oldBatteryLevel); // set initial value for this characteristic - /* Start advertising BLE. It will start continuously transmitting BLE - advertising packets and will be visible to remote BLE central devices + /* Start advertising Bluetooth® Low Energy. It will start continuously transmitting Bluetooth® Low Energy + advertising packets and will be visible to remote Bluetooth® Low Energy central devices until it receives a new connection */ // start advertising BLE.advertise(); - Serial.println("Bluetooth device active, waiting for connections..."); + Serial.println("Bluetooth® device active, waiting for connections..."); } void loop() { - // wait for a BLE central + // wait for a Bluetooth® Low Energy central BLEDevice central = BLE.central(); // if a central is connected to the peripheral: @@ -73,11 +73,11 @@ void loop() { // turn on the LED to indicate the connection: digitalWrite(LED_BUILTIN, HIGH); - // check the battery level every 200ms + // check the battery level every 200 ms // while the central is connected: while (central.connected()) { long currentMillis = millis(); - // if 200ms have passed, check the battery level: + // if 200 ms have passed, check the battery level: if (currentMillis - previousMillis >= 200) { previousMillis = currentMillis; updateBatteryLevel(); diff --git a/examples/Peripheral/ButtonLED/ButtonLED.ino b/examples/Peripheral/ButtonLED/ButtonLED.ino index bd373731..cbc14dd8 100644 --- a/examples/Peripheral/ButtonLED/ButtonLED.ino +++ b/examples/Peripheral/ButtonLED/ButtonLED.ino @@ -1,7 +1,7 @@ /* Button LED - This example creates a BLE peripheral with service that contains a + This example creates a Bluetooth® Low Energy peripheral with service that contains a characteristic to control an LED and another characteristic that represents the state of the button. @@ -10,7 +10,7 @@ Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. - Button connected to pin 4 - You can use a generic BLE central app, like LightBlue (iOS and Android) or + You can use a generic Bluetooth® Low Energy central app, like LightBlue (iOS and Android) or nRF Connect (Android), to interact with the services and characteristics created in this sketch. @@ -38,7 +38,7 @@ void setup() { // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } @@ -61,18 +61,18 @@ void setup() { // start advertising BLE.advertise(); - Serial.println("Bluetooth device active, waiting for connections..."); + Serial.println("Bluetooth® device active, waiting for connections..."); } void loop() { - // poll for BLE events + // poll for Bluetooth® Low Energy events BLE.poll(); // read the current button pin state char buttonValue = digitalRead(buttonPin); // has the value changed since the last read - boolean buttonChanged = (buttonCharacteristic.value() != buttonValue); + bool buttonChanged = (buttonCharacteristic.value() != buttonValue); if (buttonChanged) { // button state changed, update characteristics diff --git a/examples/Peripheral/CallbackLED/CallbackLED.ino b/examples/Peripheral/CallbackLED/CallbackLED.ino index a874a77b..59bda5ed 100644 --- a/examples/Peripheral/CallbackLED/CallbackLED.ino +++ b/examples/Peripheral/CallbackLED/CallbackLED.ino @@ -1,7 +1,7 @@ /* Callback LED - This example creates a BLE peripheral with service that contains a + This example creates a Bluetooth® Low Energy peripheral with service that contains a characteristic to control an LED. The callback features of the library are used. @@ -9,7 +9,7 @@ - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. - You can use a generic BLE central app, like LightBlue (iOS and Android) or + You can use a generic Bluetooth® Low Energy central app, like LightBlue (iOS and Android) or nRF Connect (Android), to interact with the services and characteristics created in this sketch. @@ -28,12 +28,12 @@ const int ledPin = LED_BUILTIN; // pin to use for the LED void setup() { Serial.begin(9600); while (!Serial); - + pinMode(ledPin, OUTPUT); // use the LED pin as an output // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } @@ -61,11 +61,11 @@ void setup() { // start advertising BLE.advertise(); - Serial.println(("Bluetooth device active, waiting for connections...")); + Serial.println(("Bluetooth® device active, waiting for connections...")); } void loop() { - // poll for BLE events + // poll for Bluetooth® Low Energy events BLE.poll(); } diff --git a/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino b/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino new file mode 100644 index 00000000..dfc9f4a0 --- /dev/null +++ b/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino @@ -0,0 +1,265 @@ +/* + Battery Monitor + + This example creates a BLE peripheral with the standard battery service and + level characteristic. The A0 pin is used to calculate the battery level. + + The circuit: + - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + + You can use a generic BLE central app, like LightBlue (iOS and Android) or + nRF Connect (Android), to interact with the services and characteristics + created in this sketch. + + This example code is in the public domain. +*/ + +#include + + +#define PAIR_BUTTON 3 // button for pairing +#define PAIR_LED 24 // LED used to signal pairing +#define PAIR_LED_ON LOW // Blue LED on Nano BLE has inverted logic +#define PAIR_INTERVAL 30000 // interval for pairing after button press in ms + +#define CTRL_LED LED_BUILTIN + + + // BLE Battery Service +BLEService batteryService("180F"); + +// BLE Battery Level Characteristic +BLEUnsignedCharCharacteristic batteryLevelChar("2A19", // standard 16-bit characteristic UUID + BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes +BLEStringCharacteristic stringcharacteristic("183E", BLERead | BLEWrite, 31); + + +// Add BLEEncryption tag to require pairing. This controls the LED. +BLEUnsignedCharCharacteristic secretValue("2a3F", BLERead | BLEWrite | BLEEncryption); + +int oldBatteryLevel = 0; // last battery level reading from analog input +unsigned long previousMillis = 0; // last time the battery level was checked, in ms +unsigned long pairingStarted = 0; // pairing start time when button is pressed +bool wasConnected = 0; +bool acceptOrReject = true; + +void setup() { + Serial.begin(9600); // initialize serial communication + while (!Serial); + + pinMode(CTRL_LED, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected + pinMode(PAIR_LED, OUTPUT); + pinMode(PAIR_BUTTON, INPUT_PULLUP); + + + Serial.println("Serial connected"); + + // Callback function with confirmation code when new device is pairing. + BLE.setDisplayCode([](uint32_t confirmCode){ + Serial.println("New device pairing request."); + Serial.print("Confirm code matches pairing device: "); + char code[6]; + sprintf(code, "%06d", confirmCode); + Serial.println(code); + }); + + // Callback to allow accepting or rejecting pairing + BLE.setBinaryConfirmPairing([&acceptOrReject](){ + Serial.print("Should we confirm pairing? "); + delay(5000); + if(acceptOrReject){ + acceptOrReject = false; + Serial.println("yes"); + return true; + }else{ + acceptOrReject = true; + Serial.println("no"); + return false; + } + }); + + // IRKs are keys that identify the true owner of a random mac address. + // Add IRKs of devices you are bonded with. + BLE.setGetIRKs([](uint8_t* nIRKs, uint8_t** BDaddrTypes, uint8_t*** BDAddrs, uint8_t*** IRKs){ + // Set to number of devices + *nIRKs = 2; + + *BDAddrs = new uint8_t*[*nIRKs]; + *IRKs = new uint8_t*[*nIRKs]; + *BDaddrTypes = new uint8_t[*nIRKs]; + + // Set these to the mac and IRK for your bonded devices as printed in the serial console after bonding. + uint8_t device1Mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t device1IRK[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + uint8_t device2Mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t device2IRK[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + + (*BDaddrTypes)[0] = 0; // Type 0 is for pubc address, type 1 is for static random + (*BDAddrs)[0] = new uint8_t[6]; + (*IRKs)[0] = new uint8_t[16]; + memcpy((*IRKs)[0] , device1IRK,16); + memcpy((*BDAddrs)[0], device1Mac, 6); + + + (*BDaddrTypes)[1] = 0; + (*BDAddrs)[1] = new uint8_t[6]; + (*IRKs)[1] = new uint8_t[16]; + memcpy((*IRKs)[1] , device2IRK,16); + memcpy((*BDAddrs)[1], device2Mac, 6); + + + return 1; + }); + // The LTK is the secret key which is used to encrypt bluetooth traffic + BLE.setGetLTK([](uint8_t* address, uint8_t* LTK){ + // address is input + Serial.print("Received request for address: "); + btct.printBytes(address,6); + + // Set these to the MAC and LTK of your devices after bonding. + uint8_t device1Mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t device1LTK[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t device2Mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t device2LTK[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + + if(memcmp(device1Mac, address, 6) == 0) { + memcpy(LTK, device1LTK, 16); + return 1; + }else if(memcmp(device2Mac, address, 6) == 0) { + memcpy(LTK, device2LTK, 16); + return 1; + } + return 0; + }); + BLE.setStoreIRK([](uint8_t* address, uint8_t* IRK){ + Serial.print(F("New device with MAC : ")); + btct.printBytes(address,6); + Serial.print(F("Need to store IRK : ")); + btct.printBytes(IRK,16); + return 1; + }); + BLE.setStoreLTK([](uint8_t* address, uint8_t* LTK){ + Serial.print(F("New device with MAC : ")); + btct.printBytes(address,6); + Serial.print(F("Need to store LTK : ")); + btct.printBytes(LTK,16); + return 1; + }); + + while(1){ + // begin initialization + if (!BLE.begin()) { + Serial.println("starting BLE failed!"); + delay(200); + continue; + } + Serial.println("BT init"); + delay(200); + + /* Set a local name for the BLE device + This name will appear in advertising packets + and can be used by remote devices to identify this BLE device + The name can be changed but maybe be truncated based on space left in advertisement packet + */ + + BLE.setDeviceName("Arduino"); + BLE.setLocalName("BatteryMonitor"); + + BLE.setAdvertisedService(batteryService); // add the service UUID + batteryService.addCharacteristic(batteryLevelChar); // add the battery level characteristic + batteryService.addCharacteristic(stringcharacteristic); + batteryService.addCharacteristic(secretValue); + + BLE.addService(batteryService); // Add the battery service + batteryLevelChar.writeValue(oldBatteryLevel); // set initial value for this characteristic + char* stringCharValue = new char[32]; + stringCharValue = "string"; + stringcharacteristic.writeValue(stringCharValue); + secretValue.writeValue(0); + + delay(1000); + + // prevent pairing until button is pressed (will show a pairing rejected message) + BLE.setPairable(false); + + /* Start advertising BLE. It will start continuously transmitting BLE + advertising packets and will be visible to remote BLE central devices + until it receives a new connection */ + + // start advertising + if(!BLE.advertise()){ + Serial.println("failed to advertise bluetooth."); + BLE.stopAdvertise(); + delay(500); + }else{ + Serial.println("advertising..."); + break; + } + BLE.end(); + delay(100); + } +} + + +void loop() { + // wait for a BLE central + BLEDevice central = BLE.central(); + + + // If button is pressed, allow pairing for 30 sec + if (!BLE.pairable() && digitalRead(PAIR_BUTTON) == LOW){ + pairingStarted = millis(); + BLE.setPairable(Pairable::ONCE); + Serial.println("Accepting pairing for 30 s"); + } else if (BLE.pairable() && millis() > pairingStarted + PAIR_INTERVAL){ + BLE.setPairable(false); + Serial.println("No longer accepting pairing"); + } + // Make LED blink while pairing is allowed + digitalWrite(PAIR_LED, (BLE.pairable() ? (millis()%400)<200 : BLE.paired()) ? PAIR_LED_ON : !PAIR_LED_ON); + + + // if a central is connected to the peripheral: + if (central && central.connected()) { + if (!wasConnected){ + wasConnected = true; + Serial.print("Connected to central: "); + // print the central's BT address: + Serial.println(central.address()); + } + + // check the battery level every 200ms + // while the central is connected: + long currentMillis = millis(); + // if 200ms have passed, check the battery level: + if (currentMillis - previousMillis >= 1000) { + previousMillis = currentMillis; + updateBatteryLevel(); + digitalWrite(CTRL_LED, secretValue.value()>0 ? HIGH : LOW); + } + } else if (wasConnected){ + wasConnected = false; + Serial.print("Disconnected from central: "); + Serial.println(central.address()); + } + +} + +void updateBatteryLevel() { + /* Read the current voltage level on the A0 analog input pin. + This is used here to simulate the charge level of a battery. + */ + int battery = analogRead(A0); + int batteryLevel = map(battery, 0, 1023, 0, 100); + + if (batteryLevel != oldBatteryLevel) { // if the battery level has changed + // Serial.print("Battery Level % is now: "); // print it + // Serial.println(batteryLevel); + batteryLevelChar.writeValue(batteryLevel); // and update the battery level characteristic + oldBatteryLevel = batteryLevel; // save the level for next comparison + } +} \ No newline at end of file diff --git a/examples/Peripheral/LED/LED.ino b/examples/Peripheral/LED/LED.ino index 1ede6535..65b88605 100644 --- a/examples/Peripheral/LED/LED.ino +++ b/examples/Peripheral/LED/LED.ino @@ -1,14 +1,14 @@ /* LED - This example creates a BLE peripheral with service that contains a + This example creates a Bluetooth® Low Energy peripheral with service that contains a characteristic to control an LED. The circuit: - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. - You can use a generic BLE central app, like LightBlue (iOS and Android) or + You can use a generic Bluetooth® Low Energy central app, like LightBlue (iOS and Android) or nRF Connect (Android), to interact with the services and characteristics created in this sketch. @@ -17,9 +17,9 @@ #include -BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // Bluetooth® Low Energy LED Service -// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central +// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and writable by central BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); const int ledPin = LED_BUILTIN; // pin to use for the LED @@ -33,7 +33,7 @@ void setup() { // begin initialization if (!BLE.begin()) { - Serial.println("starting BLE failed!"); + Serial.println("starting Bluetooth® Low Energy module failed!"); while (1); } @@ -48,7 +48,7 @@ void setup() { // add service BLE.addService(ledService); - // set the initial value for the characeristic: + // set the initial value for the characteristic: switchCharacteristic.writeValue(0); // start advertising @@ -58,7 +58,7 @@ void setup() { } void loop() { - // listen for BLE peripherals to connect: + // listen for Bluetooth® Low Energy peripherals to connect: BLEDevice central = BLE.central(); // if a central is connected to peripheral: diff --git a/extras/arduino-ble-parser.py b/extras/arduino-ble-parser.py new file mode 100644 index 00000000..8f678711 --- /dev/null +++ b/extras/arduino-ble-parser.py @@ -0,0 +1,85 @@ +''' +Convert ArduinoBLE debug files into Btsnoop files ready to be analyzed using wireshark or hcidump +Btsnoop file format reference + https://www.fte.com/WebHelpII/Sodera/Content/Technical_Information/BT_Snoop_File_Format.htm +''' + +import os +import argparse + +DEBUG = False + +parser = argparse.ArgumentParser() +parser.add_argument('-i', dest='inputPath', type=str, required=True, help='input file containing debug log') +parser.add_argument('-o', dest='outputPath', type=str, required=True, help='result file that will contain the btsnoop encoded debug file') +args = parser.parse_args() + +# Extract only hci debug messages +def extractHCIDebugPrint(inputPath, outputPath): + inputFile = open(inputPath, 'r') + outputFile = open(outputPath, 'w') + for inputLine in inputFile: + lineItems = inputLine.split() + if (len(lineItems) < 7) or (lineItems[1] != "->") or (lineItems[2] != "HCI"): + if (len(lineItems) < 4) or (lineItems[0] != "HCI") or ((lineItems[3] != "<-") and (lineItems[3] != "->")): + continue + outputFile.write(inputLine) + outputFile.close() + +# Return packet in btsnoop format +def buildBinaryPacket(hciMessage, hciDirection, hciType): + commandFlag = 1 if (hciType == "COMMAND" or hciType == "EVENT") else 0 + directionFlag = 0 if (hciDirection == "TX") else 1 + flagHex = ("0" * 7) + str((commandFlag * 2) + directionFlag) + timestampHex = "0" * 16 + packetDropHex = "0" * 8 + dataLengthHex = format( (int(len(hciMessage) / 2)), 'x') + packetLengthHex = ("0" * (8 - len(dataLengthHex))) + dataLengthHex + binaryPacket = bytearray.fromhex(packetLengthHex + packetLengthHex + flagHex + packetDropHex + timestampHex + hciMessage) + if DEBUG: + print(len(hciMessage)) + print(dataLengthHex) + print(packetLengthHex) + print(flagHex) + print('\n') + return binaryPacket + +def buildBinaryHeader(): + defaultHeader = "6274736e6f6f700000000001000003ea" + binaryHeader = bytearray.fromhex(defaultHeader) + return binaryHeader + +def convertToBtsnoop(inputPath, outputPath): + # Open output file and write the Btsnoop header + outputFile = open(outputPath,'wb') + header = buildBinaryHeader() + outputFile.write(header) + + # Open input file containing HCI debug packets + inputFile = open(inputPath, 'r') + for inputLine in inputFile: + lineItems = inputLine.split() + # For a safer script, do not use indexes but look for symbols in the line + baseIndex = lineItems.index("HCI") + hciMessage = lineItems[baseIndex + 4] + hciDirection = lineItems[baseIndex + 2] + hciType = lineItems[baseIndex + 1] + # Build and write the encoded line + btsnoopPacket = buildBinaryPacket(hciMessage, hciDirection, hciType) + outputFile.write(btsnoopPacket) + if DEBUG: + print(hciDirection) + print(hciMessage) + print(hciType) + print('\n') + outputFile.close() + +inputPath = args.inputPath +outputPath = args.outputPath +tempFile = "temp-debug-print.txt" +# Run +extractHCIDebugPrint(inputPath,tempFile) +convertToBtsnoop(tempFile, outputPath) +# Delete temp file +os.remove(tempFile) + diff --git a/extras/test/.gitignore b/extras/test/.gitignore new file mode 100644 index 00000000..7e9215ee --- /dev/null +++ b/extras/test/.gitignore @@ -0,0 +1,17 @@ +build +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ \ No newline at end of file diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt new file mode 100644 index 00000000..5e66e18b --- /dev/null +++ b/extras/test/CMakeLists.txt @@ -0,0 +1,164 @@ +########################################################################## + +set(CMAKE_VERBOSE_MAKEFILE ON) +cmake_minimum_required(VERSION 3.5) + +########################################################################## + +project(testArduinoBLE) + +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.4.0 +) + +FetchContent_MakeAvailable(Catch2) + +########################################################################## + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +########################################################################## + +set(COMMON_TEST_SRCS + src/test_main.cpp + src/Arduino.cpp + src/util/itoa.c + src/util/TestUtil.cpp + src/util/String.cpp + src/util/Common.cpp +) + +set(DUT_SRCS + ../../src/utility/BLEUuid.cpp + ../../src/BLEDevice.cpp + ../../src/BLECharacteristic.cpp + ../../src/BLEDescriptor.cpp + ../../src/BLEService.cpp + ../../src/BLEAdvertisingData.cpp + ../../src/utility/ATT.cpp + ../../src/utility/GAP.cpp + ../../src/utility/HCI.cpp + ../../src/utility/GATT.cpp + ../../src/utility/L2CAPSignaling.cpp + ../../src/utility/keyDistribution.cpp + ../../src/utility/bitDescriptions.cpp + ../../src/utility/btct.cpp + ../../src/local/BLELocalAttribute.cpp + ../../src/local/BLELocalCharacteristic.cpp + ../../src/local/BLELocalDescriptor.cpp + ../../src/local/BLELocalDevice.cpp + ../../src/local/BLELocalService.cpp + ../../src/remote/BLERemoteAttribute.cpp + ../../src/remote/BLERemoteCharacteristic.cpp + ../../src/remote/BLERemoteDescriptor.cpp + ../../src/remote/BLERemoteDevice.cpp + ../../src/remote/BLERemoteService.cpp + ../../src/BLEStringCharacteristic.cpp + ../../src/BLETypedCharacteristics.cpp +) + +set(TEST_TARGET_UUID_SRCS + # Test files + ${COMMON_TEST_SRCS} + src/test_uuid/test_uuid.cpp + # DUT files + #${DUT_SRCS} + ../../src/utility/BLEUuid.cpp +) + +set(TEST_TARGET_DISC_DEVICE_SRCS + # Test files + ${COMMON_TEST_SRCS} + src/test_discovered_device/test_discovered_device.cpp + # DUT files + ${DUT_SRCS} + # Fake classes files + src/util/HCIFakeTransport.cpp + src/test_discovered_device/FakeGAP.cpp +) + +set(TEST_TARGET_ADVERTISING_DATA_SRCS + # Test files + ${COMMON_TEST_SRCS} + src/test_advertising_data/test_advertising_data.cpp + src/test_advertising_data/test_service.cpp + src/test_advertising_data/test_local_name.cpp + src/test_advertising_data/test_manufacturer.cpp + # DUT files + ${DUT_SRCS} + # Fake classes files + src/util/HCIFakeTransport.cpp + src/test_advertising_data/FakeBLELocalDevice.cpp +) + +set(TEST_TARGET_CHARACTERISTIC_SRCS + # Test files + ${COMMON_TEST_SRCS} + src/test_characteristic/test_permissions.cpp + src/test_characteristic/test_writeValue.cpp + # DUT files + ${DUT_SRCS} + # Fake classes files + src/util/HCIFakeTransport.cpp + src/test_advertising_data/FakeBLELocalDevice.cpp +) + +########################################################################## + +set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "--coverage") +set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "--coverage") + +########################################################################## + +add_executable(TEST_TARGET_UUID ${TEST_TARGET_UUID_SRCS}) +add_executable(TEST_TARGET_DISC_DEVICE ${TEST_TARGET_DISC_DEVICE_SRCS}) +add_executable(TEST_TARGET_ADVERTISING_DATA ${TEST_TARGET_ADVERTISING_DATA_SRCS}) +add_executable(TEST_TARGET_CHARACTERISTIC_DATA ${TEST_TARGET_CHARACTERISTIC_SRCS}) + +########################################################################## + +include_directories(include) +include_directories(include/util) +include_directories(../../src) +include_directories(../../src/local) +include_directories(../../src/remote) +include_directories(../../src/utility) + +target_include_directories(TEST_TARGET_DISC_DEVICE PUBLIC include/test_discovered_device) +target_include_directories(TEST_TARGET_ADVERTISING_DATA PUBLIC include/test_advertising_data) +target_include_directories(TEST_TARGET_CHARACTERISTIC_DATA PUBLIC include/test_advertising_data) + +########################################################################## + +target_compile_definitions(TEST_TARGET_DISC_DEVICE PUBLIC FAKE_GAP) +target_compile_definitions(TEST_TARGET_ADVERTISING_DATA PUBLIC FAKE_BLELOCALDEVICE) +target_compile_definitions(TEST_TARGET_CHARACTERISTIC_DATA PUBLIC FAKE_BLELOCALDEVICE) + +########################################################################## + +# Build unit tests as a post build step +add_custom_command(TARGET TEST_TARGET_UUID POST_BUILD + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TEST_TARGET_UUID +) +add_custom_command(TARGET TEST_TARGET_DISC_DEVICE POST_BUILD + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TEST_TARGET_DISC_DEVICE +) +add_custom_command(TARGET TEST_TARGET_ADVERTISING_DATA POST_BUILD + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TEST_TARGET_ADVERTISING_DATA +) + +add_custom_command(TARGET TEST_TARGET_CHARACTERISTIC_DATA POST_BUILD + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TEST_TARGET_CHARACTERISTIC_DATA +) + +########################################################################## + +target_link_libraries( TEST_TARGET_UUID Catch2WithMain ) +target_link_libraries( TEST_TARGET_DISC_DEVICE Catch2WithMain ) +target_link_libraries( TEST_TARGET_ADVERTISING_DATA Catch2WithMain ) +target_link_libraries( TEST_TARGET_CHARACTERISTIC_DATA Catch2WithMain ) diff --git a/extras/test/include/Arduino.h b/extras/test/include/Arduino.h new file mode 100644 index 00000000..38f45554 --- /dev/null +++ b/extras/test/include/Arduino.h @@ -0,0 +1,58 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef TEST_ARDUINO_H_ +#define TEST_ARDUINO_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include +#include "String.h" +#include "Stream.h" +#include "itoa.h" +#include "Common.h" + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +typedef arduino::String String; +typedef bool boolean; + +#if defined(__cplusplus) + +#undef F +// C++11 F replacement declaration +template +auto F(T1&& A) + -> const arduino::__FlashStringHelper* +{ + return (const arduino::__FlashStringHelper*)A; +} + +#endif + +/****************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************/ + +#endif /* TEST_ARDUINO_H_ */ diff --git a/extras/test/include/test_advertising_data/FakeBLELocalDevice.h b/extras/test/include/test_advertising_data/FakeBLELocalDevice.h new file mode 100644 index 00000000..94e97730 --- /dev/null +++ b/extras/test/include/test_advertising_data/FakeBLELocalDevice.h @@ -0,0 +1,35 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _FAKE_BLELOCALDEVICE_H_ +#define _FAKE_BLELOCALDEVICE_H_ + +#define private public +#define protected public +#include "BLELocalDevice.h" + +class FakeBLELocalDevice : public BLELocalDevice { + public: + FakeBLELocalDevice(); + virtual ~FakeBLELocalDevice(); + + int advertise(); +}; + +#endif diff --git a/extras/test/include/test_discovered_device/FakeGAP.h b/extras/test/include/test_discovered_device/FakeGAP.h new file mode 100644 index 00000000..35b3233b --- /dev/null +++ b/extras/test/include/test_discovered_device/FakeGAP.h @@ -0,0 +1,33 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _FAKE_GAP_H_ +#define _FAKE_GAP_H_ + +#define private public +#define protected public +#include "GAP.h" + +class FakeGAPClass : public GAPClass { + public: + FakeGAPClass(); + virtual ~FakeGAPClass(); +}; + +#endif diff --git a/extras/test/include/util/Common.h b/extras/test/include/util/Common.h new file mode 100644 index 00000000..8d288e18 --- /dev/null +++ b/extras/test/include/util/Common.h @@ -0,0 +1,167 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +void yield(void); + +typedef enum { + LOW = 0, + HIGH = 1, + CHANGE = 2, + FALLING = 3, + RISING = 4, +} PinStatus; + +typedef enum { + INPUT = 0x0, + OUTPUT = 0x1, + INPUT_PULLUP = 0x2, + INPUT_PULLDOWN = 0x3, +} PinMode; + +typedef enum { + LSBFIRST = 0, + MSBFIRST = 1, +} BitOrder; + +#define PI 3.1415926535897932384626433832795 +#define HALF_PI 1.5707963267948966192313216916398 +#define TWO_PI 6.283185307179586476925286766559 +#define DEG_TO_RAD 0.017453292519943295769236907684886 +#define RAD_TO_DEG 57.295779513082320876798154814105 +#define EULER 2.718281828459045235360287471352 + +#define SERIAL 0x0 +#define DISPLAY 0x1 + +#ifndef constrain +#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) +#endif + +#ifndef radians +#define radians(deg) ((deg)*DEG_TO_RAD) +#endif + +#ifndef degrees +#define degrees(rad) ((rad)*RAD_TO_DEG) +#endif + +#ifndef sq +#define sq(x) ((x)*(x)) +#endif + +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrParam)(void*); + +// interrupts() / noInterrupts() must be defined by the core + +#define lowByte(w) ((uint8_t) ((w) & 0xff)) +#define highByte(w) ((uint8_t) ((w) >> 8)) + +#define bitRead(value, bit) (((value) >> (bit)) & 0x01) +#define bitSet(value, bit) ((value) |= (1UL << (bit))) +#define bitClear(value, bit) ((value) &= ~(1UL << (bit))) +#define bitToggle(value, bit) ((value) ^= (1UL << (bit))) +#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) + +#ifndef bit +#define bit(b) (1UL << (b)) +#endif + +/* TODO: request for removal */ +typedef bool boolean; +typedef uint8_t byte; +typedef uint16_t word; + +void init(void); +void initVariant(void); + +int atexit(void (*func)()) __attribute__((weak)); +int main() __attribute__((weak)); + +#ifdef EXTENDED_PIN_MODE +// Platforms who wnat to declare more than 256 pins need to define EXTENDED_PIN_MODE globally +typedef uint32_t pin_size_t; +#else +typedef uint8_t pin_size_t; +#endif + +void pinMode(pin_size_t pinNumber, PinMode pinMode); +void digitalWrite(pin_size_t pinNumber, PinStatus status); +PinStatus digitalRead(pin_size_t pinNumber); +int analogRead(pin_size_t pinNumber); +void analogReference(uint8_t mode); +void analogWrite(pin_size_t pinNumber, int value); + +unsigned long millis(void); +unsigned long micros(void); +void delay(unsigned long); +void delayMicroseconds(unsigned int us); +unsigned long pulseIn(pin_size_t pin, uint8_t state, unsigned long timeout); +unsigned long pulseInLong(pin_size_t pin, uint8_t state, unsigned long timeout); + +void shiftOut(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder, uint8_t val); +pin_size_t shiftIn(pin_size_t dataPin, pin_size_t clockPin, BitOrder bitOrder); + +void attachInterrupt(pin_size_t interruptNumber, voidFuncPtr callback, PinStatus mode); +void attachInterruptParam(pin_size_t interruptNumber, voidFuncPtrParam callback, PinStatus mode, void* param); +void detachInterrupt(pin_size_t interruptNumber); + +void setup(void); +void loop(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#ifdef __cplusplus + template + auto min(const T& a, const L& b) -> decltype((b < a) ? b : a) + { + return (b < a) ? b : a; + } + + template + auto max(const T& a, const L& b) -> decltype((b < a) ? b : a) + { + return (a < b) ? b : a; + } +#else +#ifndef min +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif +#ifndef max +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) +#endif +#endif + +#ifdef __cplusplus + +/* C++ prototypes */ +uint16_t makeWord(uint16_t w); +uint16_t makeWord(byte h, byte l); + +#define word(...) makeWord(__VA_ARGS__) + +unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L); +unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L); + +void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0); +void noTone(uint8_t _pin); + +// WMath prototypes +long random(long); +long random(long, long); +void randomSeed(unsigned long); +long map(long, long, long, long, long); + +#endif // __cplusplus diff --git a/extras/test/include/util/HCIFakeTransport.h b/extras/test/include/util/HCIFakeTransport.h new file mode 100644 index 00000000..6c8ac08c --- /dev/null +++ b/extras/test/include/util/HCIFakeTransport.h @@ -0,0 +1,19 @@ +//#include "Common.h" +#pragma once + +#include "HCITransport.h" + +class HCIFakeTransportClass : public HCITransportInterface +{ +public: + HCIFakeTransportClass() {}; + ~HCIFakeTransportClass() {}; + + int begin() {return 0;} + void end() {return;} + void wait(unsigned long timeout) {return;} + int available() {return 0;} + int peek() {return 0;} + int read() {return 0;} + size_t write(const uint8_t* data, size_t length) {return 0;} +}; \ No newline at end of file diff --git a/extras/test/include/util/Stream.h b/extras/test/include/util/Stream.h new file mode 100644 index 00000000..f367e34c --- /dev/null +++ b/extras/test/include/util/Stream.h @@ -0,0 +1,40 @@ +//#include "Common.h" +#pragma once + +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +class Stream +{ +public: + Stream(const char *name = NULL); + ~Stream(); + + void flush() {} + + size_t print(const char[]) { return 0; } + size_t print(char) { return 0; } + size_t print(unsigned char, int) { return 0; } + size_t print(int, int) { return 0; } + size_t print(unsigned int, int) { return 0; } + size_t print(long, int) { return 0; } + size_t print(unsigned long, int) { return 0; } + size_t print(long long, int) { return 0; } + size_t print(unsigned long long, int) { return 0; } + size_t print(double, int) { return 0; } + size_t print(void) { return 0; } + + size_t println(const char[]) { return 0; } + size_t println(char) { return 0; } + size_t println(unsigned char, int) { return 0; } + size_t println(int, int) { return 0; } + size_t println(unsigned int, int) { return 0; } + size_t println(long, int) { return 0; } + size_t println(unsigned long, int) { return 0; } + size_t println(long long, int) { return 0; } + size_t println(unsigned long long, int) { return 0; } + size_t println(double, int) { return 0; } + size_t println(void) { return 0; } +}; diff --git a/extras/test/include/util/String.h b/extras/test/include/util/String.h new file mode 100644 index 00000000..3021a31e --- /dev/null +++ b/extras/test/include/util/String.h @@ -0,0 +1,248 @@ +/* + String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All right reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef __cplusplus + +#ifndef __ARDUINO_STRINGS__ +#define __ARDUINO_STRINGS__ + +#include +#include +#include + +namespace arduino { + +// When compiling programs with this class, the following gcc parameters +// dramatically increase performance and memory (RAM) efficiency, typically +// with little or no increase in code size. +// -felide-constructors +// -std=c++0x + +class __FlashStringHelper; +#define F(string_literal) (reinterpret_cast(PSTR(string_literal))) + +// An inherited class for holding the result of a concatenation. These +// result objects are assumed to be writable by subsequent concatenations. +class StringSumHelper; + +// The string class +class String +{ + friend class StringSumHelper; + // use a function pointer to allow for "if (s)" without the + // complications of an operator bool(). for more information, see: + // http://www.artima.com/cppsource/safebool.html + typedef void (String::*StringIfHelperType)() const; + void StringIfHelper() const {} + +public: + // constructors + // creates a copy of the initial value. + // if the initial value is null or invalid, or if memory allocation + // fails, the string will be marked as invalid (i.e. "if (s)" will + // be false). + String(const char *cstr = ""); + String(const String &str); + String(const __FlashStringHelper *str); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String(String &&rval); + String(StringSumHelper &&rval); + #endif + explicit String(char c); + explicit String(unsigned char, unsigned char base=10); + explicit String(int, unsigned char base=10); + explicit String(unsigned int, unsigned char base=10); + explicit String(long, unsigned char base=10); + explicit String(unsigned long, unsigned char base=10); + //explicit String(float, unsigned char decimalPlaces=2); + //explicit String(double, unsigned char decimalPlaces=2); + ~String(void); + + // memory management + // return true on success, false on failure (in which case, the string + // is left unchanged). reserve(0), if successful, will validate an + // invalid string (i.e., "if (s)" will be true afterwards) + unsigned char reserve(unsigned int size); + inline unsigned int length(void) const {return len;} + + // creates a copy of the assigned value. if the value is null or + // invalid, or if the memory allocation fails, the string will be + // marked as invalid ("if (s)" will be false). + String & operator = (const String &rhs); + String & operator = (const char *cstr); + //String & operator = (const __FlashStringHelper *str); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String & operator = (String &&rval); + String & operator = (StringSumHelper &&rval); + #endif + + // concatenate (works w/ built-in types) + + // returns true on success, false on failure (in which case, the string + // is left unchanged). if the argument is null or invalid, the + // concatenation is considered unsucessful. + unsigned char concat(const String &str); + unsigned char concat(const char *cstr); + unsigned char concat(char c); + unsigned char concat(unsigned char c); + unsigned char concat(int num); + unsigned char concat(unsigned int num); + unsigned char concat(long num); + unsigned char concat(unsigned long num); + unsigned char concat(float num); + //unsigned char concat(double num); + unsigned char concat(const __FlashStringHelper * str); + + // if there's not enough memory for the concatenated value, the string + // will be left unchanged (but this isn't signalled in any way) + String & operator += (const String &rhs) {concat(rhs); return (*this);} + String & operator += (const char *cstr) {concat(cstr); return (*this);} + String & operator += (char c) {concat(c); return (*this);} + String & operator += (unsigned char num) {concat(num); return (*this);} + String & operator += (int num) {concat(num); return (*this);} + String & operator += (unsigned int num) {concat(num); return (*this);} + String & operator += (long num) {concat(num); return (*this);} + String & operator += (unsigned long num) {concat(num); return (*this);} + String & operator += (float num) {concat(num); return (*this);} + //String & operator += (double num) {concat(num); return (*this);} + String & operator += (const __FlashStringHelper *str){concat(str); return (*this);} + + friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper & operator + (const StringSumHelper &lhs, char c); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, float num); + //friend StringSumHelper & operator + (const StringSumHelper &lhs, double num); + //friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs); + + // comparison (only works w/ Strings and "strings") + operator StringIfHelperType() const { return buffer ? &String::StringIfHelper : 0; } + int compareTo(const String &s) const; + int compareTo(const char *cstr) const; + unsigned char equals(const String &s) const; + unsigned char equals(const char *cstr) const; + + friend unsigned char operator == (const String &a, const String &b) { return a.equals(b); } + friend unsigned char operator == (const String &a, const char *b) { return a.equals(b); } + friend unsigned char operator == (const char *a, const String &b) { return b == a; } + friend unsigned char operator < (const String &a, const String &b) { return a.compareTo(b) < 0; } + friend unsigned char operator < (const String &a, const char *b) { return a.compareTo(b) < 0; } + friend unsigned char operator < (const char *a, const String &b) { return b.compareTo(a) > 0; } + + friend unsigned char operator != (const String &a, const String &b) { return !(a == b); } + friend unsigned char operator != (const String &a, const char *b) { return !(a == b); } + friend unsigned char operator != (const char *a, const String &b) { return !(a == b); } + friend unsigned char operator > (const String &a, const String &b) { return b < a; } + friend unsigned char operator > (const String &a, const char *b) { return b < a; } + friend unsigned char operator > (const char *a, const String &b) { return b < a; } + friend unsigned char operator <= (const String &a, const String &b) { return !(b < a); } + friend unsigned char operator <= (const String &a, const char *b) { return !(b < a); } + friend unsigned char operator <= (const char *a, const String &b) { return !(b < a); } + friend unsigned char operator >= (const String &a, const String &b) { return !(a < b); } + friend unsigned char operator >= (const String &a, const char *b) { return !(a < b); } + friend unsigned char operator >= (const char *a, const String &b) { return !(a < b); } + + unsigned char equalsIgnoreCase(const String &s) const; + unsigned char startsWith( const String &prefix) const; + unsigned char startsWith(const String &prefix, unsigned int offset) const; + unsigned char endsWith(const String &suffix) const; + + // character acccess + char charAt(unsigned int index) const; + void setCharAt(unsigned int index, char c); + char operator [] (unsigned int index) const; + char& operator [] (unsigned int index); + void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index=0) const; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index=0) const + { getBytes((unsigned char *)buf, bufsize, index); } + const char* c_str() const { return buffer; } + char* begin() { return buffer; } + char* end() { return buffer + length(); } + const char* begin() const { return c_str(); } + const char* end() const { return c_str() + length(); } + + // search + int indexOf( char ch ) const; + int indexOf( char ch, unsigned int fromIndex ) const; + int indexOf( const String &str ) const; + int indexOf( const String &str, unsigned int fromIndex ) const; + int lastIndexOf( char ch ) const; + int lastIndexOf( char ch, unsigned int fromIndex ) const; + int lastIndexOf( const String &str ) const; + int lastIndexOf( const String &str, unsigned int fromIndex ) const; + String substring( unsigned int beginIndex ) const { return substring(beginIndex, len); }; + String substring( unsigned int beginIndex, unsigned int endIndex ) const; + + // modification + void replace(char find, char replace); + void replace(const String& find, const String& replace); + void remove(unsigned int index); + void remove(unsigned int index, unsigned int count); + void toLowerCase(void); + void toUpperCase(void); + void trim(void); + + // parsing/conversion + long toInt(void) const; + float toFloat(void) const; + double toDouble(void) const; + +protected: + char *buffer; // the actual char array + unsigned int capacity; // the array length minus one (for the '\0') + unsigned int len; // the String length (not counting the '\0') +protected: + void init(void); + void invalidate(void); + unsigned char changeBuffer(unsigned int maxStrLen); + unsigned char concat(const char *cstr, unsigned int length); + + // copy and move + String & copy(const char *cstr, unsigned int length); + String & copy(const __FlashStringHelper *pstr, unsigned int length); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + void move(String &rhs); + #endif +}; + +class StringSumHelper : public String +{ +public: + StringSumHelper(const String &s) : String(s) {} + StringSumHelper(const char *p) : String(p) {} + StringSumHelper(char c) : String(c) {} + StringSumHelper(unsigned char num) : String(num) {} + StringSumHelper(int num) : String(num) {} + StringSumHelper(unsigned int num) : String(num) {} + StringSumHelper(long num) : String(num) {} + StringSumHelper(unsigned long num) : String(num) {} + //StringSumHelper(float num) : String(num) {} + //StringSumHelper(double num) : String(num) {} +}; + +} // namespace arduino + +#endif // __cplusplus +#endif // __ARDUINO_STRINGS__ diff --git a/extras/test/include/util/TestUtil.h b/extras/test/include/util/TestUtil.h new file mode 100644 index 00000000..111607ec --- /dev/null +++ b/extras/test/include/util/TestUtil.h @@ -0,0 +1,3 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ diff --git a/extras/test/include/util/itoa.h b/extras/test/include/util/itoa.h new file mode 100644 index 00000000..55b28493 --- /dev/null +++ b/extras/test/include/util/itoa.h @@ -0,0 +1,37 @@ +/* + Copyright (c) 2016 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +// Standard C functions required in Arduino API +// If these functions are not provided by the standard library, the +// core should supply an implementation of them. + +#ifdef __cplusplus +extern "C" { +#endif + +extern char* itoa(int value, char *string, int radix); +extern char* ltoa(long value, char *string, int radix); +extern char* utoa(unsigned value, char *string, int radix); +extern char* ultoa(unsigned long value, char *string, int radix); + +#ifdef __cplusplus +} // extern "C" +#endif + diff --git a/extras/test/src/Arduino.cpp b/extras/test/src/Arduino.cpp new file mode 100644 index 00000000..6a6ab4e5 --- /dev/null +++ b/extras/test/src/Arduino.cpp @@ -0,0 +1,44 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +/****************************************************************************** + GLOBAL VARIABLES + ******************************************************************************/ + +static unsigned long current_millis = 0; + +/****************************************************************************** + PUBLIC FUNCTIONS + ******************************************************************************/ + +void set_millis(unsigned long const millis) +{ + current_millis = millis; +} + +unsigned long millis() +{ + return current_millis; +} diff --git a/extras/test/src/test_advertising_data/FakeBLELocalDevice.cpp b/extras/test/src/test_advertising_data/FakeBLELocalDevice.cpp new file mode 100644 index 00000000..7f5a71ad --- /dev/null +++ b/extras/test/src/test_advertising_data/FakeBLELocalDevice.cpp @@ -0,0 +1,40 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "FakeBLELocalDevice.h" + +FakeBLELocalDevice::FakeBLELocalDevice() +{ + +} + +FakeBLELocalDevice::~FakeBLELocalDevice() +{ + +} + +int FakeBLELocalDevice::advertise() +{ + _advertisingData.updateData(); + _scanResponseData.updateData(); + return 1; +} + +FakeBLELocalDevice FakeBLEObj; +BLELocalDevice& BLE = FakeBLEObj; diff --git a/extras/test/src/test_advertising_data/test_advertising_data.cpp b/extras/test/src/test_advertising_data/test_advertising_data.cpp new file mode 100644 index 00000000..7f2e9cb8 --- /dev/null +++ b/extras/test/src/test_advertising_data/test_advertising_data.cpp @@ -0,0 +1,204 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public + +#include "FakeBLELocalDevice.h" +#include "BLEAdvertisingData.h" + +TEST_CASE("Test flags override", "[ArduinoBLE::BLEAdvertisingData]") +{ + // Mocking advertisement packet + BLEAdvertisingData advData; + // Expected results + const uint8_t defaultData[] = {0x02, BLEFieldFlags, 0x06}; + const uint8_t goldenFlags[] = {0x02, 0x01, BLEFlagsBREDRNotSupported}; + + WHEN("Default options for flags") + { + BLE.advertise(); + REQUIRE( 0 == (memcmp(defaultData, BLE.getAdvertisingData().data(), sizeof(defaultData))) ); + REQUIRE( BLE.getScanResponseData().dataLength() == 0 ); + } + + WHEN("Setting external advertising data which has flags") + { + advData.setFlags(BLEFlagsBREDRNotSupported); + advData.updateData(); + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE( 3 == BLE.getAdvertisingData().dataLength()); + REQUIRE( 0 == (memcmp(goldenFlags, advData.data(), sizeof(goldenFlags))) ); + REQUIRE( 0 == (memcmp(goldenFlags, BLE.getAdvertisingData().data(), sizeof(goldenFlags))) ); + REQUIRE( 0 != (memcmp(defaultData, BLE.getAdvertisingData().data(), sizeof(defaultData))) ); + } + + WHEN("Setting external advertising data without flags") + { + advData.clear(); + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE( 0 == (memcmp(defaultData, BLE.getAdvertisingData().data(), sizeof(defaultData))) ); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} + +TEST_CASE("Set default flags in an already full advertising data packet", "[ArduinoBLE::BLEAdvertisingData]") +{ + BLEAdvertisingData advData; + bool retVal; + + const char* name = "tes"; + const uint8_t manufacturerData[24] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23}; + + const uint8_t defaultFlags[3] = {0x02, 0x01, 0x06}; + + const uint8_t goldenData[31] = { + (sizeof(manufacturerData) + 1), BLEFieldManufacturerData, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, + ((uint8_t)(strlen(name) + 1)), BLEFieldCompleteLocalName, 't', 'e', 's' + }; + + WHEN("Test flags when advertising data is full") + { + retVal = advData.setLocalName("tes"); + retVal = advData.setManufacturerData(manufacturerData, sizeof(manufacturerData)); + + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE(0 != memcmp(defaultFlags, BLE.getAdvertisingData().data(), sizeof(defaultFlags))); + REQUIRE(0 == memcmp(goldenData, BLE.getAdvertisingData().data(), sizeof(goldenData))); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} + +TEST_CASE("BLE overwrite a full internal advertising data with an external built one", "[ArduinoBLE::BLEAdvertisingData]") +{ + bool verify; + BLEAdvertisingData advData; + BLEAdvertisingData internalData; + + WHEN("Copy empty external advertising data") + { + BLE.setLocalName("test"); + BLE.setAdvertisedServiceUuid("1818"); + BLE.advertise(); + + THEN("Check that BLE advertising data has been set") + { + verify = (BLE.getAdvertisingData().dataLength() == (3 + (2+2))); + REQUIRE(verify); + } + + THEN("Check that BLE scan response data has been set") + { + verify = (BLE.getScanResponseData().dataLength() == (4+2)); + REQUIRE(verify); + } + + // Copy external empty adv data into advertising data + BLE.setAdvertisingData(advData); + BLE.advertise(); + THEN("BLE advertising data should be erased") + { + verify = (BLE.getAdvertisingData().dataLength() == 3); + REQUIRE(verify); + } + + // Copy external empty adv data into scan response data + BLE.setScanResponseData(advData); + BLE.advertise(); + THEN("BLE scan response data should be erased") + { + verify = (BLE.getScanResponseData().dataLength() == 0); + REQUIRE(verify); + } + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} + +TEST_CASE("BLE test raw data", "[ArduinoBLE::BLEAdvertisingData]") +{ + BLEAdvertisingData advData; + bool retVal; + + WHEN("Set too large raw data") + { + const uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; + retVal = advData.setRawData(data, sizeof(data)); + THEN("Set raw data should return false. The parameter should not be set") + { + REQUIRE(!retVal); + } + advData.clear(); + } + + WHEN("Set correct raw data") + { + const uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + retVal = advData.setRawData(data, sizeof(data)); + THEN("Set raw data should return true. The parameter should be correctly set") + { + REQUIRE(retVal); + } + advData.updateData(); + REQUIRE( 0 == memcmp(data, advData.data(), sizeof(data)) ); + advData.clear(); + } + + WHEN("Hide other parameters by setting raw data") + { + const uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + advData.setLocalName("test"); + advData.setRawData(data, sizeof(data)); + + advData.updateData(); + REQUIRE( 0 == memcmp(data, advData.data(), sizeof(data)) ); + + advData.setLocalName("test"); + advData.updateData(); + REQUIRE( 0 == memcmp(data, advData.data(), sizeof(data)) ); + + advData.clear(); + advData.setLocalName("test"); + advData.updateData(); + REQUIRE( 0 != memcmp(data, advData.data(), sizeof(data)) ); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} diff --git a/extras/test/src/test_advertising_data/test_local_name.cpp b/extras/test/src/test_advertising_data/test_local_name.cpp new file mode 100644 index 00000000..5a6fde10 --- /dev/null +++ b/extras/test/src/test_advertising_data/test_local_name.cpp @@ -0,0 +1,95 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public + +#include "FakeBLELocalDevice.h" +#include "BLEAdvertisingData.h" + +TEST_CASE("Test local name setting", "[ArduinoBLE::BLEAdvertisingData]") +{ + BLEAdvertisingData advData; + int oldRemainingLength = advData.remainingLength(); + bool retVal; + + WHEN("Set correct local name") + { + const char* name = "test"; + retVal = advData.setLocalName(name); + THEN("Set local name should return true. The name parameter should be correctly set") + { + REQUIRE(retVal); + } + + THEN("Check the exact number of bytes occupied by the name just set") + { + REQUIRE( (strlen(name) + 2) == (oldRemainingLength - advData.remainingLength()) ); + } + oldRemainingLength = advData.remainingLength(); + + advData.updateData(); + REQUIRE(advData.dataLength() == (strlen(name) + 2)); + } + + WHEN("Set too long local name") + { + const char* name = "way too long local name (len 32)"; + retVal = advData.setLocalName(name); + THEN("Set local name should return false. The name parameter should not be set") + { + REQUIRE(!retVal); + REQUIRE( oldRemainingLength == advData.remainingLength() ); + advData.updateData(); + REQUIRE( advData.dataLength() == (MAX_AD_DATA_LENGTH - oldRemainingLength) ); + } + } + + WHEN("Overwrite local name with a name as long as max data length") + { + const char* name = "local name with full length "; + retVal = advData.setLocalName(name); + THEN("The name parameter should be correctly overwritten. The remaining length should be 0") + { + REQUIRE(retVal); + REQUIRE( (strlen(name) + 2) == (oldRemainingLength - advData.remainingLength()) ); + oldRemainingLength = advData.remainingLength(); + + advData.updateData(); + REQUIRE(advData.dataLength() == (strlen(name) + 2)); + // advData should be full now + REQUIRE( 0 == advData.remainingLength() ); + REQUIRE( 0 == advData.availableForWrite() ); + } + } + + WHEN("Check consistency when setting the external advertising data") + { + const auto goldenData = advData.data(); + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE( 0 == memcmp(goldenData, BLE.getAdvertisingData().data(), advData.dataLength()) ); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} diff --git a/extras/test/src/test_advertising_data/test_manufacturer.cpp b/extras/test/src/test_advertising_data/test_manufacturer.cpp new file mode 100644 index 00000000..c2fa8483 --- /dev/null +++ b/extras/test/src/test_advertising_data/test_manufacturer.cpp @@ -0,0 +1,173 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public + +#include "FakeBLELocalDevice.h" +#include "BLEAdvertisingData.h" + +TEST_CASE("Test manufacturer data setting", "[ArduinoBLE::BLEAdvertisingData]") +{ + BLEAdvertisingData advData; + int oldRemainingLength = advData.remainingLength(); + const uint16_t companyId = 0x1100; + bool retVal; + + WHEN("Set correct manufacturer data without id") + { + const uint8_t data[] = {0x00, 0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const uint8_t goldenData[] = {(sizeof(data) + 1), BLEFieldManufacturerData, + 0x00, 0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + retVal = advData.setManufacturerData(data, sizeof(data)); + THEN("Check that the manufacturer data has been correctly set") + { + REQUIRE(retVal); + REQUIRE( (sizeof(data) + 2) == (oldRemainingLength - advData.remainingLength()) ); + } + oldRemainingLength = advData.remainingLength(); + + advData.updateData(); + REQUIRE(advData.dataLength() == sizeof(goldenData)); + REQUIRE( 0 == memcmp(goldenData, advData.data(), sizeof(goldenData)) ); + } + + WHEN("Set correct manufacturer data given a manufacturer id") + { + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const uint8_t goldenData[] = {(sizeof(data) + sizeof(companyId) + 1), BLEFieldManufacturerData, + 0x00, 0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + retVal = advData.setManufacturerData(companyId, data, sizeof(data)); + THEN("Check that the manufacturer data has been correctly set") + { + REQUIRE(retVal); + REQUIRE( (sizeof(data) + sizeof(companyId) + 2) == (oldRemainingLength - advData.remainingLength()) ); + } + oldRemainingLength = advData.remainingLength(); + + advData.updateData(); + REQUIRE(advData.dataLength() == sizeof(goldenData)); + REQUIRE( 0 == memcmp(goldenData, advData.data(), sizeof(goldenData)) ); + } + + WHEN("Set too long manufacturer data given id") + { + // extreme case, 1 byte more than the maximum + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + retVal = advData.setManufacturerData(companyId, data, sizeof(data)); + THEN("Manufacturer data was too long, check that it has not been set") + { + REQUIRE(!retVal); + REQUIRE( oldRemainingLength == advData.remainingLength() ); + } + advData.updateData(); + REQUIRE( advData.dataLength() == (MAX_AD_DATA_LENGTH - oldRemainingLength) ); + } + + WHEN("Set too long manufacturer data without id") + { + // extreme case, 1 byte more than the maximum + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; + retVal = advData.setManufacturerData(data, sizeof(data)); + THEN("Manufacturer data was too long, check that it has not been set") + { + REQUIRE(!retVal); + REQUIRE( oldRemainingLength == advData.remainingLength() ); + } + advData.updateData(); + REQUIRE( advData.dataLength() == (MAX_AD_DATA_LENGTH - oldRemainingLength) ); + } + + WHEN("Set manufacturer data without id after setting manufacturer data given id") + { + advData.clear(); + const uint8_t dataGivenId[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}; + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}; + THEN("Check that first insertion of manufacturer data given id is correctly done") + { + retVal = advData.setManufacturerData(companyId, dataGivenId, sizeof(dataGivenId)); + REQUIRE(retVal); + } + THEN("Check that the insertion of manufacturer data without after one with id is correctly done") + { + retVal = advData.setManufacturerData(data, sizeof(data)); + REQUIRE(retVal); + REQUIRE( 0 == advData.remainingLength() ); + advData.updateData(); + REQUIRE( advData.dataLength() == (MAX_AD_DATA_LENGTH) ); + } + } + + WHEN("Set manufacturer data given id after setting manufacturer data without id") + { + advData.clear(); + // dataGivenId is too long!! it should not pass + const uint8_t dataGivenId[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}; + THEN("Check that first insertion of manufacturer data WITHOUT id is correctly done") + { + retVal = advData.setManufacturerData(data, sizeof(data)); + REQUIRE(retVal); + } + THEN("Check that the insertion of manufacturer data given id after one without id is correctly done") + { + retVal = advData.setManufacturerData(companyId, dataGivenId, sizeof(dataGivenId)); + REQUIRE(!retVal); + } + } + + WHEN("Overwrite manufacturer data with one as long as max data length") + { + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}; + const uint8_t goldenData[] = {(sizeof(data) + sizeof(companyId) + 1), BLEFieldManufacturerData, + 0x00, 0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}; + retVal = advData.setManufacturerData(companyId, data, sizeof(data)); + THEN("Manufacturer data should be set correctly") + { + REQUIRE(retVal); + + advData.updateData(); + REQUIRE( 0 == advData.remainingLength() ); + REQUIRE( 0 == advData.availableForWrite() ); + REQUIRE( 0 == memcmp(goldenData, advData.data(), sizeof(goldenData)) ); + } + } + + WHEN("Check consistency when setting the external advertising data") + { + const auto goldenData = advData.data(); + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE( 0 == memcmp(goldenData, BLE.getAdvertisingData().data(), advData.dataLength()) ); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} diff --git a/extras/test/src/test_advertising_data/test_service.cpp b/extras/test/src/test_advertising_data/test_service.cpp new file mode 100644 index 00000000..87c60497 --- /dev/null +++ b/extras/test/src/test_advertising_data/test_service.cpp @@ -0,0 +1,164 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public + +#include "FakeBLELocalDevice.h" +#include "BLEAdvertisingData.h" + +TEST_CASE("Test advertised service id setting", "[ArduinoBLE::BLEAdvertisingData]") +{ + BLEAdvertisingData advData; + int oldRemainingLength = advData.remainingLength(); + bool retVal; + + WHEN("Set correct advertised service") + { + const char* service = "00112233445566770011223344556677"; + retVal = advData.setAdvertisedServiceUuid(service); + REQUIRE(retVal); + REQUIRE( (16 + 2) == (oldRemainingLength - advData.remainingLength()) ); + oldRemainingLength = advData.remainingLength(); + + advData.updateData(); + REQUIRE(advData.dataLength() == (16 + 2)); + } + + WHEN("Set an advertised id too long with respect to the remaining length") + { + advData.clear(); + const char* name = "12 chars str"; + const char* service = "00112233445566770011223344556677"; + advData.setLocalName(name); + oldRemainingLength = advData.remainingLength(); + THEN("Check that the too long parameter has not been set") + { + REQUIRE(!advData.setAdvertisedServiceUuid(service)); + REQUIRE( oldRemainingLength == advData.remainingLength() ); + advData.updateData(); + REQUIRE( advData.dataLength() == (MAX_AD_DATA_LENGTH - oldRemainingLength) ); + } + } + + WHEN("Fill to maximum an advertising packet with a service id") + { + advData.clear(); + const char* name = "11 char str"; + const char* service = "00112233445566770011223344556677"; + advData.setLocalName(name); + REQUIRE(advData.setAdvertisedServiceUuid(service)); + + advData.updateData(); + REQUIRE(advData.dataLength() == (MAX_AD_DATA_LENGTH)); + // advData should be full now + REQUIRE( 0 == advData.remainingLength() ); + REQUIRE( 0 == advData.availableForWrite() ); + } + + WHEN("Check consistency when setting the external advertising data") + { + const auto goldenData = advData.data(); + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE( 0 == memcmp(goldenData, BLE.getAdvertisingData().data(), advData.dataLength()) ); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} + +TEST_CASE("Test service data setting", "[ArduinoBLE::BLEAdvertisingData]") +{ + BLEAdvertisingData advData; + int oldRemainingLength = advData.remainingLength(); + const uint16_t uuid = 0x1100; + bool retVal; + + WHEN("Set correct service data") + { + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const uint8_t goldenData[] = {(sizeof(data) + sizeof(uuid) + 1), BLEFieldServiceData, + 0x00, 0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + retVal = advData.setAdvertisedServiceData(uuid, data, sizeof(data)); + THEN("Correctly set parameter") + { + REQUIRE(retVal); + REQUIRE( (sizeof(data) + 2 + sizeof(uuid)) == (oldRemainingLength - advData.remainingLength()) ); + } + oldRemainingLength = advData.remainingLength(); + + advData.updateData(); + THEN("Check parameter analyzing advertising raw data") + { + REQUIRE(advData.dataLength() == (sizeof(data) + 2 + sizeof(uuid))); + REQUIRE(advData.dataLength() == sizeof(goldenData)); + REQUIRE( 0 == memcmp(goldenData, advData.data(), sizeof(goldenData)) ); + } + } + + WHEN("Set too long service data") + { + // extreme case, 1 byte more than the maximum + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + retVal = advData.setAdvertisedServiceData(uuid, data, sizeof(data)); + THEN("Check that the too long parameter has not been set") + { + REQUIRE(!retVal); + } + REQUIRE( oldRemainingLength == advData.remainingLength() ); + advData.updateData(); + REQUIRE( advData.dataLength() == (MAX_AD_DATA_LENGTH - oldRemainingLength) ); + } + + WHEN("Overwrite service data with one as long as max data length") + { + const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}; + retVal = advData.setAdvertisedServiceData(uuid, data, sizeof(data)); + THEN("Check correctly set parameter") + { + REQUIRE(retVal); + } + + advData.updateData(); + // advData should be full now + THEN("advData should be full now") + { + REQUIRE( 0 == advData.remainingLength() ); + REQUIRE( 0 == advData.availableForWrite() ); + } + } + + WHEN("Check consistency when setting the external advertising data") + { + const auto goldenData = advData.data(); + BLE.setAdvertisingData(advData); + BLE.advertise(); + REQUIRE( 0 == memcmp(goldenData, BLE.getAdvertisingData().data(), advData.dataLength()) ); + } + + // Clear BLE advertising data + advData.clear(); + BLE.setAdvertisingData(advData); +} diff --git a/extras/test/src/test_characteristic/test_permissions.cpp b/extras/test/src/test_characteristic/test_permissions.cpp new file mode 100644 index 00000000..cd658580 --- /dev/null +++ b/extras/test/src/test_characteristic/test_permissions.cpp @@ -0,0 +1,126 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public + +#include "FakeBLELocalDevice.h" +#include "BLEAdvertisingData.h" +#include "BLETypedCharacteristics.h" +#include "BLELocalCharacteristic.h" +#include "BLEStringCharacteristic.h" +#include "BLEProperty.h" +#include + +int property[] = { + BLEBroadcast, + BLERead, + BLEWriteWithoutResponse, + BLEWrite, + BLENotify, + BLEIndicate, + BLEAuthSignedWrite, + BLEExtProp, + BLERead | BLEWrite | BLENotify +}; + +int permission[] = { + BLEEncryption, + BLEAuthentication, + BLEAuthorization, + BLEEncryption | BLEAuthentication +}; + +const char uuid[][31] = { + "1 Bool", + "2 Char", + "3 UnsignedChar", + "4 Byte", + "5 Short", + "6 UnsignedShort", + "7 Word", + "8 Int", + "9 UnsignedInt", + "A Long", + "B UnsignedLong", + "C Float", + "D Double", + "E String" +}; + +std::unique_ptr createCharacteristic(const char* uuid, unsigned int properties) +{ + switch(uuid[0]) + { + case '1': + return std::unique_ptr(new BLEBoolCharacteristic(uuid, properties)); + case '2': + return std::unique_ptr(new BLECharCharacteristic(uuid, properties)); + case '3': + return std::unique_ptr(new BLEUnsignedCharCharacteristic(uuid, properties)); + case '4': + return std::unique_ptr(new BLEByteCharacteristic(uuid, properties)); + case '5': + return std::unique_ptr(new BLEShortCharacteristic(uuid, properties)); + case '6': + return std::unique_ptr(new BLEUnsignedShortCharacteristic(uuid, properties)); + case '7': + return std::unique_ptr(new BLEWordCharacteristic(uuid, properties)); + case '8': + return std::unique_ptr(new BLEIntCharacteristic(uuid, properties)); + case '9': + return std::unique_ptr(new BLEUnsignedIntCharacteristic(uuid, properties)); + case 'A': + return std::unique_ptr(new BLELongCharacteristic(uuid, properties)); + case 'B': + return std::unique_ptr(new BLEUnsignedLongCharacteristic(uuid, properties)); + case 'C': + return std::unique_ptr(new BLEFloatCharacteristic(uuid, properties)); + case 'D': + return std::unique_ptr(new BLEDoubleCharacteristic(uuid, properties)); + case 'E': + return std::unique_ptr(new BLEStringCharacteristic(uuid, properties, 2)); + default: + break; + } + return nullptr; +} + +TEST_CASE("Test characteristic properties and permissions", "[ArduinoBLE::BLECharacteristic]") +{ + WHEN("Create a characteristic") + { + for(int i = 0; i < sizeof(property)/sizeof(int); i++) + { + for(int j = 0; j < sizeof(permission)/sizeof(int); j++) + { + for(int k = 0; k < 14; k++) + { + std::unique_ptr ptr = createCharacteristic(uuid[k], property[i] | permission[j]); + REQUIRE(ptr != nullptr); + REQUIRE(ptr->properties() == (property[i])); + BLELocalCharacteristic * local = ptr->local(); + REQUIRE(local->permissions() == (permission[j] >> 8)); + } + } + } + } +} diff --git a/extras/test/src/test_characteristic/test_writeValue.cpp b/extras/test/src/test_characteristic/test_writeValue.cpp new file mode 100644 index 00000000..6c7e1cd1 --- /dev/null +++ b/extras/test/src/test_characteristic/test_writeValue.cpp @@ -0,0 +1,389 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public + +#include "FakeBLELocalDevice.h" +#include "BLEAdvertisingData.h" +#include "BLETypedCharacteristics.h" +#include "BLELocalCharacteristic.h" +#include "BLEStringCharacteristic.h" +#include "BLEProperty.h" + + +TEST_CASE("Test characteristic writeValue", "[ArduinoBLE::BLECharacteristic]") +{ + WHEN("Create a bool characteristic") + { + BLEBoolCharacteristic boolCharacteristic("Bool", BLEBroadcast| BLEIndicate | BLENotify ); + bool v = false;; + int written = boolCharacteristic.writeValue(v); + REQUIRE( written == sizeof(bool) ); + + boolCharacteristic.broadcast(); + written = boolCharacteristic.writeValue(v); + REQUIRE( written == sizeof(bool) ); + + BLEDevice device; + boolCharacteristic.local()->writeCccdValue(device, 0x002); + written = boolCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + boolCharacteristic.local()->writeCccdValue(device, 0x001); + written = boolCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a boolean characteristic") + { + BLEBooleanCharacteristic booleanCharacteristic("Boolean", BLEBroadcast| BLEIndicate | BLENotify); + bool v = false; + int written = booleanCharacteristic.writeValue(v); + REQUIRE( written == sizeof(bool) ); + + booleanCharacteristic.broadcast(); + written = booleanCharacteristic.writeValue(v); + REQUIRE( written == sizeof(bool) ); + + BLEDevice device; + booleanCharacteristic.local()->writeCccdValue(device, 0x002); + written = booleanCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + booleanCharacteristic.local()->writeCccdValue(device, 0x001); + written = booleanCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a char characteristic") + { + BLECharCharacteristic charCharacteristic("Char", BLEBroadcast| BLEIndicate | BLENotify); + char v = 'a'; + int written = charCharacteristic.writeValue(v); + REQUIRE( written == sizeof(char) ); + + charCharacteristic.broadcast(); + written = charCharacteristic.writeValue(v); + REQUIRE( written == sizeof(char) ); + + BLEDevice device; + charCharacteristic.local()->writeCccdValue(device, 0x002); + written = charCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + charCharacteristic.local()->writeCccdValue(device, 0x001); + written = charCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a unsigned char characteristic") + { + BLEUnsignedCharCharacteristic unsignedCharCharacteristic("UnsignedChar", BLEBroadcast| BLEIndicate | BLENotify); + unsigned char v = 0x01; + int written = unsignedCharCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned char) ); + + unsignedCharCharacteristic.broadcast(); + written = unsignedCharCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned char) ); + + BLEDevice device; + unsignedCharCharacteristic.local()->writeCccdValue(device, 0x002); + written = unsignedCharCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + unsignedCharCharacteristic.local()->writeCccdValue(device, 0x001); + written = unsignedCharCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a byte characteristic") + { + BLEByteCharacteristic byteCharacteristic("Byte", BLEBroadcast| BLEIndicate | BLENotify); + byte v = 0x01; + int written = byteCharacteristic.writeValue(v); + REQUIRE( written == sizeof(byte) ); + + byteCharacteristic.broadcast(); + written = byteCharacteristic.writeValue(v); + REQUIRE( written == sizeof(byte) ); + + BLEDevice device; + byteCharacteristic.local()->writeCccdValue(device, 0x002); + written = byteCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + byteCharacteristic.local()->writeCccdValue(device, 0x001); + written = byteCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a short characteristic") + { + BLEShortCharacteristic shortCharacteristic("Short", BLEBroadcast| BLEIndicate | BLENotify); + short v = -1; + int written = shortCharacteristic.writeValue(v); + REQUIRE( written == sizeof(short) ); + + shortCharacteristic.broadcast(); + written = shortCharacteristic.writeValue(v); + REQUIRE( written == sizeof(short) ); + + BLEDevice device; + shortCharacteristic.local()->writeCccdValue(device, 0x002); + written = shortCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + shortCharacteristic.local()->writeCccdValue(device, 0x001); + written = shortCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a unsigned short characteristic") + { + BLEUnsignedShortCharacteristic unsignedShortCharacteristic("UnsignedShort", BLEBroadcast| BLEIndicate | BLENotify); + unsigned short v = 1; + int written = unsignedShortCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned short) ); + + unsignedShortCharacteristic.broadcast(); + written = unsignedShortCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned short) ); + + BLEDevice device; + unsignedShortCharacteristic.local()->writeCccdValue(device, 0x002); + written = unsignedShortCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + unsignedShortCharacteristic.local()->writeCccdValue(device, 0x001); + written = unsignedShortCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a word characteristic") + { + BLEWordCharacteristic wordCharacteristic("Word", BLEBroadcast| BLEIndicate | BLENotify); + word v = -1; + int written = wordCharacteristic.writeValue(v); + REQUIRE( written == sizeof(word) ); + + wordCharacteristic.broadcast(); + written = wordCharacteristic.writeValue(v); + REQUIRE( written == sizeof(word) ); + + BLEDevice device; + wordCharacteristic.local()->writeCccdValue(device, 0x002); + written = wordCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + wordCharacteristic.local()->writeCccdValue(device, 0x001); + written = wordCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a int characteristic") + { + BLEIntCharacteristic intCharacteristic("Int", BLEBroadcast| BLEIndicate | BLENotify); + int v = -1; + int written = intCharacteristic.writeValue(v); + REQUIRE( written == sizeof(int) ); + + intCharacteristic.broadcast(); + written = intCharacteristic.writeValue(v); + REQUIRE( written == sizeof(int) ); + + BLEDevice device; + intCharacteristic.local()->writeCccdValue(device, 0x002); + written = intCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + intCharacteristic.local()->writeCccdValue(device, 0x001); + written = intCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a unsigned int characteristic") + { + BLEUnsignedIntCharacteristic unsignedIntCharacteristic("UnsignedInt", BLEBroadcast| BLEIndicate | BLENotify); + unsigned int v = 1; + int written = unsignedIntCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned int) ); + + unsignedIntCharacteristic.broadcast(); + written = unsignedIntCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned int) ); + + BLEDevice device; + unsignedIntCharacteristic.local()->writeCccdValue(device, 0x002); + written = unsignedIntCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + unsignedIntCharacteristic.local()->writeCccdValue(device, 0x001); + written = unsignedIntCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a long characteristic") + { + BLELongCharacteristic longCharacteristic("Long", BLEBroadcast| BLEIndicate | BLENotify); + long v = -1; + int written = longCharacteristic.writeValue(v); + REQUIRE( written == sizeof(long) ); + + longCharacteristic.broadcast(); + written = longCharacteristic.writeValue(v); + REQUIRE( written == sizeof(long) ); + + BLEDevice device; + longCharacteristic.local()->writeCccdValue(device, 0x002); + written = longCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + longCharacteristic.local()->writeCccdValue(device, 0x001); + written = longCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a unsigned long characteristic") + { + BLEUnsignedLongCharacteristic unsignedLongCharacteristic("UnsignedLong", BLEBroadcast| BLEIndicate | BLENotify); + unsigned long v = 1; + int written = unsignedLongCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned long) ); + + unsignedLongCharacteristic.broadcast(); + written = unsignedLongCharacteristic.writeValue(v); + REQUIRE( written == sizeof(unsigned long) ); + + BLEDevice device; + unsignedLongCharacteristic.local()->writeCccdValue(device, 0x002); + written = unsignedLongCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + unsignedLongCharacteristic.local()->writeCccdValue(device, 0x001); + written = unsignedLongCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a float characteristic") + { + BLEFloatCharacteristic floatCharacteristic("Float", BLEBroadcast| BLEIndicate | BLENotify); + float v = -1.0f; + int written = floatCharacteristic.writeValue(v); + REQUIRE( written == sizeof(float) ); + + floatCharacteristic.broadcast(); + written = floatCharacteristic.writeValue(v); + REQUIRE( written == sizeof(float) ); + + BLEDevice device; + floatCharacteristic.local()->writeCccdValue(device, 0x002); + written = floatCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + floatCharacteristic.local()->writeCccdValue(device, 0x001); + written = floatCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a double characteristic") + { + BLEDoubleCharacteristic doubleCharacteristic("Double", BLEBroadcast| BLEIndicate | BLENotify); + double v = -1.0; + int written = doubleCharacteristic.writeValue(v); + REQUIRE( written == sizeof(double) ); + + doubleCharacteristic.broadcast(); + written = doubleCharacteristic.writeValue(v); + REQUIRE( written == sizeof(double) ); + + BLEDevice device; + doubleCharacteristic.local()->writeCccdValue(device, 0x002); + written = doubleCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + doubleCharacteristic.local()->writeCccdValue(device, 0x001); + written = doubleCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a string characteristic") + { + const int maxStringLength = 64; + BLEStringCharacteristic stringCharacteristic("String", BLEBroadcast| BLEIndicate | BLENotify, maxStringLength); + const char* v = "Hello"; + int written = stringCharacteristic.writeValue(v); + REQUIRE( written == min(strlen(v), maxStringLength) ); + + stringCharacteristic.broadcast(); + written = stringCharacteristic.writeValue(v); + REQUIRE( written == min(strlen(v), maxStringLength) ); + + BLEDevice device; + stringCharacteristic.local()->writeCccdValue(device, 0x002); + written = stringCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + stringCharacteristic.local()->writeCccdValue(device, 0x001); + written = stringCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + + WHEN("Create a too long string characteristic") + { + const int maxStringLength = 4; + BLEStringCharacteristic stringCharacteristic("String", BLEBroadcast| BLEIndicate | BLENotify, maxStringLength); + const char* v = "Hello"; + int written = stringCharacteristic.writeValue(v); + REQUIRE( written == min(strlen(v), maxStringLength) ); + + stringCharacteristic.broadcast(); + written = stringCharacteristic.writeValue(v); + REQUIRE( written == min(strlen(v), maxStringLength) ); + + BLEDevice device; + stringCharacteristic.local()->writeCccdValue(device, 0x002); + written = stringCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + stringCharacteristic.local()->writeCccdValue(device, 0x001); + written = stringCharacteristic.writeValue(v); + /* No peers connected */ + REQUIRE( written == 0 ); + } + +} diff --git a/extras/test/src/test_discovered_device/FakeGAP.cpp b/extras/test/src/test_discovered_device/FakeGAP.cpp new file mode 100644 index 00000000..bbf78206 --- /dev/null +++ b/extras/test/src/test_discovered_device/FakeGAP.cpp @@ -0,0 +1,31 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "FakeGAP.h" + +FakeGAPClass::FakeGAPClass() +{ +} + +FakeGAPClass::~FakeGAPClass() +{ +} + +FakeGAPClass GAPFakeObj; +GAPClass& GAP = GAPFakeObj; diff --git a/extras/test/src/test_discovered_device/test_discovered_device.cpp b/extras/test/src/test_discovered_device/test_discovered_device.cpp new file mode 100644 index 00000000..14efcc0f --- /dev/null +++ b/extras/test/src/test_discovered_device/test_discovered_device.cpp @@ -0,0 +1,52 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#define private public +#define protected public +#include "BLEDevice.h" + +TEST_CASE("BLE discovered device test", "[ArduinoBLE::BLEDevice]") +{ + + WHEN("Retrieve local name from advertisement packet") + { + // Mocking advertisement packet + uint8_t advType = 0x03; + uint8_t eirLength = 6; + uint8_t eirData[] = {0x05, 0x09, 't', 'e', 's', 't'}; + uint8_t rssi = 0; + + // Expected results + String goldenName = "test"; + + // Simulate device discovery + BLEDevice device = BLEDevice(); + device.setAdvertisementData(0x03, eirLength, eirData, rssi); + + bool hasName = device.hasLocalName(); + REQUIRE(hasName); + + String name = device.localName(); + REQUIRE(goldenName == name); + + } + +} diff --git a/extras/test/src/test_main.cpp b/extras/test/src/test_main.cpp new file mode 100644 index 00000000..958f5367 --- /dev/null +++ b/extras/test/src/test_main.cpp @@ -0,0 +1,21 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#define CATCH_CONFIG_MAIN /* This tells Catch to provide a main() - only do this in one cpp file */ +#include diff --git a/extras/test/src/test_uuid/test_uuid.cpp b/extras/test/src/test_uuid/test_uuid.cpp new file mode 100644 index 00000000..16c225eb --- /dev/null +++ b/extras/test/src/test_uuid/test_uuid.cpp @@ -0,0 +1,52 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#include "utility/BLEUuid.h" + +TEST_CASE("BLE uuid test", "[ArduinoBLE::BLEUuid]") +{ + WHEN("Set and retrieve uuid") + { + bool verify; + + const char* goldenUuid = "19b10010-e8f2-537e-4f6c-d104768a1214"; + // little-endian order + const uint8_t goldenData[] = {0x14,0x12,0x8A,0x76,0x04,0xD1,0x6C,0x4F,0x7E,0x53,0xF2,0xE8,0x10,0x00,0xB1,0x19}; + uint8_t goldenLength = 16; + + BLEUuid test(goldenUuid); + + uint8_t testLength = test.length(); + verify = (goldenLength == testLength); + REQUIRE(verify); + + const uint8_t *testData = test.data(); + verify = ( 0 == (memcmp(goldenData, testData, sizeof(goldenData))) ); + REQUIRE(verify); + + const char *testUuid = test.uuidToString(testData, testLength); + verify = ( 0 == strcmp(testUuid, goldenUuid) ); + REQUIRE(verify); + + // print the uuid + //WARN("test: " << testUuid << ", golden: " << goldenUuid); + } +} \ No newline at end of file diff --git a/extras/test/src/util/Common.cpp b/extras/test/src/util/Common.cpp new file mode 100644 index 00000000..e2e1eeb5 --- /dev/null +++ b/extras/test/src/util/Common.cpp @@ -0,0 +1,34 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "Common.h" + +void delay(unsigned long) +{ + +} + +/* C++ prototypes */ +long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +uint16_t makeWord(uint16_t w) { return w; } +uint16_t makeWord(uint8_t h, uint8_t l) { return (h << 8) | l; } \ No newline at end of file diff --git a/extras/test/src/util/HCIFakeTransport.cpp b/extras/test/src/util/HCIFakeTransport.cpp new file mode 100644 index 00000000..26645f5e --- /dev/null +++ b/extras/test/src/util/HCIFakeTransport.cpp @@ -0,0 +1,23 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "HCIFakeTransport.h" + +HCIFakeTransportClass HCIFakeTransport; +HCITransportInterface& HCITransport = HCIFakeTransport; \ No newline at end of file diff --git a/extras/test/src/util/String.cpp b/extras/test/src/util/String.cpp new file mode 100644 index 00000000..618973b6 --- /dev/null +++ b/extras/test/src/util/String.cpp @@ -0,0 +1,755 @@ +/* + String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All rights reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "String.h" +#include "itoa.h" + +/*********************************************/ +/* Constructors */ +/*********************************************/ + +namespace arduino { + +String::String(const char *cstr) +{ + init(); + if (cstr) copy(cstr, strlen(cstr)); +} + +String::String(const String &value) +{ + init(); + *this = value; +} + +String::String(const __FlashStringHelper *pstr) +{ + init(); + *this = pstr; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String::String(String &&rval) +{ + init(); + move(rval); +} +String::String(StringSumHelper &&rval) +{ + init(); + move(rval); +} +#endif + +String::String(char c) +{ + init(); + char buf[2]; + buf[0] = c; + buf[1] = 0; + *this = buf; +} + +String::String(unsigned char value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned char)]; + utoa(value, buf, base); + *this = buf; +} + +String::String(int value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(int)]; + itoa(value, buf, base); + *this = buf; +} + +String::String(unsigned int value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned int)]; + utoa(value, buf, base); + *this = buf; +} + +String::String(long value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(long)]; + ltoa(value, buf, base); + *this = buf; +} + +String::String(unsigned long value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned long)]; + ultoa(value, buf, base); + *this = buf; +} + +/* +String::String(float value, unsigned char decimalPlaces) +{ + init(); + char buf[33]; + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::String(double value, unsigned char decimalPlaces) +{ + init(); + char buf[33]; + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} +*/ + +String::~String() +{ + if (buffer) free(buffer); +} + +/*********************************************/ +/* Memory Management */ +/*********************************************/ + +inline void String::init(void) +{ + buffer = NULL; + capacity = 0; + len = 0; +} + +void String::invalidate(void) +{ + if (buffer) free(buffer); + buffer = NULL; + capacity = len = 0; +} + +unsigned char String::reserve(unsigned int size) +{ + if (buffer && capacity >= size) return 1; + if (changeBuffer(size)) { + if (len == 0) buffer[0] = 0; + return 1; + } + return 0; +} + +unsigned char String::changeBuffer(unsigned int maxStrLen) +{ + char *newbuffer = (char *)realloc(buffer, maxStrLen + 1); + if (newbuffer) { + buffer = newbuffer; + capacity = maxStrLen; + return 1; + } + return 0; +} + +/*********************************************/ +/* Copy and Move */ +/*********************************************/ + +String & String::copy(const char *cstr, unsigned int length) +{ + if (!reserve(length)) { + invalidate(); + return *this; + } + len = length; + strcpy(buffer, cstr); + return *this; +} + +/* +String & String::copy(const __FlashStringHelper *pstr, unsigned int length) +{ + if (!reserve(length)) { + invalidate(); + return *this; + } + len = length; + strcpy_P(buffer, (PGM_P)pstr); + return *this; +} +*/ + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +void String::move(String &rhs) +{ + if (buffer) { + if (rhs && capacity >= rhs.len) { + strcpy(buffer, rhs.buffer); + len = rhs.len; + rhs.len = 0; + return; + } else { + free(buffer); + } + } + buffer = rhs.buffer; + capacity = rhs.capacity; + len = rhs.len; + rhs.buffer = NULL; + rhs.capacity = 0; + rhs.len = 0; +} +#endif + +String & String::operator = (const String &rhs) +{ + if (this == &rhs) return *this; + + if (rhs.buffer) copy(rhs.buffer, rhs.len); + else invalidate(); + + return *this; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String & String::operator = (String &&rval) +{ + if (this != &rval) move(rval); + return *this; +} + +String & String::operator = (StringSumHelper &&rval) +{ + if (this != &rval) move(rval); + return *this; +} +#endif + +String & String::operator = (const char *cstr) +{ + if (cstr) copy(cstr, strlen(cstr)); + else invalidate(); + + return *this; +} + +/* +String & String::operator = (const __FlashStringHelper *pstr) +{ + if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); + else invalidate(); + + return *this; +} +*/ + +/*********************************************/ +/* concat */ +/*********************************************/ + +unsigned char String::concat(const String &s) +{ + return concat(s.buffer, s.len); +} + +unsigned char String::concat(const char *cstr, unsigned int length) +{ + unsigned int newlen = len + length; + if (!cstr) return 0; + if (length == 0) return 1; + if (!reserve(newlen)) return 0; + strcpy(buffer + len, cstr); + len = newlen; + return 1; +} + +unsigned char String::concat(const char *cstr) +{ + if (!cstr) return 0; + return concat(cstr, strlen(cstr)); +} + +unsigned char String::concat(char c) +{ + char buf[2]; + buf[0] = c; + buf[1] = 0; + return concat(buf, 1); +} + +unsigned char String::concat(unsigned char num) +{ + char buf[1 + 3 * sizeof(unsigned char)]; + itoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(int num) +{ + char buf[2 + 3 * sizeof(int)]; + itoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(unsigned int num) +{ + char buf[1 + 3 * sizeof(unsigned int)]; + utoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(long num) +{ + char buf[2 + 3 * sizeof(long)]; + ltoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(unsigned long num) +{ + char buf[1 + 3 * sizeof(unsigned long)]; + ultoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +/* +unsigned char String::concat(float num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string, strlen(string)); +} + +unsigned char String::concat(double num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string, strlen(string)); +} + +unsigned char String::concat(const __FlashStringHelper * str) +{ + if (!str) return 0; + int length = strlen_P((const char *) str); + if (length == 0) return 1; + unsigned int newlen = len + length; + if (!reserve(newlen)) return 0; + strcpy_P(buffer + len, (const char *) str); + len = newlen; + return 1; +} +*/ + +/*********************************************/ +/* Concatenate */ +/*********************************************/ + +StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(rhs.buffer, rhs.len)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr) +{ + StringSumHelper &a = const_cast(lhs); + if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, char c) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(c)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, int num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, long num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +/* +StringSumHelper & operator + (const StringSumHelper &lhs, float num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, double num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(rhs)) a.invalidate(); + return a; +} +*/ + +/*********************************************/ +/* Comparison */ +/*********************************************/ + +int String::compareTo(const String &s) const +{ + if (!buffer || !s.buffer) { + if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer; + if (buffer && len > 0) return *(unsigned char *)buffer; + return 0; + } + return strcmp(buffer, s.buffer); +} + +int String::compareTo(const char *cstr) const +{ + if (!buffer || !cstr) { + if (cstr && !*cstr) return 0 - *(unsigned char *)cstr; + if (buffer && len > 0) return *(unsigned char *)buffer; + return 0; + } + return strcmp(buffer, cstr); +} + +unsigned char String::equals(const String &s2) const +{ + return (len == s2.len && compareTo(s2) == 0); +} + +unsigned char String::equals(const char *cstr) const +{ + if (len == 0) return (cstr == NULL || *cstr == 0); + if (cstr == NULL) return buffer[0] == 0; + return strcmp(buffer, cstr) == 0; +} + +unsigned char String::equalsIgnoreCase( const String &s2 ) const +{ + if (this == &s2) return 1; + if (len != s2.len) return 0; + if (len == 0) return 1; + const char *p1 = buffer; + const char *p2 = s2.buffer; + while (*p1) { + if (tolower(*p1++) != tolower(*p2++)) return 0; + } + return 1; +} + +unsigned char String::startsWith( const String &s2 ) const +{ + if (len < s2.len) return 0; + return startsWith(s2, 0); +} + +unsigned char String::startsWith( const String &s2, unsigned int offset ) const +{ + if (offset > len - s2.len || !buffer || !s2.buffer) return 0; + return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0; +} + +unsigned char String::endsWith( const String &s2 ) const +{ + if ( len < s2.len || !buffer || !s2.buffer) return 0; + return strcmp(&buffer[len - s2.len], s2.buffer) == 0; +} + +/*********************************************/ +/* Character Access */ +/*********************************************/ + +char String::charAt(unsigned int loc) const +{ + return operator[](loc); +} + +void String::setCharAt(unsigned int loc, char c) +{ + if (loc < len) buffer[loc] = c; +} + +char & String::operator[](unsigned int index) +{ + static char dummy_writable_char; + if (index >= len || !buffer) { + dummy_writable_char = 0; + return dummy_writable_char; + } + return buffer[index]; +} + +char String::operator[]( unsigned int index ) const +{ + if (index >= len || !buffer) return 0; + return buffer[index]; +} + +void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const +{ + if (!bufsize || !buf) return; + if (index >= len) { + buf[0] = 0; + return; + } + unsigned int n = bufsize - 1; + if (n > len - index) n = len - index; + strncpy((char *)buf, buffer + index, n); + buf[n] = 0; +} + +/*********************************************/ +/* Search */ +/*********************************************/ + +int String::indexOf(char c) const +{ + return indexOf(c, 0); +} + +int String::indexOf( char ch, unsigned int fromIndex ) const +{ + if (fromIndex >= len) return -1; + const char* temp = strchr(buffer + fromIndex, ch); + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::indexOf(const String &s2) const +{ + return indexOf(s2, 0); +} + +int String::indexOf(const String &s2, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + const char *found = strstr(buffer + fromIndex, s2.buffer); + if (found == NULL) return -1; + return found - buffer; +} + +int String::lastIndexOf( char theChar ) const +{ + return lastIndexOf(theChar, len - 1); +} + +int String::lastIndexOf(char ch, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + char tempchar = buffer[fromIndex + 1]; + buffer[fromIndex + 1] = '\0'; + char* temp = strrchr( buffer, ch ); + buffer[fromIndex + 1] = tempchar; + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::lastIndexOf(const String &s2) const +{ + return lastIndexOf(s2, len - s2.len); +} + +int String::lastIndexOf(const String &s2, unsigned int fromIndex) const +{ + if (s2.len == 0 || len == 0 || s2.len > len) return -1; + if (fromIndex >= len) fromIndex = len - 1; + int found = -1; + for (char *p = buffer; p <= buffer + fromIndex; p++) { + p = strstr(p, s2.buffer); + if (!p) break; + if ((unsigned int)(p - buffer) <= fromIndex) found = p - buffer; + } + return found; +} + +String String::substring(unsigned int left, unsigned int right) const +{ + if (left > right) { + unsigned int temp = right; + right = left; + left = temp; + } + String out; + if (left >= len) return out; + if (right > len) right = len; + char temp = buffer[right]; // save the replaced character + buffer[right] = '\0'; + out = buffer + left; // pointer arithmetic + buffer[right] = temp; //restore character + return out; +} + +/*********************************************/ +/* Modification */ +/*********************************************/ + +void String::replace(char find, char replace) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + if (*p == find) *p = replace; + } +} + +void String::replace(const String& find, const String& replace) +{ + if (len == 0 || find.len == 0) return; + int diff = replace.len - find.len; + char *readFrom = buffer; + char *foundAt; + if (diff == 0) { + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + memcpy(foundAt, replace.buffer, replace.len); + readFrom = foundAt + replace.len; + } + } else if (diff < 0) { + char *writeTo = buffer; + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + unsigned int n = foundAt - readFrom; + memcpy(writeTo, readFrom, n); + writeTo += n; + memcpy(writeTo, replace.buffer, replace.len); + writeTo += replace.len; + readFrom = foundAt + find.len; + len += diff; + } + strcpy(writeTo, readFrom); + } else { + unsigned int size = len; // compute size needed for result + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + readFrom = foundAt + find.len; + size += diff; + } + if (size == len) return; + if (size > capacity && !changeBuffer(size)) return; // XXX: tell user! + int index = len - 1; + while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { + readFrom = buffer + index + find.len; + memmove(readFrom + diff, readFrom, len - (readFrom - buffer)); + len += diff; + buffer[len] = 0; + memcpy(buffer + index, replace.buffer, replace.len); + index--; + } + } +} + +void String::remove(unsigned int index){ + // Pass the biggest integer as the count. The remove method + // below will take care of truncating it at the end of the + // string. + remove(index, (unsigned int)-1); +} + +void String::remove(unsigned int index, unsigned int count){ + if (index >= len) { return; } + if (count <= 0) { return; } + if (count > len - index) { count = len - index; } + char *writeTo = buffer + index; + len = len - count; + memmove(writeTo, buffer + index + count,len - index); + buffer[len] = 0; +} + +void String::toLowerCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + *p = tolower(*p); + } +} + +void String::toUpperCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + *p = toupper(*p); + } +} + +void String::trim(void) +{ + if (!buffer || len == 0) return; + char *begin = buffer; + while (isspace(*begin)) begin++; + char *end = buffer + len - 1; + while (isspace(*end) && end >= begin) end--; + len = end + 1 - begin; + if (begin > buffer) memmove(buffer, begin, len); + buffer[len] = 0; +} + +/*********************************************/ +/* Parsing / Conversion */ +/*********************************************/ + +long String::toInt(void) const +{ + if (buffer) return atol(buffer); + return 0; +} + +float String::toFloat(void) const +{ + return float(toDouble()); +} + +double String::toDouble(void) const +{ + if (buffer) return atof(buffer); + return 0; +} + +} // namespace arduino diff --git a/extras/test/src/util/TestUtil.cpp b/extras/test/src/util/TestUtil.cpp new file mode 100644 index 00000000..111607ec --- /dev/null +++ b/extras/test/src/util/TestUtil.cpp @@ -0,0 +1,3 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + */ diff --git a/extras/test/src/util/itoa.c b/extras/test/src/util/itoa.c new file mode 100644 index 00000000..c9a275f6 --- /dev/null +++ b/extras/test/src/util/itoa.c @@ -0,0 +1,126 @@ +/* + Copyright (c) 2014 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern char* itoa( int value, char *string, int radix ) +{ + return ltoa( value, string, radix ) ; +} + +extern char* ltoa( long value, char *string, int radix ) +{ + char tmp[33]; + char *tp = tmp; + long i; + unsigned long v; + int sign; + char *sp; + + if ( string == NULL ) + { + return 0 ; + } + + if (radix > 36 || radix <= 1) + { + return 0 ; + } + + sign = (radix == 10 && value < 0); + if (sign) + { + v = -value; + } + else + { + v = (unsigned long)value; + } + + while (v || tp == tmp) + { + i = v % radix; + v = v / radix; + if (i < 10) + *tp++ = i+'0'; + else + *tp++ = i + 'a' - 10; + } + + sp = string; + + if (sign) + *sp++ = '-'; + while (tp > tmp) + *sp++ = *--tp; + *sp = 0; + + return string; +} + +extern char* utoa( unsigned int value, char *string, int radix ) +{ + return ultoa( value, string, radix ) ; +} + +extern char* ultoa( unsigned long value, char *string, int radix ) +{ + char tmp[33]; + char *tp = tmp; + long i; + unsigned long v = value; + char *sp; + + if ( string == NULL ) + { + return 0; + } + + if (radix > 36 || radix <= 1) + { + return 0; + } + + while (v || tp == tmp) + { + i = v % radix; + v = v / radix; + if (i < 10) + *tp++ = i+'0'; + else + *tp++ = i + 'a' - 10; + } + + sp = string; + + + while (tp > tmp) + *sp++ = *--tp; + *sp = 0; + + return string; +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/keywords.txt b/keywords.txt index 464cac72..aa35c72d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -77,9 +77,12 @@ setEventHandler KEYWORD2 setAdvertisingInterval KEYWORD2 setConnectionInterval KEYWORD2 setConnectable KEYWORD2 +setPairable KEYWORD2 setTimeout KEYWORD2 debug KEYWORD2 noDebug KEYWORD2 +pairable KEYWORD2 +paired KEYWORD2 properties KEYWORD2 valueSize KEYWORD2 @@ -91,7 +94,7 @@ setValue KEYWORD2 broadcast KEYWORD2 written KEYWORD2 subscribed KEYWORD2 -valueUpdated KEYWORD2 +valueUpdated KEYWORD2 addDescriptor KEYWORD2 descriptorCount KEYWORD2 hasDescriptor KEYWORD2 @@ -102,7 +105,7 @@ canWrite KEYWORD2 canSubscribe KEYWORD2 subscribe KEYWORD2 canUnsubscribe KEYWORD2 -unsubscribe KEYWORD2 +unsubscribe KEYWORD2 writeValueLE KEYWORD2 setValueLE KEYWORD2 valueLE KEYWORD2 @@ -111,7 +114,7 @@ setValueBE KEYWORD2 valueBE KEYWORD2 uuid KEYWORD2 -addCharacteristic KEYWORD +addCharacteristic KEYWORD2 ####################################### # Constants (LITERAL1) @@ -131,5 +134,5 @@ BLEIndicate LITERAL1 BLESubscribed LITERAL1 BLEUnsubscribed LITERAL1 BLEWritten LITERAL1 -BLEUpdated LITERAL1 +BLEUpdated LITERAL1 diff --git a/library.properties b/library.properties index 2d531f3a..2a3aae10 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=ArduinoBLE -version=1.1.3 +version=1.4.0 author=Arduino maintainer=Arduino -sentence=Enables BLE connectivity on the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev.2, Arduino Nano 33 IoT, and Arduino Nano 33 BLE. -paragraph=This library supports creating a BLE peripheral and BLE central mode. +sentence=Enables Bluetooth® Low Energy connectivity on the Arduino MKR WiFi 1010, Arduino UNO WiFi Rev2, Arduino Nano 33 IoT, Arduino Nano 33 BLE, Nicla Sense ME and UNO R4 WiFi. +paragraph=This library supports creating a Bluetooth® Low Energy peripheral & central mode. category=Communication url=https://www.arduino.cc/en/Reference/ArduinoBLE -architectures=samd,megaavr,mbed +architectures=samd,megaavr,mbed,apollo3,mbed_nano,mbed_portenta,mbed_nicla,esp32,mbed_giga,renesas,renesas_portenta,mbed_opta,renesas_uno,silabs includes=ArduinoBLE.h diff --git a/src/ArduinoBLE.h b/src/ArduinoBLE.h index dc6e8188..588d5cb1 100644 --- a/src/ArduinoBLE.h +++ b/src/ArduinoBLE.h @@ -24,5 +24,6 @@ #include "BLEProperty.h" #include "BLEStringCharacteristic.h" #include "BLETypedCharacteristics.h" +#include "utility/btct.h" #endif diff --git a/src/BLEAdvertisingData.cpp b/src/BLEAdvertisingData.cpp new file mode 100644 index 00000000..80105916 --- /dev/null +++ b/src/BLEAdvertisingData.cpp @@ -0,0 +1,352 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "BLEAdvertisingData.h" + +#define AD_FIELD_OVERHEAD (2) + +BLEAdvertisingData::BLEAdvertisingData() : + _dataLength(0), + _remainingLength(MAX_AD_DATA_LENGTH), + _rawData(NULL), + _rawDataLength(0), + _flags(0), + _hasFlags(false), + _localName(NULL), + _manufacturerData(NULL), + _manufacturerDataLength(0), + _manufacturerCompanyId(0), + _hasManufacturerCompanyId(false), + _advertisedServiceUuid(NULL), + _advertisedServiceUuidLength(0), + _serviceData(NULL), + _serviceDataLength(0) +{ +} + +BLEAdvertisingData::~BLEAdvertisingData() +{ +} + +inline bool BLEAdvertisingData::updateRemainingLength(int oldFieldLength, int newFieldLength) +{ + int updatedRemaining = _remainingLength + (oldFieldLength - newFieldLength); + if (updatedRemaining >= 0) { + _remainingLength = updatedRemaining; + return true; + } + return false; +} + +int BLEAdvertisingData::remainingLength() const +{ + return _remainingLength; +} + +int BLEAdvertisingData::availableForWrite() +{ + int available = (_remainingLength - AD_FIELD_OVERHEAD); + if (available < 0) available = 0; + return available; +} + +void BLEAdvertisingData::clear() +{ + _remainingLength = MAX_AD_DATA_LENGTH; + _rawData = NULL; + _rawDataLength = 0; + _hasFlags = false; + _localName = NULL; + _manufacturerData = NULL; + _manufacturerDataLength = 0; + _hasManufacturerCompanyId = false; + _advertisedServiceUuid = NULL; + _advertisedServiceUuidLength = 0; + _serviceData = NULL; + _serviceDataLength = 0; +} + +void BLEAdvertisingData::copy(const BLEAdvertisingData& adv) +{ + _remainingLength = adv._remainingLength; + _rawData = adv._rawData; + _rawDataLength = adv._rawDataLength; + _flags = adv._flags; + _hasFlags = adv._hasFlags; + _localName = adv._localName; + _manufacturerData = adv._manufacturerData; + _manufacturerDataLength = adv._manufacturerDataLength; + _manufacturerCompanyId = adv._manufacturerCompanyId; + _hasManufacturerCompanyId = adv._hasManufacturerCompanyId; + _advertisedServiceUuid = adv._advertisedServiceUuid; + _advertisedServiceUuidLength = adv._advertisedServiceUuidLength; + _serviceDataUuid = adv._serviceDataUuid; + _serviceData = adv._serviceData; + _serviceDataLength = adv._serviceDataLength; +} + +BLEAdvertisingData& BLEAdvertisingData::operator=(const BLEAdvertisingData &other) +{ + copy(other); + return *this; +} + +bool BLEAdvertisingData::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +{ + BLEUuid uuid(advertisedServiceUuid); + int previousLength = (_advertisedServiceUuidLength > 0) ? (_advertisedServiceUuidLength + AD_FIELD_OVERHEAD) : 0; + bool success = updateRemainingLength(previousLength, (uuid.length() + AD_FIELD_OVERHEAD)); + if (success) { + _advertisedServiceUuid = advertisedServiceUuid; + _advertisedServiceUuidLength = uuid.length(); + } + return success; +} + +bool BLEAdvertisingData::setAdvertisedService(const BLEService& service) +{ + return setAdvertisedServiceUuid(service.uuid()); +} + +bool BLEAdvertisingData::setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength) +{ + int previousLength = 0; + if (_manufacturerDataLength) { + previousLength = _manufacturerDataLength + AD_FIELD_OVERHEAD; + if (_hasManufacturerCompanyId) { + previousLength += sizeof(_manufacturerCompanyId); + } + } + bool success = updateRemainingLength(previousLength, (manufacturerDataLength + AD_FIELD_OVERHEAD)); + if (success) { + _manufacturerData = manufacturerData; + _manufacturerDataLength = manufacturerDataLength; + _hasManufacturerCompanyId = false; + } + return success; +} + +bool BLEAdvertisingData::setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength) +{ + int previousLength = 0; + if (_manufacturerDataLength) { + previousLength = _manufacturerDataLength + AD_FIELD_OVERHEAD; + if (_hasManufacturerCompanyId) { + previousLength += sizeof(_manufacturerCompanyId); + } + } + bool success = updateRemainingLength(previousLength, (manufacturerDataLength + sizeof(companyId) + AD_FIELD_OVERHEAD)); + if (success) { + _manufacturerData = manufacturerData; + _manufacturerDataLength = manufacturerDataLength; + _manufacturerCompanyId = companyId; + _hasManufacturerCompanyId = true; + } + return success; +} + +bool BLEAdvertisingData::setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length) +{ + int previousLength = (_serviceDataLength > 0) ? (_serviceDataLength + sizeof(uuid) + AD_FIELD_OVERHEAD) : 0; + bool success = updateRemainingLength(previousLength, (length + sizeof(uuid) + AD_FIELD_OVERHEAD)); + if (success) { + _serviceDataUuid = uuid; + _serviceData = data; + _serviceDataLength = length; + } + return success; +} + +bool BLEAdvertisingData::setLocalName(const char *localName) +{ + int previousLength = (_localName && strlen(_localName) > 0) ? (strlen(_localName) + AD_FIELD_OVERHEAD) : 0; + bool success = updateRemainingLength(previousLength, (strlen(localName) + AD_FIELD_OVERHEAD)); + if (success) { + _localName = localName; + } + return success; +} + +bool BLEAdvertisingData::setRawData(const uint8_t* data, int length) +{ + if (length > MAX_AD_DATA_LENGTH) { + return false; + } + _rawData = data; + _rawDataLength = length; + return true; +} + +bool BLEAdvertisingData::setRawData(const BLEAdvertisingRawData& rawData) +{ + if (rawData.length > MAX_AD_DATA_LENGTH) { + return false; + } + _rawData = rawData.data; + _rawDataLength = rawData.length; + return true; +} + +bool BLEAdvertisingData::setFlags(uint8_t flags) +{ + int previousLength = (_hasFlags) ? (sizeof(_flags) + AD_FIELD_OVERHEAD) : 0; + bool success = updateRemainingLength(previousLength, (sizeof(flags) + AD_FIELD_OVERHEAD)); + if (success) { + _hasFlags = true; + _flags = flags; + } + return success; +} + +bool BLEAdvertisingData::updateData() +{ + // Success indicates whether all the fields have been inserted + bool success = true; + // Reset data + _dataLength = 0; + // If rawData is present, then only rawData is inserted in the advertising packet + if (_rawData && _rawDataLength) { + return addRawData(_rawData, _rawDataLength); + } + // Try to add flags into the current advertising packet + if (_hasFlags) { + success &= addFlags(_flags); + } + // Try to add Advertised service uuid into the current advertising packet + if (_advertisedServiceUuid) { + success &= addAdvertisedServiceUuid(_advertisedServiceUuid); + } + // Try to add Manufacturer data into the current advertising packet + if (_manufacturerData && _manufacturerDataLength) { + if (_hasManufacturerCompanyId) { + success &= addManufacturerData(_manufacturerCompanyId, _manufacturerData, _manufacturerDataLength); + } else { + success &= addManufacturerData(_manufacturerData, _manufacturerDataLength); + } + } + // Try to add Service data into the current advertising packet + if (_serviceData && _serviceDataLength) { + success &= addAdvertisedServiceData(_serviceDataUuid, _serviceData, _serviceDataLength); + } + // Try to add Local name into the current advertising packet + if (_localName) { + success &= addLocalName(_localName); + } + return success; +} + +uint8_t* BLEAdvertisingData::data() +{ + return _data; +} + +int BLEAdvertisingData::dataLength() const +{ + return _dataLength; +} + +bool BLEAdvertisingData::hasFlags() const +{ + return _hasFlags; +} + +bool BLEAdvertisingData::addLocalName(const char *localName) +{ + bool success = false; + if (strlen(localName) > (MAX_AD_DATA_LENGTH - AD_FIELD_OVERHEAD)) { + success = addField(BLEFieldShortLocalName, (uint8_t*)localName, (MAX_AD_DATA_LENGTH - AD_FIELD_OVERHEAD)); + } else { + success = addField(BLEFieldCompleteLocalName, localName); + } + return success; +} + +bool BLEAdvertisingData::addAdvertisedServiceUuid(const char* advertisedServiceUuid) +{ + BLEUuid uuid(advertisedServiceUuid); + int uuidLen = uuid.length(); + BLEAdField advField; + if (uuidLen > 2) { + advField = BLEFieldIncompleteAdvertisedService128; + } else { + advField = BLEFieldIncompleteAdvertisedService16; + } + return addField(advField, uuid.data(), uuidLen); +} + +bool BLEAdvertisingData::addManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength) +{ + return addField(BLEFieldManufacturerData, manufacturerData, manufacturerDataLength); +} + +bool BLEAdvertisingData::addManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength) +{ + int tempDataLength = manufacturerDataLength + sizeof(companyId); + uint8_t tempData[MAX_AD_DATA_LENGTH]; + memcpy(tempData, &companyId, sizeof(companyId)); + memcpy(&tempData[sizeof(companyId)], manufacturerData, manufacturerDataLength); + return addField(BLEFieldManufacturerData, tempData, tempDataLength); +} + +bool BLEAdvertisingData::addAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length) +{ + int tempDataLength = length + sizeof(uuid); + uint8_t tempData[MAX_AD_DATA_LENGTH]; + memcpy(tempData, &uuid, sizeof(uuid)); + memcpy(&tempData[sizeof(uuid)], data, length); + return addField(BLEFieldServiceData, tempData, tempDataLength); +} + +bool BLEAdvertisingData::addRawData(const uint8_t* data, int length) +{ + // Bypass addField to add the integral raw data + if (length > (MAX_AD_DATA_LENGTH - _dataLength)) { + // Not enough space + return false; + } + memcpy(&_data[_dataLength], data, length); + _dataLength += length; + return true; +} + +bool BLEAdvertisingData::addFlags(uint8_t flags) +{ + return addField(BLEFieldFlags, &flags, sizeof(flags)); +} + +bool BLEAdvertisingData::addField(BLEAdField field, const char* data) +{ + int dataLength = strlen(data); + return addField(field, (uint8_t *)data, dataLength); +} + +bool BLEAdvertisingData::addField(BLEAdField field, const uint8_t* data, int length) +{ + int fieldLength = length + AD_FIELD_OVERHEAD; // Considering data TYPE and LENGTH fields + if (fieldLength > (MAX_AD_DATA_LENGTH - _dataLength)) { + // Not enough space for storing this field + return false; + } + // Insert field into advertising data of the instance + _data[_dataLength++] = length + 1; + _data[_dataLength++] = field; + memcpy(&_data[_dataLength], data, length); + _dataLength += length; + return true; +} diff --git a/src/BLEAdvertisingData.h b/src/BLEAdvertisingData.h new file mode 100644 index 00000000..dc736053 --- /dev/null +++ b/src/BLEAdvertisingData.h @@ -0,0 +1,120 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _BLE_ADVERTISING_DATA_H_ +#define _BLE_ADVERTISING_DATA_H_ + +#include +#include "utility/BLEUuid.h" +#include "BLEService.h" + +#define MAX_AD_DATA_LENGTH (31) + +enum BLEFlags { + BLEFlagsLimitedDiscoverable = 0x01, + BLEFlagsGeneralDiscoverable = 0x02, + BLEFlagsBREDRNotSupported = 0x04 +}; + +enum BLEAdField { + BLEFieldFlags = 0x01, + BLEFieldIncompleteAdvertisedService16 = 0x02, + BLEFieldCompleteAdvertisedService16 = 0x03, + BLEFieldIncompleteAdvertisedService128 = 0x06, + BLEFieldCompleteAdvertisedService128 = 0x07, + BLEFieldShortLocalName = 0x08, + BLEFieldCompleteLocalName = 0x09, + BLEFieldServiceData = 0x16, + BLEFieldManufacturerData = 0xFF, + + BLEAdFieldLast +}; + +struct BLEAdvertisingRawData { + uint8_t data[MAX_AD_DATA_LENGTH]; + int length; +}; + +class BLEAdvertisingData { +public: + BLEAdvertisingData(); + virtual ~BLEAdvertisingData(); + + int availableForWrite(); + void clear(); + void copy(const BLEAdvertisingData& adv); + BLEAdvertisingData& operator=(const BLEAdvertisingData &other); + + bool setAdvertisedService(const BLEService& service); + bool setAdvertisedServiceUuid(const char* advertisedServiceUuid); + bool setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength); + bool setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength); + bool setLocalName(const char *localName); + bool setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length); + bool setRawData(const uint8_t* data, int length); + bool setRawData(const BLEAdvertisingRawData& data); + bool setFlags(uint8_t flags); + +protected: + friend class BLELocalDevice; + bool updateData(); + uint8_t* data(); + int dataLength() const; + int remainingLength() const; + bool hasFlags() const; + +private: + bool updateRemainingLength(int oldFieldLength, int newFieldLength); + + bool addAdvertisedServiceUuid(const char* advertisedServiceUuid); + bool addManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength); + bool addManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength); + bool addLocalName(const char *localName); + bool addAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length); + bool addRawData(const uint8_t* data, int length); + bool addFlags(uint8_t flags); + + bool addField(BLEAdField field, const char* data); + bool addField(BLEAdField field, const uint8_t* data, int length); + + uint8_t _data[MAX_AD_DATA_LENGTH]; + int _dataLength; + + int _remainingLength; + + const uint8_t* _rawData; + int _rawDataLength; + + uint8_t _flags; + bool _hasFlags; + const char* _localName; + + const uint8_t* _manufacturerData; + int _manufacturerDataLength; + uint16_t _manufacturerCompanyId; + bool _hasManufacturerCompanyId; + + const char* _advertisedServiceUuid; + int _advertisedServiceUuidLength; + uint16_t _serviceDataUuid; + const uint8_t* _serviceData; + int _serviceDataLength; +}; + +#endif diff --git a/src/BLECharacteristic.cpp b/src/BLECharacteristic.cpp index 34f761fa..bfaee3ff 100644 --- a/src/BLECharacteristic.cpp +++ b/src/BLECharacteristic.cpp @@ -24,6 +24,8 @@ #include "BLECharacteristic.h" +extern "C" int strcasecmp(char const *a, char const *b); + BLECharacteristic::BLECharacteristic() : BLECharacteristic((BLELocalCharacteristic*)NULL) { @@ -47,13 +49,13 @@ BLECharacteristic::BLECharacteristic(BLERemoteCharacteristic* remote) : } } -BLECharacteristic::BLECharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength) : - BLECharacteristic(new BLELocalCharacteristic(uuid, properties, valueSize, fixedLength)) +BLECharacteristic::BLECharacteristic(const char* uuid, uint16_t permissions, int valueSize, bool fixedLength) : + BLECharacteristic(new BLELocalCharacteristic(uuid, permissions, valueSize, fixedLength)) { } -BLECharacteristic::BLECharacteristic(const char* uuid, uint8_t properties, const char* value) : - BLECharacteristic(new BLELocalCharacteristic(uuid, properties, value)) +BLECharacteristic::BLECharacteristic(const char* uuid, uint16_t permissions, const char* value) : + BLECharacteristic(new BLELocalCharacteristic(uuid, permissions, value)) { } @@ -72,11 +74,11 @@ BLECharacteristic::BLECharacteristic(const BLECharacteristic& other) BLECharacteristic::~BLECharacteristic() { - if (_local && _local->release() <= 0) { + if (_local && _local->release() == 0) { delete _local; } - if (_remote && _remote->release() <= 0) { + if (_remote && _remote->release() == 0) { delete _remote; } } @@ -234,65 +236,65 @@ int BLECharacteristic::readValue(int32_t& value) return readValue((uint8_t*)&value, sizeof(value)); } -int BLECharacteristic::writeValue(const uint8_t value[], int length) +int BLECharacteristic::writeValue(const uint8_t value[], int length, bool withResponse) { if (_local) { return _local->writeValue(value, length); } if (_remote) { - return _remote->writeValue(value, length); + return _remote->writeValue(value, length, withResponse); } return 0; } -int BLECharacteristic::writeValue(const void* value, int length) +int BLECharacteristic::writeValue(const void* value, int length, bool withResponse) { - return writeValue((const uint8_t*)value, length); + return writeValue((const uint8_t*)value, length, withResponse); } -int BLECharacteristic::writeValue(const char* value) +int BLECharacteristic::writeValue(const char* value, bool withResponse) { if (_local) { return _local->writeValue(value); } if (_remote) { - return _remote->writeValue(value); + return _remote->writeValue(value, withResponse); } return 0; } -int BLECharacteristic::writeValue(uint8_t value) +int BLECharacteristic::writeValue(uint8_t value, bool withResponse) { - return writeValue((uint8_t*)&value, sizeof(value)); + return writeValue((uint8_t*)&value, sizeof(value), withResponse); } -int BLECharacteristic::writeValue(int8_t value) +int BLECharacteristic::writeValue(int8_t value, bool withResponse) { - return writeValue((uint8_t*)&value, sizeof(value)); + return writeValue((uint8_t*)&value, sizeof(value), withResponse); } -int BLECharacteristic::writeValue(uint16_t value) +int BLECharacteristic::writeValue(uint16_t value, bool withResponse) { - return writeValue((uint8_t*)&value, sizeof(value)); + return writeValue((uint8_t*)&value, sizeof(value), withResponse); } -int BLECharacteristic::writeValue(int16_t value) +int BLECharacteristic::writeValue(int16_t value, bool withResponse) { - return writeValue((uint8_t*)&value, sizeof(value)); + return writeValue((uint8_t*)&value, sizeof(value), withResponse); } -int BLECharacteristic::writeValue(uint32_t value) +int BLECharacteristic::writeValue(uint32_t value, bool withResponse) { - return writeValue((uint8_t*)&value, sizeof(value)); + return writeValue((uint8_t*)&value, sizeof(value), withResponse); } -int BLECharacteristic::writeValue(int32_t value) +int BLECharacteristic::writeValue(int32_t value, bool withResponse) { - return writeValue((uint8_t*)&value, sizeof(value)); + return writeValue((uint8_t*)&value, sizeof(value), withResponse); } int BLECharacteristic::broadcast() diff --git a/src/BLECharacteristic.h b/src/BLECharacteristic.h index 03e1b61e..da9721e0 100644 --- a/src/BLECharacteristic.h +++ b/src/BLECharacteristic.h @@ -45,8 +45,8 @@ class BLERemoteCharacteristic; class BLECharacteristic { public: BLECharacteristic(); - BLECharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength = false); - BLECharacteristic(const char* uuid, uint8_t properties, const char* value); + BLECharacteristic(const char* uuid, uint16_t permissions, int valueSize, bool fixedLength = false); + BLECharacteristic(const char* uuid, uint16_t permissions, const char* value); BLECharacteristic(const BLECharacteristic& other); virtual ~BLECharacteristic(); @@ -68,15 +68,15 @@ class BLECharacteristic { int readValue(uint32_t& value); int readValue(int32_t& value); - int writeValue(const uint8_t value[], int length); - int writeValue(const void* value, int length); - int writeValue(const char* value); - int writeValue(uint8_t value); - int writeValue(int8_t value); - int writeValue(uint16_t value); - int writeValue(int16_t value); - int writeValue(uint32_t value); - int writeValue(int32_t value); + int writeValue(const uint8_t value[], int length, bool withResponse = true); + int writeValue(const void* value, int length, bool withResponse = true); + int writeValue(const char* value, bool withResponse = true); + int writeValue(uint8_t value, bool withResponse = true); + int writeValue(int8_t value, bool withResponse = true); + int writeValue(uint16_t value, bool withResponse = true); + int writeValue(int16_t value, bool withResponse = true); + int writeValue(uint32_t value, bool withResponse = true); + int writeValue(int32_t value, bool withResponse = true); // deprecated, use writeValue(...) int setValue(const uint8_t value[], int length) { return writeValue(value, length); } diff --git a/src/BLEDescriptor.cpp b/src/BLEDescriptor.cpp index 7a6736b0..366b89aa 100644 --- a/src/BLEDescriptor.cpp +++ b/src/BLEDescriptor.cpp @@ -72,11 +72,11 @@ BLEDescriptor::BLEDescriptor(const BLEDescriptor& other) BLEDescriptor::~BLEDescriptor() { - if (_local && _local->release() <= 0) { + if (_local && _local->release() == 0) { delete _local; } - if (_remote && _remote->release() <= 0) { + if (_remote && _remote->release() == 0) { delete _remote; } } diff --git a/src/BLEDevice.cpp b/src/BLEDevice.cpp index fa806104..e9ca9b99 100644 --- a/src/BLEDevice.cpp +++ b/src/BLEDevice.cpp @@ -25,6 +25,8 @@ #include "BLEDevice.h" +extern "C" int strcasecmp(char const *a, char const *b); + BLEDevice::BLEDevice() : _advertisementTypeMask(0), _eirDataLength(0), @@ -184,6 +186,70 @@ String BLEDevice::advertisedServiceUuid(int index) const return serviceUuid; } +bool BLEDevice::hasAdvertisementData() const +{ + return (_eirDataLength > 0); +} + +int BLEDevice::advertisementDataLength() const +{ + return _eirDataLength; +} + +int BLEDevice::advertisementData(uint8_t value[], int length) const +{ + if (length > _eirDataLength) length = _eirDataLength; + + if (length) { + memcpy(value, _eirData, length); + } + + return length; +} + +bool BLEDevice::hasManufacturerData() const +{ + return (manufacturerDataLength() > 0); +} + +int BLEDevice::manufacturerDataLength() const +{ + int length = 0; + + for (int i = 0; i < _eirDataLength;) { + int eirLength = _eirData[i++]; + int eirType = _eirData[i++]; + + if (eirType == 0xFF) { + length = (eirLength - 1); + break; + } + + i += (eirLength - 1); + } + + return length; +} + +int BLEDevice::manufacturerData(uint8_t value[], int length) const +{ + for (int i = 0; i < _eirDataLength;) { + int eirLength = _eirData[i++]; + int eirType = _eirData[i++]; + + if (eirType == 0xFF) { + if (length > (eirLength - 1)) length = (eirLength - 1); + + memcpy(value, &_eirData[i], length); + break; + } + + i += (eirLength - 1); + } + + return length; +} + int BLEDevice::rssi() { uint16_t handle = ATT.connectionHandle(_addressType, _address); diff --git a/src/BLEDevice.h b/src/BLEDevice.h index cbe79c71..bf710744 100644 --- a/src/BLEDevice.h +++ b/src/BLEDevice.h @@ -59,6 +59,14 @@ class BLEDevice { String advertisedServiceUuid() const; String advertisedServiceUuid(int index) const; + bool hasAdvertisementData() const; + int advertisementDataLength() const; + int advertisementData(uint8_t value[], int length) const; + + bool hasManufacturerData() const; + int manufacturerDataLength() const; + int manufacturerData(uint8_t value[], int length) const; + virtual int rssi(); bool connect(); diff --git a/src/BLEProperty.h b/src/BLEProperty.h index 6cdd888f..b9238ba9 100644 --- a/src/BLEProperty.h +++ b/src/BLEProperty.h @@ -17,6 +17,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +// #include + #ifndef _BLE_PROPERTY_H_ #define _BLE_PROPERTY_H_ @@ -26,7 +28,52 @@ enum BLEProperty { BLEWriteWithoutResponse = 0x04, BLEWrite = 0x08, BLENotify = 0x10, - BLEIndicate = 0x20 + BLEIndicate = 0x20, + BLEAuthSignedWrite = 1 << 6, + BLEExtProp = 1 << 7, +}; + +enum BLEPermission { + BLEEncryption = 1 << 9, + BLEAuthentication = 1 << 10, + BLEAuthorization = 1 << 11, + // BLEWriteEncryption = 1 << 11, + // BLEWriteAuthentication = 1 << 12, + // BLEWriteAuthorization = 1 << 13, +}; + +#define ESP_GATT_CHAR_PROP_BIT_BROADCAST (1 << 0) /* 0x01 */ /* relate to BTA_GATT_CHAR_PROP_BIT_BROADCAST in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_READ (1 << 1) /* 0x02 */ /* relate to BTA_GATT_CHAR_PROP_BIT_READ in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_WRITE_NR (1 << 2) /* 0x04 */ /* relate to BTA_GATT_CHAR_PROP_BIT_WRITE_NR in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_WRITE (1 << 3) /* 0x08 */ /* relate to BTA_GATT_CHAR_PROP_BIT_WRITE in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_NOTIFY (1 << 4) /* 0x10 */ /* relate to BTA_GATT_CHAR_PROP_BIT_NOTIFY in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_INDICATE (1 << 5) /* 0x20 */ /* relate to BTA_GATT_CHAR_PROP_BIT_INDICATE in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_AUTH (1 << 6) /* 0x40 */ /* relate to BTA_GATT_CHAR_PROP_BIT_AUTH in bta/bta_gatt_api.h */ +#define ESP_GATT_CHAR_PROP_BIT_EXT_PROP (1 << 7) /* 0x80 */ /* relate to BTA_GATT_CHAR_PROP_BIT_EXT_PROP in bta/bta_gatt_api.h */ + +#define ESP_GATT_PERM_READ (1 << 0) /* bit 0 - 0x0001 */ /* relate to BTA_GATT_PERM_READ in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 - 0x0002 */ /* relate to BTA_GATT_PERM_READ_ENCRYPTED in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_READ_ENC_MITM (1 << 2) /* bit 2 - 0x0004 */ /* relate to BTA_GATT_PERM_READ_ENC_MITM in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_WRITE (1 << 4) /* bit 4 - 0x0010 */ /* relate to BTA_GATT_PERM_WRITE in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 - 0x0020 */ /* relate to BTA_GATT_PERM_WRITE_ENCRYPTED in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_WRITE_ENC_MITM (1 << 6) /* bit 6 - 0x0040 */ /* relate to BTA_GATT_PERM_WRITE_ENC_MITM in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 - 0x0080 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_WRITE_SIGNED_MITM (1 << 8) /* bit 8 - 0x0100 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED_MITM in bta/bta_gatt_api.h */ +#define ESP_GATT_PERM_READ_AUTHORIZATION (1 << 9) /* bit 9 - 0x0200 */ +#define ESP_GATT_PERM_WRITE_AUTHORIZATION (1 << 10) /* bit 10 - 0x0400 */ + +enum BLE_GATT_PERM_ { + BLE_GATT_READ = 1 << 0, + READ_ENCRYPTED = 1 << 1, + READ_ENC_MITM = 1 << 2, + BLE_GATT_WRITE = 1 << 4, + WRITE_ENCRYPTED = 1 << 5, + WRITE_ENC_MITM = 1 << 6, + WRITE_SIGNED = 1 << 7, + WRITE_SIGNED_MITM = 1 << 8, + READ_AUTHORIZATION = 1 << 9, + WRITE_AUTHORIZATION = 1 << 10, }; + #endif diff --git a/src/BLEService.cpp b/src/BLEService.cpp index 7b5df148..88687b61 100644 --- a/src/BLEService.cpp +++ b/src/BLEService.cpp @@ -22,6 +22,8 @@ #include "BLEService.h" +extern "C" int strcasecmp(char const *a, char const *b); + BLEService::BLEService() : BLEService((BLELocalService*)NULL) { @@ -63,13 +65,20 @@ BLEService::BLEService(const BLEService& other) } } +void BLEService::clear() +{ + if (_local) { + _local->clear(); + } +} + BLEService::~BLEService() { - if (_local && _local->release() <= 0) { + if (_local && _local->release() == 0) { delete _local; } - if (_remote && _remote->release() <= 0) { + if (_remote && _remote->release() == 0) { delete _remote; } } diff --git a/src/BLEService.h b/src/BLEService.h index a8d8b010..5b2440d8 100644 --- a/src/BLEService.h +++ b/src/BLEService.h @@ -33,6 +33,7 @@ class BLEService { virtual ~BLEService(); const char* uuid() const; + void clear(); void addCharacteristic(BLECharacteristic& characteristic); diff --git a/src/BLEStringCharacteristic.cpp b/src/BLEStringCharacteristic.cpp index 76f0812c..a12e722d 100644 --- a/src/BLEStringCharacteristic.cpp +++ b/src/BLEStringCharacteristic.cpp @@ -19,7 +19,7 @@ #include "BLEStringCharacteristic.h" -BLEStringCharacteristic::BLEStringCharacteristic(const char* uuid, unsigned char properties, int valueSize) : +BLEStringCharacteristic::BLEStringCharacteristic(const char* uuid, unsigned int properties, int valueSize) : BLECharacteristic(uuid, properties, valueSize) { } diff --git a/src/BLEStringCharacteristic.h b/src/BLEStringCharacteristic.h index c9f28fca..65604846 100644 --- a/src/BLEStringCharacteristic.h +++ b/src/BLEStringCharacteristic.h @@ -27,7 +27,7 @@ class BLEStringCharacteristic : public BLECharacteristic { public: - BLEStringCharacteristic(const char* uuid, unsigned char properties, int valueSize); + BLEStringCharacteristic(const char* uuid, unsigned int properties, int valueSize); int writeValue(const String& value); int setValue(const String& value) { return writeValue(value); } diff --git a/src/BLETypedCharacteristic.h b/src/BLETypedCharacteristic.h index d6e6e4a1..7777d360 100644 --- a/src/BLETypedCharacteristic.h +++ b/src/BLETypedCharacteristic.h @@ -25,7 +25,7 @@ template class BLETypedCharacteristic : public BLECharacteristic { public: - BLETypedCharacteristic(const char* uuid, unsigned char properties); + BLETypedCharacteristic(const char* uuid, unsigned int permissions); int writeValue(T value); int setValue(T value) { return writeValue(value); } @@ -43,8 +43,8 @@ template class BLETypedCharacteristic : public BLECharacteristic T byteSwap(T value); }; -template BLETypedCharacteristic::BLETypedCharacteristic(const char* uuid, unsigned char properties) : - BLECharacteristic(uuid, properties, sizeof(T), true) +template BLETypedCharacteristic::BLETypedCharacteristic(const char* uuid, unsigned int permissions) : + BLECharacteristic(uuid, permissions, sizeof(T), true) { T value; memset(&value, 0x00, sizeof(value)); diff --git a/src/BLETypedCharacteristics.cpp b/src/BLETypedCharacteristics.cpp index 976f6159..800574eb 100644 --- a/src/BLETypedCharacteristics.cpp +++ b/src/BLETypedCharacteristics.cpp @@ -21,72 +21,72 @@ #include "BLETypedCharacteristics.h" -BLEBoolCharacteristic::BLEBoolCharacteristic(const char* uuid, unsigned char properties) : +BLEBoolCharacteristic::BLEBoolCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEBooleanCharacteristic::BLEBooleanCharacteristic(const char* uuid, unsigned char properties) : +BLEBooleanCharacteristic::BLEBooleanCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLECharCharacteristic::BLECharCharacteristic(const char* uuid, unsigned char properties) : +BLECharCharacteristic::BLECharCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEUnsignedCharCharacteristic::BLEUnsignedCharCharacteristic(const char* uuid, unsigned char properties) : +BLEUnsignedCharCharacteristic::BLEUnsignedCharCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEByteCharacteristic::BLEByteCharacteristic(const char* uuid, unsigned char properties) : +BLEByteCharacteristic::BLEByteCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEShortCharacteristic::BLEShortCharacteristic(const char* uuid, unsigned char properties) : +BLEShortCharacteristic::BLEShortCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEUnsignedShortCharacteristic::BLEUnsignedShortCharacteristic(const char* uuid, unsigned char properties) : +BLEUnsignedShortCharacteristic::BLEUnsignedShortCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEWordCharacteristic::BLEWordCharacteristic(const char* uuid, unsigned char properties) : +BLEWordCharacteristic::BLEWordCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEIntCharacteristic::BLEIntCharacteristic(const char* uuid, unsigned char properties) : +BLEIntCharacteristic::BLEIntCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEUnsignedIntCharacteristic::BLEUnsignedIntCharacteristic(const char* uuid, unsigned char properties) : +BLEUnsignedIntCharacteristic::BLEUnsignedIntCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLELongCharacteristic::BLELongCharacteristic(const char* uuid, unsigned char properties) : +BLELongCharacteristic::BLELongCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEUnsignedLongCharacteristic::BLEUnsignedLongCharacteristic(const char* uuid, unsigned char properties) : +BLEUnsignedLongCharacteristic::BLEUnsignedLongCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEFloatCharacteristic::BLEFloatCharacteristic(const char* uuid, unsigned char properties) : +BLEFloatCharacteristic::BLEFloatCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } -BLEDoubleCharacteristic::BLEDoubleCharacteristic(const char* uuid, unsigned char properties) : +BLEDoubleCharacteristic::BLEDoubleCharacteristic(const char* uuid, unsigned int properties) : BLETypedCharacteristic(uuid, properties) { } diff --git a/src/BLETypedCharacteristics.h b/src/BLETypedCharacteristics.h index 465fc046..8e9a08c5 100644 --- a/src/BLETypedCharacteristics.h +++ b/src/BLETypedCharacteristics.h @@ -24,72 +24,72 @@ class BLEBoolCharacteristic : public BLETypedCharacteristic { public: - BLEBoolCharacteristic(const char* uuid, unsigned char properties); + BLEBoolCharacteristic(const char* uuid, unsigned int permissions); }; -class BLEBooleanCharacteristic : public BLETypedCharacteristic { +class BLEBooleanCharacteristic : public BLETypedCharacteristic { public: - BLEBooleanCharacteristic(const char* uuid, unsigned char properties); + BLEBooleanCharacteristic(const char* uuid, unsigned int permissions); }; class BLECharCharacteristic : public BLETypedCharacteristic { public: - BLECharCharacteristic(const char* uuid, unsigned char properties); + BLECharCharacteristic(const char* uuid, unsigned int permissions); }; class BLEUnsignedCharCharacteristic : public BLETypedCharacteristic { public: - BLEUnsignedCharCharacteristic(const char* uuid, unsigned char properties); + BLEUnsignedCharCharacteristic(const char* uuid, unsigned int permissions); }; class BLEByteCharacteristic : public BLETypedCharacteristic { public: - BLEByteCharacteristic(const char* uuid, unsigned char properties); + BLEByteCharacteristic(const char* uuid, unsigned int permissions); }; class BLEShortCharacteristic : public BLETypedCharacteristic { public: - BLEShortCharacteristic(const char* uuid, unsigned char properties); + BLEShortCharacteristic(const char* uuid, unsigned int permissions); }; class BLEUnsignedShortCharacteristic : public BLETypedCharacteristic { public: - BLEUnsignedShortCharacteristic(const char* uuid, unsigned char properties); + BLEUnsignedShortCharacteristic(const char* uuid, unsigned int permissions); }; class BLEWordCharacteristic : public BLETypedCharacteristic { public: - BLEWordCharacteristic(const char* uuid, unsigned char properties); + BLEWordCharacteristic(const char* uuid, unsigned int permissions); }; class BLEIntCharacteristic : public BLETypedCharacteristic { public: - BLEIntCharacteristic(const char* uuid, unsigned char properties); + BLEIntCharacteristic(const char* uuid, unsigned int permissions); }; class BLEUnsignedIntCharacteristic : public BLETypedCharacteristic { public: - BLEUnsignedIntCharacteristic(const char* uuid, unsigned char properties); + BLEUnsignedIntCharacteristic(const char* uuid, unsigned int permissions); }; class BLELongCharacteristic : public BLETypedCharacteristic { public: - BLELongCharacteristic(const char* uuid, unsigned char properties); + BLELongCharacteristic(const char* uuid, unsigned int permissions); }; class BLEUnsignedLongCharacteristic : public BLETypedCharacteristic { public: - BLEUnsignedLongCharacteristic(const char* uuid, unsigned char properties); + BLEUnsignedLongCharacteristic(const char* uuid, unsigned int permissions); }; class BLEFloatCharacteristic : public BLETypedCharacteristic { public: - BLEFloatCharacteristic(const char* uuid, unsigned char properties); + BLEFloatCharacteristic(const char* uuid, unsigned int permissions); }; class BLEDoubleCharacteristic : public BLETypedCharacteristic { public: - BLEDoubleCharacteristic(const char* uuid, unsigned char properties); + BLEDoubleCharacteristic(const char* uuid, unsigned int permissions); }; #endif diff --git a/src/local/BLELocalAttribute.cpp b/src/local/BLELocalAttribute.cpp index ce3010a0..9929fa41 100644 --- a/src/local/BLELocalAttribute.cpp +++ b/src/local/BLELocalAttribute.cpp @@ -56,6 +56,11 @@ int BLELocalAttribute::retain() return _refCount; } +bool BLELocalAttribute::active() +{ + return _refCount > 0; +} + int BLELocalAttribute::release() { _refCount--; diff --git a/src/local/BLELocalAttribute.h b/src/local/BLELocalAttribute.h index f68f256f..53a0abfe 100644 --- a/src/local/BLELocalAttribute.h +++ b/src/local/BLELocalAttribute.h @@ -22,6 +22,8 @@ #include "utility/BLEUuid.h" +#define BLE_ATTRIBUTE_TYPE_SIZE 2 + enum BLEAttributeType { BLETypeUnknown = 0x0000, @@ -42,6 +44,7 @@ class BLELocalAttribute int retain(); int release(); + bool active(); protected: friend class ATTClass; diff --git a/src/local/BLELocalCharacteristic.cpp b/src/local/BLELocalCharacteristic.cpp index 23b5f275..dc8a819d 100644 --- a/src/local/BLELocalCharacteristic.cpp +++ b/src/local/BLELocalCharacteristic.cpp @@ -18,6 +18,7 @@ */ #include +#include "BLELocalDevice.h" #include "utility/ATT.h" #include "utility/GAP.h" @@ -28,9 +29,10 @@ #include "BLELocalCharacteristic.h" -BLELocalCharacteristic::BLELocalCharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength) : +BLELocalCharacteristic::BLELocalCharacteristic(const char* uuid, uint16_t permissions, int valueSize, bool fixedLength) : BLELocalAttribute(uuid), - _properties(properties), + _properties((uint8_t)(permissions&0x000FF)), + _permissions((uint8_t)((permissions&0xFF00)>>8)), _valueSize(min(valueSize, 512)), _valueLength(0), _fixedLength(fixedLength), @@ -41,27 +43,27 @@ BLELocalCharacteristic::BLELocalCharacteristic(const char* uuid, uint8_t propert { memset(_eventHandlers, 0x00, sizeof(_eventHandlers)); - if (properties & (BLENotify | BLEIndicate)) { + if (permissions & (BLENotify | BLEIndicate)) { BLELocalDescriptor* cccd = new BLELocalDescriptor("2902", (uint8_t*)&_cccdValue, sizeof(_cccdValue)); + cccd->retain(); _descriptors.add(cccd); } _value = (uint8_t*)malloc(valueSize); } -BLELocalCharacteristic::BLELocalCharacteristic(const char* uuid, uint8_t properties, const char* value) : - BLELocalCharacteristic(uuid, properties, strlen(value)) +BLELocalCharacteristic::BLELocalCharacteristic(const char* uuid, uint16_t permissions, const char* value) : + BLELocalCharacteristic(uuid, permissions, strlen(value)) { writeValue(value); } - BLELocalCharacteristic::~BLELocalCharacteristic() { for (unsigned int i = 0; i < descriptorCount(); i++) { BLELocalDescriptor* d = descriptor(i); - if (d->release() <= 0) { + if (d->release() == 0) { delete d; } } @@ -83,6 +85,10 @@ uint8_t BLELocalCharacteristic::properties() const return _properties; } +uint8_t BLELocalCharacteristic::permissions() const { + return _permissions; +} + int BLELocalCharacteristic::valueSize() const { return _valueSize; @@ -120,15 +126,13 @@ int BLELocalCharacteristic::writeValue(const uint8_t value[], int length) if (_broadcast) { uint16_t serviceUuid = GATT.serviceUuidForCharacteristic(this); - - GAP.setAdvertisedServiceData(serviceUuid, value, length); - + BLE.setAdvertisedServiceData(serviceUuid, value, _valueLength); if (!ATT.connected() && GAP.advertising()) { - GAP.advertise(); + BLE.advertise(); } } - return 1; + return _valueLength; } int BLELocalCharacteristic::writeValue(const char* value) diff --git a/src/local/BLELocalCharacteristic.h b/src/local/BLELocalCharacteristic.h index ee42390a..331cdd5c 100644 --- a/src/local/BLELocalCharacteristic.h +++ b/src/local/BLELocalCharacteristic.h @@ -33,13 +33,14 @@ class BLELocalDescriptor; class BLELocalCharacteristic : public BLELocalAttribute { public: - BLELocalCharacteristic(const char* uuid, uint8_t properties, int valueSize, bool fixedLength = false); - BLELocalCharacteristic(const char* uuid, uint8_t properties, const char* value); + BLELocalCharacteristic(const char* uuid, uint16_t permissions, int valueSize, bool fixedLength = false); + BLELocalCharacteristic(const char* uuid, uint16_t permissions, const char* value); virtual ~BLELocalCharacteristic(); virtual enum BLEAttributeType type() const; uint8_t properties() const; + uint8_t permissions() const; int valueSize() const; const uint8_t* value() const; @@ -75,6 +76,7 @@ class BLELocalCharacteristic : public BLELocalAttribute { private: uint8_t _properties; + uint8_t _permissions; int _valueSize; uint8_t* _value; uint16_t _valueLength; diff --git a/src/local/BLELocalDevice.cpp b/src/local/BLELocalDevice.cpp index 57477e23..5792353f 100644 --- a/src/local/BLELocalDevice.cpp +++ b/src/local/BLELocalDevice.cpp @@ -25,14 +25,23 @@ #include "BLELocalDevice.h" -#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) +#if defined(PORTENTA_H7_PINS) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_OPTA) #ifndef BT_REG_ON #define BT_REG_ON PJ_12 #endif +#elif defined(ARDUINO_NICLA_VISION) +#ifndef BT_REG_ON +#define BT_REG_ON PF_14 +#endif +#elif defined(ARDUINO_GIGA) +#ifndef BT_REG_ON +#define BT_REG_ON PA_10 +#endif #endif BLELocalDevice::BLELocalDevice() { + _advertisingData.setFlags(BLEFlagsGeneralDiscoverable | BLEFlagsBREDRNotSupported); } BLELocalDevice::~BLELocalDevice() @@ -41,7 +50,7 @@ BLELocalDevice::~BLELocalDevice() int BLELocalDevice::begin() { -#if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_SAMD_NANO_33_IOT) +#if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_SAMD_NANO_33_IOT) || defined(ARDUINO_NANO_RP2040_CONNECT) // reset the NINA in BLE mode pinMode(SPIWIFI_SS, OUTPUT); pinMode(NINA_RESETN, OUTPUT); @@ -54,16 +63,39 @@ int BLELocalDevice::begin() delay(100); digitalWrite(NINA_RESETN, LOW); delay(750); -#elif defined(ARDUINO_SAMD_NANO_33_IOT) +#elif defined(ARDUINO_SAMD_NANO_33_IOT) || defined(ARDUINO_NANO_RP2040_CONNECT) // inverted reset digitalWrite(NINA_RESETN, LOW); delay(100); digitalWrite(NINA_RESETN, HIGH); delay(750); -#elif defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) +#elif defined(PORTENTA_H7_PINS) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) // BT_REG_ON -> HIGH pinMode(BT_REG_ON, OUTPUT); + digitalWrite(BT_REG_ON, LOW); + delay(500); digitalWrite(BT_REG_ON, HIGH); + delay(500); +#elif defined(ARDUINO_PORTENTA_C33) +#define NINA_GPIO0 (100) +#define NINA_RESETN (101) + pinMode(NINA_GPIO0, OUTPUT); + pinMode(NINA_RESETN, OUTPUT); + Serial5.begin(921600); + + digitalWrite(NINA_GPIO0, HIGH); + delay(100); + digitalWrite(NINA_RESETN, HIGH); + digitalWrite(NINA_RESETN, LOW); + digitalWrite(NINA_RESETN, HIGH); + auto _start = millis(); + while (millis() - _start < 500) { + if (Serial5.available()) { + Serial5.read(); + } + } + //pinMode(94, OUTPUT); + //digitalWrite(94, LOW); #endif @@ -107,6 +139,10 @@ int BLELocalDevice::begin() end(); return 0; } + if (HCI.setLeEventMask(0x00000000000003FF) != 0) { + end(); + return 0; + } uint16_t pktLen; uint8_t maxPkt; @@ -116,6 +152,59 @@ int BLELocalDevice::begin() return 0; } + /// The HCI should allow automatic address resolution. + + // // If we have callbacks to remember bonded devices: + // if(HCI._getIRKs!=0){ + // uint8_t nIRKs = 0; + // uint8_t** BADDR_Type = new uint8_t*; + // uint8_t*** BADDRs = new uint8_t**; + // uint8_t*** IRKs = new uint8_t**; + // uint8_t* memcheck; + + + // if(!HCI._getIRKs(&nIRKs, BADDR_Type, BADDRs, IRKs)){ + // Serial.println("error"); + // } + // for(int i=0; i LOW +#elif defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) digitalWrite(BT_REG_ON, LOW); -#endif +#endif + _advertisingData.clear(); + _scanResponseData.clear(); } void BLELocalDevice::poll() @@ -156,6 +246,16 @@ bool BLELocalDevice::connected() const return ATT.connected(); } +/* + * Whether there is at least one paired device + */ +bool BLELocalDevice::paired() +{ + HCI.poll(); + + return ATT.paired(); +} + bool BLELocalDevice::disconnect() { return ATT.disconnect(); @@ -183,29 +283,57 @@ int BLELocalDevice::rssi() return 127; } -void BLELocalDevice::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +bool BLELocalDevice::setAdvertisedServiceUuid(const char* advertisedServiceUuid) +{ + return _advertisingData.setAdvertisedServiceUuid(advertisedServiceUuid); +} + +bool BLELocalDevice::setAdvertisedService(const BLEService& service) +{ + return setAdvertisedServiceUuid(service.uuid()); +} + +bool BLELocalDevice::setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length) { - GAP.setAdvertisedServiceUuid(advertisedServiceUuid); + return _advertisingData.setAdvertisedServiceData(uuid, data, length); } -void BLELocalDevice::setAdvertisedService(const BLEService& service) +bool BLELocalDevice::setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength) { - setAdvertisedServiceUuid(service.uuid()); + return _advertisingData.setManufacturerData(manufacturerData, manufacturerDataLength); } -void BLELocalDevice::setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength) +bool BLELocalDevice::setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength) { - GAP.setManufacturerData(manufacturerData, manufacturerDataLength); + return _advertisingData.setManufacturerData(companyId, manufacturerData, manufacturerDataLength); } -void BLELocalDevice::setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength) +bool BLELocalDevice::setLocalName(const char *localName) { - GAP.setManufacturerData(companyId, manufacturerData, manufacturerDataLength); + return _scanResponseData.setLocalName(localName); } -void BLELocalDevice::setLocalName(const char *localName) +void BLELocalDevice::setAdvertisingData(BLEAdvertisingData& advertisingData) { - GAP.setLocalName(localName); + _advertisingData = advertisingData; + if (!_advertisingData.hasFlags()) { + _advertisingData.setFlags(BLEFlagsGeneralDiscoverable | BLEFlagsBREDRNotSupported); + } +} + +void BLELocalDevice::setScanResponseData(BLEAdvertisingData& scanResponseData) +{ + _scanResponseData = scanResponseData; +} + +BLEAdvertisingData& BLELocalDevice::getAdvertisingData() +{ + return _advertisingData; +} + +BLEAdvertisingData& BLELocalDevice::getScanResponseData() +{ + return _scanResponseData; } void BLELocalDevice::setDeviceName(const char* deviceName) @@ -225,7 +353,10 @@ void BLELocalDevice::addService(BLEService& service) int BLELocalDevice::advertise() { - return GAP.advertise(); + _advertisingData.updateData(); + _scanResponseData.updateData(); + return GAP.advertise( _advertisingData.data(), _advertisingData.dataLength(), + _scanResponseData.data(), _scanResponseData.dataLength()); } void BLELocalDevice::stopAdvertise() @@ -291,6 +422,11 @@ void BLELocalDevice::setConnectionInterval(uint16_t minimumConnectionInterval, u L2CAPSignaling.setConnectionInterval(minimumConnectionInterval, maximumConnectionInterval); } +void BLELocalDevice::setSupervisionTimeout(uint16_t supervisionTimeout) +{ + L2CAPSignaling.setSupervisionTimeout(supervisionTimeout); +} + void BLELocalDevice::setConnectable(bool connectable) { GAP.setConnectable(connectable); @@ -301,6 +437,42 @@ void BLELocalDevice::setTimeout(unsigned long timeout) ATT.setTimeout(timeout); } +/* + * Control whether pairing is allowed or rejected + * Use true/false or the Pairable enum + */ +void BLELocalDevice::setPairable(uint8_t pairable) +{ + L2CAPSignaling.setPairingEnabled(pairable); +} + +/* + * Whether pairing is currently allowed + */ +bool BLELocalDevice::pairable() +{ + return L2CAPSignaling.isPairingEnabled(); +} + +void BLELocalDevice::setGetIRKs(int (*getIRKs)(uint8_t* nIRKs, uint8_t** BADDR_type, uint8_t*** BADDRs, uint8_t*** IRKs)){ + HCI._getIRKs = getIRKs; +} +void BLELocalDevice::setGetLTK(int (*getLTK)(uint8_t* BADDR, uint8_t* LTK)){ + HCI._getLTK = getLTK; +} +void BLELocalDevice::setStoreLTK(int (*storeLTK)(uint8_t*, uint8_t*)){ + HCI._storeLTK = storeLTK; +} +void BLELocalDevice::setStoreIRK(int (*storeIRK)(uint8_t*, uint8_t*)){ + HCI._storeIRK = storeIRK; +} +void BLELocalDevice::setDisplayCode(void (*displayCode)(uint32_t confirmationCode)){ + HCI._displayCode = displayCode; +} +void BLELocalDevice::setBinaryConfirmPairing(bool (*binaryConfirmPairing)()){ + HCI._binaryConfirmPairing = binaryConfirmPairing; +} + void BLELocalDevice::debug(Stream& stream) { HCI.debug(stream); @@ -311,4 +483,7 @@ void BLELocalDevice::noDebug() HCI.noDebug(); } -BLELocalDevice BLE; +#if !defined(FAKE_BLELOCALDEVICE) +BLELocalDevice BLEObj; +BLELocalDevice& BLE = BLEObj; +#endif diff --git a/src/local/BLELocalDevice.h b/src/local/BLELocalDevice.h index 20837c14..6c45c063 100644 --- a/src/local/BLELocalDevice.h +++ b/src/local/BLELocalDevice.h @@ -22,62 +22,102 @@ #include "BLEDevice.h" #include "BLEService.h" +#include "BLEAdvertisingData.h" + +enum Pairable { + NO = 0, + YES = 1, + ONCE = 2, +}; class BLELocalDevice { public: BLELocalDevice(); virtual ~BLELocalDevice(); - int begin(); - void end(); + virtual int begin(); + virtual void end(); + + virtual void poll(); + virtual void poll(unsigned long timeout); + + virtual bool connected() const; + virtual bool disconnect(); + + virtual String address() const; - void poll(); - void poll(unsigned long timeout); + virtual int rssi(); - bool connected() const; - bool disconnect(); + virtual bool setAdvertisedServiceUuid(const char* advertisedServiceUuid); + virtual bool setAdvertisedService(const BLEService& service); + virtual bool setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length); + virtual bool setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength); + virtual bool setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength); + virtual bool setLocalName(const char *localName); - String address() const; + virtual void setAdvertisingData(BLEAdvertisingData& advertisingData); + virtual void setScanResponseData(BLEAdvertisingData& scanResponseData); - int rssi(); + virtual void setDeviceName(const char* deviceName); + virtual void setAppearance(uint16_t appearance); - void setAdvertisedServiceUuid(const char* advertisedServiceUuid); - void setAdvertisedService(const BLEService& service); - void setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength); - void setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength); - void setLocalName(const char *localName); + virtual void addService(BLEService& service); - void setDeviceName(const char* deviceName); - void setAppearance(uint16_t appearance); + virtual int advertise(); + virtual void stopAdvertise(); - void addService(BLEService& service); + virtual int scan(bool withDuplicates = false); + virtual int scanForName(String name, bool withDuplicates = false); + virtual int scanForUuid(String uuid, bool withDuplicates = false); + virtual int scanForAddress(String address, bool withDuplicates = false); + virtual void stopScan(); - int advertise(); - void stopAdvertise(); + virtual BLEDevice central(); + virtual BLEDevice available(); - int scan(bool withDuplicates = false); - int scanForName(String name, bool withDuplicates = false); - int scanForUuid(String uuid, bool withDuplicates = false); - int scanForAddress(String address, bool withDuplicates = false); - void stopScan(); + virtual void setAdvertisingInterval(uint16_t advertisingInterval); + virtual void setConnectionInterval(uint16_t minimumConnectionInterval, uint16_t maximumConnectionInterval); + virtual void setSupervisionTimeout(uint16_t supervisionTimeout); + virtual void setConnectable(bool connectable); - BLEDevice central(); - BLEDevice available(); + virtual void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); - void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); + virtual void setTimeout(unsigned long timeout); - void setAdvertisingInterval(uint16_t advertisingInterval); - void setConnectionInterval(uint16_t minimumConnectionInterval, uint16_t maximumConnectionInterval); - void setConnectable(bool connectable); + virtual void debug(Stream& stream); + virtual void noDebug(); + + virtual void setPairable(uint8_t pairable); + virtual bool pairable(); + virtual bool paired(); - void setTimeout(unsigned long timeout); + // address - The mac to store + // IRK - The IRK to store with this mac + virtual void setStoreIRK(int (*storeIRK)(uint8_t* address, uint8_t* IRK)); + // nIRKs - the number of IRKs being provided. + // BDAddrType - an array containing the type of each address (0 public, 1 static random) + // BDAddrs - an array containing the list of addresses + virtual void setGetIRKs(int (*getIRKs)(uint8_t* nIRKs, uint8_t** BDAddrType, uint8_t*** BDAddrs, uint8_t*** IRKs)); + // address - the address to store [6 bytes] + // LTK - the LTK to store with this mac [16 bytes] + virtual void setStoreLTK(int (*storeLTK)(uint8_t* address, uint8_t* LTK)); + // address - The mac address needing its LTK + // LTK - 16 octet LTK for the mac address + virtual void setGetLTK(int (*getLTK)(uint8_t* address, uint8_t* LTK)); - void debug(Stream& stream); - void noDebug(); + virtual void setDisplayCode(void (*displayCode)(uint32_t confirmationCode)); + virtual void setBinaryConfirmPairing(bool (*binaryConfirmPairing)()); + uint8_t BDaddress[6]; + +protected: + virtual BLEAdvertisingData& getAdvertisingData(); + virtual BLEAdvertisingData& getScanResponseData(); private: + BLEAdvertisingData _advertisingData; + BLEAdvertisingData _scanResponseData; }; -extern BLELocalDevice BLE; +extern BLELocalDevice& BLE; #endif diff --git a/src/local/BLELocalService.cpp b/src/local/BLELocalService.cpp index 442c5422..7fda3cb4 100644 --- a/src/local/BLELocalService.cpp +++ b/src/local/BLELocalService.cpp @@ -28,17 +28,22 @@ BLELocalService::BLELocalService(const char* uuid) : { } +void BLELocalService::clear() { + _characteristics.clear(); + _startHandle = 0; + _endHandle = 0; +} + BLELocalService::~BLELocalService() { for (unsigned int i = 0; i < characteristicCount(); i++) { BLELocalCharacteristic* c = characteristic(i); - if (c->release() <= 0) { + if (c->release() == 0) { delete c; } } - - _characteristics.clear(); + clear(); } enum BLEAttributeType BLELocalService::type() const diff --git a/src/local/BLELocalService.h b/src/local/BLELocalService.h index e0179a93..f17c610c 100644 --- a/src/local/BLELocalService.h +++ b/src/local/BLELocalService.h @@ -36,6 +36,7 @@ class BLELocalService : public BLELocalAttribute { virtual enum BLEAttributeType type() const; void addCharacteristic(BLECharacteristic& characteristic); + void clear(); protected: friend class ATTClass; diff --git a/src/remote/BLERemoteCharacteristic.cpp b/src/remote/BLERemoteCharacteristic.cpp index ecad6229..454438b6 100644 --- a/src/remote/BLERemoteCharacteristic.cpp +++ b/src/remote/BLERemoteCharacteristic.cpp @@ -24,11 +24,12 @@ #include "BLERemoteCharacteristic.h" BLERemoteCharacteristic::BLERemoteCharacteristic(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, - uint16_t startHandle, uint8_t properties, uint16_t valueHandle) : + uint16_t startHandle, uint16_t permissions, uint16_t valueHandle) : BLERemoteAttribute(uuid, uuidLen), _connectionHandle(connectionHandle), _startHandle(startHandle), - _properties(properties), + _properties((uint8_t)(permissions & 0x00FF)), + _permissions((uint8_t)((permissions & 0xFF00)>>8)), _valueHandle(valueHandle), _value(NULL), _valueLength(0), @@ -43,7 +44,7 @@ BLERemoteCharacteristic::~BLERemoteCharacteristic() for (unsigned int i = 0; i < descriptorCount(); i++) { BLERemoteDescriptor* d = descriptor(i); - if (d->release() <= 0) { + if (d->release() == 0) { delete d; } } @@ -85,7 +86,7 @@ uint8_t BLERemoteCharacteristic::operator[] (int offset) const return 0; } -int BLERemoteCharacteristic::writeValue(const uint8_t value[], int length) +int BLERemoteCharacteristic::writeValue(const uint8_t value[], int length, bool withResponse) { if (!ATT.connected(_connectionHandle)) { return false; @@ -104,7 +105,7 @@ int BLERemoteCharacteristic::writeValue(const uint8_t value[], int length) return 0; } - if (_properties & BLEWrite) { + if ((_properties & BLEWrite) && withResponse) { uint8_t resp[4]; int respLength = ATT.writeReq(_connectionHandle, _valueHandle, value, length, resp); @@ -133,9 +134,9 @@ int BLERemoteCharacteristic::writeValue(const uint8_t value[], int length) return 0; } -int BLERemoteCharacteristic::writeValue(const char* value) +int BLERemoteCharacteristic::writeValue(const char* value, bool withResponse) { - return writeValue((uint8_t*)value, strlen(value)); + return writeValue((uint8_t*)value, strlen(value), withResponse); } bool BLERemoteCharacteristic::valueUpdated() diff --git a/src/remote/BLERemoteCharacteristic.h b/src/remote/BLERemoteCharacteristic.h index c4bc140d..b53ab031 100644 --- a/src/remote/BLERemoteCharacteristic.h +++ b/src/remote/BLERemoteCharacteristic.h @@ -29,17 +29,18 @@ class BLERemoteCharacteristic : public BLERemoteAttribute { public: - BLERemoteCharacteristic(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, uint16_t startHandle, uint8_t properties, uint16_t valueHandle); + BLERemoteCharacteristic(const uint8_t uuid[], uint8_t uuidLen, uint16_t connectionHandle, uint16_t startHandle, uint16_t permissions, uint16_t valueHandle); virtual ~BLERemoteCharacteristic(); uint8_t properties() const; + uint8_t permissions() const; const uint8_t* value() const; int valueLength() const; uint8_t operator[] (int offset) const; - int writeValue(const uint8_t value[], int length); - int writeValue(const char* value); + int writeValue(const uint8_t value[], int length, bool withResponse = true); + int writeValue(const char* value, bool withResponse = true); bool valueUpdated(); bool updatedValueRead(); @@ -66,6 +67,7 @@ class BLERemoteCharacteristic : public BLERemoteAttribute { uint16_t _connectionHandle; uint16_t _startHandle; uint8_t _properties; + uint8_t _permissions; uint16_t _valueHandle; uint8_t* _value; diff --git a/src/remote/BLERemoteDevice.cpp b/src/remote/BLERemoteDevice.cpp index 5a49f26f..1a4a67ab 100644 --- a/src/remote/BLERemoteDevice.cpp +++ b/src/remote/BLERemoteDevice.cpp @@ -50,7 +50,7 @@ void BLERemoteDevice::clearServices() for (unsigned int i = 0; i < serviceCount(); i++) { BLERemoteService* s = service(i); - if (s->release() <= 0) { + if (s->release() == 0) { delete s; } } diff --git a/src/remote/BLERemoteService.cpp b/src/remote/BLERemoteService.cpp index fd5c0ba6..f7461290 100644 --- a/src/remote/BLERemoteService.cpp +++ b/src/remote/BLERemoteService.cpp @@ -31,7 +31,7 @@ BLERemoteService::~BLERemoteService() for (unsigned int i = 0; i < characteristicCount(); i++) { BLERemoteCharacteristic* c = characteristic(i); - if (c->release() <= 0) { + if (c->release() == 0) { delete c; } } diff --git a/src/utility/ATT.cpp b/src/utility/ATT.cpp index 296caeee..ba717543 100644 --- a/src/utility/ATT.cpp +++ b/src/utility/ATT.cpp @@ -34,6 +34,8 @@ #include "ATT.h" +extern "C" int strcasecmp(char const *a, char const *b); + #define ATT_OP_ERROR 0x01 #define ATT_OP_MTU_REQ 0x02 #define ATT_OP_MTU_RESP 0x03 @@ -81,6 +83,8 @@ #define ATT_ECODE_UNSUPP_GRP_TYPE 0x10 #define ATT_ECODE_INSUFF_RESOURCES 0x11 +// #define _BLE_TRACE_ + ATTClass::ATTClass() : _maxMtu(23), _timeout(5000), @@ -95,6 +99,7 @@ ATTClass::ATTClass() : memset(_peers[i].address, 0x00, sizeof(_peers[i].address)); _peers[i].mtu = 23; _peers[i].device = NULL; + _peers[i].encryption = 0x0; } memset(_eventHandlers, 0x00, sizeof(_eventHandlers)); @@ -252,6 +257,18 @@ void ATTClass::addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrTy _peers[peerIndex].mtu = 23; _peers[peerIndex].addressType = peerBdaddrType; memcpy(_peers[peerIndex].address, peerBdaddr, sizeof(_peers[peerIndex].address)); + uint8_t BDADDr[6]; + for(int i=0; i<6; i++) BDADDr[5-i] = peerBdaddr[i]; + if(HCI.tryResolveAddress(BDADDr,_peers[peerIndex].resolvedAddress)){ +#ifdef _BLE_TRACE_ + Serial.println("Found match."); +#endif + }else{ +#ifdef _BLE_TRACE_ + Serial.println("No matching MAC"); +#endif + memset(&_peers[peerIndex].resolvedAddress, 0, 6); + } if (_eventHandlers[BLEConnected]) { _eventHandlers[BLEConnected](BLEDevice(peerBdaddrType, peerBdaddr)); @@ -267,12 +284,24 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ uint16_t mtu = this->mtu(connectionHandle); +#ifdef _BLE_TRACE_ + Serial.print("data opcode: 0x"); + Serial.println(opcode, HEX); +#endif switch (opcode) { case ATT_OP_ERROR: +#ifdef _BLE_TRACE_ + Serial.println("[Info] data error"); + // Serial.print("Error: "); + // btct.printBytes(data, dlen); +#endif error(connectionHandle, dlen, data); break; case ATT_OP_MTU_REQ: +#ifdef _BLE_TRACE_ + Serial.println("MTU"); +#endif mtuReq(connectionHandle, dlen, data); break; @@ -281,6 +310,9 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ break; case ATT_OP_FIND_INFO_REQ: +#ifdef _BLE_TRACE_ + Serial.println("Find info"); +#endif findInfoReq(connectionHandle, mtu, dlen, data); break; @@ -293,6 +325,9 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ break; case ATT_OP_READ_BY_TYPE_REQ: +#ifdef _BLE_TRACE_ + Serial.println("By type"); +#endif readByTypeReq(connectionHandle, mtu, dlen, data); break; @@ -319,6 +354,9 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ case ATT_OP_WRITE_REQ: case ATT_OP_WRITE_CMD: +#ifdef _BLE_TRACE_ + Serial.println("Write req"); +#endif writeReqOrCmd(connectionHandle, mtu, opcode, dlen, data); break; @@ -346,6 +384,9 @@ void ATTClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[ case ATT_OP_READ_MULTI_REQ: case ATT_OP_SIGNED_WRITE_CMD: default: +#ifdef _BLE_TRACE_ + Serial.println("[Info] Unhandled dara"); +#endif sendError(connectionHandle, opcode, 0x00, ATT_ECODE_REQ_NOT_SUPP); break; } @@ -398,6 +439,10 @@ void ATTClass::removeConnection(uint16_t handle, uint8_t /*reason*/) _peers[peerIndex].addressType = 0x00; memset(_peers[peerIndex].address, 0x00, sizeof(_peers[peerIndex].address)); _peers[peerIndex].mtu = 23; + _peers[peerIndex].encryption = PEER_ENCRYPTION::NO_ENCRYPTION; + _peers[peerIndex].IOCap[0] = 0; + _peers[peerIndex].IOCap[1] = 0; + _peers[peerIndex].IOCap[2] = 0; if (_peers[peerIndex].device) { delete _peers[peerIndex].device; @@ -456,6 +501,32 @@ bool ATTClass::connected(uint16_t handle) const return false; } +/* + * Return true if any of the known devices is paired (peer encrypted) + * Does not check if the paired device is also connected + */ +bool ATTClass::paired() const +{ + for(int i=0; i 0){ + return true; + } + } + return false; +} + +/* + * Return true if the specified device is paired (peer encrypted) + */ +bool ATTClass::paired(uint16_t handle) const +{ + for(int i=0; i 0; + } + return false; // unknown handle +} + uint16_t ATTClass::mtu(uint16_t handle) const { for (int i = 0; i < ATT_MAX_PEERS; i++) { @@ -482,10 +553,27 @@ bool ATTClass::disconnect() numDisconnects++; + BLEDevice bleDevice(_peers[i].addressType, _peers[i].address); + + // clear CCCD values on disconnect + for (uint16_t att = 0; att < GATT.attributeCount(); att++) { + BLELocalAttribute* attribute = GATT.attribute(att); + + if (attribute->type() == BLETypeCharacteristic) { + BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)attribute; + + characteristic->writeCccdValue(bleDevice, 0x0000); + } + } + + _longWriteHandle = 0x0000; + _longWriteValueLength = 0; + _peers[i].connectionHandle = 0xffff; _peers[i].role = 0x00; _peers[i].addressType = 0x00; memset(_peers[i].address, 0x00, sizeof(_peers[i].address)); + memset(_peers[i].resolvedAddress, 0x00, sizeof(_peers[i].resolvedAddress)); _peers[i].mtu = 23; if (_peers[i].device) { @@ -510,7 +598,7 @@ BLEDevice ATTClass::central() return BLEDevice(); } -bool ATTClass::handleNotify(uint16_t handle, const uint8_t* value, int length) +int ATTClass::handleNotify(uint16_t handle, const uint8_t* value, int length) { int numNotifications = 0; @@ -532,15 +620,16 @@ bool ATTClass::handleNotify(uint16_t handle, const uint8_t* value, int length) memcpy(¬ification[notificationLength], value, length); notificationLength += length; + /// TODO: Set encryption requirement on notify. HCI.sendAclPkt(_peers[i].connectionHandle, ATT_CID, notificationLength, notification); numNotifications++; } - return (numNotifications > 0); + return (numNotifications > 0) ? length : 0; } -bool ATTClass::handleInd(uint16_t handle, const uint8_t* value, int length) +int ATTClass::handleInd(uint16_t handle, const uint8_t* value, int length) { int numIndications = 0; @@ -577,7 +666,7 @@ bool ATTClass::handleInd(uint16_t handle, const uint8_t* value, int length) numIndications++; } - return (numIndications > 0); + return (numIndications > 0) ? length : 0; } void ATTClass::error(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) @@ -683,7 +772,8 @@ void ATTClass::findInfoReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen BLELocalAttribute* attribute = GATT.attribute(i); uint16_t handle = (i + 1); bool isValueHandle = (attribute->type() == BLETypeCharacteristic) && (((BLELocalCharacteristic*)attribute)->valueHandle() == handle); - int uuidLen = isValueHandle ? 2 : attribute->uuidLength(); + bool isDescriptor = attribute->type() == BLETypeDescriptor; + int uuidLen = (isValueHandle || isDescriptor) ? attribute->uuidLength() : BLE_ATTRIBUTE_TYPE_SIZE; int infoType = (uuidLen == 2) ? 0x01 : 0x02; if (response[1] == 0) { @@ -699,7 +789,7 @@ void ATTClass::findInfoReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen memcpy(&response[responseLength], &handle, sizeof(handle)); responseLength += sizeof(handle); - if (isValueHandle || attribute->type() == BLETypeDescriptor) { + if (isValueHandle || isDescriptor) { // add the UUID memcpy(&response[responseLength], attribute->uuidData(), uuidLen); responseLength += uuidLen; @@ -807,6 +897,14 @@ void ATTClass::readByGroupReq(uint16_t connectionHandle, uint16_t mtu, uint8_t d uint16_t endHandle; uint16_t uuid; } *readByGroupReq = (ReadByGroupReq*)data; +#ifdef _BLE_TRACE_ + Serial.print("readByGroupReq: start: 0x"); + Serial.println(readByGroupReq->startHandle,HEX); + Serial.print("readByGroupReq: end: 0x"); + Serial.println(readByGroupReq->endHandle,HEX); + Serial.print("readByGroupReq: UUID: 0x"); + Serial.println(readByGroupReq->uuid,HEX); +#endif if (dlen != sizeof(ReadByGroupReq) || (readByGroupReq->uuid != BLETypeService && readByGroupReq->uuid != 0x2801)) { sendError(connectionHandle, ATT_OP_READ_BY_GROUP_REQ, readByGroupReq->startHandle, ATT_ECODE_UNSUPP_GRP_TYPE); @@ -819,7 +917,10 @@ void ATTClass::readByGroupReq(uint16_t connectionHandle, uint16_t mtu, uint8_t d response[0] = ATT_OP_READ_BY_GROUP_RESP; response[1] = 0x00; responseLength = 2; - +#ifdef _BLE_TRACE_ + Serial.print("readByGroupReq: attrcount: "); + Serial.println(GATT.attributeCount()); +#endif for (uint16_t i = (readByGroupReq->startHandle - 1); i < GATT.attributeCount() && i <= (readByGroupReq->endHandle - 1); i++) { BLELocalAttribute* attribute = GATT.attribute(i); @@ -906,8 +1007,10 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_ return; } } - - uint16_t handle = *(uint16_t*)data; + /// if auth error, hold the response in a buffer. + bool holdResponse = false; + uint16_t handle; + memcpy(&handle, data, sizeof(handle)); uint16_t offset = (opcode == ATT_OP_READ_REQ) ? 0 : *(uint16_t*)&data[sizeof(handle)]; if ((uint16_t)(handle - 1) > GATT.attributeCount()) { @@ -962,6 +1065,12 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_ sendError(connectionHandle, opcode, handle, ATT_ECODE_READ_NOT_PERM); return; } + // If characteristic requires encryption send error & hold response until encrypted + if ((characteristic->permissions() & (BLEPermission::BLEEncryption >> 8)) > 0 && + (getPeerEncryption(connectionHandle) & PEER_ENCRYPTION::ENCRYPTED_AES)==0 ) { + holdResponse = true; + sendError(connectionHandle, opcode, handle, ATT_ECODE_INSUFF_ENC); + } uint16_t valueLength = characteristic->valueLength(); @@ -994,8 +1103,12 @@ void ATTClass::readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_ memcpy(&response[responseLength], descriptor->value() + offset, valueLength); responseLength += valueLength; } - - HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); + if(holdResponse){ + memcpy(holdBuffer, response, responseLength); + holdBufferSize = responseLength; + }else{ + HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); + } } void ATTClass::readResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) @@ -1142,7 +1255,7 @@ void ATTClass::readByTypeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t d void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op, uint8_t dlen, uint8_t data[]) { - boolean withResponse = (op == ATT_OP_WRITE_REQ); + bool withResponse = (op == ATT_OP_WRITE_REQ); if (dlen < sizeof(uint16_t)) { if (withResponse) { @@ -1151,7 +1264,8 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op return; } - uint16_t handle = *(uint16_t*)data; + uint16_t handle; + memcpy(&handle, data, sizeof(handle)); if ((uint16_t)(handle - 1) > GATT.attributeCount()) { if (withResponse) { @@ -1164,6 +1278,7 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op uint8_t* value = &data[sizeof(handle)]; BLELocalAttribute* attribute = GATT.attribute(handle - 1); + bool holdResponse = false; if (attribute->type() == BLETypeCharacteristic) { BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)attribute; @@ -1176,10 +1291,34 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op } return; } + // Check permission + if((characteristic->permissions() &( BLEPermission::BLEEncryption >> 8)) > 0 && + (getPeerEncryption(connectionHandle) & PEER_ENCRYPTION::ENCRYPTED_AES) == 0){ + holdResponse = true; + sendError(connectionHandle, ATT_OP_WRITE_REQ, handle, ATT_ECODE_INSUFF_ENC); + } for (int i = 0; i < ATT_MAX_PEERS; i++) { if (_peers[i].connectionHandle == connectionHandle) { - characteristic->writeValue(BLEDevice(_peers[i].addressType, _peers[i].address), value, valueLength); + if(holdResponse){ + + writeBufferSize = 0; + memcpy(writeBuffer, &handle, 2); + writeBufferSize+=2; + + writeBuffer[writeBufferSize++] = _peers[i].addressType; + + memcpy(&writeBuffer[writeBufferSize], _peers[i].address, sizeof(_peers[i].address)); + writeBufferSize += sizeof(_peers[i].address); + + writeBuffer[writeBufferSize] = valueLength; + writeBufferSize += sizeof(valueLength); + + memcpy(&writeBuffer[writeBufferSize], value, valueLength); + writeBufferSize += valueLength; + }else{ + characteristic->writeValue(BLEDevice(_peers[i].addressType, _peers[i].address), value, valueLength); + } break; } } @@ -1226,9 +1365,37 @@ void ATTClass::writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op response[0] = ATT_OP_WRITE_RESP; responseLength = 1; - HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); + if(holdResponse){ + memcpy(holdBuffer, response, responseLength); + holdBufferSize = responseLength; + }else{ + HCI.sendAclPkt(connectionHandle, ATT_CID, responseLength, response); + } } } +int ATTClass::processWriteBuffer(){ + if(writeBufferSize==0){ + return 0; + } + + struct __attribute__ ((packed)) WriteBuffer { + uint16_t handle; + uint8_t addressType; + uint8_t address[6]; + uint8_t valueLength; + uint8_t value[]; + } *writeBufferStruct = (WriteBuffer*)&ATT.writeBuffer; + // uint8_t value[writeBufferStruct->valueLength]; + // memcpy(value, writeBufferStruct->value, writeBufferStruct->valueLength); + BLELocalAttribute* attribute = GATT.attribute(writeBufferStruct->handle-1); + BLELocalCharacteristic* characteristic = (BLELocalCharacteristic*)attribute; +#ifdef _BLE_TRACE_ + Serial.println("Writing value"); +#endif + characteristic->writeValue(BLEDevice(writeBufferStruct->addressType, writeBufferStruct->address), writeBufferStruct->value, writeBufferStruct->valueLength); + writeBufferSize = 0; + return 1; +} void ATTClass::writeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) { @@ -1358,14 +1525,14 @@ void ATTClass::handleNotifyOrInd(uint16_t connectionHandle, uint8_t opcode, uint uint16_t handle; } *handleNotifyOrInd = (HandleNotifyOrInd*)data; - uint8_t handle = handleNotifyOrInd->handle; + uint16_t handle = handleNotifyOrInd->handle; - for (int i = 0; i < ATT_MAX_PEERS; i++) { - if (_peers[i].connectionHandle != connectionHandle) { + for (int peer = 0; peer < ATT_MAX_PEERS; peer++) { + if (_peers[peer].connectionHandle != connectionHandle) { continue; } - BLERemoteDevice* device = _peers[i].device; + BLERemoteDevice* device = _peers[peer].device; if (!device) { break; @@ -1383,7 +1550,7 @@ void ATTClass::handleNotifyOrInd(uint16_t connectionHandle, uint8_t opcode, uint BLERemoteCharacteristic* c = s->characteristic(j); if (c->valueHandle() == handle) { - c->writeValue(BLEDevice(_peers[i].addressType, _peers[i].address), &data[2], dlen - 2); + c->writeValue(BLEDevice(_peers[peer].addressType, _peers[peer].address), &data[2], dlen - 2); } } @@ -1687,4 +1854,110 @@ void ATTClass::writeCmd(uint16_t connectionHandle, uint16_t handle, const uint8_ sendReq(connectionHandle, &writeReq, 3 + dataLen, NULL); } -ATTClass ATT; +// Set encryption state for a peer +int ATTClass::setPeerEncryption(uint16_t connectionHandle, uint8_t encryption){ + for(int i=0; i 0){ + return _peers[i].connectionHandle; + } + } + return ATT_MAX_PEERS + 1; +} +// Get the encryption state for a particular peer / connection handle +uint8_t ATTClass::getPeerEncryption(uint16_t connectionHandle) { + for(int i=0; i #include "BLEDevice.h" +#include "keyDistribution.h" #define ATT_CID 0x0004 +#define BLE_CTL 0x0008 -#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) -#define ATT_MAX_PEERS 7 -#elif DM_CONN_MAX +#if DM_CONN_MAX #define ATT_MAX_PEERS DM_CONN_MAX // Mbed + Cordio -#else +#elif __AVR__ #define ATT_MAX_PEERS 3 +#else +#define ATT_MAX_PEERS 8 #endif +enum PEER_ENCRYPTION { + NO_ENCRYPTION = 0, + PAIRING_REQUEST = 1 << 0, + REQUESTED_ENCRYPTION = 1 << 1, + SENT_PUBKEY = 1 << 2, + DH_KEY_CALULATED = 1 << 3, + RECEIVED_DH_CHECK = 1 << 4, + SENT_DH_CHECK = 1 << 5, + ENCRYPTED_AES = 1 << 7 +}; + class BLERemoteDevice; class ATTClass { @@ -41,73 +54,92 @@ class ATTClass { ATTClass(); virtual ~ATTClass(); - void setMaxMtu(uint16_t maxMtu); - void setTimeout(unsigned long timeout); + virtual void setMaxMtu(uint16_t maxMtu); + virtual void setTimeout(unsigned long timeout); - bool connect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]); - bool disconnect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]); - bool discoverAttributes(uint8_t peerBdaddrType, uint8_t peerBdaddr[6], const char* serviceUuidFilter); + virtual bool connect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]); + virtual bool disconnect(uint8_t peerBdaddrType, uint8_t peerBdaddr[6]); + virtual bool discoverAttributes(uint8_t peerBdaddrType, uint8_t peerBdaddr[6], const char* serviceUuidFilter); - void addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrType, + virtual void addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint16_t interval, uint16_t latency, uint16_t supervisionTimeout, uint8_t masterClockAccuracy); - void handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - - void removeConnection(uint16_t handle, uint8_t reason); - - uint16_t connectionHandle(uint8_t addressType, const uint8_t address[6]) const; - BLERemoteDevice* device(uint8_t addressType, const uint8_t address[6]) const; - bool connected() const; - bool connected(uint8_t addressType, const uint8_t address[6]) const; - bool connected(uint16_t handle) const; - uint16_t mtu(uint16_t handle) const; - - bool disconnect(); - - BLEDevice central(); - - bool handleNotify(uint16_t handle, const uint8_t* value, int length); - bool handleInd(uint16_t handle, const uint8_t* value, int length); - - void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); - - int readReq(uint16_t connectionHandle, uint16_t handle, uint8_t responseBuffer[]); - int writeReq(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen, uint8_t responseBuffer[]); - void writeCmd(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen); - + virtual void handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + + virtual void removeConnection(uint16_t handle, uint8_t reason); + + virtual uint16_t connectionHandle(uint8_t addressType, const uint8_t address[6]) const; + virtual BLERemoteDevice* device(uint8_t addressType, const uint8_t address[6]) const; + virtual bool connected() const; + virtual bool connected(uint8_t addressType, const uint8_t address[6]) const; + virtual bool connected(uint16_t handle) const; + virtual bool paired() const; + virtual bool paired(uint16_t handle) const; + virtual uint16_t mtu(uint16_t handle) const; + + virtual bool disconnect(); + + virtual BLEDevice central(); + + virtual int handleNotify(uint16_t handle, const uint8_t* value, int length); + virtual int handleInd(uint16_t handle, const uint8_t* value, int length); + + virtual void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); + + virtual int readReq(uint16_t connectionHandle, uint16_t handle, uint8_t responseBuffer[]); + virtual int writeReq(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen, uint8_t responseBuffer[]); + virtual void writeCmd(uint16_t connectionHandle, uint16_t handle, const uint8_t* data, uint8_t dataLen); + virtual int setPeerEncryption(uint16_t connectionHandle, uint8_t encryption); + uint8_t getPeerEncryption(uint16_t connectionHandle); + uint16_t getPeerEncrptingConnectionHandle(); + virtual int getPeerAddr(uint16_t connectionHandle, uint8_t peerAddr[]); + virtual int getPeerAddrWithType(uint16_t connectionHandle, uint8_t peerAddr[]); + virtual int setPeerIOCap(uint16_t connectionHandle, uint8_t IOCap[]); + virtual int getPeerIOCap(uint16_t connectionHandle, uint8_t IOCap[]); + virtual int getPeerResolvedAddress(uint16_t connectionHandle, uint8_t* resolvedAddress); + uint8_t holdBuffer[64]; + uint8_t writeBuffer[64]; + uint8_t holdBufferSize; + uint8_t writeBufferSize; + virtual int processWriteBuffer(); + KeyDistribution remoteKeyDistribution; + KeyDistribution localKeyDistribution; + uint8_t peerIRK[16]; + /// This is just a random number... Not sure it has use unless privacy mode is active. + uint8_t localIRK[16] = {0x54,0x83,0x63,0x7c,0xc5,0x1e,0xf7,0xec,0x32,0xdd,0xad,0x51,0x89,0x4b,0x9e,0x07}; private: - void error(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void mtuReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - int mtuReq(uint16_t connectionHandle, uint16_t mtu, uint8_t responseBuffer[]); - void mtuResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void findInfoReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); - int findInfoReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint8_t responseBuffer[]); - void findInfoResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void findByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); - void readByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); - int readByTypeReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t type, uint8_t responseBuffer[]); - void readByTypeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_t opcode, uint8_t dlen, uint8_t data[]); - void readResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void readByGroupReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); - int readByGroupReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t uuid, uint8_t responseBuffer[]); - void readByGroupResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op, uint8_t dlen, uint8_t data[]); - void writeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void prepWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); - void execWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); - void handleNotifyOrInd(uint16_t connectionHandle, uint8_t opcode, uint8_t dlen, uint8_t data[]); - void handleCnf(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void sendError(uint16_t connectionHandle, uint8_t opcode, uint16_t handle, uint8_t code); - - bool exchangeMtu(uint16_t connectionHandle); - bool discoverServices(uint16_t connectionHandle, BLERemoteDevice* device, const char* serviceUuidFilter); - bool discoverCharacteristics(uint16_t connectionHandle, BLERemoteDevice* device); - bool discoverDescriptors(uint16_t connectionHandle, BLERemoteDevice* device); - - int sendReq(uint16_t connectionHandle, void* requestBuffer, int requestLength, uint8_t responseBuffer[]); + virtual void error(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void mtuReq(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual int mtuReq(uint16_t connectionHandle, uint16_t mtu, uint8_t responseBuffer[]); + virtual void mtuResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void findInfoReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + virtual int findInfoReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint8_t responseBuffer[]); + virtual void findInfoResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void findByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + virtual void readByTypeReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + virtual int readByTypeReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t type, uint8_t responseBuffer[]); + virtual void readByTypeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void readOrReadBlobReq(uint16_t connectionHandle, uint16_t mtu, uint8_t opcode, uint8_t dlen, uint8_t data[]); + virtual void readResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void readByGroupReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + virtual int readByGroupReq(uint16_t connectionHandle, uint16_t startHandle, uint16_t endHandle, uint16_t uuid, uint8_t responseBuffer[]); + virtual void readByGroupResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void writeReqOrCmd(uint16_t connectionHandle, uint16_t mtu, uint8_t op, uint8_t dlen, uint8_t data[]); + virtual void writeResp(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void prepWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + virtual void execWriteReq(uint16_t connectionHandle, uint16_t mtu, uint8_t dlen, uint8_t data[]); + virtual void handleNotifyOrInd(uint16_t connectionHandle, uint8_t opcode, uint8_t dlen, uint8_t data[]); + virtual void handleCnf(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void sendError(uint16_t connectionHandle, uint8_t opcode, uint16_t handle, uint8_t code); + + virtual bool exchangeMtu(uint16_t connectionHandle); + virtual bool discoverServices(uint16_t connectionHandle, BLERemoteDevice* device, const char* serviceUuidFilter); + virtual bool discoverCharacteristics(uint16_t connectionHandle, BLERemoteDevice* device); + virtual bool discoverDescriptors(uint16_t connectionHandle, BLERemoteDevice* device); + + virtual int sendReq(uint16_t connectionHandle, void* requestBuffer, int requestLength, uint8_t responseBuffer[]); private: uint16_t _maxMtu; @@ -117,8 +149,11 @@ class ATTClass { uint8_t role; uint8_t addressType; uint8_t address[6]; + uint8_t resolvedAddress[6]; uint16_t mtu; BLERemoteDevice* device; + uint8_t encryption; + uint8_t IOCap[3]; } _peers[ATT_MAX_PEERS]; volatile bool _cnf; @@ -137,6 +172,6 @@ class ATTClass { BLEDeviceEventHandler _eventHandlers[2]; }; -extern ATTClass ATT; +extern ATTClass& ATT; #endif diff --git a/src/utility/BLEUuid.cpp b/src/utility/BLEUuid.cpp index fba6244a..0465ea9a 100644 --- a/src/utility/BLEUuid.cpp +++ b/src/utility/BLEUuid.cpp @@ -30,6 +30,11 @@ BLEUuid::BLEUuid(const char * str) : memset(_data, 0x00, sizeof(_data)); _length = 0; + + if (str == NULL) { + return; + } + for (int i = strlen(str) - 1; i >= 0 && _length < BLE_UUID_MAX_LENGTH; i -= 2) { if (str[i] == '-') { i++; diff --git a/src/utility/CordioHCICustomDriver.h b/src/utility/CordioHCICustomDriver.h new file mode 100644 index 00000000..fc062ee8 --- /dev/null +++ b/src/utility/CordioHCICustomDriver.h @@ -0,0 +1,17 @@ +#if defined(CORE_CM4) + +#include "CyH4TransportDriver.h" + +ble::vendor::cypress_ble::CyH4TransportDriver& ble_cordio_get_h4_transport_driver() +{ + static ble::vendor::cypress_ble::CyH4TransportDriver s_transport_driver( + /* TX */ CYBSP_BT_UART_TX, /* RX */ CYBSP_BT_UART_RX, + /* cts */ CYBSP_BT_UART_CTS, /* rts */ CYBSP_BT_UART_RTS, NC, DEF_BT_BAUD_RATE, + CYBSP_BT_HOST_WAKE, CYBSP_BT_DEVICE_WAKE + ); + return s_transport_driver; +} + +#define CUSTOM_HCI_DRIVER + +#endif \ No newline at end of file diff --git a/src/utility/GAP.cpp b/src/utility/GAP.cpp index 83059899..f1bf02fe 100644 --- a/src/utility/GAP.cpp +++ b/src/utility/GAP.cpp @@ -22,19 +22,17 @@ #include "GAP.h" -#define GAP_MAX_DISCOVERED_QUEUE_SIZE 5 +#define GAP_MAX_DISCOVERED_QUEUE_SIZE 32 + +#define GAP_ADV_IND (0x00) +#define GAP_ADV_SCAN_IND (0x02) +#define GAP_ADV_NONCONN_IND (0x03) GAPClass::GAPClass() : _advertising(false), _scanning(false), - _advertisedServiceUuid(NULL), - _manufacturerData(NULL), - _manufacturerDataLength(0), - _localName(NULL), _advertisingInterval(160), _connectable(true), - _serviceData(NULL), - _serviceDataLength(0), _discoverEventHandler(NULL) { } @@ -43,41 +41,16 @@ GAPClass::~GAPClass() { } -void GAPClass::setAdvertisedServiceUuid(const char* advertisedServiceUuid) -{ - _advertisedServiceUuid = advertisedServiceUuid; -} - -void GAPClass::setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength) -{ - _manufacturerData = manufacturerData; - _manufacturerDataLength = manufacturerDataLength; -} - -void GAPClass::setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength) -{ - uint8_t* tmpManufacturerData = (uint8_t*)malloc(manufacturerDataLength + 2); - tmpManufacturerData[0] = companyId & 0xff; - tmpManufacturerData[1] = companyId >> 8; - memcpy(&tmpManufacturerData[2], manufacturerData, manufacturerDataLength); - this->setManufacturerData(tmpManufacturerData, manufacturerDataLength + 2); -} - -void GAPClass::setLocalName(const char *localName) -{ - _localName = localName; -} - bool GAPClass::advertising() { return _advertising; } -int GAPClass::advertise() +int GAPClass::advertise(uint8_t* advData, uint8_t advDataLen, uint8_t* scanData, uint8_t scanDataLen) { uint8_t directBdaddr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - uint8_t type = (_connectable) ? 0x00 : (_localName ? 0x02 : 0x03); + uint8_t type = (_connectable) ? GAP_ADV_IND : (scanDataLen ? GAP_ADV_SCAN_IND : GAP_ADV_NONCONN_IND); stopAdvertise(); @@ -85,67 +58,11 @@ int GAPClass::advertise() return 0; } - uint8_t advertisingData[31]; - uint8_t advertisingDataLen = 0; - - advertisingData[0] = 0x02; - advertisingData[1] = 0x01; - advertisingData[2] = 0x06; - advertisingDataLen += 3; - - if (_advertisedServiceUuid) { - BLEUuid uuid(_advertisedServiceUuid); - int uuidLen = uuid.length(); - - advertisingData[advertisingDataLen++] = 1 + uuidLen; - advertisingData[advertisingDataLen++] = (uuidLen > 2) ? 0x06 : 0x02; - memcpy(&advertisingData[advertisingDataLen], uuid.data(), uuidLen); - - advertisingDataLen += uuidLen; - } else if (_manufacturerData && _manufacturerDataLength) { - advertisingData[advertisingDataLen++] = 1 + _manufacturerDataLength; - advertisingData[advertisingDataLen++] = 0xff; - memcpy(&advertisingData[advertisingDataLen], _manufacturerData, _manufacturerDataLength); - - advertisingDataLen += _manufacturerDataLength; - } - - if (_serviceData && _serviceDataLength > 0 && (sizeof(advertisingData) - advertisingDataLen) >= (_serviceDataLength + 4)) { - advertisingData[advertisingDataLen++] = _serviceDataLength + 3; - advertisingData[advertisingDataLen++] = 0x16; - - memcpy(&advertisingData[advertisingDataLen], &_serviceDataUuid, sizeof(_serviceDataUuid)); - advertisingDataLen += sizeof(_serviceDataUuid); - - memcpy(&advertisingData[advertisingDataLen],_serviceData, _serviceDataLength); - advertisingDataLen += _serviceDataLength; - } - - if (HCI.leSetAdvertisingData(advertisingDataLen, advertisingData) != 0) { + if (HCI.leSetAdvertisingData(advDataLen, advData) != 0) { return 0; } - uint8_t scanResponseData[31]; - uint8_t scanResponseDataLen = 0; - - if (_localName) { - int localNameLen = strlen(_localName); - - if (localNameLen > 29) { - localNameLen = 29; - scanResponseData[1] = 0x08; - } else { - scanResponseData[1] = 0x09; - } - - scanResponseData[0] = 1 + localNameLen; - - memcpy(&scanResponseData[2], _localName, localNameLen); - - scanResponseDataLen += (2 + localNameLen); - } - - if (HCI.leSetScanResponseData(scanResponseDataLen, scanResponseData) != 0) { + if (HCI.leSetScanResponseData(scanDataLen, scanData) != 0) { return 0; } @@ -169,8 +86,14 @@ int GAPClass::scan(bool withDuplicates) { HCI.leSetScanEnable(false, true); - // active scan, 10 ms scan interval (N * 0.625), 10 ms scan window (N * 0.625), public own address type, no filter - if (HCI.leSetScanParameters(0x01, 0x0010, 0x0010, 0x00, 0x00) != 0) { + // active scan, 20 ms scan interval (N * 0.625), 20 ms scan window (N * 0.625), public own address type, no filter + /* + Warning (from BLUETOOTH SPECIFICATION 5.x): + - scan interval: mandatory range from 0x0012 to 0x1000; only even values are valid + - scan window: mandatory range from 0x0011 to 0x1000 + - The scan window can only be less than or equal to the scan interval + */ + if (HCI.leSetScanParameters(0x01, 0x0020, 0x0020, 0x00, 0x00) != 0) { return false; } @@ -264,13 +187,6 @@ void GAPClass::setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler event } } -void GAPClass::setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length) -{ - _serviceDataUuid = uuid; - _serviceData = data; - _serviceDataLength = length; -} - void GAPClass::handleLeAdvertisingReport(uint8_t type, uint8_t addressType, uint8_t address[6], uint8_t eirLength, uint8_t eirData[], int8_t rssi) { @@ -306,8 +222,10 @@ void GAPClass::handleLeAdvertisingReport(uint8_t type, uint8_t addressType, uint if (discoveredDevice == NULL) { if (_discoveredDevices.size() >= GAP_MAX_DISCOVERED_QUEUE_SIZE) { - // drop - return; + BLEDevice* device_first = _discoveredDevices.remove(0); + if (device_first != NULL) { + delete device_first; + } } discoveredDevice = new BLEDevice(addressType, address); @@ -348,4 +266,7 @@ bool GAPClass::matchesScanFilter(const BLEDevice& device) return true; } -GAPClass GAP; +#if !defined(FAKE_GAP) +GAPClass GAPObj; +GAPClass& GAP = GAPObj; +#endif diff --git a/src/utility/GAP.h b/src/utility/GAP.h index c2a99daa..2ea22938 100644 --- a/src/utility/GAP.h +++ b/src/utility/GAP.h @@ -29,56 +29,38 @@ class GAPClass { GAPClass(); virtual ~GAPClass(); - void setAdvertisedServiceUuid(const char* advertisedServiceUuid); - void setManufacturerData(const uint8_t manufacturerData[], int manufacturerDataLength); - void setManufacturerData(const uint16_t companyId, const uint8_t manufacturerData[], int manufacturerDataLength); - void setLocalName(const char *localName); + virtual bool advertising(); + virtual int advertise(uint8_t* advData, uint8_t advDataLength, uint8_t* scanData, uint8_t scanDataLength); + virtual void stopAdvertise(); - bool advertising(); - int advertise(); - void stopAdvertise(); + virtual int scan(bool withDuplicates); + virtual int scanForName(String name, bool withDuplicates); + virtual int scanForUuid(String uuid, bool withDuplicates); + virtual int scanForAddress(String address, bool withDuplicates); + virtual void stopScan(); + virtual BLEDevice available(); - int scan(bool withDuplicates); - int scanForName(String name, bool withDuplicates); - int scanForUuid(String uuid, bool withDuplicates); - int scanForAddress(String address, bool withDuplicates); - void stopScan(); - BLEDevice available(); + virtual void setAdvertisingInterval(uint16_t advertisingInterval); + virtual void setConnectable(bool connectable); - void setAdvertisingInterval(uint16_t advertisingInterval); - void setConnectable(bool connectable); - - void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); - -protected: - friend class BLELocalCharacteristic; - - void setAdvertisedServiceData(uint16_t uuid, const uint8_t data[], int length); + virtual void setEventHandler(BLEDeviceEvent event, BLEDeviceEventHandler eventHandler); protected: friend class HCIClass; - void handleLeAdvertisingReport(uint8_t type, uint8_t addressType, uint8_t address[6], + virtual void handleLeAdvertisingReport(uint8_t type, uint8_t addressType, uint8_t address[6], uint8_t eirLength, uint8_t eirData[], int8_t rssi); private: - bool matchesScanFilter(const BLEDevice& device); + virtual bool matchesScanFilter(const BLEDevice& device); private: bool _advertising; bool _scanning; - const char* _advertisedServiceUuid; - const uint8_t* _manufacturerData; - int _manufacturerDataLength; - const char* _localName; uint16_t _advertisingInterval; bool _connectable; - uint16_t _serviceDataUuid; - const uint8_t* _serviceData; - int _serviceDataLength; - BLEDeviceEventHandler _discoverEventHandler; BLELinkedList _discoveredDevices; @@ -87,6 +69,6 @@ class GAPClass { String _scanAddressFilter; }; -extern GAPClass GAP; +extern GAPClass& GAP; #endif diff --git a/src/utility/GATT.cpp b/src/utility/GATT.cpp index e1ee2c58..3550fd99 100644 --- a/src/utility/GATT.cpp +++ b/src/utility/GATT.cpp @@ -28,50 +28,84 @@ #include "GATT.h" GATTClass::GATTClass() : - _genericAccessService("1800"), - _deviceNameCharacteristic("2a00", BLERead, 20), - _appearanceCharacteristic("2a01", BLERead, 2), - _genericAttributeService("1801"), - _servicesChangedCharacteristic("2a05", BLEIndicate, 4) + _genericAccessService(NULL), + _deviceNameCharacteristic(NULL), + _appearanceCharacteristic(NULL), + _genericAttributeService(NULL), + _servicesChangedCharacteristic(NULL) { - _genericAccessService.retain(); - _genericAttributeService.retain(); - - _genericAccessService.addCharacteristic(&_deviceNameCharacteristic); - _genericAccessService.addCharacteristic(&_appearanceCharacteristic); - - _genericAttributeService.addCharacteristic(&_servicesChangedCharacteristic); } GATTClass::~GATTClass() { - clearAttributes(); + end(); } void GATTClass::begin() { + _genericAccessService = new BLELocalService("1800"); + _deviceNameCharacteristic = new BLELocalCharacteristic("2a00", BLERead, 20); + _appearanceCharacteristic = new BLELocalCharacteristic("2a01", BLERead, 2); + _genericAttributeService = new BLELocalService("1801"); + _servicesChangedCharacteristic = new BLELocalCharacteristic("2a05", BLEIndicate, 4); + + _genericAccessService->retain(); + _deviceNameCharacteristic->retain(); + _appearanceCharacteristic->retain(); + _genericAttributeService->retain(); + _servicesChangedCharacteristic->retain(); + + _genericAccessService->addCharacteristic(_deviceNameCharacteristic); + _genericAccessService->addCharacteristic(_appearanceCharacteristic); + _genericAttributeService->addCharacteristic(_servicesChangedCharacteristic); + setDeviceName("Arduino"); setAppearance(0x000); clearAttributes(); - addService(&_genericAccessService); - addService(&_genericAttributeService); + addService(_genericAccessService); + addService(_genericAttributeService); } void GATTClass::end() { - _attributes.clear(); + if (_genericAccessService && _genericAccessService->release() == 0) { + delete(_genericAccessService); + _genericAccessService = NULL; + } + + if (_deviceNameCharacteristic && _deviceNameCharacteristic->release() == 0) { + delete(_deviceNameCharacteristic); + _deviceNameCharacteristic = NULL; + } + + if (_appearanceCharacteristic && _appearanceCharacteristic->release() == 0) { + delete(_appearanceCharacteristic); + _appearanceCharacteristic = NULL; + } + + if (_genericAttributeService && _genericAttributeService->release() == 0) { + delete(_genericAttributeService); + _genericAttributeService = NULL; + } + + if (_servicesChangedCharacteristic && _servicesChangedCharacteristic->release() == 0) { + delete(_servicesChangedCharacteristic); + _servicesChangedCharacteristic = NULL; + } + + clearAttributes(); } void GATTClass::setDeviceName(const char* deviceName) { - _deviceNameCharacteristic.writeValue(deviceName); + _deviceNameCharacteristic->writeValue(deviceName); } void GATTClass::setAppearance(uint16_t appearance) { - _appearanceCharacteristic.writeValue((uint8_t*)&appearance, sizeof(appearance)); + _appearanceCharacteristic->writeValue((uint8_t*)&appearance, sizeof(appearance)); } void GATTClass::addService(BLEService& service) @@ -125,6 +159,7 @@ void GATTClass::addService(BLELocalService* service) { service->retain(); _attributes.add(service); + _services.add(service); uint16_t startHandle = attributeCount(); @@ -136,6 +171,7 @@ void GATTClass::addService(BLELocalService* service) characteristic->setHandle(attributeCount()); // add the characteristic again to make space of the characteristic value handle + characteristic->retain(); _attributes.add(characteristic); for (unsigned int j = 0; j < characteristic->descriptorCount(); j++) { @@ -155,12 +191,20 @@ void GATTClass::clearAttributes() for (unsigned int i = 0; i < attributeCount(); i++) { BLELocalAttribute* a = attribute(i); - if (a->release() <= 0) { + if (a->release() == 0) { delete a; } } - _attributes.clear(); + + for (unsigned int i = 0; i < _services.size(); i++) { + _services.get(i)->clear(); + } + _services.clear(); + } -GATTClass GATT; +#if !defined(FAKE_GATT) +GATTClass GATTObj; +GATTClass& GATT = GATTObj; +#endif diff --git a/src/utility/GATT.h b/src/utility/GATT.h index 5bc426d7..51d1537e 100644 --- a/src/utility/GATT.h +++ b/src/utility/GATT.h @@ -33,40 +33,41 @@ class GATTClass { GATTClass(); virtual ~GATTClass(); - void begin(); - void end(); + virtual void begin(); + virtual void end(); - void setDeviceName(const char* deviceName); - void setAppearance(uint16_t appearance); + virtual void setDeviceName(const char* deviceName); + virtual void setAppearance(uint16_t appearance); - void addService(BLEService& service); + virtual void addService(BLEService& service); protected: friend class ATTClass; - unsigned int attributeCount() const; - BLELocalAttribute* attribute(unsigned int index) const; + virtual unsigned int attributeCount() const; + virtual BLELocalAttribute* attribute(unsigned int index) const; protected: friend class BLELocalCharacteristic; - uint16_t serviceUuidForCharacteristic(BLELocalCharacteristic* characteristic) const; + virtual uint16_t serviceUuidForCharacteristic(BLELocalCharacteristic* characteristic) const; private: - void addService(BLELocalService* service); + virtual void addService(BLELocalService* service); - void clearAttributes(); + virtual void clearAttributes(); private: BLELinkedList _attributes; + BLELinkedList _services; - BLELocalService _genericAccessService; - BLELocalCharacteristic _deviceNameCharacteristic; - BLELocalCharacteristic _appearanceCharacteristic; - BLELocalService _genericAttributeService; - BLELocalCharacteristic _servicesChangedCharacteristic; + BLELocalService* _genericAccessService; + BLELocalCharacteristic* _deviceNameCharacteristic; + BLELocalCharacteristic* _appearanceCharacteristic; + BLELocalService* _genericAttributeService; + BLELocalCharacteristic* _servicesChangedCharacteristic; }; -extern GATTClass GATT; +extern GATTClass& GATT; #endif diff --git a/src/utility/HCI.cpp b/src/utility/HCI.cpp index 0234803f..af73ead2 100644 --- a/src/utility/HCI.cpp +++ b/src/utility/HCI.cpp @@ -21,27 +21,29 @@ #include "GAP.h" #include "HCITransport.h" #include "L2CAPSignaling.h" - +#include "btct.h" #include "HCI.h" +#include "bitDescriptions.h" +// #define _BLE_TRACE_ + -#define HCI_COMMAND_PKT 0x01 -#define HCI_ACLDATA_PKT 0x02 -#define HCI_EVENT_PKT 0x04 +#define HCI_COMMAND_PKT 0x01 +#define HCI_ACLDATA_PKT 0x02 +#define HCI_EVENT_PKT 0x04 +#define HCI_SECURITY_PKT 0x06 -#define EVT_DISCONN_COMPLETE 0x05 -#define EVT_CMD_COMPLETE 0xe -#define EVT_CMD_STATUS 0x0f -#define EVT_NUM_COMP_PKTS 0x13 -#define EVT_LE_META_EVENT 0x3e +#define EVT_DISCONN_COMPLETE 0x05 +#define EVT_ENCRYPTION_CHANGE 0x08 +#define EVT_CMD_COMPLETE 0x0e +#define EVT_CMD_STATUS 0x0f +#define EVT_NUM_COMP_PKTS 0x13 +#define EVT_RETURN_LINK_KEYS 0x15 +#define EVT_UNKNOWN 0x10 +#define EVT_LE_META_EVENT 0x3e #define EVT_LE_CONN_COMPLETE 0x01 #define EVT_LE_ADVERTISING_REPORT 0x02 -#define OGF_LINK_CTL 0x01 -#define OGF_HOST_CTL 0x03 -#define OGF_INFO_PARAM 0x04 -#define OGF_STATUS_PARAM 0x05 -#define OGF_LE_CTL 0x08 // OGF_LINK_CTL #define OCF_DISCONNECT 0x0006 @@ -72,6 +74,30 @@ #define HCI_OE_USER_ENDED_CONNECTION 0x13 +String metaEventToString(LE_META_EVENT event) +{ + switch(event){ + case CONN_COMPLETE: return F("CONN_COMPLETE"); + case ADVERTISING_REPORT: return F("ADVERTISING_REPORT"); + case LONG_TERM_KEY_REQUEST: return F("LE_LONG_TERM_KEY_REQUEST"); + case READ_LOCAL_P256_COMPLETE: return F("READ_LOCAL_P256_COMPLETE"); + case GENERATE_DH_KEY_COMPLETE: return F("GENERATE_DH_KEY_COMPLETE"); + case ENHANCED_CONN_COMPLETE: return F("ENHANCED_CONN_COMPLETE"); + default: return "event unknown"; + } +} +String commandToString(LE_COMMAND command){ + switch (command) + { + case ENCRYPT: return F("ENCRYPT"); + case LONG_TERM_KEY_REPLY: return F("LONG_TERM_KEY_REPLY"); + case READ_LOCAL_P256: return F("READ_LOCAL_P256"); + case GENERATE_DH_KEY_V1: return F("GENERATE_DH_KEY_V1"); + case GENERATE_DH_KEY_V2: return F("GENERATE_DH_KEY_V2"); + default: return "UNKNOWN"; + } +} + HCIClass::HCIClass() : _debug(NULL), _recvIndex(0), @@ -113,6 +139,13 @@ void HCIClass::poll(unsigned long timeout) while (HCITransport.available()) { byte b = HCITransport.read(); + if (_recvIndex >= (int)sizeof(_recvBuffer)) { + _recvIndex = 0; + if (_debug) { + _debug->println("_recvBuffer overflow"); + } + } + _recvBuffer[_recvIndex++] = b; if (_recvBuffer[0] == HCI_ACLDATA_PKT) { @@ -129,7 +162,7 @@ void HCIClass::poll(unsigned long timeout) handleAclDataPkt(pktLen, &_recvBuffer[1]); #ifdef ARDUINO_AVR_UNO_WIFI_REV2 - digitalWrite(NINA_RTS, LOW); + digitalWrite(NINA_RTS, LOW); #endif } } else if (_recvBuffer[0] == HCI_EVENT_PKT) { @@ -203,6 +236,17 @@ int HCIClass::readBdAddr(uint8_t addr[6]) return result; } +int HCIClass::readBdAddr(){ + uint8_t response[6]; + int result = readBdAddr(response); + if(result==0){ + for(int i=0; i<6; i++){ + localAddr[5-i] = _cmdResponse[i]; + } + } + return result; +} + int HCIClass::readRssi(uint16_t handle) { int result = sendCommand(OGF_STATUS_PARAM << 10 | OCF_READ_RSSI, sizeof(handle), &handle); @@ -226,6 +270,11 @@ int HCIClass::setEventMask(uint64_t eventMask) { return sendCommand(OGF_HOST_CTL << 10 | OCF_SET_EVENT_MASK, sizeof(eventMask), &eventMask); } +// Set LE Event mask +int HCIClass::setLeEventMask(uint64_t leEventMask) +{ + return sendCommand(OGF_LE_CTL << 10 | 0x01, sizeof(leEventMask), &leEventMask); +} int HCIClass::readLeBufferSize(uint16_t& pktLen, uint8_t& maxPkt) { @@ -315,7 +364,7 @@ int HCIClass::leSetAdvertiseEnable(uint8_t enable) return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_ADVERTISE_ENABLE, sizeof(enable), &enable); } -int HCIClass::leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window, +int HCIClass::leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window, uint8_t ownBdaddrType, uint8_t filter) { struct __attribute__ ((packed)) HCILeSetScanParameters { @@ -389,7 +438,7 @@ int HCIClass::leCancelConn() return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CANCEL_CONN, 0, NULL); } -int HCIClass::leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval, +int HCIClass::leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t supervisionTimeout) { struct __attribute__ ((packed)) HCILeConnUpdateData { @@ -412,6 +461,168 @@ int HCIClass::leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxIn return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CONN_UPDATE, sizeof(leConnUpdateData), &leConnUpdateData); } +void HCIClass::saveNewAddress(uint8_t addressType, uint8_t* address, uint8_t* peerIrk, uint8_t* localIrk){ + (void)addressType; + (void)localIrk; + if(_storeIRK!=0){ + _storeIRK(address, peerIrk); + } + // Again... this should work + // leAddResolvingAddress(addressType, address, peerIrk, localIrk); +} +void HCIClass::leAddResolvingAddress(uint8_t addressType, uint8_t* peerAddress, uint8_t* peerIrk, uint8_t* localIrk){ + leStopResolvingAddresses(); + + struct __attribute__ ((packed)) AddDevice { + uint8_t peerAddressType; + uint8_t peerAddress[6]; + uint8_t peerIRK[16]; + uint8_t localIRK[16]; + } addDevice; + addDevice.peerAddressType = addressType; + for(int i=0; i<6; i++) addDevice.peerAddress[5-i] = peerAddress[i]; + for(int i=0; i<16; i++) { + addDevice.peerIRK[15-i] = peerIrk[i]; + addDevice.localIRK[15-i] = localIrk[i]; + } +#ifdef _BLE_TRACE_ + Serial.print("ADDTYPE :"); + btct.printBytes(&addDevice.peerAddressType,1); + Serial.print("adddddd :"); + btct.printBytes(addDevice.peerAddress,6); + Serial.print("Peer IRK :"); + btct.printBytes(addDevice.peerIRK,16); + Serial.print("localIRK :"); + btct.printBytes(addDevice.localIRK,16); +#endif + sendCommand(OGF_LE_CTL << 10 | 0x27, sizeof(addDevice), &addDevice); + + leStartResolvingAddresses(); +} +int HCIClass::leStopResolvingAddresses(){ + uint8_t enable = 0; + return HCI.sendCommand(OGF_LE_CTL << 10 | 0x2D, 1,&enable); // Disable address resolution +} +int HCIClass::leStartResolvingAddresses(){ + uint8_t enable = 1; + return HCI.sendCommand(OGF_LE_CTL << 10 | 0x2D, 1,&enable); // Disable address resolution +} +int HCIClass::leReadPeerResolvableAddress(uint8_t peerAddressType, uint8_t* peerIdentityAddress, uint8_t* peerResolvableAddress){ + (void)peerResolvableAddress; + struct __attribute__ ((packed)) Request { + uint8_t addressType; + uint8_t identityAddress[6]; + } request; + request.addressType = peerAddressType; + for(int i=0; i<6; i++) request.identityAddress[5-i] = peerIdentityAddress[i]; + + + int res = sendCommand(OGF_LE_CTL << 10 | 0x2B, sizeof(request), &request); +#ifdef _BLE_TRACE_ + Serial.print("res: 0x"); + Serial.println(res, HEX); +#endif + if(res==0){ + struct __attribute__ ((packed)) Response { + uint8_t status; + uint8_t peerResolvableAddress[6]; + } *response = (Response*)_cmdResponse; +#ifdef _BLE_TRACE_ + Serial.print("Address resolution status: 0x"); + Serial.println(response->status, HEX); + Serial.print("peer resolvable address: "); + btct.printBytes(response->peerResolvableAddress,6); +#endif + } + return res; +} + +void HCIClass::writeLK(uint8_t peerAddress[], uint8_t LK[]){ + struct __attribute__ ((packed)) StoreLK { + uint8_t nKeys; + uint8_t BD_ADDR[6]; + uint8_t LTK[16]; + } storeLK; + storeLK.nKeys = 1; + memcpy(storeLK.BD_ADDR, peerAddress, 6); + for(int i=0; i<16; i++) storeLK.LTK[15-i] = LK[i]; + HCI.sendCommand(OGF_HOST_CTL << 10 | 0x11, sizeof(storeLK), &storeLK); +} +void HCIClass::readStoredLKs(){ + uint8_t BD_ADDR[6]; + readStoredLK(BD_ADDR, 1); +} +int HCIClass::readStoredLK(uint8_t BD_ADDR[], uint8_t read_all ){ + struct __attribute__ ((packed)) Request { + uint8_t BD_ADDR[6]; + uint8_t read_a; + } request = {{0},0}; + for(int i=0; i<6; i++) request.BD_ADDR[5-i] = BD_ADDR[i]; + request.read_a = read_all; + return sendCommand(OGF_HOST_CTL << 10 | 0xD, sizeof(request), &request); +} + +int HCIClass::tryResolveAddress(uint8_t* BDAddr, uint8_t* address){ + bool foundMatch = false; + if(HCI._getIRKs!=0){ + uint8_t nIRKs = 0; + uint8_t** BDAddrType = new uint8_t*; + uint8_t*** BADDRs = new uint8_t**; + uint8_t*** IRKs = new uint8_t**; + + + if(!HCI._getIRKs(&nIRKs, BDAddrType, BADDRs, IRKs)){ +#ifdef _BLE_TRACE_ + Serial.println("error getting IRKs."); +#endif + } + for(int i=0; i ", sizeof(aclHdr) + plen, txBuffer); } +#ifdef _BLE_TRACE_ + Serial.print("Data tx -> "); + for(int i=0; i< sizeof(aclHdr) + plen;i++){ + Serial.print(" 0x"); + Serial.print(txBuffer[i],HEX); + } + Serial.println("."); +#endif _pendingPkt++; HCITransport.write(txBuffer, sizeof(aclHdr) + plen); @@ -476,7 +695,14 @@ int HCIClass::sendCommand(uint16_t opcode, uint8_t plen, void* parameters) if (_debug) { dumpPkt("HCI COMMAND TX -> ", sizeof(pktHdr) + plen, txBuffer); } - +#ifdef _BLE_TRACE_ + Serial.print("Command tx -> "); + for(int i=0; i< sizeof(pktHdr) + plen;i++){ + Serial.print(" 0x"); + Serial.print(txBuffer[i],HEX); + } + Serial.println(""); +#endif HCITransport.write(txBuffer, sizeof(pktHdr) + plen); _cmdCompleteOpcode = 0xffff; @@ -498,6 +724,7 @@ void HCIClass::handleAclDataPkt(uint8_t /*plen*/, uint8_t pdata[]) uint16_t cid; } *aclHdr = (HCIACLHdr*)pdata; + uint16_t aclFlags = (aclHdr->handle & 0xf000) >> 12; if ((aclHdr->dlen - 4) != aclHdr->len) { @@ -517,6 +744,17 @@ void HCIClass::handleAclDataPkt(uint8_t /*plen*/, uint8_t pdata[]) } if ((aclHdr->dlen - 4) != aclHdr->len) { +#ifdef _BLE_TRACE_ + Serial.println("Don't have full packet yet"); + Serial.print("Handle: "); + btct.printBytes((uint8_t*)&aclHdr->handle,2); + Serial.print("dlen: "); + btct.printBytes((uint8_t*)&aclHdr->dlen,2); + Serial.print("len: "); + btct.printBytes((uint8_t*)&aclHdr->len,2); + Serial.print("cid: "); + btct.printBytes((uint8_t*)&aclHdr->cid,2); +#endif // don't have the full packet yet return; } @@ -530,8 +768,22 @@ void HCIClass::handleAclDataPkt(uint8_t /*plen*/, uint8_t pdata[]) ATT.handleData(aclHdr->handle & 0x0fff, aclHdr->len, &_recvBuffer[1 + sizeof(HCIACLHdr)]); } } else if (aclHdr->cid == SIGNALING_CID) { +#ifdef _BLE_TRACE_ + Serial.println("Signaling"); +#endif L2CAPSignaling.handleData(aclHdr->handle & 0x0fff, aclHdr->len, &_recvBuffer[1 + sizeof(HCIACLHdr)]); - } else { + } else if (aclHdr->cid == SECURITY_CID){ + // Security manager +#ifdef _BLE_TRACE_ + Serial.println("Security data"); +#endif + if (aclFlags == 0x1){ + L2CAPSignaling.handleSecurityData(aclHdr->handle & 0x0fff, aclHdr->len, &_aclPktBuffer[sizeof(HCIACLHdr)]); + }else{ + L2CAPSignaling.handleSecurityData(aclHdr->handle & 0x0fff, aclHdr->len, &_recvBuffer[1 + sizeof(HCIACLHdr)]); + } + + }else { struct __attribute__ ((packed)) { uint8_t op; uint8_t id; @@ -540,6 +792,10 @@ void HCIClass::handleAclDataPkt(uint8_t /*plen*/, uint8_t pdata[]) uint16_t localCid; uint16_t remoteCid; } l2capRejectCid= { 0x01, 0x00, 0x006, 0x0002, aclHdr->cid, 0x0000 }; +#ifdef _BLE_TRACE_ + Serial.print("rejecting packet cid: 0x"); + Serial.println(aclHdr->cid,HEX); +#endif sendAclPkt(aclHdr->handle & 0x0fff, 0x0005, sizeof(l2capRejectCid), &l2capRejectCid); } @@ -560,8 +816,13 @@ void HCIClass::handleEventPkt(uint8_t /*plen*/, uint8_t pdata[]) uint8_t evt; uint8_t plen; } *eventHdr = (HCIEventHdr*)pdata; +#ifdef _BLE_TRACE_ + Serial.print("HCI event: "); + Serial.println(eventHdr->evt, HEX); +#endif - if (eventHdr->evt == EVT_DISCONN_COMPLETE) { + if (eventHdr->evt == EVT_DISCONN_COMPLETE) + { struct __attribute__ ((packed)) DisconnComplete { uint8_t status; uint16_t handle; @@ -571,100 +832,649 @@ void HCIClass::handleEventPkt(uint8_t /*plen*/, uint8_t pdata[]) ATT.removeConnection(disconnComplete->handle, disconnComplete->reason); L2CAPSignaling.removeConnection(disconnComplete->handle, disconnComplete->reason); - HCI.leSetAdvertiseEnable(0x01); - } else if (eventHdr->evt == EVT_CMD_COMPLETE) { + if (GAP.advertising()) + { + HCI.leSetAdvertiseEnable(0x01); + } + } + else if (eventHdr->evt == EVT_ENCRYPTION_CHANGE) + { + struct __attribute__ ((packed)) EncryptionChange { + uint8_t status; + uint16_t connectionHandle; + uint8_t enabled; + } *encryptionChange = (EncryptionChange*)&pdata[sizeof(HCIEventHdr)]; +#ifdef _BLE_TRACE_ + Serial.println("[Info] Encryption changed"); + Serial.print("status : "); + btct.printBytes(&encryptionChange->status,1); + Serial.print("handle : "); + btct.printBytes((uint8_t*)&encryptionChange->connectionHandle,2); + Serial.print("enabled: "); + btct.printBytes(&encryptionChange->enabled,1); +#endif + if(encryptionChange->enabled>0){ + // 0001 1110 + if((ATT.getPeerEncryption(encryptionChange->connectionHandle)&PEER_ENCRYPTION::PAIRING_REQUEST)>0){ + if(ATT.localKeyDistribution.EncKey()){ +#ifdef _BLE_TRACE_ + Serial.println("Enc key set but should be ignored"); +#endif + }else{ +#ifdef _BLE_TRACE_ + Serial.println("No enc key distribution"); +#endif + } + // From page 1681 bluetooth standard - order matters + if(ATT.localKeyDistribution.IdKey()){ + /// We shall distribute IRK and address using identity information + { + uint8_t response[17]; + response[0] = CONNECTION_IDENTITY_INFORMATION; // Identity information. + for(int i=0; i<16; i++) response[16-i] = ATT.localIRK[i]; + HCI.sendAclPkt(encryptionChange->connectionHandle, SECURITY_CID, sizeof(response), response); +#ifdef _BLE_TRACE_ + Serial.println("Distribute ID Key"); +#endif + } + { + uint8_t response[8]; + response[0] = CONNECTION_IDENTITY_ADDRESS; // Identity address information + response[1] = 0x00; // Static local address + for(int i=0; i<6; i++) response[7-i] = HCI.localAddr[i]; + HCI.sendAclPkt(encryptionChange->connectionHandle, SECURITY_CID, sizeof(response), response); + } + } + if(ATT.localKeyDistribution.SignKey()){ + /// We shall distribut CSRK +#ifdef _BLE_TRACE_ + Serial.println("We shall distribute CSRK // not implemented"); +#endif + + }else{ + // Serial.println("We don't want to distribute CSRK"); + } + if(ATT.localKeyDistribution.LinkKey()){ +#ifdef _BLE_TRACE_ + Serial.println("We would like to use LTK to generate BR/EDR // not implemented"); +#endif + } + }else{ +#ifdef _BLE_TRACE_ + Serial.println("Reconnection, not pairing so no keys"); + Serial.println(ATT.getPeerEncryption(encryptionChange->connectionHandle),HEX); +#endif + } + + ATT.setPeerEncryption(encryptionChange->connectionHandle, PEER_ENCRYPTION::ENCRYPTED_AES); + if(ATT.writeBufferSize > 0){ + ATT.processWriteBuffer(); + } + if(ATT.holdBufferSize>0){ +#ifdef _BLE_TRACE_ + Serial.print("Sending queued response size: "); + Serial.println(ATT.holdBufferSize); +#endif + HCI.sendAclPkt(encryptionChange->connectionHandle, ATT_CID, ATT.holdBufferSize, ATT.holdBuffer); + ATT.holdBufferSize = 0; + } + }else{ + ATT.setPeerEncryption(encryptionChange->connectionHandle, PEER_ENCRYPTION::NO_ENCRYPTION); + } + } + else if (eventHdr->evt == EVT_CMD_COMPLETE) + { struct __attribute__ ((packed)) CmdComplete { uint8_t ncmd; uint16_t opcode; uint8_t status; } *cmdCompleteHeader = (CmdComplete*)&pdata[sizeof(HCIEventHdr)]; - +#ifdef _BLE_TRACE_ + Serial.print("E ncmd: 0x"); + Serial.println(cmdCompleteHeader->ncmd,HEX); + Serial.print("E opcode: 0x"); + Serial.println(cmdCompleteHeader->opcode, HEX); + Serial.print("E status: 0x"); + Serial.println(cmdCompleteHeader->status, HEX); +#endif _cmdCompleteOpcode = cmdCompleteHeader->opcode; _cmdCompleteStatus = cmdCompleteHeader->status; _cmdResponseLen = pdata[1] - sizeof(CmdComplete); _cmdResponse = &pdata[sizeof(HCIEventHdr) + sizeof(CmdComplete)]; - } else if (eventHdr->evt == EVT_CMD_STATUS) { + } + else if (eventHdr->evt == EVT_CMD_STATUS) + { struct __attribute__ ((packed)) CmdStatus { uint8_t status; uint8_t ncmd; uint16_t opcode; } *cmdStatusHeader = (CmdStatus*)&pdata[sizeof(HCIEventHdr)]; +#ifdef _BLE_TRACE_ + Serial.print("F n cmd: 0x"); + Serial.println(cmdStatusHeader->ncmd, HEX); + Serial.print("F status: 0x"); + Serial.println(cmdStatusHeader->status, HEX); + Serial.print("F opcode: 0x"); + Serial.println(cmdStatusHeader->opcode, HEX); +#endif _cmdCompleteOpcode = cmdStatusHeader->opcode; _cmdCompleteStatus = cmdStatusHeader->status; _cmdResponseLen = 0; - } else if (eventHdr->evt == EVT_NUM_COMP_PKTS) { + } + else if (eventHdr->evt == EVT_NUM_COMP_PKTS) + { uint8_t numHandles = pdata[sizeof(HCIEventHdr)]; uint16_t* data = (uint16_t*)&pdata[sizeof(HCIEventHdr) + sizeof(numHandles)]; for (uint8_t i = 0; i < numHandles; i++) { handleNumCompPkts(data[0], data[1]); - +#ifdef _BLE_TRACE_ + Serial.print("Outstanding packets: "); + Serial.println(_pendingPkt); + Serial.print("Data[0]: 0x"); + Serial.println(data[0]); + Serial.print("Data[1]: 0x"); + Serial.println(data[1]); +#endif data += 2; } - } else if (eventHdr->evt == EVT_LE_META_EVENT) { + } + else if(eventHdr->evt == 0x10) + { +#ifdef _BLE_TRACE_ + struct __attribute__ ((packed)) CmdHardwareError { + uint8_t hardwareCode; + } *cmdHardwareError = (CmdHardwareError*)&pdata[sizeof(HCIEventHdr)]; + Serial.print("Bluetooth hardware error."); + Serial.print(" Code: 0x"); + Serial.println(cmdHardwareError->hardwareCode, HEX); +#endif + } + else if (eventHdr->evt == EVT_LE_META_EVENT) + { struct __attribute__ ((packed)) LeMetaEventHeader { uint8_t subevent; } *leMetaHeader = (LeMetaEventHeader*)&pdata[sizeof(HCIEventHdr)]; +#ifdef _BLE_TRACE_ + Serial.print("\tSubEvent: 0x"); + Serial.println(leMetaHeader->subevent,HEX); +#endif + switch((LE_META_EVENT)leMetaHeader->subevent){ + case ENHANCED_CONN_COMPLETE:{ + struct __attribute__ ((packed)) EvtLeConnectionComplete { + uint8_t status; + uint16_t handle; + uint8_t role; + uint8_t peerBdaddrType; + uint8_t peerBdaddr[6]; + uint8_t localResolvablePrivateAddress[6]; + uint8_t peerResolvablePrivateAddress[6]; + uint16_t interval; + uint16_t latency; + uint16_t supervisionTimeout; + uint8_t masterClockAccuracy; + } *leConnectionComplete = (EvtLeConnectionComplete*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)]; + + if (leConnectionComplete->status == 0x00) { + ATT.addConnection(leConnectionComplete->handle, + leConnectionComplete->role, + leConnectionComplete->peerBdaddrType, + leConnectionComplete->peerBdaddr, + leConnectionComplete->interval, + leConnectionComplete->latency, + leConnectionComplete->supervisionTimeout, + leConnectionComplete->masterClockAccuracy); + + L2CAPSignaling.addConnection(leConnectionComplete->handle, + leConnectionComplete->role, + leConnectionComplete->peerBdaddrType, + leConnectionComplete->peerBdaddr, + leConnectionComplete->interval, + leConnectionComplete->latency, + leConnectionComplete->supervisionTimeout, + leConnectionComplete->masterClockAccuracy); + } + // uint8_t address[6]; + // uint8_t BDAddr[6]; + // for(int i=0; i<6; i++) BDAddr[5-i] = leConnectionComplete->peerBdaddr[i]; + // leReadPeerResolvableAddress(leConnectionComplete->peerBdaddrType,BDAddr,address); + // Serial.print("Resolving address: "); + // btct.printBytes(BDAddr, 6); + // Serial.print("BT answer : "); + // btct.printBytes(address, 6); + +#ifdef _BLE_TRACE_ + Serial.print("Resolved peer : "); + btct.printBytes(leConnectionComplete->peerResolvablePrivateAddress,6); + Serial.print("Resolved local : "); + btct.printBytes(leConnectionComplete->localResolvablePrivateAddress,6); +#endif + break; + } + case CONN_COMPLETE:{ + struct __attribute__ ((packed)) EvtLeConnectionComplete { + uint8_t status; + uint16_t handle; + uint8_t role; + uint8_t peerBdaddrType; + uint8_t peerBdaddr[6]; + uint16_t interval; + uint16_t latency; + uint16_t supervisionTimeout; + uint8_t masterClockAccuracy; + } *leConnectionComplete = (EvtLeConnectionComplete*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)]; + + if (leConnectionComplete->status == 0x00) { + ATT.addConnection(leConnectionComplete->handle, + leConnectionComplete->role, + leConnectionComplete->peerBdaddrType, + leConnectionComplete->peerBdaddr, + leConnectionComplete->interval, + leConnectionComplete->latency, + leConnectionComplete->supervisionTimeout, + leConnectionComplete->masterClockAccuracy); + + L2CAPSignaling.addConnection(leConnectionComplete->handle, + leConnectionComplete->role, + leConnectionComplete->peerBdaddrType, + leConnectionComplete->peerBdaddr, + leConnectionComplete->interval, + leConnectionComplete->latency, + leConnectionComplete->supervisionTimeout, + leConnectionComplete->masterClockAccuracy); + } + // leReadPeerResolvableAddress(leConnectionComplete->peerBdaddrType,BDAddr,address); + // Serial.print("Resolving address: "); + // btct.printBytes(BDAddr, 6); + // Serial.print("BT answer : "); + // btct.printBytes(address, 6); + break; + } + case ADVERTISING_REPORT:{ + struct __attribute__ ((packed)) EvtLeAdvertisingReport { + uint8_t status; + uint8_t type; + uint8_t peerBdaddrType; + uint8_t peerBdaddr[6]; + uint8_t eirLength; + uint8_t eirData[31]; + } *leAdvertisingReport = (EvtLeAdvertisingReport*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)]; + + if(leAdvertisingReport->eirLength > sizeof(leAdvertisingReport->eirData)){ + return ; + } - if (leMetaHeader->subevent == EVT_LE_CONN_COMPLETE) { - struct __attribute__ ((packed)) EvtLeConnectionComplete { - uint8_t status; - uint16_t handle; - uint8_t role; - uint8_t peerBdaddrType; - uint8_t peerBdaddr[6]; - uint16_t interval; - uint16_t latency; - uint16_t supervisionTimeout; - uint8_t masterClockAccuracy; - } *leConnectionComplete = (EvtLeConnectionComplete*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)]; - - if (leConnectionComplete->status == 0x00) { - ATT.addConnection(leConnectionComplete->handle, - leConnectionComplete->role, - leConnectionComplete->peerBdaddrType, - leConnectionComplete->peerBdaddr, - leConnectionComplete->interval, - leConnectionComplete->latency, - leConnectionComplete->supervisionTimeout, - leConnectionComplete->masterClockAccuracy); - - L2CAPSignaling.addConnection(leConnectionComplete->handle, - leConnectionComplete->role, - leConnectionComplete->peerBdaddrType, - leConnectionComplete->peerBdaddr, - leConnectionComplete->interval, - leConnectionComplete->latency, - leConnectionComplete->supervisionTimeout, - leConnectionComplete->masterClockAccuracy); + if (leAdvertisingReport->status == 0x01) { + // last byte is RSSI + int8_t rssi = leAdvertisingReport->eirData[leAdvertisingReport->eirLength]; + + GAP.handleLeAdvertisingReport(leAdvertisingReport->type, + leAdvertisingReport->peerBdaddrType, + leAdvertisingReport->peerBdaddr, + leAdvertisingReport->eirLength, + leAdvertisingReport->eirData, + rssi); + } + break; } - } else if (leMetaHeader->subevent == EVT_LE_ADVERTISING_REPORT) { - struct __attribute__ ((packed)) EvtLeAdvertisingReport { - uint8_t status; - uint8_t type; - uint8_t peerBdaddrType; - uint8_t peerBdaddr[6]; - uint8_t eirLength; - uint8_t eirData[31]; - } *leAdvertisingReport = (EvtLeAdvertisingReport*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)]; - - if (leAdvertisingReport->status == 0x01) { - // last byte is RSSI - int8_t rssi = leAdvertisingReport->eirData[leAdvertisingReport->eirLength]; - - GAP.handleLeAdvertisingReport(leAdvertisingReport->type, - leAdvertisingReport->peerBdaddrType, - leAdvertisingReport->peerBdaddr, - leAdvertisingReport->eirLength, - leAdvertisingReport->eirData, - rssi); + case LONG_TERM_KEY_REQUEST:{ + struct __attribute__ ((packed)) LTKRequest + { + uint8_t subEventCode; + uint16_t connectionHandle; + uint8_t randomNumber[8]; + uint8_t encryptedDiversifier[2]; + } *ltkRequest = (LTKRequest*)&pdata[sizeof(HCIEventHdr)]; +#ifdef _BLE_TRACE_ + Serial.println("LTK request received"); + Serial.print("Connection Handle: "); + btct.printBytes((uint8_t*)<kRequest->connectionHandle,2); + Serial.print("Random Number : "); + btct.printBytes(ltkRequest->randomNumber,8); + Serial.print("EDIV : "); + btct.printBytes(ltkRequest->encryptedDiversifier,2); +#endif + // Load our LTK for this connection. + uint8_t peerAddr[7]; + uint8_t resolvableAddr[6]; + uint8_t foundLTK; + ATT.getPeerAddrWithType(ltkRequest->connectionHandle, peerAddr); + + if((ATT.getPeerEncryption(ltkRequest->connectionHandle) & PEER_ENCRYPTION::PAIRING_REQUEST)>0){ + // Pairing request - LTK is one in buffer already + foundLTK = 1; + }else{ + if(ATT.getPeerResolvedAddress(ltkRequest->connectionHandle, resolvableAddr)){ + foundLTK = getLTK(resolvableAddr, HCI.LTK); + }else{ + foundLTK = getLTK(&peerAddr[1], HCI.LTK); + } + } + // } //2d + // Send our LTK back + if(foundLTK){ + struct __attribute__ ((packed)) LTKReply + { + uint16_t connectionHandle; + uint8_t LTK[16]; + } ltkReply = {0,0}; + ltkReply.connectionHandle = ltkRequest->connectionHandle; + for(int i=0; i<16; i++) ltkReply.LTK[15-i] = HCI.LTK[i]; + int result = sendCommand(OGF_LE_CTL << 10 | LE_COMMAND::LONG_TERM_KEY_REPLY,sizeof(ltkReply), <kReply); + + #ifdef _BLE_TRACE_ + Serial.println("Sending LTK as: "); + btct.printBytes(ltkReply.LTK,16); + #endif + + if(result == 0){ + struct __attribute__ ((packed)) LTKReplyResult + { + uint8_t status; + uint16_t connectionHandle; + } ltkReplyResult = {0,0}; + memcpy(<kReplyResult, _cmdResponse, 3); + + #ifdef _BLE_TRACE_ + Serial.println("LTK send success"); + Serial.print("status : "); + btct.printBytes(<kReplyResult.status,1); + Serial.print("Conn Handle: "); + btct.printBytes((uint8_t*)<kReplyResult.connectionHandle,2); + #endif + }else{ + #ifdef _BLE_TRACE_ + Serial.print("Failed to send LTK...: "); + btct.printBytes((uint8_t*)&result,2); + #endif + } + }else{ + /// do LTK rejection +#ifdef _BLE_TRACE_ + Serial.println("LTK not found, rejecting"); +#endif + sendCommand(OGF_LE_CTL << 10 | LE_COMMAND::LONG_TERM_KEY_NEGATIVE_REPLY,2, <kRequest->connectionHandle); + } + break; + } + case REMOTE_CONN_PARAM_REQ:{ + struct __attribute__ ((packed)) RemoteConnParamReq { + uint8_t subEventCode; + uint16_t connectionHandle; + uint16_t intervalMin; + uint16_t intervalMax; + uint16_t latency; + uint16_t timeOut; + } *remoteConnParamReq = (RemoteConnParamReq*)&pdata[sizeof(HCIEventHdr)]; +#ifdef _BLE_TRACE_ + Serial.println("--- Remtoe conn param req"); + Serial.print("Handle : "); + btct.printBytes((uint8_t*)&remoteConnParamReq->connectionHandle,2); + Serial.print("Interval min: "); + btct.printBytes((uint8_t*)&remoteConnParamReq->intervalMin,2); + Serial.print("Interval max: "); + btct.printBytes((uint8_t*)&remoteConnParamReq->intervalMax,2); + Serial.print("Latency : "); + btct.printBytes((uint8_t*)&remoteConnParamReq->latency,2); + Serial.print("Timeout : "); + btct.printBytes((uint8_t*)&remoteConnParamReq->timeOut,2); +#endif + struct __attribute__ ((packed)) RemoteConnParamReqReply { + uint16_t connectionHandle; + uint16_t intervalMin; + uint16_t intervalMax; + uint16_t latency; + uint16_t timeOut; + uint16_t minLength; + uint16_t maxLength; + } remoteConnParamReqReply; + memcpy(&remoteConnParamReqReply, &remoteConnParamReq->connectionHandle, sizeof(RemoteConnParamReq)-1); + + remoteConnParamReqReply.minLength = 0x000F; + remoteConnParamReqReply.maxLength = 0x0FFF; + sendCommand(OGF_LE_CTL << 10 | 0x20, sizeof(RemoteConnParamReqReply), &remoteConnParamReqReply); + break; + } + case READ_LOCAL_P256_COMPLETE:{ + struct __attribute__ ((packed)) EvtReadLocalP256Complete{ + uint8_t subEventCode; + uint8_t status; + uint8_t localPublicKey[64]; + } *evtReadLocalP256Complete = (EvtReadLocalP256Complete*)&pdata[sizeof(HCIEventHdr)]; + if(evtReadLocalP256Complete->status == 0x0){ +#ifdef _BLE_TRACE_ + Serial.println("Key read success"); +#endif + struct __attribute__ ((packed)) PairingPublicKey + { + uint8_t code; + uint8_t publicKey[64]; + } pairingPublicKey = {CONNECTION_PAIRING_PUBLIC_KEY,0}; + memcpy(pairingPublicKey.publicKey,evtReadLocalP256Complete->localPublicKey,64); + memcpy(localPublicKeyBuffer, evtReadLocalP256Complete->localPublicKey,64); + + // Send the local public key to the remote + uint16_t connectionHandle = ATT.getPeerEncrptingConnectionHandle(); + if(connectionHandle>ATT_MAX_PEERS){ +#ifdef _BLE_TRACE_ + Serial.println("failed to find connection handle"); +#endif + break; + } + HCI.sendAclPkt(connectionHandle,SECURITY_CID,sizeof(PairingPublicKey),&pairingPublicKey); + uint8_t encryption = ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::SENT_PUBKEY; + ATT.setPeerEncryption(connectionHandle, encryption); + + + uint8_t Z = 0; + + HCI.leRand(Nb); + HCI.leRand(&Nb[8]); + +#ifdef _BLE_TRACE_ + Serial.print("nb: "); + btct.printBytes(Nb, 16); +#endif + struct __attribute__ ((packed)) F4Params + { + uint8_t U[32]; + uint8_t V[32]; + uint8_t Z; + } f4Params = {{0},{0},Z}; + for(int i=0; i<32; i++){ + f4Params.U[31-i] = pairingPublicKey.publicKey[i]; + f4Params.V[31-i] = HCI.remotePublicKeyBuffer[i]; + } + + struct __attribute__ ((packed)) PairingConfirm + { + uint8_t code; + uint8_t cb[16]; + } pairingConfirm = {CONNECTION_PAIRING_CONFIRM,0}; + + btct.AES_CMAC(Nb,(unsigned char *)&f4Params,sizeof(f4Params),pairingConfirm.cb); + +#ifdef _BLE_TRACE_ + Serial.print("cb: "); + btct.printBytes(pairingConfirm.cb, 16); +#endif + + uint8_t cb_temp[sizeof(pairingConfirm.cb)]; + for(unsigned int i=0; istatus,HEX); + for(int i=0; i<64; i++){ + Serial.print(" 0x"); + Serial.print(evtReadLocalP256Complete->localPublicKey[i],HEX); + } + Serial.println("."); +#endif + } + break; + } + case GENERATE_DH_KEY_COMPLETE:{ + struct __attribute__ ((packed)) EvtLeDHKeyComplete{ + uint8_t subEventCode; + uint8_t status; + uint8_t DHKey[32]; + } *evtLeDHKeyComplete = (EvtLeDHKeyComplete*)&pdata[sizeof(HCIEventHdr)]; + if(evtLeDHKeyComplete->status == 0x0){ +#ifdef _BLE_TRACE_ + Serial.println("DH key generated"); +#endif + uint16_t connectionHandle = ATT.getPeerEncrptingConnectionHandle(); + if(connectionHandle>ATT_MAX_PEERS){ +#ifdef _BLE_TRACE_ + Serial.println("Failed to find connection handle DH key check"); +#endif + break; + } + + + for(int i=0; i<32; i++) DHKey[31-i] = evtLeDHKeyComplete->DHKey[i]; + +#ifdef _BLE_TRACE_ + Serial.println("Stored our DHKey:"); + btct.printBytes(DHKey,32); +#endif + uint8_t encryption = ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::DH_KEY_CALULATED; + ATT.setPeerEncryption(connectionHandle, encryption); + + if((encryption & PEER_ENCRYPTION::RECEIVED_DH_CHECK) > 0){ +#ifdef _BLE_TRACE_ + Serial.println("Received DHKey check already so calculate f5, f6 now."); +#endif + L2CAPSignaling.smCalculateLTKandConfirm(connectionHandle, HCI.remoteDHKeyCheckBuffer); + + }else{ +#ifdef _BLE_TRACE_ + Serial.println("Waiting on other DHKey check before calculating."); +#endif + } + }else{ +#ifdef _BLE_TRACE_ + Serial.print("Key generation error: 0x"); + Serial.println(evtLeDHKeyComplete->status, HEX); +#endif + } + break; + } + default: + { +#ifdef _BLE_TRACE_ + Serial.println("[Info] Unhandled meta event"); +#endif } } + }else{ +#ifdef _BLE_TRACE_ + Serial.println("[Info] Unhandled event"); +#endif } } +int HCIClass::leEncrypt(uint8_t* key, uint8_t* plaintext, uint8_t* status, uint8_t* ciphertext){ + (void)status; + struct __attribute__ ((packed)) LeEncryptCommand + { + uint8_t key[16]; + uint8_t plaintext[16]; + } leEncryptCommand = {{0},{0}}; + for(int i=0; i<16; i++){ + leEncryptCommand.key[15-i] = key[i]; + leEncryptCommand.plaintext[15-i] = plaintext[i]; + } + + int res = sendCommand(OGF_LE_CTL << 10 | LE_COMMAND::ENCRYPT, 32, &leEncryptCommand); + if(res == 0){ +#ifdef _BLE_TRACE_ + Serial.print("Copying from command Response length: "); + Serial.println(_cmdResponseLen); + Serial.println("."); + for(int i=0; i<20; i++){ + Serial.print(" 0x"); + Serial.print(_cmdResponse[i],HEX); + } + Serial.println("."); +#endif + for(int i=0; i<16; i++){ + ciphertext[15-i] = _cmdResponse[i]; + } + return 1; + } +#ifdef _BLE_TRACE_ + Serial.print("Error with AES: 0x"); + Serial.println(res, HEX); +#endif + return res; +} +int HCIClass::leRand(uint8_t rand[]){ + int res = sendCommand(OGF_LE_CTL << 10 | LE_COMMAND::RANDOM); + if(res == 0){ + memcpy(rand,_cmdResponse, 8); /// backwards but it's a random number + } + return res; +} +int HCIClass::getLTK(uint8_t* address, uint8_t* LTK){ + if(_getLTK!=0){ + return _getLTK(address, LTK); + }else{ + return 0; + } +} +int HCIClass::storeIRK(uint8_t* address, uint8_t* IRK){ + if(_storeIRK!=0){ + return _storeIRK(address, IRK); + }else{ + return 0; + } +} +int HCIClass::storeLTK(uint8_t* address, uint8_t* LTK){ + if(_storeLTK!=0){ + return _storeLTK(address, LTK); + }else{ + return 0; + } +} +uint8_t HCIClass::localIOCap(){ + if(_displayCode!=0){ + /// We have a display + if(_binaryConfirmPairing!=0){ + return IOCAP_DISPLAY_YES_NO; + }else{ + return IOCAP_DISPLAY_ONLY; + } + }else{ + // We have no display + return IOCAP_NO_INPUT_NO_OUTPUT; + } +} + +/// Stub function to generate parameters for local authreq +AuthReq HCIClass::localAuthreq(){ + // If get, set, IRK, LTK all set then we can bond. + AuthReq local = AuthReq(); + if(_storeIRK!=0 && _storeLTK!=0 && _getLTK!=0 && _getIRKs!=0){ + local.setBonding(true); + } + local.setSC(true); + local.setMITM(true); + local.setCT2(true); + return LOCAL_AUTHREQ; +} void HCIClass::dumpPkt(const char* prefix, uint8_t plen, uint8_t pdata[]) { @@ -686,4 +1496,7 @@ void HCIClass::dumpPkt(const char* prefix, uint8_t plen, uint8_t pdata[]) } } -HCIClass HCI; +#if !defined(FAKE_HCI) +HCIClass HCIObj; +HCIClass& HCI = HCIObj; +#endif diff --git a/src/utility/HCI.h b/src/utility/HCI.h index 86ec679c..a6fa66e8 100644 --- a/src/utility/HCI.h +++ b/src/utility/HCI.h @@ -21,64 +21,130 @@ #define _HCI_H_ #include +#include "bitDescriptions.h" + +#include "L2CAPSignaling.h" + +#define OGF_LINK_CTL 0x01 +#define OGF_HOST_CTL 0x03 +#define OGF_INFO_PARAM 0x04 +#define OGF_STATUS_PARAM 0x05 +#define OGF_LE_CTL 0x08 + +enum LE_COMMAND { + ENCRYPT = 0x0017, + RANDOM = 0x0018, + LONG_TERM_KEY_REPLY = 0x001A, + LONG_TERM_KEY_NEGATIVE_REPLY = 0x001B, + READ_LOCAL_P256 = 0x0025, + GENERATE_DH_KEY_V1 = 0x0026, + GENERATE_DH_KEY_V2 = 0x005E +}; +enum LE_META_EVENT { + CONN_COMPLETE = 0x01, + ADVERTISING_REPORT = 0x02, + LONG_TERM_KEY_REQUEST = 0x05, + REMOTE_CONN_PARAM_REQ = 0x06, + READ_LOCAL_P256_COMPLETE = 0x08, + GENERATE_DH_KEY_COMPLETE = 0x09, + ENHANCED_CONN_COMPLETE = 0x0A, +}; +String metaEventToString(LE_META_EVENT event); +String commandToString(LE_COMMAND command); class HCIClass { public: HCIClass(); virtual ~HCIClass(); - int begin(); - void end(); + virtual int begin(); + virtual void end(); - void poll(); - void poll(unsigned long timeout); + virtual void poll(); + virtual void poll(unsigned long timeout); - int reset(); - int readLocalVersion(uint8_t& hciVer, uint16_t& hciRev, uint8_t& lmpVer, + virtual int reset(); + virtual int readLocalVersion(uint8_t& hciVer, uint16_t& hciRev, uint8_t& lmpVer, uint16_t& manufacturer, uint16_t& lmpSubVer); - int readBdAddr(uint8_t addr[6]); - int readRssi(uint16_t handle); + virtual int readBdAddr(uint8_t addr[6]); + virtual int readBdAddr(); - int setEventMask(uint64_t eventMask); + virtual int readRssi(uint16_t handle); - int readLeBufferSize(uint16_t& pktLen, uint8_t& maxPkt); - int leSetRandomAddress(uint8_t addr[6]); - int leSetAdvertisingParameters(uint16_t minInterval, uint16_t maxInterval, + virtual int setEventMask(uint64_t eventMask); + virtual int setLeEventMask(uint64_t leEventMask); + virtual int readLeBufferSize(uint16_t& pktLen, uint8_t& maxPkt); + virtual int leSetRandomAddress(uint8_t addr[6]); + virtual int leSetAdvertisingParameters(uint16_t minInterval, uint16_t maxInterval, uint8_t advType, uint8_t ownBdaddrType, uint8_t directBdaddrType, uint8_t directBdaddr[6], uint8_t chanMap, uint8_t filter); - int leSetAdvertisingData(uint8_t length, uint8_t data[]); - int leSetScanResponseData(uint8_t length, uint8_t data[]); - int leSetAdvertiseEnable(uint8_t enable); - int leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window, + virtual int leSetAdvertisingData(uint8_t length, uint8_t data[]); + virtual int leSetScanResponseData(uint8_t length, uint8_t data[]); + virtual int leSetAdvertiseEnable(uint8_t enable); + virtual int leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window, uint8_t ownBdaddrType, uint8_t filter); - int leSetScanEnable(uint8_t enabled, uint8_t duplicates); - int leCreateConn(uint16_t interval, uint16_t window, uint8_t initiatorFilter, + virtual int leSetScanEnable(uint8_t enabled, uint8_t duplicates); + virtual int leCreateConn(uint16_t interval, uint16_t window, uint8_t initiatorFilter, uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint8_t ownBdaddrType, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t supervisionTimeout, uint16_t minCeLength, uint16_t maxCeLength); - int leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval, + virtual int leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t supervisionTimeout); - int leCancelConn(); - - - int sendAclPkt(uint16_t handle, uint8_t cid, uint8_t plen, void* data); - - int disconnect(uint16_t handle); - - void debug(Stream& stream); - void noDebug(); + virtual int leCancelConn(); + virtual int leEncrypt(uint8_t* Key, uint8_t* plaintext, uint8_t* status, uint8_t* ciphertext); + // Generate a 64 bit random number + virtual int leRand(uint8_t rand[]); + virtual AuthReq localAuthreq(); + virtual uint8_t localIOCap(); + + virtual void saveNewAddress(uint8_t addressType, uint8_t* address, uint8_t* peerIrk, uint8_t* remoteIrk); + virtual void leAddResolvingAddress(uint8_t addressType, uint8_t* address, uint8_t* peerIrk, uint8_t* remoteIrk); + virtual int leStopResolvingAddresses(); + virtual int leStartResolvingAddresses(); + virtual int leReadPeerResolvableAddress(uint8_t peerAddressType, uint8_t* peerIdentityAddress, uint8_t* peerResolvableAddress); + + virtual void readStoredLKs(); + virtual int readStoredLK(uint8_t BD_ADDR[], uint8_t read_all = 0); + virtual void writeLK(uint8_t peerAddress[], uint8_t LK[]); + virtual int tryResolveAddress(uint8_t* BDAddr, uint8_t* address); + + virtual int sendAclPkt(uint16_t handle, uint8_t cid, uint8_t plen, void* data); + + virtual int disconnect(uint16_t handle); + + virtual void debug(Stream& stream); + virtual void noDebug(); + + // TODO: Send command be private again & use ATT implementation of send command within ATT. + virtual int sendCommand(uint16_t opcode, uint8_t plen = 0, void* parameters = NULL); + uint8_t remotePublicKeyBuffer[64]; + uint8_t localPublicKeyBuffer[64]; + uint8_t remoteDHKeyCheckBuffer[16]; + uint8_t Na[16]; + uint8_t Nb[16]; + uint8_t DHKey[32]; + uint8_t localAddr[6]; + uint8_t LTK[16]; + virtual int getLTK(uint8_t* address, uint8_t* LTK); + virtual int storeLTK(uint8_t* address, uint8_t* LTK); + virtual int storeIRK(uint8_t* address, uint8_t* IRK); + int (*_storeIRK)(uint8_t* address, uint8_t* peerIrk) = 0; + int (*_getIRKs)(uint8_t* nIRKs,uint8_t** BADDR_type, uint8_t*** BADDRs, uint8_t*** IRKs) = 0; + int (*_storeLTK)(uint8_t*, uint8_t*) = 0; + int (*_getLTK)(uint8_t*, uint8_t*) = 0; + void (*_displayCode)(uint32_t confirmationCode) = 0; + bool (*_binaryConfirmPairing)() = 0; private: - int sendCommand(uint16_t opcode, uint8_t plen = 0, void* parameters = NULL); - void handleAclDataPkt(uint8_t plen, uint8_t pdata[]); - void handleNumCompPkts(uint16_t handle, uint16_t numPkts); - void handleEventPkt(uint8_t plen, uint8_t pdata[]); + virtual void handleAclDataPkt(uint8_t plen, uint8_t pdata[]); + virtual void handleNumCompPkts(uint16_t handle, uint16_t numPkts); + virtual void handleEventPkt(uint8_t plen, uint8_t pdata[]); - void dumpPkt(const char* prefix, uint8_t plen, uint8_t pdata[]); + virtual void dumpPkt(const char* prefix, uint8_t plen, uint8_t pdata[]); Stream* _debug; @@ -96,6 +162,6 @@ class HCIClass { uint8_t _aclPktBuffer[255]; }; -extern HCIClass HCI; +extern HCIClass& HCI; #endif diff --git a/src/utility/HCICordioTransport.cpp b/src/utility/HCICordioTransport.cpp index ab92818b..b2a911d6 100644 --- a/src/utility/HCICordioTransport.cpp +++ b/src/utility/HCICordioTransport.cpp @@ -17,15 +17,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#if defined(ARDUINO_ARCH_MBED) +#if defined(ARDUINO_ARCH_MBED) && !defined(TARGET_NANO_RP2040_CONNECT) // && !defined(CORE_CM4) +#include +#include #include #include -#include -#include - -#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) +#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) #include "ble/BLE.h" #include #endif @@ -47,35 +46,40 @@ #include "HCICordioTransport.h" -extern ble::vendor::cordio::CordioHCIDriver& ble_cordio_get_hci_driver(); +#if (MBED_VERSION > MBED_ENCODE_VERSION(6, 2, 0)) +#define BLE_NAMESPACE ble +#else +#define BLE_NAMESPACE ble::vendor::cordio +#endif -namespace ble { - namespace vendor { - namespace cordio { - struct CordioHCIHook { - static CordioHCIDriver& getDriver() { - return ble_cordio_get_hci_driver(); - } +#include "CordioHCICustomDriver.h" - static CordioHCITransportDriver& getTransportDriver() { - return getDriver()._transport_driver; - } +extern BLE_NAMESPACE::CordioHCIDriver& ble_cordio_get_hci_driver(); +extern "C" void hciTrSerialRxIncoming(uint8_t *pBuf, uint8_t len); - static void setDataReceivedHandler(void (*handler)(uint8_t*, uint8_t)) { - getTransportDriver().set_data_received_handler(handler); - } - }; +namespace BLE_NAMESPACE { + struct CordioHCIHook { + static CordioHCIDriver& getDriver() { + return ble_cordio_get_hci_driver(); } - } + + static CordioHCITransportDriver& getTransportDriver() { + return getDriver()._transport_driver; + } + + static void setDataReceivedHandler(void (*handler)(uint8_t*, uint8_t)) { + getTransportDriver().set_data_received_handler(handler); + } + }; } -using ble::vendor::cordio::CordioHCIHook; +using BLE_NAMESPACE::CordioHCIHook; #if CORDIO_ZERO_COPY_HCI extern uint8_t *SystemHeapStart; extern uint32_t SystemHeapSize; -void init_wsf(ble::vendor::cordio::buf_pool_desc_t& buf_pool_desc) { +void init_wsf(BLE_NAMESPACE::buf_pool_desc_t& buf_pool_desc) { static bool init = false; if (init) { @@ -179,7 +183,7 @@ HCICordioTransportClass::~HCICordioTransportClass() { } -#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) +#if (defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA)) && !defined(CUSTOM_HCI_DRIVER) events::EventQueue eventQueue(10 * EVENTS_EVENT_SIZE); void scheduleMbedBleEvents(BLE::OnEventsToProcessCallbackContext *context) { eventQueue.call(mbed::Callback(&context->ble, &BLE::processEvents)); @@ -195,11 +199,12 @@ int HCICordioTransportClass::begin() _rxBuf.clear(); #if CORDIO_ZERO_COPY_HCI - ble::vendor::cordio::buf_pool_desc_t bufPoolDesc = CordioHCIHook::getDriver().get_buffer_pool_description(); + BLE_NAMESPACE::buf_pool_desc_t bufPoolDesc = CordioHCIHook::getDriver().get_buffer_pool_description(); init_wsf(bufPoolDesc); #endif -#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) +#if (defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA)) && !defined(CUSTOM_HCI_DRIVER) + BLE &ble = BLE::Instance(); ble.onEventsToProcess(scheduleMbedBleEvents); @@ -211,7 +216,6 @@ int HCICordioTransportClass::begin() } #else CordioHCIHook::getDriver().initialize(); - CordioHCIHook::getDriver().start_reset_sequence(); #endif if (bleLoopThread == NULL) { @@ -233,7 +237,17 @@ void HCICordioTransportClass::end() delete bleLoopThread; bleLoopThread = NULL; } + // Reset the callback with the mbed-os default handler to properly handle the following CYW43xxx chip initializations and begins + CordioHCIHook::setDataReceivedHandler(hciTrSerialRxIncoming); + +#if (defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA)) && !defined(CUSTOM_HCI_DRIVER) + BLE &ble = BLE::Instance(); + ble.shutdown(); +#endif + +#if !defined(TARGET_STM32H7) CordioHCIHook::getDriver().terminate(); +#endif _begun = false; } diff --git a/src/utility/HCISilabsTransport.cpp b/src/utility/HCISilabsTransport.cpp new file mode 100644 index 00000000..135fd020 --- /dev/null +++ b/src/utility/HCISilabsTransport.cpp @@ -0,0 +1,130 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2024 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined(ARDUINO_SILABS) + +#include "HCISilabsTransport.h" +#include "sl_string.h" + +extern "C" { +#include "sl_btctrl_linklayer.h" +#include "sl_hci_common_transport.h" +} + +extern "C" int strcasecmp(char const *a, char const *b) { + return sl_strcasecmp(a, b); +} + +static RingBufferN<512> buf; + +HCISilabsTransportClass::HCISilabsTransportClass() +{ +} + +HCISilabsTransportClass::~HCISilabsTransportClass() +{ +} + +int HCISilabsTransportClass::begin() +{ + if(!sl_btctrl_is_initialized()) { + sl_bt_controller_init(); + } + + /* Initialize adv & scan components */ + sl_btctrl_init_adv(); + sl_btctrl_init_scan(); + sl_btctrl_init_conn(); + sl_btctrl_init_adv_ext(); + sl_btctrl_init_scan_ext(); + + /* Initialize HCI controller */ + sl_bthci_init_upper(); + sl_btctrl_hci_parser_init_default(); + sl_btctrl_hci_parser_init_conn(); + sl_btctrl_hci_parser_init_adv(); + sl_btctrl_hci_parser_init_phy(); + + return 1; +} + +void HCISilabsTransportClass::end() +{ + sl_bt_controller_deinit(); +} + +void HCISilabsTransportClass::wait(unsigned long timeout) +{ + for (unsigned long start = millis(); (millis() - start) < timeout;) { + if (available()) { + break; + } + } +} + +int HCISilabsTransportClass::available() +{ + return buf.available(); +} + +int HCISilabsTransportClass::peek() +{ + return buf.peek(); +} + +int HCISilabsTransportClass::read() +{ + return buf.read_char(); +} + +size_t HCISilabsTransportClass::write(const uint8_t* data, size_t len) +{ + int ret = 0; + ret = hci_common_transport_receive((uint8_t *)data, len, true); + + if (ret == 0) return len; + + return 0; +} + +extern "C" { + /** + * @brief Transmit HCI message using the currently used transport layer. + * The HCI calls this function to transmit a full HCI message. + * @param[in] data Packet type followed by HCI packet data. + * @param[in] len Length of the `data` parameter + * @return 0 - on success, or non-zero on failure. + */ + uint32_t hci_common_transport_transmit(uint8_t *data, int16_t len) + { + for (int i = 0; i < len; i++) { + buf.store_char(data[i]); + if (buf.isFull()) return SL_STATUS_FAIL; + } + + sl_btctrl_hci_transmit_complete(0); + return 0; + } +} + +HCISilabsTransportClass HCISilabsTransport; + +HCITransportInterface& HCITransport = HCISilabsTransport; + +#endif diff --git a/src/utility/HCISilabsTransport.h b/src/utility/HCISilabsTransport.h new file mode 100644 index 00000000..2061e782 --- /dev/null +++ b/src/utility/HCISilabsTransport.h @@ -0,0 +1,42 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HCI_SILABS_TRANSPORT_H_ +#define _HCI_SILABS_TRANSPORT_H_ + +#include "HCITransport.h" + +class HCISilabsTransportClass : public HCITransportInterface { +public: + HCISilabsTransportClass(); + virtual ~HCISilabsTransportClass(); + + virtual int begin(); + virtual void end(); + + virtual void wait(unsigned long timeout); + + virtual int available(); + virtual int peek(); + virtual int read(); + + virtual size_t write(const uint8_t* data, size_t length); +}; + +#endif \ No newline at end of file diff --git a/src/utility/HCIUartTransport.cpp b/src/utility/HCIUartTransport.cpp index 9a5f4087..191811a7 100644 --- a/src/utility/HCIUartTransport.cpp +++ b/src/utility/HCIUartTransport.cpp @@ -17,18 +17,24 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#if !defined(ARDUINO_ARCH_MBED) +#if !defined(ARDUINO_ARCH_MBED) && !defined(ESP32) && !defined(ARDUINO_SILABS) && !defined(ARDUINO_UNOR4_WIFI) || defined(TARGET_NANO_RP2040_CONNECT) //|| defined(CORE_CM4) #include "HCIUartTransport.h" #if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_AVR_UNO_WIFI_REV2) #define SerialHCI Serial2 -#elif defined(ARDUINO_SAMD_NANO_33_IOT) +#elif defined(ARDUINO_SAMD_NANO_33_IOT) || defined(ARDUINO_NANO_RP2040_CONNECT) // SerialHCI is already defined in the variant #elif defined(ARDUINO_PORTENTA_H7_M4) // SerialHCI is already defined in the variant -#elif defined(ARDUINO_PORTENTA_H7_M7) +#elif defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) #define SerialHCI Serial2 +#elif defined(ARDUINO_OPTA) +#define SerialHCI Serial3 +#elif defined(ARDUINO_PORTENTA_C33) +#define SerialHCI Serial5 +#elif defined(ARDUINO_GIGA) +arduino::UART SerialHCI(CYBSP_BT_UART_TX, CYBSP_BT_UART_RX, CYBSP_BT_UART_RTS, CYBSP_BT_UART_CTS); #else #error "Unsupported board selected!" #endif @@ -93,7 +99,7 @@ size_t HCIUartTransportClass::write(const uint8_t* data, size_t length) return result; } -#ifdef ARDUINO_AVR_UNO_WIFI_REV2 +#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_NANO_RP2040_CONNECT) HCIUartTransportClass HCIUartTransport(SerialHCI, 119600); #else HCIUartTransportClass HCIUartTransport(SerialHCI, 912600); diff --git a/src/utility/HCIVirtualTransport.cpp b/src/utility/HCIVirtualTransport.cpp new file mode 100644 index 00000000..3959d731 --- /dev/null +++ b/src/utility/HCIVirtualTransport.cpp @@ -0,0 +1,144 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined(ESP32) + +#include "HCIVirtualTransport.h" + +StreamBufferHandle_t rec_buffer; +StreamBufferHandle_t send_buffer; +TaskHandle_t bleHandle; + + +static void notify_host_send_available(void) +{ +} + +static int notify_host_recv(uint8_t *data, uint16_t length) +{ + xStreamBufferSend(rec_buffer,data,length,portMAX_DELAY); // !!!potentially waiting forever + return 0; +} + +static esp_vhci_host_callback_t vhci_host_cb = { + notify_host_send_available, + notify_host_recv +}; + +void bleTask(void *pvParameters) +{ + esp_vhci_host_register_callback(&vhci_host_cb); + size_t length; + uint8_t mybuf[258]; + + while(true){ + length = xStreamBufferReceive(send_buffer,mybuf,258,portMAX_DELAY); + while (!esp_vhci_host_check_send_available()) {} + esp_vhci_host_send_packet(mybuf, length); + } +} + + +HCIVirtualTransportClass::HCIVirtualTransportClass() +{ +} + +HCIVirtualTransportClass::~HCIVirtualTransportClass() +{ +} + +int HCIVirtualTransportClass::begin() +{ + btStarted(); // this somehow stops the arduino ide from initializing bluedroid + + rec_buffer = xStreamBufferCreate(258, 1); + send_buffer = xStreamBufferCreate(258, 1); + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + +#if CONFIG_IDF_TARGET_ESP32 + bt_cfg.mode = ESP_BT_MODE_BLE; //original esp32 chip +#else +#if !(CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2) + bt_cfg.bluetooth_mode = ESP_BT_MODE_BLE; //different api for newer models +#endif +#endif + + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + esp_bt_controller_init(&bt_cfg); + esp_bt_controller_enable(ESP_BT_MODE_BLE); + xTaskCreatePinnedToCore(&bleTask, "bleTask", 2048, NULL, 5, &bleHandle, 0); + return 1; +} + +void HCIVirtualTransportClass::end() +{ + vStreamBufferDelete(rec_buffer); + vStreamBufferDelete(send_buffer); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + vTaskDelete(bleHandle); +} + +void HCIVirtualTransportClass::wait(unsigned long timeout) +{ + for (unsigned long start = (esp_timer_get_time() / 1000ULL); ((esp_timer_get_time() / 1000ULL) - start) < timeout;) { + if (available()) { + break; + } + } +} + +int HCIVirtualTransportClass::available() +{ + size_t bytes = xStreamBufferBytesAvailable(rec_buffer); + return bytes; +} + +// never called +int HCIVirtualTransportClass::peek() +{ + return -1; +} + +int HCIVirtualTransportClass::read() +{ + uint8_t c; + if(xStreamBufferReceive(rec_buffer, &c, 1, portMAX_DELAY)) { + return c; + } + return -1; +} + +size_t HCIVirtualTransportClass::write(const uint8_t* data, size_t length) +{ + size_t result = xStreamBufferSend(send_buffer,data,length,portMAX_DELAY); + return result; +} + +HCIVirtualTransportClass HCIVirtualTransport; + +HCITransportInterface& HCITransport = HCIVirtualTransport; + +#endif diff --git a/src/utility/HCIVirtualTransport.h b/src/utility/HCIVirtualTransport.h new file mode 100644 index 00000000..0da43cac --- /dev/null +++ b/src/utility/HCIVirtualTransport.h @@ -0,0 +1,50 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "HCITransport.h" +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/stream_buffer.h" + +#include "esp_bt.h" +#include "nvs_flash.h" + +#include "esp32-hal-bt.h" // this is needed to disable bluedroid + + +class HCIVirtualTransportClass : public HCITransportInterface { +public: + HCIVirtualTransportClass(); + virtual ~HCIVirtualTransportClass(); + + virtual int begin(); + virtual void end(); + + virtual void wait(unsigned long timeout); + + virtual int available(); + virtual int peek(); + virtual int read(); + + virtual size_t write(const uint8_t* data, size_t length); +}; \ No newline at end of file diff --git a/src/utility/HCIVirtualTransportAT.cpp b/src/utility/HCIVirtualTransportAT.cpp new file mode 100644 index 00000000..7b3a24a9 --- /dev/null +++ b/src/utility/HCIVirtualTransportAT.cpp @@ -0,0 +1,112 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined(ARDUINO_UNOR4_WIFI) + +#include "HCIVirtualTransportAT.h" + +extern ModemClass modem; + +HCIVirtualTransportATClass::HCIVirtualTransportATClass() +{ +} + +HCIVirtualTransportATClass::~HCIVirtualTransportATClass() +{ +} + +static RingBufferN<258> buf; + +int HCIVirtualTransportATClass::begin() +{ + // TODO: add this helper + //modem.debug(Serial); + buf.clear(); + //modem.debug(true); + std::string res = ""; + modem.begin(); + if (modem.write(std::string(PROMPT(_HCI_BEGIN)), res, CMD(_HCI_BEGIN))) { + return 1; + } + return 0; +} + +void HCIVirtualTransportATClass::end() +{ +} + +void HCIVirtualTransportATClass::wait(unsigned long timeout) +{ + std::string res = ""; + modem.write(std::string(PROMPT(_HCI_WAIT)), res, "%d\n\r", CMD_WRITE(_HCI_WAIT), timeout); +} + +int HCIVirtualTransportATClass::available() +{ + std::string res = ""; + if (buf.available()) { + return buf.available(); + } + if (modem.write(std::string(PROMPT(_HCI_AVAILABLE)), res, CMD_READ(_HCI_AVAILABLE))) { + return atoi(res.c_str()); + } + + return 0; +} + +// never called +int HCIVirtualTransportATClass::peek() +{ + return -1; +} + +int HCIVirtualTransportATClass::read() +{ + uint8_t c; + std::string res = ""; + if (buf.available()) { + return buf.read_char(); + } + modem.avoid_trim_results(); + modem.read_using_size(); + if (modem.write(std::string(PROMPT(_HCI_READ)), res, CMD(_HCI_READ))) { + for(int i = 0; i < res.size(); i++) { + buf.store_char((uint8_t)res[i]); + } + return buf.read_char(); + } + + return -1; +} + +size_t HCIVirtualTransportATClass::write(const uint8_t* data, size_t length) +{ + std::string res = ""; + modem.write_nowait(std::string(PROMPT(_HCI_WRITE)), res, "%s%d\r\n" , CMD_WRITE(_HCI_WRITE), length); + if(modem.passthrough(data, length)) { + return length; + } + return 0; +} + +HCIVirtualTransportATClass HCIVirtualTransportAT; + +HCITransportInterface& HCITransport = HCIVirtualTransportAT; + +#endif diff --git a/src/utility/HCIVirtualTransportAT.h b/src/utility/HCIVirtualTransportAT.h new file mode 100644 index 00000000..8e29801d --- /dev/null +++ b/src/utility/HCIVirtualTransportAT.h @@ -0,0 +1,42 @@ +/* + This file is part of the ArduinoBLE library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "HCITransport.h" +#include +#include +#include + +#include "WiFiS3.h" + +class HCIVirtualTransportATClass : public HCITransportInterface { +public: + HCIVirtualTransportATClass(); + virtual ~HCIVirtualTransportATClass(); + + virtual int begin(); + virtual void end(); + + virtual void wait(unsigned long timeout); + + virtual int available(); + virtual int peek(); + virtual int read(); + + virtual size_t write(const uint8_t* data, size_t length); +}; \ No newline at end of file diff --git a/src/utility/L2CAPSignaling.cpp b/src/utility/L2CAPSignaling.cpp index f650cab2..a7582681 100644 --- a/src/utility/L2CAPSignaling.cpp +++ b/src/utility/L2CAPSignaling.cpp @@ -18,15 +18,21 @@ */ #include "HCI.h" - +#include "ATT.h" +#include "btct.h" #include "L2CAPSignaling.h" - +#include "keyDistribution.h" +#include "bitDescriptions.h" #define CONNECTION_PARAMETER_UPDATE_REQUEST 0x12 #define CONNECTION_PARAMETER_UPDATE_RESPONSE 0x13 +//#define _BLE_TRACE_ + L2CAPSignalingClass::L2CAPSignalingClass() : _minInterval(0), - _maxInterval(0) + _maxInterval(0), + _supervisionTimeout(0), + _pairing_enabled(1) { } @@ -36,7 +42,7 @@ L2CAPSignalingClass::~L2CAPSignalingClass() void L2CAPSignalingClass::addConnection(uint16_t handle, uint8_t role, uint8_t /*peerBdaddrType*/, uint8_t /*peerBdaddr*/[6], uint16_t interval, - uint16_t /*latency*/, uint16_t /*supervisionTimeout*/, + uint16_t /*latency*/, uint16_t supervisionTimeout, uint8_t /*masterClockAccuracy*/) { if (role != 1) { @@ -44,28 +50,38 @@ void L2CAPSignalingClass::addConnection(uint16_t handle, uint8_t role, uint8_t / return; } - if (!_minInterval || !_maxInterval) { - // no connection intervale to request - return; + bool updateParameters = false; + uint16_t updatedMinInterval = interval; + uint16_t updatedMaxInterval = interval; + uint16_t updatedSupervisionTimeout = supervisionTimeout; + + if (_minInterval && _maxInterval) { + if (interval < _minInterval || interval > _maxInterval) { + updatedMinInterval = _minInterval; + updatedMaxInterval = _maxInterval; + updateParameters = true; + } } - if (interval >= _minInterval && interval <= _maxInterval) { - // all good, within interval range - return; + if (_supervisionTimeout && supervisionTimeout != _supervisionTimeout) { + updatedSupervisionTimeout = _supervisionTimeout; + updateParameters = true; } - struct __attribute__ ((packed)) L2CAPConnectionParameterUpdateRequest { - uint8_t code; - uint8_t identifier; - uint16_t length; - uint16_t minInterval; - uint16_t maxInterval; - uint16_t latency; - uint16_t supervisionTimeout; - } request = { CONNECTION_PARAMETER_UPDATE_REQUEST, 0x01, 8, - _minInterval, _maxInterval, 0x0000, 0x00c8 }; + if (updateParameters) { + struct __attribute__ ((packed)) L2CAPConnectionParameterUpdateRequest { + uint8_t code; + uint8_t identifier; + uint16_t length; + uint16_t minInterval; + uint16_t maxInterval; + uint16_t latency; + uint16_t supervisionTimeout; + } request = { CONNECTION_PARAMETER_UPDATE_REQUEST, 0x01, 8, + updatedMinInterval, updatedMaxInterval, 0x0000, updatedSupervisionTimeout }; - HCI.sendAclPkt(handle, SIGNALING_CID, sizeof(request), &request); + HCI.sendAclPkt(handle, SIGNALING_CID, sizeof(request), &request); + } } void L2CAPSignalingClass::handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) @@ -97,6 +113,328 @@ void L2CAPSignalingClass::handleData(uint16_t connectionHandle, uint8_t dlen, ui connectionParameterUpdateResponse(connectionHandle, identifier, length, data); } } +void L2CAPSignalingClass::handleSecurityData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]) +{ + struct __attribute__ ((packed)) L2CAPSignalingHdr { + uint8_t code; + uint8_t data[64]; + } *l2capSignalingHdr = (L2CAPSignalingHdr*)data; +#ifdef _BLE_TRACE_ + Serial.print("dlen: "); + Serial.println(dlen); +#else + (void)dlen; +#endif + uint8_t code = l2capSignalingHdr->code; + +#ifdef _BLE_TRACE_ + Serial.print("handleSecurityData: code: 0x"); + Serial.println(code, HEX); + Serial.print("rx security:"); + btct.printBytes(data,dlen); +#endif + if (code == CONNECTION_PAIRING_REQUEST) { + + if (isPairingEnabled()){ + if (_pairing_enabled >= 2) _pairing_enabled = 0; // 2 = pair once only + + // 0x1 + struct __attribute__ ((packed)) PairingRequest { + uint8_t ioCapability; + uint8_t oobDataFlag; + uint8_t authReq; + uint8_t maxEncSize; + uint8_t initiatorKeyDistribution; + uint8_t responderKeyDistribution; + } *pairingRequest = (PairingRequest*)l2capSignalingHdr->data; + + KeyDistribution responseKD = KeyDistribution(); + responseKD.setIdKey(true); + + ATT.remoteKeyDistribution = responseKD;// KeyDistribution(pairingRequest->initiatorKeyDistribution); + ATT.localKeyDistribution = responseKD; //KeyDistribution(pairingRequest->responderKeyDistribution); + // KeyDistribution rkd(pairingRequest->responderKeyDistribution); + AuthReq req(pairingRequest->authReq); +#ifdef _BLE_TRACE_ + Serial.print("Req has properties: "); + Serial.print(req.Bonding()?"bonding, ":"no bonding, "); + Serial.print(req.CT2()?"CT2, ":"no CT2, "); + Serial.print(req.KeyPress()?"KeyPress, ":"no KeyPress, "); + Serial.print(req.MITM()?"MITM, ":"no MITM, "); + Serial.print(req.SC()?"SC, ":"no SC, "); +#endif + + uint8_t peerIOCap[3]; + peerIOCap[0] = pairingRequest->authReq; + peerIOCap[1] = pairingRequest->oobDataFlag; + peerIOCap[2] = pairingRequest->ioCapability; + ATT.setPeerIOCap(connectionHandle, peerIOCap); + ATT.setPeerEncryption(connectionHandle, ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::PAIRING_REQUEST); +#ifdef _BLE_TRACE_ + Serial.print("Peer encryption : 0b"); + Serial.println(ATT.getPeerEncryption(connectionHandle), BIN); +#endif + struct __attribute__ ((packed)) PairingResponse { + uint8_t code; + uint8_t ioCapability; + uint8_t oobDataFlag; + uint8_t authReq; + uint8_t maxEncSize; + uint8_t initiatorKeyDistribution; + uint8_t responderKeyDistribution; + } response = { CONNECTION_PAIRING_RESPONSE, HCI.localIOCap(), 0, HCI.localAuthreq().getOctet(), 0x10, responseKD.getOctet(), responseKD.getOctet()}; + + HCI.sendAclPkt(connectionHandle, SECURITY_CID, sizeof(response), &response); + + } else { + // Pairing not enabled + uint8_t ret[2] = {CONNECTION_PAIRING_FAILED, 0x05}; // reqect pairing + HCI.sendAclPkt(connectionHandle, SECURITY_CID, sizeof(ret), ret); + ATT.setPeerEncryption(connectionHandle, NO_ENCRYPTION); + } + } + else if (code == CONNECTION_PAIRING_RANDOM) + { + struct __attribute__ ((packed)) PairingRandom { + uint8_t Na[16]; + } *pairingRandom = (PairingRandom*)l2capSignalingHdr->data; + for(int i=0; i<16; i++){ + HCI.Na[15-i] = pairingRandom->Na[i]; + } +#ifdef _BLE_TRACE_ + Serial.println("[Info] Pairing random."); +#endif + struct __attribute__ ((packed)) PairingResponse { + uint8_t code; + uint8_t Nb[16]; + } response = { CONNECTION_PAIRING_RANDOM, 0}; + for(int i=0; i< 16; i++) response.Nb[15-i] = HCI.Nb[i]; + + HCI.sendAclPkt(connectionHandle, SECURITY_CID, sizeof(response), &response); + + // We now have all needed for compare value + uint8_t g2Result[4]; + uint8_t U[32]; + uint8_t V[32]; + + for(int i=0; i<32; i++){ + U[31-i] = HCI.remotePublicKeyBuffer[i]; + V[31-i] = HCI.localPublicKeyBuffer[i]; + } + + btct.g2(U,V,HCI.Na,HCI.Nb, g2Result); + uint32_t result = 0; + for(int i=0; i<4; i++) result += g2Result[3-i] << 8*i; + +#ifdef _BLE_TRACE_ + Serial.print("U : "); + btct.printBytes(U,32); + Serial.print("V : "); + btct.printBytes(V,32); + Serial.print("X : "); + btct.printBytes(HCI.Na,16); + Serial.print("Y : "); + btct.printBytes(HCI.Nb,16); + Serial.print("g2res : "); + btct.printBytes(g2Result,4); + Serial.print("Result : "); + Serial.println(result); +#endif + + if(HCI._displayCode!=0){ + HCI._displayCode(result%1000000); + } + if(HCI._binaryConfirmPairing!=0){ + if(!HCI._binaryConfirmPairing()){ +#ifdef _BLE_TRACE_ + Serial.println("User rejection"); +#endif + uint8_t rejection[2]; + rejection[0] = CONNECTION_PAIRING_FAILED; + rejection[1] = 0x0C; // Numeric comparison failed + HCI.sendAclPkt(connectionHandle, SECURITY_CID, 2, rejection); + ATT.setPeerEncryption(connectionHandle, PEER_ENCRYPTION::NO_ENCRYPTION); + }else{ +#ifdef _BLE_TRACE_ + Serial.println("User did confirm"); +#endif + } + } + } + else if (code == CONNECTION_PAIRING_RESPONSE) + { + } + else if(code == CONNECTION_PAIRING_FAILED) + { +#ifdef _BLE_TRACE_ + struct __attribute__ ((packed)) PairingFailed + { + uint8_t code; + uint8_t reason; + } *pairingFailed = (PairingFailed*)data; + Serial.print("Pairing failed with code: 0x"); + Serial.println(pairingFailed->reason,HEX); +#endif + ATT.setPeerEncryption(connectionHandle, PEER_ENCRYPTION::NO_ENCRYPTION); + } + else if (code == CONNECTION_IDENTITY_INFORMATION){ + struct __attribute__ ((packed)) IdentityInformation { + uint8_t code; + uint8_t PeerIRK[16]; + } *identityInformation = (IdentityInformation*)data; + for(int i=0; i<16; i++) ATT.peerIRK[15-i] = identityInformation->PeerIRK[i]; +#ifdef _BLE_TRACE_ + Serial.println("Saved peer IRK"); +#endif + } + else if (code == CONNECTION_IDENTITY_ADDRESS){ + struct __attribute__ ((packed)) IdentityAddress { + uint8_t code; + uint8_t addressType; + uint8_t address[6]; + } *identityAddress = (IdentityAddress*)data; + // we can save this information now. + uint8_t peerAddress[6]; + for(int i=0; i<6; i++) peerAddress[5-i] = identityAddress->address[i]; + + HCI.saveNewAddress(identityAddress->addressType, peerAddress, ATT.peerIRK, ATT.localIRK); + if(HCI._storeLTK!=0){ + HCI._storeLTK(peerAddress, HCI.LTK); + } + } + else if (code == CONNECTION_PAIRING_PUBLIC_KEY){ + /// Received a public key + struct __attribute__ ((packed)) ConnectionPairingPublicKey { + uint8_t x[32]; + uint8_t y[32]; + } *connectionPairingPublicKey = (ConnectionPairingPublicKey*)l2capSignalingHdr->data; + struct __attribute__ ((packed)) GenerateDHKeyCommand { + uint8_t x[32]; + uint8_t y[32]; + } generateDHKeyCommand = { + {0x00}, + {0x00}, + }; + memcpy(generateDHKeyCommand.x,connectionPairingPublicKey->x,32); + memcpy(generateDHKeyCommand.y,connectionPairingPublicKey->y,32); + + if(ATT.setPeerEncryption(connectionHandle, ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::REQUESTED_ENCRYPTION)){ +#ifdef _BLE_TRACE_ + Serial.println("[Info] Pairing public key"); + Serial.println("Requested encryption stored."); +#endif + }else{ +#ifdef _BLE_TRACE_ + Serial.println("[Info] Pairing public key"); + Serial.print("Failed to store encryption request with handle: 0x"); + Serial.println(connectionHandle,HEX); +#endif + } + + memcpy(HCI.remotePublicKeyBuffer,&generateDHKeyCommand,sizeof(generateDHKeyCommand)); + HCI.sendCommand( (OGF_LE_CTL << 10 )| LE_COMMAND::READ_LOCAL_P256, 0); + } + else if(code == CONNECTION_PAIRING_DHKEY_CHECK) + { + uint8_t RemoteDHKeyCheck[16]; + for(int i=0; i<16; i++) RemoteDHKeyCheck[15-i] = l2capSignalingHdr->data[i]; + + +#ifdef _BLE_TRACE_ + Serial.println("[Info] DH Key check"); + Serial.print("Remote DHKey Check: "); + btct.printBytes(RemoteDHKeyCheck, 16); +#endif + + + + uint8_t encryptionState = ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::RECEIVED_DH_CHECK; + ATT.setPeerEncryption(connectionHandle, encryptionState); + if((encryptionState & PEER_ENCRYPTION::DH_KEY_CALULATED) == 0){ +#ifdef _BLE_TRACE_ + Serial.println("DHKey not yet ready, will calculate f5, f6 later"); +#endif + // store RemoteDHKeyCheck for later check + memcpy(HCI.remoteDHKeyCheckBuffer,RemoteDHKeyCheck,16); + + } else { + // We've already calculated the DHKey so we can calculate our check and send it. + smCalculateLTKandConfirm(connectionHandle, RemoteDHKeyCheck); + + } + } +} + +void L2CAPSignalingClass::smCalculateLTKandConfirm(uint16_t handle, uint8_t expectedEa[]) +{ // Authentication stage 2: LTK Calculation + + uint8_t localAddress[7]; + uint8_t remoteAddress[7]; + ATT.getPeerAddrWithType(handle, remoteAddress); + + HCI.readBdAddr(); + memcpy(&localAddress[1],HCI.localAddr,6); + localAddress[0] = 0; // IOT 33 uses a static address // TODO: confirm for Nano BLE + + // Compute the LTK and MacKey + uint8_t MacKey[16]; + btct.f5(HCI.DHKey, HCI.Na, HCI.Nb, remoteAddress, localAddress, MacKey, HCI.LTK); + + // Compute Ea and Eb + uint8_t Ea[16]; + uint8_t Eb[16]; + uint8_t R[16]; + uint8_t MasterIOCap[3]; + uint8_t SlaveIOCap[3] = {HCI.localAuthreq().getOctet(), 0x0, HCI.localIOCap()}; + + ATT.getPeerIOCap(handle, MasterIOCap); + for(int i=0; i<16; i++) R[i] = 0; + + btct.f6(MacKey, HCI.Na,HCI.Nb,R, MasterIOCap, remoteAddress, localAddress, Ea); + btct.f6(MacKey, HCI.Nb,HCI.Na,R, SlaveIOCap, localAddress, remoteAddress, Eb); + +#ifdef _BLE_TRACE_ + Serial.println("Calculate and confirm LTK via f5, f6:"); + Serial.print("DHKey : "); btct.printBytes(HCI.DHKey,32); + Serial.print("Na : "); btct.printBytes(HCI.Na,16); + Serial.print("Nb : "); btct.printBytes(HCI.Nb,16); + Serial.print("MacKey : "); btct.printBytes(MacKey,16); + Serial.print("LTK : "); btct.printBytes(HCI.LTK,16); + Serial.print("Expected Ea: "); btct.printBytes(expectedEa, 16); + Serial.print("Ea : "); btct.printBytes(Ea, 16); + Serial.print("Eb : "); btct.printBytes(Eb,16); + Serial.print("Local Addr : "); btct.printBytes(localAddress, 7); + Serial.print("LocalIOCap : "); btct.printBytes(SlaveIOCap, 3); + Serial.print("MasterAddr : "); btct.printBytes(remoteAddress, 7); + Serial.print("MasterIOCAP: "); btct.printBytes(MasterIOCap, 3); +#endif + + // Check if Ea = expectedEa + if (memcmp(Ea, expectedEa, 16) == 0){ + // Check ok + // Send our confirmation value to complete authentication stage 2 + uint8_t ret[17]; + ret[0] = CONNECTION_PAIRING_DHKEY_CHECK; + for(uint32_t i=0; i 0; +} + void L2CAPSignalingClass::connectionParameterUpdateRequest(uint16_t handle, uint8_t identifier, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) L2CAPConnectionParameterUpdateRequest { @@ -135,6 +487,12 @@ void L2CAPSignalingClass::connectionParameterUpdateRequest(uint16_t handle, uint } } + if (_supervisionTimeout) { + if (request->supervisionTimeout != _supervisionTimeout) { + response.value = 0x0001; // reject + } + } + HCI.sendAclPkt(handle, SIGNALING_CID, sizeof(response), &response); if (response.value == 0x0000) { @@ -146,4 +504,7 @@ void L2CAPSignalingClass::connectionParameterUpdateResponse(uint16_t /*handle*/, { } -L2CAPSignalingClass L2CAPSignaling; +#if !defined(FAKE_L2CAP) +L2CAPSignalingClass L2CAPSignalingObj; +L2CAPSignalingClass& L2CAPSignaling = L2CAPSignalingObj; +#endif diff --git a/src/utility/L2CAPSignaling.h b/src/utility/L2CAPSignaling.h index 349ffb85..26042167 100644 --- a/src/utility/L2CAPSignaling.h +++ b/src/utility/L2CAPSignaling.h @@ -23,32 +23,74 @@ #include #define SIGNALING_CID 0x0005 +#define SECURITY_CID 0x0006 + + +#define CONNECTION_PAIRING_REQUEST 0x01 +#define CONNECTION_PAIRING_RESPONSE 0x02 +#define CONNECTION_PAIRING_CONFIRM 0x03 +#define CONNECTION_PAIRING_RANDOM 0x04 +#define CONNECTION_PAIRING_FAILED 0x05 +#define CONNECTION_ENCRYPTION_INFORMATION 0x06 +#define CONNECTION_MASTER_IDENTIFICATION 0x07 +#define CONNECTION_IDENTITY_INFORMATION 0x08 +#define CONNECTION_IDENTITY_ADDRESS 0x09 +#define CONNECTION_SIGNING_INFORMATION 0x0A +#define CONNECTION_SECURITY_REQUEST 0x0B +#define CONNECTION_PAIRING_PUBLIC_KEY 0x0C +#define CONNECTION_PAIRING_DHKEY_CHECK 0x0D +#define CONNECTION_PAIRING_KEYPRESS 0x0E + +#define IOCAP_DISPLAY_ONLY 0x00 +#define IOCAP_DISPLAY_YES_NO 0x01 +#define IOCAP_KEYBOARD_ONLY 0x02 +#define IOCAP_NO_INPUT_NO_OUTPUT 0x03 +#define IOCAP_KEYBOARD_DISPLAY 0x04 + + +#define LOCAL_AUTHREQ 0b00101101 +// #define LOCAL_IOCAP IOCAP_DISPLAY_ONLY // will use JustWorks pairing class L2CAPSignalingClass { public: L2CAPSignalingClass(); virtual ~L2CAPSignalingClass(); - void addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrType, + virtual void addConnection(uint16_t handle, uint8_t role, uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint16_t interval, uint16_t latency, uint16_t supervisionTimeout, uint8_t masterClockAccuracy); - void handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + virtual void handleData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); + + virtual void handleSecurityData(uint16_t connectionHandle, uint8_t dlen, uint8_t data[]); - void removeConnection(uint8_t handle, uint16_t reason); + virtual void removeConnection(uint8_t handle, uint16_t reason); + + virtual void setConnectionInterval(uint16_t minInterval, uint16_t maxInterval); + + virtual void setSupervisionTimeout(uint16_t supervisionTimeout); + + virtual void setPairingEnabled(uint8_t enabled); + virtual bool isPairingEnabled(); + + + + virtual void smCalculateLTKandConfirm(uint16_t handle, uint8_t expectedEa[]); - void setConnectionInterval(uint16_t minInterval, uint16_t maxInterval); private: - void connectionParameterUpdateRequest(uint16_t handle, uint8_t identifier, uint8_t dlen, uint8_t data[]); - void connectionParameterUpdateResponse(uint16_t handle, uint8_t identifier, uint8_t dlen, uint8_t data[]); + virtual void connectionParameterUpdateRequest(uint16_t handle, uint8_t identifier, uint8_t dlen, uint8_t data[]); + virtual void connectionParameterUpdateResponse(uint16_t handle, uint8_t identifier, uint8_t dlen, uint8_t data[]); + private: uint16_t _minInterval; uint16_t _maxInterval; + uint16_t _supervisionTimeout; + uint8_t _pairing_enabled; }; -extern L2CAPSignalingClass L2CAPSignaling; +extern L2CAPSignalingClass& L2CAPSignaling; #endif diff --git a/src/utility/bitDescriptions.cpp b/src/utility/bitDescriptions.cpp new file mode 100644 index 00000000..bf896bc1 --- /dev/null +++ b/src/utility/bitDescriptions.cpp @@ -0,0 +1,30 @@ +#include "bitDescriptions.h" + + +#define BONDING_BIT 0b00000001 +#define MITM_BIT 0b00000100 +#define SC_BIT 0b00001000 +#define KEYPRESS_BIT 0b00010000 +#define CT2_BIT 0b00100000 + + +AuthReq::AuthReq(){} +AuthReq::AuthReq(uint8_t octet):_octet(octet){} +bool AuthReq::Bonding(){ return (_octet & BONDING_BIT)>0;} +bool AuthReq::MITM(){ return (_octet & MITM_BIT)>0;} +bool AuthReq::SC(){ return (_octet & SC_BIT)>0;} +bool AuthReq::KeyPress(){ return (_octet & KEYPRESS_BIT)>0;} +bool AuthReq::CT2(){ return (_octet & CT2_BIT)>0;} + + +void AuthReq::setBonding(bool state) { _octet= state? _octet|BONDING_BIT : _octet&~BONDING_BIT;} +void AuthReq::setMITM(bool state) { _octet= state? _octet|MITM_BIT : _octet&~MITM_BIT;} +void AuthReq::setSC(bool state){ _octet= state? _octet|SC_BIT : _octet&~SC_BIT;} +void AuthReq::setKeyPress(bool state){ _octet= state? _octet|KEYPRESS_BIT : _octet&~KEYPRESS_BIT;} +void AuthReq::setCT2(bool state){ _octet= state? _octet|CT2_BIT : _octet&~CT2_BIT;} + +uint8_t _octet; + + +void AuthReq::setOctet( uint8_t octet){_octet = octet;} +uint8_t AuthReq::getOctet() {return _octet;} diff --git a/src/utility/bitDescriptions.h b/src/utility/bitDescriptions.h new file mode 100644 index 00000000..6d32c52a --- /dev/null +++ b/src/utility/bitDescriptions.h @@ -0,0 +1,41 @@ +#ifndef _BIT_DESCRIPTIONS_H_ +#define _BIT_DESCRIPTIONS_H_ +#include + +class AuthReq{ +public: + AuthReq(); + AuthReq(uint8_t octet); + void setOctet( uint8_t octet); + uint8_t getOctet(); + + + // The Bonding_Flags field is a 2-bit field that indicates the type of bonding being requested by the initiating device + bool Bonding(); + // The MITM field is a 1-bit flag that is set to one if the device is requesting MITM protection + bool MITM(); + // The SC field is a 1 bit flag. If LE Secure Connections pairing is supported by the device, then the SC field shall be set to 1, otherwise it shall be set to 0. + bool SC(); + // The keypress field is a 1-bit flag that is used only in the Passkey Entry protocol and shall be ignored in other protocols. + bool KeyPress(); + // The CT2 field is a 1-bit flag that shall be set to 1 upon transmission to indicate support for the h7 function. + bool CT2(); + + void setBonding(bool state); + void setMITM(bool state); + void setSC(bool state); + void setKeyPress(bool state); + void setCT2(bool state); +private: + uint8_t _octet; +}; + +enum IOCap { + DisplayOnly, + DisplayYesNo, + KeyboardOnly, + NoInputNoOutput, + KeyboardDisplay +}; + +#endif \ No newline at end of file diff --git a/src/utility/btct.cpp b/src/utility/btct.cpp new file mode 100644 index 00000000..77a5e818 --- /dev/null +++ b/src/utility/btct.cpp @@ -0,0 +1,386 @@ +#include "btct.h" +#include +#include "HCI.h" +#include "ArduinoBLE.h" +BluetoothCryptoToolbox::BluetoothCryptoToolbox(){} +// In step 1, AES-128 with key K is applied to an all-zero input block. +// In step 2, K1 is derived through the following operation: +// If the most significant bit of L is equal to 0, K1 is the left-shift +// of L by 1 bit. +// Otherwise, K1 is the exclusive-OR of const_Rb and the left-shift of L +// by 1 bit. +// In step 3, K2 is derived through the following operation: +// If the most significant bit of K1 is equal to 0, K2 is the left-shift +// of K1 by 1 bit. +// Otherwise, K2 is the exclusive-OR of const_Rb and the left-shift of +// K1 by 1 bit. +// In step 4, (K1,K2) := Generate_Subkey(K) is returned. +unsigned char const_Rb[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87 + }; + +#define DHKEY_LENGTH 32 +#define N_LEN 16 +#define ADDR_LEN 6 +#define LEN_LTK 16 +#define LEN_MAC_KEY 16 + +void BluetoothCryptoToolbox::printBytes(uint8_t bytes[], uint8_t length){ +#ifdef _BLE_TRACE_ + for(int i=0; i0){ + Serial.print(", 0x"); + }else{ + Serial.print("0x"); + } + Serial.print(bytes[i],HEX); + } + Serial.print('\n'); +#endif +} + + +int BluetoothCryptoToolbox::f5(uint8_t DHKey[],uint8_t N_master[], uint8_t N_slave[], + uint8_t BD_ADDR_master[], uint8_t BD_ADDR_slave[], uint8_t MacKey[], uint8_t LTK[]) +{ + uint8_t SALT[16] = {0x6C, 0x88, 0x83, 0x91, 0xAA, 0xF5, 0xA5, 0x38, 0x60, 0x37, 0x0B, 0xDB, 0x5A, 0x60, 0x83, 0xBE}; + uint8_t keyID[4] = {0x62, 0x74, 0x6c, 0x65}; + uint8_t length[2]; + length[0] = 0x01; + length[1] = 0x00; +#ifdef _BLE_TRACE_ + Serial.print("Starting f5 calculation"); + Serial.print("Using DHKey: "); + printBytes(DHKey, DHKEY_LENGTH); + Serial.print("Using N_Master: "); + printBytes(N_master, N_LEN); + Serial.print("Using N_Slave: "); + printBytes(N_slave, N_LEN); + + Serial.println("Using BD_ADDR_MASTER: "); + printBytes(BD_ADDR_master, ADDR_LEN); + Serial.println("Using BD_ADDR_SLAVE: "); + printBytes(BD_ADDR_slave, ADDR_LEN); +#endif + + uint8_t T[16]; + + struct __attribute__ ((packed)) CmacInput + { + uint8_t counter; + uint8_t keyID[4]; + uint8_t N1[16]; + uint8_t N2[16]; + uint8_t A1[7]; + uint8_t A2[7]; + uint8_t length[2]; + } cmacInput = {0,{0},{0},{0},{0},{0},{0}}; + cmacInput.counter = 0; + memcpy(cmacInput.keyID, keyID, 4); + memcpy(cmacInput.N1,N_master,16); + memcpy(cmacInput.N2,N_slave,16); + memcpy(cmacInput.A1,BD_ADDR_master,7); + memcpy(cmacInput.A2,BD_ADDR_slave,7); + memcpy(cmacInput.length,length,2); + AES_CMAC(SALT, DHKey, 32, T); + + AES_CMAC(T, (uint8_t*)&cmacInput,sizeof(cmacInput), MacKey); + cmacInput.counter=1; + AES_CMAC(T, (uint8_t*)&cmacInput, sizeof(cmacInput), LTK); + + return 1; +} +int BluetoothCryptoToolbox::f6(uint8_t W[], uint8_t N1[],uint8_t N2[],uint8_t R[], uint8_t IOCap[], uint8_t A1[], uint8_t A2[], uint8_t Ex[]) +{ + struct __attribute__ ((packed)) F6Input + { + uint8_t N1[16]; + uint8_t N2[16]; + uint8_t R[16]; + uint8_t IOCap[3]; + uint8_t A1[7]; + uint8_t A2[7]; + } f6Input = {{0},{0},{0},{0},{0},{0}}; + + memcpy(f6Input.N1, N1, 16); + memcpy(f6Input.N2, N2, 16); + memcpy(f6Input.R, R, 16); + memcpy(f6Input.IOCap, IOCap, 3); + memcpy(f6Input.A1, A1, 7); + memcpy(f6Input.A2, A2, 7); + + + AES_CMAC(W, (uint8_t*)&f6Input, sizeof(f6Input),Ex); + return 1; +} +// AES_CMAC from RFC +int BluetoothCryptoToolbox::ah(uint8_t k[16], uint8_t r[3], uint8_t* result) +{ + uint8_t r_[16]; + int i=0; + for(i=0; i<16; i++) r_[i] = 0; + for(i=0; i<3; i++) r_[i+13] = r[i]; + uint8_t intermediate[16]; + AES_128(k,r_,intermediate); + for(i=0; i<3; i++){ + result[i] = intermediate[i+13]; + } + return 1; +} +void BluetoothCryptoToolbox::testAh() +{ + uint8_t irk[16] = {0xec,0x02,0x34,0xa3,0x57,0xc8,0xad,0x05,0x34,0x10,0x10,0xa6,0x0a,0x39,0x7d,0x9b}; + uint8_t expected_final[3] = {0x0d,0xfb,0xaa}; + uint8_t ourResult[3]; + ah(irk, expected_final, ourResult); + +#ifdef _BLE_TRACE_ + Serial.print("Expected : "); + printBytes(&expected_final[3], 3); + Serial.print("Actual : "); + printBytes(ourResult, 3); +#endif +} + +int BluetoothCryptoToolbox::g2(uint8_t U[], uint8_t V[], uint8_t X[], uint8_t Y[], uint8_t out[4]) +{ + struct __attribute__ ((packed)) CmacInput { + uint8_t U[32]; + uint8_t V[32]; + uint8_t Y[16]; + } cmacInput= {{0},{0},{0}}; + memcpy(cmacInput.U,U,32); + memcpy(cmacInput.V,V,32); + memcpy(cmacInput.Y,Y,16); + uint8_t intermediate[16]; + AES_CMAC(X,(uint8_t*)&cmacInput,sizeof(CmacInput),intermediate); + memcpy(out,&intermediate[12],4); + return 1; +} +void BluetoothCryptoToolbox::testg2(){ + uint8_t U[32] = {0x20,0xb0,0x03,0xd2,0xf2,0x97,0xbe,0x2c,0x5e,0x2c,0x83,0xa7,0xe9,0xf9,0xa5,0xb9,0xef,0xf4,0x91,0x11,0xac,0xf4,0xfd,0xdb,0xcc,0x03,0x01,0x48,0x0e,0x35,0x9d,0xe6}; + uint8_t V[32] = {0x55,0x18,0x8b,0x3d,0x32,0xf6,0xbb,0x9a,0x90,0x0a,0xfc,0xfb,0xee,0xd4,0xe7,0x2a,0x59,0xcb,0x9a,0xc2,0xf1,0x9d,0x7c,0xfb,0x6b,0x4f,0xdd,0x49,0xf4,0x7f,0xc5,0xfd}; + uint8_t X[16] = {0xd5,0xcb,0x84,0x54,0xd1,0x77,0x73,0x3e,0xff,0xff,0xb2,0xec,0x71,0x2b,0xae,0xab}; + uint8_t Y[16] = {0xa6,0xe8,0xe7,0xcc,0x25,0xa7,0x5f,0x6e,0x21,0x65,0x83,0xf7,0xff,0x3d,0xc4,0xcf}; + uint8_t out[4]; + + uint32_t expected = 0; + g2(U,V,X,Y,out); + uint32_t result = 0; + for(int i=0; i<4; i++) result += out[i] << 8*i; + +#ifdef _BLE_TRACE_ + Serial.print("Expected : "); + Serial.println(expected); + Serial.print("Result : "); + Serial.println(result); + Serial.println(); +#endif +} + +void BluetoothCryptoToolbox::AES_CMAC ( unsigned char *key, unsigned char *input, int length, + unsigned char *mac ) +{ + unsigned char X[16],Y[16], M_last[16], padded[16]; + unsigned char K1[16], K2[16]; + int n, i, flag; + generateSubkey(key,K1,K2); + + n = (length+15) / 16; /* n is number of rounds */ + + if ( n == 0 ) { + n = 1; + flag = 0; + } else { + if ( (length%16) == 0 ) { /* last block is a complete block */ + flag = 1; + } else { /* last block is not complete block */ + flag = 0; + } + } + + if ( flag ) { /* last block is complete block */ + xor_128(&input[16*(n-1)],K1,M_last); + } else { + padding(&input[16*(n-1)],padded,length%16); + xor_128(padded,K2,M_last); + } + + for ( i=0; i<16; i++ ) X[i] = 0; + for ( i=0; i=0; i-- ) { + output[i] = input[i] << 1; + output[i] |= overflow; + overflow = (input[i] & 0x80)?1:0; + } + return; +} +// From RFC +void BluetoothCryptoToolbox::xor_128(unsigned char *a, unsigned char *b, unsigned char *out) +{ + int i; + for (i=0;i<16; i++) + { + out[i] = a[i] ^ b[i]; + } +} +BluetoothCryptoToolbox btct; \ No newline at end of file diff --git a/src/utility/btct.h b/src/utility/btct.h new file mode 100644 index 00000000..08f8f192 --- /dev/null +++ b/src/utility/btct.h @@ -0,0 +1,30 @@ +#ifndef _BTCT_H_ +#define _BTCT_H_ +#include + +// Implementation of functions defined in BTLE standard +class BluetoothCryptoToolbox{ +public: + BluetoothCryptoToolbox(); + void printBytes(uint8_t bytes[], uint8_t length); + void generateSubkey(uint8_t* K, uint8_t* K1, uint8_t* K2); + void AES_CMAC ( unsigned char *key, unsigned char *input, int length, + unsigned char *mac ); + int f5(uint8_t DHKey[],uint8_t N_master[], uint8_t N_slave[], + uint8_t BD_ADDR_master[], uint8_t BD_ADDR_slave[], uint8_t MacKey[], uint8_t LTK[]); + int f6(uint8_t W[], uint8_t N1[],uint8_t N2[],uint8_t R[], uint8_t IOCap[], uint8_t A1[], uint8_t A2[], uint8_t Ex[]); + int g2(uint8_t U[], uint8_t V[], uint8_t X[], uint8_t Y[], uint8_t out[4]); + int ah(uint8_t k[16], uint8_t r[3], uint8_t result[3]); + void test(); + void testF5(); + void testF6(); + void testAh(); + void testg2(); +private: + int AES_128(uint8_t key[], uint8_t data_in[], uint8_t data_out[]); + void leftshift_onebit(unsigned char *input,unsigned char *output); + void xor_128(unsigned char *a, unsigned char *b, unsigned char *out); + void padding ( unsigned char *lastb, unsigned char *pad, int length ); +}; +extern BluetoothCryptoToolbox btct; +#endif \ No newline at end of file diff --git a/src/utility/keyDistribution.cpp b/src/utility/keyDistribution.cpp new file mode 100644 index 00000000..f754366c --- /dev/null +++ b/src/utility/keyDistribution.cpp @@ -0,0 +1,24 @@ +#include "keyDistribution.h" + +KeyDistribution::KeyDistribution():_octet(0){} +KeyDistribution::KeyDistribution(uint8_t octet):_octet(octet){} + +#define ENCKEY 0b00000001 +#define IDKEY 0b00000010 +#define SIGNKEY 0b00000100 +#define LINKKEY 0b00001000 +void KeyDistribution::setOctet( uint8_t octet){_octet = octet;} +uint8_t KeyDistribution::getOctet() {return _octet;} +// Ignored when SMP is on LE transport +bool KeyDistribution::EncKey(){ return (_octet & ENCKEY)>0;} +// Device shall distribute IRK using Identity information command followed by its address using Identity address information +bool KeyDistribution::IdKey(){ return (_octet & IDKEY)>0;} +// Device shall distribute CSRK using signing information command +bool KeyDistribution::SignKey(){ return (_octet & SIGNKEY)>0;} +// Device would like to derive BR/EDR from LTK +bool KeyDistribution::LinkKey(){ return (_octet & LINKKEY)>0;} + +void KeyDistribution::setEncKey(bool state) { _octet= state? _octet|ENCKEY : _octet&~ENCKEY;} +void KeyDistribution::setIdKey(bool state) { _octet= state? _octet|IDKEY : _octet&~IDKEY;} +void KeyDistribution::setSignKey(bool state){ _octet= state? _octet|SIGNKEY : _octet&~SIGNKEY;} +void KeyDistribution::setLinkKey(bool state){ _octet= state? _octet|LINKKEY : _octet&~LINKKEY;} \ No newline at end of file diff --git a/src/utility/keyDistribution.h b/src/utility/keyDistribution.h new file mode 100644 index 00000000..d78fcc1a --- /dev/null +++ b/src/utility/keyDistribution.h @@ -0,0 +1,29 @@ +#ifndef _KEY_DISTRIBUTION_H_ +#define _KEY_DISTRIBUTION_H_ +#include + +class KeyDistribution{ +public: + KeyDistribution(); + KeyDistribution(uint8_t octet); + void setOctet( uint8_t octet); + uint8_t getOctet(); + // Ignored when SMP is on LE transport + bool EncKey(); + // Device shall distribute IRK using Identity information command followed by its address using Identity address information + bool IdKey(); + // Device shall distribute CSRK using signing information command + bool SignKey(); + // Device would like to derive BR/EDR from LTK + bool LinkKey(); + + void setEncKey(bool state); + void setIdKey(bool state); + void setSignKey(bool state); + void setLinkKey(bool state); +private: + uint8_t _octet; + // 1. IRK by the slave2. BD ADDR by the slave3. CSRK by the slave4. IRK by the master5. BD_ADDR by the master6. CSRK by the master +}; + +#endif \ No newline at end of file