Merge pull request #1 from jerryneedell/jerryn_initial_commit
Initial commit for comment and review
This commit is contained in:
commit
04ccc8e873
45 changed files with 4616 additions and 2 deletions
13
.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md
vendored
Normal file
13
.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2021 Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
Thank you for contributing! Before you submit a pull request, please read the following.
|
||||
|
||||
Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html
|
||||
|
||||
If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs
|
||||
|
||||
Before submitting the pull request, make sure you've run Pylint and Black locally on your code. You can do this manually or using pre-commit. Instructions are available here: https://adafru.it/check-your-code
|
||||
|
||||
Please remove all of this text before submitting. Include an explanation or list of changes included in your PR, as well as, if applicable, a link to any related issues.
|
||||
14
.github/workflows/build.yml
vendored
Normal file
14
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Build CI
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run Build CI workflow
|
||||
uses: adafruit/workflows-circuitpython-libs/build@main
|
||||
19
.github/workflows/failure-help-text.yml
vendored
Normal file
19
.github/workflows/failure-help-text.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# SPDX-FileCopyrightText: 2021 Scott Shawcroft for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Failure help text
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build CI"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
post-help:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||
steps:
|
||||
- name: Post comment to help
|
||||
uses: adafruit/circuitpython-action-library-ci-failed@v1
|
||||
19
.github/workflows/release_gh.yml
vendored
Normal file
19
.github/workflows/release_gh.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: GitHub Release Actions
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
upload-release-assets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run GitHub Release CI workflow
|
||||
uses: adafruit/workflows-circuitpython-libs/release-gh@main
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
upload-url: ${{ github.event.release.upload_url }}
|
||||
19
.github/workflows/release_pypi.yml
vendored
Normal file
19
.github/workflows/release_pypi.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: PyPI Release Actions
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
upload-release-assets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run PyPI Release CI workflow
|
||||
uses: adafruit/workflows-circuitpython-libs/release-pypi@main
|
||||
with:
|
||||
pypi-username: ${{ secrets.pypi_username }}
|
||||
pypi-password: ${{ secrets.pypi_password }}
|
||||
21
.pre-commit-config.yaml
Normal file
21
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.3.4
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
- repo: https://github.com/fsfe/reuse-tool
|
||||
rev: v3.0.1
|
||||
hooks:
|
||||
- id: reuse
|
||||
19
.readthedocs.yaml
Normal file
19
.readthedocs.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-latest
|
||||
tools:
|
||||
python: "3"
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- requirements: requirements.txt
|
||||
158
CODE_OF_CONDUCT.md
Normal file
158
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2014 Coraline Ada Ehmke
|
||||
SPDX-FileCopyrightText: 2019-2021 Kattni Rembor for Adafruit Industries
|
||||
|
||||
SPDX-License-Identifier: CC-BY-4.0
|
||||
-->
|
||||
# Adafruit Community Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and leaders pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level or type of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
We are committed to providing a friendly, safe and welcoming environment for
|
||||
all.
|
||||
|
||||
Examples of behavior that contributes to creating and maintaining a positive environment
|
||||
include:
|
||||
|
||||
* Be kind and courteous to others
|
||||
* Using welcoming and inclusive language
|
||||
* Respecting the identity of every community member, including asking for their
|
||||
pronouns if uncertain
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Collaborating with other community members
|
||||
* Providing desired assistance and knowledge to other community members
|
||||
* Being open to new information and ideas
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by community members include:
|
||||
|
||||
* The use of sexualized language or imagery and sexual attention or advances
|
||||
* The use of inappropriate images, including in a community member's avatar
|
||||
* The use of inappropriate language or profanity, including in a community member's nickname
|
||||
* Any spamming, flaming, baiting or other attention-stealing behavior
|
||||
* Excessive or unwelcome helping; answering outside the scope of the question
|
||||
asked
|
||||
* Discussion or promotion of activities or projects that intend or pose a risk of
|
||||
significant harm
|
||||
* Trolling, insulting/derogatory comments, and attacks of any nature (including,
|
||||
but not limited to, personal or political attacks)
|
||||
* Promoting or spreading disinformation, lies, or conspiracy theories against
|
||||
a person, group, organisation, project, or community
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Engaging in behavior that creates an unwelcoming or uninclusive environment
|
||||
* Other conduct which could reasonably be considered inappropriate
|
||||
|
||||
The Adafruit Community welcomes everyone and strives to create a safe space for all. It is built
|
||||
around sharing and contributing to technology. We encourage discussing your thoughts, experiences,
|
||||
and feelings within the scope of the community. However, there are topics that can sometimes stray
|
||||
from that scope, and can lead to hurting others and create an unwelcoming, uninclusive environment.
|
||||
|
||||
Examples of discussion topics that have been known to stray outside the scope of the Adafruit
|
||||
Community include, but are not limited to:
|
||||
|
||||
* Discussions regarding religion and related topics
|
||||
* Discussions regarding politics and related topics
|
||||
|
||||
The goal of the standards and moderation guidelines outlined here is to build
|
||||
and maintain a respectful community. We ask that you don’t just aim to be
|
||||
"technically unimpeachable", but rather try to be your best self.
|
||||
|
||||
We value many things beyond technical expertise, including collaboration and
|
||||
supporting others within our community. Providing a positive experience for
|
||||
other community members can have a much more significant impact than simply
|
||||
providing the correct answer.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project leaders are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project leaders have the right and responsibility to remove, edit, or
|
||||
reject messages, comments, commits, code, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any community member for other behaviors that they deem
|
||||
inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Moderation
|
||||
|
||||
Instances of behaviors that violate the Adafruit Community Code of Conduct
|
||||
may be reported by any member of the community. Community members are
|
||||
encouraged to report these situations, including situations they witness
|
||||
involving other community members.
|
||||
|
||||
You may report in the following ways:
|
||||
|
||||
In any situation, you may email <support@adafruit.com>.
|
||||
|
||||
On the Adafruit Discord, you may send an open message from any channel
|
||||
to all Community Moderators by tagging @community moderators. You may
|
||||
also send an open message from the #help-with-community channel, or a
|
||||
direct message to any Community Moderator.
|
||||
|
||||
The source of email and direct message reports will be kept confidential.
|
||||
|
||||
In situations on Discord where the issue is particularly offensive, possibly
|
||||
illegal, requires immediate action, or violates the Discord terms of service,
|
||||
you should also report the message directly to [Discord](https://discord.com/safety).
|
||||
|
||||
These are the steps for upholding our community’s standards of conduct.
|
||||
|
||||
1. Any member of the community may report any situation that violates the
|
||||
Adafruit Community Code of Conduct. All reports will be reviewed and
|
||||
investigated.
|
||||
2. If the behavior is a severe violation, the community member who
|
||||
committed the violation may be banned immediately, without warning.
|
||||
3. Otherwise, moderators will first respond to such behavior with a warning.
|
||||
4. Moderators follow a soft "three strikes" policy - the community member may
|
||||
be given another chance, if they are receptive to the warning and change their
|
||||
behavior.
|
||||
5. If the community member is unreceptive or unreasonable when warned by a
|
||||
moderator, or the warning goes unheeded, they may be banned for a first or
|
||||
second offense. Repeated offenses will result in the community member being
|
||||
banned.
|
||||
6. Disciplinary actions (warnings, bans, etc) for Code of Conduct violations apply
|
||||
to the platform where the violation occurred. However, depending on the severity
|
||||
of the violation, the disciplinary action may be applied across Adafruit's other
|
||||
community platforms. For example, a severe violation on the Adafruit Discord
|
||||
server may result in a ban on not only the Adafruit Discord server, but also on
|
||||
the Adafruit GitHub organisation, Adafruit Forums, Adafruit Twitter, etc.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct and the enforcement policies listed above apply to all
|
||||
Adafruit Community venues. This includes but is not limited to any community
|
||||
spaces (both public and private), the entire Adafruit Discord server, and
|
||||
Adafruit GitHub repositories. Examples of Adafruit Community spaces include
|
||||
but are not limited to meet-ups, audio chats on the Adafruit Discord, or
|
||||
interaction at a conference.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. As a community
|
||||
member, you are representing our community, and are expected to behave
|
||||
accordingly.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
||||
version 1.4, available on [contributor-covenant.org](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html),
|
||||
and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html).
|
||||
|
||||
For other projects adopting the Adafruit Community Code of
|
||||
Conduct, please contact the maintainers of those projects for enforcement.
|
||||
If you wish to use this code of conduct for your own project, consider
|
||||
explicitly mentioning your moderation policy or making a copy with your
|
||||
own moderation policy so as to avoid confusion.
|
||||
4
LICENSE
4
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 Adafruit Industries
|
||||
Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
324
LICENSES/CC-BY-4.0.txt
Normal file
324
LICENSES/CC-BY-4.0.txt
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
Creative Commons Attribution 4.0 International Creative Commons Corporation
|
||||
("Creative Commons") is not a law firm and does not provide legal services
|
||||
or legal advice. Distribution of Creative Commons public licenses does not
|
||||
create a lawyer-client or other relationship. Creative Commons makes its licenses
|
||||
and related information available on an "as-is" basis. Creative Commons gives
|
||||
no warranties regarding its licenses, any material licensed under their terms
|
||||
and conditions, or any related information. Creative Commons disclaims all
|
||||
liability for damages resulting from their use to the fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and conditions
|
||||
that creators and other rights holders may use to share original works of
|
||||
authorship and other material subject to copyright and certain other rights
|
||||
specified in the public license below. The following considerations are for
|
||||
informational purposes only, are not exhaustive, and do not form part of our
|
||||
licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are intended for use by
|
||||
those authorized to give the public permission to use material in ways otherwise
|
||||
restricted by copyright and certain other rights. Our licenses are irrevocable.
|
||||
Licensors should read and understand the terms and conditions of the license
|
||||
they choose before applying it. Licensors should also secure all rights necessary
|
||||
before applying our licenses so that the public can reuse the material as
|
||||
expected. Licensors should clearly mark any material not subject to the license.
|
||||
This includes other CC-licensed material, or material used under an exception
|
||||
or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public licenses, a licensor
|
||||
grants the public permission to use the licensed material under specified
|
||||
terms and conditions. If the licensor's permission is not necessary for any
|
||||
reason–for example, because of any applicable exception or limitation to copyright–then
|
||||
that use is not regulated by the license. Our licenses grant only permissions
|
||||
under copyright and certain other rights that a licensor has authority to
|
||||
grant. Use of the licensed material may still be restricted for other reasons,
|
||||
including because others have copyright or other rights in the material. A
|
||||
licensor may make special requests, such as asking that all changes be marked
|
||||
or described. Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations for the public
|
||||
: wiki.creativecommons.org/Considerations_for_licensees Creative Commons Attribution
|
||||
4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree to
|
||||
be bound by the terms and conditions of this Creative Commons Attribution
|
||||
4.0 International Public License ("Public License"). To the extent this Public
|
||||
License may be interpreted as a contract, You are granted the Licensed Rights
|
||||
in consideration of Your acceptance of these terms and conditions, and the
|
||||
Licensor grants You such rights in consideration of benefits the Licensor
|
||||
receives from making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
Section 1 – Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar Rights
|
||||
that is derived from or based upon the Licensed Material and in which the
|
||||
Licensed Material is translated, altered, arranged, transformed, or otherwise
|
||||
modified in a manner requiring permission under the Copyright and Similar
|
||||
Rights held by the Licensor. For purposes of this Public License, where the
|
||||
Licensed Material is a musical work, performance, or sound recording, Adapted
|
||||
Material is always produced where the Licensed Material is synched in timed
|
||||
relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright and Similar
|
||||
Rights in Your contributions to Adapted Material in accordance with the terms
|
||||
and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights closely
|
||||
related to copyright including, without limitation, performance, broadcast,
|
||||
sound recording, and Sui Generis Database Rights, without regard to how the
|
||||
rights are labeled or categorized. For purposes of this Public License, the
|
||||
rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the absence
|
||||
of proper authority, may not be circumvented under laws fulfilling obligations
|
||||
under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996,
|
||||
and/or similar international agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or any other
|
||||
exception or limitation to Copyright and Similar Rights that applies to Your
|
||||
use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database, or other
|
||||
material to which the Licensor applied this Public License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the terms and
|
||||
conditions of this Public License, which are limited to all Copyright and
|
||||
Similar Rights that apply to Your use of the Licensed Material and that the
|
||||
Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights under this
|
||||
Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or process that
|
||||
requires permission under the Licensed Rights, such as reproduction, public
|
||||
display, public performance, distribution, dissemination, communication, or
|
||||
importation, and to make material available to the public including in ways
|
||||
that members of the public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright resulting
|
||||
from Directive 96/9/EC of the European Parliament and of the Council of 11
|
||||
March 1996 on the legal protection of databases, as amended and/or succeeded,
|
||||
as well as other essentially equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights under
|
||||
this Public License. Your has a corresponding meaning.
|
||||
|
||||
Section 2 – Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License, the Licensor
|
||||
hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive,
|
||||
irrevocable license to exercise the Licensed Rights in the Licensed Material
|
||||
to:
|
||||
|
||||
A. reproduce and Share the Licensed Material, in whole or in part; and
|
||||
|
||||
B. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions
|
||||
and Limitations apply to Your use, this Public License does not apply, and
|
||||
You do not need to comply with its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section 6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The Licensor authorizes
|
||||
You to exercise the Licensed Rights in all media and formats whether now known
|
||||
or hereafter created, and to make technical modifications necessary to do
|
||||
so. The Licensor waives and/or agrees not to assert any right or authority
|
||||
to forbid You from making technical modifications necessary to exercise the
|
||||
Licensed Rights, including technical modifications necessary to circumvent
|
||||
Effective Technological Measures. For purposes of this Public License, simply
|
||||
making modifications authorized by this Section 2(a)(4) never produces Adapted
|
||||
Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed
|
||||
Material automatically receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this Public License.
|
||||
|
||||
B. No downstream restrictions. You may not offer or impose any additional
|
||||
or different terms or conditions on, or apply any Effective Technological
|
||||
Measures to, the Licensed Material if doing so restricts exercise of the Licensed
|
||||
Rights by any recipient of the Licensed Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or may be construed
|
||||
as permission to assert or imply that You are, or that Your use of the Licensed
|
||||
Material is, connected with, or sponsored, endorsed, or granted official status
|
||||
by, the Licensor or others designated to receive attribution as provided in
|
||||
Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not licensed under this
|
||||
Public License, nor are publicity, privacy, and/or other similar personality
|
||||
rights; however, to the extent possible, the Licensor waives and/or agrees
|
||||
not to assert any such rights held by the Licensor to the limited extent necessary
|
||||
to allow You to exercise the Licensed Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to collect royalties
|
||||
from You for the exercise of the Licensed Rights, whether directly or through
|
||||
a collecting society under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly reserves any right
|
||||
to collect such royalties.
|
||||
|
||||
Section 3 – License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the following
|
||||
conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified form), You must:
|
||||
|
||||
A. retain the following if it is supplied by the Licensor with the Licensed
|
||||
Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed Material and any others
|
||||
designated to receive attribution, in any reasonable manner requested by the
|
||||
Licensor (including by pseudonym if designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||
|
||||
B. indicate if You modified the Licensed Material and retain an indication
|
||||
of any previous modifications; and
|
||||
|
||||
C. indicate the Licensed Material is licensed under this Public License, and
|
||||
include the text of, or the URI or hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner
|
||||
based on the medium, means, and context in which You Share the Licensed Material.
|
||||
For example, it may be reasonable to satisfy the conditions by providing a
|
||||
URI or hyperlink to a resource that includes the required information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the information required
|
||||
by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's License You apply
|
||||
must not prevent recipients of the Adapted Material from complying with this
|
||||
Public License.
|
||||
|
||||
Section 4 – Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that apply to
|
||||
Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract,
|
||||
reuse, reproduce, and Share all or a substantial portion of the contents of
|
||||
the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database contents in
|
||||
a database in which You have Sui Generis Database Rights, then the database
|
||||
in which You have Sui Generis Database Rights (but not its individual contents)
|
||||
is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share all or
|
||||
a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not replace
|
||||
Your obligations under this Public License where the Licensed Rights include
|
||||
other Copyright and Similar Rights.
|
||||
|
||||
Section 5 – Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. Unless otherwise separately undertaken by the Licensor, to the extent possible,
|
||||
the Licensor offers the Licensed Material as-is and as-available, and makes
|
||||
no representations or warranties of any kind concerning the Licensed Material,
|
||||
whether express, implied, statutory, or other. This includes, without limitation,
|
||||
warranties of title, merchantability, fitness for a particular purpose, non-infringement,
|
||||
absence of latent or other defects, accuracy, or the presence or absence of
|
||||
errors, whether or not known or discoverable. Where disclaimers of warranties
|
||||
are not allowed in full or in part, this disclaimer may not apply to You.
|
||||
|
||||
b. To the extent possible, in no event will the Licensor be liable to You
|
||||
on any legal theory (including, without limitation, negligence) or otherwise
|
||||
for any direct, special, indirect, incidental, consequential, punitive, exemplary,
|
||||
or other losses, costs, expenses, or damages arising out of this Public License
|
||||
or use of the Licensed Material, even if the Licensor has been advised of
|
||||
the possibility of such losses, costs, expenses, or damages. Where a limitation
|
||||
of liability is not allowed in full or in part, this limitation may not apply
|
||||
to You.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided above
|
||||
shall be interpreted in a manner that, to the extent possible, most closely
|
||||
approximates an absolute disclaimer and waiver of all liability.
|
||||
|
||||
Section 6 – Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and Similar Rights
|
||||
licensed here. However, if You fail to comply with this Public License, then
|
||||
Your rights under this Public License terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under Section
|
||||
6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided it is cured
|
||||
within 30 days of Your discovery of the violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
c. For the avoidance of doubt, this Section 6(b) does not affect any right
|
||||
the Licensor may have to seek remedies for Your violations of this Public
|
||||
License.
|
||||
|
||||
d. For the avoidance of doubt, the Licensor may also offer the Licensed Material
|
||||
under separate terms or conditions or stop distributing the Licensed Material
|
||||
at any time; however, doing so will not terminate this Public License.
|
||||
|
||||
e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||
|
||||
Section 7 – Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different terms or
|
||||
conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the Licensed
|
||||
Material not stated herein are separate from and independent of the terms
|
||||
and conditions of this Public License.
|
||||
|
||||
Section 8 – Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and shall not
|
||||
be interpreted to, reduce, limit, restrict, or impose conditions on any use
|
||||
of the Licensed Material that could lawfully be made without permission under
|
||||
this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is deemed
|
||||
unenforceable, it shall be automatically reformed to the minimum extent necessary
|
||||
to make it enforceable. If the provision cannot be reformed, it shall be severed
|
||||
from this Public License without affecting the enforceability of the remaining
|
||||
terms and conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no failure
|
||||
to comply consented to unless expressly agreed to by the Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted as a limitation
|
||||
upon, or waiver of, any privileges and immunities that apply to the Licensor
|
||||
or You, including from the legal processes of any jurisdiction or authority.
|
||||
|
||||
Creative Commons is not a party to its public licenses. Notwithstanding, Creative
|
||||
Commons may elect to apply one of its public licenses to material it publishes
|
||||
and in those instances will be considered the "Licensor." The text of the
|
||||
Creative Commons public licenses is dedicated to the public domain under the
|
||||
CC0 Public Domain Dedication. Except for the limited purpose of indicating
|
||||
that material is shared under a Creative Commons public license or as otherwise
|
||||
permitted by the Creative Commons policies published at creativecommons.org/policies,
|
||||
Creative Commons does not authorize the use of the trademark "Creative Commons"
|
||||
or any other trademark or logo of Creative Commons without its prior written
|
||||
consent including, without limitation, in connection with any unauthorized
|
||||
modifications to any of its public licenses or any other arrangements, understandings,
|
||||
or agreements concerning use of licensed material. For the avoidance of doubt,
|
||||
this paragraph does not form part of the public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
19
LICENSES/MIT.txt
Normal file
19
LICENSES/MIT.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
MIT License Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
20
LICENSES/Unlicense.txt
Normal file
20
LICENSES/Unlicense.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute
|
||||
this software, either in source code form or as a compiled binary, for any
|
||||
purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and
|
||||
to the detriment of our heirs and successors. We intend this dedication to
|
||||
be an overt act of relinquishment in perpetuity of all present and future
|
||||
rights to this software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information,
|
||||
please refer to <https://unlicense.org/>
|
||||
107
README.rst
Normal file
107
README.rst
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
|
||||
.. image:: https://readthedocs.org/projects/adafruit-circuitpython-rfm/badge/?version=latest
|
||||
:target: https://docs.circuitpython.org/projects/rfm/en/latest/
|
||||
:alt: Documentation Status
|
||||
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg
|
||||
:target: https://adafru.it/discord
|
||||
:alt: Discord
|
||||
|
||||
|
||||
.. image:: https://github.com/jerryneedell/Adafruit_CircuitPython_RFM/workflows/Build%20CI/badge.svg
|
||||
:target: https://github.com/jerryneedell/Adafruit_CircuitPython_RFM/actions
|
||||
:alt: Build Status
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
||||
:target: https://github.com/astral-sh/ruff
|
||||
:alt: Code Style: Ruff
|
||||
|
||||
Support for RFM69 and RFM9x modules
|
||||
|
||||
|
||||
Dependencies
|
||||
=============
|
||||
This driver depends on:
|
||||
|
||||
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
|
||||
* `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_
|
||||
|
||||
Please ensure all dependencies are available on the CircuitPython filesystem.
|
||||
This is easily achieved by downloading
|
||||
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
|
||||
or individual libraries can be installed using
|
||||
`circup <https://github.com/adafruit/circup>`_.
|
||||
|
||||
|
||||
Installing from PyPI
|
||||
=====================
|
||||
|
||||
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
|
||||
PyPI <https://pypi.org/project/adafruit-circuitpython-rfm/>`_.
|
||||
To install for current user:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip3 install adafruit-circuitpython-rfm
|
||||
|
||||
To install system-wide (this may be required in some cases):
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo pip3 install adafruit-circuitpython-rfm
|
||||
|
||||
To install in a virtual environment in your current project:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
mkdir project-name && cd project-name
|
||||
python3 -m venv .venv
|
||||
source .env/bin/activate
|
||||
pip3 install adafruit-circuitpython-rfm
|
||||
|
||||
Installing to a Connected CircuitPython Device with Circup
|
||||
==========================================================
|
||||
|
||||
Make sure that you have ``circup`` installed in your Python environment.
|
||||
Install it with the following command if necessary:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip3 install circup
|
||||
|
||||
With ``circup`` installed and your CircuitPython device connected use the
|
||||
following command to install:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
circup install adafruit_rfm
|
||||
|
||||
Or the following command to update an existing version:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
circup update
|
||||
|
||||
Usage Example
|
||||
=============
|
||||
|
||||
See examples in the GitHub Repository. <https://github.com/jerryneedell/Adafruit_CircuitPython_RFM.git>
|
||||
|
||||
Documentation
|
||||
=============
|
||||
API documentation for this library can be found on `Read the Docs <https://docs.circuitpython.org/projects/rfm/en/latest/>`_.
|
||||
|
||||
For information on building library documentation, please check out
|
||||
`this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Contributions are welcome! Please read our `Code of Conduct
|
||||
<https://github.com/jerryneedell/Adafruit_CircuitPython_RFM/blob/HEAD/CODE_OF_CONDUCT.md>`_
|
||||
before contributing to help this project stay welcoming.
|
||||
3
README.rst.license
Normal file
3
README.rst.license
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
SPDX-FileCopyrightText: Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
SPDX-License-Identifier: MIT
|
||||
0
adafruit_rfm/__init__.py
Normal file
0
adafruit_rfm/__init__.py
Normal file
650
adafruit_rfm/rfm69.py
Normal file
650
adafruit_rfm/rfm69.py
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
# SPDX-FileCopyrightText: 2024 Jerry Needell for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
`adafruit_rfm.rfm69`
|
||||
====================================================
|
||||
|
||||
CircuitPython RFM69 packet radio module. This supports sending and
|
||||
receiving of packets with RFM69 series radios (433/915Mhz).
|
||||
|
||||
.. warning:: This is NOT for LoRa radios!
|
||||
|
||||
.. note:: This is a 'best effort' at receiving data using pure Python code. You might lose packets
|
||||
if they're sent too quickly for the board to process them.
|
||||
|
||||
* Author(s): Jerry Needell
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from micropython import const
|
||||
|
||||
from adafruit_rfm.rfm_common import RFMSPI, ticks_diff
|
||||
|
||||
HAS_SUPERVISOR = False
|
||||
|
||||
try:
|
||||
import supervisor
|
||||
|
||||
if hasattr(supervisor, "ticks_ms"):
|
||||
HAS_SUPERVISOR = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from typing import Optional
|
||||
|
||||
import busio
|
||||
import digitalio
|
||||
from circuitpython_typing import ReadableBuffer
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
__version__ = "0.0.0+auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM.git"
|
||||
|
||||
|
||||
# Internal constants:
|
||||
_RF69_REG_00_FIFO = const(0x00)
|
||||
_RF69_REG_01_OP_MODE = const(0x01)
|
||||
_RF69_REG_02_DATA_MOD = const(0x02)
|
||||
_RF69_REG_03_BITRATE_MSB = const(0x03)
|
||||
_RF69_REG_04_BITRATE_LSB = const(0x04)
|
||||
_RF69_REG_05_FDEV_MSB = const(0x05)
|
||||
_RF69_REG_06_FDEV_LSB = const(0x06)
|
||||
_RF69_REG_07_FRF_MSB = const(0x07)
|
||||
_RF69_REG_08_FRF_MID = const(0x08)
|
||||
_RF69_REG_09_FRF_LSB = const(0x09)
|
||||
_RF69_REG_10_VERSION = const(0x10)
|
||||
_RF69_REG_11_PA_LEVEL = const(0x11)
|
||||
_RF69_REG_13_OCP = const(0x13)
|
||||
_RF69_REG_19_RX_BW = const(0x19)
|
||||
_RF69_REG_1A_AFC_BW = const(0x1A)
|
||||
_RF69_REG_1B_OOK_PEAK = const(0x1B)
|
||||
_RF69_REG_1C_OOK_AVG = const(0x1C)
|
||||
_RF69_REG_1D_OOK_FIX = const(0x1D)
|
||||
_RF69_REG_24_RSSI_VALUE = const(0x24)
|
||||
_RF69_REG_25_DIO_MAPPING1 = const(0x25)
|
||||
_RF69_REG_27_IRQ_FLAGS1 = const(0x27)
|
||||
_RF69_REG_28_IRQ_FLAGS2 = const(0x28)
|
||||
_RF69_REG_2C_PREAMBLE_MSB = const(0x2C)
|
||||
_RF69_REG_2D_PREAMBLE_LSB = const(0x2D)
|
||||
_RF69_REG_2E_SYNC_CONFIG = const(0x2E)
|
||||
_RF69_REG_2F_SYNC_VALUE1 = const(0x2F)
|
||||
_RF69_REG_39_NODE_ADDR = const(0x39)
|
||||
_RF69_REG_3A_BROADCAST_ADDR = const(0x3A)
|
||||
_RF69_REG_37_PACKET_CONFIG1 = const(0x37)
|
||||
_RF69_REG_3C_FIFO_THRESH = const(0x3C)
|
||||
_RF69_REG_3D_PACKET_CONFIG2 = const(0x3D)
|
||||
_RF69_REG_3E_AES_KEY1 = const(0x3E)
|
||||
_RF69_REG_4E_TEMP1 = const(0x4E)
|
||||
_RF69_REG_4F_TEMP2 = const(0x4F)
|
||||
_RF69_REG_5A_TEST_PA1 = const(0x5A)
|
||||
_RF69_REG_5C_TEST_PA2 = const(0x5C)
|
||||
_RF69_REG_6F_TEST_DAGC = const(0x6F)
|
||||
|
||||
_TEST_PA1_NORMAL = const(0x55)
|
||||
_TEST_PA1_BOOST = const(0x5D)
|
||||
_TEST_PA2_NORMAL = const(0x70)
|
||||
_TEST_PA2_BOOST = const(0x7C)
|
||||
_OCP_NORMAL = const(0x1A)
|
||||
_OCP_HIGH_POWER = const(0x0F)
|
||||
|
||||
# The crystal oscillator frequency and frequency synthesizer step size.
|
||||
# See the datasheet for details of this calculation.
|
||||
_FXOSC = 32000000.0
|
||||
_FSTEP = _FXOSC / 524288
|
||||
|
||||
# RadioHead specific compatibility constants.
|
||||
_RH_BROADCAST_ADDRESS = const(0xFF)
|
||||
# The acknowledgement bit in the FLAGS
|
||||
# The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved
|
||||
# for application layer use.
|
||||
_RH_FLAGS_ACK = const(0x80)
|
||||
_RH_FLAGS_RETRY = const(0x40)
|
||||
|
||||
# User facing constants:
|
||||
SLEEP_MODE = 0b000
|
||||
STANDBY_MODE = 0b001
|
||||
FS_MODE = 0b010
|
||||
TX_MODE = 0b011
|
||||
RX_MODE = 0b100
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-public-methods
|
||||
class RFM69(RFMSPI):
|
||||
"""Interface to a RFM69 series packet radio. Allows simple sending and
|
||||
receiving of wireless data at supported frequencies of the radio
|
||||
(433/915mhz).
|
||||
|
||||
:param busio.SPI spi: The SPI bus connected to the chip. Ensure SCK, MOSI, and MISO are
|
||||
connected.
|
||||
:param ~digitalio.DigitalInOut cs: A DigitalInOut object connected to the chip's CS/chip select
|
||||
line.
|
||||
:param ~digitalio.DigitalInOut reset: A DigitalInOut object connected to the chip's RST/reset
|
||||
line.
|
||||
:param int frequency: The center frequency to configure for radio transmission and reception.
|
||||
Must be a frequency supported by your hardware (i.e. either 433 or 915mhz).
|
||||
:param bytes sync_word: A byte string up to 8 bytes long which represents the syncronization
|
||||
word used by received and transmitted packets. Read the datasheet for a full understanding
|
||||
of this value! However by default the library will set a value that matches the RadioHead
|
||||
Arduino library.
|
||||
:param int preamble_length: The number of bytes to pre-pend to a data packet as a preamble.
|
||||
This is by default 4 to match the RadioHead library.
|
||||
:param bytes encryption_key: A 16 byte long string that represents the AES encryption key to use
|
||||
when encrypting and decrypting packets. Both the transmitter and receiver MUST have the
|
||||
same key value! By default no encryption key is set or used.
|
||||
:param bool high_power: Indicate if the chip is a high power variant that supports boosted
|
||||
transmission power. The default is True as it supports the common RFM69HCW modules sold by
|
||||
Adafruit.
|
||||
|
||||
Remember this library makes a best effort at receiving packets with pure Python code. Trying
|
||||
to receive packets too quickly will result in lost data so limit yourself to simple scenarios
|
||||
of sending and receiving single packets at a time.
|
||||
|
||||
Also note this library defaults to be compatible with RadioHead Arduino library communication.
|
||||
This means the library sets up the radio modulation to match RadioHead's default of GFSK
|
||||
encoding, 250kbit/s bitrate, and 250khz frequency deviation. To change this requires explicitly
|
||||
setting the radio's bitrate and encoding register bits.
|
||||
Read the datasheet and study the init function to see an example of this--advanced users only!
|
||||
Advanced RadioHead features like address/node specific packets or "reliable datagram" delivery
|
||||
are supported however due to the limitations noted, "reliable datagram" is still subject to
|
||||
missed packets.
|
||||
"""
|
||||
|
||||
# Control bits from the registers of the chip:
|
||||
data_mode = RFMSPI.RegisterBits(_RF69_REG_02_DATA_MOD, offset=5, bits=2)
|
||||
modulation_type = RFMSPI.RegisterBits(_RF69_REG_02_DATA_MOD, offset=3, bits=2)
|
||||
modulation_shaping = RFMSPI.RegisterBits(_RF69_REG_02_DATA_MOD, offset=0, bits=2)
|
||||
temp_start = RFMSPI.RegisterBits(_RF69_REG_4E_TEMP1, offset=3)
|
||||
temp_running = RFMSPI.RegisterBits(_RF69_REG_4E_TEMP1, offset=2)
|
||||
sync_on = RFMSPI.RegisterBits(_RF69_REG_2E_SYNC_CONFIG, offset=7)
|
||||
sync_size = RFMSPI.RegisterBits(_RF69_REG_2E_SYNC_CONFIG, offset=3, bits=3)
|
||||
aes_on = RFMSPI.RegisterBits(_RF69_REG_3D_PACKET_CONFIG2, offset=0)
|
||||
pa_0_on = RFMSPI.RegisterBits(_RF69_REG_11_PA_LEVEL, offset=7)
|
||||
pa_1_on = RFMSPI.RegisterBits(_RF69_REG_11_PA_LEVEL, offset=6)
|
||||
pa_2_on = RFMSPI.RegisterBits(_RF69_REG_11_PA_LEVEL, offset=5)
|
||||
output_power = RFMSPI.RegisterBits(_RF69_REG_11_PA_LEVEL, offset=0, bits=5)
|
||||
rx_bw_dcc_freq = RFMSPI.RegisterBits(_RF69_REG_19_RX_BW, offset=5, bits=3)
|
||||
rx_bw_mantissa = RFMSPI.RegisterBits(_RF69_REG_19_RX_BW, offset=3, bits=2)
|
||||
rx_bw_exponent = RFMSPI.RegisterBits(_RF69_REG_19_RX_BW, offset=0, bits=3)
|
||||
afc_bw_dcc_freq = RFMSPI.RegisterBits(_RF69_REG_1A_AFC_BW, offset=5, bits=3)
|
||||
afc_bw_mantissa = RFMSPI.RegisterBits(_RF69_REG_1A_AFC_BW, offset=3, bits=2)
|
||||
afc_bw_exponent = RFMSPI.RegisterBits(_RF69_REG_1A_AFC_BW, offset=0, bits=3)
|
||||
packet_format = RFMSPI.RegisterBits(_RF69_REG_37_PACKET_CONFIG1, offset=7, bits=1)
|
||||
dc_free = RFMSPI.RegisterBits(_RF69_REG_37_PACKET_CONFIG1, offset=5, bits=2)
|
||||
crc_on = RFMSPI.RegisterBits(_RF69_REG_37_PACKET_CONFIG1, offset=4, bits=1)
|
||||
crc_auto_clear_off = RFMSPI.RegisterBits(_RF69_REG_37_PACKET_CONFIG1, offset=3, bits=1)
|
||||
address_filter = RFMSPI.RegisterBits(_RF69_REG_37_PACKET_CONFIG1, offset=1, bits=2)
|
||||
mode_ready = RFMSPI.RegisterBits(_RF69_REG_27_IRQ_FLAGS1, offset=7)
|
||||
dio_0_mapping = RFMSPI.RegisterBits(_RF69_REG_25_DIO_MAPPING1, offset=6, bits=2)
|
||||
ook_thresh_type = RFMSPI.RegisterBits(_RF69_REG_1B_OOK_PEAK, offset=6, bits=2)
|
||||
ook_thresh_step = RFMSPI.RegisterBits(_RF69_REG_1B_OOK_PEAK, offset=5, bits=3)
|
||||
ook_peak_thresh_dec = RFMSPI.RegisterBits(_RF69_REG_1B_OOK_PEAK, offset=0, bits=3)
|
||||
ook_average_thresh_filt = RFMSPI.RegisterBits(_RF69_REG_1C_OOK_AVG, offset=6, bits=2)
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
spi: busio.SPI,
|
||||
cs: digitalio.DigitalInOut,
|
||||
rst: digitalio.DigitalInOut,
|
||||
frequency: int,
|
||||
*,
|
||||
sync_word: bytes = b"\x2d\xd4",
|
||||
preamble_length: int = 4,
|
||||
encryption_key: Optional[bytes] = None,
|
||||
high_power: bool = True,
|
||||
baudrate: int = 2000000,
|
||||
crc: bool = True,
|
||||
) -> None:
|
||||
super().__init__(spi, cs, baudrate=baudrate)
|
||||
|
||||
self.module = "RFM69"
|
||||
self.max_packet_length = 60
|
||||
self.high_power = high_power
|
||||
# Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz.
|
||||
# self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0)
|
||||
# Setup reset as a digital output that's low.
|
||||
self._rst = rst
|
||||
self._rst.switch_to_output(value=False)
|
||||
self.reset() # Reset the chip.
|
||||
# Check the version of the chip.
|
||||
version = self.read_u8(_RF69_REG_10_VERSION)
|
||||
if version not in (0x23, 0x24):
|
||||
raise RuntimeError("Invalid RFM69 version, check wiring! ID found:", hex(version))
|
||||
self.idle() # Enter idle state.
|
||||
# Setup the chip in a similar way to the RadioHead RFM69 library.
|
||||
# Set FIFO TX condition to not empty and the default FIFO threshold to 15.
|
||||
self.write_u8(_RF69_REG_3C_FIFO_THRESH, 0b10001111)
|
||||
# Configure low beta off.
|
||||
self.write_u8(_RF69_REG_6F_TEST_DAGC, 0x30)
|
||||
# Set the syncronization word.
|
||||
self.sync_word = sync_word
|
||||
self.preamble_length = preamble_length # Set the preamble length.
|
||||
self.frequency_mhz = frequency # Set frequency.
|
||||
self.encryption_key = encryption_key # Set encryption key.
|
||||
# Configure modulation for RadioHead library GFSK_Rb250Fd250 mode
|
||||
# by default. Users with advanced knowledge can manually reconfigure
|
||||
# for any other mode (consulting the datasheet is absolutely
|
||||
# necessary!).
|
||||
self.modulation_shaping = 0b01 # Gaussian filter, BT=1.0
|
||||
self.bitrate = 250000 # 250kbs
|
||||
self.frequency_deviation = 250000 # 250khz
|
||||
self.rx_bw_dcc_freq = 0b111 # RxBw register = 0xE0
|
||||
self.rx_bw_mantissa = 0b00
|
||||
self.rx_bw_exponent = 0b000
|
||||
self.afc_bw_dcc_freq = 0b111 # AfcBw register = 0xE0
|
||||
self.afc_bw_mantissa = 0b00
|
||||
self.afc_bw_exponent = 0b000
|
||||
self.packet_format = 1 # Variable length.
|
||||
self.dc_free = 0b10 # Whitening
|
||||
# Set transmit power to 13 dBm, a safe value any module supports.
|
||||
self._tx_power = None
|
||||
self.tx_power = 13
|
||||
# Default to enable CRC checking on incoming packets.
|
||||
self.enable_crc = crc
|
||||
self.snr = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Perform a reset of the chip."""
|
||||
# See section 7.2.2 of the datasheet for reset description.
|
||||
self._rst.value = True
|
||||
time.sleep(0.0001) # 100 us
|
||||
self._rst.value = False
|
||||
time.sleep(0.005) # 5 ms
|
||||
|
||||
def disable_boost(self) -> None:
|
||||
"""Disable preamp boost."""
|
||||
if self.high_power:
|
||||
self.write_u8(_RF69_REG_5A_TEST_PA1, _TEST_PA1_NORMAL)
|
||||
self.write_u8(_RF69_REG_5C_TEST_PA2, _TEST_PA2_NORMAL)
|
||||
self.write_u8(_RF69_REG_13_OCP, _OCP_NORMAL)
|
||||
|
||||
def idle(self) -> None:
|
||||
"""Enter idle standby mode (switching off high power amplifiers if necessary)."""
|
||||
# Like RadioHead library, turn off high power boost if enabled.
|
||||
self.disable_boost()
|
||||
self.operation_mode = STANDBY_MODE
|
||||
|
||||
def sleep(self) -> None:
|
||||
"""Enter sleep mode."""
|
||||
self.operation_mode = SLEEP_MODE
|
||||
|
||||
def listen(self) -> None:
|
||||
"""Listen for packets to be received by the chip. Use :py:func:`receive` to listen, wait
|
||||
and retrieve packets as they're available.
|
||||
"""
|
||||
# Like RadioHead library, turn off high power boost if enabled.
|
||||
self.disable_boost()
|
||||
# Enable payload ready interrupt for D0 line.
|
||||
self.dio_0_mapping = 0b01
|
||||
# Enter RX mode (will clear FIFO!).
|
||||
self.operation_mode = RX_MODE
|
||||
|
||||
def transmit(self) -> None:
|
||||
"""Transmit a packet which is queued in the FIFO. This is a low level function for
|
||||
entering transmit mode and more. For generating and transmitting a packet of data use
|
||||
:py:func:`send` instead.
|
||||
"""
|
||||
# Like RadioHead library, turn on high power boost if needed.
|
||||
if self.high_power and (self._tx_power >= 18):
|
||||
self.write_u8(_RF69_REG_5A_TEST_PA1, _TEST_PA1_BOOST)
|
||||
self.write_u8(_RF69_REG_5C_TEST_PA2, _TEST_PA2_BOOST)
|
||||
self.write_u8(_RF69_REG_13_OCP, _OCP_HIGH_POWER)
|
||||
# Enable packet sent interrupt for D0 line.
|
||||
self.dio_0_mapping = 0b00
|
||||
# Enter TX mode (will clear FIFO!).
|
||||
self.operation_mode = TX_MODE
|
||||
|
||||
@property
|
||||
def temperature(self) -> float:
|
||||
"""The internal temperature of the chip in degrees Celsius. Be warned this is not
|
||||
calibrated or very accurate.
|
||||
|
||||
.. warning:: Reading this will STOP any receiving/sending that might be happening!
|
||||
"""
|
||||
# Start a measurement then poll the measurement finished bit.
|
||||
self.temp_start = 1
|
||||
while self.temp_running > 0:
|
||||
pass
|
||||
# Grab the temperature value and convert it to Celsius.
|
||||
# This uses the same observed value formula from the Radiohead library.
|
||||
temp = self.read_u8(_RF69_REG_4F_TEMP2)
|
||||
return 166.0 - temp
|
||||
|
||||
@property
|
||||
def operation_mode(self) -> int:
|
||||
"""The operation mode value. Unless you're manually controlling the chip you shouldn't
|
||||
change the operation_mode with this property as other side-effects are required for
|
||||
changing logical modes--use :py:func:`idle`, :py:func:`sleep`, :py:func:`transmit`,
|
||||
:py:func:`listen` instead to signal intent for explicit logical modes.
|
||||
"""
|
||||
op_mode = self.read_u8(_RF69_REG_01_OP_MODE)
|
||||
return (op_mode >> 2) & 0b111
|
||||
|
||||
@operation_mode.setter
|
||||
def operation_mode(self, val: int) -> None:
|
||||
assert 0 <= val <= 4
|
||||
# Set the mode bits inside the operation mode register.
|
||||
op_mode = self.read_u8(_RF69_REG_01_OP_MODE)
|
||||
op_mode &= 0b11100011
|
||||
op_mode |= val << 2
|
||||
self.write_u8(_RF69_REG_01_OP_MODE, op_mode)
|
||||
# Wait for mode to change by polling interrupt bit.
|
||||
if HAS_SUPERVISOR:
|
||||
start = supervisor.ticks_ms()
|
||||
while not self.mode_ready:
|
||||
if ticks_diff(supervisor.ticks_ms(), start) >= 1000:
|
||||
raise TimeoutError("Operation Mode failed to set.")
|
||||
else:
|
||||
start = time.monotonic()
|
||||
while not self.mode_ready:
|
||||
if time.monotonic() - start >= 1:
|
||||
raise TimeoutError("Operation Mode failed to set.")
|
||||
|
||||
@property
|
||||
def sync_word(self) -> bytearray:
|
||||
"""The synchronization word value. This is a byte string up to 8 bytes long (64 bits)
|
||||
which indicates the synchronization word for transmitted and received packets. Any
|
||||
received packet which does not include this sync word will be ignored. The default value
|
||||
is 0x2D, 0xD4 which matches the RadioHead RFM69 library. Setting a value of None will
|
||||
disable synchronization word matching entirely.
|
||||
"""
|
||||
# Handle when sync word is disabled..
|
||||
if not self.sync_on:
|
||||
return None
|
||||
# Sync word is not disabled so read the current value.
|
||||
sync_word_length = self.sync_size + 1 # Sync word size is offset by 1
|
||||
# according to datasheet.
|
||||
sync_word = bytearray(sync_word_length)
|
||||
self.read_into(_RF69_REG_2F_SYNC_VALUE1, sync_word)
|
||||
return sync_word
|
||||
|
||||
@sync_word.setter
|
||||
def sync_word(self, val: Optional[bytearray]) -> None:
|
||||
# Handle disabling sync word when None value is set.
|
||||
if val is None:
|
||||
self.sync_on = 0
|
||||
else:
|
||||
# Check sync word is at most 8 bytes.
|
||||
assert 1 <= len(val) <= 8
|
||||
# Update the value, size and turn on the sync word.
|
||||
self.write_from(_RF69_REG_2F_SYNC_VALUE1, val)
|
||||
self.sync_size = len(val) - 1 # Again sync word size is offset by
|
||||
# 1 according to datasheet.
|
||||
self.sync_on = 1
|
||||
|
||||
@property
|
||||
def preamble_length(self) -> int:
|
||||
"""The length of the preamble for sent and received packets, an unsigned 16-bit value.
|
||||
Received packets must match this length or they are ignored! Set to 4 to match the
|
||||
RadioHead RFM69 library.
|
||||
"""
|
||||
msb = self.read_u8(_RF69_REG_2C_PREAMBLE_MSB)
|
||||
lsb = self.read_u8(_RF69_REG_2D_PREAMBLE_LSB)
|
||||
return ((msb << 8) | lsb) & 0xFFFF
|
||||
|
||||
@preamble_length.setter
|
||||
def preamble_length(self, val: int) -> None:
|
||||
assert 0 <= val <= 65535
|
||||
self.write_u8(_RF69_REG_2C_PREAMBLE_MSB, (val >> 8) & 0xFF)
|
||||
self.write_u8(_RF69_REG_2D_PREAMBLE_LSB, val & 0xFF)
|
||||
|
||||
@property
|
||||
def frequency_mhz(self) -> float:
|
||||
"""The frequency of the radio in Megahertz. Only the allowed values for your radio must be
|
||||
specified (i.e. 433 vs. 915 mhz)!
|
||||
"""
|
||||
# FRF register is computed from the frequency following the datasheet.
|
||||
# See section 6.2 and FRF register description.
|
||||
# Read bytes of FRF register and assemble into a 24-bit unsigned value.
|
||||
msb = self.read_u8(_RF69_REG_07_FRF_MSB)
|
||||
mid = self.read_u8(_RF69_REG_08_FRF_MID)
|
||||
lsb = self.read_u8(_RF69_REG_09_FRF_LSB)
|
||||
frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF
|
||||
frequency = (frf * _FSTEP) / 1000000.0
|
||||
return frequency
|
||||
|
||||
@frequency_mhz.setter
|
||||
def frequency_mhz(self, val: float) -> None:
|
||||
assert 290 <= val <= 1020
|
||||
# Calculate FRF register 24-bit value using section 6.2 of the datasheet.
|
||||
frf = int((val * 1000000.0) / _FSTEP) & 0xFFFFFF
|
||||
# Extract byte values and update registers.
|
||||
msb = frf >> 16
|
||||
mid = (frf >> 8) & 0xFF
|
||||
lsb = frf & 0xFF
|
||||
self.write_u8(_RF69_REG_07_FRF_MSB, msb)
|
||||
self.write_u8(_RF69_REG_08_FRF_MID, mid)
|
||||
self.write_u8(_RF69_REG_09_FRF_LSB, lsb)
|
||||
|
||||
@property
|
||||
def encryption_key(self) -> bytearray:
|
||||
"""The AES encryption key used to encrypt and decrypt packets by the chip. This can be set
|
||||
to None to disable encryption (the default), otherwise it must be a 16 byte long byte
|
||||
string which defines the key (both the transmitter and receiver must use the same key
|
||||
value).
|
||||
"""
|
||||
# Handle if encryption is disabled.
|
||||
if self.aes_on == 0:
|
||||
return None
|
||||
# Encryption is enabled so read the key and return it.
|
||||
key = bytearray(16)
|
||||
self.read_into(_RF69_REG_3E_AES_KEY1, key)
|
||||
return key
|
||||
|
||||
@encryption_key.setter
|
||||
def encryption_key(self, val: bytearray) -> None:
|
||||
# Handle if unsetting the encryption key (None value).
|
||||
if val is None:
|
||||
self.aes_on = 0
|
||||
else:
|
||||
# Set the encryption key and enable encryption.
|
||||
assert len(val) == 16
|
||||
self.write_from(_RF69_REG_3E_AES_KEY1, val)
|
||||
self.aes_on = 1
|
||||
|
||||
@property
|
||||
def tx_power(self) -> int:
|
||||
"""The transmit power in dBm. Can be set to a value from -2 to 20 for high power devices
|
||||
(RFM69HCW, high_power=True) or -18 to 13 for low power devices. Only integer power
|
||||
levels are actually set (i.e. 12.5 will result in a value of 12 dBm).
|
||||
"""
|
||||
# Follow table 10 truth table from the datasheet for determining power
|
||||
# level from the individual PA level bits and output power register.
|
||||
pa0 = self.pa_0_on
|
||||
pa1 = self.pa_1_on
|
||||
pa2 = self.pa_2_on
|
||||
current_output_power = self.output_power
|
||||
if pa0 and not pa1 and not pa2:
|
||||
# -18 to 13 dBm range
|
||||
return -18 + current_output_power
|
||||
if not pa0 and pa1 and not pa2:
|
||||
# -2 to 13 dBm range
|
||||
return -18 + current_output_power
|
||||
if not pa0 and pa1 and pa2 and self.high_power and self._tx_power < 18:
|
||||
# 2 to 17 dBm range
|
||||
return -14 + current_output_power
|
||||
if not pa0 and pa1 and pa2 and self.high_power and self._tx_power >= 18:
|
||||
# 5 to 20 dBm range
|
||||
return -11 + current_output_power
|
||||
raise RuntimeError("Power amps state unknown!")
|
||||
|
||||
@tx_power.setter
|
||||
def tx_power(self, val: float):
|
||||
val = int(val)
|
||||
# Determine power amplifier and output power values depending on
|
||||
# high power state and requested power.
|
||||
pa_0_on = pa_1_on = pa_2_on = 0
|
||||
output_power = 0
|
||||
if self.high_power:
|
||||
# Handle high power mode.
|
||||
assert -2 <= val <= 20
|
||||
pa_1_on = 1
|
||||
if val <= 13:
|
||||
output_power = val + 18
|
||||
elif 13 < val <= 17:
|
||||
pa_2_on = 1
|
||||
output_power = val + 14
|
||||
else: # power >= 18 dBm
|
||||
# Note this also needs PA boost enabled separately!
|
||||
pa_2_on = 1
|
||||
output_power = val + 11
|
||||
else:
|
||||
# Handle non-high power mode.
|
||||
assert -18 <= val <= 13
|
||||
# Enable only power amplifier 0 and set output power.
|
||||
pa_0_on = 1
|
||||
output_power = val + 18
|
||||
# Set power amplifiers and output power as computed above.
|
||||
self.pa_0_on = pa_0_on
|
||||
self.pa_1_on = pa_1_on
|
||||
self.pa_2_on = pa_2_on
|
||||
self.output_power = output_power
|
||||
self._tx_power = val
|
||||
|
||||
@property
|
||||
def rssi(self) -> float:
|
||||
"""The received strength indicator (in dBm).
|
||||
May be inaccuate if not read immediatey. last_rssi contains the value read immediately
|
||||
receipt of the last packet.
|
||||
"""
|
||||
# Read RSSI register and convert to value using formula in datasheet.
|
||||
return -self.read_u8(_RF69_REG_24_RSSI_VALUE) / 2.0
|
||||
|
||||
@property
|
||||
def bitrate(self) -> float:
|
||||
"""The modulation bitrate in bits/second (or chip rate if Manchester encoding is enabled).
|
||||
Can be a value from ~489 to 32mbit/s, but see the datasheet for the exact supported
|
||||
values.
|
||||
"""
|
||||
msb = self.read_u8(_RF69_REG_03_BITRATE_MSB)
|
||||
lsb = self.read_u8(_RF69_REG_04_BITRATE_LSB)
|
||||
return _FXOSC / ((msb << 8) | lsb)
|
||||
|
||||
@bitrate.setter
|
||||
def bitrate(self, val: float) -> None:
|
||||
assert (_FXOSC / 65535) <= val <= 32000000.0
|
||||
# Round up to the next closest bit-rate value with addition of 0.5.
|
||||
bitrate = int((_FXOSC / val) + 0.5) & 0xFFFF
|
||||
self.write_u8(_RF69_REG_03_BITRATE_MSB, bitrate >> 8)
|
||||
self.write_u8(_RF69_REG_04_BITRATE_LSB, bitrate & 0xFF)
|
||||
|
||||
@property
|
||||
def frequency_deviation(self) -> float:
|
||||
"""The frequency deviation in Hertz."""
|
||||
msb = self.read_u8(_RF69_REG_05_FDEV_MSB)
|
||||
lsb = self.read_u8(_RF69_REG_06_FDEV_LSB)
|
||||
return _FSTEP * ((msb << 8) | lsb)
|
||||
|
||||
@frequency_deviation.setter
|
||||
def frequency_deviation(self, val: float) -> None:
|
||||
assert 0 <= val <= (_FSTEP * 16383) # fdev is a 14-bit unsigned value
|
||||
# Round up to the next closest integer value with addition of 0.5.
|
||||
fdev = int((val / _FSTEP) + 0.5) & 0x3FFF
|
||||
self.write_u8(_RF69_REG_05_FDEV_MSB, fdev >> 8)
|
||||
self.write_u8(_RF69_REG_06_FDEV_LSB, fdev & 0xFF)
|
||||
|
||||
@property
|
||||
def enable_crc(self) -> bool:
|
||||
"""Set to True to enable hardware CRC checking of incoming packets.
|
||||
Incoming packets that fail the CRC check are not processed. Set to
|
||||
False to disable CRC checking and process all incoming packets."""
|
||||
return self.crc_on
|
||||
|
||||
@enable_crc.setter
|
||||
def enable_crc(self, val: bool) -> None:
|
||||
# Optionally enable CRC checking on incoming packets.
|
||||
if val:
|
||||
self.crc_on = 1
|
||||
else:
|
||||
self.crc_on = 0
|
||||
|
||||
@property
|
||||
def crc_error(self) -> bool:
|
||||
"""crc status"""
|
||||
return (self.read_u8(_RF69_REG_28_IRQ_FLAGS2) & 0x2) >> 1
|
||||
|
||||
@property
|
||||
def enable_address_filter(self) -> bool:
|
||||
"""Set to True to enable address filtering.
|
||||
Incoming packets that do no match the node address or broadcast address
|
||||
will be ignored."""
|
||||
return self.address_filter
|
||||
|
||||
@enable_address_filter.setter
|
||||
def enable_address_filter(self, val: bool) -> None:
|
||||
# Enable address filtering on incoming packets.
|
||||
if val:
|
||||
self.address_filter = 2 # accept node address or broadcast address
|
||||
else:
|
||||
self.address_filter = 0
|
||||
|
||||
@property
|
||||
def fsk_node_address(self) -> int:
|
||||
"""Node Address for Address Filtering"""
|
||||
return self.read_u8(_RF69_REG_39_NODE_ADDR)
|
||||
|
||||
@fsk_node_address.setter
|
||||
def fsk_node_address(self, val: int) -> None:
|
||||
assert 0 <= val <= 255
|
||||
self.write_u8(_RF69_REG_39_NODE_ADDR, val)
|
||||
|
||||
@property
|
||||
def fsk_broadcast_address(self) -> int:
|
||||
"""Node Address for Address Filtering"""
|
||||
return self.read_u8(_RF69_REG_3A_BROADCAST_ADDR)
|
||||
|
||||
@fsk_broadcast_address.setter
|
||||
def fsk_broadcast_address(self, val: int) -> None:
|
||||
assert 0 <= val <= 255
|
||||
self.write_u8(_RF69_REG_3A_BROADCAST_ADDR, val)
|
||||
|
||||
@property
|
||||
def ook_fixed_threshold(self) -> int:
|
||||
"""Fixed threshold for data slicer in OOK mode"""
|
||||
return self.read_u8(_RF69_REG_1D_OOK_FIX)
|
||||
|
||||
@ook_fixed_threshold.setter
|
||||
def ook_fixed_threshold(self, val: int) -> None:
|
||||
assert 0 <= val <= 255
|
||||
self.write_u8(_RF69_REG_1D_OOK_FIX, val)
|
||||
|
||||
def packet_sent(self) -> bool:
|
||||
"""Transmit status"""
|
||||
return (self.read_u8(_RF69_REG_28_IRQ_FLAGS2) & 0x8) >> 3
|
||||
|
||||
def payload_ready(self) -> bool:
|
||||
"""Receive status"""
|
||||
return (self.read_u8(_RF69_REG_28_IRQ_FLAGS2) & 0x4) >> 2
|
||||
|
||||
def clear_interrupt(self) -> None:
|
||||
"""Clear interrupt flags"""
|
||||
self.write_u8(_RF69_REG_27_IRQ_FLAGS1, 0xFF)
|
||||
self.write_u8(_RF69_REG_28_IRQ_FLAGS2, 0xFF)
|
||||
|
||||
def fill_fifo(self, payload: ReadableBuffer) -> None:
|
||||
"""Write the payload to the FIFO."""
|
||||
complete_payload = bytearray(1) # prepend packet length to payload
|
||||
complete_payload[0] = len(payload)
|
||||
# put the payload lengthe in the beginning of the packet for RFM69
|
||||
complete_payload = complete_payload + payload
|
||||
# Write payload to transmit fifo
|
||||
self.write_from(_RF69_REG_00_FIFO, complete_payload)
|
||||
|
||||
def read_fifo(self) -> bytearray:
|
||||
"""Read the packet from the FIFO."""
|
||||
# Read the length of the FIFO.
|
||||
fifo_length = self.read_u8(_RF69_REG_00_FIFO)
|
||||
if fifo_length > 0: # read and clear the FIFO if anything in it
|
||||
packet = bytearray(fifo_length)
|
||||
# read the packet
|
||||
self.read_into(_RF69_REG_00_FIFO, packet, fifo_length)
|
||||
return packet
|
||||
533
adafruit_rfm/rfm9x.py
Normal file
533
adafruit_rfm/rfm9x.py
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
# SPDX-FileCopyrightText: 2024 Jerry Needell for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
`adafruit_rfm.rfm9x`
|
||||
====================================================
|
||||
|
||||
CircuitPython module for the RFM95/6/7/8 LoRa 433/915mhz radio modules.
|
||||
|
||||
* Author(s): Jerry Needell
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from micropython import const
|
||||
|
||||
from adafruit_rfm.rfm_common import RFMSPI
|
||||
|
||||
try:
|
||||
import busio
|
||||
import digitalio
|
||||
from circuitpython_typing import ReadableBuffer
|
||||
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__version__ = "0.0.0+auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM.git"
|
||||
|
||||
# pylint: disable=duplicate-code
|
||||
|
||||
# Internal constants:
|
||||
# Register names (FSK Mode even though we use LoRa instead, from table 85)
|
||||
_RF95_REG_00_FIFO = const(0x00)
|
||||
_RF95_REG_01_OP_MODE = const(0x01)
|
||||
_RF95_REG_06_FRF_MSB = const(0x06)
|
||||
_RF95_REG_07_FRF_MID = const(0x07)
|
||||
_RF95_REG_08_FRF_LSB = const(0x08)
|
||||
_RF95_REG_09_PA_CONFIG = const(0x09)
|
||||
_RF95_REG_0A_PA_RAMP = const(0x0A)
|
||||
_RF95_REG_0B_OCP = const(0x0B)
|
||||
_RF95_REG_0C_LNA = const(0x0C)
|
||||
_RF95_REG_0D_FIFO_ADDR_PTR = const(0x0D)
|
||||
_RF95_REG_0E_FIFO_TX_BASE_ADDR = const(0x0E)
|
||||
_RF95_REG_0F_FIFO_RX_BASE_ADDR = const(0x0F)
|
||||
_RF95_REG_10_FIFO_RX_CURRENT_ADDR = const(0x10)
|
||||
_RF95_REG_11_IRQ_FLAGS_MASK = const(0x11)
|
||||
_RF95_REG_12_IRQ_FLAGS = const(0x12)
|
||||
_RF95_REG_13_RX_NB_BYTES = const(0x13)
|
||||
_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB = const(0x14)
|
||||
_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB = const(0x15)
|
||||
_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB = const(0x16)
|
||||
_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB = const(0x17)
|
||||
_RF95_REG_18_MODEM_STAT = const(0x18)
|
||||
_RF95_REG_19_PKT_SNR_VALUE = const(0x19)
|
||||
_RF95_REG_1A_PKT_RSSI_VALUE = const(0x1A)
|
||||
_RF95_REG_1B_RSSI_VALUE = const(0x1B)
|
||||
_RF95_REG_1C_HOP_CHANNEL = const(0x1C)
|
||||
_RF95_REG_1D_MODEM_CONFIG1 = const(0x1D)
|
||||
_RF95_REG_1E_MODEM_CONFIG2 = const(0x1E)
|
||||
_RF95_REG_1F_SYMB_TIMEOUT_LSB = const(0x1F)
|
||||
_RF95_REG_20_PREAMBLE_MSB = const(0x20)
|
||||
_RF95_REG_21_PREAMBLE_LSB = const(0x21)
|
||||
_RF95_REG_22_PAYLOAD_LENGTH = const(0x22)
|
||||
_RF95_REG_23_MAX_PAYLOAD_LENGTH = const(0x23)
|
||||
_RF95_REG_24_HOP_PERIOD = const(0x24)
|
||||
_RF95_REG_25_FIFO_RX_BYTE_ADDR = const(0x25)
|
||||
_RF95_REG_26_MODEM_CONFIG3 = const(0x26)
|
||||
|
||||
_RF95_REG_40_DIO_MAPPING1 = const(0x40)
|
||||
_RF95_REG_41_DIO_MAPPING2 = const(0x41)
|
||||
_RF95_REG_42_VERSION = const(0x42)
|
||||
|
||||
_RF95_REG_4B_TCXO = const(0x4B)
|
||||
_RF95_REG_4D_PA_DAC = const(0x4D)
|
||||
_RF95_REG_5B_FORMER_TEMP = const(0x5B)
|
||||
_RF95_REG_61_AGC_REF = const(0x61)
|
||||
_RF95_REG_62_AGC_THRESH1 = const(0x62)
|
||||
_RF95_REG_63_AGC_THRESH2 = const(0x63)
|
||||
_RF95_REG_64_AGC_THRESH3 = const(0x64)
|
||||
|
||||
_RF95_DETECTION_OPTIMIZE = const(0x31)
|
||||
_RF95_DETECTION_THRESHOLD = const(0x37)
|
||||
|
||||
_RF95_PA_DAC_DISABLE = const(0x04)
|
||||
_RF95_PA_DAC_ENABLE = const(0x07)
|
||||
|
||||
# The crystal oscillator frequency of the module
|
||||
_RF95_FXOSC = 32000000.0
|
||||
|
||||
# The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19
|
||||
_RF95_FSTEP = _RF95_FXOSC / 524288
|
||||
|
||||
# RadioHead specific compatibility constants.
|
||||
_RH_BROADCAST_ADDRESS = const(0xFF)
|
||||
|
||||
# The acknowledgement bit in the FLAGS
|
||||
# The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved
|
||||
# for application layer use.
|
||||
_RH_FLAGS_ACK = const(0x80)
|
||||
_RH_FLAGS_RETRY = const(0x40)
|
||||
|
||||
# User facing constants:
|
||||
SLEEP_MODE = 0b000
|
||||
STANDBY_MODE = 0b001
|
||||
FS_TX_MODE = 0b010
|
||||
TX_MODE = 0b011
|
||||
FS_RX_MODE = 0b100
|
||||
RX_MODE = 0b101
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class RFM9x(RFMSPI):
|
||||
"""Interface to a RFM95/6/7/8 LoRa radio module. Allows sending and
|
||||
receiving bytes of data in long range LoRa mode at a support board frequency
|
||||
(433/915mhz).
|
||||
|
||||
You must specify the following parameters:
|
||||
- spi: The SPI bus connected to the radio.
|
||||
- cs: The CS pin DigitalInOut connected to the radio.
|
||||
- reset: The reset/RST pin DigialInOut connected to the radio.
|
||||
- frequency: The frequency (in mhz) of the radio module (433/915mhz typically).
|
||||
|
||||
You can optionally specify:
|
||||
- preamble_length: The length in bytes of the packet preamble (default 8).
|
||||
- high_power: Boolean to indicate a high power board (RFM95, etc.). Default
|
||||
is True for high power.
|
||||
- baudrate: Baud rate of the SPI connection, default is 10mhz but you might
|
||||
choose to lower to 1mhz if using long wires or a breadboard.
|
||||
- agc: Boolean to Enable/Disable Automatic Gain Control - Default=False (AGC off)
|
||||
- crc: Boolean to Enable/Disable Cyclic Redundancy Check - Default=True (CRC Enabled)
|
||||
Remember this library makes a best effort at receiving packets with pure
|
||||
Python code. Trying to receive packets too quickly will result in lost data
|
||||
so limit yourself to simple scenarios of sending and receiving single
|
||||
packets at a time.
|
||||
|
||||
Also note this library tries to be compatible with raw RadioHead Arduino
|
||||
library communication. This means the library sets up the radio modulation
|
||||
to match RadioHead's defaults.
|
||||
Advanced RadioHead features like address/node specific packets
|
||||
or "reliable datagram" delivery are supported however due to the
|
||||
limitations noted, "reliable datagram" is still subject to missed packets.
|
||||
"""
|
||||
|
||||
operation_mode = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, bits=3)
|
||||
|
||||
low_frequency_mode = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, offset=3, bits=1)
|
||||
|
||||
modulation_type = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, offset=5, bits=2)
|
||||
|
||||
# Long range/LoRa mode can only be set in sleep mode!
|
||||
long_range_mode = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, offset=7, bits=1)
|
||||
|
||||
output_power = RFMSPI.RegisterBits(_RF95_REG_09_PA_CONFIG, bits=4)
|
||||
|
||||
max_power = RFMSPI.RegisterBits(_RF95_REG_09_PA_CONFIG, offset=4, bits=3)
|
||||
|
||||
pa_select = RFMSPI.RegisterBits(_RF95_REG_09_PA_CONFIG, offset=7, bits=1)
|
||||
|
||||
pa_dac = RFMSPI.RegisterBits(_RF95_REG_4D_PA_DAC, bits=3)
|
||||
|
||||
dio0_mapping = RFMSPI.RegisterBits(_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2)
|
||||
|
||||
auto_agc = RFMSPI.RegisterBits(_RF95_REG_26_MODEM_CONFIG3, offset=2, bits=1)
|
||||
|
||||
low_datarate_optimize = RFMSPI.RegisterBits(_RF95_REG_26_MODEM_CONFIG3, offset=3, bits=1)
|
||||
|
||||
lna_boost_hf = RFMSPI.RegisterBits(_RF95_REG_0C_LNA, offset=0, bits=2)
|
||||
|
||||
auto_ifon = RFMSPI.RegisterBits(_RF95_DETECTION_OPTIMIZE, offset=7, bits=1)
|
||||
|
||||
detection_optimize = RFMSPI.RegisterBits(_RF95_DETECTION_OPTIMIZE, offset=0, bits=3)
|
||||
|
||||
bw_bins = (7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000)
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
spi: busio.SPI,
|
||||
cs: digitalio.DigitalInOut, # pylint: disable=invalid-name
|
||||
rst: digitalio.DigitalInOut,
|
||||
frequency: int,
|
||||
*,
|
||||
preamble_length: int = 8,
|
||||
high_power: bool = True,
|
||||
baudrate: int = 5000000,
|
||||
agc: bool = False,
|
||||
crc: bool = True,
|
||||
) -> None:
|
||||
super().__init__(spi, cs, baudrate=baudrate)
|
||||
self.module = "RFM9X"
|
||||
self.max_packet_length = 252
|
||||
self.high_power = high_power
|
||||
# Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz.
|
||||
# Set Default Baudrate to 5MHz to avoid problems
|
||||
# self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0)
|
||||
# Setup reset as a digital output - initially High
|
||||
# This line is pulled low as an output quickly to trigger a reset.
|
||||
self._rst = rst
|
||||
# initialize Reset High
|
||||
self._rst.switch_to_output(value=True)
|
||||
self.reset()
|
||||
# No device type check! Catch an error from the very first request and
|
||||
# throw a nicer message to indicate possible wiring problems.
|
||||
version = self.read_u8(address=_RF95_REG_42_VERSION)
|
||||
if version != 18:
|
||||
raise RuntimeError(
|
||||
"Failed to find rfm9x with expected version -- check wiring. Version found:",
|
||||
hex(version),
|
||||
)
|
||||
|
||||
# Set sleep mode, wait 10s and confirm in sleep mode (basic device check).
|
||||
# Also set long range mode (LoRa mode) as it can only be done in sleep.
|
||||
self.sleep()
|
||||
time.sleep(0.01)
|
||||
self.long_range_mode = True
|
||||
if self.operation_mode != SLEEP_MODE or not self.long_range_mode:
|
||||
raise RuntimeError("Failed to configure radio for LoRa mode, check wiring!")
|
||||
# clear default setting for access to LF registers if frequency > 525MHz
|
||||
if frequency > 525:
|
||||
self.low_frequency_mode = 0
|
||||
# Setup entire 256 byte FIFO
|
||||
self.write_u8(_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0x00)
|
||||
self.write_u8(_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00)
|
||||
# Set mode idle
|
||||
self.idle()
|
||||
# Set frequency
|
||||
self.frequency_mhz = frequency
|
||||
# Set preamble length (default 8 bytes to match radiohead).
|
||||
self.preamble_length = preamble_length
|
||||
# Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode.
|
||||
self.signal_bandwidth = 125000
|
||||
self.coding_rate = 5
|
||||
self.spreading_factor = 7
|
||||
# Default to enable CRC checking on incoming packets.
|
||||
self.enable_crc = crc
|
||||
"""CRC Enable state"""
|
||||
# set AGC - Default = False
|
||||
self.auto_agc = agc
|
||||
"""Automatic Gain Control state"""
|
||||
# Set transmit power to 13 dBm, a safe value any module supports.
|
||||
self.tx_power = 13
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Perform a reset of the chip."""
|
||||
# See section 7.2.2 of the datasheet for reset description.
|
||||
self._rst.value = False # Set Reset Low
|
||||
time.sleep(0.0001) # 100 us
|
||||
self._rst.value = True # set Reset High
|
||||
time.sleep(0.005) # 5 ms
|
||||
|
||||
def idle(self) -> None:
|
||||
"""Enter idle standby mode."""
|
||||
self.operation_mode = STANDBY_MODE
|
||||
|
||||
def sleep(self) -> None:
|
||||
"""Enter sleep mode."""
|
||||
self.operation_mode = SLEEP_MODE
|
||||
|
||||
def listen(self) -> None:
|
||||
"""Listen for packets to be received by the chip. Use :py:func:`receive`
|
||||
to listen, wait and retrieve packets as they're available.
|
||||
"""
|
||||
self.operation_mode = RX_MODE
|
||||
self.dio0_mapping = 0b00 # Interrupt on rx done.
|
||||
|
||||
def transmit(self) -> None:
|
||||
"""Transmit a packet which is queued in the FIFO. This is a low level
|
||||
function for entering transmit mode and more. For generating and
|
||||
transmitting a packet of data use :py:func:`send` instead.
|
||||
"""
|
||||
self.operation_mode = TX_MODE
|
||||
self.dio0_mapping = 0b01 # Interrupt on tx done.
|
||||
|
||||
@property
|
||||
def preamble_length(self) -> int:
|
||||
"""The length of the preamble for sent and received packets, an unsigned
|
||||
16-bit value. Received packets must match this length or they are
|
||||
ignored! Set to 8 to match the RadioHead RFM95 library.
|
||||
"""
|
||||
msb = self.read_u8(_RF95_REG_20_PREAMBLE_MSB)
|
||||
lsb = self.read_u8(_RF95_REG_21_PREAMBLE_LSB)
|
||||
return ((msb << 8) | lsb) & 0xFFFF
|
||||
|
||||
@preamble_length.setter
|
||||
def preamble_length(self, val: int) -> None:
|
||||
assert 0 <= val <= 65535
|
||||
self.write_u8(_RF95_REG_20_PREAMBLE_MSB, (val >> 8) & 0xFF)
|
||||
self.write_u8(_RF95_REG_21_PREAMBLE_LSB, val & 0xFF)
|
||||
|
||||
@property
|
||||
def frequency_mhz(self) -> Literal[433.0, 915.0]:
|
||||
"""The frequency of the radio in Megahertz. Only the allowed values for
|
||||
your radio must be specified (i.e. 433 vs. 915 mhz)!
|
||||
"""
|
||||
msb = self.read_u8(_RF95_REG_06_FRF_MSB)
|
||||
mid = self.read_u8(_RF95_REG_07_FRF_MID)
|
||||
lsb = self.read_u8(_RF95_REG_08_FRF_LSB)
|
||||
frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF
|
||||
frequency = (frf * _RF95_FSTEP) / 1000000.0
|
||||
return frequency
|
||||
|
||||
@frequency_mhz.setter
|
||||
def frequency_mhz(self, val: Literal[433.0, 915.0]) -> None:
|
||||
if val < 240 or val > 960:
|
||||
raise RuntimeError("frequency_mhz must be between 240 and 960")
|
||||
# Calculate FRF register 24-bit value.
|
||||
frf = int((val * 1000000.0) / _RF95_FSTEP) & 0xFFFFFF
|
||||
# Extract byte values and update registers.
|
||||
msb = frf >> 16
|
||||
mid = (frf >> 8) & 0xFF
|
||||
lsb = frf & 0xFF
|
||||
self.write_u8(_RF95_REG_06_FRF_MSB, msb)
|
||||
self.write_u8(_RF95_REG_07_FRF_MID, mid)
|
||||
self.write_u8(_RF95_REG_08_FRF_LSB, lsb)
|
||||
|
||||
@property
|
||||
def tx_power(self) -> int:
|
||||
"""The transmit power in dBm. Can be set to a value from 5 to 23 for
|
||||
high power devices (RFM95/96/97/98, high_power=True) or -1 to 14 for low
|
||||
power devices. Only integer power levels are actually set (i.e. 12.5
|
||||
will result in a value of 12 dBm).
|
||||
The actual maximum setting for high_power=True is 20dBm but for values > 20
|
||||
the PA_BOOST will be enabled resulting in an additional gain of 3dBm.
|
||||
The actual setting is reduced by 3dBm.
|
||||
The reported value will reflect the reduced setting.
|
||||
"""
|
||||
if self.high_power:
|
||||
return self.output_power + 5
|
||||
return self.output_power - 1
|
||||
|
||||
@tx_power.setter
|
||||
def tx_power(self, val: int) -> None:
|
||||
val = int(val)
|
||||
if self.high_power:
|
||||
if val < 5 or val > 23:
|
||||
raise RuntimeError("tx_power must be between 5 and 23")
|
||||
# Enable power amp DAC if power is above 20 dB.
|
||||
# Lower setting by 3db when PA_BOOST enabled - see Data Sheet Section 6.4
|
||||
if val > 20:
|
||||
self.pa_dac = _RF95_PA_DAC_ENABLE
|
||||
val -= 3
|
||||
else:
|
||||
self.pa_dac = _RF95_PA_DAC_DISABLE
|
||||
self.pa_select = True
|
||||
self.output_power = (val - 5) & 0x0F
|
||||
else:
|
||||
assert -1 <= val <= 14
|
||||
self.pa_select = False
|
||||
self.max_power = 0b111 # Allow max power output.
|
||||
self.output_power = (val + 1) & 0x0F
|
||||
|
||||
@property
|
||||
def rssi(self) -> float:
|
||||
"""The received strength indicator (in dBm) of the last received message."""
|
||||
# Read RSSI register and convert to value using formula in datasheet.
|
||||
# Remember in LoRa mode the payload register changes function to RSSI!
|
||||
raw_rssi = self.read_u8(_RF95_REG_1A_PKT_RSSI_VALUE)
|
||||
if self.low_frequency_mode:
|
||||
raw_rssi -= 157
|
||||
else:
|
||||
raw_rssi -= 164
|
||||
return float(raw_rssi)
|
||||
|
||||
@property
|
||||
def snr(self) -> float:
|
||||
"""The SNR (in dB) of the last received message."""
|
||||
# Read SNR 0x19 register and convert to value using formula in datasheet.
|
||||
# SNR(dB) = PacketSnr [twos complement] / 4
|
||||
snr_byte = self.read_u8(_RF95_REG_19_PKT_SNR_VALUE)
|
||||
if snr_byte > 127:
|
||||
snr_byte = (256 - snr_byte) * -1
|
||||
return snr_byte / 4
|
||||
|
||||
@property
|
||||
def signal_bandwidth(self) -> int:
|
||||
"""The signal bandwidth used by the radio (try setting to a higher
|
||||
value to increase throughput or to a lower value to increase the
|
||||
likelihood of successfully received payloads). Valid values are
|
||||
listed in RFM9x.bw_bins."""
|
||||
bw_id = (self.read_u8(_RF95_REG_1D_MODEM_CONFIG1) & 0xF0) >> 4
|
||||
if bw_id >= len(self.bw_bins):
|
||||
current_bandwidth = 500000
|
||||
else:
|
||||
current_bandwidth = self.bw_bins[bw_id]
|
||||
return current_bandwidth
|
||||
|
||||
@signal_bandwidth.setter
|
||||
def signal_bandwidth(self, val: int) -> None:
|
||||
# Set signal bandwidth (set to 125000 to match RadioHead Bw125).
|
||||
for bw_id, cutoff in enumerate(self.bw_bins):
|
||||
if val <= cutoff:
|
||||
break
|
||||
else:
|
||||
bw_id = 9
|
||||
self.write_u8(
|
||||
_RF95_REG_1D_MODEM_CONFIG1,
|
||||
(self.read_u8(_RF95_REG_1D_MODEM_CONFIG1) & 0x0F) | (bw_id << 4),
|
||||
)
|
||||
if val >= 500000:
|
||||
# see Semtech SX1276 errata note 2.3
|
||||
self.auto_ifon = True
|
||||
# see Semtech SX1276 errata note 2.1
|
||||
if self.low_frequency_mode:
|
||||
self.write_u8(0x36, 0x02)
|
||||
self.write_u8(0x3A, 0x7F)
|
||||
else:
|
||||
self.write_u8(0x36, 0x02)
|
||||
self.write_u8(0x3A, 0x64)
|
||||
else:
|
||||
# see Semtech SX1276 errata note 2.3
|
||||
self.auto_ifon = False
|
||||
self.write_u8(0x36, 0x03)
|
||||
if val == 7800:
|
||||
self.write_u8(0x2F, 0x48)
|
||||
elif val >= 62500:
|
||||
# see Semtech SX1276 errata note 2.3
|
||||
self.write_u8(0x2F, 0x40)
|
||||
else:
|
||||
self.write_u8(0x2F, 0x44)
|
||||
self.write_u8(0x30, 0)
|
||||
|
||||
@property
|
||||
def coding_rate(self) -> Literal[5, 6, 7, 8]:
|
||||
"""The coding rate used by the radio to control forward error
|
||||
correction (try setting to a higher value to increase tolerance of
|
||||
short bursts of interference or to a lower value to increase bit
|
||||
rate). Valid values are limited to 5, 6, 7, or 8."""
|
||||
cr_id = (self.read_u8(_RF95_REG_1D_MODEM_CONFIG1) & 0x0E) >> 1
|
||||
denominator = cr_id + 4
|
||||
return denominator
|
||||
|
||||
@coding_rate.setter
|
||||
def coding_rate(self, val: Literal[5, 6, 7, 8]) -> None:
|
||||
# Set coding rate (set to 5 to match RadioHead Cr45).
|
||||
denominator = min(max(val, 5), 8)
|
||||
cr_id = denominator - 4
|
||||
self.write_u8(
|
||||
_RF95_REG_1D_MODEM_CONFIG1,
|
||||
(self.read_u8(_RF95_REG_1D_MODEM_CONFIG1) & 0xF1) | (cr_id << 1),
|
||||
)
|
||||
|
||||
@property
|
||||
def spreading_factor(self) -> Literal[6, 7, 8, 9, 10, 11, 12]:
|
||||
"""The spreading factor used by the radio (try setting to a higher
|
||||
value to increase the receiver's ability to distinguish signal from
|
||||
noise or to a lower value to increase the data transmission rate).
|
||||
Valid values are limited to 6, 7, 8, 9, 10, 11, or 12."""
|
||||
sf_id = (self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) & 0xF0) >> 4
|
||||
return sf_id
|
||||
|
||||
@spreading_factor.setter
|
||||
def spreading_factor(self, val: Literal[6, 7, 8, 9, 10, 11, 12]) -> None:
|
||||
# Set spreading factor (set to 7 to match RadioHead Sf128).
|
||||
val = min(max(val, 6), 12)
|
||||
|
||||
if val == 6:
|
||||
self.detection_optimize = 0x5
|
||||
else:
|
||||
self.detection_optimize = 0x3
|
||||
|
||||
self.write_u8(_RF95_DETECTION_THRESHOLD, 0x0C if val == 6 else 0x0A)
|
||||
self.write_u8(
|
||||
_RF95_REG_1E_MODEM_CONFIG2,
|
||||
((self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) & 0x0F) | ((val << 4) & 0xF0)),
|
||||
)
|
||||
|
||||
@property
|
||||
def enable_crc(self) -> bool:
|
||||
"""Set to True to enable hardware CRC checking of incoming packets.
|
||||
Incoming packets that fail the CRC check are not processed. Set to
|
||||
False to disable CRC checking and process all incoming packets."""
|
||||
return (self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) & 0x04) == 0x04
|
||||
|
||||
@enable_crc.setter
|
||||
def enable_crc(self, val: bool) -> None:
|
||||
# Optionally enable CRC checking on incoming packets.
|
||||
if val:
|
||||
self.write_u8(
|
||||
_RF95_REG_1E_MODEM_CONFIG2,
|
||||
self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) | 0x04,
|
||||
)
|
||||
else:
|
||||
self.write_u8(
|
||||
_RF95_REG_1E_MODEM_CONFIG2,
|
||||
self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) & 0xFB,
|
||||
)
|
||||
|
||||
@property
|
||||
def crc_error(self) -> bool:
|
||||
"""crc status"""
|
||||
return (self.read_u8(_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5
|
||||
|
||||
def packet_sent(self) -> bool:
|
||||
"""Transmit status"""
|
||||
return (self.read_u8(_RF95_REG_12_IRQ_FLAGS) & 0x8) >> 3
|
||||
|
||||
def payload_ready(self) -> bool:
|
||||
"""Receive status"""
|
||||
return (self.read_u8(_RF95_REG_12_IRQ_FLAGS) & 0x40) >> 6
|
||||
|
||||
def clear_interrupt(self) -> None:
|
||||
"""Clear Interrupt flags"""
|
||||
self.write_u8(_RF95_REG_12_IRQ_FLAGS, 0xFF)
|
||||
|
||||
def fill_fifo(self, payload: ReadableBuffer) -> None:
|
||||
"""len_data is not used but is here for compatibility with rfm69
|
||||
Fill the FIFO with a packet to send"""
|
||||
self.write_u8(_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0.
|
||||
# Write payload.
|
||||
self.write_from(_RF95_REG_00_FIFO, payload)
|
||||
# Write payload and header length.
|
||||
self.write_u8(_RF95_REG_22_PAYLOAD_LENGTH, len(payload))
|
||||
|
||||
def read_fifo(self) -> bytearray:
|
||||
"""Read the data from the FIFO."""
|
||||
# Read the length of the FIFO.
|
||||
fifo_length = self.read_u8(_RF95_REG_13_RX_NB_BYTES)
|
||||
if fifo_length > 0: # read and clear the FIFO if anything in it
|
||||
packet = bytearray(fifo_length)
|
||||
current_addr = self.read_u8(_RF95_REG_10_FIFO_RX_CURRENT_ADDR)
|
||||
self.write_u8(_RF95_REG_0D_FIFO_ADDR_PTR, current_addr)
|
||||
# read the packet
|
||||
self.read_into(_RF95_REG_00_FIFO, packet)
|
||||
|
||||
# clear interrupt
|
||||
self.write_u8(_RF95_REG_12_IRQ_FLAGS, 0xFF)
|
||||
return packet
|
||||
574
adafruit_rfm/rfm9xfsk.py
Normal file
574
adafruit_rfm/rfm9xfsk.py
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
# SPDX-FileCopyrightText: 2024 Jerry Needell for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
`adafruit_rfm.rfm9xfsk`
|
||||
====================================================
|
||||
|
||||
CircuitPython module for the RFM95/6/7/8 FSK 433/915mhz radio modules.
|
||||
|
||||
* Author(s): Jerry Needell
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from micropython import const
|
||||
|
||||
from adafruit_rfm.rfm_common import RFMSPI
|
||||
|
||||
try:
|
||||
from typing import Optional
|
||||
|
||||
import busio
|
||||
import digitalio
|
||||
from circuitpython_typing import ReadableBuffer
|
||||
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__version__ = "0.0.0+auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM.git"
|
||||
|
||||
# pylint: disable=duplicate-code
|
||||
|
||||
# Internal constants:
|
||||
# Register names (FSK Mode even though we use LoRa instead, from table 85)
|
||||
_RF95_REG_00_FIFO = const(0x00)
|
||||
_RF95_REG_01_OP_MODE = const(0x01)
|
||||
_RF95_REG_02_BITRATE_MSB = const(0x02)
|
||||
_RF95_REG_03_BITRATE_LSB = const(0x03)
|
||||
_RF95_REG_04_FDEV_MSB = const(0x4)
|
||||
_RF95_REG_05_FDEV_LSB = const(0x5)
|
||||
_RF95_REG_06_FRF_MSB = const(0x06)
|
||||
_RF95_REG_07_FRF_MID = const(0x07)
|
||||
_RF95_REG_08_FRF_LSB = const(0x08)
|
||||
_RF95_REG_09_PA_CONFIG = const(0x09)
|
||||
_RF95_REG_0A_PA_RAMP = const(0x0A)
|
||||
_RF95_REG_0B_OCP = const(0x0B)
|
||||
_RF95_REG_0C_LNA = const(0x0C)
|
||||
_RF95_REG_0D_RX_CFG = const(0x0D)
|
||||
_RF95_REG_0E_RSSI_CFG = const(0x0E)
|
||||
_RF95_REG_0F_RSSI_COLLISION = const(0x0F)
|
||||
_RF95_REG_10_RSSI_THRESH = const(0x10)
|
||||
_RF95_REG_11_RSSI_VALUE = const(0x11)
|
||||
_RF95_REG_12_RX_BW = const(0x12)
|
||||
_RF95_REG_13_AFC_BW = const(0x13)
|
||||
_RF95_REG_14_OOK_PEAK = const(0x14)
|
||||
_RF95_REG_15_OOK_FIX = const(0x15)
|
||||
_RF95_REG_16_OOK_AVG = const(0x16)
|
||||
_RF95_REG_1A_AFC_FEI_CTL = const(0x1A)
|
||||
_RF95_REG_1B_AFC_MSB = const(0x1B)
|
||||
_RF95_REG_1C_AFC_LSB = const(0x1C)
|
||||
_RF95_REG_1D_FEI_MSB = const(0x1D)
|
||||
_RF95_REG_1E_FEI_LSB = const(0x1E)
|
||||
_RF95_REG_1F_PREAMBLE_DETECT = const(0x1F)
|
||||
_RF95_REG_20_RX_TIMEOUT_1 = const(0x20)
|
||||
_RF95_REG_21_RX_TIMEOUT_2 = const(0x21)
|
||||
_RF95_REG_22_RX_TIMEOUT_3 = const(0x22)
|
||||
_RF95_REG_23_RX_DELAY = const(0x23)
|
||||
_RF95_REG_24_OSC = const(0x24)
|
||||
_RF95_REG_25_PREAMBLE_MSB = const(0x25)
|
||||
_RF95_REG_26_PREAMBLE_LSB = const(0x26)
|
||||
_RF95_REG_27_SYNC_CONFIG = const(0x27)
|
||||
_RF95_REG_28_SYNC_VALUE_1 = const(0x28)
|
||||
_RF95_REG_29_SYNC_VALUE_2 = const(0x29)
|
||||
_RF95_REG_2A_SYNC_VALUE_3 = const(0x2A)
|
||||
_RF95_REG_2B_SYNC_VALUE_4 = const(0x2B)
|
||||
_RF95_REG_2C_SYNC_VALUE_5 = const(0x2C)
|
||||
_RF95_REG_2D_SYNC_VALUE_6 = const(0x2D)
|
||||
_RF95_REG_2E_SYNC_VALUE_7 = const(0x2E)
|
||||
_RF95_REG_2F_SYNC_VALUE_8 = const(0x2F)
|
||||
_RF95_REG_30_PACKET_CONFIG_1 = const(0x30)
|
||||
_RF95_REG_31_PACKET_CONFIG_2 = const(0x31)
|
||||
_RF95_REG_32_PAYLOAD_LENGTH = const(0x32)
|
||||
_RF95_REG_33_NODE_ADDR = const(0x33)
|
||||
_RF95_REG_34_BROADCAST_ADDR = const(0x34)
|
||||
_RF95_REG_35_FIFO_THRESH = const(0x35)
|
||||
_RF95_REG_36_SEQ_CFG_1 = const(0x36)
|
||||
_RF95_REG_37_SEQ_CFG_2 = const(0x37)
|
||||
_RF95_REG_38_TIMER_RES = const(0x38)
|
||||
_RF95_REG_39_TIMER1_COEF = const(0x39)
|
||||
_RF95_REG_3A_TIMER2_COEF = const(0x3A)
|
||||
_RF95_REG_3B_IMAGE_CAL = const(0x3B)
|
||||
_RF95_REG_3C_TEMP = const(0x3C)
|
||||
_RF95_REG_3D_LOW_BAT = const(0x3D)
|
||||
_RF95_REG_3E_IRQ_FLAGS_1 = const(0x3E)
|
||||
_RF95_REG_3F_IRQ_FLAGS_2 = const(0x3F)
|
||||
|
||||
_RF95_REG_40_DIO_MAPPING1 = const(0x40)
|
||||
_RF95_REG_41_DIO_MAPPING2 = const(0x41)
|
||||
_RF95_REG_42_VERSION = const(0x42)
|
||||
|
||||
_RF95_REG_44_PIII_IOP = const(0x44)
|
||||
|
||||
_RF95_REG_4B_TCXO = const(0x4B)
|
||||
_RF95_REG_4D_PA_DAC = const(0x4D)
|
||||
_RF95_REG_5B_FORMER_TEMP = const(0x5B)
|
||||
_RF95_REG_5B_BIT_RATE_FRAC = const(0x5D)
|
||||
_RF95_REG_61_AGC_REF = const(0x61)
|
||||
_RF95_REG_62_AGC_THRESH1 = const(0x62)
|
||||
_RF95_REG_63_AGC_THRESH2 = const(0x63)
|
||||
_RF95_REG_64_AGC_THRESH3 = const(0x64)
|
||||
|
||||
|
||||
_RF95_PA_DAC_DISABLE = const(0x04)
|
||||
_RF95_PA_DAC_ENABLE = const(0x07)
|
||||
|
||||
# The crystal oscillator frequency of the module
|
||||
_RF95_FXOSC = 32000000.0
|
||||
|
||||
# The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19
|
||||
_RF95_FSTEP = _RF95_FXOSC / 524288
|
||||
|
||||
# RadioHead specific compatibility constants.
|
||||
_RH_BROADCAST_ADDRESS = const(0xFF)
|
||||
|
||||
# The acknowledgement bit in the FLAGS
|
||||
# The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved
|
||||
# for application layer use.
|
||||
_RH_FLAGS_ACK = const(0x80)
|
||||
_RH_FLAGS_RETRY = const(0x40)
|
||||
|
||||
# User facing constants:
|
||||
SLEEP_MODE = 0b000
|
||||
STANDBY_MODE = 0b001
|
||||
FS_TX_MODE = 0b010
|
||||
TX_MODE = 0b011
|
||||
FS_RX_MODE = 0b100
|
||||
RX_MODE = 0b101
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-public-methods
|
||||
class RFM9xFSK(RFMSPI):
|
||||
"""Interface to a RFM95/6/7/8 FSK radio module. Allows sending and
|
||||
receiving bytes of data in FSK mode at a support board frequency
|
||||
(433/915mhz).
|
||||
|
||||
:param busio.SPI spi: The SPI bus connected to the chip. Ensure SCK, MOSI, and MISO are
|
||||
connected.
|
||||
:param ~digitalio.DigitalInOut cs: A DigitalInOut object connected to the chip's CS/chip select
|
||||
line.
|
||||
:param ~digitalio.DigitalInOut reset: A DigitalInOut object connected to the chip's RST/reset
|
||||
line.
|
||||
:param int frequency: The center frequency to configure for radio transmission and reception.
|
||||
Must be a frequency supported by your hardware (i.e. either 433 or 915mhz).
|
||||
:param bytes sync_word: A byte string up to 8 bytes long which represents the syncronization
|
||||
word used by received and transmitted packets. Read the datasheet for a full understanding
|
||||
of this value! However by default the library will set a value that matches the RadioHead
|
||||
Arduino library.
|
||||
:param int preamble_length: The number of bytes to pre-pend to a data packet as a preamble.
|
||||
This is by default 4 to match the RadioHead library.
|
||||
:param bool high_power: Indicate if the chip is a high power variant that supports boosted
|
||||
transmission power. The default is True as it supports the common RFM69HCW modules sold by
|
||||
Adafruit.
|
||||
|
||||
Also note this library tries to be compatible with raw RadioHead Arduino
|
||||
library communication. This means the library sets up the radio modulation
|
||||
to match RadioHead's defaults.
|
||||
Advanced RadioHead features like address/node specific packets
|
||||
or "reliable datagram" delivery are supported however due to the
|
||||
limitations noted, "reliable datagram" is still subject to missed packets.
|
||||
"""
|
||||
|
||||
operation_mode = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, bits=3)
|
||||
low_frequency_mode = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, offset=3, bits=1)
|
||||
modulation_type = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, offset=5, bits=2)
|
||||
modulation_shaping = RFMSPI.RegisterBits(_RF95_REG_0A_PA_RAMP, offset=5, bits=2)
|
||||
# Long range/LoRa mode can only be set in sleep mode!
|
||||
long_range_mode = RFMSPI.RegisterBits(_RF95_REG_01_OP_MODE, offset=7, bits=1)
|
||||
sync_on = RFMSPI.RegisterBits(_RF95_REG_27_SYNC_CONFIG, offset=4, bits=1)
|
||||
sync_size = RFMSPI.RegisterBits(_RF95_REG_27_SYNC_CONFIG, offset=0, bits=3)
|
||||
output_power = RFMSPI.RegisterBits(_RF95_REG_09_PA_CONFIG, bits=4)
|
||||
max_power = RFMSPI.RegisterBits(_RF95_REG_09_PA_CONFIG, offset=4, bits=3)
|
||||
pa_select = RFMSPI.RegisterBits(_RF95_REG_09_PA_CONFIG, offset=7, bits=1)
|
||||
pa_dac = RFMSPI.RegisterBits(_RF95_REG_4D_PA_DAC, bits=3)
|
||||
dio0_mapping = RFMSPI.RegisterBits(_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2)
|
||||
lna_boost_hf = RFMSPI.RegisterBits(_RF95_REG_0C_LNA, offset=0, bits=2)
|
||||
rx_bw_mantissa = RFMSPI.RegisterBits(_RF95_REG_12_RX_BW, offset=3, bits=2)
|
||||
rx_bw_exponent = RFMSPI.RegisterBits(_RF95_REG_12_RX_BW, offset=0, bits=3)
|
||||
afc_bw_mantissa = RFMSPI.RegisterBits(_RF95_REG_13_AFC_BW, offset=3, bits=2)
|
||||
afc_bw_exponent = RFMSPI.RegisterBits(_RF95_REG_13_AFC_BW, offset=0, bits=3)
|
||||
packet_format = RFMSPI.RegisterBits(_RF95_REG_30_PACKET_CONFIG_1, offset=7, bits=1)
|
||||
dc_free = RFMSPI.RegisterBits(_RF95_REG_30_PACKET_CONFIG_1, offset=5, bits=2)
|
||||
crc_on = RFMSPI.RegisterBits(_RF95_REG_30_PACKET_CONFIG_1, offset=4, bits=1)
|
||||
crc_auto_clear_off = RFMSPI.RegisterBits(_RF95_REG_30_PACKET_CONFIG_1, offset=3, bits=1)
|
||||
address_filter = RFMSPI.RegisterBits(_RF95_REG_30_PACKET_CONFIG_1, offset=1, bits=2)
|
||||
crc_type = RFMSPI.RegisterBits(_RF95_REG_30_PACKET_CONFIG_1, offset=0, bits=1)
|
||||
mode_ready = RFMSPI.RegisterBits(_RF95_REG_3E_IRQ_FLAGS_1, offset=7)
|
||||
ook_bit_sync_on = RFMSPI.RegisterBits(_RF95_REG_14_OOK_PEAK, offset=5, bits=1)
|
||||
ook_thresh_type = RFMSPI.RegisterBits(_RF95_REG_14_OOK_PEAK, offset=4, bits=2)
|
||||
ook_thresh_step = RFMSPI.RegisterBits(_RF95_REG_14_OOK_PEAK, offset=0, bits=3)
|
||||
ook_peak_thresh_dec = RFMSPI.RegisterBits(_RF95_REG_16_OOK_AVG, offset=5, bits=3)
|
||||
ook_average_offset = RFMSPI.RegisterBits(_RF95_REG_16_OOK_AVG, offset=2, bits=2)
|
||||
ook_average_thresh_filt = RFMSPI.RegisterBits(_RF95_REG_16_OOK_AVG, offset=0, bits=2)
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
spi: busio.SPI,
|
||||
cs: digitalio.DigitalInOut, # pylint: disable=invalid-name
|
||||
rst: digitalio.DigitalInOut,
|
||||
frequency: int,
|
||||
*,
|
||||
sync_word: bytes = b"\x2d\xd4",
|
||||
preamble_length: int = 4,
|
||||
high_power: bool = True,
|
||||
baudrate: int = 5000000,
|
||||
crc: bool = True,
|
||||
) -> None:
|
||||
super().__init__(spi, cs, baudrate=baudrate)
|
||||
self.module = "RFM9X"
|
||||
self.max_packet_length = 252
|
||||
self.high_power = high_power
|
||||
# Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz.
|
||||
# Set Default Baudrate to 5MHz to avoid problems
|
||||
# self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0)
|
||||
# Setup reset as a digital output - initially High
|
||||
# This line is pulled low as an output quickly to trigger a reset.
|
||||
self._rst = rst
|
||||
# initialize Reset High
|
||||
self._rst.switch_to_output(value=True)
|
||||
self.reset()
|
||||
# No device type check! Catch an error from the very first request and
|
||||
# throw a nicer message to indicate possible wiring problems.
|
||||
version = self.read_u8(address=_RF95_REG_42_VERSION)
|
||||
if version != 18:
|
||||
raise RuntimeError(
|
||||
"Failed to find rfm9x with expected version -- check wiring. Version found:",
|
||||
hex(version),
|
||||
)
|
||||
|
||||
# Set sleep mode, wait 10s and confirm in sleep mode (basic device check).
|
||||
# Also set long range mode (LoRa mode) as it can only be done in sleep.
|
||||
self.sleep()
|
||||
time.sleep(0.01)
|
||||
self.long_range_mode = False
|
||||
if self.operation_mode != SLEEP_MODE or self.long_range_mode:
|
||||
raise RuntimeError("Failed to configure radio for FSK mode, check wiring!")
|
||||
# clear default setting for access to LF registers if frequency > 525MHz
|
||||
if frequency > 525:
|
||||
self.low_frequency_mode = 0
|
||||
# Set mode idle
|
||||
self.idle()
|
||||
# Setup the chip in a similar way to the RadioHead RFM69 library.
|
||||
# Set FIFO TX condition to not empty and the default FIFO threshold to 15.
|
||||
self.write_u8(_RF95_REG_35_FIFO_THRESH, 0b10001111)
|
||||
# Set the syncronization word.
|
||||
self.sync_word = sync_word
|
||||
self.preamble_length = preamble_length # Set the preamble length.
|
||||
self.frequency_mhz = frequency # Set frequency.
|
||||
# Configure modulation for RadioHead library GFSK_Rb250Fd250 mode
|
||||
# by default. Users with advanced knowledge can manually reconfigure
|
||||
# for any other mode (consulting the datasheet is absolutely
|
||||
# necessary!).
|
||||
self.modulation_shaping = 0b01 # Gaussian filter, BT=1.0
|
||||
self.bitrate = 250000 # 250kbs
|
||||
self.frequency_deviation = 250000 # 250khz
|
||||
self.rx_bw_mantissa = 0b00
|
||||
self.rx_bw_exponent = 0b000
|
||||
self.afc_bw_mantissa = 0b00
|
||||
self.afc_bw_exponent = 0b000
|
||||
self.packet_format = 1 # Variable length.
|
||||
self.dc_free = 0b10 # Whitening
|
||||
# Set transmit power to 13 dBm, a safe value any module supports.
|
||||
self._tx_power = 13
|
||||
self.tx_power = self._tx_power
|
||||
|
||||
# Default to enable CRC checking on incoming packets.
|
||||
self.enable_crc = crc
|
||||
"""CRC Enable state"""
|
||||
self.snr = None
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Perform a reset of the chip."""
|
||||
# See section 7.2.2 of the datasheet for reset description.
|
||||
self._rst.value = False # Set Reset Low
|
||||
time.sleep(0.0001) # 100 us
|
||||
self._rst.value = True # set Reset High
|
||||
time.sleep(0.005) # 5 ms
|
||||
|
||||
def idle(self) -> None:
|
||||
"""Enter idle standby mode."""
|
||||
self.operation_mode = STANDBY_MODE
|
||||
|
||||
def sleep(self) -> None:
|
||||
"""Enter sleep mode."""
|
||||
self.operation_mode = SLEEP_MODE
|
||||
|
||||
def listen(self) -> None:
|
||||
"""Listen for packets to be received by the chip. Use :py:func:`receive`
|
||||
to listen, wait and retrieve packets as they're available.
|
||||
"""
|
||||
self.operation_mode = RX_MODE
|
||||
self.dio0_mapping = 0b00 # Interrupt on rx done.
|
||||
|
||||
def transmit(self) -> None:
|
||||
"""Transmit a packet which is queued in the FIFO. This is a low level
|
||||
function for entering transmit mode and more. For generating and
|
||||
transmitting a packet of data use :py:func:`send` instead.
|
||||
"""
|
||||
self.operation_mode = TX_MODE
|
||||
self.dio0_mapping = 0b00 # Interrupt on tx done.
|
||||
|
||||
@property
|
||||
def sync_word(self) -> bytearray:
|
||||
"""The synchronization word value. This is a byte string up to 8 bytes long (64 bits)
|
||||
which indicates the synchronization word for transmitted and received packets. Any
|
||||
received packet which does not include this sync word will be ignored. The default value
|
||||
is 0x2D, 0xD4 which matches the RadioHead RFM69 library. Setting a value of None will
|
||||
disable synchronization word matching entirely.
|
||||
"""
|
||||
# Handle when sync word is disabled..
|
||||
if not self.sync_on:
|
||||
return None
|
||||
# Sync word is not disabled so read the current value.
|
||||
sync_word_length = self.sync_size + 1 # Sync word size is offset by 1
|
||||
# according to datasheet.
|
||||
sync_word = bytearray(sync_word_length)
|
||||
self.read_into(_RF95_REG_28_SYNC_VALUE_1, sync_word)
|
||||
return sync_word
|
||||
|
||||
@sync_word.setter
|
||||
def sync_word(self, val: Optional[bytearray]) -> None:
|
||||
# Handle disabling sync word when None value is set.
|
||||
if val is None:
|
||||
self.sync_on = 0
|
||||
else:
|
||||
# Check sync word is at most 8 bytes.
|
||||
assert 1 <= len(val) <= 8
|
||||
# Update the value, size and turn on the sync word.
|
||||
self.write_from(_RF95_REG_28_SYNC_VALUE_1, val)
|
||||
self.sync_size = len(val) - 1 # Again sync word size is offset by
|
||||
# 1 according to datasheet.
|
||||
self.sync_on = 1
|
||||
|
||||
@property
|
||||
def bitrate(self) -> float:
|
||||
"""The modulation bitrate in bits/second (or chip rate if Manchester encoding is enabled).
|
||||
Can be a value from ~489 to 32mbit/s, but see the datasheet for the exact supported
|
||||
values.
|
||||
"""
|
||||
msb = self.read_u8(_RF95_REG_02_BITRATE_MSB)
|
||||
lsb = self.read_u8(_RF95_REG_03_BITRATE_LSB)
|
||||
return _RF95_FXOSC / ((msb << 8) | lsb)
|
||||
|
||||
@bitrate.setter
|
||||
def bitrate(self, val: float) -> None:
|
||||
assert (_RF95_FXOSC / 65535) <= val <= 32000000.0
|
||||
# Round up to the next closest bit-rate value with addition of 0.5.
|
||||
bitrate = int((_RF95_FXOSC / val) + 0.5) & 0xFFFF
|
||||
self.write_u8(_RF95_REG_02_BITRATE_MSB, bitrate >> 8)
|
||||
self.write_u8(_RF95_REG_03_BITRATE_LSB, bitrate & 0xFF)
|
||||
|
||||
@property
|
||||
def frequency_deviation(self) -> float:
|
||||
"""The frequency deviation in Hertz."""
|
||||
msb = self.read_u8(_RF95_REG_04_FDEV_MSB)
|
||||
lsb = self.read_u8(_RF95_REG_05_FDEV_LSB)
|
||||
return _RF95_FSTEP * ((msb << 8) | lsb)
|
||||
|
||||
@frequency_deviation.setter
|
||||
def frequency_deviation(self, val: float) -> None:
|
||||
assert 0 <= val <= (_RF95_FSTEP * 16383) # fdev is a 14-bit unsigned value
|
||||
# Round up to the next closest integer value with addition of 0.5.
|
||||
fdev = int((val / _RF95_FSTEP) + 0.5) & 0x3FFF
|
||||
self.write_u8(_RF95_REG_04_FDEV_MSB, fdev >> 8)
|
||||
self.write_u8(_RF95_REG_05_FDEV_LSB, fdev & 0xFF)
|
||||
|
||||
@property
|
||||
def temperature(self) -> float:
|
||||
"""The internal temperature of the chip.. See Sec 5.5.7 of the DataSheet
|
||||
calibrated or very accurate.
|
||||
"""
|
||||
temp = self.read_u8(_RF95_REG_3C_TEMP)
|
||||
return temp
|
||||
|
||||
@property
|
||||
def preamble_length(self) -> int:
|
||||
"""The length of the preamble for sent and received packets, an unsigned
|
||||
16-bit value. Received packets must match this length or they are
|
||||
ignored! Set to 4 to match the RF69.
|
||||
"""
|
||||
msb = self.read_u8(_RF95_REG_25_PREAMBLE_MSB)
|
||||
lsb = self.read_u8(_RF95_REG_26_PREAMBLE_LSB)
|
||||
return ((msb << 8) | lsb) & 0xFFFF
|
||||
|
||||
@preamble_length.setter
|
||||
def preamble_length(self, val: int) -> None:
|
||||
assert 0 <= val <= 65535
|
||||
self.write_u8(_RF95_REG_25_PREAMBLE_MSB, (val >> 8) & 0xFF)
|
||||
self.write_u8(_RF95_REG_26_PREAMBLE_LSB, val & 0xFF)
|
||||
|
||||
@property
|
||||
def frequency_mhz(self) -> Literal[433.0, 915.0]:
|
||||
"""The frequency of the radio in Megahertz. Only the allowed values for
|
||||
your radio must be specified (i.e. 433 vs. 915 mhz)!
|
||||
"""
|
||||
msb = self.read_u8(_RF95_REG_06_FRF_MSB)
|
||||
mid = self.read_u8(_RF95_REG_07_FRF_MID)
|
||||
lsb = self.read_u8(_RF95_REG_08_FRF_LSB)
|
||||
frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF
|
||||
frequency = (frf * _RF95_FSTEP) / 1000000.0
|
||||
return frequency
|
||||
|
||||
@frequency_mhz.setter
|
||||
def frequency_mhz(self, val: Literal[433.0, 915.0]) -> None:
|
||||
if val < 240 or val > 960:
|
||||
raise RuntimeError("frequency_mhz must be between 240 and 960")
|
||||
# Calculate FRF register 24-bit value.
|
||||
frf = int((val * 1000000.0) / _RF95_FSTEP) & 0xFFFFFF
|
||||
# Extract byte values and update registers.
|
||||
msb = frf >> 16
|
||||
mid = (frf >> 8) & 0xFF
|
||||
lsb = frf & 0xFF
|
||||
self.write_u8(_RF95_REG_06_FRF_MSB, msb)
|
||||
self.write_u8(_RF95_REG_07_FRF_MID, mid)
|
||||
self.write_u8(_RF95_REG_08_FRF_LSB, lsb)
|
||||
|
||||
@property
|
||||
def tx_power(self) -> int:
|
||||
"""The transmit power in dBm. Can be set to a value from 5 to 23 for
|
||||
high power devices (RFM95/96/97/98, high_power=True) or -1 to 14 for low
|
||||
power devices. Only integer power levels are actually set (i.e. 12.5
|
||||
will result in a value of 12 dBm).
|
||||
The actual maximum setting for high_power=True is 20dBm but for values > 20
|
||||
the PA_BOOST will be enabled resulting in an additional gain of 3dBm.
|
||||
The actual setting is reduced by 3dBm.
|
||||
The reported value will reflect the reduced setting.
|
||||
"""
|
||||
if self.high_power:
|
||||
return self.output_power + 5
|
||||
return self.output_power - 1
|
||||
|
||||
@tx_power.setter
|
||||
def tx_power(self, val: int) -> None:
|
||||
val = int(val)
|
||||
if self.high_power:
|
||||
if val < 5 or val > 23:
|
||||
raise RuntimeError("tx_power must be between 5 and 23")
|
||||
# Enable power amp DAC if power is above 20 dB.
|
||||
# Lower setting by 3db when PA_BOOST enabled - see Data Sheet Section 6.4
|
||||
if val > 20:
|
||||
self.pa_dac = _RF95_PA_DAC_ENABLE
|
||||
val -= 3
|
||||
else:
|
||||
self.pa_dac = _RF95_PA_DAC_DISABLE
|
||||
self.pa_select = True
|
||||
self.output_power = (val - 5) & 0x0F
|
||||
else:
|
||||
assert -1 <= val <= 14
|
||||
self.pa_select = False
|
||||
self.max_power = 0b111 # Allow max power output.
|
||||
self.output_power = (val + 1) & 0x0F
|
||||
|
||||
@property
|
||||
def rssi(self) -> float:
|
||||
"""The received strength indicator (in dBm) of the last received message."""
|
||||
# Read RSSI register and convert to value using formula in datasheet.
|
||||
# Remember in LoRa mode the payload register changes function to RSSI!
|
||||
raw_rssi = self.read_u8(_RF95_REG_11_RSSI_VALUE)
|
||||
return -raw_rssi / 2.0
|
||||
|
||||
@property
|
||||
def enable_crc(self) -> bool:
|
||||
"""Set to True to enable hardware CRC checking of incoming packets.
|
||||
Incoming packets that fail the CRC check are not processed. Set to
|
||||
False to disable CRC checking and process all incoming packets."""
|
||||
return self.crc_on
|
||||
|
||||
@enable_crc.setter
|
||||
def enable_crc(self, val: bool) -> None:
|
||||
# Optionally enable CRC checking on incoming packets.
|
||||
if val:
|
||||
self.crc_on = 1
|
||||
self.crc_type = 0 # use CCITT for RF69 compatibility
|
||||
else:
|
||||
self.crc_on = 0
|
||||
|
||||
@property
|
||||
def crc_error(self) -> bool:
|
||||
"""crc status"""
|
||||
return (self.read_u8(_RF95_REG_3F_IRQ_FLAGS_2) & 0x2) >> 1
|
||||
|
||||
@property
|
||||
def enable_address_filter(self) -> bool:
|
||||
"""Set to True to enable address filtering.
|
||||
Incoming packets that do no match the node address or broadcast address
|
||||
will be ignored."""
|
||||
return self.address_filter
|
||||
|
||||
@enable_address_filter.setter
|
||||
def enable_address_filter(self, val: bool) -> None:
|
||||
# Enable address filtering on incoming packets.
|
||||
if val:
|
||||
self.address_filter = 2 # accept node address or broadcast address
|
||||
else:
|
||||
self.address_filter = 0
|
||||
|
||||
@property
|
||||
def fsk_node_address(self) -> int:
|
||||
"""Node Address for Address Filtering"""
|
||||
return self.read_u8(_RF95_REG_33_NODE_ADDR)
|
||||
|
||||
@fsk_node_address.setter
|
||||
def fsk_node_address(self, val: int) -> None:
|
||||
assert 0 <= val <= 255
|
||||
self.write_u8(_RF95_REG_33_NODE_ADDR, val)
|
||||
|
||||
@property
|
||||
def fsk_broadcast_address(self) -> int:
|
||||
"""Node Address for Address Filtering"""
|
||||
return self.read_u8(_RF95_REG_34_BROADCAST_ADDR)
|
||||
|
||||
@fsk_broadcast_address.setter
|
||||
def fsk_broadcast_address(self, val: int) -> None:
|
||||
assert 0 <= val <= 255
|
||||
self.write_u8(_RF95_REG_34_BROADCAST_ADDR, val)
|
||||
|
||||
@property
|
||||
def ook_fixed_threshold(self) -> int:
|
||||
"""Fixed threshold for data slicer in OOK mode"""
|
||||
return self.read_u8(_RF95_REG_15_OOK_FIX)
|
||||
|
||||
@ook_fixed_threshold.setter
|
||||
def ook_fixed_threshold(self, val: int) -> None:
|
||||
assert 0 <= val <= 255
|
||||
self.write_u8(_RF95_REG_15_OOK_FIX, val)
|
||||
|
||||
def packet_sent(self) -> bool:
|
||||
"""Transmit status"""
|
||||
return (self.read_u8(_RF95_REG_3F_IRQ_FLAGS_2) & 0x8) >> 3
|
||||
|
||||
def payload_ready(self) -> bool:
|
||||
"""Receive status"""
|
||||
return (self.read_u8(_RF95_REG_3F_IRQ_FLAGS_2) & 0x4) >> 2
|
||||
|
||||
def clear_interrupt(self) -> None:
|
||||
"""Clear interrupt Flags"""
|
||||
self.write_u8(_RF95_REG_3E_IRQ_FLAGS_1, 0xFF)
|
||||
self.write_u8(_RF95_REG_3F_IRQ_FLAGS_2, 0xFF)
|
||||
|
||||
def fill_fifo(self, payload: ReadableBuffer) -> None:
|
||||
"""Write the payload to the FIFO."""
|
||||
complete_payload = bytearray(1) # prepend packet length to payload
|
||||
complete_payload[0] = len(payload)
|
||||
# put the payload lengthe in the beginning of the packet for RFM69
|
||||
complete_payload = complete_payload + payload
|
||||
# Write payload to transmit fifo
|
||||
self.write_from(_RF95_REG_00_FIFO, complete_payload)
|
||||
|
||||
def read_fifo(self) -> bytearray:
|
||||
"""Read the data from the FIFO."""
|
||||
# Read the length of the FIFO.
|
||||
fifo_length = self.read_u8(_RF95_REG_00_FIFO)
|
||||
if fifo_length > 0: # read and clear the FIFO if anything in it
|
||||
packet = bytearray(fifo_length)
|
||||
# read the packet
|
||||
self.read_into(_RF95_REG_00_FIFO, packet, fifo_length)
|
||||
return packet
|
||||
557
adafruit_rfm/rfm_common.py
Normal file
557
adafruit_rfm/rfm_common.py
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
# SPDX-FileCopyrightText: 2024 Jerry Needell for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
|
||||
* Author(s): Jerry Needell
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
|
||||
from adafruit_bus_device import spi_device
|
||||
|
||||
try:
|
||||
from typing import Callable, Optional, Type
|
||||
|
||||
import busio
|
||||
import digitalio
|
||||
from circuitpython_typing import ReadableBuffer, WriteableBuffer
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from micropython import const
|
||||
|
||||
HAS_SUPERVISOR = False
|
||||
|
||||
try:
|
||||
import supervisor
|
||||
|
||||
if hasattr(supervisor, "ticks_ms"):
|
||||
HAS_SUPERVISOR = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
__version__ = "0.0.0+auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM.git"
|
||||
|
||||
|
||||
# RadioHead specific compatibility constants.
|
||||
_RH_BROADCAST_ADDRESS = const(0xFF)
|
||||
|
||||
# The acknowledgement bit in the FLAGS
|
||||
# The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved
|
||||
# for application layer use.
|
||||
_RH_FLAGS_ACK = const(0x80)
|
||||
_RH_FLAGS_RETRY = const(0x40)
|
||||
|
||||
|
||||
# supervisor.ticks_ms() contants
|
||||
_TICKS_PERIOD = const(1 << 29)
|
||||
_TICKS_MAX = const(_TICKS_PERIOD - 1)
|
||||
_TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2)
|
||||
|
||||
|
||||
def ticks_diff(ticks1: int, ticks2: int) -> int:
|
||||
"""Compute the signed difference between two ticks values
|
||||
assuming that they are within 2**28 ticks
|
||||
"""
|
||||
diff = (ticks1 - ticks2) & _TICKS_MAX
|
||||
diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
|
||||
return diff
|
||||
|
||||
|
||||
def asyncio_to_blocking(function):
|
||||
"""run async function as normal blocking function"""
|
||||
|
||||
def blocking_function(self, *args, **kwargs):
|
||||
return asyncio.run(function(self, *args, **kwargs))
|
||||
|
||||
return blocking_function
|
||||
|
||||
|
||||
async def asyncio_check_timeout(flag: Callable, limit: float, timeout_poll: float) -> bool:
|
||||
"""test for timeout waiting for specified flag"""
|
||||
timed_out = False
|
||||
if HAS_SUPERVISOR:
|
||||
start = supervisor.ticks_ms()
|
||||
while not timed_out and not flag():
|
||||
if ticks_diff(supervisor.ticks_ms(), start) >= limit * 1000:
|
||||
timed_out = True
|
||||
await asyncio.sleep(timeout_poll)
|
||||
else:
|
||||
start = time.monotonic()
|
||||
while not timed_out and not flag():
|
||||
if time.monotonic() - start >= limit:
|
||||
timed_out = True
|
||||
await asyncio.sleep(timeout_poll)
|
||||
|
||||
return timed_out
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
class RFMSPI:
|
||||
"""Base class for SPI type devices"""
|
||||
|
||||
class RegisterBits:
|
||||
"""Simplify register access"""
|
||||
|
||||
# Class to simplify access to the many configuration bits avaialable
|
||||
# on the chip's registers. This is a subclass here instead of using
|
||||
# a higher level module to increase the efficiency of memory usage
|
||||
# (all of the instances of this bit class will share the same buffer
|
||||
# used by the parent RFM69 class instance vs. each having their own
|
||||
# buffer and taking too much memory).
|
||||
|
||||
# Quirk of pylint that it requires public methods for a class. This
|
||||
# is a decorator class in Python and by design it has no public methods.
|
||||
# Instead it uses dunder accessors like get and set below. For some
|
||||
# reason pylint can't figure this out so disable the check.
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
# Again pylint fails to see the true intent of this code and warns
|
||||
# against private access by calling the write and read functions below.
|
||||
# This is by design as this is an internally used class. Disable the
|
||||
# check from pylint.
|
||||
# pylint: disable=protected-access
|
||||
|
||||
def __init__(self, address: int, *, offset: int = 0, bits: int = 1) -> None:
|
||||
assert 0 <= offset <= 7
|
||||
assert 1 <= bits <= 8
|
||||
assert (offset + bits) <= 8
|
||||
self._address = address
|
||||
self._mask = 0
|
||||
for _ in range(bits):
|
||||
self._mask <<= 1
|
||||
self._mask |= 1
|
||||
self._mask <<= offset
|
||||
self._offset = offset
|
||||
|
||||
def __get__(self, obj: Optional["RFM"], objtype: Type["RFM"]) -> int:
|
||||
reg_value = obj.read_u8(self._address)
|
||||
return (reg_value & self._mask) >> self._offset
|
||||
|
||||
def __set__(self, obj: Optional["RFM"], val: int) -> None:
|
||||
reg_value = obj.read_u8(self._address)
|
||||
reg_value &= ~self._mask
|
||||
reg_value |= (val & 0xFF) << self._offset
|
||||
obj.write_u8(self._address, reg_value)
|
||||
|
||||
# pylint: disable-msg=too-many-arguments
|
||||
def __init__( # noqa: PLR0913
|
||||
self,
|
||||
spi: busio.SPI,
|
||||
cs_pin: digitalio.DigitalInOut,
|
||||
baudrate: int = 5000000,
|
||||
polarity: int = 0,
|
||||
phase: int = 0,
|
||||
):
|
||||
self.spi_device = spi_device.SPIDevice(
|
||||
spi, cs_pin, baudrate=baudrate, polarity=polarity, phase=phase
|
||||
)
|
||||
# initialize last RSSI reading
|
||||
self.last_rssi = 0.0
|
||||
"""The RSSI of the last received packet. Stored when the packet was received.
|
||||
The instantaneous RSSI value may not be accurate once the
|
||||
operating mode has been changed.
|
||||
"""
|
||||
self.last_snr = 0.0
|
||||
"""The SNR of the last received packet. Stored when the packet was received.
|
||||
The instantaneous SNR value may not be accurate once the
|
||||
operating mode has been changed.
|
||||
"""
|
||||
# initialize timeouts and delays delays
|
||||
self.ack_wait = 0.1
|
||||
"""The delay time before attempting a retry after not receiving an ACK"""
|
||||
self.receive_timeout = 0.5
|
||||
"""The amount of time to poll for a received packet.
|
||||
If no packet is received, the returned packet will be None
|
||||
"""
|
||||
self.xmit_timeout = 2.0
|
||||
"""The amount of time to wait for the HW to transmit the packet.
|
||||
This is mainly used to prevent a hang due to a HW issue
|
||||
"""
|
||||
self.ack_retries = 5
|
||||
"""The number of ACK retries before reporting a failure."""
|
||||
self.ack_delay = None
|
||||
"""The delay time before attemting to send an ACK.
|
||||
If ACKs are being missed try setting this to .1 or .2.
|
||||
"""
|
||||
# initialize sequence number counter for reliabe datagram mode
|
||||
self.sequence_number = 0
|
||||
# create seen Ids list
|
||||
self.seen_ids = bytearray(256)
|
||||
# initialize packet header
|
||||
# node address - default is broadcast
|
||||
self.node = _RH_BROADCAST_ADDRESS
|
||||
"""The default address of this Node. (0-255).
|
||||
If not 255 (0xff) then only packets address to this node will be accepted.
|
||||
First byte of the RadioHead header.
|
||||
"""
|
||||
# destination address - default is broadcast
|
||||
self.destination = _RH_BROADCAST_ADDRESS
|
||||
"""The default destination address for packet transmissions. (0-255).
|
||||
If 255 (0xff) then any receiving node should accept the packet.
|
||||
Second byte of the RadioHead header.
|
||||
"""
|
||||
# ID - contains seq count for reliable datagram mode
|
||||
self.identifier = 0
|
||||
"""Automatically set to the sequence number when send_with_ack() used.
|
||||
Third byte of the RadioHead header.
|
||||
"""
|
||||
# flags - identifies ack/reetry packet for reliable datagram mode
|
||||
self.flags = 0
|
||||
"""Upper 4 bits reserved for use by Reliable Datagram Mode.
|
||||
Lower 4 bits may be used to pass information.
|
||||
Fourth byte of the RadioHead header.
|
||||
"""
|
||||
self.radiohead = True
|
||||
"""Enable RadioHead compatibility"""
|
||||
|
||||
self.crc_error_count = 0
|
||||
self.timeout_poll = 0.001
|
||||
|
||||
# pylint: enable-msg=too-many-arguments
|
||||
|
||||
# Global buffer for SPI commands
|
||||
_BUFFER = bytearray(4)
|
||||
|
||||
# pylint: disable=no-member
|
||||
# Reconsider pylint: disable when this can be tested
|
||||
def read_into(self, address: int, buf: WriteableBuffer, length: Optional[int] = None) -> None:
|
||||
"""Read a number of bytes from the specified address into the provided
|
||||
buffer. If length is not specified (the default) the entire buffer
|
||||
will be filled."""
|
||||
if length is None:
|
||||
length = len(buf)
|
||||
with self.spi_device as device:
|
||||
self._BUFFER[0] = address & 0x7F # Strip out top bit to set 0
|
||||
# value (read).
|
||||
device.write(self._BUFFER, end=1)
|
||||
device.readinto(buf, end=length)
|
||||
|
||||
def read_u8(self, address: int) -> int:
|
||||
"""Read a single byte from the provided address and return it."""
|
||||
self.read_into(address, self._BUFFER, length=1)
|
||||
return self._BUFFER[0]
|
||||
|
||||
def write_from(self, address: int, buf: ReadableBuffer, length: Optional[int] = None) -> None:
|
||||
"""Write a number of bytes to the provided address and taken from the
|
||||
provided buffer. If no length is specified (the default) the entire
|
||||
buffer is written."""
|
||||
if length is None:
|
||||
length = len(buf)
|
||||
with self.spi_device as device:
|
||||
self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to
|
||||
# indicate a write.
|
||||
device.write(self._BUFFER, end=1)
|
||||
device.write(buf, end=length)
|
||||
|
||||
def write_u8(self, address: int, val: int) -> None:
|
||||
"""Write a byte register to the chip. Specify the 7-bit address and the
|
||||
8-bit value to write to that address."""
|
||||
with self.spi_device as device:
|
||||
self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to indicate a write.
|
||||
self._BUFFER[1] = val & 0xFF
|
||||
device.write(self._BUFFER, end=2)
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
async def asyncio_send( # noqa: PLR0912 PLR0913
|
||||
self,
|
||||
data: ReadableBuffer,
|
||||
*,
|
||||
keep_listening: bool = False,
|
||||
destination: Optional[int] = None,
|
||||
node: Optional[int] = None,
|
||||
identifier: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
) -> bool:
|
||||
"""Send a string of data using the transmitter.
|
||||
You can only send 252 bytes at a time
|
||||
(limited by chip's FIFO size and appended headers).
|
||||
if the propert radiohead is True then this appends a 4 byte header
|
||||
to be compatible with the RadioHead library.
|
||||
The header defaults to using the initialized attributes:
|
||||
(destination,node,identifier,flags)
|
||||
It may be temporarily overidden via the kwargs - destination,node,identifier,flags.
|
||||
Values passed via kwargs do not alter the attribute settings.
|
||||
The keep_listening argument should be set to True if you want to start listening
|
||||
automatically after the packet is sent. The default setting is False.
|
||||
|
||||
Returns: True if success or False if the send timed out.
|
||||
"""
|
||||
# Disable pylint warning to not use length as a check for zero.
|
||||
# This is a puzzling warning as the below code is clearly the most
|
||||
# efficient and proper way to ensure a precondition that the provided
|
||||
# buffer be within an expected range of bounds. Disable this check.
|
||||
# pylint: disable=len-as-condition
|
||||
assert 0 < len(data) <= self.max_packet_length
|
||||
# pylint: enable=len-as-condition
|
||||
self.idle() # Stop receiving to clear FIFO and keep it clear.
|
||||
# Combine header and data to form payload
|
||||
if self.radiohead:
|
||||
payload = bytearray(4)
|
||||
if destination is None: # use attribute
|
||||
payload[0] = self.destination
|
||||
else: # use kwarg
|
||||
payload[0] = destination
|
||||
if node is None: # use attribute
|
||||
payload[1] = self.node
|
||||
else: # use kwarg
|
||||
payload[1] = node
|
||||
if identifier is None: # use attribute
|
||||
payload[2] = self.identifier
|
||||
else: # use kwarg
|
||||
payload[2] = identifier
|
||||
if flags is None: # use attribute
|
||||
payload[3] = self.flags
|
||||
else: # use kwarg
|
||||
payload[3] = flags
|
||||
payload = payload + data
|
||||
elif destination is not None: # prepend destination for non RH packets
|
||||
payload = destination.to_bytes(1, "big") + data
|
||||
else:
|
||||
payload = data
|
||||
self.fill_fifo(payload)
|
||||
# Turn on transmit mode to send out the packet.
|
||||
self.transmit()
|
||||
# Wait for packet_sent interrupt with explicit polling (not ideal but
|
||||
# best that can be done right now without interrupts).
|
||||
timed_out = await asyncio_check_timeout(
|
||||
self.packet_sent, self.xmit_timeout, self.timeout_poll
|
||||
)
|
||||
# Listen again if necessary and return the result packet.
|
||||
if keep_listening:
|
||||
self.listen()
|
||||
else:
|
||||
# Enter idle mode to stop receiving other packets.
|
||||
self.idle()
|
||||
self.clear_interrupt()
|
||||
return not timed_out
|
||||
|
||||
send = asyncio_to_blocking(asyncio_send)
|
||||
"""Non-asyncio wrapper to Send a string of data using the transmitter
|
||||
using the same arguments and keywords as asyncio_send()
|
||||
"""
|
||||
|
||||
async def asyncio_send_with_ack(self, data: ReadableBuffer) -> bool:
|
||||
"""Reliable Datagram mode:
|
||||
Send a packet with data and wait for an ACK response.
|
||||
The packet header is automatically generated.
|
||||
If enabled, the packet transmission will be retried on failure
|
||||
"""
|
||||
if not self.radiohead:
|
||||
raise RuntimeError("send_with_ack onl suppoted in RadioHead mode")
|
||||
if self.ack_retries:
|
||||
retries_remaining = self.ack_retries
|
||||
else:
|
||||
retries_remaining = 1
|
||||
got_ack = False
|
||||
self.sequence_number = (self.sequence_number + 1) & 0xFF
|
||||
while not got_ack and retries_remaining:
|
||||
self.identifier = self.sequence_number
|
||||
await self.asyncio_send(data, keep_listening=True)
|
||||
# Don't look for ACK from Broadcast message
|
||||
if self.destination == _RH_BROADCAST_ADDRESS:
|
||||
got_ack = True
|
||||
else:
|
||||
# wait for a packet from our destination
|
||||
ack_packet = await self.asyncio_receive(timeout=self.ack_wait, with_header=True)
|
||||
if ack_packet is not None:
|
||||
if ack_packet[3] & _RH_FLAGS_ACK:
|
||||
# check the ID
|
||||
if ack_packet[2] == self.identifier:
|
||||
got_ack = True
|
||||
break
|
||||
# pause before next retry -- random delay
|
||||
if not got_ack:
|
||||
# delay by random amount before next try
|
||||
await asyncio.sleep(self.ack_wait + self.ack_wait * random.random())
|
||||
retries_remaining = retries_remaining - 1
|
||||
# set retry flag in packet header
|
||||
self.flags |= _RH_FLAGS_RETRY
|
||||
self.flags = 0 # clear flags
|
||||
return got_ack
|
||||
|
||||
send_with_ack = asyncio_to_blocking(asyncio_send_with_ack)
|
||||
"""Non-asyncio wrapper to Send a string of data using the transmitter
|
||||
using the same arguments and keywords as asyncio_send_with_ack()
|
||||
"""
|
||||
|
||||
async def asyncio_receive( # noqa: PLR0912
|
||||
self,
|
||||
*,
|
||||
keep_listening: bool = True,
|
||||
with_header: bool = False,
|
||||
timeout: Optional[float] = None,
|
||||
) -> Optional[bytearray]:
|
||||
"""Wait to receive a packet from the receiver. If a packet is found the payload bytes
|
||||
are returned, otherwise None is returned (which indicates the timeout elapsed with no
|
||||
reception).
|
||||
If keep_listening is True (the default) the chip will immediately enter listening mode
|
||||
after reception of a packet, otherwise it will fall back to idle mode and ignore any
|
||||
future reception.
|
||||
Packets may have a 4-byte header for compatibility with the
|
||||
RadioHead library.
|
||||
The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip
|
||||
the header before returning the packet to the caller.
|
||||
If with_header is True then the 4 byte header will be returned with the packet.
|
||||
The payload then begins at packet[4].
|
||||
"""
|
||||
if not self.radiohead and with_header:
|
||||
raise RuntimeError("with_header only supported for RadioHead mode")
|
||||
timed_out = False
|
||||
if timeout is None:
|
||||
timeout = self.receive_timeout
|
||||
if timeout is not None:
|
||||
# Wait for the payloadready signal. This is not ideal and will
|
||||
# surely miss or overflow the FIFO when packets aren't read fast
|
||||
# enough, however it's the best that can be done from Python without
|
||||
# interrupt supports.
|
||||
# Make sure we are listening for packets.
|
||||
self.listen()
|
||||
timed_out = await asyncio_check_timeout(self.payload_ready, timeout, self.timeout_poll)
|
||||
# Payload ready is set, a packet is in the FIFO.
|
||||
packet = None
|
||||
# save last RSSI reading
|
||||
self.last_rssi = self.rssi
|
||||
self.last_snr = self.snr
|
||||
|
||||
# Enter idle mode to stop receiving other packets.
|
||||
self.idle()
|
||||
if not timed_out:
|
||||
if self.enable_crc and self.crc_error:
|
||||
self.crc_error_count += 1
|
||||
else:
|
||||
packet = self.read_fifo()
|
||||
if self.radiohead:
|
||||
if len(packet) < 5:
|
||||
# reject the packet if it is too small to contain the RAdioHead Header
|
||||
packet = None
|
||||
if packet is not None:
|
||||
if (
|
||||
self.node != _RH_BROADCAST_ADDRESS # noqa: PLR1714
|
||||
and packet[0] != _RH_BROADCAST_ADDRESS
|
||||
and packet[0] != self.node
|
||||
):
|
||||
packet = None
|
||||
if not with_header and packet is not None: # skip the header if not wanted
|
||||
packet = packet[4:]
|
||||
# Listen again if necessary and return the result packet.
|
||||
if keep_listening:
|
||||
self.listen()
|
||||
else:
|
||||
# Enter idle mode to stop receiving other packets.
|
||||
self.idle()
|
||||
self.clear_interrupt()
|
||||
return packet
|
||||
|
||||
receive = asyncio_to_blocking(asyncio_receive)
|
||||
"""Non-asyncio wrapper to Receive a packet
|
||||
using the same arguments and keywords as asyncio_receive()
|
||||
"""
|
||||
|
||||
async def asyncio_receive_with_ack( # noqa: PLR0912
|
||||
self,
|
||||
*,
|
||||
keep_listening: bool = True,
|
||||
with_header: bool = False,
|
||||
timeout: Optional[float] = None,
|
||||
) -> Optional[bytearray]:
|
||||
"""Wait to receive a RadioHead packet from the receiver then send an ACK packet in response.
|
||||
AKA Reliable Datagram mode.
|
||||
If a packet is found the payload bytes are returned, otherwise None is returned
|
||||
(which indicates the timeout elapsed with no reception).
|
||||
If keep_listening is True (the default) the chip will immediately enter listening mode
|
||||
after receipt of a packet, otherwise it will fall back to idle mode and ignore
|
||||
any incomming packets until it is called again.
|
||||
All packets must have a 4-byte header for compatibility with the RadioHead library.
|
||||
The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip
|
||||
the header before returning the packet to the caller.
|
||||
If with_header is True then the 4 byte header will be returned with the packet.
|
||||
The payload then begins at packet[4].
|
||||
"""
|
||||
if not self.radiohead:
|
||||
raise RuntimeError("receive_with_ack only supported for RadioHead mode")
|
||||
timed_out = False
|
||||
if timeout is None:
|
||||
timeout = self.receive_timeout
|
||||
if timeout is not None:
|
||||
# Wait for the payloadready signal. This is not ideal and will
|
||||
# surely miss or overflow the FIFO when packets aren't read fast
|
||||
# enough, however it's the best that can be done from Python without
|
||||
# interrupt supports.
|
||||
# Make sure we are listening for packets.
|
||||
self.listen()
|
||||
timed_out = await asyncio_check_timeout(self.payload_ready, timeout, self.timeout_poll)
|
||||
# Payload ready is set, a packet is in the FIFO.
|
||||
packet = None
|
||||
# save last RSSI reading
|
||||
self.last_rssi = self.rssi
|
||||
self.last_snr = self.snr
|
||||
|
||||
# Enter idle mode to stop receiving other packets.
|
||||
self.idle()
|
||||
if not timed_out:
|
||||
if self.enable_crc and self.crc_error:
|
||||
self.crc_error_count += 1
|
||||
else:
|
||||
packet = self.read_fifo()
|
||||
if self.radiohead:
|
||||
if len(packet) < 5:
|
||||
# reject the packet if it is too small to contain the RAdioHead Header
|
||||
packet = None
|
||||
if packet is not None:
|
||||
if (
|
||||
self.node != _RH_BROADCAST_ADDRESS # noqa: PLR1714
|
||||
and packet[0] != _RH_BROADCAST_ADDRESS
|
||||
and packet[0] != self.node
|
||||
):
|
||||
packet = None
|
||||
# send ACK unless this was an ACK or a broadcast
|
||||
elif ((packet[3] & _RH_FLAGS_ACK) == 0) and (
|
||||
packet[0] != _RH_BROADCAST_ADDRESS
|
||||
):
|
||||
# delay before sending Ack to give receiver a chance to get ready
|
||||
if self.ack_delay is not None:
|
||||
await asyncio.sleep(self.ack_delay)
|
||||
# send ACK packet to sender (data is b'!')
|
||||
await self.asyncio_send(
|
||||
b"!",
|
||||
destination=packet[1],
|
||||
node=packet[0],
|
||||
identifier=packet[2],
|
||||
flags=(packet[3] | _RH_FLAGS_ACK),
|
||||
)
|
||||
# reject Retries if we have seen this idetifier from this source before
|
||||
if (self.seen_ids[packet[1]] == packet[2]) and (
|
||||
packet[3] & _RH_FLAGS_RETRY
|
||||
):
|
||||
packet = None
|
||||
else: # save the packet identifier for this source
|
||||
self.seen_ids[packet[1]] = packet[2]
|
||||
if (
|
||||
packet is not None and (packet[3] & _RH_FLAGS_ACK) != 0
|
||||
): # Ignore it if it was an ACK packet
|
||||
packet = None
|
||||
if not with_header and packet is not None: # skip the header if not wanted
|
||||
packet = packet[4:]
|
||||
# Listen again if necessary and return the result packet.
|
||||
if keep_listening:
|
||||
self.listen()
|
||||
else:
|
||||
# Enter idle mode to stop receiving other packets.
|
||||
self.idle()
|
||||
self.clear_interrupt()
|
||||
return packet
|
||||
|
||||
receive_with_ack = asyncio_to_blocking(asyncio_receive_with_ack)
|
||||
"""Non-asyncio wrapper to Receive a packet
|
||||
using the same arguments and keywords as asyncio_receive_with_ack()
|
||||
"""
|
||||
BIN
docs/_static/favicon.ico
vendored
Normal file
BIN
docs/_static/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
3
docs/_static/favicon.ico.license
vendored
Normal file
3
docs/_static/favicon.ico.license
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2018 Phillip Torrone for Adafruit Industries
|
||||
|
||||
SPDX-License-Identifier: CC-BY-4.0
|
||||
17
docs/api.rst
Normal file
17
docs/api.rst
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
.. If you created a package, create one automodule per module in the package.
|
||||
|
||||
.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py)
|
||||
.. use this format as the module name: "adafruit_foo.foo"
|
||||
|
||||
.. automodule:: adafruit_rfm.rfm_common
|
||||
:members:
|
||||
|
||||
.. automodule:: adafruit_rfm.rfm69
|
||||
:members:
|
||||
|
||||
.. automodule:: adafruit_rfm.rfm9x
|
||||
:members:
|
||||
|
||||
.. automodule:: adafruit_rfm.rfm9xfsk
|
||||
:members:
|
||||
4
docs/api.rst.license
Normal file
4
docs/api.rst.license
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
SPDX-FileCopyrightText: Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
186
docs/conf.py
Normal file
186
docs/conf.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinxcontrib.jquery",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.todo",
|
||||
]
|
||||
|
||||
# TODO: Please Read!
|
||||
# Uncomment the below if you use native CircuitPython modules such as
|
||||
# digitalio, micropython and busio. List the modules you use. Without it, the
|
||||
# autodoc module docs will fail to generate with a warning.
|
||||
# autodoc_mock_imports = ["digitalio", "busio"]
|
||||
|
||||
autodoc_preserve_defaults = True
|
||||
|
||||
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3", None),
|
||||
"BusDevice": ("https://docs.circuitpython.org/projects/busdevice/en/latest/", None),
|
||||
"CircuitPython": ("https://docs.circuitpython.org/en/latest/", None),
|
||||
}
|
||||
|
||||
# Show the docstring from both the class and its __init__() method.
|
||||
autoclass_content = "both"
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = "Adafruit CircuitPython RFM Library"
|
||||
creation_year = "2024"
|
||||
current_year = str(datetime.datetime.now().year)
|
||||
year_duration = (
|
||||
current_year if current_year == creation_year else creation_year + " - " + current_year
|
||||
)
|
||||
copyright = year_duration + " Jerry Needell"
|
||||
author = "Jerry Needell"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = "1.0"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = "1.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = "en"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = [
|
||||
"_build",
|
||||
"Thumbs.db",
|
||||
".DS_Store",
|
||||
".env",
|
||||
"CODE_OF_CONDUCT.md",
|
||||
]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#
|
||||
default_role = "any"
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#
|
||||
add_function_parentheses = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
# If this is True, todo emits a warning for each TODO entries. The default is False.
|
||||
todo_emit_warnings = True
|
||||
|
||||
napoleon_numpy_docstring = False
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
import sphinx_rtd_theme
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."]
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#
|
||||
html_favicon = "_static/favicon.ico"
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "Adafruit_CircuitPython_Rfm_Librarydoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
# Latex figure (float) alignment
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(
|
||||
master_doc,
|
||||
"Adafruit_CircuitPython_RFM_Library.tex",
|
||||
"Adafruit CircuitPython RFM Library Documentation",
|
||||
author,
|
||||
"manual",
|
||||
),
|
||||
]
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(
|
||||
master_doc,
|
||||
"Adafruit_CircuitPython_RFM_Library",
|
||||
"Adafruit CircuitPython RFM Library Documentation",
|
||||
[author],
|
||||
1,
|
||||
),
|
||||
]
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(
|
||||
master_doc,
|
||||
"Adafruit_CircuitPython_RFM_Library",
|
||||
"Adafruit CircuitPython RFM Library Documentation",
|
||||
author,
|
||||
"Adafruit_CircuitPython_RFM_Library",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
8
docs/examples.rst
Normal file
8
docs/examples.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Simple test
|
||||
------------
|
||||
|
||||
Ensure your device works with this simple test.
|
||||
|
||||
.. literalinclude:: ../examples/rfm_simpletest.py
|
||||
:caption: examples/rfm_simpletest.py
|
||||
:linenos:
|
||||
4
docs/examples.rst.license
Normal file
4
docs/examples.rst.license
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
SPDX-FileCopyrightText: Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
47
docs/index.rst
Normal file
47
docs/index.rst
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
.. include:: ../README.rst
|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
:hidden:
|
||||
|
||||
self
|
||||
|
||||
.. toctree::
|
||||
:caption: Examples
|
||||
|
||||
examples
|
||||
|
||||
.. toctree::
|
||||
:caption: API Reference
|
||||
:maxdepth: 3
|
||||
|
||||
api
|
||||
|
||||
.. toctree::
|
||||
:caption: Tutorials
|
||||
|
||||
.. toctree::
|
||||
:caption: Related Products
|
||||
|
||||
.. toctree::
|
||||
:caption: Other Links
|
||||
|
||||
Download from GitHub <https://github.com/jerryneedell/Adafruit_CircuitPython_RFM/releases/latest>
|
||||
Download Library Bundle <https://circuitpython.org/libraries>
|
||||
CircuitPython Reference Documentation <https://docs.circuitpython.org>
|
||||
CircuitPython Support Forum <https://forums.adafruit.com/viewforum.php?f=60>
|
||||
Discord Chat <https://adafru.it/discord>
|
||||
Adafruit Learning System <https://learn.adafruit.com>
|
||||
Adafruit Blog <https://blog.adafruit.com>
|
||||
Adafruit Store <https://www.adafruit.com>
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
4
docs/index.rst.license
Normal file
4
docs/index.rst.license
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
SPDX-FileCopyrightText: Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
7
docs/requirements.txt
Normal file
7
docs/requirements.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
sphinx
|
||||
sphinxcontrib-jquery
|
||||
sphinx-rtd-theme
|
||||
93
examples/rfm_fsk_node1.py
Normal file
93
examples/rfm_fsk_node1.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# SPDX-FileCopyrightText: 2020 Jerry Needell for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example to send a packet periodically
|
||||
|
||||
import time
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
# from adafruit_rfm import rfm9x
|
||||
|
||||
# rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
from adafruit_rfm import rfm9xfsk
|
||||
|
||||
rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# Disable the RadioHead Header
|
||||
rfm.radiohead = False
|
||||
# set the time interval (seconds) for sending packets
|
||||
transmit_interval = 5
|
||||
node = 1
|
||||
destination = 2
|
||||
rfm.enable_address_filter = True
|
||||
rfm.fsk_node_address = node
|
||||
rfm.fsk_broadcast_address = 0xFF
|
||||
# For RFM69 only:
|
||||
# Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# initialize counter
|
||||
counter = 0
|
||||
# send a broadcast mesage
|
||||
rfm.send(bytes(f"message number {counter}", "UTF-8"), destination=destination)
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
time_now = time.monotonic()
|
||||
while True:
|
||||
# Look for a new packet - wait up to 5 seconds:
|
||||
packet = rfm.receive(timeout=2.0)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
# send reading after any packet received
|
||||
if time.monotonic() - time_now > transmit_interval:
|
||||
# reset timeer
|
||||
time_now = time.monotonic()
|
||||
counter = counter + 1
|
||||
rfm.send(bytes(f"message number {counter}", "UTF-8"), destination=destination)
|
||||
93
examples/rfm_fsk_node2.py
Normal file
93
examples/rfm_fsk_node2.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# SPDX-FileCopyrightText: 2020 Jerry Needell for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example to send a packet periodically
|
||||
|
||||
import time
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
# from adafruit_rfm import rfm9x
|
||||
|
||||
# rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
from adafruit_rfm import rfm9xfsk
|
||||
|
||||
rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# Disable the RadioHead Header
|
||||
rfm.radiohead = False
|
||||
# set the time interval (seconds) for sending packets
|
||||
transmit_interval = 5
|
||||
node = 2
|
||||
destination = 1
|
||||
rfm.enable_address_filter = True
|
||||
rfm.fsk_node_address = node
|
||||
rfm.fsk_broadcast_address = 0xFF
|
||||
# For RFM69 only:
|
||||
# Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# initialize counter
|
||||
counter = 0
|
||||
# send a broadcast mesage
|
||||
rfm.send(bytes(f"message number {counter}", "UTF-8"), destination=destination)
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
time_now = time.monotonic()
|
||||
while True:
|
||||
# Look for a new packet - wait up to 5 seconds:
|
||||
packet = rfm.receive(timeout=2.0)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
# send reading after any packet received
|
||||
if time.monotonic() - time_now > transmit_interval:
|
||||
# reset timeer
|
||||
time_now = time.monotonic()
|
||||
counter = counter + 1
|
||||
rfm.send(bytes(f"message number {counter}", "UTF-8"), destination=destination)
|
||||
92
examples/rfm_raw.py
Normal file
92
examples/rfm_raw.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# SPDX-FileCopyrightText: 2024 Ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Simple demo of sending and recieving data with the RFM9x or RFM69 radios.
|
||||
# Author: Jerry Needell
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
# from adafruit_rfm import rfm9x
|
||||
|
||||
# rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
from adafruit_rfm import rfm9xfsk
|
||||
|
||||
rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# Disable the RadioHead Header
|
||||
rfm.radiohead = False
|
||||
|
||||
# Send a packet. Note you can only send a packet containing up to 60 bytes for an RFM69
|
||||
# and 252 bytes forn an RFM9x.
|
||||
# This is a limitation of the radio packet size, so if you need to send larger
|
||||
# amounts of data you will need to break it into smaller send calls. Each send
|
||||
# call will wait for the previous one to finish before continuing.
|
||||
rfm.send(bytes("Hello world!\r\n", "utf-8"))
|
||||
print("Sent Hello World message!")
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
|
||||
while True:
|
||||
packet = rfm.receive()
|
||||
# Optionally change the receive timeout from its default of 0.5 seconds:
|
||||
# packet = rfm9x.receive(timeout=5.0)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is None:
|
||||
# Packet has not been received
|
||||
print("Received nothing! Listening again...")
|
||||
else:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print("Hex data: ", [hex(x) for x in packet])
|
||||
# And decode to ASCII text and print it too. Note that you always
|
||||
# receive raw bytes and need to convert to a text format like ASCII
|
||||
# if you intend to do string processing on your data. Make sure the
|
||||
# sending side is sending ASCII data before you try to decode!
|
||||
try:
|
||||
packet_text = str(packet, "ascii")
|
||||
print(f"Received (ASCII): {packet_text}")
|
||||
except UnicodeError:
|
||||
pass
|
||||
# Also read the RSSI (signal strength) of the last received message and
|
||||
# print it.
|
||||
rssi = rfm.last_rssi
|
||||
print(f"Received signal strength: {rssi} dB")
|
||||
95
examples/rfm_rh_asyncio_listen.py
Normal file
95
examples/rfm_rh_asyncio_listen.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example using Interrupts to send a message and then wait indefinitely for messages
|
||||
# to be received. Interrupts are used only for receive. sending is done with polling.
|
||||
# This example is for systems that support interrupts like the Raspberry Pi with "blinka"
|
||||
# CircuitPython does not support interrupts so it will not work on Circutpython boards
|
||||
# Author: Tony DiCola, Jerry Needell
|
||||
import asyncio
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# send startup message from my_node
|
||||
# rfm.send(bytes("startup message from node {}".format(rfm.node), "UTF-8"))
|
||||
rfm.listen()
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Packet:
|
||||
"""Simple class to hold an value. Use .value to to read or write."""
|
||||
|
||||
def __init__(self):
|
||||
self.received = False
|
||||
|
||||
|
||||
# setup interrupt callback function
|
||||
async def wait_for_packets(packet_status):
|
||||
while True:
|
||||
if rfm.payload_ready():
|
||||
packet = await rfm.asyncio_receive(with_header=True, timeout=None)
|
||||
if packet is not None:
|
||||
packet_status.received = True
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print([hex(x) for x in packet])
|
||||
print(f"RSSI: {rfm.last_rssi}")
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
|
||||
async def main():
|
||||
packet_status = Packet()
|
||||
task1 = asyncio.create_task(wait_for_packets(packet_status))
|
||||
|
||||
await asyncio.gather(task1) # Don't forget "await"!
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
96
examples/rfm_rh_asyncio_listen_ack.py
Normal file
96
examples/rfm_rh_asyncio_listen_ack.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example using Interrupts to send a message and then wait indefinitely for messages
|
||||
# to be received. Interrupts are used only for receive. sending is done with polling.
|
||||
# This example is for systems that support interrupts like the Raspberry Pi with "blinka"
|
||||
# CircuitPython does not support interrupts so it will not work on Circutpython boards
|
||||
# Author: Tony DiCola, Jerry Needell
|
||||
import asyncio
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
|
||||
# send startup message from my_node
|
||||
# rfm.send(bytes("startup message from node {}".format(rfm.node), "UTF-8"))
|
||||
rfm.listen()
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Packet:
|
||||
"""Simple class to hold an value. Use .value to to read or write."""
|
||||
|
||||
def __init__(self):
|
||||
self.received = False
|
||||
|
||||
|
||||
# setup interrupt callback function
|
||||
async def wait_for_packets(packet_status):
|
||||
while True:
|
||||
if rfm.payload_ready():
|
||||
packet = await rfm.asyncio_receive_with_ack(with_header=True, timeout=None)
|
||||
if packet is not None:
|
||||
packet_status.received = True
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print([hex(x) for x in packet])
|
||||
print(f"RSSI: {rfm.last_rssi}")
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
|
||||
async def main():
|
||||
packet_status = Packet()
|
||||
task1 = asyncio.create_task(wait_for_packets(packet_status))
|
||||
|
||||
await asyncio.gather(task1) # Don't forget "await"!
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
129
examples/rfm_rh_asyncio_node1.py
Normal file
129
examples/rfm_rh_asyncio_node1.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# set node addresses
|
||||
rfm.node = 1
|
||||
rfm.destination = 100
|
||||
# send startup message from my_node
|
||||
rfm.send_with_ack(bytes(f"startup message from node {rfm.node}", "UTF-8"))
|
||||
rfm.listen()
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Packet:
|
||||
"""Simple class to hold an value. Use .value to to read or write."""
|
||||
|
||||
def __init__(self):
|
||||
self.received = False
|
||||
|
||||
|
||||
# setup interrupt callback function
|
||||
async def wait_for_packets(packet_status, lock):
|
||||
while True:
|
||||
if rfm.payload_ready():
|
||||
if lock.locked():
|
||||
print("locked waiting for receive")
|
||||
async with lock:
|
||||
packet = await rfm.asyncio_receive_with_ack(with_header=True, timeout=None)
|
||||
if packet is not None:
|
||||
packet_status.received = True
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print([hex(x) for x in packet])
|
||||
print(f"RSSI: {rfm.last_rssi}")
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
|
||||
async def send_packets(packet_status, lock):
|
||||
# initialize counter
|
||||
counter = 0
|
||||
ack_failed_counter = 0
|
||||
counter = 0
|
||||
transmit_interval = 5
|
||||
time_now = time.monotonic()
|
||||
while True:
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet_status.received:
|
||||
packet_status.received = False
|
||||
if time.monotonic() - time_now > transmit_interval:
|
||||
# reset timeer
|
||||
time_now = time.monotonic()
|
||||
counter += 1
|
||||
# send a mesage to destination_node from my_node
|
||||
if lock.locked():
|
||||
print("locked waiting for send")
|
||||
async with lock:
|
||||
if not await rfm.asyncio_send_with_ack(
|
||||
bytes(
|
||||
f"message from node {rfm.node} {counter} {ack_failed_counter}",
|
||||
"UTF-8",
|
||||
)
|
||||
):
|
||||
ack_failed_counter += 1
|
||||
print(" No Ack: ", counter, ack_failed_counter)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def main():
|
||||
packet_status = Packet()
|
||||
lock = asyncio.Lock()
|
||||
task1 = asyncio.create_task(wait_for_packets(packet_status, lock))
|
||||
task2 = asyncio.create_task(send_packets(packet_status, lock))
|
||||
|
||||
await asyncio.gather(task1, task2) # Don't forget "await"!
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
71
examples/rfm_rh_base.py
Normal file
71
examples/rfm_rh_base.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# set node addresses
|
||||
rfm.node = 100
|
||||
rfm.destination = 0xFF
|
||||
# send startup message from my_node
|
||||
rfm.send(
|
||||
bytes(f"startup message from base {rfm.node}", "UTF-8"),
|
||||
keep_listening=True,
|
||||
)
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
|
||||
|
||||
while True:
|
||||
if rfm.payload_ready():
|
||||
packet = rfm.receive(with_header=True, timeout=None)
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print([hex(x) for x in packet])
|
||||
print(f"RSSI: {rfm.last_rssi}")
|
||||
71
examples/rfm_rh_base_ack.py
Normal file
71
examples/rfm_rh_base_ack.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# set node addresses
|
||||
rfm.node = 100
|
||||
rfm.destination = 0xFF
|
||||
# send startup message from my_node
|
||||
rfm.send(
|
||||
bytes(f"startup message from base {rfm.node}", "UTF-8"),
|
||||
keep_listening=True,
|
||||
)
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
|
||||
|
||||
while True:
|
||||
if rfm.payload_ready():
|
||||
packet = rfm.receive_with_ack(with_header=True, timeout=None)
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print([hex(x) for x in packet])
|
||||
print(f"RSSI: {rfm.last_rssi}")
|
||||
87
examples/rfm_rh_node1.py
Normal file
87
examples/rfm_rh_node1.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example to send a packet periodically between addressed nodes
|
||||
# Author: Jerry Needell
|
||||
#
|
||||
import time
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# set the time interval (seconds) for sending packets
|
||||
transmit_interval = 2
|
||||
|
||||
# set node addresses
|
||||
rfm.node = 1
|
||||
rfm.destination = 100
|
||||
# initialize counter
|
||||
counter = 0
|
||||
# send a broadcast message from my_node with ID = counter
|
||||
rfm.send(bytes(f"Startup message {counter} from node {rfm.node}", "UTF-8"))
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
now = time.monotonic()
|
||||
while True:
|
||||
# Look for a new packet: only accept if addresses to my_node
|
||||
packet = rfm.receive(with_header=True, timeout=5.0)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print("Received (raw header):", [hex(x) for x in packet[0:4]])
|
||||
print(f"Received (raw payload): {packet[4:]}")
|
||||
print(f"Received RSSI: {rfm.last_rssi}")
|
||||
if time.monotonic() - now > transmit_interval:
|
||||
now = time.monotonic()
|
||||
counter = counter + 1
|
||||
# send a mesage to destination_node from my_node
|
||||
rfm.send(
|
||||
bytes(f"message number {counter} from node {rfm.node}", "UTF-8"),
|
||||
keep_listening=True,
|
||||
)
|
||||
button_pressed = None
|
||||
89
examples/rfm_rh_node1_ack.py
Normal file
89
examples/rfm_rh_node1_ack.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example to send a packet periodically between addressed nodes with ACK
|
||||
# Author: Jerry Needell
|
||||
#
|
||||
import time
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
from adafruit_rfm import rfm9x
|
||||
|
||||
rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
# from adafruit_rfm import rfm9xfsk
|
||||
|
||||
# rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# set the time interval (seconds) for sending packets
|
||||
transmit_interval = 10
|
||||
|
||||
# set node addresses
|
||||
rfm.node = 1
|
||||
rfm.destination = 100
|
||||
# initialize counter
|
||||
counter = 0
|
||||
ack_failed_counter = 0
|
||||
# send startup message from my_node
|
||||
rfm.send_with_ack(bytes(f"startup message from node {rfm.node}", "UTF-8"))
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
time_now = time.monotonic()
|
||||
while True:
|
||||
# Look for a new packet: only accept if addresses to my_node
|
||||
packet = rfm.receive_with_ack(with_header=True)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print("Received (raw header):", [hex(x) for x in packet[0:4]])
|
||||
print(f"Received (raw payload): {packet[4:]}")
|
||||
print(f"RSSI: {rfm.last_rssi}")
|
||||
# send reading after any packet received
|
||||
if time.monotonic() - time_now > transmit_interval:
|
||||
# reset timeer
|
||||
time_now = time.monotonic()
|
||||
counter += 1
|
||||
# send a mesage to destination_node from my_node
|
||||
if not rfm.send_with_ack(bytes(f"message from node node {rfm.node} {counter}", "UTF-8")):
|
||||
ack_failed_counter += 1
|
||||
print(" No Ack: ", counter, ack_failed_counter)
|
||||
88
examples/rfm_simpletest.py
Normal file
88
examples/rfm_simpletest.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# SPDX-FileCopyrightText: 2024 Ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Simple demo of sending and recieving data with the RFM9x or RFM69 radios.
|
||||
# Author: Jerry Needell
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
# from adafruit_rfm import rfm9x
|
||||
|
||||
# rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
from adafruit_rfm import rfm9xfsk
|
||||
|
||||
rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# Send a packet. Note you can only send a packet containing up to 60 bytes for an RFM69
|
||||
# and 252 bytes forn an RFM9x.
|
||||
# This is a limitation of the radio packet size, so if you need to send larger
|
||||
# amounts of data you will need to break it into smaller send calls. Each send
|
||||
# call will wait for the previous one to finish before continuing.
|
||||
rfm.send(bytes("Hello world!\r\n", "utf-8"))
|
||||
print("Sent Hello World message!")
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
|
||||
while True:
|
||||
packet = rfm.receive()
|
||||
# Optionally change the receive timeout from its default of 0.5 seconds:
|
||||
# packet = rfm9x.receive(timeout=5.0)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is None:
|
||||
# Packet has not been received
|
||||
print("Received nothing! Listening again...")
|
||||
else:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
# And decode to ASCII text and print it too. Note that you always
|
||||
# receive raw bytes and need to convert to a text format like ASCII
|
||||
# if you intend to do string processing on your data. Make sure the
|
||||
# sending side is sending ASCII data before you try to decode!
|
||||
try:
|
||||
packet_text = str(packet, "ascii")
|
||||
print(f"Received (ASCII): {packet_text}")
|
||||
except UnicodeError:
|
||||
print("Hex data: ", [hex(x) for x in packet])
|
||||
# Also read the RSSI (signal strength) of the last received message and
|
||||
# print it.
|
||||
rssi = rfm.last_rssi
|
||||
print(f"Received signal strength: {rssi} dB")
|
||||
100
examples/rfm_transmit.py
Normal file
100
examples/rfm_transmit.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Example to send a packet periodically
|
||||
# Author: Jerry Needell
|
||||
#
|
||||
import time
|
||||
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
|
||||
# Define radio parameters.
|
||||
RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your
|
||||
# module! Can be a value like 915.0, 433.0, etc.
|
||||
|
||||
# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
|
||||
CS = digitalio.DigitalInOut(board.CE1)
|
||||
RESET = digitalio.DigitalInOut(board.D25)
|
||||
|
||||
# Initialize SPI bus.
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
|
||||
# Initialze RFM radio
|
||||
# uncommnet the desired import and rfm initialization depending on the radio boards being used
|
||||
|
||||
# Use rfm9x for two RFM9x radios using LoRa
|
||||
|
||||
# from adafruit_rfm import rfm9x
|
||||
|
||||
# rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm9xfsk for two RFM9x radios or RFM9x to RFM69 using FSK
|
||||
|
||||
from adafruit_rfm import rfm9xfsk
|
||||
|
||||
rfm = rfm9xfsk.RFM9xFSK(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# Use rfm69 for two RFM69 radios using FSK
|
||||
|
||||
# from adafruit_rfm import rfm69
|
||||
|
||||
# rfm = rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)
|
||||
|
||||
# For RFM69 only: Optionally set an encryption key (16 byte AES key). MUST match both
|
||||
# on the transmitter and receiver (or be set to None to disable/the default).
|
||||
# rfm.encryption_key = None
|
||||
# rfm.encryption_key = (
|
||||
# b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
# )
|
||||
|
||||
# for OOK on RFM69 or RFM9xFSK
|
||||
# rfm.modulation_type = 1
|
||||
|
||||
# uncommnet to Disable the RadioHead Header
|
||||
# rfm.radiohead = False
|
||||
|
||||
# in FSK/OOK modes rfo RFM69 or RFM9X - addresss filtering may be enabled
|
||||
# rfm.enable_address_filter=True
|
||||
# rfm.fsk_node_address=0x2
|
||||
# rfm.fsk_broadcast_address=0xff
|
||||
|
||||
# set the time interval (seconds) for sending packets
|
||||
transmit_interval = 5
|
||||
|
||||
# Note that the radio is configured in LoRa mode so you can't control sync
|
||||
# word, encryption, frequency deviation, or other settings!
|
||||
|
||||
# You can however adjust the transmit power (in dB). The default is 13 dB but
|
||||
# high power radios like the RFM95 can go up to 23 dB:
|
||||
rfm.tx_power = 23
|
||||
|
||||
|
||||
# initialize counter
|
||||
counter = 0
|
||||
# send a broadcast mesage
|
||||
rfm.send(bytes(f"message number {counter}", "UTF-8"))
|
||||
|
||||
# Wait to receive packets.
|
||||
print("Waiting for packets...")
|
||||
# initialize flag and timer
|
||||
send_reading = False
|
||||
time_now = time.monotonic()
|
||||
while True:
|
||||
# Look for a new packet - wait up to 2 seconds:
|
||||
packet = rfm.receive(timeout=2.0)
|
||||
# If no packet was received during the timeout then None is returned.
|
||||
if packet is not None:
|
||||
# Received a packet!
|
||||
# Print out the raw bytes of the packet:
|
||||
print(f"Received (raw bytes): {packet}")
|
||||
print("Hex data: ", [hex(x) for x in packet])
|
||||
# send reading after any packet received
|
||||
if time.monotonic() - time_now > transmit_interval:
|
||||
# reset timeer
|
||||
time_now = time.monotonic()
|
||||
# clear flag to send data
|
||||
send_reading = False
|
||||
counter = counter + 1
|
||||
rfm.send(bytes(f"message number {counter}", "UTF-8"))
|
||||
3
optional_requirements.txt
Normal file
3
optional_requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
51
pyproject.toml
Normal file
51
pyproject.toml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools",
|
||||
"wheel",
|
||||
"setuptools-scm",
|
||||
]
|
||||
|
||||
[project]
|
||||
name = "adafruit-circuitpython-rfm"
|
||||
description = "Support for RFM69 and RFM9x modules"
|
||||
version = "0.0.0+auto.0"
|
||||
readme = "README.rst"
|
||||
authors = [
|
||||
{name = "Adafruit Industries", email = "circuitpython@adafruit.com"}
|
||||
]
|
||||
urls = {Homepage = "https://github.com/jerryneedell/Adafruit_CircuitPython_RFM"}
|
||||
keywords = [
|
||||
"adafruit",
|
||||
"blinka",
|
||||
"circuitpython",
|
||||
"micropython",
|
||||
"rfm",
|
||||
"RFM69",
|
||||
"RFM9x",
|
||||
"radio",
|
||||
]
|
||||
license = {text = "MIT"}
|
||||
classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Software Development :: Embedded Systems",
|
||||
"Topic :: System :: Hardware",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
]
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[tool.setuptools]
|
||||
# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,
|
||||
# CHANGE `py_modules = ['...']` TO `packages = ['...']`
|
||||
#py-modules = ["adafruit_rfm"]
|
||||
packages = ["adafruit_rfm"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = {file = ["requirements.txt"]}
|
||||
optional-dependencies = {optional = {file = ["optional_requirements.txt"]}}
|
||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2024 Jerry Needell for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
Adafruit-Blinka
|
||||
adafruit-circuitpython-busdevice
|
||||
asyncio
|
||||
99
ruff.toml
Normal file
99
ruff.toml
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
target-version = "py38"
|
||||
line-length = 100
|
||||
|
||||
[lint]
|
||||
select = ["I", "PL", "UP"]
|
||||
|
||||
extend-select = [
|
||||
"D419", # empty-docstring
|
||||
"E501", # line-too-long
|
||||
"W291", # trailing-whitespace
|
||||
"PLC0414", # useless-import-alias
|
||||
"PLC2401", # non-ascii-name
|
||||
"PLC2801", # unnecessary-dunder-call
|
||||
"PLC3002", # unnecessary-direct-lambda-call
|
||||
"E999", # syntax-error
|
||||
"PLE0101", # return-in-init
|
||||
"F706", # return-outside-function
|
||||
"F704", # yield-outside-function
|
||||
"PLE0116", # continue-in-finally
|
||||
"PLE0117", # nonlocal-without-binding
|
||||
"PLE0241", # duplicate-bases
|
||||
"PLE0302", # unexpected-special-method-signature
|
||||
"PLE0604", # invalid-all-object
|
||||
"PLE0605", # invalid-all-format
|
||||
"PLE0643", # potential-index-error
|
||||
"PLE0704", # misplaced-bare-raise
|
||||
"PLE1141", # dict-iter-missing-items
|
||||
"PLE1142", # await-outside-async
|
||||
"PLE1205", # logging-too-many-args
|
||||
"PLE1206", # logging-too-few-args
|
||||
"PLE1307", # bad-string-format-type
|
||||
"PLE1310", # bad-str-strip-call
|
||||
"PLE1507", # invalid-envvar-value
|
||||
"PLE2502", # bidirectional-unicode
|
||||
"PLE2510", # invalid-character-backspace
|
||||
"PLE2512", # invalid-character-sub
|
||||
"PLE2513", # invalid-character-esc
|
||||
"PLE2514", # invalid-character-nul
|
||||
"PLE2515", # invalid-character-zero-width-space
|
||||
"PLR0124", # comparison-with-itself
|
||||
"PLR0202", # no-classmethod-decorator
|
||||
"PLR0203", # no-staticmethod-decorator
|
||||
"UP004", # useless-object-inheritance
|
||||
"PLR0206", # property-with-parameters
|
||||
"PLR0904", # too-many-public-methods
|
||||
"PLR0911", # too-many-return-statements
|
||||
"PLR0912", # too-many-branches
|
||||
"PLR0913", # too-many-arguments
|
||||
"PLR0914", # too-many-locals
|
||||
"PLR0915", # too-many-statements
|
||||
"PLR0916", # too-many-boolean-expressions
|
||||
"PLR1702", # too-many-nested-blocks
|
||||
"PLR1704", # redefined-argument-from-local
|
||||
"PLR1711", # useless-return
|
||||
"C416", # unnecessary-comprehension
|
||||
"PLR1733", # unnecessary-dict-index-lookup
|
||||
"PLR1736", # unnecessary-list-index-lookup
|
||||
|
||||
# ruff reports this rule is unstable
|
||||
#"PLR6301", # no-self-use
|
||||
|
||||
"PLW0108", # unnecessary-lambda
|
||||
"PLW0120", # useless-else-on-loop
|
||||
"PLW0127", # self-assigning-variable
|
||||
"PLW0129", # assert-on-string-literal
|
||||
"B033", # duplicate-value
|
||||
"PLW0131", # named-expr-without-context
|
||||
"PLW0245", # super-without-brackets
|
||||
"PLW0406", # import-self
|
||||
"PLW0602", # global-variable-not-assigned
|
||||
"PLW0603", # global-statement
|
||||
"PLW0604", # global-at-module-level
|
||||
|
||||
# fails on the try: import typing used by libraries
|
||||
#"F401", # unused-import
|
||||
|
||||
"F841", # unused-variable
|
||||
"E722", # bare-except
|
||||
"PLW0711", # binary-op-exception
|
||||
"PLW1501", # bad-open-mode
|
||||
"PLW1508", # invalid-envvar-default
|
||||
"PLW1509", # subprocess-popen-preexec-fn
|
||||
"PLW2101", # useless-with-lock
|
||||
"PLW3301", # nested-min-max
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"PLR2004", # magic-value-comparison
|
||||
"UP030", # format literals
|
||||
"PLW1514", # unspecified-encoding
|
||||
|
||||
]
|
||||
|
||||
[format]
|
||||
line-ending = "lf"
|
||||
Loading…
Reference in a new issue