|
|
- # 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
|