Text menu driven ANSI/VT100 console test utility for LoRa transceivers

radio chip selection

Radio chip driver is not included, allowing choice of radio device.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
if you're using SX1280, then import sx1280 driver into your program.
if you're using LR1110, then import LR1110 driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.
If you're using Type1SJ select target DISCO_L072CZ_LRWAN1 and import sx126x driver into your program.

This is VT100 text-based menu driven test program for SX12xx transceiver devices.
Serial console is divided into horizontally into top half and bottom half.
The bottom half serves as scrolling area to log activity.
The top half serves as menu, to configure the radio.
For all devices, the serial console operates at 115200 8N1, and requires terminal with ANSI-VT100 capability, such as putty/teraterm/minicom etc.
Use program only with keyboard up/down/left/right keys. Enter to change an item, or number for value item. Some items are single bit, requiring only enter key to toggle. Others with fixed choices give a drop-down menu.

radio_sx128x.cpp

Committer:
dudmuck
Date:
2021-09-16
Revision:
14:14b9e1c08bfc
Parent:
13:8ce61a1897ab

File content as of revision 14:14b9e1c08bfc:

#include "radio.h"
#ifdef SX128x_H 

#include <float.h>
using namespace std::chrono;

#if defined(TARGET_FF_ARDUINO) || defined(TARGET_FF_ARDUINO_UNO)    /* pins of SX126xDVK1xAS board */
    #define NRST_PIN        A0
    SPI spi(D11, D12, D13); // mosi, miso, sclk
    //           spi, nss, busy, dio1
    SX128x Radio::radio(spi,  D7,   D3,   D5, NRST_PIN);

    #define LED_ON      1
    #define LED_OFF     0
    DigitalOut tx_led(A4);
    DigitalOut rx_led(A5);


    DigitalOut ant_sw(A3);
    DigitalOut cps(D6); // SE2436L

    bool fe_enable; // SE2436L

    void Radio::chipModeChange()
    {
        if (radio.chipMode == CHIPMODE_NONE) {
            cps = 0;
            tx_led = LED_OFF;
            rx_led = LED_OFF;
        } else if (radio.chipMode == CHIPMODE_TX) {
            cps = fe_enable;
            tx_led = LED_ON;
            rx_led = LED_OFF;
        } else if (radio.chipMode == CHIPMODE_RX) {
            cps = fe_enable;
            tx_led = LED_OFF;
            rx_led = LED_ON;
        }
    }
#endif /* TARGET_FF_ARDUINO */

const char* const Radio::chipNum_str = "SX1280";

uint8_t Radio::tx_param_buf[2];

ModulationParams_t Radio::Radio::mpFLRC, Radio::Radio::mpBLE_GFSK, Radio::mpLORA;
PacketParams_t Radio::ppGFSK, Radio::ppFLRC, Radio::ppLORA, Radio::ppBLE;

const RadioEvents_t* Radio::RadioEvents;
LowPowerTimer Radio::lpt;
uint8_t Radio::pktType;
uint16_t Radio::ppg;

const char* const Radio::pktType_strs[] = {
    "GFSK   ",
    "LORA   ",
    "RANGING",
    "FLRC   ",
    "BLE    ",
    NULL
};

unsigned Radio::pktType_read(bool fw)
{
    return radio.getPacketType();
}

menuMode_e Radio::pktType_write(unsigned idx)
{
    radio.setPacketType(idx);
    return MENUMODE_REINIT_MENU;
}

const char* Radio::tx_ramp_strs[] = {
    "2 ", // 0
    "4 ", // 1
    "6 ", // 2
    "8 ", // 3
    "10", // 4
    "12", // 5
    "16", // 6
    "20",  // 7
    NULL
};

unsigned Radio::tx_ramp_read(bool fw)
{
    PaPwrCtrl_t PaPwrCtrl;
    PaPwrCtrl.octet = radio.readReg(REG_ADDR_PA_PWR_CTRL, 1);

    switch (PaPwrCtrl.bits.ramp_time) {
        case 0: tx_param_buf[1] = RADIO_RAMP_02_US; break;
        case 1: tx_param_buf[1] = RADIO_RAMP_04_US; break;
        case 2: tx_param_buf[1] = RADIO_RAMP_06_US; break;
        case 3: tx_param_buf[1] = RADIO_RAMP_08_US; break;
        case 4: tx_param_buf[1] = RADIO_RAMP_10_US; break;
        case 5: tx_param_buf[1] = RADIO_RAMP_12_US; break;
        case 6: tx_param_buf[1] = RADIO_RAMP_16_US; break;
        case 7: tx_param_buf[1] = RADIO_RAMP_20_US; break;
    }

    return PaPwrCtrl.bits.ramp_time;
}

menuMode_e Radio::tx_ramp_write(unsigned val)
{
    switch (val) {
        case 0: tx_param_buf[1] = RADIO_RAMP_02_US; break;
        case 1: tx_param_buf[1] = RADIO_RAMP_04_US; break;
        case 2: tx_param_buf[1] = RADIO_RAMP_06_US; break;
        case 3: tx_param_buf[1] = RADIO_RAMP_08_US; break;
        case 4: tx_param_buf[1] = RADIO_RAMP_10_US; break;
        case 5: tx_param_buf[1] = RADIO_RAMP_12_US; break;
        case 6: tx_param_buf[1] = RADIO_RAMP_16_US; break;
        case 7: tx_param_buf[1] = RADIO_RAMP_20_US; break;
    }

    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);

    return MENUMODE_REDRAW;
}

#define TX_PWR_OFFSET           18

void Radio::tx_dbm_print()
{
    PaPwrCtrl_t PaPwrCtrl;

    PaPwrCtrl.octet = radio.readReg(REG_ADDR_PA_PWR_CTRL, 1);
    printf("%d", PaPwrCtrl.bits.tx_pwr - TX_PWR_OFFSET);

    tx_param_buf[0] = PaPwrCtrl.bits.tx_pwr;
}

bool Radio::tx_dbm_write(const char* str)
{
    int dbm;
    sscanf(str, "%d", &dbm);

    tx_param_buf[0] = dbm + TX_PWR_OFFSET;
    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);
    return false;
}

const uint8_t ramp_us[] = {
    2,  // 0
    4,  // 1
    6,  // 2
    8,  // 3
    10, // 4
    12, // 5
    16, // 6
    20  // 7
};

const char* const Radio::opmode_status_strs[] = {
    "<0>       ", // 0
    "<1>       ", // 1
    "STDBY_RC  ", // 2
    "STDBY_XOSC", // 3
    "FS        ", // 4
    "RX        ", // 5
    "TX        ", // 6
    "<7>       ", // 7
    NULL
};

const char* const Radio::opmode_select_strs[] = {
    "SLEEP     ", // 0
    "STDBY_RC  ", // 1
    "STDBY_XOSC", // 2
    "FS        ", // 3
    "RX        ", // 4
    "TX        ", // 5
    NULL
};

unsigned Radio::opmode_read(bool forWriting)
{
    status_t status;
    status.octet = radio.xfer(OPCODE_GET_STATUS, 0, 0, NULL);

    if (forWriting) {
        /* translate opmode_status_strs to opmode_select_strs */
        switch (status.bits.chipMode) {
            case 2: return 1;   // STDBY_RC
            case 3: return 2; //STDBY_XOSC
            case 4: return 3; //FS
            case 5: return 4; // RX
            case 6: return 5; // TX
            default: return 0;
        }
    } else
        return status.bits.chipMode;
}

menuMode_e Radio::opmode_write(unsigned sel)
{
    switch (sel) {
        case 0: // SLEEP
            radio.setSleep(true);
            break;
        case 1: // STDBY_RC
            radio.setStandby(STDBY_RC);
            break;
        case 2:  // STDBY_XOSC
            radio.setStandby(STDBY_XOSC);
            break; case 3:  // FS
            radio.setFS();
            break;
        case 4:  // RX
            radio.start_rx(0);
            break;
        case 5:  // TX
            {
                uint8_t buf[3];
                buf[0] = radio.periodBase;
                buf[0] = 0;
                buf[1] = 0;
                radio.xfer(OPCODE_SET_TX, 3, 0, buf);
            }
            break;
    } // ..switch (menuState.sel_idx)

    return MENUMODE_REDRAW;
}

uint8_t Radio::get_payload_length()
{
    uint8_t reg8;

    pktType = radio.getPacketType();

    switch (pktType) {
        case PACKET_TYPE_FLRC:
        case PACKET_TYPE_GFSK:
            reg8 = radio.readReg(REG_ADDR_PAYLOAD_LEN, 1);
            ppGFSK.gfskFLRC.PayloadLength = reg8;
            ppFLRC.gfskFLRC.PayloadLength = reg8;
            return ppFLRC.gfskFLRC.PayloadLength;
        case PACKET_TYPE_RANGING:
        case PACKET_TYPE_LORA:
            ppLORA.lora.PayloadLength = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);
            return ppLORA.lora.PayloadLength;
        case PACKET_TYPE_BLE:
            return 0;   // TODO BLE
    }

    return 0;
}

void Radio::set_payload_length(uint8_t len)
{
    pktType = radio.getPacketType();

    switch (pktType) {
        case PACKET_TYPE_FLRC:
            ppFLRC.gfskFLRC.PayloadLength = len;
            ppGFSK.gfskFLRC.PayloadLength = len;
            radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);
            break;
        case PACKET_TYPE_GFSK:
            ppFLRC.gfskFLRC.PayloadLength = len;
            ppGFSK.gfskFLRC.PayloadLength = len;
            radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
            break;
        case PACKET_TYPE_RANGING:
        case PACKET_TYPE_LORA:
            ppLORA.lora.PayloadLength = len;
            radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
            break;
        case PACKET_TYPE_BLE:
            // TODO BLE
            break;
    }
}

void Radio::tx_payload_length_print()
{
    printf("%u", get_payload_length());
}

bool Radio::tx_payload_length_write(const char* txt)
{
    unsigned len;

    sscanf(txt, "%u", &len);

    set_payload_length(len);

    return false;
}

void Radio::hw_reset()
{
    radio.hw_reset();

    manualRngDelay = false;
}

void Radio::clearIrqFlags()
{
    uint8_t buf[2];
    buf[0] = 0xff;
    buf[1] = 0xff;
    radio.xfer(OPCODE_CLEAR_IRQ_STATUS, 2, 0, buf);
}

void Radio::readChip()
{
    uint8_t reg8;

    reg8 = radio.readReg(REG_ADDR_PKTCTRL0, 1);
    ppGFSK.gfskFLRC.HeaderType = reg8 & 0x20;
    ppFLRC.gfskFLRC.HeaderType = reg8 & 0x20;

    reg8 = radio.readReg(REG_ADDR_PKTCTRL1, 1);
    ppGFSK.gfskFLRC.PreambleLength = reg8 & 0x70;
    ppFLRC.gfskFLRC.PreambleLength = reg8 & 0x70;
    ppGFSK.gfskFLRC.SyncWordLength = reg8 & 0x0e;
    ppFLRC.gfskFLRC.SyncWordLength = reg8 & 0x06;
    if (ppFLRC.gfskFLRC.SyncWordLength == 0x06)
        ppFLRC.gfskFLRC.SyncWordLength = FLRC_SYNC_WORD_LEN_P32S;

    reg8 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);
    ppGFSK.gfskFLRC.SyncWordMatch = reg8 & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = reg8 & 0x70;

    reg8 = radio.readReg(REG_ADDR_PAYLOAD_LEN, 1);
    ppGFSK.gfskFLRC.PayloadLength = reg8;
    ppFLRC.gfskFLRC.PayloadLength = reg8;

    reg8 = radio.readReg(REG_ADDR_PKT_TX_HEADER, 1);    // TODO hi bit of payload length
    ppBLE.ble.ConnectionState = reg8 & 0xe0;
    ppBLE.ble.BleTestPayload = reg8 & 0x1c;

    reg8 = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
    ppBLE.ble.CrcLength = reg8 & 0x30;
    ppBLE.ble.Whitening = reg8 & 0x08;
    ppGFSK.gfskFLRC.CRCLength = reg8 & 0x30;
    ppFLRC.gfskFLRC.CRCLength = reg8 & 0x30;
    ppGFSK.gfskFLRC.Whitening = reg8 & 0x08;
    ppFLRC.gfskFLRC.Whitening = reg8 & 0x08;

    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);
    switch (LoRaPktPar0.bits.modem_bw) {
        case 2: mpLORA.lora.bandwidth = LORA_BW_200; break;
        case 3: mpLORA.lora.bandwidth = LORA_BW_400; break;
        case 4: mpLORA.lora.bandwidth = LORA_BW_800; break;
        case 5: mpLORA.lora.bandwidth = LORA_BW_1600; break;
    }
    mpLORA.lora.spreadingFactor = LoRaPktPar0.bits.modem_sf << 4;

    {
        LoRaPktPar1_t LoRaPktPar1;
        LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
        mpLORA.lora.codingRate = LoRaPktPar1.bits.coding_rate;
        ppLORA.lora.InvertIQ = LoRaPktPar1.bits.rxinvert_iq ? LORA_IQ_INVERTED : LORA_IQ_STD;
        ppLORA.lora.HeaderType = LoRaPktPar1.bits.implicit_header ? IMPLICIT_HEADER : EXPLICIT_HEADER;
        // LoRaPktPar1.bits.ppm_offset
    }

    {
        LoRaPreambleReg_t LoRaPreambleReg;
        LoRaPreambleReg.octet = radio.readReg(REG_ADDR_LORA_PREAMBLE, 1);
        ppLORA.lora.PreambleLength = LoRaPreambleReg.bits.preamble_symb1_nb * (1 << LoRaPreambleReg.bits.preamble_symb_nb_exp);
    }
    ppLORA.lora.PayloadLength = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);

    {
        LoRaLrCtl_t LoRaLrCtl;
        LoRaLrCtl.octet = radio.readReg(REG_ADDR_LORA_LRCTL, 1);
        ppLORA.lora.crc = LoRaLrCtl.octet & 0x20; // LoRaLrCtl.bits.crc_en
    }

    {
        RegRxBw_t RegRxBw;
        unsigned bps;
        FloraPreambleHi_t FloraPreambleHi;
        float mi, fdev_hz;
        unsigned freqDev;
        FskModDfH_t FskModDfH;
        FskModDfH.octet = radio.readReg(REG_ADDR_FSK_MODDFH, 1);
        freqDev = FskModDfH.bits.freqDev;
        freqDev <<= 8;
        freqDev |= radio.readReg(REG_ADDR_FSK_MODDFL, 1);
        //printf("freqDev %x, %x\r\n", freqDev, freqDev);
        fdev_hz = freqDev * PLL_STEP_HZ;
        //printf("fdev hz:%f\r\n", fdev_hz);

        FloraPreambleHi.octet = radio.readReg(REG_ADDR_FLORA_PREAMBLE_HI, 1);
        switch (FloraPreambleHi.bits.data_rate) {
            case 0:
                bps = 2.0e6;
                //mpFLRC.flrc.bitrateBandwidth = ??; // 2.6
                break;
            case 1:
                bps = 1.6e6;
                //mpFLRC.flrc.bitrateBandwidth = ??; // 2.08
                break;
            case 2:
                bps = 1.0e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_300_BW_1_2; // 1.3
                break;
            case 3:
                bps = 0.8e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_000_BW_1_2; // 1.04
                break;
            case 4:
                bps = 0.5e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_650_BW_0_6; // 0.65
                break;
            case 5:
                bps = 0.4e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_520_BW_0_6; // 0.52
                break;
            case 6:
                bps = 0.25e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_325_BW_0_3; // 0.325
                break;
            case 7:
                bps = 0.125e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_260_BW_0_3; // 0.26
                break;
        }

        mi = (fdev_hz * 2.0) / bps;
        if (mi > 0.35) {
            mi -= 0.5;
            mi /= 0.25;
            mpBLE_GFSK.gfskBle.ModulationIndex = ((uint8_t)mi) + 1;
        } else
            mpBLE_GFSK.gfskBle.ModulationIndex = 0;

        RegRxBw.octet = radio.readReg(REG_ADDR_RXBW, 1);

        //printf("rx ");
        switch (RegRxBw.bits.bw) {
            case 0:
                //printf("2.4");
                if (FloraPreambleHi.bits.data_rate == 0)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_2_000_BW_2_4;
                if (FloraPreambleHi.bits.data_rate == 1)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_600_BW_2_4;
                if (FloraPreambleHi.bits.data_rate == 2)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_2_4;
                if (FloraPreambleHi.bits.data_rate == 3)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_2_4;
                break;
            case 1:
                //printf("1.2");
                if (FloraPreambleHi.bits.data_rate == 2)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_1_2;
                if (FloraPreambleHi.bits.data_rate == 3)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_1_2;
                if (FloraPreambleHi.bits.data_rate == 4)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_1_2;
                if (FloraPreambleHi.bits.data_rate == 5)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_1_2;
                break;
            case 2:
                //printf("0.6");
                if (FloraPreambleHi.bits.data_rate == 4)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_0_6;
                if (FloraPreambleHi.bits.data_rate == 5)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_0_6;
                if (FloraPreambleHi.bits.data_rate == 6)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_6;
                break;
            case 3:
                //printf("0.3");
                if (FloraPreambleHi.bits.data_rate == 6)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_3;
                if (FloraPreambleHi.bits.data_rate == 7)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_125_BW_0_3;
                break;
        }
        //printf("MHz bw:%u\r\n", RegRxBw.bits.bw);
        mpBLE_GFSK.gfskBle.bitrateBandwidth = reg8;
    }

    {
        FskCfg_t FskCfg;
        FskCfg.octet = radio.readReg(REG_ADDR_FSK_CFG, 1);
        //printf("gf_bt:%u\r\n", FskCfg.bits.gf_bt);
        mpBLE_GFSK.gfskBle.ModulationShaping = FskCfg.bits.gf_bt << 4;
        mpFLRC.flrc.ModulationShaping = mpBLE_GFSK.gfskBle.ModulationShaping;
    }

    {
        PktBitStreamCtrl_t PktBitStreamCtrl;
        PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
        mpFLRC.flrc.CodingRate = PktBitStreamCtrl.octet & 0x06; // PktBitStreamCtrl.bits.flora_coding_rate 
    }

}

void Radio::rxDone(uint8_t size, const pktStatus_t* pktStatus)
{
    float rssi, snr;

    if (pktStatus->ble_gfsk_flrc.sync.syncAddrsCode == 0) {
        int8_t s = pktStatus->lora.snr;
        rssi = -pktStatus->lora.rssiSync / 2.0;
        snr = s / 4.0;
    } else {
        rssi = -pktStatus->ble_gfsk_flrc.rssiSync / 2.0;
        snr = FLT_MIN;
    }

    RadioEvents->RxDone(size, rssi, snr);
}

void Radio::txDoneBottom()
{
    if (RadioEvents->TxDone_botHalf)
        RadioEvents->TxDone_botHalf();
}

/*void Radio::cadDone(bool det)
{
    log_printf("cadDone ");
    if (det)
        printf("CadDetected");

    printf("\r\n");
}*/

void Radio::boardInit(const RadioEvents_t* e)
{
    hw_reset();

    radio.txDone = txDoneBottom;
    radio.rxDone = rxDone;
    radio.cadDone = cadDone;

    radio.chipModeChange = chipModeChange;

    readChip();

    RadioEvents = e;

    fe_enable = true;

    lpt.start();
}

void Radio::tx_carrier()
{
    radio.xfer(OPCODE_SET_TX_CARRIER, 0, 0, NULL);

    radio.chipMode = CHIPMODE_TX;
    chipModeChange();
}

void Radio::tx_preamble()
{
    radio.xfer(OPCODE_SET_TX_PREAMBLE, 0, 0, NULL);

    radio.chipMode = CHIPMODE_TX;
    chipModeChange();
}

void Radio::rngTx()
{
    IrqFlags_t irqEnable;
    uint8_t buf[8];

    if (!manualRngDelay)
        rngUpdateDelayCal();

    irqEnable.word = 0;
    irqEnable.bits.TxDone = 1;
    irqEnable.bits.RxTxTimeout = 1;
    irqEnable.bits.RangingMasterTimeout = 1;
    irqEnable.bits.RangingMasterResultValid = 1;
    irqEnable.bits.RangingMasterRequestValid = 1;

    buf[0] = irqEnable.word >> 8;    // enable bits
    buf[1] = irqEnable.word; // enable bits

    irqEnable.bits.RangingMasterTimeout = 0;
    irqEnable.bits.RangingMasterResultValid = 0;
    irqEnable.bits.RangingMasterRequestValid = 0;
    buf[2] = irqEnable.word >> 8;     // dio1
    buf[3] = irqEnable.word;  // dio1

    buf[4] = 0; // dio2
    buf[5] = 0; // dio2
    buf[6] = 0; // dio3
    buf[7] = 0; // dio3
    radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);

    log_printf("rngTx\r\n");

    buf[0] = radio.periodBase;
    /* no timeout */
    buf[1] = 0;
    buf[2] = 0;
    radio.xfer(OPCODE_SET_TX, 3, 0, buf);

    radio.chipMode = CHIPMODE_TX;
    chipModeChange();
}

void Radio::txPkt()
{
    uint8_t txlen = 0;

    pktType = radio.getPacketType();

    switch (pktType) {
        case PACKET_TYPE_FLRC:
        case PACKET_TYPE_GFSK:
            txlen = radio.readReg(REG_ADDR_PAYLOAD_LEN, 1);
            break;
        case PACKET_TYPE_BLE:
            return; // TODO BLE
        case PACKET_TYPE_RANGING:
            rngTx();
            return;
        case PACKET_TYPE_LORA:
            txlen = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);
            break;
    }
    log_printf("txPkt%u\r\n", txlen);

    radio.setBufferBase(0, 0);

    radio.start_tx(txlen, 0);

}

#define REGBW_2_4MHZ    0
#define REGBW_1_2MHZ    1
#define REGBW_0_6MHZ    2
#define REGBW_0_3MHZ    3

unsigned Radio::gfsk_flrc_bpsbw_read(bool fw)
{
    unsigned n = UINT_MAX;
    RegRxBw_t RegRxBw;
    FloraPreambleHi_t FloraPreambleHi;
    pktType = radio.getPacketType();

    FloraPreambleHi.octet = radio.readReg(REG_ADDR_FLORA_PREAMBLE_HI, 1);

    RegRxBw.octet = radio.readReg(REG_ADDR_RXBW, 1);

    if (pktType == PACKET_TYPE_GFSK) {
        switch (FloraPreambleHi.bits.data_rate) {
            case 0:
                n = 0;  // 2Mbps, 2.4MHz
                break;
            case 1:
                n = 1;  // 1.6Mbps, 2.4MHz
                break;
            case 2:
                if (RegRxBw.bits.bw == REGBW_2_4MHZ)
                    n = 2;
                else if (RegRxBw.bits.bw == REGBW_1_2MHZ)
                    n = 3;
                break;
            case 3:
                if (RegRxBw.bits.bw == REGBW_2_4MHZ) // 0.8Mbps  2.4   2.4MHz
                    n = 4;
                else if (RegRxBw.bits.bw == REGBW_1_2MHZ) // 0.8Mbps  1.2MHz
                    n = 5;
                break;
            case 4:
                if (RegRxBw.bits.bw == REGBW_1_2MHZ)    // 0.5Mbps  1.2MHz
                    n = 6;
                else if (RegRxBw.bits.bw == REGBW_0_6MHZ)   // 0.5Mbps  0.6MHz
                    n = 7;
                break;
            case 5:
                if (RegRxBw.bits.bw == REGBW_1_2MHZ)    // 0.4Mbps  1.2MHz
                    n = 8;
                else if (RegRxBw.bits.bw == REGBW_0_6MHZ)   // 0.4Mbps  0.6MHz
                    n = 9;
                break;
            case 6:
                if (RegRxBw.bits.bw == REGBW_0_6MHZ)    // 0.25Mbps  0.6MHz
                    n = 10;
                else if (RegRxBw.bits.bw == REGBW_0_3MHZ)   // 0.25Mbps  0.3MHz
                    n = 11;
                break;
            case 7:
                n = 12; // 0.125Mbps, assume bw=0.3MHz
                break;
        } // ..switch (FloraPreambleHi.bits.data_rate)
    } else if (pktType == PACKET_TYPE_FLRC) {
        n = FloraPreambleHi.bits.data_rate;
        // datarate, bits 5,6,7..    bw bits 0,1,2
        switch (FloraPreambleHi.bits.data_rate) {
            case 0:
                break;
            case 1:
                break;
            case 2:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_300_BW_1_2;
                break;
            case 3:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_000_BW_1_2;
                break;
            case 4:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_650_BW_0_6;
                break;
            case 5:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_520_BW_0_6;
                break;
            case 6:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_325_BW_0_3;
                break;
            case 7:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_260_BW_0_3;
                break;
        } // ..switch (FloraPreambleHi.bits.data_rate)
    }

    return n;
}

menuMode_e Radio::gfsk_flrc_bpsbw_write(unsigned sidx)
{
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_GFSK) {
        switch (sidx) {
            case 0: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_2_000_BW_2_4; break;
            case 1: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_600_BW_2_4; break;
            case 2: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_2_4; break;
            case 3: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_1_2; break;
            case 4: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_2_4; break;
            case 5: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_1_2; break;
            case 6: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_1_2; break;
            case 7: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_0_6; break;
            case 8: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_1_2; break;
            case 9: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_0_6; break;
            case 10: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_6; break;
            case 11: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_3; break;
            case 12: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_125_BW_0_3; break;
        }
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpBLE_GFSK.buf);
    } else if (pktType == PACKET_TYPE_FLRC) {
        switch (sidx) {
            case 2: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_300_BW_1_2; break;
            case 3: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_000_BW_1_2; break;
            case 4: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_650_BW_0_6; break;
            case 5: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_520_BW_0_6; break;
            case 6: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_325_BW_0_3; break;
            case 7: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_260_BW_0_3; break;
            default:
                return MENUMODE_REDRAW;
        }
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpFLRC.buf);
    }

    return MENUMODE_REDRAW;
}

static const char* const gfsk_bpsbw[] = {
    "  2.0Mbps 2.4MHz", //0 GFSK_BLE_BR_2_000_BW_2_4    0x04 // Mbps:2      bw:2.4MHz
    "  1.6Mbps 2.4MHz", //1 GFSK_BLE_BR_1_600_BW_2_4    0x28 // Mbps:1.6    bw:2.4MHz
    "  1.0Mbps 2.4MHz", //2 GFSK_BLE_BR_1_000_BW_2_4    0x4C // Mbps:1      bw:2.4MHz
    "  1.0Mbps 1.2MHz", //3 GFSK_BLE_BR_1_000_BW_1_2    0x45 // Mbps:1      bw:1.2MHz
    "  0.8Mbps 2.4MHz", //4 GFSK_BLE_BR_0_800_BW_2_4    0x70 // Mbps:0.8    bw:2.4MHz
    "  0.8Mbps 1.2MHz", //5 GFSK_BLE_BR_0_800_BW_1_2    0x69 // Mbps:0.8    bw:1.2MHz
    "  0.5Mbps 1.2MHz", //6 GFSK_BLE_BR_0_500_BW_1_2    0x8D // Mbps:0.5    bw:1.2MHz
    "  0.5Mbps 0.6MHz", //7 GFSK_BLE_BR_0_500_BW_0_6    0x86 // Mbps:0.5    bw:0.6MHz
    "  0.4Mbps 1.2MHz", //8 GFSK_BLE_BR_0_400_BW_1_2    0xB1 // Mbps:0.4    bw:1.2MHz
    "  0.4Mbps 0.6MHz", //9 GFSK_BLE_BR_0_400_BW_0_6    0xAA // Mbps:0.4    bw:0.6MHz
    " 0.25Mbps 0.6MHz", //10 GFSK_BLE_BR_0_250_BW_0_6    0xCE // Mbps:0.25   bw:0.6MHz
    " 0.25Mbps 0.3MHz", //11 GFSK_BLE_BR_0_250_BW_0_3    0xC7 // Mbps:0.25   bw:0.3MHz
    "0.125Mbps 0.3MHz", //12 GFSK_BLE_BR_0_125_BW_0_3    0xEF // Mbps:0.125  bw:0.3MHz
//   01234567890123456
    NULL
};

const dropdown_item_t Radio::gfsk_bitrate_item = { _ITEM_DROPDOWN, gfsk_bpsbw, gfsk_bpsbw, gfsk_flrc_bpsbw_read, gfsk_flrc_bpsbw_write};

void Radio::modindex_print()
{
    float mi, Mbps, fdev_hz;
    unsigned bps, freqDev;
    FskModDfH_t FskModDfH;
    FloraPreambleHi_t FloraPreambleHi;

    FloraPreambleHi.octet = radio.readReg(REG_ADDR_FLORA_PREAMBLE_HI, 1);
    switch (FloraPreambleHi.bits.data_rate) {
        case 0: Mbps = 2.0; break;
        case 1: Mbps = 1.6; break;
        case 2: Mbps = 1.0; break;
        case 3: Mbps = 0.8; break;
        case 4: Mbps = 0.5; break;
        case 5: Mbps = 0.4; break;
        case 6: Mbps = 0.25; break;
        case 7: Mbps = 0.125; break;
    }

    FskModDfH.octet = radio.readReg(REG_ADDR_FSK_MODDFH, 1);
    freqDev = FskModDfH.bits.freqDev;
    freqDev <<= 8;
    freqDev |= radio.readReg(REG_ADDR_FSK_MODDFL, 1);

    fdev_hz = freqDev * PLL_STEP_HZ;
    bps = Mbps * 1e6;
    mi = (fdev_hz * 2.0) / bps;

    printf("%.2f", mi);
}

bool Radio::modindex_write(const char* valStr)
{
    float f;

    if (sscanf(valStr, "%f", &f) == 1) {
        log_printf("scanned %f from \"%s\"\r\n", f);
        if (f > 0.35) {
            f -= 0.5;
            f /= 0.25;
            log_printf("set modindex:%f\r\n", f);
            mpBLE_GFSK.gfskBle.ModulationIndex = ((uint8_t)f) + 1;
            log_printf("to set %02x\r\n", mpBLE_GFSK.gfskBle.ModulationIndex);
        } else
            mpBLE_GFSK.gfskBle.ModulationIndex = 0;

        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpBLE_GFSK.buf);
    }
    return false;
}

const value_item_t Radio::gfsk_modindex_item = { _ITEM_VALUE, 5, modindex_print, modindex_write };

static const char* const gfsk_flrc_bts[] = {
    "off",
    "1.0",
    "0.5",
    "0.3",
    NULL
};

unsigned Radio::gfsk_flrc_bt_read(bool fw)
{
    FskCfg_t FskCfg;
    FskCfg.octet = radio.readReg(REG_ADDR_FSK_CFG, 1);
    return FskCfg.bits.gf_bt;
}

menuMode_e Radio::gfsk_flrc_bt_write(unsigned sidx)
{
    mpBLE_GFSK.gfskBle.ModulationShaping = sidx << 4;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpBLE_GFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_flrc_bt_item = { _ITEM_DROPDOWN, gfsk_flrc_bts, gfsk_flrc_bts, gfsk_flrc_bt_read, gfsk_flrc_bt_write};

static const char* const gfsk_flrc_pblLens[] = {
    "4",
    "8",
    "12",
    "16",
    "20",
    "24",
    "28",
    "32",
    NULL,
};

unsigned Radio::gfsk_flrc_pl_read(bool fw)
{
    PktCtrl1_t PktCtrl1;
    PktCtrl1.octet = radio.readReg(REG_ADDR_PKTCTRL1, 1);
    ppFLRC.gfskFLRC.PreambleLength = PktCtrl1.octet & 0x70;
    ppGFSK.gfskFLRC.PreambleLength = ppFLRC.gfskFLRC.PreambleLength;
    return PktCtrl1.gfsk.preamble_len;
}

menuMode_e Radio::gfsk_flrc_pl_write(unsigned sidx)
{
    ppFLRC.gfskFLRC.PreambleLength = sidx << 4;
    ppGFSK.gfskFLRC.PreambleLength = ppFLRC.gfskFLRC.PreambleLength;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_flrc_preamble_item = { _ITEM_DROPDOWN, gfsk_flrc_pblLens, gfsk_flrc_pblLens, gfsk_flrc_pl_read, gfsk_flrc_pl_write};

static const char* const gfsk_syncLens[] = {
    "1", // 0
    "2", // 1
    "3", // 2
    "4", // 3
    "5", // 4
    NULL,
};

unsigned Radio::gfsk_synclen_read(bool fw)
{
    PktCtrl1_t PktCtrl1;
    PktCtrl1.octet = radio.readReg(REG_ADDR_PKTCTRL1, 1);

    ppGFSK.gfskFLRC.SyncWordLength = PktCtrl1.octet & 0x0e;
    ppFLRC.gfskFLRC.SyncWordLength = PktCtrl1.octet & 0x06;

    return PktCtrl1.gfsk.sync_adrs_len;
}

menuMode_e Radio::gfsk_synclen_write(unsigned sidx)
{
    ppGFSK.gfskFLRC.SyncWordLength = sidx << 1;
    //log_printf("SWL %u %02x\r\n", sidx, ppGFSK.gfskFLRC.SyncWordLength);
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    //return MENUMODE_NONE;
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_synclen_item = { _ITEM_DROPDOWN, gfsk_syncLens, gfsk_syncLens, gfsk_synclen_read, gfsk_synclen_write};

bool Radio::gfsk_flrc_fixvar_read()
{
    PktCtrl0_t PktCtrl0;
    PktCtrl0.octet = radio.readReg(REG_ADDR_PKTCTRL0, 1);

    ppGFSK.gfskFLRC.HeaderType = PktCtrl0.octet & 0x20;
    ppFLRC.gfskFLRC.HeaderType = PktCtrl0.octet & 0x20;

    return PktCtrl0.bits.pkt_len_format;
}

bool Radio::gfsk_flrc_fixvar_push()
{
    PacketParams_t* pp;
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_FLRC)
        pp = &ppFLRC;
    else if (pktType == PACKET_TYPE_GFSK)
        pp = &ppGFSK;
    else
        return false;

    if (pp->gfskFLRC.HeaderType == RADIO_PACKET_VARIABLE_LENGTH)
        pp->gfskFLRC.HeaderType = RADIO_PACKET_FIXED_LENGTH;
    else
        pp->gfskFLRC.HeaderType = RADIO_PACKET_VARIABLE_LENGTH;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, pp->buf);

    return pp->gfskFLRC.HeaderType == RADIO_PACKET_VARIABLE_LENGTH;
}

const toggle_item_t Radio::gfsk_flrc_fixvar_item = { _ITEM_TOGGLE,
    "fixed   ",
    "variable",
    gfsk_flrc_fixvar_read, gfsk_flrc_fixvar_push
};

static const char* const gfsk_flrc_crclens[] = {
    "0 off",
    "1 byte",
    "2 bytes",
    NULL,
};

unsigned Radio::gfsk_flrc_crclen_read(bool fw)
{
    PktBitStreamCtrl_t PktBitStreamCtrl;
    PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
    ppGFSK.gfskFLRC.CRCLength = PktBitStreamCtrl.octet & 0x30;
    return PktBitStreamCtrl.bits.crc_mode;
}

menuMode_e Radio::gfsk_flrc_crclen_write(unsigned sidx)
{
    pktType = radio.getPacketType();

    ppGFSK.gfskFLRC.CRCLength = (sidx & 3) << 4;
    ppFLRC.gfskFLRC.CRCLength = (sidx & 3) << 4;

    if (pktType == PACKET_TYPE_GFSK)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    else if (pktType == PACKET_TYPE_FLRC)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);

    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_flrc_crclen_item = { _ITEM_DROPDOWN, gfsk_flrc_crclens, gfsk_flrc_crclens, gfsk_flrc_crclen_read, gfsk_flrc_crclen_write};

bool Radio::gfsk_flrc_whit_read()
{
    PktBitStreamCtrl_t PktBitStreamCtrl;
    PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
    ppGFSK.gfskFLRC.Whitening = PktBitStreamCtrl.octet & 0x08;
    ppFLRC.gfskFLRC.Whitening = PktBitStreamCtrl.octet & 0x08;
    return PktBitStreamCtrl.bits.whit_disable;
}

bool Radio::gfsk_flrc_whit_push()
{
    pktType = radio.getPacketType();

    if (ppGFSK.gfskFLRC.Whitening == WHITENING_DISABLE)
        ppGFSK.gfskFLRC.Whitening = WHITENING_ENABLE;
    else
        ppGFSK.gfskFLRC.Whitening = WHITENING_DISABLE;

    ppFLRC.gfskFLRC.Whitening = ppGFSK.gfskFLRC.Whitening;

    if (pktType == PACKET_TYPE_GFSK)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    else if (pktType == PACKET_TYPE_FLRC)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);

    return ppGFSK.gfskFLRC.Whitening == WHITENING_DISABLE;
}

const toggle_item_t Radio::gfsk_flrc_whit_item = { _ITEM_TOGGLE,
    "ENABLE ",
    "DISABLE",
    gfsk_flrc_whit_read, gfsk_flrc_whit_push
};

void Radio::gfsk_flrc_crcinit_print()
{
    unsigned val = radio.readReg(0x9c8, 2);
    printf("0x%04x", val);
}

bool Radio::gfsk_flrc_crcinit_write(const char* txt)
{
    unsigned val;
    sscanf(txt, "%x", &val);
    radio.writeReg(0x9c8, val, 2);
    return false;
}

const value_item_t Radio::gfsk_flrc_crcinit_item = { _ITEM_VALUE, 7, gfsk_flrc_crcinit_print, gfsk_flrc_crcinit_write };

void Radio::gfsk_flrc_crcpoly_print(void)
{
    unsigned val = radio.readReg(0x9c6, 2);
    printf("0x%04x", val);
}

bool Radio::gfsk_flrc_crcpoly_write(const char* txt)
{
    unsigned val;
    sscanf(txt, "%x", &val);
    radio.writeReg(0x9c6, val, 2);
    return false;
}

const value_item_t Radio::gfsk_flrc_crcpoly_item = { _ITEM_VALUE, 7, gfsk_flrc_crcpoly_print, gfsk_flrc_crcpoly_write };

bool Radio::gfsk_flrc_sync1en_read()
{
    PktSyncAdrs_t PktSyncAdrs;
    PktSyncAdrs.octet = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);

    ppGFSK.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;

    return PktSyncAdrs.gfskflrc.sync_addr_mask & 1;
}

bool Radio::gfsk_flrc_sync2en_read()
{
    PktSyncAdrs_t PktSyncAdrs;
    PktSyncAdrs.octet = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);

    ppGFSK.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x20;
}

bool Radio::gfsk_flrc_sync3en_read()
{
    PktSyncAdrs_t PktSyncAdrs;
    PktSyncAdrs.octet = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);

    ppGFSK.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x40;
    //return PktSyncAdrs.gfskflrc.sync_addr_mask & 4;
}

bool Radio::gfsk_flrc_sync1en_push()
{
    if (ppGFSK.gfskFLRC.SyncWordMatch & 0x10)
        ppGFSK.gfskFLRC.SyncWordMatch &= ~0x10;
    else
        ppGFSK.gfskFLRC.SyncWordMatch |= 0x10;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);

    ppFLRC.gfskFLRC.SyncWordMatch = ppGFSK.gfskFLRC.SyncWordMatch;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x10;
}

bool Radio::gfsk_flrc_sync2en_push()
{
    if (ppGFSK.gfskFLRC.SyncWordMatch & 0x20)
        ppGFSK.gfskFLRC.SyncWordMatch &= ~0x20;
    else
        ppGFSK.gfskFLRC.SyncWordMatch |= 0x20;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);

    ppFLRC.gfskFLRC.SyncWordMatch = ppGFSK.gfskFLRC.SyncWordMatch;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x20;
}

bool Radio::gfsk_flrc_sync3en_push()
{
    if (ppGFSK.gfskFLRC.SyncWordMatch & 0x40)
        ppGFSK.gfskFLRC.SyncWordMatch &= ~0x40;
    else
        ppGFSK.gfskFLRC.SyncWordMatch |= 0x40;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);

    ppFLRC.gfskFLRC.SyncWordMatch = ppGFSK.gfskFLRC.SyncWordMatch;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x40;
}

const toggle_item_t Radio::gfsk_flrc_sync1en_item = { _ITEM_TOGGLE, "off:", " ON:", gfsk_flrc_sync1en_read, gfsk_flrc_sync1en_push};
const toggle_item_t Radio::gfsk_flrc_sync2en_item = { _ITEM_TOGGLE, "off:", " ON:", gfsk_flrc_sync2en_read, gfsk_flrc_sync2en_push};
const toggle_item_t Radio::gfsk_flrc_sync3en_item = { _ITEM_TOGGLE, "off:", " ON:", gfsk_flrc_sync3en_read, gfsk_flrc_sync3en_push};

void Radio::gfsk_flrc_sync1_print(void)
{
    uint64_t val;
    uint32_t val32 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_1+1, 4);
    uint32_t upper = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_1, 1);
    val = upper;
    val <<= 32;
    val |= val32;
    printf("%llx", val);
}

void Radio::gfsk_flrc_sync2_print(void)
{
    uint64_t val;
    uint32_t val32 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_2+1, 4);
    uint32_t upper = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_2, 1);
    val = upper;
    val <<= 32;
    val |= val32;
    printf("%llx", val);
}

void Radio::gfsk_flrc_sync3_print(void)
{
    uint64_t val;
    uint32_t val32 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_3+1, 4);
    uint32_t upper = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_3, 1);
    val = upper;
    val <<= 32;
    val |= val32;
    printf("%llx", val);
}

bool Radio::gfsk_flrc_sync1_write(const char* txt)
{
    uint32_t val32, upper;
    uint64_t val;
    sscanf(txt, "%llx", &val);

    val32 = val;
    val >>= 32;
    upper = val;

    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_1, upper, 1);
    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_1+1, val32, 4);

    return false;
}

bool Radio::gfsk_flrc_sync2_write(const char* txt)
{
    uint32_t val32, upper;
    uint64_t val;
    sscanf(txt, "%llx", &val);

    val32 = val;
    val >>= 32;
    upper = val;

    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_2, upper, 1);
    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_2+1, val32, 4);

    return false;
}

bool Radio::gfsk_flrc_sync3_write(const char* txt)
{
    uint32_t val32, upper;
    uint64_t val;
    sscanf(txt, "%llx", &val);

    val32 = val;
    val >>= 32;
    upper = val;

    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_3, upper, 1);
    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_3+1, val32, 4);

    return false;
}

const value_item_t Radio::gfsk_flrc_sync1_item = { _ITEM_VALUE, 10, gfsk_flrc_sync1_print, gfsk_flrc_sync1_write };
const value_item_t Radio::gfsk_flrc_sync2_item = { _ITEM_VALUE, 10, gfsk_flrc_sync2_print, gfsk_flrc_sync2_write };
const value_item_t Radio::gfsk_flrc_sync3_item = { _ITEM_VALUE, 10, gfsk_flrc_sync3_print, gfsk_flrc_sync3_write };


const menu_t Radio::gfsk_menu[] = {
    { {FIRST_CHIP_MENU_ROW+1,  1},              NULL,  &gfsk_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 19},      "mod index:", &gfsk_modindex_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 35},             "BT:",  &gfsk_flrc_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 43}, "PreambleLength:", &gfsk_flrc_preamble_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 61},     "SyncLength:",  &gfsk_synclen_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2, 1},        "Length:", &gfsk_flrc_fixvar_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 18},    "crcLength:",   &gfsk_flrc_crclen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 37},    "whitening:", &gfsk_flrc_whit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 57},      "crcInit:",  &gfsk_flrc_crcinit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 72},         "poly:",  &gfsk_flrc_crcpoly_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1}, "sync1 ", &gfsk_flrc_sync1en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 15},     NULL,   &gfsk_flrc_sync1_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 27}, "sync2 ", &gfsk_flrc_sync2en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 41},     NULL,   &gfsk_flrc_sync2_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 53}, "sync3 ", &gfsk_flrc_sync3en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 65},     NULL,   &gfsk_flrc_sync3_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

static const char* const flrc_bpsbw[] = {
    "2.6",
    "2.08",
    "1.3Mb/s   1.2MHz",
    "1.04Mb/s  1.2MHz",
    "0.65Mb/s  0.6MHz",
    "0.52Mb/s  0.6MHz",
    "0.325Mb/s 0.3MHz",
    "0.26Mb/s  0.3MHz",
    NULL
};

const dropdown_item_t Radio::flrc_bitrate_item = { _ITEM_DROPDOWN, flrc_bpsbw, flrc_bpsbw, gfsk_flrc_bpsbw_read, gfsk_flrc_bpsbw_write};

static const char* const flrc_crs[] = {
    "1/2",
    "3/4",
    "1  ",
    NULL
};
unsigned Radio::flrc_cr_read(bool fw)
{
    PktBitStreamCtrl_t PktBitStreamCtrl;
    PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);

    mpFLRC.flrc.CodingRate = PktBitStreamCtrl.octet & 0x06; // PktBitStreamCtrl.bits.flora_coding_rate 

    return PktBitStreamCtrl.bits.flora_coding_rate;
}

menuMode_e Radio::flrc_cr_write(unsigned sidx)
{
    mpFLRC.flrc.CodingRate = sidx << 1;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpFLRC.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::flrc_cr_item = { _ITEM_DROPDOWN, flrc_crs, flrc_crs, flrc_cr_read, flrc_cr_write};

static const char* const flrc_syncLens[] = {
    "SYNC OFF  ",
    "16BIT SYNC",
    "32BIT SYNC",
    NULL
};

unsigned Radio::flrc_synclen_read(bool fw)
{
    PktCtrl1_t PktCtrl1;
    PktCtrl1.octet = radio.readReg(REG_ADDR_PKTCTRL1, 1);
    ppFLRC.gfskFLRC.SyncWordLength = PktCtrl1.octet & 0x06;
    if (ppFLRC.gfskFLRC.SyncWordLength == 0x06) {
        ppFLRC.gfskFLRC.SyncWordLength = FLRC_SYNC_WORD_LEN_P32S;
        return 2;
    }

    return PktCtrl1.flrc.sync_adrs_len;
}

menuMode_e Radio::flrc_synclen_write(unsigned sidx)
{
    ppFLRC.gfskFLRC.SyncWordLength = sidx << 1;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::flrc_synclen_item = { _ITEM_DROPDOWN, flrc_syncLens, flrc_syncLens, flrc_synclen_read, flrc_synclen_write};


const menu_t Radio::flrc_menu[] = {
    { {FIRST_CHIP_MENU_ROW+1,  1},              NULL,  &flrc_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 20},             "cr:",       &flrc_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 27},             "BT:",      &gfsk_flrc_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 34}, "PreambleLength:", &gfsk_flrc_preamble_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 52},     "SyncLength:",  &flrc_synclen_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2, 1},        "Length:", &gfsk_flrc_fixvar_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 18},    "crcLength:",   &gfsk_flrc_crclen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 38},    "whitening:", &gfsk_flrc_whit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 58},      "crcInit:",  &gfsk_flrc_crcinit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 73},         "poly:",  &gfsk_flrc_crcpoly_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1}, "sync1 ", &gfsk_flrc_sync1en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 12},     NULL,   &gfsk_flrc_sync1_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 24}, "sync2 ", &gfsk_flrc_sync2en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 35},     NULL,   &gfsk_flrc_sync2_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 47}, "sync3 ", &gfsk_flrc_sync3en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 59},     NULL,   &gfsk_flrc_sync3_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

bool Radio::manualRngDelay;
const uint16_t Radio::rngDelays[3][6] = {
    10299, 10271, 10244, 10242, 10230, 10246,
    11486, 11474, 11453, 11426, 11417, 11401,
    13308, 13493, 13528, 13515, 13430, 13376
};

void Radio::rngUpdateDelayCal()
{
    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);

    if (LoRaPktPar0.bits.modem_bw > 2 && LoRaPktPar0.bits.modem_sf < 11) {
        uint32_t delayCal = radio.readReg(REG_ADDR_LORA_DELAY_CAL, 3);
        delayCal &= ~0x3fffff;
        delayCal |= rngDelays[LoRaPktPar0.bits.modem_bw-3][LoRaPktPar0.bits.modem_sf-5];
        /*log_printf("%u = rngDelays[%u][%u]\r\n",
            rngDelays[LoRaPktPar0.bits.modem_bw-3][LoRaPktPar0.bits.modem_sf-5],
            LoRaPktPar0.bits.modem_bw-3, LoRaPktPar0.bits.modem_sf-5
        );*/
        radio.writeReg(REG_ADDR_LORA_DELAY_CAL, delayCal, 3);
    }
}

static const char* const lora_bws[] = {
    "  50KHz", // 0
    " 100KHz", // 1
    " 200KHz", // 2
    " 400KHz", // 3
    " 800KHz", // 4
    "1600KHz", // 5
    NULL
};

const float Radio::bwMHzs[] = { 0.05, 0.1, 0.2, 0.4, 0.8, 1.6 };

LoRaPktPar0_t Radio::LoRaPktPar0;

unsigned Radio::lora_bw_read(bool fw)
{
    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);

    return LoRaPktPar0.bits.modem_bw;
}

menuMode_e Radio::lora_bw_write(unsigned sidx)
{
    switch (sidx) {
        case 0: mpLORA.lora.bandwidth = LORA_BW_50; break;
        case 1: mpLORA.lora.bandwidth = LORA_BW_100; break;
        case 2: mpLORA.lora.bandwidth = LORA_BW_200; break;
        case 3: mpLORA.lora.bandwidth = LORA_BW_400; break;
        case 4: mpLORA.lora.bandwidth = LORA_BW_800; break;
        case 5: mpLORA.lora.bandwidth = LORA_BW_1600; break;
    }
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);

    if (pktType == PACKET_TYPE_RANGING) {
        manualRngDelay = false;
        rngUpdateDelayCal();
    }

    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_bw_item = { _ITEM_DROPDOWN, lora_bws, lora_bws, lora_bw_read, lora_bw_write};

void Radio::lora_sf_print()
{
    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);

    printf("%u", LoRaPktPar0.bits.modem_sf);
}

bool Radio::lora_sf_write(const char* str)
{
    unsigned n;
    if (sscanf(str, "%u", &n) == 1) {
        mpLORA.lora.spreadingFactor = n << 4;
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);

        if (pktType == PACKET_TYPE_RANGING) {
            manualRngDelay = false;
            rngUpdateDelayCal();
        }
    }
    return false;
}

const value_item_t Radio::lora_sf_item = { _ITEM_VALUE, 3, lora_sf_print, lora_sf_write };

static const char* const lora_crs[] = {
    "4/5   ",
    "4/6   ",
    "4/7   ",
    "4/8   ",
    "4/5 LI",
    "4/6 LI",
    "4/7 LI",
    NULL
};

unsigned Radio::lora_cr_read(bool fw)
{
    LoRaPktPar1_t LoRaPktPar1;
    LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    mpLORA.lora.codingRate = LoRaPktPar1.bits.coding_rate;
    return LoRaPktPar1.bits.coding_rate;
}

menuMode_e Radio::lora_cr_write(unsigned sidx)
{
    mpLORA.lora.codingRate = sidx;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_cr_item = { _ITEM_DROPDOWN, lora_crs, lora_crs, lora_cr_read, lora_cr_write};

void Radio::lora_pblLen_print()
{
    LoRaPreambleReg_t LoRaPreambleReg;
    LoRaPreambleReg.octet = radio.readReg(REG_ADDR_LORA_PREAMBLE, 1);
    ppLORA.lora.PreambleLength = (1 << LoRaPreambleReg.bits.preamble_symb_nb_exp) * LoRaPreambleReg.bits.preamble_symb1_nb;
    printf("%u", ppLORA.lora.PreambleLength);
}

bool Radio::lora_pblLen_write(const char* str)
{
    unsigned val, exp, mant;
    sscanf(str, "%u", &val);

    for (exp = 0; exp < 16; exp++) {
        mant = val / (1 << exp);
        if (mant < 16)
            break;
    }

    ppLORA.lora.PreambleLength = (exp << 4) + mant;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);

    return false;
}

const value_item_t Radio::lora_pblLen_item = { _ITEM_VALUE, 5, lora_pblLen_print, lora_pblLen_write};

bool Radio::lora_fixlen_read()
{
    LoRaPktPar1_t LoRaPktPar1;
    LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    ppLORA.lora.HeaderType = LoRaPktPar1.bits.implicit_header ? IMPLICIT_HEADER : EXPLICIT_HEADER;
    return LoRaPktPar1.bits.implicit_header;
}

bool Radio::lora_fixlen_push()
{
    if (ppLORA.lora.HeaderType == EXPLICIT_HEADER)
        ppLORA.lora.HeaderType = IMPLICIT_HEADER;
    else
        ppLORA.lora.HeaderType = EXPLICIT_HEADER;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
    return ppLORA.lora.HeaderType == IMPLICIT_HEADER;
}

const toggle_item_t Radio::lora_fixlen_item = { _ITEM_TOGGLE,
    "EXPLICIT", // 0
    "IMPLICIT", // 1
    lora_fixlen_read, lora_fixlen_push
};

bool Radio::lora_crcon_read()
{
    LoRaLrCtl_t LoRaLrCtl;
    LoRaLrCtl.octet = radio.readReg(REG_ADDR_LORA_LRCTL, 1);
    ppLORA.lora.crc = LoRaLrCtl.octet & 0x20; // LoRaLrCtl.bits.crc_en
    return LoRaLrCtl.bits.crc_en;
}

bool Radio::lora_crcon_push()
{
    if (ppLORA.lora.crc == LORA_CRC_ENABLE)
        ppLORA.lora.crc = LORA_CRC_DISABLE;
    else
        ppLORA.lora.crc = LORA_CRC_ENABLE;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);

    return ppLORA.lora.crc == LORA_CRC_ENABLE;
}

const toggle_item_t Radio::lora_crcon_item = { _ITEM_TOGGLE,
    "CRC_DISABLE", // 0
    "CRC_ENABLE ", // 1
    lora_crcon_read, lora_crcon_push
};

bool Radio::lora_iqinv_read()
{
    LoRaPktPar1_t LoRaPktPar1;
    LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    ppLORA.lora.InvertIQ = LoRaPktPar1.bits.rxinvert_iq ? LORA_IQ_STD : LORA_IQ_INVERTED;
    return LoRaPktPar1.bits.rxinvert_iq ? 0 : 1;
}

bool Radio::lora_iqinv_push()
{
    if (ppLORA.lora.InvertIQ == LORA_IQ_STD)
        ppLORA.lora.InvertIQ = LORA_IQ_INVERTED;
    else
        ppLORA.lora.InvertIQ = LORA_IQ_STD;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);

    return ppLORA.lora.InvertIQ == LORA_IQ_INVERTED;
}

const toggle_item_t Radio::lora_iqinv_item = { _ITEM_TOGGLE,
    "IQ_STD", // 0
    "IQ_INV", // 1
    lora_iqinv_read, lora_iqinv_push
};

void Radio::lora_ppg_print()
{
    uint8_t val;
    ppg = radio.readReg(REG_ADDR_LORA_SYNC, 2);

    val = (ppg >> 8) & 0xf0;
    val |= (ppg & 0xf0) >> 4;
    printf("%02x", val);
}

bool Radio::lora_ppg_write(const char* txt)
{
    unsigned val;
    if (sscanf(txt, "%x", &val) == 1) {
        ppg &= 0x0707;
        ppg |= (val & 0xf0) << 8;
        ppg |= (val & 0x0f) << 4;
        radio.writeReg(REG_ADDR_LORA_SYNC, ppg, 2);
    }
    return false;
}

const value_item_t Radio::lora_ppg_item = { _ITEM_VALUE, 4, lora_ppg_print, lora_ppg_write};

void Radio::cad_push()
{
    radio.setCAD();
}

const button_item_t Radio::lora_cad_item = { _ITEM_BUTTON, "CAD", cad_push };

static const char* const lora_cadsymbs[] = {
    " 1",
    " 2",
    " 4",
    " 8",
    "16",
    NULL
};

unsigned Radio::lora_cadsymbs_read(bool forWriting)
{
    unsigned n = radio.readReg(REG_ADDR_LORA_FE_GAIN, 1);
    return n >> 5;
}

menuMode_e Radio::lora_cadsymbs_write(unsigned sidx)
{
    uint8_t buf = sidx << 5;
    radio.xfer(OPCODE_SET_CAD_PARAM, 1, 0, &buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_cadsymbs_item = { _ITEM_DROPDOWN, lora_cadsymbs, lora_cadsymbs, lora_cadsymbs_read, lora_cadsymbs_write};

const menu_t Radio::lora_menu[] = {
    { {FIRST_CHIP_MENU_ROW+1,  1},              NULL,     &lora_bw_item, FLAG_MSGTYPE_ALL, &rng_delay_item},
    { {FIRST_CHIP_MENU_ROW+1, 12},             "sf:",     &lora_sf_item, FLAG_MSGTYPE_ALL, &rng_delay_item},
    { {FIRST_CHIP_MENU_ROW+1, 20},             "cr:",     &lora_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 30}, "PreambleLength:", &lora_pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2,  1},              NULL, &lora_fixlen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 12},              NULL,  &lora_crcon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 25},              NULL,  &lora_iqinv_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 35},            "ppg:",    &lora_ppg_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1},          NULL,        &lora_cad_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3,  5},    "symbols:",   &lora_cadsymbs_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

bool Radio::tmp;

bool Radio::rng_role_read()
{
    //RngCfg0_t RngCfg0;
    //RngCfg0.octet = radio.readReg(REG_ADDR_RNGCFG0, 1);

    //return !RngCfg0.bits.ranging_resp_en;
    //log_printf("%02x ranging_resp_en: %u\r\n", RngCfg0.octet, RngCfg0.bits.ranging_resp_en);
    return tmp;
}

bool Radio::rng_role_push()
{
    uint8_t buf;
/*
    RngCfg0_t RngCfg0;
    RngCfg0.octet = radio.readReg(REG_ADDR_RNGCFG0, 1);

    buf = RngCfg0.bits.ranging_resp_en ? 0 : 1;
    log_printf("role %02x  %u\r\n", RngCfg0.octet, buf);
    radio.xfer(OPCODE_SET_RANGING_ROLE, 1, 0, &buf);
    return buf;
*/
    tmp ^= true;
    buf = tmp;
    radio.xfer(OPCODE_SET_RANGING_ROLE, 1, 0, &buf);
    return tmp;
}

const toggle_item_t Radio::rng_role_item = { _ITEM_TOGGLE,
    " SLAVE", // 0
    "MASTER", // 1
    rng_role_read, rng_role_push
};

void Radio::rng_id_send_print()
{
    printf("%08x", radio.readReg(REG_ADDR_LORA_MASTER_REQ_ID, 4));
}

bool Radio::rng_id_send_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%x", &n);
    radio.writeReg(REG_ADDR_LORA_MASTER_REQ_ID, n, 4);
    return false;
}

const value_item_t Radio::rng_id_send_item = { _ITEM_VALUE, 9, rng_id_send_print, rng_id_send_write};

void Radio::rng_slave_id_print()
{
    printf("%08x", radio.readReg(REG_ADDR_LORA_SLAVE_ID, 4));
}

bool Radio::rng_slave_id_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%x", &n);
    radio.writeReg(REG_ADDR_LORA_SLAVE_ID, n, 4);
    return false;
}

const value_item_t Radio::rng_slave_id_item = { _ITEM_VALUE, 9, rng_slave_id_print, rng_slave_id_write};

unsigned Radio::rng_idLength_read(bool forWriting)
{
    RngDebTh2_t RngDebTh2;
    RngDebTh2.octet = radio.readReg(REG_ADDR_LORA_RNGDEBTH2, 1);
    return RngDebTh2.bits.ranging_id_check_length;
}

menuMode_e Radio::rng_idLength_write(unsigned sidx)
{
    RngDebTh2_t RngDebTh2;
    RngDebTh2.octet = radio.readReg(REG_ADDR_LORA_RNGDEBTH2, 1);
    RngDebTh2.bits.ranging_id_check_length = sidx;
    radio.writeReg(REG_ADDR_LORA_RNGDEBTH2, RngDebTh2.octet, 1);
    return MENUMODE_REDRAW;
}

static const char* const rngLens[] = {
    " 8 bits",
    "16 bits",
    "24 bits",
    "32 bits",
    NULL
};

const dropdown_item_t Radio::rng_idLength_item = { _ITEM_DROPDOWN, rngLens, rngLens, rng_idLength_read, rng_idLength_write};


void Radio::rng_delay_print(void)
{
    if (pktType == PACKET_TYPE_RANGING) {
        printf("%u", radio.readReg(REG_ADDR_LORA_DELAY_CAL, 3) & 0x3fffff);
    }
}

bool Radio::rng_delay_write(const char* txt)
{
    unsigned n;
    uint32_t delayCal = radio.readReg(REG_ADDR_LORA_DELAY_CAL, 3);
    sscanf(txt, "%u", &n);
    delayCal &= ~0x3fffff;
    delayCal |= n;
    radio.writeReg(REG_ADDR_LORA_DELAY_CAL, delayCal, 3);

    manualRngDelay = true;
    return false;
}

const value_item_t Radio::rng_delay_item = { _ITEM_VALUE, 7, rng_delay_print, rng_delay_write };

unsigned Radio::rng_resultMux_read(bool)
{
    RngCfg1_t RngCfg1;
    RngCfg1.octet = radio.readReg(REG_ADDR_RNGCFG1, 1);
    return RngCfg1.bits.ranging_result_mux_sel;
}

menuMode_e Radio::rng_resultMux_write(unsigned sidx)
{
    RngCfg1_t RngCfg1;
    RngCfg1.octet = radio.readReg(REG_ADDR_RNGCFG1, 1);
    RngCfg1.bits.ranging_result_mux_sel = sidx;
    radio.writeReg(REG_ADDR_RNGCFG1, RngCfg1.octet, 1);
    return MENUMODE_REDRAW;
}

static const char* const rngResults[] = {
    "     raw   ",
    "  rssiAvg  ",
    " debiased  ",
    "finalFilter",
    NULL
};

const dropdown_item_t Radio::rng_resultMux_item = { _ITEM_DROPDOWN, rngResults, rngResults, rng_resultMux_read, rng_resultMux_write};


void Radio::rng_wndFltSize_print()
{
    printf("%u", radio.readReg(REG_ADDR_RNGFLTWNDSIZE, 1));
}

bool Radio::rng_wndFltSize_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    radio.writeReg(REG_ADDR_RNGFLTWNDSIZE, n, 1);
    return false;
}

const value_item_t Radio::rng_wndFltSize_item = { _ITEM_VALUE, 4, rng_wndFltSize_print, rng_wndFltSize_write};




void Radio::rng_rngRssiThresh_print()
{
    RngDebTh4H_t RngDebTh4H;
    RngDebTh4H.octet = radio.readReg(REG_ADDR_RNGDEBTH4H, 1);
    printf("%u", RngDebTh4H.bits.rng_rssi_threshold);
}

bool Radio::rng_rngRssiThresh_write(const char* txt)
{
    unsigned n;
    RngDebTh4H_t RngDebTh4H;
    RngDebTh4H.octet = radio.readReg(REG_ADDR_RNGDEBTH4H, 1);
    sscanf(txt, "%u", &n);
    RngDebTh4H.bits.rng_rssi_threshold = n;
    radio.writeReg(REG_ADDR_RNGDEBTH4H, RngDebTh4H.octet, 1);
    return false;
}

const value_item_t Radio::rng_rngRssiThresh_item = { _ITEM_VALUE, 4, rng_rngRssiThresh_print, rng_rngRssiThresh_write};

const menu_t Radio::rng_menu[] = {
    { {FIRST_CHIP_MENU_ROW+3, 19},          NULL,      &rng_role_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 28}, "ID to send:",      &rng_id_send_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 49},  "slave ID:",    &rng_slave_id_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 67},      NULL,    &rng_idLength_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4,  1},  "delay:",    &rng_delay_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 14}, "resultMux:", &rng_resultMux_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 37}, "windowFilterSize:", &rng_wndFltSize_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 59}, "rngRssiThresh:", &rng_rngRssiThresh_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

void Radio::xta_print()
{
    uint8_t trim = radio.readReg(REG_ADDR_XTA_TRIM, 1);
    printf("%02x", trim);
}

bool Radio::xta_write(const char* txt)
{
    unsigned trim;
    if (sscanf(txt, "%x", &trim) == 1)
        radio.writeReg(REG_ADDR_XTA_TRIM, trim, 1);

    return false;
}

const value_item_t Radio::xta_item = { _ITEM_VALUE, 3, xta_print, xta_write};

void Radio::xtb_print()
{
    uint8_t trim = radio.readReg(REG_ADDR_XTB_TRIM, 1);
    printf("%02x", trim);
}

bool Radio::xtb_write(const char* txt)
{
    unsigned trim;
    if (sscanf(txt, "%x", &trim) == 1)
        radio.writeReg(REG_ADDR_XTB_TRIM, trim, 1);

    return false;
}

const value_item_t Radio::xtb_item = { _ITEM_VALUE, 3, xtb_print, xtb_write};

const menu_t Radio::common_menu[] = {
    { {FIRST_CHIP_MENU_ROW, 1},         "XTA:",         &xta_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 8},         "XTB:",         &xtb_item, FLAG_MSGTYPE_ALL },
    { {0, 0}, NULL, NULL }
};

const menu_t* Radio::get_modem_menu()
{
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_RANGING || pktType == PACKET_TYPE_LORA) {
        return lora_menu;
    } else if (pktType == PACKET_TYPE_FLRC) {
        return flrc_menu;
    } else if (pktType == PACKET_TYPE_GFSK) {
        return gfsk_menu;
    }

    return NULL;
}

const menu_t* Radio::get_modem_sub_menu()
{
    if (pktType == PACKET_TYPE_RANGING) {
        return rng_menu;
    }
    return NULL;
}

bool Radio::service(int8_t statusRow)
{
    static pktStatus_t prevPktStatus;
    static IrqFlags_t prevIrqFlags;
    IrqFlags_t irqFlags;
    bool ret = false;
    static long prev_now_ms;
    auto now_tp = time_point_cast<milliseconds>(Kernel::Clock::now());
    long now_ms = now_tp.time_since_epoch().count();    

    radio.service();

    if (statusRow > 0 && now_ms-prev_now_ms > 50) {
        int cmp = 0;
        pktStatus_t pktStatus;
        uint8_t buf[6];
        radio.xfer(OPCODE_GET_IRQ_STATUS, 0, 3, buf);
        irqFlags.word = buf[1] << 8;
        irqFlags.word |= buf[2];

        if (rx_led == LED_ON) {
            uint8_t slen;
            if (pktType == PACKET_TYPE_LORA || pktType == PACKET_TYPE_RANGING)
                slen = 3;
            else
                slen = 6;

            radio.xfer(OPCODE_GET_PACKET_STATUS, 0, slen, pktStatus.buf);
            cmp = memcmp(prevPktStatus.buf, pktStatus.buf, slen);
        }

        if (irqFlags.word != prevIrqFlags.word || cmp) {
            IrqFlags_t clearIrqFlags;
            clearIrqFlags.word = 0;

            printf("\e[%u;1f", statusRow);  // set (force) cursor to row;column

            if (cmp) {
                if (pktType == PACKET_TYPE_FLRC || pktType == PACKET_TYPE_BLE || pktType == PACKET_TYPE_GFSK) {
                    if (pktStatus.ble_gfsk_flrc.errors.SyncError)
                        printf("SyncError ");
                    if (pktStatus.ble_gfsk_flrc.errors.LengthError)
                        printf("LengthError ");
                    if (pktStatus.ble_gfsk_flrc.errors.CrcError)
                        printf("CrcError ");
                    if (pktStatus.ble_gfsk_flrc.errors.AbortErr)
                        printf("AbortErr ");
                    if (pktStatus.ble_gfsk_flrc.errors.headerReceived)
                        printf("headerReceived ");
                    if (pktStatus.ble_gfsk_flrc.errors.packetReceived)
                        printf("packetReceived ");
                    if (pktStatus.ble_gfsk_flrc.errors.pktCtrlBusy)
                        printf("pktCtrlBusy ");
                }
                memcpy(prevPktStatus.buf, pktStatus.buf, sizeof(pktStatus_t));
                printf(" | ");
            }

            if (irqFlags.bits.TxDone)
                printf("TxDone ");
            if (irqFlags.bits.RxDone)
                printf("RxDone ");
            if (irqFlags.bits.SyncWordValid)
                printf("SyncWordValid ");
            if (irqFlags.bits.SyncWordError)
                printf("SyncWordError ");
            if (irqFlags.bits.HeaderValid)
                printf("HeaderValid ");
            if (irqFlags.bits.HeaderError)
                printf("HeaderError ");
            if (irqFlags.bits.CrcError)
                printf("CrcError ");
            if (irqFlags.bits.RangingSlaveResponseDone) {
                printf("RangingSlaveResponseDone ");
                clearIrqFlags.bits.RangingSlaveResponseDone = 1;
            } if (irqFlags.bits.RangingSlaveRequestDiscard) {
                printf("RangingSlaveRequestDiscard ");
                clearIrqFlags.bits.RangingSlaveRequestDiscard = 1;
            } if (irqFlags.bits.RangingMasterResultValid) {
                printf("RangingMasterResultValid ");
            } if (irqFlags.bits.RangingMasterTimeout) {
                radio.chipMode = CHIPMODE_NONE;
                chipModeChange();
                printf("RangingMasterTimeout ");
                clearIrqFlags.bits.RangingMasterTimeout = 1;
            } if (irqFlags.bits.RangingMasterRequestValid) {
                printf("RangingMasterRequestValid ");
                clearIrqFlags.bits.RangingMasterRequestValid = 1;
            } if (irqFlags.bits.CadDone)
                printf("CadDone ");
            if (irqFlags.bits.CadDetected)
                printf("CadDetected ");
            if (irqFlags.bits.RxTxTimeout)
                printf("RxTxTimeout ");
            if (irqFlags.bits.PreambleDetected)
                printf("PreambleDetected ");

            printf("\e[K");
            ret = true;

            prevIrqFlags.word = irqFlags.word;

            if (irqFlags.bits.RangingMasterResultValid) {
                float m;
                unsigned rngResult, rngRssi;
                radio.chipMode = CHIPMODE_NONE;
                chipModeChange();
                rngResult = radio.readReg(REG_ADDR_RNGRESULT, 3);
                // Distance [m] = RangingResult*150/(2^12*BwMHz)
                m = rngResult * 150 / (4096*bwMHzs[LoRaPktPar0.bits.modem_bw]);
                rngRssi = radio.readReg(REG_ADDR_RNGRSSI, 1);
                log_printf("%u rngResult %.2fm, RngRssi:%u bw%.1f\r\n", rngResult, m, rngRssi, bwMHzs[LoRaPktPar0.bits.modem_bw]);
                clearIrqFlags.bits.RangingMasterResultValid = 1;
            }

            if (irqFlags.bits.RangingSlaveResponseDone)
                log_printf("RangingSlaveResponseDone\r\n");
            if (irqFlags.bits.RangingSlaveRequestDiscard)
                log_printf("RangingSlaveRequestDiscard\r\n");
            if (irqFlags.bits.RangingMasterRequestValid)
                log_printf("RangingMasterRequestValid\r\n");

            if (clearIrqFlags.word != 0) {
                buf[0] = clearIrqFlags.word >> 8;
                buf[1] = (uint8_t)clearIrqFlags.word;
                radio.xfer(OPCODE_CLEAR_IRQ_STATUS, 2, 0, buf);
            }
        } // ..if change

        prev_now_ms = now_ms;
    }

    return ret;
}

void Radio::Rx()
{
    if (pktType == PACKET_TYPE_RANGING) {
        IrqFlags_t irqEnable;
        uint8_t buf[8];

        if (!manualRngDelay)
            rngUpdateDelayCal();

        irqEnable.word = 0;
        irqEnable.bits.RxDone = 1;
        irqEnable.bits.RxTxTimeout = 1;
        irqEnable.bits.RangingSlaveResponseDone = 1;
        irqEnable.bits.RangingSlaveRequestDiscard = 1;
        irqEnable.bits.RangingMasterRequestValid = 1;

        buf[0] = irqEnable.word >> 8;    // enable bits
        buf[1] = irqEnable.word; // enable bits

        irqEnable.bits.RangingSlaveResponseDone = 0;
        irqEnable.bits.RangingSlaveRequestDiscard = 0;
        irqEnable.bits.RangingMasterRequestValid = 0;
        buf[2] = irqEnable.word >> 8;     // dio1
        buf[3] = irqEnable.word;  // dio1

        buf[4] = 0; // dio2
        buf[5] = 0; // dio2
        buf[6] = 0; // dio3
        buf[7] = 0; // dio3
        radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);

        buf[0] = radio.periodBase;
        /* receive packets forever */
        buf[1] = 0xff;
        buf[2] = 0xff;
        radio.xfer(OPCODE_SET_RX, 3, 0, buf);

        radio.chipMode = CHIPMODE_RX;
        chipModeChange();
    } else
        radio.start_rx(0);
}

void Radio::setFS()
{
    radio.setFS();
}

void Radio::test()
{
/*
    RngCfg0_t RngCfg0;
    RngCfg0.octet = radio.readReg(REG_ADDR_RNGCFG0, 1);

    log_printf("Rngcfg0 %02x\r\n", RngCfg0.octet);*/
    unsigned a;
    log_printf("%02x ", radio.readReg(0x910, 1));
    for (a = 0x911; a < 0x980; a++) {
        printf("%02x ", radio.readReg(a, 1));
        if ((a & 0x1f) == 0x1f)
            printf("\r\n%03x ", a+1);
    }
    printf("\r\n");
}

unsigned Radio::read_register(unsigned addr)
{
    return radio.readReg(addr, 1);
}

void Radio::write_register(unsigned addr, unsigned val)
{
    radio.writeReg(addr, val, 1);
}

#endif /* ..SX126x_H */