// RH_RN2483.h
//
// Definitions for RN2483 LoRa radios per:
// https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/UserGuides/RN2483-LoRa-Technology-Module-Command-Reference-User-Guide-DS40001784G.pdf
//
// Author: Industrial Shields
// Copyright (C) 2024 Industrial Shields
//

#ifndef RH_RN2483_h
#define RH_RN2483_h

#include <RHGenericDriver.h>
#include <Stream.h>

// Max number of octets the LORA Rx/Tx FIFO can hold
#define RH_RN2483_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_RN2483_MAX_PAYLOAD_LEN RH_RN2483_FIFO_SIZE

// The length of the headers we add.
// The headers are inside the LORA's payload
#define RH_RN2483_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_RN2483_MAX_MESSAGE_LEN
 #define RH_RN2483_MAX_MESSAGE_LEN (RH_RN2483_MAX_PAYLOAD_LEN - RH_RN2483_HEADER_LEN)
#endif

// Modes for RN2483 module
#define RH_RN2483_RX_MODE     0
#define RH_RN2483_TX_MODE     1

// Some commands for the RN2483 module
#define RH_RN2483_CMND_MACPAUSE        "mac pause\r\n"

#define RH_RN2483_CMND_RADIO_TX        "radio tx "
#define RH_RN2483_RESP_RADIO_TX_OK     "radio_tx_ok\r\n"
#define RH_RN2483_CMND_RADIO_RX        "radio rx "
#define RH_RN2483_CMND_RADIO_RX_RESP   "radio_rx  "
#define RH_RN2483_CMND_RADIO_RXSTOP    "radio rxstop\r\n"

#define RH_RN2483_CMND_SYS_RESET       "sys reset\r\n"
#define RH_RN2483_CMND_SYS_GET_VER     "sys get ver\r\n"

#define RH_RN2483_CMND_RADIO_SET_PWR   "radio set pwr "
#define RH_RN2483_CMND_RADIO_SET_FREQ  "radio set freq "
#define RH_RN2483_CMND_RADIO_SET_SF    "radio set sf sf"
#define RH_RN2483_CMND_RADIO_SET_PRLEN "radio set prlen "
#define RH_RN2483_CMND_RADIO_SET_BW    "radio set bw "
#define RH_RN2483_CMND_RADIO_SET_CR    "radio set cr 4/"
#define RH_RN2483_CMND_RADIO_SET_SYNC  "radio set sync "
#define RH_RN2483_CMND_RADIO_SET_WDT   "radio set wdt "

#define RH_RN2483_CMND_CRNL             "\r\n"
#define RH_RN2483_RESP_OK               "ok\r\n"


class RH_RN2483 : public RHGenericDriver
{
public:

    /// Constructor. You can have multiple instances, but each instance must have its own
    RH_RN2483(HardwareSerial *s);


    /// Initialise the Driver transport hardware and software.
    bool init();

    /// Tests whether a new message is available from the Driver.
    /// On most drivers, this will also put the Driver into RHModeRx mode until
    /// a message is actually received by the transport, when it will be returned to RHModeIdle.
    /// 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()
    bool available();

    /// Turns the receiver on if it not already on.
    /// If there is a valid message available, 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
    /// 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 the number of octets available in buf. The number be reset to the actual number of octets copied.
    /// \return true if a valid message was copied to buf
    bool recv(uint8_t* buf, uint8_t* len);

    /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent().
    /// Then optionally waits for Channel Activity Detection (CAD)
    /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD().
    /// Then loads a message into the transmitter and starts the transmitter. Note that a message length
    /// of 0 is permitted.
    /// \param[in] data Array of data to be sent
    /// \param[in] len Number of bytes of data to send
    /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting.
    /// \return true if the message length was valid and it was correctly queued for transmit. Return false
    /// if CAD was requested and the CAD timeout timed out before clear channel was detected.
    bool send(const uint8_t* data, uint8_t len);

    /// Returns the maximum message length
    /// available in this Driver.
    /// \return The maximum legal message length
    uint8_t maxMessageLength();

    /// Sets the transmitter power output
    /// \param[in] level A valid power setting from the Power enum
    /// \return true if successful
    bool setTxPower(int8_t power);

    /// Sets the transmitter and receiver frequency.
    /// \param[in] frequency Desired frequency in MHz from 433.05 to 434.79 MHz
    ///                      or between 863 and 870 MHz
    /// \return true if successful
    bool setFrequency(int frequency);

    /// Sets the transmitter and receiver spreading factor.
    /// \param[in] spreadingFactror Desired spreading factor, from 7 to 12
    /// \return true if successful
    bool setSpreadingFactor(uint8_t spreadingFactor);

    /// Sets the transmitter and receiver preamble length.
    /// \param[in] preambleLength Desired preamble length, from 0 to 65535
    /// \return true if successful
    bool setPreambleLength(uint16_t preambleLength);

    /// Sets the transmitter and receiver bandwidth.
    /// \param[in] bandwidth Desired Bandwidth, can be 125, 250 or 500
    /// \return true if successful
    bool setSignalBandwidth(uint16_t bandwidth);

    /// Sets the transmitter and receiver coding rate.
    /// \param[in] codingrate Desired Coding Rate, can be 5, 6, 7 or 8
    /// \return true if successful
    bool setCodingRate4(uint8_t codingrate);

    /// Sets the transmitter and receiver sync word.
    /// \param[in] syncword Desired Sync Word, can be 5, 6, 7 or 8
    /// \return true if successful
    bool setSyncWord(uint8_t syncword);

    /// Prints the buffer contents in ASCII,
    /// converting each time 2 bytes into its ASCII equivalent to print a readable message
    void printBuffer(uint8_t* buf, uint8_t len);

protected:

    /// Retrieves the version number for the radio and checks that it is valid
    /// \return true if the version could be retrieved and is radio model number is correct
    bool getVersion();

    /// Issues a reset command to the radio
    /// WARNING: this seems to break reception. Why?
    /// \return true if successful
    bool reset();

    /// Examine the receive buffer to determine whether the message is for this node
    /// For internal use only
    void validateRxBuf();

    /// Clear our local receive buffer
    /// For internal use only
    void clearRxBuf();

    /// Sets the transmitter and receiver watchdog timeout.
    /// \param[in] wdt Desired Sync Word, can be from 0 to 4294967295
    /// \return true if successful
    bool setWDT(unsigned long wdt);

    /// Sets the module to RX mode
    /// \return true if successful
    bool setMode(uint8_t mode);

    /// Reads the response from the RN2483 module and comapre its with the expected one
    /// \return true if successful
    bool readResponse(const char* resp, uint8_t len);

    /// Waits for any currently transmitting packet to be completely sent
    /// Returns true if successful
    bool waitPacketSent();

private:

    /// Serial stream (hardware or software serial)
    HardwareSerial*     _s;

    /// Number of octets in the buffer
    uint8_t             _bufLen;

    /// Number of good octets in the buffer, the message itself
    uint8_t             _goodBufLen;

    /// The receiver/transmitter buffer
    uint8_t             _buf[RH_RN2483_MAX_PAYLOAD_LEN];

    /// True when there is a valid message in the buffer
    bool                _rxBufValid;

    /// Mode of the module
    uint8_t             _mode;

};

#endif
