You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

626 lines
30 KiB

# The MIT License (MIT)
#
# Copyright (c) 2017 Tony DiCola for Adafruit Industries
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
`adafruit_rfm9x`
====================================================
CircuitPython module for the RFM95/6/7/8 LoRa 433/915mhz radio modules. This is
adapted from the Radiohead library RF95 code from:
http: www.airspayce.com/mikem/arduino/RadioHead/
* Author(s): Tony DiCola, Jerry Needell
"""
import time
import digitalio
from micropython import const
import adafruit_bus_device.spi_device as spi_device
__version__ = "1.0.3"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_rfm95.git"
# pylint: disable=bad-whitespace
# Internal constants:
# Register names (FSK Mode even though we use LoRa instead, from table 85)
_RH_RF95_REG_00_FIFO = const(0x00)
_RH_RF95_REG_01_OP_MODE = const(0x01)
_RH_RF95_REG_02_RESERVED = const(0x02)
_RH_RF95_REG_03_RESERVED = const(0x03)
_RH_RF95_REG_04_RESERVED = const(0x04)
_RH_RF95_REG_05_RESERVED = const(0x05)
_RH_RF95_REG_06_FRF_MSB = const(0x06)
_RH_RF95_REG_07_FRF_MID = const(0x07)
_RH_RF95_REG_08_FRF_LSB = const(0x08)
_RH_RF95_REG_09_PA_CONFIG = const(0x09)
_RH_RF95_REG_0A_PA_RAMP = const(0x0a)
_RH_RF95_REG_0B_OCP = const(0x0b)
_RH_RF95_REG_0C_LNA = const(0x0c)
_RH_RF95_REG_0D_FIFO_ADDR_PTR = const(0x0d)
_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR = const(0x0e)
_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR = const(0x0f)
_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR = const(0x10)
_RH_RF95_REG_11_IRQ_FLAGS_MASK = const(0x11)
_RH_RF95_REG_12_IRQ_FLAGS = const(0x12)
_RH_RF95_REG_13_RX_NB_BYTES = const(0x13)
_RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB = const(0x14)
_RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB = const(0x15)
_RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB = const(0x16)
_RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB = const(0x17)
_RH_RF95_REG_18_MODEM_STAT = const(0x18)
_RH_RF95_REG_19_PKT_SNR_VALUE = const(0x19)
_RH_RF95_REG_1A_PKT_RSSI_VALUE = const(0x1a)
_RH_RF95_REG_1B_RSSI_VALUE = const(0x1b)
_RH_RF95_REG_1C_HOP_CHANNEL = const(0x1c)
_RH_RF95_REG_1D_MODEM_CONFIG1 = const(0x1d)
_RH_RF95_REG_1E_MODEM_CONFIG2 = const(0x1e)
_RH_RF95_REG_1F_SYMB_TIMEOUT_LSB = const(0x1f)
_RH_RF95_REG_20_PREAMBLE_MSB = const(0x20)
_RH_RF95_REG_21_PREAMBLE_LSB = const(0x21)
_RH_RF95_REG_22_PAYLOAD_LENGTH = const(0x22)
_RH_RF95_REG_23_MAX_PAYLOAD_LENGTH = const(0x23)
_RH_RF95_REG_24_HOP_PERIOD = const(0x24)
_RH_RF95_REG_25_FIFO_RX_BYTE_ADDR = const(0x25)
_RH_RF95_REG_26_MODEM_CONFIG3 = const(0x26)
_RH_RF95_REG_40_DIO_MAPPING1 = const(0x40)
_RH_RF95_REG_41_DIO_MAPPING2 = const(0x41)
_RH_RF95_REG_42_VERSION = const(0x42)
_RH_RF95_REG_4B_TCXO = const(0x4b)
_RH_RF95_REG_4D_PA_DAC = const(0x4d)
_RH_RF95_REG_5B_FORMER_TEMP = const(0x5b)
_RH_RF95_REG_61_AGC_REF = const(0x61)
_RH_RF95_REG_62_AGC_THRESH1 = const(0x62)
_RH_RF95_REG_63_AGC_THRESH2 = const(0x63)
_RH_RF95_REG_64_AGC_THRESH3 = const(0x64)
# RH_RF95_REG_01_OP_MODE 0x01
_RH_RF95_LONG_RANGE_MODE = const(0x80)
_RH_RF95_ACCESS_SHARED_REG = const(0x40)
_RH_RF95_MODE = const(0x07)
_RH_RF95_MODE_SLEEP = const(0x00)
_RH_RF95_MODE_STDBY = const(0x01)
_RH_RF95_MODE_FSTX = const(0x02)
_RH_RF95_MODE_TX = const(0x03)
_RH_RF95_MODE_FSRX = const(0x04)
_RH_RF95_MODE_RXCONTINUOUS = const(0x05)
_RH_RF95_MODE_RXSINGLE = const(0x06)
_RH_RF95_MODE_CAD = const(0x07)
# RH_RF95_REG_09_PA_CONFIG 0x09
_RH_RF95_PA_SELECT = const(0x80)
_RH_RF95_MAX_POWER = const(0x70)
_RH_RF95_OUTPUT_POWER = const(0x0f)
# RH_RF95_REG_0A_PA_RAMP 0x0a
_RH_RF95_LOW_PN_TX_PLL_OFF = const(0x10)
_RH_RF95_PA_RAMP = const(0x0f)
_RH_RF95_PA_RAMP_3_4MS = const(0x00)
_RH_RF95_PA_RAMP_2MS = const(0x01)
_RH_RF95_PA_RAMP_1MS = const(0x02)
_RH_RF95_PA_RAMP_500US = const(0x03)
_RH_RF95_PA_RAMP_250US = const(0x04)
_RH_RF95_PA_RAMP_125US = const(0x05)
_RH_RF95_PA_RAMP_100US = const(0x06)
_RH_RF95_PA_RAMP_62US = const(0x07)
_RH_RF95_PA_RAMP_50US = const(0x08)
_RH_RF95_PA_RAMP_40US = const(0x09)
_RH_RF95_PA_RAMP_31US = const(0x0a)
_RH_RF95_PA_RAMP_25US = const(0x0b)
_RH_RF95_PA_RAMP_20US = const(0x0c)
_RH_RF95_PA_RAMP_15US = const(0x0d)
_RH_RF95_PA_RAMP_12US = const(0x0e)
_RH_RF95_PA_RAMP_10US = const(0x0f)
# RH_RF95_REG_0B_OCP 0x0b
_RH_RF95_OCP_ON = const(0x20)
_RH_RF95_OCP_TRIM = const(0x1f)
# RH_RF95_REG_0C_LNA 0x0c
_RH_RF95_LNA_GAIN = const(0xe0)
_RH_RF95_LNA_BOOST = const(0x03)
_RH_RF95_LNA_BOOST_DEFAULT = const(0x00)
_RH_RF95_LNA_BOOST_150PC = const(0x11)
# RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11
_RH_RF95_RX_TIMEOUT_MASK = const(0x80)
_RH_RF95_RX_DONE_MASK = const(0x40)
_RH_RF95_PAYLOAD_CRC_ERROR_MASK = const(0x20)
_RH_RF95_VALID_HEADER_MASK = const(0x10)
_RH_RF95_TX_DONE_MASK = const(0x08)
_RH_RF95_CAD_DONE_MASK = const(0x04)
_RH_RF95_FHSS_CHANGE_CHANNEL_MASK = const(0x02)
_RH_RF95_CAD_DETECTED_MASK = const(0x01)
# RH_RF95_REG_12_IRQ_FLAGS 0x12
_RH_RF95_RX_TIMEOUT = const(0x80)
_RH_RF95_RX_DONE = const(0x40)
_RH_RF95_PAYLOAD_CRC_ERROR = const(0x20)
_RH_RF95_VALID_HEADER = const(0x10)
_RH_RF95_TX_DONE = const(0x08)
_RH_RF95_CAD_DONE = const(0x04)
_RH_RF95_FHSS_CHANGE_CHANNEL = const(0x02)
_RH_RF95_CAD_DETECTED = const(0x01)
# RH_RF95_REG_18_MODEM_STAT 0x18
_RH_RF95_RX_CODING_RATE = const(0xe0)
_RH_RF95_MODEM_STATUS_CLEAR = const(0x10)
_RH_RF95_MODEM_STATUS_HEADER_INFO_VALID = const(0x08)
_RH_RF95_MODEM_STATUS_RX_ONGOING = const(0x04)
_RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED = const(0x02)
_RH_RF95_MODEM_STATUS_SIGNAL_DETECTED = const(0x01)
# RH_RF95_REG_1C_HOP_CHANNEL 0x1c
_RH_RF95_PLL_TIMEOUT = const(0x80)
_RH_RF95_RX_PAYLOAD_CRC_IS_ON = const(0x40)
_RH_RF95_FHSS_PRESENT_CHANNEL = const(0x3f)
# RH_RF95_REG_1D_MODEM_CONFIG1 0x1d
_RH_RF95_BW = const(0xc0)
_RH_RF95_BW_125KHZ = const(0x00)
_RH_RF95_BW_250KHZ = const(0x40)
_RH_RF95_BW_500KHZ = const(0x80)
_RH_RF95_BW_RESERVED = const(0xc0)
_RH_RF95_CODING_RATE = const(0x38)
_RH_RF95_CODING_RATE_4_5 = const(0x00)
_RH_RF95_CODING_RATE_4_6 = const(0x08)
_RH_RF95_CODING_RATE_4_7 = const(0x10)
_RH_RF95_CODING_RATE_4_8 = const(0x18)
_RH_RF95_IMPLICIT_HEADER_MODE_ON = const(0x04)
_RH_RF95_RX_PAYLOAD_CRC_ON = const(0x02)
_RH_RF95_LOW_DATA_RATE_OPTIMIZE = const(0x01)
# RH_RF95_REG_1E_MODEM_CONFIG2 0x1e
_RH_RF95_SPREADING_FACTOR = const(0xf0)
_RH_RF95_SPREADING_FACTOR_64CPS = const(0x60)
_RH_RF95_SPREADING_FACTOR_128CPS = const(0x70)
_RH_RF95_SPREADING_FACTOR_256CPS = const(0x80)
_RH_RF95_SPREADING_FACTOR_512CPS = const(0x90)
_RH_RF95_SPREADING_FACTOR_1024CPS = const(0xa0)
_RH_RF95_SPREADING_FACTOR_2048CPS = const(0xb0)
_RH_RF95_SPREADING_FACTOR_4096CPS = const(0xc0)
_RH_RF95_TX_CONTINUOUS_MOE = const(0x08)
_RH_RF95_AGC_AUTO_ON = const(0x04)
_RH_RF95_SYM_TIMEOUT_MSB = const(0x03)
# RH_RF95_REG_4D_PA_DAC 0x4d
_RH_RF95_PA_DAC_DISABLE = const(0x04)
_RH_RF95_PA_DAC_ENABLE = const(0x07)
# The crystal oscillator frequency of the module
_RH_RF95_FXOSC = 32000000.0
# The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19
_RH_RF95_FSTEP = (_RH_RF95_FXOSC / 524288)
# RadioHead specific compatibility constants.
_RH_BROADCAST_ADDRESS = const(0xFF)
# User facing constants:
SLEEP_MODE = 0b000
STANDBY_MODE = 0b001
FS_TX_MODE = 0b010
TX_MODE = 0b011
FS_RX_MODE = 0b100
RX_MODE = 0b101
# pylint: enable=bad-whitespace
# Disable the too many instance members warning. Pylint has no knowledge
# of the context and is merely guessing at the proper amount of members. This
# is a complex chip which requires exposing many attributes and state. Disable
# the warning to work around the error.
# pylint: disable=too-many-instance-attributes
class RFM9x:
"""Interface to a RFM95/6/7/8 LoRa radio module. Allows sending and
receivng bytes of data in long range LoRa mode at a support board frequency
(433/915mhz).
You must specify the following parameters:
- spi: The SPI bus connected to the radio.
- cs: The CS pin DigitalInOut connected to the radio.
- reset: The reset/RST pin DigialInOut connected to the radio.
- frequency: The frequency (in mhz) of the radio module (433/915mhz typically).
You can optionally specify:
- preamble_length: The length in bytes of the packet preamble (default 8).
- high_power: Boolean to indicate a high power board (RFM95, etc.). Default
is True for high power.
- baudrate: Baud rate of the SPI connection, default is 10mhz but you might
choose to lower to 1mhz if using long wires or a breadboard.
Remember this library makes a best effort at receiving packets with pure
Python code. Trying to receive packets too quickly will result in lost data
so limit yourself to simple scenarios of sending and receiving single
packets at a time.
Also note this library tries to be compatible with raw RadioHead Arduino
library communication. This means the library sets up the radio modulation
to match RadioHead's defaults. Features like addressing and guaranteed
delivery need to be implemented at an application level.
"""
# 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)
class _RegisterBits:
# Class to simplify access to the many configuration bits avaialable
# on the chip's registers. This is a subclass here instead of using
# a higher level module to increase the efficiency of memory usage
# (all of the instances of this bit class will share the same buffer
# used by the parent RFM69 class instance vs. each having their own
# buffer and taking too much memory).
# Quirk of pylint that it requires public methods for a class. This
# is a decorator class in Python and by design it has no public methods.
# Instead it uses dunder accessors like get and set below. For some
# reason pylint can't figure this out so disable the check.
# pylint: disable=too-few-public-methods
# Again pylint fails to see the true intent of this code and warns
# against private access by calling the write and read functions below.
# This is by design as this is an internally used class. Disable the
# check from pylint.
# pylint: disable=protected-access
def __init__(self, address, *, offset=0, bits=1):
assert 0 <= offset <= 7
assert 1 <= bits <= 8
assert (offset + bits) <= 8
self._address = address
self._mask = 0
for _ in range(bits):
self._mask <<= 1
self._mask |= 1
self._mask <<= offset
self._offset = offset
def __get__(self, obj, objtype):
reg_value = obj._read_u8(self._address)
return (reg_value & self._mask) >> self._offset
def __set__(self, obj, val):
reg_value = obj._read_u8(self._address)
reg_value &= ~self._mask
reg_value |= (val & 0xFF) << self._offset
obj._write_u8(self._address, reg_value)
operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, bits=3)
low_frequency_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=3, bits=1)
modulation_type = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=5, bits=2)
# Long range/LoRa mode can only be set in sleep mode!
long_range_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=7, bits=1)
output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, bits=4)
max_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3)
pa_select = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1)
pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, bits=3)
dio0_mapping = _RegisterBits(_RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2)
tx_done = _RegisterBits(_RH_RF95_REG_12_IRQ_FLAGS, offset=3, bits=1)
rx_done = _RegisterBits(_RH_RF95_REG_12_IRQ_FLAGS, offset=6, bits=1)
def __init__(self, spi, cs, reset, frequency, *, preamble_length=8,
high_power=True, baudrate=5000000):
self.high_power = high_power
# Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz.
# Set Default Baudrate to 5MHz to avoid problems
self._device = spi_device.SPIDevice(spi, cs, baudrate=baudrate,
polarity=0, phase=0)
# Setup reset as a digital input (default state for reset line according
# to the datasheet). This line is pulled low as an output quickly to
# trigger a reset. Note that reset MUST be done like this and set as
# a high impedence input or else the chip cannot change modes (trust me!).
self._reset = reset
self._reset.switch_to_input(pull=digitalio.Pull.UP)
self.reset()
# No device type check! Catch an error from the very first request and
# throw a nicer message to indicate possible wiring problems.
try:
# Set sleep mode, wait 10s and confirm in sleep mode (basic device check).
# Also set long range mode (LoRa mode) as it can only be done in sleep.
self.sleep()
self.long_range_mode = True
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)))
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:
raise RuntimeError('Failed to communicate with radio, check wiring!')
# clear default setting for access to LF registers if frequency > 525MHz
if frequency > 525:
self.low_frequency_mode = 0
# Setup entire 256 byte FIFO
self._write_u8(_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0x00)
self._write_u8(_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00)
# Set mode idle
self.idle()
# Set modem config to RadioHead compatible Bw125Cr45Sf128 mode.
# Note no sync word is set for LoRa mode either!
self._write_u8(_RH_RF95_REG_1D_MODEM_CONFIG1, 0x72) # Fei msb?
self._write_u8(_RH_RF95_REG_1E_MODEM_CONFIG2, 0x74) # Fei lsb?
self._write_u8(_RH_RF95_REG_26_MODEM_CONFIG3, 0x00) # Preamble lsb?
# Set preamble length (default 8 bytes to match radiohead).
self.preamble_length = preamble_length
# Set frequency
self.frequency_mhz = frequency
# Set TX power to low defaut, 13 dB.
self.tx_power = 13
def _read_into(self, address, buf, length=None):
# Read a number of bytes from the specified address into the provided
# buffer. If length is not specified (the default) the entire buffer
# will be filled.
if length is None:
length = len(buf)
with self._device as device:
self._BUFFER[0] = address & 0x7F # Strip out top bit to set 0
# value (read).
device.write(self._BUFFER, end=1)
device.readinto(buf, end=length)
def _read_u8(self, address):
# Read a single byte from the provided address and return it.
self._read_into(address, self._BUFFER, length=1)
return self._BUFFER[0]
def _write_from(self, address, buf, length=None):
# Write a number of bytes to the provided address and taken from the
# provided buffer. If no length is specified (the default) the entire
# buffer is written.
if length is None:
length = len(buf)
with self._device as device:
self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to
# indicate a write.
device.write(self._BUFFER, end=1)
device.write(buf, end=length)
def _write_u8(self, address, val):
# Write a byte register to the chip. Specify the 7-bit address and the
# 8-bit value to write to that address.
with self._device as device:
self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to
# indicate a write.
self._BUFFER[1] = val & 0xFF
device.write(self._BUFFER, end=2)
def reset(self):
"""Perform a reset of the chip."""
# See section 7.2.2 of the datasheet for reset description.
self._reset.switch_to_output(value=False)
time.sleep(0.0001) # 100 us
self._reset.switch_to_input(pull=digitalio.Pull.UP)
time.sleep(0.005) # 5 ms
def idle(self):
"""Enter idle standby mode."""
self.operation_mode = STANDBY_MODE
def sleep(self):
"""Enter sleep mode."""
self.operation_mode = SLEEP_MODE
def listen(self):
"""Listen for packets to be received by the chip. Use :py:func:`receive`
to listen, wait and retrieve packets as they're available.
"""
self.operation_mode = RX_MODE
self.dio0_mapping = 0b00 # Interrupt on rx done.
def transmit(self):
"""Transmit a packet which is queued in the FIFO. This is a low level
function for entering transmit mode and more. For generating and
transmitting a packet of data use :py:func:`send` instead.
"""
self.operation_mode = TX_MODE
self.dio0_mapping = 0b01 # Interrupt on tx done.
@property
def preamble_length(self):
"""The length of the preamble for sent and received packets, an unsigned
16-bit value. Received packets must match this length or they are
ignored! Set to 8 to match the RadioHead RFM95 library.
"""
msb = self._read_u8(_RH_RF95_REG_20_PREAMBLE_MSB)
lsb = self._read_u8(_RH_RF95_REG_21_PREAMBLE_LSB)
return ((msb << 8) | lsb) & 0xFFFF
@preamble_length.setter
def preamble_length(self, val):
assert 0 <= val <= 65535
self._write_u8(_RH_RF95_REG_20_PREAMBLE_MSB, (val >> 8) & 0xFF)
self._write_u8(_RH_RF95_REG_21_PREAMBLE_LSB, val & 0xFF)
@property
def frequency_mhz(self):
"""The frequency of the radio in Megahertz. Only the allowed values for
your radio must be specified (i.e. 433 vs. 915 mhz)!
"""
msb = self._read_u8(_RH_RF95_REG_06_FRF_MSB)
mid = self._read_u8(_RH_RF95_REG_07_FRF_MID)
lsb = self._read_u8(_RH_RF95_REG_08_FRF_LSB)
frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF
frequency = (frf * _RH_RF95_FSTEP) / 1000000.0
return frequency
@frequency_mhz.setter
def frequency_mhz(self, val):
assert 240 <= val <= 960
# Calculate FRF register 24-bit value.
frf = int((val * 1000000.0) / _RH_RF95_FSTEP) & 0xFFFFFF
# Extract byte values and update registers.
msb = frf >> 16
mid = (frf >> 8) & 0xFF
lsb = frf & 0xFF
self._write_u8(_RH_RF95_REG_06_FRF_MSB, msb)
self._write_u8(_RH_RF95_REG_07_FRF_MID, mid)
self._write_u8(_RH_RF95_REG_08_FRF_LSB, lsb)
@property
def tx_power(self):
"""The transmit power in dBm. Can be set to a value from 5 to 23 for
high power devices (RFM95/96/97/98, high_power=True) or -1 to 14 for low
power devices. Only integer power levels are actually set (i.e. 12.5
will result in a value of 12 dBm).
The actual maximum setting for high_power=True is 20dBm but for values > 20
the PA_BOOST will be enabled resulting in an additional gain of 3dBm.
The actual setting is reduced by 3dBm.
The reported value will reflect the reduced setting.
"""
if self.high_power:
return self.output_power + 5
return self.output_power - 1
@tx_power.setter
def tx_power(self, val):
val = int(val)
if self.high_power:
assert 5 <= val <= 23
# Enable power amp DAC if power is above 20 dB.
# Lower setting by 3db when PA_BOOST enabled - see Data Sheet Section 6.4
if val > 20:
self.pa_dac = _RH_RF95_PA_DAC_ENABLE
val -= 3
else:
self.pa_dac = _RH_RF95_PA_DAC_DISABLE
self.pa_select = True
self.output_power = (val - 5) & 0x0F
else:
assert -1 <= val <= 14
self.pa_select = False
self.max_power = 0b111 # Allow max power output.
self.output_power = (val + 1) & 0x0F
@property
def rssi(self):
"""The received strength indicator (in dBm) of the last received message."""
# Read RSSI register and convert to value using formula in datasheet.
# Remember in LoRa mode the payload register changes function to RSSI!
return self._read_u8(_RH_RF95_REG_1A_PKT_RSSI_VALUE) - 137
def send(self, data, timeout=2.):
"""Send a string of data using the transmitter. You can only send 252
bytes at a time (limited by chip's FIFO size and appended headers). Note
this appends a 4 byte header to be compatible with the RadioHead library.
The timeout is just to prevent a hang (arbitrarily set to 2 Seconds).
"""
# Disable pylint warning to not use length as a check for zero.
# This is a puzzling warning as the below code is clearly the most
# efficient and proper way to ensure a precondition that the provided
# buffer be within an expected range of bounds. Disable this check.
# pylint: disable=len-as-condition
assert 0 < len(data) <= 252
# pylint: enable=len-as-condition
self.idle() # Stop receiving to clear FIFO and keep it clear.
# Fill the FIFO with a packet to send.
self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0.
# Write header bytes.
self._write_u8(_RH_RF95_REG_00_FIFO, _RH_BROADCAST_ADDRESS) # txHeaderTo
self._write_u8(_RH_RF95_REG_00_FIFO, _RH_BROADCAST_ADDRESS) # txHeaderFrom
self._write_u8(_RH_RF95_REG_00_FIFO, 0x00) # txHeaderId
self._write_u8(_RH_RF95_REG_00_FIFO, 0x00) # txHeaderFlags
# Write payload.
self._write_from(_RH_RF95_REG_00_FIFO, data)
# Write payload and header length.
self._write_u8(_RH_RF95_REG_22_PAYLOAD_LENGTH, len(data) + 4)
# Turn on transmit mode to send out the packet.
self.transmit()
# Wait for tx done interrupt with explicit polling (not ideal but
# best that can be done right now without interrupts).
start = time.monotonic()
timed_out = False
while not timed_out and not self.tx_done:
if (time.monotonic() - start) >= timeout:
timed_out = True
# Go back to idle mode after transmit.
self.idle()
# Clear interrupts.
self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF)
if timed_out:
raise RuntimeError('Timeout during packet send')
def receive(self, timeout=0.5, keep_listening=True):
"""Wait to receive a packet from the receiver. Will wait for up to
timeout amount of seconds for a packet to be received and decoded. If
a packet is found the payload bytes are returned, otherwise None is
returned (which indicates the timeout elapsed with no reception). Note
this assumes a 4-byte header is prepended to the data for compatibilty
with the RadioHead library (the header is not validated nor returned).
If keep_listening is True (the default) the chip will immediately enter
listening mode after reception of a packet, otherwise it will fall back
to idle mode and ignore any future reception.
"""
# Make sure we are listening for packets.
self.listen()
# Wait for the rx done interrupt. This is not ideal and will
# surely miss or overflow the FIFO when packets aren't read fast
# enough, however it's the best that can be done from Python without
# interrupt supports.
start = time.monotonic()
timed_out = False
while not timed_out and not self.rx_done:
if (time.monotonic() - start) >= timeout:
timed_out = True
# Payload ready is set, a packet is in the FIFO.
packet = None
if not timed_out:
# Grab the length of the received packet and check it has at least 5
# bytes to indicate the 4 byte header and at least 1 byte of user data.
length = self._read_u8(_RH_RF95_REG_13_RX_NB_BYTES)
if length < 5:
packet = None
else:
# Have a good packet, grab it from the FIFO.
# Reset the fifo read ptr to the beginning of the packet.
current_addr = self._read_u8(_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR)
self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr)
packet = bytearray(length)
# Read the packet.
self._read_into(_RH_RF95_REG_00_FIFO, packet)
# strip off the header
packet = packet[4:]
# Listen again if necessary and return the result packet.
if keep_listening:
self.listen()
else:
# Enter idle mode to stop receiving other packets.
self.idle()
# Clear interrupt.
self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF)
return packet