/*
 * Author: Jon Trulson <jtrulson@ics.com>
 * Copyright (c) 2015 Intel Corporation.
 *
 * Thanks to Semtech for their example code at:
 * https://github.com/Lora-net/LoRaMac-node
 * released under a modified BSD license, for many clues as to how to
 * initialize and operate this radio properly.
 * See src/sx1276/LICENSE.txt
 *
 * 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.
 */

#pragma once

#include <string>

#include <sys/time.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>

#include <mraa/common.hpp>
#include <mraa/spi.hpp>
#include <mraa/gpio.hpp>

// Our crystal oscillator frequency (32Mhz)
#define FXOSC_FREQ 32000000.0

// Our freq stepping resolution (in Hz) if FXOSC_FREQ is 32Mhz
// (FXOSC_FREQ / 2^19) =
#define FXOSC_STEP 61.03515625

namespace upm {
  
  /**
   * @brief SX1276 LoRa/FSK modem
   * @defgroup sx1276 libupm-sx1276
   * @ingroup spi gpio wifi
   */

  /**
   * @library sx1276
   * @sensor sx1276
   * @comname SX1276 LoRa/FSK Modem
   * @altname SX1277 SX1278 SX1279
   * @type wifi
   * @man semtech
   * @con spi gpio
   * @web http://www.digikey.com/product-search/en?vendor=0&keywords=SX1276MB1LAS
   *
   * @brief API for the SX1276 LoRa/FSK modem
   *
   * The SX1276 is a FSK/OOK/LoRa modem capable of both Low Frequency
   * and High Frequency communication.
   *
   * It requires a 3.3v power supply, do not use 5v.
   *
   * Frequency Hopping Spread Spectrum (FHSS) is not currently supported.
   *
   * While not all of the functionality of this device is supported
   * initially, methods and register definitions are provided that
   * should allow an end user to implement whatever features are
   * required.
   *
   * FSK send/receive example
   * @snippet sx1276-fsk.cxx Interesting
   * LORA send/receive example
   * @snippet sx1276-lora.cxx Interesting
   */

  class SX1276 {
  public:

    // The default chip revision
    static const uint8_t chipRevision = 0x12;


    // total FIFO size
    static const int FIFO_SIZE = 256;

    // differentiator between high and low bands
    static const int RF_MID_BAND_THRESH = 525000000;

    // LoRa RSSI offsets depending on LF or HF bands
    static const int LOR_RSSI_OFFSET_HF = -157;
    static const int LOR_RSSI_OFFSET_LF = -164;

    /**
     * What modem we are configured for
     */
    typedef enum {
      MODEM_LORA                         = 0,
      MODEM_FSK
    } RADIO_MODEM_T;

    /**
     * Events that can occurr during a TX or RX operation.
     *
     * When sending or receiving a packet (calling setTx()/send() or
     * setRx()), the state will be initialized to ESTATE_EXEC to
     * indicate the operation is in progress.  Once an event has
     * occurred, this state will be updated accordingly.
     *
     * For receiving, if RX_DONE is set, then it is safe to retrieve
     * the rx buffer (via getRxBuffer()/getRxBufferStr()) as well as
     * query the RSSI and SNR.  On RX_ERROR, these values will be the
     * same as they were last set during the last RX_DONE event.
     */
    typedef enum {
      REVENT_DONE                       = 0, // operation completed successfully
      REVENT_EXEC,                           // runninsg something
      REVENT_ERROR,                          // failed, crc error, sync timeout
      REVENT_TIMEOUT                         // timed out
    } RADIO_EVENT_T;

    /**
     * SX1276 registers
     *
     * NOTE: reserved registers must not be written into or read from.
     * Reserved bitfields must always be 0.  
     *
     * This device has a set of "common" registers, as well as
     * registers that represent different things depending on whether
     * the device in in LoRa mode or FSK/OOK mode.  So here, we will
     * prefix the register names with COM (common), LOR (LoRa mode),
     * and FSK (FSK/OOK mode) accordingly.
     */
    typedef enum {
      COM_RegFifo                      = 0x00, // FIFO r/w access
      COM_RegOpMode                    = 0x01, // LoRa/FSK

      FSK_RegBitrateMsb                = 0x02,
      LOR_Reserved02                   = 0x02, // reserved

      FSK_RegBitrateLsb                = 0x03,
      LOR_Reserved03                   = 0x03, // reserved

      FSK_RegFdevMsb                   = 0x04, // freq deviation
      LOR_Reserved04                   = 0x04, // reserved

      FSK_RegFdevLsb                   = 0x05,
      LOR_Reserved05                   = 0x05, // reserved
      
      COM_RegFrfMsb                    = 0x06, // carrier freq
      COM_RegFrfMid                    = 0x07,
      COM_RegFrfLsb                    = 0x08,
      COM_RegPaConfig                  = 0x09,
      COM_RegPaRamp                    = 0x0a,

      COM_RegOcp                       = 0x0b, // overcurrent protection
      COM_RegLna                       = 0x0c,

      FSK_RegRxConfig                  = 0x0d,
      LOR_RegFifoAddrPtr               = 0x0d,

      FSK_RegRssiConfg                 = 0x0e,
      LOR_RegFifoTxBaseAddr            = 0x0e,

      FSK_RegRssiCollision             = 0x0f,
      LOR_RegFifoRxBaseAddr            = 0x0f,

      FSK_RegRssiThresh                = 0x10,
      LOR_RegFifoRxCurrentAddr         = 0x10,

      FSK_RegRssiValue                 = 0x11,
      LOR_RegIrqFlagsMask              = 0x11,

      FSK_RegRxBw                      = 0x12,
      LOR_RegIrqFlags                  = 0x12,

      FSK_RegAfcBw                     = 0x13, // automatic freq cntrl
      LOR_RegRxNbBytes                 = 0x13, // received pkt len

      FSK_RegOokPeak                   = 0x14,
      LOR_RegRxHeaderCntValueMsb       = 0x14,

      FSK_RegOokFix                    = 0x15,
      LOR_RegRxHeaderCntValueLsb       = 0x15,

      FSK_RegOokAvg                    = 0x16,
      LOR_RegRxPacketCntValueMsb       = 0x16,

      FSK_Reserved17                   = 0x17, // reserved
      LOR_RegRxPacketCntValueLsb       = 0x17,

      FSK_Reserved18                   = 0x18, // reserved
      LOR_RegModemStat                 = 0x18,

      FSK_Reserved19                   = 0x19, // reserved
      LOR_RegPktSnrValue               = 0x19,

      FSK_RegAfcFei                    = 0x1a,
      LOR_RegPktRssiValue              = 0x1a,

      FSK_RegAfcMsb                    = 0x1b,
      LOR_RegRssiValue                 = 0x1b,

      FSK_RegAfcLsb                    = 0x1c,
      LOR_RegHopChannel                = 0x1c, // fhss starting channel

      FSK_RegFeiMsb                    = 0x1d,
      LOR_RegModemConfig1              = 0x1d,

      FSK_RegFeiLsb                    = 0x1e,
      LOR_RegModemConfig2              = 0x1e,

      FSK_RegPreambleDetect            = 0x1f,
      LOR_RegSymbTimeoutLsb            = 0x1f,

      FSK_RegRxTimeout1                = 0x20,
      LOR_RegPreambleMsb               = 0x20,

      FSK_RegRxTimeout2                = 0x21,
      LOR_RegPreambleLsb               = 0x21,

      FSK_RegRxTimeout3                = 0x22,
      LOR_RegPayloadLength             = 0x22,

      FSK_RegRxDelay                   = 0x23,
      LOR_RegMaxPayloadLength          = 0x23,

      FSK_RegOsc                       = 0x24,
      LOR_RegHopPeriod                 = 0x24,

      FSK_RegPreambleMsb               = 0x25,
      LOR_RegFifoRxByteAddr            = 0x25,

      FSK_RegPreambleLsb               = 0x26,
      LOR_RegModemConfig3              = 0x26,

      FSK_RegSyncConfig                = 0x27,
      LOR_Reserved27                   = 0x27, // reserved

      FSK_RegSyncValue1                = 0x28,
      LOR_RegFeiMsb                    = 0x28,
      
      FSK_RegSyncValue2                = 0x29,
      LOR_RegFeiMid                    = 0x29,

      FSK_RegSyncValue3                = 0x2a,
      LOR_RegFeiLsb                    = 0x2a,

      FSK_RegSyncValue4                = 0x2b,
      LOR_Reserved2b                   = 0x2b, // reserved

      FSK_RegSyncValue5                = 0x2c,
      LOR_RegRssiWideband              = 0x2c,

      FSK_RegSyncValue6                = 0x2d,
      LOR_Reserved2d                   = 0x2d, // reserved
      
      FSK_RegSyncValue7                = 0x2e,
      LOR_Reserved2e                   = 0x2e, // reserved
      
      FSK_RegSyncValue8                = 0x2f,
      LOR_Reserved2f                   = 0x2f, // reserved

      FSK_RegPacketConfig1             = 0x30,
      LOR_Reserved30                   = 0x30, // reserved

      FSK_RegPacketConfig2             = 0x31,
      LOR_RegDetectOptimize            = 0x31,

      FSK_RegPayloadLength             = 0x32,
      LOR_Reserved32                   = 0x32, // reserved

      FSK_RegNodeAddr                  = 0x33,
      LOR_RegInvertIQ                  = 0x33,

      FSK_RegBroadcastAddr             = 0x34,
      LOR_Reserved34                   = 0x34, // reserved
      
      FSK_RegFifoThresh                = 0x35,
      LOR_Reserved35                   = 0x35, // reserved
      
      FSK_RegSeqConfig1                = 0x36,
      LOR_Reserved36                   = 0x36, // reserved

      FSK_RegSeqConfig2                = 0x37,
      LOR_RegDetectionThreshold        = 0x37,

      FSK_RegTimerResol                = 0x38,
      LOR_Reserved38                   = 0x38, // reserved

      FSK_RegTimer1Coeff               = 0x39,
      LOR_RegSyncWord                  = 0x39,

      FSK_RegTimer2Coeff               = 0x3a,
      LOR_Reserved3a                   = 0x3a, // reserved

      FSK_RegImageCal                  = 0x3b,
      LOR_Reserved3b                   = 0x3b, // reserved (in datasheet)?
      LOR_RegInvertIQ2                 = 0x3b, // does not exist in datasheet
                                               // but used in Semtech code.
                                               // UNDOCUMENTED

      FSK_RegTemp                      = 0x3c,
      LOR_Reserved3c                   = 0x3c, // reserved

      FSK_RegLowBat                    = 0x3d,
      LOR_Reserved3d                   = 0x3d, // reserved

      FSK_RegIrqFlags1                 = 0x3e,
      LOR_Reserved3e                   = 0x3e, // reserved

      FSK_RegIrqFlags2                 = 0x3f,
      LOR_Reserved3f                   = 0x3f, // reserved

      COM_RegDioMapping1               = 0x40, // DIO0-DIO3
      COM_RegDioMapping2               = 0x41, // DIO4-DIO5, clk out freq

      COM_RegVersion                   = 0x42, // Semtech ID (silicon revision)

      // 0x43 reserved

      // The data sheet says this is FSK only, but the semtech code
      // implies this is only valid for LoRa.  So for now, assume the
      // datasheet is wrong.
      //
      // FSK_RegPllHop                    = 0x44, 
      // LOR_Reserved44                   = 0x44, // reserved 

      FSK_Reserved44                   = 0x44,
      LOR_RegPllHop                    = 0x44,

      // 0x45-0x4a reserved

      COM_RegTcxo                      = 0x4b,

      // 0x4c reserved

      COM_RegPaDac                     = 0x4d,

      // 0x4e-0x5a reserved

      COM_RegFormerTemp                = 0x5b,

      // 0x5c reserved

      FSK_RegBitRateFrac               = 0x5d,
      LOR_Reserved5d                   = 0x5d, // reserved

      // 0x5e-0x60 reserved

      COM_RegAgcRef                    = 0x61,
      COM_RegAgcThresh1                = 0x62,
      COM_RegAgcThresh2                = 0x63,
      COM_RegAgcThresh3                = 0x64,

      // 0x65-0x6f reserved

      COM_RegPll                       = 0x70

      // 0x71-0xff reserved
    } SX1276_REGS_T;

    /**
     * OpMode register (differing bitfields depending on mode)
     */
    typedef enum {
      OPMODE_Mode0                     = 0x01, // operating modes (sleep, etc)
      OPMODE_Mode1                     = 0x02,
      OPMODE_Mode2                     = 0x04,
      _OPMODE_Mode_MASK                = 7,
      _OPMODE_Mode_SHIFT               = 0,

      OPMODE_LowFrequencyModeOn        = 0x08,

      // 0x10 reserved

      OPMODE_FSK_ModulationType0       = 0x20,
      OPMODE_FSK_ModulationType1       = 0x40,
      _OPMODE_FSK_ModulationType_MASK  = 3,
      _OPMODE_FSK_ModulationType_SHIFT = 5,

      OPMODE_LOR_Reserved0x20          = 0x20,

      OPMODE_LOR_AccessSharedReg       = 0x40, // tmp sw to FSK regs

      OPMODE_LongRangeMode             = 0x80 // LoRa mode enable(1), else FSK
    } OPMODE_BITS_T;


    /**
     * Mode values
     */
    typedef enum {
      MODE_Sleep                        = 0,
      MODE_Standby                      = 1,
      MODE_FSTX                         = 2, // freq synth
      MODE_TxMode                       = 3,
      MODE_FSRX                         = 4, // freq synth

      MODE_FSK_RxMode                   = 5,
      MODE_LOR_RxContinuous             = 5, // continuous rx mode

      MODE_FSK_Reserved6                = 6,
      MODE_LOR_RxSingle                 = 6, // single packet rx mode

      MODE_FSK_Reserved7                = 7,
      MODE_LOR_CAD                      = 7 // channel activity detection
    } MODE_T;

    /**
     * FSK_ModulationType values
     */
    typedef enum {
      MODULATION_FSK                    = 0, // freq shift keying
      MODULATION_OOK                    = 1, // on/off keying
      // 2-3 reserved
    } FSK_MODULATION_TYPE_T;

    /**
     * RegPaConfig register
     */
    typedef enum {
      PACONFIG_OutputPower0              = 0x01,
      PACONFIG_OutputPower1              = 0x02,
      PACONFIG_OutputPower2              = 0x04,
      PACONFIG_OutputPower3              = 0x08,
      _PACONFIG_OutputPower_MASK         = 15,
      _PACONFIG_OutputPower_SHIFT        = 0,

      PACONFIG_MaxPower0                 = 0x10,
      PACONFIG_MaxPower1                 = 0x20,
      PACONFIG_MaxPower2                 = 0x40,
      _PACONFIG_MaxPower_MASK            = 7,
      _PACONFIG_MaxPower_SHIFT           = 4,

      PACONFIG_PaSelect                  = 0x80 // PA output pin,
                                                //  0 = 14dBm, 1 = 20dBm
    } PACONFIG_BITS_T;

    /**
     * RegPaRamp register
     */
    typedef enum {
      PARAMP_PaRamp0                     = 0x01, // rise/fall of ramp up/down
      PARAMP_PaRamp1                     = 0x02,
      PARAMP_PaRamp2                     = 0x04,
      PARAMP_PaRamp3                     = 0x08,
      _PARAMP_PaRamp_MASK                = 15,
      _PARAMP_PaRamp_SHIFT               = 0,

      // 0x10 reserved

      // LORA 0x20-0x40 reserved

      PARAMP_FSK_ModulationShaping0      = 0x20,
      PARAMP_FSK_ModulationShaping1      = 0x40,
      _PARAMP_FSK_ModulationShaping_MASK = 3,
      _PARAMP_FSK_ModulationShaping_SHIFT = 5

      // 0x80 reserved
    } PARAMP_BITS_T;

    /**
     * PARAMP_PaRamp values
     */
    typedef enum {
      PARAMP_3_4MS                       = 0, // 3.4ms
      PARAMP_2MS                         = 1,
      PARAMP_1MS                         = 2,
      PARAMP_500US                       = 3, // 500us
      PARAMP_250US                       = 4,
      PARAMP_125US                       = 5,
      PARAMP_100US                       = 6,
      PARAMP_62US                        = 7,
      PARAMP_50US                        = 8,
      PARAMP_40US                        = 9,
      PARAMP_31US                        = 10,
      PARAMP_25US                        = 11,
      PARAMP_20US                        = 12,
      PARAMP_15US                        = 13,
      PARAMP_12US                        = 14,
      PARAMP_10US                        = 15
    } PARAMP_T;

    /**
     * PARAMP_ModulationShaping values.  Note, these mean different
     * things depending on whether you are using FSK or OOK.  Hence
     * the FSK/OOK dups.  We will also name these as 'MODSHAPING_', rather
     * than the lengthy 'MODULATIONSHAPING... '
     */
    typedef enum {
      MODSHAPING_NOSHAPING              = 0,

      // FSK
      MODSHAPING_FSK_GaussianFilterBT1  = 1, // BT = 1.0
      MODSHAPING_FSK_GaussianFilterBT05 = 2, // BT = 0.5
      MODSHAPING_FSK_GaussianFilterBT03 = 3, // BT = 0.3

      // OOK
      MODSHAPING_OOK_FCutoffBitRate     = 1, // Fcutoff = BitRate
      MODSHAPING_OOK_FCutoffBitRate2    = 2  // Fcutoff = 2*BitRate

      // for OOK, 3 is reserved
    } MODSHAPING_T;

    /**
     * RegOcp register (see datasheet for OcpTrim values)
     */
    typedef enum {
      OCP_OcpTrim0                      = 0x01,
      OCP_OcpTrim1                      = 0x02,
      OCP_OcpTrim2                      = 0x04,
      OCP_OcpTrim3                      = 0x08,
      _OCP_OcpTrim_MASK                 = 15,
      _OCP_OcpTrim_SHIFT                = 0,

      OCP_OcpOn                         = 0x10

      // 0x20-0x80 reserved
    } OCP_BITS_T;

    /**
     * Lna register
     */
    typedef enum {
      LNA_LnaBoostHf0                    = 0x01,
      LNA_LnaBoostHf1                    = 0x02,
      _LNA_LnaBoostHf_MASK               = 3,
      _LNA_LnaBoostHf_SHIFT              = 0,

      // 0x04 reserved

      LNA_LnaBoostLf0                    = 0x08,
      LNA_LnaBoostLf1                    = 0x10,
      _LNA_LnaBoostLf_MASK               = 3,
      _LNA_LnaBoostLf_SHIFT              = 3,

      LNA_LnaGain0                       = 0x20,
      LNA_LnaGain1                       = 0x40,
      LNA_LnaGain2                       = 0x80,
      _LNA_LnaGain_MASK                  = 7,
      _LNA_LnaGain_SHIFT                 = 5
    } LNA_BITS_T;

    /**
     * LnaBoostHf values
     */
    typedef enum {
      LNABOOSTHF_Default                = 0,
      // 1-2 reserved
      LNABOOSTHF_BoostOn                = 3, // 150% LNA current
    } LNABOOSTHF_T;

    /**
     * LnaBoostLf values
     */
    typedef enum {
      LNABOOSTLF_Default                = 0
      // 1-3 reserved
    } LNABOOSTLF_T;

    /**
     * LnaGain values
     */
    typedef enum {
      // 0 reserved
      LNAGAIN_G1                        = 1, // max gain
      LNAGAIN_G2                        = 2,
      LNAGAIN_G3                        = 3,
      LNAGAIN_G4                        = 4,
      LNAGAIN_G5                        = 5,
      LNAGAIN_G6                        = 6  // minimum gain
      // 7 reserved
    } LNAGAIN_T;

    /**
     * FSK_RxConfig register.  See Table 24 in the data sheet for
     * the meanings of the RxTrigger values.
     */
    typedef enum {
      RXCONFIG_RxTrigger0               = 0x01,
      RXCONFIG_RxTrigger1               = 0x02,
      RXCONFIG_RxTrigger2               = 0x04,
      _RXCONFIG_RxTrigger_MASK          = 7,
      _RXCONFIG_RxTrigger_SHIFT         = 0,

      RXCONFIG_AgcAutoOn                = 0x08,
      RXCONFIG_AfcAutoOn                = 0x10,
      RXCONFIG_RestartRxWithPllLock     = 0x20,
      RXCONFIG_RestartRxWithoutPllLock  = 0x40,
      RXCONFIG_RestartRxOnCollision     = 0x80
    } RXCONFIG_BITS_T;

    /**
     * FSK_RssiConfig register
     */
    typedef enum {
      RSSICONFIG_RssiSmoothing0         = 0x01, // RSSI sampling/averaging
      RSSICONFIG_RssiSmoothing1         = 0x02,
      RSSICONFIG_RssiSmoothing2         = 0x04,
      _RSSICONFIG_RssiSmoothing_MASK    = 7,
      _RSSICONFIG_RssiSmoothing_SHIFT   = 0,

      RSSICONFIG_RssiOffset0            = 0x08, // 2's complement offset
      RSSICONFIG_RssiOffset1            = 0x10,
      RSSICONFIG_RssiOffset2            = 0x20,
      RSSICONFIG_RssiOffset3            = 0x40,
      RSSICONFIG_RssiOffset4            = 0x80,
      _RSSICONFIG_RssiOffset_MASK       = 31,
      _RSSICONFIG_RssiOffset_SHIFT      = 3
    } RSSICONFIG_BITS_T;

    /**
     * RssiSmoothing values
     */
    typedef enum {
      RSSISMOOTHING_2                   = 0, // 2 samples used
      RSSISMOOTHING_4                   = 1,
      RSSISMOOTHING_8                   = 2,
      RSSISMOOTHING_16                  = 3,
      RSSISMOOTHING_32                  = 4,
      RSSISMOOTHING_64                  = 5,
      RSSISMOOTHING_128                 = 6,
      RSSISMOOTHING_256                 = 7
    } RSSISMOOTHING_T;

    /**
     * LOR_RegIrqFlagsMask and LOR_RegIrqFlags registers
     */
    typedef enum {
      LOR_IRQFLAG_CadDetected            = 0x01,
      LOR_IRQFLAG_FhssChangeChannel      = 0x02,
      LOR_IRQFLAG_CadDone                = 0x04,
      LOR_IRQFLAG_TxDone                 = 0x08,

      LOR_IRQFLAG_ValidHeader            = 0x10,
      LOR_IRQFLAG_PayloadCrcError        = 0x20,
      LOR_IRQFLAG_RxDone                 = 0x40,
      LOR_IRQFLAG_RxTimeout              = 0x80
    } LOR_IRQFLAG_BITS_T;
    
    /**
     * FSK_RxBw register and FSK_RegAfcBw registers
     */
    typedef enum {
      RXBW_RxBwExp0                      = 0x01,
      RXBW_RxBwExp1                      = 0x02,
      RXBW_RxBwExp2                      = 0x04,
      _RXBW_RxBwExp_MASK                 = 7,
      _RXBW_RxBwExp_SHIFT                = 0,

      RXBW_RxBwMant0                     = 0x08,
      RXBW_RxBwMant1                     = 0x10,
      _RXBW_RxBwMant_MASK                = 3,
      _RXBW_RxBwMant_SHIFT               = 3,
      // 0x20-0x80 reserved
    } RXBW_BITS_T;

    /**
     * RXBW_RxBwMant values
     */
    typedef enum {
      RXBWMANT_0                         = 0,
      RXBWMANT_1                         = 1,
      RXBWMANT_2                         = 2
      // 3 reserved
    } RXBWMANT_T;

    /**
     * RXBW_RxBwExp values
     */
    typedef enum {
      RXBWEXP_1                          = 1,
      RXBWEXP_2                          = 2,
      RXBWEXP_3                          = 3,
      RXBWEXP_4                          = 4,
      RXBWEXP_5                          = 5,
      RXBWEXP_6                          = 6,
      RXBWEXP_7                          = 7
      // other values reserved
    } RXBWEXP_T;

    /**
     * FSK_OokPeak register
     */
    typedef enum {
      OOKPEAK_OokPeakThreshStep0         = 0x01,
      OOKPEAK_OokPeakThreshStep1         = 0x02,
      OOKPEAK_OokPeakThreshStep2         = 0x04,
      _OOKPEAK_OokPeakThreshStep_MASK    = 7,
      _OOKPEAK_OokPeakThreshStep_SHIFT   = 0,

      OOKPEAK_OokThreshType0             = 0x08,
      OOKPEAK_OokThreshType1             = 0x10,
      _OOKPEAK_OokThreshType_MASK        = 3,
      _OOKPEAK_OokThreshType_SHIFT       = 3,

      OOKPEAK_BitSyncOn                  = 0x20,

      // 0x40-0x80 reserved
    } OOKPEAK_BITS_T;

    /**
     * OokPeakThreshStep values
     */
    typedef enum {
      OOKPEAKTHRESHSTEP_05dB             = 0, // dec of RSSI threshold 0.5dB
      OOKPEAKTHRESHSTEP_1dB              = 1, // 1 dB
      OOKPEAKTHRESHSTEP_15dB             = 2, // 1.5 dB
      OOKPEAKTHRESHSTEP_2dB              = 3, // 2 dB
      OOKPEAKTHRESHSTEP_3dB              = 4,
      OOKPEAKTHRESHSTEP_4dB              = 5,
      OOKPEAKTHRESHSTEP_5dB              = 6,
      OOKPEAKTHRESHSTEP_6dB              = 7
    } OOKPEAKTHRESHSTEP_T;

    /**
     * OokPeakThreshType values
     */
    typedef enum {
      OOKTHRESHTYPE_FIXED                = 0,
      OOKTHRESHTYPE_PEAK                 = 1,
      OOKTHRESHTYPE_AVERAGE              = 2
      // 3 reserved
    } OOKTHRESHTYPE_T;

    /**
     * FSK_OokAvg register
     */
    typedef enum {
      OOKAVG_OokAvgThreshFilt0           = 0x01,
      OOKAVG_OokAvgThreshFilt1           = 0x02,
      _OOKAVG_OokAvgThreshFilt_MASK      = 3,
      _OOKAVG_OokAvgThreshFilt_SHIFT     = 0,
    
      OOKAVG_OokAvgOffset0               = 0x04,
      OOKAVG_OokAvgOffset1               = 0x08,
      _OOKAVG_OokAvgOffset_MASK          = 3,
      _OOKAVG_OokAvgOffset_SHIFT         = 2,

      // 0x10 reserved

      OOKAVG_OokPeakThreshDec0           = 0x20,
      OOKAVG_OokPeakThreshDec1           = 0x40,
      OOKAVG_OokPeakThreshDec2           = 0x80,
      _OOKAVG_OokPeakThreshDec_MASK      = 7,
      _OOKAVG_OokPeakThreshDec_SHIFT     = 5
    } OOKAVG_BITS_T;

    /**
     * OokAvgThreshFilt values
     */
    typedef enum {
      OOKAVGTHRESHFILT_32                = 0, // filter coedd in avg mode
      OOKAVGTHRESHFILT_8                 = 1,
      OOKAVGTHRESHFILT_4                 = 2,
      OOKAVGTHRESHFILT_2                 = 3
    } OOKAVGTHRESHFILT_T;

    /**
     * OokAvgOffset values
     */
    typedef enum {
      OOKAVGOFFSET_0                     = 0, // 0.0dB
      OOKAVGOFFSET_2                     = 1,
      OOKAVGOFFSET_4                     = 2,
      OOKAVGOFFSET_6                     = 3
    } OOKAVGOFFSET_T;

    /**
     * OokPeakThreshDec values
     */
    typedef enum {
      OOKPEAKTHRESHDEC_1_1               = 0, // once per chip
      OOKPEAKTHRESHDEC_1_2               = 1, // once every 2 chips...
      OOKPEAKTHRESHDEC_1_4               = 2,
      OOKPEAKTHRESHDEC_1_8               = 3,
      OOKPEAKTHRESHDEC_2_1               = 4, // twice per chip
      OOKPEAKTHRESHDEC_4_1               = 5, // 4 times every chip...
      OOKPEAKTHRESHDEC_8_1               = 6,
      OOKPEAKTHRESHDEC_16_1              = 7
    } OOKPEAKTHRESHDEC_T;

    /**
     * LOR_ModemStat register
     */
    typedef enum {
      MODEMSTAT_SignalDetected           = 0x01,
      MODEMSTAT_SignalSynchronized       = 0x02,
      MODEMSTAT_RxOngoing                = 0x04,
      MODEMSTAT_HeaderInfoValid          = 0x08,
      MODEMSTAT_ModemClear               = 0x10,
      
      MODEMSTAT_RxCodingRate0            = 0x20,
      MODEMSTAT_RxCodingRate1            = 0x40,
      MODEMSTAT_RxCodingRate2            = 0x80,
      _MODEMSTAT_RxCodingRate_MASK       = 7,
      _MODEMSTAT_RxCodingRate_SHIFT      = 5
    } MODEMSTAT_BITS_T;

    /**
     * FSK_RegAfcFei register
     */
    typedef enum {
      AFCFEI_AfcAutoClearOn              = 0x01,
      AFCFEI_AfcClear                    = 0x02,

      // 0x04-0x08 reserved

      AFCFEI_AgcStart                    = 0x10

      // 0x20-0x80 reserved
    } AFCFEI_BITS_T;

    /**
     * LOR_HopChannel register
     */
    typedef enum {
      HOPCHANNEL_FhssPresentChannel0     = 0x01, // current freq hopping channel
      HOPCHANNEL_FhssPresentChannel1     = 0x02,
      HOPCHANNEL_FhssPresentChannel2     = 0x04,
      HOPCHANNEL_FhssPresentChannel3     = 0x08,
      HOPCHANNEL_FhssPresentChannel4     = 0x10,
      HOPCHANNEL_FhssPresentChannel5     = 0x20,
      _HOPCHANNEL_FhssPresentChannel_MASK = 63,
      _HOPCHANNEL_FhssPresentChannel_SHIFT = 0,

      HOPCHANNEL_CrcOnPayload            = 0x40,
      HOPCHANNEL_PllTimeout              = 0x80
    } HOPCHANNEL_BITS_T;

    /**
     * LOR_ModemConfig1 register
     */
    typedef enum {
      MODEMCONFIG1_ImplicitHeaderModeOn  = 0x01,

      MODEMCONFIG1_CodingRate0           = 0x02,
      MODEMCONFIG1_CodingRate1           = 0x04,
      MODEMCONFIG1_CodingRate2           = 0x08,
      _MODEMCONFIG1_CodingRate_MASK      = 7,
      _MODEMCONFIG1_CodingRate_SHIFT     = 0,

      MODEMCONFIG1_Bw0                   = 0x10,
      MODEMCONFIG1_Bw1                   = 0x20,
      MODEMCONFIG1_Bw2                   = 0x40,
      MODEMCONFIG1_Bw3                   = 0x80,
      _MODEMCONFIG1_Bw_MASK              = 15,
      _MODEMCONFIG1_Bw_SHIFT             = 4
    } MODEMCONFIG1_BITS_T;

    /**
     * CodingRate values
     */
    typedef enum {
      CODINGRATE_4_5                     = 1, // Error coding rate 4/5
      CODINGRATE_4_6                     = 2,
      CODINGRATE_4_7                     = 3,
      CODINGRATE_4_8                     = 4
    } CODINGRATE_T;

    /**
     * Bw values
     */
    typedef enum {
      BW_7_8                             = 0, // 7.8Khz
      BW_10_4                            = 1,
      BW_15_6                            = 2,
      BW_20_8                            = 3,
      BW_31_25                           = 4,
      BW_41_7                            = 5,
      BW_62_5                            = 6,
      BW_125                             = 7,
      BW_250                             = 8,
      BW_500                             = 9

      // BW250 and BW500 not supported in lower band (169Mhz)
    } BW_T;

    /**
     * LOR_ModemConfig2 register
     */
    typedef enum {
      MODEMCONFIG2_SymbTimeoutMsb0       = 0x01,
      MODEMCONFIG2_SymbTimeoutMsb1       = 0x02,
      _MODEMCONFIG2_SymbTimeoutMsb_MASK  = 3,
      _MODEMCONFIG2_SymbTimeoutMsb_SHIFT = 0,

      MODEMCONFIG2_RxPayloadCrcOn        = 0x04,

      MODEMCONFIG2_TxContinuousMode      = 0x08,

      MODEMCONFIG2_SpreadingFactor0      = 0x10,
      MODEMCONFIG2_SpreadingFactor1      = 0x20,
      MODEMCONFIG2_SpreadingFactor2      = 0x40,
      MODEMCONFIG2_SpreadingFactor3      = 0x80,
      _MODEMCONFIG2_SpreadingFactor_MASK = 15,
      _MODEMCONFIG2_SpreadingFactor_SHIFT = 4,
    } MODEMCONFIG2_BITS_T;

    /**
     * SpreadingFactor values (expressed as a base-2 logarithm)
     */
    typedef enum {
      SPREADINGFACTOR_64                 = 6, // 64 chips/symbol
      SPREADINGFACTOR_128                = 7,
      SPREADINGFACTOR_256                = 8,
      SPREADINGFACTOR_512                = 9,
      SPREADINGFACTOR_1024               = 10,
      SPREADINGFACTOR_2048               = 11,
      SPREADINGFACTOR_4096               = 12

      // other values reserved
    } SPREADINGFACTOR_T;

    /**
     * FSK_PreableDetect register
     */
    typedef enum {
      PREABLEDETECT_PreambleDetectorTol0 = 0x01,
      PREABLEDETECT_PreambleDetectorTol1 = 0x02,
      PREABLEDETECT_PreambleDetectorTol2 = 0x04,
      PREABLEDETECT_PreambleDetectorTol3 = 0x08,
      PREABLEDETECT_PreambleDetectorTol4 = 0x10,
      _PREABLEDETECT_PreambleDetectorTol4_MASK = 31,
      _PREABLEDETECT_PreambleDetectorTol4_SHIFT = 0,

      PREABLEDETECT_PreambleDetectorSize0 = 0x20,
      PREABLEDETECT_PreambleDetectorSize1 = 0x40,
      _PREABLEDETECT_PreambleDetectorSize_MASK = 3,
      _PREABLEDETECT_PreambleDetectorSize_SHIFT = 5,

      PREABLEDETECT_PreambleDetectorOn   = 0x80
    } PREAMBLEDETECT_BITS_T;

    /**
     * PreambleDetectorSize values
     */
    typedef enum {
      PREAMBLEDETECTORSIZE_1             = 0, // 1 byte
      PREAMBLEDETECTORSIZE_2             = 1,
      PREAMBLEDETECTORSIZE_3             = 2

      // other values reserved
    } PREAMBLEDETECTORSIZE_T;

    /**
     * FSK_Osc register
     */
    typedef enum {
      OSC_ClkOut0                        = 0x01, // clk output freq
      OSC_ClkOut1                        = 0x02,
      OSC_ClkOut2                        = 0x04,
      _OSC_ClkOut_MASK                   = 7,
      _OSC_ClkOut_SHIFT                  = 0,

      OSC_RcCalStart                     = 0x08

      // other bits reserved
    } OSC_BITS_T;

    /**
     * ClkOut values
     */
    typedef enum {
      CLKOUT_1                           = 0, // FXOSC
      CLKOUT_2                           = 1, // FXOSC / 2 ...
      CLKOUT_4                           = 2,
      CLKOUT_8                           = 3,
      CLKOUT_16                          = 4,
      CLKOUT_32                          = 5,
      CLKOUT_RC                          = 6, // RC, (automatically enabled)
      CLKOUT_OFF                         = 7  // clkout off
    } CLKOUT_T;

    /**
     * LOR_ModemConfig3 register
     */
    typedef enum {
      // 0x01-0x02 reserved

      MODEMCONFIG3_AgcAutoOn             = 0x04,
      MODEMCONFIG3_LowDataRateOptimize   = 0x08  // req. for SF11 and SF12 and
                                                 // BW125

      // 0x10-0x80 reserved
    } MODEMCONFIG3_BITS_T;

    /**
     * FSK_SyncConfig register
     */
    typedef enum {
      SYNCCONFIG_SyncSize0               = 0x01,
      SYNCCONFIG_SyncSize1               = 0x02,
      SYNCCONFIG_SyncSize2               = 0x04,
      _SYNCCONFIG_SyncSize_MASK          = 7,
      _SYNCCONFIG_SyncSize_SHIFT         = 0,

      // 0x08 reserved

      SYNCCONFIG_SyncOn                  = 0x10,
      SYNCCONFIG_PreamblePolarity        = 0x20,

      SYNCCONFIG_AutoRestartMode0        = 0x40,
      SYNCCONFIG_AutoRestartMode1        = 0x80,
      _SYNCCONFIG_AutoRestartMode_MASK   = 3,
      _SYNCCONFIG_AutoRestartMode_SHIFT  = 6,
    } SYNCCONFIG_BITS_T;

    /**
     * AutoRestartMode values
     */
    typedef enum {
      AUTORESTARTMODE_OFF                = 0,
      AUTORESTARTMODE_ON_NOPLL           = 1, // don't wait for PLL resync
      AUTORESTARTMODE_ON_PLL             = 2  // wait for PLL resync
      // other values reserved
    } AUTORESTARTMODE_T;

    /**
     * LOR_FeiMsb register (4 bit MSB of Fei value)
     */
    typedef enum {
      FEIMSB_FreqError0                  = 0x01,
      FEIMSB_FreqError1                  = 0x02,
      FEIMSB_FreqError2                  = 0x04,
      FEIMSB_FreqError3                  = 0x08,
      _FEIMSB_FreqError_MASK             = 15,
      _FEIMSB_FreqError_SHIFT            = 0

      // 0x10-0x80 reserved
    } FEIMSB_BITS_T;

    /**
     * FSK_PacketConfig1 register
     */
    typedef enum {
      PACKETCONFIG1_CrcWhiteningType     = 0x01,

      PACKETCONFIG1_AddressFiltering0    = 0x02,
      PACKETCONFIG1_AddressFiltering1    = 0x04,
      _PACKETCONFIG1_AddressFiltering_MASK = 3,
      _PACKETCONFIG1_AddressFiltering_SHIFT = 1,

      PACKETCONFIG1_CrcAutoClearOff      = 0x08,
      PACKETCONFIG1_CrcOn                = 0x10,

      PACKETCONFIG1_DcFree0              = 0x20,
      PACKETCONFIG1_DcFree1              = 0x40,
      _PACKETCONFIG1_DcFree_MASK         = 3,
      _PACKETCONFIG1_DcFree_SHIFT        = 5,

      PACKETCONFIG1_PacketFormat         = 0x80 // fixed(0) or variable(1)
    } PACKETCONFIG1_BITS_T;

    /**
     * AddressFiltering values
     */
    typedef enum {
      ADDRESSFILTERING_NONE              = 0,
      ADDRESSFILTERING_NODE              = 1, // must match node addr
      ADDRESSFILTERING_NODE_BROADCAST    = 2, // match node or broadcast
    } ADDRESSFILTERING_T;

    /**
     * DcFree values (DC-free encoding/decoding schemes)
     */
    typedef enum {
      DCFREE_NONE                        = 0,
      DCFREE_MANCHESTER                  = 1,
      DCFREE_WHITENING                   = 2
      // other values reserved
    } DCFREE_T;

    /**
     * FSK_PacketConfig2 register
     */
    typedef enum {
      PACKETCONFIG2_PayloadLengthMsb0    = 0x01,
      PACKETCONFIG2_PayloadLengthMsb1    = 0x02,
      PACKETCONFIG2_PayloadLengthMsb2    = 0x04,
      _PACKETCONFIG2_PayloadLengthMsb_MASK = 7,
      _PACKETCONFIG2_PayloadLengthMsb_SHIFT = 0,

      PACKETCONFIG2_BeaconOn             = 0x08,
 
      // 0x10 reserved (linked to io-homecontrol compat mode (?))

      PACKETCONFIG2_IoHomeOn             = 0x20,
      PACKETCONFIG2_DataMode             = 0x40, // continuous(0), packet(1)

      // 0x80 reserved
    } PACKETCONFIG2_BITS_T;

    /**
     * LOR_DetectOptimize register
     */
    typedef enum {
      DETECTOPTIMIZE_DetectionOptimize0  = 0x01,
      DETECTOPTIMIZE_DetectionOptimize1  = 0x02,
      DETECTOPTIMIZE_DetectionOptimize2  = 0x04,
      _DETECTOPTIMIZE_DetectionOptimize_MASK = 7,
      _DETECTOPTIMIZE_DetectionOptimize_SHIFT = 0

      // 0x08-0x80 reserved
    } DETECTOPTIMIZE_BITS_T;

    /**
     * DetectionOptimize values
     */
    typedef enum {
      DETECTIONOPTIMIZE_SF7_SF12         = 3,
      DETECTIONOPTIMIZE_SF6              = 5

      // other values reserved
    } DETECTIONOPTIMIZE_T;

    /**
     * LOR_InvertIQ register
     */
    typedef enum {
      INVERTIQ_InvertIQTxOff             = 0x01, // invert LoRa I & Q signals
                                                // UNDOCUMENTED

      // 0x01-0x20 reserved

      INVERTIQ_InvertIQRx                = 0x40 // invert LoRa I & Q signals

      // 0x80 reserved
    } INVERTIQ_BITS_T;

    /**
     * FSK_FifoThresh register
     */
    typedef enum {
      FIFOTHRESH_FifoThreshold0          = 0x01,
      FIFOTHRESH_FifoThreshold1          = 0x02,
      FIFOTHRESH_FifoThreshold2          = 0x04,
      FIFOTHRESH_FifoThreshold3          = 0x08,
      FIFOTHRESH_FifoThreshold4          = 0x10,
      FIFOTHRESH_FifoThreshold5          = 0x20,
      _FIFOTHRESH_FifoThreshold_MASK     = 63,
      _FIFOTHRESH_FifoThreshold_SHIFT    = 0,

      // 0x40 reserved

      FIFOTHRESH_TxStartCondition        = 0x80
    } FIFOTHRESH_BITS_T;

    /**
     * FSK_SeqConfig1 register
     */
    typedef enum {
      SEQCONFIG1_FromTransit             = 0x01,
      SEQCONFIG1_FromIdle                = 0x02,
      SEQCONFIG1_LowPowerSelection       = 0x04,

      SEQCONFIG1_FromStart0              = 0x08,
      SEQCONFIG1_FromStart1              = 0x10,
      _SEQCONFIG1_FromStart_MASK         = 3,
      _SEQCONFIG1_FromStart_SHIFT        = 3,

      SEQCONFIG1_IdleMode                = 0x20,
      SEQCONFIG1_SequencerStop           = 0x40,
      SEQCONFIG1_SequencerStart          = 0x80
    } SEQCONFIG1_BITS_T;

    /**
     * FromStart values
     */
    typedef enum {
      FROMSTART_ToLowPowerSelection        = 0,
      FROMSTART_ToReceiveState             = 1,
      FROMSTART_ToTransmitState            = 2,
      FROMSTART_ToTransmitStateOnFifoLevel = 3
    } FROMSTART_T;

    /**
     * FSK_SeqConfig2 register
     */
    typedef enum {
      SEQCONFIG2_FromPacketReceived0       = 0x01,
      SEQCONFIG2_FromPacketReceived1       = 0x02,
      SEQCONFIG2_FromPacketReceived2       = 0x04,
      _SEQCONFIG2_FromPacketReceived_MASK  = 7,
      _SEQCONFIG2_FromPacketReceived_SHIFT = 0,

      SEQCONFIG2_FromRxTimeout0            = 0x08,
      SEQCONFIG2_FromRxTimeout1            = 0x10,
      _SEQCONFIG2_FromRxTimeout_MASK       = 3,
      _SEQCONFIG2_FromRxTimeout_SHIFT      = 3,
      
      SEQCONFIG2_FromReceive0              = 0x20,
      SEQCONFIG2_FromReceive1              = 0x40,
      SEQCONFIG2_FromReceive2              = 0x80,
      _SEQCONFIG2_FromReceive_MASK         = 3,
      _SEQCONFIG2_FromReceive_SHIFT        = 5
    } SEQCONFIG2_BITS_T;
      
    /**
     * FromPacketReceived values
     */
    typedef enum {
      FROMPACKETRECEIVED_ToSequencerOff    = 0,
      FROMPACKETRECEIVED_ToTransmitStateOnFifoEmpty = 1,
      FROMPACKETRECEIVED_ToLowPowerSelection = 2,
      FROMPACKETRECEIVED_ToReceiveViaFS    = 3, // if freq was changed
      FROMPACKETRECEIVED_ToReceive         = 4  // if freq was not changed

      // other values reserved
    } FROMPACKETRECEIVED_T;

    /**
     * FromRxTimeout values
     */
    typedef enum {
      FROMRXTIMEOUT_ToReceiveViaReceiveStart = 0,
      FROMRXTIMEOUT_ToTransmitState      = 1,
      FROMRXTIMEOUT_ToLowPowerSelection  = 2,
      FROMRXTIMEOUT_ToSequencerOffState  = 3
    } FROMRXTIMEOUT_T;

    /**
     * FromReceive values
     */
    typedef enum {
      FROMRECEIVE_ToPcketReceived        = 1,
      FROMRECEIVE_ToLowPowerSelection    = 2,
      FROMRECEIVE_ToPacketReceived       = 3,
      FROMRECEIVE_ToSequencerOffOnRSSI   = 4, // RSSI interrupt
      FROMRECEIVE_ToSequencerOffOnSync   = 5, // SyncAddr interrupt
      FROMRECEIVE_ToSequencerOffOnPreambleDetect = 6, // PreambleDetect intr
      // other values reserved
    } FROMRECEIVE_T;

    /**
     * FSK_TimerResol register
     */
    typedef enum {
      TIMERRESOL_Timer2Resolution0       = 0x01,
      TIMERRESOL_Timer2Resolution1       = 0x02,
      _TIMERRESOL_Timer2Resolution_MASK  = 3,
      _TIMERRESOL_Timer2Resolution_SHIFT = 0,

      TIMERRESOL_Timer1Resolution0       = 0x04,
      TIMERRESOL_Timer1Resolution1       = 0x08,
      _TIMERRESOL_Timer1Resolution_MASK  = 3,
      _TIMERRESOL_Timer1Resolution_SHIFT = 2

      // 0x10-0x80 reserved
    } TIMERRESOL_BITS_T;

    /**
     * Timer1/Timer2Resolution values
     */
    typedef enum {
      TIMERRESOLUTION_DISABLED           = 0,
      TIMERRESOLUTION_64us               = 1, // 64us
      TIMERRESOLUTION_4_1ms              = 2, // 4.1ms
      TIMERRESOLUTION_262ms              = 3  // 262ms
    } TIMERRESOLUTION_T;

    /**
     * FSK_ImageCal register
     */
    typedef enum {
      IMAGECAL_TempMonitorOff            = 0x01,

      IMAGECAL_TempThreshold0            = 0x02,
      IMAGECAL_TempThreshold1            = 0x04,
      _IMAGECAL_TempThreshold_MASK       = 3,
      _IMAGECAL_TempThreshold_SHIFT      = 1,

      IMAGECAL_TenpChange                = 0x08,

      // 0x10 reserved

      IMAGECAL_ImageCalRunning           = 0x20,
      IMAGECAL_ImageCalStart             = 0x40,
      IMAGECAL_AutoImageCalOn            = 0x80
    } IMAGECAL_BITS_T;

    /**
     * TempThreshold values
     */
    typedef enum {
      TEMPTHRESHOLD_5C                   = 0, // temp change to trigger new I/Q
      TEMPTHRESHOLD_10C                  = 1, //  calibration
      TEMPTHRESHOLD_15C                  = 2,
      TEMPTHRESHOLD_20C                  = 3
    } TEMPTHRESHOLD_T;

    /**
     * FSK_LowBat register
     */
    typedef enum {
      LOWBAT_LowBatTrim0                 = 0x01,
      LOWBAT_LowBatTrim1                 = 0x02,
      LOWBAT_LowBatTrim2                 = 0x04,
      _LOWBAT_LowBatTrim_MASK            = 7,
      _LOWBAT_LowBatTrim_SHIFT           = 0,

      LOWBAT_LowBatOn                    = 0x08

      // 0x10-0z80 reserved
    } LOWBAT_BITS_T;

    /**
     * LowBatTrim values
     */
    typedef enum {
      LOWBATTRIM_1_695                   = 0, // 1.695v
      LOWBATTRIM_1_764                   = 1,
      LOWBATTRIM_1_835                   = 2,
      LOWBATTRIM_1_905                   = 3,
      LOWBATTRIM_1_976                   = 4,
      LOWBATTRIM_2_045                   = 5,
      LOWBATTRIM_2_116                   = 6,
      LOWBATTRIM_2_185                   = 7
    } LOWBATTRIM_T;

    /**
     * FSK_IrqFlags1 register
     */
    typedef enum {
      IRQFLAGS1_SyncAddressMatch         = 0x01,
      IRQFLAGS1_PreambleDetect           = 0x02,
      IRQFLAGS1_Timeout                  = 0x04,
      IRQFLAGS1_Rssi                     = 0x08,
      IRQFLAGS1_PllLock                  = 0x10,
      IRQFLAGS1_TxReady                  = 0x20,
      IRQFLAGS1_RxReady                  = 0x40,
      IRQFLAGS1_ModeReady                = 0x80
    } IRQFLAGS1_BITS_T;

    /**
     * FSK_IrqFlags2 register
     */
    typedef enum {
      IRQFLAGS2_LowBat                   = 0x01,
      IRQFLAGS2_CrcOk                    = 0x02,
      IRQFLAGS2_PayloadReady             = 0x04,
      IRQFLAGS2_PacketSent               = 0x08,
      IRQFLAGS2_FifoOverrun              = 0x10,
      IRQFLAGS2_FifoLevel                = 0x20,
      IRQFLAGS2_FifoEmpty                = 0x40,
      IRQFLAGS2_FifoFull                 = 0x80
    } IRQFLAGS2_BITS_T;

    /**
     * COM_DioMapping1 register. See Tables 18, 29, and 30 in the
     * datasheet for the different mappings depending on mode.
     */
    typedef enum {
      DOIMAPPING1_Dio3Mapping0           = 0x01,
      DOIMAPPING1_Dio3Mapping1           = 0x02,
      DOIMAPPING1_Dio3Mapping_MASK       = 3,
      DOIMAPPING1_Dio3Mapping_SHIFT      = 0,

      DOIMAPPING1_Dio2Mapping0           = 0x04,
      DOIMAPPING1_Dio2Mapping1           = 0x08,
      DOIMAPPING1_Dio2Mapping_MASK       = 3,
      DOIMAPPING1_Dio2Mapping_SHIFT      = 2,

      DOIMAPPING1_Dio1Mapping0           = 0x10,
      DOIMAPPING1_Dio1Mapping1           = 0x20,
      DOIMAPPING1_Dio1Mapping_MASK       = 3,
      DOIMAPPING1_Dio1Mapping_SHIFT      = 4,

      DOIMAPPING1_Dio0Mapping0           = 0x40,
      DOIMAPPING1_Dio0Mapping1           = 0x80,
      DOIMAPPING1_Dio0Mapping_MASK       = 3,
      DOIMAPPING1_Dio0Mapping_SHIFT      = 6,
    } DIOMAPPING1_BITS_T;


    /**
     * COM_DioMapping2 register. See Tables 18, 29, and 30 in the
     * datasheet for the different mappings depending on mode.
     */
    typedef enum {
      DOIMAPPING2_MapPreambleDetect      = 0x01, // rssi intr(0), preambledet(1)

      // 0x02-0x08 reserved

      DOIMAPPING2_Dio5Mapping0           = 0x10,
      DOIMAPPING2_Dio5Mapping1           = 0x20,
      DOIMAPPING2_Dio5Mapping_MASK       = 3,
      DOIMAPPING2_Dio5Mapping_SHIFT      = 4,

      DOIMAPPING2_Dio4Mapping0           = 0x40,
      DOIMAPPING2_Dio4Mapping1           = 0x80,
      DOIMAPPING2_Dio4Mapping_MASK       = 3,
      DOIMAPPING2_Dio4Mapping_SHIFT      = 6,
    } DIOMAPPING2_BITS_T;

    /**
     * DioXMapping values
     *
     * These differ depending on LoRa, FSK packet, and FSK continous
     * modes.  See Tables 29, 30 (FSK), and 18 (LoRa) in the datasheet
     * for details.
     */
    typedef enum {
      DIOMAPPING_00                      = 0, 
      DIOMAPPING_01                      = 1,
      DIOMAPPING_10                      = 2,
      DIOMAPPING_11                      = 3
    } DIOMAPPING_T;


    /**
     * LOR_PllHop (or FSK_PllHop depending on who you believe) register
     */
    typedef enum {
      // 0x01-0x40 reserved

      PLLHOP_FastHopOn                   = 0x80
    } PLLHOP_BITS_T;

    /**
     * COM_Tcxo register
     */
    typedef enum {
      // 0x01-0x08 reserved

      TCXO_TcxoOn                        = 0x10

      // 0x20-0x80 reserved
    } TCXO_BITS_T;

    /**
     * COM_PaDac register
     */
    typedef enum {
      PADAC_PaDac0                       = 0x01,
      PADAC_PaDac1                       = 0x02,
      PADAC_PaDac2                       = 0x04,
      _PADAC_PaDac_MASK                  = 7,
      _PADAC_PaDac_SHIFT                 = 0

      // 0x08-0x80 reserved
    } PADAC_BITS_T;

    /**
     * PaDac values
     */
    typedef enum {
      PADAC_DEFAULT                      = 4, 
      PADAC_BOOST                        = 7 // +20dBm on PA_BOOST when
                                            // OuputPower = 1111
      // other values reserved
    } PADAC_T;

    /**
     * FSK_BitRateFrac register
     */
    typedef enum {
      BITRATEFRAC_BitRateFrac0           = 0x01,
      BITRATEFRAC_BitRateFrac1           = 0x02,
      BITRATEFRAC_BitRateFrac2           = 0x04,
      BITRATEFRAC_BitRateFrac3           = 0x08,
      _BITRATEFRAC_BitRateFrac_MASK      = 15,
      _BITRATEFRAC_BitRateFrac_SHIFT     = 0

      // 0x10-0x80 reserved
    } BITRATEFRAC_BITS_T;

    /**
     * COM_AgcRef register
     *
     * These registers have 2 sets of values depending on whether
     * LowFrequencyModeOn is set or unset.
     */
    typedef enum {
      AGCREF_AgcReferenceLevel0          = 0x01,
      AGCREF_AgcReferenceLevel1          = 0x02,
      AGCREF_AgcReferenceLevel2          = 0x04,
      AGCREF_AgcReferenceLevel3          = 0x08,
      AGCREF_AgcReferenceLevel4          = 0x10,
      AGCREF_AgcReferenceLevel5          = 0x20,
      _AGCREF_AgcReferenceLevel_MASK     = 63,
      _AGCREF_AgcReferenceLevel_SHIFT    = 0

      // 0x40-0x80 reserved
    } ACFREF_BITS_T;

    /**
     * COM_AgcThresh1 register
     *
     * These registers have 2 sets of values depending on whether
     * LowFrequencyModeOn is set or unset.
     */
    typedef enum {
      AGCTHRESH1_AcgStep10               = 0x01,
      AGCTHRESH1_AcgStep11               = 0x02,
      AGCTHRESH1_AcgStep12               = 0x04,
      AGCTHRESH1_AcgStep13               = 0x08,
      _AGCTHRESH1_AcgStep1_MASK          = 15,
      _AGCTHRESH1_AcgStep1_SHIFT         = 0,

      // 0x10-0x80 reserved
    } ACGTHRESH1_BITS_T;

    /**
     * COM_AgcThresh2 register
     *
     * These registers have 2 sets of values depending on whether
     * LowFrequencyModeOn is set or unset.
     */
    typedef enum {
      AGCTHRESH2_AcgStep30               = 0x01,
      AGCTHRESH2_AcgStep31               = 0x02,
      AGCTHRESH2_AcgStep32               = 0x04,
      AGCTHRESH2_AcgStep33               = 0x08,
      _AGCTHRESH2_AcgStep3_MASK          = 15,
      _AGCTHRESH2_AcgStep3_SHIFT         = 0,

      AGCTHRESH2_AcgStep20               = 0x10,
      AGCTHRESH2_AcgStep21               = 0x20,
      AGCTHRESH2_AcgStep22               = 0x40,
      AGCTHRESH2_AcgStep23               = 0x80,
      _AGCTHRESH2_AcgStep2_MASK          = 15,
      _AGCTHRESH2_AcgStep2_SHIFT         = 4
    } ACGTHRESH2_BITS_T;

    /**
     * LOR_RegDetectionThreshold values
     */
    typedef enum {
      LOR_DetectionThreshold_SF7_SF12    = 0x0a,
      LOR_DetectionThreshold_SF6         = 0x0c
    } LOR_DETECTIONTHRESHOLD_T;

    /**
     * COM_AgcThresh3 register
     *
     * These registers have 2 sets of values depending on whether
     * LowFrequencyModeOn is set or unset.
     */
    typedef enum {
      AGCTHRESH3_AcgStep50               = 0x01,
      AGCTHRESH3_AcgStep51               = 0x02,
      AGCTHRESH3_AcgStep52               = 0x04,
      AGCTHRESH3_AcgStep53               = 0x08,
      _AGCTHRESH3_AcgStep5_MASK          = 15,
      _AGCTHRESH3_AcgStep5_SHIFT         = 0,

      AGCTHRESH3_AcgStep40               = 0x10,
      AGCTHRESH3_AcgStep41               = 0x20,
      AGCTHRESH3_AcgStep42               = 0x40,
      AGCTHRESH3_AcgStep43               = 0x80,
      _AGCTHRESH3_AcgStep4_MASK          = 15,
      _AGCTHRESH3_AcgStep4_SHIFT         = 4
    } ACGTHRESH3_BITS_T;


    /**
     * SX1276 constructor
     *
     * Since this is a shield, you will not have much choice as to
     * what pins are used.
     *
     * @param chipRev chip revision, default is 0x12
     * @param bus spi bus to use
     * @param cs GPIO pin to use as SPI Chip Select
     * @param resetPin GPIO pin to use as reset (A0=GPIO14)
     * @param dio0 GPIO pin to use as reset DIO0 intr
     * @param dio1 GPIO pin to use as reset DIO1 intr
     * @param dio2 GPIO pin to use as reset DIO2 intr
     * @param dio3 GPIO pin to use as reset DIO3 intr
     * @param dio4 GPIO pin to use as reset DIO4 intr
     * @param dio5 GPIO pin to use as reset DIO5 intr
     */
    SX1276(uint8_t chipRev=chipRevision, int bus=1, int cs=10, int resetPin=14,
           int dio0=2, int dio1=3, int dio2=4, int dio3=5, int dio4=17, 
           int dio5=9);

    /**
     * SX1276 Destructor
     */
    ~SX1276();

    /**
     * read a register
     *
     * @param reg the register to read
     * @return the value of the register
     */
    uint8_t readReg(uint8_t reg);

    /**
     * write to a register
     *
     * @param reg the register to write to
     * @param val the value to write
     * @return true if successful, false otherwise
     */
    bool writeReg(uint8_t reg, uint8_t val);

    /**
     * return the chip revision
     *
     * @return the chip revision (usually 0x12)
     */
    uint8_t getChipVersion();

    /**
     * reset the modem
     */
    void reset();

    /**
     * read the FIFO into a buffer
     *
     * @param buffer The buffer to read data into
     * @param len The length of the buffer
     */
    void readFifo(uint8_t *buffer, int len);

    /**
     * write a buffer into the FIFO
     *
     * @param buffer The buffer containing the data to write
     * @param len The length of the buffer
     */
    void writeFifo(uint8_t *buffer, int len);

    /**
     * Set the frequency to transmit and receive on
     *
     * @param freq The frequency to set
     */
    void setChannel(uint32_t freq);

    /**
     * Set the operating mode
     *
     * @param opMode One of the MODE_T values
     */
    void setOpMode(MODE_T opMode);

    /**
     * Set the modem to access.  This can be either the LORA or
     * KSK/OOK modem.
     *
     * @param modem One of the MODEM_T values
     */
    void setModem(RADIO_MODEM_T modem);

    /**
     * Place the SX1276 into sleep mode
     */
    void setSleep();

    /**
     * Place the SX1276 into standby mode
     */
    void setStandby();

    /**
     * Return the current Received Signal Strength Indicator for the
     * given modem
     *
     * @param modem One of the MODEM_T values
     */
    int16_t getRSSI(RADIO_MODEM_T modem);

    /**
     * Check to see if a given channel is free by comparing the RSSI
     * to the supplied threshold.
     *
     * @param modem One of the MODEM_T values
     * @param freq The channel to check
     * @param rssiThresh The RSSI threshold, over which the channel
     * os considerd in use.
     */
    bool isChannelFree(RADIO_MODEM_T modem, uint32_t freq, int16_t rssiThresh);

    /**
     * Send the supplied string.  This writes the string into the FIFO
     * and places the modem in transmit mode (via setTx()).  This is a
     * wrapper around send().
     *
     * @param buffer The buffer to send
     * @param timeout The timeout in milliseconds
     * @return one of the RADIO_EVENT_T values
     */
    RADIO_EVENT_T sendStr(std::string buffer, int timeout);

    /**
     * Send the supplied buffer.  The writes the buffer into the FIFO
     * and places the modem in transmit mode (via setTx()).
     *
     * @param buffer The buffer to send
     * @param size The size of the buffer
     * @param timeout The timeout in milliseconds
     * @return one of the RADIO_EVENT_T values
     */
    RADIO_EVENT_T send(uint8_t *buffer, uint8_t size, int timeout);

    /**
     * Set the receive configuration for a modem.  It is important
     * that both the receive and transmit configurations match in order
     * for communication to work between two radios.
     *
     * @param modem One of the MODEM_T values

     * @param bandwidth The bandwidth to use.  Valid values are
     *                          FSK : >= 2600 and <= 250000 Hz
     *                          LoRa: [125 kHz, 250 kHz, 500 kHz] 
     * @param datarate Sets the Datarate
     *                          FSK : 600..300000 bits/s
     *                          LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
     *                                10: 1024, 11: 2048, 12: 4096  chips]
     * @param coderate Sets the coding rate (LoRa only)
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] 
     * @param bandwidthAfc Sets the AFC Bandwidth (FSK only) 
     *                          FSK : >= 2600 and <= 250000 Hz
     *                          LoRa: N/A ( set to 0 ) 
     * @param preambleLen Sets the Preamble length
     *                          FSK : Number of bytes 
     *                          LoRa: Length in symbols (the hardware adds 
     *                                4 more symbols)
     * @param symbTimeout Sets the RxSingle timeout value (LoRa only) 
     *                          FSK : N/A ( set to 0 ) 
     *                          LoRa: timeout in symbols
     * @param fixLen Fixed length packets [false: variable, true: fixed]
     * @param payloadLen Sets payload length when fixed length is used
     * @param crcOn Enables/Disables the CRC [false: OFF, true: ON]
     * @param freqHopOn Enables disables the intra-packet frequency hopping
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: [false: OFF, true: ON]
     * @param hopPeriod Number of symbols bewteen each hop
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: Number of symbols
     * @param  iqInverted Inverts IQ signals (LoRa only)
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: [false: not inverted, true: inverted]
     * @param  rxContinuous Sets the reception in continuous mode
     *                          [false: single mode, true: continuous mode]
     */
    void setRxConfig(RADIO_MODEM_T modem, uint32_t bandwidth,
                     uint32_t datarate, uint8_t coderate,
                     uint32_t bandwidthAfc, uint16_t preambleLen,
                     uint16_t symbTimeout, bool fixLen,
                     uint8_t payloadLen,
                     bool crcOn, bool freqHopOn, uint8_t hopPeriod,
                     bool iqInverted, bool rxContinuous);

    /**
     * Set the transmit configuration for a modem.  It is important
     * that both the receive and transmit configurations match in order
     * for communication to work between two radios.
     *
     * @param modem One of the MODEM_T values
     * @param power Sets the output power [dBm]
     * @param fdev Sets the frequency deviation (FSK only)
     *                          FSK : [Hz]
     *                          LoRa: 0
     * @param bandwidth Sets the bandwidth (LoRa only)
     *                          FSK : 0
     *                          LoRa: [125 kHz, 250 kHz,
     *                                 or 500 kHz] 
     * @param datarate Sets the Datarate
     *                          FSK : 600..300000 bits/s
     *                          LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
     *                                10: 1024, 11: 2048, 12: 4096  chips]
     * @param coderate Sets the coding rate (LoRa only)
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] 
     * @param preambleLen Sets the preamble length
     *                          FSK : Number of bytes 
     *                          LoRa: Length in symbols (the hardware adds 
     *                                4 more symbols)
     * @param fixLen Fixed length packets [false: variable, true: fixed]
     * @param crcOn Enables disables the CRC [false: OFF, true: ON]
     * @param freqHopOn Enables disables the intra-packet frequency hopping
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: [false: OFF, true: ON]
     * @param hopPeriod Number of symbols bewteen each hop
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: Number of symbols
     * @param iqInverted Inverts IQ signals (LoRa only)
     *                          FSK : N/A ( set to 0 )
     *                          LoRa: [false: not inverted, true: inverted]
     */
    void setTxConfig(RADIO_MODEM_T modem, int8_t power, uint32_t fdev, 
                     uint32_t bandwidth, uint32_t datarate,
                     uint8_t coderate, uint16_t preambleLen,
                     bool fixLen, bool crcOn, bool freqHopOn, 
                     uint8_t hopPeriod, bool iqInverted);

    /**
     * Start a receive operation.  The method will return when
     * completed, either successfully, or in error (crc, or other
     * issue).  If completed successfully, the returned buffer can be
     * read via getRxBuffer() or getRxBufferStr().  In addition,
     * values for RSSI and SNR (Lora only) can be retrieved.
     *
     * @param timeout The timeout in milliseconds
     * @return one of the RADIO_EVENT_T values
     */
    RADIO_EVENT_T setRx(uint32_t timeout);

    /**
     * Upon a successful receive, this method can be used to retrieve
     * the received packet.
     *
     * @return The received buffer in a std::string
     */
    std::string getRxBufferStr()
    {
      std::string rBuffer((char *)m_rxBuffer, getRxLen());
      return rBuffer;
    };

    /**
     * Upon a successful receive, this method can be used to retrieve
     * the received packet.
     *
     * @return a pointer to the received buffer.  You can use
     * getRxLen() to determine the number of valid bytes present.
     */
    uint8_t *getRxBuffer()
    {
      return (uint8_t*)m_rxBuffer;
    };

    /**
     * Upon a successful receive, this method can be used to retrieve
     * the received packet's Received Signal Strength Indicator (RSSI)
     * value.
     *
     * @return RSSI value
     */
    int getRxRSSI()
    {
      return m_rxRSSI;
    };

    /**
     * Upon a successful receive, this method can be used to retrieve
     * the received packet's Signal to Noise (SNR) value.
     *
     * @return SNR value
     */
    int getRxSNR()
    {
      return m_rxSNR;
    };

    /**
     * Upon a successful receive, this method can be used to retrieve
     * the number of bytes received.
     *
     * @return the number of bytes received
     */
    int getRxLen()
    {
      return m_rxLen;
    };


  protected:
    // I/O
    mraa::Spi m_spi;
    mraa::Gpio m_gpioCS;
    mraa::Gpio m_gpioReset;

    mraa::Gpio m_gpioDIO0;
    mraa::Gpio m_gpioDIO1;
    mraa::Gpio m_gpioDIO2;
    mraa::Gpio m_gpioDIO3;
    mraa::Gpio m_gpioDIO4;
    mraa::Gpio m_gpioDIO5;

    // calibration called during init()
    void rxChainCalibration();

    // interrupt handlers
    static void onDio0Irq(void *ctx);
    static void onDio1Irq(void *ctx);
    static void onDio2Irq(void *ctx);
    static void onDio3Irq(void *ctx);
    static void onDio4Irq(void *ctx);
    static void onDio5Irq(void *ctx);

    /**
     * What internal state are we in
     */
    typedef enum {
      STATE_IDLE                         = 0,
      STATE_RX_RUNNING,
      STATE_TX_RUNNING,
      STATE_CAD
    } RADIO_STATES_T;

    // needs to be OR'd onto registers for SPI write
    static const uint8_t m_writeMode = 0x80;

    // initialize the chip
    void init();

    // Start a transmit event (you should use send() or sendStr()
    // rather than call this function directly.
    RADIO_EVENT_T setTx(int timeout);

    void startCAD(); // non-functional/non-tested

    // not really used, maybe it should be
    void setMaxPayloadLength(RADIO_MODEM_T modem, uint8_t max);

    // Chip Select control (active LOW)
    void csOn() 
    {
      m_gpioCS.write(0);
    };

    void csOff() 
    {
      m_gpioCS.write(1);
    };

  private:
    // Thse structs will generate SWIG warnings, as we do not expose
    // this data, they can be ignored.

    // stored settings for the FSK modem
    typedef struct
    {
      int8_t   Power;
      uint32_t Fdev;
      uint32_t Bandwidth;
      uint32_t BandwidthAfc;
      uint32_t Datarate;
      uint16_t PreambleLen;
      bool     FixLen;
      uint8_t  PayloadLen;
      bool     CrcOn;
      bool     IqInverted;
      bool     RxContinuous;
    } radioFskSettings_t;

    // stored settings for the LoRa modem
    typedef struct
    {
      int8_t   Power;
      uint32_t Bandwidth;
      uint32_t Datarate;
      bool     LowDatarateOptimize;
      uint8_t  Coderate;
      uint16_t PreambleLen;
      bool     FixLen;
      uint8_t  PayloadLen;
      bool     CrcOn;
      bool     FreqHopOn;
      uint8_t  HopPeriod;
      bool     IqInverted;
      bool     RxContinuous;
    } radioLoRaSettings_t;

    // FSK packet handler state
    typedef struct
    {
      uint8_t  PreambleDetected;
      uint8_t  SyncWordDetected;
      int8_t   RssiValue;
      int32_t  AfcValue;
      uint8_t  RxGain;
      uint16_t Size;
      uint16_t NbBytes;
      uint8_t  FifoThresh;
      uint8_t  ChunkSize;
    } radioFskPacketHandler_t;

    // LoRa packet handler state
    typedef struct
    {
      int8_t SnrValue;
      int16_t RssiValue;
      uint8_t Size;
    } radioLoRaPacketHandler_t;

    // our radio settings
    struct {
      RADIO_MODEM_T  modem;
      volatile RADIO_STATES_T state;
      uint32_t       channel;

      radioFskSettings_t      fskSettings;
      volatile radioFskPacketHandler_t fskPacketHandler;

      radioLoRaSettings_t      loraSettings;
      volatile radioLoRaPacketHandler_t loraPacketHandler;
    } m_settings;

    uint8_t lookupFSKBandWidth(uint32_t bw);

    // received data (on successful completion)
    volatile int m_rxRSSI;
    volatile int m_rxSNR;
    volatile int m_rxLen;
    uint8_t m_rxBuffer[FIFO_SIZE];

    // for coordinating interrupt access
    pthread_mutex_t m_intrLock;

    void lockIntrs() { pthread_mutex_lock(&m_intrLock); };
    void unlockIntrs() { pthread_mutex_unlock(&m_intrLock); };

    // current radio event status
    volatile RADIO_EVENT_T m_radioEvent;

    // timer support
    struct timeval m_startTime;
    void initClock();
    uint32_t getMillis();
  };
}


