Files
RadioHead/RH_LoRaFileOps.h
Lazarewicz Julien 6c6451c92c first commit
2025-07-22 15:27:00 +02:00

369 lines
17 KiB
C++

// RH_LoRaFileOps.h
//
// Definitions for RadioHead driver on RPi+Linux
// and using LoRa-file-ops Linux driver ioctls to
// transmit and receive RadioHead compatible messages via SX1276/77/78/79
// and compatible radios.
// Requires a modified version of LoRa-file-ops driver to be installed,
// and a compatible radio to be connected
// appropriately
// https://github.com/starnight/LoRa/tree/file-ops
//
// Tested with:
// RPi 2 + Debian 2021-03-04 (kernel 5.10.17-v7+ #1403)
// Dragino LoRa/GPS HAT, https://wiki.dragino.com/index.php?title=Lora/GPS_HAT
// modified so RFM95 pin 5 NSS was no longer connected to RPi
// pin P1-22 (GPIO6), but is instead connected to RPi Pin P1-24 (CE0) which
// is the one used by /dev/loraSPI0.0
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2021 Mike McCauley
//
#ifndef RH_LORAFILEOPS_h
#define RH_LORAFILEOPS_h
#include <RHGenericDriver.h>
#warning RH_LoRaFileOps unfinished
// This can only build on Linux and compatible systems
// Caution also requires Lora-file-ops driver to be installed
// See https://github.com/starnight/LoRa/tree/file-ops
#if (RH_PLATFORM == RH_PLATFORM_UNIX) || defined(DOXYGEN)
// Driver constant definitions
// These are copied from LoRa file-ops branch pull request #16
// See the instructions in the RH_LoRaFileOps documentation for getting that version from github
// which is absolutely necessary if you want to support RadioHead messages with CRC enabled,
// which we strongly recommend.
// since they are not necessarily available in the compile host file
// system.
// CAUTION: these must be kept in sync with LoRa-file-ops if it changes
/* I/O control by each command. */
#include <sys/ioctl.h>
#define LORA_IOC_MAGIC '\x74'
#define LORA_SET_STATE (_IOW(LORA_IOC_MAGIC, 0, int))
#define LORA_GET_STATE (_IOR(LORA_IOC_MAGIC, 1, int))
#define LORA_SET_FREQUENCY (_IOW(LORA_IOC_MAGIC, 2, int))
#define LORA_GET_FREQUENCY (_IOR(LORA_IOC_MAGIC, 3, int))
#define LORA_SET_POWER (_IOW(LORA_IOC_MAGIC, 4, int))
#define LORA_GET_POWER (_IOR(LORA_IOC_MAGIC, 5, int))
#define LORA_SET_LNA (_IOW(LORA_IOC_MAGIC, 6, int))
#define LORA_GET_LNA (_IOR(LORA_IOC_MAGIC, 7, int))
#define LORA_SET_LNAAGC (_IOR(LORA_IOC_MAGIC, 8, int))
#define LORA_SET_SPRFACTOR (_IOW(LORA_IOC_MAGIC, 9, int))
#define LORA_GET_SPRFACTOR (_IOR(LORA_IOC_MAGIC, 10, int))
#define LORA_SET_BANDWIDTH (_IOW(LORA_IOC_MAGIC, 11, int))
#define LORA_GET_BANDWIDTH (_IOR(LORA_IOC_MAGIC, 12, int))
#define LORA_GET_RSSI (_IOR(LORA_IOC_MAGIC, 13, int))
#define LORA_GET_SNR (_IOR(LORA_IOC_MAGIC, 14, int))
/* Mikem added 2021-04-19 fro pull request 16: */
#define LORA_SET_CRC (_IOW(LORA_IOC_MAGIC, 15, int))
#define LORA_SET_CODINGRATE (_IOW(LORA_IOC_MAGIC, 16, int))
#define LORA_GET_CODINGRATE (_IOR(LORA_IOC_MAGIC, 17, int))
#define LORA_SET_IMPLICIT (_IOW(LORA_IOC_MAGIC, 18, int))
#define LORA_SET_LDRO (_IOW(LORA_IOC_MAGIC, 19, int))
#define LORA_SET_PREAMBLE (_IOW(LORA_IOC_MAGIC, 20, int))
#define LORA_GET_PREAMBLE (_IOR(LORA_IOC_MAGIC, 21, int))
#define LORA_SET_PARAMP (_IOW(LORA_IOC_MAGIC, 22, int))
#define LORA_GET_PARAMP (_IOR(LORA_IOC_MAGIC, 23, int))
#define LORA_SET_OCPIMAX (_IOW(LORA_IOC_MAGIC, 24, int))
#define LORA_GET_OCPIMAX (_IOR(LORA_IOC_MAGIC, 25, int))
#define LORA_SET_LNABOOSTHF (_IOW(LORA_IOC_MAGIC, 26, int))
#define LORA_SET_PMAX20DBM (_IOW(LORA_IOC_MAGIC, 27, int))
/* List the state of the LoRa device. */
#define LORA_STATE_SLEEP 0
#define LORA_STATE_STANDBY 1
#define LORA_STATE_TX 2
#define LORA_STATE_RX 3
#define LORA_STATE_CAD 4
// Max number of octets the SX1278 LORA Rx/Tx FIFO can hold
#define RH_LORAFILEOPS_FIFO_SIZE 255
// This is the maximum number of bytes that can be carried by the LORA.
// We use some for headers, keeping fewer for RadioHead messages
#define RH_LORAFILEOPS_MAX_PAYLOAD_LEN RH_LORAFILEOPS_FIFO_SIZE
// The length of the headers we add.
// The headers are inside the LORA's payload
#define RH_LORAFILEOPS_HEADER_LEN 4
// This is the maximum message length that can be supported by this driver.
// Can be pre-defined to a smaller size (to save SRAM) prior to including this header
// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS
#ifndef RH_LORAFILEOPS_MAX_MESSAGE_LEN
#define RH_LORAFILEOPS_MAX_MESSAGE_LEN (RH_LORAFILEOPS_MAX_PAYLOAD_LEN - RH_LORAFILEOPS_HEADER_LEN)
#endif
/////////////////////////////////////////////////////////////////////
/// \class RH_LoRaFileOps RH_LoRaFileOps.h <RH_LoRaFileOps.h>
/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa
/// capable radio transceiver on a Linux platform (possibly Raspberry Pi), using the
/// lora-file-ops driver by Jian-Hong Pan (starnight):
/// https://github.com/starnight/LoRa/tree/file-ops
///
/// This RadioHead driver is only available to Commercial licensees. Apply to info@airspayce.com.
///
/// For an excellent discussion of LoRa range and modulations, see
/// https://medium.com/home-wireless/testing-lora-radios-with-the-limesdr-mini-part-2-37fa481217ff
/// Works with Dragino LoRa/GPS HAT, https://wiki.dragino.com/index.php?title=Lora/GPS_HAT
/// modified so RFM95 pin 5 NSS was no longer connected to RPi
/// pin P1-22 (GPIO6), but is instead connected to RPi Pin P1-24 (CE0) which
/// is the one used by /dev/loraSPI0.0. Interoperates with RH_RF95
/// with modem config RH_RF95::Bw125Cr45Sf2048
///
/// \par Overview
///
/// This class provides basic functions for sending and receiving unaddressed,
/// unreliable datagrams of arbitrary length up to 251 octets per packet.
///
/// Manager classes may use this class to implement reliable, addressed datagrams and streams,
/// mesh routers, repeaters, translators etc.
///
/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and
/// modulation scheme.
///
/// This RadioHead Driver provides an object-oriented interface for sending and receiving
/// data messages with Semtech SX1276/77/78/79
/// and compatible radio modules in LoRa mode, using the lora-file-ops Linux driver. It only runs on Linux
/// such as Raspberry Pi Debian etc.is a low-cost ISM transceiver
/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and
/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which
/// is the only mode supported in this RadioHead driver (because that is the only mode supported by the
/// underlying lora-file-ops Linux driver.
///
/// This Driver provides functions for sending and receiving messages of up
/// to 251 octets on any frequency supported by the radio, in a range of
/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with
/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited
/// range of frequencies due to antenna tuning.
///
/// Up to 2 modules are supported by lora-file-ops
/// permitting the construction of translators and frequency changers, etc.
///
/// Support for other features such as transmitter power control etc is
/// also provided.
///
/// Tested with:
/// RPi 2 + Debian 2021-03-04 (kernel 5.10.17-v7+ #1403)
/// Dragino LoRa/GPS HAT, https://wiki.dragino.com/index.php?title=Lora/GPS_HAT
/// modified so RFM95 pin 5 NSS was no longer connected to RPi
/// pin P1-22 (GPIO6), but is instead connected to RPi Pin P1-24 (CE0) which
/// is the one used by /dev/loraSPI0.0
/// \par Packet Format
///
/// All messages sent and received by this RH_RF95 Driver conform to this packet format:
///
/// - LoRa mode:
/// - 8 symbol PREAMBLE
/// - Explicit header with header CRC (default CCITT, handled internally by the radio)
/// - 4 octets HEADER: (TO, FROM, ID, FLAGS)
/// - 0 to 251 octets DATA
/// - CRC (default CCITT, handled internally by the radio)
///
/// This format is compatible with the one used by RH_RF95 by default.
///
/// \par Modulation
///
/// The default modulation scheme implemented by this driver is:
/// 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
/// which is compatible withthe RH_RF95 modem config RH_RF95::Bw125Cr45Sf2048 and so this RadioHead driver will
/// interoperate with RH_RF95.
///
/// \par Installing lora-file-ops
/// For this driver to work on a Linux platform such as Raspberry Pi, it is absolutely necessary to install the
/// LoRa-file-ops Linux driver written by starnight, and which is available from github.
/// The version currently available (2021-04-19) does not support enabling CRCs in the radio, which is
/// strongly recommended, and necessary to work with any other RadioHead lora driver/
/// At this date, code to add CRC suport to the driver is avalable as a pull request on github
/// as a Git pull request #16 from flyskywhy. We strongly recommend using it as described below.
///
/// To get LoRa-file-ops from starnight, plus the necessary patches and fixes from pull request #16 from flyskywhy
/// and to build it and install it and load it into the kernel for testing:
/// \code
/// # ON a recent Debian kernel:
/// sudo apt-get install linux-headers-rpi raspberrypi-kernel-headers
/// # Enable the SPI interface in the kernel
/// sudo raspi-config:
/// -> 3 Interface Options
/// -> P4 SPI
/// -> Would you like the SPI interface to be enabled? select Yes, press Return, Return, Select Finish
/// # in a working directory, not as root:
/// git clone https://github.com/starnight/LoRa.git
/// cd LoRa/
/// git checkout file-ops
/// git fetch origin pull/16/head:file-ops-patched # only until pull #16 is not merged into master
/// git checkout file-ops-patched # only until pull #16 is not merged into master
/// cd LoRa/
/// make
/// make install
/// cd ../dts-overlay
/// make
/// cd ../
/// # and after every reboot:
/// sudo dtoverlay rpi-lora-spi
/// sudo modprobe sx1278
/// \endcode
///
/// If you want to permanently add the LoRa-file-ops Linux driver so it loads automatically
/// on every boot, add this to /boot/config.txt
/// \code
/// dtparam=rpi-lora-spi=on
/// \endcode
///
/// Note: it may be the case in the future that pull request 16 is merged into the master of LoRa-File-Ops
/// in which case 2 steps are not needed above
class RH_LoRaFileOps : public RHGenericDriver
{
public:
/// Constructor. You can have multiple instances each connected to a different LoRa port
/// \param[in] port Name of the lora-file-ops port, typically something like /dev/loraSPI0.0
RH_LoRaFileOps(const char* port);
/// Initialise the Driver transport hardware and software.
/// Opens the LorFileOps driver port and initalises the radio to default settings
/// Leaves the radio in receive mode,
/// with default configuration of: 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 2048chips/symbol, CRC on
/// which is compatible with RH_RF95::Bw125Cr45Sf2048
/// \return true if initialisation succeeded.
virtual bool init();
/// Tests whether a new message is available from the lora-file-ops Linux driver.
/// This can be called multiple times in a timeout loop
/// \return true if a new, complete, error-free uncollected message is available to be retreived by recv()
virtual bool available();
// Sigh, its not possible to implement waitAvailable and waitAvailableTimout in terms
// of select(), since the LoRa-file-ops driver does not detect any interrupts, and
// therefore select will not return when a packet is received by the radio.
// So we have to live with the RHGenericDriver implementations that call available() to poll the port.
// waitAvailableTimeout() supports an optional delay between each poll
// of available() so that on Linux at least another process can get the CPU.
/// If there is a valid message available and it is for this node, copy it to buf and return true
/// else return false.
/// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted).
/// You should be sure to call this function frequently enough to not miss any messages
/// or call it aafter available(), waitAvailable() or waitAvailableTimeout()
/// indicate that a message is avalable
/// It is recommended that you call it in your main loop.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied.
/// \return true if a valid message addressed to this node was copied to buf
virtual bool recv(uint8_t* buf, uint8_t* len);
/// Loads a message into the transmitter and starts the transmitter. Note that a message length
/// of 0 is permitted. CAD is not supported yet.
/// The lora-file-ops driver waits for the entire message to be transmitted before resuming operations.
/// \param[in] data Array of data to be sent
/// \param[in] len Number of bytes of data to send
/// \return true if the message length was valid and it was correctly transmitted.
virtual bool send(const uint8_t* data, uint8_t len);
/// Returns the maximum message length
/// available in this Driver.
/// \return The maximum legal message length
virtual uint8_t maxMessageLength();
/// Sets the transmitter and receiver
/// centre (carrier) frequency.
/// \param[in] centre Frequency in Hz. 137000000 to 1020000000. Caution: SX1276/77/78/79 comes in several
/// different frequency ranges, and setting a frequency outside that range of your radio will probably not work
/// correctly becasue the antenna coupling or antenna wont work outside their designed frequency range
/// \return true if the selected frequency centre is within range
bool setFrequency(uint32_t centre);
/// Returns the current transmitter and receiver
/// centre frequency.
/// \return Centre frequency in Hz.
uint32_t getFrequency();
/// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured
/// by the receiver.
/// \return SNR of the last received message in dB
int lastSNR();
/// Sets the transmitter power output level
/// Be a good neighbour and set the lowest power level you need.
/// Caution: legal power limits may apply in certain countries.
/// After init(), the power will be set to 13dBm
/// \param[in] power Transmitter power level in dBm. Max 20dBm.
void setTxPower(int32_t power);
/// Gets the currently set transmitter power output level
/// \return Current poer level in dbM
int32_t getTxPower();
/// Set the LoRa Spreading Factor
/// \param[in] sf The spreading factor. Valid values are 64, 128, 256, 512, 1024, 2048, 4096.
void setSpreadingFactor(int32_t sf);
/// Get the LoRa Spreading Factor
/// \return The current Spreading Factor
int32_t getSpreadingFactor();
/// Gets the RSSI of the last received packet
/// \return RSSI of the last received packet
int32_t getRSSI();
/// Gets the Signal To Noise (SNR) of the last received packet
/// \return SNR of the last received packet
int32_t getSNR();
/// Set the receiver Low Noise Amplifier (LNA) gain
/// \param[in] lna LNA gain in dBm
void setLNA(int32_t lna);
/// Get the current LNA gain
/// \return The current LNA gain in dBm
int32_t getLNA();
/// Set the LNA Automatic Gain Control (AGC) enabled
/// \param[in] lnaagc 1 to enable LNA AGC, 0 to disable it
void setLNAAGC(int32_t lnaagc);
/// Set the transmitter and receiver modulation bandwidth
/// \param[in] bw Modulation bandwidth in Hz. Valid values are 7800, 10400, 15600, 20800,
/// 312500, 41700, 62500, 125000, 250000, 500000.
void setBW(int32_t bw);
/// Get the transmitter and receiver modulation bandwidth
/// \return Modulation bandwidth in Hz
int32_t getBW();
/// Enable Cyclic Redundancy Check (CRC) in the transmitter and receiver. If enabled,
/// the transmitter will always appenda CRC to every packet, and the receiver will
/// always check the CRC on received packets, ignoring packets with incorrect CRC
/// \param[in] crc 1 to enable CRC generation and detection, 0 to disable it
void setCRC(uint32_t crc);
protected:
/// Set the current radio state, one of LORA_STATE_*
void setState(uint32_t state);
/// Get the current radio state
uint32_t getState();
private:
// The name of the Unix filesystm port for the Lora SX1278 compatible radio
// typically /dev/loraSPI0.0 or similar
const char* _port;
/// Unix file system device number of the LoRa device port. -1 if not open
int _fd;
/// Last measured SNR, dB
int8_t _lastSNR;
};
/// @example lorafileops_client.cpp
/// @example lorafileops_server.cpp
#endif
#endif