#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

#include "sx1262.h"
#include "ch32v30x_gpio.h"
#include "util/log.h"
#define TAG "SX1262"

// Global Stuff
uint8_t PacketParams[6];
char txActive;
int txLost = 0;
char debugPrint;

// Arduino compatible macros
#define delayMicroseconds(us) esp_rom_delay_us (us)
#define delay(ms) esp_rom_delay_us (ms * 1000)

void LoRaError (int error) {
    if (debugPrint) {
        MESH_LOGE (TAG, "LoRaErrorDefault=%d", error);
    }
    while (1) {
        iprintf("GONE WRONG\n");
        Delay_Ms(1);
    }
}

/*
// Convert bandwidth constant to float in kHz
float loraBwToFloat(uint8_t bw) {
    switch (bw) {
        case SX126X_LORA_BW_7_8:    return 7.8f;
        case SX126X_LORA_BW_10_4:   return 10.4f;
        case SX126X_LORA_BW_15_6:   return 15.6f;
        case SX126X_LORA_BW_20_8:   return 20.8f;
        case SX126X_LORA_BW_31_25:  return 31.25f;
        case SX126X_LORA_BW_41_7:   return 41.7f;
        case SX126X_LORA_BW_62_5:   return 62.5f;
        case SX126X_LORA_BW_125_0:  return 125.0f;
        case SX126X_LORA_BW_250_0:  return 250.0f;
        case SX126X_LORA_BW_500_0:  return 500.0f;
        default: return 0.0f; // unknown
    }
}
*/

/*
// Convert float bandwidth (kHz) to constant
uint8_t floatToLoraBw(float bw) {
    if (bw == 7.8f)     return SX126X_LORA_BW_7_8;
    if (bw == 10.4f)    return SX126X_LORA_BW_10_4;
    if (bw == 15.6f)    return SX126X_LORA_BW_15_6;
    if (bw == 20.8f)    return SX126X_LORA_BW_20_8;
    if (bw == 31.25f)   return SX126X_LORA_BW_31_25;
    if (bw == 41.7f)    return SX126X_LORA_BW_41_7;
    if (bw == 62.5f)    return SX126X_LORA_BW_62_5;
    if (bw == 125.0f)   return SX126X_LORA_BW_125_0;
    if (bw == 250.0f)   return SX126X_LORA_BW_250_0;
    if (bw == 500.0f)   return SX126X_LORA_BW_500_0;
    return 0xFF; // invalid
}
*/
/*
// Convert coding rate constant to float
float loraCrToFloat(uint8_t cr) {
    switch (cr) {
        case SX126X_LORA_CR_4_5: return 4.0f / 5.0f;
        case SX126X_LORA_CR_4_6: return 4.0f / 6.0f;
        case SX126X_LORA_CR_4_7: return 4.0f / 7.0f;
        case SX126X_LORA_CR_4_8: return 4.0f / 8.0f;
        default: return 0.0f;
    }
}
*/

/*

// Convert float coding rate to constant
uint8_t floatToLoraCr(float cr) {
    if (cr == 4.0f / 5.0f) return SX126X_LORA_CR_4_5;
    if (cr == 4.0f / 6.0f) return SX126X_LORA_CR_4_6;
    if (cr == 4.0f / 7.0f) return SX126X_LORA_CR_4_7;
    if (cr == 4.0f / 8.0f) return SX126X_LORA_CR_4_8;
    return 0xFF;
}
*/
/*
// Convert MHz to Hz with limit check
uint32_t mhzToHzLimited(double freqMHz) {
    double freqHz = freqMHz * 1000000.0;
    if (freqHz > 860000000.0 && freqHz < 880000000.0) {
        return (uint32_t)freqHz;
    }
    return 0; // out of range
}
*/

void LoRaInit (void) {

    txActive = 0;
    debugPrint = 0;

    GPIO_InitTypeDef GPIO_InitStructure = {0};
    SPI_InitTypeDef SPI_InitStructure = {0};


    // RCC_AHBPeriphClockCmd (RCC_AHBPeriph_SDIO, ENABLE);
    RCC_APB1PeriphClockCmd (RCC_APB1Periph_USART2 | RCC_APB1Periph_I2C2, ENABLE);

    RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOE | RCC_APB2Periph_SPI1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init (GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init (GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init (GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init (GPIOA, &GPIO_InitStructure);


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init (GPIOE, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init (GPIOE, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init (GPIOE, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init (GPIOE, &GPIO_InitStructure);


    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;


    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init (SPI1, &SPI_InitStructure);

    GPIO_WriteBit (GPIOE, GPIO_Pin_3, 1);

    SPI_Cmd (SPI1, ENABLE);
}

void spi_write_byte (uint8_t *DataOut, size_t DataLength) {
    spi_read_byte (NULL, DataOut, DataLength);
}

// Full-duplex transfer: TX and RX buffers
void spi_read_byte (uint8_t *rx, const uint8_t *tx, size_t len) {
    GPIO_WriteBit (GPIOA, GPIO_Pin_4, 0);
    for (size_t i = 0; i < len; i++) {
        uint8_t out = tx ? tx[i] : 0xFF;

        // Wait TX ready
        while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE) == RESET);
        if (debugPrint) {
            MESH_LOGV (TAG, "Sent %02X over spi.\n", out);
        }
        SPI_I2S_SendData (SPI1, out);
        // Wait RX ready

        while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE) == RESET);

        uint8_t in = (uint8_t)SPI_I2S_ReceiveData (SPI1);
        if (debugPrint) {
            MESH_LOGV (TAG, "Got %02X over spi.\n", in);
        }
        if (rx)
            rx[i] = in;
    }
    GPIO_WriteBit (GPIOA, GPIO_Pin_4, 1);
}

// Full-duplex transfer: TX and RX buffers
void spi_read_byte_offs (uint8_t *rx, const uint8_t *tx, size_t len, uint8_t off) {
    GPIO_WriteBit (GPIOA, GPIO_Pin_4, 0);
    for (size_t i = 0; i < len; i++) {
        uint8_t out = 0xFF;
        if (i < off) {
            out = tx ? tx[i] : 0xFF;
        }

        // Wait TX ready
        while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_TXE) == RESET);
        if (debugPrint) {
            MESH_LOGV (TAG, "Sent %02X over spi.\n", out);
        }
        SPI_I2S_SendData (SPI1, out);
        // Wait RX ready

        while (SPI_I2S_GetFlagStatus (SPI1, SPI_I2S_FLAG_RXNE) == RESET);

        uint8_t in = (uint8_t)SPI_I2S_ReceiveData (SPI1);
        if (debugPrint) {
            MESH_LOGV (TAG, "Got %02X over spi.\n", in);
        }
        if (rx && i >= off) {
            rx[i - off] = in;
        }
    }
    GPIO_WriteBit (GPIOA, GPIO_Pin_4, 1);
}

int16_t LoRaBegin (uint32_t frequencyInHz, int8_t txPowerInDbm, uint16_t tcxoVoltage, char useRegulatorLDO) {
    if (txPowerInDbm > 22) {
        txPowerInDbm = 22;
    }
    if (txPowerInDbm < -3) {
        txPowerInDbm = -3;
    }

    ResetLora();

    uint8_t wk[2];
    ReadRegister (SX126X_REG_LORA_SYNC_WORD_MSB, wk, 2);  // 0x0740
    uint16_t syncWord = (wk[0] << 8) + wk[1];
    MESH_LOGI (TAG, "syncWord=0x%x", syncWord);
    if (syncWord != SX126X_SYNC_WORD_PUBLIC && syncWord != SX126X_SYNC_WORD_PRIVATE) {
        MESH_LOGE (TAG, "SX126x error, maybe no SPI connection");
        return ERR_INVALID_MODE;
    }

    MESH_LOGI (TAG, "SX126x installed");
    SetStandby (SX126X_STANDBY_RC);

    SetDio2AsRfSwitchCtrl (1);
    //MESH_LOGI (TAG, "tcxoVoltage=%f", tcxoVoltage);
    // set TCXO control, if requested
    if (tcxoVoltage > 0) {
        SetDio3AsTcxoCtrl (tcxoVoltage, RADIO_TCXO_SETUP_TIME);  // Configure the radio to use a TCXO controlled by DIO3
    }

    Calibrate (SX126X_CALIBRATE_IMAGE_ON | SX126X_CALIBRATE_ADC_BULK_P_ON | SX126X_CALIBRATE_ADC_BULK_N_ON | SX126X_CALIBRATE_ADC_PULSE_ON | SX126X_CALIBRATE_PLL_ON | SX126X_CALIBRATE_RC13M_ON | SX126X_CALIBRATE_RC64K_ON);

    MESH_LOGI (TAG, "useRegulatorLDO=%d", useRegulatorLDO);
    if (useRegulatorLDO) {
        SetRegulatorMode (SX126X_REGULATOR_LDO);    // set regulator mode: LDO
    } else {
        SetRegulatorMode (SX126X_REGULATOR_DC_DC);  // set regulator mode: DC-DC
    }

    SetBufferBaseAddress (0, 0);
    SetPaConfig (0x04, 0x07, 0x00, 0x01);                // PA Optimal Settings +22 dBm
    SetOvercurrentProtection (60);                     // current max 60mA for the whole device
    SetPowerConfig (txPowerInDbm, SX126X_PA_RAMP_200U);  // 0 fuer Empfaenger
    SetRfFrequency (frequencyInHz);
    return ERR_NONE;
}

void FixInvertedIQ (uint8_t iqConfig) {
    // fixes IQ configuration for inverted IQ
    // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.4 for details
    // When exchanging LoRa packets with inverted IQ polarity, some packet losses may be observed for longer packets.
    // Workaround: Bit 2 at address 0x0736 must be set to:
    // “0” when using inverted IQ polarity (see the SetPacketParam(...) command)
    // “1” when using standard IQ polarity

    // read current IQ configuration
    uint8_t iqConfigCurrent = 0;
    ReadRegister (SX126X_REG_IQ_POLARITY_SETUP, &iqConfigCurrent, 1);  // 0x0736

    // set correct IQ configuration
    // if(iqConfig == SX126X_LORA_IQ_STANDARD) {
    if (iqConfig == SX126X_LORA_IQ_INVERTED) {
        iqConfigCurrent &= 0xFB;  // using inverted IQ polarity
    } else {
        iqConfigCurrent |= 0x04;  // using standard IQ polarity
    }

    // update with the new value
    WriteRegister (SX126X_REG_IQ_POLARITY_SETUP, &iqConfigCurrent, 1);  // 0x0736
}

void LoRaConfig (uint8_t spreadingFactor, uint8_t bandwidth, uint8_t codingRate, uint16_t preambleLength, uint8_t payloadLen, char crcOn, char invertIrq) {
    SetStopRxTimerOnPreambleDetect (0);
    SetLoRaSymbNumTimeout (0);
    SetPacketType (SX126X_PACKET_TYPE_LORA);  // SX126x.ModulationParams.PacketType : MODEM_LORA
    uint8_t ldro = 0;                         // LowDataRateOptimize OFF
    SetModulationParams (spreadingFactor, bandwidth, codingRate, ldro);

    PacketParams[0] = (preambleLength >> 8) & 0xFF;
    PacketParams[1] = preambleLength;
    if (payloadLen) {
        PacketParams[2] = 0x01;  // Fixed length packet (implicit header)
        PacketParams[3] = payloadLen;
    } else {
        PacketParams[2] = 0x00;  // Variable length packet (explicit header)
        PacketParams[3] = 0xFF;
    }

    if (crcOn)
        PacketParams[4] = SX126X_LORA_CRC_ON;
    else
        PacketParams[4] = SX126X_LORA_CRC_OFF;

    if (invertIrq)
        PacketParams[5] = 0x01;  // Inverted LoRa I and Q signals setup
    else
        PacketParams[5] = 0x00;  // Standard LoRa I and Q signals setup

    // fixes IQ configuration for inverted IQ
    FixInvertedIQ (PacketParams[5]);

    WriteCommand (SX126X_CMD_SET_PACKET_PARAMS, PacketParams, 6);  // 0x8C

    // Do not use DIO interruptst
    SetDioIrqParams (SX126X_IRQ_ALL,   // all interrupts enabled
                     SX126X_IRQ_NONE,  // interrupts on DIO1
                     SX126X_IRQ_NONE,  // interrupts on DIO2
                     SX126X_IRQ_NONE   // interrupts on DIO3
    );


    MESH_LOGI (TAG, "Almost done setting LoRa");
    // Receive state no receive timeoout
    SetRx (0xFFFFFF);
}

void LoRaDebugPrint (char enable) {
    debugPrint = enable;
}

uint8_t LoRaReceive (uint8_t *pData, int16_t len) {
    uint8_t rxLen = 0;
    uint16_t irqRegs = GetIrqStatus();
    // uint8_t status = GetStatus();

    if (irqRegs & SX126X_IRQ_RX_DONE) {
        // ClearIrqStatus(SX126X_IRQ_RX_DONE);
        ClearIrqStatus (SX126X_IRQ_ALL);
        rxLen = ReadBuffer (pData, len);
    }

    return rxLen;
}

char LoRaSend (uint8_t *pData, int16_t len, uint8_t mode) {
    uint16_t irqStatus;
    char rv = 0;

    if (txActive == 0) {
        txActive = 1;
        if (PacketParams[2] == 0x00) {  // Variable length packet (explicit header)
            PacketParams[3] = len;
        }
        WriteCommand (SX126X_CMD_SET_PACKET_PARAMS, PacketParams, 6);  // 0x8C

        // ClearIrqStatus(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT);
        ClearIrqStatus (SX126X_IRQ_ALL);

        WriteBuffer (pData, len);
        SetTx (5000);

        if (mode & SX126x_TXMODE_SYNC) {
            irqStatus = GetIrqStatus();
            while ((!(irqStatus & SX126X_IRQ_TX_DONE)) && (!(irqStatus & SX126X_IRQ_TIMEOUT))) {
                Delay_Ms(1);
                irqStatus = GetIrqStatus();
            }
            if (debugPrint) {
                MESH_LOGI (TAG, "irqStatus=0x%x", irqStatus);
                if (irqStatus & SX126X_IRQ_TX_DONE) {
                    MESH_LOGI (TAG, "SX126X_IRQ_TX_DONE");
                }
                if (irqStatus & SX126X_IRQ_TIMEOUT) {
                    MESH_LOGI (TAG, "SX126X_IRQ_TIMEOUT");
                }
            }
            txActive = 0;

            SetRx (0xFFFFFF);

            if (irqStatus & SX126X_IRQ_TX_DONE) {
                rv = 1;
            }
        } else {
            rv = 1;
        }
    }
    if (debugPrint) {
        MESH_LOGI (TAG, "Send rv=0x%x", rv);
    }
    if (rv == 0)
        txLost++;
    return rv;
}

char ReceiveMode (void) {
    uint16_t irq;
    char rv = 0;

    if (txActive == 0) {
        rv = 1;
    } else {
        irq = GetIrqStatus();
        if (irq & (SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT)) {
            SetRx (0xFFFFFF);
            txActive = 0;
            rv = 1;
        }
    }

    return rv;
}

void GetPacketStatus (int8_t *rssiPacket, int8_t *snrPacket, int8_t *rawSnr) {
    uint8_t buf[4];
    ReadCommand (SX126X_CMD_GET_PACKET_STATUS, buf, 4);  // 0x14
    *rssiPacket = (buf[3] >> 1) * -1;
    (buf[2] < 128) ? (*snrPacket = buf[2] >> 2) : (*snrPacket = ((buf[2] - 256) >> 2));
    *rawSnr = buf[2];
}

void SetTxPower (int8_t txPowerInDbm) {
    SetPowerConfig (txPowerInDbm, SX126X_PA_RAMP_200U);
}

void ResetLora (void) {
    Delay_Ms(10);
    GPIO_WriteBit (GPIOE, GPIO_Pin_3, 0);
    Delay_Ms(20);
    GPIO_WriteBit (GPIOE, GPIO_Pin_3, 1);
    MESH_LOGW (TAG, "Waiting for idle");
    Delay_Ms(10);
    // ensure BUSY is low (state meachine ready)
    WaitForIdle (BUSY_WAIT, "Reset", 1);
}

void Wakeup (void) {
    GetStatus();
}

void SetStandby (uint8_t mode) {
    uint8_t data = mode;
    WriteCommand (SX126X_CMD_SET_STANDBY, &data, 1);  // 0x80
}

uint8_t GetStatus (void) {
    uint8_t rv;
    ReadCommand (SX126X_CMD_GET_STATUS, &rv, 1);  // 0xC0
    return rv;
}

static uint8_t voltage_to_tcxo(uint16_t voltage)
{
    if (voltage == 1650) return SX126X_DIO3_OUTPUT_1_6;
    if (voltage == 1750) return SX126X_DIO3_OUTPUT_1_7;
    if (voltage == 1850) return SX126X_DIO3_OUTPUT_1_8;
    if (voltage == 2300)  return SX126X_DIO3_OUTPUT_2_2;
    if (voltage == 2500)  return SX126X_DIO3_OUTPUT_2_4;
    if (voltage == 2850) return SX126X_DIO3_OUTPUT_2_7;
    if (voltage == 3100)  return SX126X_DIO3_OUTPUT_3_0;
    return SX126X_DIO3_OUTPUT_3_3;
}

void SetDio3AsTcxoCtrl (uint16_t voltage, uint32_t delay) {
    uint8_t buf[4];

    // buf[0] = tcxoVoltage & 0x07;
    buf[0] = voltage_to_tcxo(voltage);

    uint32_t delayValue = (delay * 1000) / 15625;

    buf[1] = (delayValue >> 16) & 0xFF;
    buf[2] = (delayValue >> 8) & 0xFF;
    buf[3] = delayValue & 0xFF;

    WriteCommand (SX126X_CMD_SET_DIO3_AS_TCXO_CTRL, buf, 4);  // 0x97
}

void Calibrate (uint8_t calibParam) {
    uint8_t data = calibParam;
    WriteCommand (SX126X_CMD_CALIBRATE, &data, 1);  // 0x89
}
void SetDio2AsRfSwitchCtrl (uint8_t enable) {
    uint8_t data = enable;
    WriteCommand (SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, &data, 1);  // 0x9D
}

void SetRfFrequency (uint32_t frequency) {
    uint8_t buf[4];

    CalibrateImage (frequency);

    //freq = (uint32_t)((double)frequency / (double)FREQ_STEP);
    uint32_t freq = ((uint64_t)frequency << 25) / XTAL_FREQ;
    buf[0] = (uint8_t)((freq >> 24) & 0xFF);
    buf[1] = (uint8_t)((freq >> 16) & 0xFF);
    buf[2] = (uint8_t)((freq >> 8) & 0xFF);
    buf[3] = (uint8_t)(freq & 0xFF);
    WriteCommand (SX126X_CMD_SET_RF_FREQUENCY, buf, 4);  // 0x86
}

void CalibrateImage (uint32_t frequency) {
    uint8_t calFreq[2];

    if (frequency > 900000000) {
        calFreq[0] = 0xE1;
        calFreq[1] = 0xE9;
    } else if (frequency > 850000000) {
        calFreq[0] = 0xD7;
        calFreq[1] = 0xDB;
    } else if (frequency > 770000000) {
        calFreq[0] = 0xC1;
        calFreq[1] = 0xC5;
    } else if (frequency > 460000000) {
        calFreq[0] = 0x75;
        calFreq[1] = 0x81;
    } else if (frequency > 425000000) {
        calFreq[0] = 0x6B;
        calFreq[1] = 0x6F;
    }
    WriteCommand (SX126X_CMD_CALIBRATE_IMAGE, calFreq, 2);  // 0x98
}

void SetRegulatorMode (uint8_t mode) {
    uint8_t data = mode;
    WriteCommand (SX126X_CMD_SET_REGULATOR_MODE, &data, 1);  // 0x96
}

void SetBufferBaseAddress (uint8_t txBaseAddress, uint8_t rxBaseAddress) {
    uint8_t buf[2];

    buf[0] = txBaseAddress;
    buf[1] = rxBaseAddress;
    WriteCommand (SX126X_CMD_SET_BUFFER_BASE_ADDRESS, buf, 2);  // 0x8F
}

void SetPowerConfig (int8_t power, uint8_t rampTime) {
    uint8_t buf[2];

    if (power > 22) {
        power = 22;
    } else if (power < -3) {
        power = -3;
    }

    buf[0] = power;
    buf[1] = (uint8_t)rampTime;
    WriteCommand (SX126X_CMD_SET_TX_PARAMS, buf, 2);  // 0x8E
}

void SetPaConfig (uint8_t paDutyCycle, uint8_t hpMax, uint8_t deviceSel, uint8_t paLut) {
    uint8_t buf[4];

    buf[0] = paDutyCycle;
    buf[1] = hpMax;
    buf[2] = deviceSel;
    buf[3] = paLut;
    WriteCommand (SX126X_CMD_SET_PA_CONFIG, buf, 4);  // 0x95
}

void SetOvercurrentProtection(uint16_t current_mA)
{
    if (current_mA <= 140)
    {
        uint8_t buf = (current_mA * 10) / 25;
        WriteRegister(SX126X_REG_OCP_CONFIGURATION, &buf, 1);
    }
}

void SetSyncWord (int16_t sync) {
    uint8_t buf[2];

    buf[0] = (uint8_t)((sync >> 8) & 0x00FF);
    buf[1] = (uint8_t)(sync & 0x00FF);
    WriteRegister (SX126X_REG_LORA_SYNC_WORD_MSB, buf, 2);  // 0x0740
}

void SetDioIrqParams (uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask, uint16_t dio3Mask) {
    uint8_t buf[8];

    buf[0] = (uint8_t)((irqMask >> 8) & 0x00FF);
    buf[1] = (uint8_t)(irqMask & 0x00FF);
    buf[2] = (uint8_t)((dio1Mask >> 8) & 0x00FF);
    buf[3] = (uint8_t)(dio1Mask & 0x00FF);
    buf[4] = (uint8_t)((dio2Mask >> 8) & 0x00FF);
    buf[5] = (uint8_t)(dio2Mask & 0x00FF);
    buf[6] = (uint8_t)((dio3Mask >> 8) & 0x00FF);
    buf[7] = (uint8_t)(dio3Mask & 0x00FF);
    WriteCommand (SX126X_CMD_SET_DIO_IRQ_PARAMS, buf, 8);  // 0x08
}

void SetStopRxTimerOnPreambleDetect (char enable) {
    MESH_LOGI (TAG, "SetStopRxTimerOnPreambleDetect enable=%d", enable);
    // uint8_t data = (uint8_t)enable;
    uint8_t data = 0;
    if (enable)
        data = 1;
    WriteCommand (SX126X_CMD_STOP_TIMER_ON_PREAMBLE, &data, 1);  // 0x9F
}

void SetLoRaSymbNumTimeout (uint8_t SymbNum) {
    uint8_t data = SymbNum;
    WriteCommand (SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT, &data, 1);  // 0xA0
}

void SetPacketType (uint8_t packetType) {
    uint8_t data = packetType;
    WriteCommand (SX126X_CMD_SET_PACKET_TYPE, &data, 1);  // 0x01
}

void SetModulationParams (uint8_t spreadingFactor, uint8_t bandwidth, uint8_t codingRate, uint8_t lowDataRateOptimize) {
    uint8_t data[4];
    // currently only LoRa supported
    data[0] = spreadingFactor;
    data[1] = bandwidth;
    data[2] = codingRate;
    data[3] = lowDataRateOptimize;
    WriteCommand (SX126X_CMD_SET_MODULATION_PARAMS, data, 4);  // 0x8B
}

void SetCadParams (uint8_t cadSymbolNum, uint8_t cadDetPeak, uint8_t cadDetMin, uint8_t cadExitMode, uint32_t cadTimeout) {
    uint8_t data[7];
    data[0] = cadSymbolNum;
    data[1] = cadDetPeak;
    data[2] = cadDetMin;
    data[3] = cadExitMode;
    data[4] = (uint8_t)((cadTimeout >> 16) & 0xFF);
    data[5] = (uint8_t)((cadTimeout >> 8) & 0xFF);
    data[6] = (uint8_t)(cadTimeout & 0xFF);
    WriteCommand (SX126X_CMD_SET_CAD_PARAMS, data, 7);  // 0x88
}

void SetCad() {
    uint8_t data = 0;
    WriteCommand (SX126X_CMD_SET_CAD, &data, 0);  // 0xC5
}

uint16_t GetIrqStatus (void) {
    uint8_t data[3];
    ReadCommand (SX126X_CMD_GET_IRQ_STATUS, data, 3);  // 0x12
    return (data[1] << 8) | data[2];
}

void ClearIrqStatus (uint16_t irq) {
    uint8_t buf[2];

    buf[0] = (uint8_t)(((uint16_t)irq >> 8) & 0x00FF);
    buf[1] = (uint8_t)((uint16_t)irq & 0x00FF);
    WriteCommand (SX126X_CMD_CLEAR_IRQ_STATUS, buf, 2);  // 0x02
}

void SetRx (uint32_t timeout) {
    if (debugPrint) {
        MESH_LOGI (TAG, "----- SetRx timeout=%d", timeout);
    }
    SetStandby (SX126X_STANDBY_RC);
    uint8_t buf[3];
    buf[0] = (uint8_t)((timeout >> 16) & 0xFF);
    buf[1] = (uint8_t)((timeout >> 8) & 0xFF);
    buf[2] = (uint8_t)(timeout & 0xFF);
    WriteCommand (SX126X_CMD_SET_RX, buf, 3);  // 0x82

    for (int retry = 0; retry < 10; retry++) {
        if ((GetStatus() & 0x70) == 0x50)
            break;
        Delay_Ms(1);
    }
    if ((GetStatus() & 0x70) != 0x50) {
        MESH_LOGE (TAG, "SetRx Illegal Status");
        LoRaError (ERR_INVALID_SETRX_STATE);
    }
}

void SetTx (uint32_t timeoutInMs) {
    if (debugPrint) {
        MESH_LOGI (TAG, "----- SetTx timeoutInMs=%d", timeoutInMs);
    }
    SetStandby (SX126X_STANDBY_RC);
    uint8_t buf[3];
    uint32_t tout = timeoutInMs;
    if (timeoutInMs != 0) {
        uint32_t timeoutInUs = timeoutInMs * 1000;
        tout = timeoutInUs * 64;
    }
    if (debugPrint) {
        MESH_LOGI (TAG, "SetTx timeoutInMs=%d"
                        " tout=%d",
                   timeoutInMs, tout);
    }
    buf[0] = (uint8_t)((tout >> 16) & 0xFF);
    buf[1] = (uint8_t)((tout >> 8) & 0xFF);
    buf[2] = (uint8_t)(tout & 0xFF);
    WriteCommand (SX126X_CMD_SET_TX, buf, 3);  // 0x83

    if (debugPrint) {
        MESH_LOGD(TAG, "Written command, retrying");
    }

    for (int retry = 0; retry < 20; retry++) {
        if ((GetStatus() & 0x70) == 0x60)
            break;
        Delay_Ms(1);
    }
    if (debugPrint) {
        MESH_LOGI(TAG, "Broke out");
    }
    if ((GetStatus() & 0x70) != 0x60) {
        MESH_LOGE (TAG, "SetTx Illegal Status");
        LoRaError (ERR_INVALID_SETTX_STATE);
    }
}

int GetPacketLost() {
    return txLost;
}

uint8_t GetRssiInst() {
    uint8_t buf[2];
    ReadCommand (SX126X_CMD_GET_RSSI_INST, buf, 2);  // 0x15
    return buf[1];
}

void GetRxBufferStatus (uint8_t *payloadLength, uint8_t *rxStartBufferPointer) {
    uint8_t buf[3];
    ReadCommand (SX126X_CMD_GET_RX_BUFFER_STATUS, buf, 3);  // 0x13
    *payloadLength = buf[1];
    *rxStartBufferPointer = buf[2];
}

void WaitForIdleBegin (unsigned long timeout, char *text) {
    // ensure BUSY is low (state meachine ready)
    char stop = 0;
    for (int retry = 0; retry < 10; retry++) {
        if (retry == 9)
            stop = 1;
        char ret = WaitForIdle (BUSY_WAIT, text, stop);
        if (ret == 1)
            break;
        MESH_LOGW (TAG, "WaitForIdle fail retry=%d", retry);
        Delay_Ms(10);
    }
}

char WaitForIdle (unsigned long timeout, char *text, char stop) {
    char ret = 1;
    Delay_Ms(1);
    for (unsigned long i = 0; i < timeout; i++) {
        if (GPIO_ReadInputDataBit (GPIOE, GPIO_Pin_1) == 0)
            break;
        Delay_Ms(10);
    }

    if (GPIO_ReadInputDataBit (GPIOE, GPIO_Pin_1)) {
        if (stop) {
            MESH_LOGE (TAG, "WaitForIdle Timeout text=%s timeout=%lu", text, timeout);
            LoRaError (ERR_IDLE_TIMEOUT);
        } else {
            MESH_LOGW (TAG, "WaitForIdle Timeout text=%s timeout=%lu", text, timeout);
            ret = 0;
        }
    }
    return ret;
}

/*
uint8_t ReadBuffer (uint8_t *rxData, int16_t rxDataLen) {
    uint8_t offset = 0;
    uint8_t payloadLength = 0;
    GetRxBufferStatus (&payloadLength, &offset);
    if (payloadLength > rxDataLen) {
        MESH_LOGW (TAG, "ReadBuffer rxDataLen too small. payloadLength=%d rxDataLen=%d", payloadLength, rxDataLen);
        return 0;
    }

    // ensure BUSY is low (state meachine ready)
    WaitForIdle (BUSY_WAIT, "start ReadBuffer", 1);

    // start transfer
    uint8_t *buf;
    buf = malloc (payloadLength + 3);
    if (buf != NULL) {
        buf[0] = SX126X_CMD_READ_BUFFER;  // 0x1E
        buf[1] = offset;                  // offset in rx fifo
        buf[2] = SX126X_CMD_NOP;
        memset (&buf[3], SX126X_CMD_NOP, payloadLength);
        spi_read_byte (buf, buf, payloadLength + 3);
        memcpy (rxData, &buf[3], payloadLength);
        free (buf);
    } else {
        MESH_LOGE (TAG, "ReadBuffer malloc fail");
        payloadLength = 0;
    }

    // wait for BUSY to go low
    WaitForIdle (BUSY_WAIT, "end ReadBuffer", 0);

    return payloadLength;
}*/


uint8_t ReadBuffer (uint8_t *rxData, int16_t rxDataLen) {
    uint8_t offset = 0;
    uint8_t payloadLength = 0;
    GetRxBufferStatus (&payloadLength, &offset);

    if (payloadLength > rxDataLen) {
        MESH_LOGW (TAG, "ReadBuffer rxDataLen too small. payloadLength=%d rxDataLen=%d", payloadLength, rxDataLen);
        return 0;
    }

    WaitForIdle (BUSY_WAIT, "start ReadBuffer", 1);

    uint8_t cmd[3] = {SX126X_CMD_READ_BUFFER, offset, SX126X_CMD_NOP};

    spi_read_byte_offs (rxData, cmd, rxDataLen, sizeof (cmd));

    WaitForIdle (BUSY_WAIT, "end ReadBuffer", 0);

    return payloadLength;
}

void WriteBuffer (uint8_t *txData, int16_t txDataLen) {
    // ensure BUSY is low (state meachine ready)
    WaitForIdle (BUSY_WAIT, "start WriteBuffer", 1);

    // start transfer
    uint8_t *buf;
    buf = malloc (txDataLen + 2);
    if (buf != NULL) {
        buf[0] = SX126X_CMD_WRITE_BUFFER;  // 0x0E
        buf[1] = 0;                        // offset in tx fifo
        memcpy (&buf[2], txData, txDataLen);
        spi_write_byte (buf, txDataLen + 2);
        free (buf);
    } else {
        MESH_LOGE (TAG, "WriteBuffer malloc fail");
    }

    // wait for BUSY to go low
    WaitForIdle (BUSY_WAIT, "end WriteBuffer", 0);
}

void WriteRegister (uint16_t reg, uint8_t *data, uint8_t numBytes) {
    // ensure BUSY is low (state meachine ready)
    WaitForIdle (BUSY_WAIT, "start WriteRegister", 1);

    if (debugPrint) {
        MESH_LOGI (TAG, "WriteRegister: REG=0x%02x", reg);
        for (uint8_t n = 0; n < numBytes; n++) {
            MESH_LOGI (TAG, "DataOut:%02x ", data[n]);
        }
    }

    // start transfer
    uint8_t buf[16];
    buf[0] = SX126X_CMD_WRITE_REGISTER;
    buf[1] = (reg & 0xFF00) >> 8;
    buf[2] = reg & 0xff;
    memcpy (&buf[3], data, numBytes);
    spi_write_byte (buf, 3 + numBytes);

    // wait for BUSY to go low
    WaitForIdle (BUSY_WAIT, "end WriteRegister", 0);
}

void ReadRegister (uint16_t reg, uint8_t *data, uint8_t numBytes) {
    // ensure BUSY is low (state meachine ready)
    WaitForIdle (BUSY_WAIT, "start ReadRegister", 1);

    if (debugPrint) {
        MESH_LOGI (TAG, "ReadRegister: REG=0x%02x", reg);
    }

    // start transfer
    uint8_t buf[16];
    uint8_t buf2[16];
    memset (buf, SX126X_CMD_NOP, sizeof (buf));
    memset (buf2, SX126X_CMD_NOP, sizeof (buf2));
    buf[0] = SX126X_CMD_READ_REGISTER;
    buf[1] = (reg & 0xFF00) >> 8;
    buf[2] = reg & 0xff;
    if (debugPrint) {
        MESH_LOGI (TAG, "Reading bytes");
    }

    spi_read_byte (buf2, buf, 4 + numBytes);
    MESH_LOGI (TAG, "read a byte");
    memcpy (data, &buf2[4], numBytes);
    if (debugPrint) {
        for (uint8_t n = 0; n < numBytes; n++) {
            MESH_LOGI (TAG, "DataIn:%02x ", data[n]);
        }
    }

    // wait for BUSY to go low
    WaitForIdle (BUSY_WAIT, "end ReadRegister", 0);
}

// WriteCommand with retry
void WriteCommand (uint8_t cmd, uint8_t *data, uint8_t numBytes) {
    uint8_t status;
    for (int retry = 1; retry < 10; retry++) {
        status = WriteCommand2 (cmd, data, numBytes);
        if (debugPrint) {
            MESH_LOGD (TAG, "status=%02x", status);
        }
        if (status == 0)
            break;
        MESH_LOGW (TAG, "WriteCommand2 status=%02x retry=%d", status, retry);
    }
    if (status != 0) {
        MESH_LOGE (TAG, "SPI Transaction error:0x%02x", status);
        LoRaError (ERR_SPI_TRANSACTION);
    }
}

uint8_t WriteCommand2 (uint8_t cmd, uint8_t *data, uint8_t numBytes) {
    // ensure BUSY is low (state meachine ready)
    WaitForIdle (BUSY_WAIT, "start WriteCommand2", 1);

    if (debugPrint) {
        MESH_LOGI (TAG, "WriteCommand: CMD=0x%02x", cmd);
    }

    // start transfer
    uint8_t buf[16];
    buf[0] = cmd;
    memcpy (&buf[1], data, numBytes);
    spi_read_byte (buf, buf, numBytes + 1);

    uint8_t status = 0;
    uint8_t cmd_status = buf[1] & 0xe;

    switch (cmd_status) {
    case SX126X_STATUS_CMD_TIMEOUT:
    case SX126X_STATUS_CMD_INVALID:
    case SX126X_STATUS_CMD_FAILED:
        status = cmd_status;
        break;

    case 0:
    case 7:
        status = SX126X_STATUS_SPI_FAILED;
        break;
        // default: break; // success
    }

    // wait for BUSY to go low
    WaitForIdle (BUSY_WAIT, "end WriteCommand2", 0);
    return status;
}

void ReadCommand (uint8_t cmd, uint8_t *data, uint8_t numBytes) {
    // ensure BUSY is low (state meachine ready)
    WaitForIdleBegin (BUSY_WAIT, "start ReadCommand");

    if (debugPrint) {
        MESH_LOGI (TAG, "ReadCommand: CMD=0x%02x", cmd);
    }

    // start transfer
    uint8_t buf[16];
    memset (buf, SX126X_CMD_NOP, sizeof (buf));
    buf[0] = cmd;
    spi_read_byte (buf, buf, 1 + numBytes);
    if (data != NULL && numBytes)
        memcpy (data, &buf[1], numBytes);

    // wait for BUSY to go low
    Delay_Ms(1);
    WaitForIdle (BUSY_WAIT, "end ReadCommand", 0);
}