├── .env ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CHANGES.md ├── LICENSE ├── PyPI.md ├── README.md ├── arduino ├── ack-sender │ └── ack-sender.ino ├── fixed-sender │ └── fixed-sender.ino ├── mixed-sender │ └── mixed-sender.ino ├── rr-client │ └── rr-client.ino └── simple-sender │ └── simple-sender.ino ├── doc ├── nano-nrf24-1.fzz ├── nano-nrf24-1_bb.png ├── notes.txt ├── pizw-nrf24-1-irq.fzz ├── pizw-nrf24-1-irq_bb.png ├── pizw-nrf24-1.fzz ├── pizw-nrf24-1_bb.png ├── pizw-nrf24-2.fzz └── pizw-nrf24-2_bb.png ├── pythonpath ├── requirements.txt ├── setup.cfg ├── setup.py ├── src └── nrf24 │ ├── __init__.py │ └── nrf24.py └── test ├── ack-receiver.py ├── ack-sender.py ├── fixed-receiver.py ├── fixed-sender.py ├── int-receiver.py ├── int-sender.py ├── mixed-receiver.py ├── mixed-sender.py ├── multi-receiver.py ├── multi-sender.py ├── rr-client.py ├── rr-server.py ├── simple-receiver.py └── simple-sender.py /.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH=lib:src:test:venv/lib/python3.7/site-packages 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '31 1 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | __pycache__ 3 | .DS_Store 4 | /build 5 | /dist 6 | /nrf24.egg-info 7 | /src/nrf24.egg-info 8 | .vscode/** 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "venv/bin/python3" 3 | } -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This is the the change log for the NRF24 project. 4 | 5 | ## Version 2.0.0 6 | 7 | Version 2.0.0 has breaking changes compared to version 1.1.1 which was the previous version released to pypi.org. 8 | Please make sure to continue to use version 1.1.1, or please make the necessary changes to comply with the new API. 9 | 10 | This release also have an updated set of test / example programs in the `/test` folder. Make sure to consult those 11 | as well. 12 | 13 | ### Breaking Changes 14 | 15 | 16 | * **Removed** `set_local_address(address)` which is basically a way to do the same as `open_reading_pipe(RF24_RX_ADDR.P1, address)` so it has been removed to consolidate the code a bit. 17 | 18 | * **Removed** `set_remote_address(address)` which is basically a way to do the same as `open_writing_pipe(address)`so it has been removed to consolidate the code a bit. 19 | 20 | * **Removed** `from_config(...)` that supported reading configuration from a `ConfigParser` instance. Since different projects usually wants different ways of doing configuration, this function seemed most of all like dead code walking. 21 | 22 | * **Changed** `open_writing_pipe(address)` so that it does NOT pad addresses anymore. This means that you must provide an address of the same length as what has been specified in the `set_address_bytes(size)` function. If the size of the address does not match an exception is raised. Please refer to the new `make_address()` method which is used to check addresses. 23 | 24 | * **Changed** `open_reading_pipe(pipe, address)` so that it does NOT pad addresses anymore. This means that you must provide an address of the same length as what has been specified in the `set_address_bytes(size)` function. If the size of the address does not match an exception is raised. Please refer to the new `make_address()` method which is used to check addresses. 25 | 26 | ### Other Changes 27 | 28 | * **Added** `make_address(address)` which take an address argument and converts it to a list of bytes to be used when updating addresses in the NRF24L01 module. The `make_address()` method is using in `open_reading_pipe()` and `open_writing_pipe()` to ensure correct addresses are specified. For flexibility you can specify a string (Python `str`) of ASCII characters, Python `bytes` or `bytearray`, a Python `list` of integers (0 <= n < 256) or a Python `int` which will be converted to bytes using `address.to_bytes(width, 'little')`. This means that all the following addresses are valid (assuming a 5 byte address width): 29 | 30 | * `'1SNSR'` - string of ASCII characters. 31 | 32 | * `b'\x01SNSR'` or `bytes('1SNSR', 'ascii')` - bytes. 33 | 34 | * `bytearray('1SNSR', 'ascii')` - byte array. 35 | 36 | * `[1, 83, 78, 83, 82]` or `[49, 83, 78, 83, 82]` - list of integers. 37 | 38 | * `0xDEADBEEF01` - integer which will be encoded as `[1, 239, 190, 173, 222]`. Notice the byte value `1` at the first position in the array. This is because the integer is converted as little endian. 39 | 40 | * **Added** `get_writing_address()` which returns the transmission address as `bytes`. 41 | 42 | * **Added** `get_reading_address(pipe)` which returns the reception address as bytes. **PLEASE NOTE**: When returning addresses for P2, P3, P4, and P4, the address of P1 is first read and the first byte is replaced by the one-byte address of P2, P3, P4, or P5. After that the byte array is truncated to the width of the address as set by `set_address_bytes()`. 43 | 44 | * **Added** `get_retries()` which returns the number of retries for the last sent package. The maximum number of retries can be set with the new `set_retransmission` method. See below. 45 | 46 | * **Added** `get_packages_lost()` which returns the number of packages lost since the `PLOS_CNT` in the `OBSERVE_TX` register was last reset. You can reset the value before sending by using the new method `reset_packages_lost()` method, and then check if it is greater than 0 after transmission to see if your package was lost. 47 | 48 | * **Added** `reset_packages_lost()` which resets the `PLOS_CNT` of the `OBSERVE_TX` register. 49 | 50 | * **Added** `close_reading_pipe(pipe)` which closes the pipe specified for reception of packages until it is reopened. **PLEASE NOTE** If you close the P0 RX address you will not be able to get acknowledgements for packages sent until you call `open_writing_pipe(address)` again, or you call `open_reading_pipe(RF24_RX_ADDR.P0, address)` where `address` is the address of the writing pipe. 51 | 52 | * **Added** `close_all_reading_pipes()` which closes all reading pipes, including P0 which is used for receiving acknowledgements for packages sent. See above. 53 | 54 | * **Added** `reset_reading_pipes()` which disable reading from address specified for P2, P3, P4, and P5, while P0 and P1 are kept open. 55 | 56 | * **Added** `get_channel()` which returns the channel the NRF24L01 module is communicating on. 57 | 58 | * **Added** `get_address_bytes()` which returns the number of significant bytes in addresses. 59 | 60 | * **Added** `get_crc_bytes()` which returns the number of CRC bytes used. The number of CRC bytes can be set using `set_crc_bytes(crc_bytes)` with `RF24_CRC.DISABLED`, `RF24_CRC.BYTES_1`, or `RF24_CRC.BYTES_2` as valid values. 61 | 62 | * **Added** `get_data_rate()` which returns the current data rate as a `RF24_DATA_RATE` enumeration value. 63 | 64 | * **Added** `set_retransmission(delay, count)` that sets the retransmission properties of the NRF24L01 module. The `delay` parameter accepts values between 0 and 15 and determining the delay between retransmissions. The delay is calculated as `(delay + 1) * 250` yielding a value between 250 µs and 4000 µs (4 ms.). The `count` parameter accepts a value between 0 and 15 and determins the number of retransmissions attempted. The default value set is 1 and 15 giving up to 15 retransmissions with a 500 µs delay between each retransmission. 65 | 66 | * **Added** `get_retransmission()` returning a (delay, count) tuple for the current retransmission configuration. See details above. 67 | 68 | * **Added** `disable_crc()` which disables the use of CRC. This is the same as calling `set_crc_bytes(RF24_CRC.DISABLED)`. 69 | 70 | * **Added** `enable_crc()` which enables CRC checks. 71 | 72 | * **Added** `is_crc_enabled()` which return `True` or `False` depending on if CRC is enabled or not. 73 | 74 | * **Changed** `power_up_tx()`, `power_up_rx()`, `power_down()` to reflect changes to CRC enable/disable. 75 | 76 | * **Changed** `set_payload_size(size)` does not update NRF24L01 module, but sets the payload size as default to be used with `open_reading_pipe()`. 77 | 78 | * **Changed** `open_reading_pipe(pipe, address, size=None)` to take an additional `size` parameter determining the payload size for the particular pipe being opened. If no value is provided for `size` the value set by `set_payload_size(size)` is used instead. This should ensure backward compatibility. 79 | 80 | * **Added** NRF24 contructor parameter `pa_level` with a default of `RF24_PA.MAX`. 81 | 82 | * **Added** `wait_until_sent` method polling the status of NRF24L01 module until package has been sent or a timeout occurs. 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bjarne Hansen. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /PyPI.md: -------------------------------------------------------------------------------- 1 | # nrf24 for Python 2 | 3 | **PLEASE NOTE**: The new version 2.0.0 contains **breaking** changes compared to previous version 1.1.1. Please make sure to consult the `CHANGES.md` for details. 4 | 5 | This package implements 2.4Ghz communication using NRF24L01 modules on a Raspberry Pi using Python via the pigpio daemon. 6 | 7 | The code is based on a modified version of some example code found on [StackExchange](https://raspberrypi.stackexchange.com/questions/77290/nrf24l01-only-correctly-retrieving-status-and-config-registers). The author of the original code is also the author of the ```pigpio``` library found here http://abyz.me.uk/rpi/pigpio/. 8 | 9 | I have obtained the original authors approval to modify and distribute the code anyway I want. So, I have created a very basic Python package and published it on PyPI under a MIT license. 10 | 11 | The ```nrf24``` packages depends on the ```pigpio``` package that is available via PyPI as well. 12 | 13 | For further information please refer to the github project page at https://github.com/bjarne-hansen/py-nrf24. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NRF24L01 for Python 2 | 3 | This package implement 2.4Ghz communication using NRF24L01+ modules on a Raspberry Pi using Python. 4 | 5 | ## Changes 6 | 7 | * **Version 2.0.0** - Released to PyPi.org on April 8th, 2021. 8 | 9 | This version contains **breaking** changes compared to version 1.1.1. Make sure to review [**CHANGES.md**](CHANGES.md) and make changes to you client code accordingly. 10 | 11 | * **Version 1.1.1** - Released to PyPi.org on September 20th, 2020. 12 | * **Version 1.1.0** - Released to PyPi.org on September 20th, 2020. 13 | * **Version 1.0.2** - Released to PyPi.org on May 8th, 2020. 14 | * **Version 1.0.1** - Released to PyPi.org on May 8th, 2020. 15 | * **Version 1.0.0** - Released to PyPi.org on May 8th, 2020. 16 | * **Version 0.8.0** - Released to PyPi.org on May 1st, 2020. 17 | * **Version 0.5.2** - Released to PyPi.org on April 20th, 2020. 18 | 19 | ## Background 20 | 21 | The code is based on a modified version of some example code found on [StackExchange](https://raspberrypi.stackexchange.com/questions/77290/nrf24l01-only-correctly-retrieving-status-and-config-registers). The author of the original code is also the author of the ```pigpio``` library found here http://abyz.me.uk/rpi/pigpio/. 22 | 23 | I have obtained the original authors approval to modify and distribute the code anyway I want. So, I have created a very basic Python package and published it on PyPI under a MIT license. 24 | 25 | The ```nrf24``` packages depends on the ```pigpio``` package that is available via PyPI as well. Before installing and running any of the code and examples below, please make sure you the ```pigpid``` daemon running on your Raspberry. This is a library/server that provides access to the GPIO ports of the Raspberry. 26 | 27 | Details avalable at http://abyz.me.uk/rpi/pigpio/download.html 28 | 29 | Quick installation of `pigpio` on Raspbian: 30 | 31 | sudo apt-get update 32 | sudo apt-get install pigpio python-pigpio python3-pigpio 33 | 34 | ## Installing 35 | 36 | You may want to create a virtual environment before installing the `nrf24` package which depends on the `pigpio` package. 37 | 38 | $ pip install nrf24 39 | 40 | ## Examples 41 | 42 | All examples in the `test` folder can be run as command line programs. They all take optional command line arguments 43 | to specify the `hostname` (default: `localhost`) and the `port` (default: `8888`) of the `pigpio` deamon. Most of them 44 | also takes one or more addresses to use. All should have sensible defaults, so running them without arguments should 45 | be an OK first approach to testing your setup. 46 | 47 | All test have been run on a Raspberry Pi 4 and a Raspberry Pi Zero Wireless equipped with 2 x NRF24L01+ modules each. 48 | 49 | The `int-sender.py` and `int-receiver.py` examples requires extra wiring connenting the IRQ PIN of the NRF24L01+ module 50 | to a GPIO on the Raspberry. This wiring is shown in **"Raspberry Pi with Single NRF24L01+ Module (IRQ)"**. 51 | 52 | The `multi-sender.py` and `multi-receiver.py` examples requires two NRF24L01+ modules. The wiring for that setup can be 53 | seen in **"Raspberry Pi with Dual NRF24L01+ Modules"** below. 54 | 55 | The rest of the Raspberry Pi examples runs with the IRQ wiring as described above, or a simpler wiring like the one shown 56 | in **"Raspberry Pi with Single NRF24L01+ Module"** below. 57 | 58 | | Command Line | Comments | 59 | | ------------ | -------- | 60 | | `python test/simple-sender.py` | Emulates a process sending sensor readings every 10 seconds using a **dynamic** payload size (default sending address is `1SNSR`). | 61 | | `python test/simple-receiver.py` | Emulates a receiving process receiving sensor readings from the corresponding sender using a **dynamic** payload size (default listening address `1SNSR`). | 62 | | `python test/fixed-sender.py` | Emulates a process sending sensor readings every 10 seconds using a **fixed** payload size (default sending address is `1SNSR`). | 63 | | `python test/fixed-receiver.py` | Emulates a receiving process receiving sensor readings from the corresponding sender using a **fixed** payload size (default listening address `1SNSR`). | 64 | | `python test/mixed-sender.py` | Shows an example of sending both **fixed** and **dynamic** payload sized messages. Suggested address for fixed messages is `FTEST`, and the suggested address for dynamic messages is `DTEST`. | 65 | | `python test/mixed-receiver.py` | Shows how to configure reading pipes using both **fixed** and **dynamic** message sizes at the same time. | 66 | | `python test/int-sender.py` | Shows how to use interrupt to detect that a message has been sent (default sending address `1SNSR`). | 67 | | `python test/int-receiver.py` | Shows how to use interrupt to detect that a message has been received (default listening address `1SNSR`). | 68 | | `python test/rr-client.py` | Shows example of how to send a request to a server with a reply to address included in the message, and then switching to RX mode to receive the response from the server (default server (TX) address is `1SRVR` and default reply to address (RX) is `1CLNT`) | 69 | | `python test/rr-server.py` | Shows example of a server listening for requests and returning a response to the client (default server (RX) address is `1SRVR`). | 70 | | `python test/ack-sender.py` | Sends message to the receiver every 10 seconds, expecting a payload sent back with the acknowledgement (default sender address `1ACKS`). | 71 | | `python test/ack-receiver.py` | Receives message and sends acknowledgement message with payload (default listen address `1ACKS`).| 72 | | `python test/multi-sender.py` | Sends messages using 2 x NRF24L01+ modules connected to the same Raspberry Pi (defult send addresses `1SRVR` and `2SRVR`). | 73 | | `python test/multi-sender.py` | receives messages using 2 x NRF24L01+ modules connected to the same Raspberry Pi (defult listen addresses `1SRVR` and `2SRVR`). | 74 | 75 | If you do not have multiple Raspberry Pi computers, you can run some of the test programs on Arduino. In the `arduino/` directory are sender programs equivalent with 76 | some of those described above. The wiring for the Arduino Nano can be seen in **"Arduino Nano with DHT22 and NRF24L01+"** below. Unlike it's Raspberry Pi counterparts 77 | the Arduino examples have been done with an actual DHT22 sensor connected so that we do not need to emulate sensor readings. 78 | 79 | | Arduino Code | Comments | 80 | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------ | 81 | | `arduino/simple-sender` | Sends temperature and humidity readings to the `simple-receiver.py` counterpart. | 82 | | `arduino/fixed-sender` | Sends temperature and humidity readings to the `fixed-receiver.py` counterpart. | 83 | | `arduino/mixed-sender` | Sends temperature and humidity readings to the `mixed-receiver.py` counterpart. | 84 | | `arduino/rr-client` | Executes request/response calls against its `rr-server.py` counterpart. | 85 | | `arduino/ack-sender` | Sends temperature and humidity readings to the `ack-receiver.py` counterpart and receives acknowledgements with payload. | 86 | 87 | ## Wiring 88 | 89 | ### Raspberry Pi with Single NRF24L01+ Module (IRQ) 90 | 91 | ![Raspberry Pi with Single NRF24L01+ Module (IRQ)](https://github.com/bjarne-hansen/py-nrf24/blob/master/doc/pizw-nrf24-1-irq_bb.png "Raspberry Pi with Single NRF24L01+ Module (IRQ)") 92 | 93 | ### Raspberry Pi with Dual NRF24L01+ Modules 94 | 95 | The `multi-sender.py` and `multi-receiver.py` examples requires two NRF24L01+ modules wired to each Raspberry Pi. 96 | 97 | ![Raspberry Pi with Dual NRF24L01+ Modules](https://github.com/bjarne-hansen/py-nrf24/blob/master/doc/pizw-nrf24-2_bb.png "Raspberry Pi with Dual NRF24L01+ Modules") 98 | 99 | ### Raspberry Pi with Single NRF24L01+ Module 100 | 101 | All the examples, except the `multi-sender.py`, `multi-receiver.py`, `int-sender.py`, and `int-receiver.py` ones will 102 | run with the following wiring of a single NRF24L01+ module. 103 | 104 | ![Raspberry Pi with Single NRF24L01+ Module](https://github.com/bjarne-hansen/py-nrf24/blob/master/doc/pizw-nrf24-1_bb.png "Raspberry Pi with Single NRF24L01+ Module") 105 | 106 | ### Arduino Nano with DHT22 and NRF24L01+ 107 | 108 | The Arduino examples in `arduino/` can all run with the following wiring. 109 | 110 | ![Arduino Nano with DHT22 and NRF24L01+](https://github.com/bjarne-hansen/py-nrf24/blob/master/doc/nano-nrf24-1_bb.png "Arduino Nano with DHT22 and NRF24L01+") 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /arduino/ack-sender/ack-sender.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define PIN_DHT 2 // PIN for DHT sensor communication. 6 | 7 | #define PIN_RF24_CSN 9 // CSN PIN for RF24 module. 8 | #define PIN_RF24_CE 10 // CE PIN for RF24 module. 9 | 10 | #define NRF24_CHANNEL 100 // 0 ... 125 11 | #define NRF24_CRC_LENGTH RF24_CRC_16 // RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 for 16-bit 12 | #define NRF24_DATA_RATE RF24_250KBPS // RF24_2MBPS, RF24_1MBPS, RF24_250KBPS 13 | #define NRF24_DYNAMIC_PAYLOAD 1 14 | #define NRF24_PAYLOAD_SIZE 32 // Max. 32 bytes. 15 | #define NRF24_PA_LEVEL RF24_PA_MAX // RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX 16 | #define NRF24_RETRY_DELAY 5 // Delay bewteen retries, 1..15. Multiples of 250µs. 17 | #define NRF24_RETRY_COUNT 15 // Number of retries, 1..15. 18 | 19 | #define PROTOCOL 0x01 // 0x01 (byte), temperature (float), humidity (float) 20 | // Python 1: " ms_between_reads) { 57 | // Read sensor values every "ms_between_read" milliseconds. 58 | 59 | // Read the humidity and temperature. 60 | float t, h; 61 | h = dht.readHumidity(); 62 | t = dht.readTemperature(); 63 | 64 | // Report the temperature and humidity. 65 | Serial.print("Sensor values: temperature="); Serial.print(t); 66 | Serial.print(", humidity="); Serial.println(h); 67 | 68 | // Stop listening on the radio (we can't both listen and send). 69 | radio.stopListening(); 70 | 71 | // Send the data ... 72 | send_reading(PROTOCOL, t, h); 73 | 74 | // Start listening again. 75 | radio.startListening(); 76 | 77 | // Register that we have read the temperature and humidity. 78 | last_reading = millis(); 79 | } 80 | } 81 | 82 | void send_reading(byte protocol, float temperature, float humidity) 83 | { 84 | int offset = 0; 85 | Serial.println("Preparing payload."); 86 | memcpy(payload + offset, (byte *)(&protocol), sizeof(protocol)); offset += sizeof(protocol); 87 | memcpy(payload + offset, (byte *)(&temperature), sizeof(temperature)); offset += sizeof(temperature); 88 | memcpy(payload + offset, (byte *)(&humidity), sizeof(humidity)); offset += sizeof(humidity); 89 | Serial.print("Bytes packed: "); Serial.println(offset); 90 | 91 | if (radio.write(payload, offset)) { 92 | if (!radio.available()) { 93 | Serial.println("No acknowledgement payload."); 94 | } 95 | else { 96 | radio.read(&payload, sizeof(payload)); 97 | long next_id; 98 | memcpy(&next_id, payload, 4); 99 | Serial.print("Acknowledgement payload: "); Serial.println(next_id); 100 | } 101 | Serial.print("Payload sent successfully. Retries="); Serial.println(radio.getARC()); 102 | } 103 | else { 104 | Serial.print("Failed to send payload. Retries="); Serial.println(radio.getARC()); 105 | } 106 | } 107 | 108 | void nrf24_setup() 109 | { 110 | radio.begin(); 111 | radio.setAutoAck(true); 112 | radio.enableAckPayload(); 113 | radio.setPALevel(NRF24_PA_LEVEL); 114 | radio.setRetries(NRF24_RETRY_DELAY, NRF24_RETRY_COUNT); 115 | radio.setDataRate(NRF24_DATA_RATE); 116 | radio.setChannel(NRF24_CHANNEL); 117 | radio.setCRCLength(NRF24_CRC_LENGTH); 118 | radio.setPayloadSize(NRF24_PAYLOAD_SIZE); 119 | radio.openWritingPipe(rf24_tx); 120 | radio.stopListening(); 121 | } 122 | -------------------------------------------------------------------------------- /arduino/fixed-sender/fixed-sender.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #define PIN_DHT 2 // PIN for DHT sensor communication. 7 | 8 | #define PIN_RF24_CSN 9 // CSN PIN for RF24 module. 9 | #define PIN_RF24_CE 10 // CE PIN for RF24 module. 10 | 11 | #define NRF24_CHANNEL 100 // 0 ... 125 12 | #define NRF24_CRC_LENGTH RF24_CRC_16 // RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 for 16-bit 13 | #define NRF24_DATA_RATE RF24_250KBPS // RF24_2MBPS, RF24_1MBPS, RF24_250KBPS 14 | #define NRF24_DYNAMIC_PAYLOAD 1 15 | #define NRF24_PAYLOAD_SIZE 9 // Max. 32 bytes. 16 | #define NRF24_PA_LEVEL RF24_PA_MIN // RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX 17 | #define NRF24_RETRY_DELAY 5 // Delay bewteen retries, 1..15. Multiples of 250µs. 18 | #define NRF24_RETRY_COUNT 15 // Number of retries, 1..15. 19 | 20 | #define PROTOCOL 0x01 // 0x01 (byte), temperature (float), humidity (float) 21 | // Python 1: " ms_between_reads) { 58 | // Read sensor values every "ms_between_read" milliseconds. 59 | 60 | // Read the humidity and temperature. 61 | float t, h; 62 | h = dht.readHumidity(); 63 | t = dht.readTemperature(); 64 | 65 | // Report the temperature and humidity. 66 | Serial.print("Sensor values: temperature="); Serial.print(t); 67 | Serial.print(", humidity="); Serial.println(h); 68 | 69 | // Stop listening on the radio (we can't both listen and send). 70 | radio.stopListening(); 71 | 72 | // Send the data ... 73 | send_reading(PROTOCOL, t, h); 74 | 75 | // Start listening again. 76 | radio.startListening(); 77 | 78 | // Register that we have read the temperature and humidity. 79 | last_reading = millis(); 80 | } 81 | } 82 | 83 | void send_reading(byte protocol, float temperature, float humidity) 84 | { 85 | int offset = 0; 86 | Serial.println("Preparing payload."); 87 | memcpy(payload + offset, (byte *)(&protocol), sizeof(protocol)); offset += sizeof(protocol); 88 | memcpy(payload + offset, (byte *)(&temperature), sizeof(temperature)); offset += sizeof(temperature); 89 | memcpy(payload + offset, (byte *)(&humidity), sizeof(humidity)); offset += sizeof(humidity); 90 | Serial.print("Bytes packed: "); Serial.println(offset); 91 | 92 | if (radio.write(payload, offset)) { 93 | Serial.print("Payload sent successfully. Retries="); Serial.println(radio.getARC()); 94 | } 95 | else { 96 | Serial.print("Failed to send payload. Retries="); Serial.println(radio.getARC()); 97 | } 98 | } 99 | 100 | void nrf24_setup() 101 | { 102 | radio.begin(); 103 | radio.disableDynamicPayloads(); 104 | radio.setAutoAck(true); 105 | radio.setPALevel(NRF24_PA_LEVEL); 106 | radio.setRetries(NRF24_RETRY_DELAY, NRF24_RETRY_COUNT); 107 | radio.setDataRate(NRF24_DATA_RATE); 108 | radio.setChannel(NRF24_CHANNEL); 109 | radio.setCRCLength(NRF24_CRC_LENGTH); 110 | radio.setPayloadSize(NRF24_PAYLOAD_SIZE); 111 | radio.openWritingPipe(rf24_tx); 112 | radio.stopListening(); 113 | } 114 | -------------------------------------------------------------------------------- /arduino/mixed-sender/mixed-sender.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define PIN_DHT 2 // PIN for DHT sensor communication. 6 | 7 | #define PIN_RF24_CSN 9 // CSN PIN for RF24 module. 8 | #define PIN_RF24_CE 10 // CE PIN for RF24 module. 9 | 10 | #define NRF24_CHANNEL 100 // 0 ... 125 11 | #define NRF24_CRC_LENGTH RF24_CRC_16 // RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 for 16-bit 12 | #define NRF24_DATA_RATE RF24_250KBPS // RF24_2MBPS, RF24_1MBPS, RF24_250KBPS 13 | #define NRF24_PAYLOAD_SIZE 9 // Max. 32 bytes. 14 | #define NRF24_PA_LEVEL RF24_PA_MIN // RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX 15 | #define NRF24_RETRY_DELAY 5 // Delay bewteen retries, 1..15. Multiples of 250µs. 16 | #define NRF24_RETRY_COUNT 15 // Number of retries, 1..15. 17 | 18 | #define PROTOCOL 0x01 // 0x01 (byte), temperature (float), humidity (float) 19 | // Python 1: " ms_between_reads) { 59 | 60 | // Read the humidity and temperature. 61 | float t, h; 62 | h = dht.readHumidity(); 63 | t = dht.readTemperature(); 64 | 65 | // Report the temperature and humidity. 66 | Serial.print("Sensor values: temperature="); Serial.print(t); 67 | Serial.print(", humidity="); Serial.println(h); 68 | 69 | // Stop listening on the radio (we can't both listen and send). 70 | radio.stopListening(); 71 | 72 | // Send fixed payload size message ... 73 | radio.setPayloadSize(9); 74 | radio.disableDynamicPayloads(); 75 | radio.openWritingPipe(rf24_fixed_tx); 76 | send_reading(PROTOCOL, t, h); 77 | 78 | // Send dynamic payload size message ... 79 | radio.setPayloadSize(32); 80 | radio.enableDynamicPayloads(); 81 | radio.openWritingPipe(rf24_dynamic_tx); 82 | send_dynamic(count); 83 | 84 | // Start listening again. 85 | radio.startListening(); 86 | 87 | // Register that we have read the temperature and humidity. 88 | last_reading = millis(); 89 | count++; 90 | } 91 | } 92 | 93 | void send_dynamic(unsigned long count) { 94 | 95 | unsigned long payload_size = count % 32 + 1; 96 | Serial.print("Dynamic payload size: "); Serial.println(payload_size); 97 | 98 | for (int i = 0; i < payload_size; i++) { 99 | payload[i] = 0xFF; 100 | } 101 | if (radio.write(payload, payload_size)) { 102 | Serial.print("Payload sent successfully. Retries="); Serial.println(radio.getARC()); 103 | } 104 | else { 105 | Serial.print("Failed to send payload. Retries="); Serial.println(radio.getARC()); 106 | } 107 | 108 | } 109 | 110 | void send_reading(byte protocol, float temperature, float humidity) 111 | { 112 | int offset = 0; 113 | Serial.println("Preparing payload."); 114 | memcpy(payload + offset, (byte *)(&protocol), sizeof(protocol)); offset += sizeof(protocol); 115 | memcpy(payload + offset, (byte *)(&temperature), sizeof(temperature)); offset += sizeof(temperature); 116 | memcpy(payload + offset, (byte *)(&humidity), sizeof(humidity)); offset += sizeof(humidity); 117 | Serial.print("Bytes packed: "); Serial.println(offset); 118 | 119 | if (radio.write(payload, offset)) { 120 | Serial.print("Payload sent successfully. Retries="); Serial.println(radio.getARC()); 121 | } 122 | else { 123 | Serial.print("Failed to send payload. Retries="); Serial.println(radio.getARC()); 124 | } 125 | } 126 | 127 | void nrf24_setup() 128 | { 129 | radio.begin(); 130 | radio.setAutoAck(true); 131 | radio.setPALevel(NRF24_PA_LEVEL); 132 | radio.setRetries(NRF24_RETRY_DELAY, NRF24_RETRY_COUNT); 133 | radio.setDataRate(NRF24_DATA_RATE); 134 | radio.setChannel(NRF24_CHANNEL); 135 | radio.setCRCLength(NRF24_CRC_LENGTH); 136 | radio.stopListening(); 137 | } 138 | -------------------------------------------------------------------------------- /arduino/rr-client/rr-client.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define PIN_RF24_CSN 9 // CSN PIN for RF24 module. 5 | #define PIN_RF24_CE 10 // CE PIN for RF24 module. 6 | 7 | #define NRF24_CHANNEL 100 // 0 ... 125 8 | #define NRF24_CRC_LENGTH RF24_CRC_16 // RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 for 16-bit 9 | #define NRF24_DATA_RATE RF24_250KBPS // RF24_2MBPS, RF24_1MBPS, RF24_250KBPS 10 | #define NRF24_DYNAMIC_PAYLOAD 1 11 | #define NRF24_PAYLOAD_SIZE 32 // Max. 32 bytes. 12 | #define NRF24_PA_LEVEL RF24_PA_MIN // RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX 13 | #define NRF24_RETRY_DELAY 10 // Delay bewteen retries, 1..15. Multiples of 250µs. 14 | #define NRF24_RETRY_COUNT 15 // Number of retries, 1..15. 15 | 16 | byte rf24_tx[6] = "1SRVR"; // Address used when transmitting data. 17 | byte rf24_rx[6] = "1CLNT"; // Address used when receiving data. 18 | byte payload[32]; // Payload bytes. Used both for transmitting and receiving 19 | 20 | unsigned long ms_between_requests = 10000; // 10000 ms = 10 seconds 21 | unsigned long last_reading = 0; // Milliseconds since last measurement was read. 22 | 23 | RF24 radio(PIN_RF24_CE, PIN_RF24_CSN); // Instance of RF24. 24 | 25 | void setup() { 26 | // Initialize serial communication. 27 | Serial.begin(115200); 28 | printf_begin(); 29 | delay(100); 30 | 31 | // Show that program is starting. 32 | Serial.println("\n\nNRF24L01 Arduino Request/Response Client."); 33 | 34 | // Configure the NRF24 tranceiver. 35 | Serial.println("Configure NRF24 ..."); 36 | nrf24_setup(); 37 | 38 | // Show debug information for NRF24 tranceiver. 39 | radio.printDetails(); 40 | } 41 | 42 | void loop() { 43 | if (millis() - last_reading > ms_between_requests) { 44 | // Send a request once in a while ... 45 | send_request(); 46 | } 47 | } 48 | 49 | void send_request() { 50 | // Step out of listening mode ... 51 | radio.stopListening(); 52 | radio.flush_tx(); 53 | 54 | // Chose a random command 0x01 or 0x02 ... 55 | unsigned int cmd = random(1, 3); 56 | byte addr_len = 5; 57 | 58 | // Prepare payload ... 59 | int offset = 0; 60 | memcpy(payload + offset, (byte *)(&cmd), sizeof(cmd)); offset += sizeof(cmd); 61 | memcpy(payload + offset, &addr_len, sizeof(addr_len)); offset += sizeof(addr_len); 62 | memcpy(payload + offset, (byte *)(&rf24_rx), sizeof(rf24_rx)); offset += sizeof(rf24_rx) - 1; 63 | 64 | // Send request ... 65 | Serial.print("Request: cmd="); Serial.println(cmd); 66 | if (radio.write(payload, offset)) { 67 | // Request was successful. 68 | Serial.print("Success. Retries="); Serial.println(radio.getARC()); 69 | 70 | // Read response to our request. 71 | read_response(); 72 | } 73 | else { 74 | Serial.print(">>> ERROR. Retries="); Serial.println(radio.getARC()); 75 | radio.startListening(); 76 | } 77 | 78 | last_reading = millis(); 79 | 80 | } 81 | 82 | void read_response() { 83 | unsigned int cmd; 84 | boolean relay; 85 | 86 | radio.startListening(); 87 | // Wait here until we get a response, or timeout 88 | unsigned long started_waiting_at = millis(); 89 | bool timeout = false; 90 | while (!radio.available() && !timeout) { 91 | if (millis() - started_waiting_at > 500 ) { 92 | timeout = true; 93 | } 94 | } 95 | 96 | if (timeout) { 97 | Serial.println("Timeout waiting for response."); 98 | return; 99 | } 100 | 101 | uint8_t plen = radio.getDynamicPayloadSize(); 102 | radio.read(&payload, plen); 103 | 104 | // Extract command from payload 105 | memcpy(&cmd, payload, sizeof(cmd)); 106 | Serial.print("Response: cmd="); Serial.println(cmd); 107 | 108 | if (cmd == 0x01) { 109 | // Response to command #1. 110 | Serial.print("Response #1: uuid="); 111 | print_bytes(payload, 2, 16); 112 | Serial.println(); 113 | } 114 | else if (cmd == 0x02) { 115 | // Response to command #2. 116 | Serial.print("Response #2: relay="); 117 | memcpy(&relay, payload + 2, sizeof(relay)); 118 | Serial.println(relay); 119 | } 120 | else { 121 | // We process only responses to command 0x01 (get uuid) and 0x02 (get relay state). 122 | Serial.print(">>> BAD response received: "); Serial.println(cmd); 123 | } 124 | } 125 | 126 | void print_bytes(byte *buffer, int start, int count) { 127 | for (int i = start; i < start + count; i++) { 128 | if (i > start) Serial.print(":"); 129 | if (buffer[i] < 16) Serial.print("0"); 130 | Serial.print(buffer[i], HEX); 131 | } 132 | } 133 | 134 | void nrf24_setup() 135 | { 136 | radio.begin(); 137 | radio.enableDynamicPayloads(); 138 | radio.setAutoAck(true); 139 | radio.setPALevel(NRF24_PA_LEVEL); 140 | radio.setRetries(NRF24_RETRY_DELAY, NRF24_RETRY_COUNT); 141 | radio.setDataRate(NRF24_DATA_RATE); 142 | radio.setChannel(NRF24_CHANNEL); 143 | radio.setCRCLength(NRF24_CRC_LENGTH); 144 | radio.setPayloadSize(NRF24_PAYLOAD_SIZE); 145 | radio.openWritingPipe(rf24_tx); 146 | radio.openReadingPipe(1, rf24_rx); 147 | radio.startListening(); 148 | } 149 | -------------------------------------------------------------------------------- /arduino/simple-sender/simple-sender.ino: -------------------------------------------------------------------------------- 1 | // DHT sensor library, version 1.4.0 by Adafruit 2 | #include 3 | 4 | // RF24, version 1.3.9, by TMRh20 5 | #include 6 | #include 7 | 8 | #define PIN_DHT 2 // PIN for DHT sensor communication. 9 | 10 | #define PIN_RF24_CSN 9 // CSN PIN for RF24 module. 11 | #define PIN_RF24_CE 10 // CE PIN for RF24 module. 12 | 13 | #define NRF24_CHANNEL 100 // 0 ... 125 14 | #define NRF24_CRC_LENGTH RF24_CRC_16 // RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 for 16-bit 15 | #define NRF24_DATA_RATE RF24_250KBPS // RF24_2MBPS, RF24_1MBPS, RF24_250KBPS 16 | #define NRF24_DYNAMIC_PAYLOAD 1 17 | #define NRF24_PAYLOAD_SIZE 32 // Max. 32 bytes. 18 | #define NRF24_PA_LEVEL RF24_PA_MIN // RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX 19 | #define NRF24_RETRY_DELAY 5 // Delay bewteen retries, 1..15. Multiples of 250µs. 20 | #define NRF24_RETRY_COUNT 15 // Number of retries, 1..15. 21 | 22 | #define PROTOCOL 0x01 // 0x01 (byte), temperature (float), humidity (float) 23 | // Python 1: " ms_between_reads) { 68 | // Read sensor values every "ms_between_read" milliseconds. 69 | 70 | // Read the humidity and temperature. 71 | float t, h; 72 | h = dht.readHumidity(); 73 | t = dht.readTemperature(); 74 | 75 | // Report the temperature and humidity. 76 | Serial.print("Sensor values: temperature="); Serial.print(t); 77 | Serial.print(", humidity="); Serial.println(h); 78 | 79 | // Stop listening on the radio (we can't both listen and send). 80 | radio.stopListening(); 81 | 82 | // Send the data ... 83 | send_reading(PROTOCOL, t, h); 84 | 85 | // Start listening again. 86 | radio.startListening(); 87 | 88 | // Register that we have read the temperature and humidity. 89 | last_reading = millis(); 90 | } 91 | } 92 | 93 | void send_reading(byte protocol, float temperature, float humidity) 94 | { 95 | int offset = 0; 96 | Serial.println("Preparing payload."); 97 | memcpy(payload + offset, (byte *)(&protocol), sizeof(protocol)); offset += sizeof(protocol); 98 | memcpy(payload + offset, (byte *)(&temperature), sizeof(temperature)); offset += sizeof(temperature); 99 | memcpy(payload + offset, (byte *)(&humidity), sizeof(humidity)); offset += sizeof(humidity); 100 | Serial.print("Bytes packed: "); Serial.println(offset); 101 | 102 | if (radio.write(payload, offset)) { 103 | Serial.print("Payload sent successfully. Retries="); Serial.println(radio.getARC()); 104 | } 105 | else { 106 | Serial.print("Failed to send payload. Retries="); Serial.println(radio.getARC()); 107 | } 108 | } 109 | 110 | void nrf24_setup() 111 | { 112 | radio.begin(); 113 | radio.enableDynamicPayloads(); 114 | radio.setAutoAck(true); 115 | radio.setPALevel(NRF24_PA_LEVEL); 116 | radio.setRetries(NRF24_RETRY_DELAY, NRF24_RETRY_COUNT); 117 | radio.setDataRate(NRF24_DATA_RATE); 118 | radio.setChannel(NRF24_CHANNEL); 119 | radio.setCRCLength(NRF24_CRC_LENGTH); 120 | radio.setPayloadSize(NRF24_PAYLOAD_SIZE); 121 | radio.openWritingPipe(rf24_tx); 122 | radio.stopListening(); 123 | } 124 | -------------------------------------------------------------------------------- /doc/nano-nrf24-1.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/nano-nrf24-1.fzz -------------------------------------------------------------------------------- /doc/nano-nrf24-1_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/nano-nrf24-1_bb.png -------------------------------------------------------------------------------- /doc/notes.txt: -------------------------------------------------------------------------------- 1 | 2 | __init__ power_down(), power_up_rx() 3 | send() power_up_tx() 4 | 5 | RF24_PA - Enumeration of PA values. 6 | RF24_DATA_RATE - Enumeration of data rates. 7 | RF24_CRC - Enumeration of CRC options. 8 | RF24_PAYLOAD - Enumeration of payload options. 9 | SPI_CHANNEL - Enumeration of SPI channels. 10 | RF24_RX_ADDR - Enumeration of NRF24L01 receiver channels (pipes). 11 | 12 | __init__ 13 | 14 | _pi Reference to GPIO daemon 15 | _ce_pin CE PIN. 16 | _spi_handle SPI handle created from spi_channel and spi_speed 17 | _power_tx Keep track of if we are in transmission mode (__init__, is_sending, power_up_tx, power_up_rx) 18 | 19 | _payload_size Same payload size for all pipes. ACK, DYNAMIC, 1..32 (set_payload_size, send, get_payload, _configure_payload) 20 | _address_width Address width. (__init__, set_address_bytes, open_writing_pipe (padding), open_reading_pipe (padding),) 21 | _crc_bytes CRC bytes (__init__, set_crc_bytes, power_up_tx, power_up_rx, power_down) 22 | _padding Padding for address and payload. (__init__, set_padding, send, open_writing_pipe, open_reading_pipe,) 23 | 24 | DEFAULTS: 25 | PI - 26 | CE - 27 | spi_channel MAIN_CE0 28 | spi_speed 50e3 29 | channel: 76 30 | retransmission: (1, 500) 31 | payload_size: MAX=32 32 | padding: ' ' 33 | address_bytes: 5 34 | crc_bytes: 2 35 | data_rate: RF24_DATA_RATE.RATE_1MBPS 36 | pa_level: RF24_PA.MAX 37 | 38 | 39 | INITIALIZATION: 40 | =============== 41 | + set_channel(channel) 42 | + get_channel() 43 | 44 | + set_retansmission(delay, retries) 45 | + get_retransmission() 46 | 47 | + set_payload_size(size) 48 | + get_payload_size() 49 | 50 | + set_padding(padding) 51 | - get_padding() 52 | 53 | + set_address_bytes(bytes) 54 | + get_address_bytes() 55 | 56 | + disable_crc() 57 | + enable_crc() 58 | + is_crc_enabled() 59 | + set_crc_bytes(bytes) 60 | + get_crc_bytes() 61 | 62 | + set_data_rate(rate) 63 | + get_data_rate() 64 | 65 | + set_pa_level(level) 66 | + get_pa_level() 67 | 68 | + get_spi_handle 69 | 70 | + show_registers() 71 | 72 | SENDING: 73 | ======== 74 | + open_writing_pipe(addr) - Open writing pipe. 75 | + get_writing_address() - Get the writing address. 76 | 77 | + send(message) - Send message. 78 | + get_retries() - Get number of retries done while sending message. 79 | + get_packages_lost() - Get number of packages lost (since the counter was last reset) 80 | + reset_packages_lost() - Reset number of packages lost to 0. 81 | + reset_plos() - Reset number of packages lost to 0. 82 | 83 | + ack_payload() - Send acknowledgement payload. 84 | 85 | + is_sending() - Boolean indicating of the module is sending (poll until False). 86 | 87 | 88 | RECEIVING: 89 | ========== 90 | + open_reading_pipe(pipe, addr) - Open reading pipe (pipe [0..5] || [NRF24_RX_ADDR.P0..NRF24_RX_ADDR.P5]) 91 | 92 | + close_reading_pipe(pipe) - Closes (disables) the reading pipe specified. 93 | + close_all_reading_pipes(): - Close (disable) all reading pipes. 94 | + reset_reading_pipes() - Reset reading pipes to default valie (P0 and P1 enabled) 95 | 96 | + get_reading_address(pipe) - Get the reading address for the pipe specified. 97 | 98 | + data_ready() - Get boolean indicating if data is ready. 99 | + data_pipe() - Get pipe that has data ready. 100 | + data_ready_pipe() - Get (boolean, pipe) telling if data is ready, and on what pipe. 101 | + get_payload() - Get the payload available. 102 | 103 | + get_status() - Data ready, data sent, max retries, rx pipe, tx full 104 | 105 | 106 | + power_up_tx() - Powers up the NRF24L01 and places it in TX mode. 107 | + power_up_rx() - Powers up the NRF24L01 and places it in RX mode. 108 | + power_down() - Powers down the NRF24L01. 109 | 110 | 111 | LOW LEVEL: 112 | ========== 113 | + get_spi_handle() 114 | 115 | + set_ce() - Set CE PIN high. 116 | + unset_ce() - Set CE PIN low. 117 | + flush_rx() - Flush read buffers. 118 | + flush_tx() - Flush write buffers. 119 | 120 | + format_config() 121 | + format_en_aa() 122 | + format_en_rxaddr() 123 | + format_setup_aw() 124 | + format_setup_retr() 125 | + format_rf_ch() 126 | + format_rf_setup() 127 | + format_status() 128 | + format_observe_tx() 129 | + format_rpd() 130 | + format_rx_addr_px() 131 | + format_tx_addr() 132 | + format_rx_pw_px() 133 | + format_fifo_status() 134 | + format_dynpd() 135 | + format_dynpd() 136 | 137 | 138 | PA level 139 | MIN = -18 dbm 140 | LOW = -12 dBm 141 | HIGH = -6 dBm 142 | MAX = 0 dbm 143 | 144 | 1. MAJOR version when you make incompatible API changes, 145 | 2. MINOR version when you add functionality in a backwards compatible manner, and 146 | 3. PATCH version when you make backwards compatible bug fixes. 147 | -------------------------------------------------------------------------------- /doc/pizw-nrf24-1-irq.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/pizw-nrf24-1-irq.fzz -------------------------------------------------------------------------------- /doc/pizw-nrf24-1-irq_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/pizw-nrf24-1-irq_bb.png -------------------------------------------------------------------------------- /doc/pizw-nrf24-1.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/pizw-nrf24-1.fzz -------------------------------------------------------------------------------- /doc/pizw-nrf24-1_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/pizw-nrf24-1_bb.png -------------------------------------------------------------------------------- /doc/pizw-nrf24-2.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/pizw-nrf24-2.fzz -------------------------------------------------------------------------------- /doc/pizw-nrf24-2_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjarne-hansen/py-nrf24/043cec77342c85c246381c34e6b9a8a3b13868a9/doc/pizw-nrf24-2_bb.png -------------------------------------------------------------------------------- /pythonpath: -------------------------------------------------------------------------------- 1 | export PYTHONPATH=lib:src:test 2 | 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pigpio==1.46 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | # This includes the license file(s) in the wheel. 3 | # https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file 4 | license_files = LICENSE 5 | 6 | [bdist_wheel] 7 | # This flag says to generate wheels that support both Python 2 and Python 8 | # 3. If your code will not run unchanged on both Python 2 and 3, you will 9 | # need to generate separate wheels for each Python version that you 10 | # support. Removing this line (or setting universal to 0) will prevent 11 | # bdist_wheel from trying to make a universal wheel. For more see: 12 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels 13 | universal=0 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("PyPI.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | # Run setup tools... 7 | setuptools.setup( 8 | name="nrf24", 9 | version="2.0.0", 10 | keywords='nrf24l01 iot raspberry arduino', 11 | author="Bjarne Hansen", 12 | author_email="bjarne@conspicio.dk", 13 | license='MIT', 14 | description="A package enabling communication using NRF24L01 modules.", 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | url="https://github.com/bjarne-hansen/py-nrf24", 18 | package_dir={"": "src"}, 19 | packages=setuptools.find_namespace_packages(where="src"), 20 | install_requires=['pigpio'], 21 | classifiers=[ 22 | "Development Status :: 5 - Production/Stable", 23 | "Programming Language :: Python :: 3.6", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | ], 27 | python_requires='>=3.6', 28 | ) -------------------------------------------------------------------------------- /src/nrf24/__init__.py: -------------------------------------------------------------------------------- 1 | from .nrf24 import SPI_CHANNEL, RF24_CRC, RF24_DATA_RATE, RF24_PA, RF24_PAYLOAD, RF24_RX_ADDR, NRF24 2 | 3 | __all__ = ['SPI_CHANNEL', 'RF24_CRC', 'RF24_DATA_RATE', 'RF24_PA', 'RF24_PAYLOAD', 'RF24_RX_ADDR', 'NRF24'] -------------------------------------------------------------------------------- /src/nrf24/nrf24.py: -------------------------------------------------------------------------------- 1 | import pigpio 2 | from enum import Enum, IntEnum 3 | from os import environ as env 4 | import time 5 | 6 | 7 | class RF24_PA(IntEnum): 8 | MIN = 0, 9 | LOW = 1, 10 | HIGH = 2, 11 | MAX = 3, 12 | ERROR = 4 13 | 14 | @staticmethod 15 | def from_value(value): 16 | if value is None: 17 | raise ValueError(f'"None" is not a RF24_PA value.') 18 | 19 | if isinstance(value, RF24_PA): 20 | return value 21 | 22 | elif isinstance(value, int): 23 | for e in RF24_PA: 24 | if value == e.value: 25 | return e 26 | raise ValueError(f'Value {value} is not a RF24_PA value.') 27 | 28 | elif isinstance(value, str): 29 | for e in RF24_PA: 30 | if value.lower() == e.name.lower(): 31 | return e 32 | raise ValueError(f'Value {value} is not a RF24_PA name.') 33 | 34 | else: 35 | raise ValueError(f'{value} ({type(value)}) is not a RF24_PA value.') 36 | 37 | 38 | class RF24_DATA_RATE(IntEnum): 39 | RATE_1MBPS = 0, 40 | RATE_2MBPS = 1, 41 | RATE_250KBPS = 2 42 | 43 | @staticmethod 44 | def from_value(value): 45 | if value is None: 46 | raise ValueError(f'"None" is not a RF24_DATA_RATE value.') 47 | 48 | if isinstance(value, RF24_DATA_RATE): 49 | return value 50 | 51 | elif isinstance(value, int): 52 | for e in RF24_DATA_RATE: 53 | if value == e.value: 54 | return e 55 | raise ValueError(f'Value {value} is not a RF24_DATA_RATE value.') 56 | 57 | elif isinstance(value, str): 58 | for e in RF24_DATA_RATE: 59 | if value.lower() == e.name.lower(): 60 | return e 61 | raise ValueError(f'Value {value} is not a RF24_DATA_RATE name.') 62 | 63 | else: 64 | raise ValueError(f'{value} ({type(value)}) is not a RF24_DATA_RATE value.') 65 | 66 | 67 | class RF24_CRC(IntEnum): 68 | DISABLED = 0, 69 | BYTES_1 = 1, 70 | BYTES_2 = 2 71 | 72 | @staticmethod 73 | def from_value(value): 74 | 75 | if value is None: 76 | raise ValueError(f'"None" is not a RF24_CRC value.') 77 | 78 | if isinstance(value, RF24_CRC): 79 | return value 80 | 81 | elif isinstance(value, int): 82 | for e in RF24_CRC: 83 | if value == e.value: 84 | return e 85 | raise ValueError(f'Value {value} is not a RF24_CRC value.') 86 | 87 | elif isinstance(value, str): 88 | for e in RF24_CRC: 89 | if value.lower() == e.name.lower(): 90 | return e 91 | raise ValueError(f'Value {value} is not a RF24_CRC name.') 92 | 93 | else: 94 | raise ValueError(f'{value} ({type(value)}) is not an RF24_CRC value.') 95 | 96 | 97 | class RF24_PAYLOAD(IntEnum): 98 | ACK = -1 99 | DYNAMIC = 0 100 | MIN = 1 101 | MAX = 32 102 | 103 | @staticmethod 104 | def from_value(value): 105 | if value is None: 106 | raise ValueError(f'"None" is not a RF24_PAYLOAD value.') 107 | 108 | if isinstance(value, RF24_PAYLOAD): 109 | return value 110 | 111 | elif isinstance(value, int): 112 | for e in RF24_PAYLOAD: 113 | if value == e.value: 114 | return e 115 | if value >= RF24_PAYLOAD.ACK and value <= RF24_PAYLOAD.MAX: 116 | return value 117 | 118 | raise ValueError(f'Value {value} is not a RF24_PAYLOAD value.') 119 | 120 | elif isinstance(value, str): 121 | for e in RF24_PAYLOAD: 122 | if value.lower() == e.name.lower(): 123 | return e 124 | raise ValueError(f'Value {value} is not a RF24_PAYLOAD name.') 125 | 126 | else: 127 | raise ValueError(f'{value} ({type(value)}) is not an RF24_PAYLOAD value.') 128 | 129 | 130 | class SPI_CHANNEL(IntEnum): 131 | MAIN_CE0 = 0 132 | MAIN_CE1 = 1 133 | AUX_CE0 = 2 134 | AUX_CE1 = 3 135 | AUX_CE2 = 4 136 | 137 | @staticmethod 138 | def from_value(value): 139 | if value is None: 140 | raise ValueError(f'"None" is not a SPI_CHANNEL value.') 141 | 142 | if isinstance(value, SPI_CHANNEL): 143 | return value 144 | 145 | elif isinstance(value, int): 146 | for e in SPI_CHANNEL: 147 | if value == e.value: 148 | return e 149 | 150 | raise ValueError(f'Value {value} is not a SPI_CHANNEL value.') 151 | 152 | elif isinstance(value, str): 153 | for e in SPI_CHANNEL: 154 | if value.lower() == e.name.lower(): 155 | return e 156 | raise ValueError(f'Value {value} is not a SPI_CHANNEL name.') 157 | 158 | else: 159 | raise ValueError(f'{value} ({type(value)}) is not an SPI_CHANNEL value.') 160 | 161 | 162 | class RF24_RX_ADDR(IntEnum): 163 | P0 = 0x0a, 164 | P1 = 0x0b, 165 | P2 = 0x0c, 166 | P3 = 0x0d, 167 | P4 = 0x0e, 168 | P5 = 0x0f 169 | 170 | 171 | class NRF24: 172 | """ 173 | Note that RX and TX addresses must match 174 | 175 | Note that communication channels must match: 176 | 177 | Note that payload size must match: 178 | 179 | The following table describes how to configure the operational 180 | modes. 181 | 182 | +----------+--------+---------+--------+-----------------------------+ 183 | |Mode | PWR_UP | PRIM_RX | CE pin | FIFO state | 184 | +----------+--------+---------+--------+-----------------------------+ 185 | |RX mode | 1 | 1 | 1 | --- | 186 | +----------+--------+---------+--------+-----------------------------+ 187 | |TX mode | 1 | 0 | 1 | Data in TX FIFOs. Will empty| 188 | | | | | | all levels in TX FIFOs | 189 | +----------+--------+---------+--------+-----------------------------+ 190 | |TX mode | 1 | 0 | >10us | Data in TX FIFOs. Will empty| 191 | | | | | pulse | one level in TX FIFOs | 192 | +----------+--------+---------+--------+-----------------------------+ 193 | |Standby-II| 1 | 0 | 1 | TX FIFO empty | 194 | +----------+--------+---------+--------+-----------------------------+ 195 | |Standby-I | 1 | --- | 0 | No ongoing transmission | 196 | +----------+--------+---------+--------+-----------------------------+ 197 | |Power Down| 0 | --- | --- | --- | 198 | +----------+--------+---------+--------+-----------------------------+ 199 | """ 200 | 201 | #TX = 0 202 | #RX = 1 203 | 204 | def __init__(self, 205 | pi, # pigpio Raspberry PI connection 206 | ce, # GPIO for chip enable 207 | 208 | spi_channel=SPI_CHANNEL.MAIN_CE0, # SPI channel 209 | spi_speed=50e3, # SPI bps 50.000 = 50 Mhz 210 | 211 | data_rate=RF24_DATA_RATE.RATE_1MBPS, # Default data rate is 1 Mbits. 212 | channel=76, # Radio channel 213 | payload_size=RF24_PAYLOAD.MAX, # Message size in bytes (default: 32) 214 | address_bytes=5, # RX/TX address length in bytes 215 | crc_bytes=RF24_CRC.BYTES_2, # Number of CRC bytes 216 | pad=32, # Value used to pad short messages 217 | pa_level=RF24_PA.MAX # Set PA level. 218 | ): 219 | 220 | """ 221 | Instantiate with the Pi to which the card reader is connected. 222 | 223 | Optionally the SPI channel may be specified. The default is 224 | main SPI channel 0. 225 | 226 | The following constants may be used to define the channel: 227 | 228 | SPI_MAIN_CE0 - main SPI channel 0 229 | SPI_MAIN_CE1 - main SPI channel 1 230 | SPI_AUX_CE0 - aux SPI channel 0 231 | SPI_AUX_CE1 - aux SPI channel 1 232 | SPI_AUX_CE2 - aux SPI channel 2 233 | """ 234 | 235 | self._pi = pi 236 | 237 | # Chip Enable can be any PIN (~). 238 | assert 0 <= ce <= 31 239 | self._ce_pin = ce 240 | pi.set_mode(ce, pigpio.OUTPUT) 241 | self.unset_ce() 242 | 243 | # SPI Channel 244 | assert spi_channel >= SPI_CHANNEL.MAIN_CE0 and spi_channel <= SPI_CHANNEL.AUX_CE2 245 | 246 | # SPI speed between 32 KHz and 10 MHz 247 | assert 32000 <= spi_speed <= 10e6 248 | 249 | # Access SPI on the Raspberry PI. 250 | if spi_channel < SPI_CHANNEL.AUX_CE0: # WAS: NRF24.SPI_AUX_CE0: 251 | # Main SPI 252 | self._spi_handle = pi.spi_open(spi_channel, int(spi_speed)) 253 | else: 254 | # Aux SPI. 255 | self._spi_handle = pi.spi_open(spi_channel - SPI_CHANNEL.AUX_CE0, int(spi_speed), NRF24._AUX_SPI) 256 | 257 | # NRF channel (0-125) 258 | self.set_channel(channel) 259 | 260 | # Set default retransmissions to 15 retransmits @ 500µs. 261 | # Delay between retransmissions is calculated as (delay + 1) * 250 µs, so values will be in range 262 | # (0 + 1) * 250 = 250 µs to (15 + 1) * 250 = 4000 µs (or 4 ms) 263 | self.set_retransmission(1, 15) 264 | 265 | # NRF Payload size. -1 = Acknowledgement payload, 0 = Dynamic payload size, 1 - 32 = Payload size in bytes. 266 | # This ONLY sets the default payload size. The actual payload size it set in open_reading_pipe. 267 | self.set_payload_size(payload_size) 268 | 269 | # Padding for messages. 270 | self._padding = ord(' ') 271 | self.set_padding(pad) 272 | 273 | # NRF Address width in bytes. Shorter addresses will be padded using the padding above. 274 | self.set_address_bytes(address_bytes) 275 | 276 | # NRF CRC bytes. Range 0 - 2. 277 | self.set_crc_bytes(crc_bytes) 278 | 279 | # NRF data rate 280 | self.set_data_rate(data_rate) 281 | 282 | # Set PA level. 283 | self.set_pa_level(pa_level) 284 | 285 | # NRF Power Tx 286 | self._power_tx = 0 287 | 288 | # Initialize NRF to be in RX mode. 289 | self.power_down() # Power down the NRF24L01 290 | self.flush_rx() # Flush RX FIFO. 291 | self.flush_tx() # Flush TX FIFO. 292 | self.power_up_rx() # Power up and enter RX mode. 293 | 294 | 295 | def set_channel(self, channel): 296 | assert 0 <= channel <= 125 297 | # frequency (2400 + channel) MHz 298 | self.unset_ce() 299 | self._nrf_write_reg(self.RF_CH, channel) 300 | self.set_ce() 301 | 302 | 303 | def get_channel(self): 304 | return self._nrf_read_reg(self.RF_CH, 1)[0] 305 | 306 | 307 | def set_retransmission(self, delay, retries): 308 | assert 0 <= delay < 16, "Delay must be between 0 and 15." 309 | assert 0 <= retries < 16, "Retries must be between 0 and 15." 310 | 311 | self.unset_ce() 312 | self._nrf_write_reg(self.SETUP_RETR, ((delay << 4) | retries)) 313 | self.set_ce() 314 | 315 | 316 | def get_retransmission(self): 317 | setup_retr = self._nrf_read_reg(self.SETUP_RETR, 1)[0] 318 | delay = (setup_retr & (15 << 4)) >> 4 319 | retries = (setup_retr & 15) 320 | return (delay, retries) 321 | 322 | 323 | def set_payload_size(self, payload_size): 324 | # RF24_PAYLOAD.ACK = -1, RF24_PAYLOAD.DYNAMIC = 0, RF24_PAYLOAD.MIN = 1, RF24_PAYLOAD.MAX = 32 325 | assert RF24_PAYLOAD.ACK <= payload_size <= RF24_PAYLOAD.MAX 326 | self._payload_size = payload_size 327 | 328 | 329 | def get_payload_size(self): 330 | return self._payload_size 331 | 332 | 333 | # TODO: Padding should not be necessary. 334 | def set_padding(self, pad): 335 | try: 336 | self._padding = ord(pad) 337 | except: 338 | self._padding = pad 339 | assert 0 <= self._padding <= 255 340 | 341 | 342 | def set_address_bytes(self, address_bytes): 343 | assert 3 <= address_bytes <= 5, "Number of address bytes must be between 3 and 5." 344 | self._address_width = address_bytes 345 | self.unset_ce() 346 | self._nrf_write_reg(self.SETUP_AW, self._address_width - 2) 347 | self.set_ce() 348 | 349 | 350 | def get_address_bytes(self): 351 | return self._nrf_read_reg(self.SETUP_AW, 1)[0] + 2 352 | 353 | 354 | def disable_crc(self): 355 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 356 | mask = ~self.EN_CRC & 0xFF 357 | self.unset_ce() 358 | self._nrf_write_reg(self.CONFIG, config & mask) 359 | self.set_ce() 360 | 361 | 362 | def enable_crc(self): 363 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 364 | self.unset_ce() 365 | self._nrf_write_reg(self.CONFIG, config | self.EN_CRC) 366 | self.set_ce() 367 | 368 | 369 | def is_crc_enabled(self): 370 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 371 | if config & self.EN_CRC: 372 | return True 373 | else: 374 | return False 375 | 376 | 377 | def set_crc_bytes(self, crc_bytes): 378 | assert RF24_CRC.DISABLED <= crc_bytes <= RF24_CRC.BYTES_2 379 | 380 | if crc_bytes == RF24_CRC.DISABLED: 381 | self.disable_crc() 382 | else: 383 | if crc_bytes == RF24_CRC.BYTES_1: 384 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 385 | mask = ~self.CRCO & 0xFF 386 | new_config = config & mask 387 | self.unset_ce() 388 | self._nrf_write_reg(self.CONFIG, new_config) 389 | self.set_ce() 390 | else: 391 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 392 | mask = self.CRCO 393 | new_config = config | mask 394 | self.unset_ce() 395 | self._nrf_write_reg(self.CONFIG, new_config) 396 | self.set_ce() 397 | 398 | 399 | def get_crc_bytes(self): 400 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 401 | if config & self.EN_CRC: 402 | return RF24_CRC.DISABLED 403 | else: 404 | if config & self.CRCO: 405 | return RF24_CRC.BYTES_2 406 | else: 407 | return RF24_CRC.BYTES_1 408 | 409 | 410 | def set_data_rate(self, rate): 411 | # RF24_1MBPS = 0, RF24_2MBPS = 1, RF24_250KBPS = 2. 412 | assert RF24_DATA_RATE.RATE_1MBPS <= rate <= RF24_DATA_RATE.RATE_250KBPS 413 | 414 | # Read current setup value from register. 415 | value = self._nrf_read_reg(self.RF_SETUP, 1)[0] 416 | 417 | # Reset RF_DR_LOW and RF_DR_HIGH to 00 which is 1 Mbps (default) 418 | value &= ~(NRF24.RF_DR_LOW | NRF24.RF_DR_HIGH) 419 | 420 | # Set the RF_DR_LOW bit if speed is 250 Kbps 421 | if rate == RF24_DATA_RATE.RATE_250KBPS: 422 | value |= NRF24.RF_DR_LOW 423 | # Set the RF_DR_HIGH bit if speed is 2 Mbps 424 | elif rate == RF24_DATA_RATE.RATE_2MBPS: 425 | value |= NRF24.RF_DR_HIGH 426 | 427 | # Write value back to setup register. 428 | self.unset_ce() 429 | self._nrf_write_reg(self.RF_SETUP, value) 430 | self.set_ce() 431 | 432 | 433 | def get_data_rate(self): 434 | # Read value of RF_SETUP. 435 | rf_setup = self._nrf_read_reg(self.RF_SETUP, 1)[0] 436 | 437 | # Calculate rate from 2 bits. 438 | rate = ((rf_setup & NRF24.RF_DR_LOW) >> 4) & ((rf_setup & NRF24.RF_DR_HIGH) >> 3) 439 | 440 | # Return the corresponding enumeration value. 441 | return RF24_DATA_RATE.from_value(rate) 442 | 443 | 444 | def set_pa_level(self, level): 445 | 446 | if not isinstance(level, int): 447 | raise ValueError("PA level must be int.") 448 | 449 | if level < RF24_PA.MIN or level > RF24_PA.MAX: 450 | level = (RF24_PA.MAX << 1) + 1 451 | else: 452 | level = (level << 1) + 1 453 | 454 | value = self._nrf_read_reg(NRF24.RF_SETUP, 1)[0] 455 | value &= 0xf8 456 | value |= level 457 | 458 | self.unset_ce() 459 | self._nrf_write_reg(NRF24.RF_SETUP, value) 460 | self.set_ce() 461 | 462 | 463 | def get_pa_level(self): 464 | value = self._nrf_read_reg(NRF24.RF_SETUP, 1)[0] 465 | value &= (NRF24.RF_PWR_LOW | NRF24.RF_PWR_HIGH) 466 | value >>= 1 467 | return RF24_PA(value) 468 | 469 | 470 | def get_spi_handle(self): 471 | return self._spi_handle 472 | 473 | 474 | def show_registers(self): 475 | print("Registers:") 476 | print("----------") 477 | print(self.format_config()) 478 | print(self.format_en_aa()) 479 | print(self.format_en_rxaddr()) 480 | print(self.format_setup_aw()) 481 | print(self.format_setup_retr()) 482 | print(self.format_rf_ch()) 483 | print(self.format_rf_setup()) 484 | print(self.format_status()) 485 | print(self.format_observe_tx()) 486 | print(self.format_rpd()) 487 | print(self.format_rx_addr_px()) 488 | print(self.format_tx_addr()) 489 | print(self.format_rx_pw_px()) 490 | print(self.format_fifo_status()) 491 | print(self.format_dynpd()) 492 | print(self.format_feature()) 493 | print("----------") 494 | 495 | 496 | def _make_fixed_width(self, msg, width, pad): 497 | if isinstance(msg, str): 498 | msg = map(ord, msg) 499 | 500 | msg = list(msg) 501 | 502 | if len(msg) >= width: 503 | return msg[:width] 504 | else: 505 | msg.extend([pad] * (width - len(msg))) 506 | return msg 507 | 508 | 509 | def send(self, data): 510 | # We expect a list of byte values to be sent. However, popular types 511 | # such as string, integer, bytes, and bytearray are handled automatically using 512 | # this conversion code. 513 | if not isinstance(data, list): 514 | if isinstance(data, str): 515 | data = list(map(ord, data)) 516 | elif isinstance(data, int): 517 | data = list(data.to_bytes(-(-data.bit_length() // 8), 'little')) 518 | else: 519 | data = list(data) 520 | 521 | # Flush TX if buffers are full or max retries is set. 522 | status = self.get_status() 523 | if status & (self.TX_FULL | self.MAX_RT): 524 | self.flush_tx() 525 | 526 | if self._payload_size >= RF24_PAYLOAD.MIN: # fixed payload 527 | data = self._make_fixed_width(data, self._payload_size, self._padding) 528 | 529 | self._nrf_command([self.W_TX_PAYLOAD] + data) 530 | self.power_up_tx() 531 | 532 | 533 | def get_retries(self): 534 | v = self._nrf_read_reg(NRF24.OBSERVE_TX, 1)[0] 535 | arc = v & 15 536 | return arc 537 | 538 | 539 | def get_packages_lost(self): 540 | v = self._nrf_read_reg(NRF24.OBSERVE_TX, 1)[0] 541 | plos = (v >> 4) & 15 542 | return plos 543 | 544 | 545 | def reset_packages_lost(self): 546 | self.reset_plos() 547 | 548 | 549 | def reset_plos(self): 550 | v = self._nrf_read_reg(NRF24.RF_CH, 1)[0] 551 | self.unset_ce() 552 | self._nrf_write_reg(NRF24.RF_CH, v) 553 | self.set_ce() 554 | 555 | 556 | def ack_payload(self, pipe, data): 557 | # We expect a list of byte values to be sent. However, popular types 558 | # such as string, integer, bytes, and bytearray are handled automatically using 559 | # this conversion code. 560 | if not isinstance(data, list): 561 | if isinstance(data, str): 562 | data = list(map(ord, data)) 563 | elif isinstance(data, int): 564 | data = list(data.to_bytes(-(-data.bit_length() // 8), 'little')) 565 | else: 566 | data = list(data) 567 | 568 | # If a pipe is given as 0..5 add the 0x0a value corresponding to RX_ADDR_P0 569 | if (0 <= pipe <= 5): 570 | pipe = pipe + NRF24.RX_ADDR_P0 571 | 572 | if (pipe < NRF24.RX_ADDR_P0 or pipe > NRF24.RX_ADDR_P5): 573 | raise ValueError(f"pipe out of range ({NRF24.RX_ADDR_P0:02x} <= pipe <= and {NRF24.RX_ADDR_P5:02x}).") 574 | 575 | self._nrf_command([self.W_ACK_PAYLOAD | ((pipe - RF24_RX_ADDR.P0) & 0x07)] + data) 576 | 577 | 578 | def make_address(self, address): 579 | 580 | if isinstance(address, str): 581 | addr = list(bytes(address, 'ascii')) 582 | 583 | elif isinstance(address, bytes) or isinstance(address, bytearray): 584 | addr = list(address) 585 | 586 | elif isinstance(address, list): 587 | for n in address: 588 | if not (isinstance(n, int) and (0 <= n < 256)): 589 | raise ValueError(f'Invalid value {n} in address. All values must be between 0 and 256.') 590 | addr = address 591 | 592 | elif isinstance(address, int): 593 | addr = list(address.to_bytes(self._address_width, 'little')) 594 | 595 | else: 596 | raise ValueError("Unsupported address type.") 597 | 598 | return addr 599 | 600 | 601 | def open_writing_pipe(self, address, size=None): 602 | 603 | # Make sure address is properly formatted. 604 | addr = self.make_address(address) 605 | assert len(addr) == self._address_width, f"Invalid address length {len(addr)} of address {address} ({addr})." 606 | 607 | en_rxaddr = self._nrf_read_reg(NRF24.EN_RXADDR, 1)[0] # Get currenly enabled addresses. 608 | en_aa = self._nrf_read_reg(NRF24.EN_AA, 1)[0] # Get currently enabled auto-acknowledgement. 609 | enable = 1 # Enable acknowledgement etc. for pipe P0. 610 | 611 | # Update the transmission address and P0 as the acknowledgement address. 612 | self.unset_ce() # Enter standby. 613 | self._nrf_write_reg(self.TX_ADDR, addr) # Set the transmission address. 614 | self._open_reading_pipe(RF24_RX_ADDR.P0, addr, size) # Open P0 for reading the acknowledgement. 615 | self.set_ce() # Leave standby. 616 | 617 | 618 | def get_writing_address(self): 619 | return bytes(self._nrf_read_reg(NRF24.TX_ADDR, 5))[0:self._address_width] 620 | 621 | 622 | def _open_reading_pipe(self, pipe, address, size=None): 623 | 624 | # If no payload size is specified, use the default one. 625 | if not size: 626 | size = self._payload_size 627 | else: 628 | # If a payload size is specified, verify that it is within valid range. 629 | assert RF24_PAYLOAD.ACK <= size <= RF24_PAYLOAD.MAX, "Payload size must be between RF24_PAYLOAD.ACK and RF24_PAYLOAD.MAX" 630 | 631 | en_rxaddr = self._nrf_read_reg(NRF24.EN_RXADDR, 1)[0] # Get currently enabled pipes. 632 | dynpd = self._nrf_read_reg(NRF24.DYNPD, 1)[0] # Get currently enabled dynamic payload. 633 | en_aa = self._nrf_read_reg(NRF24.EN_AA, 1)[0] # Get currently enabled auto-acknowledgement. 634 | 635 | enable = 1 << (pipe - NRF24.RX_ADDR_P0) # Calculate "enable" value 636 | disable = ~enable & 0xFF # Calculate "disable" mask. 637 | 638 | if RF24_PAYLOAD.MIN <= size <= RF24_PAYLOAD.MAX: 639 | # Static payload size. 640 | self._nrf_write_reg(NRF24.DYNPD, dynpd & disable) # Disable dynamic payload. 641 | self._nrf_write_reg(NRF24.RX_PW_P0 + (pipe - NRF24.RX_ADDR_P0), size) # Set size of payload. 642 | elif size == RF24_PAYLOAD.DYNAMIC or RF24_PAYLOAD.ACK: 643 | # Dynamic payload size / dynamic payload size with acknowledgement payload. 644 | self._nrf_write_reg(NRF24.RX_PW_P0 + (pipe - NRF24.RX_ADDR_P0), 0) # Set size of payload to 0. 645 | self._nrf_write_reg(NRF24.DYNPD, dynpd | enable) # Enable dynamic payload. 646 | if size == RF24_PAYLOAD.DYNAMIC: 647 | self._nrf_write_reg(NRF24.FEATURE, NRF24.EN_DPL) # Enable dynamic payload. 648 | else: 649 | self._nrf_write_reg(NRF24.FEATURE, NRF24.EN_DPL | NRF24.EN_ACK_PAY) # Enable dynamic payload and acknowledgement payload feature. 650 | 651 | self._nrf_write_reg(pipe, address) # Set address for pipe. 652 | self._nrf_write_reg(NRF24.EN_AA, en_aa | enable) # Enable auto-acknowledgement. 653 | self._nrf_write_reg(NRF24.EN_RXADDR, en_rxaddr | enable) # Enable reception on pipe. 654 | 655 | 656 | def open_reading_pipe(self, pipe, address, size=None): 657 | # Validate pipe input. 658 | if not (isinstance(pipe, int) or isinstance(pipe, RF24_RX_ADDR)): 659 | raise ValueError(f"pipe must be int or RF24_RX_ADDR enum.") 660 | 661 | # If a pipe is given as 0..5 add the 0x0a value corresponding to RX_ADDR_P0 662 | if (0 <= pipe <= 5): 663 | pipe = pipe + NRF24.RX_ADDR_P0 664 | 665 | if (pipe < NRF24.RX_ADDR_P0 or pipe > NRF24.RX_ADDR_P5): 666 | raise ValueError(f"pipe out of range ({NRF24.RX_ADDR_P0:02x} <= pipe <= and {NRF24.RX_ADDR_P5:02x}).") 667 | 668 | # Adjust address. 669 | addr = self.make_address(address) 670 | assert len(addr) == self._address_width, f"Invalid address length {len(addr)} of address {address} ({addr})." 671 | 672 | # If the address is greater that RF24_RX_ADDR.P1, we use only the first byte of the address. 673 | if pipe > RF24_RX_ADDR.P1: 674 | addr = addr[:1] 675 | 676 | self.unset_ce() 677 | self._open_reading_pipe(pipe, addr, size) 678 | self.set_ce() 679 | 680 | 681 | def close_reading_pipe(self, pipe): 682 | # We accept pipe addresses 0..5 or RX_ADDR_P0..RX_ADDR_P5 683 | 684 | if RF24_RX_ADDR.P0 <= pipe <= RF24_RX_ADDR.P5: 685 | pipe -= RF24_RX_ADDR.P0 686 | 687 | assert 0 <= pipe <= 5, "Pipe should be in range 0..5 or RF24_RX_ADDR.P0..RF24_RX_ADDR.P5." 688 | 689 | # Read the EN_RXADDR register. 690 | en_rxaddr = self._nrf_read_reg(NRF24.EN_RXADDR, 1)[0] 691 | 692 | # Calculate a mask for disabling the particular bit of the pipe. 693 | mask = ~(1 << pipe) & 0xFF 694 | 695 | # Write back the register masked to disable the pipe. 696 | self.unset_ce() 697 | self._nrf_write_reg(NRF24.EN_RXADDR, en_rxaddr & mask) 698 | self.set_ce() 699 | 700 | 701 | def close_all_reading_pipes(self): 702 | # Close all reading pipes. 703 | # PLEASE NOTE: This will disable acknowledgements for transmission on P0. 704 | self.unset_ce() 705 | self._nrf_write_reg(NRF24.EN_RXADDR, 0) 706 | self.set_ce() 707 | 708 | 709 | def reset_reading_pipes(self): 710 | # Resets reading pipes to standard configuration as per product sheet. 711 | self.unset_ce() 712 | self._nrf_write_reg(NRF24.EN_RXADDR, 0b00000011) 713 | self.set_ce() 714 | 715 | 716 | def get_reading_address(self, pipe): 717 | # Validate pipe input. 718 | if not (isinstance(pipe, int) or isinstance(pipe, RF24_RX_ADDR)): 719 | raise ValueError(f"pipe must be int or RF24_RX_ADDR enum.") 720 | 721 | # If a pipe is given as 0..5 add the 0x0a value corresponding to RX_ADDR_P0 722 | if (pipe >= 0 and pipe <= 5): 723 | pipe = pipe + NRF24.RX_ADDR_P0 724 | 725 | if (pipe < NRF24.RX_ADDR_P0 or pipe > NRF24.RX_ADDR_P5): 726 | raise ValueError(f"pipe out of range ({NRF24.RX_ADDR_P0:02x} <= pipe <= and {NRF24.RX_ADDR_P5:02x}).") 727 | 728 | if (pipe >= NRF24.RX_ADDR_P0 and pipe <= NRF24.RX_ADDR_P1): 729 | return bytes(self._nrf_read_reg(pipe, 5))[0:self._address_width] 730 | else: 731 | p1 = self._nrf_read_reg(RF24_RX_ADDR.P1, 5) 732 | b = self._nrf_read_reg(pipe, 1)[0] 733 | p1[0] = b 734 | return bytes(p1)[0:self._address_width] 735 | 736 | 737 | def data_ready_pipe(self): 738 | status = self.get_status() 739 | pipe = (status >> 1) & 0x07 740 | 741 | if status & self.RX_DR: 742 | return True, pipe 743 | 744 | fifo_status = self._nrf_read_reg(self.FIFO_STATUS, 1)[0] 745 | if fifo_status & self.FRX_EMPTY: 746 | return False, pipe 747 | else: 748 | return True, pipe 749 | 750 | 751 | def data_pipe(self): 752 | status = self.get_status() 753 | pipe = (status >> 1) & 0x07 754 | return pipe 755 | 756 | 757 | def data_ready(self): 758 | status = self.get_status() 759 | if status & self.RX_DR: 760 | return True 761 | 762 | fifo_status = self._nrf_read_reg(self.FIFO_STATUS, 1)[0] 763 | if fifo_status & self.FRX_EMPTY: 764 | return False 765 | else: 766 | return True 767 | 768 | 769 | def wait_until_sent(self, timeout_ns=100000000): 770 | # Time out after 100 ms should ensure that we do not abandon good communication. 771 | start_wait = time.monotonic_ns() 772 | while self.is_sending(): 773 | 774 | if time.monotonic_ns() - start_wait > timeout_ns: 775 | self.power_up_rx() 776 | raise TimeoutError('Timed out wating for send to complete.') 777 | 778 | # Wait 250µs before checking again. That is the retransmit delay. 779 | time.sleep(0.000250) 780 | 781 | 782 | def is_sending(self): 783 | if self._power_tx > 0: 784 | status = self.get_status() 785 | if status & (self.TX_DS | self.MAX_RT): 786 | self.power_up_rx() 787 | return False 788 | return True 789 | return False 790 | 791 | 792 | def get_payload(self): 793 | if self._payload_size < RF24_PAYLOAD.MIN: 794 | # dynamic payload 795 | bytes_count = self._nrf_command([self.R_RX_PL_WID, 0])[1] 796 | else: 797 | # fixed payload 798 | bytes_count = self._payload_size 799 | 800 | d = self._nrf_read_reg(self.R_RX_PAYLOAD, bytes_count) 801 | self.unset_ce() # added 802 | self._nrf_write_reg(self.STATUS, self.RX_DR) 803 | self.set_ce() # added 804 | return d 805 | 806 | 807 | def get_status(self): 808 | return self._nrf_command(self.NOP)[0] 809 | 810 | 811 | def power_up_tx(self): 812 | self._power_tx = 1 813 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 814 | config &= (~self.PRIM_RX & 0xFF) # Disable receive. 815 | config |= self.PWR_UP # Enable power. 816 | self.unset_ce() 817 | self._nrf_write_reg(self.CONFIG, config) 818 | self._nrf_write_reg(self.STATUS, self.RX_DR | self.TX_DS | self.MAX_RT) 819 | self.set_ce() 820 | 821 | 822 | def power_up_rx(self): 823 | self._power_tx = 0 824 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 825 | self.unset_ce() 826 | self._nrf_write_reg(self.CONFIG, config | self.PWR_UP | self.PRIM_RX) 827 | self._nrf_write_reg(self.STATUS, self.RX_DR | self.TX_DS | self.MAX_RT) 828 | self.set_ce() 829 | 830 | 831 | def power_down(self): 832 | config = self._nrf_read_reg(self.CONFIG, 1)[0] 833 | mask = ~self.PWR_UP & 0xFF 834 | self.unset_ce() 835 | self._nrf_write_reg(self.CONFIG, config & mask) 836 | 837 | 838 | def set_ce(self): 839 | self._pi.write(self._ce_pin, 1) 840 | 841 | 842 | def unset_ce(self): 843 | self._pi.write(self._ce_pin, 0) 844 | 845 | 846 | def flush_rx(self): 847 | self._nrf_command(self.FLUSH_RX) 848 | 849 | 850 | def flush_tx(self): 851 | self._nrf_command(self.FLUSH_TX) 852 | 853 | 854 | def _nrf_xfer(self, data): 855 | b, d = self._pi.spi_xfer(self._spi_handle, data) 856 | return d 857 | 858 | 859 | def _nrf_command(self, arg): 860 | if type(arg) is not list: 861 | arg = [arg] 862 | return self._nrf_xfer(arg) 863 | 864 | 865 | def _nrf_read_reg(self, reg, count): 866 | return self._nrf_xfer([reg] + [0] * count)[1:] 867 | 868 | 869 | def _nrf_write_reg(self, reg, arg): 870 | """ 871 | Write arg (which may be one or more bytes) to reg. 872 | 873 | This function is only permitted in a powerdown or 874 | standby mode. 875 | """ 876 | if type(arg) is not list: 877 | arg = [arg] 878 | self._nrf_xfer([self.W_REGISTER | reg] + arg) 879 | 880 | 881 | # Constants related to NRF24 configuration/operation. 882 | _AUX_SPI = (1 << 8) 883 | 884 | R_REGISTER = 0x00 # reg in bits 0-4, read 1-5 bytes 885 | W_REGISTER = 0x20 # reg in bits 0-4, write 1-5 bytes 886 | 887 | R_RX_PL_WID = 0x60 888 | R_RX_PAYLOAD = 0x61 # read 1-32 bytes 889 | 890 | W_TX_PAYLOAD = 0xA0 # write 1-32 bytes 891 | W_ACK_PAYLOAD = 0xA8 # pipe in bits 0-2, write 1-32 bytes 892 | W_TX_PAYLOAD_NO_ACK = 0xB0 # no ACK, write 1-32 bytes 893 | 894 | FLUSH_TX = 0xE1 895 | FLUSH_RX = 0xE2 896 | REUSE_TX_PL = 0xE3 897 | 898 | NOP = 0xFF # no operation 899 | 900 | CONFIG = 0x00 901 | EN_AA = 0x01 902 | EN_RXADDR = 0x02 903 | SETUP_AW = 0x03 904 | SETUP_RETR = 0x04 905 | RF_CH = 0x05 906 | RF_SETUP = 0x06 907 | STATUS = 0x07 908 | OBSERVE_TX = 0x08 909 | RPD = 0x09 910 | RX_ADDR_P0 = 0x0A 911 | RX_ADDR_P1 = 0x0B 912 | RX_ADDR_P2 = 0x0C 913 | RX_ADDR_P3 = 0x0D 914 | RX_ADDR_P4 = 0x0E 915 | RX_ADDR_P5 = 0x0F 916 | TX_ADDR = 0x10 917 | RX_PW_P0 = 0x11 918 | RX_PW_P1 = 0x12 919 | RX_PW_P2 = 0x13 920 | RX_PW_P3 = 0x14 921 | RX_PW_P4 = 0x15 922 | RX_PW_P5 = 0x16 923 | FIFO_STATUS = 0x17 924 | DYNPD = 0x1C 925 | FEATURE = 0x1D 926 | 927 | # CONFIG 928 | MASK_RX_DR = 1 << 6 929 | MASK_TX_DS = 1 << 5 930 | MASK_MAX_RT = 1 << 4 931 | 932 | EN_CRC = 1 << 3 # 8 (default) 933 | CRCO = 1 << 2 # 4 934 | PWR_UP = 1 << 1 # 2 935 | PRIM_RX = 1 << 0 # 1 936 | 937 | def format_config(self): 938 | v = self._nrf_read_reg(NRF24.CONFIG, 1)[0] 939 | s = f"CONFIG: (0x{v:02x}) => " 940 | 941 | if v & NRF24.MASK_RX_DR: 942 | s += "no RX_DR IRQ, " 943 | else: 944 | s += "RX_DR IRQ, " 945 | 946 | if v & NRF24.MASK_TX_DS: 947 | s += "no TX_DS IRQ, " 948 | else: 949 | s += "TX_DS IRQ, " 950 | 951 | if v & NRF24.MASK_MAX_RT: 952 | s += "no MAX_RT IRQ, " 953 | else: 954 | s += "MAX_RT IRQ, " 955 | 956 | if v & NRF24.EN_CRC: 957 | s += "CRC on, " 958 | else: 959 | s += "CRC off, " 960 | 961 | if v & NRF24.CRCO: 962 | s += "CRC 2 byte, " 963 | else: 964 | s += "CRC 1 byte, " 965 | 966 | if v & NRF24.PWR_UP: 967 | s += "Power up, " 968 | else: 969 | s += "Power down, " 970 | 971 | if v & NRF24.PRIM_RX: 972 | s += "RX" 973 | else: 974 | s += "TX" 975 | 976 | return s 977 | 978 | # EN_AA 979 | ENAA_P5 = 1 << 5 # default 980 | ENAA_P4 = 1 << 4 # default 981 | ENAA_P3 = 1 << 3 # default 982 | ENAA_P2 = 1 << 2 # default 983 | ENAA_P1 = 1 << 1 # default 984 | ENAA_P0 = 1 << 0 # default 985 | 986 | def format_en_aa(self): 987 | v = self._nrf_read_reg(NRF24.EN_AA, 1)[0] 988 | s = f"EN_AA: (0x{v:02x}) => " 989 | for i in range(6): 990 | if v & (1 << i): 991 | s += f"P{i}:ACK " 992 | else: 993 | s += f"P{i}:no ACK " 994 | return s 995 | 996 | # EN_RXADDR 997 | ERX_P5 = 1 << 5 998 | ERX_P4 = 1 << 4 999 | ERX_P3 = 1 << 3 1000 | ERX_P2 = 1 << 2 1001 | ERX_P1 = 1 << 1 # default 1002 | ERX_P0 = 1 << 0 # default 1003 | 1004 | def format_en_rxaddr(self): 1005 | v = self._nrf_read_reg(NRF24.EN_RXADDR, 1)[0] 1006 | s = f"EN_RXADDR: (0x{v:02x}) => " 1007 | for i in range(6): 1008 | if v & (1 << i): 1009 | s += f"P{i}:on " 1010 | else: 1011 | s += f"P{i}:off " 1012 | return s 1013 | 1014 | # SETUP_AW (Address width) 1015 | AW_3 = 1 1016 | AW_4 = 2 1017 | AW_5 = 3 # default 1018 | 1019 | def format_setup_aw(self): 1020 | v = self._nrf_read_reg(NRF24.SETUP_AW, 1)[0] 1021 | s = f"SETUP_AW: (0x{v:02x}) => address width bytes " 1022 | if v == NRF24.AW_3: 1023 | s += "3" 1024 | elif v == NRF24.AW_4: 1025 | s += "4" 1026 | elif v == NRF24.AW_5: 1027 | s += "5" 1028 | else: 1029 | s += "invalid" 1030 | return s 1031 | 1032 | # SETUP_RETR (Retry delay and retries) 1033 | # ARD 7-4 1034 | # ARC 3-0 1035 | def format_setup_retr(self): 1036 | v = self._nrf_read_reg(NRF24.SETUP_RETR, 1)[0] 1037 | ard = (((v >> 4) & 15) * 250) + 250 1038 | arc = v & 15 1039 | s = f"SETUP_RETR: (0x{v:02x}) => retry delay {ard} us, retries {arc}" 1040 | return s 1041 | 1042 | # RF_CH (Channel) 1043 | # RF_CH 6-0 1044 | def format_rf_ch(self): 1045 | v = self._nrf_read_reg(NRF24.RF_CH, 1)[0] 1046 | s = f"RF_CH: (0x{v:02x}) => channel={v & 127}" 1047 | return s 1048 | 1049 | # RF_SETUP 1050 | CONT_WAVE = 1 << 7 1051 | RF_DR_LOW = 1 << 5 1052 | PLL_LOCK = 1 << 4 1053 | RF_DR_HIGH = 1 << 3 1054 | RF_PWR_LOW = 1 << 1 1055 | RF_PWR_HIGH = 1 << 2 1056 | 1057 | # RF_PWR 2-1 1058 | def format_rf_setup(self): 1059 | v = self._nrf_read_reg(NRF24.RF_SETUP, 1)[0] 1060 | s = f"RF_SETUP: (0x{v:02x}) => " 1061 | 1062 | if v & NRF24.CONT_WAVE: 1063 | s += "continuos carrier on, " 1064 | else: 1065 | s += "no continuous carrier, " 1066 | 1067 | if v & NRF24.PLL_LOCK: 1068 | s += "force PLL lock on, " 1069 | else: 1070 | s += "no force PLL lock, " 1071 | 1072 | dr = 0 1073 | if v & NRF24.RF_DR_LOW: 1074 | dr += 2 1075 | if v & NRF24.RF_DR_HIGH: 1076 | dr += 1 1077 | 1078 | if dr == 0: 1079 | s += "1 Mbps, " 1080 | elif dr == 1: 1081 | s += "2 Mbps, " 1082 | elif dr == 2: 1083 | s += "250 kbps, " 1084 | else: 1085 | s += "illegal speed, " 1086 | 1087 | pwr = (v >> 1) & 3 1088 | if pwr == 0: 1089 | s += "-18 dBm" 1090 | elif pwr == 1: 1091 | s += "-12 dBm" 1092 | elif pwr == 2: 1093 | s += "-6 dBm" 1094 | else: 1095 | s += "0 dBm" 1096 | return s 1097 | 1098 | # STATUS 1099 | RX_DR = 1 << 6 1100 | TX_DS = 1 << 5 1101 | MAX_RT = 1 << 4 1102 | # RX_P_NO 3-1 1103 | RX_P_NO = 1 1104 | TX_FULL = 1 << 0 1105 | 1106 | def format_status(self): 1107 | v = self._nrf_read_reg(NRF24.STATUS, 1)[0] 1108 | s = f"STATUS: (0x{v:02x}) => " 1109 | 1110 | if v & NRF24.RX_DR: 1111 | s += "RX data, " 1112 | else: 1113 | s += "no RX data, " 1114 | 1115 | if v & NRF24.TX_DS: 1116 | s += "TX ok, " 1117 | else: 1118 | s += "no TX, " 1119 | 1120 | if v & NRF24.MAX_RT: 1121 | s += "TX retries bad, " 1122 | else: 1123 | s += "TX retries ok, " 1124 | 1125 | p = (v >> 1) & 7 1126 | if p < 6: 1127 | s += f"pipe {p} data, " 1128 | elif p == 6: 1129 | s += "PIPE 6 ERROR, " 1130 | else: 1131 | s += "no pipe data, " 1132 | 1133 | if v & NRF24.TX_FULL: 1134 | s += "TX FIFO full" 1135 | else: 1136 | s += "TX FIFO not full" 1137 | 1138 | return s 1139 | 1140 | # OBSERVE_TX 1141 | # PLOS_CNT 7-4 1142 | # ARC_CNT 3-0 1143 | def format_observe_tx(self): 1144 | v = self._nrf_read_reg(NRF24.OBSERVE_TX, 1)[0] 1145 | plos = (v >> 4) & 15 1146 | arc = v & 15 1147 | s = f"OBSERVE_TX: (0x{v:02x}) => lost packets {plos}, retries {arc}" 1148 | return s 1149 | 1150 | # RPD 1151 | # RPD 1 << 0 1152 | def format_rpd(self): 1153 | v = self._nrf_read_reg(NRF24.RPD, 1)[0] 1154 | s = f"RPD: (0x{v:02x}) => received power detector {v & 1}" 1155 | return s 1156 | 1157 | # RX_ADDR_P0 - RX_ADDR_P5 1158 | @staticmethod 1159 | def _byte2hex(s): 1160 | hex_value = ''.join('{:02x}'.format(c) for c in reversed(s)) 1161 | return hex_value 1162 | 1163 | def format_rx_addr_px(self): 1164 | p0 = self._nrf_read_reg(NRF24.RX_ADDR_P0, 5) 1165 | p1 = self._nrf_read_reg(NRF24.RX_ADDR_P1, 5) 1166 | p2 = self._nrf_read_reg(NRF24.RX_ADDR_P2, 1)[0] 1167 | p3 = self._nrf_read_reg(NRF24.RX_ADDR_P3, 1)[0] 1168 | p4 = self._nrf_read_reg(NRF24.RX_ADDR_P4, 1)[0] 1169 | p5 = self._nrf_read_reg(NRF24.RX_ADDR_P5, 1)[0] 1170 | 1171 | s = "RX ADDR_PX: " 1172 | s += f"P0=0x{self._byte2hex(p0)} " 1173 | s += f"P1=0x{self._byte2hex(p1)} " 1174 | s += f"P2=0x{p2:02x} " 1175 | s += f"P3=0x{p3:02x} " 1176 | s += f"P4=0x{p4:02x} " 1177 | s += f"P5=0x{p5:02x}" 1178 | 1179 | return s 1180 | 1181 | # TX_ADDR 1182 | def format_tx_addr(self): 1183 | p0 = self._nrf_read_reg(NRF24.TX_ADDR, 5) 1184 | s = f"TX_ADDR: 0x{self._byte2hex(p0)} " 1185 | return s 1186 | 1187 | # RX_PW_P0 - RX_PW_P5 1188 | def format_rx_pw_px(self): 1189 | p0 = self._nrf_read_reg(NRF24.RX_PW_P0, 1)[0] 1190 | p1 = self._nrf_read_reg(NRF24.RX_PW_P1, 1)[0] 1191 | p2 = self._nrf_read_reg(NRF24.RX_PW_P2, 1)[0] 1192 | p3 = self._nrf_read_reg(NRF24.RX_PW_P3, 1)[0] 1193 | p4 = self._nrf_read_reg(NRF24.RX_PW_P4, 1)[0] 1194 | p5 = self._nrf_read_reg(NRF24.RX_PW_P5, 1)[0] 1195 | s = "RX_PW_PX: " 1196 | s += f"P0={p0:02x} P1={p1:02x} P2={p2:02x} P3={p3:02x} P4={p4:02x} P5={p5:02x} " 1197 | return s 1198 | 1199 | # FIFO_STATUS 1200 | FTX_REUSE = 1 << 6 1201 | FTX_FULL = 1 << 5 1202 | FTX_EMPTY = 1 << 4 1203 | FRX_FULL = 1 << 1 1204 | FRX_EMPTY = 1 << 0 1205 | 1206 | def format_fifo_status(self): 1207 | v = self._nrf_read_reg(NRF24.FIFO_STATUS, 1)[0] 1208 | s = f"FIFO_STATUS: (0x{v:02x}) => " 1209 | 1210 | if v & NRF24.FTX_REUSE: 1211 | s += "TX reuse set, " 1212 | else: 1213 | s += "TX reuse not set, " 1214 | 1215 | if v & NRF24.FTX_FULL: 1216 | s += "TX FIFO full, " 1217 | elif v & NRF24.FTX_EMPTY: 1218 | s += "TX FIFO empty, " 1219 | else: 1220 | s += "TX FIFO has data, " 1221 | 1222 | if v & NRF24.FRX_FULL: 1223 | s += "RX FIFO full, " 1224 | elif v & NRF24.FRX_EMPTY: 1225 | s += "RX FIFO empty" 1226 | else: 1227 | s += "RX FIFO has data" 1228 | 1229 | return s 1230 | 1231 | # DYNPD 1232 | DPL_P7 = 1 << 7 1233 | DPL_P6 = 1 << 6 1234 | DPL_P5 = 1 << 5 1235 | DPL_P4 = 1 << 4 1236 | DPL_P3 = 1 << 3 1237 | DPL_P2 = 1 << 2 1238 | DPL_P1 = 1 << 1 1239 | DPL_P0 = 1 << 0 1240 | 1241 | def format_dynpd(self): 1242 | v = self._nrf_read_reg(NRF24.DYNPD, 1)[0] 1243 | s = f"DYNPD: (0x{v:02x}) => " 1244 | for i in range(6): 1245 | if v & (1 << i): 1246 | s += f"P{i}:on " 1247 | else: 1248 | s += f"P{i}:off " 1249 | return s 1250 | 1251 | # FEATURE 1252 | EN_DPL = 1 << 2 1253 | EN_ACK_PAY = 1 << 1 1254 | EN_DYN_ACK = 1 << 0 1255 | 1256 | def format_feature(self): 1257 | v = self._nrf_read_reg(NRF24.FEATURE, 1)[0] 1258 | s = f"FEATURE: (0x{v:02x}) => " 1259 | 1260 | if v & NRF24.EN_DPL: 1261 | s += "Dynamic payload on, " 1262 | else: 1263 | s += "Dynamic payload off, " 1264 | 1265 | if v & NRF24.EN_ACK_PAY: 1266 | s += "ACK payload on, " 1267 | else: 1268 | s += "ACK payload off, " 1269 | 1270 | if v & NRF24.EN_DYN_ACK: 1271 | s += "W_TX_PAYLOAD_NOACK on" 1272 | else: 1273 | s += "W_TX_PAYLOAD_NOACK off" 1274 | 1275 | return s 1276 | -------------------------------------------------------------------------------- /test/ack-receiver.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from datetime import datetime 3 | import struct 4 | import sys 5 | import time 6 | import traceback 7 | from uuid import uuid4 8 | 9 | import pigpio 10 | from nrf24 import * 11 | 12 | # 13 | # A simple NRF24L receiver that connects to a PIGPIO instance on a hostname and port, default "localhost" and 8888, and 14 | # starts receiving data on the address specified sending a continiously increasing integer as acknowledgement payload. 15 | # Use the companion program "ack-sender.py" to send data to it from a different Raspberry Pi. 16 | # 17 | if __name__ == "__main__": 18 | 19 | print("Python NRF24 Receiver with Acknowledgement Payload Example.") 20 | 21 | # Parse command line argument. 22 | parser = argparse.ArgumentParser(prog="ack-receiver.py", description="Simple NRF24 Receiver with Acknowledgement Payload.") 23 | parser.add_argument('-n', '--hostname', type=str, default='localhost', help="Hostname for the Raspberry running the pigpio daemon.") 24 | parser.add_argument('-p', '--port', type=int, default=8888, help="Port number of the pigpio daemon.") 25 | parser.add_argument('address', type=str, nargs='?', default='1ACKS', help="Address to listen to (3 to 5 ASCII characters).") 26 | 27 | args = parser.parse_args() 28 | hostname = args.hostname 29 | port = args.port 30 | address = args.address 31 | 32 | # Verify that address is between 3 and 5 characters. 33 | if not (2 < len(address) < 6): 34 | print(f'Invalid address {address}. Addresses must be between 3 and 5 ASCII characters.') 35 | sys.exit(1) 36 | 37 | # Connect to pigpiod 38 | print(f'Connecting to GPIO daemon on {hostname}:{port} ...') 39 | pi = pigpio.pi(hostname, port) 40 | if not pi.connected: 41 | print("Not connected to Raspberry Pi ... goodbye.") 42 | exit() 43 | 44 | # Create NRF24 object. 45 | # PLEASE NOTE: PA level is set to MIN, because test sender/receivers are often close to each other, and then MIN works better. 46 | nrf = NRF24(pi, ce=25, payload_size=RF24_PAYLOAD.ACK, channel=100, data_rate=RF24_DATA_RATE.RATE_250KBPS, pa_level=RF24_PA.MIN) 47 | nrf.set_address_bytes(len(address)) 48 | 49 | # Listen on the address specified as parameter 50 | nrf.open_reading_pipe(RF24_RX_ADDR.P1, address) 51 | 52 | # Display the content of NRF24L01 device registers. 53 | nrf.show_registers() 54 | 55 | # Set the UUID that will be the payload of the next acknowledgement. 56 | next_id = 1 57 | nrf.ack_payload(RF24_RX_ADDR.P1, struct.pack(' 0 else -1 74 | 75 | hex = ':'.join(f'{i:02x}' for i in payload) 76 | 77 | # Show message received as hex. 78 | print(f"{now:%Y-%m-%d %H:%M:%S.%f}: pipe: {pipe}, len: {len(payload)}, bytes: {hex}, count: {count}") 79 | 80 | # If the length of the message is 9 bytes and the first byte is 0x01, then we try to interpret the bytes 81 | # sent as an example message holding a temperature and humidity sent from the "simple-sender.py" program. 82 | if len(payload) == 9 and payload[0] == 0x01: 83 | values = struct.unpack(" 0 else -1 80 | 81 | hex = ':'.join(f'{i:02x}' for i in payload) 82 | 83 | # Show message received as hex. 84 | print(f"{now:%Y-%m-%d %H:%M:%S.%f}: pipe: {pipe}, len: {len(payload)}, bytes: {hex}, count: {count}") 85 | 86 | # If the length of the message is 9 bytes and the first byte is 0x01, then we try to interpret the bytes 87 | # sent as an example message holding a temperature and humidity sent from the "simple-sender.py" program. 88 | if len(payload) == 9 and payload[0] == 0x01: 89 | values = struct.unpack(" 0 else -1 89 | 90 | hex = ':'.join(f'{i:02x}' for i in payload) 91 | 92 | # Show message received as hex. 93 | print(f"{now:%Y-%m-%d %H:%M:%S.%f}: Module #1 - pipe={pipe}, len={len(payload)}, bytes={hex}, count={count1}") 94 | 95 | # If the length of the message is 9 bytes and the first byte is 0x01, then we try to interpret the bytes 96 | # sent as an example message holding a temperature and humidity sent from the "simple-sender.py" program. 97 | if len(payload) == 9 and payload[0] == 0x01: 98 | values = struct.unpack(" 0 else -1 113 | 114 | hex = ':'.join(f'{i:02x}' for i in payload) 115 | 116 | # Show message received as hex. 117 | print(f"{now:%Y-%m-%d %H:%M:%S.%f}: Module #2 - pipe={pipe}, len={len(payload)}, bytes={hex}, count={count2}") 118 | 119 | # If the length of the message is 9 bytes and the first byte is 0x01, then we try to interpret the bytes 120 | # sent as an example message holding a temperature and humidity sent from the "simple-sender.py" program. 121 | if len(payload) == 9 and payload[0] == 0x01: 122 | values = struct.unpack(" 1: 124 | # If we have waited more than 1 second on a response, we time out. 125 | # This obviously depends on the application. 126 | print('Timeout waiting for response.') 127 | break 128 | 129 | # Wait 10 seconds before sending the next request. 130 | print('Wait 10 seconds before sending new request.') 131 | time.sleep(10) 132 | except: 133 | traceback.print_exc() 134 | nrf.power_down() 135 | pi.stop() 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /test/rr-server.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from datetime import datetime 3 | import random 4 | import struct 5 | import sys 6 | import time 7 | import traceback 8 | from uuid import uuid4 9 | 10 | import pigpio 11 | from nrf24 import * 12 | 13 | 14 | # 15 | # A simple NRF24L receiver that connects to a PIGPIO instance on a hostname and port, default "localhost" and 8888, and 16 | # starts receiving data on the address specified. For each request, a response message is returned. 17 | # Use the companion program "rr-client.py" to send requests and receive responses from this server. 18 | # 19 | if __name__ == "__main__": 20 | 21 | print("Python NRF24 Request/Response Server Example.") 22 | 23 | # Parse command line argument. 24 | parser = argparse.ArgumentParser(prog="rr-server.py", description="Simple NRF24 Request/Response Server Example.") 25 | parser.add_argument('-n', '--hostname', type=str, default='localhost', help="Hostname for the Raspberry running the pigpio daemon.") 26 | parser.add_argument('-p', '--port', type=int, default=8888, help="Port number of the pigpio daemon.") 27 | parser.add_argument('address', type=str, nargs='?', default='1SRVR', help="Address to listen to (3 to 5 ASCII characters).") 28 | 29 | args = parser.parse_args() 30 | hostname = args.hostname 31 | port = args.port 32 | address = args.address 33 | 34 | # Verify that address is between 3 and 5 characters. 35 | if not (2 < len(address) < 6): 36 | print(f'Invalid address {address}. Addresses must be between 3 and 5 ASCII characters.') 37 | sys.exit(1) 38 | 39 | # Connect to pigpiod 40 | print(f'Connecting to GPIO daemon on {hostname}:{port} ...') 41 | pi = pigpio.pi(hostname, port) 42 | if not pi.connected: 43 | print("Not connected to Raspberry Pi ... goodbye.") 44 | sys.exit() 45 | 46 | # Create NRF24 object. 47 | # PLEASE NOTE: PA level is set to MIN, because test sender/receivers are often close to each other, and then MIN works better. 48 | nrf = NRF24(pi, ce=25, payload_size=RF24_PAYLOAD.DYNAMIC, channel=100, data_rate=RF24_DATA_RATE.RATE_250KBPS, pa_level=RF24_PA.MIN) 49 | nrf.set_address_bytes(len(address)) 50 | 51 | # Display the content of NRF24L01 device registers. 52 | nrf.show_registers() 53 | 54 | # Enter a loop receiving data on the address specified. 55 | try: 56 | print(f'Receive from {address}') 57 | count = 0 58 | while True: 59 | 60 | # Listen on the address specified as parameter 61 | nrf.open_reading_pipe(RF24_RX_ADDR.P1, address) 62 | 63 | # As long as data is ready for processing, process it. 64 | while nrf.data_ready(): 65 | 66 | # Count message and record time of reception. 67 | count += 1 68 | now = datetime.now() 69 | 70 | # Read pipe and payload for message. 71 | pipe = nrf.data_pipe() 72 | payload = nrf.get_payload() 73 | 74 | # Resolve protocol number. 75 | protocol = payload[0] if len(payload) > 0 else -1 76 | 77 | hex = ':'.join(f'{i:02x}' for i in payload) 78 | 79 | # Show message received as hex. 80 | print(f"{now:%Y-%m-%d %H:%M:%S.%f}: pipe: {pipe}, len: {len(payload)}, bytes: {hex}, count: {count}") 81 | 82 | # We expect the request to be a message with a 2 byte "command", and a 6 byte char[] reply-to address. 83 | if len(payload) == 8: 84 | 85 | command, reply_to = struct.unpack(" 0 else -1 73 | 74 | hex = ':'.join(f'{i:02x}' for i in payload) 75 | 76 | # Show message received as hex. 77 | print(f"{now:%Y-%m-%d %H:%M:%S.%f}: pipe: {pipe}, len: {len(payload)}, bytes: {hex}, count: {count}") 78 | 79 | # If the length of the message is 9 bytes and the first byte is 0x01, then we try to interpret the bytes 80 | # sent as an example message holding a temperature and humidity sent from the "simple-sender.py" program. 81 | if len(payload) == 9 and payload[0] == 0x01: 82 | values = struct.unpack("