├── .ci ├── book.sh ├── config.json └── doc.sh ├── .dir-locals.el ├── .github ├── rodbot.yaml └── workflows │ ├── ci.yaml │ ├── doc.yml │ ├── release_trouble_host.yaml │ ├── release_trouble_host_macros.yaml │ ├── rodbot.yaml │ └── tests.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benchmarks └── nrf-sdc │ ├── .cargo │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ ├── memory-nrf52832.x │ ├── memory-nrf52833.x │ ├── memory-nrf52840.x │ └── src │ └── bin │ ├── l2cap_benchmark_central.rs │ └── l2cap_benchmark_peripheral.rs ├── ci.sh ├── docs ├── Makefile ├── images │ ├── L2CAP_MTU_uses_all_buffers.png │ ├── L2CAP_MTU_uses_half_of_buffers.png │ ├── L2CAP_PDU.png │ ├── L2CAP_fragmentation.png │ ├── PDU-251_L2CAP-MTU-252.png │ ├── PDU.png │ ├── connection_interval.png │ ├── default_BLE-connection_event.png │ └── default_BLE-connection_events.png ├── index.adoc └── pages │ ├── concepts.adoc │ ├── faq.adoc │ ├── getting_started.adoc │ ├── overview.adoc │ └── performance.adoc ├── examples ├── README.md ├── apache-nimble │ ├── .cargo │ │ └── config.toml │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── memory.x │ └── src │ │ └── bin │ │ └── ble_bas_peripheral.rs ├── apps │ ├── Cargo.toml │ └── src │ │ ├── ble_advertise.rs │ │ ├── ble_advertise_multiple.rs │ │ ├── ble_bas_central.rs │ │ ├── ble_bas_central_sec.rs │ │ ├── ble_bas_peripheral.rs │ │ ├── ble_bas_peripheral_sec.rs │ │ ├── ble_beacon.rs │ │ ├── ble_l2cap_central.rs │ │ ├── ble_l2cap_peripheral.rs │ │ ├── ble_scanner.rs │ │ ├── common.rs │ │ ├── fmt.rs │ │ ├── high_throughput_ble_l2cap_central.rs │ │ ├── high_throughput_ble_l2cap_peripheral.rs │ │ └── lib.rs ├── esp32 │ ├── .cargo │ │ └── config.toml │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ │ └── bin │ │ ├── ble_bas_central.rs │ │ ├── ble_bas_central_sec.rs │ │ ├── ble_bas_peripheral.rs │ │ ├── ble_bas_peripheral_sec.rs │ │ ├── ble_l2cap_central.rs │ │ ├── ble_l2cap_peripheral.rs │ │ └── ble_scanner.rs ├── nrf-sdc │ ├── .cargo │ │ └── config.toml │ ├── .dir-locals.el │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── memory-nrf52832.x │ ├── memory-nrf52833.x │ ├── memory-nrf52840.x │ └── src │ │ └── bin │ │ ├── ble_advertise.rs │ │ ├── ble_advertise_multiple.rs │ │ ├── ble_bas_central.rs │ │ ├── ble_bas_central_sec.rs │ │ ├── ble_bas_peripheral.rs │ │ ├── ble_bas_peripheral_sec.rs │ │ ├── ble_l2cap_central.rs │ │ ├── ble_l2cap_peripheral.rs │ │ └── ble_scanner.rs ├── rp-pico-2-w │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── memory.x │ └── src │ │ └── bin │ │ ├── ble_bas_central.rs │ │ └── ble_bas_peripheral.rs ├── rp-pico-w │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── memory.x │ └── src │ │ └── bin │ │ ├── ble_bas_central.rs │ │ ├── ble_bas_peripheral.rs │ │ └── ble_beacon.rs ├── serial-hci │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── alloc.rs │ │ └── bin │ │ ├── ble_bas_central.rs │ │ ├── ble_bas_central_sec.rs │ │ ├── ble_bas_peripheral.rs │ │ ├── ble_bas_peripheral_sec.rs │ │ ├── ble_l2cap_central.rs │ │ ├── ble_l2cap_peripheral.rs │ │ ├── high_throughput_ble_l2cap_central.rs │ │ └── high_throughput_ble_l2cap_peripheral.rs └── tests │ ├── Cargo.toml │ ├── src │ ├── lib.rs │ ├── probe │ │ └── mod.rs │ └── serial.rs │ └── tests │ ├── ble_l2cap_central.rs │ └── ble_l2cap_peripheral.rs ├── host-macros ├── Cargo.toml └── src │ ├── characteristic.rs │ ├── ctxt.rs │ ├── lib.rs │ ├── server.rs │ ├── service.rs │ └── uuid.rs ├── host ├── Cargo.toml ├── build.rs ├── gen_config.py ├── src │ ├── advertise.rs │ ├── att.rs │ ├── attribute.rs │ ├── attribute_server.rs │ ├── central.rs │ ├── channel_manager.rs │ ├── codec.rs │ ├── command.rs │ ├── config.rs │ ├── connection.rs │ ├── connection_manager.rs │ ├── cursor.rs │ ├── fmt.rs │ ├── gap.rs │ ├── gatt.rs │ ├── host.rs │ ├── l2cap.rs │ ├── l2cap │ │ └── sar.rs │ ├── lib.rs │ ├── mock_controller.rs │ ├── packet_pool.rs │ ├── pdu.rs │ ├── peripheral.rs │ ├── scan.rs │ ├── security_manager │ │ ├── constants.rs │ │ ├── crypto.rs │ │ ├── mod.rs │ │ └── types.rs │ └── types │ │ ├── gatt_traits.rs │ │ ├── l2cap.rs │ │ ├── mod.rs │ │ ├── primitives.rs │ │ └── uuid.rs └── tests │ ├── common.rs │ ├── gatt.rs │ ├── gatt_derive.rs │ ├── l2cap.rs │ └── service_attribute_macro.rs ├── rust-toolchain-nightly.toml ├── rust-toolchain.toml └── rustfmt.toml /.ci/book.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | mkdir -p ~/.kube 5 | echo "${KUBECONFIG}" > ~/.kube/config 6 | export KUBECONFIG=~/.kube/config 7 | POD=$(kubectl -n embassy get po -l app=website -o jsonpath={.items[0].metadata.name}) 8 | kubectl exec $POD -- mkdir -p /usr/share/nginx/html 9 | kubectl cp trouble.tar $POD:/usr/share/nginx/html/ 10 | kubectl exec $POD -- find /usr/share/nginx/html 11 | kubectl exec $POD -- tar -C /usr/share/nginx/html -xvf /usr/share/nginx/html/trouble.tar 12 | -------------------------------------------------------------------------------- /.ci/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "chip": "nRF52833_xxAA", 5 | "probe": "0d28:0204:9904360258994e450055901000000046000000009796990b", 6 | "labels": { 7 | "target": "nrf52", 8 | "board": "microbit" 9 | } 10 | }, 11 | { 12 | "chip": "esp32c3", 13 | "probe": "303a:1001:60:55:F9:BF:A9:44", 14 | "labels": { 15 | "target": "esp32", 16 | "board": "esp-rust-board" 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.ci/doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | mkdir -p ~/.kube 5 | echo "${KUBECONFIG}" > ~/.kube/config 6 | export KUBECONFIG=~/.kube/config 7 | POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) 8 | kubectl cp crates $POD:/data 9 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((prog-mode . ((lsp-rust-analyzer-linked-projects . [ 2 | "host/Cargo.toml" 3 | "host-macros/Cargo.toml" 4 | "examples/apps/Cargo.toml" 5 | ;; uncomment the examples you want to explore below 6 | ;; "examples/esp32/Cargo.toml" 7 | ;; "examples/nrf-sdc/Cargo.toml" 8 | ;; "examples/rp-pico-w/Cargo.toml" 9 | ;; "examples/rp-pico-2-w/Cargo.toml" 10 | ;; "examples/serial-hci/Cargo.toml" 11 | ;; "examples/apache-nimble/Cargo.toml" 12 | ]))) 13 | ) 14 | -------------------------------------------------------------------------------- /.github/rodbot.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | issue_comment: 3 | - if: 4 | - command: "test" 5 | - user_is: ["MEMBER", "OWNER", "COLLABORATOR"] 6 | - is_pr 7 | steps: 8 | - run: | 9 | gh workflow run tests.yaml -R embassy-rs/trouble -F prNr=${{ github.event.issue.number }} 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*" 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Add dependencies 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install --no-install-recommends libudev-dev 20 | - name: Set up cargo cache 21 | uses: actions/cache@v3 22 | continue-on-error: true 23 | with: 24 | path: | 25 | ~/.cargo/bin/ 26 | ~/.cargo/registry/index/ 27 | ~/.cargo/registry/cache/ 28 | ~/.cargo/git/db/ 29 | target_ci/ 30 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 31 | restore-keys: ${{ runner.os }}-cargo- 32 | 33 | - name: CI 34 | run: | 35 | ./ci.sh 36 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | env: 8 | BUILDER_THREADS: '1' 9 | 10 | jobs: 11 | book: 12 | runs-on: ubuntu-latest 13 | 14 | # This will ensure at most one doc build job is running at a time 15 | # If another job is already running, the new job will wait. 16 | # If another job is already waiting, it'll be canceled. 17 | # This means some commits will be skipped, but that's fine because 18 | # we only care that the latest gets built. 19 | concurrency: book 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Install asciidoctor 24 | run: | 25 | sudo apt-get install asciidoctor 26 | 27 | - name: build 28 | run: | 29 | cd docs 30 | make 31 | 32 | - name: upload 33 | run: | 34 | mkdir -p build 35 | mv docs/book build/trouble 36 | tar -C build -cf trouble.tar trouble 37 | .ci/book.sh 38 | env: 39 | KUBECONFIG: ${{secrets.KUBECONFIG}} 40 | doc: 41 | runs-on: ubuntu-latest 42 | 43 | # This will ensure at most one doc build job is running at a time 44 | # If another job is already running, the new job will wait. 45 | # If another job is already waiting, it'll be canceled. 46 | # This means some commits will be skipped, but that's fine because 47 | # we only care that the latest gets built. 48 | concurrency: doc 49 | 50 | steps: 51 | - uses: actions/checkout@v2 52 | - name: Install docserver 53 | run: | 54 | wget -q -O /usr/local/bin/docserver-builder "https://github.com/embassy-rs/docserver/releases/download/v0.5/builder" 55 | chmod +x /usr/local/bin/docserver-builder 56 | 57 | - name: build 58 | run: | 59 | mv rust-toolchain-nightly.toml rust-toolchain.toml 60 | mkdir crates 61 | docserver-builder -i host -o crates/trouble-host/git.zup 62 | 63 | - name: upload 64 | run: | 65 | .ci/doc.sh 66 | env: 67 | KUBECONFIG: ${{secrets.KUBECONFIG}} 68 | -------------------------------------------------------------------------------- /.github/workflows/release_trouble_host.yaml: -------------------------------------------------------------------------------- 1 | name: Trouble Host Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'trouble-host-v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | # Re Run the Checks 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Add dependencies 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install --no-install-recommends libudev-dev 21 | - name: Set up cargo cache 22 | uses: actions/cache@v3 23 | continue-on-error: true 24 | with: 25 | path: | 26 | ~/.cargo/bin/ 27 | ~/.cargo/registry/index/ 28 | ~/.cargo/registry/cache/ 29 | ~/.cargo/git/db/ 30 | target_ci/ 31 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 32 | restore-keys: ${{ runner.os }}-cargo- 33 | 34 | - name: CI 35 | run: | 36 | ./ci.sh 37 | 38 | release_trouble_host: 39 | # Only run if the checks pass 40 | name: Publish Trouble Host 41 | needs: build 42 | runs-on: ubuntu-latest 43 | timeout-minutes: 10 44 | if: github.ref == 'refs/heads/main' 45 | steps: 46 | - name: Checkout code 47 | uses: actions/checkout@v4 48 | 49 | - name: Verify Version 50 | run: | 51 | TAG_VERSION=${GITHUB_REF#refs/tags/trouble-host-v} 52 | CARGO_VERSION=$(grep '^version =' host/Cargo.toml | sed -E 's/version = "([^"]+)"/\1/') 53 | if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then 54 | echo "Version mismatch: tag is $TAG_VERSION but Cargo.toml is $CARGO_VERSION" 55 | exit 1 # Exits with a non-zero status to fail the workflow 56 | fi 57 | shell: bash 58 | 59 | - name: Set up Rust 60 | uses: dtolnay/rust-toolchain@stable 61 | with: 62 | toolchain: stable 63 | 64 | - name: Build project 65 | run: | 66 | cd host 67 | cargo build --release 68 | 69 | - name: Create GitHub release 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | tag: ${{ github.ref_name }} 73 | run: | 74 | gh release create "$tag" \ 75 | --repo="$GITHUB_REPOSITORY" \ 76 | --title="${GITHUB_REPOSITORY#*/} host v${tag#trouble-host-v}" \ 77 | --generate-notes 78 | 79 | - name: Publish to crates.io 80 | env: 81 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 82 | # Publish to the main registry 83 | run: | 84 | echo "Publishing to crates.io" 85 | cargo publish 86 | # To perform a dry run uncomment the following lines 87 | # run: | 88 | # echo "Performing dry-run publish to crates.io" 89 | # cd host 90 | # cargo publish --dry-run 91 | -------------------------------------------------------------------------------- /.github/workflows/release_trouble_host_macros.yaml: -------------------------------------------------------------------------------- 1 | name: Trouble Host macros Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'trouble-host-macros-v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | # Re Run the Checks 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Add dependencies 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install --no-install-recommends libudev-dev 21 | - name: Set up cargo cache 22 | uses: actions/cache@v3 23 | continue-on-error: true 24 | with: 25 | path: | 26 | ~/.cargo/bin/ 27 | ~/.cargo/registry/index/ 28 | ~/.cargo/registry/cache/ 29 | ~/.cargo/git/db/ 30 | target_ci/ 31 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 32 | restore-keys: ${{ runner.os }}-cargo- 33 | 34 | - name: CI 35 | run: | 36 | ./ci.sh 37 | 38 | release_trouble_host_macros: 39 | # Only run if the checks pass 40 | name: Publish Trouble Host Macros 41 | needs: build 42 | runs-on: ubuntu-latest 43 | timeout-minutes: 10 44 | if: github.ref == 'refs/heads/main' 45 | steps: 46 | - name: Checkout code 47 | uses: actions/checkout@v4 48 | 49 | - name: Verify Version 50 | run: | 51 | TAG_VERSION=${GITHUB_REF#refs/tags/trouble-host-macros-v} 52 | CARGO_VERSION=$(grep '^version =' host-macros/Cargo.toml | sed -E 's/version = "([^"]+)"/\1/') 53 | if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then 54 | echo "Version mismatch: tag is $TAG_VERSION but Cargo.toml is $CARGO_VERSION" 55 | exit 1 # Exits with a non-zero status to fail the workflow 56 | fi 57 | shell: bash 58 | 59 | - name: Set up Rust 60 | uses: dtolnay/rust-toolchain@stable 61 | with: 62 | toolchain: stable 63 | 64 | - name: Build project 65 | run: | 66 | cd host-macros 67 | cargo build --release 68 | 69 | - name: Create GitHub release 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | tag: ${{ github.ref_name }} 73 | run: | 74 | gh release create "$tag" \ 75 | --repo="$GITHUB_REPOSITORY" \ 76 | --title="${GITHUB_REPOSITORY#*/} host macros v${tag#trouble-host-macros-v}" \ 77 | --generate-notes 78 | 79 | - name: Publish to crates.io 80 | env: 81 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 82 | # Publish to the main registry 83 | run: | 84 | echo "Publishing to crates.io" 85 | cargo publish 86 | # To perform a dry run uncomment the following lines 87 | # run: | 88 | # echo "Performing dry-run publish to crates.io" 89 | # cd host-macros 90 | # cargo publish --dry-run 91 | -------------------------------------------------------------------------------- /.github/workflows/rodbot.yaml: -------------------------------------------------------------------------------- 1 | name: rodbot 2 | 3 | on: 4 | issue_comment: 5 | types: [ "created" ] 6 | 7 | jobs: 8 | rodbot: 9 | runs-on: ubuntu-24.04 10 | 11 | env: 12 | GH_TOKEN: ${{ secrets.BOT_PAT }} 13 | 14 | steps: 15 | - run: | 16 | echo $GITHUB_EVENT_PATH 17 | cat $GITHUB_EVENT_PATH 18 | 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | 22 | - uses: ctron/rodbot-action@v0.1 23 | with: 24 | config: .github/rodbot.yaml 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/target_ci 3 | Cargo.lock 4 | .idea -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "rust-lang.rust-analyzer", 7 | "tamasfe.even-better-toml", 8 | "fill-labs.dependi", 9 | ], 10 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 11 | "unwantedRecommendations": [] 12 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[rust]": { 3 | "editor.formatOnSave": true, 4 | "editor.formatOnSaveMode": "file", 5 | }, 6 | "[toml]": { 7 | "editor.formatOnSave": false 8 | }, 9 | "[markdown]": { 10 | "editor.formatOnSave": false 11 | }, 12 | "rust-analyzer.check.allTargets": false, 13 | "rust-analyzer.linkedProjects": [ 14 | "host/Cargo.toml", 15 | "host-macros/Cargo.toml", 16 | "examples/apps/Cargo.toml", 17 | // uncomment the examples you want to explore below 18 | // "examples/esp32/Cargo.toml", 19 | // "examples/nrf-sdc/Cargo.toml", 20 | // "examples/rp-pico-w/Cargo.toml", 21 | // "examples/rp-pico-2-w/Cargo.toml", 22 | // "examples/serial-hci/Cargo.toml", 23 | // "examples/apache-nimble/Cargo.toml", 24 | ] 25 | } -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Embassy project contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trouble 2 | 3 | [![CI](https://github.com/embassy-rs/trouble/actions/workflows/ci.yaml/badge.svg)](https://github.com/embassy-rs/trouble/actions/workflows/ci.yaml) 4 | 5 | TrouBLE is a Bluetooth Low Energy (BLE) Host implementation for embedded devices written in Rust, with a future goal of qualification. The initial implementation was based on [`bleps`](https://github.com/bjoernQ/bleps) but has been adapted to work with types and traits from [`bt-hci`](https://github.com/embassy-rs/bt-hci) and adding support for more of the BLE specification such as L2CAP connection oriented channels. The current implementation also takes strong inspiration from the [`nrf-softdevice`](https://github.com/embassy-rs/nrf-softdevice) project. 6 | 7 | ## What is a Host? 8 | 9 | A BLE Host is one side of the Host Controller Interface (HCI). The BLE specification defines the software of a BLE implementation in terms of a `controller` (lower layer) and a `host` (upper layer). 10 | 11 | These communicate via a standardized protocol, that may run over different transports such as as UART, USB or a custom in-memory IPC implementation. 12 | 13 | The advantage of this split is that the Host can generally be reused for different controller implementations. 14 | 15 | ## Hardware support 16 | 17 | TrouBLE can use any controller that implements the traits from `bt-hci`. At present, that includes: 18 | 19 | * [nRF Softdevice Controller](https://github.com/alexmoon/nrf-sdc). 20 | * [UART HCI](https://docs.zephyrproject.org/latest/samples/bluetooth/hci_uart/README.html). 21 | * [Raspberry Pi Pico W](https://github.com/embassy-rs/embassy/tree/main/cyw43). 22 | * [Apache NimBLE Controller](https://github.com/benbrittain/apache-nimble-sys). 23 | * [ESP32](https://github.com/esp-rs/esp-hal). 24 | 25 | ## Current status 26 | 27 | The implementation has the following functionality working: 28 | 29 | * Peripheral role - advertise as a peripheral and accept connections. 30 | * Central role - scan for devices and establish connections. 31 | * Basic GATT server supporting write, read, notifications 32 | * Basic GATT client supporting service and characteristic lookup and read + write 33 | * L2CAP CoC (Connection oriented Channels) with credit management (for both central and peripheral) 34 | 35 | See the [issues](https://github.com/embassy-rs/trouble/issues) for a list of TODOs. 36 | 37 | ## Documentation 38 | 39 | See the [documentation](https://embassy.dev/trouble) and the [rustdoc](https://docs.embassy.dev/trouble-host/git/default/index.html). 40 | 41 | ## Minimum supported Rust version (MSRV) 42 | 43 | Trouble is guaranteed to compile on stable Rust 1.80 and up. It *might* 44 | compile with older versions but that may change in any new patch release. 45 | 46 | ## Examples 47 | 48 | See `examples` for example applications for different BLE controllers. 49 | 50 | * `nrf-sdc` for the nRF52 based using the [`nrf-sdc`](https://github.com/alexmoon/nrf-sdc) crate. 51 | * `serial-hci` which runs on std using a controller attached via a serial port (Such as [this Zephyr sample](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/samples/bluetooth/hci_uart/README.html)). 52 | * `apache-nimble` which uses the controller from the [NimBLE stack](https://github.com/apache/mynewt-nimble) through high-level bindings from the [`apache-nimble`](https://github.com/benbrittain/apache-nimble-sys) crate. 53 | * `esp32` which uses the BLE controller in the [esp-hal](https://github.com/esp-rs/esp-hal). 54 | * `rp-pico-w` which uses the BLE controller in the [Raspberry Pi Pico W](https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#raspberry-pi-pico-w). 55 | * `rp-pico-2-w` which uses the BLE controller in the [Raspberry Pi Pico 2 W](https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#raspberry-pi-pico-2-w). 56 | 57 | Since a lot of the examples demo the same BLE functionality, they only contain basic wiring specific to the BLE controller, and share the 'business logic' within the `examples/apps` folder. 58 | 59 | More information on these examples can be found is the [examples/README.md](examples/README.md). 60 | 61 | ## License 62 | 63 | Trouble is licensed under either of 64 | 65 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) 66 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 67 | 68 | at your option. 69 | -------------------------------------------------------------------------------- /benchmarks/nrf-sdc/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | #runner = "probe-rs run --chip nRF52832_xxAA" 3 | runner = "probe-rs run --chip nRF52833_xxAA" 4 | #runner = "probe-rs run --chip nRF52840_xxAA" 5 | 6 | [build] 7 | # Pick ONE of these compilation targets 8 | # target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ 9 | # target = "thumbv7m-none-eabi" # Cortex-M3 10 | # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) 11 | target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) 12 | 13 | [env] 14 | DEFMT_LOG = "trouble_host=info,info" 15 | -------------------------------------------------------------------------------- /benchmarks/nrf-sdc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-nrf-sdc-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | embassy-executor = { version = "0.7", default-features = false, features = ["arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] } 9 | embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } 10 | embassy-nrf = { version = "0.3", default-features = false, features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac", "rt"] } 11 | embassy-futures = "0.1.1" 12 | embassy-sync = { version = "0.7", features = ["defmt"] } 13 | trouble-host = { path = "../../host", default-features = false, features = ["defmt", "l2cap-rx-queue-size-4", "l2cap-tx-queue-size-4", "central", "peripheral", "scan", "gatt", "default-packet-pool", "default-packet-pool-mtu-251"] } 14 | 15 | futures = { version = "0.3", default-features = false, features = ["async-await"]} 16 | nrf-sdc = { version = "0.1.0", default-features = false, features = ["defmt", "peripheral", "central"] } 17 | nrf-mpsl = { version = "0.1.0", default-features = false, features = ["defmt", "critical-section-impl"] } 18 | bt-hci = { version = "0.3", default-features = false, features = ["defmt"] } 19 | 20 | defmt = "0.3" 21 | defmt-rtt = "0.4.0" 22 | 23 | cortex-m = { version = "0.7.6" } 24 | cortex-m-rt = "0.7.0" 25 | panic-probe = { version = "0.3", features = ["print-defmt"] } 26 | static_cell = "2" 27 | 28 | [profile.release] 29 | debug = 2 30 | 31 | [patch.crates-io] 32 | nrf-sdc = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "7be9b853e15ca0404d65c623d1ec5795fd96c204" } 33 | nrf-mpsl = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "7be9b853e15ca0404d65c623d1ec5795fd96c204" } 34 | 35 | embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 36 | embassy-nrf = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 37 | embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 38 | embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 39 | embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 40 | embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 41 | embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 42 | 43 | #embassy-executor = {path = "../../../embassy/embassy-executor"} 44 | #embassy-nrf = {path = "../../../embassy/embassy-nrf"} 45 | #embassy-sync = {path = "../../../embassy/embassy-sync"} 46 | #embassy-futures = {path = "../../../embassy/embassy-futures"} 47 | #embassy-time = {path = "../../../embassy/embassy-time"} 48 | #embassy-time-driver = {path = "../../../embassy/embassy-time-driver"} 49 | #embassy-embedded-hal = {path = "../../../embassy/embassy-embedded-hal"} 50 | #embassy-hal-internal = {path = "../../../embassy/embassy-hal-internal"} 51 | #nrf-sdc = { path = "../../../nrf-sdc/nrf-sdc" } 52 | #nrf-mpsl = { path = "../../../nrf-sdc/nrf-mpsl" } 53 | #bt-hci = { path = "../../../bt-hci" } 54 | 55 | [features] 56 | nrf52832 = [ 57 | "embassy-nrf/nrf52832", 58 | "nrf-sdc/nrf52832", 59 | ] 60 | nrf52833 = [ 61 | "embassy-nrf/nrf52833", 62 | "nrf-sdc/nrf52833", 63 | ] 64 | nrf52840 = [ 65 | "embassy-nrf/nrf52840", 66 | "nrf-sdc/nrf52840", 67 | ] 68 | -------------------------------------------------------------------------------- /benchmarks/nrf-sdc/build.rs: -------------------------------------------------------------------------------- 1 | //! This build script copies the `memory.x` file from the crate root into 2 | //! a directory where the linker can always find it at build time. 3 | //! For many projects this is optional, as the linker always searches the 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you 5 | //! are using a workspace or have a more complicated build setup, this 6 | //! build script becomes required. Additionally, by requesting that 7 | //! Cargo re-run the build script whenever `memory.x` is changed, 8 | //! updating `memory.x` ensures a rebuild of the application with the 9 | //! new memory settings. 10 | 11 | use std::env; 12 | use std::fs::File; 13 | use std::io::Write; 14 | use std::path::PathBuf; 15 | 16 | fn linker_data() -> &'static [u8] { 17 | #[cfg(feature = "nrf52832")] 18 | return include_bytes!("memory-nrf52832.x"); 19 | #[cfg(feature = "nrf52833")] 20 | return include_bytes!("memory-nrf52833.x"); 21 | #[cfg(feature = "nrf52840")] 22 | return include_bytes!("memory-nrf52840.x"); 23 | #[cfg(not(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840")))] 24 | unimplemented!("must select a target") 25 | } 26 | 27 | fn main() { 28 | // Put `memory.x` in our output directory and ensure it's 29 | // on the linker search path. 30 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 31 | File::create(out.join("memory.x")) 32 | .unwrap() 33 | .write_all(linker_data()) 34 | .unwrap(); 35 | println!("cargo:rustc-link-search={}", out.display()); 36 | 37 | // By default, Cargo will re-run a build script whenever 38 | // any file in the project changes. By specifying `memory.x` 39 | // here, we ensure the build script is only re-run when 40 | // `memory.x` is changed. 41 | println!("cargo:rerun-if-changed=memory.x"); 42 | 43 | println!("cargo:rustc-link-arg-bins=--nmagic"); 44 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); 45 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); 46 | } 47 | -------------------------------------------------------------------------------- /benchmarks/nrf-sdc/memory-nrf52832.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 512K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 6 | } 7 | -------------------------------------------------------------------------------- /benchmarks/nrf-sdc/memory-nrf52833.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 512K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | } 7 | -------------------------------------------------------------------------------- /benchmarks/nrf-sdc/memory-nrf52840.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | /* These values correspond to the NRF52840 */ 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 7 | } 8 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | if ! command -v cargo-batch &> /dev/null; then 6 | mkdir -p $HOME/.cargo/bin 7 | curl -L https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.6.0/cargo-batch > $HOME/.cargo/bin/cargo-batch 8 | chmod +x $HOME/.cargo/bin/cargo-batch 9 | fi 10 | 11 | export RUSTFLAGS=-Dwarnings 12 | export DEFMT_LOG=trace 13 | export CARGO_NET_GIT_FETCH_WITH_CLI=true 14 | if [[ -z "${CARGO_TARGET_DIR}" ]]; then 15 | export CARGO_TARGET_DIR=target_ci 16 | fi 17 | 18 | cargo batch \ 19 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features peripheral \ 20 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features central \ 21 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features central,scan \ 22 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features central,peripheral \ 23 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features central,peripheral,defmt \ 24 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral \ 25 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,central \ 26 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral,central,scan \ 27 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral,central,scan,security \ 28 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral,central,scan,controller-host-flow-control \ 29 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral,central,scan,controller-host-flow-control,connection-metrics,channel-metrics \ 30 | --- build --release --manifest-path host/Cargo.toml --no-default-features --features gatt,peripheral,central,scan,controller-host-flow-control,connection-metrics,channel-metrics,l2cap-sdu-reassembly-optimization \ 31 | --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52840 \ 32 | --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52840,security \ 33 | --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52833 --artifact-dir tests/nrf-sdc \ 34 | --- build --release --manifest-path examples/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52832 \ 35 | --- build --release --manifest-path examples/esp32/Cargo.toml --features esp32c3 --target riscv32imc-unknown-none-elf --artifact-dir tests/esp32 \ 36 | --- build --release --manifest-path examples/serial-hci/Cargo.toml \ 37 | --- build --release --manifest-path examples/tests/Cargo.toml \ 38 | --- build --release --manifest-path benchmarks/nrf-sdc/Cargo.toml --target thumbv7em-none-eabihf --features nrf52840 \ 39 | --- build --release --manifest-path examples/rp-pico-w/Cargo.toml --target thumbv6m-none-eabi --features skip-cyw43-firmware \ 40 | --- build --release --manifest-path examples/rp-pico-2-w/Cargo.toml --target thumbv8m.main-none-eabihf --features skip-cyw43-firmware 41 | # --- build --release --manifest-path examples/apache-nimble/Cargo.toml --target thumbv7em-none-eabihf 42 | 43 | cargo fmt --check --manifest-path ./host/Cargo.toml 44 | cargo clippy --manifest-path ./host/Cargo.toml --features gatt,peripheral,central 45 | cargo test --manifest-path ./host/Cargo.toml --lib -- --nocapture 46 | cargo test --manifest-path ./host/Cargo.toml --no-run -- --nocapture 47 | cargo test --manifest-path ./examples/tests/Cargo.toml --no-run -- --nocapture 48 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | asciidoctor -d book -D book/ index.adoc 3 | cp -r images book 4 | 5 | clean: 6 | rm -rf book 7 | 8 | .PHONY: all clean 9 | -------------------------------------------------------------------------------- /docs/images/L2CAP_MTU_uses_all_buffers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/L2CAP_MTU_uses_all_buffers.png -------------------------------------------------------------------------------- /docs/images/L2CAP_MTU_uses_half_of_buffers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/L2CAP_MTU_uses_half_of_buffers.png -------------------------------------------------------------------------------- /docs/images/L2CAP_PDU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/L2CAP_PDU.png -------------------------------------------------------------------------------- /docs/images/L2CAP_fragmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/L2CAP_fragmentation.png -------------------------------------------------------------------------------- /docs/images/PDU-251_L2CAP-MTU-252.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/PDU-251_L2CAP-MTU-252.png -------------------------------------------------------------------------------- /docs/images/PDU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/PDU.png -------------------------------------------------------------------------------- /docs/images/connection_interval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/connection_interval.png -------------------------------------------------------------------------------- /docs/images/default_BLE-connection_event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/default_BLE-connection_event.png -------------------------------------------------------------------------------- /docs/images/default_BLE-connection_events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embassy-rs/trouble/cb4b2cd424cdb2af54bad52922798a58d8786d7f/docs/images/default_BLE-connection_events.png -------------------------------------------------------------------------------- /docs/index.adoc: -------------------------------------------------------------------------------- 1 | :description: Trouble Documentation 2 | :sectanchors: 3 | :doctype: book 4 | :toc: 5 | :toc-placement: left 6 | :toclevels: 2 7 | :imagesdir: images 8 | 9 | # Trouble Documentation 10 | 11 | Welcome to the Trouble Documentation. This page is for everyone who wants to use Trouble and understand how Trouble works. 12 | 13 | include::pages/overview.adoc[leveloffset = 1] 14 | include::pages/concepts.adoc[leveloffset = 1] 15 | include::pages/getting_started.adoc[leveloffset = 1] 16 | include::pages/performance.adoc[leveloffset = 1] 17 | include::pages/faq.adoc[leveloffset = 1] 18 | -------------------------------------------------------------------------------- /docs/pages/concepts.adoc: -------------------------------------------------------------------------------- 1 | = Concepts 2 | 3 | A few BLE concepts frequently used in Trouble are explained here. 4 | 5 | == Central 6 | 7 | A BLE central is a device that can scan and connect to other BLE devices. Usually the central is a more powerful device like a phone or a PC, 8 | but there are no restrictions on this, and battery powered embedded devices may also act as a central. 9 | 10 | == Peripheral 11 | 12 | A BLE Peripheral is a device that advertises its presence and may be connected to. Common examples include heart rate monitors, fitness trackers, and smart sensors. Peripherals may use *GATT (Generic Attribute Profile)* to expose services and characteristics, but can also 13 | support l2cap connection oriented channels. 14 | 15 | == Communication Process 16 | 17 | 1. The Peripheral advertises its presence using *advertising packets*. 18 | 2. The Central scans for nearby Peripherals and initiates a connection. 19 | 3. Once connected, both the central and peripheral may open an l2cap channel and/or a GATT server/client. 20 | 21 | == Addresses 22 | 23 | Every BLE device is identified by a unique *Bluetooth Device Address*, which is a 48-bit identifier similar to a MAC address. BLE addresses are categorized into two main types: *Public* and *Random*. 24 | 25 | === Public Address 26 | 27 | A Public Address is globally unique and assigned by the IEEE. It remains constant and is typically used by devices requiring a stable identifier. 28 | 29 | === Random Address 30 | 31 | A Random Address can be *static* or *dynamic*: 32 | 33 | - *Static Random Address*: Remains fixed until the device restarts or resets. 34 | - *Private Random Address*: Changes periodically for privacy purposes. It can be *Resolvable* (can be linked to the original device using an Identity Resolving Key) or *Non-Resolvable* (completely anonymous). 35 | 36 | Random addresses enhance privacy by preventing device tracking. 37 | -------------------------------------------------------------------------------- /docs/pages/faq.adoc: -------------------------------------------------------------------------------- 1 | = Frequently Asked Questions 2 | 3 | These are a list of unsorted, commonly asked questions and answers. 4 | 5 | Please feel free to add items to link:https://github.com/embassy-rs/trouble/edit/main/docs/pages/faq.adoc[this page], especially if someone in the chat answered a question for you! 6 | -------------------------------------------------------------------------------- /docs/pages/overview.adoc: -------------------------------------------------------------------------------- 1 | = Introduction 2 | 3 | TrouBLE is a Bluetooth Low Energy (BLE) Host implementation written in Rust, with a future goal of qualification. 4 | 5 | A BLE Host is one side of the Host Controller Interface (HCI). The BLE specification defines the software of a BLE implementation in terms of a `controller` (lower layer) and a `host` (upper layer). 6 | 7 | These communicate via a standardized protocol, that may run over different transports such as as UART, USB or a custom in-memory IPC implementation. 8 | 9 | The advantage of this split is that the Host can be reused for different controller implementations. This means that 10 | you can write BLE applications that can work on different hardware due to the HCI interface. 11 | 12 | Trouble uses the `bt-hci` crate for the HCI interface, which means that any controller implementing the traits in `bt-hci` can work with Trouble. At present, the 13 | following controllers are available: 14 | 15 | * link:https://github.com/alexmoon/nrf-sdc[nRF Softdevice Controller] 16 | * link:https://docs.zephyrproject.org/latest/samples/bluetooth/hci_uart/README.html[UART HCI] 17 | * link:https://github.com/embassy-rs/embassy/tree/main/cyw43[Raspberry Pi Pico W] 18 | * link:https://github.com/benbrittain/apache-nimble-sys[Apache NimBLE Controller] 19 | * link:https://github.com/esp-rs/esp-hal[ESP32] 20 | 21 | The link:https://github.com/embassy-rs/trouble/tree/main/examples[examples] show how you can use Trouble with the different controllers. 22 | 23 | The APIs available in Trouble are documented in link:https://docs.embassy.dev/trouble-host/git/default/index.html[rustdoc]. 24 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | The example logic can be found in the `app` directory. 4 | The different target directories contain the required setup code for the different targets. 5 | Any target specific documentation can be found in the respective target directory. 6 | 7 | ## High throughput example 8 | 9 | The high throughput example showcases optimum configuration settings for achieving high throughput. 10 | Throughput of around 1.4 Mbps have been measured with some of the more aggressive configurations. 11 | It is important to note that these configurations compromise on energy conservation. 12 | 13 | This example requires specific hardware and HCI firmware configurations that can support the configurations used. 14 | Any specific target requirements will be outlined in the target readme. 15 | 16 | For more information on performance tuning, see the [documentation](https://embassy.dev/trouble/#_performance). 17 | -------------------------------------------------------------------------------- /examples/apache-nimble/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = [ 3 | "probe-rs", 4 | "run", 5 | "--chip", 6 | "nRF52840_xxAA", 7 | "--log-format", 8 | "{t} [ {L}] {f}:{l} {s}" 9 | ] 10 | 11 | [build] 12 | target = "thumbv7em-none-eabihf" 13 | 14 | [env] 15 | DEFMT_LOG = "trace" 16 | BINDGEN_EXTRA_CLANG_ARGS = "--sysroot=/usr/arm-none-eabi" 17 | -------------------------------------------------------------------------------- /examples/apache-nimble/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-apache-nimble-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | defmt = "0.3" 8 | defmt-rtt = "0.4" 9 | panic-probe = { version = "0.3", features = ["print-defmt"] } 10 | cortex-m-rt = "0.7.0" 11 | cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } 12 | apache-nimble = { version = "0.1.0", features = ["port-layer-embassy", "nrf52840", "controller"] } 13 | embassy-time = { version = "0.4.0", features = ["defmt", "defmt-timestamp-uptime"] } 14 | embassy-sync = "0.6.0" 15 | embassy-futures = "0.1.0" 16 | embassy-executor = { version = "0.7.0", features = ["defmt", "executor-thread", "arch-cortex-m"] } 17 | embassy-nrf = { version = "0.3.0", features = ["defmt", "nfc-pins-as-gpio", "time-driver-rtc1", "nrf52840"] } 18 | trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["defmt"] } 19 | static_cell = "1.0.0" 20 | 21 | [patch.crates-io] 22 | apache-nimble = { git = "https://github.com/benbrittain/apache-nimble-sys.git", branch = "master" } 23 | 24 | [profile.dev] 25 | debug = 2 26 | 27 | [profile.release] 28 | debug = 2 29 | lto = true 30 | # opt-level = "z" 31 | -------------------------------------------------------------------------------- /examples/apache-nimble/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-arg-bins=--nmagic"); 3 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); 4 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); 5 | } 6 | -------------------------------------------------------------------------------- /examples/apache-nimble/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/apache-nimble/src/bin/ble_bas_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use apache_nimble::controller::NimbleController; 5 | use apache_nimble::controller::NimbleControllerTask; 6 | use embassy_time::{Duration, Ticker, Timer}; 7 | use trouble_example_apps::ble_bas_peripheral; 8 | use {defmt_rtt as _, panic_probe as _}; 9 | 10 | #[::embassy_executor::task] 11 | async fn other_task() { 12 | let mut ticker = Ticker::every(Duration::from_secs(1)); 13 | loop { 14 | ticker.next().await; 15 | defmt::info!("test"); 16 | } 17 | } 18 | 19 | #[embassy_executor::main] 20 | async fn main(spawner: embassy_executor::Spawner) { 21 | let mut conf = embassy_nrf::config::Config::default(); 22 | conf.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal; 23 | conf.lfclk_source = embassy_nrf::config::LfclkSource::ExternalXtal; 24 | embassy_nrf::init(conf); 25 | 26 | apache_nimble::initialize_nimble(); 27 | let controller = NimbleController::new(); 28 | 29 | spawner.spawn(run_controller(controller.create_task())).unwrap(); 30 | 31 | // wait for RNG to calm down 32 | Timer::after(Duration::from_secs(1)).await; 33 | 34 | ble_bas_peripheral::run::<_, 128>(controller).await; 35 | } 36 | 37 | #[embassy_executor::task] 38 | async fn run_controller(controller_task: NimbleControllerTask) { 39 | controller_task.run().await 40 | } 41 | -------------------------------------------------------------------------------- /examples/apps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-example-apps" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | trouble-host = { path = "../../host", features = ["derive", "scan"] } 9 | bt-hci = { version = "0.3" } 10 | embassy-executor = { version = "0.7.0" } 11 | embassy-futures = "0.1.1" 12 | embassy-sync = { version = "0.7" } 13 | embassy-time = "0.4" 14 | embedded-hal = "1.0" 15 | static_cell = "2" 16 | embedded-io = "0.6" 17 | heapless = "0.8" 18 | rand_core = { version = "0.6", default-features = false } 19 | 20 | defmt = { version = "0.3", optional = true } 21 | log = { version = "0.4", optional = true } 22 | 23 | 24 | [features] 25 | defmt = [ 26 | "dep:defmt", 27 | "trouble-host/defmt", 28 | "bt-hci/defmt", 29 | "embedded-io/defmt-03", 30 | "embedded-hal/defmt-03" 31 | ] 32 | log = [ 33 | "dep:log", 34 | "trouble-host/log", 35 | "bt-hci/log" 36 | ] 37 | security = [ 38 | "trouble-host/security", 39 | ] 40 | -------------------------------------------------------------------------------- /examples/apps/src/ble_advertise.rs: -------------------------------------------------------------------------------- 1 | use bt_hci::cmd::le::*; 2 | use bt_hci::controller::ControllerCmdSync; 3 | use embassy_futures::join::join; 4 | use embassy_time::{Duration, Timer}; 5 | use trouble_host::prelude::*; 6 | 7 | pub async fn run(controller: C) 8 | where 9 | C: Controller 10 | + for<'t> ControllerCmdSync> 11 | + ControllerCmdSync 12 | + ControllerCmdSync 13 | + ControllerCmdSync 14 | + ControllerCmdSync 15 | + for<'t> ControllerCmdSync> 16 | + for<'t> ControllerCmdSync>, 17 | { 18 | let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 19 | info!("Our address = {:?}", address); 20 | 21 | let mut resources: HostResources = HostResources::new(); 22 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 23 | let Host { 24 | mut peripheral, 25 | mut runner, 26 | .. 27 | } = stack.build(); 28 | 29 | let mut adv_data = [0; 31]; 30 | let len = AdStructure::encode_slice( 31 | &[ 32 | AdStructure::CompleteLocalName(b"Trouble Advert"), 33 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 34 | ], 35 | &mut adv_data[..], 36 | ) 37 | .unwrap(); 38 | 39 | info!("Starting advertising"); 40 | let _ = join(runner.run(), async { 41 | loop { 42 | let mut params = AdvertisementParameters::default(); 43 | params.interval_min = Duration::from_millis(100); 44 | params.interval_max = Duration::from_millis(100); 45 | let _advertiser = peripheral 46 | .advertise( 47 | ¶ms, 48 | Advertisement::NonconnectableScannableUndirected { 49 | adv_data: &adv_data[..len], 50 | scan_data: &[], 51 | }, 52 | ) 53 | .await 54 | .unwrap(); 55 | loop { 56 | info!("Still running"); 57 | Timer::after(Duration::from_secs(60)).await; 58 | } 59 | } 60 | }) 61 | .await; 62 | } 63 | -------------------------------------------------------------------------------- /examples/apps/src/ble_advertise_multiple.rs: -------------------------------------------------------------------------------- 1 | use bt_hci::cmd::le::*; 2 | use bt_hci::controller::ControllerCmdSync; 3 | use embassy_futures::join::join; 4 | use embassy_time::{Duration, Timer}; 5 | use trouble_host::prelude::*; 6 | 7 | pub async fn run(controller: C) 8 | where 9 | C: Controller 10 | + for<'t> ControllerCmdSync> 11 | + ControllerCmdSync 12 | + ControllerCmdSync 13 | + ControllerCmdSync 14 | + ControllerCmdSync 15 | + for<'t> ControllerCmdSync> 16 | + for<'t> ControllerCmdSync>, 17 | { 18 | let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 19 | info!("Our address = {:?}", address); 20 | 21 | let mut resources: HostResources = HostResources::new(); 22 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 23 | let Host { 24 | mut peripheral, 25 | mut runner, 26 | .. 27 | } = stack.build(); 28 | 29 | let mut adv_data = [0; 31]; 30 | let len = AdStructure::encode_slice( 31 | &[ 32 | AdStructure::CompleteLocalName(b"Trouble Multiadv"), 33 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 34 | ], 35 | &mut adv_data[..], 36 | ) 37 | .unwrap(); 38 | 39 | let mut params_1m = AdvertisementParameters::default(); 40 | params_1m.primary_phy = PhyKind::Le1M; 41 | params_1m.secondary_phy = PhyKind::Le1M; 42 | params_1m.interval_min = Duration::from_millis(160); 43 | params_1m.interval_max = Duration::from_millis(160); 44 | 45 | let mut params_coded = AdvertisementParameters::default(); 46 | params_coded.primary_phy = PhyKind::LeCoded; 47 | params_coded.secondary_phy = PhyKind::LeCoded; 48 | params_coded.interval_min = Duration::from_millis(400); 49 | params_coded.interval_max = Duration::from_millis(400); 50 | let sets = [ 51 | AdvertisementSet { 52 | params: params_1m, 53 | data: Advertisement::ExtNonconnectableScannableUndirected { 54 | scan_data: &adv_data[..len], 55 | }, 56 | }, 57 | AdvertisementSet { 58 | params: params_coded, 59 | data: Advertisement::ExtNonconnectableScannableUndirected { 60 | scan_data: &adv_data[..len], 61 | }, 62 | }, 63 | ]; 64 | let mut handles = AdvertisementSet::handles(&sets); 65 | 66 | info!("Starting advertising"); 67 | let _ = join(runner.run(), async { 68 | loop { 69 | let _advertiser = peripheral.advertise_ext(&sets, &mut handles).await.unwrap(); 70 | loop { 71 | info!("Still running"); 72 | Timer::after(Duration::from_secs(60)).await; 73 | } 74 | } 75 | }) 76 | .await; 77 | } 78 | -------------------------------------------------------------------------------- /examples/apps/src/ble_bas_central.rs: -------------------------------------------------------------------------------- 1 | use embassy_futures::join::join; 2 | use embassy_time::{Duration, Timer}; 3 | use trouble_host::prelude::*; 4 | 5 | /// Max number of connections 6 | const CONNECTIONS_MAX: usize = 1; 7 | 8 | /// Max number of L2CAP channels. 9 | const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC 10 | 11 | pub async fn run(controller: C) 12 | where 13 | C: Controller, 14 | { 15 | // Using a fixed "random" address can be useful for testing. In real scenarios, one would 16 | // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). 17 | let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]); 18 | info!("Our address = {:?}", address); 19 | 20 | let mut resources: HostResources = HostResources::new(); 21 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 22 | let Host { 23 | mut central, 24 | mut runner, 25 | .. 26 | } = stack.build(); 27 | 28 | // NOTE: Modify this to match the address of the peripheral you want to connect to. 29 | // Currently it matches the address used by the peripheral examples 30 | let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 31 | 32 | let config = ConnectConfig { 33 | connect_params: Default::default(), 34 | scan_config: ScanConfig { 35 | filter_accept_list: &[(target.kind, &target.addr)], 36 | ..Default::default() 37 | }, 38 | }; 39 | 40 | info!("Scanning for peripheral..."); 41 | let _ = join(runner.run(), async { 42 | info!("Connecting"); 43 | 44 | let conn = central.connect(&config).await.unwrap(); 45 | info!("Connected, creating gatt client"); 46 | 47 | let client = GattClient::::new(&stack, &conn) 48 | .await 49 | .unwrap(); 50 | 51 | let _ = join(client.task(), async { 52 | info!("Looking for battery service"); 53 | let services = client.services_by_uuid(&Uuid::new_short(0x180f)).await.unwrap(); 54 | let service = services.first().unwrap().clone(); 55 | 56 | info!("Looking for value handle"); 57 | let c: Characteristic = client 58 | .characteristic_by_uuid(&service, &Uuid::new_short(0x2a19)) 59 | .await 60 | .unwrap(); 61 | 62 | info!("Subscribing notifications"); 63 | let mut listener = client.subscribe(&c, false).await.unwrap(); 64 | 65 | let _ = join( 66 | async { 67 | loop { 68 | let mut data = [0; 1]; 69 | client.read_characteristic(&c, &mut data[..]).await.unwrap(); 70 | info!("Read value: {}", data[0]); 71 | Timer::after(Duration::from_secs(10)).await; 72 | } 73 | }, 74 | async { 75 | loop { 76 | let data = listener.next().await; 77 | info!("Got notification: {:?} (val: {})", data.as_ref(), data.as_ref()[0]); 78 | } 79 | }, 80 | ) 81 | .await; 82 | }) 83 | .await; 84 | }) 85 | .await; 86 | } 87 | -------------------------------------------------------------------------------- /examples/apps/src/ble_bas_central_sec.rs: -------------------------------------------------------------------------------- 1 | use embassy_futures::join::join; 2 | use embassy_time::{Duration, Timer}; 3 | use rand_core::{CryptoRng, RngCore}; 4 | use trouble_host::prelude::*; 5 | 6 | /// Max number of connections 7 | const CONNECTIONS_MAX: usize = 1; 8 | 9 | /// Max number of L2CAP channels. 10 | const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC 11 | 12 | pub async fn run(controller: C, random_generator: &mut RNG) 13 | where 14 | C: Controller, 15 | RNG: RngCore + CryptoRng, 16 | { 17 | // Using a fixed "random" address can be useful for testing. In real scenarios, one would 18 | // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). 19 | let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]); 20 | info!("Our address = {:?}", address); 21 | 22 | let mut resources: HostResources = HostResources::new(); 23 | let stack = trouble_host::new(controller, &mut resources) 24 | .set_random_address(address) 25 | .set_random_generator_seed(random_generator); 26 | 27 | let Host { 28 | mut central, 29 | mut runner, 30 | .. 31 | } = stack.build(); 32 | 33 | // NOTE: Modify this to match the address of the peripheral you want to connect to. 34 | // Currently it matches the address used by the peripheral examples 35 | let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 36 | 37 | let config = ConnectConfig { 38 | connect_params: Default::default(), 39 | scan_config: ScanConfig { 40 | filter_accept_list: &[(target.kind, &target.addr)], 41 | ..Default::default() 42 | }, 43 | }; 44 | 45 | info!("Scanning for peripheral..."); 46 | let _ = join(runner.run(), async { 47 | info!("Connecting"); 48 | 49 | let conn = central.connect(&config).await.unwrap(); 50 | info!("Connected, creating gatt client"); 51 | 52 | #[cfg(feature = "security")] 53 | { 54 | if let Err(_error) = central.pairing(&conn).await { 55 | error!("Pairing failed"); 56 | } 57 | } 58 | 59 | let client = GattClient::::new(&stack, &conn) 60 | .await 61 | .unwrap(); 62 | 63 | let _ = join(client.task(), async { 64 | info!("Looking for battery service"); 65 | let services = client.services_by_uuid(&Uuid::new_short(0x180f)).await.unwrap(); 66 | let service = services.first().unwrap().clone(); 67 | 68 | info!("Looking for value handle"); 69 | let c: Characteristic = client 70 | .characteristic_by_uuid(&service, &Uuid::new_short(0x2a19)) 71 | .await 72 | .unwrap(); 73 | 74 | info!("Subscribing notifications"); 75 | let mut listener = client.subscribe(&c, false).await.unwrap(); 76 | 77 | let _ = join( 78 | async { 79 | loop { 80 | let mut data = [0; 1]; 81 | client.read_characteristic(&c, &mut data[..]).await.unwrap(); 82 | info!("Read value: {}", data[0]); 83 | Timer::after(Duration::from_secs(10)).await; 84 | } 85 | }, 86 | async { 87 | loop { 88 | let data = listener.next().await; 89 | info!("Got notification: {:?} (val: {})", data.as_ref(), data.as_ref()[0]); 90 | } 91 | }, 92 | ) 93 | .await; 94 | }) 95 | .await; 96 | }) 97 | .await; 98 | } 99 | -------------------------------------------------------------------------------- /examples/apps/src/ble_beacon.rs: -------------------------------------------------------------------------------- 1 | // BLE beacon example 2 | // 3 | // A beacon is a device that advertises packets that are constantly being 4 | // updated to reflect the current state of the device, but usually does not 5 | // accept any conections. This allows broadcasting device information. 6 | // 7 | 8 | use bt_hci::cmd::le::*; 9 | use bt_hci::controller::ControllerCmdSync; 10 | use embassy_futures::join::join; 11 | use embassy_time::{Duration, Instant, Timer}; 12 | use trouble_host::prelude::*; 13 | 14 | // Use your company ID (register for free with Bluetooth SIG) 15 | const COMPANY_ID: u16 = 0xFFFF; 16 | 17 | fn make_adv_payload(start: Instant, update_count: u32) -> [u8; 8] { 18 | let mut data = [0u8; 8]; 19 | let elapsed_ms = Instant::now().duration_since(start).as_millis() as u32; 20 | data[0..4].copy_from_slice(&update_count.to_be_bytes()); 21 | data[4..8].copy_from_slice(&elapsed_ms.to_be_bytes()); 22 | data 23 | } 24 | 25 | pub async fn run(controller: C) 26 | where 27 | C: Controller 28 | + for<'t> ControllerCmdSync> 29 | + ControllerCmdSync 30 | + ControllerCmdSync 31 | + ControllerCmdSync 32 | + ControllerCmdSync 33 | + for<'t> ControllerCmdSync> 34 | + for<'t> ControllerCmdSync>, 35 | { 36 | let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 37 | info!("Our address = {:?}", address); 38 | 39 | let mut resources: HostResources = HostResources::new(); 40 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 41 | let Host { 42 | mut peripheral, 43 | mut runner, 44 | .. 45 | } = stack.build(); 46 | 47 | let mut adv_data = [0; 64]; 48 | let mut update_count = 0u32; 49 | let start = Instant::now(); 50 | let len = AdStructure::encode_slice( 51 | &[ 52 | AdStructure::CompleteLocalName(b"Trouble Beacon"), 53 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 54 | AdStructure::ManufacturerSpecificData { 55 | company_identifier: COMPANY_ID, 56 | payload: &make_adv_payload(start, update_count), 57 | }, 58 | ], 59 | &mut adv_data[..], 60 | ) 61 | .unwrap(); 62 | 63 | info!("Starting advertising"); 64 | let _ = join(runner.run(), async { 65 | loop { 66 | let mut params = AdvertisementParameters::default(); 67 | params.interval_min = Duration::from_millis(25); 68 | params.interval_max = Duration::from_millis(150); 69 | let _advertiser = peripheral 70 | .advertise( 71 | ¶ms, 72 | Advertisement::NonconnectableNonscannableUndirected { 73 | adv_data: &adv_data[..len], 74 | }, 75 | ) 76 | .await 77 | .unwrap(); 78 | loop { 79 | Timer::after(Duration::from_millis(10)).await; 80 | update_count = update_count.wrapping_add(1); 81 | 82 | let len = AdStructure::encode_slice( 83 | &[ 84 | AdStructure::CompleteLocalName(b"Trouble Beacon"), 85 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 86 | AdStructure::ManufacturerSpecificData { 87 | company_identifier: COMPANY_ID, 88 | payload: &make_adv_payload(start, update_count), 89 | }, 90 | ], 91 | &mut adv_data[..], 92 | ) 93 | .unwrap(); 94 | 95 | peripheral 96 | .update_adv_data(Advertisement::NonconnectableNonscannableUndirected { 97 | adv_data: &adv_data[..len], 98 | }) 99 | .await 100 | .unwrap(); 101 | 102 | if update_count % 100 == 0 { 103 | info!("Still running: Updated the beacon {} times", update_count); 104 | } 105 | } 106 | } 107 | }) 108 | .await; 109 | } 110 | -------------------------------------------------------------------------------- /examples/apps/src/ble_l2cap_central.rs: -------------------------------------------------------------------------------- 1 | use embassy_futures::join::join; 2 | use embassy_time::{Duration, Timer}; 3 | use trouble_host::prelude::*; 4 | 5 | use crate::common::PSM_L2CAP_EXAMPLES; 6 | 7 | /// Max number of connections 8 | const CONNECTIONS_MAX: usize = 1; 9 | 10 | /// Max number of L2CAP channels. 11 | const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC 12 | 13 | pub async fn run(controller: C) 14 | where 15 | C: Controller, 16 | { 17 | // Using a fixed "random" address can be useful for testing. In real scenarios, one would 18 | // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). 19 | let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]); 20 | info!("Our address = {:?}", address); 21 | 22 | let mut resources: HostResources = HostResources::new(); 23 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 24 | let Host { 25 | mut central, 26 | mut runner, 27 | .. 28 | } = stack.build(); 29 | 30 | // NOTE: Modify this to match the address of the peripheral you want to connect to. 31 | // Currently, it matches the address used by the peripheral examples 32 | let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 33 | 34 | let config = ConnectConfig { 35 | connect_params: Default::default(), 36 | scan_config: ScanConfig { 37 | filter_accept_list: &[(target.kind, &target.addr)], 38 | ..Default::default() 39 | }, 40 | }; 41 | 42 | info!("Scanning for peripheral..."); 43 | let _ = join(runner.run(), async { 44 | loop { 45 | let conn = central.connect(&config).await.unwrap(); 46 | info!("Connected, creating l2cap channel"); 47 | const PAYLOAD_LEN: usize = 27; 48 | let config = L2capChannelConfig { 49 | mtu: Some(PAYLOAD_LEN as u16), 50 | ..Default::default() 51 | }; 52 | let mut ch1 = L2capChannel::create(&stack, &conn, PSM_L2CAP_EXAMPLES, &config) 53 | .await 54 | .unwrap(); 55 | info!("New l2cap channel created, sending some data!"); 56 | for i in 0..10 { 57 | let tx = [i; PAYLOAD_LEN]; 58 | ch1.send(&stack, &tx).await.unwrap(); 59 | } 60 | info!("Sent data, waiting for them to be sent back"); 61 | let mut rx = [0; PAYLOAD_LEN]; 62 | for i in 0..10 { 63 | let len = ch1.receive(&stack, &mut rx).await.unwrap(); 64 | assert_eq!(len, rx.len()); 65 | assert_eq!(rx, [i; PAYLOAD_LEN]); 66 | } 67 | 68 | info!("Received successfully!"); 69 | 70 | Timer::after(Duration::from_secs(60)).await; 71 | } 72 | }) 73 | .await; 74 | } 75 | -------------------------------------------------------------------------------- /examples/apps/src/ble_l2cap_peripheral.rs: -------------------------------------------------------------------------------- 1 | use embassy_futures::join::join; 2 | use embassy_time::{Duration, Timer}; 3 | use trouble_host::prelude::*; 4 | 5 | use crate::common::PSM_L2CAP_EXAMPLES; 6 | 7 | /// Max number of connections 8 | const CONNECTIONS_MAX: usize = 1; 9 | 10 | /// Max number of L2CAP channels. 11 | const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC 12 | 13 | pub async fn run(controller: C) 14 | where 15 | C: Controller, 16 | { 17 | // Hardcoded peripheral address 18 | let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 19 | info!("Our address = {:?}", address); 20 | 21 | let mut resources: HostResources = HostResources::new(); 22 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 23 | let Host { 24 | mut peripheral, 25 | mut runner, 26 | .. 27 | } = stack.build(); 28 | 29 | let mut adv_data = [0; 31]; 30 | let adv_data_len = AdStructure::encode_slice( 31 | &[AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED)], 32 | &mut adv_data[..], 33 | ) 34 | .unwrap(); 35 | 36 | let mut scan_data = [0; 31]; 37 | let scan_data_len = AdStructure::encode_slice(&[AdStructure::CompleteLocalName(b"Trouble")], &mut scan_data[..]).unwrap(); 38 | 39 | let _ = join(runner.run(), async { 40 | loop { 41 | info!("Advertising, waiting for connection..."); 42 | let advertiser = peripheral 43 | .advertise( 44 | &Default::default(), 45 | Advertisement::ConnectableScannableUndirected { 46 | adv_data: &adv_data[..adv_data_len], 47 | scan_data: &scan_data[..scan_data_len], 48 | }, 49 | ) 50 | .await 51 | .unwrap(); 52 | let conn = advertiser.accept().await.unwrap(); 53 | 54 | info!("Connection established"); 55 | 56 | let config = L2capChannelConfig { 57 | mtu: Some(PAYLOAD_LEN as u16), 58 | ..Default::default() 59 | }; 60 | let mut ch1 = L2capChannel::accept(&stack, &conn, &[PSM_L2CAP_EXAMPLES], &config) 61 | .await 62 | .unwrap(); 63 | 64 | info!("L2CAP channel accepted"); 65 | 66 | // Size of payload we're expecting 67 | const PAYLOAD_LEN: usize = 27; 68 | let mut rx = [0; PAYLOAD_LEN]; 69 | for i in 0..10 { 70 | let len = ch1.receive(&stack, &mut rx).await.unwrap(); 71 | assert_eq!(len, rx.len()); 72 | assert_eq!(rx, [i; PAYLOAD_LEN]); 73 | } 74 | 75 | info!("L2CAP data received, echoing"); 76 | Timer::after(Duration::from_secs(1)).await; 77 | for i in 0..10 { 78 | let tx = [i; PAYLOAD_LEN]; 79 | ch1.send(&stack, &tx).await.unwrap(); 80 | } 81 | info!("L2CAP data echoed"); 82 | 83 | Timer::after(Duration::from_secs(60)).await; 84 | } 85 | }) 86 | .await; 87 | } 88 | -------------------------------------------------------------------------------- /examples/apps/src/ble_scanner.rs: -------------------------------------------------------------------------------- 1 | use bt_hci::cmd::le::LeSetScanParams; 2 | use bt_hci::controller::ControllerCmdSync; 3 | use core::cell::RefCell; 4 | use embassy_futures::join::join; 5 | use embassy_time::{Duration, Timer}; 6 | use heapless::Deque; 7 | use trouble_host::prelude::*; 8 | 9 | /// Max number of connections 10 | const CONNECTIONS_MAX: usize = 1; 11 | const L2CAP_CHANNELS_MAX: usize = 1; 12 | 13 | pub async fn run(controller: C) 14 | where 15 | C: Controller + ControllerCmdSync, 16 | { 17 | // Using a fixed "random" address can be useful for testing. In real scenarios, one would 18 | // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). 19 | let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]); 20 | 21 | info!("Our address = {:?}", address); 22 | 23 | let mut resources: HostResources = HostResources::new(); 24 | let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 25 | 26 | let Host { 27 | central, mut runner, .. 28 | } = stack.build(); 29 | 30 | let printer = Printer { 31 | seen: RefCell::new(Deque::new()), 32 | }; 33 | let mut scanner = Scanner::new(central); 34 | let _ = join(runner.run_with_handler(&printer), async { 35 | let mut config = ScanConfig::default(); 36 | config.active = true; 37 | config.phys = PhySet::M1; 38 | config.interval = Duration::from_secs(1); 39 | config.window = Duration::from_secs(1); 40 | let mut _session = scanner.scan(&config).await.unwrap(); 41 | // Scan forever 42 | loop { 43 | Timer::after(Duration::from_secs(1)).await; 44 | } 45 | }) 46 | .await; 47 | } 48 | 49 | struct Printer { 50 | seen: RefCell>, 51 | } 52 | 53 | impl EventHandler for Printer { 54 | fn on_adv_reports(&self, mut it: LeAdvReportsIter<'_>) { 55 | let mut seen = self.seen.borrow_mut(); 56 | while let Some(Ok(report)) = it.next() { 57 | if seen.iter().find(|b| b.raw() == report.addr.raw()).is_none() { 58 | info!("discovered: {:?}", report.addr); 59 | if seen.is_full() { 60 | seen.pop_front(); 61 | } 62 | seen.push_back(report.addr).unwrap(); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/apps/src/common.rs: -------------------------------------------------------------------------------- 1 | // PSM from the dynamic range (0x0080-0x00FF) according to the Bluetooth 2 | // Specification for L2CAP channels using LE Credit Based Flow Control mode. 3 | // used for the BLE L2CAP examples. 4 | // 5 | // https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-60/out/en/host/logical-link-control-and-adaptation-protocol-specification.html#UUID-1ffdf913-7b8a-c7ba-531e-2a9c6f6da8fb 6 | // 7 | pub(crate) const PSM_L2CAP_EXAMPLES: u16 = 0x0081; 8 | -------------------------------------------------------------------------------- /examples/apps/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![allow(dead_code)] 3 | 4 | pub(crate) mod common; 5 | pub(crate) mod fmt; 6 | 7 | pub mod ble_advertise; 8 | pub mod ble_advertise_multiple; 9 | pub mod ble_bas_central; 10 | pub mod ble_bas_central_sec; 11 | pub mod ble_bas_peripheral; 12 | pub mod ble_bas_peripheral_sec; 13 | pub mod ble_beacon; 14 | pub mod ble_l2cap_central; 15 | pub mod ble_l2cap_peripheral; 16 | pub mod ble_scanner; 17 | pub mod high_throughput_ble_l2cap_central; 18 | pub mod high_throughput_ble_l2cap_peripheral; 19 | -------------------------------------------------------------------------------- /examples/esp32/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | esp32 = "run --release --no-default-features --features=esp32 --target=xtensa-esp32-none-elf" 3 | esp32c2 = "run --release --no-default-features --features=esp32c2 --target=riscv32imc-unknown-none-elf" 4 | esp32c3 = "run --release --no-default-features --features=esp32c3 --target=riscv32imc-unknown-none-elf" 5 | esp32c6 = "run --release --no-default-features --features=esp32c6 --target=riscv32imac-unknown-none-elf" 6 | esp32h2 = "run --release --no-default-features --features=esp32h2 --target=riscv32imac-unknown-none-elf" 7 | esp32s3 = "run --release --no-default-features --features=esp32s3 --target=xtensa-esp32s3-none-elf" 8 | 9 | [target.'cfg(all(any(target_arch = "riscv32", target_arch = "xtensa"), target_os = "none"))'] 10 | runner = "espflash flash --monitor" 11 | 12 | [build] 13 | rustflags = [ 14 | # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.) 15 | # NOTE: May negatively impact performance of produced code 16 | "-C", "force-frame-pointers", 17 | ] 18 | 19 | [env] 20 | ESP_LOG = "info" 21 | 22 | # Xtensa only: 23 | # Needed for nightly, until llvm upstream has support for Rust Xtensa. 24 | [unstable] 25 | build-std = ["alloc", "core"] 26 | -------------------------------------------------------------------------------- /examples/esp32/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-esp32-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | embassy-executor = { version = "0.7.0", features = ["task-arena-size-32768"] } 9 | esp-backtrace = { version = "0.15", features = [ "exception-handler", "panic-handler", "println" ] } 10 | esp-hal = { version = "1.0.0-beta.0", features = [ "unstable" ] } 11 | esp-hal-embassy = { version = "0.7.0" } 12 | esp-alloc = { version = "0.7.0" } 13 | esp-println = { version = "0.13.0", features = ["log"] } 14 | esp-wifi = { version = "0.13.0", features = [ "ble" ] } 15 | trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["log"] } 16 | trouble-host = { path = "../../host", features = ["default-packet-pool-mtu-255"] } 17 | 18 | [features] 19 | default = ["esp32c3"] 20 | 21 | esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-hal-embassy/esp32", "esp-println/esp32", "esp-wifi/esp32"] 22 | esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-hal-embassy/esp32c2", "esp-println/esp32c2", "esp-wifi/esp32c2"] 23 | esp32c3 = ["esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-hal-embassy/esp32c3", "esp-println/esp32c3", "esp-wifi/esp32c3"] 24 | esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-hal-embassy/esp32c6", "esp-println/esp32c6", "esp-wifi/esp32c6"] 25 | esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-hal-embassy/esp32h2", "esp-println/esp32h2", "esp-wifi/esp32h2"] 26 | esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-hal-embassy/esp32s3", "esp-println/esp32s3", "esp-wifi/esp32s3"] 27 | 28 | security = [ 29 | "trouble-example-apps/security", 30 | ] 31 | 32 | [profile.dev] 33 | # Rust debug is too slow. 34 | # For debug builds always builds with some optimization 35 | opt-level = "s" 36 | 37 | [profile.release] 38 | codegen-units = 1 # LLVM can perform better optimizations using a single thread 39 | debug = 2 40 | debug-assertions = false 41 | incremental = false 42 | lto = 'thin' 43 | opt-level = 3 44 | overflow-checks = false 45 | 46 | [[bin]] 47 | name = "ble_bas_central_sec" 48 | required-features = ["security"] 49 | 50 | [[bin]] 51 | name = "ble_bas_peripheral_sec" 52 | required-features = ["security"] 53 | 54 | [patch.crates-io] 55 | esp-wifi = {git = "https://github.com/esp-rs/esp-hal.git", rev = "56be259c41305f24276852d2af4fce16247107bd"} 56 | esp-backtrace = {git = "https://github.com/esp-rs/esp-hal.git", rev = "56be259c41305f24276852d2af4fce16247107bd"} 57 | esp-hal = {git = "https://github.com/esp-rs/esp-hal.git", rev = "56be259c41305f24276852d2af4fce16247107bd"} 58 | esp-hal-embassy = {git = "https://github.com/esp-rs/esp-hal.git", rev = "56be259c41305f24276852d2af4fce16247107bd"} 59 | esp-alloc = {git = "https://github.com/esp-rs/esp-hal.git", rev = "56be259c41305f24276852d2af4fce16247107bd"} 60 | esp-println = {git = "https://github.com/esp-rs/esp-hal.git", rev = "56be259c41305f24276852d2af4fce16247107bd"} 61 | -------------------------------------------------------------------------------- /examples/esp32/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 examples 2 | 3 | ## Setup 4 | 5 | These examples are set up assuming you are using espflash instead of probe-rs for flashing and debugging your chips. Make sure you have it with: 6 | 7 | ```bash 8 | cargo install cargo-binstall # binary installer tool (optional but recommended) 9 | cargo binstall cargo-espflash espflash # or cargo install if you don't use binstall 10 | ``` 11 | 12 | You may also need to install the chip's toolchain too, if so that would be done with, for example: 13 | 14 | ```bash 15 | rustup target add riscv32imac-unknown-none-elf 16 | ``` 17 | 18 | We use features to turn on the appropriate configurations for the chip you are flashing to. Currently supported chips are: 19 | 20 | - `esp32` 21 | - `esp32c2` 22 | - `esp32c3` 23 | - `esp32c6` 24 | - `esp32h2` 25 | - `esp32s3` 26 | - (esp32c5 coming soon) 27 | 28 | ## Run 29 | 30 | To build and run an example on your device, plug it in and run i.e.: 31 | 32 | ```bash 33 | cd examples/esp32 # make sure you are in the right directory 34 | cargo run --release --no-default-features --features=esp32c6 --target=riscv32imac-unknown-none-elf --bin ble_bas_peripheral 35 | ``` 36 | 37 | We have added aliases to make this simpler (see how in [./.cargo/config.toml](./.cargo/config.toml)), so you can instead run: 38 | 39 | ```bash 40 | # cargo --bin 41 | cargo esp32c6 --bin ble_bas_peripheral 42 | ``` 43 | 44 | See [esp32c3-devkit-demo](https://github.com/jamessizeland/esp32c3-devkit-demo) for more examples of setting up esp devices with trouble, specifically the [rust devkit](https://github.com/esp-rs/esp-rust-board) which is an esp32c3. 45 | -------------------------------------------------------------------------------- /examples/esp32/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-arg-bins=-Tlinkall.x"); 3 | } 4 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_bas_central.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_bas_central; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let init = esp_wifi::init( 19 | timg0.timer0, 20 | esp_hal::rng::Rng::new(peripherals.RNG), 21 | peripherals.RADIO_CLK, 22 | ) 23 | .unwrap(); 24 | 25 | #[cfg(not(feature = "esp32"))] 26 | { 27 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 28 | esp_hal_embassy::init(systimer.alarm0); 29 | } 30 | #[cfg(feature = "esp32")] 31 | { 32 | esp_hal_embassy::init(timg0.timer1); 33 | } 34 | 35 | let bluetooth = peripherals.BT; 36 | let connector = BleConnector::new(&init, bluetooth); 37 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 38 | 39 | ble_bas_central::run(controller).await; 40 | } 41 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_bas_central_sec.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_bas_central_sec; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let mut rng = esp_hal::rng::Trng::new(peripherals.RNG, peripherals.ADC1); 19 | 20 | let init = esp_wifi::init(timg0.timer0, rng.rng.clone(), peripherals.RADIO_CLK).unwrap(); 21 | 22 | #[cfg(not(feature = "esp32"))] 23 | { 24 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 25 | esp_hal_embassy::init(systimer.alarm0); 26 | } 27 | #[cfg(feature = "esp32")] 28 | { 29 | esp_hal_embassy::init(timg0.timer1); 30 | } 31 | 32 | let bluetooth = peripherals.BT; 33 | let connector = BleConnector::new(&init, bluetooth); 34 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 35 | 36 | ble_bas_central_sec::run(controller, &mut rng).await; 37 | } 38 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_bas_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_bas_peripheral; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let init = esp_wifi::init( 19 | timg0.timer0, 20 | esp_hal::rng::Rng::new(peripherals.RNG), 21 | peripherals.RADIO_CLK, 22 | ) 23 | .unwrap(); 24 | 25 | #[cfg(not(feature = "esp32"))] 26 | { 27 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 28 | esp_hal_embassy::init(systimer.alarm0); 29 | } 30 | #[cfg(feature = "esp32")] 31 | { 32 | esp_hal_embassy::init(timg0.timer1); 33 | } 34 | 35 | let bluetooth = peripherals.BT; 36 | let connector = BleConnector::new(&init, bluetooth); 37 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 38 | 39 | ble_bas_peripheral::run(controller).await; 40 | } 41 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_bas_peripheral_sec.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_bas_peripheral_sec; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let mut rng = esp_hal::rng::Trng::new(peripherals.RNG, peripherals.ADC1); 19 | 20 | let init = esp_wifi::init(timg0.timer0, rng.rng.clone(), peripherals.RADIO_CLK).unwrap(); 21 | 22 | #[cfg(not(feature = "esp32"))] 23 | { 24 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 25 | esp_hal_embassy::init(systimer.alarm0); 26 | } 27 | #[cfg(feature = "esp32")] 28 | { 29 | esp_hal_embassy::init(timg0.timer1); 30 | } 31 | 32 | let bluetooth = peripherals.BT; 33 | let connector = BleConnector::new(&init, bluetooth); 34 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 35 | 36 | ble_bas_peripheral_sec::run(controller, &mut rng).await; 37 | } 38 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_l2cap_central.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_l2cap_central; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let init = esp_wifi::init( 19 | timg0.timer0, 20 | esp_hal::rng::Rng::new(peripherals.RNG), 21 | peripherals.RADIO_CLK, 22 | ) 23 | .unwrap(); 24 | 25 | #[cfg(not(feature = "esp32"))] 26 | { 27 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 28 | esp_hal_embassy::init(systimer.alarm0); 29 | } 30 | #[cfg(feature = "esp32")] 31 | { 32 | esp_hal_embassy::init(timg0.timer1); 33 | } 34 | 35 | let bluetooth = peripherals.BT; 36 | let connector = BleConnector::new(&init, bluetooth); 37 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 38 | 39 | ble_l2cap_central::run(controller).await; 40 | } 41 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_l2cap_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_l2cap_peripheral; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let init = esp_wifi::init( 19 | timg0.timer0, 20 | esp_hal::rng::Rng::new(peripherals.RNG), 21 | peripherals.RADIO_CLK, 22 | ) 23 | .unwrap(); 24 | 25 | #[cfg(not(feature = "esp32"))] 26 | { 27 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 28 | esp_hal_embassy::init(systimer.alarm0); 29 | } 30 | #[cfg(feature = "esp32")] 31 | { 32 | esp_hal_embassy::init(timg0.timer1); 33 | } 34 | 35 | let bluetooth = peripherals.BT; 36 | let connector = BleConnector::new(&init, bluetooth); 37 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 38 | 39 | ble_l2cap_peripheral::run(controller).await; 40 | } 41 | -------------------------------------------------------------------------------- /examples/esp32/src/bin/ble_scanner.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embassy_executor::Spawner; 5 | use esp_hal::{clock::CpuClock, timer::timg::TimerGroup}; 6 | use esp_wifi::ble::controller::BleConnector; 7 | use trouble_example_apps::ble_scanner; 8 | use trouble_host::prelude::ExternalController; 9 | use {esp_alloc as _, esp_backtrace as _}; 10 | 11 | #[esp_hal_embassy::main] 12 | async fn main(_s: Spawner) { 13 | esp_println::logger::init_logger_from_env(); 14 | let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); 15 | esp_alloc::heap_allocator!(size: 72 * 1024); 16 | let timg0 = TimerGroup::new(peripherals.TIMG0); 17 | 18 | let init = esp_wifi::init( 19 | timg0.timer0, 20 | esp_hal::rng::Rng::new(peripherals.RNG), 21 | peripherals.RADIO_CLK, 22 | ) 23 | .unwrap(); 24 | 25 | #[cfg(not(feature = "esp32"))] 26 | { 27 | let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER); 28 | esp_hal_embassy::init(systimer.alarm0); 29 | } 30 | #[cfg(feature = "esp32")] 31 | { 32 | esp_hal_embassy::init(timg0.timer1); 33 | } 34 | 35 | let bluetooth = peripherals.BT; 36 | let connector = BleConnector::new(&init, bluetooth); 37 | let controller: ExternalController<_, 20> = ExternalController::new(connector); 38 | 39 | ble_scanner::run(controller).await; 40 | } 41 | -------------------------------------------------------------------------------- /examples/nrf-sdc/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | #runner = "probe-rs run --chip nRF52832_xxAA" 3 | #runner = "probe-rs run --chip nRF52833_xxAA" 4 | runner = "probe-rs run --chip nRF52840_xxAA" 5 | 6 | [build] 7 | # Pick ONE of these compilation targets 8 | # target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ 9 | # target = "thumbv7m-none-eabi" # Cortex-M3 10 | # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) 11 | target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) 12 | 13 | [env] 14 | DEFMT_LOG = "trouble_host=trace,info" 15 | -------------------------------------------------------------------------------- /examples/nrf-sdc/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((rustic-mode . ((rustic-cargo-build-arguments . "--release --features nrf52840")))) 2 | -------------------------------------------------------------------------------- /examples/nrf-sdc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-nrf-sdc-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | embassy-executor = { version = "0.7", default-features = false, features = ["arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] } 9 | embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } 10 | embassy-nrf = { version = "0.3", default-features = false, features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac", "rt"] } 11 | embassy-futures = "0.1.1" 12 | embassy-sync = { version = "0.7", features = ["defmt"] } 13 | 14 | futures = { version = "0.3", default-features = false, features = ["async-await"]} 15 | nrf-sdc = { version = "0.1.0", default-features = false, features = ["defmt", "peripheral", "central"] } 16 | nrf-mpsl = { version = "0.1.0", default-features = false, features = ["defmt", "critical-section-impl"] } 17 | bt-hci = { version = "0.3", default-features = false, features = ["defmt"] } 18 | trouble-host = { path = "../../host", features = ["derive", "scan"] } 19 | trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["defmt"] } 20 | 21 | defmt = "0.3" 22 | defmt-rtt = "0.4.0" 23 | 24 | cortex-m = { version = "0.7.6" } 25 | cortex-m-rt = "0.7.0" 26 | panic-probe = { version = "0.3", features = ["print-defmt"] } 27 | rand = { version = "0.8.5", default-features = false } 28 | static_cell = "2" 29 | rand_core = { version = "0.6"} 30 | rand_chacha = { version = "0.3", default-features = false } 31 | 32 | [profile.release] 33 | debug = 2 34 | 35 | [patch.crates-io] 36 | nrf-sdc = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "7be9b853e15ca0404d65c623d1ec5795fd96c204" } 37 | nrf-mpsl = { git = "https://github.com/alexmoon/nrf-sdc.git", rev = "7be9b853e15ca0404d65c623d1ec5795fd96c204" } 38 | 39 | embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 40 | embassy-nrf = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 41 | embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 42 | embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 43 | embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 44 | embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 45 | embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 46 | 47 | 48 | #embassy-executor = {path = "../../../embassy/embassy-executor"} 49 | #embassy-nrf = {path = "../../../embassy/embassy-nrf"} 50 | #embassy-sync = {path = "../../../embassy/embassy-sync"} 51 | #embassy-futures = {path = "../../../embassy/embassy-futures"} 52 | #embassy-time = {path = "../../../embassy/embassy-time"} 53 | #embassy-time-driver = {path = "../../../embassy/embassy-time-driver"} 54 | #embassy-embedded-hal = {path = "../../../embassy/embassy-embedded-hal"} 55 | #embassy-hal-internal = {path = "../../../embassy/embassy-hal-internal"} 56 | #nrf-sdc = { path = "../../../nrf-sdc/nrf-sdc" } 57 | #nrf-mpsl = { path = "../../../nrf-sdc/nrf-mpsl" } 58 | #bt-hci = { path = "../../../bt-hci" } 59 | 60 | [features] 61 | nrf52832 = [ 62 | "embassy-nrf/nrf52832", 63 | "nrf-sdc/nrf52832", 64 | ] 65 | nrf52833 = [ 66 | "embassy-nrf/nrf52833", 67 | "nrf-sdc/nrf52833", 68 | ] 69 | nrf52840 = [ 70 | "embassy-nrf/nrf52840", 71 | "nrf-sdc/nrf52840", 72 | ] 73 | security = [ 74 | "trouble-example-apps/security", 75 | ] 76 | 77 | [[bin]] 78 | name = "ble_bas_central_sec" 79 | required-features = ["security"] 80 | 81 | [[bin]] 82 | name = "ble_bas_peripheral_sec" 83 | required-features = ["security"] 84 | -------------------------------------------------------------------------------- /examples/nrf-sdc/README.md: -------------------------------------------------------------------------------- 1 | # nRF examples 2 | 3 | ## Setup 4 | 5 | These examples are set up assuming you are using [probe-rs](https://probe.rs) for flashing and debugging your chips. Make sure you have it with: 6 | 7 | ```bash 8 | cargo install cargo-binstall # binary installer tool (optional but recommended) 9 | cargo binstall probe-rs-tools # or cargo install if you don't use binstall 10 | ``` 11 | 12 | You may also need to install the chip's toolchain too, if so that would be done with, for example: 13 | 14 | ```bash 15 | rustup target add thumbv7em-none-eabihf 16 | ``` 17 | 18 | Build dependencies (nrf-sdc) also require clang to compile: 19 | 20 | ```bash 21 | # Linux 22 | sudo apt install llvm 23 | # Windows (multiple ways to install) 24 | choco install llvm 25 | # Mac 26 | brew install llvm 27 | ``` 28 | 29 | We use features to turn on the appropriate configurations for the chip you are flashing to. Currently supported chips are: 30 | 31 | - `nrf52832` 32 | - `nrf52833` 33 | - `nrf52840` 34 | 35 | ## Run 36 | 37 | To build and run an example on your device, plug it in and run i.e.: 38 | 39 | ```bash 40 | cd examples/nrf-sdc # make sure you are in the right directory 41 | 42 | cargo run --release --features nrf52833 --target thumbv7em-none-eabihf --bin ble_bas_peripheral 43 | ``` 44 | 45 | See [microbit-bsp](https://github.com/lulf/microbit-bsp) for more examples of setting up nrf devices with trouble, specifically the BBC Microbit which is an nrf52833. 46 | -------------------------------------------------------------------------------- /examples/nrf-sdc/build.rs: -------------------------------------------------------------------------------- 1 | //! This build script copies the `memory.x` file from the crate root into 2 | //! a directory where the linker can always find it at build time. 3 | //! For many projects this is optional, as the linker always searches the 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you 5 | //! are using a workspace or have a more complicated build setup, this 6 | //! build script becomes required. Additionally, by requesting that 7 | //! Cargo re-run the build script whenever `memory.x` is changed, 8 | //! updating `memory.x` ensures a rebuild of the application with the 9 | //! new memory settings. 10 | 11 | use std::env; 12 | use std::fs::File; 13 | use std::io::Write; 14 | use std::path::PathBuf; 15 | 16 | fn linker_data() -> &'static [u8] { 17 | #[cfg(feature = "nrf52832")] 18 | return include_bytes!("memory-nrf52832.x"); 19 | #[cfg(feature = "nrf52833")] 20 | return include_bytes!("memory-nrf52833.x"); 21 | #[cfg(feature = "nrf52840")] 22 | return include_bytes!("memory-nrf52840.x"); 23 | #[cfg(not(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840")))] 24 | unimplemented!("must select a target") 25 | } 26 | 27 | fn main() { 28 | // Put `memory.x` in our output directory and ensure it's 29 | // on the linker search path. 30 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 31 | File::create(out.join("memory.x")) 32 | .unwrap() 33 | .write_all(linker_data()) 34 | .unwrap(); 35 | println!("cargo:rustc-link-search={}", out.display()); 36 | 37 | // By default, Cargo will re-run a build script whenever 38 | // any file in the project changes. By specifying `memory.x` 39 | // here, we ensure the build script is only re-run when 40 | // `memory.x` is changed. 41 | println!("cargo:rerun-if-changed=memory.x"); 42 | 43 | println!("cargo:rustc-link-arg-bins=--nmagic"); 44 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); 45 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); 46 | } 47 | -------------------------------------------------------------------------------- /examples/nrf-sdc/memory-nrf52832.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 512K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 6 | } 7 | -------------------------------------------------------------------------------- /examples/nrf-sdc/memory-nrf52833.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 512K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 6 | } 7 | -------------------------------------------------------------------------------- /examples/nrf-sdc/memory-nrf52840.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | /* These values correspond to the NRF52840 */ 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 7 | } 8 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_advertise.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_advertise; 13 | use {defmt_rtt as _, panic_probe as _}; 14 | 15 | bind_interrupts!(struct Irqs { 16 | RNG => rng::InterruptHandler; 17 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 18 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 19 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 20 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 21 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | }); 23 | 24 | #[embassy_executor::task] 25 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 26 | mpsl.run().await 27 | } 28 | fn build_sdc<'d, const N: usize>( 29 | p: nrf_sdc::Peripherals<'d>, 30 | rng: &'d mut rng::Rng, 31 | mpsl: &'d MultiprotocolServiceLayer, 32 | mem: &'d mut sdc::Mem, 33 | ) -> Result, nrf_sdc::Error> { 34 | sdc::Builder::new()?.support_adv()?.build(p, rng, mpsl, mem) 35 | } 36 | 37 | #[embassy_executor::main] 38 | async fn main(spawner: Spawner) { 39 | let p = embassy_nrf::init(Default::default()); 40 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 41 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 42 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 43 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 44 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 45 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 46 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 47 | }; 48 | static MPSL: StaticCell = StaticCell::new(); 49 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 50 | spawner.must_spawn(mpsl_task(&*mpsl)); 51 | 52 | let sdc_p = sdc::Peripherals::new( 53 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 54 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 55 | ); 56 | 57 | let mut rng = rng::Rng::new(p.RNG, Irqs); 58 | 59 | let mut sdc_mem = sdc::Mem::<1112>::new(); 60 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 61 | 62 | ble_advertise::run(sdc).await; 63 | } 64 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_advertise_multiple.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_advertise_multiple; 13 | use {defmt_rtt as _, panic_probe as _}; 14 | 15 | bind_interrupts!(struct Irqs { 16 | RNG => rng::InterruptHandler; 17 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 18 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 19 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 20 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 21 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | }); 23 | 24 | #[embassy_executor::task] 25 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 26 | mpsl.run().await 27 | } 28 | fn build_sdc<'d, const N: usize>( 29 | p: nrf_sdc::Peripherals<'d>, 30 | rng: &'d mut rng::Rng, 31 | mpsl: &'d MultiprotocolServiceLayer, 32 | mem: &'d mut sdc::Mem, 33 | ) -> Result, nrf_sdc::Error> { 34 | sdc::Builder::new()? 35 | .support_le_coded_phy()? 36 | .support_le_2m_phy()? 37 | .support_adv()? 38 | .support_ext_adv()? 39 | .adv_count(2)? 40 | .build(p, rng, mpsl, mem) 41 | } 42 | 43 | #[embassy_executor::main] 44 | async fn main(spawner: Spawner) { 45 | let p = embassy_nrf::init(Default::default()); 46 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 47 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 48 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 49 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 50 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 51 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 52 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 53 | }; 54 | static MPSL: StaticCell = StaticCell::new(); 55 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 56 | spawner.must_spawn(mpsl_task(&*mpsl)); 57 | 58 | let sdc_p = sdc::Peripherals::new( 59 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 60 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 61 | ); 62 | 63 | let mut rng = rng::Rng::new(p.RNG, Irqs); 64 | 65 | let mut sdc_mem = sdc::Mem::<12848>::new(); 66 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 67 | 68 | ble_advertise_multiple::run(sdc).await; 69 | } 70 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_bas_central.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_bas_central; 13 | use trouble_host::prelude::*; 14 | use {defmt_rtt as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | RNG => rng::InterruptHandler; 18 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 19 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 20 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 21 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | }); 24 | 25 | #[embassy_executor::task] 26 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 27 | mpsl.run().await 28 | } 29 | 30 | /// How many outgoing L2CAP buffers per link 31 | const L2CAP_TXQ: u8 = 3; 32 | 33 | /// How many incoming L2CAP buffers per link 34 | const L2CAP_RXQ: u8 = 3; 35 | 36 | fn build_sdc<'d, const N: usize>( 37 | p: nrf_sdc::Peripherals<'d>, 38 | rng: &'d mut rng::Rng, 39 | mpsl: &'d MultiprotocolServiceLayer, 40 | mem: &'d mut sdc::Mem, 41 | ) -> Result, nrf_sdc::Error> { 42 | sdc::Builder::new()? 43 | .support_scan()? 44 | .support_central()? 45 | .central_count(1)? 46 | .buffer_cfg( 47 | DefaultPacketPool::MTU as u16, 48 | DefaultPacketPool::MTU as u16, 49 | L2CAP_TXQ, 50 | L2CAP_RXQ, 51 | )? 52 | .build(p, rng, mpsl, mem) 53 | } 54 | 55 | #[embassy_executor::main] 56 | async fn main(spawner: Spawner) { 57 | let p = embassy_nrf::init(Default::default()); 58 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 59 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 60 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 61 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 62 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 63 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 64 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 65 | }; 66 | static MPSL: StaticCell = StaticCell::new(); 67 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 68 | spawner.must_spawn(mpsl_task(&*mpsl)); 69 | 70 | let sdc_p = sdc::Peripherals::new( 71 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 72 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 73 | ); 74 | 75 | let mut rng = rng::Rng::new(p.RNG, Irqs); 76 | 77 | let mut sdc_mem = sdc::Mem::<4864>::new(); 78 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 79 | 80 | ble_bas_central::run(sdc).await; 81 | } 82 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_bas_central_sec.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use rand_chacha::ChaCha12Rng; 12 | use rand_core::SeedableRng; 13 | use static_cell::StaticCell; 14 | use trouble_example_apps::ble_bas_central_sec; 15 | use {defmt_rtt as _, panic_probe as _}; 16 | 17 | bind_interrupts!(struct Irqs { 18 | RNG => rng::InterruptHandler; 19 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 20 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 21 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 24 | }); 25 | 26 | #[embassy_executor::task] 27 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 28 | mpsl.run().await 29 | } 30 | 31 | /// How many outgoing L2CAP buffers per link 32 | const L2CAP_TXQ: u8 = 3; 33 | 34 | /// How many incoming L2CAP buffers per link 35 | const L2CAP_RXQ: u8 = 3; 36 | 37 | /// Size of L2CAP packets 38 | const L2CAP_MTU: usize = 72; 39 | 40 | fn build_sdc<'d, const N: usize>( 41 | p: nrf_sdc::Peripherals<'d>, 42 | rng: &'d mut rng::Rng, 43 | mpsl: &'d MultiprotocolServiceLayer, 44 | mem: &'d mut sdc::Mem, 45 | ) -> Result, nrf_sdc::Error> { 46 | sdc::Builder::new()? 47 | .support_scan()? 48 | .support_central()? 49 | .central_count(1)? 50 | .buffer_cfg(L2CAP_MTU as u16, L2CAP_MTU as u16, L2CAP_TXQ, L2CAP_RXQ)? 51 | .build(p, rng, mpsl, mem) 52 | } 53 | 54 | #[embassy_executor::main] 55 | async fn main(spawner: Spawner) { 56 | let p = embassy_nrf::init(Default::default()); 57 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 58 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 59 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 60 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 61 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 62 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 63 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 64 | }; 65 | static MPSL: StaticCell = StaticCell::new(); 66 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 67 | spawner.must_spawn(mpsl_task(&*mpsl)); 68 | 69 | let sdc_p = sdc::Peripherals::new( 70 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 71 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 72 | ); 73 | 74 | let mut rng = rng::Rng::new(p.RNG, Irqs); 75 | let mut rng_2 = ChaCha12Rng::from_rng(&mut rng).unwrap(); 76 | 77 | let mut sdc_mem = sdc::Mem::<6544>::new(); 78 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 79 | 80 | ble_bas_central_sec::run(sdc, &mut rng_2).await; 81 | } 82 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_bas_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_bas_peripheral; 13 | use trouble_host::prelude::*; 14 | use {defmt_rtt as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | RNG => rng::InterruptHandler; 18 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 19 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 20 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 21 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | }); 24 | 25 | #[embassy_executor::task] 26 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 27 | mpsl.run().await 28 | } 29 | 30 | /// How many outgoing L2CAP buffers per link 31 | const L2CAP_TXQ: u8 = 3; 32 | 33 | /// How many incoming L2CAP buffers per link 34 | const L2CAP_RXQ: u8 = 3; 35 | 36 | fn build_sdc<'d, const N: usize>( 37 | p: nrf_sdc::Peripherals<'d>, 38 | rng: &'d mut rng::Rng, 39 | mpsl: &'d MultiprotocolServiceLayer, 40 | mem: &'d mut sdc::Mem, 41 | ) -> Result, nrf_sdc::Error> { 42 | sdc::Builder::new()? 43 | .support_adv()? 44 | .support_peripheral()? 45 | .peripheral_count(1)? 46 | .buffer_cfg( 47 | DefaultPacketPool::MTU as u16, 48 | DefaultPacketPool::MTU as u16, 49 | L2CAP_TXQ, 50 | L2CAP_RXQ, 51 | )? 52 | .build(p, rng, mpsl, mem) 53 | } 54 | 55 | #[embassy_executor::main] 56 | async fn main(spawner: Spawner) { 57 | let p = embassy_nrf::init(Default::default()); 58 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 59 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 60 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 61 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 62 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 63 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 64 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 65 | }; 66 | static MPSL: StaticCell = StaticCell::new(); 67 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 68 | spawner.must_spawn(mpsl_task(&*mpsl)); 69 | 70 | let sdc_p = sdc::Peripherals::new( 71 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 72 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 73 | ); 74 | 75 | let mut rng = rng::Rng::new(p.RNG, Irqs); 76 | 77 | let mut sdc_mem = sdc::Mem::<4720>::new(); 78 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 79 | 80 | ble_bas_peripheral::run(sdc).await; 81 | } 82 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_bas_peripheral_sec.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use rand_chacha::ChaCha12Rng; 12 | use rand_core::SeedableRng; 13 | use static_cell::StaticCell; 14 | use trouble_example_apps::ble_bas_peripheral_sec; 15 | use {defmt_rtt as _, panic_probe as _}; 16 | 17 | bind_interrupts!(struct Irqs { 18 | RNG => rng::InterruptHandler; 19 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 20 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 21 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 24 | }); 25 | 26 | #[embassy_executor::task] 27 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 28 | mpsl.run().await 29 | } 30 | 31 | /// How many outgoing L2CAP buffers per link 32 | const L2CAP_TXQ: u8 = 3; 33 | 34 | /// How many incoming L2CAP buffers per link 35 | const L2CAP_RXQ: u8 = 3; 36 | 37 | /// Size of L2CAP packets 38 | const L2CAP_MTU: usize = 72; 39 | 40 | fn build_sdc<'d, const N: usize>( 41 | p: nrf_sdc::Peripherals<'d>, 42 | rng: &'d mut rng::Rng, 43 | mpsl: &'d MultiprotocolServiceLayer, 44 | mem: &'d mut sdc::Mem, 45 | ) -> Result, nrf_sdc::Error> { 46 | sdc::Builder::new()? 47 | .support_adv()? 48 | .support_peripheral()? 49 | .peripheral_count(1)? 50 | .buffer_cfg(L2CAP_MTU as u16, L2CAP_MTU as u16, L2CAP_TXQ, L2CAP_RXQ)? 51 | .build(p, rng, mpsl, mem) 52 | } 53 | 54 | #[embassy_executor::main] 55 | async fn main(spawner: Spawner) { 56 | let p = embassy_nrf::init(Default::default()); 57 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 58 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 59 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 60 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 61 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 62 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 63 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 64 | }; 65 | static MPSL: StaticCell = StaticCell::new(); 66 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 67 | spawner.must_spawn(mpsl_task(&*mpsl)); 68 | 69 | let sdc_p = sdc::Peripherals::new( 70 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 71 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 72 | ); 73 | 74 | let mut rng = rng::Rng::new(p.RNG, Irqs); 75 | let mut rng_2 = ChaCha12Rng::from_rng(&mut rng).unwrap(); 76 | 77 | let mut sdc_mem = sdc::Mem::<3312>::new(); 78 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 79 | 80 | ble_bas_peripheral_sec::run(sdc, &mut rng_2).await; 81 | } 82 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_l2cap_central.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use embassy_time::{Duration, Timer}; 10 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 11 | use nrf_sdc::{self as sdc, mpsl}; 12 | use static_cell::StaticCell; 13 | use trouble_example_apps::ble_l2cap_central; 14 | use trouble_host::prelude::*; 15 | use {defmt_rtt as _, panic_probe as _}; 16 | 17 | bind_interrupts!(struct Irqs { 18 | RNG => rng::InterruptHandler; 19 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 20 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 21 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 24 | }); 25 | 26 | #[embassy_executor::task] 27 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 28 | mpsl.run().await 29 | } 30 | 31 | /// How many outgoing L2CAP buffers per link 32 | const L2CAP_TXQ: u8 = 3; 33 | 34 | /// How many incoming L2CAP buffers per link 35 | const L2CAP_RXQ: u8 = 3; 36 | 37 | fn build_sdc<'d, const N: usize>( 38 | p: nrf_sdc::Peripherals<'d>, 39 | rng: &'d mut rng::Rng, 40 | mpsl: &'d MultiprotocolServiceLayer, 41 | mem: &'d mut sdc::Mem, 42 | ) -> Result, nrf_sdc::Error> { 43 | sdc::Builder::new()? 44 | .support_scan()? 45 | .support_central()? 46 | .central_count(1)? 47 | .buffer_cfg( 48 | DefaultPacketPool::MTU as u16, 49 | DefaultPacketPool::MTU as u16, 50 | L2CAP_TXQ, 51 | L2CAP_RXQ, 52 | )? 53 | .build(p, rng, mpsl, mem) 54 | } 55 | 56 | #[embassy_executor::main] 57 | async fn main(spawner: Spawner) { 58 | let p = embassy_nrf::init(Default::default()); 59 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 60 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 61 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 62 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 63 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 64 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 65 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 66 | }; 67 | static MPSL: StaticCell = StaticCell::new(); 68 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 69 | spawner.must_spawn(mpsl_task(&*mpsl)); 70 | 71 | let sdc_p = sdc::Peripherals::new( 72 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 73 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 74 | ); 75 | 76 | let mut rng = rng::Rng::new(p.RNG, Irqs); 77 | 78 | let mut sdc_mem = sdc::Mem::<4864>::new(); 79 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 80 | 81 | Timer::after(Duration::from_millis(200)).await; 82 | 83 | ble_l2cap_central::run(sdc).await; 84 | } 85 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 10 | use nrf_sdc::{self as sdc, mpsl}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_l2cap_peripheral; 13 | use trouble_host::prelude::*; 14 | use {defmt_rtt as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | RNG => rng::InterruptHandler; 18 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 19 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 20 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 21 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | }); 24 | 25 | #[embassy_executor::task] 26 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 27 | mpsl.run().await 28 | } 29 | 30 | /// How many outgoing L2CAP buffers per link 31 | const L2CAP_TXQ: u8 = 3; 32 | 33 | /// How many incoming L2CAP buffers per link 34 | const L2CAP_RXQ: u8 = 3; 35 | 36 | fn build_sdc<'d, const N: usize>( 37 | p: nrf_sdc::Peripherals<'d>, 38 | rng: &'d mut rng::Rng, 39 | mpsl: &'d MultiprotocolServiceLayer, 40 | mem: &'d mut sdc::Mem, 41 | ) -> Result, nrf_sdc::Error> { 42 | sdc::Builder::new()? 43 | .support_adv()? 44 | .support_peripheral()? 45 | .peripheral_count(1)? 46 | .buffer_cfg( 47 | DefaultPacketPool::MTU as u16, 48 | DefaultPacketPool::MTU as u16, 49 | L2CAP_TXQ, 50 | L2CAP_RXQ, 51 | )? 52 | .build(p, rng, mpsl, mem) 53 | } 54 | 55 | #[embassy_executor::main] 56 | async fn main(spawner: Spawner) { 57 | let p = embassy_nrf::init(Default::default()); 58 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 59 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 60 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 61 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 62 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 63 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 64 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 65 | }; 66 | static MPSL: StaticCell = StaticCell::new(); 67 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 68 | spawner.must_spawn(mpsl_task(&*mpsl)); 69 | 70 | let sdc_p = sdc::Peripherals::new( 71 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 72 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 73 | ); 74 | 75 | let mut rng = rng::Rng::new(p.RNG, Irqs); 76 | 77 | let mut sdc_mem = sdc::Mem::<4720>::new(); 78 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 79 | 80 | ble_l2cap_peripheral::run(sdc).await; 81 | } 82 | -------------------------------------------------------------------------------- /examples/nrf-sdc/src/bin/ble_scanner.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use defmt::unwrap; 5 | use embassy_executor::Spawner; 6 | use embassy_nrf::mode::Async; 7 | use embassy_nrf::peripherals::RNG; 8 | use embassy_nrf::{bind_interrupts, rng}; 9 | use embassy_time::{Duration, Timer}; 10 | use nrf_sdc::mpsl::MultiprotocolServiceLayer; 11 | use nrf_sdc::{self as sdc, mpsl}; 12 | use static_cell::StaticCell; 13 | use trouble_example_apps::ble_scanner; 14 | use {defmt_rtt as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | RNG => rng::InterruptHandler; 18 | EGU0_SWI0 => nrf_sdc::mpsl::LowPrioInterruptHandler; 19 | CLOCK_POWER => nrf_sdc::mpsl::ClockInterruptHandler; 20 | RADIO => nrf_sdc::mpsl::HighPrioInterruptHandler; 21 | TIMER0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 22 | RTC0 => nrf_sdc::mpsl::HighPrioInterruptHandler; 23 | }); 24 | 25 | #[embassy_executor::task] 26 | async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { 27 | mpsl.run().await 28 | } 29 | 30 | fn build_sdc<'d, const N: usize>( 31 | p: nrf_sdc::Peripherals<'d>, 32 | rng: &'d mut rng::Rng, 33 | mpsl: &'d MultiprotocolServiceLayer, 34 | mem: &'d mut sdc::Mem, 35 | ) -> Result, nrf_sdc::Error> { 36 | sdc::Builder::new()? 37 | .support_scan()? 38 | .support_ext_scan()? 39 | .support_central()? 40 | .support_ext_central()? 41 | .central_count(1)? 42 | .build(p, rng, mpsl, mem) 43 | } 44 | 45 | #[embassy_executor::main] 46 | async fn main(spawner: Spawner) { 47 | let p = embassy_nrf::init(Default::default()); 48 | let mpsl_p = mpsl::Peripherals::new(p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31); 49 | let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { 50 | source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, 51 | rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, 52 | rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, 53 | accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, 54 | skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, 55 | }; 56 | static MPSL: StaticCell = StaticCell::new(); 57 | let mpsl = MPSL.init(unwrap!(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg))); 58 | spawner.must_spawn(mpsl_task(&*mpsl)); 59 | 60 | let sdc_p = sdc::Peripherals::new( 61 | p.PPI_CH17, p.PPI_CH18, p.PPI_CH20, p.PPI_CH21, p.PPI_CH22, p.PPI_CH23, p.PPI_CH24, p.PPI_CH25, p.PPI_CH26, 62 | p.PPI_CH27, p.PPI_CH28, p.PPI_CH29, 63 | ); 64 | 65 | let mut rng = rng::Rng::new(p.RNG, Irqs); 66 | 67 | let mut sdc_mem = sdc::Mem::<2712>::new(); 68 | let sdc = unwrap!(build_sdc(sdc_p, &mut rng, mpsl, &mut sdc_mem)); 69 | 70 | Timer::after(Duration::from_millis(200)).await; 71 | 72 | ble_scanner::run(sdc).await; 73 | } 74 | -------------------------------------------------------------------------------- /examples/rp-pico-2-w/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | #runner = "probe-rs run --chip RP2040" 3 | runner = "picotool load -u -v -x -t elf" 4 | 5 | [build] 6 | target = "thumbv8m.main-none-eabihf" 7 | 8 | [env] 9 | DEFMT_LOG = "debug" 10 | -------------------------------------------------------------------------------- /examples/rp-pico-2-w/.gitignore: -------------------------------------------------------------------------------- 1 | cyw43-firmware/ -------------------------------------------------------------------------------- /examples/rp-pico-2-w/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-rp23-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | embassy-executor = { version = "0.7", default-features = false, features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } 9 | embassy-time = { version = "0.4", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } 10 | embassy-rp = { version = "0.4", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } 11 | embassy-futures = "0.1.1" 12 | embassy-sync = { version = "0.7", features = ["defmt"] } 13 | 14 | futures = { version = "0.3", default-features = false, features = ["async-await"]} 15 | trouble-example-apps= { version = "0.1.0", path = "../apps", features = ["defmt"] } 16 | trouble-host = { path = "../../host", features = ["default-packet-pool-mtu-128"] } 17 | cyw43 = { version = "0.3", features = ["defmt", "firmware-logs", "bluetooth"] } 18 | cyw43-pio = { version = "0.4", features = ["defmt"] } 19 | 20 | defmt = "0.3" 21 | defmt-rtt = "0.4.0" 22 | 23 | cortex-m = { version = "0.7" } 24 | cortex-m-rt = "0.7" 25 | panic-probe = { version = "0.3", features = ["print-defmt"] } 26 | static_cell = "2" 27 | portable-atomic = { version = "1.5", features = ["critical-section"] } 28 | rand_core = { version = "0.6"} 29 | rand_chacha = { version = "0.3", default-features = false } 30 | 31 | [build-dependencies] 32 | reqwest = { version = "0.12.9", features = ["blocking"]} 33 | 34 | [features] 35 | skip-cyw43-firmware = [] 36 | 37 | [profile.release] 38 | debug = 2 39 | 40 | [patch.crates-io] 41 | embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 42 | embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 43 | embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 44 | embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 45 | embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 46 | embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 47 | embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 48 | cyw43 = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 49 | cyw43-pio = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 50 | -------------------------------------------------------------------------------- /examples/rp-pico-2-w/build.rs: -------------------------------------------------------------------------------- 1 | //! This build script copies the `memory.x` file from the crate root into 2 | //! a directory where the linker can always find it at build time. 3 | //! For many projects this is optional, as the linker always searches the 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you 5 | //! are using a workspace or have a more complicated build setup, this 6 | //! build script becomes required. Additionally, by requesting that 7 | //! Cargo re-run the build script whenever `memory.x` is changed, 8 | //! updating `memory.x` ensures a rebuild of the application with the 9 | //! new memory settings. 10 | 11 | use std::env; 12 | use std::fs::File; 13 | use std::io::Write; 14 | use std::path::PathBuf; 15 | 16 | fn main() { 17 | // Put `memory.x` in our output directory and ensure it's 18 | // on the linker search path. 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 20 | File::create(out.join("memory.x")) 21 | .unwrap() 22 | .write_all(include_bytes!("memory.x")) 23 | .unwrap(); 24 | println!("cargo:rustc-link-search={}", out.display()); 25 | 26 | #[cfg(not(feature = "skip-cyw43-firmware"))] 27 | download_cyw43_firmware(); 28 | 29 | // By default, Cargo will re-run a build script whenever 30 | // any file in the project changes. By specifying `memory.x` 31 | // here, we ensure the build script is only re-run when 32 | // `memory.x` is changed. 33 | println!("cargo:rerun-if-changed=memory.x"); 34 | 35 | println!("cargo:rustc-link-arg-bins=--nmagic"); 36 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); 37 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); 38 | } 39 | 40 | #[cfg(not(feature = "skip-cyw43-firmware"))] 41 | fn download_cyw43_firmware() { 42 | let download_folder = "cyw43-firmware"; 43 | let url_base = "https://github.com/embassy-rs/embassy/raw/refs/heads/main/cyw43-firmware"; 44 | let file_names = [ 45 | "43439A0.bin", 46 | "43439A0_btfw.bin", 47 | "43439A0_clm.bin", 48 | "LICENSE-permissive-binary-license-1.0.txt", 49 | "README.md", 50 | ]; 51 | 52 | println!("cargo:rerun-if-changed=build.rs"); 53 | println!("cargo:rerun-if-changed={}", download_folder); 54 | std::fs::create_dir_all(download_folder).expect("Failed to create download directory"); 55 | 56 | // download each file into the folder "cyw43-firmware" 57 | for file in file_names { 58 | let url = format!("{}/{}", url_base, file); 59 | // only fetch if it doesn't exist 60 | if std::path::Path::new(download_folder).join(file).exists() { 61 | continue; 62 | } 63 | match reqwest::blocking::get(&url) { 64 | Ok(response) => { 65 | let content = response.bytes().expect("Failed to read file content"); 66 | let file_path = PathBuf::from(download_folder).join(file); 67 | std::fs::write(file_path, &content).expect("Failed to write file"); 68 | } 69 | Err(err) => panic!( 70 | "Failed to download the cyw43 firmware from {}: {}, required for pi-pico-w example", 71 | url, err 72 | ), 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/rp-pico-2-w/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | /* 3 | * The RP2350 has either external or internal flash. 4 | * 5 | * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. 6 | */ 7 | FLASH : ORIGIN = 0x10000000, LENGTH = 2048K 8 | /* 9 | * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. 10 | * This is usually good for performance, as it distributes load on 11 | * those banks evenly. 12 | */ 13 | RAM : ORIGIN = 0x20000000, LENGTH = 512K 14 | /* 15 | * RAM banks 8 and 9 use a direct mapping. They can be used to have 16 | * memory areas dedicated for some specific job, improving predictability 17 | * of access times. 18 | * Example: Separate stacks for core0 and core1. 19 | */ 20 | SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K 21 | SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K 22 | } 23 | 24 | SECTIONS { 25 | /* ### Boot ROM info 26 | * 27 | * Goes after .vector_table, to keep it in the first 4K of flash 28 | * where the Boot ROM (and picotool) can find it 29 | */ 30 | .start_block : ALIGN(4) 31 | { 32 | __start_block_addr = .; 33 | KEEP(*(.start_block)); 34 | KEEP(*(.boot_info)); 35 | } > FLASH 36 | 37 | } INSERT AFTER .vector_table; 38 | 39 | /* move .text to start /after/ the boot info */ 40 | _stext = ADDR(.start_block) + SIZEOF(.start_block); 41 | 42 | SECTIONS { 43 | /* ### Picotool 'Binary Info' Entries 44 | * 45 | * Picotool looks through this block (as we have pointers to it in our 46 | * header) to find interesting information. 47 | */ 48 | .bi_entries : ALIGN(4) 49 | { 50 | /* We put this in the header */ 51 | __bi_entries_start = .; 52 | /* Here are the entries */ 53 | KEEP(*(.bi_entries)); 54 | /* Keep this block a nice round size */ 55 | . = ALIGN(4); 56 | /* We put this in the header */ 57 | __bi_entries_end = .; 58 | } > FLASH 59 | } INSERT AFTER .text; 60 | 61 | SECTIONS { 62 | /* ### Boot ROM extra info 63 | * 64 | * Goes after everything in our program, so it can contain a signature. 65 | */ 66 | .end_block : ALIGN(4) 67 | { 68 | __end_block_addr = .; 69 | KEEP(*(.end_block)); 70 | } > FLASH 71 | 72 | } INSERT AFTER .uninit; 73 | 74 | PROVIDE(start_to_end = __end_block_addr - __start_block_addr); 75 | PROVIDE(end_to_start = __start_block_addr - __end_block_addr); -------------------------------------------------------------------------------- /examples/rp-pico-2-w/src/bin/ble_bas_central.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cyw43_pio::{PioSpi, RM2_CLOCK_DIVIDER}; 5 | use defmt::*; 6 | use embassy_executor::Spawner; 7 | use embassy_rp::bind_interrupts; 8 | use embassy_rp::gpio::{Level, Output}; 9 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; 10 | use embassy_rp::pio::{InterruptHandler, Pio}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_bas_central; 13 | use trouble_host::prelude::ExternalController; 14 | use {defmt_rtt as _, embassy_time as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | PIO0_IRQ_0 => InterruptHandler; 18 | }); 19 | 20 | #[embassy_executor::task] 21 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { 22 | runner.run().await 23 | } 24 | 25 | #[embassy_executor::main] 26 | async fn main(spawner: Spawner) { 27 | let p = embassy_rp::init(Default::default()); 28 | 29 | #[cfg(feature = "skip-cyw43-firmware")] 30 | let (fw, clm, btfw) = (&[], &[], &[]); 31 | 32 | #[cfg(not(feature = "skip-cyw43-firmware"))] 33 | let (fw, clm, btfw) = { 34 | // IMPORTANT 35 | // 36 | // Download and make sure these files from https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware 37 | // are available in `./examples/rp-pico-2-w`. (should be automatic) 38 | // 39 | // IMPORTANT 40 | let fw = include_bytes!("../../cyw43-firmware/43439A0.bin"); 41 | let clm = include_bytes!("../../cyw43-firmware/43439A0_clm.bin"); 42 | let btfw = include_bytes!("../../cyw43-firmware/43439A0_btfw.bin"); 43 | (fw, clm, btfw) 44 | }; 45 | 46 | let pwr = Output::new(p.PIN_23, Level::Low); 47 | let cs = Output::new(p.PIN_25, Level::High); 48 | let mut pio = Pio::new(p.PIO0, Irqs); 49 | let spi = PioSpi::new( 50 | &mut pio.common, 51 | pio.sm0, 52 | RM2_CLOCK_DIVIDER, 53 | pio.irq0, 54 | cs, 55 | p.PIN_24, 56 | p.PIN_29, 57 | p.DMA_CH0, 58 | ); 59 | 60 | static STATE: StaticCell = StaticCell::new(); 61 | let state = STATE.init(cyw43::State::new()); 62 | let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; 63 | unwrap!(spawner.spawn(cyw43_task(runner))); 64 | control.init(clm).await; 65 | 66 | let controller: ExternalController<_, 10> = ExternalController::new(bt_device); 67 | 68 | ble_bas_central::run(controller).await; 69 | } 70 | -------------------------------------------------------------------------------- /examples/rp-pico-2-w/src/bin/ble_bas_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cyw43_pio::{PioSpi, RM2_CLOCK_DIVIDER}; 5 | use defmt::*; 6 | use embassy_executor::Spawner; 7 | use embassy_rp::bind_interrupts; 8 | use embassy_rp::gpio::{Level, Output}; 9 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; 10 | use embassy_rp::pio::{InterruptHandler, Pio}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_bas_peripheral; 13 | use trouble_host::prelude::ExternalController; 14 | use {defmt_rtt as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | PIO0_IRQ_0 => InterruptHandler; 18 | }); 19 | 20 | #[embassy_executor::task] 21 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { 22 | runner.run().await 23 | } 24 | 25 | #[embassy_executor::main] 26 | async fn main(spawner: Spawner) { 27 | let p = embassy_rp::init(Default::default()); 28 | 29 | #[cfg(feature = "skip-cyw43-firmware")] 30 | let (fw, clm, btfw) = (&[], &[], &[]); 31 | 32 | #[cfg(not(feature = "skip-cyw43-firmware"))] 33 | let (fw, clm, btfw) = { 34 | // IMPORTANT 35 | // 36 | // Download and make sure these files from https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware 37 | // are available in `./examples/rp-pico-2-w`. (should be automatic) 38 | // 39 | // IMPORTANT 40 | let fw = include_bytes!("../../cyw43-firmware/43439A0.bin"); 41 | let clm = include_bytes!("../../cyw43-firmware/43439A0_clm.bin"); 42 | let btfw = include_bytes!("../../cyw43-firmware/43439A0_btfw.bin"); 43 | (fw, clm, btfw) 44 | }; 45 | 46 | let pwr = Output::new(p.PIN_23, Level::Low); 47 | let cs = Output::new(p.PIN_25, Level::High); 48 | let mut pio = Pio::new(p.PIO0, Irqs); 49 | let spi = PioSpi::new( 50 | &mut pio.common, 51 | pio.sm0, 52 | RM2_CLOCK_DIVIDER, 53 | pio.irq0, 54 | cs, 55 | p.PIN_24, 56 | p.PIN_29, 57 | p.DMA_CH0, 58 | ); 59 | 60 | static STATE: StaticCell = StaticCell::new(); 61 | let state = STATE.init(cyw43::State::new()); 62 | let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; 63 | unwrap!(spawner.spawn(cyw43_task(runner))); 64 | control.init(clm).await; 65 | 66 | let controller: ExternalController<_, 10> = ExternalController::new(bt_device); 67 | 68 | ble_bas_peripheral::run(controller).await; 69 | } 70 | -------------------------------------------------------------------------------- /examples/rp-pico-w/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "probe-rs run --chip RP2040" 3 | 4 | [build] 5 | target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ 6 | 7 | [env] 8 | DEFMT_LOG = "debug" 9 | -------------------------------------------------------------------------------- /examples/rp-pico-w/.gitignore: -------------------------------------------------------------------------------- 1 | cyw43-firmware/ -------------------------------------------------------------------------------- /examples/rp-pico-w/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-rp-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | embassy-executor = { version = "0.7", default-features = false, features = ["arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] } 9 | embassy-time = { version = "0.4.0", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } 10 | embassy-rp = { version = "0.4.0", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } 11 | embassy-futures = "0.1.1" 12 | embassy-sync = { version = "0.7", features = ["defmt"] } 13 | 14 | futures = { version = "0.3", default-features = false, features = ["async-await"]} 15 | trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["defmt"] } 16 | trouble-host = { path = "../../host", features = ["default-packet-pool-mtu-128"] } 17 | cyw43 = { version = "0.3.0", features = ["defmt", "firmware-logs", "bluetooth"] } 18 | cyw43-pio = { version = "0.4.0", features = ["defmt"] } 19 | 20 | defmt = "0.3" 21 | defmt-rtt = "0.4.0" 22 | 23 | cortex-m = { version = "0.7.6" } 24 | cortex-m-rt = "0.7.0" 25 | panic-probe = { version = "0.3", features = ["print-defmt"] } 26 | static_cell = "2" 27 | portable-atomic = { version = "1.5", features = ["critical-section"] } 28 | 29 | [build-dependencies] 30 | reqwest = { version = "0.12.9", features = ["blocking"]} 31 | 32 | [features] 33 | skip-cyw43-firmware = [] 34 | 35 | [profile.release] 36 | debug = 2 37 | 38 | [patch.crates-io] 39 | embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 40 | embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 41 | embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 42 | embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 43 | embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 44 | embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 45 | embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 46 | cyw43 = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 47 | cyw43-pio = { git = "https://github.com/embassy-rs/embassy.git", rev = "f35aa4005a63e8d478b2b95aaa2bfb316b72dece" } 48 | #embassy-executor = {path = "../../../embassy/embassy-executor"} 49 | #embassy-nrf = {path = "../../../embassy/embassy-nrf"} 50 | #embassy-sync = {path = "../../../embassy/embassy-sync"} 51 | #embassy-futures = {path = "../../../embassy/embassy-futures"} 52 | #embassy-time = {path = "../../../embassy/embassy-time"} 53 | #embassy-time-driver = {path = "../../../embassy/embassy-time-driver"} 54 | #embassy-embedded-hal = {path = "../../../embassy/embassy-embedded-hal"} 55 | #embassy-hal-internal = {path = "../../../embassy/embassy-hal-internal"} 56 | #nrf-sdc = { path = "../../../nrf-sdc/nrf-sdc" } 57 | #nrf-mpsl = { path = "../../../nrf-sdc/nrf-mpsl" } 58 | #bt-hci = { path = "../../../bt-hci" } 59 | -------------------------------------------------------------------------------- /examples/rp-pico-w/build.rs: -------------------------------------------------------------------------------- 1 | //! This build script copies the `memory.x` file from the crate root into 2 | //! a directory where the linker can always find it at build time. 3 | //! For many projects this is optional, as the linker always searches the 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you 5 | //! are using a workspace or have a more complicated build setup, this 6 | //! build script becomes required. Additionally, by requesting that 7 | //! Cargo re-run the build script whenever `memory.x` is changed, 8 | //! updating `memory.x` ensures a rebuild of the application with the 9 | //! new memory settings. 10 | 11 | use std::env; 12 | use std::fs::File; 13 | use std::io::Write; 14 | use std::path::PathBuf; 15 | 16 | fn main() { 17 | // Put `memory.x` in our output directory and ensure it's 18 | // on the linker search path. 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 20 | File::create(out.join("memory.x")) 21 | .unwrap() 22 | .write_all(include_bytes!("memory.x")) 23 | .unwrap(); 24 | println!("cargo:rustc-link-search={}", out.display()); 25 | 26 | #[cfg(not(feature = "skip-cyw43-firmware"))] 27 | download_cyw43_firmware(); 28 | 29 | // By default, Cargo will re-run a build script whenever 30 | // any file in the project changes. By specifying `memory.x` 31 | // here, we ensure the build script is only re-run when 32 | // `memory.x` is changed. 33 | println!("cargo:rerun-if-changed=memory.x"); 34 | 35 | println!("cargo:rustc-link-arg-bins=--nmagic"); 36 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); 37 | println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); 38 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); 39 | } 40 | 41 | #[cfg(not(feature = "skip-cyw43-firmware"))] 42 | fn download_cyw43_firmware() { 43 | let download_folder = "cyw43-firmware"; 44 | let url_base = "https://github.com/embassy-rs/embassy/raw/refs/heads/main/cyw43-firmware"; 45 | let file_names = [ 46 | "43439A0.bin", 47 | "43439A0_btfw.bin", 48 | "43439A0_clm.bin", 49 | "LICENSE-permissive-binary-license-1.0.txt", 50 | "README.md", 51 | ]; 52 | 53 | println!("cargo:rerun-if-changed=build.rs"); 54 | println!("cargo:rerun-if-changed={}", download_folder); 55 | std::fs::create_dir_all(download_folder).expect("Failed to create download directory"); 56 | 57 | // download each file into the folder "cyw43-firmware" 58 | for file in file_names { 59 | let url = format!("{}/{}", url_base, file); 60 | // only fetch if it doesn't exist 61 | if std::path::Path::new(download_folder).join(file).exists() { 62 | continue; 63 | } 64 | match reqwest::blocking::get(&url) { 65 | Ok(response) => { 66 | let content = response.bytes().expect("Failed to read file content"); 67 | let file_path = PathBuf::from(download_folder).join(file); 68 | std::fs::write(file_path, &content).expect("Failed to write file"); 69 | } 70 | Err(err) => panic!( 71 | "Failed to download the cyw43 firmware from {}: {}, required for pi-pico-w example", 72 | url, err 73 | ), 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/rp-pico-w/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 3 | FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 4 | 5 | /* Pick one of the two options for RAM layout */ 6 | 7 | /* OPTION A: Use all RAM banks as one big block */ 8 | /* Reasonable, unless you are doing something */ 9 | /* really particular with DMA or other concurrent */ 10 | /* access that would benefit from striping */ 11 | RAM : ORIGIN = 0x20000000, LENGTH = 264K 12 | 13 | /* OPTION B: Keep the unstriped sections separate */ 14 | /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ 15 | /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ 16 | /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ 17 | } 18 | -------------------------------------------------------------------------------- /examples/rp-pico-w/src/bin/ble_bas_central.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cyw43_pio::PioSpi; 5 | use defmt::*; 6 | use embassy_executor::Spawner; 7 | use embassy_rp::bind_interrupts; 8 | use embassy_rp::gpio::{Level, Output}; 9 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; 10 | use embassy_rp::pio::{InterruptHandler, Pio}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_bas_central; 13 | use trouble_host::prelude::ExternalController; 14 | use {defmt_rtt as _, embassy_time as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | PIO0_IRQ_0 => InterruptHandler; 18 | }); 19 | 20 | #[embassy_executor::task] 21 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { 22 | runner.run().await 23 | } 24 | 25 | #[embassy_executor::main] 26 | async fn main(spawner: Spawner) { 27 | let p = embassy_rp::init(Default::default()); 28 | 29 | #[cfg(feature = "skip-cyw43-firmware")] 30 | let (fw, clm, btfw) = (&[], &[], &[]); 31 | 32 | #[cfg(not(feature = "skip-cyw43-firmware"))] 33 | let (fw, clm, btfw) = { 34 | // IMPORTANT 35 | // 36 | // Download and make sure these files from https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware 37 | // are available in `./examples/rp-pico-w`. (should be automatic) 38 | // 39 | // IMPORTANT 40 | let fw = include_bytes!("../../cyw43-firmware/43439A0.bin"); 41 | let clm = include_bytes!("../../cyw43-firmware/43439A0_clm.bin"); 42 | let btfw = include_bytes!("../../cyw43-firmware/43439A0_btfw.bin"); 43 | (fw, clm, btfw) 44 | }; 45 | 46 | let pwr = Output::new(p.PIN_23, Level::Low); 47 | let cs = Output::new(p.PIN_25, Level::High); 48 | let mut pio = Pio::new(p.PIO0, Irqs); 49 | let spi = PioSpi::new( 50 | &mut pio.common, 51 | pio.sm0, 52 | cyw43_pio::DEFAULT_CLOCK_DIVIDER, 53 | pio.irq0, 54 | cs, 55 | p.PIN_24, 56 | p.PIN_29, 57 | p.DMA_CH0, 58 | ); 59 | 60 | static STATE: StaticCell = StaticCell::new(); 61 | let state = STATE.init(cyw43::State::new()); 62 | let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; 63 | unwrap!(spawner.spawn(cyw43_task(runner))); 64 | control.init(clm).await; 65 | 66 | let controller: ExternalController<_, 10> = ExternalController::new(bt_device); 67 | 68 | ble_bas_central::run(controller).await; 69 | } 70 | -------------------------------------------------------------------------------- /examples/rp-pico-w/src/bin/ble_bas_peripheral.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cyw43_pio::PioSpi; 5 | use defmt::*; 6 | use embassy_executor::Spawner; 7 | use embassy_rp::bind_interrupts; 8 | use embassy_rp::gpio::{Level, Output}; 9 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; 10 | use embassy_rp::pio::{InterruptHandler, Pio}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_bas_peripheral; 13 | use trouble_host::prelude::ExternalController; 14 | use {defmt_rtt as _, embassy_time as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | PIO0_IRQ_0 => InterruptHandler; 18 | }); 19 | 20 | #[embassy_executor::task] 21 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { 22 | runner.run().await 23 | } 24 | 25 | #[embassy_executor::main] 26 | async fn main(spawner: Spawner) { 27 | let p = embassy_rp::init(Default::default()); 28 | 29 | #[cfg(feature = "skip-cyw43-firmware")] 30 | let (fw, clm, btfw) = (&[], &[], &[]); 31 | 32 | #[cfg(not(feature = "skip-cyw43-firmware"))] 33 | let (fw, clm, btfw) = { 34 | // IMPORTANT 35 | // 36 | // Download and make sure these files from https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware 37 | // are available in `./examples/rp-pico-w`. (should be automatic) 38 | // 39 | // IMPORTANT 40 | let fw = include_bytes!("../../cyw43-firmware/43439A0.bin"); 41 | let clm = include_bytes!("../../cyw43-firmware/43439A0_clm.bin"); 42 | let btfw = include_bytes!("../../cyw43-firmware/43439A0_btfw.bin"); 43 | (fw, clm, btfw) 44 | }; 45 | 46 | let pwr = Output::new(p.PIN_23, Level::Low); 47 | let cs = Output::new(p.PIN_25, Level::High); 48 | let mut pio = Pio::new(p.PIO0, Irqs); 49 | let spi = PioSpi::new( 50 | &mut pio.common, 51 | pio.sm0, 52 | cyw43_pio::DEFAULT_CLOCK_DIVIDER, 53 | pio.irq0, 54 | cs, 55 | p.PIN_24, 56 | p.PIN_29, 57 | p.DMA_CH0, 58 | ); 59 | 60 | static STATE: StaticCell = StaticCell::new(); 61 | let state = STATE.init(cyw43::State::new()); 62 | let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; 63 | unwrap!(spawner.spawn(cyw43_task(runner))); 64 | control.init(clm).await; 65 | 66 | let controller: ExternalController<_, 10> = ExternalController::new(bt_device); 67 | 68 | ble_bas_peripheral::run(controller).await; 69 | } 70 | -------------------------------------------------------------------------------- /examples/rp-pico-w/src/bin/ble_beacon.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cyw43_pio::PioSpi; 5 | use defmt::*; 6 | use embassy_executor::Spawner; 7 | use embassy_rp::bind_interrupts; 8 | use embassy_rp::gpio::{Level, Output}; 9 | use embassy_rp::peripherals::{DMA_CH0, PIO0}; 10 | use embassy_rp::pio::{InterruptHandler, Pio}; 11 | use static_cell::StaticCell; 12 | use trouble_example_apps::ble_beacon; 13 | use trouble_host::prelude::ExternalController; 14 | use {defmt_rtt as _, embassy_time as _, panic_probe as _}; 15 | 16 | bind_interrupts!(struct Irqs { 17 | PIO0_IRQ_0 => InterruptHandler; 18 | }); 19 | 20 | #[embassy_executor::task] 21 | async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { 22 | runner.run().await 23 | } 24 | 25 | #[embassy_executor::main] 26 | async fn main(spawner: Spawner) { 27 | let p = embassy_rp::init(Default::default()); 28 | 29 | #[cfg(feature = "skip-cyw43-firmware")] 30 | let (fw, clm, btfw) = (&[], &[], &[]); 31 | 32 | #[cfg(not(feature = "skip-cyw43-firmware"))] 33 | let (fw, clm, btfw) = { 34 | // IMPORTANT 35 | // 36 | // Download and make sure these files from https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware 37 | // are available in `./examples/rp-pico-w`. (should be automatic) 38 | // 39 | // IMPORTANT 40 | let fw = include_bytes!("../../cyw43-firmware/43439A0.bin"); 41 | let clm = include_bytes!("../../cyw43-firmware/43439A0_clm.bin"); 42 | let btfw = include_bytes!("../../cyw43-firmware/43439A0_btfw.bin"); 43 | (fw, clm, btfw) 44 | }; 45 | 46 | let pwr = Output::new(p.PIN_23, Level::Low); 47 | let cs = Output::new(p.PIN_25, Level::High); 48 | let mut pio = Pio::new(p.PIO0, Irqs); 49 | let spi = PioSpi::new( 50 | &mut pio.common, 51 | pio.sm0, 52 | cyw43_pio::DEFAULT_CLOCK_DIVIDER, 53 | pio.irq0, 54 | cs, 55 | p.PIN_24, 56 | p.PIN_29, 57 | p.DMA_CH0, 58 | ); 59 | 60 | static STATE: StaticCell = StaticCell::new(); 61 | let state = STATE.init(cyw43::State::new()); 62 | let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; 63 | unwrap!(spawner.spawn(cyw43_task(runner))); 64 | control.init(clm).await; 65 | 66 | let controller: ExternalController<_, 10> = ExternalController::new(bt_device); 67 | 68 | ble_beacon::run(controller).await; 69 | } 70 | -------------------------------------------------------------------------------- /examples/serial-hci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serial-hci" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | env_logger = "0.10.0" 8 | log = "0.4" 9 | embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } 10 | embassy-sync = { version = "0.7", features = ["log"] } 11 | embassy-time = { version = "0.4", features = ["log", "std", "generic-queue-8"] } 12 | critical-section = { version = "1.1", features = ["std"] } 13 | rand = { version = "0.8.5", features = ["getrandom"] } 14 | rand_core = { version = "0.3" } 15 | tokio = { version = "1", features = ["full"] } 16 | tokio-serial = "5.4" 17 | trouble-example-apps = { version = "0.1.0", path = "../apps", features = ["log"] } 18 | trouble-host = { path = "../../host" } 19 | 20 | [features] 21 | security = [ 22 | "trouble-example-apps/security", 23 | ] 24 | -------------------------------------------------------------------------------- /examples/serial-hci/README.md: -------------------------------------------------------------------------------- 1 | # Serial HCI example 2 | 3 | These examples require two Bluetooth dongles with a Host Controller Interface (HCI). 4 | The examples have been tested with two nRF52840 dongles running the [HIC UART example](https://github.com/nrfconnect/sdk-zephyr/tree/main/samples/bluetooth/hci_uart). 5 | 6 | ## Using Zephyr HCI-UART 7 | 8 | The [Zephyr HCI-UART sample](https://docs.zephyrproject.org/latest/samples/bluetooth/hci_uart/README.html) an be used as a controller for the serial-hci host. 9 | 10 | ### nRF52840 Dongle 11 | 12 | To use the nRF528040-dongle as a HCI serial controller, build the HCI-UART sample for the `nrf52840dongle` board. 13 | 14 | ```bash 15 | west build -p always -b nrf52840dongle samples/bluetooth/hci_uart 16 | ``` 17 | 18 | For refrence, see the [Nordic HCI-UART sample page](https://docs.nordicsemi.com/bundle/ncs-latest/page/zephyr/samples/bluetooth/hci_uart/README.html#bluetooth_hci_uart). 19 | 20 | ## High throughput example 21 | 22 | The high throughput examples require some modifications to the default configurations of the HCI UART example. 23 | The default configuration will set up an HCI UART Bluetooth dongle that has 3, 27-byte wide TX buffers. 24 | This is indicated by the following information log 25 | 26 | ```bash 27 | INFO trouble_host::host] [host] setting txq to 3, fragmenting at 27 28 | ``` 29 | 30 | The high throughput examples require these buffers to be the maximum size of 251 and as many buffers as allowed, 20. 31 | To affect these changes, add the following to the `proj.conf` file of the HCI UART example. 32 | 33 | ```conf 34 | # Enable data length extension 35 | CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 36 | CONFIG_BT_BUF_ACL_TX_SIZE=251 37 | 38 | # Increase buffer count 39 | CONFIG_BT_CTLR_SDC_TX_PACKET_COUNT=20 40 | CONFIG_BT_CTLR_SDC_RX_PACKET_COUNT=20 41 | ``` 42 | -------------------------------------------------------------------------------- /examples/serial-hci/src/alloc.rs: -------------------------------------------------------------------------------- 1 | use std::boxed::Box; 2 | use trouble_host::prelude::{Packet, PacketPool}; 3 | 4 | const MTU: usize = 2510; 5 | 6 | pub struct Buf { 7 | pub data: [u8; MTU], 8 | } 9 | 10 | pub struct BigAlloc; 11 | pub struct BigBuf(Box); 12 | 13 | impl PacketPool for BigAlloc { 14 | type Packet = BigBuf; 15 | const MTU: usize = MTU; 16 | fn allocate() -> Option { 17 | let b = Buf { data: [0; MTU] }; 18 | Some(BigBuf(Box::new(b))) 19 | } 20 | 21 | fn capacity() -> usize { 22 | 64 23 | } 24 | } 25 | 26 | impl AsRef<[u8]> for BigBuf { 27 | fn as_ref(&self) -> &[u8] { 28 | &self.0.data[..] 29 | } 30 | } 31 | 32 | impl AsMut<[u8]> for BigBuf { 33 | fn as_mut(&mut self) -> &mut [u8] { 34 | &mut self.0.data[..] 35 | } 36 | } 37 | 38 | impl Packet for BigBuf {} 39 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/ble_bas_central.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use tokio::time::Duration; 5 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 6 | use trouble_example_apps::ble_bas_central; 7 | use trouble_host::prelude::{ExternalController, SerialTransport}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | env_logger::builder() 12 | .filter_level(log::LevelFilter::Trace) 13 | .format_timestamp_nanos() 14 | .init(); 15 | 16 | let baudrate = 1000000; 17 | 18 | if std::env::args().len() != 2 { 19 | println!("Provide the serial port as the one and only command line argument."); 20 | return; 21 | } 22 | 23 | let args: Vec = std::env::args().collect(); 24 | 25 | let mut port = SerialStream::open( 26 | &tokio_serial::new(args[1].as_str(), baudrate) 27 | .baud_rate(baudrate) 28 | .data_bits(DataBits::Eight) 29 | .parity(Parity::None) 30 | .stop_bits(StopBits::One), 31 | ) 32 | .unwrap(); 33 | 34 | // Drain input 35 | tokio::time::sleep(Duration::from_secs(1)).await; 36 | loop { 37 | let mut buf = [0; 1]; 38 | match port.try_read(&mut buf[..]) { 39 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 40 | _ => {} 41 | } 42 | } 43 | info!("Ready!"); 44 | 45 | let (reader, writer) = tokio::io::split(port); 46 | 47 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 48 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 49 | 50 | let driver: SerialTransport = SerialTransport::new(reader, writer); 51 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 52 | 53 | ble_bas_central::run(controller).await; 54 | } 55 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/ble_bas_central_sec.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use rand::rngs::OsRng; 5 | use tokio::time::Duration; 6 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 7 | use trouble_example_apps::ble_bas_central_sec; 8 | use trouble_host::prelude::{ExternalController, SerialTransport}; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | env_logger::builder() 13 | .filter_level(log::LevelFilter::Trace) 14 | .format_timestamp_nanos() 15 | .init(); 16 | 17 | let baudrate = 1000000; 18 | 19 | if std::env::args().len() != 2 { 20 | println!("Provide the serial port as the one and only command line argument."); 21 | return; 22 | } 23 | 24 | let args: Vec = std::env::args().collect(); 25 | 26 | let mut port = SerialStream::open( 27 | &tokio_serial::new(args[1].as_str(), baudrate) 28 | .baud_rate(baudrate) 29 | .data_bits(DataBits::Eight) 30 | .parity(Parity::None) 31 | .stop_bits(StopBits::One), 32 | ) 33 | .unwrap(); 34 | 35 | // Drain input 36 | tokio::time::sleep(Duration::from_secs(1)).await; 37 | loop { 38 | let mut buf = [0; 1]; 39 | match port.try_read(&mut buf[..]) { 40 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 41 | _ => {} 42 | } 43 | } 44 | info!("Ready!"); 45 | 46 | let (reader, writer) = tokio::io::split(port); 47 | 48 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 49 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 50 | 51 | let driver: SerialTransport = SerialTransport::new(reader, writer); 52 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 53 | 54 | ble_bas_central_sec::run(controller, &mut OsRng).await; 55 | } 56 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/ble_bas_peripheral.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use tokio::time::Duration; 5 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 6 | use trouble_example_apps::ble_bas_peripheral; 7 | use trouble_host::prelude::{ExternalController, SerialTransport}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | env_logger::builder() 12 | .filter_level(log::LevelFilter::Trace) 13 | .format_timestamp_nanos() 14 | .init(); 15 | 16 | let baudrate = 1000000; 17 | 18 | if std::env::args().len() != 2 { 19 | println!("Provide the serial port as the one and only command line argument."); 20 | return; 21 | } 22 | 23 | let args: Vec = std::env::args().collect(); 24 | 25 | let mut port = SerialStream::open( 26 | &tokio_serial::new(args[1].as_str(), baudrate) 27 | .baud_rate(baudrate) 28 | .data_bits(DataBits::Eight) 29 | .parity(Parity::None) 30 | .stop_bits(StopBits::One), 31 | ) 32 | .unwrap(); 33 | 34 | // Drain input 35 | tokio::time::sleep(Duration::from_secs(1)).await; 36 | loop { 37 | let mut buf = [0; 1]; 38 | match port.try_read(&mut buf[..]) { 39 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 40 | _ => {} 41 | } 42 | } 43 | info!("Ready!"); 44 | 45 | let (reader, writer) = tokio::io::split(port); 46 | 47 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 48 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 49 | 50 | let driver: SerialTransport = SerialTransport::new(reader, writer); 51 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 52 | 53 | ble_bas_peripheral::run(controller).await; 54 | } 55 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/ble_bas_peripheral_sec.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use rand::rngs::OsRng; 5 | use tokio::time::Duration; 6 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 7 | use trouble_example_apps::ble_bas_peripheral_sec; 8 | use trouble_host::prelude::{ExternalController, SerialTransport}; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | env_logger::builder() 13 | .filter_level(log::LevelFilter::Trace) 14 | .format_timestamp_nanos() 15 | .init(); 16 | 17 | let baudrate = 1000000; 18 | 19 | if std::env::args().len() != 2 { 20 | println!("Provide the serial port as the one and only command line argument."); 21 | return; 22 | } 23 | 24 | let args: Vec = std::env::args().collect(); 25 | 26 | let mut port = SerialStream::open( 27 | &tokio_serial::new(args[1].as_str(), baudrate) 28 | .baud_rate(baudrate) 29 | .data_bits(DataBits::Eight) 30 | .parity(Parity::None) 31 | .stop_bits(StopBits::One), 32 | ) 33 | .unwrap(); 34 | 35 | // Drain input 36 | tokio::time::sleep(Duration::from_secs(1)).await; 37 | loop { 38 | let mut buf = [0; 1]; 39 | match port.try_read(&mut buf[..]) { 40 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 41 | _ => {} 42 | } 43 | } 44 | info!("Ready!"); 45 | 46 | let (reader, writer) = tokio::io::split(port); 47 | 48 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 49 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 50 | 51 | let driver: SerialTransport = SerialTransport::new(reader, writer); 52 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 53 | 54 | ble_bas_peripheral_sec::run(controller, &mut OsRng).await; 55 | } 56 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/ble_l2cap_central.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use tokio::time::Duration; 5 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 6 | use trouble_example_apps::ble_l2cap_central; 7 | use trouble_host::prelude::{ExternalController, SerialTransport}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | env_logger::builder() 12 | .filter_level(log::LevelFilter::Trace) 13 | .format_timestamp_nanos() 14 | .init(); 15 | 16 | let baudrate = 1000000; 17 | 18 | if std::env::args().len() != 2 { 19 | println!("Provide the serial port as the one and only command line argument."); 20 | return; 21 | } 22 | 23 | let args: Vec = std::env::args().collect(); 24 | 25 | let mut port = SerialStream::open( 26 | &tokio_serial::new(args[1].as_str(), baudrate) 27 | .baud_rate(baudrate) 28 | .data_bits(DataBits::Eight) 29 | .parity(Parity::None) 30 | .stop_bits(StopBits::One), 31 | ) 32 | .unwrap(); 33 | 34 | // Drain input 35 | tokio::time::sleep(Duration::from_secs(1)).await; 36 | loop { 37 | let mut buf = [0; 1]; 38 | match port.try_read(&mut buf[..]) { 39 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 40 | _ => {} 41 | } 42 | } 43 | info!("Ready!"); 44 | 45 | let (reader, writer) = tokio::io::split(port); 46 | 47 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 48 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 49 | 50 | let driver: SerialTransport = SerialTransport::new(reader, writer); 51 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 52 | 53 | ble_l2cap_central::run(controller).await; 54 | } 55 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/ble_l2cap_peripheral.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use tokio::time::Duration; 5 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 6 | use trouble_example_apps::ble_l2cap_peripheral; 7 | use trouble_host::prelude::{ExternalController, SerialTransport}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | env_logger::builder() 12 | .filter_level(log::LevelFilter::Trace) 13 | .format_timestamp_nanos() 14 | .init(); 15 | 16 | let baudrate = 1000000; 17 | 18 | if std::env::args().len() != 2 { 19 | println!("Provide the serial port as the one and only command line argument."); 20 | return; 21 | } 22 | 23 | let args: Vec = std::env::args().collect(); 24 | 25 | let mut port = SerialStream::open( 26 | &tokio_serial::new(args[1].as_str(), baudrate) 27 | .baud_rate(baudrate) 28 | .data_bits(DataBits::Eight) 29 | .parity(Parity::None) 30 | .stop_bits(StopBits::One), 31 | ) 32 | .unwrap(); 33 | 34 | // Drain input 35 | tokio::time::sleep(Duration::from_secs(1)).await; 36 | loop { 37 | let mut buf = [0; 1]; 38 | match port.try_read(&mut buf[..]) { 39 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 40 | _ => {} 41 | } 42 | } 43 | info!("Ready!"); 44 | 45 | let (reader, writer) = tokio::io::split(port); 46 | 47 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 48 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 49 | 50 | let driver: SerialTransport = SerialTransport::new(reader, writer); 51 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 52 | 53 | ble_l2cap_peripheral::run(controller).await; 54 | } 55 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/high_throughput_ble_l2cap_central.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use tokio::time::Duration; 5 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 6 | use trouble_example_apps::high_throughput_ble_l2cap_central; 7 | use trouble_host::prelude::{ExternalController, SerialTransport}; 8 | 9 | #[path = "../alloc.rs"] 10 | mod alloc; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | env_logger::builder() 15 | .filter_level(log::LevelFilter::Trace) 16 | .format_timestamp_nanos() 17 | .init(); 18 | 19 | let baudrate = 1000000; 20 | 21 | if std::env::args().len() != 2 { 22 | println!("Provide the serial port as the one and only command line argument."); 23 | return; 24 | } 25 | 26 | let args: Vec = std::env::args().collect(); 27 | 28 | let mut port = SerialStream::open( 29 | &tokio_serial::new(args[1].as_str(), baudrate) 30 | .baud_rate(baudrate) 31 | .data_bits(DataBits::Eight) 32 | .parity(Parity::None) 33 | .stop_bits(StopBits::One), 34 | ) 35 | .unwrap(); 36 | 37 | // Drain input 38 | tokio::time::sleep(Duration::from_secs(1)).await; 39 | loop { 40 | let mut buf = [0; 1]; 41 | match port.try_read(&mut buf[..]) { 42 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 43 | _ => {} 44 | } 45 | } 46 | info!("Ready!"); 47 | 48 | let (reader, writer) = tokio::io::split(port); 49 | 50 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 51 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 52 | 53 | let driver: SerialTransport = SerialTransport::new(reader, writer); 54 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 55 | 56 | // Setting the L2CAP MTU to be ten times the size of the PDU. 57 | // This size of the L2CAP MTU does not consume all the controller buffers. 58 | high_throughput_ble_l2cap_central::run::<_, alloc::BigAlloc>(controller).await; 59 | } 60 | -------------------------------------------------------------------------------- /examples/serial-hci/src/bin/high_throughput_ble_l2cap_peripheral.rs: -------------------------------------------------------------------------------- 1 | // Use with any serial HCI 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 3 | use log::*; 4 | use tokio::time::Duration; 5 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 6 | use trouble_example_apps::high_throughput_ble_l2cap_peripheral; 7 | use trouble_host::prelude::{ExternalController, SerialTransport}; 8 | 9 | #[path = "../alloc.rs"] 10 | mod alloc; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | env_logger::builder() 15 | .filter_level(log::LevelFilter::Trace) 16 | .format_timestamp_nanos() 17 | .init(); 18 | 19 | let baudrate = 1000000; 20 | 21 | if std::env::args().len() != 2 { 22 | println!("Provide the serial port as the one and only command line argument."); 23 | return; 24 | } 25 | 26 | let args: Vec = std::env::args().collect(); 27 | 28 | let mut port = SerialStream::open( 29 | &tokio_serial::new(args[1].as_str(), baudrate) 30 | .baud_rate(baudrate) 31 | .data_bits(DataBits::Eight) 32 | .parity(Parity::None) 33 | .stop_bits(StopBits::One), 34 | ) 35 | .unwrap(); 36 | 37 | // Drain input 38 | tokio::time::sleep(Duration::from_secs(1)).await; 39 | loop { 40 | let mut buf = [0; 1]; 41 | match port.try_read(&mut buf[..]) { 42 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 43 | _ => {} 44 | } 45 | } 46 | info!("Ready!"); 47 | 48 | let (reader, writer) = tokio::io::split(port); 49 | 50 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 51 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 52 | 53 | let driver: SerialTransport = SerialTransport::new(reader, writer); 54 | let controller: ExternalController<_, 10> = ExternalController::new(driver); 55 | 56 | // Setting the L2CAP MTU to be ten times the size of the PDU. 57 | // This size of the L2CAP MTU does not consume all the controller buffers. 58 | high_throughput_ble_l2cap_peripheral::run::<_, alloc::BigAlloc>(controller).await; 59 | } 60 | -------------------------------------------------------------------------------- /examples/tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-example-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | trouble-host = { path = "../../host", features = ["derive", "log"] } 9 | bt-hci = { version = "0.3" } 10 | serde = { version = "1", features = ["derive"] } 11 | futures = "0.3" 12 | serde_json = "1" 13 | log = "0.4" 14 | tokio = { version = "1", default-features = false, features = [ 15 | "time", 16 | "rt-multi-thread", 17 | "macros", 18 | "process" 19 | ] } 20 | pretty_env_logger = "0.5.0" 21 | reqwest = "0.12" 22 | hilbench-agent = "0.1.0" 23 | embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } 24 | embedded-io-async = { version = "0.6.1" } 25 | embedded-io = { version = "0.6.1" } 26 | critical-section = { version = "1", features = ["std"] } 27 | embassy-sync = "0.7" 28 | tokio-serial = "5.4" 29 | tokio-util = "0.7" 30 | rand = "0.8.5" 31 | heapless = "0.8.0" 32 | anyhow = "1" 33 | tempfile = "3.15" 34 | 35 | [patch.crates-io] 36 | hilbench-agent = { git = "https://github.com/lulf/hilbench.git", rev = "700693cec2f813967f6717341296828d4c2971ae" } 37 | -------------------------------------------------------------------------------- /examples/tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use hilbench_agent::ProbeConfig; 3 | use probe::DeviceUnderTest; 4 | 5 | pub mod probe; 6 | pub mod serial; 7 | 8 | pub struct TestContext { 9 | pub serial_adapters: Vec, 10 | pub probe_config: ProbeConfig, 11 | } 12 | 13 | impl TestContext { 14 | pub fn new() -> Self { 15 | let serial_adapters = serial::find_controllers(); 16 | let config = std::env::var("PROBE_CONFIG").unwrap(); 17 | log::info!("Using probe config {}", config); 18 | let probe_config = serde_json::from_str(&config).unwrap(); 19 | 20 | Self { 21 | serial_adapters, 22 | probe_config, 23 | } 24 | } 25 | 26 | pub fn find_dut(&self, labels: &[(&str, &str)]) -> Result, anyhow::Error> { 27 | let selector = hilbench_agent::init(self.probe_config.clone()); 28 | let target = selector 29 | .select(labels) 30 | .ok_or(anyhow!("Unable to find DUT for {:?}", labels))?; 31 | Ok(DeviceUnderTest::new(target)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/tests/src/probe/mod.rs: -------------------------------------------------------------------------------- 1 | use std::process::Stdio; 2 | use tokio::io::AsyncBufReadExt; 3 | use tokio::io::BufReader; 4 | use tokio::process::Command; 5 | use tokio::select; 6 | use tokio_util::sync::CancellationToken; 7 | 8 | use hilbench_agent::ProbeConfig; 9 | use hilbench_agent::Target; 10 | 11 | pub fn init(config: ProbeConfig) { 12 | hilbench_agent::init(config); 13 | } 14 | 15 | pub struct Firmware { 16 | pub data: Vec, 17 | } 18 | 19 | pub struct DeviceUnderTest<'d> { 20 | target: Target<'d>, 21 | token: CancellationToken, 22 | } 23 | 24 | impl<'d> DeviceUnderTest<'d> { 25 | pub(crate) fn new(target: Target<'d>) -> Self { 26 | Self { 27 | target, 28 | token: CancellationToken::new(), 29 | } 30 | } 31 | pub fn token(&self) -> CancellationToken { 32 | self.token.clone() 33 | } 34 | 35 | pub async fn run(self, firmware: String) -> Result { 36 | let mut flasher = if self.target.config().chip.starts_with("esp32") { 37 | Command::new("espflash") 38 | .stdout(Stdio::piped()) 39 | .stderr(Stdio::piped()) 40 | .arg("flash") 41 | .arg(&firmware) 42 | .arg("--monitor") 43 | .spawn() 44 | .unwrap() 45 | } else { 46 | Command::new("probe-rs") 47 | .stdout(Stdio::piped()) 48 | .stderr(Stdio::piped()) 49 | .arg("run") 50 | .arg(&firmware) 51 | .arg("--chip") 52 | .arg(&self.target.config().chip) 53 | .arg("--probe") 54 | .arg(&self.target.config().probe) 55 | .spawn() 56 | .unwrap() 57 | }; 58 | 59 | let stdout = flasher.stdout.take().unwrap(); 60 | let stderr = flasher.stderr.take().unwrap(); 61 | let mut stdout_reader = BufReader::new(stdout).lines(); 62 | let mut stderr_reader = BufReader::new(stderr).lines(); 63 | 64 | let mut lines: Vec = Vec::new(); 65 | select! { 66 | r = flasher.wait() => { 67 | log::warn!("flasher exited unexpectedly: {:?}", r); 68 | for line in lines { 69 | log::warn!("{}", line); 70 | } 71 | return Err(anyhow::anyhow!("flasher exited unexpectedly: {:?}", r)); 72 | } 73 | _ = self.token.cancelled() => { 74 | flasher.kill().await.unwrap(); 75 | } 76 | _ = async { 77 | loop { 78 | select! { 79 | r = stdout_reader.next_line() => { 80 | if let Ok(Some(r)) = r { 81 | lines.push(r); 82 | } 83 | } 84 | r = stderr_reader.next_line() => { 85 | if let Ok(Some(r)) = r { 86 | lines.push(r); 87 | } 88 | } 89 | } 90 | } 91 | } => { 92 | 93 | } 94 | } 95 | log::info!("waiting for process exit"); 96 | flasher.wait().await.unwrap(); 97 | Ok(FirmwareLogs { lines }) 98 | } 99 | } 100 | 101 | pub struct FirmwareLogs { 102 | pub lines: Vec, 103 | } 104 | -------------------------------------------------------------------------------- /examples/tests/src/serial.rs: -------------------------------------------------------------------------------- 1 | use bt_hci::controller::ExternalController; 2 | use bt_hci::transport::SerialTransport; 3 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 4 | use embedded_io_adapters::tokio_1::FromTokio; 5 | use tokio::io::{ReadHalf, WriteHalf}; 6 | use tokio::time::Duration; 7 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 8 | 9 | pub type Controller = ExternalController< 10 | SerialTransport>, FromTokio>>, 11 | 10, 12 | >; 13 | 14 | pub fn find_controllers() -> Vec { 15 | let folder = "/dev/serial/by-id"; 16 | let mut paths = Vec::new(); 17 | for entry in std::fs::read_dir(folder).unwrap() { 18 | let entry = entry.unwrap(); 19 | let path = entry.path(); 20 | 21 | let file_name = path.file_name().unwrap().to_string_lossy(); 22 | if file_name.starts_with("usb-ZEPHYR_Zephyr_HCI_UART_sample") { 23 | paths.push(path.to_string_lossy().to_string()); 24 | } 25 | } 26 | paths 27 | } 28 | 29 | pub async fn create_controller( 30 | port: &str, 31 | ) -> ExternalController< 32 | SerialTransport>, FromTokio>>, 33 | 10, 34 | > { 35 | let baudrate = 1000000; 36 | let mut port = SerialStream::open( 37 | &tokio_serial::new(port, baudrate) 38 | .baud_rate(baudrate) 39 | .data_bits(DataBits::Eight) 40 | .parity(Parity::None) 41 | .stop_bits(StopBits::One), 42 | ) 43 | .unwrap(); 44 | 45 | // Drain input 46 | tokio::time::sleep(Duration::from_secs(1)).await; 47 | loop { 48 | let mut buf = [0; 1]; 49 | match port.try_read(&mut buf[..]) { 50 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 51 | _ => {} 52 | } 53 | } 54 | 55 | let (reader, writer) = tokio::io::split(port); 56 | 57 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 58 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 59 | 60 | let driver: SerialTransport = SerialTransport::new(reader, writer); 61 | ExternalController::new(driver) 62 | } 63 | -------------------------------------------------------------------------------- /examples/tests/tests/ble_l2cap_peripheral.rs: -------------------------------------------------------------------------------- 1 | use futures::future::join; 2 | use std::time::Duration; 3 | use tokio::select; 4 | use trouble_example_tests::{TestContext, serial}; 5 | use trouble_host::prelude::*; 6 | 7 | #[tokio::test] 8 | async fn ble_l2cap_peripheral_nrf52() { 9 | let _ = pretty_env_logger::try_init(); 10 | let firmware = "bins/nrf-sdc/ble_l2cap_peripheral"; 11 | let local = tokio::task::LocalSet::new(); 12 | local 13 | .run_until(run_l2cap_peripheral_test( 14 | &[("target", "nrf52"), ("board", "microbit")], 15 | firmware, 16 | )) 17 | .await; 18 | } 19 | 20 | /*#[tokio::test] 21 | async fn ble_l2cap_peripheral_esp32c3() { 22 | let _ = pretty_env_logger::try_init(); 23 | let firmware = "bins/esp32/ble_l2cap_peripheral"; 24 | let local = tokio::task::LocalSet::new(); 25 | local 26 | .run_until(run_l2cap_peripheral_test( 27 | &[("target", "esp32"), ("board", "esp-rust-board")], 28 | firmware, 29 | )) 30 | .await; 31 | }*/ 32 | 33 | async fn run_l2cap_peripheral_test(labels: &[(&str, &str)], firmware: &str) { 34 | let ctx = TestContext::new(); 35 | let central = ctx.serial_adapters[0].clone(); 36 | 37 | let dut = ctx.find_dut(labels).unwrap(); 38 | let token = dut.token(); 39 | let token2 = token.clone(); 40 | 41 | // Spawn a runner for the target 42 | let mut dut = tokio::task::spawn_local(dut.run(firmware.to_string())); 43 | 44 | // Run the central in the test using the serial adapter to verify 45 | let peripheral_address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 46 | let central = tokio::task::spawn_local(async move { 47 | let controller_central = serial::create_controller(¢ral).await; 48 | let mut resources: HostResources = HostResources::new(); 49 | let stack = trouble_host::new(controller_central, &mut resources); 50 | let Host { 51 | mut central, 52 | mut runner, 53 | .. 54 | } = stack.build(); 55 | select! { 56 | r = runner.run() => { 57 | r 58 | } 59 | r = async { 60 | let config = ConnectConfig { 61 | connect_params: Default::default(), 62 | scan_config: ScanConfig { 63 | active: true, 64 | filter_accept_list: &[(peripheral_address.kind, &peripheral_address.addr)], 65 | ..Default::default() 66 | }, 67 | }; 68 | 69 | log::info!("[central] connecting"); 70 | loop { 71 | let conn = central.connect(&config).await.unwrap(); 72 | log::info!("[central] connected"); 73 | const PAYLOAD_LEN: usize = 27; 74 | let config = L2capChannelConfig { 75 | mtu: Some(PAYLOAD_LEN as u16), 76 | ..Default::default() 77 | }; 78 | let mut ch1 = L2capChannel::create(&stack, &conn, 0x81, &config).await?; 79 | log::info!("[central] channel created"); 80 | for i in 0..10 { 81 | let tx = [i; PAYLOAD_LEN]; 82 | ch1.send(&stack, &tx).await?; 83 | } 84 | log::info!("[central] data sent"); 85 | let mut rx = [0; PAYLOAD_LEN]; 86 | for i in 0..10 { 87 | let len = ch1.receive(&stack, &mut rx).await?; 88 | assert_eq!(len, rx.len()); 89 | assert_eq!(rx, [i; PAYLOAD_LEN]); 90 | } 91 | log::info!("[central] data received"); 92 | token.cancel(); 93 | break; 94 | } 95 | Ok(()) 96 | } => { 97 | r 98 | } 99 | } 100 | }); 101 | 102 | match tokio::time::timeout(Duration::from_secs(30), join(&mut dut, central)).await { 103 | Err(_) => { 104 | println!("Test timed out"); 105 | token2.cancel(); 106 | let _ = tokio::time::timeout(Duration::from_secs(1), dut).await; 107 | assert!(false); 108 | } 109 | Ok((p, c)) => { 110 | p.expect("peripheral failed").unwrap(); 111 | c.expect("central failed").unwrap(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /host-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trouble-host-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | description = "An async Rust BLE host - Derive macros crate" 7 | keywords = ["no-std"] 8 | categories = ["embedded", "hardware-support", "no-std"] 9 | 10 | [dependencies] 11 | convert_case = "0.8.0" 12 | syn = { version = "^2", features = ["full", "extra-traits"] } 13 | quote = "1.0.7" 14 | darling = "0.20.10" 15 | proc-macro2 = "1.0.18" 16 | uuid = "1.10.0" 17 | 18 | [lib] 19 | proc-macro = true 20 | -------------------------------------------------------------------------------- /host-macros/src/ctxt.rs: -------------------------------------------------------------------------------- 1 | //! nifty utility borrowed from serde :) 2 | //! 3 | //! 4 | 5 | use std::cell::RefCell; 6 | use std::fmt::Display; 7 | use std::thread; 8 | 9 | use proc_macro2::TokenStream; 10 | use quote::{ToTokens, quote}; 11 | 12 | /// A type to collect errors together and format them. 13 | /// 14 | /// Dropping this object will cause a panic. It must be consumed using `check`. 15 | /// 16 | /// References can be shared since this type uses run-time exclusive mut checking. 17 | #[derive(Default)] 18 | pub struct Ctxt { 19 | // The contents will be set to `None` during checking. This is so that checking can be 20 | // enforced. 21 | errors: RefCell>>, 22 | } 23 | 24 | impl Ctxt { 25 | /// Create a new context object. 26 | /// 27 | /// This object contains no errors, but will still trigger a panic if it is not `check`ed. 28 | pub fn new() -> Self { 29 | Ctxt { 30 | errors: RefCell::new(Some(Vec::new())), 31 | } 32 | } 33 | 34 | /// Add an error to the context object with a tokenenizable object. 35 | /// 36 | /// The object is used for spanning in error messages. 37 | pub fn error_spanned_by(&self, obj: A, msg: T) { 38 | self.errors 39 | .borrow_mut() 40 | .as_mut() 41 | .unwrap() 42 | // Curb monomorphization from generating too many identical methods. 43 | .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); 44 | } 45 | 46 | /// Add one of Syn's parse errors. 47 | #[allow(unused)] 48 | pub fn syn_error(&self, err: syn::Error) { 49 | self.errors.borrow_mut().as_mut().unwrap().push(err); 50 | } 51 | 52 | /// Consume this object, producing a formatted error string if there are errors. 53 | pub fn check(self) -> Result<(), TokenStream> { 54 | let errors = self.errors.borrow_mut().take().unwrap(); 55 | match errors.len() { 56 | 0 => Ok(()), 57 | _ => Err(to_compile_errors(errors)), 58 | } 59 | } 60 | } 61 | 62 | fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { 63 | let compile_errors = errors.iter().map(syn::Error::to_compile_error); 64 | quote!(#(#compile_errors)*) 65 | } 66 | 67 | impl Drop for Ctxt { 68 | fn drop(&mut self) { 69 | if !thread::panicking() && self.errors.borrow().is_some() { 70 | panic!("forgot to check for errors"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /host-macros/src/uuid.rs: -------------------------------------------------------------------------------- 1 | //! UUID parsing and generation. This is used internally by the proc macros for parsing UUIDs from attributes. 2 | //! 3 | //! The UUIDs will then be converted to trouble-host UUIDs in the generated code. 4 | 5 | use core::str::FromStr; 6 | 7 | use darling::FromMeta; 8 | use proc_macro2::TokenStream as TokenStream2; 9 | use quote::quote; 10 | 11 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 12 | pub enum Uuid { 13 | Uuid16(u16), 14 | Uuid128([u8; 16]), 15 | } 16 | 17 | impl FromMeta for Uuid { 18 | fn from_string(value: &str) -> darling::Result { 19 | if let Ok(u) = uuid::Uuid::from_str(value) { 20 | let mut bytes = u.as_bytes().to_owned(); 21 | bytes.reverse(); // Little-endian, as per Bluetooth spec 22 | return Ok(Uuid::Uuid128(bytes)); 23 | } 24 | 25 | if value.len() == 4 { 26 | if let Ok(u) = u16::from_str_radix(value, 16) { 27 | return Ok(Uuid::Uuid16(u)); 28 | } 29 | } 30 | 31 | Err(darling::Error::custom( 32 | "Invalid UUID (must be a 16-bit or 128-bit UUID)", 33 | )) 34 | } 35 | } 36 | 37 | impl quote::ToTokens for Uuid { 38 | fn to_tokens(&self, tokens: &mut TokenStream2) { 39 | match self { 40 | Uuid::Uuid16(u) => tokens.extend(quote!(::trouble_host::types::uuid::Uuid::new_short(#u))), 41 | Uuid::Uuid128(u) => { 42 | let mut s = TokenStream2::new(); 43 | for b in u { 44 | s.extend(quote!(#b,)) 45 | } 46 | tokens.extend(quote!(::trouble_host::types::uuid::Uuid::new_long([#s]))); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /host/build.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Write; 3 | use std::path::PathBuf; 4 | use std::{env, fs}; 5 | 6 | static CONFIGS: &[(&str, usize)] = &[ 7 | // BEGIN AUTOGENERATED CONFIG FEATURES 8 | // Generated by gen_config.py. DO NOT EDIT. 9 | ("CONNECTION_EVENT_QUEUE_SIZE", 2), 10 | ("L2CAP_RX_QUEUE_SIZE", 8), 11 | ("L2CAP_TX_QUEUE_SIZE", 8), 12 | ("DEFAULT_PACKET_POOL_SIZE", 16), 13 | ("DEFAULT_PACKET_POOL_MTU", 251), 14 | ("GATT_CLIENT_NOTIFICATION_MAX_SUBSCRIBERS", 1), 15 | ("GATT_CLIENT_NOTIFICATION_QUEUE_SIZE", 1), 16 | // END AUTOGENERATED CONFIG FEATURES 17 | ]; 18 | 19 | struct ConfigState { 20 | value: usize, 21 | seen_feature: bool, 22 | seen_env: bool, 23 | } 24 | 25 | fn main() { 26 | let crate_name = env::var("CARGO_PKG_NAME") 27 | .unwrap() 28 | .to_ascii_uppercase() 29 | .replace('-', "_"); 30 | 31 | // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any 32 | // other file changed. 33 | println!("cargo::rustc-check-cfg=cfg(test)"); 34 | println!("cargo:rerun-if-changed=build.rs"); 35 | 36 | // Rebuild if config envvar changed. 37 | for (name, _) in CONFIGS { 38 | println!("cargo:rerun-if-env-changed={crate_name}_{name}"); 39 | } 40 | 41 | let mut configs = HashMap::new(); 42 | for (name, default) in CONFIGS { 43 | configs.insert( 44 | *name, 45 | ConfigState { 46 | value: *default, 47 | seen_env: false, 48 | seen_feature: false, 49 | }, 50 | ); 51 | } 52 | 53 | let prefix = format!("{crate_name}_"); 54 | for (var, value) in env::vars() { 55 | if let Some(name) = var.strip_prefix(&prefix) { 56 | let Some(cfg) = configs.get_mut(name) else { 57 | panic!("Unknown env var {name}") 58 | }; 59 | 60 | let Ok(value) = value.parse::() else { 61 | panic!("Invalid value for env var {name}: {value}") 62 | }; 63 | 64 | cfg.value = value; 65 | cfg.seen_env = true; 66 | } 67 | 68 | if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { 69 | if let Some(i) = feature.rfind('_') { 70 | let name = &feature[..i]; 71 | let value = &feature[i + 1..]; 72 | if let Some(cfg) = configs.get_mut(name) { 73 | let Ok(value) = value.parse::() else { 74 | panic!("Invalid value for feature {name}: {value}") 75 | }; 76 | 77 | // envvars take priority. 78 | if !cfg.seen_env { 79 | if cfg.seen_feature { 80 | panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); 81 | } 82 | 83 | cfg.value = value; 84 | cfg.seen_feature = true; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | let mut data = String::new(); 92 | 93 | for (name, cfg) in &configs { 94 | writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); 95 | } 96 | 97 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 98 | let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); 99 | fs::write(out_file, data).unwrap(); 100 | } 101 | -------------------------------------------------------------------------------- /host/gen_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | abspath = os.path.abspath(__file__) 4 | dname = os.path.dirname(abspath) 5 | os.chdir(dname) 6 | 7 | features = [] 8 | 9 | 10 | def feature(name, description, default, min=None, max=None, pow2=None, vals=None, factors=[]): 11 | if vals is None: 12 | assert min is not None 13 | assert max is not None 14 | 15 | vals = set() 16 | val = min 17 | while val <= max: 18 | vals.add(val) 19 | for f in factors: 20 | if val*f <= max: 21 | vals.add(val*f) 22 | if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0: 23 | val *= 2 24 | else: 25 | val += 1 26 | vals.add(default) 27 | vals = sorted(list(vals)) 28 | 29 | features.append( 30 | { 31 | "name": name, 32 | "default": default, 33 | "vals": vals, 34 | "description": description, 35 | } 36 | ) 37 | 38 | 39 | feature( 40 | "connection_event_queue_size", 41 | "Controls the size of the per-connection event queue.", 42 | default=2, min=1, max=64, pow2=True) 43 | feature("l2cap_rx_queue_size", 44 | "Controls the size of the L2CAP inbound queue per channel.", 45 | default=8, min=1, max=64, pow2=True) 46 | feature("l2cap_tx_queue_size", 47 | "Controls the size of the L2CAP outbound queue per channel.", 48 | default=8, min=1, max=64, pow2=True) 49 | feature("default_packet_pool_size", 50 | "Controls the pool size of the default packet pool, if enabled.", 51 | default=16, min=1, max=128, pow2=True) 52 | feature("default_packet_pool_mtu", 53 | "Controls the packet MTU of the default packet pool, if enabled.", 54 | default=251, vals = [27, 48, 64, 128, 251, 255, 512, 1024]) 55 | feature("gatt_client_notification_max_subscribers", 56 | "When using the GATT client, this controls how many subscribers can be created.", 57 | default=1, min=1, max=512, pow2=True) 58 | feature("gatt_client_notification_queue_size", 59 | "When using the GATT client, this controls how many notifications can be queued for each subscriber.", 60 | default=1, min=1, max=512, pow2=True) 61 | 62 | # ========= Update Cargo.toml 63 | 64 | things = "" 65 | for f in features: 66 | name = f["name"].replace("_", "-") 67 | desc = f["description"] 68 | things += f"# {desc}\n" 69 | for val in f["vals"]: 70 | things += f"{name}-{val} = []" 71 | if val == f["default"]: 72 | things += " # Default" 73 | things += "\n" 74 | things += "\n" 75 | 76 | SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" 77 | SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" 78 | HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" 79 | HELP += "# Most of these features are explained in detail in the Trouble Book.\n\n" 80 | with open("Cargo.toml", "r") as f: 81 | data = f.read() 82 | before, data = data.split(SEPARATOR_START, maxsplit=1) 83 | _, after = data.split(SEPARATOR_END, maxsplit=1) 84 | data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after 85 | with open("Cargo.toml", "w") as f: 86 | f.write(data) 87 | 88 | 89 | # ========= Update build.rs 90 | 91 | things = "" 92 | for f in features: 93 | name = f["name"].upper() 94 | things += f' ("{name}", {f["default"]}),\n' 95 | 96 | SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" 97 | SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" 98 | HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" 99 | with open("build.rs", "r") as f: 100 | data = f.read() 101 | before, data = data.split(SEPARATOR_START, maxsplit=1) 102 | _, after = data.split(SEPARATOR_END, maxsplit=1) 103 | data = before + SEPARATOR_START + HELP + \ 104 | things + " " + SEPARATOR_END + after 105 | with open("build.rs", "w") as f: 106 | f.write(data) 107 | -------------------------------------------------------------------------------- /host/src/codec.rs: -------------------------------------------------------------------------------- 1 | //! Opinionated BLE codec 2 | //! 3 | //! Assumes little endian for all types 4 | 5 | pub trait FixedSize: Sized { 6 | const SIZE: usize; 7 | } 8 | 9 | pub trait Type: Sized { 10 | fn size(&self) -> usize; 11 | } 12 | 13 | pub trait Encode: Type { 14 | fn encode(&self, dest: &mut [u8]) -> Result<(), Error>; 15 | } 16 | 17 | pub trait Decode<'d>: Type { 18 | fn decode(src: &'d [u8]) -> Result; 19 | } 20 | 21 | impl Type for T { 22 | fn size(&self) -> usize { 23 | Self::SIZE 24 | } 25 | } 26 | 27 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 28 | #[derive(Debug, Clone, Copy, PartialEq)] 29 | pub enum Error { 30 | InsufficientSpace, 31 | InvalidValue, 32 | } 33 | -------------------------------------------------------------------------------- /host/src/command.rs: -------------------------------------------------------------------------------- 1 | use core::cell::RefCell; 2 | use core::future::poll_fn; 3 | use core::task::{Context, Poll}; 4 | 5 | use embassy_sync::waitqueue::WakerRegistration; 6 | 7 | pub enum State { 8 | Active, 9 | Cancel(CTX), 10 | Idle, 11 | } 12 | 13 | pub struct Inner { 14 | state: State, 15 | host: WakerRegistration, 16 | controller: WakerRegistration, 17 | } 18 | 19 | /// A helper type for keeping track of the state of a controller command. 20 | pub struct CommandState { 21 | inner: RefCell>, 22 | } 23 | 24 | impl CommandState { 25 | pub fn new() -> Self { 26 | Self { 27 | inner: RefCell::new(Inner { 28 | state: State::Idle, 29 | host: WakerRegistration::new(), 30 | controller: WakerRegistration::new(), 31 | }), 32 | } 33 | } 34 | 35 | fn with_inner) -> R, R>(&self, mut f: F) -> R { 36 | let mut inner = self.inner.borrow_mut(); 37 | f(&mut inner) 38 | } 39 | 40 | /// Request a new command 41 | pub async fn request(&self) { 42 | poll_fn(|cx| { 43 | self.with_inner(|inner| { 44 | inner.host.register(cx.waker()); 45 | match inner.state { 46 | State::Idle => { 47 | inner.state = State::Active; 48 | Poll::Ready(()) 49 | } 50 | _ => Poll::Pending, 51 | } 52 | }) 53 | }) 54 | .await 55 | } 56 | 57 | /// Request a new command. 58 | pub async fn wait_idle(&self) { 59 | poll_fn(|cx| { 60 | self.with_inner(|inner| { 61 | inner.host.register(cx.waker()); 62 | match inner.state { 63 | State::Idle => Poll::Ready(()), 64 | _ => Poll::Pending, 65 | } 66 | }) 67 | }) 68 | .await 69 | } 70 | 71 | /// Poll if the command should be canceled 72 | pub fn poll_cancelled(&self, cx: &mut Context<'_>) -> Poll { 73 | self.with_inner(|inner| { 74 | inner.controller.register(cx.waker()); 75 | match inner.state { 76 | State::Cancel(ctx) => Poll::Ready(ctx), 77 | _ => Poll::Pending, 78 | } 79 | }) 80 | } 81 | 82 | /// Request that any pending command be canceled 83 | pub fn cancel(&self, ctx: CTX) { 84 | self.with_inner(|inner| { 85 | inner.state = State::Cancel(ctx); 86 | inner.controller.wake(); 87 | }) 88 | } 89 | 90 | /// Signal that a command has been canceled. 91 | pub fn canceled(&self) { 92 | self.with_inner(|inner| { 93 | inner.state = State::Idle; 94 | inner.host.wake(); 95 | }) 96 | } 97 | 98 | pub fn done(&self) { 99 | self.with_inner(|inner| { 100 | inner.state = State::Idle; 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /host/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Compile-time configuration. 2 | //! 3 | //! `trouble` has some configuration settings that are set at compile time. 4 | //! 5 | //! They can be set in two ways: 6 | //! 7 | //! - Via Cargo features: enable a feature like `-`. `name` must be in lowercase and 8 | //! use dashes instead of underscores. For example. `l2cap-rx-queue-size-4`. Only a selection of values 9 | //! is available, check `Cargo.toml` for the list. 10 | //! - Via environment variables at build time: set the variable named `TROUBLE_HOST_`. For example 11 | //! `TROUBLE_HOST_L2CAP_RX_QUEUE_SIZE=1 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. 12 | //! Any value can be set, unlike with Cargo features. 13 | //! 14 | //! Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting 15 | //! with different values, compilation fails. 16 | mod raw { 17 | #![allow(unused)] 18 | include!(concat!(env!("OUT_DIR"), "/config.rs")); 19 | } 20 | 21 | /// Connection event queue size 22 | /// 23 | /// This is the connection event queue size for every connection. 24 | /// 25 | /// Default: 2. 26 | pub const CONNECTION_EVENT_QUEUE_SIZE: usize = raw::CONNECTION_EVENT_QUEUE_SIZE; 27 | 28 | // ======== L2CAP parameters 29 | // 30 | /// L2CAP TX queue size 31 | /// 32 | /// This is the tx queue size for l2cap packets not sent directly in HCI (i.e. attributes). 33 | /// 34 | /// If the controller does not support tx buffering, increasing this value will allow 35 | /// a higher throughput between the controller and host. 36 | /// 37 | /// Default: 1. 38 | pub const L2CAP_TX_QUEUE_SIZE: usize = raw::L2CAP_TX_QUEUE_SIZE; 39 | 40 | /// L2CAP RX queue size 41 | /// 42 | /// This is the rx queue size of every l2cap channel. Every channel have to be able 43 | /// to buffer at least 1 packet, but if the controller already does buffering this 44 | /// may be sufficient. 45 | /// 46 | /// If the controller does not support rx buffering, increasing this value will allow 47 | /// a higher throughput between the controller and host. 48 | /// 49 | /// Default: 1. 50 | pub const L2CAP_RX_QUEUE_SIZE: usize = raw::L2CAP_RX_QUEUE_SIZE; 51 | 52 | /// L2CAP default packet pool size 53 | /// 54 | /// This is the default packet pool size of all l2cap channels. There has to be at least 55 | /// 1 packet that can be allocated, but the pool is shared among different channels. 56 | /// 57 | /// Default: 8. 58 | pub const DEFAULT_PACKET_POOL_SIZE: usize = raw::DEFAULT_PACKET_POOL_SIZE; 59 | 60 | /// L2CAP default packet pool mtu 61 | /// 62 | /// This is the default packet pool mtu for all l2cap channels. 63 | /// 64 | /// Default: 27. 65 | pub const DEFAULT_PACKET_POOL_MTU: usize = raw::DEFAULT_PACKET_POOL_MTU; 66 | 67 | /// Default: 1. 68 | pub const GATT_CLIENT_NOTIFICATION_MAX_SUBSCRIBERS: usize = raw::GATT_CLIENT_NOTIFICATION_MAX_SUBSCRIBERS; 69 | 70 | /// GATT notification queue size. 71 | /// 72 | /// Default: 1. 73 | pub const GATT_CLIENT_NOTIFICATION_QUEUE_SIZE: usize = raw::GATT_CLIENT_NOTIFICATION_QUEUE_SIZE; 74 | -------------------------------------------------------------------------------- /host/src/mock_controller.rs: -------------------------------------------------------------------------------- 1 | use core::convert::Infallible; 2 | use core::future::Future; 3 | 4 | use bt_hci::cmd::{self, AsyncCmd, SyncCmd}; 5 | use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync}; 6 | 7 | pub struct MockController {} 8 | 9 | impl MockController { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | impl embedded_io::ErrorType for MockController { 16 | type Error = Infallible; 17 | } 18 | 19 | impl bt_hci::controller::blocking::Controller for MockController { 20 | fn write_acl_data(&self, packet: &bt_hci::data::AclPacket) -> Result<(), Self::Error> { 21 | todo!() 22 | } 23 | 24 | fn write_sync_data(&self, packet: &bt_hci::data::SyncPacket) -> Result<(), Self::Error> { 25 | todo!() 26 | } 27 | 28 | fn write_iso_data(&self, packet: &bt_hci::data::IsoPacket) -> Result<(), Self::Error> { 29 | todo!() 30 | } 31 | 32 | fn try_write_acl_data( 33 | &self, 34 | packet: &bt_hci::data::AclPacket, 35 | ) -> Result<(), bt_hci::controller::blocking::TryError> { 36 | todo!() 37 | } 38 | 39 | fn try_write_sync_data( 40 | &self, 41 | packet: &bt_hci::data::SyncPacket, 42 | ) -> Result<(), bt_hci::controller::blocking::TryError> { 43 | todo!() 44 | } 45 | 46 | fn try_write_iso_data( 47 | &self, 48 | packet: &bt_hci::data::IsoPacket, 49 | ) -> Result<(), bt_hci::controller::blocking::TryError> { 50 | todo!() 51 | } 52 | 53 | fn read<'a>(&self, buf: &'a mut [u8]) -> Result, Self::Error> { 54 | todo!() 55 | } 56 | 57 | fn try_read<'a>( 58 | &self, 59 | buf: &'a mut [u8], 60 | ) -> Result, bt_hci::controller::blocking::TryError> { 61 | todo!() 62 | } 63 | } 64 | 65 | impl bt_hci::controller::Controller for MockController { 66 | fn write_acl_data(&self, packet: &bt_hci::data::AclPacket) -> impl Future> { 67 | async { todo!() } 68 | } 69 | 70 | fn write_sync_data(&self, packet: &bt_hci::data::SyncPacket) -> impl Future> { 71 | async { todo!() } 72 | } 73 | 74 | fn write_iso_data(&self, packet: &bt_hci::data::IsoPacket) -> impl Future> { 75 | async { todo!() } 76 | } 77 | 78 | fn read<'a>( 79 | &self, 80 | buf: &'a mut [u8], 81 | ) -> impl Future, Self::Error>> { 82 | async { todo!() } 83 | } 84 | } 85 | 86 | impl ControllerCmdSync for MockController { 87 | fn exec(&self, cmd: &C) -> impl Future>> { 88 | async { todo!() } 89 | } 90 | } 91 | 92 | impl ControllerCmdAsync for MockController { 93 | fn exec(&self, cmd: &C) -> impl Future>> { 94 | async { todo!() } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /host/src/pdu.rs: -------------------------------------------------------------------------------- 1 | use crate::Packet; 2 | 3 | pub(crate) struct Pdu

{ 4 | packet: P, 5 | len: usize, 6 | } 7 | 8 | impl

Pdu

{ 9 | pub(crate) fn new(packet: P, len: usize) -> Self { 10 | Self { packet, len } 11 | } 12 | pub(crate) fn len(&self) -> usize { 13 | self.len 14 | } 15 | pub(crate) fn into_inner(self) -> P { 16 | self.packet 17 | } 18 | } 19 | 20 | impl AsRef<[u8]> for Pdu

{ 21 | fn as_ref(&self) -> &[u8] { 22 | &self.packet.as_ref()[..self.len] 23 | } 24 | } 25 | 26 | impl AsMut<[u8]> for Pdu

{ 27 | fn as_mut(&mut self) -> &mut [u8] { 28 | &mut self.packet.as_mut()[..self.len] 29 | } 30 | } 31 | 32 | /// Service Data Unit 33 | /// 34 | /// A unit of payload that can be received or sent over an L2CAP channel. 35 | pub struct Sdu

{ 36 | pdu: Pdu

, 37 | } 38 | 39 | impl

Sdu

{ 40 | /// Create a new SDU using the allocated packet that has been pre-populated with data. 41 | pub fn new(packet: P, len: usize) -> Self { 42 | Self { 43 | pdu: Pdu::new(packet, len), 44 | } 45 | } 46 | 47 | pub(crate) fn from_pdu(pdu: Pdu

) -> Self { 48 | Self { pdu } 49 | } 50 | 51 | /// Payload length. 52 | pub fn len(&self) -> usize { 53 | self.pdu.len() 54 | } 55 | 56 | /// Payload length. 57 | pub fn is_empty(&self) -> bool { 58 | self.pdu.len() == 0 59 | } 60 | 61 | /// Retrieve the inner packet. 62 | pub fn into_inner(self) -> P { 63 | self.pdu.into_inner() 64 | } 65 | } 66 | 67 | impl AsRef<[u8]> for Sdu

{ 68 | fn as_ref(&self) -> &[u8] { 69 | self.pdu.as_ref() 70 | } 71 | } 72 | 73 | impl AsMut<[u8]> for Sdu

{ 74 | fn as_mut(&mut self) -> &mut [u8] { 75 | self.pdu.as_mut() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /host/src/security_manager/constants.rs: -------------------------------------------------------------------------------- 1 | /// 128-bit encryption key size 2 | pub(crate) const ENCRYPTION_KEY_SIZE_128_BITS: u8 = 128 / 8; 3 | -------------------------------------------------------------------------------- /host/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common types. 2 | 3 | /// Traits for conversion between types and their GATT representations 4 | pub mod gatt_traits; 5 | pub(crate) mod l2cap; 6 | pub(crate) mod primitives; 7 | 8 | pub mod uuid; 9 | -------------------------------------------------------------------------------- /host/src/types/primitives.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::{Decode, Encode, Error, FixedSize}; 2 | 3 | // 4 | // Implementations for primitives 5 | // 6 | impl FixedSize for u8 { 7 | const SIZE: usize = 1; 8 | } 9 | 10 | impl FixedSize for u16 { 11 | const SIZE: usize = 2; 12 | } 13 | 14 | impl FixedSize for u32 { 15 | const SIZE: usize = 4; 16 | } 17 | 18 | impl Decode<'_> for u8 { 19 | fn decode(src: &[u8]) -> Result { 20 | if src.is_empty() { 21 | return Err(Error::InvalidValue); 22 | } 23 | Ok(src[0]) 24 | } 25 | } 26 | 27 | impl Decode<'_> for u16 { 28 | fn decode(src: &[u8]) -> Result { 29 | if src.len() < 2 { 30 | return Err(Error::InvalidValue); 31 | } 32 | Ok(u16::from_le_bytes([src[0], src[1]])) 33 | } 34 | } 35 | 36 | impl Decode<'_> for u32 { 37 | fn decode(src: &[u8]) -> Result { 38 | if src.len() < 4 { 39 | return Err(Error::InvalidValue); 40 | } 41 | Ok(u32::from_le_bytes([src[0], src[1], src[2], src[3]])) 42 | } 43 | } 44 | 45 | impl Encode for u8 { 46 | fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { 47 | dest[0] = *self; 48 | Ok(()) 49 | } 50 | } 51 | 52 | impl Encode for u16 { 53 | fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { 54 | dest.copy_from_slice(&self.to_le_bytes()[..]); 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl Encode for u32 { 60 | fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { 61 | dest.copy_from_slice(&self.to_le_bytes()[..]); 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /host/src/types/uuid.rs: -------------------------------------------------------------------------------- 1 | //! UUID types. 2 | 3 | use bt_hci::uuid::BluetoothUuid16; 4 | 5 | use crate::codec::{Decode, Encode, Error, Type}; 6 | 7 | /// A 16-bit or 128-bit UUID. 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub enum Uuid { 11 | /// 16-bit UUID 12 | Uuid16([u8; 2]), 13 | /// 128-bit UUID 14 | Uuid128([u8; 16]), 15 | } 16 | 17 | impl From for Uuid { 18 | fn from(data: bt_hci::uuid::BluetoothUuid16) -> Self { 19 | Uuid::Uuid16(data.into()) 20 | } 21 | } 22 | 23 | impl From for Uuid { 24 | fn from(data: u128) -> Self { 25 | Uuid::Uuid128(data.to_le_bytes()) 26 | } 27 | } 28 | 29 | impl From<[u8; 16]> for Uuid { 30 | fn from(data: [u8; 16]) -> Self { 31 | Uuid::Uuid128(data) 32 | } 33 | } 34 | 35 | impl From<[u8; 2]> for Uuid { 36 | fn from(data: [u8; 2]) -> Self { 37 | Uuid::Uuid16(data) 38 | } 39 | } 40 | 41 | impl From for Uuid { 42 | fn from(data: u16) -> Self { 43 | Uuid::Uuid16(data.to_le_bytes()) 44 | } 45 | } 46 | 47 | impl Uuid { 48 | /// Create a new 16-bit UUID. 49 | pub const fn new_short(val: u16) -> Self { 50 | Self::Uuid16(val.to_le_bytes()) 51 | } 52 | 53 | /// Create a new 128-bit UUID. 54 | pub const fn new_long(val: [u8; 16]) -> Self { 55 | Self::Uuid128(val) 56 | } 57 | 58 | /// Copy the UUID bytes into a slice. 59 | pub fn bytes(&self, data: &mut [u8]) { 60 | match self { 61 | Uuid::Uuid16(uuid) => data.copy_from_slice(uuid), 62 | Uuid::Uuid128(uuid) => data.copy_from_slice(uuid), 63 | } 64 | } 65 | 66 | /// Get the UUID type. 67 | pub fn get_type(&self) -> u8 { 68 | match self { 69 | Uuid::Uuid16(_) => 0x01, 70 | Uuid::Uuid128(_) => 0x02, 71 | } 72 | } 73 | 74 | pub(crate) fn size(&self) -> usize { 75 | match self { 76 | Uuid::Uuid16(_) => 6, 77 | Uuid::Uuid128(_) => 20, 78 | } 79 | } 80 | 81 | /// Get the 16-bit UUID value. 82 | pub fn as_short(&self) -> u16 { 83 | match self { 84 | Uuid::Uuid16(data) => u16::from_le_bytes([data[0], data[1]]), 85 | _ => panic!("wrong type"), 86 | } 87 | } 88 | 89 | /// Get the 128-bit UUID value. 90 | pub fn as_raw(&self) -> &[u8] { 91 | match self { 92 | Uuid::Uuid16(uuid) => uuid, 93 | Uuid::Uuid128(uuid) => uuid, 94 | } 95 | } 96 | } 97 | 98 | impl TryFrom<&[u8]> for Uuid { 99 | type Error = crate::Error; 100 | 101 | fn try_from(value: &[u8]) -> Result { 102 | match value.len() { 103 | // Slice length has already been verified, so unwrap can be used 104 | 2 => Ok(Uuid::Uuid16(value.try_into().unwrap())), 105 | 16 => { 106 | let mut bytes = [0; 16]; 107 | bytes.copy_from_slice(value); 108 | Ok(Uuid::Uuid128(bytes)) 109 | } 110 | _ => Err(crate::Error::InvalidUuidLength(value.len())), 111 | } 112 | } 113 | } 114 | 115 | impl Type for Uuid { 116 | fn size(&self) -> usize { 117 | self.as_raw().len() 118 | } 119 | } 120 | 121 | impl Decode<'_> for Uuid { 122 | fn decode(src: &[u8]) -> Result { 123 | if src.len() < 2 { 124 | Err(Error::InvalidValue) 125 | } else { 126 | let val: u16 = u16::from_le_bytes([src[0], src[1]]); 127 | // Must be a long id 128 | if val == 0 { 129 | if src.len() < 16 { 130 | return Err(Error::InvalidValue); 131 | } 132 | Ok(Uuid::Uuid128(src[0..16].try_into().map_err(|_| Error::InvalidValue)?)) 133 | } else { 134 | Ok(Uuid::new_short(val)) 135 | } 136 | } 137 | } 138 | } 139 | 140 | impl Encode for Uuid { 141 | fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { 142 | self.bytes(dest); 143 | Ok(()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /host/tests/common.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use bt_hci::controller::ExternalController; 4 | use bt_hci::transport::SerialTransport; 5 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 6 | use embedded_io_adapters::tokio_1::FromTokio; 7 | use tokio::io::{ReadHalf, WriteHalf}; 8 | use tokio::time::Duration; 9 | use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; 10 | 11 | #[allow(dead_code)] 12 | pub type Controller = ExternalController< 13 | SerialTransport>, FromTokio>>, 14 | 10, 15 | >; 16 | 17 | pub fn find_controllers() -> Vec { 18 | let folder = "/dev/serial/by-id"; 19 | let mut paths = Vec::new(); 20 | for entry in std::fs::read_dir(folder).unwrap() { 21 | let entry = entry.unwrap(); 22 | let path = entry.path(); 23 | 24 | let file_name = path.file_name().unwrap().to_string_lossy(); 25 | if file_name.starts_with("usb-ZEPHYR_Zephyr_HCI_UART_sample") { 26 | paths.push(path.to_path_buf()); 27 | } 28 | } 29 | paths 30 | } 31 | 32 | #[allow(unused)] 33 | pub(crate) async fn create_controller( 34 | port: &PathBuf, 35 | ) -> ExternalController< 36 | SerialTransport>, FromTokio>>, 37 | 10, 38 | > { 39 | let port = port.to_string_lossy(); 40 | let baudrate = 1000000; 41 | let mut port = SerialStream::open( 42 | &tokio_serial::new(port, baudrate) 43 | .baud_rate(baudrate) 44 | .data_bits(DataBits::Eight) 45 | .parity(Parity::None) 46 | .stop_bits(StopBits::One), 47 | ) 48 | .unwrap(); 49 | 50 | // Drain input 51 | tokio::time::sleep(Duration::from_secs(1)).await; 52 | loop { 53 | let mut buf = [0; 1]; 54 | match port.try_read(&mut buf[..]) { 55 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, 56 | _ => {} 57 | } 58 | } 59 | 60 | let (reader, writer) = tokio::io::split(port); 61 | 62 | let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); 63 | let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); 64 | 65 | let driver: SerialTransport = SerialTransport::new(reader, writer); 66 | ExternalController::new(driver) 67 | } 68 | -------------------------------------------------------------------------------- /host/tests/service_attribute_macro.rs: -------------------------------------------------------------------------------- 1 | //! This test is for the gatt_service derive macro. It will check that all attributes and arguments are able to be processed by the macro 2 | 3 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; 4 | use trouble_host::prelude::*; 5 | 6 | #[gatt_service(uuid = "7e701cf1-b1df-42a1-bb5f-6a1028c793b0")] 7 | struct CustomService { 8 | #[characteristic(uuid = "2a37", read, write)] 9 | short_uuid: u8, 10 | #[characteristic(uuid = "7e711cf1-b1df-42a1-bb5f-6a1028c793b0", write_without_response, indicate)] 11 | long_uuid: f32, 12 | #[characteristic(uuid = "2a38", read, notify)] 13 | notify: [u8; 8], 14 | non_characteristic_field: u8, 15 | } 16 | 17 | #[tokio::test] 18 | async fn gatt_service_derive() { 19 | let mut table: AttributeTable = AttributeTable::new(); 20 | let service = CustomService::new(&mut table); 21 | 22 | // Check all fields of service have been generated and are accessible 23 | let _handle = service.handle; 24 | let _non_characteristic_field = service.non_characteristic_field; 25 | let _characteristic_short_uuid = service.short_uuid; 26 | let _characteristic_long_uuid = service.long_uuid; 27 | let _notify = service.notify; 28 | } 29 | -------------------------------------------------------------------------------- /rust-toolchain-nightly.toml: -------------------------------------------------------------------------------- 1 | # Before upgrading check that everything is available on all tier1 targets here: 2 | # https://rust-lang.github.io/rustup-components-history 3 | [toolchain] 4 | channel = "nightly-2025-02-17" 5 | components = [ "rust-src", "rustfmt", "llvm-tools-preview", "clippy" ] 6 | targets = [ 7 | "thumbv7em-none-eabi" 8 | ] 9 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # Before upgrading check that everything is available on all tier1 targets here: 2 | # https://rust-lang.github.io/rustup-components-history 3 | [toolchain] 4 | channel = "1.85" 5 | components = [ "rust-src", "rustfmt", "llvm-tools-preview", "clippy"] 6 | targets = [ 7 | "thumbv8m.main-none-eabihf", 8 | "thumbv7em-none-eabi", 9 | "thumbv7em-none-eabihf", 10 | "thumbv6m-none-eabi", 11 | "riscv32imc-unknown-none-elf", 12 | ] 13 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | max_width=120 3 | group_imports = "StdExternalCrate" 4 | imports_granularity = "Module" 5 | --------------------------------------------------------------------------------