From aec4db1399e070e6ad8fe7692f3e3bc1275cecc8 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 7 Oct 2024 23:05:01 -0700 Subject: [PATCH] switch to monorepo introduce supplemental docs (using mkdocs) and WIP examples (incomplete at this time) migrate to using [just](https://just.systems) as a task runner and utilize it in CI jobs. --- .config/.readthedocs.yaml | 22 +++ .config/cliff.toml | 144 ++++++++++++++++++ .config/nextest.toml | 22 +++ .github/dependabot.yml | 11 +- .github/workflows/build.yml | 36 ----- .github/workflows/docs.yml | 54 +++++++ .github/workflows/tests.yml | 59 +++++++ .gitignore | 7 + CHANGELOG.md | 7 + Cargo.toml | 20 +-- README.md | 23 ++- cspell.config.yml | 21 +++ docs/README.md | 15 ++ docs/mkdocs.yml | 92 +++++++++++ docs/requirements.txt | 4 + docs/src/api-diff.md | 71 +++++++++ docs/src/changelog.md | 4 + docs/src/images/favicon.ico | Bin 0 -> 4286 bytes docs/src/images/logo.jpg | Bin 0 -> 49076 bytes docs/src/index.md | 8 + docs/src/stylesheets/extra.css | 3 + examples/.cargo/config.toml | 2 + examples/Cargo.toml | 23 +++ examples/Embed.toml | 0 examples/README.md | 23 +++ examples/src/lib.rs | 4 + examples/src/linux.rs | 71 +++++++++ examples/src/main.rs | 17 +++ examples/src/rp2040.rs | 40 +++++ justfile | 53 +++++++ lib/Cargo.toml | 17 +++ lib/README.md | 3 + {src => lib/src}/enums.rs | 0 {src => lib/src}/lib.rs | 7 +- {src => lib/src}/radio/mod.rs | 0 {src => lib/src}/radio/rf24/auto_ack.rs | 0 {src => lib/src}/radio/rf24/channel.rs | 0 {src => lib/src}/radio/rf24/constants.rs | 0 {src => lib/src}/radio/rf24/crc_length.rs | 0 {src => lib/src}/radio/rf24/data_rate.rs | 0 {src => lib/src}/radio/rf24/fifo.rs | 0 {src => lib/src}/radio/rf24/mod.rs | 0 {src => lib/src}/radio/rf24/pa_level.rs | 0 {src => lib/src}/radio/rf24/payload_length.rs | 14 +- {src => lib/src}/radio/rf24/pipe.rs | 2 +- {src => lib/src}/radio/rf24/power.rs | 0 {src => lib/src}/radio/rf24/radio.rs | 12 +- {src => lib/src}/radio/rf24/status.rs | 0 48 files changed, 835 insertions(+), 76 deletions(-) create mode 100644 .config/.readthedocs.yaml create mode 100644 .config/cliff.toml create mode 100644 .config/nextest.toml delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/tests.yml create mode 100644 CHANGELOG.md create mode 100644 cspell.config.yml create mode 100644 docs/README.md create mode 100644 docs/mkdocs.yml create mode 100644 docs/requirements.txt create mode 100644 docs/src/api-diff.md create mode 100644 docs/src/changelog.md create mode 100644 docs/src/images/favicon.ico create mode 100644 docs/src/images/logo.jpg create mode 100644 docs/src/index.md create mode 100644 docs/src/stylesheets/extra.css create mode 100644 examples/.cargo/config.toml create mode 100644 examples/Cargo.toml create mode 100644 examples/Embed.toml create mode 100644 examples/README.md create mode 100644 examples/src/lib.rs create mode 100644 examples/src/linux.rs create mode 100644 examples/src/main.rs create mode 100644 examples/src/rp2040.rs create mode 100644 justfile create mode 100644 lib/Cargo.toml create mode 100644 lib/README.md rename {src => lib/src}/enums.rs (100%) rename {src => lib/src}/lib.rs (82%) rename {src => lib/src}/radio/mod.rs (100%) rename {src => lib/src}/radio/rf24/auto_ack.rs (100%) rename {src => lib/src}/radio/rf24/channel.rs (100%) rename {src => lib/src}/radio/rf24/constants.rs (100%) rename {src => lib/src}/radio/rf24/crc_length.rs (100%) rename {src => lib/src}/radio/rf24/data_rate.rs (100%) rename {src => lib/src}/radio/rf24/fifo.rs (100%) rename {src => lib/src}/radio/rf24/mod.rs (100%) rename {src => lib/src}/radio/rf24/pa_level.rs (100%) rename {src => lib/src}/radio/rf24/payload_length.rs (87%) rename {src => lib/src}/radio/rf24/pipe.rs (98%) rename {src => lib/src}/radio/rf24/power.rs (100%) rename {src => lib/src}/radio/rf24/radio.rs (98%) rename {src => lib/src}/radio/rf24/status.rs (100%) diff --git a/.config/.readthedocs.yaml b/.config/.readthedocs.yaml new file mode 100644 index 0000000..7ea3e19 --- /dev/null +++ b/.config/.readthedocs.yaml @@ -0,0 +1,22 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + # rust: latest + python: latest + +mkdocs: + configuration: docs/mkdocs.yml + + +# Optionally declare the Python requirements required to build your docs + +python: + install: + - requirements: docs/requirements.txt diff --git a/.config/cliff.toml b/.config/cliff.toml new file mode 100644 index 0000000..e05eabb --- /dev/null +++ b/.config/cliff.toml @@ -0,0 +1,144 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +# template for the changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +{%- set UNRELEASED = "Unreleased" -%} +{%- set init_commit = "f8863cc36d66708bfa0fb2fb1a219c7b2f97f7d6" -%} +{%- set this_version = UNRELEASED -%} + +{% if version -%} + {%- set this_version = version | trim_start_matches(pat="v") -%} + ## [{{ this_version }}] - {{ timestamp | date(format="%Y-%m-%d") }} + {%- if message %} + + > {{ message }} + {%- endif %} +{% else -%} + ## [{{ UNRELEASED }}]{% if previous and previous.timestamp %} - {{ previous.timestamp | date(format="%Y-%m-%d") }} to present{% endif %} +{% endif -%} + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | split(pat="\n") | first | upper_first | trim }}\ + {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%} + {% if commit.remote.pr_number %} in \ + [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) + {%- else %} in \ + [`{{ commit.id | truncate(length=7, end="") }}`]({{ self::remote_url() }}/commit/{{commit.id }}) + {%- endif -%} + {% endfor %} +{% endfor -%} + +{% set last_commit = "HEAD" -%} +{%- set first_commit = init_commit -%} +{% if version -%} + {%- set last_commit = version -%} + {%- if previous and previous.version -%} + {%- set first_commit = previous.version -%} + {%- endif -%} +{%- endif %} +[{{ this_version }}]: {{ self::remote_url() }}/compare/{{ first_commit }}...{{ last_commit }} + +Full commit diff: [`{% if previous.version -%} + {{ first_commit }} +{%- else -%} + {{ init_commit | truncate(length=7, end="") }} +{%- endif %}...{{ last_commit }}`][{{ this_version }}] +{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} + ## New Contributors +{%- endif -%} + +{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + * @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} in \ + [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ + {%- endif %} +{%- endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true +# The file path for output. This can be overridden with `--output` CLI arg +# output = "CHANGELOG.md" + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # remove issue numbers from commits + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { field = "github.pr_labels", pattern = "breaking", group = " 💥 Breaking changes" }, + { field = "github.pr_labels", pattern = "breaking-change", group = " 💥 Breaking changes" }, + { field = "github.pr_labels", pattern = "feature", group = " 🚀 Added" }, + { field = "github.pr_labels", pattern = "enhancement", group = " 🚀 Added" }, + { field = "github.pr_labels", pattern = "deprecated", group = " 🚫 Deprecated" }, + { field = "github.pr_labels", pattern = "removed", group = " 🗑️ Removed" }, + { field = "github.pr_labels", pattern = "bug", group = " 🛠️ Fixed" }, + { field = "github.pr_labels", pattern = "security", group = " 🔐 Security" }, + { field = "github.pr_labels", pattern = "dependencies", group = " 📦 Dependency updates" }, + { field = "github.pr_labels", pattern = "test", group = "🚦 Tests"}, + { field = "github.pr_labels", pattern = "tests", group = "🚦 Tests"}, + { field = "github.pr_labels", pattern = "documentation", group = " 📝 Documentation" }, + { field = "github.pr_labels", pattern = "refactor", group = " 🗨️ Changed" }, + { field = "github.pr_labels", pattern = "skip-changelog", skip = true }, + { field = "github.pr_labels", pattern = "no-changelog", skip = true }, + { field = "github.pr_labels", pattern = "invalid", skip = true }, + # The order of parsers matters. Put rules for PR labels first to prioritize PR labels. + { message = "^[a|A]dd", group = " 🚀 Added" }, + { message = "^[s|S]upport", group = " 🚀 Added" }, + { message = "^.*: support", group = " 🚀 Added" }, + { message = "^.*: add", group = " 🚀 Added" }, + { message = "^.*: deprecated", group = " 🚫 Deprecated" }, + { message = "[d|D]eprecate", group = " 🚫 Deprecated" }, + { message = "[t|T]ests", group = "🚦 Tests"}, + { message = "[r|R]emove", group = " 🗑️ Removed" }, + { message = "^.*: remove", group = " 🗑️ Removed" }, + { message = "^.*: delete", group = " 🗑️ Removed" }, + { message = "^[f|F]ix", group = " 🛠️ Fixed" }, + { message = "^.*: fix", group = " 🛠️ Fixed" }, + { message = "^.*: secure", group = " 🔐 Security" }, + { message = "[s|S]ecure", group = " 🔐 Security" }, + { message = "[s|S]ecurity", group = " 🔐 Security" }, + { message = "^.*: security", group = " 🔐 Security" }, + { message = "doc", group = " 📝 Documentation" }, + { message = "docs", group = " 📝 Documentation" }, + { message = "documentation", group = " 📝 Documentation" }, + { message = "[r|R]efactor", group = " 🗨️ Changed" }, + { field = "github.pr_labels", pattern = ".*", group = " 🗨️ Changed" }, + { message = "^.*", group = " 🗨️ Changed" }, +] +# filter out the commits that are not matched by commit parsers +filter_commits = true +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" + +# [remote.github] +# owner = "cpp-linter" +# repo = "cpp-linter-rs" diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000..25c130f --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,22 @@ +# required minimum nextest version +nextest-version = "0.9.77" + +[profile.default] +# A profile to run most tests, except tests that run longer than 10 seconds +default-filter = "all()" + +# This will flag any test that runs longer than 10 seconds. Useful when writing new tests. +slow-timeout = "10s" + +[profile.ci] +# A profile to run only tests that use clang-tidy and/or clang-format +# NOTE: This profile is intended to keep CI runtime low. Locally, use default or all profiles + +# This is all tests in tests/ folder + unit test for --extra-args. +default-filter = "all()" + +# show wich tests were skipped +status-level = "skip" + +# show log output from each test +failure-output = "immediate-final" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6b0724a..87c341c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,8 +16,17 @@ updates: - package-ecosystem: cargo directory: / schedule: - interval: "daily" + interval: "weekly" groups: cargo: patterns: - "*" + + - package-ecosystem: pip + directory: / + schedule: + interval: "weekly" + groups: + pip: + patterns: + - "*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index c0c6365..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: build and test CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - - run: cargo build - - run: cargo check - - run: cargo fmt - - run: cargo doc --no-deps - - name: save docs as artifact - uses: actions/upload-artifact@v4 - with: - name: rf24-rs docs - path: target/doc - - run: rustup component add llvm-tools-preview - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@v2 - with: - tool: cargo-llvm-cov - - name: Run tests - run: cargo llvm-cov --lcov --output-path lcov.info - - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - files: lcov.info - fail_ci_if_error: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..da61321 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,54 @@ +name: Docs + +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'lib/**' + - Cargo.toml + - '*.md' + pull_request: + branches: [main] + paths: + - 'docs/**' + - 'lib/**' + - Cargo.toml + - '*.md' + +jobs: + aupplemental: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main + - name: Install Just + run: cargo binstall -y just + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install -r docs/requirements.txt + - run: just docs-build + - name: Save docs build as artifact + uses: actions/upload-artifact@v4 + with: + path: docs/site + name: supplemental-docs + + api: + runs-on: ubuntu-latest + steps: + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main + - name: Install Just + run: cargo binstall -y just + - run: just docs-rs + - name: save docs as artifact + uses: actions/upload-artifact@v4 + with: + name: api-docs + path: target/doc + \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..18ad6f1 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,59 @@ +name: tests + +on: + push: + branches: [main] + paths: + - 'lib/**' + - '!lib/README.md' + - Cargo.toml + pull_request: + branches: [main] + paths: + - 'lib/**' + - '!lib/README.md' + - Cargo.toml + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main + - name: Install Just + run: cargo binstall -y just + - name: Cache deps + uses: actions/cache@v4 + with: + path: ~/.cargo + key: cargo-lib-${{ hashFiles('lib/src/**', 'lib/Cargo.toml') }} + - run: just lint + + test: + needs: [lint] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main + - name: Install Just + run: cargo binstall -y just cargo-nextest cargo-llvm-cov + - name: Cache deps + uses: actions/cache@v4 + with: + path: ~/.cargo + key: cargo-lib-${{ hashFiles('lib/src/**', 'lib/Cargo.toml') }} + - run: rustup component add llvm-tools-preview + # this enables a tool (for default toolchain) needed to measure code coverage. + - name: Run tests and generate reports + run: just test ci lcov + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index 7e0013f..3854ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,10 @@ Cargo.lock # .vscode settings .vscode/ + +# coverage output +coverage.json +lcov.info + +# supplemental docs build +docs/site/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b27c02f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + diff --git a/Cargo.toml b/Cargo.toml index 14fe45b..0534a5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,12 @@ -[package] -name = "rf24-rs" -description = "A pure-rust driver for the nRF24L01 wiresless tranceiver" +[workspace] +members = ["lib", "examples"] +default-members = ["lib"] +resolver = "2" + +[workspace.package] version = "0.1.0" repository = "https://github.com/nRF24/rf24-rs" +homepage = "https://nRF24.github.io/rf24-rs" edition = "2021" rust-version = "1.70" -exclude = [".github/", "codecov.yml"] -keywords = ["nrf24l01", "wireless", "transceiver", "embedded", "nRF24", "RF24"] license-file = "LICENSE" -autobins = false -autoexamples = false - -[dependencies] -embedded-hal = "1.0.0" - -[dev-dependencies] -embedded-hal-mock = "0.11.1" diff --git a/README.md b/README.md index 6cf5373..e507000 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,45 @@ + [![build and test CI][build-ci-badge]][build-ci-runs] [![codecov][codecov-badge]][codecov-project] - # rf24-rs + This is a pure-rust driver for the nRF24L01 wireless transceivers. -> [!warning] +> [!WARNING] > This project is a Work-In-Progress. > This warning will be removed when this project is ready for deployment. ## Supported platforms + This project aims to support the [embedded rust][embedded-rs] ecosystem. This includes but is not limited to Linux on RPi. Other points of interest: + - [crates.io for embedded-hal crates][crates-hal] - the [awesome embedded rust][awesome-hal] list - the [embedded-hal][eh] framework ## Goals + Here is the intended roadmap: + - [x] implement driver for the nRF24L01 (OTA compatible with other RF24 library) - This should be HAL-agnostic in terms of MCU. It would also be nice to - reimplement the same API (using [rust's `trait` feature][rust-traits]) - for use on nRF5x radios. + This should be HAL-agnostic in terms of MCU. It would also be nice to + reimplement the same API (using [rust's `trait` feature][rust-traits]) + for use on nRF5x radios. + - [ ] implement network layers (OTA compatible with RF24Network and RF24Mesh libraries) - [ ] implement ESB support for nRF5x MCUs. This might be guarded under [cargo features][cargo-feat]. ## Why? -Mostly because I :heart: rust. There are [other driver the nRF24L01 in pure rust][crates-rf24], + +Mostly because I :heart: rust. There are [other driver libraries for the nRF24L01 in pure rust][crates-rf24], but they all seem unmaintained or designed to be application-specific. There's even a [crate to use the nRF5x chips' ESB support][crate-esb], but this too seems lacking maintainers' attention. -[build-ci-badge]: https://github.com/nRF24/rf24-rs/actions/workflows/build.yml/badge.svg -[build-ci-runs]: https://github.com/nRF24/rf24-rs/actions/workflows/build.yml +[build-ci-badge]: https://github.com/nRF24/rf24-rs/actions/workflows/tests.yml/badge.svg +[build-ci-runs]: https://github.com/nRF24/rf24-rs/actions/workflows/tests.yml [codecov-badge]: https://codecov.io/gh/nRF24/rf24-rs/graph/badge.svg?token=BMQ97Y5RVP [codecov-project]: https://codecov.io/gh/nRF24/rf24-rs [embedded-rs]: https://docs.rust-embedded.org/book/ diff --git a/cspell.config.yml b/cspell.config.yml new file mode 100644 index 0000000..db9cb1c --- /dev/null +++ b/cspell.config.yml @@ -0,0 +1,21 @@ +version: "0.2" +words: + - Cdev + - Doherty + - DYNPD + - fontawesome + - gpio + - gpiochip + - inlinehilite + - Kbps + - linenums + - Mbps + - mkdocs + - pymdownx + - RETR + - rustc + - RXADDR + - Spidev + - struct + - superfences + - tasklist diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..1c97d2e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# Supplemental documentation + +This folder has the documentation sources that are not suitable for API documentation. + +To build these docs install mkdocs and relevant plugins: + +```shell +pip install -r docs/requirements.txt +``` + +Then build and view the docs using: + +```shell +mkdocs serve --config-file docs/mkdocs.yml --open +``` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..a035906 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,92 @@ +site_name: rf24-rs +site_description: "The RF24 rust library" +site_url: "https://nRF24.github.io/rf24-rs" +repo_url: "https://github.com/nRF24/rf24-rs" +repo_name: "nRF24/rf24-rs" +edit_uri: "edit/main/docs/" +docs_dir: src +nav: + - index.md + - api-diff.md + - changelog.md + +theme: + name: material + features: + - navigation.top + - content.tabs.link + - content.tooltips + - content.code.annotate + - content.code.copy + - content.action.view + - content.action.edit + - navigation.footer + - search.suggest + - search.share + - navigation.tracking + - toc.follow + logo: images/logo.jpg + favicon: images/favicon.ico + icon: + repo: fontawesome/brands/github + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + primary: yellow + accent: cyan + toggle: + icon: material/brightness-auto + name: Switch to light mode + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: yellow + accent: cyan + toggle: + icon: material/lightbulb-outline + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: yellow + accent: cyan + toggle: + icon: material/lightbulb + name: Switch to system preference +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/nRF24/rf24-rs + +extra_css: + - stylesheets/extra.css + +plugins: + - search + - include-markdown + +markdown_extensions: + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - toc: + permalink: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + linenums_style: pymdownx-inline + # - pymdownx.inlinehilite + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.snippets: + check_paths: true + - attr_list + - admonition + - markdown_gfm_admonition diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..b76dad4 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +markdown-gfm-admonition==0.1.1 +mkdocs==1.6.1 +mkdocs-include-markdown-plugin==6.2.2 +mkdocs-material==9.5.39 diff --git a/docs/src/api-diff.md b/docs/src/api-diff.md new file mode 100644 index 0000000..fa5ed04 --- /dev/null +++ b/docs/src/api-diff.md @@ -0,0 +1,71 @@ +# Differences in RF24 API conventions + +This document will highlight the differences between RF24 API implemented in C++ and this rf24-rs package's API. + +There are some important design decisions here. + +[traits]: https://doc.rust-lang.org/book/ch10-02-traits.html +[result]: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#handling-potential-failure-with-result + +## STATUS byte exposed + +As with our other implementations, the STATUS byte returned on every SPI transaction is cached to a private member. Understanding the meaning of the status byte is publicly exposed via + +- `clear_status_flags()`: with parameters to specify which flag(s) should be cleared. +- `get_status_flags()`: has a signature similar to C++ `whatHappened()` but does not clear the flags. +- `set_status_flags()`: similar to C++ `maskIRQ()` except the boolean parameters' meaning is not reversed. + + | lang | only trigger on RX_DR events | + |:----:|:-----------------------------| + | C++ | `radio.maskIRQ(false, true, true)` | + | Rust | `radio.set_status_flags(true, false, false)` | + +## No babysitting + +To transmit something, RF24 struct offers + +- `write()`: non-blocking uploads to TX FIFO. +- `send()`: blocking wrapper around `write()` + +There will be no equivalents to C++ `writeBlocking()`, `startFastWrite()`, `writeFast()`, `txStandby()`. +Considering the exposed STATUS byte, these can all be done from the user space (if needed). + +Additionally, `send()` does _**not**_ implement a timeout. +Every member function in the `RF24` struct (except the `new()`) returns a [`Result`][result], +so problems with the SPI connections should be detected early in the app lifecycle. +The rustc compiler will warn users about unhandled [`Result`][result]s. + +As an alternative, I've been considering an optional `irq_pin` parameter to the constructor. +If specified in user code, then `send()` shall wait for the IRQ pin to become active instead of pinging the radio's STATUS byte over SPI. + +> [!TIP] +> Rust does offer a way to overload functions using [traits] (feature akin to C++ templates), +> but it isn't traditionally relied upon in a public API. + +## API structure + +You'll notice that I used an API structure similar to CircuitPython_nRF24L01. +Under the hood is very much like C++ RF24 lib with respect to radio configuration. + +Using rust's [trait][traits] feature, I plan to have an API structured like so + +```mermaid +flowchart TD + subgraph radio + esb("EsbRadio (trait)") --> RF24 + esb --> nrf51{{RF51}} + esb --> nrf52{{RF52}} + esb--> ble{{FakeBle}} + end + + radio --> net{{RF24Network}} + net --> mesh{{RF24Mesh}} +``` + + +!!! info "Graph Legend" + + In the above graph, nodes in angle brackets are not implemented yet. + This is just how I envision the final result. + +This way users can devise their own implementation of the the `EsbRadio` traits and still pass their derivative to the network/mesh implementations provided. diff --git a/docs/src/changelog.md b/docs/src/changelog.md new file mode 100644 index 0000000..9136dfc --- /dev/null +++ b/docs/src/changelog.md @@ -0,0 +1,4 @@ + +{% + include-markdown "../../CHANGELOG.md" +%} diff --git a/docs/src/images/favicon.ico b/docs/src/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c15a1650af557d6ec815ebd7c0b5dbf5cc080676 GIT binary patch literal 4286 zcmc(iTT@%t8HTmbTLO|^B{RuQF8dF1+sS`u|3GnV5NCurSYW$$;sBD+2}$S-;$VR> z*!ajn#&(=Z-R9)9?o6jMz3hMR?3p+3S{oE}NPB1FS+=ab*Lv3Xt?zxmg~y}t&nKUF z#NVEqzwvlJ@OV7G<=|JI-*JfVf9*%_oqePG=NxOG=-7*E4&Qs5*y0{Sc65_ zxlNG&Hiskmy>qbo^6(~C-zHnIT>f|pwm%O$@s9XCv#@)!u;VN5EPkUm0$=F}T^rw^ ztNIsoZoNdu_Fv(<{|df4-@&){J-iQofcMdlu=iiVKKcpP@jqZae+~1+KVf~%!O!A5 z@ytGW<-WVeIh-rj=-By|YyM5H-Bo=lSZ@9qKkEQ`ZwR`70s5r^^noa}p%}E`IJD6O zw6P@AiyYU=P^VH*gJ~!;87Nmct}Q{ip5@57|DQ?A=kmRYMLACka1F7}bP?KMOs=o@ zM;+=_PX@TIokOW%Sw zvkmXkHtg(e>T(BmVHb9B7gqTWtd+a4Dtj>3?!nyPsNRRU^}rqC^G%NRd$3mdPLc15 zIVH{)Yvj3B?l#xv$ZR{pdzb@w&o9CE4(d;vNQ)ehpM^JGdF%DB`R85!g*N;TKZNI_ zkN)@g_+xlfwQ=A7Z_ep+`j6H(?=T0jdvXo&E1!Of_iO*6MqZ+mTItxL79YQcZ@#%bpF{=GSp2O#yGWf zeG{tOLlpYlohJS?w5=EPfkpDk{E|AIt3r$3fp%q?{20{HD6H_~6Mo3iE-t$Lr-}bw z_9^`+Og~tHcc%tBwGK0}0c-s(%=n&MOaF2gdh~4kA#YnIhK68uM8{R#LpfxpY+Dh z+wj+(F@EwlUo`T=cxif5P|rOD%lw68WPs7}svM z#ILWCe}(+DC$0R%BbT2zkms)sh9%z|DuMT}Mt+*}d&2PcH1W@M!^>yd2>DmHTlp(b zpNB>59b*6g9nA0mG+#9l*2tQ|iD?}cJx{DPl4;a_gUFCRu zWD*AX+T^sOlFuB#94*WGKE3~)p1*M+1mAh`WBGb+RPud`HTppm=FF{@_>JNd>Ym!; zeWJPkv<&NSiu}xx8~-r*!K~D_&{qSWXDs=DDzx&GNAxA~gVa`2{Tq4mZ?IPv4qN%z z1E`l4VPtDAe2Ja_E zj$T0jQ)jY{Ivjy#wjQqg`n3Lou8uf21kx#$XmfG-(hw^NU!U=zr z@sE%{u|obvL;kyj|I&Ya=jaX1@kdYi4MCCGTBh4PRd*G;oA7EbCm-WjP*UV5e){wX-s{!`du_%ma8@`Btwsg{)*Btd)N$i0=nYm#ds>5Oa%ew~qyEe{! z_VI2q%(F?A>v2dH{UK;m8F@bt9!f8B=ZhX6Ab+6^zarvKLJ7v9d{JLbCpQx%!9SJ6*+RygSpod4H6581`|My-fOPFvIm? ij#+u`=)qupp7^Zoy>yf-DMy)(JIbZl8`j1<{rMRY83?oh literal 0 HcmV?d00001 diff --git a/docs/src/images/logo.jpg b/docs/src/images/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2065584ca6e27aaec2329c4b4c397da2d61f4510 GIT binary patch literal 49076 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 z +{% + include-markdown "../../README.md" +%} + +## Docs.rs + +The API documentation is hosted at [docs.rs](https://docs.rs/). diff --git a/docs/src/stylesheets/extra.css b/docs/src/stylesheets/extra.css new file mode 100644 index 0000000..c1397bc --- /dev/null +++ b/docs/src/stylesheets/extra.css @@ -0,0 +1,3 @@ +th { + background-color: var(--md-default-fg-color--lightest); +} diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml new file mode 100644 index 0000000..7e5c716 --- /dev/null +++ b/examples/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.] +runner = 'probe-rs run --chip ' diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 0000000..3667297 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rf24-rs-examples" +version.workspace = true +edition.workspace = true + +[dependencies] +rf24-rs = {path = "../lib"} +embedded-hal = "1.0.0" +anyhow = {version = "1.0.89", default-features = false } +linux-embedded-hal = {version = "0.4.0", optional = true} +embassy-rp = {version = "0.1.0", optional = true} +embassy-sync = {version = "0.5.0", optional = true} +embassy-embedded-hal = {version = "0.1.0", optional = true} + +[features] +default = ["linux"] +# default = ["rp2040"] +rp2040 = ["dep:embassy-rp", "dep:embassy-sync", "dep:embassy-embedded-hal"] +linux = ["dep:linux-embedded-hal"] + +[[bin]] +name = "getting-started" +path = "src/main.rs" diff --git a/examples/Embed.toml b/examples/Embed.toml new file mode 100644 index 0000000..e69de29 diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..b5ecc43 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,23 @@ +# rf24-rs examples +This directory is a separate cargo project to demonstrate the rf24-rs package being used on various boards. The following boards are supported: +- [x] linux armhf (32bit OS on a Raspberry Pi or similar linux machine) +- [x] linux aarch64 (64bit OS on a Raspberry Pi or similar linux machine) +- [ ] rp2040 +- [ ] esp32 +- [ ] nRF52840 +- [ ] nRF52833 (like MicroBit v2) +- [ ] nRF51822 (like MicroBit v1) + + +## Running an example + +First you have to build the example before you run it. + +For Linux boards, this can be done simply by executing the command: +``` +cargo run --bin getting-started --release +``` + +For microcontrollers, you need a way to upload the built binary to the board. For this, we recommend using [`probe-rs`](https://probe.rs). Once installed, you'll have a new `cargo flash` subcommand at your disposal. But you'll still have to select what chip you are going to flash the example to. + +1. use diff --git a/examples/src/lib.rs b/examples/src/lib.rs new file mode 100644 index 0000000..f121d50 --- /dev/null +++ b/examples/src/lib.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "linux")] +pub mod linux; +#[cfg(feature = "rp2040")] +pub mod rp2040; diff --git a/examples/src/linux.rs b/examples/src/linux.rs new file mode 100644 index 0000000..42dc521 --- /dev/null +++ b/examples/src/linux.rs @@ -0,0 +1,71 @@ +use anyhow::{anyhow, Error, Result}; +use linux_embedded_hal::{ + gpio_cdev::{chips, Chip, LineRequestFlags}, + spidev::{SpiModeFlags, SpidevOptions}, + CdevPin, Delay, SpidevDevice, +}; + +pub struct BoardHardware { + pub spi: SpidevDevice, + pub ce_pin: CdevPin, + #[allow(dead_code)] + gpio: Chip, + pub delay: Delay, +} + +impl BoardHardware { + pub fn new(dev_gpio_chip: u8, ce_pin: u32, dev_spi_bus: u8, cs_pin: u8) -> Result { + // get the desired "dev/gpiochip{dev_gpio_chip}" + let mut dev_gpio = chips()? + .find(|chip| { + if let Ok(chip) = chip { + if chip.path().ends_with(dev_gpio_chip.to_string()) { + return true; + } + } + false + }) + .ok_or(anyhow!( + "Could not find specified dev/gpiochip{dev_gpio_chip} for this system." + ))??; + let ce_line = dev_gpio + .get_line(ce_pin) + .map_err(|_| anyhow!("GPIO{ce_pin} is unavailable"))?; + let ce_line_handle = ce_line + .request(LineRequestFlags::OUTPUT, 0, "rf24-rs") + .map_err(Error::from)?; + let ce_pin = CdevPin::new(ce_line_handle).map_err(Error::from)?; + + let mut spi = + SpidevDevice::open(format!("/dev/spidev{dev_spi_bus}.{cs_pin}")).map_err(|_| { + anyhow!( + "SPI bus {dev_spi_bus} with CS pin option {cs_pin} is not available in this system" + ) + })?; + let config = SpidevOptions::new() + .max_speed_hz(10000000) + .mode(SpiModeFlags::SPI_MODE_0) + .bits_per_word(8) + .build(); + spi.configure(&config).map_err(Error::from)?; + + Ok(BoardHardware { + spi, + ce_pin, + gpio: dev_gpio, + delay: Delay, + }) + } + + #[allow(clippy::should_implement_trait)] + pub fn default() -> Result { + Self::new( + option_env!("RF24_EXAMPLE_GPIO_CHIP") + .unwrap_or("0") + .parse()?, + 22, + 0, + 0, + ) + } +} diff --git a/examples/src/main.rs b/examples/src/main.rs new file mode 100644 index 0000000..310be05 --- /dev/null +++ b/examples/src/main.rs @@ -0,0 +1,17 @@ +use anyhow::{anyhow, Result}; +use rf24_rs::radio::{prelude::*, RF24}; +#[cfg(feature = "linux")] +use rf24_rs_examples::linux::BoardHardware; +#[cfg(feature = "rp2040")] +use rf24_rs_examples::rp2040::BoardHardware; + +fn main() -> Result<()> { + // instantiate a hardware peripherals on the board + let board = BoardHardware::default()?; + + // instantiate radio object using board's hardware + let mut radio = RF24::new(board.ce_pin, board.spi, board.delay); + + // initialize the radio hardware + radio.init().map_err(|e| anyhow!("{e:?}")) +} diff --git a/examples/src/rp2040.rs b/examples/src/rp2040.rs new file mode 100644 index 0000000..8ed5483 --- /dev/null +++ b/examples/src/rp2040.rs @@ -0,0 +1,40 @@ +use core::cell::RefCell; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{PIN_10, PIN_25, SPI1}; +use embassy_rp::spi::{Blocking, Config, Spi}; +use embassy_rp::Peripherals; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; + +pub struct BoardHardware<'b> { + peri: Peripherals, + spi_bus_mutex: Mutex>>, + pub spi_device: SpiDevice<'b, NoopRawMutex, Spi<'b, SPI1, Blocking>, Output<'b, PIN_10>>, + pub ce_pin: Output<'b, PIN_25>, +} + +impl BoardHardware<'_> { + pub fn new() -> Self { + let peri = embassy_rp::init(Default::default()); + let mut spi_config = Config::default(); + spi_config.frequency = 10_000_000; + let clk = peri.PIN_10; + let mosi = peri.PIN_11; + let miso = peri.PIN_12; + let ce = peri.PIN_9; + let cs = peri.PIN_25; + + let spi = Spi::new_blocking(peri.SPI1, clk, mosi, miso, spi_config); + let spi_bus_mutex: Mutex> = Mutex::new(RefCell::new(spi)); + let cs_pin = Output::new(ce, Level::High); + let ce_pin = Output::new(cs, Level::Low); + let spi_device = SpiDevice::new(&spi_bus_mutex, cs_pin); + BoardHardware { + peri, + spi_bus_mutex, + spi_device, + ce_pin, + } + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..b6526c6 --- /dev/null +++ b/justfile @@ -0,0 +1,53 @@ +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] + +# run the test suite +[group("code coverage")] +test profile='default': + cargo llvm-cov --no-report \ + nextest --manifest-path lib/Cargo.toml \ + --lib --tests --color always --profile {{ profile }} + +# Clear previous test build artifacts +[group("code coverage")] +test-clean: + cargo llvm-cov clean + +# pass "--open" to this recipe's args to load HTML in your browser +# generate pretty coverage report +[group("code coverage")] +pretty-cov *args='': + cargo llvm-cov report --json --output-path coverage.json --ignore-filename-regex main + llvm-cov-pretty coverage.json {{ args }} + +# pass "--open" to this recipe's args to load HTML in your browser +# generate detailed coverage report +[group("code coverage")] +llvm-cov *args='': + cargo llvm-cov report --html --ignore-filename-regex main {{ args }} + +# generate lcov.info +[group("code coverage")] +lcov: + cargo llvm-cov report --lcov --output-path lcov.info --ignore-filename-regex main + +# pass "--open" to this recipe's "open" arg to load HTML in your browser +# serve mkdocs +[group("docs")] +docs open='': + mkdocs serve --config-file docs/mkdocs.yml {{ open }} + +# build mkdocs +[group("docs")] +docs-build: + mkdocs build --config-file docs/mkdocs.yml + +# pass "--open" to this recipe's "open" arg to load HTML in your browser +# rust API docs +[group("docs")] +docs-rs open='': + cargo doc --no-deps --lib --manifest-path Cargo.toml {{ open }} + +# run clippy and rustfmt +lint: + cargo clippy --allow-staged --allow-dirty --fix + cargo fmt diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 0000000..2f95c6a --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rf24-rs" +description = "A pure-rust driver for the nRF24L01 wiresless tranceiver" +version.workspace = true +repository.workspace = true +edition.workspace = true +rust-version.workspace = true +license-file.workspace = true +exclude = [".github/", "codecov.yml", "docs", "examples"] +keywords = ["nrf24l01", "wireless", "transceiver", "embedded", "RF24"] +categories = ["embedded", "no-std"] + +[dependencies] +embedded-hal = "1.0.0" + +[dev-dependencies] +embedded-hal-mock = "0.11.1" diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..cff2bdd --- /dev/null +++ b/lib/README.md @@ -0,0 +1,3 @@ +# rf24-rs + +This is a pure-rust driver for the nRF24L01 wireless transceivers. diff --git a/src/enums.rs b/lib/src/enums.rs similarity index 100% rename from src/enums.rs rename to lib/src/enums.rs diff --git a/src/lib.rs b/lib/src/lib.rs similarity index 82% rename from src/lib.rs rename to lib/src/lib.rs index c75dc52..230a589 100644 --- a/src/lib.rs +++ b/lib/src/lib.rs @@ -1,9 +1,8 @@ #![doc( - html_logo_url = "https://raw.githubusercontent.com/nRF24/RF24/master/docs/sphinx/_static/Logo%20large.png" -)] -#![doc( - html_favicon_url = "https://github.com/nRF24/RF24/raw/master/docs/sphinx/_static/new_favicon.ico" + html_logo_url = "https://raw.githubusercontent.com/nRF24/RF24/master/docs/images/Logo%20large.png" )] +#![doc(html_favicon_url = "https://github.com/nRF24/RF24/raw/master/docs/images/favicon.ico")] +#![doc = include_str!("../README.md")] #![no_std] mod enums; diff --git a/src/radio/mod.rs b/lib/src/radio/mod.rs similarity index 100% rename from src/radio/mod.rs rename to lib/src/radio/mod.rs diff --git a/src/radio/rf24/auto_ack.rs b/lib/src/radio/rf24/auto_ack.rs similarity index 100% rename from src/radio/rf24/auto_ack.rs rename to lib/src/radio/rf24/auto_ack.rs diff --git a/src/radio/rf24/channel.rs b/lib/src/radio/rf24/channel.rs similarity index 100% rename from src/radio/rf24/channel.rs rename to lib/src/radio/rf24/channel.rs diff --git a/src/radio/rf24/constants.rs b/lib/src/radio/rf24/constants.rs similarity index 100% rename from src/radio/rf24/constants.rs rename to lib/src/radio/rf24/constants.rs diff --git a/src/radio/rf24/crc_length.rs b/lib/src/radio/rf24/crc_length.rs similarity index 100% rename from src/radio/rf24/crc_length.rs rename to lib/src/radio/rf24/crc_length.rs diff --git a/src/radio/rf24/data_rate.rs b/lib/src/radio/rf24/data_rate.rs similarity index 100% rename from src/radio/rf24/data_rate.rs rename to lib/src/radio/rf24/data_rate.rs diff --git a/src/radio/rf24/fifo.rs b/lib/src/radio/rf24/fifo.rs similarity index 100% rename from src/radio/rf24/fifo.rs rename to lib/src/radio/rf24/fifo.rs diff --git a/src/radio/rf24/mod.rs b/lib/src/radio/rf24/mod.rs similarity index 100% rename from src/radio/rf24/mod.rs rename to lib/src/radio/rf24/mod.rs diff --git a/src/radio/rf24/pa_level.rs b/lib/src/radio/rf24/pa_level.rs similarity index 100% rename from src/radio/rf24/pa_level.rs rename to lib/src/radio/rf24/pa_level.rs diff --git a/src/radio/rf24/payload_length.rs b/lib/src/radio/rf24/payload_length.rs similarity index 87% rename from src/radio/rf24/payload_length.rs rename to lib/src/radio/rf24/payload_length.rs index 46e521d..8f44d3e 100644 --- a/src/radio/rf24/payload_length.rs +++ b/lib/src/radio/rf24/payload_length.rs @@ -87,31 +87,31 @@ mod test { let spi_expectations = spi_test_expects![ // set payload length to 32 bytes on all pipes ( - vec![registers::RX_PW_P0 + 0 | commands::W_REGISTER, 32u8], + vec![registers::RX_PW_P0 | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8] ), ( - vec![registers::RX_PW_P0 + 1 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 1) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8] ), ( - vec![registers::RX_PW_P0 + 2 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 2) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8] ), ( - vec![registers::RX_PW_P0 + 3 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 3) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8] ), ( - vec![registers::RX_PW_P0 + 4 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 4) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8] ), ( - vec![registers::RX_PW_P0 + 5 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 5) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8] ), // get payload length for all pipe 0 (because all pipes will use the same static length) - (vec![registers::RX_PW_P0 + 0, 0u8], vec![0xEu8, 32u8]), + (vec![registers::RX_PW_P0, 0u8], vec![0xEu8, 32u8]), ]; let mut spi_mock = SpiMock::new(&spi_expectations); let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); diff --git a/src/radio/rf24/pipe.rs b/lib/src/radio/rf24/pipe.rs similarity index 98% rename from src/radio/rf24/pipe.rs rename to lib/src/radio/rf24/pipe.rs index 01c17c1..7c41a51 100644 --- a/src/radio/rf24/pipe.rs +++ b/lib/src/radio/rf24/pipe.rs @@ -107,7 +107,7 @@ mod test { let spi_expectations = spi_test_expects![ // open_rx_pipe(5) ( - vec![registers::RX_ADDR_P0 + 5 | commands::W_REGISTER, 0x55u8], + vec![(registers::RX_ADDR_P0 + 5) | commands::W_REGISTER, 0x55u8], vec![0xEu8, 0u8], ), // set EN_RXADDR diff --git a/src/radio/rf24/power.rs b/lib/src/radio/rf24/power.rs similarity index 100% rename from src/radio/rf24/power.rs rename to lib/src/radio/rf24/power.rs diff --git a/src/radio/rf24/radio.rs b/lib/src/radio/rf24/radio.rs similarity index 98% rename from src/radio/rf24/radio.rs rename to lib/src/radio/rf24/radio.rs index d233efd..ef23c5f 100644 --- a/src/radio/rf24/radio.rs +++ b/lib/src/radio/rf24/radio.rs @@ -314,27 +314,27 @@ mod test { ), // set payload length to 32 bytes on all pipes ( - vec![registers::RX_PW_P0 + 0 | commands::W_REGISTER, 32u8], + vec![registers::RX_PW_P0 | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8], ), ( - vec![registers::RX_PW_P0 + 1 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 1) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8], ), ( - vec![registers::RX_PW_P0 + 2 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 2) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8], ), ( - vec![registers::RX_PW_P0 + 3 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 3) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8], ), ( - vec![registers::RX_PW_P0 + 4 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 4) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8], ), ( - vec![registers::RX_PW_P0 + 5 | commands::W_REGISTER, 32u8], + vec![(registers::RX_PW_P0 + 5) | commands::W_REGISTER, 32u8], vec![0xEu8, 0u8], ), // set_address_length(5) diff --git a/src/radio/rf24/status.rs b/lib/src/radio/rf24/status.rs similarity index 100% rename from src/radio/rf24/status.rs rename to lib/src/radio/rf24/status.rs