From b7662ba0001703a089025064e1d6a4ba6bf158a4 Mon Sep 17 00:00:00 2001 From: BLavery Date: Wed, 5 Nov 2014 21:34:16 +1000 Subject: [PATCH] V0.3 BETA --- README.md | 14 + example-nrf24-pair.py | 154 +++++++++ example-nrf24-recv.py | 56 +++ example-nrf24-send.py | 51 +++ lib_nrf24.py | 787 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1062 insertions(+) create mode 100644 README.md create mode 100644 example-nrf24-pair.py create mode 100644 example-nrf24-recv.py create mode 100644 example-nrf24-send.py create mode 100644 lib_nrf24.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..11f869b --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +lib_nrf24 +========= + +Python2/3 library for NRF24L01+ Transceivers + +V0.3 beta + +For Raspberry Pi and virtual-GPIO. +NRF24: strictly 3.3V supply!! Although logic pins are 5V tolerant. + +This is BETA only so far. +Everything has worked earlier, send, receive, including two RF24 on one host, and including RPI, virtual-GPIO and regular arduino sketch, all talking to each other. + +But recent testing has been only LIBRARY plus "example-nrf24-pair.py" on virtual-GPIO, so other parts are yet to be re-verified. diff --git a/example-nrf24-pair.py b/example-nrf24-pair.py new file mode 100644 index 0000000..609144c --- /dev/null +++ b/example-nrf24-pair.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# +# Example program to send packets between 2 NRF24L01+ radios +# +# Demonstrates: 2 x NRF24 radios both operating from SPI on one virt-GPIO, doing 2-way radio data transfer +# Dynamic payload size both ways (to 32 bytes). +# "Auto-Ack" mode, with "ack-payload" carrying the return data. +# Forward payload and return ack-payload data content is rather arbitrary, but is identifiable. +# (Single PTX-PRX link is demonstrated, although PRX has 6-channel capacity.) + +# Setup: 2 x NRF24L01+ (each with 5V to 3.3V "adapter plate") (total eBay cost under $5, "8 pin" types) +# Radio1 in PTX (master) mode, radio2 in PRX (slave/server) mode. +# Pins: Radio1 Arduino pin9 as NRF24-CSN (spi CE), Tie RF24-CE to HIGH (+3 or +5) +# Radio2 Arduino pin10 as NRF24-CSN, Tie RF24-CE to HIGH (+3 or +5) +# (VirtGPIO/arduino uses notation "CE" differently from NRF24 usage!) +# Both radios: Arduino pin11 = MOSI, pin12 = MISO, pin13 = SCLK +# Note: NRF24 power MUST be 3.3V, NOT 5v. (Its data pins may operate at 3.3V or 5V.) + +# Optionally, a LED on pin6, as switched-across "activity Led". + + +import virtGPIO as GPIO +from lib_nrf24 import NRF24 +import time + + +pipes = [[0xe7, 0xe7, 0xe7, 0xe7, 0xe7], [0xc2, 0xc2, 0xc2, 0xc2, 0xc2]] + +# Comment re multiple SPIDEV devices: +# Official spidev documentation is sketchy. Implementation in virtGPIO allows multiple SpiDev() objects. +# This may not work on RPi? Probably RPi uses alternating open() / xfer2() /close() within one SpiDev() object??? +# On virtGPIO each of multiple SpiDev() stores its own mode and cePin. Multiple RF24 used here becomes easy. +# This issue affects only using MULTIPLE Spi devices. + +################################################################## +# SET UP RADIO1 - PTX + +radio1 = NRF24(GPIO, GPIO.SpiDev()) +radio1.begin(9) # SPI-CE=RF24-CSN=pin9, no RF24-CE pin +time.sleep(1) +radio1.setRetries(15,15) +radio1.setPayloadSize(32) +radio1.setChannel(0x62) +radio1.setDataRate(NRF24.BR_2MBPS) +radio1.setPALevel(NRF24.PA_MIN) +radio1.setAutoAck(True) +radio1.enableDynamicPayloads() +radio1.enableAckPayload() + +radio1.openWritingPipe(pipes[1]) +radio1.openReadingPipe(1, pipes[0]) + +if not radio1.isPVariant(): + # If radio configures correctly, we confirmed a "plus" (ie "variant") nrf24l01+ + # Else print diagnostic stuff & exit. + radio1.printDetails() + # (or we could always just print details anyway, even on good setup, for debugging) + print ("NRF24L01+ not found.") + exit() + + +################################################################## +# AND THEN RADIO2 - PRX - VIRTUALLY IDENTICAL ! + +radio2 = NRF24(GPIO, GPIO.SpiDev()) +radio2.begin(10) # SPI-CE=RF24-CSN=pin10, no RF24-CE pin +time.sleep(1) +radio2.setRetries(15,15) + +radio2.setPayloadSize(32) +radio2.setChannel(0x62) +radio2.setDataRate(NRF24.BR_2MBPS) +radio2.setPALevel(NRF24.PA_MIN) + +radio2.setAutoAck(True) +radio2.enableDynamicPayloads() +radio2.enableAckPayload() + +radio2.openWritingPipe(pipes[0]) +radio2.openReadingPipe(1, pipes[1]) + +radio2.startListening() + +if not radio2.isPVariant(): + radio2.stopListening() + radio2.printDetails() + print ("NRF24L01+ not found.") + exit() + + +################################################################## +c1 = 1 +def serviceRadio1(): + # Let's deal with PTX - radio1: + + print ("TX:") + global c1 + global radio1 + buf = ['H', 'E', 'L', 'O',(c1 & 255)] # something to recognise at other end + c1 += 1 + # send a packet to receiver + radio1.write(buf) + # RF24 handles all timeouts, retries and ACKs and ACK-payload + # So the call to radio.write() only returns after ack and its payload have finished + print ("\033[31;1mPTX Sent:\033[0m"), + print (buf) + # did a payload come back with the ACK? + if radio1.isAckPayloadAvailable(): + pl_buffer=[] + radio1.read(pl_buffer, radio1.getDynamicPayloadSize()) + print ("\033[31;1mPTX Received back:\033[0m"), + print (pl_buffer) + else: + print ("PTX Received: Ack only, no payload") + +################################################################## +c2 = 1 +def serviceRadio2(): + # Now deal separately with PRX - radio 2 + print ("RX?") + global c2 + global radio2 + akpl_buf = [(c2& 255),1, 2, 3,4,5,6,7,8,9,0,1, 2, 3,4,5,6,7,8] # We should see this returned to PTX + pipe = [0] + if not radio2.available(pipe): + return + + recv_buffer = [] + radio2.read(recv_buffer, radio2.getDynamicPayloadSize()) + print ("\033[32;1mPRX Received:\033[0m") , + print (recv_buffer) + c2 += 1 + if (c2&1) == 0: # alternate times - so we can see difference beteeen ack-payload and no ack-payload + radio2.writeAckPayload(1, akpl_buf, len(akpl_buf)) + print ("PRX Loaded payload reply:"), + print (akpl_buf) + else: + print ("PRX: (No return payload)") + + +################################################################## +# We could experiment with differing payload lengths above, up to max 32 bytes each way. + + +c=0 +while True: + c += 1 + print ("Loop %d" % c), + if not (c % 3): # only once per x loops + serviceRadio1() # send something + time.sleep(0.01) + else: + serviceRadio2() # has it arrived? (if so, maybe send return data) + time.sleep(2) # 1 sec per loop diff --git a/example-nrf24-recv.py b/example-nrf24-recv.py new file mode 100644 index 0000000..faf7174 --- /dev/null +++ b/example-nrf24-recv.py @@ -0,0 +1,56 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Example program to receive packets from the radio link +# + +import virtGPIO as GPIO +from lib_nrf24 import NRF24 +import time + + + +pipes = [[0xe7, 0xe7, 0xe7, 0xe7, 0xe7], [0xc2, 0xc2, 0xc2, 0xc2, 0xc2]] + +radio2 = NRF24(GPIO, GPIO.SpiDev()) +radio2.begin(9, 7) + +radio2.setRetries(15,15) + +radio2.setPayloadSize(32) +radio2.setChannel(0x60) +radio2.setDataRate(NRF24.BR_2MBPS) +radio2.setPALevel(NRF24.PA_MIN) + +radio2.setAutoAck(True) +radio2.enableDynamicPayloads() +radio2.enableAckPayload() + +radio2.openWritingPipe(pipes[0]) +radio2.openReadingPipe(1, pipes[1]) + +radio2.startListening() +radio2.stopListening() + +radio2.printDetails() + +radio2.startListening() + +c=1 +while True: + akpl_buf = [c,1, 2, 3,4,5,6,7,8,9,0,1, 2, 3,4,5,6,7,8] + pipe = [0] + while not radio2.available(pipe): + time.sleep(10000/1000000.0) + + recv_buffer = [] + radio2.read(recv_buffer, radio2.getDynamicPayloadSize()) + print ("Received:") , + print (recv_buffer) + c = c + 1 + if (c&1) == 0: + radio2.writeAckPayload(1, akpl_buf, len(akpl_buf)) + print ("Loaded payload reply:"), + print (akpl_buf) + else: + print ("(No return payload)") diff --git a/example-nrf24-send.py b/example-nrf24-send.py new file mode 100644 index 0000000..0ea8a64 --- /dev/null +++ b/example-nrf24-send.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Example program to send packets to the radio link +# + + +import virtGPIO as GPIO +from lib_nrf24 import NRF24 +import time + + + +pipes = [[0xe7, 0xe7, 0xe7, 0xe7, 0xe7], [0xc2, 0xc2, 0xc2, 0xc2, 0xc2]] + +radio = NRF24(GPIO, GPIO.SpiDev()) +radio.begin(10, 8) #Set spi-ce pin10, and rf24-CE pin 8 +time.sleep(1) +radio.setRetries(15,15) +radio.setPayloadSize(32) +radio.setChannel(0x60) + +radio.setDataRate(NRF24.BR_2MBPS) +radio.setPALevel(NRF24.PA_MIN) +radio.setAutoAck(True) +radio.enableDynamicPayloads() +radio.enableAckPayload() + + +radio.openWritingPipe(pipes[1]) +radio.openReadingPipe(1, pipes[0]) +radio.printDetails() + + +c=1 +while True: + buf = ['H', 'E', 'L', 'O',c] + c = (c + 1) & 255 + # send a packet to receiver + radio.write(buf) + print ("Sent:"), + print (buf) + # did it return with a payload? + if radio.isAckPayloadAvailable(): + pl_buffer=[] + radio.read(pl_buffer, radio.getDynamicPayloadSize()) + print ("Received back:"), + print (pl_buffer) + else: + print ("Received: Ack only, no payload") + time.sleep(10) diff --git a/lib_nrf24.py b/lib_nrf24.py new file mode 100644 index 0000000..bfd5f7c --- /dev/null +++ b/lib_nrf24.py @@ -0,0 +1,787 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# + + +# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24". + +# So this is my tweak for Raspberry Pi and "Virtual GPIO" ... +# ... of Barraca's port to BeagleBone python ... (Joao Paulo Barraca ) +# ... of maniacbug's NRF24L01 C++ library for Arduino. +# Brian Lavery Oct 2014 + + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + + +import sys +import time + +if __name__ == '__main__': + print (sys.argv[0], 'is an importable module:') + print ("... from", sys.argv[0], "import lib_nrf24") + print ("") + + exit() + +def _BV(x): + return 1 << x + + +class NRF24: + MAX_CHANNEL = 127 + MAX_PAYLOAD_SIZE = 32 + + # PA Levels + PA_MIN = 0 + PA_LOW = 1 + PA_HIGH = 2 + PA_MAX = 3 + PA_ERROR = 4 + + # Bit rates + BR_1MBPS = 0 + BR_2MBPS = 1 + BR_250KBPS = 2 + + # CRC + CRC_DISABLED = 0 + CRC_8 = 1 + CRC_16 = 2 + CRC_ENABLED = 3 + + # Registers + CONFIG = 0x00 + EN_AA = 0x01 + EN_RXADDR = 0x02 + SETUP_AW = 0x03 + SETUP_RETR = 0x04 + RF_CH = 0x05 + RF_SETUP = 0x06 + STATUS = 0x07 + OBSERVE_TX = 0x08 + CD = 0x09 + RX_ADDR_P0 = 0x0A + RX_ADDR_P1 = 0x0B + RX_ADDR_P2 = 0x0C + RX_ADDR_P3 = 0x0D + RX_ADDR_P4 = 0x0E + RX_ADDR_P5 = 0x0F + TX_ADDR = 0x10 + RX_PW_P0 = 0x11 + RX_PW_P1 = 0x12 + RX_PW_P2 = 0x13 + RX_PW_P3 = 0x14 + RX_PW_P4 = 0x15 + RX_PW_P5 = 0x16 + FIFO_STATUS = 0x17 + DYNPD = 0x1C + FEATURE = 0x1D + + + # Bit Mnemonics */ + MASK_RX_DR = 6 + MASK_TX_DS = 5 + MASK_MAX_RT = 4 + EN_CRC = 3 + CRCO = 2 + PWR_UP = 1 + PRIM_RX = 0 + ENAA_P5 = 5 + ENAA_P4 = 4 + ENAA_P3 = 3 + ENAA_P2 = 2 + ENAA_P1 = 1 + ENAA_P0 = 0 + ERX_P5 = 5 + ERX_P4 = 4 + ERX_P3 = 3 + ERX_P2 = 2 + ERX_P1 = 1 + ERX_P0 = 0 + AW = 0 + ARD = 4 + ARC = 0 + PLL_LOCK = 4 + RF_DR = 3 + RF_PWR = 6 + RX_DR = 6 + TX_DS = 5 + MAX_RT = 4 + RX_P_NO = 1 + TX_FULL = 0 + PLOS_CNT = 4 + ARC_CNT = 0 + TX_REUSE = 6 + FIFO_FULL = 5 + TX_EMPTY = 4 + RX_FULL = 1 + RX_EMPTY = 0 + DPL_P5 = 5 + DPL_P4 = 4 + DPL_P3 = 3 + DPL_P2 = 2 + DPL_P1 = 1 + DPL_P0 = 0 + EN_DPL = 2 + EN_ACK_PAY = 1 + EN_DYN_ACK = 0 + + # Instruction Mnemonics + R_REGISTER = 0x00 + W_REGISTER = 0x20 + REGISTER_MASK = 0x1F + ACTIVATE = 0x50 + R_RX_PL_WID = 0x60 + R_RX_PAYLOAD = 0x61 + W_TX_PAYLOAD = 0xA0 + W_ACK_PAYLOAD = 0xA8 + FLUSH_TX = 0xE1 + FLUSH_RX = 0xE2 + REUSE_TX_PL = 0xE3 + NOP = 0xFF + + + # Non-P omissions + LNA_HCURR = 0x00 + + # P model memory Map + RPD = 0x09 + + # P model bit Mnemonics + RF_DR_LOW = 5 + RF_DR_HIGH = 3 + RF_PWR_LOW = 1 + RF_PWR_HIGH = 2 + + # Signal Mnemonics + LOW = 0 + HIGH = 1 + + datarate_e_str_P = ["1MBPS", "2MBPS", "250KBPS"] + model_e_str_P = ["nRF24L01", "nRF24l01+"] + crclength_e_str_P = ["Disabled", "8 bits", "16 bits"] + pa_dbm_e_str_P = ["PA_MIN", "PA_LOW", "PA_MED", "PA_HIGH"] + child_pipe = [RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5] + + child_payload_size = [RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5] + child_pipe_enable = [ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5] + + GPIO = None + spidev = None + + def __init__(self, gpio, spidev): + # It should be possible to instantiate multiple objects, with different GPIO / spidev + # EG on Raspberry, one could be RPI GPIO & spidev module, other could be virtual-GPIO + # On rpi, only bus 0 is supported here, not bus 1 of the model B plus + self.GPIO = gpio # the GPIO module + self.spidev = spidev # the spidev object/instance + self.channel = 76 + self.data_rate = NRF24.BR_1MBPS + self.wide_band = False # 2Mbs data rate in use? + self.p_variant = False # False for RF24L01 and true for RF24L01P (nrf24l01+) + self.payload_size = 5 #*< Fixed size of payloads + self.ack_payload_available = False #*< Whether there is an ack payload waiting + self.dynamic_payloads_enabled = False #*< Whether dynamic payloads are enabled. + self.ack_payload_length = 5 #*< Dynamic size of pending ack payload. + self.pipe0_reading_address = None #*< Last address set on pipe 0 for reading. + + def ce(self, level): + if self.ce_pin == 0: + return + # rf24-CE is optional. Tie to HIGH if not used. (Altho, left floating seems to read HIGH anyway??? - risky!) + # Some RF24 modes may NEED control over CE. + # non-powerdown, fixed PTX or RTX role, dynamic payload size & ack-payload: does NOT need CE. + if level == NRF24.HIGH: + self.GPIO.output(self.ce_pin, self.GPIO.HIGH) + else: + self.GPIO.output(self.ce_pin, self.GPIO.LOW) + return + + + + def read_register(self, reg, blen=1): + buf = [NRF24.R_REGISTER | ( NRF24.REGISTER_MASK & reg )] + for col in range(blen): + buf.append(NRF24.NOP) + + resp = self.spidev.xfer2(buf) + if blen == 1: + return resp[1] + + return resp[1:blen + 1] + + def write_register(self, reg, value, length=-1): + buf = [NRF24.W_REGISTER | ( NRF24.REGISTER_MASK & reg )] + ###if isinstance(value, (int, long)): # ng for python3. but value should never be long anyway + if isinstance(value, int): + if length < 0: + length = 1 + + length = min(4, length) + for i in range(length): + buf.insert(1, int(value & 0xff)) + value >>= 8 + + elif isinstance(value, list): + if length < 0: + length = len(value) + + for i in range(min(len(value), length)): + buf.append(int(value[len(value) - i - 1] & 0xff)) + else: + raise Exception("Value must be int or list") + + return self.spidev.xfer2(buf)[0] + + + def write_payload(self, buf): + data_len = min(self.payload_size, len(buf)) + blank_len = 0 + if not self.dynamic_payloads_enabled: + blank_len = self.payload_size - data_len + + txbuffer = [NRF24.W_TX_PAYLOAD] + for n in buf: + t = type(n) + if t is str: + txbuffer.append(ord(n)) + elif t is int: + txbuffer.append(n) + else: + raise Exception("Only ints and chars are supported: Found " + str(t)) + + if blank_len != 0: + blank = [0x00 for i in range(blank_len)] + txbuffer.extend(blank) + + return self.spidev.xfer2(txbuffer) + + def read_payload(self, buf, buf_len=-1): + if buf_len < 0: + buf_len = self.payload_size + data_len = min(self.payload_size, buf_len) + blank_len = 0 + if not self.dynamic_payloads_enabled: + blank_len = self.payload_size - data_len + + txbuffer = [NRF24.NOP for i in range(0, blank_len + data_len + 1)] + txbuffer[0] = NRF24.R_RX_PAYLOAD + + payload = self.spidev.xfer2(txbuffer) + del buf[:] + buf.extend(payload[1:data_len + 1]) + return data_len + + def flush_rx(self): + return self.spidev.xfer2([NRF24.FLUSH_RX])[0] + + def flush_tx(self): + return self.spidev.xfer2([NRF24.FLUSH_TX])[0] + + def get_status(self): + return self.spidev.xfer2([NRF24.NOP])[0] + + def print_status(self, status): + status_str = "STATUS\t = 0x{0:02x} RX_DR={1:x} TX_DS={2:x} MAX_RT={3:x} RX_P_NO={4:x} TX_FULL={5:x}".format( + status, + 1 if status & _BV(NRF24.RX_DR) else 0, + 1 if status & _BV(NRF24.TX_DS) else 0, + 1 if status & _BV(NRF24.MAX_RT) else 0, + ((status >> NRF24.RX_P_NO) & 7), + 1 if status & _BV(NRF24.TX_FULL) else 0) + + print (status_str) + + def print_observe_tx(self, value): + print ("Observe Tx: %02x Lost Pkts: %d Retries: %d" % (value, value >> NRF24.PLOS_CNT, value & 15)) + + + def print_byte_register(self, name, reg, qty=1): + extra_tab = '\t' if len(name) < 8 else 0 + print ("%s\t%c =" % (name, extra_tab)), + while qty > 0: + print ("0x%02x" % (self.read_register(reg))), + qty -= 1 + reg += 1 + + print ("") + + def print_address_register(self, name, reg, qty=1): + extra_tab = '\t' if len(name) < 8 else 0 + print ("%s\t%c =" % (name, extra_tab)), + + while qty > 0: + qty -= 1 + buf = reversed(self.read_register(reg, 5)) + reg += 1 + sys.stdout.write(" 0x"), + for i in buf: + sys.stdout.write("%02x" % i) + + print ("") + + + def setChannel(self, channel): + self.channel = min(max(0, channel), NRF24.MAX_CHANNEL) + self.write_register(NRF24.RF_CH, self.channel) + + def getChannel(self): + return self.read_register(NRF24.RF_CH) + + def setPayloadSize(self, size): + self.payload_size = min(max(size, 1), NRF24.MAX_PAYLOAD_SIZE) + + def getPayloadSize(self): + return self.payload_size + + def printDetails(self): + self.print_status(self.get_status()) + self.print_address_register("RX_ADDR_P0-1", NRF24.RX_ADDR_P0, 2) + self.print_byte_register("RX_ADDR_P2-5", NRF24.RX_ADDR_P2, 4) + self.print_address_register("TX_ADDR", NRF24.TX_ADDR) + + self.print_byte_register("RX_PW_P0-6", NRF24.RX_PW_P0, 6) + self.print_byte_register("EN_AA", NRF24.EN_AA) + self.print_byte_register("EN_RXADDR", NRF24.EN_RXADDR) + self.print_byte_register("RF_CH", NRF24.RF_CH) + self.print_byte_register("RF_SETUP", NRF24.RF_SETUP) + self.print_byte_register("CONFIG", NRF24.CONFIG) + self.print_byte_register("DYNPD/FEATURE", NRF24.DYNPD, 2) + + # + print ("Data Rate\t = %s" % NRF24.datarate_e_str_P[self.getDataRate()]) + print ("Model\t\t = %s" % NRF24.model_e_str_P[self.isPVariant()]) + print ("CRC Length\t = %s" % NRF24.crclength_e_str_P[self.getCRCLength()]) + print ("PA Power\t = %s" % NRF24.pa_dbm_e_str_P[self.getPALevel()]) + + def begin(self, csn_pin, ce_pin=0): # csn & ce are RF24 terminology. csn = SPI's CE! + # Initialize SPI bus.. + # ce_pin is for the rx=listen or tx=trigger pin on RF24 (they call that ce !!!) + # CE optional (at least in some circumstances, eg fixed PTX PRX roles, no powerdown) + # CE seems to hold itself as (sufficiently) HIGH, but tie HIGH is safer! + self.spidev.open(0, csn_pin) + self.ce_pin = ce_pin + + if ce_pin: + self.GPIO.setup(self.ce_pin, self.GPIO.OUT) + + time.sleep(5 / 1000000.0) + + # Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier + # WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet + # sizes must never be used. See documentation for a more complete explanation. + self.write_register(NRF24.SETUP_RETR, (0b0100 << NRF24.ARD) | 0b1111) + + # Restore our default PA level + self.setPALevel(NRF24.PA_MAX) + + # Determine if this is a p or non-p RF24 module and then + # reset our data rate back to default value. This works + # because a non-P variant won't allow the data rate to + # be set to 250Kbps. + if self.setDataRate(NRF24.BR_250KBPS): + self.p_variant = True + + # Then set the data rate to the slowest (and most reliable) speed supported by all + # hardware. + self.setDataRate(NRF24.BR_1MBPS) + + # Initialize CRC and request 2-byte (16bit) CRC + self.setCRCLength(NRF24.CRC_16) + + # Disable dynamic payloads, to match dynamic_payloads_enabled setting + self.write_register(NRF24.DYNPD, 0) + + # Reset current status + # Notice reset and flush is the last thing we do + self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT)) + + # Set up default configuration. Callers can always change it later. + # This channel should be universally safe and not bleed over into adjacent + # spectrum. + self.setChannel(self.channel) + + # Flush buffers + self.flush_rx() + self.flush_tx() + + def end(self): + if self.spidev: + self.spidev.close() + self.spidev = None + + def startListening(self): + self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) | _BV(NRF24.PRIM_RX)) + self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT)) + + # Restore the pipe0 address, if exists + if self.pipe0_reading_address: + self.write_register(self.RX_ADDR_P0, self.pipe0_reading_address, 5) + + # Go! + self.ce(NRF24.HIGH) + + # wait for the radio to come up (130us actually only needed) + time.sleep(130 / 1000000.0) + + def stopListening(self): + self.ce(NRF24.LOW) + self.flush_tx() + self.flush_rx() + + def powerDown(self): + self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) & ~_BV(NRF24.PWR_UP)) + + def powerUp(self): + self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP)) + time.sleep(150 / 1000000.0) + + def write(self, buf): + # Begin the write + self.startWrite(buf) + + timeout = self.getMaxTimeout() #s to wait for timeout + sent_at = time.time() + + while True: + #status = self.read_register(NRF24.OBSERVE_TX, 1) + status = self.get_status() + if (status & (_BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))) or (time.time() - sent_at > timeout ): + break + time.sleep(10 / 1000000.0) + #obs = self.read_register(NRF24.OBSERVE_TX) + #self.print_observe_tx(obs) + #self.print_status(status) + # (for debugging) + + what = self.whatHappened() + + result = what['tx_ok'] + if what['tx_fail']: + self.flush_tx(); # bl - dont jam up the fifo + # Handle the ack packet + if what['rx_ready']: + self.ack_payload_length = self.getDynamicPayloadSize() + self.ack_payload_available = True ## bl + + return result + + def startWrite(self, buf): + # Transmitter power-up + self.write_register(NRF24.CONFIG, (self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) ) & ~_BV(NRF24.PRIM_RX)) + + # Send the payload + self.write_payload(buf) + + # Allons! + if self.ce_pin: + if self.GPIO.RPI_REVISION > 0: + self.ce(self.GPIO.HIGH) + time.sleep(10 / 1000000.0) + self.ce(self.GPIO.LOW) + else: + # virtGPIO is slower. A 10 uSec pulse is better done with pulseOut(): + self.GPIO.pulseOut(self.ce_pin, self.GPIO.HIGH, 10) + + + + def getDynamicPayloadSize(self): + return self.spidev.xfer2([NRF24.R_RX_PL_WID, NRF24.NOP])[1] + + def available(self, pipe_num=None): + if not pipe_num: + pipe_num = [] + + status = self.get_status() + result = False + + # Sometimes the radio specifies that there is data in one pipe but + # doesn't set the RX flag... + if status & _BV(NRF24.RX_DR) or (status & 0b00001110 != 0b00001110): + result = True + + if result: + # If the caller wants the pipe number, include that + if len(pipe_num) >= 1: + pipe_num[0] = ( status >> NRF24.RX_P_NO ) & 0b00000111 + + # Clear the status bit + + # ??? Should this REALLY be cleared now? Or wait until we + # actually READ the payload? + self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR)) + + # Handle ack payload receipt + if status & _BV(NRF24.TX_DS): + self.write_register(NRF24.STATUS, _BV(NRF24.TX_DS)) + + return result + + def read(self, buf, buf_len=-1): + # Fetch the payload + self.read_payload(buf, buf_len) + + # was this the last of the data available? + return self.read_register(NRF24.FIFO_STATUS) & _BV(NRF24.RX_EMPTY) + + def whatHappened(self): + # Read the status & reset the status in one easy call + # Or is that such a good idea? + status = self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT)) + + # Report to the user what happened + tx_ok = status & _BV(NRF24.TX_DS) + tx_fail = status & _BV(NRF24.MAX_RT) + rx_ready = status & _BV(NRF24.RX_DR) + return {'tx_ok': tx_ok, "tx_fail": tx_fail, "rx_ready": rx_ready} + + def openWritingPipe(self, value): + # Note that the NRF24L01(+) + # expects it LSB first. + + self.write_register(NRF24.RX_ADDR_P0, value, 5) + self.write_register(NRF24.TX_ADDR, value, 5) + + max_payload_size = 32 + self.write_register(NRF24.RX_PW_P0, min(self.payload_size, max_payload_size)) + + def openReadingPipe(self, child, address): + # If this is pipe 0, cache the address. This is needed because + # openWritingPipe() will overwrite the pipe 0 address, so + # startListening() will have to restore it. + if child == 0: + self.pipe0_reading_address = address + + if child <= 6: + # For pipes 2-5, only write the LSB + if child < 2: + self.write_register(NRF24.child_pipe[child], address, 5) + else: + self.write_register(NRF24.child_pipe[child], address, 1) + + self.write_register(NRF24.child_payload_size[child], self.payload_size) + + # Note it would be more efficient to set all of the bits for all open + # pipes at once. However, I thought it would make the calling code + # more simple to do it this way. + self.write_register(NRF24.EN_RXADDR, + self.read_register(NRF24.EN_RXADDR) | _BV(NRF24.child_pipe_enable[child])) + + + def closeReadingPipe(self, pipe): + self.write_register(NRF24.EN_RXADDR, + self.read_register(EN_RXADDR) & ~_BV(NRF24.child_pipe_enable[pipe])) + + + def toggle_features(self): + buf = [NRF24.ACTIVATE, 0x73] + self.spidev.xfer2(buf) + + def enableDynamicPayloads(self): + # Enable dynamic payload throughout the system + self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL)) + + # If it didn't work, the features are not enabled + if not self.read_register(NRF24.FEATURE): + # So enable them and try again + self.toggle_features() + self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL)) + + # Enable dynamic payload on all pipes + + # Not sure the use case of only having dynamic payload on certain + # pipes, so the library does not support it. + self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P5) | _BV(NRF24.DPL_P4) | _BV( + NRF24.DPL_P3) | _BV(NRF24.DPL_P2) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0)) + + self.dynamic_payloads_enabled = True + + + def enableAckPayload(self): + # enable ack payload and dynamic payload features + self.write_register(NRF24.FEATURE, + self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL)) + + # If it didn't work, the features are not enabled + if not self.read_register(NRF24.FEATURE): + # So enable them and try again + self.toggle_features() + self.write_register(NRF24.FEATURE, + self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL)) + + # Enable dynamic payload on pipes 0 & 1 + self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0)) + + def writeAckPayload(self, pipe, buf, buf_len): + txbuffer = [NRF24.W_ACK_PAYLOAD | ( pipe & 0x7 )] + + max_payload_size = 32 + data_len = min(buf_len, max_payload_size) + txbuffer.extend(buf[0:data_len]) + + self.spidev.xfer2(txbuffer) + + def isAckPayloadAvailable(self): + result = self.ack_payload_available + self.ack_payload_available = False + return result + + def isPVariant(self): + return self.p_variant + + def setAutoAck(self, enable): + if enable: + self.write_register(NRF24.EN_AA, 0b111111) + else: + self.write_register(NRF24.EN_AA, 0) + + def setAutoAckPipe(self, pipe, enable): + if pipe <= 6: + en_aa = self.read_register(NRF24.EN_AA) + if enable: + en_aa |= _BV(pipe) + else: + en_aa &= ~_BV(pipe) + + self.write_register(NRF24.EN_AA, en_aa) + + def testCarrier(self): + return self.read_register(NRF24.CD) & 1 + + def testRPD(self): + return self.read_register(NRF24.RPD) & 1 + + def setPALevel(self, level): + setup = self.read_register(NRF24.RF_SETUP) + setup &= ~( _BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) + # switch uses RAM (evil!) + if level == NRF24.PA_MAX: + setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) + elif level == NRF24.PA_HIGH: + setup |= _BV(NRF24.RF_PWR_HIGH) + elif level == NRF24.PA_LOW: + setup |= _BV(NRF24.RF_PWR_LOW) + elif level == NRF24.PA_MIN: + nop = 0 + elif level == NRF24.PA_ERROR: + # On error, go to maximum PA + setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) + + self.write_register(NRF24.RF_SETUP, setup) + + + def getPALevel(self): + power = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)) + + if power == (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)): + return NRF24.PA_MAX + elif power == _BV(NRF24.RF_PWR_HIGH): + return NRF24.PA_HIGH + elif power == _BV(NRF24.RF_PWR_LOW): + return NRF24.PA_LOW + else: + return NRF24.PA_MIN + + def setDataRate(self, speed): + result = False + setup = self.read_register(NRF24.RF_SETUP) + + # HIGH and LOW '00' is 1Mbs - our default + self.wide_band = False + setup &= ~(_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH)) + + if speed == NRF24.BR_250KBPS: + # Must set the RF_DR_LOW to 1 RF_DR_HIGH (used to be RF_DR) is already 0 + # Making it '10'. + self.wide_band = False + setup |= _BV(NRF24.RF_DR_LOW) + else: + # Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1 + # Making it '01' + if speed == NRF24.BR_2MBPS: + self.wide_band = True + setup |= _BV(NRF24.RF_DR_HIGH) + else: + # 1Mbs + self.wide_band = False + + self.write_register(NRF24.RF_SETUP, setup) + + # Verify our result + if self.read_register(NRF24.RF_SETUP) == setup: + result = True + else: + self.wide_band = False + return result + + def getDataRate(self): + dr = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH)) + # Order matters in our case below + if dr == _BV(NRF24.RF_DR_LOW): + # '10' = 250KBPS + return NRF24.BR_250KBPS + elif dr == _BV(NRF24.RF_DR_HIGH): + # '01' = 2MBPS + return NRF24.BR_2MBPS + else: + # '00' = 1MBPS + return NRF24.BR_1MBPS + + + def setCRCLength(self, length): + config = self.read_register(NRF24.CONFIG) & ~( _BV(NRF24.CRC_16) | _BV(NRF24.CRC_ENABLED)) + + if length == NRF24.CRC_DISABLED: + # Do nothing, we turned it off above. + self.write_register(NRF24.CONFIG, config) + return + elif length == NRF24.CRC_8: + config |= _BV(NRF24.CRC_ENABLED) + config |= _BV(NRF24.CRC_8) + else: + config |= _BV(NRF24.CRC_ENABLED) + config |= _BV(NRF24.CRC_16) + + self.write_register(NRF24.CONFIG, config) + + def getCRCLength(self): + result = NRF24.CRC_DISABLED + config = self.read_register(NRF24.CONFIG) & ( _BV(NRF24.CRCO) | _BV(NRF24.EN_CRC)) + + if config & _BV(NRF24.EN_CRC): + if config & _BV(NRF24.CRCO): + result = NRF24.CRC_16 + else: + result = NRF24.CRC_8 + + return result + + def disableCRC(self): + disable = self.read_register(NRF24.CONFIG) & ~_BV(NRF24.EN_CRC) + self.write_register(NRF24.CONFIG, disable) + + def setRetries(self, delay, count): + # see specs. Delay code below 5 can conflict with some ACK lengths + # and count should be set = 0 for non-ACK modes + self.write_register(NRF24.SETUP_RETR, (delay & 0xf) << NRF24.ARD | (count & 0xf)) + + def getRetries(self): + return self.read_register(NRF24.SETUP_RETR) + + def getMaxTimeout(self): # seconds + retries = self.getRetries() + tout = (((250+(250*((retries& 0xf0)>>4 ))) * (retries & 0x0f)) / 1000000.0 * 2) + 0.008 + # Fudged up to about double Barraca's calculation + # Was too short & was timeing out wrongly. BL + return tout