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 0000000..c15a165 Binary files /dev/null and b/docs/src/images/favicon.ico differ diff --git a/docs/src/images/logo.jpg b/docs/src/images/logo.jpg new file mode 100644 index 0000000..2065584 Binary files /dev/null and b/docs/src/images/logo.jpg differ diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..2c52f77 --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,8 @@ + +{% + 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