Code Monkey home page Code Monkey logo

adafruit_circuitpython_rfm9x's Introduction

Introduction

Documentation Status Discord Build Status Code Style: Black

CircuitPython module for the RFM95/6/7/8 LoRa 433/915mhz radio modules.

Dependencies

This driver depends on:

Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading the Adafruit library and driver bundle.

Installing from PyPI

On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally from PyPI. To install for current user:

pip3 install adafruit-circuitpython-rfm9x

To install system-wide (this may be required in some cases):

sudo pip3 install adafruit-circuitpython-rfm9x

To install in a virtual environment in your current project:

mkdir project-name && cd project-name
python3 -m venv .venv
source .venv/bin/activate
pip3 install adafruit-circuitpython-rfm9x

Usage Example

Initialization of the RFM radio requires specifying a frequency appropriate to your radio hardware (i.e. 868-915 or 433 MHz) and specifying the pins used in your wiring from the controller board to the radio module.

This example code matches the wiring used in the LoRa and LoRaWAN Radio for Raspberry Pi project:

import digitalio
import board
import busio
import adafruit_rfm9x

RADIO_FREQ_MHZ = 915.0
CS = digitalio.DigitalInOut(board.CE1)
RESET = digitalio.DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

Note: the default baudrate for the SPI is 50000000 (5MHz). The maximum setting is 10Mhz but transmission errors have been observed expecially when using breakout boards. For breakout boards or other configurations where the boards are separated, it may be necessary to reduce the baudrate for reliable data transmission. The baud rate may be specified as an keyword parameter when initializing the board. To set it to 1000000 use :

# Initialze RFM radio with a more conservative baudrate
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ, baudrate=1000000)

Optional controls exist to alter the signal bandwidth, coding rate, and spreading factor settings used by the radio to achieve better performance in different environments. By default, settings compatible with RadioHead Bw125Cr45Sf128 mode are used, which can be altered in the following manner (continued from the above example):

# Apply new modem config settings to the radio to improve its effective range
rfm9x.signal_bandwidth = 62500
rfm9x.coding_rate = 6
rfm9x.spreading_factor = 8
rfm9x.enable_crc = True

See examples/rfm9x_simpletest.py for an expanded demo of the usage.

Documentation

API documentation for this library can be found on Read the Docs.

For information on building library documentation, please check out this guide.

Contributing

Contributions are welcome! Please read our Code of Conduct before contributing to help this project stay welcoming.

adafruit_circuitpython_rfm9x's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adafruit_circuitpython_rfm9x's Issues

RFM9X RESET not working with Raspberry Pi 4

I have been tryinog to use an RFM9x on a Raspberry Pi 4 with Raspian "Buster"
At first I kept getting the message that it could not verify the device ID but I checked the wiring and it is OK. ( I use the same wiring with an RFM69 and it works)

The issue appears to be related to the toggling of the RESET which is different for the RFM9x vs RFM69

If I disconnect the RESET line then it can deter the chip ID and it receives packets, to appear to miss many packets as well.

I also tried it with the TinyLoRa library which is xmit only. It works OK as long as the RESET line is not connected (RESET is not used for TinyLoRa)

I have a lot more digging to do , but just wanted to post this as an alert and see if anyone else can reproduce the issue.

I'll update when I can do more testing.

Note: The rfm9x library and RESET work on a Raspberry Pi Zero-W with Raspian Stretch

Library uses short argument names

pylint suggests using argument names with at least 3 letters. This library uses argument names of shorter length, and while these warnings have been disabled for now, they should be considered for renaming. This may require the rework of Learn Guides and other references to code snippets.

Lib improvements & silicon errata

Having taken the CircuitPython_RFM9x lib and heavily modifying it for my own applications, there are some general register settings that could be implemented to improve performance as well as some SX1276/7/8/9 errata that may want to be included.

General Performance Improvements

Many of these are not present in the HopeRF datasheet (but ARE described in the SX1276/7/8/9 datasheet). My testing with HopeRF RFM98PW modules has convinced me these specific semtech registers DO make a difference.

  1. Enabling automatic gain control has improved my LoRa Rx packet RSSI sensitivity in all situations I've encountered. I just created another registerbits instance: auto_agc = _RegisterBits(_RH_RF95_REG_26_MODEM_CONFIG3, offset=2, bits=1) and set it True during init. See register 0x26 in either HopeRF or Semtech datasheet.
  2. In it's current state, I'm unable to this library for low data rate operation in standard (benchtop) testing conditions. This can be remedied by setting bit 3 of register 0x26. Semtech calls it "Low Data Rate Optimization" but HopeRF calls this bit "MobileNode." See semtech datasheet for more detail.
  3. (for high frequency modules) setting LnaBoostHF (register 0x0C bits 0,1) to 0b11

Addressing Errata

Semtech has an: errata doc for the SX1276/7/8/9. For all my modules, the silicon version matches that which is specified in the beginning of the errata (0x12), and implementing the following fixes DID make a difference in performance.

  1. Section 2.1 Sensitivity Optimization with a 500 kHz Bandwidth -- which can be just an easy check & write in during the signal_bandwidth setter.
  2. Section 2.3 Receiver Spurious Reception of a LoRa Signal -- a handful of different configuration cases will need to be accounted for, but I saw improved rejection for the bandwidths I'm utilizing.
    My hacky implementation look like this:
    @signal_bandwidth.setter
    def signal_bandwidth(self, val):
        # Set signal bandwidth (set to 125000 to match RadioHead Bw125).
        for bw_id, cutoff in enumerate(self.bw_bins):
            if val <= cutoff:
                break
        else:
            bw_id = 9
        self._write_u8(
            _RH_RF95_REG_1D_MODEM_CONFIG1,
            (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0F) | (bw_id << 4))
        if val >= 500000:
            # see Semtech SX1276 errata note 2.1
            self._write_u8(0x36,0x02)
            self._write_u8(0x3a,0x64)
        else:
            if val == 7800:
                self._write_u8(0x2F,0x48)
            elif val >= 62500:
                # see Semtech SX1276 errata note 2.3
                self._write_u8(0x2F,0x40)
            else:
                self._write_u8(0x2F,0x44)
            self._write_u8(0x30,0)
    @spreading_factor.setter
    def spreading_factor(self, val):
        # Set spreading factor (set to 7 to match RadioHead Sf128).
        val = min(max(val, 6), 12)
        self._write_u8(_RH_RF95_DETECTION_OPTIMIZE, 0xC5 if val == 6 else 0xC3)

        if self.signal_bandwidth >= 5000000:
            self._write_u8(_RH_RF95_DETECTION_OPTIMIZE, 0xC5 if val == 6 else 0xC3)
        else:
            # see Semtech SX1276 errata note 2.3
            self._write_u8(_RH_RF95_DETECTION_OPTIMIZE, 0x45 if val == 6 else 0x43)

        self._write_u8(_RH_RF95_DETECTION_THRESHOLD, 0x0C if val == 6 else 0x0A)
        self._write_u8(
            _RH_RF95_REG_1E_MODEM_CONFIG2,
            (
                (self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0x0F)
                | ((val << 4) & 0xF0)
            ),
        )

I'm filing an issue rather than a PR since I don't have the time to implement and test atm ๐Ÿ˜‡
EDIT: @jerryneedell this will likely remedy the "garbage" packets you were investigating last year

Setting of rfm9x.signal_bandwidth does not appear to work for receiving unit.

Using a simple set up with two LoRa devices attached to RP2040/CP7, one set up to TX a message every 1/2 second, the other to receive. Without setting the band_width parameter (i.e. leaving it default) the TX/RX works flawless. However, setting the BW to any value (including the default) fails. Using an SDR tuned to 915MHz I verified the TX unit is properly transmitting and appears to respond to different BW values when set, however the RX unit never receives any packets.

I have attached a simple code.py file that can be run on TX as well as RX unit - use the 'my_role' parameter on line 16 to determine which unit is TX/RX. TX will send a short message each 1/2 second and flips the NEO from red to green. If the RX is working, it will follow with red/green toggle. If not, it will toggle black/blue (or purple if CRC error).

Change rfm9x.signal_bandwidth on line 43 in both RX/TX units. Comment out to accept default value of 125kHz - this works, any other value does not. Different spreading_factor/signal_bandwidth combinations make no difference, all fail.

code.py.zip

Add FSK/OOK compatibility to Driver

I am working on using the RFM98PW on a satellite, and as part of that, I will need to leverage the OOK capabilities of the radio module to transmit an easily-understood beacon. To do this, I have been working on adding FSK/OOK capabilities to the RFM9x driver.

I have been working on a barebones implementation on a fork, although it needs more testing and probably more configuration. I have been mostly targeting OOK, although it shares a lot of overlap with FSK.

I have a few questions regarding this:

  • Would this be functionality that adafruit would be interested in merging into this repo (or separating into a completely separate driver)?
  • Would people who are more familiar with either the specific module or radio use in general be able to provide feedback on my code as I write it, since I am still relatively new to radio hardware and CircuitPython?

I plan to update this issue as I go, as well as create a PR when the code is in a more mature state.

Long range settings

I tried to make settings for long-range, but with bw 31.25kHz, the message wasn't received. Also, I observed that with sf12 bw500kHz works to send and receive packet of data but with small length. If someone knows what settings works for 3km range. Another problem that I observed, if I try to use arduino with radiohead library, at RX the RSSI is around -18dB, and with cpy, is around -50dB at 50cm distance between rx and tx. Sf11 and Sf12 works with 128 preamble length, but the rx lose packets. I have adafruit feather m0 rfm9x lora, and I use 433 MHz.

Implement "reliable datagram"

The Radiohead library has a "Reliable Datagram" mode that uses acknowledgement and retries to insure packet delivery.

Is there interest in implementing that here. I'll be happy to take it on, but wanted to see if there was any concern or interest.

SPI Bus interference from SDCard

In testing a recent PR I noticed that using the RFM9x Radio FeatherWing on a PyGamer (7.3.0-beta.2) when I have an SDCard plugged in the radio doesn't behave correctly. It will cause the same message to get received over and over again rapidly, as well as messages after the first one not being received by the other side.

Removing the SDCard and then re-running the same examples on the same devices then works successfully and behaves as expected when the SDCard is not plugged in.

My best guess is that something internally within the core is perhaps initializing or starting communications with the SDCard on the SPI bus and that is causing interference when the RFM9x radio tries to communicate on the same SPI bus.

Receiving garbage data

Actual data:
bytearray(b'{waterLevel:71,name:TAOF0201,solenoidState:0,valid:1,ultrasonicState:1,waterLevelDistance:43,freeMemory:38,compareStatus:0}')

"Received data:"
Received (raw bytes): bytearray(b"+w4\xb4irL\xa9v\xa5l:7\x11.Lame:TAOF5b51,solenoidState:0,valid:1Lulr\xfa\xf9wonicState:1,waterLevelDistance:43,freeMemory:38,coe\xf8a'`\x06tatus:0}")

I am receiving the above data instead of actual data, some times data was printing correct and some times it behaving strangely. please help me with this.

I am using Arduino uno for client and Raspberry pi for the server with RFM9x

Thankyou

Enhancement: Switch time.monotonic() to supervisor.ticks_ms()

As many RFM9x projects are likely to be in a remote location, and running for long'ish periods, the accuracy of time.monotonic() will over time degrade into the seconds range, making send_with_ack, and receive timeouts longer then desired.

self.ack_wait = 0.5 would effectively become self.ack_wait = 1
self.receive_timeout = 0.5 would become self.receive_timeout = 1
This would become worse over time. (higher values)

Switching to supervisor.ticks_ms() would solve this, however I'm unsure if its available on all platforms yet.

rfm9x.send causes ble disconnect

I have been using the bluefruit M0 for a project with LoRa but I really needed more ram, so I am trying to switch to the nRF52840. The M0 was successfully sending and receiving data to and from a custom iOS app over SPI and I'm using uart on the nRF52.

Using this library is causing the ble connection to terminate. See the below for an example, running as is and then commenting out the rfm9x.send() line. Is this a known issue?


import time
import busio
import board
from digitalio import DigitalInOut, Direction, Pull
from analogio import AnalogIn

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService

import adafruit_rfm9x


# Define radio parameters.
RADIO_FREQ_MHZ = 915.0  # Frequency of radio in Mhz.

# Define pins
CS = DigitalInOut(board.D10)  # "B" on the board
RESET = DigitalInOut(board.D11) # "A" on board

# Define the onboard LED
LED = DigitalInOut(board.D3)
LED.direction = Direction.OUTPUT
LED.value = True

# Initialize SPI bus.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialze RFM radio
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
rfm9x.tx_power = 20

rfm9x.signal_bandwidth = 250000
rfm9x.coding_rate = 5
rfm9x.spreading_factor = 11
rfm9x.enable_crc = False


ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)


while True:
    ble.start_advertising(advertisement)
    print("Waiting to connect")
    while not ble.connected:
        pass
    print("Connected")
    while ble.connected:
        s = uart.read()
        if s:
            try:
                rfm9x.send(bytes(str(s)[-2], "utf-8"))
                result = str(eval(s))
            except Exception as e:
                result = repr(e)

            uart.write(result.encode("utf-8"))

I also am having really strange issues where the uart is sending everything that I print() in my code over to the iphone and not just what i send with uart.write(). That may or may not be a related issue to the library.

incorrect output power settings

This is just a heads up It looks like the output power level is not being set correctly resulting in the output power being significantly lower than expected. What I thought was a simple fix, does not appear to have worked so I'll continue digging and will post a detailed explanation soon. I just wanted to put out a warning in case anyone else notices this.

In brief:
I think this patch is needed in any case, but it does not seem to result is a significant improvement.
From the data sheet and comparing to the Arduino RadioHead code, subtracting 3 from the request power should be done here to ensure that the value sent to the register at lin 507 (requested power -5) is between 0 and 15. The register only accepts a 4 bit value. Without this change, a setting of 23 will result in a value of 18 being sent, but 18 &0xf is 2 not the desired 15 for maximum power.

https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/blob/master/adafruit_rfm9x.py#L513

patch to fix power level:

diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py
index 72d5d93..9b33184 100644
--- a/adafruit_rfm9x.py
+++ b/adafruit_rfm9x.py
@@ -507,6 +507,7 @@ class RFM9x:
             # Enable power amp DAC if power is above 20 dB.
             if val > 20:
                 self.pa_dac = _RH_RF95_PA_DAC_ENABLE
+                val -= 3
             else:
                 self.pa_dac = _RH_RF95_PA_DAC_DISABLE
             self.pa_select = True

Applying this patch does not result in the expected output power improvement so I'll keep looking into it,

rx radio listening interferes with gps updates

I am very new to circuitpython. What I am trying to make is basically a much simpler version of the GlitterPos example: a lora gps using the Feather M0 lora chip + GPS Featherwing to make a dog tracker that sends out my dog's location every 4 seconds. I would like to add a feature where I can send the dog's tracker a message and have the update interval change from 4 seconds to 5 minutes.

The trouble that I am running into is that when I have rx listening (even at a timeout of 0.2), the gps data is ruined and it sends 0's in the dates, satellites, etc, and sends the same timestamp for about 15 to 20 seconds before grabbing another.

Can anyone tell me why this interference is happening?

Ideally I would like for the radio to be able to receive data while sending other data out, but I don't know how to make those happen concurrently and don't think Ive read that there is a possibility of parallel processes with this hardware. Either way, I didn't think that a short listening timeout would do this to the gps data though.

gpos.py:

"""Adapting from adafruit glitter positioning system example"""
import time
import board
import busio
import digitalio
from analogio import AnalogIn
import math

import adafruit_gps
import adafruit_rfm9x

import gc

RADIO_FREQ_MHZ = 915.0
CS = digitalio.DigitalInOut(board.RFM9X_CS)
RESET = digitalio.DigitalInOut(board.RFM9X_RST)

# Define the onboard LED
LED = digitalio.DigitalInOut(board.D13)
LED.direction = digitalio.Direction.OUTPUT

# ##### SETUP BATTERY LEVEL ## - Call it later
vbat_voltage = AnalogIn(board.VOLTAGE_MONITOR)
def get_voltage(pin):
    return (pin.value * 3.3) / 65536 * 2

class GPOS:

    def __init__(self):
        """configure sensors, radio, blinkenlights"""
        self.last_send = time.monotonic()
        
        # self.coords = (0, 0)
        self.init_radio()
        self.init_gps()

    def init_radio(self):
        """Set up RFM95."""
        spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
        self.rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
        self.rfm9x.tx_power = 23  # Default is 13 dB; the RFM95 goes up to 23 dB
        self.radio_tx('Radio initialized')
        time.sleep(1)

    def init_gps(self):
        """Set up GPS module."""
        uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=3)
        gps = adafruit_gps.GPS(uart)
        time.sleep(1)

        # https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf
        # Turn on the basic GGA and RMC info (what you typically want), then
        # set update to once a second:
        gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
        gps.send_command(b"PMTK220,1000")
        time.sleep(1)

        self.gps = gps


    # ############################
    # ############################
    def advance_frame(self):
        """
        Check the radio for new packets, poll GPS and compass data, send a
        radio packet if coordinates have changed (or if it's been a while), and
        update NeoPixel display.  Called in an infinite loop by code.py.
        To inspect the state of the system, initialize a new GlitterPOS object
        from the CircuitPython REPL, and call gp.advance_frame() manually.  You
        can then access the instance variables defined in __init__() and
        init_()* methods.
        """
        # print("hey")  # see how fast looping is happening
        self.gps.update()
        current = time.monotonic()
        self.radio_rx(timeout=0.2)
        

        if not self.gps.has_fix:
            # Try again if we don't have a fix yet.
            LED.value = True
            time.sleep(0.05)
            LED.value = False
            return

        # We want to send coordinates out roughly
        # every 4 seconds:
        if (current - self.last_send < 4):
            return

        
        # gps_coords = (self.gps.latitude, self.gps.longitude)
        # if gps_coords == self.coords:
        #    return

        # self.coords = (self.gps.latitude, self.gps.longitude)

        
        # date, time, latitude, longitude, altitude, speed, angle, satellites, fixquality, hdop, geoid, batterylevel
        # put a comma after every number
        
        if self.gps.altitude_m is not None:
            altitud = "{:.0f}".format(self.gps.altitude_m)
        else:
            altitud = 0
        if self.gps.speed_knots is not None:
            speed = "{:.1f}".format(self.gps.speed_knots)
        else:
            speed = 0
        if self.gps.track_angle_deg is not None:
            angle = "{:.0f}".format(self.gps.track_angle_deg)
        else:
            angle = 0
        if self.gps.satellites is not None:
            satellites = self.gps.satellites
        else:
            satellites = 0
        """if self.gps.horizontal_dilution is not None:
            hdop = self.gps.horizontal_dilution
        else:
            hdop = 0
        if self.gps.height_geoid is not None:
            geoid = self.gps.height_geoid
        else:
            geoid = 0"""

        # ### Get battery level
        battery_voltage = get_voltage(vbat_voltage)
        battery_percentage = math.floor(
            (battery_voltage - 3.2) / 1 * 100
        )  

        
        # ### Packet and print the whole string comma delimited
        send_packet = "{}/{}/{}T{:02}:{:02}:{:02},{},{},{},{},{},{},{},{}".format(
            self.gps.timestamp_utc.tm_year,
            self.gps.timestamp_utc.tm_mon,  # Grab parts of the time from the struct_time object that holds
            self.gps.timestamp_utc.tm_mday,  # the fix time.  Note you might
            self.gps.timestamp_utc.tm_hour,  # not get all data like year, day,
            self.gps.timestamp_utc.tm_min,  # month!
            self.gps.timestamp_utc.tm_sec,
            "{0:.6f}".format(self.gps.latitude),
            "{0:.6f}".format(self.gps.longitude),
            altitud,
            speed,
            angle,
            satellites,
            self.gps.fix_quality, 
            # hdop,
            # geoid,
            "{:.0f}".format(battery_percentage),
        )


        # print('   quality: {}'.format(self.gps.fix_quality))
        print('   ' + str(gc.mem_free()) + " bytes free")

        # Send a location packet:
        self.radio_tx(send_packet)
        


    def radio_tx(self, msg):
        """send a packet over radio (I took away id prefix)"""
        packet = msg
        print('sending: ' + packet)

        # Blocking, max of 252 bytes:
        LED.value = True
        self.rfm9x.send(packet)
        LED.value = False
        self.last_send = time.monotonic()


    def radio_rx(self, timeout):
        """check radio for new packets, handle incoming data"""

        packet = self.rfm9x.receive(timeout=timeout)

        # If no packet was received during the timeout then None is returned:
        if packet is None:
            return

        packet = bytes(packet)
        
        # print('   received signal strength: {0} dB'.format(self.rfm9x.rssi))
        # print('   received (raw bytes): {0}'.format(packet))

        packet_text = str(packet, 'ascii')
        print('Received: {0}'.format(packet_text))
        
        # blink a lot to show receipt
        LED.value = True
        time.sleep(0.02)
        LED.value = False
        LED.value = True
        time.sleep(0.02)
        LED.value = False
        LED.value = True
        time.sleep(0.02)
        LED.value = False
        LED.value = True
        time.sleep(0.02)
        LED.value = False
        LED.value = True
        time.sleep(0.02)
        LED.value = False

code.py:

from gpos import GPOS

gp = GPOS()
while True:
    gp.advance_frame()

Non-default Spreading Factor not working

I have two other devices in the same room that are able to communicate while my LoRa Radio Bonnet on my Pi Zero cannot. The other devices can receive (FFFF0000345733E11203B22B47187E) what is sent but my Pi doesn't get anything that is sent.
One device is a Wio Terminal using the LoRa-E5 with these setting:

AT+TEST=RFCFG,915,SF12,125,12,15,14,ON,OFF,OFF
//AT+TEST=RFCFG,[F],[SF],[BW],[TXPR],[RXPR],[POW],[CRC],[IQ],[NET]

The other is a Feather nRF52840 with the FeatherWing RFM95W, which I assumed would be the easiest to communicate with being the same LoRa module. I am using this library https://github.com/sandeepmistry/arduino-LoRa since the RadioHead lib doesn't compile for the nRF52. It's settings:

  long loraFreq=915E6;
  if (!LoRa.begin( loraFreq)) {             // initialize ratio at 915 MHz
    while (true);                       // if failed, do nothing
  }
  LoRa.setTxPower(12);
  LoRa.setSpreadingFactor(12);
  LoRa.enableCrc();

And finally the settings from the Pi:

rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)
rfm9x.signal_bandwidth = 125000
rfm9x.coding_rate = 6
rfm9x.tx_power = 12
rfm9x.spreading_factor = 12
rfm9x.enable_crc = False
#True didn't work either

Any help would be appreciated.

No RH Mesh adaptation

The current adaptation of the RadioHead library is the minimum implementation. Is there a plan to incorporate more advanced features - such as the Mesh Networking capability?

rfm9x_simpletest.py fails under Blinka

I tried running try rfm9x_simpletest.py on a RaspberryPi 3B+ with the latest Blinka (0.2) - the SPI stuff seems fine, but I get the following error

pi@gjnpi3p-1:~/projects/rfm9x/orig $ python3.6 rfm9x_simpletest.py
Traceback (most recent call last):
  File "rfm9x_simpletest.py", line 39, in <module>
    rfm9x.send('Hello world!\r\n')
  File "/usr/local/lib/python3.6/site-packages/adafruit_rfm9x.py", line 561, in send
    self._write_from(_RH_RF95_REG_00_FIFO, data)
  File "/usr/local/lib/python3.6/site-packages/adafruit_rfm9x.py", line 414, in _write_from
    device.write(buf, end=length)
  File "/usr/local/lib/python3.6/site-packages/busio.py", line 112, in write
    return self._spi.write(buf, start, end)
  File "/usr/local/lib/python3.6/site-packages/adafruit_blinka/microcontroller/raspi_23/spi.py", line 47, in write
    self._spi.writebytes([x for x in buf[start:end]])
TypeError: Non-Int/Long value in arguments: 76a38380.

By making this change - the error goes away and it appears to run OK.

39c39,40
< rfm9x.send('Hello world!\r\n')
---
> msg = str.encode('Hello worlt!\n\r')
> rfm9x.send(msg)
pi@gjnpi3p-1:~/projects/rfm9x/orig $ python3.6 rfm9x_simpletest_jerryn.py
Sent hello world message!
Waiting for packets...
Received nothing! Listening again...
Received nothing! Listening again...
Received nothing! Listening again...
Received nothing! Listening again...

I have not fully tested that this is transmitting and receiving. I am testing it remotely and only have one rfm9x connected. I'll do more testing next weekend. The original flle runs OK und CircuitPython -- or it did last time I tried it.

Adafruit LoRa Radio Bonnet with OLED โ€” RFM95W @ 915MHz โ€” RadioFruit

I brough one a few weeks ago and I cant get it to send data to the things network I have this code

import threading
import time
import subprocess
import busio
from digitalio import DigitalInOut,Direction,Pull
import board
import adafruit_ssd1306
from adafruit_tinylora.adafruit_tinylora import TTN,TinyLoRa

#Button A
btnA = DigitalInOut(board.D5)
btnA.direction = Direction.INPUT
btnA.pull = Pull.UP

#Button B
btnB = DigitalInOut(board.D6)
btnB.direction = Direction.INPUT
btnB.pull = Pull.UP

#Button C
btnC = DigitalInOut(board.D12)
btnC.direction = Direction.INPUT
btnC.pull = Pull.UP

#Create the I2C interface
i2c = busio.I2C(board.SCL,board.SDA)

#128x32 OLED Display
reset_pin = DigitalInOut(board.D4)
display = adafruit_ssd1306.SSD1306_I2C(128,32,i2c,reset=reset_pin)
#clear the display
display.fill(0)
display.show()
width = display.width
height = display.height

#TinyLora Configuration
spi = busio.SPI(board.SCK, MOSI=board.MOSI,MISO=board.MISO)
cs= DigitalInOut(board.CE1)
irq = DigitalInOut(board.D22)
rst = DigitalInOut(board.D25)

#TTN Device Address,4 Bytes MSB
devaddr = bytearray([0x00, 0x00, 0x00, 0x00])
#TTN Network Key 16 bytes,MSB
nwkey = bytearray([0x3C, 0xD4, 0x7D, 0xEF, 0x75, 0xF1, 0xFA, 0x28, 0xD8, 0x84, 0xDA, 0x5D, 0xB6, 0xF8, 0x9D, 0x0D])
#TTN Application Key,16 Bytes,MSB
app = bytearray([0x99, 0xEC, 0x31, 0x76, 0x43, 0xC7, 0x9E, 0xE1, 0xFB, 0x08, 0x61, 0xD3, 0x72, 0xD4, 0xE3, 0xB3])

#Initialize ThingsNetwork configuration
ttn_config = TTN(devaddr,nwkey,app, country='AUS')
#Initialize lora object
lora = TinyLoRa(spi, cs ,irq, rst, ttn_config)
#2b array to store sensor data
data_pkt = bytearray(2)
#time to delay periodic packet sends (in second)
data_pkt_delay = 5.0

def send_pi_data_periodic():
threading.Timer(data_pkt_delay,send_pi_data_periodic).start()
print("Sending periodic data...")
send_pi_data(CPU)
print('CPU:',CPU)

def send_pi_data(data):
#Encode float as int
data = int(data * 100)
#Encode payload as bytes
data_pkt[0] = (data >> 8) & 0xff
data_pkt[1] = data & 0xff
#send data packet
lora.send_data(data_pkt,len(data_pkt),lora.frame_counter)
lora.frame_counter += 1
display.fill(0)
display.text('send Data to TTN!',15,15,1)
print('Data sent!')
display.show()
time.sleep(0.5)

while True:
packet = None
#draw a bow to clear the image
display.fill(0)
display.text('RASPI LORAWAN',35,0,1)

#read the raspberry pi cpu load
cmd = "top -bn1 | grep load | awk '{printf\"%.1f\",$(NF-2)}'"
CPU = subprocess.check_output(cmd , shell = True)
CPU = float(CPU)

if not btnA.value:
    #send packet
    send_pi_data(CPU)
if not btnB.value:
    #Display CPU Load
    display.fill(0)
    display.text('CPU load%',45,0,1)
    display.text(str(CPU), 60, 15, 1)
    display.show()
    time.sleep(0.1)
if not btnC.value:
    display.fill(0)
    display.text('*Periodic Mode *' , 15, 0, 1)
    display.show()
    time.sleep(0.5)
    send_pi_data_periodic()
    
display.show
time.sleep(.1)

The code doen't work on the things network v3 does anyone know how to fix this

Cannot import in python 3.7

>>> import adafruit_rfm9x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pi/circuit/venv/lib/python3.7/site-packages/adafruit_rfm9x.py", line 142, in <module>
    class RFM9x:
  File "/home/pi/circuit/venv/lib/python3.7/site-packages/adafruit_rfm9x.py", line 265, in RFM9x
    crc: bool = True
NameError: name 'SPI' is not defined

The real exception, hidden by the typing try-except is the following:

  File "/home/pi/circuit/venv/lib/python3.7/site-packages/adafruit_rfm9x.py", line 31, in <module>
    from typing import Optional, Type, Literal
ImportError: cannot import name 'Literal' from 'typing' (/usr/lib/python3.7/typing.py)

Literal is in python 3.8.

example code crashes on packet receive

I have a m0 adalogger with the 900mhz lora wing
i have an m0 lora feather

i download example code from
https://learn.adafruit.com/radio-featherwing/circuitpython-for-rfm9x-lora

on the m0 lora feather i did the pin assignment change in in the comments.

both of them display the same blow error. whichever one I start first shows the error when it receive the packet from the second one

boot_out.txt

Adafruit CircuitPython 8.0.3 on 2023-02-23; Adafruit Feather M0 Adalogger with samd21g18
Board ID:feather_m0_adalogger

serial console

Traceback (most recent call last):
  File "code.py", line 56, in <module>
  File "adafruit_rfm9x.py", line 840, in receive
  File "adafruit_rfm9x.py", line 678, in rx_done
  File "adafruit_rfm9x.py", line 397, in _read_u8
  File "adafruit_rfm9x.py", line 387, in _read_into
KeyboardInterrupt: 

Code done running.

Press any key to enter the REPL. Use CTRL-D to reload.

Adafruit CircuitPython 8.0.3 on 2023-02-23; Adafruit Feather M0 Adalogger with samd21g18
>>> 

enable CRC by default for Arduino (Radiohead) compatibility

The Arduino RadioHead library has the CRC enabled by default, but the CircuitPython library has it disabled by default. It is easily enable by setting the enable_crc property to True.
We have generally tried to make the defaults compatible with the RadioHead defaults so this should probably be changed.
Let me know if there are any objections. I will prepare a PR for discussion.
The change may impact "cross-platform" users since they will have to make a change to remain compatible.

Getting Runtime Error

Hello,
I was trying out the library using the tutorial given on Adafruit Website with Adafruit RFM95W LoRa Radio Transceiver Breakout - 433MHz (https://learn.adafruit.com/lora-and-lorawan-radio-for-raspberry-pi) with Raspberry Pi 3B+ running Raspbian Buster.

Upon running just the following code:

import digitalio
import board
import busio
import adafruit_rfm9x

CS = digitalio.DigitalInOut(board.CE1)
RESET = digitalio.DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 433.0)

I got the following error:

Traceback (most recent call last):
  File "test.py", line 9, in <module>
    rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 433.0)
  File "/usr/local/lib/python3.7/dist-packages/adafruit_rfm9x.py", line 367, in __init__
    raise RuntimeError('Failed to find rfm9x with expected version -- check wiring')
RuntimeError: Failed to find rfm9x with expected version -- check wiring

I checked my wiring but still the same error was coming.
Please help me with this.

please add support for LoRaWAN, OTA and ABP in circuit pythin

I love circuit python since 2017. programming with python is fun. and with circuit python its amazing. I have some feather m0 lora module. i like to use them as LoRaWAN and send sensor data (will be collected for each sector of my house like garden, kitchen, store, garage etc) from my gateway device to ThingsSpeak server or Cayenne Server or Adafruit Mqtt IO Server.

please add support for LoRaWAN, OTA, ABP on circuit python.
Thanks

Enhancement: expose spreading factor, coding rate, signal bandwidth, CRC check

I suggest adding the ability to control spreading factor, coding rate, signal bandwidth, and perform CRC checks on inbound packets. These features are sadly absent from the venerable RadioHead library implementation which heavily inspired the current implementation of this module but these features are available in the hardware of the Adafruit RFM95W (and close cousins) and through the software of other libraries such as https://github.com/MZachmann/LightLora_Arduino or https://github.com/MZachmann/LightLora_MicroPython .

As it is, the current implementation only supports fixed values for these controls but to make good use of the RFM9x hardware, to achieve good LoRa performance over long distances under non-ideal (real) conditions, the ability to modify these settings is rather important.

When dealing with the real world, noise can cause data corruption and the hardware's ability to perform CRC checks on packets becomes especially useful. For this reason, enabling the detection of corruption through hardware-based CRC checking is also proposed.

I have a patch to offer the above functionality that may be worth considering. It retains the current default behavior while offering the ability to change these settings at the time of instantiation of the RFM9x class.

Fails on Feather M4 unless self._write_u8(_RH_RF95_REG_01_OP_MODE, 0b10001000) is called in __init__()

At line 357 in adafruit_rfm9x.py, I was getting the exception with Failed to configure radio for LoRa mode, check wiring!. Changed:

--- lib/adafruit_rfm9x.py       2018-08-04 16:14:14.000000000 -0600
+++ src/lib/adafruit_rfm9x.py   2018-08-06 00:38:26.275907581 -0600
@@ -354,10 +354,10 @@
             # Also set long range mode (LoRa mode) as it can only be done in sleep.
             self.sleep()
             self.long_range_mode = True
-            #self._write_u8(_RH_RF95_REG_01_OP_MODE, 0b10001000)
+            self._write_u8(_RH_RF95_REG_01_OP_MODE, 0b10001000)
             time.sleep(0.01)
-            #val = self._read_u8(_RH_RF95_REG_01_OP_MODE)
-            #print('op mode: {0}'.format(bin(val)))
+            val = self._read_u8(_RH_RF95_REG_01_OP_MODE)
+            print('op mode: {0}'.format(bin(val)))
             if self.operation_mode != SLEEP_MODE or not self.long_range_mode:
                 raise RuntimeError('Failed to configure radio for LoRa mode, check wiring!')
         except OSError:

...now seems to work.

No device detected on SPI0 with CE0

Hi together,
I'm using a RFM95 module directly connected via jumper cables to a Raspberry Pi 3B+ (Linux raspberrypi 5.4.51-v7+ #1333 SMP Mon Aug 10 16:45:19 BST 2020 armv7l GNU/Linux) running Raspbian GNU/Linux 10 (buster)

I've followed the steps of installation in this readme and the Adafruit LoRa learning page

Everything is working nice and smooth with CE1/D7 or e.g. D25 but CE0/D8 fails. May I missed to enable something in the /boot/config.txt?

Logs and used code below

pip freeze

Adafruit-Blinka==5.6.0
adafruit-circuitpython-busdevice==5.0.1
adafruit-circuitpython-rfm69==2.1.0
adafruit-circuitpython-rfm9x==2.0.4
Adafruit-PlatformDetect==2.20.0
Adafruit-PureIO==1.1.7
pkg-resources==0.0.0
pyftdi==0.52.0
pyserial==3.4
pyusb==1.1.0
rpi-ws281x==4.2.4
RPi.GPIO==0.7.0
spidev==3.5
sysv-ipc==1.0.1

cat /boot/config.txt

# For more options and information see
# http://rpf.io/configtxt
# Some settings may impact device functionality. See link above for details

# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1

# uncomment this if your display has a black border of unused pixels visible
# and your display can output without overscan
#disable_overscan=1

# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16

# uncomment to force a console size. By default it will be display's size minus
# overscan.
#framebuffer_width=1280
#framebuffer_height=720

# uncomment if hdmi display is not detected and composite is being output
#hdmi_force_hotplug=1

# uncomment to force a specific HDMI mode (this will force VGA)
#hdmi_group=1
#hdmi_mode=1

# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
#hdmi_drive=2

# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4

# uncomment for composite PAL
#sdtv_mode=2

#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800

# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on

# Uncomment this to enable infrared communication.
#dtoverlay=gpio-ir,gpio_pin=17
#dtoverlay=gpio-ir-tx,gpio_pin=18

# Additional overlays and parameters are documented /boot/overlays/README

# Enable audio (loads snd_bcm2835)
dtparam=audio=on

[pi4]
# Enable DRM VC4 V3D driver on top of the dispmanx display stack
dtoverlay=vc4-fkms-v3d
max_framebuffers=2

[all]
#dtoverlay=vc4-fkms-v3d

cat rfm9x_check.py

import time
import busio
from digitalio import DigitalInOut, Direction, Pull
import board
import adafruit_rfm9x

# Configure RFM9x LoRa Radio
# CS = DigitalInOut(board.CE1) # works
# CS = DigitalInOut(board.D7) # works

CS = DigitalInOut(board.D25) # works

# CS = DigitalInOut(board.CE0) # no device detected
# CS = DigitalInOut(board.D8) # no device detected

RESET = DigitalInOut(board.D24)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Attempt to set up the RFM9x Module
try:
    rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 868.0)
    print('RFM9x: Detected')
    exit()
except RuntimeError as error:
    # Thrown on version mismatch
    print('RFM9x Error: ', error)
    exit()

RFM9x Error: Failed to find rfm9x with expected version -- check wiring

"keep_listening" may not work as intended.

A discussion in Discord exposed a potential flaw in receive():

klovingToday at 7:45 PM
@jerryn I now see the keep_listening parameter to send() and receive(). And  I see how those methods call listen() after they are done if keep_listening is true. So if I use keep_listening=true, then  (I think) while my CP main loop is off doing other things, the radio will be putting the next incoming packet into the FIFO and will set rx_done. So when my CP loop comes around to receive() again, the method will return right away, returning the packet that came in while I was busy.  Is this correct? If so, I'm happy.
jerrynToday at 9:19 PM
@kloving I agree it should work that way in principle, but looking at the code, there may be a problem with that -- this line https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/blob/master/adafruit_rfm9x.py#L863 will cause the Mode to be set to reset to RX and if I recall correctly, that causes the input FIFO to be flushed. I may be mistaken and need to review the datasheet. I recall experimenting with this in the past and I think this part should be revised to fix this. It seems to circumvent the whole purpose of "keep_listening". I will look into this tomorrow and see if I can verify the operation and possible workaround. Thank you for raising this.  I will open an issue in the repistory to track this.

I think this can be resolved by adding a test for already listening. I recall struggling with this a while ago but don't recall why it was not implemented.

updated: I may be confusing things -- In a quick look at the data sheet _ I don't see why there should be a problem, The issue may have been with switching between transmit and receive. It may well be OK as is. I'll dig deeper into this tomorrow and confirm or deny the issue.

Receive packets in the form of char string

Hi all,

So far rfm9x.receive() function can receive the packets in the form of bytes right. Can the same function receive the packets in the form of char string as well?

Thanks,
Akshay

rfm9x.crc_error breaks rfm9x.receive() booleans

Adafruit CircuitPython 7.3.3 on 2022-08-29; Adafruit Feather ESP32S2 with ESP32S2

Setting rfm9x.crc_error boolean to True or False breaks all booleans in the receive() function.

rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ, agc=True, crc=True)
rfm9x.crc_error = True  # <---- offender

Then, any parameter in rfm9x.receive() using a boolean gets broken.

        packet = rfm9x.receive(keep_listening=True,
                               timeout=15,
                               with_header=True,
                               with_ack=True)

The problem with bool only exists while rfm9x.crc_error is set.

Traceback (most recent call last):
  File "code.py", line 15, in <module>
  File "receiver.py", line 101, in <module>
  File "adafruit_rfm9x.py", line 857, in receive
TypeError: 'bool' object is not callable

Remove rfm9x.crc_error = True and the error goes away. Doesn't matter if it's set to true or false. Am I just using this wrong or is this a bug?

Interoperability between SX1262 and SX1272

Hi,

I have been using these libraries for so long now on dragino lora hat on Pi. Earlier we used to have dual communication between Adafruit atmega 32u4 and dragino lora hat.

Now we have our custom board which has new Semtech SX1262 on it and it can successfully transmit data packets to SX1272 (using these libraries). But when I try to send data back to SX1262, it doesnt receive it.

Config on both sides -> 923.3mhz, sf=7, cr=4/5, power=22, BW=500khz, CRC is on.

I am wondering what could be the reason of SX1262 not receiving the data packet from SX1272. Does 4 byte header compatible with RadioHead's implementation make a difference like I am trying to extract the 4-byte header like this way:

image

Missing Type Annotations

There are missing type annotations for some functions in this library.

The typing module does not exist on CircuitPython devices so the import needs to be wrapped in try/except to catch the error for missing import. There is an example of how that is done here:

try:
    from typing import List, Tuple
except ImportError:
    pass

Once imported the typing annotations for the argument type(s), and return type(s) can be added to the function signature. Here is an example of a function that has had this done already:

def wrap_text_to_pixels(
    string: str, max_width: int, font=None, indent0: str = "", indent1: str = ""
) -> List[str]:

If you are new to Git or Github we have a guide about contributing to our projects here: https://learn.adafruit.com/contribute-to-circuitpython-with-git-and-github

There is also a guide that covers our CI utilities and how to run them locally to ensure they will pass in Github Actions here: https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/check-your-code In particular the pages: Sharing docs on ReadTheDocs and Check your code with pre-commit contain the tools to install and commands to run locally to run the checks.

If you are attempting to resolve this issue and need help, you can post a comment on this issue and tag both @FoamyGuy and @kattni or reach out to us on Discord: https://adafru.it/discord in the #circuitpython-dev channel.

The following locations are reported by mypy to be missing type annotations:

  • adafruit_rfm9x.py:169
  • adafruit_rfm9x.py:181
  • adafruit_rfm9x.py:185
  • adafruit_rfm9x.py:224
  • adafruit_rfm9x.py:346
  • adafruit_rfm9x.py:358
  • adafruit_rfm9x.py:363
  • adafruit_rfm9x.py:375
  • adafruit_rfm9x.py:426
  • adafruit_rfm9x.py:444
  • adafruit_rfm9x.py:473
  • adafruit_rfm9x.py:529
  • adafruit_rfm9x.py:574
  • adafruit_rfm9x.py:593
  • adafruit_rfm9x.py:619
  • adafruit_rfm9x.py:644
  • adafruit_rfm9x.py:719
  • adafruit_rfm9x.py:757

Pull up required for reset pin?

Is enabling internal pull up required for the reset pin? Or is simply setting to input adequate? Use case is for a board that lacks internal pull ups on inputs pins.
image

Check wiring error when the wiring is good

Hi,

I am trying to make two raspberry pi communicate through LoRa (SX127x). (https://media.digikey.com/pdf/Data%20Sheets/Semtech%20PDFs/SX1272MB2DAS_HM.PDF)

When I try the following code, I get the following error :

Code :
**import digitalio
import board
import busio
import adafruit_rfm9x

RADIO_FREQ_MHZ = 915.0
CS = digitalio.DigitalInOut(board.CE1)
RESET = digitalio.DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)**

Error :
Traceback (most recent call last):
File "/home/pi/Downloads/test.py", line 10, in
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
File "/usr/local/lib/python3.7/dist-packages/adafruit_rfm9x.py", line 250, in init
"Failed to find rfm9x with expected version -- check wiring"
RuntimeError: Failed to find rfm9x with expected version -- check wiring

The wiring is good (double-triple-quadruple checked)

I tried switching CE1 and CE2 and GPIO's, but it doesn't seem to work.

Now I know that this is a RFM library, but the RFM is build on SX127x's chips isn't it?

Any help would be greatly appreciated!

Clarify the 4 bytes added to the beginning of each buffer

In working with the library and using it to send to an ESP based LoRa device that does not use this library, I found that this library adds 4 bytes to the start of every buffer for compatibility with RadioHead. I found this extra 4 bytes were being added by examining the code.
Solutions:

  1. Add a setting that turns this compatibility off and on (preferred)
  2. Clarify in the examples (and in the header of the adafruit_rfm9x.py) that when sending to or receiving from devices that are not RadioHead compatible, they will need to account for it.

Receiving all zeroes eventually

Hello!

I am running into an issue where after successfully receiving for a while, eventually I receive all 0s, on any call to receive

I tried calling the reset method when this occurs, but it persists anyway. The only solution I've found is to restart the little script I have (which indicts it's not a transmit problem)

Program here: https://gist.github.com/tahnok/dad135976baead3b38fb074ff500dfa6

I can try and debug, but I'm not really sure how to get more useful information...

How to set the Network Id?

How to set the network id? I've briefly inspected the code and I don't see how to set it.
Or what network id I should set on Arduino side (while using RFM69_LowPowerLib where network id is being specified).

Support interrupts instead of timeouts

It is difficult to use this library with bidirectional communication. read_lora_data(timeout=5s) loops are currently required to check if there is new data to transmit. If interrupts could be supported, we wouldn't need to stop listening for data ever except when there is data to transmit. I think this is even referenced in the code; perhaps now would be a better time for adafruit to consider it?

rfm9x corrupted header

when using rfm9x to transfer sensor data to another rfm9x to forward to AIO, I have observed something strange.

EDIT 4/14/2022

Throughout working this issue myself, I have found some interesting things.

  1. The corruption is limited to the flag position of the header. There is never another place in the package that is corrupted.
  2. The transmit is done using CRC enabled, and 'with header' so ACK should be working. I never receive a 'no ack' unless the receiver program is not running or the radios are too far apart.
  3. Could it be that the CRC and ACK are limited to the payload and not the entire packet?
  4. The program worked better when I did not use asyncio. I needed a way to keep the data acquisition going because I needed nearly continuous monitoring of the wind speed. I worked this into the program using asyncio, but failures began to get worse.
  5. Not only is the flag byte the only one that gets corrupted, it is always (I think) corrupted the same way. For example, if rfm9x.flags is set to 5, and the flag byte gets corrupted, this is what it looks like in hex.
    Received raw packet: ['0x2', '0x3', '0x5e', '0x45', '0x31', '0x2e', '0x34', '0x33']

Note that the fourth byte has the value of 0x45, which is the insertion of the number 4 before the flag value of 5. Every corrupt flag is the same, the flag value is preceded by a 4.

several examples: corrupted byte /
Received raw packet: ['0x2', '0x3', '0x64', '0x42', '0x39', '0x39', '0x36', '0x2e', '0x32']
Received raw packet: ['0x2', '0x3', '0x56', '0x42', '0x39', '0x39', '0x36', '0x2e', '0x33']
Received raw packet: ['0x2', '0x3', '0x3e', '0x45', '0x32', '0x2e', '0x31', '0x34']
Received raw packet: ['0x2', '0x3', '0x19', '0x43', '0x35', '0x30', '0x2e', '0x31', '0x33']
Received raw packet: ['0x2', '0x3', '0x11', '0x43', '0x34', '0x39', '0x2e', '0x36', '0x34']
Received raw packet: ['0x2', '0x3', '0xf7', '0x43', '0x34', '0x38', '0x2e', '0x33', '0x31']
Received raw packet: ['0x2', '0x3', '0xf3', '0x44', '0x34', '0x2e', '0x34', '0x31']
Received raw packet: ['0x2', '0x3', '0xf1', '0x43', '0x34', '0x38', '0x2e', '0x39', '0x32']

The above examples were the output of the following code
elif flag != 1 or 2 or 3 or 4 or 5 or 6: print("Received raw packet:", [hex(x) for x in packet[:]])

I am attempting to use rfm9x.flags as a way to distinguish up to 6 different streams of sensor data. Since these packets were corrupted in the flag byte, they get printed out at the end.

here are sections of code that are t

while True:
    temperature = (bme280.temperature)
    temperature=str(temperature)
    rfm9x.flags=1
    print(rfm9x.flags, temperature)
    if not rfm9x.send_with_ack(temperature.encode("utf-8")):
        #ack_failed_counter += 1
        print(" No Ack T: ", ack_failed_counter) 
    time.sleep(1)
    await asyncio.sleep(interval)

async def get_pressure(interval):
while True:
pressure = (bme280.pressure)
pressure = str(pressure)
rfm9x.flags=2
print(rfm9x.flags, pressure)
#time.sleep(.1)
if not rfm9x.send_with_ack(pressure.encode("utf-8")):
#ack_failed_counter += 1
print(" No Ack T: ", ack_failed_counter)
time.sleep(1)
await asyncio.sleep(interval)

the serial monitor then prints:
1 24.8129
2 997.658

When the receiver gets these packets however, using the following pertinent section of code:
while True:
# Look for a new packet: only accept if addresses to my_node
packet = rfm9x.receive(with_ack=True, with_header=True)
# If no packet was received during the timeout then None is returned.
if packet is not None:
flag=packet[3]
print(flag)
print("received (raw bytes):{0}".format(packet))
if flag==1:
print(flag)
temperature = str(packet[4:], "utf-8")
temperature = (float(temperature)*1.8 + 32)
temperature=round(temperature, 2)
print("temperature = ", temperature, "F")
#print("Received RSSI: {0}".format(rfm9x.last_rssi))
if adafruit.is_active:
aio.send(temperature_feed.key, str(temperature))
else:
print("wifi not ready")
if flag==2:
print(flag)
pressure = str(packet[4:], "utf-8")
print("pressure = ", pressure, "mB")
#print("Received RSSI: {0}".format(rfm9x.last_rssi))
if adafruit.is_active:
aio.send(pressure_feed.key, str(pressure))
else:
print("wifi not ready")

The serial monitor then prints the following:
1
received (raw bytes):bytearray(b'\x02\x01\x02\x0124.8129')
1
temperature = 76.66 F
66
received (raw bytes):bytearray(b'\x02\x01\x03B997.658')

the first packet received is correct in that the four byte header contains the appropriate information, 02= this node, 01 = sending node, x02 is an 'identifier' and the fourth is x01, meaning the flag was set and received as 1.

The second packet was sent with rfm9x.flags = 2 (shown above in the serial output)
but the received flag was printed as '66' and thus missed by the "if rfm9x.flags ==2 test and pases through without sending the data on to AIO. Note that the payload 997.658 is correct.

This is one example. The CRC is enabled, and the 'with header' is enabled. there is no 'no ack' sent back to the sending node. This I assume means that the header was received, correctly? But parsed or decoded incorrectly.

I really need to be able to use the rfm9x.flags in the header to filter and sort the 6 different data streams I am using (only showed 1,2)

I do not know if this is due to using asyncio and really typing up the processor (RPI PICO), or if it may be a hardware issue.

One final thing. The error is not always on the same flagged packets, meaning sometime flag=2 is passed and parsed correctly. However, the failure is not random, the payload with flags = 2 is corrupted much more frequently.

Any help would be appreciated.

Project dependencies may have API risk issues

Hi, In Adafruit_CircuitPython_RFM9x, inappropriate dependency versioning constraints can cause risks.

Below are the dependencies and version constraints that the project is using

Adafruit-Blinka>=7.2.3
adafruit-circuitpython-busdevice

The version constraint == will introduce the risk of dependency conflicts because the scope of dependencies is too strict.
The version constraint No Upper Bound and * will introduce the risk of the missing API Error because the latest version of the dependencies may remove some APIs.

After further analysis, in this project,
The version constraint of dependency Adafruit-Blinka can be changed to >=0.1.5,<=8.0.2.

The above modification suggestions can reduce the dependency conflicts as much as possible,
and introduce the latest version as much as possible without calling Error in the projects.

The invocation of the current project includes all the following methods.

The calling methods from the Adafruit-Blinka
micropython.const
The calling methods from the all methods
self._read_u8
obj._read_u8
device.readinto
bytes
self.listen
micropython.const
sphinx_rtd_theme.get_html_theme_path
random.random
self.rx_done
os.path.abspath
sys.path.insert
datetime.datetime.now
adafruit_ssd1306.SSD1306_I2C.show
self.transmit
self.crc_error
hasattr
format
RPi.GPIO.add_event_callback
self._write_from
RPi.GPIO.setmode
self.sleep
time.monotonic
self.send
self.idle
adafruit_rfm9x.RFM9x.receive
self._write_u8
obj._write_u8
busio.SPI
adafruit_bus_device.spi_device.SPIDevice
min
adafruit_rfm9x.RFM9x.send
adafruit_ssd1306.SSD1306_I2C.fill
busio.I2C
adafruit_rfm9x.RFM9x.send_with_ack
adafruit_ssd1306.SSD1306_I2C.text
range
adafruit_ssd1306.SSD1306_I2C
supervisor.ticks_ms
max
time.sleep
os.environ.get
RuntimeError
str
print
self.tx_done
hex
self._reset.switch_to_output
digitalio.DigitalInOut
len
self.reset
self._read_into
int
self.receive
ticks_diff
device.write
_RegisterBits
adafruit_rfm9x.RFM9x
RPi.GPIO.add_event_detect
bytearray
enumerate
RPi.GPIO.setup

@developer
Could please help me check this issue?
May I pull a request to fix it?
Thank you very much.

I believe the RSSI function is returning the wrong number.

On line 553 the RSSI register (for Lora) is read and 137 is subtracted from the value. During testing I seem to lose the LORA signal at -101 dBm. Looking at the Semtech data sheet (section 5.5.5) the formula for RSSI is:
RSSI (dBm) = -157 + Rssi, (when using the High Frequency (HF) port) or RSSI (dBm) = -164 + Rssi, (when using the Low Frequency (LF) port)

Working the numbers back when this library reads -101, the register would have contained 36, running the correct formula gives me an RSSI of -121 dBm. I'm running the Lora radio at 125 KHz bandwidth and a SF of 7, which should give me a theoretical -125 dBm receive sensitivity. This seems in the realm of possibility.

Am I missing something or is the number incorrect in the code?

Unclear about size of _BUFFER

I am confused about the initialization of the global buffer for the radio _BUFFER.
It should be 256 bytes which is the size specified in the radio's data sheet.
However, it seems at though its only being initialized to 10 bytes?
Here is the code for initialization:

# Global buffer to hold data sent and received with the chip.  This must be
# at least as large as the FIFO on the chip (256 bytes)!  Keep this on the
# class level to ensure only one copy ever exists (with the trade-off that
# this is NOT re-entrant or thread safe code by design).
_BUFFER = bytearray(10)  

If someone could provide insight into the behavior of _BUFFER, that would be greatly appreciated.

LoRaWAN

Is there any way to use this module to connect to a Chripstack server? I know the SX 1276 chip that is on the radio supports LoRaWAN but I don't see any circuit python example. Thanks!

Library uses short argument names

pylint suggests using argument names with at least 3 letters. This library uses argument names of shorter length, and while these warnings have been disabled for now, they should be considered for renaming. This may require the rework of Learn Guides and other references to code snippets.

Potential hang when changing tx_power or frequency_mhz

Using the setter to change tx_power or frequency_mhz after using the radio to send a packet can result in an indefinite hang. It is unknown if this similarly impacts updates to other registers on the radio. To reproduce, try altering tx_power after having already sent packets.

If a fix to prevent such hangs is possible, that would be ideal. If it turns out there is no solution other than first performing a reset, then this behavior should probably be documented.

Receiver Disabled Every Other Save (toggling state)

Been working with rfm95 featherwing for hours & hours over the past week and have finally narrowed down the erratic behavior to the receiver disabling itself every other save. It toggles between working and not receiving anything, on save, every time. Almost as if a rfm9x.receiver() mode state is being toggled. This might also explain some other bug reports that could account for receiver being disabled.

During issue all other code works including sending a packet. Only rfm9x.receive() gets broken.

In my example I have both transmitting and receiving on both boards. Using transmitter.py and receiver.py just helps me to differentiate them when opening USB instead of both having identical files however both are slightly different because I'm still working on the project but they do work perfectly as long as you ctrl+s at just the right time on both boards.

Project is transmitting a fake chat stream over and over by design for testing purposes. The time.monotonic timestamps change each message so I can track each original message.

Issue happens on both 7.3.3 and 8.0.beta, Currently running mixed CP versions but again issue happens regardless of CP version and I've flashed both to 7.33 and 8.0 trying to track down this bug. Also happens on an RP2040 feather. The lora boards or library is the common denominator.

Code.py

# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries
# SPDX-License-Identifier: MIT
# Code.py (1 file per board)
# RFM95 LoRa Featherwing Example
import gc
import time
import board
import busio
import digitalio
import adafruit_rfm9x

# Choose the Mode (receive or transmit)

# RECEIVER MODE (HOUSE)
# import receiver

# TRANSMITTER MODE (MAILBOX)
import transmitter

Receiver.py

# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries
# SPDX-License-Identifier: MIT
# Receiver.py
# Adafruit CircuitPython 7.3.3 on 2022-08-29; Adafruit Feather ESP32S2 with ESP32S2
import gc
import time
import board
import busio
import digitalio
import adafruit_rfm9x

CS = digitalio.DigitalInOut(board.D10)
RESET = digitalio.DigitalInOut(board.D6)
# CircuitPython build:
# CS = digitalio.DigitalInOut(board.RFM9X_CS)
# RESET = digitalio.DigitalInOut(board.RFM9X_RST)

# Define the onboard LED
LED = digitalio.DigitalInOut(board.D13)
LED.direction = digitalio.Direction.OUTPUT

# Initialize SPI bus.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialze RFM radio
RADIO_FREQ_MHZ = 915.0  # Frequency of the radio in Mhz
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

# Radio configured in LoRa mode. You can't control sync
# encryption, frequency deviation, or other settings!
rfm9x.tx_power = 13  # Transmit Power. Default 13dB. RFM95 (0-23)dB:
rfm9x.enable_crc = False
rfm9x.xmit_timeout = 2.0 # Recommended at least 2 seconds for HW reliability
with_header = True  # Set if you want header bytes printed for debug

# Node (0-255) Only packets addressed to this node will be accepted.
# Comment out to disable and receive all packets from anywhere
# rfm9x.node = 13

# Wait to receive packets.  Note that this library can't receive data at a fast
# rate, in fact it can only receive and process one 252 byte packet at a time.
# This means you should only use this for low bandwidth scenarios, like sending
# and receiving a single message at a time.
print("Waiting for packets...")

while True:
    # Time in seconds from when board was powered on
    now = time.monotonic()
    # Changing timeout and sleep duration definitely has an effect on dropped packets.
    # Timeout 5 and sleep 0.5 works great with no dropped packets in my experience.
    packet = rfm9x.receive(keep_listening=True, timeout=rfm9x.xmit_timeout, with_header=False)
    # If no packet was received during timeout then packet=None is returned.
    if packet is None:
        # Packet has not been received
        LED.value = False
        print("Packet: None - Sleep for " + str(rfm9x.xmit_timeout) + " seconds...")
    else:
        # Now we're inside a received packet!
        LED.value = True
        try:
            # Header bytes are raw bytes. Good for debugging.
            if with_header:
                packet_text = str(packet)
            # No header strips header bytes and decodes to ascii.
            if not with_header:
                packet_text = str(packet, "ascii")
        except (UnicodeError) as e:
            print("Unicode Error", e)
            continue
        
        rssi = rfm9x.last_rssi
        last_snr = rfm9x.last_snr
        # Print out the raw bytes of the packet:
        print("Received (raw bytes): {0}".format(packet))
        # And decode to ASCII text and print it too.  Note that you always
        # receive raw bytes and need to convert to a text format like ASCII
        # if you intend to do string processing on your data.  Make sure the
        # sending side is sending ASCII data before you try to decode!
        print("Timestamp:", now)
        print("Signal Noise: {0} dB".format(last_snr))
        print("Signal Strength: {0} dB".format(rssi))
        print("Received (ASCII): {0}".format(packet_text))
        gc.collect
        
    message = str("M4 Express Msg Test ๐Ÿ˜‡ ")
    rfm9x.send(bytes(str(now) + " " + message + "\r\n", "utf-8"), keep_listening=True)
    print("(Sent)" + " " + message + "\n")
    time.sleep(2)

Transmitter.py

# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries
# SPDX-License-Identifier: MIT
# Transmitter.py
# Adafruit CircuitPython 8.0.0-alpha.1 on 2022-06-09; Adafruit Feather M4 Express with samd51j19
import gc
import time
import board
import busio
import digitalio
import adafruit_rfm9x

# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
CS = digitalio.DigitalInOut(board.D10)
RESET = digitalio.DigitalInOut(board.D6)
# Or uncomment and instead use these if using a Feather M0 RFM9x board and the appropriate
# CircuitPython build:
# CS = digitalio.DigitalInOut(board.RFM9X_CS)
# RESET = digitalio.DigitalInOut(board.RFM9X_RST)

# Define the onboard LED
LED = digitalio.DigitalInOut(board.D13)
LED.direction = digitalio.Direction.OUTPUT

# Initialize SPI bus.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialze RFM radio
RADIO_FREQ_MHZ = 915.0  # Frequency of the radio in Mhz
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

# Radio configured in LoRa mode. You can't control sync
# encryption, frequency deviation, or other settings!
rfm9x.tx_power = 13  # Transmit Power. Default 13dB. RFM95 (0-23)dB:
rfm9x.enable_crc = False  # Ensures packets are correctly formatted
rfm9x.ack_retries = 5  # Number of retries to send an ack packet
rfm9x.ack_delay = .1  # If ACK's are being missed try .1 or .2
rfm9x.xmit_timeout = 2.0 # Recommended at least 2 seconds for HW reliability
with_header = True  # Set if you want header bytes printed for debug

def _format_datetime(datetime):
    return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format(
        datetime.tm_mon,
        datetime.tm_mday,
        datetime.tm_year,
        datetime.tm_hour,
        datetime.tm_min,
        datetime.tm_sec,
    )
    
print("Waiting for packets... \n")

while True:
    # Time in seconds from when board was powered on
    now = time.monotonic()
    # Show fictional timestamp starting in year 2000.
    # Fake date are great for syn/ack timestamps on boards with no RTC or Wifi
    # Just don't print timestamps to end user in these scenarios.
    # It would obviously confuse them if they think the timestamps are wrong, they're not.
    
    # If your board DOES have wifi time server or RTC (not included) 
    # plug your fetched unix time into unix_time and uncomment below
    # unix_time = 1660764970 # Wed Aug 17 2022 19:36:10 GMT+0000
    # tz_offset_seconds = -14400  # NY Timezone
    # get_timestamp = int(unix_time + tz_offset_seconds)
    # and swap out CURRENT_UNIX_TIME with this one
    # current_unix_time = time.localtime(get_timestamp)
    
    current_unix_time = time.localtime()
    current_struct_time = time.struct_time(current_unix_time)
    current_date = "{}".format(_format_datetime(current_struct_time))
    
    # Changing timeout and sleep duration definitely has an effect on dropped packets.
    # Timeout 5 and sleep 0.5 works great with no dropped packets in my experience.
    packet = rfm9x.receive(keep_listening=False, timeout=rfm9x.xmit_timeout, with_header=with_header)
    
    # If no packet was received during timeout then packet=None is returned.
    if packet is None:
        # Packet has not been received
        LED.value = False
        print("Packet: None - Sleep for " + str(rfm9x.xmit_timeout) + " seconds...")
    else:
        # Now we're inside a received packet!
        LED.value = True
        try:
            # Header bytes are raw bytes. Good for debugging.
            if with_header:
                packet_text = str(packet)
            # No header strips header bytes and decodes to ascii.
            if not with_header:
                packet_text = str(packet, "ascii")
        except (UnicodeError) as e:
            print("Unicode Error", e)
            continue
        
        # received sig strenth and sig/noise
        rssi = rfm9x.last_rssi
        last_snr = rfm9x.last_snr
        # Debug printing of packet data:
        print("Received (raw bytes): {0}".format(packet))
        print("Timestamp:", now)
        print("Localtime: ", current_date)
        print("Signal Noise: {0} dB".format(last_snr))
        print("Signal Strength: {0} dB".format(rssi))
        print("(Received): {0}".format(packet_text))

    # Original New Packet Transmit (252 byte maximum)
    # Each send waits for previous send to finish
    message = str("Orignating Transmit Test ๐Ÿ™‚")
    rfm9x.send(bytes(str(now) + " " +  message + "\r\n", "utf-8"))
    print("(Sent)" + " " + message + "\n")
    time.sleep(0.5)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.